1. 程式人生 > >【基於libRTMP的流媒體直播之 AAC、H264 推送】

【基於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] 第 個位元組高 位 |1010| 代表音訊資料編碼型別為 AAC,接下來 

 |11| 表示取樣率為 44kHz,接下來 1 位 |1| 表示取樣點位數16bit,最低  |1| 表示雙聲道。其第二個位元組表示資料包型別,0 則為 AAC 音訊同步包,1 則為普通 AAC 資料包。

        音訊同步包的後兩個位元組 [AudioSpecificConfig] 的結構,援引其他博主圖如下:

wKioL1QiuO7zrhUwAAJxI9ZTnCM355.jpg

        我們只需參照上述結構計算出對應的值即可。至此,個位元組的音訊同步包組裝完畢,便可推送至 RTMP 伺服器,示例程式碼如下:

wKiom1Qjwf_AhpYBAALewqMU8R4358.jpg

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

        完成音訊同步包的推送後,我們便可向伺服器推送普通的 AAC 資料包,推送資料包時,[AACDecoderSpecificInfo] 則變為 0xAF01,向伺服器說明這個包是普通 AAC 資料包。後面的資料為 AAC 原始資料去掉前 個位元組(若存在 CRC 校驗,則去掉前 個位元組),我們同樣以一張簡化的表格加以闡釋:

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 包的系統時間作為該包的時間戳。目前區域網測試播放效果良好,音視訊同步且流暢。

參考博文

引自:http://billhoo.blog.51cto.com/2337751/1557646