【基於libRTMP的流媒體直播之 AAC、H264 推送】

        這段時間在搗騰基於 RTMP 協議的流媒體直播框架,其間參考了衆多博主的文章,剩下一些細節問題自行琢磨也算摸索出個門道,現將自己認爲比較惱人的 AAC 音頻幀的推送和解析、H264 碼流的推送和解析以及網上沒說清楚的地方分享給各位。

        RTMP 協議棧的實現,Bill 直接使用的 libRTMP,關於 libRTMP 的編譯、基本使用方法,以及簡單的流媒體直播框架,請參見博文[C++實現RTMP協議發送H.264編碼及AAC編碼的音視頻],言簡意賅,故不再贅述。

        言歸正傳,我們首先來看看 AAC 以及 H264 的推送。

        不論向 RTMP 服務器推送音頻還是視頻,都需要按照 FLV 的格式進行封包。因此,在我們向服務器推送第一個 AAC H264 數據包之前,需要首先推送一個音頻 Tag [AAC Sequence Header] 以下簡稱“音頻同步包”,或者視頻 Tag [AVC Sequence Header] 以下簡稱“視頻同步包”。


AAC 音頻幀的推送                                         

        我們首先來看看音頻 Tag,根據 FLV 標準 Audio Tags 一節的描述:

wKioL1Qje6_ApXbFAALZEhnUQhw347.jpg

wKioL1Qje7CzZWgCAADA_wp5OpM894.jpg

wKiom1QjjFvDUS-PAADRrM6v_UU397.jpg

        我們可以將其簡化並得到 AAC 音頻同步包的格式如下:

wKiom1Qj3lqRKafiAAKNXyQMvTU565.jpg

        音頻同步包大小固定爲 4 個字節。前兩個字節被稱爲 [AACDecoderSpecificInfo],用於描述這個音頻包應當如何被解析。後兩個字節稱爲 [AudioSpecificConfig],更加詳細的指定了音頻格式。

        [AACDecoderSpecificInfo] 倆字節可以直接使用 FAAC 庫的 faacEncGetDecoderSpecificInfo 函數來獲取,也可以根據自己的音頻源進行計算。一般情況下,雙聲道,44kHz 採樣率的 AAC 音頻,其值爲 0xAF00,示例代碼:

wKioL1QjvBOTgyzaAAGVe-V9kmI359.jpg

        根據 FLV 標準 不難得知,[AACDecoderSpecificInfo]1 個字節高 4 |1010| 代表音頻數據編碼類型爲 AAC,接下來 2 |11| 表示採樣率爲 44kHz,接下來 1|1| 表示採樣點位數 16bit,最低 1 |1| 表示雙聲道。其第二個字節表示數據包類型,0 則爲 AAC 音頻同步包,1 則爲普通 AAC 數據包。

        音頻同步包的後兩個字節 [AudioSpecificConfig] 的結構,援引其他博主圖如下:

wKioL1QiuO7zrhUwAAJxI9ZTnCM355.jpg

        我們只需參照上述結構計算出對應的值即可。至此,4 個字節的音頻同步包組裝完畢,便可推送至 RTMP 服務器,示例代碼如下:

wKiom1Qjwf_AhpYBAALewqMU8R4358.jpg

        網上有博主說音頻採樣率小於等於 44100 SamplingFrequencyIndex 應當選擇 3(48kHz)Bill 測試發現採樣率等於 44100 時設置標記爲 34 均能正常推送並在客戶端播放,不過我們還是應當按照標準規定的行事,故此處的 SamplingFrequencyIndex 選 4

        完成音頻同步包的推送後,我們便可向服務器推送普通的 AAC 數據包,推送數據包時,[AACDecoderSpecificInfo] 則變爲 0xAF01,向服務器說明這個包是普通 AAC 數據包。後面的數據爲 AAC 原始數據去掉前 7 個字節(若存在 CRC 校驗,則去掉前 9 個字節),我們同樣以一張簡化的表格加以闡釋:

wKiom1Qj3mqCw5lHAAIa-4cP-8I493.jpg

        推送普通 AAC 數據包的示例代碼:

wKioL1QjwrvxaltsAAK8YUN-Lxc350.jpg

        至此,我們便完成了 AAC 音頻的推送流程。此時可嘗試使用 VLC 或其他支持 RTMP 協議的播放器連接到服務器測試正在直播的 AAC 音頻流。     



H264 碼流的推送                                           

        前面提到過,向 RTMP 服務器發送 H264 碼流,需要按照 FLV 格式進行封包,並且首先需要發送視頻同步包 [AVC Sequence Header]。我們依舊先閱讀 FLV 標準 Video Tags 一節:

wKioL1QjxnHgHEnEAAKJgSNqtus964.jpg

wKiom1QjxgTxHxcGAAHIvqsTyqY918.jpg

        由於視頻同步包前半部分比較簡單易懂,仔細閱讀上述標準便可明白如何操作,故 Bill 不另作圖闡釋。由上圖可知,我們的視頻同步包 FrameType == 1CodecID == 7VideoData == AVCVIDEOPACKET,繼續展開 AVCVIDEOPACKET,我們可以得到 AVCPacketType == 0x00CompositionTime == 0x000000Data == AVCDecoderConfigurationRecord

        因此構造視頻同步包的關鍵點便是構造 AVCDecoderConfigurationRecord。同樣,我們援引其他博主的圖片來闡釋這個結構的細節:

wKiom1QjyPqD1WfpAAL6V06Ylu8204.jpg

        其中需要額外計算的是 H264 碼流的 Sps 以及 Pps,這兩個關鍵數據可以在開始編碼 H264 的時候提取出來並加以保存,在需要時直接使用即可。具體做法請讀者自行 Google 或參見 參考博文[2],在此不再贅述。

        當我們得到本次 H264 碼流的 Sps 以及 Pps 的相關信息後,我們便可以完成視頻同步包的組裝,示例代碼如下:

wKiom1Qjzaaji__hAAKucP6fUmk422.jpg

wKioL1Qj2FiScNksAAL966Ultw0411.jpg


        至此,視頻同步包便構造完畢並推送給 RTMP 服務器。接下來只需要將普通 H264 碼流稍加封裝便可實現 H264 直播,下面我們來看一下普通視頻包的組裝過程。

        回顧 FLV 標準Video Tags 一節,我們可以得到 H264 普通數據包的封包信息,FrameType == H264 I ? 1 : 2),CodecID == 7VideoData == AVCVIDEOPACKET,繼續展開,我們可以得到  AVCPacketType == 0x01CompositionTime 此處仍然設置爲 0x000000,具體原因 TODO(billhoo)Data == H264 NALU Size + NALU Raw Data

        構造視頻數據包的示例代碼如下:

wKiom1Qj2_XiM6C9AAHC8RxCixU908.jpg

wKioL1Qj3Brwx8vTAAF2JsPqjeg495.jpg

        至此 H264 碼流的整個推送流程便已完成,我們可以使用 VLC 或其他支持 RTMP 協議的播放器進行測試。


關於 AAC 音頻幀及 H264 碼流的時間戳         

        通過前文的步驟我們已經能夠將 AAC 音頻幀以及 H264 碼流正常推送到 RTMP 直播服務器,並能夠使用相關播放器進行播放。但播放的效果如何還取決於時間戳的設定。

        在網絡良好的情況下,自己最開始使用的音頻流時間戳爲 AAC 編碼器剛輸出一幀的時間,視頻流時間戳爲 H264 編碼器剛編碼出來一幀的時間,VLC 播放端就頻繁報異常,要麼是重新緩衝,要麼直接沒聲音或花屏。在排除了推送步驟實現有誤的問題後,Bill 發現問題出在時間戳上。

        之後有網友說直播流的時間戳不論音頻還是視頻,在整體時間線上應當呈現遞增趨勢。由於 Bill 最開始的時間戳計算方法是按照音視頻分開計算,而音頻時戳和視頻時戳並不是在一條時間線上,這就有可能出現音頻時戳在某一個時間點比對應的視頻時戳小, 在某一個時間點又跳變到比對應的視頻時戳大,導致播放端無法對齊。

        目前採用的時間戳爲底層發送 RTMP 包的時間,不區分音頻流還是視頻流,統一使用即將發送 RTMP 包的系統時間作爲該包的時間戳。目前局域網測試播放效果良好,音視頻同步且流暢。


參考博文

[1][C++實現RTMP協議發送 H.264 編碼及 AAC 編碼的音視頻]

[2][使用 libRtmp 進行 H264 與 AAC 直播]

[3][RTMP直播到FMS中的AAC音頻直播]





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章