淺析 Hi MPP 中的 uvc_app


以往我們說UVC一般搜索到的內容是板端作為主機,外接USB視頻設備並使用UVC去控制,那么板端也就是從機中的UVC是如何實現的。下面就記錄一個海思SDK中的例子,源碼路徑HISDK/mpp/sample/uvc_app

文件描述

文件 說明
application.c 主函數起始
hiuac.c 提供hiuac對象,負責音頻控制
hiuvc.c 提供hiuvc對象,負責視頻控制
camera.c 提供hicamera攝像頭對象,負責hiuvc,hiuac對象控制
uvc_gadget.c 實現uvc設備操作功能
frame_cache.c 實現uvc緩存操作功能
histream.c 實現視頻流操作功能
sample*.c 實現對接mpp媒體開發框架操作功能

對象操作

以文件划分功能,文件的操作函數都為靜態文件作用域(對外部不可見),這些函數最終被賦值到變量中,而這個變量也是靜態的,只能被唯一一個的全局作用域函數get_xx()獲取。

static int __init(void){};
static int __run(void){};

static hicamera __hi_camera =
{
    .init = __init,
    .run = __run,
};

hicamera *get_hicamera(void)
{
    return &__hi_camera;
}

對象分析

直接進入正題以hiuvc對象為切入點,它由初始化、打開、關閉和運行四個部分組成。對象主要負責流程控制,不包含具體實現,其中__init並沒有做任何事,__open__close為直接調用,__run創建了線程uvc_send_data_thread去循環run_uvc_data,然后主線程就進入循環run_uvc_device狀態。通過查找可以發現這些對象支持函數都指向文件uvc_gadget.c

__open

深入open_uvc_device函數,最后可以看出它的最終執行的是v4l2常規流程,首先open設備視頻設備節點獲得fd,其次ioctl VIDIOC_UERYCAP去查詢v4l2能力,最后再ioctl VIDIOC_SUBSCRBE_EVENT去設定訂閱事件,如:VC處理(UVC_EVENT_SETUP),VS處理(UVC_EVENT_DATA),開啟流(UVC_EVENT_STREAMON), 停止流(UVC_EVENT_STREAMOFF)。

__close

關閉是打開的相反操作,主要是去close掉打開的描述符,在這之前需要關閉視頻能力。

__run

這個函數會創建線程循環run_uvc_data,自身進入run_uvc_device循環。

run_uvc_data

這個功能塊就負責一件事,在流啟動后去監聽視頻設備描述符,就緒時就通過uvc_video_process_userptr把一幀數據推入UVC視頻緩沖區,具體功能實現先跳過。

run_uvc_device

這個功能塊負責執行UVC事件處理,函數通過select監聽描述符,當描述符就緒時就從視頻設備的事件隊列中出隊一個事件並做處理。當初始化事件完成后會觸發UVC_EVENT_STREAMON事件,對應的執行enable_uvc_video()去啟動流。當不需要據流時觸發UVC_EVENT_STREAMOFF事件去執行disable_uvc_video()停止流。

至此整個框架基本完成,首先open_uvc_device打開視頻設備驅動,其次run_uvc_device去控制應用層的視頻流開啟關閉,最后通過run_uvc_data推入流到驅動向外輸出。接下來就看看數據是如何被啟動關閉的又是如何流轉的,這里就需要提到uvc_cache視頻設備緩存管理。

數據緩存

uvc_cache它由有6個幀節點和2個幀隊列組成。其中幀隊列free_queue表示空閑節點隊列,初始化時得到了所有節點的。幀隊列ok_queue表示完成節點隊列,當節點填充完數據后才會被put到這個隊列當中。

create_uvc_cache
    create_cache_node_list
        node = malloc //創建6塊
        put_node_to_queue(uvc_cache->free_queue, node)

數據制造

前面提到,當觸發UVC_EVENT_STREAMON事件是會執行enable_uvc_video去啟動流,可用看到啟動通常是先經過清理關機再開機的方式。直接進入histream_startup()這個函數動作,以看到最終創建了一條線程不斷去監聽Venc描述符,當圖像就緒時去獲取保存。首先會從空閑隊列free_queue中取出節點,然后填充幀數據,最后put到完成隊列ok_queue中,這是節點在隊列中的第一次位置交換。函數最后可用看到dev-streaming被置1這就標志着流被開啟,上面說到的run_uvc_data根據這個狀態就可以開始推數據了。

數據首次消費

到這里數據流是開啟了,但是初始化並沒有完成,對於視頻設備/dev/video目前也就僅經歷了open和訂閱UVC_EVENT_操作。接下來enable_uvc_videoioctl VIDIOC_REQBUFS去命令驅動申請緩存空間。接着從完成隊列ok_queue中取出節點,並將節點成員node->mem賦值到v4l2_bufioctrl VIDIOC_QBUF入隊到內核緩存空間中,這個node也還被記錄在等待隊列__waited_node[]上表示這個節點真正被處理。到這里就完成了視頻幀的第一次消費。

數據后續消費

初始化后,數據制造者_SAMPLE_COMM_VENC_SaveData()會不斷的從free_queue取出節點填充並掛到ok_queue上。而后續的消費工作也交回到線程的run_uvc_data去處理。可以看到首先會ioctl VIDIOC_DQBUF從內核出隊一個幀,並從完成隊列取出一個節點,被出隊的幀號也對應等待隊列的標號,幀可以出隊就表示它被處理完了,這時對應等待隊列中的節點__waited_node[buf->index] 就可以把他放回空閑隊列中去,並記錄下本次取出節點。同樣節點成員node->mem也將通過v4l2_buf被ioctrl到內核中。這各過程被重復執行就實現了內核與用戶數據源源不斷的輪換。

結束工作

UVC_EVENT_STREAMOFF事件到來時,表示結束當前工作,首先ioctl VIDIOC_STREAMOFF停止內核流傳輸,接着關閉應用成流生產, 最后清空完成隊列ok_queue


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM