Android Media Framework(十二)OMXNodeInstance - Ⅴ

在本篇中,我们将对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

关注公众号《青山渺渺》阅读全文
请添加图片描述

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值