zhangAndroidBlogjava

          linux之V4L2攝像頭應用流程

          本文源自http://blog.csdn.net/eastmoon502136/article/details/8190262

              對于v4l2,上次是在調(diào)試收音機驅(qū)動的時候用過,其他也就只是用i2c配置一些寄存器就可以了。那時只是粗粗的了解了,把收音機當作v4l2的設備后會在/dev目錄下生成一個radio的節(jié)點。然后就可以操作了。后來就沒怎么接觸了。這周,需要調(diào)試下usb的攝像頭。因為有問題,所以就要跟進,于是也就要開始學習下linux的v4l2了。看到一篇很不錯的文章,下面參考這篇文章,加上自己的一些見解,做一些總結把。

                 Video for Linuxtwo(Video4Linux2)簡稱V4L2,是V4L的改進版。V4L2是linux操作系統(tǒng)下用于采集圖片、視頻和音頻數(shù)據(jù)的API接口,配合適當?shù)囊曨l采集設備和相應的驅(qū)動程序,可以實現(xiàn)圖片、視頻、音頻等的采集。在遠程會議、可視電話、視頻監(jiān)控系統(tǒng)和嵌入式多媒體終端中都有廣泛的應用。

          在Linux下,所有外設都被看成一種特殊的文件,成為“設備文件”,可以象訪問普通文件一樣對其進行讀寫。一般來說,采用V4L2驅(qū)動的攝像頭設備文件是/dev/video0。V4L2支持兩種方式來采集圖像:內(nèi)存映射方式(mmap)和直接讀取方式(read)。V4L2在include/linux/videodev.h文件中定義了一些重要的數(shù)據(jù)結構,在采集圖像的過程中,就是通過對這些數(shù)據(jù)的操作來獲得最終的圖像數(shù)據(jù)。Linux系統(tǒng)V4L2的能力可在Linux內(nèi)核編譯階段配置,默認情況下都有此開發(fā)接口。

                 而攝像頭所用的主要是capature了,視頻的捕捉,具體linux的調(diào)用可以參考下圖。

          應用程序通過V4L2進行視頻采集的原理

          V4L2支持內(nèi)存映射方式(mmap)和直接讀取方式(read)來采集數(shù)據(jù),前者一般用于連續(xù)視頻數(shù)據(jù)的采集,后者常用于靜態(tài)圖片數(shù)據(jù)的采集,本文重點討論內(nèi)存映射方式的視頻采集。

          應用程序通過V4L2接口采集視頻數(shù)據(jù)分為五個步驟:

          首先,打開視頻設備文件,進行視頻采集的參數(shù)初始化,通過V4L2接口設置視頻圖像的采集窗口、采集的點陣大小和格式;

          其次,申請若干視頻采集的幀緩沖區(qū),并將這些幀緩沖區(qū)從內(nèi)核空間映射到用戶空間,便于應用程序讀取/處理視頻數(shù)據(jù);

          第三,將申請到的幀緩沖區(qū)在視頻采集輸入隊列排隊,并啟動視頻采集;

          第四,驅(qū)動開始視頻數(shù)據(jù)的采集,應用程序從視頻采集輸出隊列取出幀緩沖區(qū),處理完后,將幀緩沖區(qū)重新放入視頻采集輸入隊列,循環(huán)往復采集連續(xù)的視頻數(shù)據(jù);

          第五,停止視頻采集。

          具體的程序?qū)崿F(xiàn)流程可以參考下面的流程圖:


          其實其他的都比較簡單,就是通過ioctl這個接口去設置一些參數(shù)。最主要的就是buf管理。他有一個或者多個輸入隊列和輸出隊列。

          啟動視頻采集后,驅(qū)動程序開始采集一幀數(shù)據(jù),把采集的數(shù)據(jù)放入視頻采集輸入隊列的第一個幀緩沖區(qū),一幀數(shù)據(jù)采集完成,也就是第一個幀緩沖區(qū)存滿一幀數(shù)據(jù)后,驅(qū)動程序?qū)⒃搸彌_區(qū)移至視頻采集輸出隊列,等待應用程序從輸出隊列取出。驅(qū)動程序接下來采集下一幀數(shù)據(jù),放入第二個幀緩沖區(qū),同樣幀緩沖區(qū)存滿下一幀數(shù)據(jù)后,被放入視頻采集輸出隊列。

          應用程序從視頻采集輸出隊列中取出含有視頻數(shù)據(jù)的幀緩沖區(qū),處理幀緩沖區(qū)中的視頻數(shù)據(jù),如存儲或壓縮。

          最后,應用程序?qū)⑻幚硗陻?shù)據(jù)的幀緩沖區(qū)重新放入視頻采集輸入隊列,這樣可以循環(huán)采集,如圖所示。


          每一個幀緩沖區(qū)都有一個對應的狀態(tài)標志變量,其中每一個比特代表一個狀態(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)核提供給應用程序訪問音、視頻驅(qū)動的統(tǒng)一接口。

           

          2. 工作流程:

          打開設備-> 檢查和設置設備屬性->設置幀格式-> 設置一種輸入輸出方法(緩沖區(qū)管理)-> 循環(huán)獲取數(shù)據(jù)-> 關閉設備。

           

          3. 設備的打開和關閉:

           

          #include<fcntl.h>

          int open(constchar *device_name, int flags);

           

          #include <unistd.h>

          int close(intfd);

          例:


          1. int fd=open(“/dev/video0”,O_RDWR);// 打開設備  
          2. close(fd);// 關閉設備

          注意:V4L2 的相關定義包含在頭文件<linux/videodev2.h>中.

           

          4. 查詢設備屬性: VIDIOC_QUERYCAP

          相關函數(shù):

          1. int ioctl(intfd, int request, struct v4l2_capability *argp);  

          相關結構體:

          1. structv4l2_capability  
          2. {  
          3. __u8 driver[16];     // 驅(qū)動名字  
          4. __u8 card[32];       // 設備名字  
          5. __u8bus_info[32]; // 設備在系統(tǒng)中的位置  
          6. __u32 version;       // 驅(qū)動版本號  
          7. __u32capabilities;  // 設備支持的操作  
          8. __u32reserved[4]; // 保留字段  
          9. };  
          10. capabilities 常用值:  
          11. V4L2_CAP_VIDEO_CAPTURE    // 是否支持圖像獲取 

          例:顯示設備信息

          1. structv4l2_capability cap;  
          2. ioctl(fd,VIDIOC_QUERYCAP,&cap);  
          3. 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. 幀格式:

          1. VIDIOC_ENUM_FMT// 顯示所有支持的格式  
          2. int ioctl(intfd, int request, struct v4l2_fmtdesc *argp);  
          3. structv4l2_fmtdesc  
          4. {  
          5. __u32 index;   // 要查詢的格式序號,應用程序設置  
          6. enumv4l2_buf_type type;     // 幀類型,應用程序設置  
          7. __u32 flags;    // 是否為壓縮格式  
          8. __u8       description[32];      // 格式名稱  
          9. __u32pixelformat; // 格式  
          10. __u32reserved[4]; // 保留  
          11. };  

          例:顯示所有支持的格式

          1. structv4l2_fmtdesc fmtdesc;  
          2. fmtdesc.index=0;  
          3. fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
          4. printf("Supportformat:/n");  
          5. while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)  
          6. {  
          7. printf("/t%d.%s/n",fmtdesc.index+1,fmtdesc.description);  
          8. fmtdesc.index++;  
          9. }  

          // 查看或設置當前格式

          VIDIOC_G_FMT,VIDIOC_S_FMT

          // 檢查是否支持某種格式

          1. VIDIOC_TRY_FMT  
          2. int ioctl(intfd, int request, struct v4l2_format *argp);  
          3. structv4l2_format  
          4. {  
          5. enumv4l2_buf_type type;// 幀類型,應用程序設置  
          6. union fmt  
          7. {  
          8. structv4l2_pix_format pix;// 視頻設備使用  
          9. structv4l2_window win;  
          10. structv4l2_vbi_format vbi;  
          11. structv4l2_sliced_vbi_format sliced;  
          12. __u8raw_data[200];  
          13. };  
          14. };  


          1. structv4l2_pix_format  
          2. {  
          3. __u32 width;  // 幀寬,單位像素  
          4. __u32 height;  // 幀高,單位像素  
          5. __u32pixelformat; // 幀格式  
          6. enum v4l2_fieldfield;  
          7. __u32bytesperline;  
          8. __u32 sizeimage;  
          9. enumv4l2_colorspace colorspace;  
          10. __u32 priv;  
          11. };

          例:顯示當前幀的相關信息

          1. structv4l2_format fmt;  
          2. fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
          3. ioctl(fd,VIDIOC_G_FMT,&fmt);  
          4. printf(“Currentdata format information:  
          5. /n/twidth:%d/n/theight:%d/n”,fmt.fmt.width,fmt.fmt.height);  
          6. structv4l2_fmtdesc fmtdesc;  
          7. fmtdesc.index=0;  
          8. fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
          9. while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)  
          10. {  
          11. if(fmtdesc.pixelformat& fmt.fmt.pixelformat)  
          12. {  
          13. printf(“/tformat:%s/n”,fmtdesc.description);  
          14. break;  
          15. }  
          16. fmtdesc.index++;  
          17. }  


          例:檢查是否支持某種幀格式

          1. structv4l2_format fmt;  
          2. fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
          3. fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB32;  
          4. if(ioctl(fd,VIDIOC_TRY_FMT,&fmt)==-1)  
          5. if(errno==EINVAL)  
          6. printf(“notsupport format RGB32!/n”);  

          6. 圖像的縮放

          1. VIDIOC_CROPCAP  
          2. int ioctl(int fd,int request, struct v4l2_cropcap *argp);  
          3. structv4l2_cropcap  
          4. {  
          5. enumv4l2_buf_type type;// 應用程序設置  
          6. struct v4l2_rectbounds;//     最大邊界  
          7. struct v4l2_rectdefrect;// 默認值  
          8. structv4l2_fract pixelaspect;  
          9. };  


          // 設置縮放

          1. VIDIOC_G_CROP,VIDIOC_S_CROP  
          2. int ioctl(intfd, int request, struct v4l2_crop *argp);  
          3. int ioctl(intfd, int request, const struct v4l2_crop *argp);  
          4. struct v4l2_crop  
          5. {  
          6. enumv4l2_buf_type type;// 應用程序設置  
          7. struct v4l2_rectc;  
          8. }  

          7. 申請和管理緩沖區(qū),應用程序和設備有三種交換數(shù)據(jù)的方法,直接read/write ,內(nèi)存映射(memorymapping) ,用戶指針。這里只討論 memorymapping.

          // 向設備申請緩沖區(qū)

          1. VIDIOC_REQBUFS  
          2. int ioctl(intfd, int request, struct v4l2_requestbuffers *argp);  
          3. structv4l2_requestbuffers  
          4. {  
          5. __u32 count;  // 緩沖區(qū)內(nèi)緩沖幀的數(shù)目  
          6. enumv4l2_buf_type type;     // 緩沖幀數(shù)據(jù)格式  
          7. enum v4l2_memorymemory;       // 區(qū)別是內(nèi)存映射還是用戶指針方式  
          8. __u32 reserved[2];  
          9. };  
          10.    
          11. enum v4l2_memoy{V4L2_MEMORY_MMAP,V4L2_MEMORY_USERPTR};  
          12. //count,type,memory都要應用程序設置  


          例:申請一個擁有四個緩沖幀的緩沖區(qū)

          1. structv4l2_requestbuffers req;  
          2. req.count=4;  
          3. req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
          4. req.memory=V4L2_MEMORY_MMAP;  
          5. ioctl(fd,VIDIOC_REQBUFS,&req);  


          獲取緩沖幀的地址,長度:

          VIDIOC_QUERYBUF

          int ioctl(intfd, int request, struct v4l2_buffer *argp);


          1. structv4l2_buffer  
          2. {  
          3. __u32 index;   //buffer 序號  
          4. enumv4l2_buf_type type;     //buffer 類型  
          5. __u32 byteused;     //buffer 中已使用的字節(jié)數(shù)  
          6. __u32 flags;    // 區(qū)分是MMAP 還是USERPTR  
          7. enum v4l2_fieldfield;  
          8. struct timevaltimestamp;// 獲取第一個字節(jié)時的系統(tǒng)時間  
          9. structv4l2_timecode timecode;  
          10. __u32 sequence;// 隊列中的序號  
          11. enum v4l2_memorymemory;//IO 方式,被應用程序設置  
          12. union m  
          13. {  
          14. __u32 offset;// 緩沖幀地址,只對MMAP 有效  
          15. unsigned longuserptr;  
          16. };  
          17. __u32 length;// 緩沖幀長度  
          18. __u32 input;  
          19. __u32 reserved;  
          20. };  


          MMAP ,定義一個結構體來映射每個緩沖幀。

          1. Struct buffer  
          2. {  
          3. void* start;  
          4. unsigned intlength;  
          5. }*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 標志映射后能否被讀寫,其值為PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE

          //flags 確定此內(nèi)存映射能否被其他進程共享,MAP_SHARED,MAP_PRIVATE

          //fd,offset, 確定被映射的內(nèi)存地址

          返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1);

           

          int munmap(void*addr, size_t length);// 斷開映射

          //addr 為映射后的地址,length 為映射后的內(nèi)存長度

           

          例:將四個已申請到的緩沖幀映射到應用程序,用buffers 指針記錄。

          1. buffers =(buffer*)calloc (req.count, sizeof (*buffers));  
          2. if (!buffers) {  
          3. fprintf (stderr,"Out of memory/n");  
          4. exit(EXIT_FAILURE);  
          5. }  

          // 映射

          1. for (unsignedint n_buffers = 0; n_buffers < req.count; ++n_buffers) {  
          2. struct v4l2_bufferbuf;  
          3. memset(&buf,0,sizeof(buf));  
          4. buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;  
          5. buf.memory =V4L2_MEMORY_MMAP;  
          6. buf.index =n_buffers;  
          7. // 查詢序號為n_buffers 的緩沖區(qū),得到其起始物理地址和大小  
          8. if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf))  
          9. exit(-1);  
          10. buffers[n_buffers].lengthbuf.length;  
          11. // 映射內(nèi)存  
          12. buffers[n_buffers].start=mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);  
          13. if (MAP_FAILED== buffers[n_buffers].start)  
          14. exit(-1);  
          15. }  

          8. 緩沖區(qū)處理好之后,就可以開始獲取數(shù)據(jù)了

          1. // 啟動/ 停止數(shù)據(jù)流  
          2. VIDIOC_STREAMON,VIDIOC_STREAMOFF  
          3. int ioctl(intfd, int request, const int *argp);  
          4. //argp 為流類型指針,如V4L2_BUF_TYPE_VIDEO_CAPTURE.  
          5. 在開始之前,還應當把緩沖幀放入緩沖隊列:  
          6. VIDIOC_QBUF// 把幀放入隊列  
          7. VIDIOC_DQBUF// 從隊列中取出幀  
          8. int ioctl(intfd, int request, struct v4l2_buffer *argp);  

          例:把四個緩沖幀放入隊列,并啟動數(shù)據(jù)流

          1. unsigned int i;  
          2. enum v4l2_buf_typetype;  
          3. // 將緩沖幀放入隊列  
          4. for (i = 0; i< 4; ++i)  
          5. {  
          6. structv4l2_buffer buf;  
          7. buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;  
          8. buf.memory =V4L2_MEMORY_MMAP;  
          9. buf.index = i;  
          10. ioctl (fd,VIDIOC_QBUF, &buf);  
          11. }  
          12. type =V4L2_BUF_TYPE_VIDEO_CAPTURE;  
          13. ioctl (fd,VIDIOC_STREAMON, &type);  


          例:獲取一幀并處理

          1. structv4l2_buffer buf;  
          2. CLEAR (buf);  
          3. buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;  
          4. buf.memory =V4L2_MEMORY_MMAP;  
          5. // 從緩沖區(qū)取出一個緩沖幀  
          6. ioctl (fd,VIDIOC_DQBUF, &buf);  
          7. // 圖像處理  
          8. process_image(buffers[buf.index].start);  
          9. // 將取出的緩沖幀放回緩沖區(qū)  
          10. ioctl (fd, VIDIOC_QBUF,&buf);  

          至于驅(qū)動的實現(xiàn),可以參考內(nèi)核中,我是用usb攝像頭的,所以,其實現(xiàn)都是好的。主要就是應用程序的實現(xiàn)了。驅(qū)動都哦在uvc目錄下面,這個待理解。





          posted on 2014-04-30 13:22 ZC小欄 閱讀(510) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導航:
           

          導航

          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          統(tǒng)計

          留言簿

          文章分類

          文章檔案

          搜索

          最新評論

          主站蜘蛛池模板: 岳阳市| 连山| 龙南县| 竹山县| 浑源县| 临邑县| 宜兰市| 信丰县| 华蓥市| 舒兰市| 杭锦后旗| 龙口市| 平度市| 广饶县| 商河县| 嘉善县| 松溪县| 灵寿县| 大兴区| 思茅市| 铜陵市| 栖霞市| 收藏| 高雄市| 合作市| 安化县| 泽州县| 丰县| 婺源县| 屯门区| 庄河市| 阳东县| 大宁县| 阳朔县| 玛曲县| 舟曲县| 靖边县| 共和县| 阿勒泰市| 沂南县| 贵阳市|