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

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

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

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

        不論向 RTMP 服務器推送音頻仍是視頻,都須要按照 FLV 的格式進行封包。所以,在咱們向服務器推送第一個 AAC H264 數據包以前,須要首先推送一個音頻 Tag [AAC Sequence Header] 如下簡稱「音頻同步包」,或者視頻 Tag [AVC Sequence Header] 如下簡稱「視頻同步包」。框架


AAC 音頻幀的推送                                         ide

        咱們首先來看看音頻 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,示例代碼:spa

wKioL1QjvBOTgyzaAAGVe-V9kmI359.jpg

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

        音頻同步包的後兩個字節 [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音頻直播]

相關文章
相關標籤/搜索