【音视频】V4L2摄像头应用编程
文章目錄
- 1 V4L2
- 1.1 V4L2特點
- 1.2 V4L2設備
- 2 V4L2設備訪問接口
- 2.1設備訪問
- 2.1.1 查詢設備屬性
- 2.1.2 查詢設備輸出格式
- 2.1.3 設置幀輸出格式
- 2.1.4 申請幀緩存
- 2.1.5 內核內存轉換
- 2.1.6 緩沖幀內存入隊操作
- 2.1.7 啟動視頻流采集
- 2.1.8 讀取數據幀內存序號
- 2.1.9 關閉視頻流采集
- 2.2 視頻流讀取
- 3 V4L2應用開發流程
- 4 參考文章
1 V4L2
??V4L2全稱是Video for Linux two(Video4Linux2),是V4L2改進衍生版本。V4L2是linux操作系統下的一個標注化的音頻、視頻設備驅動框架,向下屏蔽底層設備的差異,向上提供標準統一的訪問接口,提高用戶在音視頻方面的應用開發效率。只要底層音視頻設備(如攝像頭)兼容V4L2標準,則應用程序可以無縫接入該音視頻設備。本文主要描述V4L2視頻設備(攝像頭)的應用。
1.1 V4L2特點
??屏蔽底層差異,向上提供標準的用戶訪問接口,更換支持V4L2標準的物理音視頻設備,原則上應用程序可以完全兼容。V4L2對用戶提供的接口包括:
-
視頻采集接口
-
視頻輸出接口
-
視頻覆蓋/預覽接口
-
視頻輸出覆蓋接口
-
編解碼接口
1.2 V4L2設備
??linux思想是一切皆文件。V4L2設備接入并且驅動被加載成功時在"/dev"生成設備文件,名稱為"videoX"(/dev/videoX),X為設備設備序號;一般按接入的設備順序排序,只有一個設備時為video0。V4L2應用程序通過調用linux標準的設備文件系統接口,訪問音視頻設備。
2 V4L2設備訪問接口
??V4L2音視頻設備可通過標準文件系統接口open/read/ioctl/close訪問,設備訪問分為兩大部分,分別是設備訪問和視頻流獲取。關于V4L2的接口聲明、宏定義、枚舉類型可以在“include/uapi/linux/videodev2.h”頭文件中查閱。
2.1設備訪問
??設備訪問包括獲取和設置設備信息,如設備驅動信息、設備屬性、圖像幀格式、控制圖像捕獲等。這些都通過ioctl來實現訪問,訪問格式如下。
ioctl(fd, cmd, param); /* 文件描述符, 命令字, 參數信息 */??常用命令字:
VIDIOC_QUERYCAP /* 查詢設備屬性 */ VIDIOC_ENUM_FMT /* 查詢設備支持的輸出格式*/ VIDIOC_G_FMT /* 查詢設備輸出幀格式 */ VIDIOC_S_FMT /* 設置設備輸出幀格式 */ VIDIOC_REQBUFS /* 申請幀緩存 */ VIDIOC_QUERYBUF /* 獲取申請的幀緩存 */ VIDIOC_QBUF /* 將幀緩存加入視頻流采集隊列 */ VIDIOC_DQBUF /* 獲取已采集視頻流的緩存幀 */ VIDIOC_STREAMON /* 開啟視頻流采集 */ VIDIOC_STREAMOFF /* 關閉視頻流采集 */2.1.1 查詢設備屬性
函數原型:
int ioctl(int fd, int cmd, struct v4l2_capability *cap);-
cmd,命令字:VIDIOC_QUERYCAP
-
cap,設備屬性數據結構:
【1】capabilities,視頻設備支持的操作,以每一位表示,常用類型宏位于“/uapi/linux/videodev2.h”定義。
/* Values for 'capabilities' field */ #define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */ #define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* Is a video output device */ #define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* Can do video overlay */ #define V4L2_CAP_VBI_CAPTURE 0x00000010 /* Is a raw VBI capture device */ #define V4L2_CAP_VBI_OUTPUT 0x00000020 /* Is a raw VBI output device */ #define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 /* Is a sliced VBI capture device */ #define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 /* Is a sliced VBI output device */ #define V4L2_CAP_RDS_CAPTURE 0x00000100 /* RDS data capture */ #define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 /* Can do video output overlay */ #define V4L2_CAP_HW_FREQ_SEEK 0x00000400 /* Can do hardware frequency seek */ #define V4L2_CAP_RDS_OUTPUT 0x00000800 /* Is an RDS encoder */查詢設備屬性偽代碼:
struct v4l2_capability cap = {0};ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);2.1.2 查詢設備輸出格式
函數原型:
int ioctl(int fd, int cmd, struct v4l2_fmtdesc *fmt);-
cmd,命令字:VIDIOC_ENUM_FMT
-
fmt,設備輸出格式數據結構:
【1】index,查詢序號,從0開始查詢
【2】type,設備類型,實際類型為enum v4l2_buf_type,位于“/uapi/linux/videodev2.h”定義;如果是camera設備,則設置為V4L2_BUF_TYPE_VIDEO_CAPTURE
【3】flags,一般不用
循環獲取設備輸出格式偽代碼:
struct v4l2_fmtdesc fmtdesc = {0}; fmtdesc.index = 0 ; fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; printf("the video device support format:\n"); while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) { printf("%d.%s\n", fmtdesc.index+1, fmtdesc.description);fmtdesc.index++; }2.1.3 設置幀輸出格式
??圖像幀輸出格式基本參數包括:
- 像素寬度
- 像素高度
- 數據類型
函數原型:
int ioctl(int fd, int cmd, struct v4l2_format *format);-
cmd,命令字:VIDIOC_S_FMT
-
format,設置輸出格式數據結構:
【1】type,設備類型,實際類型為enum v4l2_buf_type,如果是camera設備,則設置為V4L2_BUF_TYPE_VIDEO_CAPTURE
【2】fmt,一個共用體,不同設備的具體配置參數,以camera為例,其數據結構struct v4l2_pix_format如下
struct v4l2_pix_format {__u32 width; /* 像素寬度 */__u32 height; /* 像素高度 */__u32 pixelformat;/* 輸出格式,根據camera支持的格式選擇,JPG或YUV */__u32 field; /* enum v4l2_field */__u32 bytesperline; /* for padding, zero if unused */__u32 sizeimage;__u32 colorspace; /* enum v4l2_colorspace */__u32 priv; /* private data, depends on pixelformat */__u32 flags; /* format flags (V4L2_PIX_FMT_FLAG_*) */__u32 ycbcr_enc; /* enum v4l2_ycbcr_encoding */__u32 quantization; /* enum v4l2_quantization */__u32 xfer_func; /* enum v4l2_xfer_func */ };2.1.4 申請幀緩存
函數原型:
int ioctl(int fd, int cmd, struct v4l2_requestbuffers *reqbuf);-
cmd,命令字:VIDIOC_REQBUFS
-
reqbuf,申請內存數據結構:
【1】count,申請內存塊數目,至少為1
【2】type,設備類型,實際類型為enum v4l2_buf_type,如果是camera設備,則設置為V4L2_BUF_TYPE_VIDEO_CAPTURE
【3】memory,內存用途,實際類型為enum v4l2_memory,一般用作內存映射V4L2_MEMORY_MMAP
enum v4l2_memory {V4L2_MEMORY_MMAP = 1, /* 內存映射 */V4L2_MEMORY_USERPTR = 2, /* 用戶指針 */V4L2_MEMORY_OVERLAY = 3, /* 內存覆蓋 */V4L2_MEMORY_DMABUF = 4, /* DMA映射 */ };申請幀緩存偽代碼:
struct v4l2_requestbuffers req_buf = {0};req_buf.count = 1; req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req_buf.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd, VIDIOC_REQBUFS, &req_buf);2.1.5 內核內存轉換
??通VIDIOC_S_FMT命令字申請的內核態內存,還需轉換為物理內存,用于映射到用戶態,這樣用戶能直接從物理內存中獲取視頻流數據,提高效率。
函數原型:
int ioctl(int fd, int cmd, struct v4l2_buffer *buf);-
cmd,命令字:VIDIOC_QUERYBUF
-
buf,返回內存數據結構:
應用偽代碼:
struct v4l2_requestbuffers req_buf = {0}; struct v4l2_buffer buf = {0};req_buf.count = 1; req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req_buf.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd, VIDIOC_REQBUFS, &req_buf);for(i=0; i<req_buf.count; i++) {buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = i;ret = ioctl(pdevice->fd, VIDIOC_QUERYBUF, &buf);pdevice->mmap_buf[i].size = buf.length;pdevice->mmap_buf[i].addr = (char *)mmap(NULL, buf.length, PROT_READ|PROT_WRITE,MAP_PRIVATE, pdevice->fd, buf.m.offset);if(MAP_FAILED == pdevice->mmap_buf[i].addr){perror("mmap failed");} }??關于內存映射mmap使用,參考文章mmap內存映射。
2.1.6 緩沖幀內存入隊操作
??該操作是將緩沖內存加入V4L2驅動的采集隊列中,視頻設備采集完成,數據存于該內存中。
函數原型:
int ioctl(int fd, int cmd, struct v4l2_buffer *buf);- cmd,命令字:VIDIOC_QBUF
- buf,幀緩存數據結構
應用代碼:
int set_video_device_stream_queue(struct _v4l2_video *pdevice, int index) {int ret = 0;struct v4l2_buffer buf = {0};/* 將內核緩存放入隊列 */buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = index;ret = ioctl(pdevice->fd, VIDIOC_QBUF, &buf);if(ret < 0){perror("ioctl call \'VIDIOC_QBUF\' failed");return -1;} }2.1.7 啟動視頻流采集
??執行該命令,視頻設備執行數據采集,采集完成,將視頻流數據存于指定的內存空間。
函數原型:
int ioctl(int fd, int cmd, enum v4l2_buf_type *type);- cmd,命令字:VIDIOC_STREAMON
- type,設備(緩存幀)類型,camera設備為V4L2_BUF_TYPE_VIDEO_CAPTURE
2.1.8 讀取數據幀內存序號
??實質上,執行“開啟采集”命令成功后,視頻流數據會存于預先申請的物理內存空間。然而,如果申請了多個數量的幀緩存,此時需知道視頻流存于哪個幀緩存中,用戶根據序號訪問內存塊。通過該命令可以獲取存放視頻流數據幀內存序號。
函數原型:
int ioctl(int fd, int cmd, struct v4l2_buffer *buf);- cmd,命令字:VIDIOC_DQBUF
- buf,幀緩存數據結構
應用代碼:
int read_video_device_stream_frame(struct _v4l2_video *pdevice, int *out_buf_index) {int ret = 0;int i;struct v4l2_buffer buf = {0};/* 從隊列取出數據 */buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;ret = ioctl(pdevice->fd, VIDIOC_DQBUF, &buf);if(ret < 0){perror("ioctl call \'VIDIOC_DQBUF\' failed");return -1;}if (buf.index > pdevice->mmap_buf_cnt){printf("buf overflow[%d]\n", buf.index);}*out_buf_index = buf.index;return 0; }注:
即使只申請一個幀緩存空間,也需要執行該命令,以確定數據是否正確采集完成,否則內存中數據是未知狀態。
2.1.9 關閉視頻流采集
??結束視頻流采集后,調用VIDIOC_STREAMOFF關閉視頻流采集。
函數原型:
int ioctl(int fd, int cmd, enum v4l2_buf_type *type);- cmd,命令字:VIDIOC_STREAMOFF
- type,設備(緩存幀)類型,camera設備為V4L2_BUF_TYPE_VIDEO_CAPTURE
2.2 視頻流讀取
??視頻流讀取有兩種方式。
- 通過標準文件系統接口read讀取
- 將V4L2設備內核態映射(mmap)到用戶態,直接從物理內存獲取視頻流
- 用戶指針模式,內存由用戶分配,與內存映射類似,使用較少
??通過read函數讀取視頻流,需經過物理內存到內核態、內核態到用戶態兩個內存拷貝過程,效率比較低,一般用于靜態圖像的采集獲取,一般比較少使用。內存映射是常用的方式, 省去兩個拷貝過程,效率高。
3 V4L2應用開發流程
??應用程序訪問一個V4L2設備的的總體流程如下圖。
關鍵步驟分析:
-
查詢設備屬性,包驅動信息、支持視頻流格式,以方便后續設置視頻輸出屬性
-
設置設備屬性,根據獲取的設備屬性設置,主要是設置視頻流輸出格式,如制式、寬度、高度、編碼方式
-
幀緩存申請,用于存放驅動采集的視頻流,并映射到用戶態
-
幀緩存加入采集隊列,如果需循環采集視頻流,每次獲取視頻流后都需執行將幀緩存加入采集隊列
-
結束過程,包括關閉視頻流采集、釋放內存映射、關閉設備描述符
示例:
- 獲取攝像頭信息
- 獲取視頻流,保存為圖片信息
編譯測試
- 系統:Ubuntu16
- 攝像頭:筆記本自帶攝像頭
??從執行結果來看,此筆記本自帶的攝像頭是USB接口的,支持YUV和JPEG格式輸出。程序執行后在當前目錄生成5張.jpg格式的圖片文件。
acuity@ubuntu:/mnt/hgfs/LSW/STHB/camera$ tree . ├── image0.jpg ├── image1.jpg ├── image2.jpg ├── image3.jpg ├── image4.jpg ├── v4l2_base └── v4l2_base.c4 參考文章
【1】linux v4l2攝像頭應用層編程介紹
【2】和菜鳥一起學linux之V4L2攝像頭應用流程
總結
以上是生活随笔為你收集整理的【音视频】V4L2摄像头应用编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java自定义窗口,java 自定义窗口
- 下一篇: IEEE 802.3标准就是ISO 80