先明确一点,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&extractor) { for (size_t i = 0; i < extractor->countTracks(); ++i) {-------------------countTracks()很复杂,下面会单独讲 spmeta = extractor->getTrackMetaData(i); ... } } bool haveAudio = false; bool haveVideo = false; //下面开始对每一个解析出来的流做分离(音频/视频/字幕流分离),然后给Awesomeplayer的 //mAudioTrack和mVideoTrack成员变量赋值成XXXSource,例如MPEG4Source/MP3Source for (size_t i = 0; i < extractor->countTracks(); ++i) { spmeta = 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)); 函数的实现如下: spMPEG4Extractor::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保存起来,其中VectormPortBuffers[2] 。至于数据的填充和使用,后面会看到