结合芯片exynos 4412介绍V4L2用来视频编解码的驱动

这里结合芯片exynos 4412介绍一下V4L2用来视频编解码的驱动结构
内核代码基于3.4.106
 linux-3.4.106\drivers\media\video\s5p-mfc
 linux-3.4.106\drivers\media\video

1,V4L2结构

结合芯片exynos 4412介绍V4L2用来视频编解码的驱动_第1张图片



2,几个主要接口
主要接口(ioctl下面的一层)
vidioc_qbuf
vidioc_dqbuf
vidioc_reqbufs
vidioc_s_fmt


3,主要数据结构
struct vb2_queue
struct v4l2_buffer
struct s5p_mfc_ctx
struct vb2_buffer



4,接口调用链

V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE是未解码数据,存放ES流数据
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE是已经解码数据,存放frame data buffer

v4l2_qbuf流程
结合芯片exynos 4412介绍V4L2用来视频编解码的驱动_第2张图片





vidioc_qbuf--vb2_qbuf

---  __enqueue_in_driver

----  q->ops->buf_queue(vb);

----  s5p_mfc_buf_queue

----   list_add_tail(&mfc_buf->list, &ctx->dst_queue);

----    s5p_mfc_try_run





v4l2_dqbuf流程

结合芯片exynos 4412介绍V4L2用来视频编解码的驱动_第3张图片




解出来一帧:

s5p_mfc_irq

---   s5p_mfc_handle_frame

---   s5p_mfc_handle_frame_new

---  vb2_buffer_done

---    wake_up(&q->done_wq);

----   list_add_tail(&vb->done_entry, &q->done_list); 把解出来的一帧挂上队列


vidioc_dqbuf

---  vb2_dqbuf

---  __vb2_get_done_vb

---  __vb2_wait_for_done_vb(查找是否有可用的vb)

---   wait_event_interruptible(q->done_wq,





5,内存管理方式


主要队列:
分为capture plane(解码后)和output plane(解码前)

从另外一个维度看,
每个plane都有一个done_list队列,表示解码完比的,不用的ES buffer,或者存有有效YUV数据的Frame data buffer,用户态dqbuffer就从这里面取
每个plane都有一个另外的queue队列,表示要解码的ES buffer,或者已经显示完毕的YUV数据的Frame data buffer,用户态qbuffer就从这里取

v4l2-core操作的是vb2-buffer,  这只是个handle而已,实际给MFC的是5p_mfc_buf , 这两种buffer通过v4l2-buffer里面的index来对应起来






结合芯片exynos 4412介绍V4L2用来视频编解码的驱动_第4张图片


初始化分配 input buffer:

vidioc_reqbufs

----vb2_reqbufs

----__vb2_queue_alloc(挂到q->bufs)

---__vb2_buf_mem_alloc

---- call_memop(q, alloc, q->alloc_ctx[plane]

----vb2_dma_contig_alloc

----dma_alloc_coherent分配dmabuffer

----  call_qop(q, buf_init, vb);

---  s5p_mfc_buf_init(相关信息填充到 ctx->src_bufs[i])    这里申请的内存,存放解吗前的数据



初始化分配 out buffer

vidioc_reqbufs

----vb2_reqbufs

----__vb2_queue_alloc(挂到q->bufs)

---__vb2_buf_mem_alloc

---- call_memop(q, alloc, q->alloc_ctx[plane]

--- vb2_dma_contig_alloc

----dma_alloc_coherent分配dmabuffer

---- call_qop(q, buf_init, vb);

---s5p_mfc_buf_init(相关信息填充到 ctx->src_bufs[i]) 这里申请的内存,存放解码完的数据


分配buffer给mfc用

vidioc_reqbufs---s5p_mfc_alloc_codec_buffers---vb2_dma_contig_alloc //分配buffer给mfc用。



中断处理流程

s5p_mfc_irq

---s5p_mfc_handle_seq_done

---s5p_mfc_try_run

----s5p_mfc_set_dec_frame_buffer

---mfc_write(dev, OFFSETA(ctx->dst_bufs[i].cookie.raw.chroma),S5P_FIMV_DEC_CHROMA_ADR + i * 4);

把所有out buffer的地址写进MFC

---s5p_mfc_set_dec_stream_buffer

---把此次要解码的原始数据in buffer地址写进MFC



有效数据存在哪里?

解码前的和解码后的,都在vb->planes[plane].mem_priv里面,这个mem_priv是struct vb2_dc_buf ,里面记录了这块DMA内存的虚拟地址和物理地址,都是在vidioc_reqbufs----vb2_reqbufs----__vb2_queue_alloc(挂到q->bufs)---__vb2_buf_mem_alloc---- call_memop(q, alloc, q->alloc_ctx[plane]---------vb2_dma_contig_alloc的时候记录好的。
然后这些内存的物理地址,在vidioc_reqbufs----vb2_reqbufs----__vb2_queue_alloc(挂到q->bufs)--> call_qop(q, buf_init, vb);--->s5p_mfc_buf_init时候,赋给了ctx->dst_bufs[i].cookie.raw.luma (解码后)   ctx->dst_bufs[i].cookie.raw.chroma(解码后)    ctx->src_bufs[i].cookie.stream ,(解码前)
然后这些地址,在s5p_mfc_set_dec_stream_buffer   ,s5p_mfc_set_dec_frame_buffer时候写进了寄存器,告诉MFC具体地址




Mmap/ querybuf
s5p_mfc_mmap通过 (offset 与 DST_QUEUE_OFF_BASE)来判断是vq_src还是vq_dst ,这个offset是querybuf的时候填上buf->m.planes[i].m.mem_offset的。
buf->m.planes[i].m.mem_offset 是__vb2_queue_alloc里面__setup_offsets的时候,为每个plane的内存写上的vb->v4l2_planes[plane].m.mem_offset = off;  这个mem_offset实际上没什么用。实际上就是个标志位。就是为了mmap的时候能通过这个mem_offset找到每个plane的内存__find_plane_by_offset
这样s5p_mfc_mmap里面通过这个offset判断是ctx->vq_src还是ctx->vq_dst,然后调用vb2_mmap,通过__find_plane_by_offset找到对应vb2_buffer和vb2_buffer里面的plane,然后通过相应的vb->planes[plane].mem_priv,就可以调用vb2_dma_contig_mmap---remap_pfn_range调用标准内核API来mmap了
实际上整个mmap的过程就是找到对应的buffer和plane,一个个的mmap的



6 ,编解码参数设置在那里?


vidioc_s_fmt里面通过fmt = find_format(f, MFC_FMT_DEC);找到static struct s5p_mfc_fmt formats[]里面对应的要解码的格式,然后对struct s5p_mfc_ctx *ctx进行赋值,以便后面使用


V4L2最终要通过对MFC的寄存器读写来控制解码过程

1,启动需要操作的寄存器
MFC poweron 
clk
载入固件
重启MFC
判断固件版本
为两个plane分配内存
初始化各种等待,以及数据队列


2,开始播放需要的寄存器

s5p_mfc_run_init_dec
分配一个tmp buffer,给MFC用
设置sharememory
设置slice,delay之类的
设置第一帧
 
3,播放一帧:
s5p_mfc_run_dec_frame
设置ES流数据的地址和size
告诉MFC哪些buffer不能用
设置第一帧还是中间真还是最后一针























你可能感兴趣的:(linux,驱动开发)