Android NuPlayer播放框架
NuPlayer簡介
Android2.3時引入流媒體框架,而流媒體框架的核心是NuPlayer。在之前的版本中一般認爲Local Playback就用Stagefrightplayer+Awesomeplayer,流媒體用NuPlayer。Android4.0之後HttpLive和RTSP協議開始使用NuPlayer播放器,Android5.0(L版本)之後本地播放也開始使用NuPlayer播放器。 Android7.0(N版本)則完全去掉了Awesomeplayer。
通俗點說,NuPlayer是AOSP中提供的多媒體播放框架,能夠支持本地文件、HTTP(HLS)、RTSP等協議的播放,通常支持H.264、H.265/HEVC、AAC編碼格式,支持MP4、MPEG-TS封裝。
在實現上NuPlayer和Awesomeplayer不同,NuPlayer基於StagefrightPlayer的基礎類構建,利用了更底層的ALooper/AHandler機制來異步地處理請求,ALooper列隊消息請求,AHandler中去處理,所以有更少的Mutex/Lock在NuPlayer中。Awesomeplayer中利用了omxcodec而NuPlayer中利用了Acodec。
NuPlayer框架
下圖是NuPlayer整體框架圖
或者下圖
Android層的多媒體框架,有多層實現,甚至有跨進程的調用。這裏重點關注NuPlayerDriver之後的實現和相關邏輯。至於上層的調用邏輯,建議參考其他資料。
各部分功能如下:
- NuPlayer::Source:解析模塊(parser,功能類似FFmpeg的avformat)。其接口與MediaExtractor和MediaSource組合的接口差不多,同時提供了用於快速定位的seekTo接口。
- NuPlayer::Decoder:解碼模塊(decoder,功能類似FFmpeg的avcodec),封裝了用於AVC、AAC解碼的接口,通過ACodec實現解碼(包含OMX硬解碼和軟解碼)。
- NuPlayer::Render:渲染模塊(render,功能類似聲卡驅動和顯卡驅動),主要用於音視頻渲染和同步,與NativeWindow有關。
多媒體文件如何通過NuPlayer播放的
在AOSP中,通常將一個多媒體文件或者URL稱爲DataSource。通常多媒體文件中包含至少一個音頻流、視頻流或者字幕流,NuPlayer將這三種統稱爲Track,細分下也有AudioTrack、VideoTrack、SubtitleTrack。將一個多媒體文件解析之後就可以通過解碼器還原爲原始數據,然後渲染了。具體流程參考下圖:
DataSource有兩個概念:
- 上圖中的DataSourceInput(DataSource)指的是單純的原始數據(容器格式,沒有經過demuxer處理)。
- 在後文中setDataSource中DataSource指的是從數據輸入到demux輸出的一個過程(即圖中最外層的DataSource)。
VideoTrack與AudioTrack指的是Extractor(即demux)的兩個通道,從這裏輸出的分別就是單純的解複用後的Video和Audio流。再經過Decoder後輸出的就是音、視頻的輸出了:
- VideoRenderer + Surface即視頻的輸出;
- AudioSink即音頻的輸出;
至於Android應用層如何調用MediaPlayer,建議參考我之前的文章MediaPlayer Interface&State。
對於AOSP的源碼分析的方法
鑑於AOSP是一個操作系統,整體比較複雜,從實際出發,可以關注於某個點。比如我這裏主要關注NuPlayer的框架,其內部實現邏輯。那麼最終就落實到如何從一個類中提取出需要的框架及知識點。那麼一個類的對外接口部分通常包括:
- 構造函數和析構函數
- 必須調用的接口
- 可選的調用接口
在多媒體播放中,通過關注的點有:
- 如何實現解複用,得到音頻、視頻、字幕等數據
- 如何實現解碼
- 如何實現音視頻同步
- 如何渲染視頻
- 如何播放音頻
- 如何實現快速定位
NuPlayer接口實現分析(NuPlayerDriver)
NuPlayer框架中最頂層的類是NuPlayerDriver,繼承自MediaPlayerInterface,主要提供一個狀態轉換機制,作爲NuPlayer類的Wrapper。NuPlayerDriver類中最重要的成員是以下幾個:
State mState
播放器狀體標誌sp<ALooper> mLooper
內部消息驅動機制sp<NuPlayer> mPlayer
真正完成播放器的類
先說明下我參考的是Android 7的源碼,NuPlayerDriver.cpp (目錄:./frameworks/av/media/libmediaplayerservice/nuplayer/)
構造函數&析構函數
從代碼中可以看到,構造函數中最主要的作用是創建ALooper和NuPlayer實例,並將它們關聯起來。
mLooper = (new ALooper);
mLooper->start(
false, /* runOnCallingThread */
true, /* canCallJava */
PRIORITY_AUDIO);
mPlayer = new NuPlayer(pid);
mLooper->registerHandler(mPlayer);
mPlayer->setDriver(this);
析構函數主要就是銷燬創建的ALooper和NuPlayer,由於是智能指針,直接調用stop即可。
mLooper->stop();
SetDataSource
這個接口實現很簡單,檢查當前的播放狀態,然後直接調用NuPlayer::setDataSourceAsync函數。
status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) {
ALOGV("setDataSource(%p) file(%d)", this, fd);
Mutex::Autolock autoLock(mLock);
if (mState != STATE_IDLE) {
return INVALID_OPERATION;
}
mState = STATE_SET_DATASOURCE_PENDING;
mPlayer->setDataSourceAsync(fd, offset, length);
while (mState == STATE_SET_DATASOURCE_PENDING) {
mCondition.wait(mLock);
}
return mAsyncResult;
}
最後等待該函數返回,並調用NuPlayerDriver::notifySetDataSourceCompleted接口,改變播放器狀態。
setVideoSurfaceTexture
這個接口的實現思路跟SetDataSource,不過調用的是NuPlayer::setVideoSurfaceTextureAsync,該請求處理完之後調用NuPlayerDriver::notifySetSurfaceComplete接口。
prepare/prepareAsync
這兩個接口基本功能是一致的,只是第二個是異步的調用過程。第一個通過prepare_l接口實現,二者最終均調用NuPlayer::prepareAsync,請求處理完成之後調用NuPlayerDriver::notifyPrepareCompleted接口。不過這裏面有大量的關於播放狀態判斷的代碼。比如prepareAsync中代碼
ALOGV("prepareAsync(%p)", this);
Mutex::Autolock autoLock(mLock);
switch (mState) {
case STATE_UNPREPARED:
mState = STATE_PREPARING;
mIsAsyncPrepare = true;
mPlayer->prepareAsync();
return OK;
case STATE_STOPPED:
// this is really just paused. handle as seek to start
mAtEOS = false;
mState = STATE_STOPPED_AND_PREPARING;
mIsAsyncPrepare = true;
mPlayer->seekToAsync(0, true /* needNotify */);
return OK;
default:
return INVALID_OPERATION;
};
start/stop
這兩個函數作爲開始播放和停止播放的接口,主要涉及到播放器內部狀態的切換和判斷。最終功能實現通過調用NuPlayer::start和NuPlayer::pause接口。
下面是start函數實現代碼(start_l),主要需要判斷不同狀態下的調用邏輯:
switch (mState) {
case STATE_UNPREPARED:
{
status_t err = prepare_l();
if (err != OK) {
return err;
}
CHECK_EQ(mState, STATE_PREPARED);
// fall through
}
case STATE_PAUSED:
case STATE_STOPPED_AND_PREPARED:
case STATE_PREPARED:
{
mPlayer->start();
// fall through
}
case STATE_RUNNING:
{
if (mAtEOS) {
mPlayer->seekToAsync(0);
mAtEOS = false;
mPositionUs = -1;
}
break;
}
default:
return INVALID_OPERATION;
}
mState = STATE_RUNNING;
stop接口實現則相對簡單,主要是判斷什麼狀態下可以調用stop接口,並上報MEDIA_STOPPED狀態。代碼如下:
switch (mState) {
case STATE_RUNNING:
mPlayer->pause();
// fall through
case STATE_PAUSED:
mState = STATE_STOPPED;
notifyListener_l(MEDIA_STOPPED);
break;
case STATE_PREPARED:
case STATE_STOPPED:
case STATE_STOPPED_AND_PREPARING:
case STATE_STOPPED_AND_PREPARED:
mState = STATE_STOPPED;
break;
default:
return INVALID_OPERATION;
}
pause / reset
pause用於實現暫停,其實現比較簡單,直接調用NuPlayer::puase實現,代碼如下:
switch (mState) {
case STATE_PAUSED:
case STATE_PREPARED:
return OK;
case STATE_RUNNING:
mState = STATE_PAUSED;
notifyListener_l(MEDIA_PAUSED);
mPlayer->pause();
break;
default:
return INVALID_OPERATION;
}
reset重置播放器,這是一個同步調用的接口。最終實現通過調用NuPlayer::resetAsync接口,然後調用NuPlayerDriver::notifyResetComplete通知。
isPlaying / getDuration / getCurrentPosition
這幾個接口主要用於獲取播放器的狀態。
isPlaying直接通過播放器狀態判斷,其實現如下:
bool NuPlayerDriver::isPlaying() {
return mState == STATE_RUNNING && !mAtEOS;
}
getDuration的實現也相對簡單,代碼如下。不過具體獲取的mDurationUs需要通過NuPlayer上報或者定期查詢更新下。
status_t NuPlayerDriver::getDuration(int *msec) {
Mutex::Autolock autoLock(mLock);
if (mDurationUs < 0) {
return UNKNOWN_ERROR;
}
*msec = (mDurationUs + 500ll) / 1000;
return OK;
}
getCurrentPosition則是通過調用NuPlayer::getCurrentPosition獲取。
getMetadata
這個接口主要是判斷播放器的屬性,比如是否支持暫停、seek、向前seek、向後seek等。
NuPlayerDriver的接口實現,包括:
- ALooper機制
- NuPlayer
- NuPlayer::Decoder
- NuPlayer::Source
- NuPlayer::Render
- ACodec
NuPlayerDriver主要是接口層的一個銜接,並記錄了播放器內部的狀態數據,以保證其符合Android MediaPlayer狀態調用邏輯。