imx6 V4L2视频采集和播放(输入video0,输出为video17)

本文章参考:

http://www.cnblogs.com/emouse/archive/2013/03/04/2943243.html

还参考了imx6测试代码:Mxc_v4l2_tvin.c

对以上内容进行自己的总结

v4l2详情可以参考:

http://work-blog.readthedocs.io/en/latest/v4l2%20intro.html


1.V4L2的定义

Video For Linux Two 是内核提供给应用程序访问音、视频驱动的统一接口,我们只需要利用其提供的API进行应用程序编程


2.具体流程

1)linux一切皆文件,首先打开设备文件

2)V4L2获取图像设置(Capture)

3)V4L2输出图像设置(Output)

4)Frambuffer的设置

5)开始循环获取图像数据并输出

6)关闭设备


3.步骤分析

(注意,以下代码不是完整代码,许多错误处理等内容都已省略)

1)打开设备文件

#define TFAIL -1

char v4l_capture_dev[100] = "/dev/video0";
char v4l_output_dev[100] = "/dev/video17";

fd_capture_v4l = open(v4l_capture_dev, O_RDWR, 0)

fd_output_v4l = open(v4l_output_dev, O_RDWR, 0)) < 0)

fd_fb = open(fb_device, O_RDWR )
 

打开设备没什么可说的,/dev/video0为输入,/dev/video17为输出


2)获取图片的设置

if (v4l_capture_setup() < 0) {
        printf("Setup v4l capture failed.\n");
        return TFAIL;
    }

int v4l_capture_setup(void)主要包括以下几个方面:

(注意:在Capture中,所有type属性皆为V4L2_BUF_TYPE_VIDEO_CAPTURE)


a.查询设备属性capabilities:

    struct v4l2_capability cap

    ioctl (fd_capture_v4l, VIDIOC_QUERYCAP, &cap)


b.获取视频的图像缩放(这里一般取默认值),并设置crop

    struct v4l2_cropcap cropcap;
    struct v4l2_crop crop;

获取视频捕捉能力参数cropcap:

    cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ioctl (fd_capture_v4l, VIDIOC_CROPCAP, &cropcap)

设置实际取景参数crop:

    crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        crop.c = cropcap.defrect; /* 设为默认大小 */

    ioctl (fd_capture_v4l, VIDIOC_S_CROP, &crop)


c.视频帧率的设置parm

    struct v4l2_streamparm parm;

    parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    parm.parm.capture.timeperframe.numerator = 1;
    parm.parm.capture.timeperframe.denominator = 30;
    parm.parm.capture.capturemode = 0; 

    其中帧率计算方式为  numerator/denominator

    ioctl(fd_capture_v4l, VIDIOC_S_PARM, &parm)


d.获取或设置视频格式fmt

    struct v4l2_format fmt

设置:

    memset(&fmt, 0, sizeof(fmt));
    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = 800;                                                              //视频分辨率宽度
    fmt.fmt.pix.height      = 480;                                                              //视频分辨率高度
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;                               //像素格式,根据实际设置
    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;                            //视频采样为隔行采样

    ioctl (fd_capture_v4l, VIDIOC_S_FMT, &fmt)

获取:

    ioctl(fd_capture_v4l, VIDIOC_G_FMT, &fmt)

这里先设置,后获取是因为VIDIOC_S_FMT可能会改变实际的宽和高,有时候设置的并不是实际的宽高值,所以要再获取一次

设置真正的宽和高:

    g_in_width = fmt.fmt.pix.width;
    g_in_height = fmt.fmt.pix.height;


e.最后请求视频数据缓冲区

    struct v4l2_requestbuffers req

    memset(&req, 0, sizeof (req));
    req.count               = g_capture_num_buffers;                                   //缓冲区个数,不能少于3个,我这里的g_capture_num_buffers设置为3
    req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory              = V4L2_MEMORY_MMAP;                              //缓冲区为内存映射方式

    ioctl (fd_capture_v4l, VIDIOC_REQBUFS, &req)                               //申请拥有3个缓冲帧的缓冲区

 

3)输出视频设置

if (v4l_output_setup() < 0) {
        printf("Setup v4l output failed.\n");
        close(fd_capture_v4l);
        return TFAIL;
    }


int v4l_output_setup(void)的主要内容如下(与Capture比较相似):

(注意:在Output中,所有type属性皆为V4L2_BUF_TYPE_VIDEO_OUTPUT)

a.设备属性cap查询:

    ioctl(fd_output_v4l, VIDIOC_QUERYCAP, &cap)


b.设备输出格式fmt查询:

    struct v4l2_fmtdesc fmtdesc

    fmtdesc.index = 0;                                                             //查询格式序号
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

    ioctl(fd_output_v4l, VIDIOC_ENUM_FMT, &fmtdesc)

c.获取捕捉能力cropcap,设置输出景象crop:

获取:

    memset(&cropcap, 0, sizeof(cropcap));
    cropcap.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

    ioctl(fd_output_v4l, VIDIOC_CROPCAP, &cropcap)

设置:

    crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
    crop.c.top = g_display_top;                                                   // 0
    crop.c.left = g_display_left;                                                   // 0
    crop.c.width = g_display_width;                                             // 显示宽度
    crop.c.height = g_display_height;                                          // 显示高度

    ioctl(fd_output_v4l, VIDIOC_S_CROP, &crop)


d.输出视频格式fmt设置:

    fb.flags = V4L2_FBUF_FLAG_OVERLAY;
    ioctl(fd_output_v4l, VIDIOC_S_FBUF, &fb);

    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
    fmt.fmt.pix.width= g_in_width;

    fmt.fmt.pix.height= g_in_height;                                              //g_in_width和g_in_height已经在Capture中配置好了
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
    fmt.fmt.pix.bytesperline = g_in_width;
    fmt.fmt.pix.priv = 0;
    fmt.fmt.pix.sizeimage = 0;

    ioctl(fd_output_v4l, VIDIOC_S_FMT, &fmt)

    ioctl(fd_output_v4l, VIDIOC_G_FMT, &fmt)

    g_frame_size = fmt.fmt.pix.sizeimage;                                    //设置图像帧大小


e.申请输出缓冲区

    memset(&buf_req, 0, sizeof(buf_req));
    buf_req.count = g_output_num_buffers;                                  //这里输出缓冲帧设置为4
    buf_req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
    buf_req.memory = V4L2_MEMORY_MMAP;                           //同样是采用内存映射方式

    ioctl(fd_output_v4l, VIDIOC_REQBUFS, &buf_req)


4) Overlay设置

    struct mxcfb_gbl_alpha alpha

    alpha.alpha = 0;
    alpha.enable = 1;
    ioctl(fd_fb, MXCFB_SET_GBL_ALPHA, &alpha)


5) 开始播放

    调用mxc_v4l_tvin_test()

    int mxc_v4l_tvin_test(void)主要内容如下:


a.输出的缓冲区准备int prepare_output(void)

    全局的输出缓冲区:struct testbuffer output_buffers[4]

    其中,    struct testbuffer
                {
                        unsigned char *start;
                        size_t offset;
                        unsigned int length;
                };

    struct v4l2_buffer output_buf;

    for (i = 0; i < g_output_num_buffers; i++)
    {
        memset(&output_buf, 0, sizeof(output_buf));
        output_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
        output_buf.memory = V4L2_MEMORY_MMAP;
        output_buf.index = i;
        if (ioctl(fd_output_v4l, VIDIOC_QUERYBUF, &output_buf) < 0)
        {
            printf("VIDIOC_QUERYBUF error\n");
            return TFAIL;
        }

        output_buffers[i].length = output_buf.length;
        output_buffers[i].offset = (size_t) output_buf.m.offset;
        output_buffers[i].start = mmap (NULL, output_buffers[i].length,
                        PROT_READ | PROT_WRITE, MAP_SHARED,
                        fd_output_v4l, output_buffers[i].offset);
        if (output_buffers[i].start == NULL) {
            printf("v4l2 tvin test: output mmap failed\n");
            return TFAIL;
        }
    }

以上内容就是VIDIOC_QUERYBUF查询输出缓冲区的内容并将其放入全局的输出缓冲帧数组output_buffers,对缓冲区起始地址进行内存映射


b.开始获取:int start_capturing(void)

unsigned int i;
        struct v4l2_buffer buf;
        enum v4l2_buf_type type;

        for (i = 0; i < g_capture_num_buffers; i++)
        {
                memset(&buf, 0, sizeof (buf));
                buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buf.memory = V4L2_MEMORY_MMAP;
                buf.index = i;
                if (ioctl(fd_capture_v4l, VIDIOC_QUERYBUF, &buf) < 0)
                {
                        printf("VIDIOC_QUERYBUF error\n");
                        return TFAIL;
                }

                capture_buffers[i].length = buf.length;
                capture_buffers[i].offset = (size_t) buf.m.offset;
                capture_buffers[i].start = mmap (NULL, capture_buffers[i].length,
                    PROT_READ | PROT_WRITE, MAP_SHARED,
                    fd_capture_v4l, capture_buffers[i].offset);
        memset(capture_buffers[i].start, 0xFF, capture_buffers[i].length);
    }

同理,VIDIOC_QUERYBUF查询capture缓冲区的内容并将其放入全局的capture缓冲帧数组capture_buffers,对缓冲区起始地址进行内存映射


for (i = 0; i < g_capture_num_buffers; i++)

    {

        memset(&buf, 0, sizeof (buf));

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        buf.memory = V4L2_MEMORY_MMAP;

        buf.index = i;

        buf.m.offset = capture_buffers[i].offset;

        if (ioctl (fd_capture_v4l, VIDIOC_QBUF, &buf) < 0) {

            printf("VIDIOC_QBUF error\n");

            return TFAIL;

        }

    }

VIDIOC_QBUF将capture缓冲帧依次放入capture队列中准备开始获取图像


type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl (fd_capture_v4l, VIDIOC_STREAMON, &type) < 0) {
        printf("VIDIOC_STREAMON error\n");
        return TFAIL;
    }

VIDIOC_STREAMON开始获取图像流


c.进入while(1)循环,不断获取图像并输出:

循环中分为begin和next两部分:

begin:   

        if (ioctl(fd_capture_v4l, VIDIOC_G_STD, &id)) {

            printf("VIDIOC_G_STD failed.\n");

            return TFAIL;

        }

        if (id == g_current_std)

            goto next;

        else if (id == V4L2_STD_PAL || id == V4L2_STD_NTSC) {

            type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

            ioctl(fd_output_v4l, VIDIOC_STREAMOFF, &type);

            type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

            ioctl(fd_capture_v4l, VIDIOC_STREAMOFF, &type);

            for (j = 0; j < g_output_num_buffers; j++)

            {

                munmap(output_buffers[j].start, output_buffers[j].length);

            }

            for (j = 0; j < g_capture_num_buffers; j++)

            {

                munmap(capture_buffers[j].start, capture_buffers[j].length);

            }

            if (v4l_capture_setup() < 0) {

                printf("Setup v4l capture failed.\n");

                return TFAIL;

            }

            if (v4l_output_setup() < 0) {

                printf("Setup v4l output failed.\n");

                return TFAIL;

            }

            if (prepare_output() < 0)

            {

                printf("prepare_output failed\n");

                return TFAIL;

            }

            if (start_capturing() < 0)

            {

                printf("start_capturing failed\n");

                return TFAIL;

            }

            i = 0;

            printf("TV standard changed\n");

        } else {

            sleep(1);

            /* Try again */

            if (ioctl(fd_capture_v4l, VIDIOC_G_STD, &id)) {

                printf("VIDIOC_G_STD failed.\n");

                return TFAIL;

            }

            if (id != V4L2_STD_ALL)

                goto begin;

            printf("Cannot detect TV standard\n");

            return 0;

        }

begin部分其实内容不多,通过VIDIOC_G_STD再次获取v4l2_std_id与之前的id做对比,若相同则正式进入循环,若不同则重新建立capture和output并prepare_output和start_capturing,相当于对配置重新来一遍


next:
        memset(&capture_buf, 0, sizeof(capture_buf));
        capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        capture_buf.memory = V4L2_MEMORY_MMAP;
        if (ioctl(fd_capture_v4l, VIDIOC_DQBUF, &capture_buf) < 0) {
            printf("VIDIOC_DQBUF failed.\n");
            return TFAIL;
        }

        memset(&output_buf, 0, sizeof(output_buf));
        output_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
        output_buf.memory = V4L2_MEMORY_MMAP;
        if (i < g_output_num_buffers) {
            output_buf.index = i;
            if (ioctl(fd_output_v4l, VIDIOC_QUERYBUF, &output_buf) < 0)
            {
                printf("VIDIOC_QUERYBUF failed\n");
                return TFAIL;
            }
        } else {
            output_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
            output_buf.memory = V4L2_MEMORY_MMAP;
            if (ioctl(fd_output_v4l, VIDIOC_DQBUF, &output_buf) < 0)
            {
                printf("VIDIOC_DQBUF failed\n");
                return TFAIL;
            }
        }

        memcpy(output_buffers[output_buf.index].start, capture_buffers[capture_buf.index].start, g_frame_size);
        if (ioctl(fd_capture_v4l, VIDIOC_QBUF, &capture_buf) < 0) {
            printf("VIDIOC_QBUF failed\n");
            return TFAIL;
        }

        output_buf.timestamp.tv_sec = tv_start.tv_sec;
        output_buf.timestamp.tv_usec = tv_start.tv_usec + (g_frame_period * i);
        if (g_vdi_enable)
            output_buf.field = g_tb ? V4L2_FIELD_INTERLACED_TB :
                          V4L2_FIELD_INTERLACED_BT;
        if (ioctl(fd_output_v4l, VIDIOC_QBUF, &output_buf) < 0)
        {
            printf("VIDIOC_QBUF failed\n");
            return TFAIL;
        }
        if (i == 1) {
            type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
            if (ioctl(fd_output_v4l, VIDIOC_STREAMON, &type) < 0) {
                printf("Could not start stream\n");
                return TFAIL;
            }
        }
    }

next部分的逻辑也不复杂,通过VIDIOC_DQBUF将capture和output缓冲帧分别取出保存在capture_buf和output_buf,再通过memcpy将capture映射的地址赋值给output,相当于将视频采集的数据传递给输出端,然后将capture和output缓冲区通过VIDIOC_QBUF重新放回队列,最后通过VIDIOC_STREAMON开启视频输出


6) 收尾

    type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

    ioctl(fd_output_v4l, VIDIOC_STREAMOFF, &type);

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ioctl(fd_capture_v4l, VIDIOC_STREAMOFF, &type);

    for (i = 0; i < g_output_num_buffers; i++)

    {

        munmap(output_buffers[i].start, output_buffers[i].length);

    }

    for (i = 0; i < g_capture_num_buffers; i++)

    {

        munmap(capture_buffers[i].start, capture_buffers[i].length);

    }

    close(fd_capture_v4l);

    close(fd_output_v4l);

    close(fd_fb);

关闭视频流,解除内存映射,关闭文件













你可能感兴趣的:(imx6)