Android 13 - Media框架(29)- MediaCodec(四)

上一节我们了解了如何通过 onInputBufferAvailable 和 getInputBuffer 获取到 input buffer index,接下来我们一起学习上层如何拿到buffer并且向下写数据的。

1、获取 input Buffer

获取 MediaCodec 中的 buffer 有两种方式,一种是调用 getInputBuffers 获取端口上所有的buffer,另一种是根据索引获取某一个 buffer。

1.1、getInputBuffers

getInputBuffers 和 getOutputBuffers 实现方式相同,都是发送一条 kWhatGetBuffers 消息,阻塞获取 buffer 数组:

status_t MediaCodec::getInputBuffers(Vector<sp<MediaCodecBuffer> > *buffers) const {sp<AMessage> msg = new AMessage(kWhatGetBuffers, this);msg->setInt32("portIndex", kPortIndexInput);msg->setPointer("buffers", buffers);sp<AMessage> response;return PostAndAwaitResponse(msg, &response);
}
        case kWhatGetBuffers:{sp<AReplyToken> replyID;CHECK(msg->senderAwaitsResponse(&replyID));// 如果不是 executing 状态 或者 是异步的状态直接返回errorif (!isExecuting() || (mFlags & kFlagIsAsync)) {PostReplyWithError(replyID, INVALID_OPERATION);break;} else if (mFlags & kFlagStickyError) {PostReplyWithError(replyID, getStickyError());break;}int32_t portIndex;CHECK(msg->findInt32("portIndex", &portIndex));Vector<sp<MediaCodecBuffer> > *dstBuffers;CHECK(msg->findPointer("buffers", (void **)&dstBuffers));dstBuffers->clear();// If we're using input surface (either non-persistent created by// createInputSurface(), or persistent set by setInputSurface()),// give the client an empty input buffers array.if (portIndex != kPortIndexInput || !mHaveInputSurface) {if (portIndex == kPortIndexInput) {mBufferChannel->getInputBufferArray(dstBuffers);} else {mBufferChannel->getOutputBufferArray(dstBuffers);}}(new AMessage)->postReply(replyID);break;}

处理 getInputBuffers 消息之前会先判断当前的状态是否是 executing?在之前的学习中我们了解到start 之后,buffer 才会全部分配完成,所以这个方法的调用需要在start之后。另外还会判断MediaCodec是否在异步模式下运行,如果是则会直接报错,意味着异步模式是不允许上层获取到所有buffer的。

getInputBuffer 获取到的 buffer 数组是直接从 ACodecBufferChannel 中获得的,并不会从 MediaCodec 存储的内容中获得。

1.1、getInputBuffer

getInputBuffer 和 getOutputBuffer 以及 getOutputFormat 的实现方式相同,只不过函数调用回传的内容不一样:

status_t MediaCodec::getInputBuffer(size_t index, sp<MediaCodecBuffer> *buffer) {sp<AMessage> format;return getBufferAndFormat(kPortIndexInput, index, buffer, &format);
}

内部实现 getBufferAndFormat 并没有使用 AMessage 机制,直接使用锁来进行同步:

status_t MediaCodec::getBufferAndFormat(size_t portIndex, size_t index,sp<MediaCodecBuffer> *buffer, sp<AMessage> *format) {// 检查传出参数是否为 nullif (buffer == NULL) {ALOGE("getBufferAndFormat - null MediaCodecBuffer");return INVALID_OPERATION;}// 检查传出参数是否为 nullif (format == NULL) {ALOGE("getBufferAndFormat - null AMessage");return INVALID_OPERATION;}// 清除 返回值 中的内容buffer->clear();format->clear();// 调用必须检查状态是否为 isExecutingif (!isExecuting()) {ALOGE("getBufferAndFormat - not executing");return INVALID_OPERATION;}// we do not want mPortBuffers to change during this section// we also don't want mOwnedByClient to change during thisMutex::Autolock al(mBufferLock);std::vector<BufferInfo> &buffers = mPortBuffers[portIndex];if (index >= buffers.size()) {ALOGE("getBufferAndFormat - trying to get buffer with ""bad index (index=%zu buffer_size=%zu)", index, buffers.size());return INVALID_OPERATION;}const BufferInfo &info = buffers[index];if (!info.mOwnedByClient) {ALOGE("getBufferAndFormat - invalid operation ""(the index %zu is not owned by client)", index);return INVALID_OPERATION;}*buffer = info.mData;*format = info.mData->format();return OK;
}

mBufferLock 这个锁是用来管理 MediaCodec 持有的 mPortBuffers 的,getInputBuffer 是直接从 mPortBuffers 中获取 buffer,所以需要加锁。至于为什么这里不用异步消息机制来写,还要再考究,个人感觉是差不多的,用异步消息机制可以省略锁的使用。

2、写入数据

上层拿到 input buffer(MediaCodecBuffer),向 buffer 中写入数据之后,需要通知 ACodec 数据已经写完了,ACodec 再紧接着通知 OMX Node 读取数据。我们这里看第一个步骤,如何通知 ACodec 数据已经写入完毕了呢?

看 MediaCodec 的头文件我们发现有两个相关的接口,一个是 queueInputBuffer,另一个是 queueSecureInputBuffer,这两个方法使用同一个消息,只不过传递的参数会不一样。

        case kWhatQueueInputBuffer:{sp<AReplyToken> replyID;CHECK(msg->senderAwaitsResponse(&replyID));if (!isExecuting()) {PostReplyWithError(replyID, INVALID_OPERATION);break;} else if (mFlags & kFlagStickyError) {PostReplyWithError(replyID, getStickyError());break;}status_t err = UNKNOWN_ERROR;if (!mLeftover.empty()) {mLeftover.push_back(msg);size_t index;msg->findSize("index", &index);err = handleLeftover(index);} else {err = onQueueInputBuffer(msg);}PostReplyWithError(replyID, err);break;}

处理 kWhatQueueInputBuffer 时同样会先判断当前状态是否是executing的状态,接下来的过程会有一些 CCodec 相关的流程,我们这里暂时跳过,直接看 onQueueInputBuffer。

onQueueInputBuffer 的代码非常长,主要是考虑了 ACodec 以及 CCodec,普通流以及加密流这四种情况的组合,同样的我们忽略 CCodec 相关的部分:

status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) {size_t index;size_t offset;size_t size;int64_t timeUs;uint32_t flags;CHECK(msg->findSize("index", &index));CHECK(msg->findInt64("timeUs", &timeUs));CHECK(msg->findInt32("flags", (int32_t *)&flags));std::shared_ptr<C2Buffer> c2Buffer;sp<hardware::HidlMemory> memory;sp<RefBase> obj;// ......else {CHECK(msg->findSize("offset", &offset));}const CryptoPlugin::SubSample *subSamples;size_t numSubSamples;const uint8_t *key = NULL;const uint8_t *iv = NULL;CryptoPlugin::Mode mode = CryptoPlugin::kMode_Unencrypted;// We allow the simpler queueInputBuffer API to be used even in// secure mode, by fabricating a single unencrypted subSample.CryptoPlugin::SubSample ss;CryptoPlugin::Pattern pattern;if (msg->findSize("size", &size)) {if (hasCryptoOrDescrambler()) {ss.mNumBytesOfClearData = size;ss.mNumBytesOfEncryptedData = 0;subSamples = &ss;numSubSamples = 1;pattern.mEncryptBlocks = 0;pattern.mSkipBlocks = 0;}} else if (!c2Buffer) {// 获取解密或者解扰需要的信息if (!hasCryptoOrDescrambler()) {ALOGE("[%s] queuing secure buffer without mCrypto or mDescrambler!",mComponentName.c_str());return -EINVAL;}CHECK(msg->findPointer("subSamples", (void **)&subSamples));CHECK(msg->findSize("numSubSamples", &numSubSamples));CHECK(msg->findPointer("key", (void **)&key));CHECK(msg->findPointer("iv", (void **)&iv));CHECK(msg->findInt32("encryptBlocks", (int32_t *)&pattern.mEncryptBlocks));CHECK(msg->findInt32("skipBlocks", (int32_t *)&pattern.mSkipBlocks));int32_t tmp;CHECK(msg->findInt32("mode", &tmp));mode = (CryptoPlugin::Mode)tmp;size = 0;for (size_t i = 0; i < numSubSamples; ++i) {size += subSamples[i].mNumBytesOfClearData;size += subSamples[i].mNumBytesOfEncryptedData;}}if (index >= mPortBuffers[kPortIndexInput].size()) {return -ERANGE;}BufferInfo *info = &mPortBuffers[kPortIndexInput][index];sp<MediaCodecBuffer> buffer = info->mData;// ......if (buffer == nullptr || !info->mOwnedByClient) {return -EACCES;}// 检查 buffer 相关的信息if (offset + size > buffer->capacity()) {return -EINVAL;}// 将信息整合至 MediaCodecBuffer 中buffer->setRange(offset, size);buffer->meta()->setInt64("timeUs", timeUs);if (flags & BUFFER_FLAG_EOS) {buffer->meta()->setInt32("eos", true);}if (flags & BUFFER_FLAG_CODECCONFIG) {buffer->meta()->setInt32("csd", true);}if (mTunneled) {TunnelPeekState previousState = mTunnelPeekState;switch(mTunnelPeekState){case TunnelPeekState::kEnabledNoBuffer:buffer->meta()->setInt32("tunnel-first-frame", 1);mTunnelPeekState = TunnelPeekState::kEnabledQueued;ALOGV("TunnelPeekState: %s -> %s",asString(previousState),asString(mTunnelPeekState));break;case TunnelPeekState::kDisabledNoBuffer:buffer->meta()->setInt32("tunnel-first-frame", 1);mTunnelPeekState = TunnelPeekState::kDisabledQueued;ALOGV("TunnelPeekState: %s -> %s",asString(previousState),asString(mTunnelPeekState));break;default:break;}}status_t err = OK;// 如果是加密的流,并且不是 CCodec,调用 queueSecureInputBufferif (hasCryptoOrDescrambler() && !c2Buffer && !memory) {AString *errorDetailMsg;CHECK(msg->findPointer("errorDetailMsg", (void **)&errorDetailMsg));// Notify mCrypto of video resolution changesif (mTunneled && mCrypto != NULL) {int32_t width, height;if (mInputFormat->findInt32("width", &width) &&mInputFormat->findInt32("height", &height) && width > 0 && height > 0) {if (width != mTunneledInputWidth || height != mTunneledInputHeight) {mTunneledInputWidth = width;mTunneledInputHeight = height;mCrypto->notifyResolution(width, height);}}}err = mBufferChannel->queueSecureInputBuffer(buffer,(mFlags & kFlagIsSecure),key,iv,mode,pattern,subSamples,numSubSamples,errorDetailMsg);if (err != OK) {mediametrics_setInt32(mMetricsHandle, kCodecQueueSecureInputBufferError, err);ALOGW("Log queueSecureInputBuffer error: %d", err);}} else {// 否则调用 queueInputBuffererr = mBufferChannel->queueInputBuffer(buffer);if (err != OK) {mediametrics_setInt32(mMetricsHandle, kCodecQueueInputBufferError, err);ALOGW("Log queueInputBuffer error: %d", err);}}if (err == OK) {// synchronization boundary for getBufferAndFormatMutex::Autolock al(mBufferLock);info->mOwnedByClient = false;info->mData.clear();statsBufferSent(timeUs, buffer);}return err;
}

删除掉 CCodec 的内容后,整体的内容变得简单很多,前面的部分是检查传入参数的正确性,中间的部分是将传入参数整合进 MediaCodecBuffer 中,后面的部分是通知 ACodec 数据已经写完。

如果码流结束,那么需要写入flag BUFFER_FLAG_EOS,这个 flag 写入有两种情况,一种是随着数据写入flag,另一种是单独写一个flag。

如果是要传 csd buffer,那么需要写入 flag BUFFER_FLAG_CODECCONFIG。csd buffer写入有两种,一种是在configure时传入csd 信息,input buffer到达后会自动帮我们写入 csd buffer;另一种是configure时不写,我们自己在第一个buffer到达时向内部写入csd信息,并且填入flag。

接下来讲一讲对 queueSecureInputBuffer 和 queueInputBuffer 的理解:

从queueSecureInputBuffer的名字来看,它是安全的流程中使用的,联想到之前我们会创建 secure component,很容易就会把这两个关联起来(创建secure组件后向下写入数据就要调用queueSecureInputBuffer),但是这个理解是不对的。queueSecureInputBuffer 这里的 secure 指的应该是码流本身是否是加密的,是否需要解密的意思。如果写入的是加密/加扰的码流,那么传递给decoder之前我们需要先做解密/解扰的动作,这个动作会在ACodecBufferChannel中完成,因此MediaCodec和ACodecBufferChannel都有queueSecureInputBuffer方法,用于处理解密/解扰的流程。用于存储加密/加扰数据的buffer其实是普通buffer,解密后的数据会存储到buffer handle中被保护起来

secure组件可以使用queueInputBuffer吗?当然可以了,这种情况下上层的buffer使用的就是底层创建的buffer handle,我们需要用单独的api才能完成数据拷贝/移动,整个流程数据都是被保护的。

再抛出一个问题,当使用queueSecureInput buffer时,一定要使用secure组件吗?答案不是的哦,如果使用的是secure组件,那么解密出来的清流就是受保护的。如果使用的是non-secure组件,那么清流是不受保护的,之前的加密也就没有意义了。

请添加图片描述

如图所示,前面两列整个流程中传递的都是清流,MediaCodecBuffer都是指向同一个缓冲区。最后一列上层写给MediaCodec的是加密流,进入到ACodecBufferChannel后会进行解密,把buffer写到mCodecBuffer中。

3、ACodec::BaseState::onInputBufferFilled

MediaCodec 调用 ACodecBufferChannel 的 queueSecureInputBuffer/queueInputBuffer 来通知 ACodec Buffer已经被填充,ACodec 此时处于Executing的状态,但是消息处理的实现在BaseState中:

void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {IOMX::buffer_id bufferID;// 获取 buffer idCHECK(msg->findInt32("buffer-id", (int32_t*)&bufferID));sp<MediaCodecBuffer> buffer;int32_t err = OK;bool eos = false;// 获取当前 state 中的 port modePortMode mode = getPortMode(kPortIndexInput);// 获取是否是discardint32_t discarded = 0;if (msg->findInt32("discarded", &discarded) && discarded) {// these are unfilled buffers returned by client// buffers are returned on MediaCodec.flushmode = KEEP_BUFFERS;}sp<RefBase> obj;// 查找随消息送来的 MediaCodecBufferCHECK(msg->findObject("buffer", &obj));buffer = static_cast<MediaCodecBuffer *>(obj.get());// 判断是否eosint32_t tmp;if (buffer != NULL && buffer->meta()->findInt32("eos", &tmp) && tmp) {eos = true;err = ERROR_END_OF_STREAM;}// 找到bufferinfo的状态BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID);BufferInfo::Status status = BufferInfo::getSafeStatus(info);if (status != BufferInfo::OWNED_BY_UPSTREAM) {ALOGE("Wrong ownership in IBF: %s(%d) buffer #%u", _asString(status), status, bufferID);mCodec->dumpBuffers(kPortIndexInput);mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);return;}int32_t cvo;if (mCodec->mNativeWindow != NULL && buffer != NULL &&buffer->meta()->findInt32("cvo", &cvo)) {ALOGV("cvo(%d) found in buffer #%u", cvo, bufferID);setNativeWindowRotation(mCodec->mNativeWindow.get(), cvo);}// 设置 bufferinfo的状态,把mediacodec buffer和bufferinfo重新绑定info->mStatus = BufferInfo::OWNED_BY_US;info->mData = buffer;// 根据port mode选择动作switch (mode) {// 持有 buffercase KEEP_BUFFERS:{if (eos) {if (!mCodec->mPortEOS[kPortIndexInput]) {mCodec->mPortEOS[kPortIndexInput] = true;mCodec->mInputEOSResult = err;}}break;}// 重新提交 buffer case RESUBMIT_BUFFERS:{// buffer 不为NULL且没有到达 eosif (buffer != NULL && !mCodec->mPortEOS[kPortIndexInput]) {// Do not send empty input buffer w/o EOS to the component.// 检查 buffer size 是否为0,只有eos的情况下buffer size可以为0if (buffer->size() == 0 && !eos) {// 重新填充buffpostFillThisBuffer(info);break;}// 检查ptsint64_t timeUs;CHECK(buffer->meta()->findInt64("timeUs", &timeUs));// 设置 flag,默认flag为OMX_BUFFERFLAG_ENDOFFRAMEOMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME;// 判读是否是csd buffer,如果是需要设定对应的flagint32_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;}flags |= OMX_BUFFERFLAG_CODECCONFIG;}// 如果是 eos 也需要设定 flagif (eos) {flags |= OMX_BUFFERFLAG_EOS;}size_t size = buffer->size();size_t offset = buffer->offset();// 检查是否需要做格式转换if (buffer->base() != info->mCodecData->base()) {ALOGV("[%s] Needs to copy input data for buffer %u. (%p != %p)",mCodec->mComponentName.c_str(),bufferID,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 {info->mCodecData->setRange(offset, size);}// 打印 logif (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_TIMINGALOGI("[%s] calling emptyBuffer %u w/ time %lld us",mCodec->mComponentName.c_str(), bufferID, (long long)timeUs);
#elseALOGV("[%s] calling emptyBuffer %u w/ time %lld us",mCodec->mComponentName.c_str(), bufferID, (long long)timeUs);
#endif}// debug log,以pts作为key,数据写入时间为value
#if TRACK_BUFFER_TIMINGACodec::BufferStats stats;stats.mEmptyBufferTimeUs = ALooper::GetNowUs();stats.mFillBufferDoneTimeUs = -1ll;mCodec->mBufferStats.add(timeUs, stats);
#endif// 如果是 dynamic native window output bufferif (mCodec->storingMetadataInDecodedBuffers()) {// try to submit an output buffer for each input buffer// 获取 port modePortMode outputMode = getPortMode(kPortIndexOutput);ALOGV("MetadataBuffersToSubmit=%u portMode=%s",mCodec->mMetadataBuffersToSubmit,(outputMode == FREE_BUFFERS ? "FREE" :outputMode == KEEP_BUFFERS ? "KEEP" : "RESUBMIT"));// 如果是重新提交,那么就提交一个 output buffer 给OMX组件if (outputMode == RESUBMIT_BUFFERS) {status_t err = mCodec->submitOutputMetadataBuffer();if (mCodec->mIsLowLatency&& err == OK&& mCodec->mMetadataBuffersToSubmit > 0) {maybePostExtraOutputMetadataBufferRequest();}}}// 更新 input buffer fenceinfo->checkReadFence("onInputBufferFilled");status_t err2 = OK;// 获取ACodec input port mode,根据mode填入不同的参数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_PLATFORMScase 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;
#endifdefault: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;}// 设置fence为-1info->mFenceFd = -1;if (err2 != OK) {mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err2));return;}// 重新设置buffer info的mStatusinfo->mStatus = BufferInfo::OWNED_BY_COMPONENT;// Hold the reference while component is using the buffer.// OMX 组件使用 buffer时需要持有引用info->mData = buffer;// 尝试把ACodec持有的buffer送给上层if (!eos && err == OK) {getMoreInputDataIfPossible();} else {ALOGV("[%s] Signalled EOS (%d) on the input port",mCodec->mComponentName.c_str(), err);// 如果到达eos,则将 mPortEOS标记为truemCodec->mPortEOS[kPortIndexInput] = true;mCodec->mInputEOSResult = err;}} // ......break;}case FREE_BUFFERS:break;default:ALOGE("invalid port mode: %d", mode);break;}
}

onInputBufferFilled 代码比较长,我们将其进行分解:

  • 首先来看 getPortMode,这个方法是 BaseState 提供的,和 ACodec中的getPortMode是完全不同的;BaseState::getPortMode 获取的是 ACodec 当前处理 Buffer 的动作,分为三种:
    • KEEP_BUFFERS:表示 ACodec 持有 buffer,不向上/向下传递;
    • RESUBMIT_BUFFERS:表示 ACodec将buffer向上/向下传递;
    • FREE_BUFFERS:ACodec 释放 buffer,具体的释放动作会因为 input /output 而不同。
    • 默认情况下的port mode如下;
    • 当 MediaCodec 调用 discard Buffer时,port mode会变为KEEP_BUFFERS;
    • 当 output format 发生变化时,需要销毁原来分配的 output buffer,所以port mode 变成FREE_BUFFERS;
// 默认为KEEP_BUFFERS
ACodec::BaseState::PortMode ACodec::BaseState::getPortMode(OMX_U32 /* portIndex */) {return KEEP_BUFFERS;
}
// 运行是port mode为RESUBMIT_BUFFERS
ACodec::BaseState::PortMode ACodec::ExecutingState::getPortMode(OMX_U32 /* portIndex */) {return RESUBMIT_BUFFERS;
}
// output端口发生变化时,output对应的mode为free,而input对应的mode为resubmit
ACodec::BaseState::PortMode ACodec::OutputPortSettingsChangedState::getPortMode(OMX_U32 portIndex) {if (portIndex == kPortIndexOutput) {return FREE_BUFFERS;}CHECK_EQ(portIndex, (OMX_U32)kPortIndexInput);return RESUBMIT_BUFFERS;
}
  • 我们在之前一节了解到,ACodec将input buffer送到上层时,会解除 mData 对 MediaCodecBuffer 的引用,所以当 input buffer 重回 ACodec 时会重新进行引用,找到其对应的 BufferInfo,检查其当前的状态是否为 OWNED_BY_UPSTREAM;
  • 当 port mode为 RESUBMIT_BUFFERS,也就是处于ExecutingState状态,ACodec 会把input buffer传递给 OMX 组件,传递前会把上层使用的 flag 转换为 OMX 可以识别的 flag,默认flag为 OMX_BUFFERFLAG_ENDOFFRAME,表示一帧结束(默认一笔数据为一帧);如果是 csd buffer,则会设置 OMX_BUFFERFLAG_CODECCONFIG;如果是 eos则会设置 OMX_BUFFERFLAG_EOS;
  • 我们要注意的是,如果flag不是eos,那么buffer size不允许为0;但是buffer size 不为0,也是可以设置 eos的,这就对应了我们之前所说的传递eos的两种情况。
  • 在正式把input buffer写入OMX之前,会有一个重要的动作 submitOutputMetadataBuffer,我们之前讲过start完成时,并没有真正分配出output buffer,第一次分配的地方就在这里。我们在之前的章节中讲过,系统不会一下子把所有的native window buffer全部分配出来,而是会先分配出mMetadataBuffersToSubmit 个,所以会在submitOutputMetadataBuffer中检查已经分配了多少个,如果全部分配了,那么在 onInputBufferFilled 中就不会再有分配的动作了。具体如何分配output buffer的我们下节再了解。
status_t ACodec::submitOutputMetadataBuffer() {// 检查是否为meta data modeCHECK(storingMetadataInDecodedBuffers());// 检查需要提交的buffer的数量if (mMetadataBuffersToSubmit == 0)return OK;// 分配bufferBufferInfo *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");// 传递给omxreturn fillBuffer(info);
}
  • info有个checkReadFence方法,对于input buffer而言,这个fence值是-1,病不起真正的作用;
  • 这里只讨论input port mode是kPortModePresetByteBuffer 和 kPortModePresetSecureBuffer 的情况,这两种情况下调用 emptyBuffer 传入的参数是一样的;input buffer给OMX组件使用时,buffer info的 mStatus会置为OWNED_BY_COMPONENT
  • 如果收到eos,那么会把ACodec的mPortEOS对应端口置为 true,接下来再收到input buffer将不会有任何动作;
  • 对于 input buffer 而言,ACodec 没有显式的 free 过程,free时port mode为KEEP_BUFFERS,拿到 buffer不做动作,等到引用计数为0时,自动销毁所有buffer;
  • ACodec 还提供一个debug log TRACK_BUFFER_TIMING,以key-value的形式记录下数据的pts以及数据的写入时间,当输出对应的pts时打印出消耗时间。

4、OMXNodeInstance::emptyBuffer

status_t OMXNodeInstance::emptyBuffer(buffer_id buffer, const OMXBuffer &omxBuffer,OMX_U32 flags, OMX_TICKS timestamp, int fenceFd) {Mutex::Autolock autoLock(mLock);if (mHandle == NULL) {return DEAD_OBJECT;}switch (omxBuffer.mBufferType) {case OMXBuffer::kBufferTypePreset:return emptyBuffer_l(buffer, omxBuffer.mRangeOffset, omxBuffer.mRangeLength,flags, timestamp, fenceFd);case OMXBuffer::kBufferTypeANWBuffer:return emptyGraphicBuffer_l(buffer, omxBuffer.mGraphicBuffer, flags, timestamp, fenceFd);case OMXBuffer::kBufferTypeNativeHandle:return emptyNativeHandleBuffer_l(buffer, omxBuffer.mNativeHandle, flags, timestamp, fenceFd);default:break;}return BAD_VALUE;
}

进入 OMXNodeInstance 中,会先检查 OMXBuffer Type,对于kPortModePresetByteBuffer 和 kPortModePresetSecureBuffer而言,它们的buffer type都是kBufferTypePreset,所以会调用到 emptyBuffer_l。

status_t OMXNodeInstance::emptyBuffer_l(IOMX::buffer_id buffer,OMX_U32 rangeOffset, OMX_U32 rangeLength,OMX_U32 flags, OMX_TICKS timestamp, int fenceFd) {// no emptybuffer if using input surfaceif (getBufferSource() != NULL) {android_errorWriteLog(0x534e4554, "29422020");return INVALID_OPERATION;}// 根据 id 找到 buffer headerOMX_BUFFERHEADERTYPE *header = findBufferHeader(buffer, kPortIndexInput);if (header == NULL) {ALOGE("b/25884056");return BAD_VALUE;}// 获取 buffer header 中的 buffer metaBufferMeta *buffer_meta =static_cast<BufferMeta *>(header->pAppPrivate);// set up proper filled length if component is configured for gralloc metadata mode// ignore rangeOffset in this case (as client may be assuming ANW meta buffers).if (mMetadataType[kPortIndexInput] == kMetadataBufferTypeGrallocSource) {header->nFilledLen = rangeLength ? sizeof(VideoGrallocMetadata) : 0;header->nOffset = 0;} else {// rangeLength and rangeOffset must be a subset of the allocated data in the buffer.// corner case: we permit rangeOffset == end-of-buffer with rangeLength == 0.if (rangeOffset > header->nAllocLen|| rangeLength > header->nAllocLen - rangeOffset) {CLOG_ERROR(emptyBuffer, OMX_ErrorBadParameter, FULL_BUFFER(NULL, header, fenceFd));if (fenceFd >= 0) {::close(fenceFd);}return BAD_VALUE;}header->nFilledLen = rangeLength;header->nOffset = rangeOffset;// 将 buffer meta中的数据拷贝到 buffer header中buffer_meta->CopyToOMX(header);}return emptyBuffer_l(header, flags, timestamp, (intptr_t)buffer, fenceFd);
}

在之前的学习中,我们了解到preset byte input buffer 和 preset secure input buffer其实都存在 BufferHeader 的 pAppPrivate 中,上层写入的数据都会写到BufferMeta中。

如果是quirks mode,那么preset byte input buffer中的数据需要拷贝到BufferHeader的pBuffer中,其余情况 BufferMeta 和 BufferHeader的pBuffer指向同一块buffer,不需要做拷贝。

status_t OMXNodeInstance::emptyBuffer_l(OMX_BUFFERHEADERTYPE *header, OMX_U32 flags, OMX_TICKS timestamp,intptr_t debugAddr, int fenceFd) {header->nFlags = flags;header->nTimeStamp = timestamp;// 对于input buffer而言,fenceFd为-1,这里不起作用status_t res = storeFenceInMeta_l(header, fenceFd, kPortIndexInput);if (res != OK) {CLOG_ERROR(emptyBuffer::storeFenceInMeta, res, WITH_STATS(FULL_BUFFER(debugAddr, header, fenceFd)));return res;}{Mutex::Autolock _l(mDebugLock);// 记录所有送下去的 input buffermInputBuffersWithCodec.add(header);}// 通知 OMX 组件数据写入完成OMX_ERRORTYPE err = OMX_EmptyThisBuffer(mHandle, header);CLOG_IF_ERROR(emptyBuffer, err, FULL_BUFFER(debugAddr, header, fenceFd));return StatusFromOMXError(err);
}

最后调用 OMX_EmptyThisBuffer,调用 OMX 组件的 empty buffer 方法将ibput数据信息传下去,并且把送下去的buffer记录到mInputBuffersWithCodec中,当读取完成时再从mInputBuffersWithCodec中取出。

到这里,input buffer的流程就阅读完成了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/229375.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ArcGIS微课1000例】0083:地震灾害图件制作之土壤类型分布图

本文基于1:400万矢量土壤图,制作甘肃积石山6.2级地震100km范围内土壤类型分布图。 文章目录 一、土壤分布图预览二、数据集来源及简介三、土壤分布图制作一、土壤分布图预览 二、数据集来源及简介 1. 数据来源 数据集为1:400万中国土壤图,1:400万中国土壤图(2000)由中国科…

BloombergGPT—金融领域大模型

文章目录 背景BloombergGPT数据集金融领域数据集通用数据集分词 模型模型结构模型相关参数训练配置训练过程 模型评估评估任务分布模型对比金融领域评估通用领域评估 背景 GPT-3的发布证明了训练非常大的自回归语言模型&#xff08;LLM&#xff09;的强大优势。GPT-3有1750亿个…

工程(十七)——自己数据集跑R2live

博主创建了一个科研互助群Q&#xff1a;772356582&#xff0c;欢迎大家加入讨论。 r2live是比较早的算法&#xff0c;编译过程有很多问题&#xff0c;通过以下两个博客可以解决 编译R2LIVE问题&解决方法-CSDN博客 r2live process has died 问题解决了_required process …

3Dmax模型打开后灯光全没了---模大狮模型网

在3ds Max中&#xff0c;有时您可能会打开一个3dmax模型文件后发现灯光似乎丢失了。这可能是由于以下原因之一导致的&#xff1a; 灯光层被隐藏或删除了。在3ds Max中&#xff0c;您可以将不同的对象分配给不同的层&#xff0c;以方便管理和编辑。如果灯光对象被分配到另一个层…

【数学建模美赛M奖速成系列】Matplotlib绘图技巧(二)

Matplotlib绘图技巧&#xff08;二&#xff09; 写在前面2. 函数间区域填充函数fill_between()和fill()参数&#xff1a; 3. 散点图 scatter4. 直方图 hist5. 条形图 bar5.1 一个数据样本的条形图参数&#xff1a; 5.2 多个数据样本进行对比的直方图5.3 水平条形图参数 5.4 绘制…

堆排序(C语言版)

一.堆排序 堆排序即利用堆的思想来进行排序&#xff0c;总共分为两个步骤&#xff1a; 1. 建堆 升序&#xff1a;建大堆 降序&#xff1a;建小堆 2. 利用堆删除思想来进行排序 1.1.利用上下调整法实现堆排序 第一步&#xff1a;建堆 好了&#xff0c;每次建堆都要问自己…

ArkTS基本概念装饰器

目录 ArkTS基本概念 装饰器汇总 ArkTS基本概念 ArkTS是HarmonyOS的主力应用开发语言。 它在TypeScript&#xff08;简称TS&#xff09;的基础上&#xff0c;匹配ArkUI框架&#xff0c;扩展了声明式UI、状态管理等相应的能力&#xff0c;让开发者以更简洁、更自然的方式开发跨…

cocos creator + vscode debug

安装插件 安装插件&#xff1a;JavaScript Debugger 配置 7456 为本地cocos creator的启动端口 启动debug调试 选择对应的启动方式

低成本TB级数据库技术选型之思考两三点

一、背景 前段时间在搞毕业论文的选题&#xff0c;最头疼的就是大量的文献检索和阅读&#xff0c;从研究的角度上我们可以将文献分为四类&#xff1a; 理论文献&#xff1a;为研究提供理论的框架和基础的文献。这些文献可能并不会和所做的研究直接相关&#xff0c;甚至由于理…

叫板GPT-4的Gemini,我做了一个聊天网页,可图片输入,附教程

先看效果&#xff1a; 简介 Gemini 是谷歌研发的最新一代大语言模型&#xff0c;目前有三个版本&#xff0c;被称为中杯、大杯、超大杯&#xff0c;Gemini Ultra 号称可与GPT-4一较高低&#xff1a; Gemini Nano(预览访问) 为设备端体验而构建的最高效模型,支持离线使用场景。…

基于PI控制的PMSM永磁同步电机控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 PMSM数学模型 4.2 矢量控制策略 4.3 PI控制器设计 4.4 控制系统实现 5.完整工程文件 1.课题概述 基于PI控制的PMSM永磁同步电机控制系统simulink建模与仿真。其中&#xff0c;基于PI&#xff08;…

查看ios app运行日志

摘要 本文介绍了一款名为克魔助手的iOS应用日志查看工具&#xff0c;该工具可以方便地查看iPhone设备上应用和系统运行时的实时日志和奔溃日志。同时还提供了奔溃日志分析查看模块&#xff0c;可以对苹果奔溃日志进行符号化、格式化和分析&#xff0c;极大地简化了开发者的调试…

极值和平均值-第11届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第22讲。 极值和平均值&…

java设计模式实战【策略模式+观察者模式+命令模式+组合模式,混合模式在支付系统中的应用】

引言 在代码开发的世界里&#xff0c;理论知识的重要性毋庸置疑&#xff0c;但实战经验往往才是知识的真正试金石。正所谓&#xff0c;“读万卷书不如行万里路”&#xff0c;理论的学习需要通过实践来验证和深化。设计模式作为软件开发中的重要理论&#xff0c;其真正的价值在…

【心得】PHP反序列化高级利用(phar|session)个人笔记

目录 ①phar反序列化 ②session反序列化 ①phar反序列化 phar 认为是java的jar包 calc.exe phar能干什么 多个php合并为独立压缩包&#xff0c;不解压就能执行里面的php文件&#xff0c;支持web服务器和命令行 phar协议 phar://xxx.phar $phar->setmetadata($h); m…

计算机视觉与自然语言处理(Open AI)

1.语音识别技术 语音识别是将语音转换为文本的技术&#xff0c; 是自然语言处理的一个分支。通过特征的提取、模式的匹配将语音信号变为文本或命令&#xff0c;以实现机器识别和理解语音。 按照应用场景的不同&#xff0c;可以大致分为三类&#xff1b; • 电信级系统应用&…

动画墙纸:将视频、网页、游戏、模拟器变成windows墙纸——Lively Wallpaper

文章目录 前言下载github地址&#xff1a;网盘 关于VideoWebpagesYoutube和流媒体ShadersGIFs游戏和应用程序& more:Performance:多监视器支持&#xff1a;完结 前言 Lively Wallpaper是一款开源的视频壁纸桌面软件&#xff0c;类似 Wallpaper Engine&#xff0c;兼容 Wal…

echarts手动触发气泡的显示和隐藏

点击echarts图表后将点击的那个进行突出显示 <template><div id"demo"> </div><el-button type"primary" click"set">设置</el-button><el-button type"primary" click"cancel">取消&…

ubuntu20部署Bringing-Old-Photos-Back-to-Life

环境准备&#xff1a; ubuntu20.04 Python 3.8.10 首先将微软的「Bringing-Old-Photos-Back-to-Life」库 clone 到本地&#xff1a; git clone https://github.com/microsoft/Bringing-Old-Photos-Back-to-Life.git cd Face_Enhancement/models/networks/ git clone https:/…

API 开放平台项目(已整理,已废弃)

项目大纲 前端 React 18Ant Design Pro 5.x 脚手架Ant Design & Procomponents 组件库Umi 4 前端框架OpenAPI 前端代码生成 后端 Java Spring BootMySQL 数据库MyBatis-Plus 及 MyBatis X 自动生成API 签名认证&#xff08;Http 调用&#xff09;Spring Boot Starter&#…