前面我们知道了采集音频,播放音频,保存音频数据,我们知道PCM纯音频数据没有经过压缩编码处理的数据是很大的。很有必要了解编解码来处理这个问题。
简介
音视频的编码方式分为两种。
- 硬编码:
用设备GPU去实现编解码,这样可以减轻CPU的压力。 - 软编码:
让CPU来进行编解码,在c层代码来进行编解码,因为c/c++有很多好的编解码库。 - 软硬编码对比:
硬编的好处主要在于速度快,而且系统自带不需要引入外部的库,但是特性支持有限,而且硬编的压缩率一般偏低,而对于软编码来说,虽然速度较慢,但是压缩率比较高,而且支持的H264特性也会比硬编码多很多,相对来说比较可控。就可用性而言,在4.4+的系统上,MediaCodec的可用性是能够基本保证的,但是不同等级的机器的编码器能力会有不少差别,建议可以根据机器的配置,选择不同的编码器配置。
在Android 4.1之前没有提供硬编解码的API,所以基本都是采用开源的那些库,比如著名的FFMpeg实现软编解码。但是通常情况下,同一平台同一硬件环境,硬编码的速度快于软件编码,软编码使用CPU来进行计算,会消耗一些app的运算效率。在Android4.1出来了一个新的API:MediaCodec可以支持硬编解码。MediaCodec可以支持对音频和视频的编解码,我们就要学会使用它来进行音频的编解码操作。
MediaCodec
简介
对于API的介绍我们的第一反应,看官网
还发现了一个哥们翻译的中文版本
总结来说,MediaCodec它是官方提供的硬编码API,首先对他进行参数的配置,然后把数据扔给它,它在内部完成编码或者解码的工作,然后把处理好的数据输出给我们。
贴一张官网的处理图片:
解析:
MediaCodec采用了两个缓冲区队列,异步处理数据。
- 客户端从input缓冲区队列申请empty buffer(调用dequeueInputBuffer方法)
- 客户端将要编解码的数据拷贝到empty buffer,然后放入input缓冲区队列(调用queueInputBuffer方法)
- MediaCodec内部从input缓冲区队列取出一帧数据进行编解码处理
- 处理结束后将原始数据buffer置为empty再放回input缓冲区队列,将编解码的数据放入到output缓冲区队列
- 客户端从output缓冲区队列申请编解码的buffer(调用dequeueoutputBuffer方法)
- 客户端对编解码后的buffer数据进行渲染或者播放
- 客户端处理完上面的步骤后再将该buffer放回到output缓冲区队列(调用releaseOutputBuffer)
MediaCodec使用流程
- 创建MediaCodec(调用createDecoderByType方法)
- 配置MediaCodec(调用configure方法)
- 开始编解码(调用start方法)
- 循环数据输入输出
- dequeueInputBuffer
- queueInputBuffer
- dequeueOutputBuffer
- releaseOutputBuffer
- 停止编解码(调用stop方法)
- 释放资源(调用release方法)
MediaCodec使用代码
摘自官网代码示例
- 同步使用
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
codec.start();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(…);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
outputFormat = codec.getOutputFormat(); // option B
}
}
codec.stop();
codec.release();
- 异步使用
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is equivalent to mOutputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
}
@Override
void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
mOutputFormat = format; // option B
}
@Override
void onError(…) {
…
}
});
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat(); // option B
codec.start();
// wait for processing to complete
codec.stop();
codec.release();
- Android 5.0以下使用
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
codec.start();
//API的区别在这里
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(…);
if (inputBufferId >= 0) {
// fill inputBuffers[inputBufferId] with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
// outputBuffers[outputBufferId] is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
outputBuffers = codec.getOutputBuffers();
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
MediaFormat format = codec.getOutputFormat();
}
}
codec.stop();
codec.release();
我们实现一个实时的音频录制播放的demo,查看