Android_ics_stagefright框架数据流向分析

原文:

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

链接:http://blog.csdn.net/mci2004/article/details/7629146


先明确一点,stagefright框架是典型的事件驱动型,数据的流向也受到事件驱动(driven by event)的影响,在awesomePlayer中主要的驱动事件有:onPrepareAsyncEventonVideoEventonStreamDone......这些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个字节来表示),即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用来存放时间/偏移的映射表。这个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_OMX_In_Stagefright------>2开始解码(软解)

当应用层调用mediaplayer.start()的时候,在framework层对应的是在awesomeplayerpost一个mVideoEvent

TimedEventQueue中等待被调度。

        当其被调度到的时候,会激活回调函数onVideoEvent
        在这个回调函数中,会做音视频的同步处理。代码很长捡关键的贴。
        void AwesomePlayer::onVideoEvent() {

        for (;;) {
	...
            status_t err = mVideoSource->read(&mVideoBuffer, &options);
 	...
            initRenderer_l();
	...
             mVideoRenderer->render(mVideoBuffer); 
    }
	
在这个回调函数中可以看到这样一句话,status_t err = mVideoSource->read(&mVideoBuffer, &options);其中,mVideoBuffer 是一个
MediaBuffer类型的成员变量。还记得mVideoSource是什么类型吗?这里的mVideoSource,就是前面返回的OMXCodec,那么实际
调用是的:
        status_t OMXCodec::read(MediaBuffer **buffer, const ReadOptions *options),这个read函数会填充MediaBuffer *
mVideoBuffer这个成员变量,然后交给Renderer来渲染输出。看看read函数的实现:
         
        status_t OMXCodec::read(MediaBuffer **buffer, const ReadOptions *options) {
        …...
        drainInputBuffers();
        if (mState == EXECUTING) {
            fillOutputBuffers();
        }
    }
        …...

    while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
        if ((err = waitForBufferFilled_l()) != OK) {
            return err;
        }
    }

    if (mState == ERROR) {
        return UNKNOWN_ERROR;
    }
    if(seeking) {
        CHECK_EQ((int)mState, (int)FLUSHING);
        setState(EXECUTING);
    }

    if (mFilledBuffers.empty()) {
        return mSignalledEOS ? mFinalStatus : ERROR_END_OF_STREAM;
    }

    if (mOutputPortSettingsHaveChanged) {
        mOutputPortSettingsHaveChanged = false;

        return INFO_FORMAT_CHANGED;
    }

    size_t index = *mFilledBuffers.begin();
    mFilledBuffers.erase(mFilledBuffers.begin());

    BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index);
    CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);
    info->mStatus = OWNED_BY_CLIENT;

    info->mMediaBuffer->add_ref();
    *buffer = info->mMediaBuffer;

    return OK;
}

在这个read函数中最重要的两个方法是drainInputBuffers()fillOutputBuffers(),可以看到这两个函数是先后执行
的。先来看看drainInputBuffers()方法中,做了哪些操作。

        void OMXCodec::drainInputBuffers() {
        …...
        Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput];
        for (size_t i = 0; i < buffers->size(); ++i) {
            BufferInfo *info = &buffers->editItemAt(i);
         …...
           
            if (!drainInputBuffer(info)) {
                break;
            }
         …...
}
这个 drainInputBuffers函数从输入端口的buffers中取出之前分配好的BufferInfo,然后交给drainInputBuffer
(info)函数来处理,函数实现如下:

bool OMXCodec::drainInputBuffer(BufferInfo *info) {
    
        //comment by Alan, this buffer has only codec config informations
        status_t err = mOMX->emptyBuffer(
                mNode, info->mBuffer, 0, size,
                OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
                0);
       
    for (;;) {
        ….... 
        MediaBuffer *srcBuffer;
        …...
        err = mSource->read(&srcBuffer);
        …...
        memcpy((uint8_t *)info->mData + offset,
                        (const uint8_t *)srcBuffer->data()
                            + srcBuffer->range_offset(),
                        srcBuffer->range_length());
        ….....	
        err = mOMX->emptyBuffer(
            mNode, info->mBuffer, 0, offset,
            flags, timestampUs);
        …....
}

这个函数比较复杂,主要的关键代码如上,在这个函数中,首先会把一些和编解码组件相关的specific data送给omx框架来调
用一次emptybuffer,然后会进入一个for(;;)循环,在这个for循环内,会通过mSource->read(&srcBuffer)
srcBuffer中填充一些原始的流数据(从XXXExtractor解析出来未解码的数据),这里mSource就是前面XXXExtractor在
文件解析的过程中new XXXSource这里假定是MPEG4Source。函数的大致实现如下:

       status_t MPEG4Source::read(MediaBuffer **out, const ReadOptions *options) {
        …...
        err = mGroup->acquire_buffer(&mBuffer);

        
        if (usesDRM) {
            num_bytes_read =
                mDataSource->readAt(offset, (uint8_t*)mBuffer->data(), size);
        } else {
            num_bytes_read = mDataSource->readAt(offset, mSrcBuffer, size);
        }

        …...
        *out = mBuffer;
        mBuffer = NULL;
        return OK;
}
这个函数的实现很复杂,在read的时候不但考虑了seek的问题,而且还涉及到了不少多媒体容器格式方面的一些问题。但是关于
数据流向的函数就是两个,首先acquire_buffer,这个acquire_buffer和前面的add_buffer对应,在MPEG4Source::
start中会被调用到。获得一个mBuffer后就可以通过mDataSource->readAt来往这个buffer上填充数据了。这里的
mDataSource实际上可以对应到一个FileSource或者来之网络的CacheSourcemBuffer数据填充完后赋值给调用函数的
 srcBuffer,这样就相当于给 drainInputBuffer(BufferInfo *info)中的info填充了数据,然后回到
drainInputBuffer函数,接着emptyBuffer。实际上这里的BufferInfo *info的数据填充过程还有一些细节需要弄清楚,
BufferInfo的结构体各个字段的意义还有待进一步弄明白。

        到了mOMX->emptyBuffer()基本就进入OMX框架了,无非是OMX那一套消息机制,之前已经说过,这里就不在赘述了。
如果还有不清楚的话,其在openMax文档的时候我画的一张时序图。还有一点需要明确,在emptyBuffer的时候会将App Data 
 copyToOMX,将来之Appcation的数据copyOMX框架中,而在OMXNodeInstance::onMessage  FILL_BUFFER_DONE
的时候,会将数据从OMX框架中copyAppcation空间,即copyFromOMXAppcation<--  data --->OMX
       来看看其中一个函数的实现,
       void CopyToOMX(const OMX_BUFFERHEADERTYPE *header) {
        if (!mIsBackup) {
            return;
        }

        memcpy(header->pBuffer + header->nOffset,
               (const OMX_U8 *)mMem->pointer() + header->nOffset,
               header->nFilledLen);
    }
注意以下,mMem这个成员变量具体指什么,我们看看emptyBuffer这个函数的实现:

       status_t OMXNodeInstance::emptyBuffer(
        OMX::buffer_id buffer,
        OMX_U32 rangeOffset, OMX_U32 rangeLength,
        OMX_U32 flags, OMX_TICKS timestamp) {
    Mutex::Autolock autoLock(mLock);

    OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer;
    header->nFilledLen = rangeLength;
    header->nOffset = rangeOffset;
    header->nFlags = flags;
    header->nTimeStamp = timestamp;

    BufferMeta *buffer_meta =
        static_cast<BufferMeta *>(header->pAppPrivate);
    buffer_meta->CopyToOMX(header);

    OMX_ERRORTYPE err = OMX_EmptyThisBuffer(mHandle, header);

    return StatusFromOMXError(err);
}

去看一下BufferMeta的构造函数就可以发现,上面mMem实际上指的是head->pAppPrivate

那么具体的解码过程什么时候开始呢?
        OMX框架中所有的软解部分,最终都会通过OMX的消息机制走到SimpleSoftOMXComponent::
onMessageReceived,函数的实现如下:

 void SimpleSoftOMXComponent::onMessageReceived(const sp<AMessage> &msg) {
    Mutex::Autolock autoLock(mLock);

    switch (msg->what()) {
        case kWhatSendCommand:
        {
            int32_t cmd, param;
            CHECK(msg->findInt32("cmd", &cmd));
            CHECK(msg->findInt32("param", &param));

            onSendCommand((OMX_COMMANDTYPE)cmd, (OMX_U32)param);
            break;
        }

        case kWhatEmptyThisBuffer:
        case kWhatFillThisBuffer:
        {
            OMX_BUFFERHEADERTYPE *header;
            CHECK(msg->findPointer("header", (void **)&header));

            CHECK(mState == OMX_StateExecuting && mTargetState == mState);

            bool found = false;
            for (size_t i = 0; i < mPorts.size(); ++i) {
                PortInfo *port = &mPorts.editItemAt(i);

                for (size_t j = 0; j < port->mBuffers.size(); ++j) {
                    BufferInfo *buffer = &port->mBuffers.editItemAt(j);

                    if (buffer->mHeader == header) {
                        CHECK(!buffer->mOwnedByUs);

                        buffer->mOwnedByUs = true;

                        CHECK((msg->what() == kWhatEmptyThisBuffer
                                    && port->mDef.eDir == OMX_DirInput)
                                || (port->mDef.eDir == OMX_DirOutput));

                        port->mQueue.push_back(buffer);
                        onQueueFilled(i);

                        found = true;
                        break;
                    }
                }
            }

            CHECK(found);
            break;
        }

        default:
            TRESPASS();
            break;
    }
}
显然这是一个消息的处理函数,对于 kWhatEmptyThisBufferkWhatFillThisBuffer这类消息都会进入到 
onQueueFilled(i)函数,这个 onQueueFilled函数相当于是OMX软件编解码组件的一个入口,函数显示如下:

void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) {
            …...
            Bool success = PVInitVideoDecoder(
                    mHandle, vol_data, &vol_size, 1, mWidth, mHeight, mode);

           …...
            MP4DecodingMode actualMode = PVGetDecBitstreamMode(mHandle);
           
            PVSetPostProcType((VideoDecControls *) mHandle, 0);
                notifyEmptyBufferDone(inHeader);
            …...
            PVSetReferenceYUV(mHandle, outHeader->pBuffer);

        if (PVDecodeVideoFrame(mHandle, &bitstream, &timestamp, &tmp,
            &useExtTimestamp,
            outHeader->pBuffer) != PV_TRUE) {

            notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
            mSignalledError = true;
            return;
        }
        …...
        notifyFillBufferDone(outHeader);

}

上面的函数只保留了几个关键的函数调用,可以看到在这个onQueueFilled函数中会通过一些类似与 PVDecodeVideoFrame
进入到OMX 框架中软件编解码的部分。可以看到通过 PVDecodeVideoFrame之后的yuv数据会保存在outHeader->pBuffer
字段中。然后通过notifyEmptyBufferDone(inHeader); 或者notifyFillBufferDone(outHeader);来向组件OMX
外部通知已经从inPutPort消费了一个buffer或者已经向outPutPort填充了一个buffer
        简单说来,当notifyEmptyBufferDone的时候,OMX会记录已经被消费的buffer的索引,然后继续在该索引对应
的bufferdrainInputBuffer,当notifyFillBufferDone时候最终会通过OMX的消息机制走到OMXCodec::
on_message中进入case omx_message::FILL_BUFFER_DONE:主要工作是,记录这个已经填充的buffer的索引号,然后
将这个索引号保存在

                mFilledBuffers.push_back(i);
                mBufferFilled.signal();
看到mBufferFilled这个Conditionsignal了,那么谁wait在这个Condition上呢?看上OMXCodec::read函数中我
红色标注的部分。原来在OMXCodec::read函数的后半段中会一直
while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
        if ((err = waitForBufferFilled_l()) != OK) {
            return err;
        }
    }
       那么FILL_BUFFER_DONE之后,在OMXCodec::read函数中被返回给AwesomePlayermVideoBuffer就是在
OMX框架中outPutPort上的buffer


通过上面的分析可以看到,虽然在OMX框架中 Input/OutPutPort上的buffer的生产和消费是异步,但是还是通过了一个
Condition mBufferFilled来做同步。这个生产者消费者的问题,来用一个信号量做同步,还是比较好的。

原文三:

Android_ics openmax in stagefright 学习记录------1

链接:http://blog.csdn.net/mci2004/article/details/7753741

这几篇文章是之前学习openmax的输出,记录在这里,希望不要误导菜鸟的同时又能得到牛牛们的指导。


android_ics openmax_in_stagefright 再次学习

/*
*在学习android源代码的工程中,一点要时刻牢记C/S架构
*任何时刻都要搞清除,这个时候的代码是运行在客户端,
*还是服务端,这个对象来之,客户端还是服务端的代理。
*/


<---以下的讨论,目的都在于弄清楚,stagefright框架内,OpenMaXIL和各个编解码的组件是如何通信的--->

At first:

	OpenMax是事实上的标准,也是android上多媒体编解码框架未来的趋势。
	

分析的比较凌乱,后面在做整理。

/
//1,OMX 从何开始?


	看下awesomeplayer的构造函数
	
	AwesomePlayer::AwesomePlayer()
    : mQueueStarted(false),
	  ...
      mTextPlayer(NULL) {
    /*mClient是一个OMXClient类型的成员变量*/
    CHECK_EQ(mClient.connect(), (status_t)OK);
    DataSource::RegisterDefaultSniffers();
    mVideoEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoEvent);
	...
    mVideoLagEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoLagUpdate);
    mVideoEventPending = false;
    mCheckAudioStatusEvent = new AwesomeEvent(
            this, &AwesomePlayer::onCheckAudioStatus);
    ...
}


    /*connect函数的定义*/
    
	status_t OMXClient::connect() {
    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> binder = sm->getService(String16("media.player"));
    sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);

    CHECK(service.get() != NULL);

    mOMX = service->getOMX();
    CHECK(mOMX.get() != NULL);

    return OK;
}
	
	上面的函数,通过binder取请求MediaPlayerService的getOmx然后反回一个OMX实例,事实上这个时候在awesomePlayer
中的mOMX是一个来之服务端的实例。打通了一条Client->Service的通道。个人认为这就是OpenMax框架的入口。
     
     /*OMX的构造函数如下,@OMX.cpp*/
     OMX::OMX():mMaster(new OMXMaster),mNodeCounter(0) {
	}
	
-------------------------以下是插曲,首次看请忽略--------------------------------------------------------->	
	/*下面完整的贴出OMX的结构,其中有些成员的作用,在后面的学习中,会慢慢的揭开其真面目*/
	class OMX : public BnOMX,
            public IBinder::DeathRecipient {
public:
    OMX();

    virtual bool livesLocally(pid_t pid);

    virtual status_t listNodes(List<ComponentInfo> *list);

    virtual status_t allocateNode(
            const char *name, const sp<IOMXObserver> &observer, node_id *node);

    virtual status_t freeNode(node_id node);

    virtual status_t sendCommand(
            node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param);

    virtual status_t getParameter(
            node_id node, OMX_INDEXTYPE index,
            void *params, size_t size);

    virtual status_t setParameter(
            node_id node, OMX_INDEXTYPE index,
            const void *params, size_t size);

    virtual status_t getConfig(
            node_id node, OMX_INDEXTYPE index,
            void *params, size_t size);

    virtual status_t setConfig(
            node_id node, OMX_INDEXTYPE index,
            const void *params, size_t size);

    virtual status_t getState(
            node_id node, OMX_STATETYPE* state);

    virtual status_t enableGraphicBuffers(
            node_id node, OMX_U32 port_index, OMX_BOOL enable);

    virtual status_t getGraphicBufferUsage(
            node_id node, OMX_U32 port_index, OMX_U32* usage);

    virtual status_t storeMetaDataInBuffers(
            node_id node, OMX_U32 port_index, OMX_BOOL enable);

    virtual status_t useBuffer(
            node_id node, OMX_U32 port_index, const sp<IMemory> &params,
            buffer_id *buffer);

    virtual status_t useGraphicBuffer(
            node_id node, OMX_U32 port_index,
            const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer);

    virtual status_t allocateBuffer(
            node_id node, OMX_U32 port_index, size_t size,
            buffer_id *buffer, void **buffer_data);

    virtual status_t allocateBufferWithBackup(
            node_id node, OMX_U32 port_index, const sp<IMemory> &params,
            buffer_id *buffer);

    virtual status_t freeBuffer(
            node_id node, OMX_U32 port_index, buffer_id buffer);

    virtual status_t fillBuffer(node_id node, buffer_id buffer);

    virtual status_t emptyBuffer(
            node_id node,
            buffer_id buffer,
            OMX_U32 range_offset, OMX_U32 range_length,
            OMX_U32 flags, OMX_TICKS timestamp);

    virtual status_t getExtensionIndex(
            node_id node,
            const char *parameter_name,
            OMX_INDEXTYPE *index);

    virtual void binderDied(const wp<IBinder> &the_late_who);

    OMX_ERRORTYPE OnEvent(
            node_id node,
            OMX_IN OMX_EVENTTYPE eEvent,
            OMX_IN OMX_U32 nData1,
            OMX_IN OMX_U32 nData2,
            OMX_IN OMX_PTR pEventData);

    OMX_ERRORTYPE OnEmptyBufferDone(
            node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer);

    OMX_ERRORTYPE OnFillBufferDone(
            node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer);

    void invalidateNodeID(node_id node);

protected:
    virtual ~OMX();

private:
    struct CallbackDispatcherThread;  //关注下
    struct CallbackDispatcher;		  //关注下

    Mutex mLock;
    OMXMaster *mMaster;
    int32_t mNodeCounter;

    KeyedVector<wp<IBinder>, OMXNodeInstance *> mLiveNodes;
    KeyedVector<node_id, OMXNodeInstance *> mNodeIDToInstance;
    KeyedVector<node_id, sp<CallbackDispatcher> > mDispatchers;

    node_id makeNodeID(OMXNodeInstance *instance);
    OMXNodeInstance *findInstance(node_id node);
    sp<CallbackDispatcher> findDispatcher(node_id node);

    void invalidateNodeID_l(node_id node);

    OMX(const OMX &);
    OMX &operator=(const OMX &);
};

}  // namespace android

#endif  // ANDROID_OMX_H_
<-------------------------以上是插曲,首次看请忽略---------------------------------------------------------

    可以看到,在构造OMX的时候,会new一个OMXMaster给成员变量mMaster,这个mMaster是对OMXPluginBase(基类)
类型的插件的管理,其中各个插件在ICS的框架中就是软硬件编解码的插件。
 
#ifndef OMX_MASTER_H_

#define OMX_MASTER_H_

#include <media/stagefright/OMXPluginBase.h>

#include <utils/threads.h>
#include <utils/KeyedVector.h>
#include <utils/List.h>
#include <utils/String8.h>

namespace android {

struct OMXMaster : public OMXPluginBase {
    OMXMaster();
    virtual ~OMXMaster();

    virtual OMX_ERRORTYPE makeComponentInstance(     //符合OpenMAX标准的接口
            const char *name,
            const OMX_CALLBACKTYPE *callbacks,
            OMX_PTR appData,
            OMX_COMPONENTTYPE **component);

    virtual OMX_ERRORTYPE destroyComponentInstance(  //符合OpenMAX标准的接口
            OMX_COMPONENTTYPE *component);

    virtual OMX_ERRORTYPE enumerateComponents(       //符合OpenMAX标准的接口
            OMX_STRING name,
            size_t size,
            OMX_U32 index);

    virtual OMX_ERRORTYPE getRolesOfComponent(		 //符合OpenMAX标准的接口
            const char *name,
            Vector<String8> *roles);

private:
    Mutex mLock;
    List<OMXPluginBase *> mPlugins;
    KeyedVector<String8, OMXPluginBase *> mPluginByComponentName;
    KeyedVector<OMX_COMPONENTTYPE *, OMXPluginBase *> mPluginByInstance;

    void *mVendorLibHandle;

    void addVendorPlugin();
    void addPlugin(const char *libname);
    void addPlugin(OMXPluginBase *plugin);
    void clearPlugins();

    OMXMaster(const OMXMaster &);
    OMXMaster &operator=(const OMXMaster &);
};

}  // namespace android

#endif  // OMX_MASTER_H_

-----到目前为止,android2.3和ICS在该部分没有明显区别-----

在OMXMaster的构造函数中,就开始不同了。


2.3
OMXMaster::OMXMaster()
    : mVendorLibHandle(NULL) {
    addVendorPlugin();

#ifndef NO_OPENCORE
    addPlugin(new OMXPVCodecsPlugin);
#endif
}
2.3中,硬解部分还是使用原来OMX的标准来进行即addVendorPlugin(),然后软件部分的话,在这里没有说明,
事实上软件部分,2.3之间返回了XXXDecoder

4.0
OMXMaster::OMXMaster()
    : mVendorLibHandle(NULL) {
    addVendorPlugin();
    addPlugin(new SoftOMXPlugin);
}

4.0上对于软硬编解码,都使用OMX标准,挂载plugins的方式来进行。软解部分使用addPlugin(new SoftOMXPlugin)。

/
//2,一个重要的类OMXCodecObserver
/

OMXCodecObserver是一个OMXCodec的内部类,它在OMXCodec的Create函数中会实例化一个,并且使用observer->setCodec(codec)
接口,对一个OMXCodec进行管理。OMXCodecObserver还有一个接口onMessage,其作用是对由observer管理的codec进行消息处理,
这个位于OMXCodecObserver的onMessage函数是一个统一的入口,具体的消息处理,由被observer管理的codec自己实现。


/
//3,在OMXCodec的create中还做了些什么
/
现在看看相关的代码片段

sp<MediaSource> OMXCodec::Create(
        const sp<IOMX> &omx,
        const sp<MetaData> &meta, bool createEncoder,
        const sp<MediaSource> &source,
        const char *matchComponentName,
        uint32_t flags,
        const sp<ANativeWindow> &nativeWindow) {
    int32_t requiresSecureBuffers;
    if (source->getFormat()->findInt32(
                kKeyRequiresSecureBuffers,
                &requiresSecureBuffers)
            && requiresSecureBuffers) {
        flags |= kIgnoreCodecSpecificData;
        flags |= kUseSecureInputBuffers;
        flags |= kEnableGrallocUsageProtected;
    }
    else
    {
        flags &= ~kEnableGrallocUsageProtected;
    }

    const char *mime;
    bool success = meta->findCString(kKeyMIMEType, &mime);
    CHECK(success);

    Vector<String8> matchingCodecs;
    findMatchingCodecs(
            mime, createEncoder, matchComponentName, flags, &matchingCodecs);
    if (matchingCodecs.isEmpty()) {
        return NULL;
    }
    sp<OMXCodecObserver> observer = new OMXCodecObserver;
    IOMX::node_id node = 0;

    for (size_t i = 0; i < matchingCodecs.size(); ++i) {
        const char *componentNameBase = matchingCodecs[i].string();
        const char *componentName = componentNameBase;

        AString tmp;
        if (flags & kUseSecureInputBuffers) {
            tmp = componentNameBase;
            tmp.append(".secure");

            componentName = tmp.c_str();
        }
		...

        status_t err = omx->allocateNode(componentName, observer, &node);

        ...
        
            sp<OMXCodec> codec = new OMXCodec(
                    omx, node, quirks, flags,
                    createEncoder, mime, componentName,
                    source, nativeWindow);

            observer->setCodec(codec);

            err = codec->configureCodec(meta);

            if (err == OK) {
                if (!strcmp("OMX.Nvidia.mpeg2v.decode", componentName)) {
                    codec->mFlags |= kOnlySubmitOneInputBufferAtOneTime;
                }

                return codec;
			...

    return NULL;
}

从OMXCodec::Create的代码中可以看到,这个Create函数主要做了一下几件事:

//这里,根据mime的类型,先找到对应的容器格式,然后把这个ComponentName放到matchingCodecs中。如果有指定的容器话,作为实参传递给matchComponentName。

1,findMatchingCodecs(mime, createEncoder, matchComponentName, flags, &matchingCodecs);

//实例化一个OMXCodecObserver,作用上面有提到,然后给即将创建的node分配一个node_id,id号被初始化为0
2,sp<OMXCodecObserver> observer = new OMXCodecObserver;
IOMX::node_id node = 0;

//allocateNode函数会通过binder调用到服务端的OMX::allocateNode,函数的具体实现如下
3,status_t err = omx->allocateNode(componentName, observer, &node);

status_t OMX::allocateNode(
        const char *name, const sp<IOMXObserver> &observer, node_id *node) {
    Mutex::Autolock autoLock(mLock);

    *node = 0;
    //OMXNodeInstance,与node_id一一对应,指的是OMX标准下node的实例,这个OMXNodeInstance内部封装了一下对不同实例的操作,以及回调。
    OMXNodeInstance *instance = new OMXNodeInstance(this, observer);

    OMX_COMPONENTTYPE *handle;
    
    //这里会一直调用具体的OMXPlugins的的的makeComponentInstance函数,假设这里的具体OMX plugin是SoftOMXPlugin,那么
    //SoftOMXPlugin的makeComponentInstance函数会根据容器名来动态的打开一个名为llibstagefright_soft_XXX.so的的动态库
    //然后使用ddlopen和dlsym来来调用这个so中的函数。这个操作完成之后,会在底层对应一个具体的编解码组件,例如SoftMPEG4,并对
    //这个编解码组件进行初始化,例如initPorts(),initDecoder()......
    OMX_ERRORTYPE err = mMaster->makeComponentInstance(
            name, &OMXNodeInstance::kCallbacks,
            instance, &handle);

    if (err != OMX_ErrorNone) {
        LOGV("FAILED to allocate omx component '%s'", name);

        instance->onGetHandleFailed();

        return UNKNOWN_ERROR;
    }

    *node = makeNodeID(instance);
    //首先由node_id来区分不同的node,然后给各个不同的node实例化一个CallbackDispatcher来与之对应,这个CallbackDispatcher
    //靠OMXNodeInstance instance来区分。CallbackDispatcher在实例化之后,内部会开启一个线程,这个线程循环的监听List<omx_message> mQueue;
    //看看是否有新的omx_message被分派过来。如果有就dispatch(msg)。
    mDispatchers.add(*node, new CallbackDispatcher(instance));

//给这个OMXNodeInstance绑定一个node_id,和handler,这个handler是上面mMaster->makeComponentInstance传出的。
    instance->setHandle(*node, handle);

    mLiveNodes.add(observer->asBinder(), instance);
    observer->asBinder()->linkToDeath(this);

    return OK;
}

   //在框架层实例化一个OOMXCodec
4,sp<OMXCodec> codec = new OMXCodec(
                    omx, node, quirks, flags,
                    createEncoder, mime, componentName,
                    source, nativeWindow);
   //将前面的codec拉入observer的管理
   observer->setCodec(codec);
   //根据媒体流的format对codec进行一些配置
   err = codec->configureCodec(meta);
   
下面来看张图:




从上面的时序图看一看出omx在stagefright框架中的条用关系,
1,在awesomeplayer中,有一个OMXClient成员变量mClient,作用与服务端OMX交互的代理。
2,stagefright框架通过OMXCodec进入到OMX(即OMX的服务端)
3,通过服务端的OMX去和具体的OMXNodeInstance还有编解码Components打交道。
4,回调机制可以从图中看出来,是通过CallbackDispatcher分派消息,然后一层一层的往上调用onMessage().


节约篇幅再开一篇


原文四: 

Android_ics openmax in stagefright 学习记录------1

链接:

http://blog.csdn.net/mci2004/article/details/7753759

/
//4,回到awesomeplayer initVideoDecoder()中
/

    mVideoSource = OMXCodec::Create(
            mClient.interface(), mVideoTrack->getFormat(),
            false, // createEncoder
            mVideoTrack,
            NULL, flags, USE_SURFACE_ALLOC ? mNativeWindow : NULL);

    if (mVideoSource != NULL) {
        int64_t durationUs;
        if (mVideoTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) {
            Mutex::Autolock autoLock(mMiscStateLock);
            if (mDurationUs < 0 || durationUs > mDurationUs) {
                mDurationUs = durationUs;
            }
        }
       //这里的mVideoSource就是上面返回的OMXCodec类型,所以mVideoSource->start()的函数定义如下,但是这里的OMXCodec类型靠node_id来标示,而且
OMXCodec(mVideoSource)所扮演的角色类型也不同(setComponentRole())。
      status_t err = mVideoSource->start();

status_t OMXCodec::start(MetaData *meta) {
    CODEC_LOGV("OMXCodec::start ");
    Mutex::Autolock autoLock(mLock);

    if(mPaused) {
        if (!strncmp(mComponentName, "OMX.qcom.", 9)) {
            while (isIntermediateState(mState)) {
                mAsyncCompletion.wait(mLock);
            }
            CHECK_EQ(mState, (status_t)PAUSED);
            status_t err = mOMX->sendCommand(mNode,
            OMX_CommandStateSet, OMX_StateExecuting);
            CHECK_EQ(err, (status_t)OK);
            setState(IDLE_TO_EXECUTING);
            mPaused = false;
            while (mState != EXECUTING && mState != ERROR) {
                mAsyncCompletion.wait(mLock);
            }
            drainInputBuffers();
            return mState == ERROR ? UNKNOWN_ERROR : OK;
        } else {   // SW Codec
            mPaused = false;
            return OK;
        }
    }

    if (mState != LOADED) {
        return UNKNOWN_ERROR;
    }

    sp<MetaData> params = new MetaData;
    if (mQuirks & kWantsNALFragments) {
        params->setInt32(kKeyWantsNALFragments, true);
    }
    if (meta) {
        int64_t startTimeUs = 0;
        int64_t timeUs;
        if (meta->findInt64(kKeyTime, &timeUs)) {
            startTimeUs = timeUs;
        }
        params->setInt64(kKeyTime, startTimeUs);
    }
    //第一次跳过前面的代码,走到这里,注意这里的mSource是OMXCodec构造函数中传进来的。追根溯源会发 现这里的mSource其实是mVideoTrack(awesomeplayer
的成员变量),而mVideoTrack又是在AwesomePlayer::setVideoSource中被赋值的,在awesomeplayer的setdatasource()函数中setVideoSource(extractor
->getTrack(i));所以这里的mSource应该是一个具体的XXXExtractor.getTrack(i)之后得到的。最终发现,mSource实际上是某种格式的一段媒体流。例如
MPEG4Source等等。所以这个start函数的做用是根据这段媒体流的实际需要,分配一个合适大小的buf供以后使用。

    status_t err = mSource->start(params.get());

    if (err != OK) {
        return err;
    }

    mCodecSpecificDataIndex = 0;
    mInitialBufferSubmit = true;
    mSignalledEOS = false;
    mNoMoreOutputData = false;
    mOutputPortSettingsHaveChanged = false;
    mSeekTimeUs = -1;
    mSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;
    mTargetTimeUs = -1;
    mFilledBuffers.clear();
    mPaused = false;
    //从这个函数开始通过OMX和OMX的components通信了
    return init();
}

status_t OMXCodec::init() {
    // mLock is held.

    CHECK_EQ((int)mState, (int)LOADED);

    status_t err;
    //mQuirks是一些和具体编解码组件相关的特性参数,他描述了该组件在工作时候需要的一些注意事项
    //mQuirks主要很硬件编解码组件有关
    if (!(mQuirks & kRequiresLoadedToIdleAfterAllocation)) {
        //向特定的node放送Command,这个node有node_id即mNode表示,后面会详细介绍-------------    1 
        err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle);
        CHECK_EQ(err, (status_t)OK);
        setState(LOADED_TO_IDLE);
    }
    
   //为不同的nodeInstance分配其输入和输出端口上的buffer,并确定分配大小和分配策略
    err = allocateBuffers();
    if (err != (status_t)OK) {
        CODEC_LOGE("Allocate Buffer failed - error = %d", err);
        setState(ERROR);
        return err;
    }

    if (mQuirks & kRequiresLoadedToIdleAfterAllocation) {
        err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle);
        CHECK_EQ(err, (status_t)OK);

        setState(LOADED_TO_IDLE);
    }

    while (mState != EXECUTING && mState != ERROR) {
        mAsyncCompletion.wait(mLock);
    }

    return mState == ERROR ? UNKNOWN_ERROR : OK;
}




///
//5,看看mOMX->sendCommand(mNode,...,...)做了什么
///
在OMXCodec会通过OMX服务去调用:

//该函数会查找node_id号所对应的nodeInstance,然后去调用这个nodeInstance的sendCommand函数。
status_t OMX::sendCommand(
        node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) {
    return findInstance(node)->sendCommand(cmd, param);
}
	|
	|
	|
	V
//注意这里这个OMXNodeInstance对应就是具体node了,OMX_SendCommand函数中的第一个参数就是在instance->setHandle(*node, handle)传进出的,而且这个
handle是由mMaster->makeComponentInstance传出的,上面有提到。
status_t OMXNodeInstance::sendCommand(
        OMX_COMMANDTYPE cmd, OMX_S32 param) {
    Mutex::Autolock autoLock(mLock);

    OMX_ERRORTYPE err = OMX_SendCommand(mHandle, cmd, param, NULL);
    return StatusFromOMXError(err);
}

//OMX_SendCommand由下面的宏定义,可以看到最后调用的就是那个mHandle的SendCommand方法
#define OMX_SendCommand(                                    \
         hComponent,                                        \
         Cmd,                                               \
         nParam,                                            \
         pCmdData)                                          \
     ((OMX_COMPONENTTYPE*)hComponent)->SendCommand(         \
         hComponent,                                        \
         Cmd,                                               \
         nParam,                                            \
         pCmdData)                          /* Macro End */








//特别要注意的地方是上面((OMX_COMPONENTTYPE*)hComponent)->SendCommand函数实际上是调用的过程是,首先通过
SoftOMXComponent::SendCommandWrapper----->SoftOMXComponent *me =(SoftOMXComponent *)((OMX_COMPONENTTYPE *)component)->
pComponentPrivate;(me被转换成一个this指针)---->me->sendCommand(cmd, param, data);(这个me->sendCommand调用的肯定是
SimpleSoftOMXComponent::sendCommand) 
注意一个继承关系   SoftMPEG4--->SimpleSoftOMXComponent--->SoftOMXComponent  (箭头理解为继承于)

//sendCommand的函数实现如下:
OMX_ERRORTYPE SimpleSoftOMXComponent::sendCommand(
        OMX_COMMANDTYPE cmd, OMX_U32 param, OMX_PTR data) {
    CHECK(data == NULL);

    sp<AMessage> msg = new AMessage(kWhatSendCommand, mHandler->id());
    msg->setInt32("cmd", cmd);
    msg->setInt32("param", param);
    //消息被投递出去了
    msg->post();------------------------------------------------------- 1

    return OMX_ErrorNone;
}

//消息投递出去之后,什么时候得到处理呢?看下 SimpleSoftOMXComponent的构造函数就明白了
SimpleSoftOMXComponent::SimpleSoftOMXComponent(
        const char *name,
        const OMX_CALLBACKTYPE *callbacks,
        OMX_PTR appData,
        OMX_COMPONENTTYPE **component)
    : SoftOMXComponent(name, callbacks, appData, component),
      mLooper(new ALooper),
      mHandler(new AHandlerReflector<SimpleSoftOMXComponent>(this)),
      mState(OMX_StateLoaded),
      mTargetState(OMX_StateLoaded) {
    mLooper->setName(name);
    mLooper->registerHandler(mHandler);

    mLooper->start(
            false, // runOnCallingThread
            false, // canCallJava
            ANDROID_PRIORITY_FOREGROUND);
}



//在 SimpleSoftOMXComponent中有一个mHandler(new AHandlerReflector<SimpleSoftOMXComponent>(this)),---注意这个mHandle的参数,其中 
SimpleSoftOMXComponent做为这个mHandle的投递目标,也就是消息将有 SimpleSoftOMXComponent处理。
最终,消息会被投递到mLooper中的mEventQueue,并在消息循环(也就是mLooper.loop()函数中,被deliverMessage,然后被相应的hanler处理,也就是
SimpleSoftOMXComponent::onMessageReceived函数)。


在来看一个继承关系图。


这个继承关系图,也许能说明一些问题。最右边的是具体的软件编/解码模块的实现的代码,在openmanx框架中,为了实现动态绑定,这些moudle最终会被编译成libstagefright_soft_XXXX.so。具体的编译步骤,可以去看相关路径下的Android.mk文件。可以说明一点的是,这些libstagefright_soft_XXXX.so是首先由一些编解码的srcfile编译出一个类似于libstagefright_m4vh263dec的静态库,然后这个静态库再被编译进一个.so里。

        在这个继承关系图上,位于中间的SimpleSoftOMXComponent,完成了大部分的工作,它负责sendCommand的同时又要将一些反馈信息notify到其父类
SoftOMXCodec来处理。这些反馈的信息包括,组件状态的变化,ports的状态的变化等等。父类SoftOMXComponent来处理的notify的信息,其主要的工作是,找到具体的
handler然后调用适当的CallBacks。


关于组建的结构再来看一张图,下面是一张组件的模型图:


说的简单一点,command从组件的外部进来,在组件内进行一系列的传递和处理(组件内部自己处理)之后,再将反馈信息传回组件外部,传递给OMXCodec框架层。


说点废话,把时序图再贴一次:




首先,可以明确一点的是,整个stagefright框架都是事件驱动模式的。上面的时序图主要描述了,收到onPrepareAsyncEvent时间后,OMX框架是如何是如何建立自己的编
解码组件。

上面的时序图中,要明确两点 1,事件驱动模式是异步的 2,时序图的末端 CallbackDispatcher也是异步分派的

从上面的时序图中还有一点没有办法看出来。就是对应于具体的编解码组件,编解码的过程时候时候发生。下面就这个问题,展开讨论。



//
//下面的研究将围绕三个问题
/
1,待解码的原始数据问题  
2,具体编解码组件是如何完成编解码工作的  
3,解码之后的数据处理问题


以上的三个问题,在我的《数据流向分析》中有介绍。


OK,备忘做完了,还是那句话,菜鸟们请批判性的看,comments are very welcome.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值