首页 > 代码库 > Android Multimedia框架总结(二十)MediaCodec状态周期及Codec与输入/输出Buffer过程(附实例)

Android Multimedia框架总结(二十)MediaCodec状态周期及Codec与输入/输出Buffer过程(附实例)

转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/53183718

前言:前面几节都是介绍Camera2相关,对于Camera2预览把图像显示在SurfaceView上,还有录像时,时时刷新当前图像区域。追溯到最早介绍的MediaPlayer播放视频,这些都离不开重要角色MediaCodec,今天介绍MediaCodec,看下Agenda:

  • MediaCodec是什么?
  • codec操作的数据类型
    • 压缩buffer
    • 原始音频buffer
    • 原始视频buffer
  • MediaCodec状态周期图
  • Codec-specific数据
  • Codec数据处理过程
  • MediaCodec案例

MediaCodec是什么?

MediaCodec类可以访问底层媒体编解码器框架(StageFright或openMAX),即编码器/解码器组件。这是Android low-level多媒体支持基础设施的一部分(通常与MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, and AudioTrack.一起使用))。


技术分享

从广义上讲,codec处理输入数据产生输出数据。异步处理数据,并使用一组输入和输出buffer。在简单的层面,请求(或接收)空输入缓冲区,加满数据并将其发送到codec进行处理。codec使用的数据转换成一个空的输出缓冲区。最后,请求(或接收)输出buffer,消耗其内容并释放它回codec。

codec操作的数据类型

codec操作三种数据:压缩数据,原始音频数据和原始的视频数据。所有这三种数据可以使用ByteBuffers处理,应该对原始视频数据使用一个Surface来提高编码性能。Surface使用native视频的buffers没有映射或复制他们ByteBuffers;因此,它更高效。通常不能访问原始视频数据使用一个Surface时,可以使用ImageReader类访问未固定的解码视频帧(原生)。比起使用ByteBuffers的话,这可能会更有效的,一些native 的buffers可能直接映射到ByteBuffers。当使用ByteBuffer模式中,您可以使用Image类及getInput / OutputImage(int)访问原始视频帧。

压缩buffer

输入缓冲区(解码器)和输出缓冲区(编码器)包含压缩数据格式的类型。对于视频类型,这是一个压缩的视频帧。对于音频数据,这通常是一个单一的访问单元(一个音频段通常包含几毫秒的音频编码格式决定的类型),但这个需求略有放松,缓冲区可能包含多个接入单位的音频编码。在这两种情况下,在任意字byte边界上,buffer不会start和end,直到在(帧/访问)unit边界上。

原始音频buffer

原始音频缓冲区包含整个PCM音频帧数据,下面是一个示例为每个通道的通道顺序。每一个样例都是16位带符号整数在native byte通道中。

short[] getSamplesForChannel(MediaCodec codec, int bufferId, int channelIx) {
   ByteBuffer outputBuffer = codec.getOutputBuffer(bufferId);
   MediaFormat format = codec.getOutputFormat(bufferId);
   ShortBuffer samples = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
   int numChannels = formet.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
   if (channelIx < 0 || channelIx >= numChannels) {
     return null;
   }
   short[] res = new short[samples.remaining() / numChannels];
   for (int i = 0; i < res.length; ++i) {
     res[i] = samples.get(i * numChannels + channelIx);
   }
   return res;
 }

原始视频buffer

ByteBuffer模式视频缓冲区根据他们的颜色格式布局。你可以从getCodecInfo().getCapabilitiesForType .colorFormats(…)得到支持的颜色格式数组。视频编解码器支持三种颜色格式:

  • native raw video format: 带有COLOR_FormatSurface标记,并用于输入/输出到Surface.
  • flexible YUV buffers (带有COLOR_FormatYUV420Flexible): 当在 ByteBuffer 模式下,通过getInput/OutputImage(int)方法,也用于输入/输出到Surface.
  • specific formats: 这些都是通常只支持ByteBuffer模式,一些特定于供应商的颜色格式,其他人则MediaCodecInfo.CodecCapabilities中定义,对于color formats ,相当于一个灵活的格式,你依旧可以使用getInput/OutputImage(int)

自LOLLIPOP_MR1开始,所有视频编解码器支持灵活的YUV 4:2:0的buffer

本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/53183718

MediaCodec状态

在codec的生命周期的概念上存在三种状态之一:Stopped, Executing or Released. 。停止所有状态实际上是聚集的三种状态:Uninitialized, Configured and Error,而执行状态概念上的进展通过三个子状态:Flushed, Running and End-of-Stream。


技术分享

当您使用一个工厂方法创建一个codec时,codec处于未初始化的状态。首先,您需要配置它通过configue(…),这让它处于配置的状态,然后调用start()方法将它移动到Executing状态。在这种状态下你可以通过上述缓冲队列操作过程数据。
执行状态有三个子状态:Flushed, Running and End-of-Stream。在start()方法调用后,codec会立刻刷新子状态,它拥有所有的buffer。一旦第一个输入buffer从列中移除,codec将花费比较长的时间移动到正在运行的子状态上。当你队列的输入buffer带有end-of-stream标记,codec将转换到end-of-stream子状态。在这种状态下的codec不再接受进一步输入buffer,但仍然生成输出缓冲区,直到达到end-of-stream输出。你可以移回到Flushed子状态在任何时候当在Executing 状态时使用flush()方法。

调用stop()方法返回的codec未初始化(Uninitialized)状态,因为它可能是再次被配置了。当你再使用一个编解码器(codec),你必须释放它通过调用release()方法。
在极少数情况下的codec可能会遇到一个错误,Error状态。这是沟通使用无效的返回值从一个队列操作,或者通过一个例外。调用reset()方法让编解码器(codec)能再次可用。你可以调用它 把当前的任何状态转移到Uninitialized状态,否则,调用release()方法,回到终端Released状态。

本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/53183718

Codec-specific数据

一些格式,特别是AAC音频和MPEG4,H.264和H.265视频格式需要通过大量的buffer包含设置数据,或codec特定数据来作为真实数据的开头。在处理这样的压缩格式时,在任意帧数据开始前,调用start方法后,这个数据必须提交给codec。当在调用queueInputBuffer时,这些数据必须使用flag:BUFFER_FLAG_CODEC_CONFIG。

Codec-specific数据也可以包含在ByteBuffer条目的格式传递给配置钥匙“csd-0”、“csd-1”,等等。这些钥匙总是包含在跟踪MediaFormat从MediaExtractor获得。Codec-specific数据格式自动提交给codec在start调用;你不能明确地提交这些数据。如果不包含编码特定的数据格式,你可以选择提交使用指定的缓冲区以正确的顺序,按照格式要求。对于H.264 AVC,您还可以连接所有codec-specific数据并提交它作为单个codec-config缓冲区。
Android使用以下codec-specific数据缓冲区。这些也需要跟踪格式中设置适当的MediaMuxer track 配置。每个参数设置和codec-specific-data部分标注(*)必须从以”\x00\x00\x00\x01”开始。


技术分享

注意:必须注意codec在start之后马上刷新,在任何output buffer或输出格式被改变被返回之前, codec specific data也许会丢失在刷新过程中。在这种刷新时,要确保合适的codec操作不出问题,必须要重提交data,使用带BUFFER_FLAG_CODEC_CONFIG标志的buffers。

在任意有效的output buffers输出的buffer带有codec-config 标志前,编码器(或codec生成压缩数据)将创建并返回编码特定的数据。buffer包含codec-specific-data没有意义的时间戳。
本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/53183718

Codec数据处理过程

每个codec维护的一组输入和输出buffer被指向在API调用的buffer-ID。成功调用start()方法后,client“拥有”输入和输出buffer。在同步模式下,调用dequeueInput / OutputBuffer(…)来获得(所有权)codec的输入或输出buffer。在异步模式下,您通过MediaCodec.Callback.onInput / OutputBufferAvailable(…)回调方法将自动接收buffer。
在获得一个输入缓冲区,填入数据并提交它的codec使用queueInputBuffer——或者queueSecureInputBuffer如果使用解密。不要使用相同的时间戳提交多个输入buffer(除非codec-specific数据被标记为)。

codec在异步模式下通过onOutputBufferAvailable回调方法将返回一个只读输出buffer,或在同步模式下响应dequeuOutputBuffer调用。在输出buffer被处理后,调用一个releaseOutputBuffer方法返回codec的buffer。

当你不需要重新提交/立即释放codec的buffer,抓住输入和/或输出buffer可能在codec中延后,这种情况和设备相关。具体地说,codec可能暂缓生成输出buffer,直到所有buffer的未发布/重新提交。因此,试图得到可用buffer尽可能少。
根据API版本,您可以在三个方面:处理数据


技术分享

异步处理使用缓冲区
自LOLLIPOP开始,首选的方法处理异步过程数据是通过设置一个回调在调用配置之前。异步模式改变了状态转换,因为刷新后必须调用start()方法的codec过渡到正在运行的子状态,开始接收输入buffer。同样,在一个初始直接调用开始后,codec将正在运行的子状态,开始通过回调传递可用的输入buffer。


技术分享

本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/53183718

案例

此前我们曾用MediaPlayer播放网络视频及本地视频,今天不用MediaPlayer,转而用MediaCodec来Codec一个视频到SurfaceView上,下面看代码:


技术分享
技术分享
技术分享
技术分享

效果图


技术分享

案例工程已上传到github:https://github.com/hejunlin2013/MultiMediaSample,之前其他多媒体框架相关案例也在此仓库中。

第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。


技术分享

如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android Multimedia框架总结(二十)MediaCodec状态周期及Codec与输入/输出Buffer过程(附实例)