赞
踩
最近需要使用MediaCodec做一些工作,因此对MediaCodec做了些研究和代码编写,在此先对MediaCodec的一些基础原理、工作流程、常用API等做个初步总结,方便后续开发过程中查阅。
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可以对输入数据和输出数据进行异步处理
MediaCodec对应用端提供Java Api,其内部则通过调用各native方法实现对Android底层多媒体编解码器的调用,完成上层应用对音视频文件的编码和解码
开发者用到的是MediaCodec.java,它是android.media框架中的一个类
MediaCodec.java 位于Android源码:/frameworks/base/media/java/android/media/
谷歌developer网站上关于MediaCodec的详细注解:
MediaCodec | Android Developers
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拆分出来的一个专门用于音视频编解码的进程
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;
......
官网注解图:
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的数据处理是异步的,
数据输入和数据输出操作相互独立,
底层服务判断何时可以进行输入/输出,然后进行相应回调,
开发者在回调中进行数据输入/输出处理。
MediaCodec Buffer与底层Acodec,以及OpenMAX标准的交互流程:
MediaCodec有三种状态:Stopped、Executing、Released
其中Stopped与Executing状态又分别包含三个子状态
所以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在不同的数据处理模式下状态间的转换会有些许差别,
接下来我们分别对同步模式和异步模式下的状态转换做一些分析
官网示图:
1) createDecoderByType(...) / createEncoderByType(...) / createByCodecName(...)
调用三个方法中的任意一个创建一个MediaCodec对象实例后,
MediaCodec处于Stopped:Uninitialized子状态
2) MediaCodec.configure(...)配置,切换到Stopped:Configured子状态
3) MediaCodec.start()启动,切换到Executing:Flushed子状态。
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状态
官网示图:
异步模式下状态转换与同步模式下大同小异,主要有两点区别:
1) MediaCodec.start()启动,会直接切换到Executing:Running子状态;
2) MediaCodec.flush()切换到Executing:Flushed子状态后,必须调用MediaCodec.start()才能切换回Executing:Running子状态。
其他情况下均与同步模式下相同,就不在此赘述
编解码器创建和配置:
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():编码器启动,切换到Executing:Flushed状态
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数据
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);
MediaCodec的基本原理就先介绍到这里,本篇主要是理论讲解,先对MediaCodec的运用有个初步的认知,暂未涉及实践开发和Demo代码演示,后续博文会逐步跟进。
关于MediaCodec,网上优秀的资料和博文也很多,在学习MediaCodec过程中查阅了大量资料,
链接就不一一列举了,其中主要优秀博文链接有:
MediaCodec基本原理及使用_有心好书的博客-CSDN博客_mediacodecMediaCodec的使用介绍 - 简书
MediaCodec基础入门_Kayson12345的博客-CSDN博客
Android MediaExtractor + MediaCodec 实现简易播放器 - 简书
Android MediaCodec 状态(States)转换分析 - 二的次方 - 博客园
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。