這裏結合芯片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不能用
設置第一幀還是中間真還是最後一針