linux下通過V4L2驅動USB攝像頭
目錄
前言
在移植羅技C270攝像頭到6818的過程中,核心已經檢測到了USB攝像頭,但是直接用OpenCV的API(比如CvCapture*cvCaptureFromCAM(int index)
介面,無法開啟USB攝像頭,至少目前我是這麼認為的。然後,網上搜索答案說是要使用V4l2
進行操作。沒有別的辦法!只有一邊學一邊試試看行不行嘍!
感謝超群天晴大神的(原創)基於ZedBoard的Webcam設計(一):USB攝像頭(V4L2介面)的圖片採集這篇博文中提供的原始碼,我直接移植後,在6818是成功獲取bmp圖片和yuv圖片。雖然在Ubuntu中開啟bmp圖片和yuv圖片是錯誤的!!!
學習!分享!感謝!
v4l2
解析
v4l2
介紹
v4l2
是linux作業系統下用於採集圖片、視訊和音訊資料的API介面,配合適當的視訊採集裝置和相應的驅動程式,可以實現圖片、視訊、音訊等的採集。在遠端會議、可視電話、視訊監控系統和嵌入式多媒體終端中都有廣泛應用。
在linux下,所有外設都被看成一種特殊的檔案,稱為”裝置檔案”,可以像訪問普通檔案一樣對裝置檔案進行訪問。
V4L2支援兩種方式來採集影象:記憶體對映(mmap)和直接讀取方式(read)。V4L2在include/linux/video.h
檔案下定義了一些重要的資料結構,在採集影象的過程中,就是通過對這些資料的操作來獲得最終的影象資料。Linux系統V4L2使能可在核心編譯階段配置,預設情況下是在make menuconfig
是開啟的。
linux下視訊捕捉具體的linux呼叫參見下圖:
應用程式可以通過
V4L2
進行視訊採集。V4L2
支援記憶體對映(mmap)方式和直接讀取方式(read)方式採集資料。前者一般用於連續的視訊資料採集,後者常用靜態圖片資料採集。
v4l2
中不僅定義了通用API元素,影象的格式,輸入/輸出方法,還定義了Linux核心驅動處理視訊資訊的一系列介面,這些介面主要有:
視訊採集介面——Video Capture interface
視訊輸出介面 ——Video Output Interface;
視訊覆蓋/預覽介面——Video Overlay Interface;
視訊輸出覆蓋介面—— Video Output Overlay Interface;
編解碼介面——Codec Interface
應用程式通過V4L2
介面採集視訊資料步驟
- 開啟視訊裝置檔案,通過視訊採集的引數初始化,通過
V4L2
介面設定視訊影象屬性。 - 申請若干視訊採集的幀快取區,並將這些幀緩衝區從核心空間對映到使用者空間,便於應用程式讀取/處理視訊資料。
- 將申請到的幀緩衝區在視訊採集輸入佇列排隊,並啟動視訊採集。
- 驅動開始視訊資料的採集,應用程式從視訊採集輸出佇列中取出幀緩衝區,處理後,將幀緩衝區重新放入視訊採集輸入佇列,迴圈往復採集連續的視訊資料。
停止視訊採集。
具體實現過程如下圖:
整理下其中ioctl控制符:
VIDIOC_QUERYCAP 查詢裝置的屬性
VIDIOC_ENUM_FMT 幀格式
VIDIOC_S_FMT 設定視訊幀格式,對應struct v4l2_format
VIDIOC_G_FMT 獲取視訊幀格式等
VIDIOC_REQBUFS 請求/申請若干個幀緩衝區,一般為不少於3個
VIDIOC_QUERYBUF 查詢幀緩衝區在核心空間的長度和偏移量
VIDIOC_QBUF 將申請到的幀緩衝區全部放入視訊採集輸出佇列
VIDIOC_STREAMON 開始視訊流資料的採集
VIDIOC_DQBUF 應用程式從視訊採集輸出佇列中取出已含有采集資料的幀緩衝區
VIDIOC_STREAMOFF 應用程式將該幀緩衝區重新掛入輸入佇列
其實,圖解過程已經很詳細了,但是比較笨,而且圖片也比較容易眼花,重新總結下。整個過程:
首先:先啟動視訊採集,驅動程式開始採集一幀資料,把採集的資料放入視訊採集輸入佇列的第一個幀緩衝區,一幀資料採集完成,也就是第一個幀緩衝區存滿一幀資料後,驅動程式將該幀緩衝區移至視訊採集輸出佇列,等待應用程式從輸出佇列取出。驅動程式則繼續採集下一幀資料放入第二個緩衝區,同樣幀緩衝區存滿下一幀資料後,被放入視訊採集輸出佇列。
然後:應用程式從視訊採集輸出佇列中取出含有視訊資料的幀緩衝區,處理幀緩衝區中的視訊資料,如儲存或壓縮。
最後:應用程式將處理完資料的幀緩衝區重新放入視訊採集輸入佇列,這樣可以迴圈採集。
看到這裡,摘錄一段Hi3519的開發文件中《系統控制》部分的介紹:
視訊快取池主要向媒體業務提供大塊實體記憶體管理功能,負責記憶體的分配和回收,充分發揮記憶體快取池的作用,讓實體記憶體資源在各個媒體處理模組中合理使用。
一組大小相同、實體地址連續的快取塊組成一個視訊快取池。
視訊輸入通道需要使用公共視訊快取池。所有的視訊輸入通道都可以從公共視訊快取池中獲取視訊快取塊用於儲存採集的影象。由於視訊輸入通道不提供建立愛你和銷燬公共視訊快取池功能。因此,在系統初始化之前,必須為視訊輸入通道配置公共快取池。根據業務的不同,公共快取池的數量、快取塊的大小和數量不同。快取塊的生存期是指經過VPSS通道傳給後續模組的情形。如果該快取塊完全沒有經過VPSS通道傳給其他模組,則將在VPSS模組處理後被放回公共快取池。
所以,我們從攝像頭中獲取的視訊幀資料會放入視訊快取佇列中,當其他模組需要處理對應的視訊幀的時候,就會佔用快取塊,也就是這一塊記憶體被佔用,當處理完之後,對應的資料通過VO/VENC/VDA顯示之後,這一快取塊就沒有用了,可以回收利用。現在來看,其實海思的底層處理和linux的底層處理是一樣的。不過海思本身使用的就是linux核心。應該也就是對這一塊進行封裝了而已吧!
從這張圖可以看出,海思的公共視訊快取池按我的理解應該有兩部分,一部分是視訊採集輸入佇列,另一部分是視訊採集輸出佇列,VI通道是是視訊採集輸出佇列中獲取的視訊幀,而中間linux核心的驅動程式會在視訊採集輸入佇列中填充視訊幀,變成視訊輸出佇列。
每一個幀緩衝區都有一個對應的狀態標誌變數,其中每一個位元代表一個狀態:
V4L2_BUF_FLAG_UNMAPPED 0B0000
V4L2_BUF_FLAG_MAPPED 0B0001
V4L2_BUF_FLAG_ENQUEUED 0B0010
V4L2_BUF_FLAG_DONE 0B0100
緩衝區的狀態轉化如圖:
在申請了緩衝區(VIDIOC_REQBUFS
)後標誌為0000,但是這時候還沒有對映到應用層,所以在Mmap之後,緩衝區的表示為0001。
應用程式(VIDIOC_DQBUF
)從視訊採集輸出佇列中取出已含有采集資料的幀緩衝區,這時候(V4L2_BUF_FLAG_DONE|V4L2_BUF_FLAG_MAPPED
),所以緩衝區標誌為0101.
VIDIOC_QBUF
將申請到的幀緩衝區全部放入視訊採集輸出佇列:V4L2_BUF_FLAG_ENQUEUED|V4L2_BUF_FLAG_MAPPED
相關結構體解析
VIDIOC_QUERYCAP
——–>struct v4l2_capability
struct v4l2_capability {
__u8 driver[16]; // 驅動模組的名字
__u8 card[32]; // 裝置名字
__u8 bus_info[32]; // 匯流排資訊
__u32 version; // 核心版本
__u32 capabilities; // 整個物理裝置支援的功能
__u32 device_caps; // 通過這個特定裝置訪問的功能
__u32 reserved[3];
};
如下是我的羅技C270攝像頭的通過VIDIOC_QUERYCAP
獲取的裝置功能
driver: uvcvideo
card: UVC Camera (046d:0825)
bus_info: usb-nxp-ehci-1.3
version: 197671
capabilities: 4000001
Device /dev/video9: supports capture.
Device /dev/video9: supports streaming.
其中capabilities: 4000001
通過與各種巨集位與,可以獲得物理裝置的功能屬性。比如:
//#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */ // 是否支援視訊捕獲
if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE)
{
printf("Device %s: supports capture.\n", FILE_VIDEO);
}
//#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */ // 是否支援輸入輸出流控制
if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING)
{
printf("Device %s: supports streaming.\n", FILE_VIDEO);
}
VIDIOC_ENUM_FMT
——–>struct v4l2_fmtdesc
/*
* F O R M A T E N U M E R A T I O N
*/
struct v4l2_fmtdesc {
__u32 index; /* Format number */
__u32 type; /* enum v4l2_buf_type */
__u32 flags;
__u8 description[32]; /* Description string */
__u32 pixelformat; /* Format fourcc */
__u32 reserved[4];
};
通過這個結構體,可以顯示對應的攝像頭所支援視訊幀格式。
struct v4l2_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++;
}
我的攝像頭輸出如下:
Support format:
1.YUV 4:2:2 (YUYV)
2.MJPEG
所以,我要讀取視訊幀的時候就要使用YUV422
這種格式。
3. VIDIOC_S_FMT&VIDIOC_G_FMT
——–>struct v4l2_format
檢視或設定視訊幀格式
struct v4l2_format {
__u32 type; // 幀型別
union {
/* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format pix; //畫素格式
/* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_pix_format_mplane pix_mp;
/* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_window win;
/* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_vbi_format vbi;
/* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced;
/* V4L2_BUF_TYPE_SDR_CAPTURE */
struct v4l2_sdr_format sdr;
/* user-defined */
__u8 raw_data[200];
} fmt;
};
/*
* V I D E O I M A G E F O R M A T
*/
struct v4l2_pix_format {
__u32 width; // 畫素高度
__u32 height; // 畫素寬度
__u32 pixelformat; // 畫素格式
__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 */
};
仔細看,發現這些其實和海思中的一些結構體非常類似,linux真的很強大!
4. VIDIOC_CROPCAP
——–>struct v4l2_cropcap
5. VIDIOC_G_PARM&VIDIOC_S_PARM
——–>struct v4l2_streamparm
設定Stream
資訊,主要設定幀率。
struct v4l2_streamparm {
__u32 type; /* enum v4l2_buf_type */
union {
struct v4l2_captureparm capture;
struct v4l2_outputparm output;
__u8 raw_data[200]; /* user-defined */
} parm;
};
struct v4l2_captureparm {
/* Supported modes */
__u32 capability;
/* Current mode */
__u32 capturemode;
/* Time per frame in seconds */
struct v4l2_fract timeperframe;
/* Driver-specific extensions */
__u32 extendedmode;
/* # of buffers for read */
__u32 readbuffers;
__u32 reserved[4];
};
// timeperframe
// numerator和denominator可描述為每numerator秒有denominator幀
struct v4l2_fract {
__u32 numerator; // 分子
__u32 denominator; // 分母
};
VIDIOC_REQBUFS
——–>struct v4l2_requestbuffers
申請和管理緩衝區,應用程式和裝置有三種交換資料方法,直接read/write(裸機)、記憶體對映(系統),使用者指標(…)。一般在作業系統管理下,都是使用記憶體對映的方式。
/*
* 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; // 緩衝區內緩衝幀的數目
__u32 type; // 緩衝幀資料格式
__u32 memory; //
__u32 reserved[2];
};
enum v4l2_memory {
V4L2_MEMORY_MMAP = 1, // 記憶體對映
V4L2_MEMORY_USERPTR = 2, // 使用者指標
V4L2_MEMORY_OVERLAY = 3,
V4L2_MEMORY_DMABUF = 4,
};
VIDIOC_QUERYBUF
——–>struct v4l2_buffer
struct v4l2_buffer {
__u32 index; // buffer的id
__u32 type; // enum v4l2_buf_type
__u32 bytesused; // buf中已經使用的位元組數
__u32 flags; // MMAP 或 USERPTR
__u32 field;
struct timeval timestamp; // 幀時間戳
struct v4l2_timecode timecode;
__u32 sequence; // 佇列中的序號
/* memory location */
__u32 memory;
union {
__u32 offset; // 裝置記憶體起始offset
unsigned long userptr; // 指向使用者空間的指標
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length; // 快取幀長度
__u32 reserved2;
__u32 reserved;
};
VIDIOC_DQBUF
應用程式從視訊採集輸出佇列中取出已含有采集資料的幀緩衝區VIDIOC_STREAMON&VIDIOC_STREAMOFF
開始視訊採集和關閉視訊採集VIDIOC_QBUF
應用程式將該幀緩衝區重新掛入輸入佇列
總結
使用C語言高階應用—操作linux下V4L2攝像頭應用程式原始碼成功的在我的開發板上顯示出了800*600的視訊影象。我的開發板的顯示屏是1024*600的,當我設定為1024*600時,實際顯示為1024*576,感覺很奇怪!而且顯示的視訊是反的,都是小問題。總之,顯示視訊的那一刻真的好開心!