Android Media Framework(十七)ACodec - Ⅴ

本篇文章我们一起来分析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,看看我们说的对不对:

  1. 有native window时,检查output buffer是属于ACodec还是属于native window,如果都不是就返回error;
  2. 检查output buffer是否属于native window,如果是则不会提交给组件使用;
  3. 没有native window时,如果output buffer不属于ACodec,就会返回error;
  4. 调用ACodec::BufferInfo的checkWriteFence方法,这个方法没有什么实质性作用,只做log提醒。提交buffer给组件使用,是想要组件做数据写入,因此graphic output buffer持有的是write fence。
  5. 调用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;
}
  1. 如果ACodec已经收到input eos,那么input buffer将不会再回传给上层;
  2. 将input format设定到mData中;
  3. 调用ACodecBufferChannel的fillThisBuffer方法,将可用buffer id送给上层;
  4. 清除ACodec对mData的引用;
  5. 将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

关注公众号《青山渺渺》阅读全文
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值