美文网首页Android开发android源码音视频
Android MediaCodec的数据处理方式分析

Android MediaCodec的数据处理方式分析

作者: crystaltiger | 来源:发表于2018-07-10 13:32 被阅读1次

    *由于工作需要,需要利用MediaCodec实现Playback及Transcode等功能,故在学习过程中翻译了Google官方的MediaCodec API文档,由于作者水平限制,文中难免有错误和不恰当之处,望批评指正。

    *转载请注明出处:http://www.cnblogs.com/roger-yu/

    概述

    Android MediaCodec可以访问底层的media codecs,我们很容易利用MediaCodec来构建encoder或decoder来实现音视频编码和音视频解码的功能。

    简单点儿理解,一个Codec(可以认为是一个MediaCodec的实例对象)就相当于一个“处理器”:处理输入数据,并产生输出数据。

    如下图所示,每一个Codec都维护着一组 input buffers 和 output buffers。开始时Codec拥有所有buffers的所有权,Client(可以暂且理解为MediaCodec之外写的程序)无法向 input buffer 写入数据,也无法读取 output buffer 中的数据。数据处理开始后,Client向Codec请求一个(同步模式)或者接收到(异步模式)一个空的 input buffer,将要处理的数据写入到该buffer中,然后提交给Codec处理,Codec处理完数据后会将处理的结果写入到一个空的 output buffer 中,之后Client就可以请求或接收到这个存有结果的 output buffer,Client对结果使用完毕后就可以release这个output buffer,Codec就可以再次使用这个buffer,如此过程完成整个的处理。

    image

    Android MediaCodec主要有3种数据处理的方式:

    1. 使用Buffers的异步处理方式(Asynchronous Processing using Buffers)

    2. 使用Buffers的同步处理方式(Synchronous Processing using Buffers)

    3. 使用Buffer数组的同步处理方式(Synchronous Processing using Buffer Arrays (deprecated))

    依据Android版本不同可以采用不同的方式,如下图:

    image

    目前最常用的是前两种模式,故接下来重点讲解。

    使用Buffers的异步处理方式(Asynchronous Processing using Buffers)

    基本处理流程:

    注意:

    1. 在调用configure配置MediaCodec之前需要为MediaCodec设置callback,需要实现MediaCodec.Callback接口并重写其中的方法:onInputBufferAvailable 、onOutputBufferAvailable、onOutputFormatChanged、onError,工作时MediaCodec会利用    这四个回调方法来自动的通知Client什么时候input buffer有效,什么时候output buffer有效,什么时候media format发生变化,什么时候运行出错,也是在这些方法中Client向Codec送入数据并得到处理的结果及获取Codec的一些其他信息。

    2. 异步模式下MediaCodec的状态转换会有些许不同,在调用start方法后会直接进入Running状态;

    异步处理模式下,调用MediaCodec.start()后Codec 立即进入Running子状态,通过设置的callback中的回调方法onInputBufferAvailable()会自动收到可用(empty)的input buffer,此时可以根据input buffer id调用getInputBuffer(id)得到这个buffer,并将需要的处理的数据写入该buffer中,最后调用queueInputBuffer(id, ...)将该buffer提交给Codec处理;Codec每处理完一帧数据就会将处理结果写入一个空的output buffer,并通过回调函数onOutputBufferAvailable来通知Client来读取结果,Client可以根据output bufffer id调用getOutputBuffer(id)获取该buffer并读取结果,完毕后可以调用releaseOutputBuffer(id, ...)释放该buffer给Codec再次使用。

    典型的代码设计:

    MediaCodec codec = MediaCodec.createByCodecName(name);
    MediaFormat mOutputFormat; // member variable
    // 异步模式下需要在configure之前设置callback
    codec.setCallback(new MediaCodec.Callback() {
        /**
         * 在onInputBufferAvailable回调方法中,MediaCodec会通知什么时候input
         * buffer有效,根据buffer id,调用getInputBuffer(id)可以获得这个buffer,
         * 此时就可以向这个buffer中写入数据,最后调用queueInputBuffer(id, …)提交
         * 给MediaCodec处理。
         */
        @Override
        void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
        ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
        // fill inputBuffer with valid data
        …
        codec.queueInputBuffer(inputBufferId, …);
        }
    
        /**
         * 在onOutputBufferAvailable回调方法中,MediaCodec会通知什么时候output
         * buffer有效,根据buffer id,调用getOutputBuffer(id)可以获得这个buffer,
         * 此时就可以读取这个buffer中的数据,最后调用releaseOutputBuffer(id, …)释放
         * 给MediaCodec再次使用。
         */
    
        @Override
        void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …)     {
            ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
            MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
            // bufferFormat is equivalent to mOutputFormat
            // outputBuffer is ready to be processed or rendered.
            …
            codec.releaseOutputBuffer(outputBufferId, …);
        }
    /**
        * 当MediaCodec的output format发生变化是会回调该方法,一般在start之后都会首先回调该方法
        */
        @Override
        void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
            // Subsequent data will conform to new format.
            // Can ignore if using getOutputFormat(outputBufferId)
            mOutputFormat = format; // option B
        }
        /**
         * MediaCodec运行发生错误时会回调该方法
         */
        @Override
        void onError(…) {
        …
        }
    });
    codec.configure(format, …);
    mOutputFormat = codec.getOutputFormat(); // option B
    codec.start(); // start 之后MediaCodec立即进入Running子状态,并会回调callback中的方法
    // wait for processing to complete
    codec.stop();  // stop后MediaCodec进入Uninitialized子状态
    codec.release(); //使用完毕要释放掉MediaCdoec占用的资源
    

    使用Buffers的同步处理方式(Synchronous Processing using Buffers)
    基本处理流程:

    同步模式下,MediaCodec调用start()方法后会进入Flushed子状态,然后第一次调用dequeueInputBuffer()后才会进入Running子状态。

    这种模式下,程序需要在一个无限循环中通过调用dequeueInputBuffer(...)和dequeueOutputBuffer(...)来不断地请求Codec是否有可用的input buffer 或 output buffer:

    > 如果有可用的input buffer:根据得到的buffer id,调用getInputBuffer(id)获取该buffer,并向其中写入待处理的数据,然后调用queueInputBuffer(id,..)提交到Codec进行处理

    > 如果有可用的output buffer: 根据得到的buffer id,调用getOutputBuffer(id)获取该buffer,读取其中的处理结果,然后调用releaseOutputBuffer(id,..)释放该buffer供Codec再次使用

    > 处理过程中还可能受到一些特殊标记的buffer id,比如MediaCodec.INFO_OUTPUT_FORMAT_CHANGED,要作出恰当处理

    典型的代码设计:

     MediaCodec codec = MediaCodec.createByCodecName(name);
     codec.configure(format, ...);
     MediaFormat outputFormat = codec.getOutputFormat(); // option B
     codec.start();  // start()方法后会进入Flushed子状态
     /**
      * 在一个无限循环中不断地请求Codec是否有可用的input buffer 或 output buffer
      */
     for (;;) {
         int inputBufferId = codec.dequeueInputBuffer(timeoutUs); // 请求是否有可用的input buffer
         if (inputBufferId >= 0) {
             ByteBuffer inputBuffer = codec.getInputBuffer(...); // 获取input buffer
             // fill inputBuffer with valid data
             ...
             codec.queueInputBuffer(inputBufferId, ...); // 提交数据给Codec
         }
         int outputBufferId = codec.dequeueOutputBuffer(...); // 请求是否有可用的output buffer
         if (outputBufferId >= 0) {
             ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); // 获取output buffer
             MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
             // bufferFormat is identical to outputFormat
             // outputBuffer is ready to be processed or rendered.
             ...
             codec.releaseOutputBuffer(outputBufferId, ...); // 释放output buffer供Codec再次使用
         } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
             // Subsequent data will conform to new format.
             // Can ignore if using getOutputFormat(outputBufferId)
             outputFormat = codec.getOutputFormat(); // option B
         }
      }
      codec.stop(); 
      codec.release(); //释放资源
    

    异步模式与同步模式的区别在于:
      》异步模式下通过回调函数来自动的传递可用的input buffer 或 output buffer

    》同步模式下需要通过dequeueInputBuffer(...)或dequeueOutputBuffer(...)来请求获取可用的input buffer 或 output buffer

    相关文章

      网友评论

        本文标题:Android MediaCodec的数据处理方式分析

        本文链接:https://www.haomeiwen.com/subject/ikmfpftx.html