前面介绍了一些初始化的东西。下面介绍一下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
当整个编解码流程运行起来之后,会面临着一个输入\输出缓冲区更新的问题。
输入缓冲区更新:
如果一个输入缓冲区数据被读取完了,openmax会触发事件omx_message::EMPTY_BUFFER_DONE通知上层,
在这个事件处理流程中,会根据发送来的bufferid找到对应的输入缓冲区,
然后把这个缓冲区传递给drainInputBuffer,继续往下执行。
如下:
解码完比后,openmax组件触发omx_message::FILL_BUFFER_DONE,
输出缓冲区会被传出交给上层使用(传递给surfaceflinger来显示),使用完后需要把这个缓存区重新交给openmax,
上层使用完输出缓冲区后会调用MediaBuffer::release进行销毁,在这个接口中会把输出缓冲区的引用计数减1,
然后调用signalBufferReturned,实际对应OMXCodec::signalBufferReturned接口,
再下一层调用fillOutputBuffer,把这个缓冲区重新交给openmax。
下面我们用输出缓冲区为例,再细化一下上面讲到的流程:
openmax component解码完一帧之后,
会调用 ppCallbacks->FillBufferDone,也就是调用了之前初始化好的OMX::OnFillBufferDone
输入缓冲区的处理较为简单,不再赘述。可以参考输出缓冲区的流程。
熟悉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。
(后面会有单独文章讲解这一部分)
下面我们用输出缓冲区为例,再细化一下上面讲到的流程:
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来做同步。这个生产者消费者的问题,来用一个信号量做同步,还是比较好的。
输入缓冲区的处理较为简单,不再赘述。可以参考输出缓冲区的流程。