上一节我们了解了如何通过 onInputBufferAvailable 和 getInputBuffer 获取到 input buffer index,接下来我们一起学习上层如何拿到buffer并且向下写数据的。
1、获取 input Buffer
获取 MediaCodec 中的 buffer 有两种方式,一种是调用 getInputBuffers
获取端口上所有的buffer,另一种是根据索引获取某一个 buffer。
1.1、getInputBuffers
getInputBuffers 和 getOutputBuffers 实现方式相同,都是发送一条 kWhatGetBuffers 消息,阻塞获取 buffer 数组:
cpp
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);
}
cpp
case kWhatGetBuffers:
{
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
// 如果不是 executing 状态 或者 是异步的状态直接返回error
if (!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 的实现方式相同,只不过函数调用回传的内容不一样:
cpp
status_t MediaCodec::getInputBuffer(size_t index, sp<MediaCodecBuffer> *buffer) {
sp<AMessage> format;
return getBufferAndFormat(kPortIndexInput, index, buffer, &format);
}
内部实现 getBufferAndFormat 并没有使用 AMessage 机制,直接使用锁来进行同步:
cpp
status_t MediaCodec::getBufferAndFormat(
size_t portIndex, size_t index,
sp<MediaCodecBuffer> *buffer, sp<AMessage> *format) {
// 检查传出参数是否为 null
if (buffer == NULL) {
ALOGE("getBufferAndFormat - null MediaCodecBuffer");
return INVALID_OPERATION;
}
// 检查传出参数是否为 null
if (format == NULL) {
ALOGE("getBufferAndFormat - null AMessage");
return INVALID_OPERATION;
}
// 清除 返回值 中的内容
buffer->clear();
format->clear();
// 调用必须检查状态是否为 isExecuting
if (!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 this
Mutex::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
,这两个方法使用同一个消息,只不过传递的参数会不一样。
cpp
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 相关的部分:
cpp
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,调用 queueSecureInputBuffer
if (hasCryptoOrDescrambler() && !c2Buffer && !memory) {
AString *errorDetailMsg;
CHECK(msg->findPointer("errorDetailMsg", (void **)&errorDetailMsg));
// Notify mCrypto of video resolution changes
if (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 {
// 否则调用 queueInputBuffer
err = mBufferChannel->queueInputBuffer(buffer);
if (err != OK) {
mediametrics_setInt32(mMetricsHandle, kCodecQueueInputBufferError, err);
ALOGW("Log queueInputBuffer error: %d", err);
}
}
if (err == OK) {
// synchronization boundary for getBufferAndFormat
Mutex::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中:
cpp
void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
IOMX::buffer_id bufferID;
// 获取 buffer id
CHECK(msg->findInt32("buffer-id", (int32_t*)&bufferID));
sp<MediaCodecBuffer> buffer;
int32_t err = OK;
bool eos = false;
// 获取当前 state 中的 port mode
PortMode mode = getPortMode(kPortIndexInput);
// 获取是否是discard
int32_t discarded = 0;
if (msg->findInt32("discarded", &discarded) && discarded) {
// these are unfilled buffers returned by client
// buffers are returned on MediaCodec.flush
mode = KEEP_BUFFERS;
}
sp<RefBase> obj;
// 查找随消息送来的 MediaCodecBuffer
CHECK(msg->findObject("buffer", &obj));
buffer = static_cast<MediaCodecBuffer *>(obj.get());
// 判断是否eos
int32_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) {
// 持有 buffer
case KEEP_BUFFERS:
{
if (eos) {
if (!mCodec->mPortEOS[kPortIndexInput]) {
mCodec->mPortEOS[kPortIndexInput] = true;
mCodec->mInputEOSResult = err;
}
}
break;
}
// 重新提交 buffer
case RESUBMIT_BUFFERS:
{
// buffer 不为NULL且没有到达 eos
if (buffer != NULL && !mCodec->mPortEOS[kPortIndexInput]) {
// Do not send empty input buffer w/o EOS to the component.
// 检查 buffer size 是否为0,只有eos的情况下buffer size可以为0
if (buffer->size() == 0 && !eos) {
// 重新填充buff
postFillThisBuffer(info);
break;
}
// 检查pts
int64_t timeUs;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
// 设置 flag,默认flag为OMX_BUFFERFLAG_ENDOFFRAME
OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME;
// 判读是否是csd buffer,如果是需要设定对应的flag
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;
}
flags |= OMX_BUFFERFLAG_CODECCONFIG;
}
// 如果是 eos 也需要设定 flag
if (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);
}
// 打印 log
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
}
// debug log,以pts作为key,数据写入时间为value
#if TRACK_BUFFER_TIMING
ACodec::BufferStats stats;
stats.mEmptyBufferTimeUs = ALooper::GetNowUs();
stats.mFillBufferDoneTimeUs = -1ll;
mCodec->mBufferStats.add(timeUs, stats);
#endif
// 如果是 dynamic native window output buffer
if (mCodec->storingMetadataInDecodedBuffers()) {
// try to submit an output buffer for each input buffer
// 获取 port mode
PortMode 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 fence
info->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_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;
}
// 设置fence为-1
info->mFenceFd = -1;
if (err2 != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err2));
return;
}
// 重新设置buffer info的mStatus
info->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标记为true
mCodec->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;
cpp
// 默认为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的我们下节再了解。
cpp
status_t ACodec::submitOutputMetadataBuffer() {
// 检查是否为meta data mode
CHECK(storingMetadataInDecodedBuffers());
// 检查需要提交的buffer的数量
if (mMetadataBuffersToSubmit == 0)
return OK;
// 分配buffer
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");
// 传递给omx
return 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
cpp
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。
cpp
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 surface
if (getBufferSource() != NULL) {
android_errorWriteLog(0x534e4554, "29422020");
return INVALID_OPERATION;
}
// 根据 id 找到 buffer header
OMX_BUFFERHEADERTYPE *header = findBufferHeader(buffer, kPortIndexInput);
if (header == NULL) {
ALOGE("b/25884056");
return BAD_VALUE;
}
// 获取 buffer header 中的 buffer meta
BufferMeta *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,不需要做拷贝。
cpp
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 buffer
mInputBuffersWithCodec.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的流程就阅读完成了。