這段時間在搗騰基於 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 一節的描述:函數
咱們能夠將其簡化並獲得 AAC 音頻同步包的格式以下:測試
音頻同步包大小固定爲 4 個字節。前兩個字節被稱爲 [AACDecoderSpecificInfo],用於描述這個音頻包應當如何被解析。後兩個字節稱爲 [AudioSpecificConfig],更加詳細的指定了音頻格式。編碼
[AACDecoderSpecificInfo] 倆字節能夠直接使用 FAAC 庫的 faacEncGetDecoderSpecificInfo 函數來獲取,也能夠根據本身的音頻源進行計算。通常狀況下,雙聲道,44kHz 採樣率的 AAC 音頻,其值爲 0xAF00,示例代碼:spa
根據 FLV 標準 不可貴知,[AACDecoderSpecificInfo] 第 1 個字節高 4 位 |1010| 表明音頻數據編碼類型爲 AAC,接下來 2 位 |11| 表示採樣率爲 44kHz,接下來 1 位 |1| 表示採樣點位數 16bit,最低 1 位 |1| 表示雙聲道。其第二個字節表示數據包類型,0 則爲 AAC 音頻同步包,1 則爲普通 AAC 數據包。.net
音頻同步包的後兩個字節 [AudioSpecificConfig] 的結構,援引其餘博主圖以下:
咱們只需參照上述結構計算出對應的值便可。至此,4 個字節的音頻同步包組裝完畢,即可推送至 RTMP 服務器,示例代碼以下:
網上有博主說音頻採樣率小於等於 44100 時 SamplingFrequencyIndex 應當選擇 3(48kHz),Bill 測試發現採樣率等於 44100 時設置標記爲 3 或 4 均能正常推送並在客戶端播放,不過咱們仍是應當按照標準規定的行事,故此處的 SamplingFrequencyIndex 選 4。
完成音頻同步包的推送後,咱們即可向服務器推送普通的 AAC 數據包,推送數據包時,[AACDecoderSpecificInfo] 則變爲 0xAF01,向服務器說明這個包是普通 AAC 數據包。後面的數據爲 AAC 原始數據去掉前 7 個字節(若存在 CRC 校驗,則去掉前 9 個字節),咱們一樣以一張簡化的表格加以闡釋:
推送普通 AAC 數據包的示例代碼:
至此,咱們便完成了 AAC 音頻的推送流程。此時可嘗試使用 VLC 或其餘支持 RTMP 協議的播放器鏈接到服務器測試正在直播的 AAC 音頻流。
H264 碼流的推送
前面提到過,向 RTMP 服務器發送 H264 碼流,須要按照 FLV 格式進行封包,而且首先須要發送視頻同步包 [AVC Sequence Header]。咱們依舊先閱讀 FLV 標準 Video Tags 一節:
因爲視頻同步包前半部分比較簡單易懂,仔細閱讀上述標準即可明白如何操做,故 Bill 不另做圖闡釋。由上圖可知,咱們的視頻同步包 FrameType == 1,CodecID == 7,VideoData == AVCVIDEOPACKET,繼續展開 AVCVIDEOPACKET,咱們能夠獲得 AVCPacketType == 0x00,CompositionTime == 0x000000,Data == AVCDecoderConfigurationRecord。
所以構造視頻同步包的關鍵點即是構造 AVCDecoderConfigurationRecord。一樣,咱們援引其餘博主的圖片來闡釋這個結構的細節:
其中須要額外計算的是 H264 碼流的 Sps 以及 Pps,這兩個關鍵數據能夠在開始編碼 H264 的時候提取出來並加以保存,在須要時直接使用便可。具體作法請讀者自行 Google 或參見 參考博文[2],在此再也不贅述。
當咱們獲得本次 H264 碼流的 Sps 以及 Pps 的相關信息後,咱們即可以完成視頻同步包的組裝,示例代碼以下:
至此,視頻同步包便構造完畢並推送給 RTMP 服務器。接下來只須要將普通 H264 碼流稍加封裝即可實現 H264 直播,下面咱們來看一下普通視頻包的組裝過程。
回顧 FLV 標準 的 Video Tags 一節,咱們能夠獲得 H264 普通數據包的封包信息,FrameType == (H264 I 幀 ? 1 : 2),CodecID == 7,VideoData == AVCVIDEOPACKET,繼續展開,咱們能夠獲得 AVCPacketType == 0x01,CompositionTime 此處仍然設置爲 0x000000,具體緣由 TODO(billhoo),Data == H264 NALU Size + NALU Raw Data。
構造視頻數據包的示例代碼以下:
至此 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音頻直播]