Linux下的V4L2的API編程總結


    由於工作的需要,經過幾天的了解之后,終於熟悉了V4L2的API應用的一個整體框架,在此感謝兩位博主的分享,無私貢獻這兩篇有關V4L2介紹的博客:
博客一:
http://blog.csdn.net/eastmoon502136/article/details/8190262
博客二:
http://blog.chinaunix.net/uid-26833883-id-3249346.html

    下面我就直接貼了,在根據這兩篇博客的基礎上,加以自己的一些注釋,希望和我一樣的初學者在看有關V4L2的API編程的時候或多或少有些思路吧!

一:V4L2的應用流程

       Video for Linux two(Video4Linux2)簡稱V4L2,是V4L的改進版。V4L2是linux操作系統下用於采集圖片、視頻和音頻數據的API接口,配合適當的視頻采集設備和相應的驅動程序,可以實現圖片、視頻、音頻等的采集。在遠程會議、可視電話、視頻監控系統和嵌入式多媒體終端中都有廣泛的應用。

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

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



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

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

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

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

其次,申請若干視頻采集的幀緩沖區,並將這些幀緩沖區從內核空間映射到用戶空間,便於應用程序讀取/處理視頻數據;

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

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

第五,停止視頻采集。

具體的程序實現流程可以參考下面的流程圖:


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

啟動視頻采集后,驅動程序開始采集一幀數據,把采集的數據放入視頻采集輸入隊列的第一個幀緩沖區,一幀數據采集完成,也就是第一個幀緩沖區存滿一幀數據后,驅動程序將該幀緩沖區移至視頻采集輸出隊列,等待應用程序從輸出隊列取出。驅動程序接下來采集下一幀數據,放入第二個幀緩沖區,同樣幀緩沖區存滿下一幀數據后,被放入視頻采集輸出隊列。

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

最后,應用程序將處理完數據的幀緩沖區重新放入視頻采集輸入隊列,這樣可以循環采集,如圖所示。

 

每一個幀緩沖區都有一個對應的狀態標志變量,其中每一個比特代表一個狀態

  V4L2_BUF_FLAG_UNMAPPED 0B0000

  V4L2_BUF_FLAG_MAPPED 0B0001

  V4L2_BUF_FLAG_ENQUEUED 0B0010

  V4L2_BUF_FLAG_DONE 0B0100




緩沖區的狀態轉化如圖所示。

 

下面的程序注釋的很好,就拿來參考下:


V4L2 編程

1. 定義

V4L2(Video ForLinux Two) 是內核提供給應用程序訪問音、視頻驅動的統一接口。


2. 工作流程:

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


3. 設備的打開和關閉:


#include

int open(constchar *device_name, int flags);


#include 

int close(intfd);

例:

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

2. close(fd);// 關閉設備  


注意:V4L2 的相關定義包含在頭文件中.


4. 查詢設備屬性: VIDIOC_QUERYCAP

相關函數:

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

相關結構體:

1. structv4l2_capability  

2. {  

3. __u8 driver[16];     // 驅動名字  

4. __u8 card[32];       // 設備名字  

5. __u8bus_info[32]; // 設備在系統中的位置  

6. __u32 version;       // 驅動版本號  

7. __u32capabilities;  // 設備支持的操作  

8. __u32reserved[4]; // 保留字段  

9. };  

10. capabilities 常用值:  

11. V4L2_CAP_VIDEO_CAPTURE    // 是否支持圖像獲取  

12.    


例:顯示設備信息

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. 申請和管理緩沖區,應用程序和設備有三種交換數據的方法,直接read/write ,內存映射(memorymapping) ,用戶指針。這里只討論 memorymapping.

// 向設備申請緩沖區

1. VIDIOC_REQBUFS  

2. int ioctl(intfd, int request, struct v4l2_requestbuffers *argp);  

3. structv4l2_requestbuffers  

4. {  

5. __u32 count;  // 緩沖區內緩沖幀的數目  

6. enumv4l2_buf_type type;     // 緩沖幀數據格式  

7. enum v4l2_memorymemory;       // 區別是內存映射還是用戶指針方式  

8. __u32 reserved[2];  

9. };  

10.    

11. enum v4l2_memoy{V4L2_MEMORY_MMAP,V4L2_MEMORY_USERPTR};  

12. //count,type,memory都要應用程序設置  


例:申請一個擁有四個緩沖幀的緩沖區

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 中已使用的字節數  

6. __u32 flags;    // 區分是MMAP 還是USERPTR  

7. enum v4l2_fieldfield;  

8. struct timevaltimestamp;// 獲取第一個字節時的系統時間  

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. };  



定義一個結構體來保存每個緩沖幀的地址和長度。

1. Struct buffer  

2. {  

3. void* start;  

4. unsigned intlength;  

5. }*buffers;  



#include

void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);

//addr 映射起始地址,一般為NULL ,讓內核自動選擇

//length 被映射內存塊的長度

//prot 標志映射后能否被讀寫,其值為PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE

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

//fd,offset, 確定被映射的內存地址

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


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

//addr 為映射后的地址,length 為映射后的內存長度


例:將四個已申請到的緩沖幀映射到應用程序,用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 的緩沖區,得到其起始物理地址和大小  

8. if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf))  

9. exit(-1);  

10. buffers[n_buffers].length= buf.length;  

11. // 映射內存  

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. 緩沖區處理好之后,就可以開始獲取數據了

1. // 啟動/ 停止數據流  

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);  


例:把四個緩沖幀放入隊列,並啟動數據流

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. // 從緩沖區取出一個緩沖幀  

6. ioctl (fd,VIDIOC_DQBUF, &buf);  

7. // 圖像處理  

8. process_image(buffers[buf.index].start);  

9. // 將取出的緩沖幀放回緩沖區  

10. ioctl (fd, VIDIOC_QBUF,&buf);  


 二:完整的代碼及相應注釋如下:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct     //定義一個結構體來記錄每個緩沖幀映射后的地址和長度。
{
    void *start;
    int length;
}BUFTYPE;

BUFTYPE *user_buf;
int n_buffer = 0;

//打開攝像頭設備
int open_camer_device()
{
    int fd;

     if((fd = open("/dev/video0",O_RDWR | O_NONBLOCK)) < 0)  //以非阻塞的方式打開攝像頭,返回攝像頭的fd
    {
        perror("Fail to open");
        exit(EXIT_FAILURE);
    } 

    return fd;
}

int init_mmap(int fd)
{
    int i = 0;
    struct v4l2_requestbuffers reqbuf;          //向驅動申請幀緩沖的請求,里面包含申請的個數

    bzero(&reqbuf,sizeof(reqbuf));               //查看Man手冊,我們知道,這個是初始化reqbuf這個變量里邊的值為0
    reqbuf.count = 4;                            //緩沖區內緩沖幀的數目
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;   //緩沖幀數據格式
    reqbuf.memory = V4L2_MEMORY_MMAP;            //區別是內存映射還是用戶指針方式,在這里是內存映射

    //申請視頻緩沖區(這個緩沖區位於內核空間,需要通過mmap映射)
    //這一步操作可能會修改reqbuf.count的值,修改為實際成功申請緩沖區個數
    if(-1 == ioctl(fd,VIDIOC_REQBUFS,&reqbuf))     //向設備申請緩沖區
    {
        perror("Fail to ioctl 'VIDIOC_REQBUFS'");
        exit(EXIT_FAILURE);
    }

    n_buffer = reqbuf.count;  //把實際成功申請緩沖區個數的值賦給n_buffer這個變量,因為在申請的時候可能會修改reqbuf.count的值

    printf("n_buffer = %d\n",n_buffer);

    user_buf = calloc(reqbuf.count,sizeof(*user_buf));   //為這個結構體變量分配內存,這個結構體主要的目的保存的是
                                                                                //每一個緩沖幀的地址和大小。
    if(user_buf == NULL)
    {
    fprintf(stderr,"Out of memory\n");
    exit(EXIT_FAILURE);
    }

/*******************************函數注釋*********************************/
//將這些幀緩沖區從內核空間映射到用戶空間,便於應用程序讀取/處理視頻數據;
//void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
//addr 映射起始地址,一般為NULL ,讓內核自動選擇
//length 被映射內存塊的長度
//prot 標志映射后能否被讀寫,其值為PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE
//flags 確定此內存映射能否被其他進程共享,MAP_SHARED,MAP_PRIVATE
//fd,offset, 確定被映射的內存地址
//返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1);
//int munmap(void *addr, size_t length);// 斷開映射
//addr 為映射后的地址,length 為映射后的內存長度
 /*******************************函數注釋*********************************/


 //思考:在mmap映射之后,在用戶空間可以對視頻采集的幀緩沖區進行怎樣的操作?能否舉個實例?
//在read_frame()的process_image(user_buf[buf.index].start,user_buf[buf.index].length)中可以找到答案
    for(i = 0; i < reqbuf.count; i ++)
    {
        struct v4l2_buffer buf;

        bzero(&buf,sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

    //查詢申請到內核緩沖區的信息
        if(-1 == ioctl(fd,VIDIOC_QUERYBUF,&buf))  //通過fd就可以找到內核的緩沖區么?
        {
        perror("Fail to ioctl : VIDIOC_QUERYBUF");
        exit(EXIT_FAILURE);
        }

        user_buf[i].length = buf.length;
        user_buf[i].start =                               //返回映射后的地址
        mmap(                                               //關於mmap的注釋,可參考上方
        NULL,/*start anywhere*/
        buf.length,
        PROT_READ | PROT_WRITE,
        MAP_SHARED,
        fd,buf.m.offset
        );
        if(MAP_FAILED == user_buf[i].start)  //MAP_FAILED表示mmap沒有成功映射,其返回的值
        {
        perror("Fail to mmap");
        exit(EXIT_FAILURE);
        }
    } 

return 0;
}


/**********************注釋*******************************************
//VIDIOC_ENUM_FMT// 顯示所有支持的格式  
//int ioctl(intfd, int request, struct v4l2_fmtdesc *argp);  
//structv4l2_fmtdesc  
//{  
//__u32 index;   // 要查詢的格式序號,應用程序設置  
//enumv4l2_buf_type type;     // 幀類型,應用程序設置  
//__u32 flags;    // 是否為壓縮格式  
//__u8       description[32];      // 格式名稱  
//__u32pixelformat; // 格式  
//__u32reserved[4]; // 保留  
//};  
*****************************注釋*************************************/
/****************************注釋*********************************************
//structv4l2_capability  
//{  
//__u8 driver[16];     // 驅動名字  
//__u8 card[32];       // 設備名字  
//__u8bus_info[32]; // 設備在系統中的位置  
//__u32 version;       // 驅動版本號  
//__u32capabilities;  // 設備支持的操作  
//__u32reserved[4]; // 保留字段  
//};  
//capabilities 常用值:  
//V4L2_CAP_VIDEO_CAPTURE    // 是否支持圖像獲取
*******************************注釋*******************************************/
/******************************注釋********************************************
//structv4l2_format  
//{  
//enumv4l2_buf_type type;// 幀類型,應用程序設置  
//union fmt  
//{  
//structv4l2_pix_format pix;// 視頻設備使用  
//structv4l2_window win;  
//structv4l2_vbi_format vbi;  
//structv4l2_sliced_vbi_format sliced;  
//__u8raw_data[200];  
//};  
//};  
********************************注釋*******************************************/


//初始化視頻設備
int init_camer_device(int fd)
{
    struct v4l2_fmtdesc fmt;              
    struct v4l2_capability cap;         
    struct v4l2_format stream_fmt;       
    int ret;

    //當前視頻設備支持的視頻格式
    memset(&fmt,0,sizeof(fmt));
    fmt.index = 0;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;


    while((ret = ioctl(fd,VIDIOC_ENUM_FMT,&fmt)) == 0)  
    {
        fmt.index ++ ;


        printf("{pixelformat = %c%c%c%c},description = '%s'\n",
        fmt.pixelformat & 0xff,(fmt.pixelformat >> 8)&0xff,
        (fmt.pixelformat >> 16) & 0xff,(fmt.pixelformat >> 24)&0xff,
        fmt.description);
    }


    //查詢設備屬性
    ret = ioctl(fd,VIDIOC_QUERYCAP,&cap);
    if(ret < 0){
    perror("FAIL to ioctl VIDIOC_QUERYCAP");
    exit(EXIT_FAILURE);
    }


    //判斷是否是一個視頻捕捉設備
    if(!(cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE))
    {
    printf("The Current device is not a video capture device\n");
    exit(EXIT_FAILURE);

    }


    //判斷是否支持視頻流形式
    if(!(cap.capabilities & V4L2_CAP_STREAMING))
    {
    printf("The Current device does not support streaming i/o\n");
    exit(EXIT_FAILURE);
    }


    //設置攝像頭采集數據格式,如設置采集數據的
    //長,寬,圖像格式(JPEG,YUYV,MJPEG等格式)
    stream_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    stream_fmt.fmt.pix.width = 680;
    stream_fmt.fmt.pix.height = 480;
    stream_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    stream_fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

    if(-1 == ioctl(fd,VIDIOC_S_FMT,&stream_fmt))
    {
    perror("Fail to ioctl");
    exit(EXIT_FAILURE);
    }

    //初始化視頻采集方式(mmap)
    init_mmap(fd);

    return 0;
}


/************************注釋**************************************************
//structv4l2_buffer  
//{  
//__u32 index;   //buffer 序號  
//enumv4l2_buf_type type;     //buffer 類型  
//__u32 byteused;     //buffer 中已使用的字節數  
//__u32 flags;    // 區分是MMAP 還是USERPTR  
//enum v4l2_fieldfield;  
//struct timevaltimestamp;// 獲取第一個字節時的系統時間  
//structv4l2_timecode timecode;  
//__u32 sequence;// 隊列中的序號  
//enum v4l2_memorymemory;//IO 方式,被應用程序設置  
//union m  
//{  
//__u32 offset;// 緩沖幀地址,只對MMAP 有效  
//unsigned longuserptr;  
//};  
//__u32 length;// 緩沖幀長度  
//__u32 input;  
//__u32 reserved;  
//};  
*****************************注釋************************************************/


 //開始采集數據
int start_capturing(int fd)          //啟動視頻采集后,驅動程序開始采集一幀數據,把采集的數據放入視頻采集輸入隊列的第一個幀緩沖區,
{                                             //一幀數據采集完成,也就是第一個幀緩沖區存滿一幀數據后,驅動程序將該幀緩沖區移至視頻采集
      unsigned int i;                     //輸出隊列,等待應用程序 從輸出隊列取出。驅動程序接下來采集下一幀數據,放入第二個幀緩沖區,
       enum v4l2_buf_type type;   //同樣幀緩沖區存滿下一幀數據后,被放入視頻采集輸出隊列。所以在開始采集視頻數據之前,
                                              //我們需要將申請的緩沖區放入視頻采集輸入隊列中排隊,這樣視頻采集輸入隊列中才有幀緩沖                                                                 //區,這樣也才能保存我們才采集的數據
    
    //將申請的內核緩沖區放入視頻采集輸入隊列中排隊
    for(i = 0;i < n_buffer;i ++)
    {
        struct v4l2_buffer buf;

        bzero(&buf,sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if(-1 == ioctl(fd,VIDIOC_QBUF,&buf))       //思考:申請的內核緩沖區和視頻采集輸入隊列排隊怎么聯系起來的?                                                                                                  //貌似根本就沒有與前面申請 的緩沖區,
        {                                                          //不知道V4L2底層驅動是如何實現的,這有待自己進一步去深鑽V4L2驅動的實現
                perror("Fail to ioctl 'VIDIOC_QBUF'");
                exit(EXIT_FAILURE);
        }

    }


    //開始采集數據
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;                //在采集數據的時候,它是怎么和視頻輸入隊列中的幀緩沖區聯系起來的?                                                                                      //參數中都沒有給出,如何聯系?
    if(-1 == ioctl(fd,VIDIOC_STREAMON,&type))          //這有得去深鑽V4L2的底層驅動的實現,聽說ioctl涉及79個回調函數
    {
        printf("i = %d.\n",i);
        perror("Fail to ioctl 'VIDIOC_STREAMON'");
        exit(EXIT_FAILURE);
    }

    return 0;
}


//將采集好的數據放到文件中
int process_image(void *addr,int length)   //思考一下addr是指啥?在read_frame()中可以找到答案,先可以跳過process_image,
{                                         //直接在read_frame函數中去看,在看的過程中,遇到process_image函數的時候再過來看
    FILE *fp;
    static int num = 0;
    char picture_name[20];

    // sprintf(picture_name,"test.avi");             //將一幀幀的數據存入test.avi文件中
    sprintf(picture_name,"picture%d.jpg",num ++);  //格式化picture_name這個變量

    if((fp = fopen(picture_name,"w")) == NULL)   //這個方式和上述的sprintf結合起來就實現了文件的自動命名,此方法可以借鑒一下
    {
        perror("Fail to fopen");
        exit(EXIT_FAILURE);
    }


    fwrite(addr,length,1,fp);   //從addr寫到fp中
    usleep(500);

    fclose(fp);

    return 0;
}


int read_frame(int fd)
{
    struct v4l2_buffer buf;       
    unsigned int i;               

    bzero(&buf,sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    //從輸出隊列中去取緩沖區
    if(-1 == ioctl(fd,VIDIOC_DQBUF,&buf)) //buf和fd是怎樣的一個連接方式或者說對應關系?也就是不明白,在ioctl中,                                                                                       //是如何把這兩者聯 系起來的
    {                                                     //歸根結底,一個問題就是搞清楚V4L2中的ioctl的具體底層驅動的實現,待需要再去鑽吧!
        perror("Fail to ioctl 'VIDIOC_DQBUF'");
        exit(EXIT_FAILURE);
    }


    assert(buf.index < n_buffer);      //assert函數的作用是判斷計算表達式 expression ,如果其值為假(即為0),                                                                                    //那么它先向stderr打印一條出錯信息,然后通過調用 abort 來終止程序運行。

    //讀取進程空間的數據到一個文件中
    process_image(user_buf[buf.index].start,user_buf[buf.index].length);

    if(-1 == ioctl(fd,VIDIOC_QBUF,&buf))      //這個的作用是:將視頻輸出的緩沖幀放回到視頻輸入的緩沖區中去  
    {
        perror("Fail to ioctl 'VIDIOC_QBUF'");
        exit(EXIT_FAILURE);
    }

    return 1;
}


int mainloop(int fd)         //這個mainloop()函數中主要涉及的知識就是select()函數了,可見上一篇我轉載的博客

    int count = 10;

    while(count -- > 0)     
    {
        for(;;)
        {
            fd_set fds;
            struct timeval tv;
            int r;

            FD_ZERO(&fds);
            FD_SET(fd,&fds);


            /*Timeout*/
            tv.tv_sec = 2;
            tv.tv_usec = 0;

            r = select(fd + 1,&fds,NULL,NULL,&tv);   


                if(-1 == r)
                {
                    if(EINTR == errno)
                    continue;

                    perror("Fail to select");
                    exit(EXIT_FAILURE);
                }


                if(0 == r)
                {
                    fprintf(stderr,"select Timeout\n");
                    exit(EXIT_FAILURE);
                }


                if(read_frame(fd))
                break;

            }

        }

    return 0;

}


void stop_capturing(int fd)
{
    enum v4l2_buf_type type;

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(-1 == ioctl(fd,VIDIOC_STREAMOFF,&type))
    {
        perror("Fail to ioctl 'VIDIOC_STREAMOFF'");
        exit(EXIT_FAILURE);
    }

    return;

}


void uninit_camer_device()
{
    unsigned int i;


    for(i = 0;i < n_buffer;i ++)
    {
        if(-1 == munmap(user_buf[i].start,user_buf[i].length))
        {
            exit(EXIT_FAILURE);
        }

    }

    free(user_buf);

    return;

}


void close_camer_device(int fd)
{
    if(-1 == close(fd))
    {
        perror("Fail to close fd");
        exit(EXIT_FAILURE);
    }

    return;

}


int main()
{
    int fd;       
                         
    fd = open_camer_device();

    init_camer_device(fd);

    start_capturing(fd);

    mainloop(fd);

    stop_capturing(fd);

    uninit_camer_device(fd);

    close_camer_device(fd);

    return 0;

}

注:V4L2的英文資料
v4l2.pdf


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com