天天看點

鴻蒙媒體子系統解讀-編碼錄像流程解讀

鴻蒙媒體子系統解讀-編碼錄像流程解讀

本文作者:江蘇潤和軟體股份有限公司 王高浩

一、鴻蒙媒體子系統簡介

媒體子系統旨在為多媒體應用開發者開發者提供統一的開發接口,使得開發者可以專注于應用業務的開發,輕松使用多媒體的資源。下圖分别展現媒體子系統的架構及業務流程。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖 1 媒體子系統架構圖

如圖1,多媒體架構支援相機、錄像和播放業務功能,這些功能支援鴻蒙JS應用開發及各種使用媒體能力的KIT子產品開發,系統架構包括framework層,framework對外提供應用調用的native接口及其對應的業務實作,針對相機、錄像及播放業務,framework實作了音視訊輸入輸出,音視訊編解碼,視訊檔案的打包及解複用等功能。core service層,core service利用平台提供的能力去實作對底層硬體及相關驅動使用,另外core server實作檔案管理,存儲管理及日志管理。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖 2 多媒體業務流程圖

如圖2,多媒體包括Camera,Recorder和Player,Camera提供YUV、RGB、JPEG以及H264,H265資料到共享記憶體surface中,Recorder子產品将surface中h264/h265資料和音頻aac資料打包成mp4檔案,Player子產品把mp4檔案解複用成音頻和視訊資料,分别送入對應解碼器解碼,然後進行播放。

目錄結構

表1 輕量級多媒體子系統源代碼目錄結構

名稱 描述
foundation/multimedia/frameworks 内部架構實作,包括Audio,Camera,Player,Recorder
foundation/multimedia/interfaces/kits 應用接口對外頭檔案
foundation/multimedia/services/media_lite 應用接口底層服務實作
foundation/multimedia/utils/lite 應用接口通用子產品實作
foundation/multimedia/hals 硬體平台相關媒體适配接口頭檔案

使用

Native應用接口調用可以參考applications/sample/camera/media下demo實作。應用開發者使用多媒體接口實作錄像、預覽和播放音視訊,使用可以參考《多媒體開發指南》。

本文解讀媒體子系統裡面的編碼錄像流程。

二、編碼錄像流程涉及的各子產品

1、錄像部分(Recorder子系統)的各功能子產品

子產品(類)名稱 功能
Recorder 用于實作recorder功能的對外接口類,針對錄像的各個方面做設定:設定源、音視訊編碼格式、視訊尺寸、視訊幀率、視訊碼率、音頻通道數、最大錄像時長、錄像檔案格式。。。以及一些操作:準備、開始、停止、暫停、繼續、重置、釋放。
RecorderImpl Recorder類的具體實作。該類有兩個成員變量:sourceManager_和recorderSink_,前者負責音視訊的碼流的擷取,後者負責錄像檔案的寫入。對Recorder類的所有設定和操作其實都由這兩個變量實作。
RecorderSink 與寫錄像檔案功能相關。設定檔案句柄、最大時長、最大位元組數、建立複用器(muxer)、增加track、寫檔案、設定檔案格式、用回調處理一般性消息和錯誤消息等。
RecorderVideoSource 基本上的功能就是設定buffer和擷取視訊碼流
RecorderAudioSource 基本上的功能就是設定建立音頻源和音頻編碼器,以及讀取音頻流。

2、視訊編碼部分的各功能子產品

子產品(類)名稱 功能
CameraDevice 作用把CameraAbility(幀率和分辨率)裡的參數傳入設定攝像頭,并初始化成員變量prcessorHdls_(處理器句柄)和prcessorAttrs_(處理能力屬性)。并在運作之前把攝像頭的輸出狀态設定為以下三者之一:Record、Preview、Capture,并根據輸出狀态設定對應的assistant。以及執行擷取碼流。
RecordAssistant CameraDevice類所擁有的用于錄像的類,它的功能有設定視訊編碼器句柄、設定視訊碼流緩沖區、設定響應碼流生成完畢消息的回調函數、以及開始執行編碼。

需要注意的是音頻的采集和編碼功能是在Recorder子系統獨立完成的,而視訊的采集編碼功能在CameraDevice和RecordAssistant子產品中完成。

三、編碼錄像流程代碼解讀

applications\sample\camera\media\camera_sample.cpp是一個打開攝像頭并且進行錄像、預覽、截圖的例子,通過例子程式可以深入了解媒體子系統的編碼錄像流程。下文先對代碼做盡可能的精簡,再對剩餘代碼做簡要的解釋。

static int32_t SampleGetRecordFd()
{
	//建立一個字尾為mp4的檔案并傳回檔案句柄,用于儲存錄像檔案。
}
Recorder *SampleCreateRecorder()
{
//1、規定各種音視訊編碼參數
//2、Recorder *recorder = new Recorder();
    //3、用這些音視訊參數設定recorder
    //4、return recorder;
}
//該類可以處理相機狀态變化的回調;同時該類還可以執行record、preview、capture操作。
class SampleCameraStateMng : public CameraStateCallback {
public:
    int PrepareRecorder()
    {
recorder_ = SampleCreateRecorder();		//建立Recorder對象
recordFd_ = SampleGetRecordFd();		//建立錄像檔案句柄
    }
void StartRecord()		//執行Record
    {
        int ret = PrepareRecorder();			//建立Recoder
        ret = recorder_->SetOutputFile(recordFd_);
        ret = recorder_->Prepare();			//設定Muxer
        ret = recorder_->Start();				//等待audioSource/videoSource裡面的碼流資料,寫入Muxer
        //建立并設定FrameConfig *fc,主要是把surface設定進去
        ret = cam_->TriggerLoopingCapture(*fc);	//觸發錄像
    }
};
int main()
{
    char input;
    while (cin >> input) {
        switch (input) {
            case '2':
                CamStateMng.StartRecord();		//執行record
                break; 
            default:
                SampleHelp();
                break;
        }
    }
    return 0;
}

           

關于Camera架構的部分已經在之前的文章中分析過了,是以現在隻考慮在Camera的架構下,編碼錄像流程是如何工作的。由以上的代碼可知,當使用者輸入‘2’的時候,會執行SampleCameraStateMng類的StartRecord函數,此時就會開始錄像。是以從這個函數開始分析。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖3 StartRecord函數

StartRecord函數的核心就是圖3的5個函數,接下來逐一分析。

3.1、PrepareRecorder函數

StartRecord函數首先會執行PrepareRecorder函數,它的功能有兩個,第一個是建立和設定Recorder對象,第二個是建立用于儲存錄像檔案的檔案句柄。第二個非常簡單,是以我們隻分析第一個。第一個功能的代碼如下圖。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖4 建立和設定Recorder對象 可以看到該代碼分為2個部分,第一個部分是紅框前的代碼,是為了滿足業務需要而設定的各種音視訊采集和編碼參數;第二個部分是紅框内的代碼,建立一個Recorder類的對象,并用之前确定的各種參數來設定這個對象。至此recorder_對象建立和設定完成。不過recorder_對象隻是一個包裝,真正被設定的是包含在裡面的sourceManager_變量,以及sourceManager_裡面的videoSource變量和audioSource變量。sourceManager_的資料結構如下圖所示。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖5 sourceManager_變量的資料結構

可以看到sourceManager_變量的核心部分是videoSource和audioSource,sourceManager_變量的功能就是維護這兩個音視訊的編碼碼流。

3.2、recorder_->SetOutputFile(recordFd_)函數

有了recorder_對象後,該函數将錄像檔案句柄recordFd_指派給recorder_裡的recorderSink_變量的outputFd_變量。留作備用。

3.3、recorder_->Prepare()函數

如前所述,Recorder類由RecorderImpl類具體實作,而RecorderImpl類又由内部的sourceManager_和recorderSink_變量來實作具體的設定和操作。其中sourceManager_又分為video和audio兩部分。是以整個Prepare函數也就分為recorderSink_、video、audio這3個部分,分别做prepare,如下圖所示。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖6 Prepare函數

第一個紅框處的PrepareRecorderSink由recorderSink_->Prepare()來實作,它的作用就是将收集到的寫錄像檔案句柄、錄像檔案最大時長、錄像檔案最大檔案容量設定到recorderSink_的成員變量formatMuxerHandle_中,也就是建立了一個用于寫錄像檔案的音視訊複用器(Muxer)。

第二個紅框處的PrepareVideoSource函數的功能是讀出sourceManager_變量裡的videoSource(視訊源)的一系列設定:編碼格式、圖像寬高、編碼帶寬、幀率、關鍵幀間隔,并把這些設定加入formatMuxerHandle_中,作為要寫入的錄像檔案的一個視訊track。

第三個紅框處的PrepareAudioSource函數的功能與PrepareVideoSource函數的功能大體類似,先利用audioSource的一系列設定做音頻初始化,分為音頻源初始化和音頻編碼器初始化。然後再把這些設定加入formatMuxerHandle_中,作為要寫入的錄像檔案的一個音頻track。

3.4、recorder_->Start()函數

與前面的函數類似,該Start函數同樣分成三個部分:recorderSink、videoSource、audioSource分别做start,如下圖所示。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖7 Start函數

第一個紅框的recorderSink_->Start();比較簡單,功能是1、設定Muxer(音視訊複用器)的一般性消息和錯誤消息的回調。2、啟動該Muxer,執行寫錄像檔案的操作。

第二個紅框的StartVideoSource();函數,作用是啟動了一個新的線程,該線程執行VideoSourceProcess函數,該函數如下圖所示。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖8 VideoSourceProcess函數

這個函數的功能是,它在一個線程中,不停的用第一個紅框裡的videoSource來擷取視訊碼流,再用第二個紅框裡的recorderSink把碼流寫入Muxer(音視訊複用器)裡,也就是寫到錄像檔案中。第二個紅框的内容比較清晰,是以我們隻分析第一個紅框裡的内容,也就是AcquireBuffer函數的功能。該函數代碼如下圖所示。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖9 AcquireBuffer函數

可以看出AcquireBuffer函數從RecorderVideoSource的surface_裡面擷取碼流的起始位址和長度,然後把它們設定到buffer裡面。那麼surface_又是從哪裡得到碼流的呢?但是RecorderVideoSource類裡面并沒有明确的和編碼器綁定的部分,這是需要弄清楚的。

第三個紅框的StartAudioSource();函數,也是同樣的,啟動了一個新的線程,該線程執行AudioSourceProcess函數。該函數的功能也類似于VideoSourceProcess函數,它在一個線程中不停的用audioSource來擷取音頻碼流,再用recorderSink把碼流寫入Muxer(音視訊複用器)裡,也就是寫到錄像檔案中。然而不同之處在于audioSource包含自己的拾音器、編碼器、緩沖區,整過過程是完整的,無需像videoSource那樣從外界擷取碼流。

至此,錄像部分的流程(Recorder子系統)就開始工作了。總結一下該流程:

a、先建立一個Recorder類的對象,它統領整個Recorder架構,并用滿足業務需要的音視訊采集和編碼參數去設定它。

b、設定好該Recorder對象的Muxer(音視訊複用器),并用上面設定好的音視訊采集和編碼參數建立和設定音視訊2條track,再把這2條track加入到這個Muxer裡面。并且還要設定好音頻的采樣源和編碼器,但是無需對視訊部分做設定。

c、最後啟動這個Recorder對象,讓它裡面的audioSource(音頻源)和videoSource(視訊源)在2個單獨的線程裡面不斷的擷取編碼資料,并将資料寫入Muxer,最終寫入錄像檔案。

雖然這個流程看上去是成立的,但是還不夠完整。隻有音頻部分是完整的,視訊部分沒有設定和啟動編碼器,也沒有告訴我們videoSource的surface_是如何初始化的,也沒有告訴我們surface_是從哪裡擷取到視訊碼流的,以及Recorder架構和Camera架構是如何關聯的。這些問題的答案在圖3的第5個紅框裡面,也就是cam_->TriggerLoopingCapture(*fc)函數。

3.5、cam_->TriggerLoopingCapture(*fc)函數

重新貼一下圖3的代碼,再增加一個紅框,觀察一下fc變量是如何建立和初始化的。如下圖所示。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖10 與編碼和擷取碼流相關代碼

圖10的第一個紅框裡的recorder_->GetSurface(0)才真正的建立了recorder_裡的surface_,接着surface_被設定了分辨率(1920*1080)等參數後,又被放置到fc中,而第二個紅框則表明fc又被CameraImpl類的cam_對象使用。是以,Recorder架構和Camera架構發生關聯,既Camera架構将Recorder架構所擁有的surface_對象作為自己的碼流緩沖區,産生的碼流放入這個緩沖區;而Recorder架構從這個緩沖區讀取視訊碼流,寫入錄像檔案。分析圖10的第二個紅框的代碼可以得到更多的架構細節。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖11 CameraImpl類和CameraDevice類的TriggerLoopingCapture函數

圖11的紅色箭頭表明cam_->TriggerLoopingCapture(*fc)函數是由device_對象的TriggerLoopingCapture(FrameConfig &fc)函數具體實作的。第一個紅框的意思是如果fc被設定為FRAME_CONFIG_RECORD(錄像)模式,那就會使用recordAssistant_對象來完成TriggerLoopingCapture函數。

第二個紅框則是本函數的核心,它是将Recorder架構和Camera架構連接配接在一起的關鍵。它的代碼如圖12。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖12 SetFrameConfig函數

圖12第一個紅框的意思是根據我們在fc的surface_中設定的分辨率,在可用的分辨率組合attrs中找到最接近的那個,并以此來決定需要用到的那個處理器的Id(ProcessorIdx),最終得到處理器的裝置Id(deviceId)。

第二個紅框的意思是根據fc(分辨率和輸出碼流緩沖區)、attrs(最接近的幀率和分辨率)、deviceId(處理器的裝置Id)來生成一個編碼器句柄codecHdl。

第三個紅框的意思是将回調函數清單recordCodecCb_和編碼器句柄codecHdl綁定在一起。recordCodecCb_這個回調函數清單裡面隻有RecordAssistant::OnVencBufferAvailble這個函數是有效的,它的作用是當編碼器句柄codecHdl有了碼流以後,就把這段碼流拷貝到codecHdl對應的緩沖區vencSurfaces_。

第四個紅框的意思則表示所謂的vencSurfaces_裡面包含的就是surface_。這樣Recorder架構和Camera架構就連接配接在了一起。

回到圖11的第三個紅框,它最終調用了CodecStart(vencHdls_[i]);,意思就是啟動編碼器開始編碼。至此,圖11的TriggerLoopingCapture函數完成,Camera架構開始采集和編碼,并且編碼資料會被放到Recorder架構的surface_裡面。同時,圖10的StartRecord函數也一并完成,Recorder架構也開始工作。于是整個編碼錄像流程在兩個架構的合作下就開始工作了。

四、整個編碼錄像流程總結

通過上文的分析,我們已經知道了整個編碼錄像的大體細節,但是不太連貫,是以現在我們來總結一下整個流程。

4.1、Camera架構初始化完成,cam_、device_、recordAssistant_這些視訊編碼相關對象已經建立完畢。

4.2、device_執行Initialize函數,用系統提供的各種可用的幀率+分辨率的組合來初始化device_裡的prcessorAttrs_和prcessorHdls_,前者表示了這個組合對處理器算力的要求,後者表示為了完成這個任務需要配置設定什麼樣的處理器。

4.3、這時使用者輸入‘2’,執行SampleCameraStateMng類的StartRecord函數。該函數涉及Camera架構的部分會設定device_的工作模式為FRAME_CONFIG_RECORD,也就是錄像模式。并且設定把Recorder架構的surface_變量傳入device_。

4.4、根據在4.2初始化的prcessorAttrs_和prcessorHdls_,以及在4.3中設定的fc,建立處理器裝置句柄deviceId和編碼器句柄vencHdls_。并用回調函數規定當vencHdls_産生碼流時,surface_變量接收該碼流。

4.5、與此同時,4.3會建立和初始化Recorder類的對象recorder,它代表整個Recorder架構。然後設定recorder裡面的Muxer(音視訊複用器)、audioSource(音頻采集、編碼、音頻碼流擷取和傳送)、videoSource(視訊碼流擷取和傳送)。

4.6、啟動recorder,于是audioSource和videoSource會在兩個單獨的線程裡面等待各自的碼流,然後寫入Muxer,最終儲存為檔案。

4.7、啟動編碼器,産生碼流。

編碼錄像流程的各子產品各變量之間的關系如下圖所示。

鴻蒙媒體子系統解讀-編碼錄像流程解讀

圖13 編碼錄像流程的各子產品各變量之間的關系

圖13表示了各子產品各變量之間的關系,其中2個紫色的粗線箭頭表示中間的surface被左邊的surface_指派,而右邊的vencSurfaces_又被中間的surface指派;橙色的細線箭頭表示變量之間的隸屬關系,箭頭所在變量隸屬于箭尾所在變量。

繼續閱讀