天天看點

v4l2的學習建議和流程解析一、Video for Linux two二、v4l2結構體介紹三、調用v4l2的工作流程

轉自: https://www.cnblogs.com/silence-hust/p/4464291.html

  • 一、Video for Linux two
  • 二、v4l2結構體介紹
    • 1、常用的結構體在核心目錄include/linux/videodev2.h中定義
    • 2、常用的IOCTL接口指令也在include/linux/videodev2.h中定義
  • 三、調用v4l2的工作流程

上各種找資料後,才發現其實v4l2已經分裝好了驅動程式,隻要我們根據需要調用相應的接口和函數,進而實作視訊的擷取和處理。隻要認真的看幾篇文章就對v4l2有一定的了解了,由于是第一次接觸,網上的資料良莠不齊,難得可以找到幾篇自己感覺很不錯的。記錄下來:(沒必要看太多,很多都是一樣的意思)

  • 基于V4L2的視訊驅動開發(2)

    這篇是不錯的介紹,很讨厭有彈窗

  • Linux之V4L2基礎程式設計

    這個可以作為第一篇來看,部落客整理的不錯

  • 基于Linux的V4L2接口編寫視訊采集應用程式的方法
  • V4L2驅動的移植與應用(二+三)
  • 嵌入式LINUX環境下視訊采集知識(V4L2)

    這篇也比較詳細

  • 用v4l2和framebuffer實作usb攝像頭視訊采集并顯示

    這篇提到的問題和我遇到的一樣,花屏了,記憶體沒有讀取好

  • V4L2官方首頁的視訊應用例子

    capture.c檔案

  • v4l2讀取攝像頭程式流程解析

    對capture.c檔案的解讀

  • v4l2 程式設計接口(一) — ioctl

    對各個結構體有比較好的說明

一、Video for Linux two

v4l2為linux下視訊裝置程式提供了一套接口規範。包括一套資料結構和底層V4L2驅動接口。隻能在linux下使用。它使程式有發現裝置和操作裝置的能力。它主要是用一系列的回調函數來實作這些功能。像設定攝像頭的頻率、幀頻、視訊壓縮格式和圖像參數等等。當然也可以用于其他多媒體的開發,如音頻等。

在Linux下,所有外設都被看成一種特殊的檔案,成為“裝置檔案”,可以象通路普通檔案一樣對其進行讀寫。一般來說,采用V4L2驅動的攝像頭裝置文是

/dev/v4l/video0

。為了通用,可以建立一個到

/dev/video0

的連結。V4L2支援兩種方式來采集圖像:記憶體映射方式(mmap)和直接讀取方式(read)。V4L2在

include/linux/videodev.h

檔案中定義了一些重要的資料結構,在采集圖像的過程中,就是通過對這些資料的操作來獲得最終的圖像資料。Linux系統V4L2的能力可在Linux核心編譯階段配置,預設情況下都有此開發接口。V4L2從Linux 2.5.x版本的核心中開始出現。

V4L2規範中不僅定義了通用API元素(Common API Elements),圖像的格式(Image Formats),輸入/輸出方法(Input/Output),還定義了Linux核心驅動處理視訊資訊的一系列接口(Interfaces),這些接口主要有:

視訊采集接口——Video Capture Interface;

視訊輸出接口—— Video Output Interface;

視訊覆寫/預覽接口——Video Overlay Interface;

視訊輸出覆寫接口——Video Output Overlay Interface;

編解碼接口——Codec Interface。

二、v4l2結構體介紹

1、常用的結構體在核心目錄include/linux/videodev2.h中定義

struct v4l2_requestbuffers	//申請幀緩沖,對應指令VIDIOC_REQBUFS 
struct v4l2_capability		//視訊裝置的功能,對應指令VIDIOC_QUERYCAP 
struct v4l2_input			//視訊輸入資訊,對應指令VIDIOC_ENUMINPUT
struct v4l2_standard		//視訊的制式,比如PAL,NTSC,對應指令VIDIOC_ENUMSTD 
struct v4l2_format			//幀的格式,對應指令VIDIOC_G_FMT、VIDIOC_S_FMT等
struct v4l2_buffer			//驅動中的一幀圖像緩存,對應指令VIDIOC_QUERYBUF 
struct v4l2_crop			//視訊信号矩形邊框
v4l2_std_id	stdid			//視訊制式
           

常用結構體的内容:

struct v4l2_capability
{
	u8 driver[16];	// 驅動名字
	u8 card[32];	// 裝置名字
	u8 bus_info[32]; // 裝置在系統中的位置
	u32 version;	// 驅動版本号
	u32 capabilities;// 裝置支援的操作
	u32 reserved[4]; // 保留字段
};
           

其中域

capability

代表裝置支援的操作模式,常見的值有

V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING

表示是一個視訊捕捉裝置并且具有資料流控制模式;另外

driver

域需要和

struct video_device

中的

name

比對。

struct v4l2_format { 
    enum v4l2_buf_type type; 
    union { 
        struct v4l2_pix_format         pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */ 
        struct v4l2_window             win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */ 
        struct v4l2_vbi_format         vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */ 
        struct v4l2_sliced_vbi_format  sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */ 
        __u8   raw_data[200];                   /* user-defined */ 
    } fmt; 
}; 
           

其中

enum v4l2_buf_type { 
    V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1, 
    V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2, 
    V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3, 
    ... 
    V4L2_BUF_TYPE_PRIVATE              = 0x80, 
}; 
   
struct v4l2_pix_format { 
    __u32                   width; 
    __u32                   height; 
    __u32                   pixelformat; 
    enum v4l2_field         field; 
    __u32                   bytesperline;   /* for padding, zero if unused */ 
    __u32                   sizeimage; 
    enum v4l2_colorspace    colorspace; 
    __u32                   priv;           /* private data, depends on pixelformat */ 
};
           

常見的捕獲模式為

V4L2_BUF_TYPE_VIDEO_CAPTURE

即視訊捕捉模式,在此模式下

fmt

聯合體采用域

v4l2_pix_format

:其中

width

為視訊的寬、

height

為視訊的高、

pixelformat

為視訊資料格式(常見的值有

V4L2_PIX_FMT_YUV422P | V4L2_PIX_FMT_RGB565

)、

bytesperline

為一行圖像占用的位元組數、

sizeimage

則為圖像占用的總位元組數、

colorspace

指定裝置的顔色空間。

struct v4l2_requestbuffers

VIDIOC_REQBUFS

VIDIOC_REQBUFS

指令通過結構

v4l2_requestbuffers

請求驅動申請一片連續的記憶體用于緩存視訊資訊:

struct v4l2_requestbuffers {
    __u32                   count;
    enum v4l2_buf_type      type;
    enum v4l2_memory        memory;
    __u32                   reserved[2];
};
enum v4l2_memory {
    V4L2_MEMORY_MMAP             = 1,
    V4L2_MEMORY_USERPTR          = 2,
    V4L2_MEMORY_OVERLAY          = 3,
};
           

count

指定根據圖像占用空間大小申請的緩存區個數,

type

為視訊捕獲模式,

memory

為記憶體區的使用方式。

struct v4l2_buffer {
    __u32   index;					// 緩存編号
    enum v4l2_buf_type    type;		// 視訊捕獲模式
    __u32    bytesused;				// 緩存已使用空間大小
    __u32    flags;					// 緩存目前狀态(常見值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表目前緩存已經映射、緩存可以采集資料、緩存可以提取資料)
    enum v4l2_field  field;
    struct timeval    timestamp;	// 時間戳
    struct v4l2_timecode   timecode;
    __u32     sequence;				// 緩存序号
  
    /* memory location */
    enum v4l2_memory    memory;		// 緩存使用方式
    union {
            __u32   offset;			// 目前緩存與記憶體區起始位址的偏移
            unsigned long   userptr;
    } m;
    __u32    length;				// 緩存大小
    __u32    input;
    __u32    reserved;				// 一般用于傳遞實體位址值。
};
           

另外

VIDIOC_QBUF

VIDIOC_DQBUF

指令都采用結構

v4l2_buffer

與驅動通信:

VIDIOC_QBUF

指令向驅動傳遞應用程式已經處理完的緩存,即将緩存加入空閑可捕獲視訊的隊列,傳遞的主要參數為

index

VIDIOC_DQBUF

指令向驅動擷取已經存放有視訊資料的緩存,

v4l2_buffer

的各個域幾乎都會被更新,但主要的參數也是

index

,應用程式會根據

index

确定可用資料的起始位址和範圍。

2、常用的IOCTL接口指令也在include/linux/videodev2.h中定義

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。
           

三、調用v4l2的工作流程

打開裝置-> 檢查和設定裝置屬性-> 設定幀格式-> 設定一種輸入輸出方法(緩沖 區管理)-> 循環擷取資料-> 關閉裝置。

(1)打開裝置檔案

打開視訊裝置非常簡單,在V4L2中,視訊裝置被看做一個檔案。使用open函數打開這個裝置:

1. 用非阻塞模式打開攝像頭裝置

int cameraFd;
cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK);
           

2. 如果用阻塞模式打開攝像頭裝置,上述代碼變為:

關于阻塞模式和非阻塞模式

應用程式能夠使用阻塞模式或非阻塞模式打開視訊裝置,如果使用非阻塞模式調用視訊裝置,即使尚未捕獲到資訊,驅動依舊會把緩存(

DQBUFF

)裡的東西傳回給應用程式。

(2)取得裝置的

capability

struct v4l2_capability capability;
int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);
           

看看裝置具有什麼功能,比如是否具有視訊輸入特性。

struct v4l2_capability cap;
 
memset(&cap, 0, sizeof(cap));

/* 擷取裝置支援的操作 */
if(ioctl(dev->fd, VIDIOC_QUERYCAP, &cap) < 0){
    if(EINVAL == errno){   /*EINVAL為傳回的錯誤值*/
        printf(stderr,"%s is no V4L2 device\n", dev->dev);
        return TFAIL;
    }
    else
    {
        printf(stderr,"%s is not V4L2 device,unknow error\n", dev->dev);
        return TFAIL;
    }
}

//擷取成功,檢查是否有視訊捕獲功能
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
    printf(stderr, "%s is no video capture device\n",dev->dev);
    return TFAIL;
}
/* streaming I/O ioctls */
if(!(cap.capabilities & V4L2_CAP_STREAMING)){
    printf(stderr, "%s does not support streaming i/o\n",dev->dev);
    return TFAIL;
}
           

(3)選擇視訊輸入

struct v4l2_input input;
……初始化input
int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);
           

一個視訊裝置可以有多個視訊輸入。如果隻有一路輸入,這個功能可以沒有。

VIDIOC_G_INPUT

VIDIOC_S_INPUT

用來查詢和選則目前的 input,一個 video 裝置節點可能對應多個視訊源,比如 saf7113 可以最多支援四路 cvbs 輸入,如果上層想在四個cvbs視訊輸入間切換,那麼就要調用

ioctl(fd, VIDIOC_S_INPUT, &input)

來切換。

VIDIOC_G_INPUT

and

VIDIOC_G_OUTPUT

傳回目前的 video input和output的index.

struct v4l2_input {
	__u32 index; /* Which input *
	/__u8 name[32]; /* Label */
	__u32 type; /* Type of input */
	__u32 audioset; /* Associated audios (bitfield) */
	__u32 tuner; /* Associated tuner */
	v4l2_std_id std;
	__u32 status;
	__u32 reserved[4];
};
           

(4)檢測視訊支援的制式

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)設定視訊捕獲格式

v4l2_format

結構體用來設定攝像頭的視訊制式、幀格式等,在設定這個參數時應先填 好

v4l2_format

的各個域,如

type

(傳輸流類型),

fmt.pix.width

(寬),

fmt.pix.heigth

(高),

fmt.pix.field

(采樣區域,如隔行采樣),

fmt.pix.pixelformat

(采樣類型,如 YUV4:2:2),然後通過

VIDIO_S_FMT

操作指令設定視訊捕捉格式。

struct v4l2_format fmt;

memset(&fmt, 0, sizeof(fmt));
fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width       = g_display_width;
fmt.fmt.pix.height      = g_display_height;
fmt.fmt.pix.pixelformat = g_fmt;
fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

/* 設定裝置捕獲視訊的格式 */
if(ioctl(dev->fd, VIDIOC_S_FMT, &fmt) < 0)
{
    printf(stderr, "%s iformat not supported \n",dev->dev);
    close(dev->fd);
    return TFAIL;
}
           

注意:如果該視訊裝置驅動不支援你所設定的圖像格式,視訊驅動會重新修改

struct v4l2_format

結構體變量的值為該視訊裝置所支援的圖像格式,是以在程式設計中,設定完所有的視訊格式後,要擷取實際的視訊格式,要重新讀取

struct v4l2_format

結構體變量。

(6)向驅動申請幀緩存

一般不超過5個,

CAP_BUF_NUM = 4

struct v4l2_requestbuffers req;

/* 申請裝置的緩存區 */
memset(&req, 0, sizeof(req));
req.count = CAP_BUF_NUM;  //申請一個擁有四個緩沖幀的緩沖區
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
 
if (ioctl(dev->fd, VIDIOC_REQBUFS, &req) < 0)
{
	if (EINVAL == errno)
	{
		printf(stderr, "%s does not support "
	 		"memory mapping\n", dev->dev);
		return TFAIL;
	}
	else
	{
		printf(stderr, "%s does not support "
			"memory mapping, unknow error\n", dev->dev);
		return TFAIL;
	}
}

if (req.count < 2)
{
	printf(stderr, "Insufficient buffer memory on %s\n",
		dev->dev);
	return TFAIL;
}
           

v4l2_requestbuffers

結構中定義了緩存的數量,驅動會據此申請對應數量的視訊緩存。多個緩存可以用于建立FIFO,來提高視訊采集的效率。控制指令

VIDIOC_REQBUFS

功能: 請求V4L2驅動配置設定視訊緩沖區(申請V4L2視訊驅動配置設定記憶體),V4L2是視訊裝置的驅動層,位于核心空間,是以通過

VIDIOC_REQBUFS

控制指令字申請的記憶體位于核心空間,應用程式不能直接通路,需要通過調用

mmap

記憶體映射函數把核心空間記憶體映射到使用者空間後,應用程式通過通路使用者空間位址來通路核心空間。

參數說明:參數類型為V4L2的申請緩沖區資料結構體類型

struct v4l2_requestbuffers

傳回值說明: 執行成功時,函數傳回值為 0;V4L2驅動層配置設定好了視訊緩沖區;

(7)擷取每個緩存的資訊,并

mmap

到使用者空間

應用程式和裝置有三種交換資料的方法,直接

read/write

、記憶體映射(

memory mapping

)和使用者指針。這裡隻讨論記憶體映射(

memory mapping

)。

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;
	
	//擷取到對應index的緩存資訊,此處主要利用length資訊及offset資訊來完成後面的mmap操作
	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;
	}
}
           

addr 映射起始位址,一般為NULL ,讓核心自動選擇

length 被映射記憶體塊的長度

prot 标志映射後能否被讀寫,其值為PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE

flags 确定此記憶體映射能否被其他程序共享,MAP_SHARED,MAP_PRIVATE

fd,offset, 确定被映射的記憶體位址 傳回成功映射後的位址,不成功傳回MAP_FAILED ((void*)-1)

int munmap(void *addr, size_t length);// 斷開映射
//addr 為映射後的位址,length 為映射後的記憶體長度
           

(8)開始采集視訊 (在緩沖區處理好之後就可以獲得視訊了 )

在開始之前,還應當把緩沖幀放入緩沖隊列,應用程式和裝置有三種交換資料的方法,直接 read/write、記憶體映射(memory mapping)和使用者指針。這裡隻讨論記憶體映射(memory mapping)。

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

//把四個緩沖幀放入隊列
for (i = 0; i < CAP_BUF_NUM; i++)
{
       memset(&buf, 0, sizeof(buf));
       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
       buf.memory = V4L2_MEMORY_MMAP;
       buf.index = i;
       buf.m.offset = dev->buffer[i].offset;
       /* 将空閑的記憶體加入可捕獲視訊的隊列 */
       if(ioctl(dev->fd, VIDIOC_QBUF, &buf) < 0)
       {
           printf("ERROR: VIDIOC_QBUF[%s], FUNC[%s], LINE[%d]\n", dev->dev, __FUNCTION__, __LINE__);
           return TFAIL;
       }
}
 
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* 打開裝置視訊流 */
if(ioctl(dev->fd, VIDIOC_STREAMON, &type) < 0)
{
       printf("ERROR: VIDIOC_STREAMON[%s], FUNC[%s], LINE[%d]\n", dev->dev, __FUNCTION__, __LINE__);
       return TFAIL;
}
           

前期初始化完成後,隻是解決了一幀視訊資料的格式和大小問題,而連續視訊幀資料的采集需要用幀緩沖區隊列的方式來解決,即要通過驅動程式在記憶體中申請幾個幀緩沖區來存放視訊資料。

應用程式通過API接口提供的方法(

VIDIOC_REQBUFS

)申請若幹個視訊資料的幀緩沖區,申請幀緩沖區數量一般不低于3個,每個幀緩沖區存放一幀視訊資料,這些幀緩沖區在核心空間。

應用程式通過API接口提供的查詢方法(

VIDIOC_QUERYBUF

)查詢到幀緩沖區在核心空間的長度和偏移量位址。

應用程式再通過記憶體映射方法(mmap),将申請到的核心空間幀緩沖區的位址映射到使用者空間位址,這樣就可以直接處理幀緩沖區的資料。

(1)将幀緩沖區在視訊輸入隊列排隊,并啟動視訊采集

在驅動程式處理視訊的過程中,定義了兩個隊列:視訊采集輸入隊列(incoming queues)和視訊采集輸出隊列(outgoing queues),前者是等待驅動存放視訊資料的隊列,後者是驅動程式已經放入了視訊資料的隊列。如圖2所示。

應用程式需要将上述幀緩沖區在視訊采集輸入隊列排隊(

VIDIOC_QBUF

),然後可啟動視訊采集。

(2)循環往複,采集連續的視訊資料

啟動視訊采集後,驅動程式開始采集一幀資料,把采集的資料放入視訊采集輸入隊列的第一個幀緩沖區,一幀資料采集完成,也就是第一個幀緩沖區存滿一幀資料後,驅動程式将該幀緩沖區移至視訊采集輸出隊列,等待應用程式從輸出隊列取出。驅動程式接下來采集下一幀資料,放入第二個幀緩沖區,同樣幀緩沖區存滿下一幀資料後,被放入視訊采集輸出隊列。

應用程式從視訊采集輸出隊列中取出含有視訊資料的幀緩沖區,處理幀緩沖區中的視訊資料,如存儲或壓縮。

最後,應用程式将處理完資料的幀緩沖區重新放入視訊采集輸入隊列,這樣可以循環采集,如圖1所示。

v4l2的學習建議和流程解析一、Video for Linux two二、v4l2結構體介紹三、調用v4l2的工作流程

(9)取出FIFO緩存中已經采樣的幀緩存

struct v4l2_buffer capture_buf;
memset(&capture_buf, 0, sizeof(capture_buf));
capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
capture_buf.memory = V4L2_MEMORY_MMAP;
/* 将已經捕獲好視訊的記憶體拉出已捕獲視訊的隊列 */
if (ioctl(dev.fd, VIDIOC_DQBUF, &capture_buf) < 0)
{
       printf("ERROR: VIDIOC_DQBUF[%s], FUNC[%s], LINE[%d]\n", dev, __FUNCTION__, __LINE__);
       return TFAIL;
       }
}
 
image_data_handle(buffer[capture_buf.index].start, capture_buf.bytesused);
           

(10)将剛剛處理完的緩沖重新入隊列尾,這樣可以循環采集

if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
	return -1;
}
           

(11)停止視訊的采集,解除映射

int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
munmap(buffer[j].start, buffer[j].length);
           

(12)關閉視訊裝置

最後這個是一般

mmap

形式的使用流程,還有使用read/write方式的記憶體讀寫流程,具體的可以參考官方的

capture.c

這個文檔,程式的流程很清楚,也有相關的博文有寫到。

文字描述版流程:

(1)打開視訊裝置檔案。

int fd=open("/dev/video0",O_RDWR);

(2)查詢視訊裝置的能力,比如是否具有視訊輸入,或者音頻輸入輸出等。

ioctl(fd_v4l, VIDIOC_QUERYCAP, &cap)

(3)設定視訊采集的參數

  設定視訊的制式,制式包括PAL/NTSC,使用

ioctl(fd_v4l, VIDIOC_S_STD, &std_id)

  設定視訊圖像的采集視窗的大小,使用

ioctl(fd_v4l, VIDIOC_S_CROP, &crop)

  設定視訊幀格式,包括幀的點陣格式,寬度和高度等,使用

ioctl(fd_v4l, VIDIOC_S_FMT, &fmt)

  設定視訊的幀率,使用

ioctl(fd_v4l, VIDIOC_S_PARM, &parm)

  設定視訊的旋轉方式,使用

ioctl(fd_v4l, VIDIOC_S_CTRL, &ctrl)

(4)向驅動申請視訊流資料的幀緩沖區

  請求/申請若幹個幀緩沖區,一般為不少于3個,使用

ioctl(fd_v4l, VIDIOC_REQBUFS, &req)

  查詢幀緩沖區在核心空間中的長度和偏移量

ioctl(fd_v4l, VIDIOC_QUERYBUF, &buf)

(5)應用程式通過記憶體映射,将幀緩沖區的位址映射到使用者空間,這樣就可以直接操作采集到的幀了,而不必去複制。

buffers[i].start = mmap (NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l, buffers[i].offset);

(6)将申請到的幀緩沖全部放入視訊采集輸出隊列,以便存放采集的資料。

ioctl (fd_v4l, VIDIOC_QBUF, &buf)

(7)開始視訊流資料的采集。

ioctl (fd_v4l, VIDIOC_STREAMON, &type)

(8) 驅動将采集到的一幀視訊資料存入輸入隊列第一個幀緩沖區,存完後将該幀緩沖區移至視訊采集輸出隊列。

(9)應用程式從視訊采集輸出隊列中取出已含有采集資料的幀緩沖區。

ioctl (fd_v4l, VIDIOC_DQBUF, &buf)

,應用程式處理該幀緩沖區的原始視訊資料。

(10)處理完後,應用程式的将該幀緩沖區重新排入輸入隊列,這樣便可以循環采集資料。

ioctl (fd_v4l, VIDIOC_QBUF, &buf)

重複上述步驟8到10,直到停止采集資料。

(11)停止視訊的采集。

ioctl (fd_v4l, VIDIOC_STREAMOFF, &type)

(12)釋放申請的視訊幀緩沖區unmap,關閉視訊裝置檔案

close(fd_v4l)

以上的程式流程,包含了視訊裝置采集連續的視訊資料的邏輯關系。而在實際運用中,往往還要加入對視訊資料進行處理(如壓縮編碼)的工作,否則,視訊流資料量相當大,需要很大的存儲空間和傳輸帶寬。

繼續閱讀