• Android codec2 编码 -- 基于录屏


    前言

    本篇文章主要是理解Android 12编码的流程, 首先从上层的应用出发理解mediacodec提供给外部API的用法。然后针对每个api 分析编码各个流程中框架中的流程。

    熟悉一个框架的流程 可以从简单到复杂、从整体到局部去展开。 同时在理解过中会产生各种各样的问题,各种问题的解决就是一个知识经验的形成过程。

    android 原生的应用srcreenrecord

    • 应用和代码路径

    代码路径:frameworks\av\cmds\screenrecord\screenrecord.cpp

    编译生成的是screenrecord在system/bin目录,默认在android系统都会携带。

    使用命令:这个命令会将屏幕的操作录制到/sdcard/test.mp4下。

     screenrecord /sdcard/test.mp4
    
    • 1
    • 应用流程

      • 首先在编码器mediacodec调用createInputSurface创建一个inputSurface。这个inputSurface传递出来到显示 作为虚拟显示的bufferProducer。
      • 在surfaceFlinger 端,inputSurface作为prepareVirtualDisplay的参数, 使得surfaceFlinger从这个surface中获取bufffer, 然后将屏幕合成后的数据写到这个buffer里面。
      • 在编码端将这个buffer 作为编码的输入进行处理。mediacodec编码完成之后调用dequeueOutputBuffer 将编码之后的数据取出来写到文件,然后调用releaseOutputBuffer将这个buffer释放回去。
      • 在编码器这边,surfaceflinger 是生产端,mediacodec是消费端。其他有关屏幕录制或者surface 直接到编码的流程大概都是这样的。
      创建编码器,创建输入的surface,配置format,启动编码器
      sp format = new AMessage;
      format->setInt32(KEY_WIDTH, gVideoWidth);
      format->setInt32(KEY_HEIGHT, gVideoHeight);
      .....
      codec = MediaCodec::CreateByType(looper, kMimeTypeAvc, true);    
      err = codec->configure(format, NULL, NULL,
                 MediaCodec::CONFIGURE_FLAG_ENCODE);
      err = codec->createInputSurface(&bufferProducer);
      err = codec->start();
      
      err = prepareVirtualDisplay(displayState, bufferProducer, &dpy);
      
      从编码器中取出buffer,后续就是将这个buffer写入到mp4文件中。
      Vector > buffers;
      err = encoder->getOutputBuffers(&buffers);
      err = encoder->dequeueOutputBuffer(&bufIndex, &offset, &size, &ptsUsec,
      &flags, kTimeout);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

    上述流程的疑问?

    1. mediacodec是如何将surface的数据取出来 然后进行编码的?

      相对应于解码的流程,会有一个queueInbufferbuffer 将未解码的数据喂给mediacodec,而在编码这边编码器只有一个从codec创建出来的Surface,这个surface会配置到surfaceFlinger那边的虚拟显示中。

    MediaCodec获取编码数据流程

    代码路径:
    frameworks\av\media\libstagefright\MediaCodec.cpp
    frameworks\av\media\codec2\sfplugin\CCodec.cpp
    frameworks\av\media\libstagefright\bqhelper\GraphicBufferSource.cpp
    frameworks\av\media\codec2\sfplugin\C2OMXNode.cpp

    简单的理解可以分为这两个路径:

    1. 生产者: surfaceFlinger从MediaCodec创建的InputSurface中申请buffer,然后将各个图层的数据合成到这块buffer中,合成后 通知到消费者 也就是componet这一端。
    2. 消费者:componet收到生产者surfaceFlinger的通知后,将这个合成的buffer给到硬解或者软解编码器进行编码。编码后的数据,外部应用通过dequeueOutputBuffer可以获取到。

    这里我们关注消费者这一端的实现。

    • mediacodec creatInputSurface

      • 调用流程
        遵循 mediacodec—>ccodec这样的流程,ccodec调用的是codec2client。client 通过HIDL调用到componentstore端(在IComponetSotore.hal中有定义了这样的接口C2PlatformComponentStore–>componentStore–>IComponetSotore 具体用vendor定义的还是default google实现的 是在之前service端创建服务的时候决定的)。

      • createInputSurface

        创建了GraphicBufferSource, 在这个类的初始化中调用BufferQueue::createBufferQueue
        创建Producer和Consumer,通过将GraphicBufferSource监听注册到mConsumer中,
        这里就是onFrameAvailable注册的地方。Producer和GraphicBufferSource会封装到InputSurface 返回到codec2client。

      • GraphicBufferSourceWrapper的connect
        创建好之后的InputSurface会强制转换为GraphicBufferSourceWrapper,然后会调用这个类的connect。connect中是创建了C2OMXNode,传递的参数是之前MediaCodec::CreateByType
        创建的componet。同时通过调用GraphicBufferSource::configure,将这个C2OMXNode配置到GraphicBufferSource的mComponent。

      CCodec::createInputSurface()
              int32_t width = 0;
              (void)outputFormat->findInt32("width", &width);
              int32_t height = 0;
              (void)outputFormat->findInt32("height", &height);
              err = setupInputSurface(std::make_shared(
                      gbs, width, height, usage));
              bufferProducer = persistentSurface->getBufferProducer();
      
      CCodec::setupInputSurface:
         status_t err = mChannel->setInputSurface(surface);
      
      CCodecBufferChannel::setInputSurface:
           mInputSurface->connect(mComponent);
      
      class GraphicBufferSourceWrapper : public InputSurfaceWrapper {
      connect(const std::shared_ptr &comp) {
              mNode = new C2OMXNode(comp);
              mOmxNode = new hardware::media::omx::V1_0::utils::TWOmxNode(mNode);
              mNode->setFrameSize(mWidth, mHeight);
              // Usage is queried during configure(), so setting it beforehand.
              OMX_U32 usage = mConfig.mUsage & 0xFFFFFFFF;
              (void)mNode->setParameter(
                      (OMX_INDEXTYPE)OMX_IndexParamConsumerUsageBits,
                      &usage, sizeof(usage));
              mSource->configure(
                      mOmxNode, static_cast(mDataSpace));
              return OK;
      }
      }
      
      status_t GraphicBufferSource::configure(
              const sp& component,
              int32_t dataSpace,
              int32_t bufferCount,
              uint32_t frameWidth,
              uint32_t frameHeight,
              uint32_t consumerUsage)
      {
          mComponent = component;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
    • onFrameAvailable
      • 通过之前在GraphicBufferSource注册onFrameAvailable到producer中lister,当合成后又buffer 可用的时候,会回调到GraphicBufferSource的onFrameAvailable。
      • onFrameAvailable经过一系列的处理 会调用到mComponent->submitBuffer,这个调用C2OMXNode->emptyBuffer。
        c2OMXNode这边将这块omxBuf 封装成c2Buffer,然后queue到c2OMXNode 的队列中去。C2OMXNode有专门的mQueueThread来把队列中c2works queue到Codec2Client中。
      • 在client中的Codec2Client::Component::queue()在调用 mComponent->queue_nb(&c2works)。
        mComponet 是simpleC2Componet, 在其中的queue_nb会把上面传递的items 放到componet的mWorkQueue中,然后发送kWhatProcess消息, 收到消息后调用processQueue。然后调用各个组件的process。
    // BufferQueue::ConsumerListener callback
    void GraphicBufferSource::onFrameAvailable(const BufferItem& item __unused) 
       onBufferAcquired_l(buffer);
    
    void GraphicBufferSource::onBufferAcquired_l(const VideoBuffer &buffer)
    	fillCodecBuffer_l();
    
    bool GraphicBufferSource::fillCodecBuffer_l() {
    	err = submitBuffer_l(item); 
    
    status_t GraphicBufferSource::submitBuffer_l(const VideoBuffer &item)
        status_t err = mComponent->submitBuffer(
                codecBufferId, graphicBuffer, codecTimeUs, buffer->getAcquireFenceFd());
          
    class OmxComponentWrapper : public ComponentWrapper {
        status_t submitBuffer(
                int32_t bufferId, const sp &buffer,
                int64_t timestamp, int fenceFd) override {
            ALOGD("submitBuffer bufferId:%d", (int)bufferId);
            return mOmxNode->emptyBuffer(
                    bufferId, OMX_BUFFERFLAG_ENDOFFRAME, buffer, timestamp, fenceFd);
        }
      
     status_t C2OMXNode::emptyBuffer(
            buffer_id buffer, const OMXBuffer &omxBuf,
            OMX_U32 flags, OMX_TICKS timestamp, int fenceFd) {
            mQueueThread->queue(comp, fenceFd, std::move(work), std::move(fd0), std::move(fd1));
        }
     
    class C2OMXNode::QueueThread : public Thread {
     protected:
        bool threadLoop() override {
       	 comp->queue(&items);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    总结: Android 录屏编码这一部分 调用的路径非常长,主要连接surface和componet的是GraphicBufferSource类。在这里监听surface buffer的生成,并将其传递给编码的componet。

    请添加图片描述

  • 相关阅读:
    玩转Vue3全家桶02丨上手:一个清单应用帮你入门Vue
    【技术积累】Vue.js中的核心知识【三】
    Curriculum Vitae;CV
    开发知识点-NodeJs-npm/Pnpm/Vite/Yarn包管理器
    HTML5+CSS网页作业:汽车介绍特斯拉 (dreamweaver作业静态HTML网页设计模板)
    Lazada如何申请入驻Lazmall品牌商城,会给商家带来哪些权益
    2018年五一杯数学建模B题商业银行人民币贷款规模分配及盈利问题解题全过程文档及程序
    巨细!Python爬虫详解
    代码随想录算法训练营第五十一天| 309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费
    简单的python爬虫工具,B站视频爬虫
  • 原文地址:https://blog.csdn.net/H2008066215019910120/article/details/132864163