Linux V4l2视频设备

简介: Linux V4l2视频设备

简介

安装

sudo apt-get install v4l-utils libv4l-dev

一般流程

  1. 打开设备,进行初始化参数设置,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;

  2. 申请图像帧缓冲,并进行内存映射,将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取、处理图像数据;

  3. 将帧缓冲进行入队操作,启动视频采集;

  4. 驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;

  5. 释放资源,停止采集工作。

常用命令标识符

#define VIDIOC_QUERYCAP         _IOR('V',  0, struct v4l2_capability)
#define VIDIOC_ENUM_FMT         _IOWR('V',  2, struct v4l2_fmtdesc)
#define VIDIOC_G_FMT        _IOWR('V',  4, struct v4l2_format)
#define VIDIOC_S_FMT        _IOWR('V',  5, struct v4l2_format)
#define VIDIOC_REQBUFS        _IOWR('V',  8, struct v4l2_requestbuffers)
#define VIDIOC_QUERYBUF        _IOWR('V',  9, struct v4l2_buffer)
#define VIDIOC_G_FBUF         _IOR('V', 10, struct v4l2_framebuffer)
#define VIDIOC_S_FBUF         _IOW('V', 11, struct v4l2_framebuffer)
#define VIDIOC_OVERLAY         _IOW('V', 14, int)
#define VIDIOC_QBUF        _IOWR('V', 15, struct v4l2_buffer)
#define VIDIOC_EXPBUF        _IOWR('V', 16, struct v4l2_exportbuffer)
#define VIDIOC_DQBUF        _IOWR('V', 17, struct v4l2_buffer)
#define VIDIOC_STREAMON         _IOW('V', 18, int)
#define VIDIOC_STREAMOFF     _IOW('V', 19, int)
#define VIDIOC_G_PARM        _IOWR('V', 21, struct v4l2_streamparm)
#define VIDIOC_S_PARM        _IOWR('V', 22, struct v4l2_streamparm)
#define VIDIOC_G_STD         _IOR('V', 23, v4l2_std_id)
#define VIDIOC_S_STD         _IOW('V', 24, v4l2_std_id)
#define VIDIOC_ENUMSTD        _IOWR('V', 25, struct v4l2_standard)
#define VIDIOC_ENUMINPUT    _IOWR('V', 26, struct v4l2_input)
#define VIDIOC_G_CTRL        _IOWR('V', 27, struct v4l2_control)
#define VIDIOC_S_CTRL        _IOWR('V', 28, struct v4l2_control)
#define VIDIOC_G_TUNER        _IOWR('V', 29, struct v4l2_tuner)
#define VIDIOC_S_TUNER         _IOW('V', 30, struct v4l2_tuner)
#define VIDIOC_G_AUDIO         _IOR('V', 33, struct v4l2_audio)
#define VIDIOC_S_AUDIO         _IOW('V', 34, struct v4l2_audio)
#define VIDIOC_QUERYCTRL    _IOWR('V', 36, struct v4l2_queryctrl)
#define VIDIOC_QUERYMENU    _IOWR('V', 37, struct v4l2_querymenu)
#define VIDIOC_G_INPUT         _IOR('V', 38, int)
#define VIDIOC_S_INPUT        _IOWR('V', 39, int)
#define VIDIOC_G_EDID        _IOWR('V', 40, struct v4l2_edid)
#define VIDIOC_S_EDID        _IOWR('V', 41, struct v4l2_edid)
#define VIDIOC_G_OUTPUT         _IOR('V', 46, int)
#define VIDIOC_S_OUTPUT        _IOWR('V', 47, int)
#define VIDIOC_ENUMOUTPUT    _IOWR('V', 48, struct v4l2_output)
#define VIDIOC_G_AUDOUT         _IOR('V', 49, struct v4l2_audioout)
#define VIDIOC_S_AUDOUT         _IOW('V', 50, struct v4l2_audioout)
#define VIDIOC_G_MODULATOR    _IOWR('V', 54, struct v4l2_modulator)
#define VIDIOC_S_MODULATOR     _IOW('V', 55, struct v4l2_modulator)
#define VIDIOC_G_FREQUENCY    _IOWR('V', 56, struct v4l2_frequency)
#define VIDIOC_S_FREQUENCY     _IOW('V', 57, struct v4l2_frequency)
#define VIDIOC_CROPCAP        _IOWR('V', 58, struct v4l2_cropcap)
#define VIDIOC_G_CROP        _IOWR('V', 59, struct v4l2_crop)
#define VIDIOC_S_CROP         _IOW('V', 60, struct v4l2_crop)
#define VIDIOC_G_JPEGCOMP     _IOR('V', 61, struct v4l2_jpegcompression)
#define VIDIOC_S_JPEGCOMP     _IOW('V', 62, struct v4l2_jpegcompression)
#define VIDIOC_QUERYSTD         _IOR('V', 63, v4l2_std_id)
#define VIDIOC_TRY_FMT        _IOWR('V', 64, struct v4l2_format)
#define VIDIOC_ENUMAUDIO    _IOWR('V', 65, struct v4l2_audio)
#define VIDIOC_ENUMAUDOUT    _IOWR('V', 66, struct v4l2_audioout)
#define VIDIOC_G_PRIORITY     _IOR('V', 67, __u32) /* enum v4l2_priority */
#define VIDIOC_S_PRIORITY     _IOW('V', 68, __u32) /* enum v4l2_priority */
#define VIDIOC_G_SLICED_VBI_CAP _IOWR('V', 69, struct v4l2_sliced_vbi_cap)
#define VIDIOC_LOG_STATUS         _IO('V', 70)
#define VIDIOC_G_EXT_CTRLS    _IOWR('V', 71, struct v4l2_ext_controls)
#define VIDIOC_S_EXT_CTRLS    _IOWR('V', 72, struct v4l2_ext_controls)
#define VIDIOC_TRY_EXT_CTRLS    _IOWR('V', 73, struct v4l2_ext_controls)
#define VIDIOC_ENUM_FRAMESIZES    _IOWR('V', 74, struct v4l2_frmsizeenum)
#define VIDIOC_ENUM_FRAMEINTERVALS _IOWR('V', 75, struct v4l2_frmivalenum)
#define VIDIOC_G_ENC_INDEX       _IOR('V', 76, struct v4l2_enc_idx)
#define VIDIOC_ENCODER_CMD      _IOWR('V', 77, struct v4l2_encoder_cmd)
#define VIDIOC_TRY_ENCODER_CMD  _IOWR('V', 78, struct v4l2_encoder_cmd)
  • VIDIOC_REQBUFS:分配内存;

  • VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址;

  • VIDIOC_QUERYCAP:查询驱动功能;

  • VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式;

  • VIDIOC_S_FMT:设置当前驱动的视频捕获格式;

  • VIDIOC_G_FMT:读取当前驱动的视频捕获格式;

  • VIDIOC_TRY_FMT:验证当前驱动的显示格式;

  • VIDIOC_CROPCAP:查询驱动的修剪功能;

  • VIDIOC_S_CROP:设置视频信号的边框;

  • VIDIOC_G_CROP:读取视频信号的边框;

  • VIDIOC_QBUF:把数据从缓存中读取出来;

  • VIDIOC_DQBUF:把数据放回缓存队列;

  • VIDIOC_STREAMOP:开始视频显示函数;

  • VIDIOC_STREAMOFF:结束视频显示函数;

  • VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC;

API说明

查询设备属性(v4l2_capability)

  查询设备属性需要使用struct v4l2_capability结构体,该结构体描述了视频采集设备的driver信息。

struct v4l2_capability
{
     __u8 driver[16];       // 驱动名字
     __u8 card[32];         // 设备名字
     __u8 bus_info[32];     // 设备在系统中的位置
     __u32 version;         // 驱动版本号
     __u32 capabilities;    // 设备支持的操作
     __u32 reserved[4];     // 保留字段
};

  通过VIDIOC_QUERYCAP命令来查询driver是否合乎规范。因为V4L2要求所有driver和device都支持这个ioctl。所以,通过VIDIOC_QUERYCAP命令是否成功来判断当前device和driver是否符合V4L2规范。当然,这个命令执行成功的同时还能够得到设备足够的信息,如struct v4l2_capability结构体所示内容。

iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
    DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
    goto err_exit;
}

if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
    DBG_PRINTF("%s is not a video capture device\n", strDevName);
    goto err_exit;
}

if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
    DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}

if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
    DBG_PRINTF("%s supports read i/o\n", strDevName);
}

  代码示例 - 列出所有设备[^1]

显示所有支持的格式(v4l2_fmtdesc)

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

  显示所有支持的格式需要用到struct v4l2_fmtdesc结构体,该结构体描述当前camera支持的格式信息。

  使用VIDIOC_ENUM_FMT命令查询当前camera支持的所有格式。struct v4l2_fmtdesc结构体中index要设置,从0开始;enum v4l2_buf_type type也要设置,如果使用的是camera设备,则enum v4l2_buf_type type要设置为V4L2_BUF_TYPE_VIDEO_CAPTURE,因为camera是CAPTURE设备。结构体中的其他内容driver会填充。其中__u32 pixelformat参数在设置图像帧格式时需要使用。

  列出可用格式列出可用格式[^2]

设置图像帧格式

   设置图像格式需要用到struct v4l2_format结构体,该结构体描述每帧图像的具体格式,包括帧类型以及图像的长、宽等信息。

struct v4l2_format {
    __u32     type;//镇类型, 应用程序设置
    union {
        struct v4l2_pix_format        pix;    // 视频设备使用
        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 */
        struct v4l2_meta_format        meta;    /* V4L2_BUF_TYPE_META_CAPTURE */
        __u8    raw_data[200];                   /* user-defined */
    } fmt;
};

   struct v4l2_format结构体需要设置enum v4l2_buf_type type和union fmt中的struct v4l2_pix_format pix。enum v4l2_buf_type type因为使用的是camera设备,camera是CAPTURE设备,所以设置成V4L2_BUF_TYPE_VIDEO_CAPTURE。struct v4l2_pix_format pix设置一帧图像的长、宽和格式等,由于要适配LCD输出,所以长、宽设置为LCD支持的长、宽,如124~125行所示。

119     /* set format in */
120     GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);
121     memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
122     tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
123     tV4l2Fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;
124     tV4l2Fmt.fmt.pix.width       = iLcdWidth;
125     tV4l2Fmt.fmt.pix.height      = iLcdHeigt;
126     tV4l2Fmt.fmt.pix.field       = V4L2_FIELD_ANY;
127
128     /* 如果驱动程序发现无法某些参数(比如分辨率),
129      * 它会调整这些参数, 并且返回给应用程序
130      */
131     iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);
132     if (iError)
133     {
134             DBG_PRINTF("Unable to set format\n");
135         goto err_exit;
136     }

代码示例

列出所有设备


static void v4l2_device_list() {
    std::string device = "/dev/";
    bool cur_device_found = false;
    size_t cur_device_index;
    std::string cur_device_name;

    fs::path dir("/sys/class/video4linux");
    std::vector<std::string> device_list;
    if (fs::exists(dir) && fs::is_directory(dir)) {
        // 遍历该目录中的所有文件和目录
        for (auto &entry: fs::directory_iterator(dir)) {
            if (!entry.is_symlink()) {
                continue;
            }
            device.resize(5);
            device.append(entry.path().filename());
            std::cout << device << endl;
            int fd = open(device.c_str(), O_RDWR | O_NONBLOCK);
            if (fd == -1) {
                std::cout << "Unable to open " << device << endl;
                continue;
            }
            struct v4l2_capability video_cap;

            if (ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1) {
                cout << "Failed to query capabilities for " << device << endl;
                close(fd);
                continue;
            }

            uint32_t caps = (video_cap.capabilities & V4L2_CAP_DEVICE_CAPS)
                            ? video_cap.device_caps
                            : video_cap.capabilities;

            if (!(caps & V4L2_CAP_VIDEO_CAPTURE)) {
                cout << device << " seems to not support video capture" << endl;
                close(fd);
                continue;
            }

            /* make sure device names are unique */
            char unique_device_name[68];
            int ret = snprintf(unique_device_name,
                               sizeof(unique_device_name), "%s (%s)",
                               video_cap.card, video_cap.bus_info);
            if (ret >= (int) sizeof(unique_device_name)) {
                cout << "linux-v4l2: A format truncation may have occurred."
                        " This can be ignored since it is quite improbable." << endl;
            }

            device_list.emplace_back(device);

            cout << "Found device " << video_cap.card << device << endl;

            /* check if this is the currently used device */
            if (!cur_device_name.empty() && cur_device_name == device) {
                cur_device_found = true;
            }

            close(fd);

        }
    }
}

列出可用格式

static void v4l2_format_list(int fd) {
    cout << "fd:" << fd << endl;
    struct v4l2_fmtdesc fmt;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.index = 0;
    std::string buffer;
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) {
        buffer = (char *) fmt.description;

        if (fmt.flags & V4L2_FMT_FLAG_EMULATED) {
            buffer.append(" (Emulated)");
        }
        // 主流格式
        if (fmt.pixelformat == V4L2_PIX_FMT_MJPEG ||
            fmt.pixelformat == V4L2_PIX_FMT_H264) {

            cout << "Pixelformat:" << buffer << "(available)" << endl;
        } else {
            cout << "Pixelformat: " << buffer << " (unavailable)" << endl;
        }
        fmt.index++;
    }
}

v4l2-utils编译

  https://linuxtv.org/downloads/v4l-utils/

wget https://linuxtv.org/downloads/v4l-utils/v4l-utils-1.24.1.tar.bz2
tar xvf v4l-utils-1.24.1.tar.bz2
cd v4l-utils-1.24.1
./confiure
make -j8

参考资料

  V4L2学习记录

  [Linux 基础] -- V4L2 实例分析 —— vivi.c 源码详解(深度好文)

  Linux应用开发【第七章】摄像头V4L2编程应用开发

  手把手教你使用linux摄像头(V4L2框架)

  ‍

[^1]: ## 列出所有设备

[^2]: ## 列出可用格式

相关文章
|
1月前
|
Shell Linux C语言
【Shell 命令集合 设备管理 】Linux 创建设备文件 MAKEDEV命令 使用指南
【Shell 命令集合 设备管理 】Linux 创建设备文件 MAKEDEV命令 使用指南
35 0
|
1月前
|
传感器 数据采集 存储
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用(一)
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用
77 0
|
1月前
|
Linux C语言 SoC
嵌入式linux总线设备驱动模型分析
嵌入式linux总线设备驱动模型分析
32 1
|
1月前
|
监控 Linux Shell
【Shell 命令集合 网络通讯 】Linux管理终端设备的登录过程 getty命令 使用指南
【Shell 命令集合 网络通讯 】Linux管理终端设备的登录过程 getty命令 使用指南
34 0
|
1月前
|
Shell Linux C语言
【Shell 命令集合 磁盘管理 】Linux losetup命令使用教程 将一个文件或设备与一个回环设备(loop device)进行关联
【Shell 命令集合 磁盘管理 】Linux losetup命令使用教程 将一个文件或设备与一个回环设备(loop device)进行关联
44 0
|
1月前
|
存储 Shell Linux
【Shell 命令集合 磁盘管理 】Linux 从远程磁带设备中删除文件或目录rmt命令使用教程
【Shell 命令集合 磁盘管理 】Linux 从远程磁带设备中删除文件或目录rmt命令使用教程
27 0
|
1月前
|
Shell Linux C语言
【Shell 命令集合 磁盘管理 】Linux 控制光驱或可移动媒体设备的弹出和关闭操作 eject命令使用教程
【Shell 命令集合 磁盘管理 】Linux 控制光驱或可移动媒体设备的弹出和关闭操作 eject命令使用教程
36 1
|
1月前
|
监控 Linux Shell
【Shell 命令集合 磁盘维护 】Linux 交换分区的特殊文件或设备 swapon命令使用指南
【Shell 命令集合 磁盘维护 】Linux 交换分区的特殊文件或设备 swapon命令使用指南
38 1
|
1月前
|
存储 Shell Linux
【Shell 命令集合 磁盘维护 】Linux 创建一个用作交换空间(swap space)的特殊文件或设备 mkswap命令使用教程
【Shell 命令集合 磁盘维护 】Linux 创建一个用作交换空间(swap space)的特殊文件或设备 mkswap命令使用教程
34 0
|
1月前
|
传感器 Linux 数据处理
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用(二)
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用
47 1