Android_ics_stagefright框架数据流向分析——1,待解码的原始数据从何而来

先明确一点, 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
}
对回调实现中的部分函数做简单分析:
 afinishSetDataSource_l()中主要做了三件事:

一,根据不同的数据来源,来确定dataSource的来源,如果数据来源于网络(Http/widevine)则dataSource来自与cacheSource(流媒体播放的缓冲),
如果数据来源于文件,则dataSource会被绑定到FileSource(uri)

二,根据前面确定的dataSource的类型来:
extractor = MediaExtractor::Create(dataSource, sniffedMIME.empty() ? NULL : sniffedMIME.c_str());
产生一个具体的文件解析器,在这个Create函数中,会根据DataSource基类中所注册的Sniff函数(有很多个Sniff函数,例如SniffMPEG4SniffOgg等)来“嗅探”这个dataSource。根据confidence值来确定这个dataSourcemimetype,然后再根据确定的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
//mAudioTrackmVideoTrack成员变量赋值成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函数根据不同的媒体容器格式标准,例如MPEG4AACMP3等。对原始文件来源,即mDataSource进行读(按照容器的标准,例如chunktype占多大,chunksize一般用chunktype前面的4个字节来表示),即readAtmDataSource就是根据前面媒体文件的来源确定的,所以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用来存放时间/偏移的映射表。这个sampleTableseek的时候会被用到。
               关于parsechunk函数还有一点要特别提一下,在parsechunk函数中,经常会看到这样的函数
track->meta->setCStringmLastTrack->meta->setInt64mLastTrack->meta->setCString....这些函数的作用是,在解析媒体文件的时候,当遇到一些表示文件数据内容字段的时候,比如媒体流的duration,语言,等。当遇到这些表示信息的时候,就把他记录在流的metadata内,也就是Track->meta内。去看看Track类的结构就明白了,metadataTrack的成员变量,metadata可以理解会原数据,就是标识媒体流原始的数据信息。这些setxxx函数会吧这样原数据的代表的类型,以key-->value的形式放到metadataKeyVector  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);



binitVideoDecoder();做了什么?
	
   函数的实现如下:
   status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {//注意这里mVideoTrackXXXSource例如 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解析媒体流的时候newMPEG4Source来代表这个媒体流,这里的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回到MPEG4Sourceread函数中被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]。至于数据的填充和使用,后面会看到

你可能感兴趣的:(Android_ics_stagefright框架数据流向分析——1,待解码的原始数据从何而来)