1. 打開設備
1)用非阻塞模式打開攝像頭設備
int cameraFd;
cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK);
2)如果用阻塞模式打開攝像頭設備,上述代碼變爲:
cameraFd = open("/dev/video0", O_RDWR);
關於阻塞模式和非阻塞模式:
應用程序能夠使用阻塞模式或非阻塞模式打開視頻設備,如果使用非阻塞模式調用視頻設備,即使尚未捕獲到信息,驅動依舊會把緩存(DQBUFF)裏的東西返回給應用程序。
2. 查詢視頻設備的功能
控制命令VIDIOC_QUERYCAP
功能: 查詢視頻設備的功能;
參數說明:參數類型爲V4L2的能力描述類型structv4l2_capability ;
返回值說明: 執行成功時,函數返回值爲 0;函數執行成功後,structv4l2_capability 結構體變量中的返回當前視頻設備所支持的功能;例如支持視頻捕獲功能V4L2_CAP_VIDEO_CAPTURE、V4L2_CAP_STREAMING等。
執行完VIDIOC_QUERYCAP命令後,cap變量中包含了該視頻設備的能力信息,程序中通過檢查cap中的設備能力信息來判斷設備是否支持某項功能。
結構體:
描述設備能力結構struct v4l2_capability
struct v4l2_capability { __u8 driver[16]; // 驅動名字 __u8 card[32]; // 設備名字 __u8 bus_info[32]; // 設備在系統中的位置 __u32 version; // 驅動版本號 __u32 capabilities; // 設備支持的操作 __u32 reserved[4]; // 保留字段 }; |
capabilities 常用值:
V4L2_CAP_VIDEO_CAPTURE// 是否支持圖像獲取
例:顯示設備信息
#include <errno.h> #define CLEAR(d) memset(&d, 0, sizeof(d)); /** * @brief query_cap * 查詢攝像頭參數及驅動信息:打印struct v4l2_capability * @param fd 攝像頭文件描述符 * */ void query_cap(int fd) { struct v4l2_capability cap; CLEAR(cap); if(ioctl(fd,VIDIOC_QUERYCAP,&cap) < 0) //查詢攝像頭的功能 { perror("query_cap()"); //也是出錯處理 exit(EXIT_FAILURE); } printf("===============Camera Info begin===============\n"); printf("Driver: %s \n",cap.driver); printf("Card: %s \n",cap.card); printf("BusInfo: %s \n",cap.bus_info); printf("Version: %d \n",cap.version); printf("Capabilities: 0x%x \n",cap.capabilities); printf("===============Camera Info end===============\n\n"); //printf("Device Cap: 0x%x \n",cap.device_caps); //printf("Reserved: 0x%x \n",cap.reserved); }
|
使用舉例:
struct v4l2_capability cap; //這個設備的功能,比如是否是視頻輸入設備 /*查詢設備的功能,*/ if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { if (EINVAL == errno) { fprintf(stderr, "%s is no V4L2 device\n", dev_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", dev_name); exit(EXIT_FAILURE); } /*判斷是否有啓動設備功能*/ if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "%s does not support streaming i/o\n", dev_name); exit(EXIT_FAILURE); } |
2、獲取視頻設備支持的接口格式
1) 視頻採集接口(video capture interface):這種應用的設備可以是高頻頭或者攝像頭.V4L2的最初設計就是應用於這種功能的. --- 今天課程重點
2) 視頻輸出接口(video output interface):可以驅動計算機的外圍視頻圖像設備--像可以輸出電視信號格式的設備.
3)直接傳輸視頻接口(video overlay interface):它的主要工作是把從視頻採集設備採集過來的信號直接輸出到輸出設備之上,而不用經過系統的CPU.
4)視頻間隔消隱信號接口(VBI interface):它可以使應用可以訪問傳輸消隱期的視頻信號.
5)收音機接口(radio interface):可用來處理從AM或FM高頻頭設備接收來的音頻流.控制命令VIDIOC_ENUM_FMT
功能: 獲取當前視頻設備支持的接口格式。
參數說明:參數類型爲V4L2的接口格式描述符類型 structv4l2_fmtdesc
返回值說明: 執行成功時,函數返回值爲 0;struct v4l2_fmtdesc 結構體中的.pixelformat和 .description 成員返回當前視頻設備所支持的接口格式;
結構體:
structv4l2_fmtdesc 視頻格式描述符類型
/* * F O R M A T E N U M E R A T I O N視頻格式描述符類型 */ struct v4l2_fmtdesc { __u32 index; // 要查詢的格式序號,應用程序設置,從0開始,依次上升。 enum v4l2_buf_type type; // 幀類型,應用程序設置,Camera,則填寫V4L2_BUF_TYPE_VIDEO_CAPTURE __u32 flags; // 是否爲壓縮格式,如果壓縮的,則Driver 填寫:V4L2_FMT_FLAG_COMPRESSED,否則爲0 __u8 description[32]; // image format格式名稱,如:”YUV 4:2:2 (YUYV)” //”RGB888” __u32 pixelformat; // 格式 __u32 reserved[4]; // 保留 };
|
這個結構通常用於枚舉設備所支持的imageformat: VIDIOC_ENUM_FMT
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(Handle, VIDIOC_ENUM_FMT, &fmtdesc);
使用ioctl VIDIOC_ENUM_FMT 依次詢問,type爲:V4L2_BUF_TYPE_VIDEO_CAPTURE。 index從0開始,依次增加,直到返回. Driver會填充結構體structv4l2_fmtdesc的其它內容,如果index超出範圍,則返回-1。
enumv4l2_buf_type 視頻緩衝區類型
enum v4l2_buf_type { V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, //捕獲功能 V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, V4L2_BUF_TYPE_VIDEO_OVERLAY = 3, V4L2_BUF_TYPE_VBI_CAPTURE = 4, V4L2_BUF_TYPE_VBI_OUTPUT = 5, V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6, V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7, #if 1 /* Experimental */ V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, #endif V4L2_BUF_TYPE_PRIVATE = 0x80, }; |
1) typedef __u64 v4l2_std_id 視頻設備支持的標準
typedef __u64 v4l2_std_id;
/* one bit for each */ #define V4L2_STD_PAL_B ((v4l2_std_id)0x00000001) #define V4L2_STD_PAL_B1 ((v4l2_std_id)0x00000002) #define V4L2_STD_PAL_G ((v4l2_std_id)0x00000004) #define V4L2_STD_PAL_H ((v4l2_std_id)0x00000008) #define V4L2_STD_PAL_I ((v4l2_std_id)0x00000010) #define V4L2_STD_PAL_D ((v4l2_std_id)0x00000020) #define V4L2_STD_PAL_D1 ((v4l2_std_id)0x00000040) #define V4L2_STD_PAL_K ((v4l2_std_id)0x00000080)
#define V4L2_STD_PAL_M ((v4l2_std_id)0x00000100) #define V4L2_STD_PAL_N ((v4l2_std_id)0x00000200) #define V4L2_STD_PAL_Nc ((v4l2_std_id)0x00000400) #define V4L2_STD_PAL_60 ((v4l2_std_id)0x00000800)
#define V4L2_STD_NTSC_M ((v4l2_std_id)0x00001000) #define V4L2_STD_NTSC_M_JP ((v4l2_std_id)0x00002000) #define V4L2_STD_NTSC_443 ((v4l2_std_id)0x00004000) #define V4L2_STD_NTSC_M_KR ((v4l2_std_id)0x00008000)
#define V4L2_STD_SECAM_B ((v4l2_std_id)0x00010000) #define V4L2_STD_SECAM_D ((v4l2_std_id)0x00020000) #define V4L2_STD_SECAM_G ((v4l2_std_id)0x00040000) #define V4L2_STD_SECAM_H ((v4l2_std_id)0x00080000) #define V4L2_STD_SECAM_K ((v4l2_std_id)0x00100000) #define V4L2_STD_SECAM_K1 ((v4l2_std_id)0x00200000) #define V4L2_STD_SECAM_L ((v4l2_std_id)0x00400000) #define V4L2_STD_SECAM_LC ((v4l2_std_id)0x00800000)
/* ATSC/HDTV */ #define V4L2_STD_ATSC_8_VSB ((v4l2_std_id)0x01000000) #define V4L2_STD_ATSC_16_VSB ((v4l2_std_id)0x02000000)
/* FIXME: Although std_id is 64 bits, there is an issue on PPC32 architecture that makes switch(__u64) to break. So, there's a hack on v4l2-common.c rounding this value to 32 bits. As, currently, the max value is for V4L2_STD_ATSC_16_VSB (30 bits wide), it should work fine. However, if needed to add more than two standards, v4l2-common.c should be fixed. */
/* some merged standards */ #define V4L2_STD_MN (V4L2_STD_PAL_M|V4L2_STD_PAL_N|V4L2_STD_PAL_Nc|V4L2_STD_NTSC) #define V4L2_STD_B (V4L2_STD_PAL_B|V4L2_STD_PAL_B1|V4L2_STD_SECAM_B) #define V4L2_STD_GH (V4L2_STD_PAL_G|V4L2_STD_PAL_H|V4L2_STD_SECAM_G|V4L2_STD_SECAM_H) #define V4L2_STD_DK (V4L2_STD_PAL_DK|V4L2_STD_SECAM_DK)
/* some common needed stuff */ #define V4L2_STD_PAL_BG (V4L2_STD_PAL_B |\ V4L2_STD_PAL_B1 |\ V4L2_STD_PAL_G) #define V4L2_STD_PAL_DK (V4L2_STD_PAL_D |\ V4L2_STD_PAL_D1 |\ V4L2_STD_PAL_K) #define V4L2_STD_PAL (V4L2_STD_PAL_BG |\ V4L2_STD_PAL_DK |\ V4L2_STD_PAL_H |\ V4L2_STD_PAL_I) #define V4L2_STD_NTSC (V4L2_STD_NTSC_M |\ V4L2_STD_NTSC_M_JP |\ V4L2_STD_NTSC_M_KR) #define V4L2_STD_SECAM_DK (V4L2_STD_SECAM_D |\ V4L2_STD_SECAM_K |\ V4L2_STD_SECAM_K1) #define V4L2_STD_SECAM (V4L2_STD_SECAM_B |\ V4L2_STD_SECAM_G |\ V4L2_STD_SECAM_H |\ V4L2_STD_SECAM_DK |\ V4L2_STD_SECAM_L |\ V4L2_STD_SECAM_LC)
#define V4L2_STD_525_60 (V4L2_STD_PAL_M |\ V4L2_STD_PAL_60 |\ V4L2_STD_NTSC |\ V4L2_STD_NTSC_443) #define V4L2_STD_625_50 (V4L2_STD_PAL |\ V4L2_STD_PAL_N |\ V4L2_STD_PAL_Nc |\ V4L2_STD_SECAM) #define V4L2_STD_ATSC (V4L2_STD_ATSC_8_VSB |\ V4L2_STD_ATSC_16_VSB)
#define V4L2_STD_UNKNOWN 0 #define V4L2_STD_ALL (V4L2_STD_525_60 |\ V4L2_STD_625_50)
|
2) structv4l2_standard
struct v4l2_standard { __u32 index; v4l2_std_id id; __u8 name[24]; struct v4l2_fract frameperiod; /* Frames, not fields */ __u32 framelines; __u32 reserved[4]; };
|
使用舉例:
/******************************************************************************************/ //獲取視頻設備圖像格式 struct v4l2_fmtdesc fmt1; memset(&fmt1, 0, sizeof(fmt1)); fmt1.index = 0; fmt1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; while ((ioctl(fd, VIDIOC_ENUM_FMT, &fmt1)) == 0) //獲取當前視頻設備的幀格式 比如:YUYV ,RGB等等 { fmt1.index++; printf("{ pixelformat = '%c%c%c%c', description = '%s' }\n", fmt1.pixelformat & 0xFF, (fmt1.pixelformat >> 8) & 0xFF, (fmt1.pixelformat >> 16) & 0xFF, (fmt1.pixelformat >> 24) & 0xFF,fmt1.description); } /*************************************************************************************/ |
|
3. 設置視頻設備的視頻數據格式
控制命令VIDIOC_S_FMT
功能: 設置視頻設備的視頻數據格式,例如設置視頻圖像數據的長、寬,圖像格式(JPEG、YUYV格式);
參數說明:參數類型爲V4L2的視頻數據格式類型 structv4l2_format ;
返回值說明: 執行成功時,函數返回值爲 0;
結構體:struct v4l2_format 視頻數據格式
struct v4l2_format { enum v4l2_buf_type type; // 數據流類型,必須永遠是V4L2_BUF_TYPE_VIDEO_CAPTURE union { struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */ struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */ struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */ struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */ __u8 raw_data[200]; /* user-defined */ } fmt; }; |
這個結構用來查看或設置當前格式或檢查是否支持某種格式,相應的命令是VIDIOC_G_FMT, VIDIOC_S_FMT和VIDIOC_TRY_FMT。使用方法:intioctl(int fd, int request, struct v4l2_format *argp);
struct v4l2_pix_format
/* * V I D E O I M A G E F O R M A T */ struct v4l2_pix_format { __u32 width;// 寬,必須是16的倍數 __u32 height;// 高,必須是16的倍數 __u32 pixelformat;// 視頻數據存儲類型,例如是YUV4:2:2還是RGB enum v4l2_field field; __u32 bytesperline; /* for padding, zero if unused */ __u32 sizeimage; enum v4l2_colorspace colorspace; __u32 priv; /* private data, depends on pixelformat */ }; |
field:表示是否逐行掃描,是否隔行掃描. 通常採用V4L2_FIELD_NONE,逐行放置數據
可取值還有:V4L2_FIELD_INTERLACED,V4L2_FIELD_ANY。
使用舉例:
struct v4l2_format tv4l2_format; tv4l2_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; tv4l2_format.fmt.pix.width = img_width; tv4l2_format.fmt.pix.height = img_height; tv4l2_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; tv4l2_format.fmt.pix.field = V4L2_FIELD_INTERLACED; iret = ioctl(fd_usbcam, VIDIOC_S_FMT, &tv4l2_format);
|
注意:如果該視頻設備驅動不支持你所設定的圖像格式,視頻驅動會重新修改struct v4l2_format結構體變量的值爲該視頻設備所支持的圖像格式,所以在程序設計中,設定完所有的視頻格式後,要獲取實際的視頻格式,要重新讀取struct v4l2_format結構體變量。
4. 設置視頻的採集窗口參數
控制命令VIDIOC_S_CROP
功能:置採集窗口就是在攝像頭設備的取景範圍之內設定一個視頻採集區域。主要是對結構體struct v4l2_crop賦值,v4l2_crop由一個v4l2_buffer_type枚舉類型的type和v4l2_rect類型的結構體c構成,來描述視頻採集窗口的類型和大小。type設置爲視頻採集類型V4L2_BUF_TYPE_VIDEO_CAPTURE。crop.c.left, crop.c.top. crop.c.width, crop.c.height定義了一個長方形區域,這個區域是capture或者overlay模式下的取景範圍,
賦值後,用ioctl函數通過這個結構體對fd_v4l2進行設置。
struct v4l2_crop { enum v4l2_buf_type type; struct v4l2_rect c; };
返回值說明: 執行成功時,函數返回值爲 0;
示例:
舉個例子,圖片的active(actual) size是720x576, 現在你只想capture或者overlay(64,64) ,(556, 64), (64,512),(556, 512)這四個點組成區域內的像素. 即在原始圖像上下左右各裁掉64像素大小, 那麼可如下設置
struct v4l2_crop crop; memset(&crop, 0, sizeof(struct v4l2_crop)); crop.type = V4L2_BUF_TYPE_VIDEO_OVERLAY; crop.c.left = 64; crop.c.top = 64; crop.c.width = 592; crop.c.height = 448; ret = xioctl(fd, VIDIOC_S_CROP, &crop); if (ret) { printf("xioctl VIDIOC_S_CROP failed, errno(%d)\n", errno); exit(EXIT_FAILURE); } |
5. 請求V4L2驅動分配視頻緩衝區
控制命令VIDIOC_REQBUFS
功能: 請求V4L2驅動分配視頻緩衝區(申請V4L2視頻驅動分配內存),V4L2是視頻設備的驅動層,位於內核空間,所以通過VIDIOC_REQBUFS控制命令字申請的內存位於內核空間,應用程序不能直接訪問,需要通過調用mmap內存映射函數把內核空間內存映射到用戶空間後,應用程序通過訪問用戶空間地址來訪問內核空間。
參數說明:參數類型爲V4L2的申請緩衝區數據結構體類型structv4l2_requestbuffers ;
返回值說明: 執行成功時,函數返回值爲 0;V4L2驅動層分配好了視頻緩衝區;
結構體:
structv4l2_requestbuffers 描述請求分配的視頻採集緩衝區信息
/* * M E M O R Y - M A P P I N G B U F F E R S */ struct v4l2_requestbuffers { __u32 count; // 緩存數量,也就是說在緩存隊列裏保持多少張照片 enum v4l2_buf_type type; // 數據流類型,必須永遠是V4L2_BUF_TYPE_VIDEO_CAPTURE enum v4l2_memory memory;// V4L2_MEMORY_MMAP 或 V4L2_MEMORY_USERPTR __u32 reserved[2]; }; |
使用舉例:
/*自定義視頻緩衝區數據結構*/ struct buffer { void * start; size_t length;//buffer's length is different from cap_image_size }; /*定義視頻緩衝區指針*/ struct buffer * buffers = NULL; /*視頻設備控制函數*/ static int xioctl(int fd, int request, void * arg) { int r; do r = ioctl(fd, request, arg); while (-1 == r && EINTR == errno); return r; } struct v4l2_requestbuffers req; CLEAR (req); req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { if (EINVAL == errno) { fprintf(stderr, "%s does not support " "memory mapping\n", dev_name); exit(EXIT_FAILURE); } else { errno_exit("VIDIOC_REQBUFS"); } } if (req.count < 2) { fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); exit(EXIT_FAILURE); } buffers = calloc(req.count, sizeof(*buffers)); if (!buffers) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } |
注意:VIDIOC_REQBUFS會修改req的count值,req的count值返回實際申請成功的視頻緩衝區數目;
6. 查詢和設置流參數
控制命令:VIDIOC_G_PARM, VIDIOC_S_PARM
流信息,這個和video capture是相同的,這裏的param.type是V4L2_BUF_TYPE_VIDEO_CAPTURE,其中timeperframe的分母是需要設定的幀率,而分子是1。
結構定義如下:
struct v4l2_streamparm { enum v4l2_buf_type type; union { struct v4l2_captureparm capture; struct v4l2_outputparm output; __u8 raw_data[200]; } parm; };
|
如果設備是是Camera,則是capture. 它選擇使用 structv4l2_captureparm。
struct v4l2_captureparm { __u32 capability; //是否可以被timeperframe控制幀數。可以則:V4L2_CAP_TIMEPERFRAME __u32 capturemode; //是否爲高清模式。如果是:則設置爲:V4L2_MODE_HIGHQUALITY。 高清模式會犧牲其它信息。通常設置爲0。 struct v4l2_fract timeperframe; //幀數。 __u32 extendedmode; //定製的。如果不支持,設置爲0 __u32 readbuffers; __u32 reserved[4]; };
|
struct v4l2_fract timeperframe; 用來描述幀數。 struct v4l2_fract { __u32 numerator; // 分子。 例:1 __u32 denominator; //分母。 例:30 };
|
1) 得到Stream信息:
用戶只需要填充type爲V4L2_BUF_TYPE_VIDEO_CAPTURE。 Driver就會把結構體中其它部分填充好。
示例:
struct v4l2_streamparm Stream_Parm; memset(&Stream_Parm, 0, sizeof(struct v4l2_streamparm)); Stream_Parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; io_rel = ioctl(Handle, VIDIOC_G_PARM, &Stream_Parm);
|
2)設置採集幀數:
設置參數,用戶先填充好結構體,再使用命令設置。
示例:
struct v4l2_streamparm Stream_Parm; memset(&Stream_Parm, 0, sizeof(struct v4l2_streamparm)); Stream_Parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; Stream_Parm.parm.capture.timeperframe.denominator =Denominator;; Stream_Parm.parm.capture.timeperframe.numerator = Numerator; io_rel = ioctl(Handle, VIDIOC_S_PARM, &Stream_Parm); |
請注意,哪怕ioctl返回0。也有可能沒設置成功。所以需要再次Get。
當然,哪怕Get發現設置成功。真正抓幀也可能沒那麼高。
7. 查詢已經分配的V4L2的視頻緩衝區的相關信息
控制命令VIDIOC_QUERYBUF
功能: 查詢已經分配的V4L2的視頻緩衝區的相關信息,包括視頻緩衝區的使用狀態、在內核空間的偏移地址、緩衝區長度等。在應用程序設計中通過調VIDIOC_QUERYBUF來獲取內核空間的視頻緩衝區信息,然後調用函數mmap把內核空間地址映射到用戶空間,這樣應用程序才能夠訪問位於內核空間的視頻緩衝區。
參數說明:參數類型爲V4L2緩衝區數據結構類型 struct v4l2_buffer ;
返回值說明: 執行成功時,函數返回值爲 0;struct v4l2_buffer結構體變量中保存了指令的緩衝區的相關信息;
一般情況下,應用程序中調用VIDIOC_QUERYBUF取得了內核緩衝區信息後,緊接着調用mmap函數把內核空間地址映射到用戶空間,方便用戶空間應用程序的訪問。
結構體:
structv4l2_buffer 描述分配到的視頻採集緩衝信息(地址,大小,類型等)
struct v4l2_buffer { __u32 index; //buffer 序號 enum v4l2_buf_type type; //buffer 類型 __u32 byteused; //buffer 中已使用的字節數 __u32 flags; // 區分是MMAP 還是USERPTR enum v4l2_field field; struct timeval timestamp; // 獲取第一個字節時的系統時間 struct v4l2_timecode timecode; __u32 sequence; // 隊列中的序號 enum v4l2_memory memory; //IO 方式,被應用程序設置 union m { __u32 offset; // 緩衝幀地址,只對MMAP 有效 unsigned long userptr; }; __u32 length; // 緩衝幀長度 __u32 input; __u32 reserved; }; |
使用舉例:
/*自定義視頻緩衝區數據結構*/ struct buffer { void * start; size_t length;//buffer's length is different from cap_image_size }; /*定義視頻緩衝區指針*/ struct buffer * buffers = NULL;
/*視頻設備控制函數*/ static int xioctl(int fd, int request, void * arg) { int r; do r = ioctl(fd, request, arg); while (-1 == r && EINTR == errno); return r; } static void init_mmap(void) { struct v4l2_requestbuffers req; CLEAR (req); req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { if (EINVAL == errno) { fprintf(stderr, "%s does not support " "memory mapping\n", dev_name); exit(EXIT_FAILURE); } else { errno_exit("VIDIOC_REQBUFS"); } } if (req.count < 2) { fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); exit(EXIT_FAILURE); } buffers = calloc(req.count, sizeof(*buffers)); if (!buffers) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); }
//映射所有的緩存 for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { struct v4l2_buffer buf; CLEAR (buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = n_buffers;
//獲取到對應index的緩存信息,此處主要利用length信息及offset信息來完成後面的mmap操作。 if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) errno_exit("VIDIOC_QUERYBUF");
buffers[n_buffers].length = buf.length; // 轉換成相對地址 buffers[n_buffers].start = mmap(NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, fd, buf.m.offset); if (MAP_FAILED == buffers[n_buffers].start) errno_exit("mmap"); } } |
上述代碼在通過調用VIDIOC_QUERYBUF取得內核空間的緩衝區信息後,接着調用mmap函數把內核空間緩衝區映射到用戶空間;關於mmap函數的用法,請讀者查詢相關資料;
8. 投放一個空的視頻緩衝區到視頻緩衝區輸入隊列中
控制命令VIDIOC_QBUF
功能: 投放一個空的視頻緩衝區到視頻緩衝區輸入隊列中;
參數說明:參數類型爲V4L2緩衝區數據結構類型 structv4l2_buffer ;
返回值說明: 執行成功時,函數返回值爲 0;函數執行成功後,指令的視頻緩衝區進入視頻輸入隊列,在啓動視頻設備拍攝圖像時,相應的視頻數據被保存到視頻輸入隊列相應的視頻緩衝區中。
使用舉例:
/*把申請到的視頻緩衝區全部加入到數據採集緩衝隊列中*/ for (i = 0; i < n_buffers; ++i) { struct v4l2_buffer buf; CLEAR (buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i;//指定要投放到視頻輸入隊列中的內核空間視頻緩衝區的編號; if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); } |
9. 啓動視頻採集命令
控制命令VIDIOC_STREAMON
功能: 啓動視頻採集命令,應用程序調用VIDIOC_STREAMON啓動視頻採集命令後,視頻設備驅動程序開始採集視頻數據,並把採集到的視頻數據保存到視頻驅動的視頻緩衝區中。
參數說明:參數類型爲V4L2的視頻緩衝區類型 enumv4l2_buf_type ;
返回值說明: 執行成功時,函數返回值爲 0;函數執行成功後,視頻設備驅動程序開始採集視頻數據,此時應用程序一般通過調用select函數來判斷一幀視頻數據是否採集完成,當視頻設備驅動完成 一幀視頻數據採集並保存到視頻緩衝區中時,select函數返回,應用程序接着可以讀取視頻數據;否則select函數阻塞直到視頻數據採集完成。 Select函數的使用請讀者參考相關資料。
使用舉例:
enum v4l2_buf_type type; /*啓動視頻採集功能*/ type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)) errno_exit("VIDIOC_STREAMON"); |
10. 從視頻緩衝區取出數據。
控制命令VIDIOC_DQBUF
功能: 從視頻緩衝區的輸出隊列中取得一個已經保存有一幀視頻數據的視頻緩衝區;
參數說明:參數類型爲V4L2緩衝區數據結構類型 structv4l2_buffer ;
返回值說明: 執行成功時,函數返回值爲 0;函數執行成功後,相應的內核視頻緩衝區中保存有當前拍攝到的視頻數據,應用程序可以通過訪問用戶空間來讀取該視頻數據。(前面已經通過調用函數mmap做了用戶空間和內核空間的內存映射).
使用舉例:
struct v4l2_buffer buf; unsigned int i; CLEAR (buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP;
//取出FIFO緩存中已經採樣的幀緩存,根據返回的buf.index找到對應的mmap映射好的緩存,取出視頻數據。 if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { switch (errno) { case EAGAIN: return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: errno_exit("VIDIOC_DQBUF"); } } |
說明: VIDIOC_DQBUF命令結果, 使從隊列刪除的緩衝幀信息傳給了此buf。
V4L2_buffer結構體的作用就相當於申請的緩衝幀的代理,找緩衝幀的都要先問問它,通過它來聯繫緩衝幀,起了中間橋樑的作用。
11. 把剛取出的緩衝區重新投放到視頻緩衝區輸入隊列中
控制命令VIDIOC_QBUF
請參見前面第7步
/*將剛剛處理完的緩衝重新入隊列尾,這樣可以循環採集*/ if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); |
12. 停止視頻採集
控制命令VIDIOC_STREAMOFF
功能: 停止視頻採集命令,應用程序調用VIDIOC_ STREAMOFF停止視頻採集命令後,視頻設備驅動程序不在採集視頻數據。
參數說明:參數類型爲V4L2的視頻緩衝區類型 enumv4l2_buf_type ;
返回值說明: 執行成功時,函數返回值爲 0;函數執行成功後,視頻設備停止採集視頻數據。
使用舉例:
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &type))/*停止視頻採集*/ errno_exit("VIDIOC_STREAMOFF"); |
通過使用以上控制流程控制命令,可以完成一幅視頻數據的採集過程。
13. 獲取視頻設備支持的制式
控制命令VIDIOC_QUERYSTD
功能:在亞洲,一般使用PAL(720X576)制式的攝像頭,而歐洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD來檢測:
參數說明:
返回值說明:
typedef__u64 v4l2_std_id;
使用舉例:
v4l2_std_id std; do{ ret= ioctl(fd, VIDIOC_QUERYSTD, &std); } while(ret== -1 && errno== EAGAIN); switch(std) { caseV4L2_STD_NTSC: //…… caseV4L2_STD_PAL: //…… } |
這個命令不是必須的。可以不定。