android多媒體本地播放流程video playback–base on jellybean (五)

前面兩篇文章,我們分別講瞭setdataSource和prepare的過程,獲得瞭mVideoTrack,mAudioTrack,mVideoSourc,mAudioSource,前兩個來自於setdataSource過程,後面兩是prepare。
 
status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {…
if (!haveVideo && !strncasecmp(mime.string(), "video/", 6)) {
            setVideoSource(extractor->getTrack(i));}
else if (!haveAudio && !strncasecmp(mime.string(), "audio/", 6)) {
            setAudioSource(extractor->getTrack(i));
……………..
}
}
void AwesomePlayer::setVideoSource(sp<MediaSource> source) {
    CHECK(source != NULL);
    mVideoTrack = source;
}
void AwesomePlayer::setAudioSource(sp<MediaSource> source) {
    CHECK(source != NULL);
    mAudioTrack = source;
}
 
mVideoSource = OMXCodec::Create(
            mClient.interface(), mVideoTrack->getFormat(),
            false, // createEncoder
            mVideoTrack,
            NULL, flags, USE_SURFACE_ALLOC ? mNativeWindow : NULL);
mAudioSource = OMXCodec::Create(
                mClient.interface(), mAudioTrack->getFormat(),
                false, // createEncoder
                mAudioTrack);
通過mVideoTrack,mAudioTrack我們找到瞭相應的解碼器,並初始化瞭,下面我們就開講mediaplayer如何播放瞭。前面的一些接口實現,我們就不講瞭,不懂的可以回到setdataSource這一篇繼續研究,我們直接看Awesomeplayer的實現。先看大體的時序圖吧:

status_t AwesomePlayer::play_l() {
    modifyFlags(SEEK_PREVIEW, CLEAR);
…………
    modifyFlags(PLAYING, SET);
    modifyFlags(FIRST_FRAME, SET); —設置PLAYING和FIRST_FRAME的標志位
…………………..
    if (mAudioSource != NULL) {—–mAudioSource不為空時初始化Audioplayer
        if (mAudioPlayer == NULL) {
            if (mAudioSink != NULL) {
 
        (1)        mAudioPlayer = new AudioPlayer(mAudioSink, allowDeepBuffering, this);
                mAudioPlayer->setSource(mAudioSource);
 
                seekAudioIfNecessary_l();
            }
        }
        CHECK(!(mFlags & AUDIO_RUNNING));
 
        if (mVideoSource == NULL) {—–如果單是音頻,直接播放
….
     (2)       status_t err = startAudioPlayer_l(
                    false /* sendErrorNotification */);
 
                modifyFlags((PLAYING | FIRST_FRAME), CLEAR);
…………..           
                return err;
            }
        }
    }
   ……
    if (mVideoSource != NULL) {—–有視頻時,發送event到queue,等待處理
        // Kick off video playback
       (3) postVideoEvent_l();
 
        if (mAudioSource != NULL && mVideoSource != NULL) {—-有視頻,音頻時,檢查他們是否同步
       (4)     postVideoLagEvent_l();
        }
    }
    }
…………..
 
    return OK;
}
 
在playe_l方法裡,我們可以看到首先是實例化一個audioplayer來播放音頻,如果單單是音頻直接就播放,現在我們是本地視頻播放,將不會走第二步,直接走第三和第四步。我們看下postVideoEvent_l()方法,跟我們在講prepareAsync_l的類似:
void AwesomePlayer::postVideoEvent_l(int64_t delayUs) {
……………
    mVideoEventPending = true;
    mQueue.postEventWithDelay(mVideoEvent, delayUs < 0 ? 10000 : delayUs);
}
 
mVideoEvent在我們構造awesomeplayer時已經定義:
mVideoEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoEvent);
 
所以我們看onVideoEvent方法:
void AwesomePlayer::onVideoEvent() {
if (!mVideoBuffer) {
for (;;) {
         (1)   status_t err = mVideoSource->read(&mVideoBuffer, &options); —mVideoSource(omxcodec)
            options.clearSeekTo();
             ++mStats.mNumVideoFramesDecoded;
}
(2)  status_t err = startAudioPlayer_l();
 
if ((mNativeWindow != NULL)
            && (mVideoRendererIsPreview || mVideoRenderer == NULL)) {
        mVideoRendererIsPreview = false;
 
     (3)   initRenderer_l();
    }
 
    if (mVideoRenderer != NULL) {
        mSinceLastDropped++;
     (4)   mVideoRenderer->render(mVideoBuffer);
    }
  (5)   postVideoEvent_l();
}
 
我們看到通過read方法去解碼一個個sample,獲取videobuffer,然後render到surfaceTexture。
read 方法:
status_t OMXCodec::read(
        MediaBuffer **buffer, const ReadOptions *options) {
if (mInitialBufferSubmit) {
        mInitialBufferSubmit = false;
 
        if (seeking) {
            CHECK(seekTimeUs >= 0);
            mSeekTimeUs = seekTimeUs;
            mSeekMode = seekMode;
 
            // There's no reason to trigger the code below, there's
            // nothing to flush yet.
            seeking = false;
            mPaused = false;
        }
 
        drainInputBuffers();—對應emptybuffer,輸入端
 
        if (mState == EXECUTING) {
            // Otherwise mState == RECONFIGURING and this code will trigger
            // after the output port is reenabled.
            fillOutputBuffers();–對應fillbuffer,輸出端
        }
    }
….
  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();
    if (mSkipCutBuffer != NULL) {
        mSkipCutBuffer->submit(info->mMediaBuffer);
    }
    *buffer = info->mMediaBuffer;
 
}
在講read之前我們先來回顧下prepare時候的omxcodec::start方法,因為跟我們講read有千絲萬縷的關系,start方法:
status_t OMXCodec::start(MetaData *meta) {
    Mutex::Autolock autoLock(mLock);
……….
    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);
    }
    status_t err = mSource->start(params.get()); —我們以mp4為例,就是mpeg4source
 
    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;
 
    return init();
}
status_t OMXCodec::init() {
….
     err = allocateBuffers();
if (mQuirks & kRequiresLoadedToIdleAfterAllocation) {
        err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle);
        CHECK_EQ(err, (status_t)OK);
 
        setState(LOADED_TO_IDLE);  ——-發送命令到component,讓component處於Idle狀態,經過兩次回調後使component處於OMX_StateExecuting
    }
….
}
由於我們以MP4為例,所以mSource就是MPEG4Source,MPEG4Source在MPEG4Extractor.cpp,我們看下start方法做瞭什麼:
status_t MPEG4Source::start(MetaData *params) {
    Mutex::Autolock autoLock(mLock);
…………..
    mGroup = new MediaBufferGroup;
 
    int32_t max_size;
    CHECK(mFormat->findInt32(kKeyMaxInputSize, &max_size));
 
    mGroup->add_buffer(new MediaBuffer(max_size));
 
    mSrcBuffer = new uint8_t[max_size];
 
    mStarted = true;
 
    return OK;
}
原來是設定輸入的最大buffer.
我再看看allocateBuffers();
status_t OMXCodec::allocateBuffers() {
    status_t err = allocateBuffersOnPort(kPortIndexInput);—-配置輸入端的buffer總量,大小等OMX_PARAM_PORTDEFINITIONTYPE
 
    if (err != OK) {
        return err;
    }
 
    return allocateBuffersOnPort(kPortIndexOutput);—配置輸出端,並dequeuebuffer到OMX端
}
OMX_PARAM_PORTDEFINITIONTYPE 是component的配置信息。
typedef struct OMX_PARAM_PORTDEFINITIONTYPE {
    OMX_U32 nSize;                 /**< Size of the structure in bytes */
    OMX_VERSIONTYPE nVersion;      /**< OMX specification version information */
    OMX_U32 nPortIndex;            /**< Port number the structure applies to */
    OMX_DIRTYPE eDir;              /**< Direction (input or output) of this port */
    OMX_U32 nBufferCountActual;    /**< The actual number of buffers allocated on this port */
    OMX_U32 nBufferCountMin;       /**< The minimum number of buffers this port requires */
    OMX_U32 nBufferSize;           /**< Size, in bytes, for buffers to be used for this channel */
    OMX_BOOL bEnabled;             /**< Ports default to enabled and are enabled/disabled by
                                        OMX_CommandPortEnable/OMX_CommandPortDisable.
                                        When disabled a port is unpopulated. A disabled port
                                        is not populated with buffers on a transition to IDLE. */
    OMX_BOOL bPopulated;           /**< Port is populated with all of its buffers as indicated by
                                        nBufferCountActual. A disabled port is always unpopulated.
                                        An enabled port is populated on a transition to OMX_StateIdle
                                        and unpopulated on a transition to loaded. */
    OMX_PORTDOMAINTYPE eDomain;    /**< Domain of the port. Determines the contents of metadata below. */
    union {
        OMX_AUDIO_PORTDEFINITIONTYPE audio;
        OMX_VIDEO_PORTDEFINITIONTYPE video;
        OMX_IMAGE_PORTDEFINITIONTYPE image;
        OMX_OTHER_PORTDEFINITIONTYPE other;
    } format;
    OMX_BOOL bBuffersContiguous;
    OMX_U32 nBufferAlignment;
} OMX_PARAM_PORTDEFINITIONTYPE;
OMX_PARAM_PORTDEFINITIONTYPE的參數從哪裡來呢?原來來自解碼器端,包括輸入輸出端的buffer大小,總數等信息。
status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) {
    if (mNativeWindow != NULL && portIndex == kPortIndexOutput) {
        return allocateOutputBuffersFromNativeWindow();——當輸出的時候走這裡,給輸出端分配內存空間,並dequeue buffer 到OMX。
    }
    OMX_PARAM_PORTDEFINITIONTYPE def;
    InitOMXParams(&def);
    def.nPortIndex = portIndex;
 
    err = mOMX->getParameter(
            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));—從component獲取OMX_PARAM_PORTDEFINITIONTYPE相關配置,具體哪些可以看上面的結構體
 
    if (err != OK) {
        return err;
    }
    size_t totalSize = def.nBufferCountActual * def.nBufferSize; —從getParameter獲得的每個輸入/輸出端的buffer大小和總數
    mDealer[portIndex] = new MemoryDealer(totalSize, "OMXCodec");
 
    for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
        sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);
        CHECK(mem.get() != NULL);
 
        BufferInfo info;
        info.mData = NULL;
        info.mSize = def.nBufferSize;
 
        IOMX::buffer_id buffer;
        if (portIndex == kPortIndexInput
                && ((mQuirks & kRequiresAllocateBufferOnInputPorts)
                    || (mFlags & kUseSecureInputBuffers))) {
            if (mOMXLivesLocally) {
                mem.clear();
 
                err = mOMX->allocateBuffer(
                        mNode, portIndex, def.nBufferSize, &buffer,
                        &info.mData);—–給輸入端分配內存空間,並使info.mData指向mNode的header
…………….
        info.mBuffer = buffer;
        info.mStatus = OWNED_BY_US;
        info.mMem = mem;
        info.mMediaBuffer = NULL;
 
      mPortBuffers[portIndex].push(info); —BufferInfo 放到Vector<BufferInfo> mPortBuffers[2] mPortBuffers進行管理,到read的時候用,0是輸入,1是輸出。
………………………….
}
 
復習完start方法,我們就來講reader方法瞭:
status_t OMXCodec::read(
        MediaBuffer **buffer, const ReadOptions *options) {
if (mInitialBufferSubmit) {
        mInitialBufferSubmit = false;
………….
  drainInputBuffers();
 
        if (mState == EXECUTING) {
            // Otherwise mState == RECONFIGURING and this code will trigger
            // after the output port is reenabled.
            fillOutputBuffers();
        }
…………………..
    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();
    if (mSkipCutBuffer != NULL) {
        mSkipCutBuffer->submit(info->mMediaBuffer);
    }
    *buffer = info->mMediaBuffer;
}
先看drainInputBuffers方法,主要是從mediasource讀取數據元,
void OMXCodec::drainInputBuffers() {
    CHECK(mState == EXECUTING || mState == RECONFIGURING);
    if (mFlags & kUseSecureInputBuffers) {
        Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput];—mPortBuffers是我們allocateBuffersOnPort方法存下來的對應的輸入/輸出bufferinfo數據
        for (size_t i = 0; i < buffers->size(); ++i) {—循環每次輸入端能填充數據的buffer總數,這是由component的結構決定的,各個廠商的解碼器配置不一樣
            if (!drainAnyInputBuffer()—–往buffer裡面填元數據,給解碼器解碼
                    || (mFlags & kOnlySubmitOneInputBufferAtOneTime)) {
                break;
            }
        }
    }
………………
}
 
bool OMXCodec::drainAnyInputBuffer() {
    return drainInputBuffer((BufferInfo *)NULL);
}
 
bool OMXCodec::drainInputBuffer(BufferInfo *info) {
for (;;) {
        MediaBuffer *srcBuffer;
        if (mSeekTimeUs >= 0) {
            if (mLeftOverBuffer) {
                mLeftOverBuffer->release();
                mLeftOverBuffer = NULL;
            }
 
            MediaSource::ReadOptions options;
            options.setSeekTo(mSeekTimeUs, mSeekMode);
 
            mSeekTimeUs = -1;
            mSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;—seek模式
            mBufferFilled.signal();
 
            err = mSource->read(&srcBuffer, &options);—讀mediasource,我們以mpeg4為例,它的實現就在MPEG4Extrator.cpp(),根據seek模式和seek時間從sampletable裡面找到meta_data。存到srcBuffer。
 
if (mFlags & kUseSecureInputBuffers) {
            info = findInputBufferByDataPointer(srcBuffer->data());—讓bufferinfo的mData指向元數據的data
            CHECK(info != NULL);
        }
      err = mOMX->emptyBuffer(
            mNode, info->mBuffer, 0, offset,
            flags, timestampUs); —-對應component的方法是OMX_EmptyThisBuffer,回調消息為:EmptyBufferDone。
 
    if (err != OK) {
        setState(ERROR);
        return false;
    }
 
    info->mStatus = OWNED_BY_COMPONENT;—-設置狀態為OWNED_BY_COMPONENT
}
 
 
從上面的分析,我們得知emtyBuffer後在5msec之內會有個EmptyBufferDone回調,我們看下omxcodec對該回調的處理:
void OMXCodec::on_message(const omx_message &msg) {
case omx_message::EMPTY_BUFFER_DONE:
………………
IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer;
 
            CODEC_LOGV("EMPTY_BUFFER_DONE(buffer: %p)", buffer);
 
            Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput];
            size_t i = 0;
            while (i < buffers->size() && (*buffers)[i].mBuffer != buffer) {
                ++i;
            }
 
          BufferInfo* info = &buffers->editItemAt(i);
 ————-通過buffer_id找到Vector<BufferInfo> bufferInfo
            info->mStatus = OWNED_BY_US;——-設置info的狀態為OWNED_BY_US
           info->mMediaBuffer->release();—–釋放mediabuffer
           info->mMediaBuffer = NULL;
       
 
…………….
if (mState != ERROR
                    && mPortStatus[kPortIndexInput] != SHUTTING_DOWN) {
                CHECK_EQ((int)mPortStatus[kPortIndexInput], (int)ENABLED);
 
                if (mFlags & kUseSecureInputBuffers) {
                    drainAnyInputBuffer();—-下一片段buffer移交給component
                } else {
                    drainInputBuffer(&buffers->editItemAt(i));
                }
}
 
emptybuffer後應該就是fillOutputBuffer:
void OMXCodec::fillOutputBuffer(BufferInfo *info) {
    CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);
 
    if (mNoMoreOutputData) {
        CODEC_LOGV("There is no more output data available, not "
             "calling fillOutputBuffer");————–沒有數據瞭退出
        return;
    }
 
    if (info->mMediaBuffer != NULL) {
        sp<GraphicBuffer> graphicBuffer = info->mMediaBuffer->graphicBuffer();
        if (graphicBuffer != 0) {
            // When using a native buffer we need to lock the buffer before
            // giving it to OMX.
            CODEC_LOGV("Calling lockBuffer on %p", info->mBuffer);
            int err = mNativeWindow->lockBuffer(mNativeWindow.get(),
                    graphicBuffer.get()); ——-鎖定該buffer,準備render圖像
            if (err != 0) {
                CODEC_LOGE("lockBuffer failed w/ error 0x%08x", err);
 
                setState(ERROR);
                return;
            }
        }
    }
 
    CODEC_LOGV("Calling fillBuffer on buffer %p", info->mBuffer);
    status_t err = mOMX->fillBuffer(mNode, info->mBuffer);———填充輸出端buffer
 
……….
    info->mStatus = OWNED_BY_COMPONENT;
}
fillbuffer後獲得mVideoBuffer就可以在Awesomeplayer的onvideoEvent方法中的mVideoRenderer->render(mVideoBuffer);進行圖像的顯示瞭。
以上我們就是播放的過程瞭。到此多媒體本地播放流程全部講完瞭,裡面很多細節的東西,還得大夥自己深入理解,往後有什麼需要補充和添加的,我會再次補充上。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *