博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android音视频处理之MediaCodec
阅读量:6315 次
发布时间:2019-06-22

本文共 5767 字,大约阅读时间需要 19 分钟。

MediaCodec是Android中媒体编解码器,可以对媒体进行编/解码

MediaCodec采用同步/异步方式处理数据,并且使用了一组输入输出缓存(ByteBuffer)。通过请求一个空的输入缓存(ByteBuffer),向其中填充满数据并将它传递给编解码器处理。编解码器处理完这些数据并将处理结果输出至一个空的输出缓存(ByteBuffer)中。使用完输出缓存的数据之后,将其释放回编解码器

MediaCodec的生命周期有三种状态:停止态-Stopped、执行态-Executing、释放态-Released

停止状态(Stopped)包括了三种子状态:未初始化(Uninitialized)、配置(Configured)、错误(Error)。

执行状态(Executing)会经历三种子状态:刷新(Flushed)、运行(Running)、流结束(End-of-Stream)

(1)当创建了一个MediaCodec对象,此时MediaCodec处于Uninitialized状态。

MediaCodec主要提供了createEncoderByType(String type)、createDecoderByType(String type)两个方法来创建编解码器,它们均需要传入一个MIME类型多媒体格式。常见的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 复制代码

String AUDIO_MIME = "audio/mp4a-latm";MediaCodec mAudioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME);复制代码

MediaCodec还提供了一个createByCodecName (String name)方法,支持使用组件的具体名称来创建编解码器。但是该方法使用起来有些麻烦,且官方是建议最好是配合MediaCodecList使用,因为MediaCodecList记录了所有可用的编解码器。另外,我们也可以使用该类对传入的minmeType参数进行判断,以匹配出MediaCodec对该mineType类型的编解码器是否支持。以指定MIME类型为"video/avc"为例,代码如下:

private static MediaCodecInfo selectCodec(String mimeType) {     // 获取所有支持编解码器数量     int numCodecs = MediaCodecList.getCodecCount();     for (int i = 0; i < numCodecs; i++) {        // 编解码器相关性信息存储在MediaCodecInfo中         MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);         // 判断是否为编码器         if (!codecInfo.isEncoder()) {             continue;         }        // 获取编码器支持的MIME类型,并进行匹配         String[] types = codecInfo.getSupportedTypes();         for (int j = 0; j < types.length; j++) {             if (types[j].equalsIgnoreCase(mimeType)) {                 return codecInfo;             }         }     }     return null; }复制代码

(2)创建完MediaCodec之后,需要使用configure(…)方法对MediaCodec进行配置,这时MediaCodec转为Configured状态。 

编解码器配置使用的是MediaCodec的configure方法,在配置时,configure方法需要传入format、surface、crypto、flags参数,其中format为MediaFormat的实例,它使用”key-value”键值对的形式存储多媒体数据格式信息;surface用于指明解码器的数据源来自于该surface;crypto用于指定一个MediaCrypto对象,以便对媒体数据进行安全解密;flags指明配置的是编码器(CONFIGURE_FLAG_ENCODE)。

MediaFormat mFormat = MediaFormat.createVideoFormat("video/avc", 640 ,480);     // 创建MediaFormatmFormat.setInteger(MediaFormat.KEY_BIT_RATE,600);       // 指定比特率mFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30);  // 指定帧率mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,mColorFormat);  // 指定编码器颜色格式  mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); // 指定关键帧时间间隔mVideoEncodec.configure(mFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);复制代码

(3)配置完MediaCodec之后,调用MediaCodec的start()方法使其转入Executing状态。

在调用start()方法后MediaCodec立即进入Flushed子状态,此时MediaCodec会拥有所有的缓存。一旦第一个输入缓存(input buffer)被移出队列,MediaCodec就转入Running子状态,这种状态占据了MediaCodec的大部分生命周期。当你将一个带有end-of-stream marker标记的输入缓存入队列时,MediaCodec将转入End-of-Stream子状态。在这种状态下,MediaCodec不再接收之后的输入缓存,但它仍然产生输出缓存直到end-of- stream标记输出。你可以在Executing状态的任何时候通过调用flush()方法返回到Flushed子状态。

下面看看整个过程:

调用MediaCodec的start()方法,此时MediaCodec处于Executing状态,可以通过getInputBuffers()方法和getOutputBuffers()方法获取缓存队列:

mAudioEncoder.start();mAudioInputBuffers = mAudioEncoder.getInputBuffers();mAudioOutputBuffers = mAudioEncoder.getOutputBuffers();复制代码

当MediaCodec处于Executing状态之后就可以对数据进行处理了。

  • 首先通过dequeueInputBuffer(long timeoutUs)请求一个输入缓存,timeoutUs代表等待时间,设置为-1代表无限等待:

int inputBufIndex = mAudioEncoder.dequeueInputBuffer(1000);复制代码

返回的整型变量为请求到的输入缓存的index,通过getInputBuffers()得到的是输入缓存数组,通过index和输入缓存数组可以得到当前请求的输入缓存,在使用之前要clear一下,避免之前的缓存数据影响当前数据

mInputBuffer = mAudioInputBuffers[inputBufIndex];mInputBuffer.clear();复制代码

也可以直接通过mAudioEncoder.getInputBuffer(inputBufIndex)得到输入缓存

  • 接着就是把数据添加到输入缓存中,并调用queueInputBuffer(...)把缓存数据入队

mInputBuffer.put(bytes, 0, BUFFER_SIZE_IN_BYTES);mAudioEncoder.queueInputBuffer(inputBufIndex, 0, BUFFER_SIZE_IN_BYTES, (1000000 * mEncodedSize / AUDIO_BYTE_PER_SAMPLE), 0);复制代码

获取输出缓存和获取输入缓存类似

  • 首先通过dequeueOutputBuffer(BufferInfo info, long timeoutUs)来请求一个输出缓存,这里需要传入一个BufferInfo对象,用于存储ByteBuffer的信息

int outputBufIndex = mAudioEncoder.dequeueOutputBuffer(mOutBufferInfo, 1000);复制代码

public final static class BufferInfo {    public void set(            int newOffset, int newSize, long newTimeUs, @BufferFlag int newFlags) {        offset = newOffset;        size = newSize;        presentationTimeUs = newTimeUs;        flags = newFlags;    }    public int offset // 偏移量    public int size;    // 缓存区有效数据大小    public long presentationTimeUs; // 显示时间戳    public int flags;                   // 缓存区标志    @NonNull    public BufferInfo dup() {        BufferInfo copy = new BufferInfo();        copy.set(offset, size, presentationTimeUs, flags);        return copy;    }};复制代码

  • 然后通过返回的index得到输出缓存,并通过BufferInfo获取ByteBuffer的信息,我们可以得到当前数据是否Codec-specific Data:

mOutBuffer = mAudioOutputBuffers[outputBufIndex];if ((mOutBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {    // Codec-specific Data, 这里可以从ByteBuffer中获取csd参数    // audioFormat.setByteBuffer("csd-0", mOutBuffer);} else {    // 处理数据}mAudioEncoder.releaseOutputBuffer(outputBufIndex, false);复制代码

也可以直接通过mAudioEncoder.getOutputBuffer(outputBufIndex)得到输出缓存

注意一定要调用releaseOutputBuffer方法。

通过调用stop()方法使MediaCodec返回到Uninitialized状态,因此这个MediaCodec可以再次重新配置 。

当使用完MediaCodec后,必须调用release()方法释放其资源。

在极少情况下MediaCodec会遇到错误并进入Error状态。这个错误可能是在队列操作时返回一个错误的值或者有时候产生了一个异常导致的。通过调用 reset()方法使MediaCodec再次可用。你可以在任何状态调用reset()方法使MediaCodec返回到Uninitialized状态。否则,调用 release()方法进入最终的Released状态。

参考文章: 

https://www.jianshu.com/p/30e596112015 https://blog.csdn.net/AndrExpert/article/details/79578149复制代码

你可能感兴趣的文章
我是一个硬盘
查看>>
块级元素与内联元素
查看>>
二次剩余Cipolla算法学习笔记
查看>>
树形DP URAL 1039 Anniversary Party
查看>>
String类的一些常用方法
查看>>
hdu 4122(RMQ)2011福州现场赛B题
查看>>
小组项目冲刺第四天的个人总结
查看>>
Two Sum II - Input array is sorted(leetcode167) - Solution2
查看>>
Mybatis入门
查看>>
nyoj198数数
查看>>
2019.2.15 t2
查看>>
[bzoj 4833]最小公倍佩尔数
查看>>
17、ListView & GridView
查看>>
maya,mel,eval,stringarray
查看>>
ACM题解系列之三:秋田拓哉:《挑战程序设计竞赛》(第2版)
查看>>
HDU1576 A/B【扩展欧几里得算法】
查看>>
mondb 常用命令学习记录
查看>>
泛型委托
查看>>
win+ R下的常见命令
查看>>
ubuntu右键添加打开终端的快捷菜单
查看>>