Android codec2 视频框架之输出端的内存管理

news/2024/7/24 10:39:17 标签: android, 音视频, codec2, MediaCodec

文章目录

      • 前言
      • setSurface
      • start
      • 从哪个pool中申请buffer
      • 解码后框架的处理流程
      • renderOutbuffer 输出显示

前言

在这里插入图片描述

输出buffer整体的管理流程主要可以分为三个部分:

  1. MediaCodc 和 应用之间的交互 包括设置Surface、解码输出回调到MediaCodec。将输出buffer render或者releas到surface。
  2. MediaCodec到CCodecBufferChannel,主要是传递控制命令
  3. CCodecbufferChannel到componet buffer的封装 传递 控制等等。
  4. componet到bufferqueuepool buffer的申请

外部设置Surface进来,然后把输入buffer 输入,等待输出buffer 的回调,回调回来后 根据音视频同步的策略。在合适的时机renderOutput 送到MediaCodec

需要了解的几个方面

  1. setSurface内部做了什么处理。
  2. 什么时候有输出的buffer可用?
  3. 输出buffer render到MediaCodec.内部做了什么处理。

setSurface

外部的setSurface调用到 MediaCodec的kWhatSetSurface

  1. MediaCodec::setSurface
    调用下面的connetToSurface 对surface进行连接
 nativeWindowConnect(surface.get(), "connectToSurface(reconnect)");

  1. CCodecBufferChannel::setSurface

根据bufferChanned的信息配置surface,比如配置deuque buffer 的超时时间、
dequeue最大的buffer数,当然这些值在后续可能还会改变,后续在解码器中解码出来的delay改变的话 回重新设置这个delay,
然后在handlework 重新设置最大的可dequeue的buffer 数。赋值mOutputSurface的相关变量。

        Mutexed<OutputSurface>::Locked output(mOutputSurface);
        output->surface = newSurface;
        output->generation = generation;
  1. 设置surface到 C2BufferQueueBlockPool 用于后续的解码buffer的申请

在ccodecbufferchannel的start中调用configureProducer设置外部surface的GraphicBufferProducer到
bufferQueueBlockpopl中。

          outputSurface = output->surface ?
                    output->surface->getIGraphicBufferProducer() : nullptr;


        if (outputSurface) {
            mComponent->setOutputSurface(
                    outputPoolId_,
                    outputSurface,
                    outputGeneration,
                    maxDequeueCount);
        }

Return<Status> Component::setOutputSurface(
        uint64_t blockPoolId,
        const sp<HGraphicBufferProducer2>& surface) {
    std::shared_ptr<C2BlockPool> pool;
    GetCodec2BlockPool(blockPoolId, mComponent, &pool);
    if (pool && pool->getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) {
        if (bqPool) {
            bqPool->setRenderCallback(cb);
            bqPool->configureProducer(surface);
        }
    }
    return Status::OK;
}

void configureProducer(const sp<HGraphicBufferProducer> &producer,
                       native_handle_t *syncHandle,
                       uint64_t producerId,
                       uint32_t generation,
                       uint64_t usage,
                       bool bqInformation) {        
          if (producer) {
                mProducer = producer;
                mProducerId = producerId;
                mGeneration = bqInformation ? generation : 0;
         }

}

start

start 中跟输出buffer 相关的主要是两个方面

  1. 可以从surface最大能够dequeue出的buffer 数。由4个值组成 其中
    kSmoothnessFactor为4 kRenderingDepth为3。outputDelay由各个解码组件进行设置
    比如h264的默认设置为8, 同时会在解码过程handlework进行重新设置。

具体来说:

  • 在解码组件中解析到相关的reorder系数变化时 将系统放到输出的work中携带出去。
    在外部的ccodebufferchannel 中取出系统设置到surface中。
  • 实现动态的控制surface最大可以dequeue的buffer 数量。 外部通过dequeue申请的最大的buffer数是通过surface的setMaxDequeuedBufferCount
    设置到bufferqueueproducter 中,后续调用dequeue的时候会进行判断。
  • 比如解码器会重新设置为i4_reorder_depth。i4_reorder_depth 是什么? 怎么赋值的?(显示次序在某帧图像之后,解码次序在某帧图像之前的图像数量的最大值。因为编码器中的B帧不仅有前向参考,还有后向参考。 后向参考要求当前图像编码前,参考的后向图像已经编码完成,所以会导致图像的编码顺序和显示顺序不一样。)hevc 是存储在sps的sps_max_num_reorder_pics语言当中。
hevc'解码为例
ps_dec_op->i4_reorder_depth =
ps_sps->ai1_sps_max_num_reorder_pics[ps_sps->i1_sps_max_sub_layers - 1];

mOutputDelay = ps_decode_op->i4_reorder_depth;
ALOGV("New Output delay %d ", mOutputDelay);
C2PortActualDelayTuning::output outputDelay(mOutputDelay);
std::vector<std::unique_ptr<C2SettingResult>> failures;
c2_status_t err =
    mIntf->config({&outputDelay}, C2_MAY_BLOCK, &failures);
if (err == OK) {
    work->worklets.front()->output.configUpdate.push_back(
        C2Param::Copy(outputDelay));
 }
 
 
bool CCodecBufferChannel::handleWork(
        std::unique_ptr<C2Work> work,
        const sp<AMessage> &outputFormat,
        const C2StreamInitDataInfo::output *initData) {
        
while (!worklet->output.configUpdate.empty()) {
        std::unique_ptr<C2Param> param;
        worklet->output.configUpdate.back().swap(param);
        worklet->output.configUpdate.pop_back();

                if (param->forOutput()) {
                    C2PortActualDelayTuning::output outputDelay;
                    if (outputDelay.updateFrom(*param)) {
                        ALOGE("[%s] onWorkDone: updating output delay %u",
                              mName, outputDelay.value);
                        (void)mPipelineWatcher.lock()->outputDelay(outputDelay.value);
                        newOutputDelay = outputDelay.value;
                        needMaxDequeueBufferCountUpdate = true;
                    }
                }
                break;

    if (needMaxDequeueBufferCountUpdate) {
        int maxDequeueCount = 0;
        {
            Mutexed<OutputSurface>::Locked output(mOutputSurface);
            maxDequeueCount = output->maxDequeueBuffers =
                    numOutputSlots + reorderDepth + kRenderingDepth;
            if (output->surface) {
                output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);
            }
        }
        if (maxDequeueCount > 0) {
            mComponent->setOutputSurfaceMaxDequeueCount(maxDequeueCount);
        }
    }





}

constexpr size_t kSmoothnessFactor = 4;
constexpr size_t kRenderingDepth = 3;

    C2PortActualDelayTuning::output outputDelay(0);
    c2_status_t err = mComponent->query(
            {
                &iStreamFormat,
                &oStreamFormat,
                &kind,
                &reorderDepth,
                &reorderKey,
                &inputDelay,
                &pipelineDelay,
                &outputDelay,
                &secureMode,
            },
            {},
            C2_DONT_BLOCK,
            nullptr);
        size_t numOutputSlots = outputDelayValue + kSmoothnessFactor

        sp<IGraphicBufferProducer> outputSurface;
        uint32_t outputGeneration;
        int maxDequeueCount = 0;
        {
            Mutexed<OutputSurface>::Locked output(mOutputSurface);
            maxDequeueCount = output->maxDequeueBuffers = numOutputSlots +
                    reorderDepth.value + kRenderingDepth;
            outputSurface = output->surface ?
                    output->surface->getIGraphicBufferProducer() : nullptr;
            if (outputSurface) {
                output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);
            }
            outputGeneration = output->generation;

constexpr uint32_t kDefaultOutputDelay = 8;

        addParameter(
                DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY)
                .withDefault(new C2PortActualDelayTuning::output(kDefaultOutputDelay))
                .withFields({C2F(mActualOutputDelay, value).inRange(0, kMaxOutputDelay)})
                .withSetter(Setter<decltype(*mActualOutputDelay)>::StrictValueWithNoDeps)
                .build());



   if (ps_decode_op->i4_reorder_depth >= 0 && mOutputDelay != ps_decode_op->i4_reorder_depth) {
            mOutputDelay = ps_decode_op->i4_reorder_depth;
            ALOGV("New Output delay %d ", mOutputDelay);
            C2PortActualDelayTuning::output outputDelay(mOutputDelay);
            std::vector<std::unique_ptr<C2SettingResult>> failures;
            c2_status_t err =
                mIntf->config({&outputDelay}, C2_MAY_BLOCK, &failures);
            if (err == OK) {
                work->worklets.front()->output.configUpdate.push_back(
                    C2Param::Copy(outputDelay));
            } else {
                ALOGE("Cannot set output delay");
                mSignalledError = true;
                work->workletsProcessed = 1u;
                work->result = C2_CORRUPTED;
                return;
            }
        }


从哪个pool中申请buffer

在CCodecBufferChannel::start的时候决定,在下面代码中将pools的allocatedID转为
C2BufferQueueBlockPool。 在这之后调用mComponent->createBlockPool。Codec2Client::Component::createBlockPool调用c2store的c2_status_t createBlockPool()然后调用_createBlockPool,在之前设置了是BUFFERQUEUE,这边就保存了创建好的C2BufferQueueBlockPool。 在后面解码的流程中fetchGrallocBlock,使用的是这个类型的
C2BufferQueueBlockPool。

poolmask的默认值:

int GetCodec2PoolMask() {
    return property_get_int32(
            "debug.stagefright.c2-poolmask",
            1 << C2PlatformAllocatorStore::ION |
            1 << C2PlatformAllocatorStore::BUFFERQUEUE);
}

int poolMask = GetCodec2PoolMask();

申请的buffer的类型函数是bufferqueue

if (pools->outputAllocatorId == C2PlatformAllocatorStore::GRALLOC
&& err != C2_OK
&& ((poolMask >> C2PlatformAllocatorStore::BUFFERQUEUE) & 1)) {
pools->outputAllocatorId = C2PlatformAllocatorStore::BUFFERQUEUE;
}
}


bufferqueue的申请调用的是C2PlatformAllocatorStoreImpl的fetchAllocator

case C2PlatformAllocatorStore::BUFFERQUEUE:
res = allocatorStore->fetchAllocator(
C2PlatformAllocatorStore::BUFFERQUEUE, &allocator);
if (res == C2_OK) {
std::shared_ptr<C2BlockPool> ptr(
new C2BufferQueueBlockPool(allocator, poolId), deleter);
*pool = ptr;
mBlockPools[poolId] = ptr;
mComponents[poolId].insert(
mComponents[poolId].end(),
components.begin(), components.end());
}
break;

fetchAllocator返回gralloc的allocator。

std::shared_ptr<C2Allocator> C2PlatformAllocatorStoreImpl::fetchBufferQueueAllocator() {
    static std::mutex mutex;
    static std::weak_ptr<C2Allocator> grallocAllocator;
    std::lock_guard<std::mutex> lock(mutex);
    std::shared_ptr<C2Allocator> allocator = grallocAllocator.lock();
    if (allocator == nullptr) {
        allocator = std::make_shared<C2AllocatorGralloc>(
                C2PlatformAllocatorStore::BUFFERQUEUE, true);
        grallocAllocator = allocator;
    }
    return allocator;
}


### fetchGraphicBlock 流程

  • fetchGraphicBlock

fetch经过一系列判断和处理 最终调用mProducer的dequeueBuffer

    c2_status_t fetchGraphicBlock(
            uint32_t width,
            uint32_t height,
            uint32_t format,
            C2MemoryUsage usage,
            std::shared_ptr<C2GraphicBlock> *block /* nonnull */,
            C2Fence *fence) {

c2_status_t status = fetchFromIgbp_l(width, height, format, usage, block, fence);
             c2Status = dequeueBuffer(width, height, format, usage,
                              &slot, &bufferNeedsReallocation, &fence);                         
        if (fence) {
            static constexpr int kFenceWaitTimeMs = 10;
            status_t status = fence->wait(kFenceWaitTimeMs);
     }
               
    其中 dequeueBuffer
    
        Return<void> transResult = mProducer->dequeueBuffer(
                Input{
                    width,
                    height,
                    format,
                    androidUsage.asGrallocUsage()},
                [&status, slot, needsRealloc,
                 fence](HStatus hStatus,
                         int32_t hSlot,
                         Output const& hOutput) {
                    *slot = static_cast<int>(hSlot);
                    if (!h2b(hStatus, &status) ||
                            !h2b(hOutput.fence, fence)) {
                        status = ::android::BAD_VALUE;
                    } else {
                        *needsRealloc =
                                hOutput.bufferNeedsReallocation;
                    }
                });




  • dequebuffer中的fence 有什么作用

Fence是一种同步机制,用于GraphicBuffer的同步。用来处理跨硬件平台不同的情况(CPU和GPU),尤其是CPU、GPU和HWC之间的同步。另外,也可用于多个时间点之间的同步,当Graphics Buffer的生产者或消费者在对buffer处理完之后,通过fence发出信号,这样系统可以异步queue当前不需要但有可能接下来会使用读写的buffer。

简言之,在合适的时间发一种信号,将先到的buffer拦住,等后来的到达,两者步调一致再一起走。也就是dequeuebuffer 之后并不能直接用这块buffer,需要等待buffer的fence发送上来之后 才可以使用这块buffer。

  • 完整一个获取fetch buffer的流程

dequeueBuffer ---->(获取到slot或fence) fence->wait -----> mProducer->requestBuffer(通过slot 获取到buffer)

将从gralloc 获取到的buffer (native_handle_t)通过调用android::WrapNativeCodec2GrallocHandle转化为C2Handle
这个C2Handle 会生成C2AllocationGralloc,这个alloc最后会new 封装成C2GraphicBlock。这个block就是返回给外部解码申请的地方。

经过上面的这个流程 解码要的共享的buffer 就从gralloc这边申请出来了,然后这个buffer就可以给到后面的解码器使用了,如果是软解就map出虚拟地址,然后将软解后的数据拷贝到里面。但一般厂商不会用软解,正常的实现是这块buffer给到硬件,硬解数据直接写到这块buffer。

解码的buffer准备好之后,会把grallocblock的buffer 转换为c2buffer 然后会放到c2work中output buffers里面。

std::shared_ptr<C2Buffer> buffer
= createGraphicBuffer(std::move(entry->outblock),
C2Rect(mWidth, mHeight).at(left, top));

解码后框架的处理流程

解码后的哪些信息是携带在work里面的, 解码的buffer,

work->worklets.front()->output.flags = (C2FrameData::flags_t)0;
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.buffers.push_back(buffer);
work->worklets.front()->output.ordinal = work->input.ordinal;
work->workletsProcessed = 1u;

  • 从应用开始, 应用调用的是dequeueOutputBuffer返回的是index 时间戳等等信息,这个调用到mediacodec, mediacodec 从 mAvailPortBuffers 取出可用的buffer。
  • mAvailPortBuffers是通过解码那边 BufferCallback onOutputBufferAvailable来把解码buffer push 到mAvailPortBuffers。这个回调是simpleC2Componet 的finish的listener->onWorkDone_nb调用到CCodec的onWorkDone。
  • onWorkDone调用到mChannel->onWorkDone。 在mChannel的workDone 中 调用handleWork。
  • handlework 里面将解码器传递在work 中outputbuffer 转换为mediacodec的用的index 和 mediaCodecbuffer。同时返回到MediaCodec之前设置的callback。这个最后会返回应用设置callback的地方。
mCallback->onOutputBufferAvailable(index, outBuffer);

这个callback 是从何而来的。 在mediacodec的init的时候会新建一个codec 并将codec设置到codec2里面。mCodec->setCallback(
std::unique_ptrCodecBase::CodecCallback(
new CodecCallback(new AMessage(kWhatCodecNotify, this))));

  • 各个buffer 直接的转换

首先从解码这边出来的是C2GraphicBlock,会在codecbufferchannel中转为index 传递出去给mediacodec 转换过程是 内部有一个
mBuffers数组,在handlework先pushToStash到这里面。然后从这里面取出来。popFromStashAndRegister是这个里面去转换为mediacodec的buffer 和index 的。转换的MediaCodecBuffer, 就是把c2buffer的一个结构体赋值到Codec2Buffer中。c2Buffer->copy(buffer)。

renderOutbuffer 输出显示

  • render的时候传递的是index,同样也是mAvailPortBuffers 取出可用的buffer。
  • 这个buffer 通过status_t err = mBufferChannel->renderOutputBuffer(buffer, renderTimeNs)。将MediaCodecBuffer转换为C2buffer。
  • 从这个C2buffer 中取出C2ConstGraphicBlock, block 在转换为bqslot。 这个slot最后queue到surface那边。
getBufferQueueAssignment(block, &generation, &bqId, &bqSlot)
status = outputIgbp->queueBuffer(static_cast<int>(bqSlot),
input, output);

http://www.niftyadmin.cn/n/5201096.html

相关文章

C语言--判断年月日是否合理

一.题目描述 比如输入2001&#xff0c;2&#xff0c;29&#xff0c;输出&#xff1a; 不合理 。因为平年的二月只有28天 比如输入2000&#xff0c;6&#xff0c;31&#xff0c;输出&#xff1a;不合理。因为6月是小月&#xff0c;只有30天。 二.思路分析 本题主要注意两个问…

现货白银MACD实战分析例子

MACD这个技术指标的全称是平滑异同移动平均线&#xff0c;主要表示经过平滑处理后均线的差异程度&#xff0c;一般用来研判现货白银价格变化的方向、强度和趋势。MT4中的MACD指标&#xff0c;主要是由信号线、&#xff08;上升/下跌&#xff09;动能柱、0轴这三部分组成。 MACD…

Memcpy运行时内存增加

结论&#xff1a;Memcpy不会导致内存增加。 原因&#xff1a;所需内存过大&#xff0c;动态申请时系统并未分配空间&#xff0c;而是边使用边分配&#xff0c;导致出现该现象&#xff0c;在所有内存均使用后&#xff0c;内存不会增长。

力扣 622.设计循环队列

目录 1.解题思路2.代码实现 1.解题思路 首先&#xff0c;该题是设计循环队列&#xff0c;因此我们有两种实现方法&#xff0c;即数组和链表&#xff0c;但具体考虑后&#xff0c;发现数组实现要更容易一些&#xff0c;因此使用数组实现&#xff0c;因此我们要给出头和尾变量&a…

线程(线程基本概念、java实现多线程、使用多线程、线程的生命周期、线程同步、线程死锁)

&#xff08;一&#xff09;线程基本概念 一、 程序, 进程, 线程的概念 程序: 使用某种语言编写一组指令(代码)的集合,静态的 进程: 运行的程序,表示程序一次完整的执行, 当程序运行完成, 进程也就结束了 个人电脑: CPU 单个, 双核, CPU的时间分片, 抢占式 每个独立执行的程…

ES学习看这一篇文章就够了

ES学习看这一篇文章就够了 ES学习看这一篇文章就够了-CSDN博客

Linux中路由route

route 显示当前路由表信息 route add -net 192.168.10.0 netmask 255.255.255.0 dev ens160去往192.168.10.0/24网段的路由通过ens160网卡出去add 添加路由&#xff08;del表示删除路由&#xff09;-A 设置地址类型&#xff08;默认ipv4 配置ipv6地址时&#xff1a;-A …