没志青年
发布于 2025-08-10 / 23 阅读
0

[8] Linux V4L2 摄像头

V4L2介绍

Linux摄像头(v4l2应用)——在LCD上实时显示摄像头采集JPEG数据 - 知乎 (zhihu.com)

FFmpeg解码USB摄像头MJPEG输出 - 相对维度 - 博客园 (cnblogs.com)

对于视频相关参考:

开启内核支持

Linxu内核——内核添加V4L2_v4l2安装-CSDN博客

Device Driver

Multimedia support

选中Cameras and video grabber

返回

Media core support

选中vid4Linux options

返回

Device Drivers  --->
    Multimedia support  --->
        <*> Multimedia support  [Enable this option]
        [*]   Cameras/video grabbers support
        [*]   Media USB Adapters  --->
            <*>   V4L USB devices  [Enable this option if you have USB V4L devices]
        [*]   Media PCI Adapters  --->
            <*>   V4L PCI devices  [Enable this option if you have PCI V4L devices]
        <*>   V4L platform devices  [Enable this option if you have platform V4L devices]
        <*>   V4L mem2mem devices  [Enable this option for memory-to-memory V4L devices]
        [*]   V4L2 sub-device userspace API
        [*]   V4L2 sub-device internal API
        [*]   Enable advanced debug functionality on V4L2 devices

2.1 V4L2简介

Video For Linux Two(Video4Linux2)简称为V4L2,是linux内核中自带的采集图片、视频、音频的一套API接口,它的特点如下:。

1)是linux内核自带的,所以可以在类linux系统上直接使用;

2)主流的视频设备几乎都适配了这套框架;

3)开发人员可以很方便的使用这一套api操作不同的视频设备,免去了学习视频设备驱动的成本。

2.2 V4L2接口查询相机功能

相机接入系统后,开发者最关心的是这台相机是否有想要的视频功能、音频功能、无线功能等等。那么这时除了看说明书问厂商以外,开发者还能够从V4L2接口中获得相机的功能信息。

1)打开系统下可以访问的摄像头,使用文件IO函数open()打开设备:

int fd = open("/dev/video0", O_RDWR);

2)获取摄像头属性值,利用标准IO函数ioctl()获取摄像头属性结构体:

struct v4l2_capability cap = {0}; //存储设备信息的结构体 v4l2_capability.

ioctl(fd, VIDIOC_QUERYCAP, &cap); //fd:打开设备返回的文件句柄.

3)打印结构体成员,也就是设备功能属性值:

printf("cap.driver = %s \n",cap.driver); //驱动名,通常是uvcvideo

printf("cap.card = %s \n",cap.card); // Device名,厂商定制

printf("cap.bus_info = %s \n",cap.bus_info); //总线信息

printf("cap.version = %d \n",cap.version); //内核版本

printf("cap.capabilities = %x \n",cap.capabilities); //所有能力集

printf("cap.device_caps = %x \n",cap.device_caps); //开放能力集

printf("cap.reserved = %x \n",cap.reserved); //保留

注意: 这里的宏VIDIOC_QUERYCAP以及结构体v4l2_capability全部来源于videodev2.h这个头文件,也就是V4L2接口的一部分。

2.3 相机的能力集capabilities

结构体v4l2_capability描述的是相机设备的功能。其中最重要的两个成员就是cap.capabilities和cap.device_caps,它们描述的是相机拥有的能力和人为干预的能力,通过追踪头文件定义可获得详细描述如下:

/* Values for 'capabilities' field */

#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 //是视频捕获设备.

#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 //是视频输出设备.

#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 //可以做视频叠加.

#define V4L2_CAP_VBI_CAPTURE 0x00000010 //原始VBI捕获设备.

#define V4L2_CAP_VBI_OUTPUT 0x00000020 //原始VBI输出设备.

#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 //切片VBI捕获设备.

#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 //切片VBI输出设备.

#define V4L2_CAP_HW_FREQ_SEEK 0x00000400 //硬件帧率控制.

#define V4L2_CAP_TUNER 0x00010000 //支持调谐器

#define V4L2_CAP_AUDIO 0x00020000 //支持音频

#define V4L2_CAP_RADIO 0x00040000 //支持无线

#define V4L2_CAP_STREAMING 0x04000000 //支持视频流

注意:能力集可以快速判断目前的相机设备是否支持后续操控,比如有的相机只有拍照没有视频功能,有的相机能拍照的同时记录音频等等,所以检查相机能力是必要的。

2.4 获取相机支持的格式信息

已知相机具备拍照能力V4L2_CAP_VIDEO_CAPTURE,那么拍出来的照片支持何种格式的呢?这时就需再次使用标准IO函数ioctl()来查询相机的格式信息:

struct v4l2_fmtdesc fmt = {

.index = 0, //第0种支持的格式.

.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, //拍照模式下.

}

while((ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmt)) == 0){

printf("{pixelformat = %c%c%c%c},description = '%s'\n",

fmt.pixelformat & 0xff,

(fmt.pixelformat >> 8)&0xff,

(fmt.pixelformat >> 16) & 0xff,

(fmt.pixelformat >> 24)&0xff,

fmt.description);

fmt.index ++ ;

}

上面一段代码的输出结果是跟硬件相关的,测试发现输出结果是这样的:

注意:实际相机拍照时支持的照片格式往往不止一种,所以上面用循环遍历出所有可能的格式。

问题:

现在知道了相机能拍照V4L2_CAP_VIDEO_CAPTURE、相机拍照能输出多种格式的图像,那么如何获得一张指定格式的图像呢?

V4L2视频采集开发流程

mjpg-streamer:https://github.com/jacksonliam/mjpg-streamer

video2lcd:这是百问网编写的APP,它可以在LCD上直接显示摄像头的图像

可以参考这些文件:

  • mjpg-streamer\mjpg-streamer-experimental\plugins\input_control\input_uvc.c

  • video2lcd\video\v4l2.c

Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2支持三种方式来采集图像:

  • 内存映射方式(mmap)

  • 直接读取方式(read)

  • 用户指针

内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少,如有兴趣可自行研究。

数据采集流程

缓冲区 Buffer

摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。

那么这个buffer应该几帧?

一般设置2帧或3帧

其它的设置会出现的问题:

  • 1帧,应用程序在读取这个帧,占用了整个缓冲,那么USB摄像头就无法写入新帧,导致丢失帧。

  • 太多帧数,新的帧在写入缓冲区后,需要等待较长时间才能被应用程序读取,帧的处理和编码耗时会增加,会增加延时。

假设同样过了50ms,应用程序显示的是50ms前采集的帧,就会感觉实时性不强

帧数少的话,应用程序显示的是20ms前采集的帧,反正比帧数过多的实时性高。

少:丢帧

多:延迟

v4l2Cap这个摄像头有哪些能力

摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。

驱动程序周而复始地做如下事情:

  • 从硬件采集到数据

  • 把"空闲链表"取出buffer,把数据存入buffer

  • 把含有数据的buffer放入"完成链表"

APP也会周而复始地做如下事情:

  • 监测"完成链表",等待它含有buffer

  • 从"完成链表"中取出buffer

  • 处理数据

  • 把buffer放入"空闲链表"

4、设置恰当的分辨率

  • open:打开设备节点/dev/videoX

  • ioctl VIDIOC_QUERYCAP:Query Capbility,查询能力,比如

    • 确认它是否是"捕获设备",因为有些节点是输出设备

    • 确认它是否支持mmap操作,还是仅支持read/write操作

  • ioctl VIDIOC_ENUM_FMT:枚举它支持的格式

  • ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式

  • ioctl VIDIOC_REQBUFS:申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到

  • ioctl VIDIOC_QUERYBUF和mmap:查询buffer信息、映射

    • 如果申请到了N个buffer,这个ioctl就应该执行N次

    • 执行mmap后,APP就可以直接读写这些buffer

  • ioctl VIDIOC_QBUF:把buffer放入"空闲链表"

    • 如果申请到了N个buffer,这个ioctl就应该执行N次

  • ioctl VIDIOC_STREAMON:启动摄像头

  • 这里是一个循环:使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表"

    • poll/select

    • ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer

    • 处理:前面使用mmap映射了每个buffer的地址,处理时就可以直接使用地址来访问buffer

    • ioclt VIDIOC_QBUF:把buffer放入"空闲链表"

ioctl VIDIOC_STREAMOFF:停止摄像头

缓冲区

struct v4l2_buffer {
    __u32 index;     // 缓冲区索引
    __u32 type;      // 缓冲区类型,例如 V4L2_BUF_TYPE_VIDEO_CAPTURE
    __u32 bytesused; // 缓冲区中已使用的数据字节数
    __u32 flags;     // 缓冲区标志
    __u32 field;     // 图像场类型
    struct timeval timestamp; // 时间戳
    struct v4l2_timecode timecode; // 时间码
    __u32 sequence;  // 序列号
    __u32 memory;    // 内存映射类型,例如 V4L2_MEMORY_MMAP
    union {
        __u32 offset; // 对于内存映射:缓冲区的偏移量
        unsigned long userptr; // 对于用户指针:缓冲区的指针
        __s32 fd; // 对于 DMA 缓冲区:文件描述符
    } m;
    __u32 length;    // 缓冲区的长度
    __u32 reserved2;
    __u32 reserved;
};

检测设备是否可用

v4l2-ctl --all -d /dev/video0

列出支持的格式和分辨率

v4l2-ctl --list-formats-ext -d /dev/video0

Linux摄像头(v4l2应用)——获取摄像头一帧图像 - 知乎 (zhihu.com)

ubuntu虚拟机中V4L2在VIDIOC_DQBUF处阻塞解决方法

设置摄像头输出格式

v4l2-ctl --set-fmt-video=pixelformat=YUYV

使用ffplay测试是否可以使用

ls -l /dev/video0

视频设备控制

我们可以调整摄像头的参数,达到我们想要的输出效果:

  • 对于视频流本身:

    • 设置格式:比如V4L2_PIX_FMT_YUYV、V4L2_PIX_FMT_MJPEG、V4L2_PIX_FMT_RGB565

    • 设置分辨率:1024*768等

  • 对于控制部分:

    • 调节亮度

    • 调节对比度

    • 调节色度

判断摄像托有没有捕获的能力,即能否采集视频,而不是只能拍照。

获取当前的视频设备配置

struct v4l2_format currentFormat;
memset(&currentFormat, 0, sizeof(struct v4l2_format));
// 设置条件,根据“视频捕获”条件去获取
currentFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(vd->fd, VIDIOC_G_FMT, &currentFormat);

视频设备配置

struct v4l2_format {
    __u32 type;    // 缓冲区类型
    union {
        struct v4l2_pix_format        pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
        struct v4l2_pix_format_mplane pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
        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 */
        struct v4l2_sdr_format        sdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */
        __u8 raw_data[200];                    /* 用户自定义数据 */
    } fmt;
};

帧格式

struct v4l2_pix_format {
    __u32 width;           // 图像的宽度(像素)
    __u32 height;          // 图像的高度(像素)
    __u32 pixelformat;     // 像素格式(FourCC格式)
    __u32 field;           // 视频场类型(enum v4l2_field)
    __u32 bytesperline;    // 每行的字节数(行跨度),用于填充,未使用时为0
    __u32 sizeimage;       // 图像大小(字节)
    __u32 colorspace;      // 色彩空间类型(enum v4l2_colorspace)
    __u32 priv;            // 私有数据,依赖于像素格式
    __u32 flags;           // 格式标志(V4L2_PIX_FMT_FLAG_*)
    __u32 ycbcr_enc;       // YCbCr编码(enum v4l2_ycbcr_encoding)
    __u32 quantization;    // 量化(enum v4l2_quantization)
    __u32 xfer_func;       // 传输函数(enum v4l2_xfer_func)
};

应用程序开发

参考mjpg-streamer和video2lcd,总结了摄像头的使用流程,如下:

1、打开设备

int fd;
fd = open("/dev/video0", O_RDWR);
if (fd < 0)
{
    printf("can not open video device\n");
    return -1;
}

查询当前输入源

// 读到的value从0开始, 0表示第1个input源
int value;
ioctl(h->fd,VIDIOC_G_INPUT,&value);

设置输入源

// 0表示第1个input源
int value = 0;
ioctl(h->fd,VIDIOC_S_INPUT,&value)

2、查询摄像头有哪些能力

加入,我们想分辨率19201080,但是摄像头最大1080720,此时不会错,

3、获取摄像头支持的格式

// 内核中格式结构体
struct v4l2_fmtdesc {
    __u32           index;             // 格式编号
    __u32           type;              // 缓冲区的数据类型
    __u32           flags;             // 标志位
    __u8            description[32];   // 描述信息
    __u32           pixelformat;       // 像素格式
    __u32           reserved[4];       // 保留字段
};

读取数据

关闭摄像头

Linux摄像头(v4l2应用)——获取摄像头一帧图像 - 知乎 (zhihu.com)

USB摄像头——v4l2打开设备、获取设备支持的格式 - 阿风小子 - 博客园 (cnblogs.com)

示例程序