本人作为初入音视频领域的新人,观摩了各位大佬关于V4L2详细开发流程的满满干货,特意为这两周的学习做个总结,希望后来者能顺利完成关于V4L2的第一个demo。这期内容主要内容为V4L2的各类实用VIDIOC命令调用,各命令顺序总体上呈现的一个V4L2开发流程。
读前必看:
1. 在函数原型中出现的 int request , 其中request 参数即所属的VIDIOC命令。
2. 在例程中出现的函数形参 int fd , fd 是open()函数返回的文件描述符。
3. 各类VIDIOC命令相关的ioctl接口函数返回值 ret,若小于零,则表示函数调用失败,若等于零,表示成功执行。
4. 各类VIDIOC命令相关的ioctl接口函数最好有执行失败检测,方便后续调试找问题。
5. 常用的ioctl接口命令也在include/linux/videodev2.h中定义,则V4L2 必备的头文件:#include
6. 挖掘头文件的时候,也看看 “/include/media/v4l2-dev.h”,它定义了很多V4L2 ioctl()
将要用到的结构体。
常用的VIDIOC命令:
1. VIDIOC_QUERYCAP (查询设备属性)
2. VIDIOC_ENUM_FMT (显示所有支持的格式)
3. VIDIOC_S_FMT (设置视频捕获格式)
4. VIDIOC_G_FMT (获取硬件现在的视频捕获格式)
5. VIDIOC_TRY_FMT (检查是否支持某种帧格式)
6. VIDIOC_ENUM_FRAMESIZES (枚举设备支持的分辨率信息)
7. VIDIOC_ENUM_FRAMEINTERVALS (获取设备支持的帧间隔)
8. VIDIOC_S_PARM && VIDIOC_G_PARM (设置和获取流参数)
9. VIDIOC_QUERYCAP (查询驱动的修剪能力)
10. VIDIOC_S_CROP (设置视频信号的边框)
11. VIDIOC_G_CROP (读取设备信号的边框)
12. VIDIOC_REQBUFS (向设备申请缓存区)
13. VIDIOC_QUERYBUF (获取缓存帧的地址、长度)
14. VIDIOC_QBUF (把帧放入队列)
15. VIDIOC_DQBUF (从队列中取出帧)
16. VIDIOC_STREAMON && VIDIOC_STREAMOFF (启动/停止视频数据流)
1.打开设备
open()
头文件:
#include
#include
#include
函数原型: int fd=open(const char *pathname, int flags);
or int fd=open(const char *pathname, int flags, mode_t mode);
ps: 基本上用第一个函数原型
函数参数:
1. pathname:打开文件的路径名 ,如 “/dev/video0” ,也可以是预定义字符串的宏,或char*类型的字符串指针。
2. flags:用来控制打开文件的模式。
3. mode:用来设置创建文件的权限(rwx). 当flags使用 O_CREAT时才有效。
flags参数详解:
1. O_READONLY: 只读模式
2. O_WRONLY :只写模式
3. O_RDWR: 可读可写模式
返回值:
1.调用失败: 返回-1,并修改errno
2. 调用成功: 返回一个int类型的文件描述符fd
应用于V4L2的实例,打开设备节点: /dev/video0,则int fd = open("/dev/video0", O_RDWR);。
2.关闭设备
头文件:#include
原型: int close(int fd);
实例: close(fd);
2.1 VIDIOC_QUERYCAP
Fun:查询设备属性,即查询你是谁,你能干什么?
函数原型:int ioctl(int fd, int request, struct v4l2_capability *argp);
ps: 此处request 即 VIDIOC_QUERYCAP
/*相关结构体:*/
struct v4l2_capability
{
__u8 driver[16]; // 驱动名字
__u8 card[32]; // 设备名字
__u8 bus_info[32]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32 capabilities; // 设备支持的操作
__u32 reserved[4]; // 保留字段
};
其中capabilities在摄像头中设置值为 V4L2_CAP_VIDEO_CAPTURE
//例程:
int querycap_camera(int fd)
{
//获取驱动信息,VIDIOC_QUERCAP
struct v4l2_capability cap;
int ret = 0;
memset(&cap,0, sizeof(cap));
ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
if(ret < 0)
{
printf("VIDIOC_QUERYCAP failed(%d)\n", ret);
return -1;
}
else
{
/*输出 caoability 信息*/
printf("Capability Informations:\n");
printf(" driver: %s\n", cap.driver);
printf(" card: %s\n", cap.card);
printf(" bus_info: %s\n", cap.bus_info);
printf(" version: %08X\n", cap.version);
printf(" capabilities: %08X\n", cap.capabilities);
}
printf("--------------------\n"); //可作为分割线。
return ret;
}
2.2 图像格式四剑客:
1. VIDIOC_ENUM_FMT (显示所有支持的格式)
2. VIDIOC_S_FMT(设置视频捕获格式)
3. VIDIOC_G_FMT(获取硬件现在的视频捕获格式)
4. VIDIOC_TRY_FMT(检查是否支持某种帧格式)
1. VIDIOC_ENUM_FMT
Fun:显示所有支持的格式,即允许应用查询所支持的格式,属于格式协商的部分
函数原型: int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);
此处 request 即 VIDIOC_ENUM_FMT,后续request 都由前文标注的VIDIOC 命令代替。
//相关的结构体:
struct v4l2_fmtdesc
{
__u32 index; // 要查询的格式序号,应用程序设置
enum v4l2_buf_type type; // 帧类型,应用程序设置
__u32 flags; // 是否为压缩格式
__u8 description[32]; // 格式名称
__u32 pixelformat; // 格式
__u32 reserved[4]; // 保留,不使用设置为0
};
参数分析:
1. index :是用来确定格式的一个简单整型数。与其他V4L2所使用的索引一样,这个也是从0开始递增,至最大允许值为止。应用可以通过一直递增索引值知道返回-EINVAL的方式枚举所有支持的格式。
2. type:是描述数据流类型的字段。对于视频捕获设备(摄像头或调谐器)来说,就是V4L2_BUF_TYPE_VIDEO_CAPTURE。
3. flags: 只定义了一个值,即V4L2_FMT_FLAG_COMPRESSED,表示这是一个压缩的视频格式。
4. description: 是对这个格式的一种简短的字符串描述。
5. pixelformat: 应是描述视频表现方式的四字符码
ps: 其中第3、4、5项为就index所支持的某个图像格式,V4L2驱动会自动填写的结构体成员信息。
//fun: display all supported formats
void enum_camera_format(int fd)
{
struct v4l2_fmtdesc fmtdesc;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("Supported format:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{
printf("\t%d.%s\n",fmtdesc.index+1, fmtdesc.description);
fmtdesc.index++;
}
}
2. VIDIOC_S_FMT
Fun:设置视频捕获格式
函数原型: int ioctl(int fd, int request, struct v4l2_format* argp);
//相关的结构体
struct v4l2_format
{
enum v4l2_buf_type type;
union
{
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
__u8 raw_data[200]; /* user-defined */
} fmt;
};
PS: 此处type 用于视频捕获设备来说也就是V4L2_BUF_TYPE_VIDEO_CAPTURE,对应fmt共用体中的
pix,即pix是重点。
//其中:
enum v4l2_buf_type
{
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
...
V4L2_BUF_TYPE_PRIVATE = 0x80,
};
struct v4l2_pix_format
{
__u32 width; // 宽,必须是16的倍数
__u32 height; // 高,必须是16的倍数
__u32 pixelformat; // 视频数据存储类型,例如是//YUV4:2:2
enum v4l2_field field;
__u32 bytesperline; // 一行图像占用的字节数
__u32 sizeimage;
enum v4l2_colorspace colorspace;
__u32 priv; /* private data, depends on pixelformat */
};
描述:
可用v4l2_format 结构体用来设置摄像头的视频制式、帧格式等,在设置这个参数时应先填好
v4l2_format 的各个域,如 type(传输流类型),fmt.pix.width(宽)fmt.pix.heigth(高),
fmt.pix.field(采样区域,如隔行采样),fmt.pix.pixelformat(采样类型,如 YUV4:2:2),
然后通过 VIDIO_S_FMT 操作命令设置视频捕捉格式。
//例程:
int set_camera_fmt_c(int fd, struct v4l2_format *fmt)
{
//设置视频格式,格式先在fmt结构体里设置好,再调用VIDIOC_S_FMT,
int ret = 0;
memset(fmt, 0, sizeof(*fmt));
fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt->fmt.pix.width = 1920;
fmt->fmt.pix.height =1080; //获取1080P的视频帧
fmt->fmt.pix.pixelformat =V4L2_PIX_FMT_YUYV; //格式设置为YUV格式
fmt->fmt.pix.field = V4L2_FIELD_ALTERNATE;
ret = ioctl(fd, VIDIOC_S_FMT, fmt);
if(ret < 0)
{
printf("VIDIOC_S_FMT failed(%d)\n", ret);
return -1;
}
else
{
printf("VIDIOC_S_FMT succeed(%d)\n", ret);
}
return 0;
}
3. VIDIOC_G_FMT
Fun:获取硬件现在的视频捕获格式
函数原型: int ioctl(int fd, int request, struct v4l2_format* argp);
ps: VIDIOC_G_FMT命令与VIDIOC_S_FMT所使用的结构体是相同的,另外需要先有 VIDIOC_S_FMT设置图像格式,才能通过VIDIOC_G_FMT获取图像格式,否则会失败。
//例程:
int get_camera_fmt(int fd, struct v4l2_format *fmt)
{
//获取视频格式
int ret = 0;
ret = ioctl(fd, VIDIOC_G_FMT, fmt);
if(ret < 0)
{
printf("VIDIOC_G_FMT failed\n");
}
else{
printf("VIDIOC_G_FMT succeed\n");
}
//printf stream format
printf("Stream format informations:\n");
printf(" type: %d\n", fmt->type); //设置了空格,讲究格式。
printf(" width: %d\n", fmt->fmt.pix.width);
printf(" height: %d\n", fmt->fmt.pix.height);
char fmtstr[8]; //定义一个字符数组,存储格式
memset(fmtstr, 0, 8);
memcpy(fmtstr, &fmt->fmt.pix.pixelformat, 8);
printf(" pixelformat: %s\n", fmtstr);
printf(" field: %d\n", fmt->fmt.pix.field);
printf(" bytesperline: %d\n", fmt->fmt.pix.bytesperline);
printf(" sizeimage: %d\n", fmt->fmt.pix.sizeimage);
printf(" colorspace: %d\n", fmt->fmt.pix.colorspace);
printf(" priv: %d\n", fmt->fmt.pix.priv);
return 0;
}
4. VIDIOC_TRY_FMT
Fun:检查是否支持某种帧格式
ps: 用处不多,本质上是不能改变图像格式的
函数原型: int ioctl(int fd, int request, struct v4l2_format *argp);
相关的结构体: 和VIDIOC_S_FMT、VIDIOC_G_FMT使用的结构体是同一个类型
//例程:
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32;
if(ioctl(fd, VIDIOC_TRY_FMT, &fmt))
{
if(errno == EINVAL)
{
printf("not support format RGB32\n");
}
}
2.3 枚举 设备支持的分辨率信息
VIDIOC_ENUM_FRAMESIZES
fun: 针对VIDIOC_ENUM_FMT所列出的图像格式信息,列举某一格式所对应的分辨率信息。
函数原型: int ioctl( int fd, int request, struct v4l2_frmsizeenum *arg)
这个ioctl 命令允许应用枚举就VIDIOC_ENUM_FMT获取的设备支持的某一图像格式,视频 设备支持的所有的视频帧大小。
//相关的结构体:
struct v4l2_frmsizeenum
{
__u32 index; //IN:index of the given frame size in the enumeration
__u32 pixel_format; //IN:pixel format for which the frame sizes are enumerated
__u32 type; //OUT: frame size type the device supports
union //OUT: frame size with the given index
{
struct v4l2_frmsize_discrete discrete;
struct v4l2_frmsize_stepwise stepwise;
};
__u32 reserved[2];
};
struct v4l2_frmsize_discrete
{
__u32 width;
__u32 height;
};
struct v4l2_frmsize_stepwise
{
__u32 min_width; //minimum frame width[pixel]
__u32 max_width; //maximum frame width[pixel]
__u32 step_width; //frame width step size[pixel]
__u32 min_height; //minimum frame height[pixel]
__u32 max_height; //maximum frame height[pixel]
__u32 step_height; //frame height step size[pixel]
};
//例程:
//ps: int resolution [][3][15][2] 采集各设备节点各格式的分辨率
int enum_camera_fmt_frmsize(int resolution [][3][15][2])
{
int camera_node= 0;
char device_node[20]={};
int j=sprintf(device_node, CAMERA_DEVICE);
int k= 0; //分辨率数组的第一维,表示设备节点,dev_node,0-15
for(camera_node; camera_node<16;camera_node++)
{
sprintf(device_node+j, "%d",camera_node);
printf("camera node: %d\n",camera_node);
printf("%d.this is %s\n", camera_node, device_node);
k = camera_node; //分辨率数组的第一维,表示设备节点,0-15
//打开设备节点
int fd = 0;
fd = open(device_node, O_RDWR,0);
if(fd < 0)
{
printf("%d.Open %s failed\n", camera_node, device_node);
//return -1;
return 0; //不想在此处结束程序,返回main函数,因为遍历总会遇到不存在的设备节点
}
struct v4l2_fmtdesc fmtdesc; // VIDIOC_ENUM_FMT
struct v4l2_frmsizeenum frmsize; //VIDIOC_ENUM_FRAMSIZES
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int x=0,y=0; //四维数组的第二维,第三维. format, dpi(resolution)
printf("Supported format:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0)
{
printf("%c%c%c%c\n",fmtdesc.pixelformat &0xff,
(fmtdesc.pixelformat>>8)&0xff,
(fmtdesc.pixelformat>>16)&0xff,
(fmtdesc.pixelformat>>24)&0xff);
printf("\t%d.%s\n",fmtdesc.index+1, fmtdesc.description); //__u8 string
// 列举格式后,列举设备的分辨率
frmsize.pixel_format = fmtdesc.pixelformat; //VIDIOC_ENUM_FRAMSIZES 初始化
frmsize.index = 0;
while(ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)==0)
{
if(frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE)
{
resolution[k][x][y][0]=frmsize.discrete.width;
resolution[k][x][y][1]=frmsize.discrete.height;
printf("DISCRETE: line-%d %d: {%d*%d}\n", __LINE__,
frmsize.index,frmsize.discrete.width,
frmsize.discrete.height);
}
else if(frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE)
{
resolution[k][x][y][0]=frmsize.stepwise.max_width;
resolution[k][x][y][1]=frmsize.stepwise.max_height;
printf("STEPWISE: line: %d %d: {%d*%d}\n", __LINE__,
frmsize.index,frmsize.stepwise.max_width,
frmsize.stepwise.max_height);
}
//获取帧间隔
enum_camera_frmival(fd, &frmsize);
frmsize.index++;
y = frmsize.index; //即Y代表某格式的一个分辨率
}
fmtdesc.index++;
x= fmtdesc.index; //即x代表某一格式。
y= 0; //到下一个格式,分辨率清零
}
}
return 0;
}
2.4 获取设备支持的帧间隔
VIDIOC_ENUM_FRAMEINTERVALS
Fun: enumerate frame intervals
函数: int ioctl(int fd, int request, struct v4l2_frmivalenum *argp);
描述:
这个ioctl函数允许应用枚举那些给定像素格式和帧大小的所有间隔。要获取支持的像素格式和帧大小可通过VIDIOC_ENUM_FMT和VIDIOC_ENUM_FRAMESIZES回调函数获取。返回值和 v4l2_frmivalenum.type filed 依赖于该设备支持的帧间隔类型。
//相关的结构体:
struct v4l2_frmivalenum
{
__u32 index;
__u32 pixel_format;
__u32 width;
__u32 height;
__u32 type;
union
{
struct v4l2_fract discrete;
struct v4l2_frmival stepwise;
};
__u32 reserved[2];
};
enum v4l2_frmivaltypes
{
V4L2_FRMIVAL_TYPE_DISCRETE = 1,
V4L2_FRMIVAL_TYPE_CONTINUOUS = 2,
v4l2_FRMIVAL_TYPE_STEPWISE =3
};
//例程:
/*列举可以有的帧间隔 frame intervals */
int enum_camera_frmival(int fd, struct v4l2_frmsizeenum* framesize)
{
struct v4l2_frmivalenum frmival;
memset(&frmival,0, sizeof(frmival));
frmival.pixel_format = framesize ->pixel_format;
frmival.width = framesize->discrete.width;
frmival.height = framesize->discrete.height;
//frmival.type = V4L2_FRMIVAL_TYPE_DISCRETE;
frmival.index = 0 ;
printf("\tthe frame intervals enum\n");
while(ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival)==0)
{
//输出分数,即帧间隔
printf("%d.frameinterval:%u/%u\n ", frmival.index,
frmival.discrete.numerator, frmival.discrete.denominator);
frmival.index++;
}
printf(".........................................\n");
return 0;
}
2.5 设置和获取流参数
VIDIOC_S_PARM && VIDIOC_G_PARM
功能: 设置和获取数据流的参数,用于设置帧率等参数
函数原型: int ioctl( int fd, int request, v4l2_streamparm *argp);
Ps: 先设置后获取, 否则先获取会失败。
描述:
现行的视频标准决定了每一秒名义上的帧数。如果捕捉和输出的帧数少于这个数量,那么应用将会要求在驱动侧跳帧或者复制。这个在使用read()或write()函数时特别有用,这类函数并不能被时间戳或者序列计数所扩张,并且能避免无效的数据copy。
进一步的这些ioctl函数能用于决定驱动在I/O模式使用的内在的buffer的数量。这方面看论述read()功能的部分。为了获取/设置流参数,应用分别调用了VIDIOC_G_PARM,VIDIOC_S_PARM ioctl,它们带有指向v4l2_streamparm的指针,结构体里含有 一个存放输入输出设备的各自参数的联合体。
//结构体1:
struct v4l2_streamparm
{
/*The buffer (stream) type, same as struct v4l2_formattype*/
enum v4l2_buf_type type;
union
{
/*Parameters for capture devices, used when type is V4L2_BUF_TYPE_VIDEO_CAPTURE.*/
struct v4l2_captureparm capture;
/*Parameters for output devices, used when type is V4L2_BUF_TYPE_VIDEO_OUTPUT*/
struct v4l2_outputparm output;
__u8 raw_data[200]; //A place holder for future extensions.
}parm;
};
//结构体2:
struct v4l2_captureparm
{
__u32 capability;
__u32 capturemode;
struct v4l2_fract timeperframe;
__u32 extendedmode;
__u32 readbuffers;
__u32 reserved[4];
};
成员分析:
1. capability成员为一组功能标签,目前为止仅有一个被定义:V4L2_CAP_TIMEPERFRAME,它代表可以改变的帧频率。
2. capturemode也只定义了一个标签:V4L2_MODE_HIGHQUALITY,这个标签意在使硬件在适于单帧捕获的高清模式下工作。这种模式为达到设备可处理的最佳图片质量,能做出任何牺牲(包括支持的格式、曝光时间)。
3. timeperframe成员用于指定想要使用的帧率,也是一个结构体,在应用中可设置,为最高的帧率,驱动会自适应。
4. extendedmode在API中没有明确意义,不使用的时候应确保其为0。
5. readbuffers字段是read()操作被调用时内核应为输入帧准备的缓冲区数量。
6. reserved不使用时,永远把reserved 设为0。
//结构体3:
struct v4l2_fract //fraction的简写,分数
{
__u32 numerator; //分子
__u32 denominator; //分母
};
ps: numerator 和 denominator所描述的系数是成功帧之间的时间间隔
总体:
设置参数将调用VIDIOC_S_PARM。在这种情况下,驱动将参数设为与应用所提供的参数尽可能近的值,并调整v4l2_streamparm结构体以反应当前值。如果应用提供timeperframe为0, 驱动应设置为与当前帧视频制式相对应的帧率。若readuffers或writebuffers是0, 驱动应返回现行设置而非删除缓存区。
//例程:
/*VIDIOC_S_PARM*/
int main()
{
int framerate; //自定义
struct v4l2_streamparm *setfps;
setfps = (struct v4l2_streamparm*)calloc(1, sizeof(struct v4l2_streamparm));
set_camera_streamparm(fd,setfps,framerate)
}
int set_camera_streamparm(int fd, struct v4l2_streamparm *setfps, int framerate)
{
memset(setfps,0, sizeof(*setfps));
setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //1.设置捕捉设备格式
setfps->parm.capture.timeperframe.numerator =1;//分子
//分母, 每秒钟通过的帧数
setfps->parm.capture.timeperframe.denominator = framerate;
int ret = ioctl(fd, VIDIOC_S_PARM, setfps);
if(ret)
{
printf("VIDIOC_S_PARM to set fps failed(%d)\n",ret);
return -1;
}
else
{
printf("VIDIOC_S_PARM to set fps succeed(%d)\n",ret);
}
return 0;
}
/* VIDIOC_G_PARM */
int get_camera_streamparm(int fd, struct v4l2_streamparm *setfps)
{
int ret = ioctl(fd, VIDIOC_G_PARM, setfps);
if(ret) //ret ==0 表示函数成功,
{
printf("VIDIOC_G_PARM failed(%d)\n",ret);
return -1;
}
else
{
__u32 x = setfps->parm.capture.timeperframe.numerator;
__u32 y = setfps->parm.capture.timeperframe.denominator;
printf("this camera is %dfps\n",y/x);
printf("VIDIOC_G_PARM succeed(%d)\n",ret);
}
return 0;
}
2.6 VIDIOC_CROP三剑士
1. VIDIOC_QUERYCAP : 查询驱动的修剪能力
2. VIDIOC_S_CROP: 设置视频信号的边框
3. VIDIOC_G_CROP: 读取设备信号的边框
1.VIDIOC_CROPCAP
函数:
int ioctl(int fd,int request, struct _cropcap *argp);
//相关的结构体:
struct v4l2_cropcap
{
enum v4l2_buf_type type; // 数据流的类型,应用程序设置
struct v4l2_rect bounds; // 这是 camera 的镜头能捕捉到的窗口大小的局限
struct v4l2_rect defrect; // 定义默认窗口大小,包括起点位置及长,宽的大小
struct v4l2_fract pixelaspect; // 定义了图片的宽高比
};
struct v4l2_rect
{
__s32 left;
__s32 top;
__s32 width;
__s32 height;
};
struct v4l2_fract //fraction的简写,分数
{
__u32 numerator; //分子
__u32 denominator; //分母
};
2. VIDIOC_G_CROP&&VIDIOC_S_CROP函数
int ioctl(int fd, int request, struct v4l2_crop *argp);
int ioctl(int fd, int request, const struct v4l2_crop *argp);
//相关的结构体:
struct v4l2_crop
{
enum v4l2_buf_type type;// 应用程序设置
struct v4l2_rect c;
};
以下案例是转载:
Example 1-10. Resetting the cropping parameters
/* Fun:VIDIOC_S_CROP裁剪 VIDIOC_CROPCAP所得到的默认框的大小 */
struct v4l2_cropcap cropcap;
struct v4l2_crop crop;
memset (&cropcap, 0, sizeof (cropcap));
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl (fd, VIDIOC_CROPCAP, &cropcap))
{
perror ("VIDIOC_CROPCAP");
exit (EXIT_FAILURE);
}
memset (&crop, 0, sizeof (crop));
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect;
/* Ignore if cropping is not supported (EINVAL). */
if (-1 == ioctl (fd, VIDIOC_S_CROP, &crop)&& errno != EINVAL)
{
perror ("VIDIOC_S_CROP");
exit (EXIT_FAILURE);
}
2.7 VIDIOC_STD 四剑客
ps: 视频标准在摄像头V4L2应用开发中不是很重要,作为补充信息。若不是紧要,可略过
1. VIDIOC_QUERYSTD: 检查当前视频设备支持的标准,例如PAL或NTSC
2. VIDIOC_ENUMSTD: 允许应用查询设备实现了哪些标准
3. VIDIOC_S_STD: 用于应用想要申请某个特定标准
4. VIDIOC_G_STD: 查询设备当前激活的标准
1. VIDIOC_QUERYSTD:
函数原型: int ioctl(int fd, int request, v4l2_std_id *std);
2. VIDIOC_ENUMSTD
函数原型: int ioctl (int fd, int request, struct v4l2_standard *argp);
3. VIDIOC_S_STD
函数原型: int ioctl(int fd, int request, v4l2_std_id std);
4. VIDIOC_G_STD
函数原型: int ioctl(int fd, int request, v4l2_std_id *std);
相关的数据类型
1.typedef u64 v4l2_std_id;
2.struct v4l2_standard
struct v4l2_standard
{
U32 index;
v4l2_std_id id;
u8 name[24];
struct v4l2_fract frameperiod; /* Frames, not fields */
u32 framelines;
U32 reserved[4];
};
描述:
视频标准通常是由每一个国家的监管部门制定,描述的是视频如何为传输而进行格式化分辨率、帧率等。现在世界上使用的标准主要有三个 :
1. NTSC(北美用 )
2. PAL(欧洲 、非洲和中国 )
3. SECAM( 法、俄和非洲部分地区)
然而这些标准在国家之间都有变化,而且有些设备比其他设备能更加灵活,能与更多的标准变 种协同工作。V4L2使用 v4l2_std_id来代表视频标准, 它是一个 64 位的掩码 。每个标准变种在掩码 中就是一位。所 以 “ 标准 ”NTSC 的定义为 V4L2_STD_NTSC_M,值为 0x1000,而日本的变种就是V4L2_STD_NTSC_M_JP (0x2000)。如果一个设备可以处理所有 NTSC变种,它就可以设为V4L2_STD_NTSC,它将所有相关位置位。对PAL和 SECAM标准,也存在一组类似的位集。
2.8 VIDIOC_INPUT 三剑师
ps:选择视频输入
一个视频设备可以有多个视频输入,如果只有一路输入,这个功能可以没有。
函数:
1. VIDIOC_ENUMINPUT
int ioctl(int fd, int request , struct v4l2_input *input);
Ps:视频捕获的应用应首先要通过VIDIOC_ENUMINPUT来枚举所有可用的输入.
2. VIDIOC_S_INPUT
int ioctl(int fd, int requeset, unsigned int input.index);
3. VIDIOC_G_INPUT
int ioctl(int fd, int requeset, unsigned int *input.index);
//相关的结构体:
struct v4l2_input
{
__u32 index; /* Which input */
__u8 name[32]; /* Label */
__u32 type; /* Type of input */
__u32 audioset; /* Associated audios (bitfield) */
__u32 tuner; /* Associated tuner */
v4l2_std_id std;
__u32 status;
__u32 reserved[4];
};
参数分析:
1. index:应用关注的输入索引号,这是唯一一个用户空间设定的字段。驱动要分配索引号给输入,从0开始,依次增加。想要知道所有可用的输入,应用会调用VIDIOC_ENUMINPUT,索引号会从0 开始,并开始递增。一旦驱动返回-EINVAL(超出限制),应用就知道,输入已经枚举完了。另外只要有输入,输入索引号0就一定存在。
2. name:输入的名称,由驱动设定。可以简单的设为"Camera",诸如此类,如果卡上有多个输入,名称就要与接口的打印信息相符合。
3. type:输入类型。目前只有两个值可选:V4L2_INPUT_TYPE_TUNER和 V4L2_INPUT_TYPE_CAMERA。
4. audiose:描述哪个音频输入可以与哪些视频输入相关联。音频输入与视频输入一样通过索引号枚举,但并非所有的音频与视频的组合都是可用的。这个字段是一个掩码,代表对于当前枚举出 的视频而言,哪些音频输入是可以与之关联的。如果没有音频输入可以与之关联,或是只有一个 可选,那么就将这个字段置0。
5. tuner: 电子调谐器。如果输入是一个调谐器(type字段是V4L2_INPUT_TYPE_TUNER),这个字段就是会包含一个相应的调谐设备的索引号。
6. v4l2_std_id std:描述设备支持哪个或哪些视频标准。
7. status: 给出输入状态。完整的标识符集合可以在V4L2文档中找到;简要地说,status中设置的每一位都代表一个问题。这些问题包括掉电、无信号、失锁,存在Macrovision模拟防拷贝系统或是其他一些不幸的问题。
8. reserved[4]:保留字段,驱动应设置为0。
2.9 VIDIOC_OUTPUT三间客
函数:
1. VIDIOC_ENUMOUTPUT
int ioctl(int fd, int request, struct v4l2_output *output);
2. VIDIOC_S_OUTPUT
int ioctl(int fd, int request, unsigned int index);
3. VIDIOC_G_OUTPUT
int ioctl(int fd, int request, unsigned int *index);
结构体:
Ps:VIDIOC_OUTPUT三间客都涉及该结构体,具体形参不同
struct v4l2_output
{
__u32 index; //输出索引号,工作方式与输入的索引号相同,从0开始递增
__u8 name[32]; //输出的名称
__u32 type; //输出类型
__u32 audioset; //能与视频协同工作的音频集
__u32 modulator; //与此设备相关的调制器(仅当 type==V4L2_OUTPUT_TYPE_MODULATOR)
v4l2_std_id std; //输出所支持的视频标准
__u32 reserved[4]; //保留字段,应设置为0
}
__u32 type 支持的输出类型:
1. V4L2_OUTPUT_TYPE_MODULATOR:用于模拟电视调制器
2. V4L2_OUTPUT_TYPE_ANALOG:用于基本模拟视频视频输出
3. V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY:用于模拟VGA覆盖设备
3.1 VIDIOC_REQBUFS
Fun: 向设备申请缓存区
函数原型:int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);
//相关结构体;
struct v4l2_requestbuffers
{
__u32 count; // 缓存数量,也就是说在缓存队列里保持多少张照片
enum v4l2_buf_type type; // 数据流类型,对视频捕获设备应是V4L2_BUF_TYPE_VIDEO_CAPTURE
enum v4l2_memory memory; // V4L2_MEMORY_MMAP 或 V4L2_MEMORY_USERPTR
__u32 reserved[2];
};
//其中:
enum v4l2_memory
{
V4L2_MEMORY_MMAP = 1,
V4L2_MEMORY_USERPTR = 2,
V4L2_MEMORY_OVERLAY = 3,
};
//例程:
int reqbuf_camera(int fd,struct v4l2_requestbuffers *reqbuf)
{
int ret = 0;
//请求分配内存 VIDIOC_REQBUFS
reqbuf->count = BUFFER_COUNT;
reqbuf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf->memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, reqbuf);
if(ret < 0)
{
printf("VIDIOC_REQBUFS failed(%d)\n", ret);
return -1;
}
else
{
printf("VIDIOC_REQBUFS succeed(%d)\n", ret);
}
return 0;
}
3.2 VIDIOC_QUERYBUF
Fun: 获取缓存帧的地址、长度
函数原型:int ioctl(int fd, int request, struct v4l2_buffer *argp);
//相关的结构体:
struct v4l2_buffer
{
u32 index; // buffer 序号
enum v4l2_buf_type type; // buffer 类型
u32 bytesused; // buffer 中已使用的字节数
u32 flags; // 区分是MMAP 还是USERPTR
enum v4l2_field field;
struct timeval timestamp; // 获取第一个字节时的系统时间
struct v4l2_timecode timecode;
u32 sequence; // 队列中的序号
enum v4l2_memory memory; // IO 方式,被应用程序设置
union m
{
u32 offset; // 缓冲帧地址,只对MMAP 有效
unsigned long userptr;
};
u32 length; // 缓冲帧长度
u32 input;
u32 reserved;
};
补充:
1. VIDIOC_QBUF 和 VIDIOC_DQBUF 命令都采用结构 v4l2_buffer。
2. VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频 的队列,传递的主要参数为 index;VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据 的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会 根据index确定可用数据的起始地址和范围。
3.3 内存映射(Memmory Mapping)
1. 映射(mmap)
头文件:#include
函数:
void mmap(void addr,size_t length,int prot,int flags,int fd,off_t offset)
参数详解:
1. addr: 映射起始地址,一般为NULL ,让内核自动选择
2. length:被映射内存块的长度
3. prot :标志映射后能否被读写,其值 PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE
4. flags: 确定此内存映射能否被其他进程共享,MAP_SHARED, MAP_PRIVATE
5. fd,offset, 确定被映射的内存地址返回成功映射后的地址,不成功返回 MAP_FAILED ((void*)-1);
2. 断开映射(munmap)
头文件:#include
函数:int munmap(void* addr, size_t length);
参数: addr 为映射后的地址,length 为映射后的内存长度
//例程:
int main()
{
VideoBuffer* framebuf = calloc(reqbuf->count,sizeof(VideoBuffer));
/*set buf*/
struct v4l2_buffer buffer, *buf;
buf = &buffer;
querybuf_and_mmap(fd, reqbuf, framebuf, buf);
}
int querybuf_and_mmap(int fd, struct v4l2_requestbuffers *reqbuf, VideoBuffer* framebuf, struct v4l2_buffer *buf)
{
memset(buf, 0, sizeof(*buf));
int i=0, ret=0;
for(i=0; icount; i++)
{ //设置buf的index,type,memoy
buf->index = i;
buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf->memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_QUERYBUF, buf);
if(ret<0)
{
printf("VIDIOC_QUERYBUF failed(%d)\n",ret);
return -1;
}
//mmap buffer,逐一映射buffer
framebuf[i].length = buf->length; //重新获得buf的长度,首地址
framebuf[i].start = (char *)mmap(0, buf->length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf->m.offset);
if(framebuf[i].start == MAP_FAILED)
{
printf("mmap(%d)failed: %s\n", i,strerror(errno));
return -1;
}
//qbuf,放入队列
queue_buf(fd, buf);
//显示缓存帧信息
printf("Frame buffer %d: address=0x%x, length=%d\n", i, (unsigned int)framebuf[i].start, framebuf[i].length);
}
return 0;
}
3.4 VIDIOC_QBUF
Fun: 把帧放入队列
函数:int ioctl(int fd, int request, struct v4l2_buffer *argp);
结构体类型与 VIDIOC_QUERYBUF,VIDIOC_DQBUF相同
//例程:
int queue_buf(int fd, struct v4l2_buffer *buf )
{
int ret = ioctl(fd, VIDIOC_QBUF, buf);
if(ret<0)
{
printf("VIDIOC_QBUF failed(%d)\n",ret);
return -1;
}
else
{
printf("VIDIOC_QBUF buf.index:%d\n",buf->index);
printf("VIDIOC_QBUF succeed(%d)\n",ret);
}
return 0;
}
3.5 VIDIOC_DQBUF
Fun: 从队列中取出帧
函数:int ioctl(int fd, int request, struct v4l2_buffer *argp);
结构体类型与VIDEOC_QBUF、VIDIOC_QUERYBUF相同
//例程:
int dequeue_buf(int fd, struct v4l2_buffer *buf)
{
//get frame VIDIOC_DQBUF
int ret = ioctl(fd, VIDIOC_DQBUF, buf);
if(ret <0)
{
printf("VIDIOC_DQBUF failed(%d)\n",ret);
return -1;
}
else
{
printf("VIDIOC_DQBUF buf.index:%d\n",buf->index);
printf("VIDIOC_DQBUF succeed(%d)\n",ret);
}
return 0;
}
VIDIOC_STREAMON && VIDIOC_STREAMOFF
接第三部分缓冲区处理后,就可以开始获取数据了
函数原型:int ioctl(int fd, int request, const int *argp);
1. VIDIOC_STREAMON
函数原型: int ioctl(int fd, int request, enum v4l2_buf_type *type);
Fun: 启动数据流
int ret = ioctl(fd, VIDIOC_STREAMON, &type);
2. VIDIOC_STREAMOFF
函数原型: int ioctl(int fd, int request, enum v4l2_buf_type *type);
Fun: 关闭数据流
int ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
//例程:
int get_camera_videodata(int fd)
{
//开始录制 VIDIOC_STREAMON
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(fd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
printf("VIDIOC_STREAMON failed(%d)\n", ret);
return ret;
}
else
{
printf("VIDIOC_STREAMON succeed(%d)\n", ret);
}
return 0;
}
int close_camera(int fd,VideoBuffer* framebuf)
{
//关闭设备并解除映射
close(fd);
int i=0;
for(i=0;i<4;i++)
{
munmap(framebuf[i].start, framebuf[i].length);
}
printf("Camera test Done.\n");
return 0;
}
|版权声明:欢迎文明转载共享,请标明文章出处。作者Blog地址 :https://blog.csdn.net/Mark_minGE/article/details/81427489