Android:MediaCodec基本原理

最近需要使用MediaCodec做一些工作,因此对MediaCodec做了些研究和代码编写,在此先对MediaCodec的一些基础原理、工作流程、常用API等做个初步总结,方便后续开发过程中查阅。


1.MediaCodec简介

1.1 MediaCodec来历

Android从最初的API 1.0版本开始,集成了一种使用起来比较简单,用于播放音视频文件,但是只支持MP4、3GPP两种媒体格式的MediaPlayer,为了播放不支持的格式,许多开发人员不得不在开发过程中集成FFmpeg软件解码器

Android 4.1(API16),MediaCodec首个版本推出,开发者在应用层就能直接调用底层编解码器,实现丰富的音视频功能

Android 4.3(API18),MediaCodec引入Surface和OpenGLES,可以用OpenGLES对数据进行渲染,使用摄像头的预览数据作为输入

Android 5.0,API21,引入了异步模式,MediaCodec可以对输入数据和输出数据进行异步处理

1.2 MediaCodec是什么?

MediaCodec对应用端提供Java Api,其内部则通过调用各native方法实现对Android底层多媒体编解码器的调用,完成上层应用对音视频文件的编码和解码

开发者用到的是MediaCodec.java,它是android.media框架中的一个类

MediaCodec.java 位于Android源码:/frameworks/base/media/java/android/media/

谷歌developer网站上关于MediaCodec的详细注解:

MediaCodec  |  Android Developers

1.3 android.media

android.media框架是Framework中的多媒体管理模块,它并不是紧密耦合的单个模块,而是多个media相关功能组件的联合

AndroidN之前,framework中android.media的所有模块(包括播放、录制、camera、audio等)都在mediaserver进程中

AndroidN开始,出于安全性考虑(“stagefright安全漏洞“),mediaserver拆分为以下几个进程:
1)mediaserver:包括播放功能Mediaplayer和录制功能Mediarecoder;
2)mediacodec:音视频编解码功能;
3)mediaextractor:视频文件解封装;
4)cameraserver:相机服务;
5)audioserver:音频输出模块;
6)mediadrm:数字版权加解密;

也就是说MediaCodec是Android Media框架下,从MediaServer拆分出来的一个专门用于音视频编解码的进程 


2.MediaCodec工作机制

2.1 关联功能类

MediaCodec.java只是Framework中android.media里的一个类,在使用MediaCodec Java API对音视频文件进行编解码时,需要联合其他media功能文件一起使用,这一点在谷歌的官方注解网站里也有说明

常用的联合使用的功能类主要有如下:

 android.media.MediaExtractor;
 android.media.MediaSync;
 android.media.MediaMuxer;
 android.media.MediaFormat;
 android.media.MediaCrypto;
 android.media.MediaDrm;
 android.media.MediaCodec.BufferInfo;
 android.media.Image;
 android.media.AudioTrack;
 android.view.Surface;
 ......

2.2 MediaCodec工作流程

官网注解图:

MediaCodec的工作流程就是:处理输入数据产生输出数据

MediaCodec运行过程中的数据传输,可以看成两路数据流:Input流、Output流
  ● Input流:客户端输入的待编码或解码的数据
  Output流:客户端输出的已编码或解码的数据

MediaCodec使用两组Buffer队列,通过同步异步方式处理两路数据流:
  包含一组InputBuffer(格式ByteBuffer)的InputBufferQueue
  包含一组OutputBuffer(格式ByteBuffer)的OutputBufferQueue

1)Client也就是调用者从MediaCodec的InputBufferQueue中获取空的InputBuffer,写入要编码或解码的数据,提交给MediaCodec

2)MediaCodec收到数据进行处理,处理完毕后,将其转存到OutputBufferQueue中空的OutputBuffer(拷贝过程由mediacodec内部完成,调用者只需要关注OutputBufferQueue中空闲Buffer索引的变化),同时释放InputBuffer,将它重新放回到InputBufferQueue

3)Client从MediaCodec的OutputBufferQueue中获取到一个空的OutputBuffer,读取数据进行使用,待Client处理完数据后,释放OutputBuffer并将其放回OutputBufferQueue

上述过程不断重复
直至MediaCodec将数据处理完毕后,手动释放OutputBuffer

另外需要先注解一下的是:

同步模式:编解码器对Input和Output的数据处理是同步的,
                  数据输入和数据输出依次进行,
                  上一次数据输入后才能输出数据,
                  上一次数据输出后才能再次进行数据输入。

异步模式:编解码器对Input和Output的数据处理是异步的,
                  数据输入和数据输出操作相互独立,
                  底层服务判断何时可以进行输入/输出,然后进行相应回调,
                  开发者在回调中进行数据输入/输出处理。

2.3 MediaCodec Buffer与底层交互

MediaCodec Buffer与底层Acodec,以及OpenMAX标准的交互流程:


3.MediaCodec状态转换

3.1 MediaCodec的几种状态

MediaCodec有三种状态:Stopped、Executing、Released
其中StoppedExecuting状态又分别包含三个子状态

所以MediaCodec状态可以细分为如下:
1) 停止态(Stopped):
      1.1) 未初始化状态(Uninitialized
      1.2) 配置状态(Configured
      1.3) 错误状态(Error

2) 执行态(Executing):
      2.1) 刷新状态(Flushed
      2.2) 运行状态( Running
      2.3) 流结束状态(End-of-Stream

3) 释放态(Released)

MediaCodec在不同的数据处理模式下状态间的转换会有些许差别,
接下来我们分别对同步模式异步模式下的状态转换做一些分析

3.2 同步模式下的状态转换

官网示图:


 

1) createDecoderByType(...) / createEncoderByType(...) / createByCodecName(...)
调用三个方法中的任意一个创建一个MediaCodec对象实例后,
MediaCodec处于Stopped:Uninitialized子状态

2) MediaCodec.configure(...)配置,切换到StoppedConfigured子状态

3) MediaCodec.start()启动,切换到ExecutingFlushed子状态。
MediaCodec此时拥有全部InputBuffer和OutputBuffers的所有权,Client无法操作这些Buffers,
但是MediaCodec在Flush状态下并不进行任何内部数据传送和编解码处理

4) MediaCodec.dequeueInputBuffer()请求到一个有效的InputBuffer Index, 第一个InputBuffer出队列时,
MediaCodec立即进入到了Executing:Running子状态

5) Executing:Running状态下,MediaCodec进行数据处理(解码、编码)工作,完成生命周期的主要阶段

6) MediaCodec.queueInputBuffer(... , MediaCodec.BUFFER_FLAG_END_OF_STREAM)被调用,输入端带有"End-Of-Stream"标记的InputBuffer加入InputBufferQueue,MediaCodec随即进入Executing:End of Stream子状态,且不再接受新的InputBuffer数据,
但是仍然会处理完之前InputBufferQueue中未处理的InputBuffer并产生OutputBuffer,直到"End-Of-Stream"标记到达输出端,数据处理的过程也随即终止

7) Executing状态下,MediaCodec.flush(),切换到Executing:Flushed子状态

8) Executing状态下,MediaCodec.stop()MediaCodec.reset(),切换到Stopped:Uninitialized子状态

9) MediaCodec运行出错时会切换到Stopped:Error状态,可以调用MediaCodec.reset()进行重置,使其再次可用

10) MediaCodec数据处理任务完成或不再需要使用MediaCodec时,MediaCodec.release()方法释放其资源,MediaCodec切换到Released状态

3.3 异步模式下的状态转换

官网示图:

异步模式下状态转换与同步模式下大同小异,主要有两点区别

1) MediaCodec.start()启动,会直接切换到Executing:Running子状态;

2) MediaCodec.flush()切换到Executing:Flushed子状态后,必须调用MediaCodec.start()才能切换回Executing:Running子状态。

其他情况下均与同步模式下相同,就不在此赘述


4.MediaCodec主要API和数据格式:

4.1 主要API:

编解码器创建和配置:
createDecoderByType():根据特定mime类型(如"video/avc")创建编解码器(MediaCodec实例)
createEncoderBytype(): 同上
createByCodecName:根据组件的确切名称(如OMX.google.mp3.decoder)创建编解码器,使用MediaCodecList可以获取组件的名称
configure():对编解码器进行配置,使编解码器切换到Stopped:Configured状态

常见的mime类型多媒体格式如下:
● “video/x-vnd.on2.vp8” - VP8 video (i.e. video in .webm)
● “video/x-vnd.on2.vp9” - VP9 video (i.e. video in .webm)
● “video/avc”                  - H.264/AVC video
● “video/mp4v-es”          - MPEG4 video
● “video/3gpp”                - H.263 video
● “audio/3gpp”                - AMR narrowband audio
● “audio/amr-wb”            - AMR wideband audio
● “audio/mpeg”               - MPEG1/2 audio layer III
● “audio/mp4a-latm”       - AAC audio (note, this is raw AAC packets, not packaged in LATM!)
● “audio/vorbis”              - vorbis audio
● “audio/g711-alaw”       - G.711 alaw audio
● “audio/g711-mlaw”      - G.711 ulaw audio

编解码器状态控制:
start():编码器启动,切换到ExecutingFlushed状态
stop():编码器结束,返回到Stopped:Uninitialized状态
release():释放实例资源

Surface创建和配置:
createInputSurface():创建输入缓冲Surface
setOutputSurface():设置输出缓冲Surface

Buffer操作:
getInputBuffers():获取InputBufferQueue,返回ByteBuffer[ ]
dequeueInputBuffer():从InputBufferQueue中取InputBuffer
queueInputBuffer():Client往InputBuffer写入数据后,将InputBuffer加入到InputBufferQueue
getOutputBuffers():获取OutputBufferQueue,返回ByteBuffer[ ]
dequeueOutputBuffer():从OutputBufferQueue中取OutputBuffer
releaseOutputBuffer():释放ByteBuffer数据

4.2 数据类型和载体

1) Mediacodecde的数据流有两种载体:ByteBuffer和Surface

    ● 使用ByteBuffer作为数据载体时,可以用Image类和getInput/OutputImage(int)获取原始视频帧

    ● 使用Surface来获取/呈现原始的视频数据时,Surface使用本地的视频Buffer,不需要进行ByteBuffers拷贝,编解码器的效率更高

    ● 通常在使用Surface的时候,无法访问原始的视频数据,但是可以使用ImageReader访问解码后的原始视频帧

2) Mediacodec接受三种数据类型:

2.1)压缩数据:
压缩数据可以作为解码器的输入、编码器的输出,
但是需要指定数据格式,这样MediaCodec才知道如何处理这些压缩数据.

压缩数据支持的格式有:
   ● MediaFormat.KEY_MIME支持的所有格式类型
   ● 对于视频类型,通常是一个单独的压缩视频帧
   ● 对于音频数据,通常是一个单独的访问单元

无论是视频还是音频,buffer都不会在任意字节边界上开始或结束,而是在帧/访问单元边界上开始或结束,除非它们被BUFFER_FLAG_PARTIAL_FRAME标记

2.2)原始音频数据:PCM音频数据帧

原始音频Buffer包含PCM音频数据的整个帧,这是每个通道按通道顺序的一个样本。
每个样本都是一个 AudioFormat#ENCODING_PCM_16BIT

2.3)原始视频数据:
在ByteBuffer模式下,视频buffer根据它们的MediaFormat#KEY_COLOR_FORMAT进行布局.
可以从getCodecInfo(). MediaCodecInfo.getCapabilitiesForType.CodecCapability.colorFormats获取支持的颜色格式。

视频编解码器支持三种颜色格式:   
   ● native raw video format:CodecCapabilities.COLOR_FormatSurface,
                                                  可以与输入/输出的Surface一起使用

   ● flexible YUV buffers:例如CodecCapabilities.COLOR_FormatYUV420Flexible,
                                           可以使用getInput/OutputImage(int)与输入/输出Surface一起使用,
                                           也可以在ByteBuffer模式下使用

   ● other,specific formats:通常只支持ByteBuffer模式;
                                               有些颜色格式是厂商特有的,其他定义在CodecCapabilities;
                                               对于等价于flexible格式的颜色格式,可以使用getInput/OutputImage(int);


5.结语

MediaCodec的基本原理就先介绍到这里,本篇主要是理论讲解,先对MediaCodec的运用有个初步的认知,暂未涉及实践开发和Demo代码演示,后续博文会逐步跟进。

关于MediaCodec,网上优秀的资料和博文也很多,在学习MediaCodec过程中查阅了大量资料,
链接就不一一列举了,其中主要优秀博文链接有:

MediaCodec基本原理及使用_有心好书的博客-CSDN博客_mediacodecMediaCodec的使用介绍 - 简书
MediaCodec基础入门_Kayson12345的博客-CSDN博客
Android MediaExtractor + MediaCodec 实现简易播放器 - 简书
Android MediaCodec 状态(States)转换分析 - 二的次方 - 博客园

猜你喜欢

转载自blog.csdn.net/geyichongchujianghu/article/details/128694561