Android NuPlayer播放框架

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整體框架圖
nuplayer arch

或者下圖
nuplayer class diagram

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。將一個多媒體文件解析之後就可以通過解碼器還原爲原始數據,然後渲染了。具體流程參考下圖:
multimedia-file-play-proc

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狀態調用邏輯。

參考文獻

  1. NuPlayer介紹
  2. NuPlayer for HTTP live streaming
  3. Stagefright框架中視頻播放流程
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章