V4L2驅動的移植與應用(二)

2、V4L2的應用 算法

    下面簡單介紹一下V4L2驅動的應用流程。編程

一、  視頻採集的基本流程緩存

通常的,視頻採集都有以下流程:app

 

 

二、  打開視頻設備ide

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

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

關於阻塞模式和非阻塞模式:應用程序可以使用阻塞模式或非阻塞模式打開視頻設備,若是使用非阻塞模式調用視頻設備,即便還沒有捕獲到信息,驅動依舊會把緩存(DQBUFF)裏的東西返回給應用程序。spa

三、  設定屬性及採集方式操作系統

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

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

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

__request:具體的命令標誌符。

在進行V4L2開發中,通常會用到如下的命令標誌符:

VIDIOC_REQBUFS:分配內存

VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的數據緩存轉換成物理地址

VIDIOC_QUERYCAP:查詢驅動功能

VIDIOC_ENUM_FMT:獲取當前驅動支持的視頻格式

VIDIOC_S_FMT:設置當前驅動的頻捕獲格式

VIDIOC_G_FMT:讀取當前驅動的頻捕獲格式

VIDIOC_TRY_FMT:驗證當前驅動的顯示格式

VIDIOC_CROPCAP:查詢驅動的修剪能力

VIDIOC_S_CROP:設置視頻信號的邊框

VIDIOC_G_CROP:讀取視頻信號的邊框

VIDIOC_QBUF:把數據從緩存中讀取出來

VIDIOC_DQBUF:把數據放回緩存隊列

VIDIOC_STREAMON:開始視頻顯示函數

VIDIOC_STREAMOFF:結束視頻顯示函數

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函數轉換成應用程序中的絕對地址,最後把這段緩存放入緩存隊列:

 

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捕獲的數據,最初是存放在內核空間的,這意味着用戶不能直接訪問該段內存,必須經過某些手段來轉換地址。

一共有三種視頻採集方式:

1)使用read、write方式:直接使用 read 和 write 函數進行讀寫。這種方式最簡單,可是這種方式會在 用戶空間和內核空間不斷拷貝數據 ,同時在用戶空間和內核空間佔用 了 大量內存,效率不高。

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

3)用戶指針模式:內存由用戶空間的應用程序分配,並把地址傳遞到內核中的驅動程序,而後由 v4l2 驅動程序直接將數據填充到用戶空間的內存中。這點須要在v4l2_requestbuffers裏將memory字段設置成V4L2_MEMORY_USERPTR。

第一種方式效率是最低的,後面兩種方法都能提升執行的效率,可是對於mmap 方式,文檔中有這樣一句描述 --Remember the buffers are allocated in physical memory, as opposed to virtual memory which can be swapped out to disk. Applications should free the buffers as soon as possible with the munmap () function .(使用mmap方法的時候,buffers至關因而在內核空間中分配的,這種狀況下,這些buffer是不能被交換到虛擬內存中,雖然這種方法不怎麼影響讀寫效率,可是它一直佔用着內核空間中的內存,當系統的內存有限的時候,若是同時運行有大量的進程,則對系統的總體性能會有必定的影響。)

       因此,對於三種視頻採集方式的選擇,推薦的順序是 userptr 、 mmap 、 read-write 。當使用 mmap 或 userptr 方式的時候,有一個環形緩衝隊列的概念,這個隊列中,有 n 個 buffer ,驅動程序採集到的視頻幀數據,就是存儲在每一個 buffer 中。在每次用 VIDIOC_DQBUF 取出一個 buffer ,而且處理完數據後,必定要用 VIDIOC_QBUF 將這個 buffer 再次放回到環形緩衝隊列中。環形緩衝隊列,也使得這兩種視頻採集方式的效率高於直接 read/write 。

九、  處理採集數據

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)

相關文章
相關標籤/搜索