在本篇中,我们将对OMXNodeInstance中的callback线程与OMXNodeInstance的创建与销毁进行讨论。
1、CallbackDispatcher
不知大伙是否还记得,在Omx::allocateNode创建完组件实例后,会调用setHandle将组件句柄传递给OMXNodeInstance。
void OMXNodeInstance::setHandle(OMX_HANDLETYPE handle) {
CLOG_LIFE(allocateNode, "handle=%p", handle);
CHECK(mHandle == NULL);
mHandle = handle;
if (handle != NULL) {
mDispatcher = new CallbackDispatcher(this);
}
}
在setHandle实现中,我们会看到创建了一个CallbackDispatcher对象,这个CallbackDispatcher是做什么用的呢?一起来看看它的声明:
struct OMXNodeInstance::CallbackDispatcher : public RefBase {
explicit CallbackDispatcher(const sp<OMXNodeInstance> &owner);
void post(const omx_message &msg, bool realTime = true);
bool loop();
protected:
virtual ~CallbackDispatcher();
private:
enum {
kMaxQueueSize = 12,
};
Mutex mLock;
sp<OMXNodeInstance> const mOwner;
bool mDone;
Condition mQueueChanged;
std::list<omx_message> mQueue;
sp<CallbackDispatcherThread> mThread;
void dispatch(std::list<omx_message> &messages);
};
CallbackDispatcher内部维护了一个omx_message队列,有条件变量和锁,所以它是一个经典的生产者-消费者模型。OMX组件在模型中充当生产者的角色,上层OMXNodeInstance充当消费者的角色,CallbackDispatcher起着缓冲区的作用,接收来自OMX组件的消息或数据,并暂时存储这些信息,等待进一步的处理或转发。
struct OMXNodeInstance::CallbackDispatcherThread : public Thread {
explicit CallbackDispatcherThread(CallbackDispatcher *dispatcher)
: mDispatcher(dispatcher) {
}
private:
CallbackDispatcher *mDispatcher;
bool threadLoop();
};
CallbackDispatcher有一个CallbackDispatcherThread成员,该类型继承于Thread,创建该类型对象时需要传入一个CallbackDispatcher对象,因此可以猜到消息的转发和处理是在该线程中执行的。
bool OMXNodeInstance::CallbackDispatcherThread::threadLoop() {
return mDispatcher->loop();
}
CallbackDispatcherThread启动时会执行CallbackDispatcher的loop方法,到这里我们应该可以猜到loop方法内部有一个while循环,有消息就处理消息,没有消息就等待条件变量。
bool OMXNodeInstance::CallbackDispatcher::loop() {
for (;;) {
std::list<omx_message> messages;
{
Mutex::Autolock autoLock(mLock);
while (!mDone && mQueue.empty()) {
mQueueChanged.wait(mLock);
}
if (mDone) {
break;
}
messages.swap(mQueue);
}
dispatch(messages);
}
return false;
}
void OMXNodeInstance::CallbackDispatcher::dispatch(std::list<omx_message> &messages) {
if (mOwner == NULL) {
ALOGV("Would have dispatched a message to a node that's already gone.");
return;
}
mOwner->onMessages(messages);
}
可以看到消息分发、处理是调用的dispatch方法,最终调用OMXNodeInstance的onMessages方法来消费消息。
从上面的代码中我们可以看到,循环退出需要将mDone置为true,再进一步思考一下,线程是如何启动、停止的呢?
OMXNodeInstance::CallbackDispatcher::CallbackDispatcher(const sp<OMXNodeInstance> &owner)
: mOwner(owner),
mDone(false) {
mThread = new CallbackDispatcherThread(this);
mThread->run("OMXCallbackDisp", ANDROID_PRIORITY_FOREGROUND);
}
OMXNodeInstance::CallbackDispatcher::~CallbackDispatcher() {
{
Mutex::Autolock autoLock(mLock);
mDone = true;
mQueueChanged.signal();
}
status_t status = mThread->join();
if (status != WOULD_BLOCK) {
CHECK_EQ(status, (status_t)NO_ERROR);
}
}
我们需要看CallbackDispatcher的构造函数与析构函数,CallbackDispatcher创建完成后消息处理线程就被启动了,销毁CallbackDispatcher实例时会先等待线程停止。
关于CallbackDispatcher我们就了解到这里,这部分应该不难。
2、Callbacks
OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = {
&OnEvent, &OnEmptyBufferDone, &OnFillBufferDone
};
OMXNodeInstance有三个Callback,它们的作用之前在Spec阅读章节中已经做过了解,这里我们来简单看看它们的实现。
OnEmptyBufferDone
OMX_ERRORTYPE OMXNodeInstance::OnEmptyBufferDone(
OMX_IN OMX_HANDLETYPE /* hComponent */,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
if (pAppData == NULL) {
ALOGE("b/25884056");
return OMX_ErrorBadParameter;
}
OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
if (instance->mDying) {
return OMX_ErrorNone;
}
int fenceFd = instance->retrieveFenceFromMeta_l(pBuffer, kPortIndexOutput);
omx_message msg;
msg.type = omx_message::EMPTY_BUFFER_DONE;
msg.fenceFd = fenceFd;
msg.u.buffer_data.buffer = instance->findBufferID(pBuffer);
instance->mDispatcher->post(msg);
return OMX_ErrorNone;
}
进入函数体首先会看到retrieveFenceFromMeta_l,它和我们在上一篇文章最后看到的storeFenceInMeta_l是相互对应的,在OMXNodeInstance.h有对该函数功能的描述:
// Retrieves the fence from buffer if ANWBuffer type and has enough space. Otherwise, returns -1
int retrieveFenceFromMeta_l(OMX_BUFFERHEADERTYPE *header, OMX_U32 portIndex);
翻译过来是:如果是ANWBuffer并且有足够的空间,就从BufferHeader中获取fence,否则返回-1。
OnEmptyBufferDone对应的是input buffer的回调,为什么retrieveFenceFromMeta_l传入的第二个参数的值为kPortIndexOutput呢?
int OMXNodeInstance::retrieveFenceFromMeta_l(
OMX_BUFFERHEADERTYPE *header, OMX_U32 portIndex) {
OMX_U32 metaSize = portIndex == kPortIndexInput ? header->nAllocLen : header->nFilledLen;
int fenceFd = -1;
if (mMetadataType[portIndex] == kMetadataBufferTypeANWBuffer
&& header->nAllocLen >= sizeof(VideoNativeMetadata)) {
VideoNativeMetadata &nativeMeta = *(VideoNativeMetadata *)(header->pBuffer);
if (nativeMeta.eType == kMetadataBufferTypeANWBuffer) {
fenceFd = nativeMeta.nFenceFd;
nativeMeta.nFenceFd = -1;
}
if (metaSize < sizeof(nativeMeta) && fenceFd >= 0) {
CLOG_ERROR(foundFenceInEmptyMeta, BAD_VALUE, FULL_BUFFER(
NULL, header, nativeMeta.nFenceFd));
fenceFd = -1;
}
}
return fenceFd;
}
从retrieveFenceFromMeta_l可以看到,当portIndex为kPortIndexOutput时,metaSize等于header->nFilledLen,对于使用Metadata的端口而言,nFilledLen的大小等于Metadata size。根据上一篇文章最后所说的,nAllocLen应该也能表示Metadata size,所以我认为这里retrieveFenceFromMeta_l的参数填kPortIndexInput也是可以的。
如果MetadataType是kMetadataBufferTypeANWBuffer,retrieveFenceFromMeta_l会从pBuffer中获取到fenceFd,并将pBuffer记录的fence置为-1。
再回到OnEmptyBufferDone这个方法,它负责将需要回传的input buffer相关信息封装为omx_message对象,并最终将这个消息对象添加到队列中。omx_message声明于IOMX.h:
struct omx_message {
enum {
EVENT,
EMPTY_BUFFER_DONE,
FILL_BUFFER_DONE,
FRAME_RENDERED,
} type;
int fenceFd;
union {
// if type == EVENT
struct {
OMX_EVENTTYPE event;
OMX_U32 data1;
OMX_U32 data2;
OMX_U32 data3;
OMX_U32 data4;
} event_data;
// if type == EMPTY_BUFFER_DONE
struct {
IOMX::buffer_id buffer;
} buffer_data;
// if type == FILL_BUFFER_DONE
struct {
IOMX::buffer_id buffer;
OMX_U32 range_offset;
OMX_U32 range_length;
OMX_U32 flags;
OMX_TICKS timestamp;
} extended_buffer_data;
// if type == FRAME_RENDERED
struct {
OMX_TICKS timestamp;
OMX_S64 nanoTime;
} render_data;
} u;
};
omx_message主要包含三个字段:
- type:记录message的类型,帮助ACodec解析message;
- fenceFd:buffer对应的fence
- u:这是一个联合体,不同的type需要回传的信息不一样,ACodec会根据type解析联合体存储的内容。
OnFillBufferDone
OMX_ERRORTYPE OMXNodeInstance::OnFillBufferDone(
OMX_IN OMX_HANDLETYPE /* hComponent */,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
if (instance->mDying) {
return OMX_ErrorNone;
}
int fenceFd = instance->retrieveFenceFromMeta_l(pBuffer, kPortIndexOutput);
omx_message msg;
msg.type = omx_message::FILL_BUFFER_DONE;
msg.fenceFd = fenceFd;
msg.u.extended_buffer_data.buffer = instance->findBufferID(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;
instance->mDispatcher->post(msg);
return OMX_ErrorNone;
}
OnFillBufferDone和OnEmptyBufferDone类似,它负责将需要回传的output buffer相关信息封装为omx_message对象,需要回传的信息包含buffer-id、数据起始偏移量、数据长度、flags、pts。
OnEvent
OMX_ERRORTYPE OMXNodeInstance::OnEvent(
OMX_IN OMX_HANDLETYPE /* hComponent */,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_EVENTTYPE eEvent,
OMX_IN OMX_U32 nData1,
OMX_IN OMX_U32 nData2,
OMX_IN OMX_PTR pEventData) {
OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
if (instance->mDying) {
return OMX_ErrorNone;
}
instance->onEvent(eEvent, nData1, nData2);
if (eEvent == OMX_EventOutputRendered) {
if (pEventData == NULL) {
return OMX_ErrorBadParameter;
}
// process data from array
OMX_VIDEO_RENDEREVENTTYPE *renderData = (OMX_VIDEO_RENDEREVENTTYPE *)pEventData;
for (size_t i = 0; i < nData1; ++i) {
omx_message msg;
msg.type = omx_message::FRAME_RENDERED;
msg.fenceFd = -1;
msg.u.render_data.timestamp = renderData[i].nMediaTimeUs;
msg.u.render_data.nanoTime = renderData[i].nSystemTimeNs;
bool realTime = msg.u.render_data.timestamp == INT64_MAX;
instance->mDispatcher->post(msg, realTime);
}
return OMX_ErrorNone;
}
omx_message msg;
msg.type = omx_message::EVENT;
msg.fenceFd = -1;
msg.u.event_data.event = eEvent;
msg.u.event_data.data1 = nData1;
msg.u.event_data.data2 = nData2;
instance->mDispatcher->post(msg, true /* realTime */);
return OMX_ErrorNone;
}
进入OnEvent首先会调用另一个OMXNodeInstance::onEvent方法,该方法没有做什么实质性的工作,只是用于打印debug log。说起log,默认状态下OMXNodeInstance的log处于关闭的状态,我们可以在console先执行以下命令再起播,这样就能打开所有debug log了。
$ setprop debug.stagefright.omx-debug 5
接下来,OnEvent将事件分为两类:
- OMX_EventOutputRendered:该事件只有在Tunnel Mode下会使用;
- 其他。
在Tunnel Mode下,OnFillBufferDone回调函数将不再被触发,output buffer不再被回传到上层。因此,上层应用无法直接获取到当前播放内容的对应的PTS,OMX_EventOutputRendered事件的设计就是为了解决这个问题的。
我们都知道每一帧数据会对应一个pts,所以每渲染一帧都要回传一个OMX_EventOutputRendered事件给上层,真的要这么做吗?我们平时看视频,进度条一般0.5s/1s才会更新一次,所以pts回传不需要那么频繁。Android的做法是在调用CallbackDispatcher->post方法时,将第二个参数realTime置为false,这样消息就不会被立即发送给上层。那这些事件什么时候会发送呢?当有实时事件被发送时,这些非实时事件会被携带发送。
不过有一个例外,当pts为INT64_MAX时,OMX_EventOutputRendered事件会被立即发送,pts为INT64_MAX只有EOS时会出现。
3、freeNode
关注公众号《青山渺渺》阅读全文