全新系列文章已更新:
- Android Media Framework - 开篇
- Android Media Framework(一)OpenMAX 框架简介
- Android Media Framework(二)OpenMAX 类型阅读与分析
- Android Media Framework(三)OpenMAX API阅读与分析
- Android Media Framework(四)Non-Tunneled组件的状态转换与buffer分配过程分析
- Android Media Framework(五)Tunnel Mode
- Android Media Framework(六)插件式编程与OMXStore
- Android Media Framework(七)MediaCodecService
- Android Media Framework(八)OMXNodeInstance - Ⅰ
- Android Media Framework(九)OMXNodeInstance - Ⅱ
- Android Media Framework(十)OMXNodeInstance - Ⅲ
- Android Media Framework(十一)OMXNodeInstance - Ⅳ
- Android Media Framework(十二)OMXNodeInstance - Ⅴ
- Android Media Framework(十三)ACodec - Ⅰ
- Android Media Framework(十四)ACodec - Ⅱ
- Android Media Framework(十五)ACodec - Ⅲ
- Android Media Framework(十六)ACodec - Ⅳ
前一节我们学习了Output Format Changed事件是如何上抛并且被处理的,这一节我们紧接着来学习OutputBuffer是如何上抛并且被处理的
1、CodecObserver::onMessages
virtual void onMessages(const std::list<omx_message> &messages) {
for (std::list<omx_message>::const_iterator it = messages.cbegin();
it != messages.cend(); ++it) {
const omx_message &omx_msg = *it;
sp<AMessage> msg = new AMessage;
msg->setInt32("type", omx_msg.type);
switch (omx_msg.type) {
case omx_message::FILL_BUFFER_DONE:
{
msg->setInt32(
"buffer", omx_msg.u.extended_buffer_data.buffer);
msg->setInt32(
"range_offset",
omx_msg.u.extended_buffer_data.range_offset);
msg->setInt32(
"range_length",
omx_msg.u.extended_buffer_data.range_length);
msg->setInt32(
"flags",
omx_msg.u.extended_buffer_data.flags);
msg->setInt64(
"timestamp",
omx_msg.u.extended_buffer_data.timestamp);
msg->setInt32(
"fence_fd", omx_msg.fenceFd);
break;
}
}
msgList->getList().push_back(msg);
}
notify->setObject("messages", msgList);
notify->post();
}
OMXNodeInstance通过CodecObserver将event和message组织成为omx_message后上抛给ACodec,这一节我们重点来看OutputBuffer的处理流程。
因为OutputBuffer是被OMX组件填充完成,所以上抛的消息名称为FILL_BUFFER_DONE,组织到omx_message中的信息包含
buffer id
:buffer索引;buffer offset
:buffer中数据的偏移量;buffer length
:buffer中数据的长度;flags
:携带的标志位;timestamp
:该帧的时间戳;fence fd
:同步句柄;
2、BaseState::onOMXFillBufferDone
消息会发送到ACodec Looper线程中,最终在ACodec::BaseState::onOMXFillBufferDone函数中做处理:
bool ACodec::BaseState::onOMXFillBufferDone(
IOMX::buffer_id bufferID,
size_t rangeOffset, size_t rangeLength,
OMX_U32 flags,
int64_t timeUs,
int fenceFd) {
ALOGV("[%s] onOMXFillBufferDone %u time %" PRId64 " us, flags = 0x%08x",
mCodec->mComponentName.c_str(), bufferID, timeUs, flags);
ssize_t index;
status_t err= OK;
// 这部分是预埋的log,某一帧pts的输出时间 - 某一帧pts的输入时间 = 该帧的解码时长
#if TRACK_BUFFER_TIMING
index = mCodec->mBufferStats.indexOfKey(timeUs);
if (index >= 0) {
ACodec::BufferStats *stats = &mCodec->mBufferStats.editValueAt(index);
stats->mFillBufferDoneTimeUs = ALooper::GetNowUs();
ALOGI("frame PTS %lld: %lld",
timeUs,
stats->mFillBufferDoneTimeUs - stats->mEmptyBufferTimeUs);
mCodec->mBufferStats.removeItemsAt(index);
stats = NULL;
}
#endif
// 遍历BufferInfo数组,根据buffer id找到对应的BufferInfo
BufferInfo *info =
mCodec->findBufferByID(kPortIndexOutput, bufferID, &index);
// 检查当前BufferInfo的状态
BufferInfo::Status status = BufferInfo::getSafeStatus(info);
if (status != BufferInfo::OWNED_BY_COMPONENT) {
ALOGE("Wrong ownership in FBD: %s(%d) buffer #%u", _asString(status), status, bufferID);
mCodec->dumpBuffers(kPortIndexOutput);
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
if (fenceFd >= 0) {
::close(fenceFd);
}
return true;
}
// 记录当前 BufferInfo 在第几帧输出被使用
info->mDequeuedAt = ++mCodec->mDequeueCounter;
// 修改当前 BufferInfo 状态
info->mStatus = BufferInfo::OWNED_BY_US;
if (info->mRenderInfo != NULL) {
// The fence for an emptied buffer must have signaled, but there still could be queued
// or out-of-order dequeued buffers in the render queue prior to this buffer. Drop these,
// as we will soon requeue this buffer to the surface. While in theory we could still keep
// track of buffers that are requeued to the surface, it is better to add support to the
// buffer-queue to notify us of released buffers and their fences (in the future).
mCodec->notifyOfRenderedFrames(true /* dropIncomplete */);
}
// byte buffers cannot take fences, so wait for any fence now
if (mCodec->mNativeWindow == NULL) {
(void)mCodec->waitForFence(fenceFd, "onOMXFillBufferDone");
fenceFd = -1;
}
info->setReadFence(fenceFd, "onOMXFillBufferDone");
// 获取当前端口模式
PortMode mode = getPortMode(kPortIndexOutput);
switch (mode) {
// 如果端口状态为 KEEP_BUFFERS,那么ACodec 持有Buffer,什么都不做
case KEEP_BUFFERS:
break;
case RESUBMIT_BUFFERS:
{
// 如果数据长度为0,并且flag不是eos,说明这是一帧无效输出
if (rangeLength == 0 && (!(flags & OMX_BUFFERFLAG_EOS)
|| mCodec->mPortEOS[kPortIndexOutput])) {
ALOGV("[%s] calling fillBuffer %u",
mCodec->mComponentName.c_str(), info->mBufferID);
// 重新送给OMX组件填充
err = mCodec->fillBuffer(info);
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
return true;
}
break;
}
sp<MediaCodecBuffer> buffer = info->mData;
// 给 BufferInfo 设置 format
if (mCodec->mOutputFormat != mCodec->mLastOutputFormat && rangeLength > 0) {
// pretend that output format has changed on the first frame (we used to do this)
if (mCodec->mBaseOutputFormat == mCodec->mOutputFormat) {
mCodec->onOutputFormatChanged(mCodec->mOutputFormat);
}
mCodec->sendFormatChange();
}
buffer->setFormat(mCodec->mOutputFormat);
if (mCodec->usingSecureBufferOnEncoderOutput()) {
native_handle_t *handle = NULL;
sp<SecureBuffer> secureBuffer = static_cast<SecureBuffer *>(buffer.get());
if (secureBuffer != NULL) {
#ifdef OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS
// handle is only valid on 32-bit/mediaserver process
handle = NULL;
#else
handle = (native_handle_t *)secureBuffer->getDestinationPointer();
#endif
}
buffer->meta()->setPointer("handle", handle);
buffer->meta()->setInt32("rangeOffset", rangeOffset);
buffer->meta()->setInt32("rangeLength", rangeLength);
} else if (buffer->base() == info->mCodecData->base()) {
// 如果不需要converter做转换,即codecData 和 mData指向同一块缓冲区,则直接设定buffer的读取范围
buffer->setRange(rangeOffset, rangeLength);
} else {
// 如果需要converter,则做数据转换,将codecData中的数据转换到mData中
info->mCodecData->setRange(rangeOffset, rangeLength);
// in this case we know that mConverter is not null
status_t err = mCodec->mConverter[kPortIndexOutput]->convert(
info->mCodecData, buffer);
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
return true;
}
}
#if 0
// 如果没有native window,如果mData中的数据可访问,可以检查是否是IDR帧
if (mCodec->mNativeWindow == NULL) {
if (IsIDR(info->mData->data(), info->mData->size())) {
ALOGI("IDR frame");
}
}
#endif
if (mCodec->mSkipCutBuffer != NULL) {
mCodec->mSkipCutBuffer->submit(buffer);
}
// 设置 BufferInfo 的时间戳
buffer->meta()->setInt64("timeUs", timeUs);
// 清除 BufferInfo 对 mData 的引用
info->mData.clear();
// 将 BufferID 交给 BufferChannel,让他上抛给 MediaCodec
mCodec->mBufferChannel->drainThisBuffer(info->mBufferID, flags);
// 设置 BufferInfo 的状态为 OWNED_BY_DOWNSTREAM
info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM;
// 如果是 EOS,那么需要标注端口收到 EOS
if (flags & OMX_BUFFERFLAG_EOS) {
ALOGV("[%s] saw output EOS", mCodec->mComponentName.c_str());
mCodec->mCallback->onEos(mCodec->mInputEOSResult);
mCodec->mPortEOS[kPortIndexOutput] = true;
}
break;
}
case FREE_BUFFERS:
// 如果端口状态为 Free_Buffers,那么就释放掉该output Buffer
err = mCodec->freeBuffer(kPortIndexOutput, index);
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
return true;
}
break;
default:
ALOGE("Invalid port mode: %d", mode);
return false;
}
return true;
}
这部分的代码有一点点长,但是思路很简单,主要是要理清getPortMode的意思:
- OMX callback 回来的是一个 id,我们首先要利用这个id,找到ACodec存储的BufferInfo,以及BufferInfo在数组中的索引;
- 检查BufferInfo的归属,是否是属于OMX组件,如果不是则状态异常;
- 记录当前输出帧数,同时记录当前 BufferInfo 在第几帧输出被使用,这在之前的章节中已经讲过如何使用了;
- 修改当前 BufferInfo 的归属为 ACodec;
- 获取当前端口模式,这个步骤非常关键,所谓端口模式,指的就是当前播放器状态,普通状态下port mode为KEEP_BUFFERS,表示ACodec会持有buffer,不做任何动作;运行状态下port mode为RESUBMIT_BUFFERS,ACodec会向目标提交Buffer,保持Buffer的流转;OutputPortSettingsChangedState状态下,会直接销毁 Output Buffer,这一部分内容在前一节中已经学习过了;KEEP_BUFFERS很简单,所以我们只看RESUBMIT_BUFFERS。
- 检查buffer数据长度,如果数据长度为0,并且flag不是eos,说明这是一帧无效输出,直接重新送给OMX组件填充;
- 获取 BufferInfo 中与 ACodecBufferChannel 交互的 MediaCodecBuffer(mData),为改mData设置输出格式,设置缓冲区的范围和偏移量,以及时间戳;
- 设置完成后移除 BufferInfo 对改 MediaCodecBuffer 的引用;
- 调用 ACodecBufferChannel 的 drainThisBuffer 方法,将Buffer传递给MediaCodec;
- 设置BufferInfo的归属为 上层(OWNED_BY_DOWNSTREAM);
- 如果flag是 EOS,那么需要标注端口收到 EOS。
到这FILL_BUFFER_DONE的消息就处理完成了。