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 状态 或者 是异步的状态直接返回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 的实现方式相同,只不过函数调用回传的内容不一样:

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) {
	// 检查传出参数是否为 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,这两个方法使用同一个消息,只不过传递的参数会不一样。

        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,调用 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中:

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;
// 默认为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 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

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 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,不需要做拷贝。

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的流程就阅读完成了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在vscode中创建任务编译module源文件

接昨天的文章 [创建并使用自己的C模块&#xff08;Windows10MSVC&#xff09;-CSDN博客]&#xff0c;觉得每次编译转到命令行下paste命令过于麻烦&#xff0c;于是研究了一下在vscode中创建自动编译任务。 经过尝试&#xff0c;在task.json中增加如下代码&#xff1a; {"…

IDEA2023 最新版详细图文安装教程(安装+运行测试+汉化+背景图设置)

IDEA2023 最新版详细图文安装教程 名人说&#xff1a;工欲善其事&#xff0c;必先利其器。——《论语》 作者&#xff1a;Code_流苏(CSDN) o(‐&#xff3e;▽&#xff3e;‐)o很高兴你打开了这篇博客&#xff0c;跟着教程去一步步尝试安装吧。 目录 IDEA2023 最新版详细图文安…

SpringBoot整合Validator

前言 @Validation是一套帮助我们继续对传输的参数进行数据校验的注解,通过配置Validation可以很轻松的完成对数据的约束。 通过对DTO中实体类的约束,可以大大增加代码的简洁性。 错误的状态码 返回的响应码推荐使用400 bad request. 参数注解含义 实体类 /*** @author:…

索引语法SQL性能分析

创建 查看 删除 SQL执行频率 Com后七个下划线 慢查询日志 show profiles explain explain执行计划 各字段含义&#xff1a; 多表查询 根据主键或者唯一索引时会出现const const就已经是很棒的性能了&#xff0c;实际中 NULL几乎不会出现

信息安全管理与评估省赛经验总结

信息技能大赛 在比赛开始之前&#xff0c;一定要检查设配&#xff0c;认真审查注意事项&#xff1b;拿到题之后&#xff0c;把对应设备的基本配置完成&#xff0c;任何异常及时报告&#xff0c;这个时候可以把设备的线链接上配置好&#xff0c;登录清单上管理地址等查看是否能登…

二维码地址门牌系统技术服务:让您的生活更便捷,一码通行,安全无忧

文章目录 前言一、融合二维码技术与门牌的便捷服务二、手机开门便捷功能三、智能化安全保障四、智能化、便捷化的新型技术 前言 在数字化时代&#xff0c;二维码门牌系统技术应运而生&#xff0c;为了满足人们对安全、便捷生活的需求。这项技术将二维码与门牌结合&#xff0c;…

leetcode链表小练(1.反转链表2.链表的中间节点3.合并两个有序链表4.环形链表①5.环形链表②)详解 (୨୧• ᴗ •͈)◞︎ᶫᵒᵛᵉ ♡

目录 一.反转链表 思路一反转指针反向&#xff1a; 思路二头插法&#xff1a; 二.链表的中间节点&#xff1a; 三.合并两个有序数组: 思路一&#xff1a;从头开始&#xff0c;取两个链表中小的那个尾插到新链表。定义指针head,tail指向空&#xff0c;代表新链表的头结点。…

Hive/SparkSQL中UDF/UDTF/UDAF的含义、区别、有哪些函数

Hive官网&#xff1a;https://cwiki.apache.org/confluence/display/Hive/LanguageManualUDF#LanguageManualUDF-Built-inTable-GeneratingFunctions(UDTF) 1.UDF(User-Defined Function) 含义 即用户定义函数&#xff0c;UDF用于处理一行数据并返回一个标量值(单个值)&#x…

测试自动创建设备节点的功能

一. 简介 上一篇文章在 新设备驱动框架代码的基础上&#xff0c;添加了自动创建设备节点的代码。文章地址如下&#xff1a; 自动创建设备节点代码的实现-CSDN博客 本文对自动创建设备节点的功能进行测试。 二. 自动创建设备节点代码的测试 1. 编译驱动&#xff0c;并拷贝…

关于编程模式的总结与思考

淘宝创新业务的优化迭代是非常高频且迅速的&#xff0c;在这过程中要求技术也必须是快且稳的&#xff0c;而为了适应这种快速变化的节奏&#xff0c;我们在项目开发过程中采用了一些面向拓展以及敏捷开发的设计&#xff0c;本文旨在总结并思考其中一些通用的编程模式。 前言 静…

【Vue2+3入门到实战】(19)Vuex状态管理器通过辅助函数 - mapState获取 state中的数据代码实现 详细讲解

目录 一、通过辅助函数 - mapState获取 state中的数据1.第一步&#xff1a;导入mapState (mapState是vuex中的一个函数)2.第二步&#xff1a;采用数组形式引入state属性3.第三步&#xff1a;利用**展开运算符**将导出的状态映射给计算属性 二、开启严格模式及Vuex的单项数据流1…

2024年美赛数学建模ABCDEF题思路选题分析

文章目录 1 赛题思路2 美赛比赛日期和时间3 赛题类型4 美赛常见数模问题5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 美赛比赛日期和时间 比赛开始时间&#xff1a;北京时间2024年2月2日&#xff08;周五&#xff…

MVCC 并发控制原理-源码解析(非常详细)

基础概念 并发事务带来的问题 1&#xff09;脏读&#xff1a;一个事务读取到另一个事务更新但还未提交的数据&#xff0c;如果另一个事务出现回滚或者进一步更新&#xff0c;则会出现问题。 2&#xff09;不可重复读&#xff1a;在一个事务中两次次读取同一个数据时&#xff0c…

Java实现短信发送业务

1、业务需求 发送短信功能是一个很普遍的需求&#xff0c;比如验证码&#xff0c;快递单号&#xff0c;通知信息一类。 而在Java中实现短信功能相对简单&#xff0c;只需要调用短信服务商提供的API。接下来以阿里云为例&#xff0c;介绍如何实现短信发送功能&#xff0c;其他短…

运算符的优先级(规矩是人定的)

运算符的优先级&#xff08;规矩是人定的&#xff09; 什么是经典&#xff1f;经典就是理论不随时间变迁而变化。《东方不败》中的很多台词让人时不时想起来振聋发聩。比如 很多事情不是自己想的那样&#xff0c;规矩是人定的。 舔狗和有思想 从小到大&#xff0c;我们都学过…

使用sdf文件+urdf文件模拟机器人示例(不用把urdf转sdf)

gazebo版本&#xff1a;harmonic&#xff1b; <launch> <group> <let name"robot_description" value"$(command xacro $(find-pkg-share gazebo_pkg)/urdf/total.xacro)"/> <node pkg"rviz2" exec"rviz2" name…

前端文件上传组件最全封装+删除+下载+预览

前言&#xff1a;使用的是若依的框架element uivue2封装的。如果有不对的地方欢迎指出。后台管理使用&#xff0c;文件需要上传。回显列表&#xff0c;详情也需要回显预览 // 开始封装组件&#xff1a;封装在 src/components/FileUpload/index.vue中 <template><div c…

如何使用Pyxamstore快速解析Xamarin AssemblyStore文件

关于Pyxamstore Pyxamstore是一款针对Xamarin AssemblyStore文件&#xff08;assemblies.blob&#xff09;的强大解析工具&#xff0c;该工具基于纯Python 2.7开发&#xff0c;支持从一个APK文件中解包并重封装assemblies.blob和assemblies.manifest Xamarin文件。 什么是ass…

YOLOv5算法进阶改进(10)— 更换主干网络之MobileViTv3 | 轻量化Backbone

前言:Hello大家好,我是小哥谈。MobileViTv3是一种改进的模型架构,用于图像分类任务。它是在MobileViTv1和MobileViTv2的基础上进行改进的,通过引入新的模块和优化网络结构来提高性能。本节课就给大家介绍一下如何在主干网络中引入MobileViTv3网络结构,希望大家学习之后能够…

生活明朗,万物可爱人间值得,未来可期

这是一件洋溢着温馨 与童真的时尚佳作它以细腻的织法、柔暖的材质 给您的宝贝带来舒适的穿着体验独特的韩版设计&#xff0c;彰显时尚品味 让您的孩子从小就走在 潮流的前沿适合各种场合 无论是周末聚会还是平日上学 都能让您的孩子焕发自信光彩