先明确一点,
stagefright
框架是典型的事件驱动型,数据的流向也受到事件驱动(
driven by event
)的影响,在
awesomePlayer
中主要的驱动事件有:
onPrepareAsyncEvent
,
onVideoEvent
,
onStreamDone......
这些
event
会在
awesomeplayer
中维护的
TimedEventQueue mQueue
中按照时间的顺序被放入这个队列中。然后
TimedEventQueue
根据时间顺序来调度事件。这样做的目的是:因为,按照
mQueue
中事件的是按事件排序的,所以,在视频数据到来时,可以根据视频的时间戳来进行音视频同步的调节。
AwesomePlayer中音视频的同步处理就是在onVideoEvent()回调中来做的。
当应用层调用mediaplayer.prepare()的时候,在框架内最终对应的是AwesomePlayer::prepareAsync_l(),这个函数的实现很简单,看下主要的实现部分:
status_t AwesomePlayer::prepareAsync_l() {
…
mAsyncPrepareEvent = new AwesomeEvent(this, &AwesomePlayer::onPrepareAsyncEvent);
mQueue.postEvent(mAsyncPrepareEvent); //向mQueue中投递一个事件
}
那么当mQueue在进行事件调度的时候,会执行到事件对应的回调函数,例如上面 mAsyncPrepareEvent对应的回调函数就是 onPrepareAsyncEvent。回调函数的实现大致如下,有些部分直接略掉了
void AwesomePlayer::onPrepareAsyncEvent() {
...
status_t err = finishSetDataSource_l();--------------------------------a
status_t err = initVideoDecoder();--------------------------------------b
status_t err = initAudioDecoder();
finishAsyncPrepare_l();-----------------------------------------------------c
}
对回调实现中的部分函数做简单分析:
a,finishSetDataSource_l()中主要做了三件事:
一,根据不同的数据来源,来确定dataSource的来源,如果数据来源于网络(Http/widevine)则dataSource来自与cacheSource(流媒体播放的缓冲),
如果数据来源于文件,则dataSource会被绑定到FileSource(uri)。
二,根据前面确定的dataSource的类型来:
extractor = MediaExtractor::Create(dataSource, sniffedMIME.empty() ? NULL : sniffedMIME.c_str());
产生一个具体的文件解析器,在这个Create函数中,会根据DataSource基类中所注册的Sniff函数(有很多个Sniff函数,例如SniffMPEG4,SniffOgg等)来“嗅探”这个dataSource。根据confidence值来确定这个dataSource的mimetype,然后再根据确定的mimetype来创建具体的XXXExtractor。本文中所讨论的对象是MPEG4Extractor。
三,最后一步也是最复杂的一部分,这一步决定了原始数据的来源,在finishSetDataSource_l()函数中最后一步调用的是status_t err = setDataSource_l(extractor);这里的extractor就是前面所创建的XXXExtractor,假定是MPEG4Extractor。下面简单的分析如下,setDataSource_l(extractor);实现如下
status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
for (size_t i = 0; i < extractor->countTracks(); ++i) {-------------------countTracks()很复杂,下面会单独讲
sp<MetaData> meta = extractor->getTrackMetaData(i);
...
}
}
bool haveAudio = false;
bool haveVideo = false;
//下面开始对每一个解析出来的流做分离(音频/视频/字幕流分离),然后给Awesomeplayer的
//mAudioTrack和mVideoTrack成员变量赋值成XXXSource,例如MPEG4Source/MP3Source
for (size_t i = 0; i < extractor->countTracks(); ++i) {
sp<MetaData> meta = extractor->getTrackMetaData(i);
if (!haveVideo && !strncasecmp(mime.string(), "video/", 6)) {
setVideoSource(extractor->getTrack(i));----------------------------------getTrack(i)做了什么
haveVideo = true;
// Set the presentation/display size
int32_t displayWidth, displayHeight;
bool success = meta->findInt32(kKeyDisplayWidth, &displayWidth);
if (success) {
success = meta->findInt32(kKeyDisplayHeight, &displayHeight);
}
if (success) {
mDisplayWidth = displayWidth;
mDisplayHeight = displayHeight;
}
...
} else if (!haveAudio && !strncasecmp(mime.string(), "audio/", 6)) {
setAudioSource(extractor->getTrack(i));
haveAudio = true;
...
} else if (!strcasecmp(mime.string(), MEDIA_MIMETYPE_TEXT_3GPP)) {
addTextSource(extractor->getTrack(i));
}
}
…
}
对上面extractor->countTracks函数的详解:
这个函数绝对不像函数名那么简单,只是简单的count Tracks。函数的实现如下:
size_t MPEG4Extractor::countTracks() {
//这个readMetaData()----->MPEG4Extractor::readMetaData()------>MPEG4Extractor::parseChunk()
if ((err = readMetaData()) != OK) {
return 0;
}
//从下面开始就正真的是根据前面readMetaData后得到的结果来计算这个dataSource中有多少个不
//同的数据流,即tracks
size_t n = 0;
Track *track = mFirstTrack;
while (track) {
++n;
track = track->next;
}
return n;
}
那么parseChunk()中又做了什么呢?这个函数是真正解析媒体文件(媒体容器)的关键函数。函数很长,就不在这里贴代码了。parseChunk函数根据不同的媒体容器格式标准,例如MPEG4,AAC,MP3等。对原始文件来源,即mDataSource进行读(按照容器的标准,例如chunktype占多大,chunksize一般用chunktype前面的4个字节来表示),即readAt,mDataSource就是根据前面媒体文件的来源确定的,所以mDataSoure可能来之cacheSource或者FileSource。
当ParseChunk()读到't', 'r', 'a', 'k'字段的时候,那么mDataSource下面的信息就代表了一个媒体流的全部信息。这些tracks抽象成Track 类并且被连结成一个链表。然后这些tracks上存放的信息,被如下方式写入tracks对应的Track对象中,
…
track->meta = new MetaData;
track->includes_expensive_metadata = false;
track->skipTrack = false;
track->timescale = 0;
track->meta->setCString(kKeyMIMEType, "application/octet-stream");
…
mLastTrack->sampleTable->setSampleDescParams(entry_count, *offset, chunk_data_size);
有个知识点要注意,在媒体文件的最后一个流中,有一个sampleTable用来存放时间/偏移的映射表。这个sampleTable在seek的时候会被用到。
关于parsechunk函数还有一点要特别提一下,在parsechunk函数中,经常会看到这样的函数
track->meta->setCString,mLastTrack->meta->setInt64,mLastTrack->meta->setCString....这些函数的作用是,在解析媒体文件的时候,当遇到一些表示文件数据内容字段的时候,比如媒体流的duration,语言,等。当遇到这些表示信息的时候,就把他记录在流的metadata内,也就是Track->meta内。去看看Track类的结构就明白了,metadata是Track的成员变量,metadata可以理解会原数据,就是标识媒体流原始的数据信息。这些setxxx函数会吧这样原数据的代表的类型,以key-->value的形式放到metadata的KeyVector mItem成员变量中。这样做的目的是,当后面在解压tracks数据的时候,可以从metadata字段通过findxxx函数来明确当前数据表示的哪种类型(type)的信息。
对上面extracor->getTrack(i)函数的分析
上面在音视频分离的时候,调用了setVideoSource(extractor->getTrack(i));
函数的实现如下:
sp<MediaSource> MPEG4Extractor::getTrack(size_t index) {
//先通过遍历tracks的链表来找到index对应的track
return new MPEG4Source(track->meta, mDataSource, track->timescale, track->sampleTable);
b,initVideoDecoder();做了什么?
函数的实现如下:
status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
…
//注意这里mVideoTrack是XXXSource例如 MPEG4Source。
//而mVideoTrack->getFormat()实际上是MPEG4Extracor解析出来的tracks中的metadata字段。
mVideoSource = OMXCodec::Create(
mClient.interface(), mVideoTrack->getFormat(),
false, // createEncoder
mVideoTrack,
NULL, flags, USE_SURFACE_ALLOC ? mNativeWindow : NULL);
...
status_t err = mVideoSource->start();
...
}
则mVideoSource就是用特定的媒体数据,和特定的tracks的原数据(metadata)所创建的OMXCodec。所以mVideoSource->start()的函数实现如下:
status_t OMXCodec::start(MetaData *meta) {
…
//根据OMXCodec的构造函数可以看出来mSource就是前面的mVideoSource也就是XXXSource假定是MPEG4Source。
status_t err = mSource->start(params.get());----------(1)
...
return init(); //这个函数开始进入OMX框架做一些初始化的工作,另一篇文章有说的。
}
上面MPEG4Source::start()函数实现如下:
status_t MPEG4Source::start(MetaData *params) {
...
mGroup = new MediaBufferGroup;
int32_t max_size;
//这里的mFormat就是在MPEG4Extractor解析媒体流的时候new了MPEG4Source来代表这个媒体流,这里的mFormat就是MPEG4Source中的metadata,代表了这个媒体流的原数据。还记得我前面的说的parsechunk的时候,会使用setInt32类似的函数,来给这个流标识一些信息。下面就用到了对应的findInt32函数,把这些在parsechunk中记录的信息“找出来”。
CHECK(mFormat->findInt32(kKeyMaxInputSize, &max_size));
mGroup->add_buffer(new MediaBuffer(max_size));
mSrcBuffer = new uint8_t[max_size];
...
}
可以看到这个函数中,new了一个MediaBufferGroup mGroup并存了(add_buffer)一个与这个流数据大小相当的MediaBuffer到这个mGroup中。这个buffer回到MPEG4Source中read函数中被acquire_buffer用到。
//貌似这个时候MediaBuffer中还有没有数据哦!恩,对的,后面会继续研究
上面有提到的一点,看来是不得不说了,在OMXCodec::start()函数中,最后会调用一个OMXCodec::init()函数,这个函数的作用是做OMX框架的初始化。具体一点说就是,这个init()函数,会通过mOMX->sendCommand的方式来和OMX组件通信,来改变或者更新这些组件的状态。然后会通过allocateBuffers()-->allocateBuffersOnPort(kPortIndexInput);来给OMX组件的Input/Outputports来分配一些buffer。然后通过BufferInfo info来分配一些数据空间,这个时候并没有实际的媒体数据在buffer上,只是简单的把所需的数据控件分配好了。最后,mPortBuffers[portIndex].push(info);把这些分配好的bufferinfo保存起来,其中Vector<BufferInfo> mPortBuffers[2]。至于数据的填充和使用,后面会看到