Android音视频处理之MediaCodec

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);     // 创建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状态。

 

 

 

 

 

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值