V4L2 video capture principle

I. Introduction

Video for Linuxtwo (Video4Linux2) referred V4L2, V4L is an improved version. V4L2 is linux operating system for acquiring images, the API interface to the video and audio data, with appropriate video capture devices and corresponding drivers can realize images, video, audio collection. Free uvc can directly drive the camera operation. In remote conferencing, video telephony, video surveillance systems and embedded multimedia terminal has a wide range of applications.

Two, V4L2 video capture principle

V4L2 support memory mapping mode (the mmap) and the direct access (Read) to collect data, the former is typically used to capture a continuous video data, which is used to capture still picture data. We generally use memory-mapped approach to video capture.
  V4L2 video data capture five steps:
  First, open a video file device performs parameter initialization video capture, video acquisition window provided by the interface V4L2 collected dot size and format;
  secondly, apply a number of video capture frame buffer zone and the frame buffer mapping from kernel space to user space, to facilitate the application read / processed video data;
  third, will apply to the video capture frame buffer queued in the input queue, and to start the video acquisition;
  fourth , driving start of the video data capture, video capture application is removed from the frame buffer output queue, after processing, the video capture frame buffer back into the input queue, the cycle used to capture the video data;
  fifth, stop the video capture .

            Acquisition flowchart V4L2

其实其他的都比较简单,就是通过ioctl这个接口去设置一些参数。最主要的就是buf管理。他有一个或者多个输入队列和输出队列。
  启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。
  应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。
  最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图所示

                循环采集图

三、基于v4l2的远程监控测试程序

测试程序属于未完成的阶段,v4l2部分已经完成。
  V4l2各项函数定义在测试程序的camera.cpp中。
  程序设计师按照以上流程设计,查看源码的时候可以对照调用流程图,对于其中一些参数理解可以参考参考文献的第一篇文章。

3.1打开摄像头

<pre>

void open_camera(Camera* cam)
{
cam->fd=open(cam->device_name,O_RDWR);
if(cam->fd==-1)
{
cout<<"Cannot open the device."??endl;
exit(1);
}
else
{
cout<<"Open the device."??endl;
}
}

 


</pre>

3.2查看摄像头支持的模式已经初始化

需要用到的结构体:
<pre>

struct v4l2_capability
{
__u8 driver[16]; // 驱动名字
__u8 card[32]; // 设备名字
__u8 bus_info[32]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32 capabilities; // 设备支持的操作
__u32 reserved[4]; // 保留字段
};
</pre>
  capabilities常用值:
<pre>
V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取
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];
};
</pre>
  实现函数:
<pre>
void init_camera(Camera* cam){
struct v4l2_capability cap;

if (-1 == ioctl(cam->fd, VIDIOC_QUERYCAP, &cap))
{
    if (EINVAL == errno)
    {
        fprintf(stderr, "%s is no V4L2 device\n", cam->device_name);
        exit(EXIT_FAILURE);
    }
    else
    {
        errno_exit("VIDIOC_QUERYCAP");
    }
}

if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
    fprintf(stderr, "%s is no video capture device\n", cam->device_name);
    exit(EXIT_FAILURE);
}

if (!(cap.capabilities & V4L2_CAP_STREAMING))
{
    fprintf(stderr, "%s does not support streaming i/o\n",cam->device_name);
    exit(EXIT_FAILURE);
}

//#ifdef DEBUG_CAM
printf("\nVIDOOC_QUERYCAP\n");
printf("the camera driver is %s\n", cap.driver);
printf("the camera card is %s\n", cap.card);
printf("the camera bus info is %s\n", cap.bus_info);
printf("the version is %d\n", cap.version);

struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = cam->width;
fmt.fmt.pix.height = cam->height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

if (ioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0)
{
    close(cam->fd);
}
cout<<"init the camera."<<endl;
init_mmap(cam);
}

 

</pre>

3.3内存映射

需要用到的结构体:
<pre>

structv4l2_requestbuffers
{
__u32count;//缓冲区内缓冲帧的数目
enumv4l2_buf_typetype;//缓冲帧数据格式
enumv4l2_memorymemory;//区别是内存映射还是用户指针方式
__u32 reserved[2];
};
struct v4l2_buffer
{
__u32index;//buffer序号
enumv4l2_buf_typetype;//buffer类型
__u32byteused;//buffer中已使用的字节数
__u32flags;//区分是MMAP还是USERPTR
enumv4l2_fieldfield;
structtimevaltimestamp;//获取第一个字节时的系统时间
structv4l2_timecode timecode;
__u32sequence;//队列中的序号
enum v4l2_memorymemory;//IO方式,被应用程序设置
union m
{
__u32 offset;//缓冲帧地址,只对MMAP有效
unsignedlonguserptr;
};
__u32length;//缓冲帧长度
__u32input;
__u32reserved;
};

 


</pre>
  自己定义的一个结构体来映射每个缓存帧:
<pre>

struct buffer
{
void* start;
unsigned int length;
}buffers;
</pre>
  实现函数:
<pre>
void init_mmap(Camera cam)
{
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;

if (ioctl(cam->fd, VIDIOC_REQBUFS, &req) < 0)
{
    fprintf(stderr, "Request buffers failure.\n");
    exit(EXIT_FAILURE);
}
if (req.count < 2)
{
    fprintf(stderr, "Insufficient buffer memory on %s\n",
            cam->device_name);
    return;
}
cam->buffers = (Buffer *)calloc(req.count, sizeof(*cam->buffers));
struct v4l2_buffer buf;
for (unsigned int numBufs = 0; numBufs < req.count; numBufs++)
{
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = numBufs;
    if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf) == -1)
    {
        return ;
    }
    cam->buffers[numBufs].length = buf.length;
    cam->buffers[numBufs].start = mmap(NULL, buf.length,PROT_READ | PROT_WRITE,
        MAP_SHARED,
        cam->fd, buf.m.offset);
    if (cam->buffers[numBufs].start == MAP_FAILED)
    {
        return ;
    }
}
cout<<"mmap the camera."<<endl;
}

 

</pre>

3.4开启流

<pre>

void start_capturing(Camera* cam)
{
struct v4l2_buffer buf;
enum v4l2_buf_type type;
for (int i = 0; i < 4; i++)
{
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
// buf.m.offset = buffer[i].offset;

     if (ioctl(cam->fd, VIDIOC_QBUF, &buf) < 0)
     {

     }
 }

 type =  V4L2_BUF_TYPE_VIDEO_CAPTURE;;
 if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < 0)
 {

 }
 cout<<"STREAMON"<<endl;
}

 

</pre>

3.5读取一帧并交给用户程序处理

<pre>

int read_and_encode_frame(Camera* cam)
{
struct v4l2_buffer capture_buf;
memset(&capture_buf, 0, sizeof(capture_buf));
capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
capture_buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(cam->fd, VIDIOC_DQBUF, &capture_buf) < 0)
{
cout<<"cannot get buf"<<endl;
}

cout<<"read_and_encode_frame"<<endl;
encode_frame(cam,capture_buf.index,capture_buf.length);

if (-1 == ioctl(cam->fd, VIDIOC_QBUF, &capture_buf))
            return -1;
return 0;
}

 

</pre>

3.6自定义处理程序:

在这里,我把获得的帧数据保存到自己定义的队列中,相对应的可以将此函数改为你所需的功能。
<pre>

void encode_frame(Camera* cam,unsigned int i,unsigned int length)
{
unsigned char *yuv_frame=static_cast<unsigned char *>(cam->buffers[i].start);
if(yuv_frame[0]=='\0')
{
cout<<"yuv_frame[0]=='\0' "<<endl;
return;
}
//fwrite(yuv_frame,length,1,cam->yuv_fp);

mBuffer *inBuffer=(mBuffer*)malloc(sizeof(mBuffer));
inBuffer->mpBuffer=(char*)malloc(length);
memcpy(inBuffer->mpBuffer, yuv_frame, length);
//inBuffer->mpBuffer=(char*)yuv_frame;
inBuffer->mSize=length;
putBufferWithData(&cam->buffer_list,inBuffer );

//fwrite(inBuffer->mpBuffer,length,1,outfile);
 free(yuv_frame);
cout<<"fwrite done."<<endl;
}

 

</pre>

3.7结束采集

后面就是采集结束后的释放过程,原先demo程序在释放资源过程中一直存在问题,一直还没解决。

参考资料

1.v4l2参数和机构体说明http://blog.sina.com.cn/s/blog_602f87700100znq7.html
2.V4l2采集流程http://blog.csdn.net/eastmoon502136/article/details/8190262



作者:onesixthree
链接:https://www.jianshu.com/p/fd5730e939e7
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

Guess you like

Origin www.cnblogs.com/cyyljw/p/11274725.html