"本文转载自:[yanbixing123]的Android MultiMedia框架完全解析 - prepareAsync的过程分析"
1.概述
MediaPlayer播放音视频时,首先是setDataSource()方法设置数据源到GenericSource,然后是执行prepareAsync。java层调用的prepareAsync()方法最终还是要回归到MediaPlayerService中的Client中,整体流程如下:
![](https://img.haomeiwen.com/i18565088/2430099348561258.png)
前半部分的流程主要是prepareAsync()方法的层层调用,内容较为简单。下面将重要的过程拉出来分析。
2.DataSource
根据上面的整体流程图,第4步中调用到GenericSource::prepareAsync()方法,具体代码如下:
- GenericSource.cpp
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();
}
这个NuPlayer::GenericSource继承自NuPlayer::Source,而NuPlayer::Source又继承自AHandler,所以在GenericSource中也可以使用AHandler-Amessage-ALooper机制,在这个函数中创建了ALooper,并且设置Looper的Handler为这个GenericSource,然后发送kWhatPrepareAsync这个AMessage来交给onMessageReceived函数来运行,最终运行到NuPlayer::GenericSource::onPrepareAsync函数中(GenericSource.cpp):
void NuPlayer::GenericSource::onPrepareAsync() {
ALOGV("onPrepareAsync: mDataSource: %d", (mDataSource != NULL));
// delayed data source creation
if (mDataSource == NULL) {
// set to false first, if the extractor
// comes back as secure, set it to true then.
mIsSecure = false;
if (!mUri.empty()) {// 因为是本地文件,所以mUri不用初始化,自然为空。
// 略掉网络媒体源创建DataSource相关代码。
} else {// 处理本地媒体文件源
// 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) {
close(mFd);
mFd = -1;
}
} else {
ALOGW("extractor service cannot make data source");
}
} else {
ALOGW("extractor service not running");
}
}
// 如果没有从extractor服务中成功获取DataSource就自己创建
if (mDataSource == nullptr) {
ALOGD("FileSource local");
mDataSource = new FileSource(mFd, mOffset, mLength);
}
// TODO: close should always be done on mFd, see the lines following
// DataSource::CreateFromIDataSource above,
// and the FileSource constructor should dup the mFd argument as needed.
mFd = -1;
}
...
}
// init extractor from data source
status_t err = initFromDataSource();
...
finishPrepareAsync();
ALOGV("onPrepareAsync: Done");
}
Android底层框架为了解封装的通用性,直接提供了一个解封装相关的服务:MediaExtractorService,服务名称为:“media.extractor”。NuPlayer作为众多播放器的一种,也是可以直接使用该服务的。在这里就通过该服务,创建了一个DataSource对象,并为其初始化。主要的初始化方式有两个:
(1)从MediaExtractorService服务中获取。(最终还是new FileSource)
(2)如果第一步未能初始化成功,直接自己创建一个new FileSource。
-
getService(String16("media.extractor")):Binder远端获取指定服务;
-
mediaExService->makeIDataSource:调用服务接口,创建IDataSource对象;
-
DataSource::CreateFromIDataSource:调用CreateFromIDataSource通过前面创建的IDataSource初始化mDataSource。
接下来再看一下第8步,调用CreateFromIDataSource()方法:
sp<DataSource> DataSource::CreateFromIDataSource(const sp<IDataSource> &source) {
return new TinyCacheSource(new CallbackDataSource(source));
}
将server端的FileSource对象,通过IDataSource接口传递到client端后,依次通过CallbackDataSource、TinyCacheSource对象包起来,已达到后续可以通过IDataSource对象调用远端FileSource对象的目的。
![](https://img.haomeiwen.com/i18565088/73fbc92cde92c5fb.png)
-
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)的提取也有较高效率。
总结:
-
prepareAsync()方法是异步执行的,整个的prepare动作是在子线程执行的onPrepareAsync函数中。
-
onPrepareAsync函数主要的作用就是初始化mDataSource字段。共有两种方式,首先尝试通过"media.extractor"服务获取server端DataSource,失败后尝试直接自己new FileSource。
-
远端服务实例化mDataSource能否成功,主要看该服务在系统中是否启用(一般来说都是正常运行的)。
-
如果无法通过"media.extractor"初始化mDataSource,就直接自己创建(new FileSource)。
-
不管通过server端还是自己new的方式,mDataSource最终关联的对象都是FileSource的实例。
3.initFromDataSource
上接NuPlayer::GenericSource::onPrepareAsync()方法中在创建DataSource对象后,会调用initFromDataSource接口从数据源初始化提取器。接下来的代码流程如下:
![](https://img.haomeiwen.com/i18565088/89a171fff8446e78.png)
(1)首先,看一下第2步的initFromDataSource()方法:
status_t NuPlayer::GenericSource::initFromDataSource() {
sp<IMediaExtractor> extractor;
CHECK(mDataSource != NULL);
// 创建提取器
extractor = MediaExtractor::Create(mDataSource, NULL,
mIsStreaming ? 0 : AVNuUtils::get()->getFlags());
...
mFileMeta = extractor->getMetaData(); // 获取元数据
...
size_t numtracks = extractor->countTracks(); // 获取媒体源中的轨道数量,通常为三个,音频、视频、字幕各一个
...
for (size_t i = 0; i < numtracks; ++i) {// 遍历轨道,将音视频轨道信息的mime添加到mMimes中
sp<IMediaSource> track = extractor->getTrack(i);// 获取各轨道
...
sp<MetaData> meta = extractor->getTrackMetaData(i);// 获取各轨道的元数据
...
// Do the string compare immediately with "mime",
// we can't assume "mime" would stay valid after another
// extractor operation, some extractors might modify meta
// during getTrack() and make it invalid.
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;
}
if (AVNuUtils::get()->isByteStreamModeEnabled(meta)) {
mIsByteMode = true;
}
mMimes.add(String8(mime));// 将音频轨道mime信息,添加到mMimes中
}
} else if (!strncasecmp(mime, "video/", 6)) {// 视频轨道
if (!strncasecmp(mime, MEDIA_MIMETYPE_VIDEO_NOT_SUPPORT, 17)) {
ALOGE("video codec not support");
return UNKNOWN_ERROR;
}
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;
}
}
ALOGV("initFromDataSource mSources.size(): %zu mIsSecure: %d mime[0]: %s", mSources.size(),
mIsSecure, (mMimes.isEmpty() ? "NONE" : mMimes[0].string()));
if (mSources.size() == 0) {
ALOGE("b/23705695");
return UNKNOWN_ERROR;
}
// Modular DRM: The return value doesn't affect source initialization.
(void)checkDrmInfo();
// 初始化比特率
mBitrate = totalBitrate;
return OK;
}
这里首先会根据mDataSource类型创建对应的Extractor(它通过sniff函数检测出媒体类型,然后创建出对应Extractor),然后通过Extractor获取数据源的元数据和tracks数量等重要信息。然后再通过一个for循环来遍历这些tracks,将从Extractor中解析出来的metadata等等数据,保存在GenericSource结构体中的mVideoTrack和mAudioTrack中。
(2)在第6步中会通过CreateFromService()方法创建Extractor:
sp<MediaExtractor> MediaExtractor::CreateFromService(
const sp<DataSource> &source, const char *mime,
const uint32_t flags) {
ALOGV("MediaExtractor::CreateFromService %s flags %d", mime, flags);
RegisterDefaultSniffers();//(1)注册嗅探器
...
if (mime == NULL) {
float confidence;
if (!sniff(source, &tmp, &confidence, &meta)) {//(2)嗅探源文件
ALOGW("FAILED to autodetect media content.");
return NULL;
}
mime = tmp.string();
ALOGV("Autodetected media content as '%s' with confidence %.2f",
mime, confidence);
}
...
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;
}
在RegisterDefaultSniffers先将不同格式的嗅探器进行注册,然后再使用sniff()方法对媒体输入源进行文件头的读取,根据文件头内容嗅探出需要什么样的解封装组件,也就是不同的MediaExtractor实现。MediaExtractor有大量的实现,分别针对MP3、AAC、OGG、WAV、MPEG4等格式输入的解封装操作。如下常见的一些实现:
![](https://img.haomeiwen.com/i18565088/7314f3a74bedd1a3.png)
嗅探器的实现原理基本上都是读取媒体源文件的头信息,不同格式都会有自己的特征,嗅探器就是根据这些特征,来判断是否是需要找的类型。
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;
}
其中:
if ((*it)(source, &newMimeType, &newConfidence, &newMeta)) {
会去遍历各提取器的嗅探函数,目的就是判断源文件(码流)类型是否和当前格式匹配。
-
source:这是个DataSource类型的指针,该类型通过层层包裹包含了一系列读取媒体源文件的功能。嗅探函数通过该指针通源文件中读取头信息,来判断源文件的类型。
-
newMimeType:String8类型的指针,一旦嗅探函数通过头信息探测出源文件属于当前类型,该变量会通过指针赋值。这些类型定义在MediaDefs.cpp中如:
const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mp4";
const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/x-wav";
const char *MEDIA_MIMETYPE_CONTAINER_OGG = "application/ogg";
const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA = "video/x-matroska";
const char *MEDIA_MIMETYPE_CONTAINER_MPEG2TS = "video/mp2ts";
const char *MEDIA_MIMETYPE_CONTAINER_AVI = "video/avi";
const char *MEDIA_MIMETYPE_CONTAINER_MPEG2PS = "video/mp2p";
再返回到GenericSource::initFromDataSource()方法中:
mFileMeta = extractor->getMetaData();
对于媒体文件而言,元数据一般有:音频采样率、视频帧率、视频尺寸、比特率、编解码、播放时长等基本信息,此外也可能含有其它杂七杂八的信息:名称、版权、专辑、时间、艺术家等。
4.finishPrepareAsync
onPrepareAsync()执行完数据初始化(initFromDataSource)后,最后通过finishPrepareAsync接口结束数据的准备工作。其代码流程如下:
![](https://img.haomeiwen.com/i18565088/0ff6b5be1f6ce008.png)
(1)首先,看到finishPrepareAsync()方法
void NuPlayer::GenericSource::finishPrepareAsync() {
ALOGV("finishPrepareAsync");
status_t err = startSources();
if (err != OK) {
ALOGE("Failed to init start data source!");
notifyPreparedAndCleanup(err);
return;
}
if (mIsStreaming) {
mCachedSource->resumeFetchingIfNecessary();
mPreparing = true;
schedulePollBuffering();
} else {
notifyPrepared();
}
if (mAudioTrack.mSource != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
}
if (mVideoTrack.mSource != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
}
}
(2)先看第一段代码:startSources()
status_t NuPlayer::GenericSource::startSources() {
// 在我们开始缓冲之前,立即启动所选的A/V track。
// 如果我们将它延迟到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;
}
(3)再看到notifyPrepared()
跳转到NuPlayer::Source::notifyPrepared中去执行了:
void NuPlayer::Source::notifyPrepared(status_t err) {
ALOGV("Source::notifyPrepared %d", err);
sp<AMessage> notify = dupNotify();
notify->setInt32("what", kWhatPrepared);
notify->setInt32("err", err);
notify->post();
}
发送消息到NuPlayer::onSourceNotify中去执行,最终是找到NuPlayerDriver:
driver->notifyPrepareCompleted(err);
对应notifyPrepareCompleted()接口的定义:
void NuPlayerDriver::notifyPrepareCompleted(status_t err) {
ALOGV("notifyPrepareCompleted %d", err);
Mutex::Autolock autoLock(mLock);
if (mState != STATE_PREPARING) {
// We were preparing asynchronously when the client called
// reset(), we sent a premature "prepared" notification and
// then initiated the reset. This notification is stale.
CHECK(mState == STATE_RESET_IN_PROGRESS || mState == STATE_IDLE);
return;
}
CHECK_EQ(mState, STATE_PREPARING);
mAsyncResult = err;
if (err == OK) {
// update state before notifying client, so that if client calls back into NuPlayerDriver
// in response, NuPlayerDriver has the right state
mState = STATE_PREPARED;
if (mIsAsyncPrepare) {
notifyListener_l(MEDIA_PREPARED);
}
} else {
mState = STATE_UNPREPARED;
if (mIsAsyncPrepare) {
notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
}
}
sp<MetaData> meta = mPlayer->getFileMeta();
int32_t loop;
if (meta != NULL
&& meta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
mAutoLoop = true;
}
mCondition.broadcast();
}
这里notifyListener_l(MEDIA_PREPARED)直接调用到NuPlayerDriver::notifyListener_l()函数,然后同样通过IMediaPlayerClient的Bp端传到Bn端,再传递到mediaplayer.cpp中。
网友评论