張倩敏,劉 擁,吳其林,劉 運
(巢湖學院 信息工程學院,安徽 巢湖 238000)
隨著互聯(lián)網(wǎng)通信的快速發(fā)展和5G技術(shù)的普及,各種直播和短視頻平臺開始快速涌現(xiàn),視頻已經(jīng)成為我們獲取外界信息最快速的方式之一[1]。隨著多媒體技術(shù)的不斷發(fā)展,視頻編解碼的應用場景也越來越復雜,衍生出多種不同的使用領域,如stateless codecs、region of interest(ROI)、High-Dynamic Range (HDR10+)[2-3]、Complex Camera、Codec V2等。V4L2[4-5]的框架已經(jīng)不能滿足這些復雜應用場景的性能需求,為了解決部分場景的應用問題,kernel 4.20后在圖像輸入端添加了Request api新架構(gòu)。
在視頻編碼時,需要對每一張輸入圖像設置不同的參數(shù)(metadata),再讓硬件去編碼。如果不借助Request api,直接調(diào)用V4L2的接口送一張圖像數(shù)據(jù)和編碼需要的參數(shù)給硬件進行處理,等硬件處理完這張圖像后才能送下一張圖像的數(shù)據(jù)和參數(shù)。在引入Request api架構(gòu)后,V4L2可以調(diào)用media request的相關(guān)接口把每一張圖像數(shù)據(jù)和對應的參數(shù)進行綁定,用戶只需要調(diào)用V4L2的接口把數(shù)據(jù)連續(xù)不斷地送給硬件處理,不用擔心圖像數(shù)據(jù)和參數(shù)匹配錯誤的問題,可以有效地提高數(shù)據(jù)并行處理的能力,尤其在user space等需要大量數(shù)據(jù)計算的場景中效率提高得非常顯著。
鑒于Linux開源、穩(wěn)定且具有良好的移植性、完善的參考文獻和龐大的用戶群體,Linux系統(tǒng)的使用越來越廣泛。尤其是隨著嵌入式系統(tǒng)的快速發(fā)展,Linux已經(jīng)滲透到我們生活中的方方面面。智能電視、安防監(jiān)控、智能手機,甚至在汽車多媒體中都可以看到Linux系統(tǒng)的身影。
傳統(tǒng)視頻多媒體服務層的架構(gòu)如圖1所示,每一種多媒體框架都有一個服務層來驅(qū)動硬件進行工作,如Android 系統(tǒng)的OMX和Codec V2,用于汽車電子的Gstreamer[6-8],用于嵌入式領域的QT,google新開發(fā)的stateless codecs等。若有多個框架,則需要有多套驅(qū)動程序進行適配,移植性很差且給開發(fā)者帶來很大的工作量。
圖1 傳統(tǒng)多媒體架構(gòu)
隨著技術(shù)的發(fā)展,還會有越來越多的新架構(gòu)產(chǎn)生。若對每一種架構(gòu)都寫一個服務層驅(qū)動視頻編解碼硬件進行工作,需要消耗大量的時間和成本。
為了方便用戶的開發(fā)和接口的統(tǒng)一,Linux系統(tǒng)提供了一整套標準的接口供使用。如圖2所示,用戶只需要調(diào)用這些接口開發(fā)一個公共的服務層,就可以讓所有的多媒體框架共用同一套驅(qū)動程序,有效地節(jié)省了開發(fā)時間,提高了工作效率。
圖2 V4L2多媒體框架結(jié)構(gòu)
視頻應用的多元化和多樣性在不同領域發(fā)生著較大的變化。比如:為了發(fā)揮出硬件的最大性能,google開發(fā)了新的架構(gòu)stateless codecs;在android 多媒體架構(gòu)中為了支持更多的功能,添加了新框架Codec V2取代原有的OMX;在使用手機時,希望拍出色彩更加飽和的圖片和視頻,提出了Complex Camera;在汽車電子中,為了防止剮蹭,希望可以看到周圍360度的全景影像,全景影像系統(tǒng)Around View Monitor(AVM)由此誕生。這些復雜的架構(gòu)對軟件的性能要求也越來越高,Request api設計的思想就是用來解決在這些復雜的應用領域中現(xiàn)階段V4L2框架無法工作或性能較差的問題。
Request api對media controller api進行擴展,把media controller device和video device鏈接在一起,借助media controller device讓每一張輸入圖像都可以攜帶自己的私有參數(shù),視頻編解碼硬件可以根據(jù)不同的參數(shù)對輸入圖像進行不同的特殊處理。
非Request api的工作方式如圖3所示。硬件在處理每一張圖像時,需要把輸入圖像數(shù)據(jù)和參數(shù)(metadata)一起送給視頻編解碼硬件,待硬件處理完這張圖像數(shù)據(jù)后,User space才能送下一張圖像數(shù)據(jù)和參數(shù)給視頻編解碼硬件進行處理,所有的圖像只能按照串行的方式進行工作,否則就會出現(xiàn)數(shù)據(jù)覆蓋的問題,對于性能要求較高的使用場景,無法滿足需求。
圖4是引入Request api后的工作方式,圖像數(shù)據(jù)和參數(shù)(metadata)通過media controller device綁定在一起,在硬件需要處理某一張圖像時,調(diào)用media controller device的接口獲取當前這張圖像攜帶的參數(shù)(metadata)。User space 不斷地向視頻編解碼硬件送數(shù)據(jù)和參數(shù),而不用擔心數(shù)據(jù)覆蓋的問題,硬件則連續(xù)地從隊列中獲取數(shù)據(jù)進行編解碼,這樣硬件就可以一直處于高速的工作狀態(tài),最大性能地發(fā)揮出硬件的處理能力,高效地達到并行處理數(shù)據(jù)的目的,也給后面更加復雜的應用場景提供可行性的依據(jù)。
圖4 Request api編解碼架構(gòu)
為了讓V4L2架構(gòu)支持Request api,需要在kernel中設置一些參數(shù)讓V4L2架構(gòu)[9]的Request api可以正常工作,可以按照圖5的流程進行設置,主要包括以下幾個部分:
圖5 Request api參數(shù)設置流程圖
(1)注冊media device和video device
Request api使用了media controller device framework的功能,需要在probe函數(shù)中調(diào)用函數(shù)v4l2_m2m_register_media_controller()和media_device_register()注冊media device,調(diào)用函數(shù)video_device_register()和v4l2_m2m_init()注冊video device。
(2)實現(xiàn)media device operations
Media device 的callback函數(shù)比較多,需要實現(xiàn)其中的兩個req_validate和req_queue。實現(xiàn)的callback函數(shù)如下:
struct media_device_ops media_ops = {
.req_validate = v4l2_m2m_req_validate,
.req_queue = v4l2_m2m_req_queue};
(3)初始化videobuf2 queue list
struct vb2_queue結(jié)構(gòu)體變量中有兩個重要的參數(shù)supports_requests和requires_requests,需要把這兩個變量的值設置為true。support_requests表示隊列支持Request api,requires_requests表示當前隊列需要Request api的支持,如果support_requests的值為true,requires_requests的值也必須設置為true。
(4)初始化videobuf2 operations
Videobuf2的callback函數(shù)也比較多,buf_out_validate 和buf_request_complete是Request api特有的,如果支持Request api必須要實現(xiàn),否則就會出錯。其余的callback函數(shù)是Request api和非Request api共用的,根據(jù)需要進行實現(xiàn)。
(5) 初始化CID controls
需要按照設置CID的標準方式對支持的不同CID controls[10]進行設置,保證user space設置當前圖像的參數(shù)(metadata)能夠按照指定的格式正確地設置到kernel中給硬件使用,設置CID controls調(diào)用的函數(shù)如下:
struct v4l2_ctrl_handler *output_hdl;
struct v4l2_ctrl * request_ctrl;
v4l2_ctrl_handler_init(output_hdl, count);
request_ctrl = v4l2_ctrl_new_custom (output_hdl, &request_ctrls[i].cfg, NULL);
v4l2_ctrl_handler_setup(output _hdl);
按照以上的步驟設置完成后,就可以把kernel的Request api功能打開,在output queue(輸入圖像隊列)上使用這個功能。
由于capture queue(輸出圖像隊列)上無法使用Request api,對于需要把解碼過程中的一些狀態(tài)信息帶給user space的使用場景,現(xiàn)有的框架無法實現(xiàn)。比如:在智能電視和高清機頂盒的使用過程中,由于屏幕較大,用戶希望輸出圖像的色彩更加鮮艷,需要把HDR信息和輸出圖像數(shù)據(jù)一起丟給后級進行顯示;在安防監(jiān)控領域,希望根據(jù)不同的場景標記出感興趣的事物:行人軌跡、車輛信息等,把ROI[11-12]的信息和輸出圖像進行配對供檢索;獲取視頻編解碼硬件在實際使用過程中的一些狀態(tài)信息判斷當前硬件的工作狀態(tài)等場景。本文對Request api架構(gòu)做了較大的擴充和改進,提出一種可行性的方案可以讓Request api支持capture queue,實現(xiàn)的具體過程如圖6所示。
圖6 Request api在capture queue中的實現(xiàn)過程
(1)添加支持capture queue CID controls的control hanlder。在struct v4l2_fh 結(jié)構(gòu)體中添加變量:struct v4l2_ctrl_handler *cap_handler,所有capture queue用到的CID controls(用來存放每張圖對應的數(shù)據(jù)信息)都會指向這個handler,用來和output queue的handler作區(qū)分。
(2)設置capture queue的capability屬性。在函數(shù)fill_buf_caps()中添加capture queue的capability屬性:
if (q->supports_ro_requests)
*caps |= V4L2_BUF_CAP_SUPPORTS_NEW_REQUESTS;
(3)在函數(shù)v4l2_m2m_qbuf中把判斷當前隊列是capture queue時就會報錯的條件移除,并且在函數(shù)v4l2_m2m_request_queue中添加對capture queue的支持, m2m_ctx_obj=container_of(vb-> vb2_queue, struct v4l2_m2m_ctx, cap_q_ctx.q)。
(4)在函數(shù)v4l2_ctrl_request_hdl_find()中添加判斷條件,如果當前capture buffer沒有申請新的內(nèi)存空間去存放當前這張圖對應的數(shù)據(jù):HDR或ROI,則申請一塊新的內(nèi)存空間,同時根據(jù)ref fd返回當前capture buffer對應的參數(shù)指針。
(5)在set/get ext controls時,移除判斷當前CID control是capture queue就返回錯誤的條件。添加接口,根據(jù)CID controls判斷屬于output queue還是capture queue,并調(diào)用output queue或capture queue的CID controls handler把參數(shù)填到對應的內(nèi)存中。
(6)因為capture queue和output queue的狀態(tài)在數(shù)據(jù)處理過程中是不一樣的。Output queue在使用時先set CID controls再get CID controls,而capture queue只需要get CID controls,因此狀態(tài)變化和output queue有點差異,需要在函數(shù)media_request_object_bind()中對capture queue的狀態(tài)做判斷,對不同的queue做不同的處理。
在V4L2架構(gòu)中需要按照以上的過程添加對capture queue的支持,讓每一個capture buffer都有自己獨立的參數(shù)信息而不會被覆蓋,使HDR、ROI等需要把capture buffer和參數(shù)信息做綁定的場景得以實現(xiàn),達到并行處理數(shù)據(jù)的目的。
output queue(輸入圖像)和capture queue(輸出圖像)在user space中的使用方法一樣,只是在kernel中會有不同的邏輯,Request api在user space 中的使用分成兩部分:
設置的具體過程如圖7所示。需要特別注意media fd、codec fd和req fd這3個file handle的區(qū)別,否則會使用錯誤的device,導致整個功能不能正常工作。media fd是打開media controller device的file handle(/dev/mediaX);codec fd是打開 video device的file handle(/dev/videoX);req fd 是通過調(diào)用media controller device的接口 MEDIA_IOC_REQUEST_ALLOC獲取的media request file handle。
(1)調(diào)用media controller device的接口MEDIA_IOC_REQUEST_ALLOC設置圖像參數(shù)(metadata)的file handle(req fd);
(2)調(diào)用VIDIOC_S_EXT_CTRLS將當前圖像的參數(shù)輸送給視頻編解碼硬件;
(3) 調(diào)用VIDIOC_QBUF將當前圖像的數(shù)據(jù)輸送給視頻編解碼硬件;
(4)調(diào)用MEDIA_REQUEST_IOC_QUEUE[13-14]把當前圖像的參數(shù)和圖像數(shù)據(jù)在kernel中進行綁定,在kernel中就可以根據(jù)圖像數(shù)據(jù)獲取對應的參數(shù)信息,視頻編解碼硬件就可以正常工作。
圖7 user space中使用Request api的方法
視頻編解碼硬件處理完圖像數(shù)據(jù)后,會通知kernel發(fā)送處理完成的event事件給user space。user space有一個單獨的線程一直polling是否有來自kernel處理完成的event事件,當polling到信息后,調(diào)用MEDIA_REQUEST_IOC_REINIT通知kernel當前這張圖像對應的緩沖區(qū)已經(jīng)處理完成,這個緩沖區(qū)現(xiàn)在處于空閑狀態(tài),user space便可把下一張待處理的圖像數(shù)據(jù)填充到這個緩沖區(qū)中并且把當前這張圖像攜帶的參數(shù)信息調(diào)用VIDIOC_S_EXT_CTRLS送到kernel中。如此循環(huán)往復,就可以把所有的視頻編解碼數(shù)據(jù)處理完成。需要注意的是如果不調(diào)用MEDIA_REQUEST_IOC_REINIT[15]就直接設置下一張圖像的數(shù)據(jù)給視頻編解碼硬件,因為kernel此時還以為當前數(shù)據(jù)緩沖區(qū)還在使用,就會發(fā)送錯誤的返回值到user space,整個處理流程就會被阻塞。
對于HDR、ROI需要在capture queue上使用的場景,在硬件編解碼結(jié)束后把數(shù)據(jù)寫到對應的內(nèi)存空間中,user space在polling到event事件后就可以拿到對應的信息。User space polling event事件和REINIT的具體調(diào)用過程如圖8所示:
圖8 Request api buffer回收過程
本研究在嵌入式平臺上對Request api和非Request api兩種架構(gòu)的性能做了詳細的測試,測試結(jié)果如下。
讓Request api和非Request api都設置相同的參數(shù)到kernel給硬件進行處理。處理的原始數(shù)據(jù)都是一樣的,以處理1000張圖的數(shù)據(jù)進行對比,1路和10路的測試數(shù)據(jù)如表1所示:
表1 兩種架構(gòu)處理時間的對比結(jié)果 ms
從上面的測試結(jié)果看出,因為Request api是讓軟件的處理流程達到并行,所以Request api和非Request api的硬件處理時間是固定的。但在軟件的處理時間上,Request api要比非Request api好很多。對于1路播放,1000張圖就快了88 ms,對應硬件的時間可以多處理44張圖;而對于10路播放,因為系統(tǒng)的loading變大,相應的軟件處理時間變長,Request api的優(yōu)勢更加明顯。
Request api在capture queue上的使用以ROI為例進行測試。假設每張圖對應的感興趣區(qū)域為2個不同的矩形框,每張圖矩形框的坐標都會變大一個像素,達到寬和高的最大值后再重新計算。測試結(jié)果如圖9所示,與實際使用過程中的原理類似,只需要把對應的CID controls的參數(shù)填好,無論需要什么數(shù)據(jù)都是可以通過ref fd獲取的。
圖9 Request api在capture queue上的測試結(jié)果
綜上,隨著信息技術(shù)和嵌入式系統(tǒng)的快速發(fā)展,新產(chǎn)品的更新?lián)Q代也越來越快,同時用戶對視頻畫質(zhì)的要求也越來越高,從原來的480 P到現(xiàn)在2 K的普及,給傳統(tǒng)的視頻編解碼架構(gòu)帶來很大的挑戰(zhàn),也不利于驅(qū)動程序的維護和使用。若能夠按照V4L2的標準接口讓每個產(chǎn)品線在user space service層的對下接口都能夠統(tǒng)一,勢必會給視頻編解碼工程師帶來巨大的收益和效率的提升。本文對Request api的架構(gòu)做了詳細的研究,并對capture queue上使用Request api做了擴展和改進。
同時Linux社區(qū)也在不斷地更新V4L2框架,讓V4L2的功能變得越來越強大,以應對現(xiàn)階段或以后能想到的各種復雜的應用和需求,Linux社區(qū)也會給使用者提供非常詳細的參考文檔,以便讓更多的使用者和開發(fā)者參與其中。