標籤:
轉載請把頭部出處連結和尾部二維碼一起轉載,本文出自逆流的魚yuiop:http://blog.csdn.net/hejjunlin/article/details/52560012
前言:上篇文中最後介紹了資料解碼放到Buffer過程,今天分析的是stagefright架構中音視頻輸出過程:
先看下今天的Agenda:
- 一張圖回顧資料處理過程
- 視頻渲染器構建過程
- 音頻資料到Buffer過程
- AudioPlayer在AwesomePlayer運行過程
- 音視頻同步
- 音視頻輸出
- 一張圖看音視頻輸出
一張圖回顧資料處理過程
視頻渲染器構建過程
在構造時,new AweSomeEvent時,就開始把AwesomePlayer把onVideoEvent注入進去。
以上代碼最會調用initRenderer_l函數
從上面代碼來看:AwesomeRemoteRenderer的本質由OMX::createRenderer會先建立一個hardware renderer就是:mVideoRenderer =
new AwesomeNativeWindowRenderer(mNativeWindow, rotationDegrees);若失敗,則建立new AwesomeLocalRenderer(mNativeWindow, format);
接下來看下:
而另一個AwesomeLocalRenderer在構造時new SoftwareRenderer(nativeWindow)
AwesomeLocalRender的本質上是由OMX:createRenderer,createRenderer會建立一個渲染器。如果video decoder是software component,則建立一個AwesomeLocalRenderer作為mVideoRenderer
AwesomeLocalRenderer的constructor會呼叫本身的init函數,其所做的事和OMX::createRenderer一模一樣。可以理解為把read的資料顯示在渲染器中。
渲染器渲染出畫面後,我們可能會想,MediaExtractor把音視頻進行分開,那音頻呢?誰來讓他們保持同步的呢?
音頻資料到Buffer過程
無論是音頻也好,還是視頻,都是bufferdata,音頻或視頻總有一個來維持時間軸的流。舉個例子:我們看過雙簧,一個人說話,一個人示範動作,動作快了不行,話說快,動作跟不上也不行。中間在聯絡台詞時,自然有一些停頓或暗號。在OpenCore中,設定了一個主clock,而audio和video就分別以此作為輸出的依據。而在Stagefright中,audio的輸出是透過callback函式來驅動,video則根據audio的timestamp來做同步。在這之前,我們得瞭解下音頻相關playback過程:
Stagefright架構中,audio的部分是交由AudioPlayer來處理,它是在AwesomePlayer::play_l中被建立的。貼一段以前分析過的代碼:只不過當時沒有向AudioPlayer方向向下看
建立AudioPlayer
再接著看下startAudioPlayer_l函數,
接下來看下音頻mAudioPlayer->start(true)的操作,上面的過程都是在AwesomePlayer中,接下來變到AudioPlayer.cpp類中:
這裡首先要介紹一下mAudioSink ,當mAudioSink不為NULL的時候,AudioPlayer會將其傳入建構函式。
而且AudioPlayer中的播放操作都會依考mAudioSink來完成。
此處mAudioSink是從MediaPlayerService註冊而來的AudioOut對象。具體代碼在MediaPlayerservice中
間接地調用到stagefrightplayer->setAudioSink,最終到awesomeplayer中,如下:
而構造AudioPlayer時用到的就是mAudioSink成員,因此後面分析傳入的mAudioSink的操作時,記住實際的對象為AudioOut對象,在MediaPlayerService定義。
本文出自逆流的魚yuiop:http://blog.csdn.net/hejjunlin/article/details/52560012
AudioPlayer在AwesomePlayer運行過程
下面看AudioPlayer建構函式
主要是進行初始化,並將傳入的mAudioSink存在成員mAudioSink中
再回到上面的start函數中
總結如下:
- 調用mSource->read 啟動解碼,解碼第一幀相當於啟動瞭解碼迴圈
- 擷取音頻參數:採樣率、聲道數、以及量化位元(這裡只支援PCM_16_BIT)
- 啟動輸出:這裡若mAudioSink非空,則啟動mAudioSink進行輸出,否則構造一個AudioTrack進行音訊輸出,這裡AudioTrack是比較底層的介面 AudioOut是AudioTrack的封裝。
- 在start方法中主要是調用mAudioSink進行工作,主要代碼如下:
剛介紹過mAudioSink是AudioOut對象,看下實際的實現(代碼在mediaplayerservice.cpp中)
首先mAudioSink->open 需要注意的是傳入的參數中有個函數指標 AudioPlayer::AudioSinkCallback ,其主要作用就是audioout播放pcm的時候會定期調用此回呼函數填充資料,具體實現如下
以上代碼總結為:
- 1、處理傳入的參數,回呼函數儲存在mCallback中, cookie代表的是AudioPlayer對象指標類型,接下來是根據採樣率聲道數等計算 frameCount。
- 2、構造AudioTrack對象,並且賦值給t
- 3、將audiotrackObject Storage Service在mTrack成員中
當以上過程完成後,繼續分析AudioPlayer.start函數時,最後都會執行個體化一個AudioTrack對象,然後擷取幀大小,位元等資訊,然後調用mAudioTrack.start,最後到達mediaplayerservice音訊輸出start函數。
調用mTrack->start,audiotrack啟動後就會周期性的調用 回呼函數從解碼器擷取資料.
本文出自逆流的魚yuiop:http://blog.csdn.net/hejjunlin/article/details/52560012
音視頻同步
回到我們前面的問題:音視頻如何同步?通過fillBuffer,不斷填充buffer。
代碼如下:
以上代碼總結為:當callback函數回調AudioPlayer讀取解碼後的資料時,AudioPlayer會取得兩個時間戳記:mPositionTimeMediaUs和mPositionTimeRealUs,mPositionTimeMediaUs是資料裡面所持有的時間戳記(timestamp);mPositionTimeRealUs則是播放此資料的實際時間(依據frame number及sample rate得出)。
以上代碼總結為:
- 在構造audioplayer的時候會執行mTimeSource = mAudioPlayer,
即將AudioPlayer作為參考時鐘,
- 上述代碼中成員變數mSeekTimeUs是由如下語句獲得:CHECK(mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs));
- realTimeOffset = getRealTimeUsLocked() - mPositionTimeRealUs; 當顯示畫面是第一幀時,表示當前的audio的播放時間與第一幀video的時間差值
- 其中變數是通過mAudioPlayer->getMediaTimeMapping( int64_t *realtime_us, int64_t *mediatime_us) {
Mutex::Autolock autoLock(mLock)得到
二者的差值表示這一包pcm資料已經播放了多少。Stagefright中的video便依據從AudioPlayer得出來之兩個時間戳記的差值,作為播放的依據
音視頻輸出
最後回到本文開頭的onVideoEvent方法中,
這樣最終音視頻資料通過渲染器就到Surface顯示畫面,就可看到視頻和聽到聲音了。
本文出自逆流的魚yuiop:http://blog.csdn.net/hejjunlin/article/details/52560012
一張圖看音視頻輸出
第一時間獲得部落格更新提醒,以及更多android乾貨,源碼分析,歡迎關注我的公眾號,掃一掃下方二維碼或者長按識別二維碼,即可關注。
如果你覺得好,隨手點贊,也是對筆者的肯定,也可以分享此公眾號給你更多的人,原創不易
Android Multimedia架構總結(十)Stagefright架構之音視頻輸出過程