掘金 人工智能 05月22日 17:58
V4L2摄像头数据采集
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入解析了Video4Linux2(V4L2)这一Linux系统中用于视频采集的关键技术。文章详细阐述了V4L2的三种图像采集方式,重点介绍了内存映射方式(mmap)在连续视频采集中的应用。同时,文章还剖析了V4L2中buffer的管理机制,以及驱动程序和应用程序如何协同工作以实现高效的数据传输。此外,文章还结合实际代码,讲解了摄像头的使用流程,包括设备打开、能力查询、格式枚举与设置、buffer申请与映射、以及数据流的启动与停止等关键步骤。最后,文章还介绍了如何通过APP接口对视频流的格式、分辨率以及亮度、对比度等参数进行控制。

💾V4L2支持内存映射(mmap)、直接读取(read)和用户指针三种图像采集方式,其中内存映射方式速度快,常用于连续视频采集,直接读取方式速度较慢,适用于静态图片采集。

🔄摄像头采集数据时,驱动程序将采集到的数据放入buffer,APP从buffer中获取数据。驱动程序从“空闲链表”取出buffer填充数据,放入“完成链表”;APP从“完成链表”取出buffer处理数据后,再放回“空闲链表”,实现循环利用。

⚙️APP通过ioctl系统调用与V4L2驱动交互,完成设备打开、能力查询(VIDIOC_QUERYCAP)、格式枚举(VIDIOC_ENUM_FMT)、格式设置(VIDIOC_S_FMT)、buffer申请(VIDIOC_REQBUFS)与映射(VIDIOC_QUERYBUF和mmap)、以及数据流的启动(VIDIOC_STREAMON)和停止(VIDIOC_STREAMOFF)等操作。

💡APP可以使用VIDIOC_G_INPUT和VIDIOC_S_INPUT来获得和设置当前输入源。对于亮度等参数,可以通过VIDIOC_QUERYCTRL、VIDIOC_G_CTRL和VIDIOC_S_CTRL结合参数ID进行查询、获取和设置。

1. 数据采集流程

Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针

1.1 buffer的管理

使用摄像头时,核心是"获得数据"。所以先讲如何获取数据,即如何得到buffer

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

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

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

1.2 完整的使用流程

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

以下是根据流程对百问网编写的APP:video2lcd\video\v4l2.c进分析

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

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

    ioctl VIDIOC_ENUM_FMT:枚举它支持的格式

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

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

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

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

2. 控制流程

使用摄像头时,我们可以调整很多参数,比如:

2.1 APP接口

就APP而言,对于这些参数由3套接口:查询或枚举(Query/Enum)、获得(Get)、设置(Set)

2.1.1 数据格式

以设置数据格式为例,可以先枚举

struct v4l2_fmtdesc fmtdesc;fmtdesc.index = 0;  // 比如从0开始fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 指定type为"捕获"ioctl(vd->fd, VIDIOC_ENUM_FMT, &fmtdesc);#if 0/* *F O R M A T   E N U M E R A T I O N */struct v4l2_fmtdesc {__u32    index;             /* Format number      */__u32    type;              /* enum v4l2_buf_type */__u32               flags;__u8    description[32];   /* Description string */__u32    pixelformat;       /* Format fourcc      */__u32    reserved[4];};#endif

还可以获得当前的格式:

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);#if 0struct v4l2_format {__u32 type;union {struct v4l2_pix_formatpix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */struct v4l2_pix_format_mplanepix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */struct v4l2_windowwin;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */struct v4l2_vbi_formatvbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */struct v4l2_sliced_vbi_formatsliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */struct v4l2_sdr_formatsdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */__u8raw_data[200];                   /* user-defined */} fmt;};/* *V I D E O   I M A G E   F O R M A T */struct v4l2_pix_format {v4l2_format__u32         width;__u32height;__u32pixelformat;__u32field;/* enum v4l2_field */__u32            bytesperline;/* for padding, zero if unused */__u32          sizeimage;__u32colorspace;/* enum v4l2_colorspace */__u32priv;/* private data, depends on pixelformat */__u32flags;/* format flags (V4L2_PIX_FMT_FLAG_*) */__u32ycbcr_enc;/* enum v4l2_ycbcr_encoding */__u32quantization;/* enum v4l2_quantization */__u32xfer_func;/* enum v4l2_xfer_func */};#endif

也可以设置当前的格式:

struct v4l2_format fmt;memset(&fmt, 0, sizeof(struct v4l2_format));fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;fmt.fmt.pix.width = 1024;fmt.fmt.pix.height = 768;fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;fmt.fmt.pix.field = V4L2_FIELD_ANY;int ret = ioctl(vd->fd, VIDIOC_S_FMT, &fmt);

2.1.2 选择输入源

可以获得当期输入源、设置当前输入源:

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

2.1.3 其他参数

如果每一参数都提供一系列的ioctl cmd,那使用起来很不方便。对于这些参数,APP使用对应ID来选中它,然后使用VIDIOC_QUERYCTRL、VIDIOC_G_CTRL、VIDIOC_S_CTRL来操作它。不同参数的ID值不同。以亮度Brightness为例,有如下调用方法:

struct v4l2_queryctrl   qctrl;memset(&qctrl, 0, sizeof(qctrl));qctrl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;ioctl(fd, VIDIOC_QUERYCTRL, &qctrl);/*  Used in the VIDIOC_QUERYCTRL ioctl for querying controls */struct v4l2_queryctrl {__u32     id;__u32     type;/* enum v4l2_ctrl_type */__u8     name[32];/* Whatever */__s32     minimum;/* Note signedness */__s32     maximum;__s32     step;__s32     default_value;__u32                flags;__u32     reserved[2];};
struct v4l2_control c;c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;ioctl(h->fd, VIDIOC_G_CTRL, &c);/* *C O N T R O L S */struct v4l2_control {__u32     id;__s32     value;};
struct v4l2_control c;c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;c.value = 99;ioctl(h->fd, VIDIOC_S_CTRL, &c);

2.2 理解接口

2.2.1 概念

2.2.2 操作方法

3. 编写APP

3.1 完整流程

完整流程:打开设备、查询能力、枚举格式、设置格式、申请缓冲区、映射内存、缓冲队列管理、启动流(摄像头)和数据处理循环。

编译:arm-buildroot-linux-gnueabihf-gcc -o video_test video_test.c -lpthread

3.2 运行结果

3.3 完整代码:

参考:mjpg-streamer,github.com/jacksonliam…

#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/ioctl.h>#include <unistd.h>#include <stdio.h>#include <string.h>#include <linux/types.h>          /* for videodev2.h */#include <linux/videodev2.h>#include <poll.h>#include <sys/mman.h>#include <pthread.h>/* ./video_test </dev/video0> */static void *thread_brightness_control (void *args){    int fd = (int)args;    unsigned char c;    int brightness;    int delta;        struct v4l2_queryctrl   qctrl;    memset(&qctrl, 0, sizeof(qctrl));    qctrl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;    if (0 != ioctl(fd, VIDIOC_QUERYCTRL, &qctrl))    {        printf("can not query brightness\n");        return NULL;    }    printf("brightness min = %d, max = %d\n", qctrl.minimum, qctrl.maximum);    delta = (qctrl.maximum - qctrl.minimum) / 10;            struct v4l2_control ctl;    ctl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;    ioctl(fd, VIDIOC_G_CTRL, &ctl);    while (1)    {        c = getchar();        if (c == 'u' || c == 'U')        {            ctl.value += delta;        }        else if (c == 'd' || c == 'D')        {            ctl.value -= delta;        }        if (ctl.value > qctrl.maximum)            ctl.value = qctrl.maximum;        if (ctl.value < qctrl.minimum)            ctl.value = qctrl.minimum;        ioctl(fd, VIDIOC_S_CTRL, &ctl);    }    return NULL;}int main(int argc, char **argv){    int fd;    struct v4l2_fmtdesc fmtdesc;    struct v4l2_frmsizeenum fsenum;    int fmt_index = 0;    int frame_index = 0;    int i;    void *bufs[32];    int buf_cnt;    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;    struct pollfd fds[1];    char filename[32];    int file_cnt = 0;    if (argc != 2)    {        printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);        return -1;    }    /* open */    fd = open(argv[1], O_RDWR);    if (fd < 0)    {        printf("can not open %s\n", argv[1]);        return -1;    }    /* 查询能力 */    struct v4l2_capability cap;    memset(&cap, 0, sizeof(struct v4l2_capability));        if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap))    {                if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {            fprintf(stderr, "Error opening device %s: video capture not supported.\n",                    argv[1]);            return -1;        }                if(!(cap.capabilities & V4L2_CAP_STREAMING)) {            fprintf(stderr, "%s does not support streaming i/o\n", argv[1]);            return -1;        }    }    else    {        printf("can not get capability\n");        return -1;    }    while (1)    {        /* 枚举格式 */        fmtdesc.index = fmt_index;  // 比如从0开始        fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 指定type为"捕获"        if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))            break;        frame_index = 0;        while (1)        {            /* 枚举这种格式所支持的帧大小 */            memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));            fsenum.pixel_format = fmtdesc.pixelformat;            fsenum.index = frame_index;            if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)            {                printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height);            }            else            {                break;            }            frame_index++;        }        fmt_index++;    }    /* 设置格式 */    struct v4l2_format fmt;    memset(&fmt, 0, sizeof(struct v4l2_format));    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;    fmt.fmt.pix.width = 1024;    fmt.fmt.pix.height = 768;    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;    fmt.fmt.pix.field = V4L2_FIELD_ANY;    if (0 == ioctl(fd, VIDIOC_S_FMT, &fmt))    {        printf("set format ok: %d x %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);    }    else    {        printf("can not set format\n");        return -1;    }    /*     * 申请buffer     */    struct v4l2_requestbuffers rb;    memset(&rb, 0, sizeof(struct v4l2_requestbuffers));    rb.count = 32;    rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;    rb.memory = V4L2_MEMORY_MMAP;    if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb))    {        /* 申请成功后, mmap这些buffer */        buf_cnt = rb.count;        for(i = 0; i < rb.count; i++) {            struct v4l2_buffer buf;            memset(&buf, 0, sizeof(struct v4l2_buffer));            buf.index = i;            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;            buf.memory = V4L2_MEMORY_MMAP;            if (0 == ioctl(fd, VIDIOC_QUERYBUF, &buf))            {                /* mmap */                bufs[i] = mmap(0 /* start anywhere */ ,                                  buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,                                  buf.m.offset);                if(bufs[i] == MAP_FAILED) {                    perror("Unable to map buffer");                    return -1;                }            }            else            {                printf("can not query buffer\n");                return -1;            }                    }        printf("map %d buffers ok\n", buf_cnt);            }    else    {        printf("can not request buffers\n");        return -1;    }    /* 把所有buffer放入"空闲链表" */    for(i = 0; i < buf_cnt; ++i) {        struct v4l2_buffer buf;        memset(&buf, 0, sizeof(struct v4l2_buffer));        buf.index = i;        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;        buf.memory = V4L2_MEMORY_MMAP;        if (0 != ioctl(fd, VIDIOC_QBUF, &buf))        {            perror("Unable to queue buffer");            return -1;        }    }    printf("queue buffers ok\n");    /* 启动摄像头 */    if (0 != ioctl(fd, VIDIOC_STREAMON, &type))    {        perror("Unable to start capture");        return -1;    }    printf("start capture ok\n");    /* 创建线程用来控制亮度 */    pthread_t thread;    pthread_create(&thread, NULL, thread_brightness_control, (void *)fd);       while (1)    {        /* poll */        memset(fds, 0, sizeof(fds));        fds[0].fd = fd;        fds[0].events = POLLIN;        if (1 == poll(fds, 1, -1))        {            /* 把buffer取出队列 */            struct v4l2_buffer buf;            memset(&buf, 0, sizeof(struct v4l2_buffer));            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;            buf.memory = V4L2_MEMORY_MMAP;                        if (0 != ioctl(fd, VIDIOC_DQBUF, &buf))            {                perror("Unable to dequeue buffer");                return -1;            }                        /* 把buffer的数据存为文件 */            sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++);            int fd_file = open(filename, O_RDWR | O_CREAT, 0666);            if (fd_file < 0)            {                printf("can not create file : %s\n", filename);            }            printf("capture to %s\n", filename);            write(fd_file, bufs[buf.index], buf.bytesused);            close(fd_file);            /* 把buffer放入队列 */            if (0 != ioctl(fd, VIDIOC_QBUF, &buf))            {                perror("Unable to queue buffer");                return -1;            }        }    }    if (0 != ioctl(fd, VIDIOC_STREAMOFF, &type))    {        perror("Unable to stop capture");        return -1;    }    printf("stop capture ok\n");    close(fd);    return 0;}

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

V4L2 视频采集 Linux驱动 摄像头 mmap
相关文章