Android音视频处理之MediaCodec

MediaCodec是Android中媒体编解码器,能够对媒体进行编/解码

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


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

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

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


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

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"为例,代码以下:
ide

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状态。 
ui

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

MediaFormat mFormat = MediaFormat.createVideoFormat("video/avc", 640 ,480);     // 建立MediaFormat
mFormat.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复制代码
相关文章
相关标签/搜索