NuPlayer解封裝模塊
文章目錄
系列文章分爲如下幾個模塊:
解封裝模塊的重要作用,是將封裝好的音視頻源文件,通過不同的封裝協議,解析成碼流後,送到解碼器解碼。
NuPlayer中和解封裝相關的類有:
- NuPlayer::Source:解封裝模塊的基類,定義瞭解封裝的基本接口。
- GenericSource:本地文件相關。
- HTTPLiveSource:HLS流媒體使用的解封裝類。
- RTSPSource:SDP協議媒體流使用的解封裝類。
此外,還需要DataSource等配合操作。類圖如下:
篇幅有限,本文主要介紹本地媒體文件的例子。也就是GenericSource播放本地文件爲例。
Android播放器的一般步驟
一個Android播放器典型的播放步驟一般是:
- 播放器創建。
- 設置媒體源文件(本地文件路徑、或者Uri)。
- 準備媒體數據。
- 播放視頻。
- 停止播放。
爲了方便分析解封裝模塊,也從該順序逐步分析解封裝過程。對應的播放器調用接口如下:
-
GenericSource:創建
-
setDataSource:設置媒體源數據
-
prepareAsync:準備媒體數據
-
start:播放視頻
-
stop&pause:停止播放
GenericSource:創建
先來分析第一個步驟:GenericSource的創建,即播放器創建部分。在流程中的位置是:
-
GenericSource的創建
-
setDataSource:設置媒體源數據
-
prepareAsync:準備媒體數據
-
start:啓動
-
stop&pause&resume:停止&暫停&恢復
上一篇文章中,我們提到,在NuPlayer
的setDataSourceAsync
函數中創建了GenericSource
對象。並調用了setDataSource
函數。用一張圖回憶一下:
再來看看對應代碼:
void NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) {
sp<AMessage> msg = new AMessage(kWhatSetDataSource, this); // 新建消息,這屬於常規操作了
sp<AMessage> notify = new AMessage(kWhatSourceNotify, this); // 新建消息,用於和解封裝模塊通信,類似於一種listener的功能。
sp<GenericSource> source = new GenericSource(notify, mUIDValid, mUID); // 創建解封裝器
status_t err = source->setDataSource(fd, offset, length); // 爲GenericSource設置媒體源
msg->setObject("source", source);
msg->post(); // 將創建並設置好的setDataSource,post給下一個流程處理
mDataSourceType = DATA_SOURCE_TYPE_GENERIC_FD;
}
這段代碼中,首次創建了一個GenericSource實例,先來看看實例化過程。
NuPlayer::GenericSource::GenericSource(
const sp<AMessage> ¬ify,
bool uidValid,
uid_t uid)
: Source(notify), // 將一個AMessage對象存放在父類Source的mNotify字段中,這是個通用操作,用來通知調用者,當前資源狀態的。
mAudioTimeUs(0),
mAudioLastDequeueTimeUs(0),
mVideoTimeUs(0),
mVideoLastDequeueTimeUs(0),
mFetchSubtitleDataGeneration(0),
mFetchTimedTextDataGeneration(0),
mDurationUs(-1ll),
mAudioIsVorbis(false), // 音頻是否爲Vorbis壓縮格式,默認爲false
mIsSecure(false),
mIsStreaming(false),
mFd(-1), // 文件句柄
mBitrate(-1ll), // 比特率
mPendingReadBufferTypes(0) {
mBufferingMonitor = new BufferingMonitor(notify); // 新建一個BufferingMonitor實例
resetDataSource(); // 重置一些DataSource數據到初始狀態。
}
從構造函數默認初始化列表中的字段含義來看,GenericSource包含了除了Buffer以外幾乎所有的解封裝相關數據,如文件句柄(mFd)、媒體時長(mDurationUs)等。
而關於Buffer狀態的管理和監聽使用的是BufferingMonitor類來實現。
- BufferingMonitor:協助監控Buffer的狀態,每秒輪詢一次,必要時會將Buffer的狀態通過AMessage通知Player。
可見其重要性,來簡單看一下該結構體和部分函數,間接感受一下它的功能:
struct BufferingMonitor : public AHandler {
public:
explicit BufferingMonitor(const sp<AMessage> ¬ify);
// 重新啓動監視任務。
void restartPollBuffering();
// 停止緩衝任務併發送相應的事件。
void stopBufferingIfNecessary();
// 確保數據源正在獲取數據。
void ensureCacheIsFetching();
// 更新從DataSource剛剛提取的緩衝區的媒體時間。
void updateQueuedTime(bool isAudio, int64_t timeUs);
// 更新發送到解碼器的最後出隊緩衝區的媒體時間。
void updateDequeuedBufferTime(int64_t mediaUs);
protected:
virtual ~BufferingMonitor();
virtual void onMessageReceived(const sp<AMessage> &msg);
}
setDataSource:設置媒體源數據
setDataSource在播放流程中的位置爲:
-
GenericSource的創建
-
setDataSource:設置媒體源數據
-
prepareAsync:準備媒體數據
-
start:啓動
-
stop&pause&resume:停止&暫停&恢復
status_t NuPlayer::GenericSource::setDataSource(int fd, int64_t offset, int64_t length) {
ALOGV("setDataSource %d/%lld/%lld", fd, (long long)offset, (long long)length);
resetDataSource(); // 重置一些DataSource數據到初始狀態。
mFd = dup(fd); // 將文件的句柄複製一份給mFd字段
mOffset = offset; // 數據的偏移量
mLength = length; // 文件長度
// delay data source creation to prepareAsync() to avoid blocking
// the calling thread in setDataSource for any significant time.
return OK;
}
dup(fd)
是什麼?該函數定義在/frameworks/base/core/java/android/os/ParcelFileDescriptor.java中,函數原型爲:public static ParcelFileDescriptor dup(FileDescriptor orig)
作用:創建一個新的ParcelFileDescriptor,它是現有FileDescriptor的副本。 這遵循標準POSIX語義,其中新文件描述符共享狀態,例如文件位置與原始文件描述符。
可以看到,setDataSource除了將媒體文件相關參數保存下來外,並沒有做其他的工作。順便看一看resetDataSource
函數吧:
void NuPlayer::GenericSource::resetDataSource() {
mUri.clear();
mUriHeaders.clear();
if (mFd >= 0) {
close(mFd);
mFd = -1;
}
mOffset = 0;
mLength = 0;
mStarted = false;
mStopRead = true;
if (mBufferingMonitorLooper != NULL) { // 讓BufferingMonitor停止循環監聽buffer
mBufferingMonitorLooper->unregisterHandler(mBufferingMonitor->id());
mBufferingMonitorLooper->stop();
mBufferingMonitorLooper = NULL;
}
mBufferingMonitor->stop();
mMimes.clear();
}
主要有兩個方面作用:
- 將一些媒體資源文件相關索引(值),以及解析器狀態重置爲默認狀態。
- 停止使用讓BufferingMonitor停止循環監聽buffer。
下面來看看如何準備資源的
prepareAsync:準備媒體數據
prepareAsync在播放流程中的位置爲:
-
GenericSource的創建
-
setDataSource:設置媒體源數據
-
prepareAsync:準備媒體數據
-
start:啓動
-
stop&pause&resume:停止&暫停&恢復
void NuPlayer::GenericSource::prepareAsync() {
ALOGV("prepareAsync: (looper: %d)", (mLooper != NULL));
if (mLooper == NULL) { // 創建looper並啓動AHandler循環
mLooper = new ALooper;
mLooper->setName("generic");
mLooper->start();
mLooper->registerHandler(this);
}
sp<AMessage> msg = new AMessage(kWhatPrepareAsync, this);
msg->post();
}
雖然代碼少,但這是一個很重要的調用:創建ALooper並且讓Looper 循環起來了。這個信息告訴我們,GenericSource本身組成了一個NativeHandler體系,用於傳遞自身消息。
GenericSource類通過繼承NuPlayer::Source
間接繼承了AHandler,用於處理消息。
這些,都說明GenericSource的函數會有部分是異步的,函數名中prepareAsync
中的Async也表明了這一點。
不熟悉的朋友可以翻一翻這篇文章:Android媒體底層通信框架Native Handler
啓動了looper循環處理消息後,發送了一個kWhatPrepareAsync的消息,給looper線程來處理。
熟悉NativeHandler的朋友應該知道,GenericSource函數作爲AHandler,必然要重寫onMessageReceived函數,用於處理數據:
void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatPrepareAsync:
{
onPrepareAsync();
break;
}
case kWhatStart:
case kWhatResume:
{
mBufferingMonitor->restartPollBuffering();
break;
}
// .......省略一萬行.......
}
}
AMessage的標誌是kWhatPrepareAsync,在onMessageReceived並沒有做什麼處理,直接調用了onPrepareAsync函數。
void NuPlayer::GenericSource::onPrepareAsync() { // 該函數運行在looper所在的子線程中
// delayed data source creation
if (mDataSource == NULL) { // 第一次進來,mDataSource肯定爲空
mIsSecure = false; // 先設置爲false,如果extractor返回爲安全,再設置爲true.
if (!mUri.empty()) { // 因爲是本地文件,所以mUri不用初始化,自然爲空。
// 略掉網絡媒體源創建DataSource相關代碼。
} else { // 處理本地媒體文件源
// media.stagefright.extractremote屬性一般不會設置,
if (property_get_bool("media.stagefright.extractremote", true) &&
!FileSource::requiresDrm(mFd, mOffset, mLength, nullptr /* mime */)) {
sp<IBinder> binder =
defaultServiceManager()->getService(String16("media.extractor"));
if (binder != nullptr) {
ALOGD("FileSource remote");
sp<IMediaExtractorService> mediaExService(
interface_cast<IMediaExtractorService>(binder));
sp<IDataSource> source =
mediaExService->makeIDataSource(mFd, mOffset, mLength);
ALOGV("IDataSource(FileSource): %p %d %lld %lld",
source.get(), mFd, (long long)mOffset, (long long)mLength);
if (source.get() != nullptr) {
mDataSource = DataSource::CreateFromIDataSource(source);
if (mDataSource != nullptr) { // 過河拆遷,初始化mDataSource成功後
// Close the local file descriptor as it is not needed anymore.
close(mFd);
mFd = -1;
}
}
}
}
if (mDataSource == nullptr) { // 如果沒有從extractor服務中成功獲取DataSource就自己創建
ALOGD("FileSource local");
mDataSource = new FileSource(mFd, mOffset, mLength);
}
mFd = -1;
}
if (mDataSource == NULL) { // 到這裏基本上是不可能爲NULL了
ALOGE("Failed to create data source!");
notifyPreparedAndCleanup(UNKNOWN_ERROR);
return;
}
}
if (mDataSource->flags() & DataSource::kIsCachingDataSource) {
mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get());
}
// For cached streaming cases, we need to wait for enough
// buffering before reporting prepared.
mIsStreaming = (mCachedSource != NULL);
// init extractor from data source
status_t err = initFromDataSource();
// ...
finishPrepareAsync();
ALOGV("onPrepareAsync: Done");
}
從函數代碼中可以看出,該函數唯一的目的就是爲了初始化mDataSource,主要的初始化方式有兩個:
- 從MediaExtractorService服務中獲取。
- 如果第一步未能初始化成功,直接自己創建一個
new FileSource
。
這裏沒有想到的是,Android底層框架爲了解封裝的通用性,直接提供了一個解封裝相關的服務:MediaExtractorService,服務名稱爲:“media.extractor”,NuPlayer作爲衆多播放器的一種,也是可以直接享受該服務的。在這裏就通過該服務,創建了一個DataSource對象。
這裏有個問題,最終NuPLayer使用的到底是通過ExtractorService獲取DataSource對象,還是直接自己new FileSource
呢。
我們當然可以通過日誌來判斷,但這會失去對播放邏輯的學習。所以,我一般都通過代碼來判斷。
因爲代碼調用的先後順序,我們先來看通過服務獲取的過程。
MediaExtractor服務獲取DataSource
// media.stagefright.extractremote屬性一般不會設置,
if (property_get_bool("media.stagefright.extractremote", true) &&
!FileSource::requiresDrm(mFd, mOffset, mLength, nullptr /* mime */)) {
// 通過Binder機制,獲取"media.extractor"服務的遠程代理
sp<IBinder> binder =
defaultServiceManager()->getService(String16("media.extractor"));
if (binder != nullptr) { // 獲取失敗時爲空指針
ALOGD("FileSource remote");
// 強轉爲IMediaExtractorService對象指針
sp<IMediaExtractorService> mediaExService(
interface_cast<IMediaExtractorService>(binder));
// 調用服務的代理對象接口,獲取IDataSource對象指針
sp<IDataSource> source =
mediaExService->makeIDataSource(mFd, mOffset, mLength);
ALOGV("IDataSource(FileSource): %p %d %lld %lld",
source.get(), mFd, (long long)mOffset, (long long)mLength);
if (source.get() != nullptr) {
// 通過獲取IDataSource對象指針初始化mDataSource
mDataSource = DataSource::CreateFromIDataSource(source);
if (mDataSource != nullptr) { // 過河拆遷,初始化mDataSource成功後
// Close the local file descriptor as it is not needed anymore.
close(mFd);
mFd = -1;
}
}
}
}
這一段代碼,比較重要的函數調用,都加上了註釋,這裏再囉嗦得總結一下吧:
getService(String16("media.extractor"))
:熟悉binder機制的同學都知道,這是Binder遠端獲取指定服務的基本操作了。有時間整理一份文章出來,敬請期待吧。mediaExService->makeIDataSource
:調用服務接口,創建IDataSource對象。DataSource::CreateFromIDataSource
:調用CreateFromIDataSource通過前面創建的IDataSource初始化mDataSource。
基本上就這麼回事兒。第一點就不說了,東西太多。這裏稍微展開一下第二、第三點的調用。
makeIDataSource
該函數是通過Binder的遠端調用,最終會調用到服務端的代碼,也就是MediaExtractorService中:
代碼路徑:/frameworks/av/services/mediaextractor/MediaExtractorService.cpp
sp<IDataSource> MediaExtractorService::makeIDataSource(int fd, int64_t offset, int64_t length)
{
sp<DataSource> source = DataSource::CreateFromFd(fd, offset, length);
return source.get() != nullptr ? source->asIDataSource() : nullptr;
}
再看CreateFromFd幹了啥:
sp<DataSource> DataSource::CreateFromFd(int fd, int64_t offset, int64_t length) {
sp<FileSource> source = new FileSource(fd, offset, length); // 也是直接new了FileSource
return source->initCheck() != OK ? nullptr : source; // 檢查是否有sp時候爲有效指針,有效把指針丟回去
}
咦~這代碼看着耳熟啊!!!也是直接new FileSource
。
那個
initCheck()
函數,就不說了,說多了又是長篇大論。
CreateFromIDataSource
sp<DataSource> DataSource::CreateFromIDataSource(const sp<IDataSource> &source) {
return new TinyCacheSource(new CallbackDataSource(source));
}
我去,又來了兩個陌生的類,其實他們都和DataSource有千絲萬縷的聯繫,看看一下類圖:
有關CallbackDataSource
和TinyCacheSource
的定義,都在CallbackDataSource.h頭文件中。它們的定義都比較簡單,就不貼代碼了,有興趣自己去看,源碼路徑如下:
\frameworks\av\include\media\stagefright\CallbackDataSource.h
\frameworks\av\media\libstagefright\CallbackDataSource.cpp
下面來稍微總結一下前面的類圖:
-
DataSource
:該類規定了媒體源文件基本的操作接口。 -
IDataSource
:它是實現遠程調用stagefright DataSource的Binder接口。Android媒體相關的各種服務中,創建的DataSource
對象,都通過這個client的遠程接口句柄來調用。 -
CallbackDataSource
:實現了DataSource接口(實現關係),但它的私有字段mIDataSource
中,保留了IDataSource
(服務端DataSource)的引用(組合關係),讓Client端程序可以回調到server端的DataSource
對象,從而具備了”回調“功能。 -
TinyCacheSource
:該類實現了DataSource
接口(實現關係),在私有字段mSource
中可以持有DataSource
的引用,這個引用通常是用來存放CallbackDataSource
對象的,所以和CallbackDataSource
形成了組合關係。另外,該類中還有一個用於緩存的數組mCache[kCacheSize]
,對於小於kCacheSize
的讀取,它將提前讀取並緩存在mCache
中,這不僅極大減少了Client端到Server端的數據讀取操作,對提高數據類型嗅探和元數據(metadata)的提取也有較高效率。
回頭來看代碼:
return new TinyCacheSource(new CallbackDataSource(source));
也就稀鬆平常了,不過是將server端的FileSource
對象,通過IDataSource
接口傳遞到client端後,依次通過CallbackDataSource
、TinyCacheSource
對象包起來,已達到後續可以通過IDataSource
對象調用遠端FileSource
對象的目的。
new FileSource
整個onPrepareAsync
函數執行的前一部分,都在想法設法的通過"media.extractor"服務,獲取初始化mDataSource
字段,如果初始化失敗,那麼這個這個邏輯不會執行,如果失敗,那麼mDataSource
的值爲NULL
。
if (mDataSource == nullptr) { // 如果沒有從extractor服務中成功獲取DataSource就自己創建
ALOGD("FileSource local");
mDataSource = new FileSource(mFd, mOffset, mLength);
}
mFd = -1;
這段代碼就比較簡潔,直接創建一個FileSource,將文件句柄和偏移量,長度等信息作出構造參數傳遞過去。這裏就先不展開FileSource源碼的分析,後面涉及到的時候再聊。
小結
所以,總結一下prepareAsync函數:
- 該函數是異步執行的,整整的prepare動作,是在子線程執行的
onPrepareAsync
函數中。 onPrepareAsync
函數主要的作用就是初始化mDataSource
字段。共有兩種方式,首相嘗試通過"media.extractor"服務獲取server端DataSource
,失敗後嘗試直接自己new FileSource
。- 遠端服務實例化
mDataSource
能否成功,主要看該服務在系統中是否啓用(一般來說都是正常運行的)。 - 如果無法通過"media.extractor"初始化
mDataSource
,就直接自己創建(new FileSource
)。 - 不管通過server端還是自己new的方式,
mDataSource
最終關聯的對象都是FileSource
的實例。
initFromDataSource
想方設法將mDataSource字段初始化後,接着往下看(不考慮初始化失敗的場景)。
if (mDataSource->flags() & DataSource::kIsCachingDataSource) { // 16 & 4 = 0
mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get());
}
mIsStreaming = (mCachedSource != NULL); // mIsStreaming = false
// 通過data source初始化extractorinit
status_t err = initFromDataSource();
mDataSource->flags()
這段代碼,會經歷漫長的路程,大概是這樣:
mDataSource->flags() ==>> TinyCacheSource::flags() ==>> CallbackDataSource::flags() ==>> FileSource::flags()。
virtual uint32_t flags() {
return kIsLocalFileSource;
}
最終返回一個固定的值kIsLocalFileSource
也就是16。該值定義在一個DataSource的結構體中:
enum Flags {
kWantsPrefetching = 1,
kStreamedFromLocalHost = 2,
kIsCachingDataSource = 4,
kIsHTTPBasedSource = 8,
kIsLocalFileSource = 16,
};
而DataSource::kIsCachingDataSource
的值爲4,16&4 = 0。結果可想而知,哎,走了步閒棋。
該幹正事了,分析一下這段調用中最重要的函數之一:initFromDataSource
。
status_t NuPlayer::GenericSource::initFromDataSource() {
sp<IMediaExtractor> extractor;
extractor = MediaExtractor::Create(mDataSource, NULL); // 創建
mFileMeta = extractor->getMetaData();
if (mFileMeta != NULL) {
int64_t duration;
if (mFileMeta->findInt64(kKeyDuration, &duration)) {
mDurationUs = duration;
}
}
int32_t totalBitrate = 0;
size_t numtracks = extractor->countTracks();
mMimes.clear();
for (size_t i = 0; i < numtracks; ++i) {
sp<IMediaSource> track = extractor->getTrack(i);
sp<MetaData> meta = extractor->getTrackMetaData(i);
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
ALOGV("initFromDataSource track[%zu]: %s", i, mime);
if (!strncasecmp(mime, "audio/", 6)) {
if (mAudioTrack.mSource == NULL) {
mAudioTrack.mIndex = i;
mAudioTrack.mSource = track;
mAudioTrack.mPackets =
new AnotherPacketSource(mAudioTrack.mSource->getFormat());
if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
mAudioIsVorbis = true;
} else {
mAudioIsVorbis = false;
}
mMimes.add(String8(mime));
}
} else if (!strncasecmp(mime, "video/", 6)) {
if (mVideoTrack.mSource == NULL) {
mVideoTrack.mIndex = i;
mVideoTrack.mSource = track;
mVideoTrack.mPackets =
new AnotherPacketSource(mVideoTrack.mSource->getFormat());
// video always at the beginning
mMimes.insertAt(String8(mime), 0);
}
}
mSources.push(track);
int64_t durationUs;
if (meta->findInt64(kKeyDuration, &durationUs)) {
if (durationUs > mDurationUs) {
mDurationUs = durationUs;
}
}
int32_t bitrate;
if (totalBitrate >= 0 && meta->findInt32(kKeyBitRate, &bitrate)) {
totalBitrate += bitrate;
} else {
totalBitrate = -1;
}
}
ALOGV("initFromDataSource mSources.size(): %zu mIsSecure: %d mime[0]: %s", mSources.size(),
mIsSecure, (mMimes.isEmpty() ? "NONE" : mMimes[0].string()));
mBitrate = totalBitrate;
return OK;
}
IMediaExtractor創建
代碼挺長,先來看看MediaExtractor::Create(mDataSource, NULL)
:
// static
sp<IMediaExtractor> MediaExtractor::Create(
const sp<DataSource> &source, const char *mime) {
if (!property_get_bool("media.stagefright.extractremote", true)) {
// 本地 extractor
ALOGW("creating media extractor in calling process");
return CreateFromService(source, mime);
} else { // 使用遠程extractor
ALOGV("get service manager");
sp<IBinder> binder = defaultServiceManager()->getService(String16("media.extractor"));
if (binder != 0) {
sp<IMediaExtractorService> mediaExService(interface_cast<IMediaExtractorService>(binder));
sp<IMediaExtractor> ex = mediaExService->makeExtractor(source->asIDataSource(), mime);
return ex;
} else {
ALOGE("extractor service not running");
return NULL;
}
}
return NULL;
}
第一個條件判斷,獲取系統屬性“media.stagefright.extractremote”,通常該屬性都是默認未設置的。
media.stagefright.extractremote系統屬性,用於判斷是否之處遠程extractor服務。
所以property_get_bool
調用返回默認值true
,!true
則條件不成立。所以通過遠程服務“media.extractor”創建一個MediaExtractor返回。
MediaExtractorService::makeExtractor
sp<IMediaExtractor> MediaExtractorService::makeExtractor(
const sp<IDataSource> &remoteSource, const char *mime) {
ALOGV("@@@ MediaExtractorService::makeExtractor for %s", mime);
sp<DataSource> localSource = DataSource::CreateFromIDataSource(remoteSource);
sp<IMediaExtractor> ret = MediaExtractor::CreateFromService(localSource, mime);
ALOGV("extractor service created %p (%s)", ret.get(), ret == NULL ? "" : ret->name());
if (ret != NULL) {
registerMediaExtractor(ret, localSource, mime);
}
return ret;
}
DataSource::CreateFromIDataSource
前面已經詳細說明了,這裏就不贅述了。來看看更重要的函數
MediaExtractor::CreateFromService
sp<MediaExtractor> MediaExtractor::CreateFromService(
const sp<DataSource> &source, const char *mime) {
ALOGV("MediaExtractor::CreateFromService %s", mime);
RegisterDefaultSniffers();
sp<AMessage> meta;
String8 tmp;
if (mime == NULL) {
float confidence;
if (!sniff(source, &tmp, &confidence, &meta)) {
ALOGW("FAILED to autodetect media content.");
return NULL;
}
mime = tmp.string();
}
MediaExtractor *ret = NULL;
if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
|| !strcasecmp(mime, "audio/mp4")) {
ret = new MPEG4Extractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
ret = new MP3Extractor(source, meta);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
ret = new AMRExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
ret = new FLACExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
ret = new WAVExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
ret = new OggExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
ret = new MatroskaExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
ret = new MPEG2TSExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
ret = new AACExtractor(source, meta);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {
ret = new MPEG2PSExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MIDI)) {
ret = new MidiExtractor(source);
}
// 略掉了一些跟蹤信息的代碼
return ret;
}
天哪嚕,感覺又捅了一個馬蜂窩,哎,源碼真是輕易看不得啊啊啊啊啊啊。內容是真的多,感覺又可以單獨拎出來,另起一篇了。
來看看MediaExtractor的類圖吧。
MediaExtractor::RegisterDefaultSniffers
// static
void MediaExtractor::RegisterDefaultSniffers() {
Mutex::Autolock autoLock(gSnifferMutex);
if (gSniffersRegistered) { // 只註冊一次
return;
}
RegisterSniffer_l(SniffMPEG4);
RegisterSniffer_l(SniffMatroska);
RegisterSniffer_l(SniffOgg);
RegisterSniffer_l(SniffWAV);
RegisterSniffer_l(SniffFLAC);
RegisterSniffer_l(SniffAMR);
RegisterSniffer_l(SniffMPEG2TS);
RegisterSniffer_l(SniffMP3);
RegisterSniffer_l(SniffAAC);
RegisterSniffer_l(SniffMPEG2PS);
RegisterSniffer_l(SniffMidi);
gSniffersRegistered = true;
}
snif
f的意思是“嗅、用鼻子吸“,sniffer
可以翻譯爲”嗅探器“。所以,該函數是註冊默認嗅探器的意思。
在媒體框架中,嗅探器又是個什麼概念呢?
這裏的嗅探,實際上是對媒體輸入源進行文件頭的讀取,根據文件頭內容嗅探出需要什麼樣的解封裝組件,也就是不同的MediaExtractor
實現。通過類圖,我們也可以知道MediaExtractor有大量的實現,分別針對MP3、AAC、OGG、WAV、MPEG4等格式輸入的解封裝操作。
回到代碼上,函數體中,通過RegisterSniffer_l
函數,將不同解封裝器的嗅探函數指針保存到了列表gSniffers
中。顯然,針對於不同封裝格式的解封裝器,嗅探函數也是不一樣的,當然需要對應的解封裝器自己實現。
具體註冊嗅探器的代碼如下:
List<MediaExtractor::SnifferFunc> MediaExtractor::gSniffers;
// static
void MediaExtractor::RegisterSniffer_l(SnifferFunc func) {
for (List<SnifferFunc>::iterator it = gSniffers.begin();
it != gSniffers.end(); ++it) {
if (*it == func) {
return;
}
}
gSniffers.push_back(func);
}
MediaExtractor::sniff
// static
bool MediaExtractor::sniff(
const sp<DataSource> &source, String8 *mimeType, float *confidence, sp<AMessage> *meta) {
*mimeType = "";
*confidence = 0.0f;
meta->clear();
{
Mutex::Autolock autoLock(gSnifferMutex);
if (!gSniffersRegistered) { // 在“嗅探”之前必須已經註冊了嗅探器
return false;
}
}
for (List<SnifferFunc>::iterator it = gSniffers.begin();
it != gSniffers.end(); ++it) { // 遍歷所有嗅探器
String8 newMimeType;
float newConfidence;
sp<AMessage> newMeta;
if ((*it)(source, &newMimeType, &newConfidence, &newMeta)) { // 調用嗅探器的嗅探函數
if (newConfidence > *confidence) {
*mimeType = newMimeType;
*confidence = newConfidence;
*meta = newMeta;
}
}
}
return *confidence > 0.0;
}
嗅探器的實現原理基本上都是讀取媒體源文件的頭信息,不同格式都會有自己的特徵,嗅探器就是根據這些特徵,來判斷是否是需要找的類型。
嗅探函數的目的,就是判斷源文件(碼流)類型是否和當前格式匹配。
說到嗅探函數的實現,必然會涉及到各種編碼格式的特徵,鑑於這部分內容實在太多,就不進一步詳細分析了。
這裏就簡單的說一下幾個參數的意義:
-
source:這是個
DataSource
類型的指針,該類型通過層層包裹包含了一系列讀取媒體源文件的功能。嗅探函數通過該指針通源文件中讀取頭信息,來判斷源文件的類型。 -
newMimeType:
String8
類型的指針,一旦嗅探函數通過頭信息探測出源文件屬於當前類型,該變量會通過指針賦值。這些類型定義在MediaDefs.cpp
中如:const char *MEDIA_MIMETYPE_IMAGE_JPEG = "image/jpeg"; const char *MEDIA_MIMETYPE_VIDEO_HEVC = "video/hevc"; const char *MEDIA_MIMETYPE_VIDEO_MPEG2 = "video/mpeg2"; const char *MEDIA_MIMETYPE_AUDIO_MPEG = "audio/mpeg"; const char *MEDIA_MIMETYPE_AUDIO_FLAC = "audio/flac"; const char *MEDIA_MIMETYPE_AUDIO_AC3 = "audio/ac3"; const char *MEDIA_MIMETYPE_AUDIO_EAC3 = "audio/eac3"; const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mp4"; const char *MEDIA_MIMETYPE_CONTAINER_AVI = "video/avi"; // ......................此處略去一萬字
-
newConfidence:
float
類型指針,一旦嗅探函數通過頭信息探測出源文件屬於當前類型,該變量會通過指針賦值。該值的意思是**“信心”,每個判斷了是自己類型的函數,都會給出對於源文件類型的判斷的信心值**,然後通過比較,信心值最大的類型判斷獲勝,該源文件便會被判定爲該類型。例如:SniffAAC對自己的信心值爲:0.2、SniffMPEG4:0.4、SniffMatroska:0.6、SniffOgg:0.2等。 -
newMeta:這是一個AMessage對象,用於將嗅探結果(一些和格式相關的頭信息)傳遞給調用者。對於不同格式傳遞的類型會不一樣。
SniffMPEG4傳遞的是:文件頭結束的偏移量
if (moovAtomEndOffset >= 0) { mpeg4 *meta = new AMessage; (*meta)->setInt64("meta-data-size", moovAtomEndOffset); }
SniffAAC傳遞:也是文件頭結束的偏移位置。
*meta = new AMessage; aac (*meta)->setInt64("offset", pos);
SniffMP3:有文件頭結束的偏移量,也有特有的格式位置信息。
*meta = new AMessage; (*meta)->setInt64("offset", pos); (*meta)->setInt32("header", header); (*meta)->setInt64("post-id3-offset", post_id3_pos);
最後說一下整個嗅探函數的返回值return *confidence > 0.0;
只需要信息之大於0.0就返回true
,說明已經嗅探到格式信息。
一般來說,支持嗅探的格式越多,失敗的可能越小。Android默認支持的類型已經足夠普通使用了,所以,分析的時候我就當它返回true
了。
回到MediaExtractor::CreateFromService
中,經過嗅探函數對源文件進行嗅探後,基本能夠確定源文件的類型,並把嗅探出來的newMimeType
字符串的指針賦值給mime
,最終通過在CreateFromService對該類型進行比較,創建對應類型的XXXExtractor。代碼如下:
創建指定格式的Extractor
MediaExtractor *ret = NULL;
if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
|| !strcasecmp(mime, "audio/mp4")) {
ret = new MPEG4Extractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
ret = new MP3Extractor(source, meta);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
ret = new AMRExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
ret = new FLACExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
ret = new WAVExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
ret = new OggExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
ret = new MatroskaExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
ret = new MPEG2TSExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
ret = new AACExtractor(source, meta);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {
ret = new MPEG2PSExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MIDI)) {
ret = new MidiExtractor(source);
}
創建完指定的MediaExtractor
之後,還需要將剛剛創建的XXXExtractor
註冊一下:
if (ret != NULL) {
registerMediaExtractor(ret, localSource, mime);
}
registerMediaExtractor
void IMediaExtractor::registerMediaExtractor(
const sp<IMediaExtractor> &extractor,
const sp<DataSource> &source,
const char *mime) {
ExtractorInstance ex;
ex.mime = mime == NULL ? "NULL" : mime;
ex.name = extractor->name();
ex.sourceDescription = source->toString();
ex.owner = IPCThreadState::self()->getCallingPid();
ex.extractor = extractor;
// ...
sExtractors.push_front(ex);
// ...
}
該函數很短,作用也很簡單,直接將闖入的XXXExtractor
實例用ExtractorInstance
包裝一下,存放在sExtractors
(一個vector
)隊首中。方便以後查詢。
至此IMediaExtractor
創建工作纔算完成。
小結
簡單小結一下創建過程中都做了那些值得注意的事:
- 將之前流程中創建的
FileSource
對象,通過包裝成了一個DataSource
對象。 - 註冊各種格式
Extractor
的嗅探函數。 - 通過調用嗅探函數,利用
DataSource
讀取媒體文件頭,並分析媒體文件是何種格式。 - 根據媒體文件格式,創建對應格式的
XXXExtractor
。
####初始化媒體源基本參數:mFileMeta、mDurationUs
initFromDataSource
函數通過千辛萬苦創建了Extractor
後,任務其實已經完成了一大半了。後面都是一些從Extractor
實例後的對象中拿數據進行填充的過程。
接着看看後面初始化mFileMeta
和mDurationUs
的調用
mFileMeta = extractor->getMetaData();
if (mFileMeta != NULL) {
int64_t duration;
if (mFileMeta->findInt64(kKeyDuration, &duration)) {
mDurationUs = duration;
}
}
第一行代碼,通過extractor調用getMetaData獲取文件的元數據(metadata)。對於getMetaData函數的實現,不同類型的Extractor會有不同的實現手段,例如:
sp<MetaData> MPEG4Extractor::getMetaData() {
status_t err;
if ((err = readMetaData()) != OK) { // 從源文件中讀取MetaData信息,初始化mFileMetaData
return new MetaData;
}
return mFileMetaData; // 將MetaData 返回給調用者
}
sp<MetaData> MP3Extractor::getMetaData() {
sp<MetaData> meta = new MetaData; // 直接new
if (mInitCheck != OK) {
return meta;
}
// ...
meta->setCString(kKeyMIMEType, "audio/mpeg");
// ...
meta->setCString(kMap[i].key, s);
// ...
meta->setData(kKeyAlbumArt, MetaData::TYPE_NONE, data, dataSize);
meta->setCString(kKeyAlbumArtMIME, mime.string());
return meta;
}
MPEG4Extractor
和MP3Extractor
的getMetaData
函數實現就大爲不同,不能再展開了。這裏只順便提及一下什麼是元數據(MetaData):
對於媒體文件而言,元數據一般有:音頻採樣率、視頻幀率、視頻尺寸、比特率、編解碼、播放時長等基本信息,此外也可能含有其它雜七雜八的信息:名稱、版權、專輯、時間、藝術家等。
初始化媒體源基本參數:mMimes、mSources、mBitrate
下面這些代碼就不一一解讀了,啥都不用說,都在註釋裏。
int32_t totalBitrate = 0;
size_t numtracks = extractor->countTracks(); // 獲取媒體源中的軌道數量,通常爲三個,音頻、視頻、字幕各一個
mMimes.clear(); // 清理掉mMime信息,準備裝新的。
for (size_t i = 0; i < numtracks; ++i) { // 遍歷軌道,將音視頻軌道信息的mime添加到mMimes中
sp<IMediaSource> track = extractor->getTrack(i); // 獲取各軌道
sp<MetaData> meta = extractor->getTrackMetaData(i); // 獲取各軌道的元數據
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
ALOGV("initFromDataSource track[%zu]: %s", i, mime);
if (!strncasecmp(mime, "audio/", 6)) { // 音頻軌道
if (mAudioTrack.mSource == NULL) { // 初始化各種音頻軌道信息
mAudioTrack.mIndex = i;
mAudioTrack.mSource = track;
mAudioTrack.mPackets =
new AnotherPacketSource(mAudioTrack.mSource->getFormat());
if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
mAudioIsVorbis = true;
} else {
mAudioIsVorbis = false;
}
mMimes.add(String8(mime)); // 將音頻軌道mime信息,添加到mMimes中
}
} else if (!strncasecmp(mime, "video/", 6)) { // 視頻軌道
if (mVideoTrack.mSource == NULL) { // 初始化各種視頻軌道信息
mVideoTrack.mIndex = i;
mVideoTrack.mSource = track;
mVideoTrack.mPackets =
new AnotherPacketSource(mVideoTrack.mSource->getFormat());
// video always at the beginning
mMimes.insertAt(String8(mime), 0); // 將視頻軌道mime信息,添加到mMimes隊首
}
}
mSources.push(track); // 將各軌道信息統一保存在保存在mSources中
int64_t durationUs;
if (meta->findInt64(kKeyDuration, &durationUs)) { // 獲取媒體播放時長
if (durationUs > mDurationUs) { // 將個軌道中最大的播放時長作爲媒體文件的播放時長
mDurationUs = durationUs;
}
}
// 通比特率爲各軌道比特率之和
int32_t bitrate;
if (totalBitrate >= 0 && meta->findInt32(kKeyBitRate, &bitrate)) {
totalBitrate += bitrate;
} else {
totalBitrate = -1;
}
}
mBitrate = totalBitrate; // 初始化比特率
扯了這麼多,才把initFromDataSource
搞完,趕緊看一下最後一個函數:
finishPrepareAsync
void NuPlayer::GenericSource::finishPrepareAsync() {
ALOGV("finishPrepareAsync");
status_t err = startSources(); // 啓動各資源對象
// ....
if (mIsStreaming) { // 通常爲false
// ....
} else {
notifyPrepared(); // 該函數幾乎啥都沒做。
}
}
status_t NuPlayer::GenericSource::startSources() {
// 在我們開始緩衝之前,立即啓動所選的A / V曲目。
// 如果我們將它延遲到start(),那麼在準備期間緩衝的所有數據都將被浪費。
// (並不是在start()開始執行後,纔開始讀取數據)
if (mAudioTrack.mSource != NULL && mAudioTrack.mSource->start() != OK) { // 啓動音頻
ALOGE("failed to start audio track!");
return UNKNOWN_ERROR;
}
if (mVideoTrack.mSource != NULL && mVideoTrack.mSource->start() != OK) { // 啓動視頻
ALOGE("failed to start video track!");
return UNKNOWN_ERROR;
}
return OK;
}
除了註釋部分的信息,關於mSource->start()
我也沒什麼準備在本文補充了,如果以後有機會,我會寫n篇來盡未盡之事。
小結prepareSync
好了,prepareSync
函數算是告一段落。花了巨大的篇幅來描述,總要總結一波的。
- 不管是通過直接創建,還是通過服務創建,總之拐彎抹角的創建了一個
FileSource
對象。並通過各種封裝,達到不一樣的用途。 - 通過各種封裝好的
FileSource
對象,讀取並嗅探源文件的頭信息,判斷文件格式,並創建對應格式的XXXExtractor
。 - 通過創建好的
XXXExtractor
,初始化各種媒體相關字段,如:mFileMeta
、mDurationUs
、mMimes
、mSources
、mBitrate
。 mSources
中包含所有track
信息,track
中又包含了對應的流信息,通過這些信息,啓動了音視頻數據的讀取。
start:啓動
start在播放流程中的位置爲:
-
GenericSource的創建
-
setDataSource:設置媒體源數據
-
prepareAsync:準備媒體數據
-
start:啓動
-
stop&pause&resume:停止&暫停&恢復
現在我們來看看GenericSource是怎麼參與到播放流程中的。
void NuPlayer::GenericSource::start() {
ALOGI("start");
mStopRead = false; // 啓動播放時,自然要不暫停讀取數據false掉
if (mAudioTrack.mSource != NULL) { // 在prepareAsync中,已經賦值,自然不能爲空
postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
}
if (mVideoTrack.mSource != NULL) { // 在prepareAsync中,已經賦值,自然不能爲空
postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
}
mStarted = true;
(new AMessage(kWhatStart, this))->post();
}
postReadBuffer函數其實做的事情不多,就是把trackType一路向下異步傳遞,最後讓NuPlayer::GenericSource::readBuffer
摘桃子,調用鏈如下:
postReadBuffer ==> onMessageReceived ==> onReadBuffer ==> readBuffer
基本上沒啥看頭,跳過,直接來readBuffer
NuPlayer::GenericSource::readBuffer
void NuPlayer::GenericSource::readBuffer(
media_track_type trackType, int64_t seekTimeUs, MediaPlayerSeekMode mode,
int64_t *actualTimeUs, bool formatChange)
if (mStopRead) {
return;
}
Track *track;
size_t maxBuffers = 1;
switch (trackType) { // 根據track類型分配最大buffer,並初始化track
case MEDIA_TRACK_TYPE_VIDEO: // 音頻
track = &mVideoTrack;
maxBuffers = 8; // 最大buffer值爲64,太大的buffer值會導致不能流暢的執行seek操作。
break;
case MEDIA_TRACK_TYPE_AUDIO: // 視頻
track = &mAudioTrack;
maxBuffers = 64; // 最大buffer值爲64
break;
case MEDIA_TRACK_TYPE_SUBTITLE: // 字幕
track = &mSubtitleTrack;
break;
// 篇幅有限,能省一行是一行
}
// 篇幅有限,能省一行是一行
for (size_t numBuffers = 0; numBuffers < maxBuffers; ) {
Vector<MediaBuffer *> mediaBuffers;
status_t err = NO_ERROR;
// 從文件中讀取媒體數據,用於填充mediaBuffers
if (couldReadMultiple) { // 這個值一般爲true
err = track->mSource->readMultiple(
&mediaBuffers, maxBuffers - numBuffers, &options);
} else { // read函數其實最終也是調用了readMultiple,只是read的最大buffer數爲1
MediaBuffer *mbuf = NULL;
err = track->mSource->read(&mbuf, &options);
if (err == OK && mbuf != NULL) {
mediaBuffers.push_back(mbuf);
}
}
size_t id = 0;
size_t count = mediaBuffers.size();
for (; id < count; ++id) { // 將所有剛纔讀到的MediaBuffer中的數據摘出來封裝到mPackets中
int64_t timeUs;
MediaBuffer *mbuf = mediaBuffers[id];
// 根據類型,通過mBufferingMonitor監視器更新狀態
if (trackType == MEDIA_TRACK_TYPE_AUDIO) {
mAudioTimeUs = timeUs;
mBufferingMonitor->updateQueuedTime(true /* isAudio */, timeUs);
} else if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
mVideoTimeUs = timeUs;
mBufferingMonitor->updateQueuedTime(false /* isAudio */, timeUs);
}
// 根據類型,將MediaBuffer轉換爲ABuffer
sp<ABuffer> buffer = mediaBufferToABuffer(mbuf, trackType);
// 篇幅有限,能省一行是一行
track->mPackets->queueAccessUnit(buffer); // 將buffer入隊,等待播放
formatChange = false;
seeking = false;
++numBuffers;
}
}
}
除了trackType
參數外,其它都是有默認參數的,在start調用鏈中,readBuffer
只傳入了這個參數。其它參數可以控制seek功能。代碼其實比這個長多了,我刪掉了些暫時不重要的seek、異常中斷等邏輯。
能說的代碼註釋裏說了,繼續看一下start
函數接下來發送的kWhatResume
消息幹了啥
case kWhatResume:
{
mBufferingMonitor->restartPollBuffering(); // 只是讓buffer監視器重新循環起來
break;
}
start小結
- start函數調用鏈中,最終的的就是readBuffer函數,該函數最終要的功能就是將各種類型的數據讀取並解析到track的buffer隊列中,等待播放。
- 需要注意的是:解封裝模塊的start函數和NuPlayer的start功能並不相同,NuPlayer的start函數是播放,而解封裝模塊的start函數則是加載數據,後者是前者的子集。
stop&pause&resume:停止&暫停&恢復
start在播放流程中的位置爲:
-
GenericSource的創建
-
setDataSource:設置媒體源數據
-
prepareAsync:準備媒體數據
-
start:播放視頻
-
stop&pause&resume:停止&暫停&恢復
void NuPlayer::GenericSource::stop() { // 停止
mStarted = false;
}
void NuPlayer::GenericSource::pause() { // 暫停
mStarted = false;
}
void NuPlayer::GenericSource::resume() { // 恢復
mStarted = true;
(new AMessage(kWhatResume, this))->post();
}
停止、暫停、恢復幾個動作,相關函數中僅是改變mStarted,其它幾乎什麼事情都沒做。
這有提醒了我解封裝模塊和播放器的區別:
- 播放器的暫停:表示的是暫停播放
- 解封裝模塊的暫停:表示暫停將讀取並緩存好的數據提供給播放器,這一點同樣適用於停止,回覆和start則相反。
所以,不管是停止、暫停還是回覆的函數,關鍵都不在函數本身,而在於mStarted變量對於向外提供數據的函數的影響,也就是dequeueAccessUnit
。
status_t NuPlayer::GenericSource::dequeueAccessUnit(
bool audio, sp<ABuffer> *accessUnit) {
if (audio && !mStarted) { // 如果是音頻,並且mStarted爲false,則不提供數據,返回block
return -EWOULDBLOCK;
}
// ...
}
該函數用於爲播放器提供原始媒體數據,audio表示是否爲音頻,accessUnit則是需要填充的buffer指針。
可以看到,如果GenericSource::stop()
或者GenericSource::pause()
函數調用後,mStarted變爲了false,那麼播放器將無法得到媒體數據,也就無法播放了。
那麼,有人問,如果是視頻不就可以了麼。是的,視頻還是可以從該函數中獲取數據,但對於播放器而言,視頻和音頻肯定是同時播放,如果沒了音頻,視頻也不會獨活的。
好了,解封裝模塊終於搞定了。媽呀!