一些涉及的基本概念:git
短視頻APP中錄製完成後,爲何要作轉碼:github
爲何不在服務端作轉碼呢?算法
轉碼的主要流程以下:異步
其中Audio Filter和Video Filter分別是指音頻和視頻的預處理。ide
Demuxer模塊的實現,主要有如下三種方案:模塊化
方案一,使用播放器
播放器的主要功能是播放,也就是從原始文件/流中提取出音視頻,按照pts完成音視頻的渲染。轉碼並不須要渲染,要求在保持音視頻同步的狀況下,儘快把解碼數據從新按要求編碼成新的音視頻包,從新複用成文件。咱們也曾經爲了實現儘快這個要求,把播放器強行改形成快速播放的模式,但後來遇到了不少問題:測試
SurfaceTexture
中獲取的timestamp不許。所以最後放棄了這個方案。方案二,使用MediaExtractor
MediaExtractor是Android系統封裝好的用來分離容器中的視頻track和音頻track的Java類。優勢是使用簡單,缺點是支持的格式有限。優化
方案三,使用FFmpeg
使用FFmpeg的av_read_frame
API來作解複用,即實現簡易版的播放器邏輯。編碼
方案二的兼容性不如方案三。相比方案一,方案三把音視頻的解複用和解碼都放到了同一個線程,av_read_frame
能輸出同步交織的音視頻packet,上層邏輯調用更清晰。
同時短視頻其餘功能模塊已經引入了FFmpeg,轉碼模塊引入FFmpeg並不增長包大小,因此選擇了FFmpeg方案。線程
金山雲多媒體SDK實踐中,Demuxer其實是在C層作的,可是接口的封裝是在Java層。解碼結構也是同樣。Demuxer和Decoder之間如何高效地在Java和C層之間傳遞待解碼的音視頻包?
FFmpeg的demuxer模塊解複用出來的爲音頻或視頻的AVPacket。最開始的時候咱們並無在Java層對整個AVPacket的地址指針進行封裝,而是把數據封裝在ByteBuffer
和其餘的參數中。這樣遇到了不少由於AVPacket中的參數沒有傳遞到解碼模塊致使的問題。
最終咱們經過intptr_t
在C層保存AVPacket的指針,同時在Java層以long
類型來保存和傳遞這個指針,解決了這個問題。
爲了實現模塊的複用,咱們把Demuxer和Decoder分紅了兩個模塊。使用FFmpeg來實現時,Decoder模塊能夠和Demuxer模塊共用AVFormatContext
,經過AVFormatContext
來建立AVCodecContext
。
可是這樣會有一個問題,Demuxer的工做速度會快於Decoder,此時AVFormatContext
是由Demuxer來建立的,Demuxer中止的時候會釋放AVFormatContext
。若是交給Decoder模塊來釋放,不利於模塊的複用和解耦。最終咱們發如今FFmpeg 3.3的版本中,AVCodecParams
結構圖中有Decoder所須要的所有信息,能夠經過傳遞AVCodecParams
來構造AVCodecContext
。
轉碼的速度是客戶很是關心的一個點,轉碼時間太長,用戶體驗會很是差。咱們花了很是多的精力來對短視頻的轉碼時間進行提速。經驗主要有如下這些點:
轉碼的時間大部分都被視頻的編碼佔用了,咱們把x264編碼作了調整,在保證畫質影響較小的前提下,節省了30%以上的編碼時間。
使用視頻軟編時,如何從GPU中把數據「下載」到CPU上,咱們嘗試了不少中方案,具體的咱們會在另外一篇文章中詳細解釋。以前的方案是使用ImageReader
讀取RGBA數據。優化爲用OpenGL ES將RGBA轉換爲YUVA。讀取數據後從YUVA再轉爲I420,下載和格式轉化總耗時,提速了大約40%。
硬編的缺點: 在Android平臺上,硬編的兼容性較差,同時視頻硬編的壓縮比差於軟編。
硬編的優勢是顯而易見的,編碼器速度快,佔用的資源也相對較少。
通過大量的測試,硬解的兼容性相較於硬編會好不少,使用硬解碼,直接使用MediaCodec渲染到texture上,省去手動上傳YUV的步驟,也節省了軟解碼的時間開銷。
關於Android的硬編解網上已經有不少例子,官方文檔也比較完善。不過在實現過程當中仍是會遇到一些意想不到的問題。
在硬編上線後,咱們對比畫質發現轉碼圖像質量較差。緣由是使用MediaCodec API時,選擇的是MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR
,CBR的好處是碼率比較穩定,可是會犧牲畫質,移動直播中選用CBR更合理。短視頻轉碼場景硬編時推薦使用MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR
,VBR會得到更好的圖像質量。對於軟編時,咱們也嘗試過ABR(也就是VBR),但實際測試下來效果並不能保證。
H.264碼流主要分Annex-B和AVCC兩種格式,H.265碼流主要分爲Annex-B和HVCC格式。AnnexB與AVCC/HVCC的區別在於參數集與幀格式,AnnexB的參數集sps、pps以NAL的形式存在碼流中(帶內傳輸),以startcode分割NAL。
而AVCC/HVCC 的參數集存儲在extradata中(帶外傳輸),使用NALU長度(固定字節,一般爲4字節,從extradata中解析)分隔NAL,一般MP4、MKV使用AVCC格式來存儲。
Android的硬解只接受Annex-B格式的碼流,因此在解碼MP4 Demux出的視頻流時,須要解析extradata,取出sps、pps,經過CSD(Codec-Specific Data
)來初始化解碼器;而且將AVCC碼流轉換爲Annex-B,在ffmpeg中使用h264_mp4toannexb_filter
或hevc_mp4toannexb
作轉換。
硬解碼器解碼視頻到Surface,此時經過SurfaceTexture.getTimestamp()
得到時間戳並不許確,某些機型會出現異常。因此仍是要使用解碼輸入的時間戳,可將解碼過程由異步轉爲同步,或者將pts存儲到隊列中來實現。
MediaCodec的音頻編解碼具體實現和機型有關,許多機型的MediaCodec音頻編解碼工做仍然是軟件方案。通過測試MediaCodec音頻硬編碼較軟編碼有6%左右的提速,但MediaCodec音頻硬解反而比軟解的的速度慢,具體緣由有待進一步調查。不過這只是部分機型的測試結果,更多機型的比較你們可使用咱們demo的轉碼/合成功能進行測試。
下面以三星S8爲例,短視頻SDK在轉碼速度上的進步,更多機型的對比數據,請移步github wiki查看。
將1分鐘1080p 18Mbps視頻,轉碼成540p 1.2Mbps,不一樣版本時間開銷大體以下:
機型 | 版本 | 編碼方式 | 第一次合成時長 | 第二次合成時長 | 第三次合成時長 | 平均值 |
---|---|---|---|---|---|---|
三星S8 | V1.0.4 | 軟編 | 52s | 54s | 58s | 54.7s |
V1.1.2 | 軟編 | 49s | 50s | 50s | 49.7s | |
V1.1.2 | 硬編 | 35s | 36s | 38s | 36.3s | |
V1.4.7 | 硬編 | 21.5s | 21.9s | 22.5s | 22.0s |
能夠看到,使用了硬編、硬解等提速手段後,合成速度由54秒優化到22秒。
金山雲短視頻SDK的基礎模塊是基於直播SDK,總體來講,是一套push模式的流水線。
流水線中的每一個模塊都很好地實現瞭解耦,單獨模塊完成單一的功能,模塊的複用也很是方便。前置模塊在產生新的音視頻幀後,會當即push給後續模塊,後續模塊須要儘快把前置模塊產生的音視頻幀消化掉,最大程度上保證明時性。爲了保證音視頻同步等邏輯,引入了大量同步鎖。在短視頻的開發中,遇到了很多的死鎖和不方便。對於短視頻這種非實時的場景,更多的時候,須要由後續模塊(而非前置模塊)來控制整個流程的進度。
當前處理過程當中須要實現暫停,須要在前置模塊加鎖來實現。爲了能方便之後的開發,咱們會在接下來從新梳理這種push流水線的方式, 實現模塊化的同時,儘可能減小同步鎖的使用。
轉碼對於普通用戶來講不可見的,但倒是短視頻SDK的一個重要過程。怎麼樣讓轉碼過程耗時更短,轉碼圖像質量更高,特效添加更靈活,減小咱們團隊自身的開發和維護成本,同時也爲開發者提供最方便易用的API,一直是金山雲多媒體SDK團隊的目標。 團隊在很用心的開發短視頻SDK,歡迎試用!