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

來源:互聯網
上載者:User

二、V4L2的應用

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

1、  視頻採集的基本流程

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

 

 

2、  開啟視頻裝置

在V4L2中,視頻裝置被看做一個檔案。使用open函數開啟這個裝置:

// 用非阻塞模式開啟網路攝影機裝置
int cameraFd;
cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
// 如果用阻塞模式開啟網路攝影機裝置,上述代碼變為:
//cameraFd = open("/dev/video0", O_RDWR, 0);

關於阻塞模式和非阻塞模式:應用程式能夠使用阻塞模式或非阻塞模式開啟視頻裝置,如果使用非阻塞模式調用視頻裝置,即使尚未捕獲到資訊,驅動依舊會把緩衝(DQBUFF)裡的東西返回給應用程式。

3、  設定屬性及採集方式

開啟視頻裝置後,可以設定該視頻裝置的屬性,例如裁剪、縮放等。這一步是可選的。在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調用,有些是必須的,有些是可選擇的。

4、  檢查當前視頻裝置支援的標準

在亞洲,一般使用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:
        //……
}

5、  設定視頻捕獲格式

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

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

6、  分配記憶體

接下來可以為視頻捕獲分配記憶體:

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

7、  擷取並記錄緩衝的物理空間

使用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;
    }
}

8、  關於視頻採集方式

作業系統一般把系統使用的記憶體劃分成使用者空間和核心空間,分別由應用程式管理和作業系統管理。應用程式可以直接存取記憶體的地址,而核心空間存放的是 供核心訪問的代碼和資料,使用者不能直接存取。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 。

9、  處理採集資料

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

10、              關閉視頻裝置

使用close函數關閉一個視頻裝置

close(cameraFd)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.