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,那么播放器将无法得到媒体数据,也就无法播放了。
那么,有人问,如果是视频不就可以了么。是的,视频还是可以从该函数中获取数据,但对于播放器而言,视频和音频肯定是同时播放,如果没了音频,视频也不会独活的。
好了,解封装模块终于搞定了。妈呀!