譯註:在前幾節我們介紹了如何初始化v4l2驅動的框架、查詢能力值、設置輸入/視頻標準/格式,但是還沒有真正地傳輸過一幀數據。
萬事俱備,只欠東風,本節將會重點介紹"流媒體"中的數據流。
流模式,數據流主要通過如下幾種方式進行傳輸:
●read/write接口:這種的基本比較少用。
●內存映射流 I / O:驅動程序分配的內存,mmap()到用戶空間。
●用戶指針流 I / O:由用戶空間分配的內存,由於用戶空間的內存可能是零散的,只在虛擬空間上連續,因此需要分散 - 聚集DMA支持。
●DMABUF流 I / O:由另一個設備驅動分配的內存,導出為DMABUF文件處理程序並在此驅動程序中導入。
本例子使用的是第二種,驅動程序分配內存。
#include <media/videobuf2-dma-contig.h> // 處理videobuf需添加的頭文件一共有三種,該頭文件代表使用的dma內存是連續的 // 離散dma使用 videobuf2-dma-sg.h,用戶空間內存使用 videobuf2-vmalloc.h struct skeleton { ... struct vb2_queue queue; //video buffer放置在該隊列中 struct vb2_alloc_ctx *alloc_ctx; //用於分配內存的上下文 spinlock_t ; //用於streaming的同步,和核心鎖配合使用 struct list_head buf_list; unsigned int sequence; //可以認為是幀號 }; struct skel_buffer { //本地用於管理buffer的結構體 struct vb2_buffer vb; struct list_head list; }; static inline struct skel_buffer *to_skel_buffer(struct vb2_buffer *vb2) { return container_of(vb2, struct skel_buffer, vb); }
/* 處理videobuf需添加的頭文件一共有三種,該頭 * 文件代表使用的dma內存是連續的離散dma使用 * videobuf2-dma-sg.h,用戶空間內存使用 * videobuf2-vmalloc.h */ #include <media/videobuf2-dma-contig.h> struct skeleton { ... struct vb2_queue queue; //video buffer放置在該隊列中 struct vb2_alloc_ctx *alloc_ctx; //用於分配內存的上下文 spinlock_t ; //用於streaming的同步,和核心鎖配合使用 struct list_head buf_list; unsigned int sequence; //可以認為是幀號 }; struct skel_buffer { //本地用於管理buffer的結構體 struct vb2_buffer vb; struct list_head list; }; static inline struct skel_buffer *to_skel_buffer(struct vb2_buffer *vb2) { return container_of(vb2, struct skel_buffer, vb); }
static int skeleton_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { ... q = &skel->queue; q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //代表是視頻捕獲設備,也就是Camera q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; q->drv_priv = skel; q->buf_struct_size = sizeof(struct skel_buffer); q->ops = &skel_qops; /* Required ops for USERPTR types: get_userptr, put_userptr. * Required ops for MMAP types: alloc, put, num_users, mmap. * Required ops for read/write access types: alloc, put, num_users, vaddr * Required ops for DMABUF types: attach_dmabuf, detach_dmabuf, map_dmabuf, * unmap_dmabuf. * get more in videobuf2-core.h */ q->mem_ops = &vb2_dma_contig_memops; //使用v4l2框架的內存申請操作,開發者也可以根據系統情況自行定義 q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->lock = &skel->lock; q->gfp_flags = GFP_DMA32; //系統支持32位dma ret = vb2_queue_init(q); if (ret) goto v4l2_dev_unreg; skel->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); //三種內存類型中只有dma連續內存需要 //alloc_ctx if (IS_ERR(skel->alloc_ctx)) { dev_err(&pdev->dev, "Can't allocate buffer context"); ret = PTR_ERR(skel->alloc_ctx); goto v4l2_dev_unreg; } INIT_LIST_HEAD(&skel->buf_list); spin_lock_init(&skel->qlock); ... vdev->queue = q; ... }
以下這些都根據需要,驅動可以使用v4l2框架的實現或者自行實現。
static struct vb2_ops skel_qops = { .queue_setup = queue_setup, //在內存分配之前從VIDIOC_REQBUFS和VIDIOC_CREATE_BUFS處理程序調用, //如果* num_planes!= 0,在分配後驗證較少數量的buffer。驅動程序 //應該返回* num_buffers中所需的buffer數量以及* num_planes中每個buffer //所需的planar數量;每個planar的大小應該在alloc_ctxs[] 數組中的sizes []數 //組和可選的每平面分配器特定上下文中設置。當從VIDIOC_REQBUFS, // fmt == NULL調用時,驅動程序必須使用當前配置的格式,* num_buffers //是正在分配的buffer總數。當從VIDIOC_CREATE_BUFS,fmt!= NULL調用時, //它描述目標幀格式。在這種情況下,* num_buffers另外被分配到q-> num_buffers。 .buf_prepare = buffer_prepare, //每次buffer從用戶空間queue入和VIDIOC_PREPARE_BUF ioctl時調用;驅動程序可以 //在該回調中的硬件操作之前執行任何初始化;如果返回錯誤,則buffer不 //會在驅動程序中排隊; .buf_queue = buffer_queue, //傳遞buffer vb到驅動程序;驅動器可以在該buffer上開始硬件操作;驅動程序 //應該通過調用vb2_buffer_done()函數返回buffer;它調用STREAMON ioctl之 //後被調用;如果在調用STREAMON之前用戶預排隊buffer,可能在start_streaming //回調之前調用 .start_streaming = start_streaming, //只需調用一次進入“流”狀態;驅動程序可以在調用@start_streaming之前 //接收帶有@buf_queue回調的buffer;驅動程序在count參數中獲取已排隊 //buffer的數量;驅動程序可能會返回錯誤,如果硬件失敗或沒有足夠的 //已排隊buffer,在這種情況下,所有已經由@buf_queue回調給出的buffer無效。 .stop_streaming = stop_streaming, //當'streaming'狀態必須被禁用時調用;驅動程序應停止任何DMA事務或等待, //直到它們完成並返回它從buf_queue()獲得的所有buffer .wait_prepare = vb2_ops_wait_prepare, //釋放調用vb2函數時發生的任何鎖;因此有些驅動直接就命名為XXX_unlock; //它在ioctl需要等待新的buffer到達之前被調用;需要避免阻塞訪問類型中的死鎖 .wait_finish = vb2_ops_wait_finish, //重新獲取在wait_prepare中釋放的所有鎖;等待新的buffer到達後需要在繼續休眠前的操作 }; static const struct v4l2_ioctl_ops skel_ioctl_ops = { ... .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_querybuf = vb2_ioctl_querybuf, .vidioc_qbuf = vb2_ioctl_qbuf, .vidioc_dqbuf = vb2_ioctl_dqbuf, .vidioc_streamon = vb2_ioctl_streamon, .vidioc_streamoff = vb2_ioctl_streamoff, }; static const struct v4l2_file_operations skel_fops = { .owner = THIS_MODULE, .open = v4l2_fh_open, .release = vb2_fop_release, .unlocked_ioctl = video_ioctl2, .read = vb2_fop_read, .mmap = vb2_fop_mmap, .poll = vb2_fop_poll, };
static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[]) { struct skeleton *skel = vb2_get_drv_priv(vq); if (*nbuffers < 3) *nbuffers = 3; *nplanes = 1; sizes[0] = skel->format.sizeimage; alloc_ctxs[0] = skel->alloc_ctx; return 0; }
static int start_streaming(struct vb2_queue *vq, unsigned int count) { struct skeleton *skel = vb2_get_drv_priv(vq); if (count < 2) //這裏控制在啟流前需要queue入buffer的最小個數 return -ENOBUFS; skel->sequence = 0; /* TODO: start DMA */ return 0; } static int stop_streaming(struct vb2_queue *vq) { struct skeleton *skel = vb2_get_drv_priv(vq); struct skel_buffer *buf, *node; unsigned long flags; /* TODO: stop DMA */ /* Release all active buffers */ spin_lock_irqsave(&skel->qlock, flags); list_for_each_entry_safe(buf, node, &skel->buf_list, list) { vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); list_del(&buf->list); } spin_unlock_irqrestore(&skel->qlock, flags); return 0; }
static int buffer_prepare(struct vb2_buffer *vb) { struct skeleton *skel = vb2_get_drv_priv(vb->vb2_queue); unsigned long size = skel->format.sizeimage; if (vb2_plane_size(vb, 0) < size) { dev_err(&skel->pdev->dev, "buffer too small (%lu < %lu)\n", vb2_plane_size(vb, 0), size); return -EINVAL; } vb2_set_plane_payload(vb, 0, size); vb->v4l2_buf.field = skel->format.field; return 0; } static void buffer_queue(struct vb2_buffer *vb) { struct skeleton *skel = vb2_get_drv_priv(vb->vb2_queue); struct skel_buffer *buf = to_skel_buffer(vb); unsigned long flags; spin_lock_irqsave(&skel->qlock, flags); list_add_tail(&buf->list, &skel->buf_list); /* TODO: update any DMA pointers if necessary */ spin_unlock_irqrestore(&skel->qlock, flags); }
static irqreturn_t skeleton_irq(int irq, void *dev_id) { struct skeleton *skel = dev_id; /* TODO: handle interrupt */ if (captured_new_frame) { ... spin_lock(&skel->qlock); list_del(&new_buf->list); spin_unlock(&skel->qlock); new_buf->vb.v4l2_buf.sequence = skel->sequence++; v4l2_get_timestamp(&new_buf->vb.v4l2_buf.timestamp); vb2_buffer_done(&new_buf->vb, VB2_BUF_STATE_DONE); } return IRQ_HANDLED; }
最後,在如下函數中
skeleton_s_input()
skeleton_s_std()
skeleton_s_dv_timings()
skeleton_s_fmt_vid_cap()
增加這項檢查:
if(vb2_is_busy(&skel->queue))
return-EBUSY;
Tags: streaming sequence 虛擬空間 include return
文章來源: