linux之V4L2攝像頭應(yīng)用流程
對于v4l2,上次是在調(diào)試收音機驅(qū)動的時候用過,其他也就只是用i2c配置一些寄存器就可以了。那時只是粗粗的了解了,把收音機當(dāng)作v4l2的設(shè)備后會在/dev目錄下生成一個radio的節(jié)點。然后就可以操作了。后來就沒怎么接觸了。這周,需要調(diào)試下usb的攝像頭。因為有問題,所以就要跟進(jìn),于是也就要開始學(xué)習(xí)下linux的v4l2了。看到一篇很不錯的文章,下面參考這篇文章,加上自己的一些見解,做一些總結(jié)把。
Video for Linuxtwo(Video4Linux2)簡稱V4L2,是V4L的改進(jìn)版。V4L2是linux操作系統(tǒng)下用于采集圖片、視頻和音頻數(shù)據(jù)的API接口,配合適當(dāng)?shù)囊曨l采集設(shè)備和相應(yīng)的驅(qū)動程序,可以實現(xiàn)圖片、視頻、音頻等的采集。在遠(yuǎn)程會議、可視電話、視頻監(jiān)控系統(tǒng)和嵌入式多媒體終端中都有廣泛的應(yīng)用。
在Linux下,所有外設(shè)都被看成一種特殊的文件,成為“設(shè)備文件”,可以象訪問普通文件一樣對其進(jìn)行讀寫。一般來說,采用V4L2驅(qū)動的攝像頭設(shè)備文件是/dev/video0。V4L2支持兩種方式來采集圖像:內(nèi)存映射方式(mmap)和直接讀取方式(read)。V4L2在include/linux/videodev.h文件中定義了一些重要的數(shù)據(jù)結(jié)構(gòu),在采集圖像的過程中,就是通過對這些數(shù)據(jù)的操作來獲得最終的圖像數(shù)據(jù)。Linux系統(tǒng)V4L2的能力可在Linux內(nèi)核編譯階段配置,默認(rèn)情況下都有此開發(fā)接口。
而攝像頭所用的主要是capature了,視頻的捕捉,具體linux的調(diào)用可以參考下圖。
應(yīng)用程序通過V4L2進(jìn)行視頻采集的原理
V4L2支持內(nèi)存映射方式(mmap)和直接讀取方式(read)來采集數(shù)據(jù),前者一般用于連續(xù)視頻數(shù)據(jù)的采集,后者常用于靜態(tài)圖片數(shù)據(jù)的采集,本文重點討論內(nèi)存映射方式的視頻采集。
應(yīng)用程序通過V4L2接口采集視頻數(shù)據(jù)分為五個步驟:
首先,打開視頻設(shè)備文件,進(jìn)行視頻采集的參數(shù)初始化,通過V4L2接口設(shè)置視頻圖像的采集窗口、采集的點陣大小和格式;
其次,申請若干視頻采集的幀緩沖區(qū),并將這些幀緩沖區(qū)從內(nèi)核空間映射到用戶空間,便于應(yīng)用程序讀取/處理視頻數(shù)據(jù);
第三,將申請到的幀緩沖區(qū)在視頻采集輸入隊列排隊,并啟動視頻采集;
第四,驅(qū)動開始視頻數(shù)據(jù)的采集,應(yīng)用程序從視頻采集輸出隊列取出幀緩沖區(qū),處理完后,將幀緩沖區(qū)重新放入視頻采集輸入隊列,循環(huán)往復(fù)采集連續(xù)的視頻數(shù)據(jù);
第五,停止視頻采集。
具體的程序?qū)崿F(xiàn)流程可以參考下面的流程圖:
其實其他的都比較簡單,就是通過ioctl這個接口去設(shè)置一些參數(shù)。最主要的就是buf管理。他有一個或者多個輸入隊列和輸出隊列。
啟動視頻采集后,驅(qū)動程序開始采集一幀數(shù)據(jù),把采集的數(shù)據(jù)放入視頻采集輸入隊列的第一個幀緩沖區(qū),一幀數(shù)據(jù)采集完成,也就是第一個幀緩沖區(qū)存滿一幀數(shù)據(jù)后,驅(qū)動程序?qū)⒃搸彌_區(qū)移至視頻采集輸出隊列,等待應(yīng)用程序從輸出隊列取出。驅(qū)動程序接下來采集下一幀數(shù)據(jù),放入第二個幀緩沖區(qū),同樣幀緩沖區(qū)存滿下一幀數(shù)據(jù)后,被放入視頻采集輸出隊列。
應(yīng)用程序從視頻采集輸出隊列中取出含有視頻數(shù)據(jù)的幀緩沖區(qū),處理幀緩沖區(qū)中的視頻數(shù)據(jù),如存儲或壓縮。
最后,應(yīng)用程序?qū)⑻幚硗陻?shù)據(jù)的幀緩沖區(qū)重新放入視頻采集輸入隊列,這樣可以循環(huán)采集,如圖所示。
每一個幀緩沖區(qū)都有一個對應(yīng)的狀態(tài)標(biāo)志變量,其中每一個比特代表一個狀態(tài)
V4L2_BUF_FLAG_UNMAPPED 0B0000
V4L2_BUF_FLAG_MAPPED 0B0001
V4L2_BUF_FLAG_ENQUEUED 0B0010
V4L2_BUF_FLAG_DONE 0B0100
緩沖區(qū)的狀態(tài)轉(zhuǎn)化如圖所示。
下面的程序注釋的很好,就拿來參考下:
V4L2 編程
1. 定義
V4L2(Video ForLinux Two) 是內(nèi)核提供給應(yīng)用程序訪問音、視頻驅(qū)動的統(tǒng)一接口。
2. 工作流程:
打開設(shè)備-> 檢查和設(shè)置設(shè)備屬性->設(shè)置幀格式-> 設(shè)置一種輸入輸出方法(緩沖區(qū)管理)-> 循環(huán)獲取數(shù)據(jù)-> 關(guān)閉設(shè)備。
3. 設(shè)備的打開和關(guān)閉:
#include<fcntl.h>
int open(constchar *device_name, int flags);
#include <unistd.h>
int close(intfd);
例:
- int fd=open(“/dev/video0”,O_RDWR);// 打開設(shè)備
- close(fd);// 關(guān)閉設(shè)備
注意:V4L2 的相關(guān)定義包含在頭文件<linux/videodev2.h>中.
4. 查詢設(shè)備屬性: VIDIOC_QUERYCAP
相關(guān)函數(shù):
- int ioctl(intfd, int request, struct v4l2_capability *argp);
相關(guān)結(jié)構(gòu)體:
- structv4l2_capability
- {
- __u8 driver[16]; // 驅(qū)動名字
- __u8 card[32]; // 設(shè)備名字
- __u8bus_info[32]; // 設(shè)備在系統(tǒng)中的位置
- __u32 version; // 驅(qū)動版本號
- __u32capabilities; // 設(shè)備支持的操作
- __u32reserved[4]; // 保留字段
- };
- capabilities 常用值:
- V4L2_CAP_VIDEO_CAPTURE // 是否支持圖像獲取
例:顯示設(shè)備信息
- structv4l2_capability cap;
- ioctl(fd,VIDIOC_QUERYCAP,&cap);
- printf(“DriverName:%s/nCard Name:%s/nBus info:%s/nDriverVersion:%u.%u.%u/n”,cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF,(cap.version>>8)&0XFF,cap.version&OXFF);
5. 幀格式:
- VIDIOC_ENUM_FMT// 顯示所有支持的格式
- int ioctl(intfd, int request, struct v4l2_fmtdesc *argp);
- structv4l2_fmtdesc
- {
- __u32 index; // 要查詢的格式序號,應(yīng)用程序設(shè)置
- enumv4l2_buf_type type; // 幀類型,應(yīng)用程序設(shè)置
- __u32 flags; // 是否為壓縮格式
- __u8 description[32]; // 格式名稱
- __u32pixelformat; // 格式
- __u32reserved[4]; // 保留
- };
例:顯示所有支持的格式
- structv4l2_fmtdesc fmtdesc;
- fmtdesc.index=0;
- fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- printf("Supportformat:/n");
- while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
- {
- printf("/t%d.%s/n",fmtdesc.index+1,fmtdesc.description);
- fmtdesc.index++;
- }
// 查看或設(shè)置當(dāng)前格式
VIDIOC_G_FMT,VIDIOC_S_FMT
// 檢查是否支持某種格式
- VIDIOC_TRY_FMT
- int ioctl(intfd, int request, struct v4l2_format *argp);
- structv4l2_format
- {
- enumv4l2_buf_type type;// 幀類型,應(yīng)用程序設(shè)置
- union fmt
- {
- structv4l2_pix_format pix;// 視頻設(shè)備使用
- structv4l2_window win;
- structv4l2_vbi_format vbi;
- structv4l2_sliced_vbi_format sliced;
- __u8raw_data[200];
- };
- };
- structv4l2_pix_format
- {
- __u32 width; // 幀寬,單位像素
- __u32 height; // 幀高,單位像素
- __u32pixelformat; // 幀格式
- enum v4l2_fieldfield;
- __u32bytesperline;
- __u32 sizeimage;
- enumv4l2_colorspace colorspace;
- __u32 priv;
- };
例:顯示當(dāng)前幀的相關(guān)信息
- structv4l2_format fmt;
- fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ioctl(fd,VIDIOC_G_FMT,&fmt);
- printf(“Currentdata format information:
- /n/twidth:%d/n/theight:%d/n”,fmt.fmt.width,fmt.fmt.height);
- structv4l2_fmtdesc fmtdesc;
- fmtdesc.index=0;
- fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
- {
- if(fmtdesc.pixelformat& fmt.fmt.pixelformat)
- {
- printf(“/tformat:%s/n”,fmtdesc.description);
- break;
- }
- fmtdesc.index++;
- }
例:檢查是否支持某種幀格式
- structv4l2_format fmt;
- fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB32;
- if(ioctl(fd,VIDIOC_TRY_FMT,&fmt)==-1)
- if(errno==EINVAL)
- printf(“notsupport format RGB32!/n”);
6. 圖像的縮放
- VIDIOC_CROPCAP
- int ioctl(int fd,int request, struct v4l2_cropcap *argp);
- structv4l2_cropcap
- {
- enumv4l2_buf_type type;// 應(yīng)用程序設(shè)置
- struct v4l2_rectbounds;// 最大邊界
- struct v4l2_rectdefrect;// 默認(rèn)值
- structv4l2_fract pixelaspect;
- };
// 設(shè)置縮放
- VIDIOC_G_CROP,VIDIOC_S_CROP
- int ioctl(intfd, int request, struct v4l2_crop *argp);
- int ioctl(intfd, int request, const struct v4l2_crop *argp);
- struct v4l2_crop
- {
- enumv4l2_buf_type type;// 應(yīng)用程序設(shè)置
- struct v4l2_rectc;
- }
7. 申請和管理緩沖區(qū),應(yīng)用程序和設(shè)備有三種交換數(shù)據(jù)的方法,直接read/write ,內(nèi)存映射(memorymapping) ,用戶指針。這里只討論 memorymapping.
// 向設(shè)備申請緩沖區(qū)
- VIDIOC_REQBUFS
- int ioctl(intfd, int request, struct v4l2_requestbuffers *argp);
- structv4l2_requestbuffers
- {
- __u32 count; // 緩沖區(qū)內(nèi)緩沖幀的數(shù)目
- enumv4l2_buf_type type; // 緩沖幀數(shù)據(jù)格式
- enum v4l2_memorymemory; // 區(qū)別是內(nèi)存映射還是用戶指針方式
- __u32 reserved[2];
- };
- enum v4l2_memoy{V4L2_MEMORY_MMAP,V4L2_MEMORY_USERPTR};
- //count,type,memory都要應(yīng)用程序設(shè)置
例:申請一個擁有四個緩沖幀的緩沖區(qū)
- structv4l2_requestbuffers req;
- req.count=4;
- req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- req.memory=V4L2_MEMORY_MMAP;
- ioctl(fd,VIDIOC_REQBUFS,&req);
獲取緩沖幀的地址,長度:
VIDIOC_QUERYBUF
int ioctl(intfd, int request, struct v4l2_buffer *argp);
- structv4l2_buffer
- {
- __u32 index; //buffer 序號
- enumv4l2_buf_type type; //buffer 類型
- __u32 byteused; //buffer 中已使用的字節(jié)數(shù)
- __u32 flags; // 區(qū)分是MMAP 還是USERPTR
- enum v4l2_fieldfield;
- struct timevaltimestamp;// 獲取第一個字節(jié)時的系統(tǒng)時間
- structv4l2_timecode timecode;
- __u32 sequence;// 隊列中的序號
- enum v4l2_memorymemory;//IO 方式,被應(yīng)用程序設(shè)置
- union m
- {
- __u32 offset;// 緩沖幀地址,只對MMAP 有效
- unsigned longuserptr;
- };
- __u32 length;// 緩沖幀長度
- __u32 input;
- __u32 reserved;
- };
MMAP ,定義一個結(jié)構(gòu)體來映射每個緩沖幀。
- Struct buffer
- {
- void* start;
- unsigned intlength;
- }*buffers;
#include<sys/mman.h>
void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);
//addr 映射起始地址,一般為NULL ,讓內(nèi)核自動選擇
//length 被映射內(nèi)存塊的長度
//prot 標(biāo)志映射后能否被讀寫,其值為PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE
//flags 確定此內(nèi)存映射能否被其他進(jìn)程共享,MAP_SHARED,MAP_PRIVATE
//fd,offset, 確定被映射的內(nèi)存地址
返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1);
int munmap(void*addr, size_t length);// 斷開映射
//addr 為映射后的地址,length 為映射后的內(nèi)存長度
例:將四個已申請到的緩沖幀映射到應(yīng)用程序,用buffers 指針記錄。
- buffers =(buffer*)calloc (req.count, sizeof (*buffers));
- if (!buffers) {
- fprintf (stderr,"Out of memory/n");
- exit(EXIT_FAILURE);
- }
// 映射
- for (unsignedint n_buffers = 0; n_buffers < req.count; ++n_buffers) {
- struct v4l2_bufferbuf;
- memset(&buf,0,sizeof(buf));
- buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory =V4L2_MEMORY_MMAP;
- buf.index =n_buffers;
- // 查詢序號為n_buffers 的緩沖區(qū),得到其起始物理地址和大小
- if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
- exit(-1);
- buffers[n_buffers].length= buf.length;
- // 映射內(nèi)存
- buffers[n_buffers].start=mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);
- if (MAP_FAILED== buffers[n_buffers].start)
- exit(-1);
- }
8. 緩沖區(qū)處理好之后,就可以開始獲取數(shù)據(jù)了
- // 啟動/ 停止數(shù)據(jù)流
- VIDIOC_STREAMON,VIDIOC_STREAMOFF
- int ioctl(intfd, int request, const int *argp);
- //argp 為流類型指針,如V4L2_BUF_TYPE_VIDEO_CAPTURE.
- 在開始之前,還應(yīng)當(dāng)把緩沖幀放入緩沖隊列:
- VIDIOC_QBUF// 把幀放入隊列
- VIDIOC_DQBUF// 從隊列中取出幀
- int ioctl(intfd, int request, struct v4l2_buffer *argp);
例:把四個緩沖幀放入隊列,并啟動數(shù)據(jù)流
- unsigned int i;
- enum v4l2_buf_typetype;
- // 將緩沖幀放入隊列
- for (i = 0; i< 4; ++i)
- {
- structv4l2_buffer buf;
- buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory =V4L2_MEMORY_MMAP;
- buf.index = i;
- ioctl (fd,VIDIOC_QBUF, &buf);
- }
- type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ioctl (fd,VIDIOC_STREAMON, &type);
例:獲取一幀并處理
- structv4l2_buffer buf;
- CLEAR (buf);
- buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory =V4L2_MEMORY_MMAP;
- // 從緩沖區(qū)取出一個緩沖幀
- ioctl (fd,VIDIOC_DQBUF, &buf);
- // 圖像處理
- process_image(buffers[buf.index].start);
- // 將取出的緩沖幀放回緩沖區(qū)
- ioctl (fd, VIDIOC_QBUF,&buf);
至于驅(qū)動的實現(xiàn),可以參考內(nèi)核中,我是用usb攝像頭的,所以,其實現(xiàn)都是好的。主要就是應(yīng)用程序的實現(xiàn)了。驅(qū)動都哦在uvc目錄下面,這個待理解。