V4L2學習(二)結構介紹

v4l2_devicenode

v4l2_device在v4l2框架中充當全部v4l2_subdev的父設備,管理着註冊在其下的子設備。如下是v4l2_device結構體原型(去掉了無關的成員):數組

struct v4l2_device {
 
         structlist_head subdevs;    //用鏈表管理註冊的subdev
 
         charname[V4L2_DEVICE_NAME_SIZE];    //device 名字
 
         structkref ref;      //引用計數
 
         ……
}

能夠看出v4l2_device的主要做用是管理註冊在其下的子設備,方便系統查找引用到。緩存

V4l2_device的註冊和註銷:cookie

int v4l2_device_register(struct device*dev, struct v4l2_device *v4l2_dev)
 
static void v4l2_device_release(struct kref *ref)

V4l2_subdev框架

V4l2_subdev表明子設備,包含了子設備的相關屬性和操做。先來看下結構體原型:ide

struct v4l2_subdev {
 
         struct v4l2_device *v4l2_dev;  //指向父設備
 
         //提供一些控制v4l2設備的接口
 
         const struct v4l2_subdev_ops *ops;
 
         //向V4L2框架提供的接口函數
 
         const struct v4l2_subdev_internal_ops *internal_ops;
 
         //subdev控制接口
 
         struct v4l2_ctrl_handler *ctrl_handler;
 
         /* name must be unique */
 
         charname[V4L2_SUBDEV_NAME_SIZE];
 
         /*subdev device node */
 
         struct video_device *devnode;  
 
};

 

每一個子設備驅動都須要實現一個v4l2_subdev結構體,v4l2_subdev能夠內嵌到其它結構體中,也能夠獨立使用。結構體中包含了對子設備操做的成員v4l2_subdev_ops和v4l2_subdev_internal_ops。函數

v4l2_subdev_ops結構體原型以下:oop

struct v4l2_subdev_ops {
 
//視頻設備通用的操做:初始化、加載FW、上電和RESET等
 
         const struct v4l2_subdev_core_ops        *core;    //tuner特有的操做
 
         const struct v4l2_subdev_tuner_ops      *tuner;    //audio特有的操做
 
         const struct v4l2_subdev_audio_ops      *audio;    //視頻設備的特有操做:設置幀率、裁剪圖像、開關視頻流等
 
         const struct v4l2_subdev_video_ops      *video;
……
 
};

視頻設備一般須要實現core和video成員,這兩個OPS中的操做都是可選的,可是對於視頻流設備video->s_stream(開啓或關閉流IO)必需要實現。ui

v4l2_subdev_internal_ops結構體原型以下:this

struct v4l2_subdev_internal_ops {
 
    //當subdev註冊時被調用,讀取IC的ID來進行識別
 
         int(*registered)(struct v4l2_subdev *sd);
 
         void(*unregistered)(struct v4l2_subdev *sd);
 
//當設備節點被打開時調用,一般會給設備上電和設置視頻捕捉FMT
 
         int(*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
 
         int(*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
 
};

v4l2_subdev_internal_ops是向V4L2框架提供的接口,只能被V4L2框架層調用。在註冊或打開子設備時,進行一些輔助性操做。

Subdev的註冊和註銷

當咱們把v4l2_subdev須要實現的成員都已經實現,就能夠調用如下函數把子設備註冊到V4L2核心層:

int v4l2_device_register_subdev(struct v4l2_device*v4l2_dev, struct v4l2_subdev *sd)

 當卸載子設備時,能夠調用如下函數進行註銷:

void v4l2_device_unregister_subdev(struct v4l2_subdev*sd)

video_device

video_device結構體用於在/dev目錄下生成設備節點文件,把操做設備的接口暴露給用戶空間。

struct video_device
 
{
 
         const struct v4l2_file_operations *fops;  //V4L2設備操做集合
 
         /*sysfs */
 
         struct device dev;             /* v4l device */
 
         struct cdev *cdev;            //字符設備
 
         /* Seteither parent or v4l2_dev if your driver uses v4l2_device */
 
         struct device *parent;              /* deviceparent */
 
         struct v4l2_device *v4l2_dev;          /*v4l2_device parent */
 
         /*Control handler associated with this device node. May be NULL. */
 
         struct v4l2_ctrl_handler *ctrl_handler;
 
         /* 指向video buffer隊列*/
 
         struct vb2_queue *queue;
 
         int vfl_type;      /* device type */
 
         int minor;  //次設備號
 
         /* V4L2file handles */
 
         spin lock_t                  fh_lock; /* Lock for allv4l2_fhs */
 
         struct list_head        fh_list; /* List ofstruct v4l2_fh */
 
         /*ioctl回調函數集,提供file_operations中的ioctl調用 */
 
         const struct v4l2_ioctl_ops *ioctl_ops;
 
         ……
 
};

Video_device分配和釋放,用於分配和釋放video_device結構體:

struct video_device *video_device_alloc(void)
 
void video_device_release(struct video_device *vdev)

video_device註冊和註銷,實現video_device結構體的相關成員後,就能夠調用下面的接口進行註冊:

static inline int __must_checkvideo_register_device(struct video_device *vdev, inttype, int nr)
 
void video_unregister_device(struct video_device*vdev);

vdev:須要註冊和註銷的video_device;

type:設備類型,包括VFL_TYPE_GRABBER、VFL_TYPE_VBI、VFL_TYPE_RADIO和VFL_TYPE_SUBDEV。

nr:設備節點名編號,如/dev/video[nr]。

v4l2_fh

v4l2_fh是用來保存子設備的特有操做方法,也就是下面要分析到的v4l2_ctrl_handler,內核提供一組v4l2_fh的操做方法,一般在打開設備節點時進行v4l2_fh註冊。

初始化v4l2_fh,添加v4l2_ctrl_handler到v4l2_fh:

void v4l2_fh_init(struct v4l2_fh *fh, structvideo_device *vdev)

添加v4l2_fh到video_device,方便核心層調用到:

void v4l2_fh_add(struct v4l2_fh *fh)

v4l2_ctrl_handler

v4l2_ctrl_handler是用於保存子設備控制方法集的結構體,對於視頻設備這些ctrls包括設置亮度、飽和度、對比度和清晰度等,用鏈表的方式來保存ctrls,能夠經過v4l2_ctrl_new_std函數向鏈表添加ctrls。

struct v4l2_ctrl *v4l2_ctrl_new_std(structv4l2_ctrl_handler *hdl,
 
                            conststruct v4l2_ctrl_ops *ops,
 
                            u32id, s32 min, s32 max, u32 step, s32 def)

hdl是初始化好的v4l2_ctrl_handler結構體;

ops是v4l2_ctrl_ops結構體,包含ctrls的具體實現;

id是經過IOCTL的arg參數傳過來的指令,定義在v4l2-controls.h文件;

min、max用來定義某操做對象的範圍。如:

v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS,-208, 127, 1, 0);

用戶空間能夠經過ioctl的VIDIOC_S_CTRL指令調用到v4l2_ctrl_handler,id透過arg參數傳遞。

三、ioctl框架

你可能觀察到用戶空間對V4L2設備的操做基本都是ioctl來實現的,V4L2設備都有大量可操做的功能(配置寄存器),因此V4L2的ioctl也是十分龐大的。它是一個怎樣的框架,是怎麼實現的呢?

Ioctl框架是由v4l2_ioctl.c文件實現,文件中定義結構體數組v4l2_ioctls,能夠看作是ioctl指令和回調函數的關係表。用戶空間調用系統調用ioctl,傳遞下來ioctl指令,而後經過查找此關係表找到對應回調函數。

如下是截取數組的兩項:

IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf,v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
 
IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf,v4l_print_framebuffer, 0),

內核提供兩個宏(IOCTL_INFO_FNC和IOCTL_INFO_STD)來初始化結構體,參數依次是ioctl指令、回調函數或者v4l2_ioctl_ops結構體成員、debug函數、flag。若是回調函數是v4l2_ioctl_ops結構體成員,則使用IOCTL_INFO_STD;若是回調函數是v4l2_ioctl.c本身實現的,則使用IOCTL_INFO_FNC。

IOCTL調用的流程圖以下:

IOCTL

用戶空間經過打開/dev/目錄下的設備節點,獲取到文件的file結構體,經過系統調用ioctl把cmd和arg傳入到內核。經過一系列的調用後最終會調用到__video_do_ioctl函數,而後經過cmd檢索v4l2_ioctls[],判斷是INFO_FL_STD仍是INFO_FL_FUNC。若是是INFO_FL_STD會直接調用到視頻設備驅動中video_device->v4l2_ioctl_ops函數集。若是是INFO_FL_FUNC會先調用到v4l2本身實現的標準回調函數,而後根據arg再調用到video_device->v4l2_ioctl_ops或v4l2_fh->v4l2_ctrl_handler函數集。

四、IO訪問

V4L2支持三種不一樣IO訪問方式(內核中還支持了其它的訪問方式,暫不討論):

read和write,是基本幀IO訪問方式,經過read讀取每一幀數據,數據須要在內核和用戶之間拷貝,這種方式訪問速度可能會很是慢;

內存映射緩衝區(V4L2_MEMORY_MMAP),是在內核空間開闢緩衝區,應用經過mmap()系統調用映射到用戶地址空間。這些緩衝區能夠是大而連續DMA緩衝區、經過vmalloc()建立的虛擬緩衝區,或者直接在設備的IO內存中開闢的緩衝區(若是硬件支持);

用戶空間緩衝區(V4L2_MEMORY_USERPTR),是用戶空間的應用中開闢緩衝區,用戶與內核空間之間交換緩衝區指針。很明顯,在這種狀況下是不須要mmap()調用的,但驅動爲有效的支持用戶空間緩衝區,其工做將也會更困難。

Read和write方式屬於幀IO訪問方式,每一幀都要經過IO操做,須要用戶和內核之間數據拷貝,然後兩種是流IO訪問方式,不須要內存拷貝,訪問速度比較快。內存映射緩衝區訪問方式是比較經常使用的方式。

內存映射緩存區方式

硬件層的數據流傳輸

Camerasensor捕捉到圖像數據經過並口或MIPI傳輸到CAMIF(camera interface),CAMIF能夠對圖像數據進行調整(翻轉、裁剪和格式轉換等)。而後DMA控制器設置DMA通道請求AHB將圖像數據傳到分配好的DMA緩衝區。

step

待圖像數據傳輸到DMA緩衝區以後,mmap操做把緩衝區映射到用戶空間,應用就能夠直接訪問緩衝區的數據。

vb2_queue

爲了使設備支持流IO這種方式,驅動須要實現struct vb2_queue,來看下這個結構體:

struct vb2_queue {
 
         enum v4l2_buf_type                  type;  //buffer類型
 
         unsigned int                        io_modes;  //訪問IO的方式:mmap、userptr etc
 
         const struct vb2_ops                 *ops;   //buffer隊列操做函數集合
 
         const struct vb2_mem_ops     *mem_ops;  //buffer memory操做集合
 
         struct vb2_buffer              *bufs[VIDEO_MAX_FRAME];  //表明每一個buffer
 
         unsigned int                        num_buffers;    //分配的buffer個數
 
……
 
};

Vb2_queue表明一個videobuffer隊列,vb2_buffer是這個隊列中的成員,vb2_mem_ops是緩衝內存的操做函數集,vb2_ops用來管理隊列。

vb2_mem_ops

vb2_mem_ops包含了內存映射緩衝區、用戶空間緩衝區的內存操做方法:

struct vb2_mem_ops {
 
void           *(*alloc)(void *alloc_ctx, unsignedlong size);  //分配視頻緩存
 
void           (*put)(void *buf_priv);            //釋放視頻緩存
 
//獲取用戶空間視頻緩衝區指針
 
void           *(*get_userptr)(void *alloc_ctx,unsigned long vaddr,
 
unsigned long size, int write);
 
void           (*put_userptr)(void *buf_priv);       //釋放用戶空間視頻緩衝區指針
 
//用於緩存同步
 
void           (*prepare)(void *buf_priv);
 
void           (*finish)(void *buf_priv);
 
void           *(*vaddr)(void *buf_priv);
 
void           *(*cookie)(void *buf_priv);
 
unsigned int     (*num_users)(void *buf_priv);         //返回當期在用戶空間的buffer數
 
int              (*mmap)(void *buf_priv, structvm_area_struct *vma);  //把緩衝區映射到用戶空間
 
};

這是一個至關龐大的結構體,這麼多的結構體須要實現還不得累死,幸運的是內核都已經幫咱們實現了。提供了三種類型的視頻緩存區操做方法:連續的DMA緩衝區、集散的DMA緩衝區以及vmalloc建立的緩衝區,分別由videobuf2-dma-contig.c、videobuf2-dma-sg.c和videobuf-vmalloc.c文件實現,能夠根據實際狀況來使用。

vb2_ops

vb2_ops是用來管理buffer隊列的函數集合,包括隊列和緩衝區初始化

struct vb2_ops {
 
//隊列初始化
 
int(*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,
 
unsigned int *num_buffers, unsigned int*num_planes,
 
unsigned int sizes[], void *alloc_ctxs[]);
 
//釋放和獲取設備操做鎖
 
void(*wait_prepare)(struct vb2_queue *q);
 
void(*wait_finish)(struct vb2_queue *q);
 
//對buffer的操做
 
int(*buf_init)(struct vb2_buffer *vb);
 
int(*buf_prepare)(struct vb2_buffer *vb);
 
int(*buf_finish)(struct vb2_buffer *vb);
 
void(*buf_cleanup)(struct vb2_buffer *vb);
 
//開始視頻流
 
int(*start_streaming)(struct vb2_queue *q, unsigned int count);
 
//中止視頻流
 
int(*stop_streaming)(struct vb2_queue *q);
 
//把VB傳遞給驅動
 
void(*buf_queue)(struct vb2_buffer *vb);
 
};

vb2_buffer是緩存隊列的基本單位,內嵌在其中v4l2_buffer是核心成員。當開始流IO時,幀以v4l2_buffer的格式在應用和驅動之間傳輸。一個緩衝區能夠有三種狀態:

在驅動的傳入隊列中,驅動程序將會對此隊列中的緩衝區進行處理,用戶空間經過IOCTL:VIDIOC_QBUF把緩衝區放入到隊列。對於一個視頻捕獲設備,傳入隊列中的緩衝區是空的,驅動會往其中填充數據;

在驅動的傳出隊列中,這些緩衝區已由驅動處理過,對於一個視頻捕獲設備,緩存區已經填充了視頻數據,正等用戶空間來認領;

用戶空間狀態的隊列,已經經過IOCTL:VIDIOC_DQBUF傳出到用戶空間的緩衝區,此時緩衝區由用戶空間擁有,驅動沒法訪問。

這三種狀態的切換以下圖所示:

state

v4l2_buffer結構以下:

struct v4l2_buffer {
 
         __u32                          index;  //buffer 序號
 
         __u32                          type;   //buffer類型
 
         __u32                          bytesused;  緩衝區已使用byte數
 
         __u32                          flags;
 
         __u32                          field;
 
         struct timeval           timestamp;  //時間戳,表明幀捕獲的時間
 
         struct v4l2_timecode       timecode;
 
         __u32                          sequence;
 
         /*memory location */
 
         __u32                          memory;  //表示緩衝區是內存映射緩衝區仍是用戶空間緩衝區
 
         union {
 
                   __u32           offset;  //內核緩衝區的位置
 
                   unsigned long   userptr;   //緩衝區的用戶空間地址
 
                   struct v4l2_plane *planes;
 
                   __s32                 fd;
 
         } m;
 
         __u32                          length;   //緩衝區大小,單位byte
 
};

當用戶空間拿到v4l2_buffer,能夠獲取到緩衝區的相關信息。Byteused是圖像數據所佔的字節數,若是是V4L2_MEMORY_MMAP方式,m.offset是內核空間圖像數據存放的開始地址,會傳遞給mmap函數做爲一個偏移,經過mmap映射返回一個緩衝區指針p,p+byteused是圖像數據在進程的虛擬地址空間所佔區域;若是是用戶指針緩衝區的方式,能夠獲取的圖像數據開始地址的指針m.userptr,userptr是一個用戶空間的指針,userptr+byteused即是所佔的虛擬地址空間,應用能夠直接訪問。

五、用戶空間訪問設備

下面經過內核映射緩衝區方式訪問視頻設備(capturedevice)的流程。

1>    打開設備文件

fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);
dev_name[/dev/videoX]

2>    查詢設備支持的能力

struct v4l2_capability  cap;
 
ioctl(fd, VIDIOC_QUERYCAP, &cap);

3>    設置視頻捕獲格式

fmt.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
fmt.fmt.pix.width       = 640;
 
fmt.fmt.pix.height      = 480;
 
fmt.fmt.pix.pixelformat= V4L2_PIX_FMT_YUYV;  //像素格式
 
fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;
 
ioctl(fd,VIDIOC_S_FMT, & fmt);

4>    向驅動申請緩衝區

struct  v4l2_requestbuffers req;
 
req.count= 4;  //緩衝個數
 
req.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
req.memory= V4L2_MEMORY_MMAP;
 
if(-1 == xioctl(fd, VIDIOC_REQBUFS, &req))
{
...
}

 

5>    獲取每一個緩衝區的信息,映射到用戶空間

struct buffer {
 
        void  *start;
 
        size_t length;
 
} *buffers;
 
buffers = calloc(req.count, sizeof(*buffers));
 
for (n_buffers= 0; n_buffers < req.count; ++n_buffers) {
 
struct  v4l2_buffer buf;
 
buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
buf.memory      = V4L2_MEMORY_MMAP;
 
buf.index       = n_buffers;
 
if (-1 ==xioctl(fd, VIDIOC_QUERYBUF, & buf))
 
                       errno_exit("VIDIOC_QUERYBUF");
 
buffers[n_buffers].length= buf.length;
 
buffers[n_buffers].start=
 
        mmap(NULL /* start anywhere */,
 
        buf.length,
 
        PROT_READ | PROT_WRITE /* required */,
 
        MAP_SHARED /* recommended */,
 
        fd, buf.m.offset);
 
 }

 

6>    把緩衝區放入到傳入隊列上,打開流IO,開始視頻採集

for (i =0; i < n_buffers; ++i) {
 
    struct v4l2_buffer buf;
 
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
    buf.memory = V4L2_MEMORY_MMAP;
 
    buf.index = i;
 
    if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
 
          errno_exit("VIDIOC_QBUF");
 
 }
 
 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
 if (-1 == xioctl(fd, VIDIOC_STREAMON, & type))
{
...
}

7>  調用select監測文件描述符,緩衝區的數據是否填充好,而後對視頻數據

for (;;) 
{
 
    fd_set fds;
    struct timeval tv;
    int r;
    FD_ZERO(&amp;fds);
    FD_SET(fd,&amp;fds);
     /* Timeout. */
    tv.tv_sec = 2;
    tv.tv_usec = 0;
    //監測文件描述是否變化
    r = select(fd + 1,& fds, NULL, NULL, & tv);
    if (-1 == r) 
    {
        if (EINTR ==errno)
        continue;
        errno_exit("select");
    }
 
    if (0 == r)
    {
        fprintf(stderr,"select timeout\n");
        exit(EXIT_FAILURE);
     }
    //對視頻數據進行處理
 
     if (read_frame())
        break;
    /* EAGAIN - continueselect loop. */
               } 

8>    取出已經填充好的緩衝,獲取到視頻數據的大小,而後對數據進行處理。這裏取出的緩衝只包含緩衝區的信息,並無進行視頻數據拷貝。

buf.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
buf.memory= V4L2_MEMORY_MMAP;
 
if (-1 ==ioctl(fd, VIDIOC_DQBUF, & buf))    //取出緩衝
 
           errno_exit("VIDIOC_QBUF");
 
process_image(buffers[buf.index].start,buf.bytesused);   //視頻數據處理
 
if (-1 ==xioctl(fd, VIDIOC_QBUF, & buf))  //而後又放入到傳入隊列
 
     errno_exit("VIDIOC_QBUF");

9>    中止視頻採集

type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
ioctl(fd,VIDIOC_STREAMOff, & type);

10> 關閉設備

Close(fd);
相關文章
相關標籤/搜索