一、簡介
Video for Linuxtwo(Video4Linux2)簡稱V4L2,是V4L的改進版。V4L2是linux操作系統下用於采集圖片、視頻和音頻數據的API接口,配合適當的視頻采集設備和相應的驅動程序,可以實現圖片、視頻、音頻等的采集。可以對uvc免驅攝像頭直接操作。在遠程會議、可視電話、視頻監控系統和嵌入式多媒體終端中都有廣泛的應用。
二、V4L2視頻采集原理
V4L2支持內存映射方式(mmap)和直接讀取方式(read)來采集數據,前者一般用於連續視頻數據的采集,后者常用於靜態圖片數據的采集。我們一般使用內存映射方式來進行視頻采集。
V4L2采集視頻數據的五個步驟:
首先,打開視頻設備文件,進行視頻采集的參數初始化,通過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
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。