这里结合芯片exynos 4412介绍一下V4L2用来视频编解码的驱动结构
内核代码基于3.4.106
linux-3.4.106\drivers\media\video\s5p-mfc
linux-3.4.106\drivers\media\video
1,V4L2结构
主要接口(ioctl下面的一层)
vidioc_qbuf
vidioc_dqbuf
vidioc_reqbufs
vidioc_s_fmt
3,主要数据结构
struct vb2_queue
struct v4l2_buffer
struct s5p_mfc_ctx
struct vb2_buffer
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE是未解码数据,存放ES流数据
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE是已经解码数据,存放frame data buffer
v4l2_qbuf流程
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流程
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来对应起来
初始化分配 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]) 这里申请的内存,存放解吗前的数据
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用
中断处理流程
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不能用
设置第一帧还是中间真还是最后一针