awesomeplayer_openmax回调函数运行流程

前面介绍了一些初始化的东西。下面介绍一下OMX回调函数的注册。
熟悉OMX的同学可能都知道,OMX运行的时候,最实质上的函数是依靠的fillBuffer, emptyBuffer,FillBufferDone,EmptyBufferDone等几个函数。
那么这几个函数是如何注册,以及运行起来的呢?下面总结一下
大体流程是这样的:
OMXCodec使用emptyBuffer()函数(IL层中为OMX_EmptyThisBuffer())传递未解码的buffer给component,
component收到该命令后会读取input port buffer中的数据,将其组装成帧进行解码,
读取buffer中的数据完成后会调用EmptyBufferDone通知OMXCodec。
Compoment使用EmptyBufferDone消息通知OMXCodec已完成input buffer的读取, 
具体的实现是通过调用回调函数OnEmptyBufferDone()实现的。
OMXCodec收到该命令后会通过mVideoTrack读取新的视频码流到input port的buffer中,并调用OMX_EmptyThisBuffer通知component。
OMXCodec使用OMX_FillThisBuffer传递空的bffer给component用于存储解码后的帧,Component收到该命令后将解码好的帧数据复制到该buffer上,然后调用FillBufferDone通知OMXCodec。
Compoment使用FillBufferDone通知OMXCodec已完成output port buffer的填充,
具体的实现是通过调用回调函数OnFillBufferDone()实现的。
OMXCodec收到该命令后将解码好的帧存入可显示队列中,
AwesomePlayer调用OMXCodec::read()函数读出可显示队列的对头送给Renderer完成颜色转换等操作再传递给SurfaceFlinger进行图像绘制,
同时调用release()函数,其中的SignalBufferDone()会用OMX_FillThisBuffer通知component有空的buffer可填充。

见图一,图二:












下面我们结合代码分析一下这几个函数如何被调用的。
先看input/output buffer是如何被部署到openmax,以及是如何轮转起来的。
在onVideoEvent中,(我们最后再讲解onVideoEvent)
首先mVideoSource->read,
实际上就是调用了OMXCodec::read
status_t OMXCodec::read(        MediaBuffer **buffer, const ReadOptions *options) 
{
    if (mInitialBufferSubmit) {
        mInitialBufferSubmit = false;   // 初始化值为true,置为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();  //调用drainInputBuffers,把输入通道中的所有输入缓存区,逐个传递给drainInputBuffer,即先把inputbuffer都读满,
然后一次性送给具体的component,让其慢慢解码,drainInputBuffer的实现见后面
        if (mState == EXECUTING) {
            // Otherwise mState == RECONFIGURING and this code will trigger
            // after the output port is reenabled.
            fillOutputBuffers();
        }
    }
    while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
        if ((err = waitForBufferFilled_l()) != OK) {
            return err;
        }
    }
//等待输出缓冲区的数据,如果有数据就往下走,读取数据输出。
//等OMXCodec::read后续调用时,就直接到这一步,等待输出缓冲区数据。
//这个信号主要是在omx_message::FILL_BUFFER_DONE处理流程中发送,这个事件是openmax解码数据后把一个输出缓冲区填充满了就触发这个事件。
//在omx_message::FILL_BUFFER_DONE处理流程中有下面两行代
//                mFilledBuffers.push_back(i);
//                mBufferFilled.signal();
//即把已经填充好的输出缓冲区索引保存到mFilledBuffers中,然后再发信号
    size_t index = *mFilledBuffers.begin();
    mFilledBuffers.erase(mFilledBuffers.begin());
//检查到有数据可读了,就从mFilledBuffers读取头部的缓冲区索引,同时把这个索引从List中删除,
    BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index);
    CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);
    info->mStatus = OWNED_BY_CLIENT;
    info->mMediaBuffer->add_ref();
//然后根据索引找到缓冲区,再把缓冲区地址赋值给输出指针输出,这个缓存区的引用计数会加1,上层使用完会释放
    *buffer = info->mMediaBuffer;
    return OK;
}
bool OMXCodec::drainInputBuffer(BufferInfo *info)
{
    MediaBuffer *srcBuffer;
    err = mSource->read(&srcBuffer, &options);
//在drainInputBuffer中会从调用mSource->read读取原始数据,填充到缓存区中,然后调用mOMX->emptyBuffer发消息给openmax component。
    err = mOMX->emptyBuffer(
            mNode, info->mBuffer, 0, offset,
            flags, timestampUs);
}
void OMXCodec::fillOutputBuffer(BufferInfo *info) {
    status_t err = mOMX->fillBuffer(mNode, info->mBuffer);
//调用fillOutputBuffers把输出通道中的输出缓冲区,逐个传递给fillOutputBuffer。
//在fillOutputBuffer中调用mOMX->fillBuffer发消息给openmax,相当于把缓冲区传递给openmax
}
上面讲的是在第一次执行OMXCodec::read时的操作。
当整个编解码流程运行起来之后,会面临着一个输入\输出缓冲区更新的问题。

输入缓冲区更新:
如果一个输入缓冲区数据被读取完了,openmax会触发事件omx_message::EMPTY_BUFFER_DONE通知上层,
在这个事件处理流程中,会根据发送来的bufferid找到对应的输入缓冲区,
然后把这个缓冲区传递给drainInputBuffer,继续往下执行。
如下:      
void OMXCodec::on_message(const omx_message &msg) {
  case omx_message::EMPTY_BUFFER_DONE:
        {
                    drainInputBuffer(&buffers->editItemAt(i));
            break;
        }
}
输出缓冲区更新:
解码完比后,openmax组件触发omx_message::FILL_BUFFER_DONE,
输出缓冲区会被传出交给上层使用(传递给surfaceflinger来显示),使用完后需要把这个缓存区重新交给openmax,
上层使用完输出缓冲区后会调用MediaBuffer::release进行销毁,在这个接口中会把输出缓冲区的引用计数减1,
然后调用signalBufferReturned,实际对应OMXCodec::signalBufferReturned接口,
再下一层调用fillOutputBuffer,把这个缓冲区重新交给openmax。

(后面会有单独文章讲解这一部分)


总结:这样就是通过第一次调用drainInputBuffers触发openmax,然后后面依靠openmax的事件驱动来完成数据的读取、解码操作。

下面我们用输出缓冲区为例,再细化一下上面讲到的流程:
openmax component解码完一帧之后,
会调用 ppCallbacks->FillBufferDone,也就是调用了之前初始化好的OMX::OnFillBufferDone
OMX_ERRORTYPE OMX::OnFillBufferDone(
        node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) {
    ALOGV("OnFillBufferDone buffer=%p", pBuffer);
    omx_message msg;
    msg.type = omx_message::FILL_BUFFER_DONE;  //设置msg的type,便于接收端进行辨识
    msg.node = node;
    msg.u.extended_buffer_data.buffer = pBuffer;
    msg.u.extended_buffer_data.range_offset = pBuffer->nOffset;
    msg.u.extended_buffer_data.range_length = pBuffer->nFilledLen;
    msg.u.extended_buffer_data.flags = pBuffer->nFlags;
    msg.u.extended_buffer_data.timestamp = pBuffer->nTimeStamp;
    msg.u.extended_buffer_data.platform_private = pBuffer->pPlatformPrivate;
    msg.u.extended_buffer_data.data_ptr = pBuffer->pBuffer;
    findDispatcher(node)->post(msg);    //将msg信息打包,然后通过node找到之前注册好的Dispatcher,把msg post出去
    return OMX_ErrorNone;
}
void OMX::CallbackDispatcher::post(const omx_message &msg) {
    Mutex::Autolock autoLock(mLock);
    mQueue.push_back(msg);
    mQueueChanged.signal();//msg 放入队列,触发信号
}
bool OMX::CallbackDispatcher::loop() {  //前面讲过,OMX::allocateNode的时候,new CallbackDispatcher实例化,创建一个loop,
    for (;;) {
        omx_message msg;
        {
            Mutex::Autolock autoLock(mLock);
            while (!mDone && mQueue.empty()) {
                mQueueChanged.wait(mLock); //队列被唤醒,
            }
            if (mDone) {
                break;
            }
            msg = *mQueue.begin();
            mQueue.erase(mQueue.begin());  //获取到刚才post过来的msg
        }
        dispatch(msg);   //调用CallbackDispatcher::dispatch,msg分发
    }
    return false;
}
void OMX::CallbackDispatcher::dispatch(const omx_message &msg) {
    if (mOwner == NULL) {
        ALOGV("Would have dispatched a message to a node that's already gone.");
        return;
    }
    mOwner->onMessage(msg);  //
}
void OMXNodeInstance::onMessage(const omx_message &msg) {
    if (msg.type == omx_message::FILL_BUFFER_DONE) {
        OMX_BUFFERHEADERTYPE *buffer =
            static_cast<OMX_BUFFERHEADERTYPE *>(
                    msg.u.extended_buffer_data.buffer);
        BufferMeta *buffer_meta =
            static_cast<BufferMeta *>(buffer->pAppPrivate);
        buffer_meta->CopyFromOMX(buffer);
    } else if (msg.type == omx_message::EMPTY_BUFFER_DONE) {
        const sp<GraphicBufferSource>& bufferSource(getGraphicBufferSource());
        if (bufferSource != NULL) {
            // This is one of the buffers used exclusively by
            // GraphicBufferSource.
            // Don't dispatch a message back to ACodec, since it doesn't
            // know that anyone asked to have the buffer emptied and will
            // be very confused.
            OMX_BUFFERHEADERTYPE *buffer =
                static_cast<OMX_BUFFERHEADERTYPE *>(
                        msg.u.buffer_data.buffer);
            bufferSource->codecBufferEmptied(buffer);
            return;
        }
    }
mObserver->onMessage(msg);
}
<pre name="code" class="cpp">virtual void onMessage(const omx_message &msg) {  //经过跨binder调用,最终调用到OMXcodec端,即awesomeplayer的进程空间
        sp<OMXCodec> codec = mTarget.promote();
        if (codec.get() != NULL) {
            Mutex::Autolock autoLock(codec->mLock);
            codec->on_message(msg);
            codec.clear();
        }
    }
  void OMXCodec::on_message(const omx_message &msg) {
    switch (msg.type) {
        case omx_message::FILL_BUFFER_DONE:
        {
..............
                mFilledBuffers.push_back(i);   
//mFilledBuffers数据类型是List<size_t>,它存储的不是缓冲区地址而是输出缓冲区索引。
//即把已经填充好的输出缓冲区索引保存到mFilledBuffers中,然后再发信号
//那么谁wait在这个Condition上呢?看上OMXCodec::read函数中我红色标注的部分。原来在OMXCodec::read函数的后半段中的waitForBufferFilled_l会一直wait
//在OMXCodec::read函数中被返回给AwesomePlayer的mVideoBuffer就是在OMX框架中outPutPort上的buffer。
                mBufferFilled.signal();
                if (mIsEncoder) {
                    sched_yield();
                }
        }
}       
 通过上面的分析可以看到,虽然在OMX框架中 Input/OutPutPort上的buffer的生产和消费是异步,但是还是通过了一个Condition mBufferFilled来做同步。这个生产者消费者的问题,来用一个信号量做同步,还是比较好的。 


输入缓冲区的处理较为简单,不再赘述。可以参考输出缓冲区的流程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值