v4l2总结

V4L2(Video For Linux Two) 是内核提供给应用程序访问音、视频驱动的统一接口。

在应用程序获取视频数据的流程中,都是通过 ioctl 命令与驱动程序进行交互,常见的 ioctl 命令有:

VIDIOC_QUERYCAP     /* 查询设备属性 */
VIDIOC_G_FMT        /* 查询的视频格式 */
VIDIOC_S_FMT        /* 设置捕获视频的格式 */
VIDIOC_REQBUFS      /* 向驱动申请内存的请求 */
VIDIOC_QUERYBUF     /* 向驱动查询申请到的内存 */
VIDIOC_QBUF         /* 将空闲的内存加入可捕获视频的队列 */
VIDIOC_DQBUF        /* 将已经捕获好视频的内存拉出已捕获视频的队列 */
VIDIOC_STREAMON     /* 打开视频流 */
VIDIOC_STREAMOFF    /* 关闭视频流 */
VIDIOC_QUERYCTRL    /* 查询驱动是否支持该命令 */
VIDIOC_CROPCAP      /* 图像裁剪 */
VIDIOC_G_INPUT      /* 查询当前input */
VIDIOC_S_INPUT      /* 设置当前input */
VIDIOC_QBUF         /* 把帧放入队列 */
VIDIOC_DQBUF        /* 从队列中取出帧 */

1.查询设备属性: VIDIOC_QUERYCAP

  相关函数:

int ioctl(int fd, int request, struct v4l2_capability *argp);

  相关结构体:

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 | V4L2_CAP_STREAMING 表示是一个视频捕捉设备并且具有数据流控制模式.

2.查询并显示所有支持的格式:VIDIOC_ENUM_FMT

  相关函数:

int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);

  相关结构体:

struct v4l2_fmtdesc
{
    u32 index; // 要查询的格式序号,应用程序设置
    enum v4l2_buf_type type; // 帧类型,应用程序设置
    u32 flags; // 是否为压缩格式
    u8 description[32]; // 格式名称
    u32 pixelformat; // 格式
    u32 reserved[4]; // 保留
};

3.查看或设置当前格式: VIDIOC_G_FMT, VIDIOC_S_FMT

  相关函数:

int ioctl(int fd, int request, struct v4l2_format *argp);

  相关结构体:

struct v4l2_format
{
    enum v4l2_buf_type type; // 帧类型,应用程序设置
    union fmt
    {
        struct v4l2_pix_format pix; // 视频设备使用
        struct v4l2_window win;
        struct v4l2_vbi_format vbi;
        struct v4l2_sliced_vbi_format sliced;
        u8 raw_data[200];
    };

};

struct v4l2_pix_format
{
    u32 width; // 帧宽,单位像素
    u32 height; // 帧高,单位像素
    u32 pixelformat; // 帧格式
    enum v4l2_field field;
    u32 bytesperline;
    u32 sizeimage;
    enum v4l2_colorspace colorspace;
    u32 priv;
};

通过VIDIOC_S_FMT命令和结构体 v4l2_format 初始化捕获视频的格式,如果要改变格式则用 VIDIOC_TRY_FMT 命令,常见的捕获模式为 V4L2_BUF_TYPE_VIDEO_CAPTURE 即视频捕捉模式,在此模式下 fmt 联合体采用域 v4l2_pix_format:其中 width 为视频的宽、height 为视频的高、pixelformat 为视频数据格式(常见的值有 V4L2_PIX_FMT_YUV422P | V4L2_PIX_FMT_RGB565)、bytesperline 为一行图像占用的字节数、sizeimage 则为图像占用的总字节数、colorspace 指定设备的颜色空间。

4.查看或设置当前Inputs and Outputs

  相关函数:

int ioctl(int fd, int request, struct v4l2_input *argp);

  相关结构体:

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];
};

VIDIOC_G_INPUT 和 VIDIOC_S_INPUT 用来查询和设置当前的 input,一个 video 设备 节点可能对应多个视频源,比如 saf7113 可以最多支持四路 cvbs 输入,如果上层想在四 个cvbs视频输入间切换,那么就要调用 ioctl(fd, VIDIOC_S_INPUT, &input) 来切换。

5.向设备申请缓冲区 VIDIOC_REQBUFS

  相关函数:

int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);

  相关结构体:

struct v4l2_requestbuffers
{
    u32 count; // 缓冲区内缓冲帧的数目
    enum v4l2_buf_type type; // 缓冲帧数据格式
    enum v4l2_memory memory; // 区别是内存映射还是用户指针方式
    u32 reserved[2];
};

Camera设备有三个队列。缓存刚分配时放在EMPTY队列,等待设备填充时放在IN队列,设备填充好时放在OUT队列。

下面的代码中,使用V4L2_MEMORY_MMAP要求设备分配6个缓存。这时缓存放在EMPTY队列。

struct v4l2_requestbuffers rb;
memset (&rb, 0, sizeof(rb));
rb.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
rb.count  = 6;

ret = ioctl (fd, VIDIOC_REQBUFS, &rb);

 V4L2_MEMORY_MMAP之外还可能有其他选择,如V4L2_MEMORY_DMABUF。这时缓存不是设备分配,而是应用程序传进去。它可能来自其他Video设备,或任何能分配DMABUF的设备。

6.查询缓冲帧信息:VIDIOC_QUERYBUF

  相关函数:

int ioctl(int fd, int request, struct v4l2_buffer *argp);

  相关结构体:

struct v4l2_buffer {
	__u32                   index;
	enum v4l2_buf_type      type;
	__u32                   bytesused;
	__u32                   flags;
	enum v4l2_field         field;
	struct timeval          timestamp;
	struct v4l2_timecode    timecode;
	__u32                   sequence;
 
	/* memory location */
	enum v4l2_memory        memory;
	union {
	        __u32           offset;
	        unsigned long   userptr;
	} m;
	__u32                   length;
	__u32                   input;
	__u32                   reserved;
};

      index 为缓存编号,type 为视频捕获模式,bytesused 为缓存已使用空间大小,flags 为缓存当前状态(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据),timestamp 为时间戳,sequence为缓存序号,memory 为缓存使用方式,offset 为当前缓存与内存区起始地址的偏移,length 为缓存大小,reserved 一般用于传递物理地址值。
       另外VIDIOC_QBUF 和 VIDIOC_DQBUF 命令都采用结构 v4l2_buffer 与驱动通信:VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频的队列,传递的主要参数为 index;VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会根据 index 确定可用数据的起始地址和范围。缓冲区处理好之后,就可以开始获取数据了, 通过命令VIDIOC_STREAMON, VIDIOC_STREAMOFF启动或停止数据流。

      下面的代码中,使用索引值依次得到6个缓存。对每个缓存,使用VIDIOC_QUERYBUF从EMPTY 队列取出,再使用VIDIOC_QBUF放到 IN 队列。如果应用层代码需要处理帧,则可以使用mmap()将它映射到虚拟地址空间。

void* mmap[6];
int mlen;
struct v4l2_buffer buf;
for (int i = 0; i < 6; i++) 
{
    memset (&buf, 0, sizeof(struct v4l2_buffer));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    
    // get it from EMPTY  queue.
    ret = ioctl (fd, VIDIOC_QUERYBUF, &buf);

    // map it to file descriptor
    mmap[i] = mmap (0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
    mlen = buf.length;

    // put it into IN queue.
    ret = ioctl (fd, VIDIOC_QBUF, &buf);
}

如果设备支持,也可以使用VIDIOC_EXPBUF导出buffer,得到dmafd。

  • 其他模块,如OpenGL,可以使用这个dmafd显示图像。
  • 也可以使用send_fd()将dmafd传给其他进程处理。这样做的好处是避免大块数据的复制。
struct v4l2_exportbuffer expbuf;
memset (&expbuf, 0, sizeof(expbuf));
expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
expbuf.index = i;
ret = ioctl (fd, VIDIOC_EXPBUF, &expbuf);

int dmfd = expbuf.fd;

 

你可能感兴趣的:(音视频)