V4L2 - Linux下視頻驅動模型

在DM6446平臺,我們在GPP端一般使用MontaVista操作系統來進行程序控制。MontaVista作爲一種嵌入式的Linux,和桌 面Linux類似,同樣使用視頻驅動V4L2(Video For Linux Two)來進行視頻採集、輸出。本文就V4L2的使用方式做簡易說明。

視頻採集的基本流程

一般的,視頻採集都有如下流程:

image

打開視頻設備

在V4L2中,視頻設備被看做一個文件。使用open函數打開這個設備:

// 用非阻塞模式打開攝像頭設備
int cameraFd ;
cameraFd = open ("/dev/video0" , O_RDWR | O_NONBLOCK , 0);
// 如果用阻塞模式打開攝像頭設備,上述代碼變爲:
//cameraFd = open("/dev/video0", O_RDWR, 0);

關於阻塞模式和非阻塞模式

應用程序能夠使用阻塞模式或非阻塞模式打開視頻設備,如果使用非阻塞模式調用視頻設備,即使尚未捕獲到信息,驅動依舊會把緩存(DQBUFF)裏的東西返回給應用程序。

設定屬性及採集方式

打開視頻設備後,可以設置該視頻設備的屬性,例如裁剪、縮放等。這一步是可選的。在Linux編程中,一般使用ioctl函數來對設備的I/O通道進行管理:

extern int 
ioctl 
(int 
__fd
, unsigned long int 
__request
, ...) __THROW
;

__fd:設備的ID,例如剛纔用open函數打開視頻通道後返回的cameraFd;

__request:具體的命令標誌符。

在進行V4L2開發中,一般會用到以下的命令標誌符:

  1. VIDIOC_REQBUFS:分配內存
  2. VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的數據緩存轉換成物理地址
  3. VIDIOC_QUERYCAP:查詢驅動功能
  4. VIDIOC_ENUM_FMT:獲取當前驅動支持的視頻格式
  5. VIDIOC_S_FMT:設置當前驅動的頻捕獲格式
  6. VIDIOC_G_FMT:讀取當前驅動的頻捕獲格式
  7. VIDIOC_TRY_FMT:驗證當前驅動的顯示格式
  8. VIDIOC_CROPCAP:查詢驅動的修剪能力
  9. VIDIOC_S_CROP:設置視頻信號的邊框
  10. VIDIOC_G_CROP:讀取視頻信號的邊框
  11. VIDIOC_QBUF:把數據從緩存中讀取出來
  12. VIDIOC_DQBUF:把數據放回緩存隊列
  13. VIDIOC_STREAMON:開始視頻顯示函數
  14. VIDIOC_STREAMOFF:結束視頻顯示函數
  15. VIDIOC_QUERYSTD:檢查當前視頻設備支持的標準,例如PAL或NTSC。

這些IO調用,有些是必須的,有些是可選擇的。

檢查當前視頻設備支持的標準

在亞洲,一般使用PAL(720X576)制式的攝像頭,而歐洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD來檢測:

v4l2_std_id std
;
do {
ret = ioctl (fd , VIDIOC_QUERYSTD , &std );
} while (ret == -1 && errno == EAGAIN );
switch (std ) {
case V4L2_STD_NTSC :
//……
case V4L2_STD_PAL :
//……
}

設置視頻捕獲格式

當檢測完視頻設備支持的標準後,還需要設定視頻捕獲格式:

struct 
v4l2_format    fmt
;
memset ( &fmt , 0, sizeof (fmt ) );
fmt .type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
fmt .fmt .pix .width = 720 ;
fmt .fmt .pix .height = 576 ;
fmt .fmt .pix .pixelformat = V4L2_PIX_FMT_YUYV ;
fmt .fmt .pix .field = V4L2_FIELD_INTERLACED ;
if (ioctl (fd , VIDIOC_S_FMT , &fmt ) == -1) {
return -1;
}

v4l2_format結構體定義如下:

struct 
v4l2_format
{
enum v4l2_buf_type type ; // 數據流類型,必須永遠是V4L2_BUF_TYPE_VIDEO_CAPTURE
union
{
struct v4l2_pix_format pix ;
struct v4l2_window win ;
struct v4l2_vbi_format vbi ;
__u8 raw_data [200];
} fmt ;
};
struct v4l2_pix_format
{
__u32 width ; // 寬,必須是16的倍數
__u32 height ; // 高,必須是16的倍數
__u32 pixelformat ; // 視頻數據存儲類型,例如是YUV4:2:2還是RGB
enum v4l2_field field ;
__u32 bytesperline ;
__u32 sizeimage ;
enum v4l2_colorspace colorspace ;
__u32 priv ;
};

分配內存

接下來可以爲視頻捕獲分配內存:

struct 
v4l2_requestbuffers  req
;
if (ioctl (fd , VIDIOC_REQBUFS , &req ) == -1) {
return -1;
}

v4l2_requestbuffers定義如下:

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

獲取並記錄緩存的物理空間

使用VIDIOC_REQBUFS ,我們獲取了req.count個緩存,下一步通過調用VIDIOC_QUERYBUF命令來獲取這些緩存的地址,然後使用mmap函數轉換成應用程序中的絕對地址,最後把這段緩存放入緩存隊列:

image

typedef struct 
VideoBuffer 
{
void *start ;
size_t length ;
} VideoBuffer ;

VideoBuffer * buffers = calloc ( req .count , sizeof (*buffers ) );
struct v4l2_buffer buf ;

for (numBufs = 0; numBufs < req .count ; numBufs ++) {
memset ( &buf , 0, sizeof (buf ) );
buf .type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
buf .memory = V4L2_MEMORY_MMAP ;
buf .index = numBufs ;
// 讀取緩存
if (ioctl (fd , VIDIOC_QUERYBUF , &buf ) == -1) {
return -1;
}

buffers [numBufs ].length = buf .length ;
// 轉換成相對地址
buffers [numBufs ].start = mmap (NULL , buf .length ,
PROT_READ | PROT_WRITE ,
MAP_SHARED ,
fd , buf .m .offset );

if (buffers [numBufs ].start == MAP_FAILED ) {
return -1;
}

// 放入緩存隊列
if (ioctl (fd , VIDIOC_QBUF , &buf ) == -1) {
return -1;
}
}

關於視頻採集方式

操作系統一般把系統使用的內存劃分成用戶空間和內核空間,分別由應用程序管理和操作系統管理。應用程序可以直接訪問內存的地址,而內核空間存放的是 供內核訪問的代碼和數據,用戶不能直接訪問。v4l2捕獲的數據,最初是存放在內核空間的,這意味着用戶不能直接訪問該段內存,必須通過某些手段來轉換地 址。

一共有三種視頻採集方式:使用read、write方式;內存映射方式和用戶指針模式。

read、write方式,在用戶空間和內核空間不斷拷貝數據,佔用了大量用戶內存空間,效率不高。

內存映射方式:把設備裏的內存映射到應用程序中的內存控件,直接處理設備內存,這是一種有效的方式。上面的mmap函數就是使用這種方式。

用戶指針模式:內存片段由應用程序自己分配。這點需要在v4l2_requestbuffers裏將memory字段設置成V4L2_MEMORY_USERPTR。

處理採集數據

V4L2有一個數據緩存,存放req.count數量的緩存數據。數據緩存採用FIFO的方式,當應用程序調用緩存數據時,緩存隊列將最先採集到的 視頻數據緩存送出,並重新採集一張視頻數據。這個過程需要用到兩個ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF:

struct 
v4l2_buffer buf
;
memset (&buf ,0,sizeof (buf ));
buf .type =V4L2_BUF_TYPE_VIDEO_CAPTURE ;
buf .memory =V4L2_MEMORY_MMAP ;
buf .index =0;
//讀取緩存

if (ioctl (cameraFd , VIDIOC_DQBUF , &buf ) == -1)
{
return -1;
}
//…………視頻處理算法
//重新放入緩存隊列
if (ioctl (cameraFd , VIDIOC_QBUF , &buf ) == -1) {

return -1;
}

關閉視頻設備

使用close函數關閉一個視頻設備

close
(cameraFd
)

還需要使用munmap方法。

小結和參考文獻

在MontaVista裏,使用V4L2驅動來操作視頻輸入、輸出。V4L2採用流水線的方式,打開視頻設備、設置格式、處理數據、關閉設備。具體操作和ioctl函數有關。

另外,要在MontaVista中使用V4L2驅動,還需要修改一下系統目錄。

進入MontaVista安裝目錄下的pro/devkit/lsp/ti-davinci_evm-arm_v5t_le/linux-2.6.10_mvl401/include目錄,建立一個名爲asm的快捷方式指向asm-arm文件夾:

image

進入asm-arm文件夾,建立一個快捷方式指向arch-davinci目錄:

image

 

參考文獻:LSP 1.20 DaVinci Linux V4L2 Display Driver User’s Guide

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章