本篇文章我们一起来分析Executing状态下的数据处理流程。
首先对上一篇文章做勘误:
实际在调用allocateOutputBuffersFromNativeWindow分配buffer时我们会看到,一开始确实是分配了nBufferCountActual个buffer,但是后面又调用cancelBufferToNativeWindow销毁掉了备用的。可能有人要问,前面调用了useBuffer把备用的buffer也共享给了组件,会不会有问题呢?这些被销毁的buffer是无法被使用的,实际能用的buffer数量就是nBufferCountMin个,至于为什么后续的文章会做了解。
画线部分修改为:实际能用的buffer数量是nBufferCountActual个,被cancel的buffer到后面会重新dequeue出来。
1、ExecutingState::resume
在 Android Media Framework(十五)ACodec - Ⅲ 一文最后,我们讲到组件正式切换到OMX_Executing状态后,ACodec会在IdleToExecutingState状态下先调用ExecutingState::resume方法,然后切换至ExecutingState,正式启动数据处理流程。
void ACodec::ExecutingState::resume() {
if (mActive) {
return;
}
submitOutputBuffers();
if (mCodec->mBuffers[kPortIndexInput].size() == 0u) {
}
for (size_t i = 0; i < mCodec->mBuffers[kPortIndexInput].size(); i++) {
BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(i);
if (info->mStatus == BufferInfo::OWNED_BY_US) {
postFillThisBuffer(info);
}
}
mActive = true;
}
组件刚启动时,除了Tunnel Mode下的output buffer和kPortModeDynamicANWBuffer下的output buffer不属于ACodec外,其他所有的buffer都归属于ACodec。resume会将ACodec持有的input buffer交给上层使用,将output buffer交给组件使用。
resume除了会在启动时调用,还会在flush之后的重启过程中用到。
先来看提交output buffer的过程:
void ACodec::ExecutingState::submitOutputBuffers() {
submitRegularOutputBuffers();
if (mCodec->storingMetadataInDecodedBuffers()) {
submitOutputMetaBuffers();
}
}
submitRegularOutputBuffers
从名字上来看submitRegularOutputBuffers这个方法,是用来提交常规的Output Buffer的,Regular在这里指的是所有归属于ACodec的output buffer。
void ACodec::ExecutingState::submitRegularOutputBuffers() {
bool failed = false;
for (size_t i = 0; i < mCodec->mBuffers[kPortIndexOutput].size(); ++i) {
BufferInfo *info = &mCodec->mBuffers[kPortIndexOutput].editItemAt(i);
if (mCodec->mNativeWindow != NULL) {
// 1.
if (info->mStatus != BufferInfo::OWNED_BY_US
&& info->mStatus != BufferInfo::OWNED_BY_NATIVE_WINDOW) {
failed = true;
break;
}
// 2.
if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW) {
continue;
}
} else {
// 3.
if (info->mStatus != BufferInfo::OWNED_BY_US) {
ALOGE("buffers should be owned by us");
failed = true;
break;
}
}
// 4.
info->checkWriteFence("submitRegularOutputBuffers");
// 5.
status_t err = mCodec->fillBuffer(info);
if (err != OK) {
failed = true;
break;
}
}
if (failed) {
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
}
}
简单分析一下submitRegularOutputBuffers,看看我们说的对不对:
- 有native window时,检查output buffer是属于ACodec还是属于native window,如果都不是就返回error;
- 检查output buffer是否属于native window,如果是则不会提交给组件使用;
- 没有native window时,如果output buffer不属于ACodec,就会返回error;
- 调用ACodec::BufferInfo的checkWriteFence方法,这个方法没有什么实质性作用,只做log提醒。提交buffer给组件使用,是想要组件做数据写入,因此graphic output buffer持有的是write fence。
- 调用ACodec::fillBuffer将buffer提交给组件。
阅读上述流程我们可以知道,有native window的情况下,启动时:
- PortMode为kPortModeDynamicANWBuffer时,output buffer归属于native window,所以没有buffer要提交;Tunnel Mode没有output buffer,因此也无需提交。
- 其他情况下会把所有属于ACodec的buffer都提交。
status_t ACodec::fillBuffer(BufferInfo *info) {
status_t err;
if (!storingMetadataInDecodedBuffers() || !info->mNewGraphicBuffer) {
err = mOMXNode->fillBuffer(
info->mBufferID, OMXBuffer::sPreset, info->mFenceFd);
} else {
err = mOMXNode->fillBuffer(
info->mBufferID, info->mGraphicBuffer, info->mFenceFd);
}
info->mNewGraphicBuffer = false;
info->mFenceFd = -1;
if (err == OK) {
info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
}
return err;
}
fillBuffer有一段注释,翻译出来是这样:“在dynamic ANW buffer mode下,如果graphic buffer没有变化,那么用sPreset代替相同的graphic buffer送给组件,组件端就不需要更新meta data。”
调用OMXNode的fillBuffer时第二个参数可能有两种:
- OMXBuffer::sPreset:当PortMode不为kPortModeDynamicANWBuffer时,或者PortMode为kPortModeDynamicANWBuffer但是buffer没有被更新时,使用此case;
- info->mGraphicBuffer:当PortMode为kPortModeDynamicANWBuffer且buffer被更新时,使用此case。
status_t OMXNodeInstance::fillBuffer(
IOMX::buffer_id buffer, const OMXBuffer &omxBuffer, int fenceFd) {
if (omxBuffer.mBufferType == OMXBuffer::kBufferTypeANWBuffer) {
// 更新meta data
status_t err = updateGraphicBufferInMeta_l(
kPortIndexOutput, omxBuffer.mGraphicBuffer, buffer, header);
} else if (omxBuffer.mBufferType != OMXBuffer::kBufferTypePreset) {
return BAD_VALUE;
}
// 更新fence
status_t res = storeFenceInMeta_l(header, fenceFd, kPortIndexOutput);
if (res != OK) {
return res;
}
OMX_ERRORTYPE err = OMX_FillThisBuffer(mHandle, header);
return StatusFromOMXError(err);
}
调用fillBuffer时如果传入的是GraphicBuffer,将会调用updateGraphicBufferInMeta_l更新BufferHeader的pBuffer字段存储的meta data。如果kBufferTypePreset,说明meta data无需更新。
submitOutputMetaBuffers
调用submitOutputMetaBuffers有一个条件,需要PortMode为kPortModeDynamicANWBuffer。
void ACodec::ExecutingState::submitOutputMetaBuffers() {
for (size_t i = 0; i < mCodec->mBuffers[kPortIndexInput].size(); ++i) {
BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(i);
if (info->mStatus == BufferInfo::OWNED_BY_COMPONENT) {
if (mCodec->submitOutputMetadataBuffer() != OK)
break;
}
}
}
调用submitOutputMetadataBuffer又要有一个条件,需要有input buffer属于组件,是不是有点奇怪?起播时,所有的input buffer都不属于组件,因此并不会调用submitOutputMetadataBuffer。这个方法有什么用呢?它其实是用在OutputPortSettingsChangedState状态下的,我们后续再对此展开。
postFillThisBuffer
当input buffer归属于ACodec时,可以调用BaseState::postFillThisBuffer将它送给上层使用:
void ACodec::BaseState::postFillThisBuffer(BufferInfo *info) {
// 1.
if (mCodec->mPortEOS[kPortIndexInput]) {
return;
}
CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US);
// 2.
info->mData->setFormat(mCodec->mInputFormat);
// 3.
mCodec->mBufferChannel->fillThisBuffer(info->mBufferID);
// 4.
info->mData.clear();
// 5.
info->mStatus = BufferInfo::OWNED_BY_UPSTREAM;
}
- 如果ACodec已经收到input eos,那么input buffer将不会再回传给上层;
- 将input format设定到mData中;
- 调用ACodecBufferChannel的fillThisBuffer方法,将可用buffer id送给上层;
- 清除ACodec对mData的引用;
- 将BufferInfo的归属设定为OWNED_BY_UPSTREAM。
2、BaseState::onInputBufferFilled
上层向input buffer写完数据后会调用ACodecBufferChannel的queueInputBuffer或者queueSecureInputBuffer向ACodec发送一条kWhatInputBufferFilled消息。由于ExecutingState没有实现对该消息的处理逻辑,因此最终调用到BaseState::onInputBufferFilled。
onInputBufferFilled方法很长,我将代码全部贴出并且补上注释,可以先从上到下阅读:
void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
IOMX::buffer_id bufferID;
CHECK(msg->findInt32("buffer-id", (int32_t*)&bufferID));
sp<MediaCodecBuffer> buffer;
int32_t err = OK;
bool eos = false;
// 获取当前对buffer的处理方式
PortMode mode = getPortMode(kPortIndexInput);
int32_t discarded = 0;
// 检查是否调用的是discardBuffer
if (msg->findInt32("discarded", &discarded) && discarded) {
// 如果是将mode切换为KEEP_BUFFERS
mode = KEEP_BUFFERS;
}
sp<RefBase> obj;
CHECK(msg->findObject("buffer", &obj));
buffer = static_cast<MediaCodecBuffer *>(obj.get());
int32_t tmp;
if (buffer != NULL && buffer->meta()->findInt32("eos", &tmp) && tmp) {
eos = true;
err = ERROR_END_OF_STREAM;
}
BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID);
BufferInfo::Status status = BufferInfo::getSafeStatus(info);
// 检查bufferInfo存储的状态
if (status != BufferInfo::OWNED_BY_UPSTREAM) {
mCodec->dumpBuffers(kPortIndexInput);
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
return;
}
// 修改bufferInfo的状态,并且重新添加mData对buffer的引用
info->mStatus = BufferInfo::OWNED_BY_US;
info->mData = buffer;
// 根据mode对buffer做不同的处理
switch (mode) {
case KEEP_BUFFERS:
{
// 如果是keep buffers,那么ACodec会持有buffer,什么都不做
if (eos) {
if (!mCodec->mPortEOS[kPortIndexInput]) {
mCodec->mPortEOS[kPortIndexInput] = true;
mCodec->mInputEOSResult = err;
}
}
break;
}
// 如果是resubmit buffer就将buffer提交给组件使用
case RESUBMIT_BUFFERS:
{
// 如果buffer不为NULL且没有收到input eos
if (buffer != NULL && !mCodec->mPortEOS[kPortIndexInput]) {
// 如果buffer数据为空,并且不是eos,那么重新将buffer送给上层填充
if (buffer->size() == 0 && !eos) {
postFillThisBuffer(info);
break;
}
int64_t timeUs;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
// 设定eof,表示当前是完整的一帧
OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME;
int32_t isCSD = 0;
if (buffer->meta()->findInt32("csd", &isCSD) && isCSD != 0) {
if (mCodec->mIsLegacyVP9Decoder) {
ALOGV("[%s] is legacy VP9 decoder. Ignore %u codec specific data",
mCodec->mComponentName.c_str(), bufferID);
postFillThisBuffer(info);
break;
}
// 如果包含csd buffer,拉起对应的标志位
flags |= OMX_BUFFERFLAG_CODECCONFIG;
}
// 如果是eos,那么拉起eos flag
if (eos) {
flags |= OMX_BUFFERFLAG_EOS;
}
size_t size = buffer->size();
size_t offset = buffer->offset();
// 如果需要做数据转换,那么就把mData中的数据转换到mCodecData中
if (buffer->base() != info->mCodecData->base()) {
sp<DataConverter> converter = mCodec->mConverter[kPortIndexInput];
if (converter == NULL || isCSD) {
converter = getCopyConverter();
}
status_t err = converter->convert(buffer, info->mCodecData);
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, err);
return;
}
size = info->mCodecData->size();
} else {
// 设定buffer信息
info->mCodecData->setRange(offset, size);
}
if (flags & OMX_BUFFERFLAG_CODECCONFIG) {
ALOGV("[%s] calling emptyBuffer %u w/ codec specific data",
mCodec->mComponentName.c_str(), bufferID);
} else if (flags & OMX_BUFFERFLAG_EOS) {
ALOGV("[%s] calling emptyBuffer %u w/ EOS",
mCodec->mComponentName.c_str(), bufferID);
} else {
#if TRACK_BUFFER_TIMING
ALOGI("[%s] calling emptyBuffer %u w/ time %lld us",
mCodec->mComponentName.c_str(), bufferID, (long long)timeUs);
#else
ALOGV("[%s] calling emptyBuffer %u w/ time %lld us",
mCodec->mComponentName.c_str(), bufferID, (long long)timeUs);
#endif
}
#if TRACK_BUFFER_TIMING
// 记录buffer写入时间
ACodec::BufferStats stats;
stats.mEmptyBufferTimeUs = ALooper::GetNowUs();
stats.mFillBufferDoneTimeUs = -1ll;
mCodec->mBufferStats.add(timeUs, stats);
#endif
if (mCodec->storingMetadataInDecodedBuffers()) {
// try to submit an output buffer for each input buffer
// 获取输出端口的buffer处理方式,和输入端口的处理方式相同
PortMode outputMode = getPortMode(kPortIndexOutput);
ALOGV("MetadataBuffersToSubmit=%u portMode=%s",
mCodec->mMetadataBuffersToSubmit,
(outputMode == FREE_BUFFERS ? "FREE" :
outputMode == KEEP_BUFFERS ? "KEEP" : "RESUBMIT"));
if (outputMode == RESUBMIT_BUFFERS) {
// 调用submitOutputMetadataBuffer
status_t err = mCodec->submitOutputMetadataBuffer();
if (mCodec->mIsLowLatency
&& err == OK
&& mCodec->mMetadataBuffersToSubmit > 0) {
maybePostExtraOutputMetadataBufferRequest();
}
}
}
info->checkReadFence("onInputBufferFilled");
// 调用OMXNode的emptyBuffer,将数据交给组件
status_t err2 = OK;
switch (mCodec->mPortMode[kPortIndexInput]) {
case IOMX::kPortModePresetByteBuffer:
case IOMX::kPortModePresetANWBuffer:
case IOMX::kPortModePresetSecureBuffer:
{
err2 = mCodec->mOMXNode->emptyBuffer(
bufferID, info->mCodecData, flags, timeUs, info->mFenceFd);
}
break;
#ifndef OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS
case IOMX::kPortModeDynamicNativeHandle:
if (info->mCodecData->size() >= sizeof(VideoNativeHandleMetadata)) {
VideoNativeHandleMetadata *vnhmd =
(VideoNativeHandleMetadata*)info->mCodecData->base();
sp<NativeHandle> handle = NativeHandle::create(
vnhmd->pHandle, false /* ownsHandle */);
err2 = mCodec->mOMXNode->emptyBuffer(
bufferID, handle, flags, timeUs, info->mFenceFd);
}
break;
case IOMX::kPortModeDynamicANWBuffer:
if (info->mCodecData->size() >= sizeof(VideoNativeMetadata)) {
VideoNativeMetadata *vnmd = (VideoNativeMetadata*)info->mCodecData->base();
sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(vnmd->pBuffer);
err2 = mCodec->mOMXNode->emptyBuffer(
bufferID, graphicBuffer, flags, timeUs, info->mFenceFd);
}
break;
#endif
default:
ALOGW("Can't marshall %s data in %zu sized buffers in %zu-bit mode",
asString(mCodec->mPortMode[kPortIndexInput]),
info->mCodecData->size(),
sizeof(buffer_handle_t) * 8);
err2 = ERROR_UNSUPPORTED;
break;
}
info->mFenceFd = -1;
if (err2 != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err2));
return;
}
// 切换BufferInfo的状态
info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
info->mData = buffer;
// 如果没有到input eos,那么尝试将ACodec持有的一个input buffer送给上层填充
if (!eos && err == OK) {
getMoreInputDataIfPossible();
} else {
ALOGV("[%s] Signalled EOS (%d) on the input port",
mCodec->mComponentName.c_str(), err);
// 如果收到eos,那么就将mPortEOS置为true
mCodec->mPortEOS[kPortIndexInput] = true;
mCodec->mInputEOSResult = err;
}
} else if (!mCodec->mPortEOS[kPortIndexInput]) {
// 还有一种情况,buffer等于NULL,且还未收到Input EOS
if (err != OK && err != ERROR_END_OF_STREAM) {
ALOGV("[%s] Signalling EOS on the input port due to error %d",
mCodec->mComponentName.c_str(), err);
} else {
ALOGV("[%s] Signalling EOS on the input port",
mCodec->mComponentName.c_str());
}
ALOGV("[%s] calling emptyBuffer %u signalling EOS",
mCodec->mComponentName.c_str(), bufferID);
info->checkReadFence("onInputBufferFilled");
// 单独给组件送一个input eos
status_t err2 = mCodec->mOMXNode->emptyBuffer(
bufferID, OMXBuffer::sPreset, OMX_BUFFERFLAG_EOS, 0, info->mFenceFd);
info->mFenceFd = -1;
if (err2 != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err2));
return;
}
// 切换BufferInfo状态,并且将mPortEOS置为true。
info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
mCodec->mPortEOS[kPortIndexInput] = true;
mCodec->mInputEOSResult = err;
}
break;
}
// BaseState不会处理FREE_BUFFERS。
case FREE_BUFFERS:
break;
default:
ALOGE("invalid port mode: %d", mode);
break;
}
}
整体来说,BaseState::onInputBufferFilled的逻辑比较简单,提取并重新组织msg携带的信息,然后调用emptyBuffer将数据送入组件。里面有几点需要再提一下:
- 如果PortMode为kPortModeDynamicANWBuffer,并且是在起播阶段,我们知道这时候Graphic Output Buffer是没有分配的。在onInputBufferFilled中会尝试调用submitOutputMetadataBuffer为每个input buffer提交一个对应的output buffer;
- 数据写入成功后ACodec还会尝试将ACodec持有的一个input buffer送给上层填充,理想状态下每写入一个input buffer,就要向上层回传一个input buffer;
- 在ExecutingState下,input和output PortMode都是RESUBMIT_BUFFERS;
- 有两种方式通知组件Input EOS,一种是随着有效数据添加eos flag,还有一种是单独调用emptyBuffer通知组件,此时Buffer为NULL;
- PortMode为kPortModeDynamicNativeHandle和kPortModeDynamicANWBuffer时,Metadata会写在mData中,传输数据时需要将buffer强转为对应类型的Metadata,然后从中获取handle/graphic buffer,再调用emptyBuffer通知组件更新Metadata。
3、submitOutputMetadataBuffer
status_t ACodec::submitOutputMetadataBuffer() {
CHECK(storingMetadataInDecodedBuffers());
if (mMetadataBuffersToSubmit == 0)
return OK;
BufferInfo *info = dequeueBufferFromNativeWindow();
if (info == NULL) {
return ERROR_IO;
}
ALOGV("[%s] submitting output meta buffer ID %u for graphic buffer %p",
mComponentName.c_str(), info->mBufferID, info->mGraphicBuffer->handle);
--mMetadataBuffersToSubmit;
info->checkWriteFence("submitOutputMetadataBuffer");
return fillBuffer(info);
}
4、dequeueBufferFromNativeWindow
5、BaseState::onOMXEmptyBufferDone
6、BaseState::onOMXFillBufferDone
7、BaseState::onOutputBufferDrained
关注公众号《青山渺渺》阅读全文