Android 多媒体——MediaCodec编码AAC
Posted VNanyesheshou
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 多媒体——MediaCodec编码AAC相关的知识,希望对你有一定的参考价值。
Android 音频开发——AudioRecord录音
Android 音频开发——AudioTrack播放
Android 多媒体——MediaCodec编码AAC
MeidaCodec 官方地址:https://developer.android.google.cn/reference/android/media/MediaCodec
通过AudioRecord录制的音频数据(pcm格式),占用空间较大,消耗内存和磁盘,更不适合用于网络传输。而AAC是比较通用的压缩格式,这里整理下MeidaCodec编码AAC。
1. MediaCodec
MediaCodec类用于访问编码器、解码器组件。它是多媒体架构的一部分(通常与MediaExtractor
,MediaSync
,MediaMuxer
,MediaCrypto
, MediaDrm
,Image
,Surface
,AudioTrack
,AndroidRecord
一起使用)
简单说,编解码器处理输入数据以生成输出数据。MediaCodec异步处理数据,并使用一组输入和输出缓冲区。
流程:
- 请求(或接收)一个空的输入缓冲区;
- 将缓冲区填入数据;
- 将缓冲区加入队列;
- 编解码器处理输入数据,并将数据转换输出缓冲区;
- 请求(或接收)一个输出缓冲区;
- 获取输出缓冲区的数据;
- 释放输出缓冲区;
2. 处理方式
处理方式 | API版本<= 20 Jelly Bean / KitKat | API版本> = 21 Lollipop及更高版本 |
---|---|---|
使用缓冲区数组的同步API | 支持的 | 不推荐使用 |
使用缓冲区的同步API | 无法使用 | 支持的 |
使用缓冲区的异步API | 无法使用 | 支持的 |
使用缓冲区的异步处理
首选方法是通过在调用configure
之前设置回调来异步处理数据。如下示例:
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();
使用缓冲区的同步处理
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();
3. 流末处理
到达输入数据的末尾时,必须通过调用queueInputBuffer
将BUFFER_FLAG_END_OF_STREAM
标志发送给编解码器 。可以在最后一个有效的输入缓冲区上执行此操作,也可以通过提交附加的空输入缓冲区(设置了流结束标志)来执行此操作。如果使用空缓冲区,则时间戳将被忽略。
编解码器将继续返回输出缓冲区,直到在 dequeueOutputBuffer
或onOutputBufferAvailable
接收到相同流结束标志。这可以在最后一个有效的输出缓冲区上设置,也可以在最后一个有效的输出缓冲区后的空白缓冲区上设置。这种空缓冲区的时间戳应该被忽略。
在发出输入流结束信号后不要提交其他输入缓冲区,除非已 flushed, or stopped and restarted
4. 具体实现
音频录制pcm可以参考之前的文章,这里就不贴了。
1 获取指定 type(audio/mp4a-latm)对应的 编码信息;
编码器名称示例:c2.android.aac.encoder、OMX.google.aac.encoder
public static MediaCodecInfo getEncoderCodecInfo(String mimeType)
MediaCodecInfo[] mediaCodecInfos =
new MediaCodecList(MediaCodecList.REGULAR_CODECS).getCodecInfos();
MediaCodecInfo mediaCodecInfo = null;
for (MediaCodecInfo codecInfo : mediaCodecInfos)
if (!codecInfo.isEncoder())
continue;
String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++)
if (types[j].equalsIgnoreCase(mimeType))
return codecInfo;
return null;
2 创建MediaCodec
MediaCodec 有两种创建编码器的方式
- 通过编码器名称,createByCodecName(@NonNull String name)
- 通过编码类型,MediaCodec createEncoderByType(@NonNull String type)
这里使用的是第一种。
private void initMediaCodec(int sampleRate,
int channelCount)
MediaCodecInfo info = MediaUtil.getEncoderCodecInfo(AAC_TYPE);
if (info != null)
try
mCodec = MediaCodec.createByCodecName(info.getName());
mediaFormat = MediaFormat.createAudioFormat(AAC_TYPE
, sampleRate,
channelCount);
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
aacLevel);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize);
mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING,
AudioFormat.ENCODING_PCM_16BIT);
mCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
catch (IOException e)
e.printStackTrace();
if (mCodec == null)
mCodec.release();
mCodec = null;
3 配置mediaformat
代码如上所示;
mCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
4 开始编码
mCodec.start();
5 处理数据
编码pcm数据,并将编码后的aac 数据添加adts 头信息,写入文件。
private void encodeAudioDataToFile()
Log.i(TAG, "encodeAudioDataToFile start");
FileOutputStream out;
try
out = new FileOutputStream(audioFile);
catch (FileNotFoundException e)
e.printStackTrace();
out = null;
long totalSize = 0;
if (out != null)
try
//应该够保存编码后的aac数据
byte[] aacData = new byte[bufferSize / 5];
byte[] aacHeader = new byte[AAC_HEADER_SIZE];
//首次主要设置header的1~3、7字节,后续只需要修改4~6字节即可。
AacUtil.addADTStoPacket(aacLevel, sampleRate, channelCount, aacHeader, 0);
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!isEncodeEnd)
int inputBufferId = mCodec.dequeueInputBuffer(TIME_OUT);
if (inputBufferId >= 0)
BufferPool.AudioData audioData = mBufferPool.pollDataBuffer(TIME_OUT,
TimeUnit.MILLISECONDS);
if (audioData != null)
ByteBuffer inputBuffer = mCodec.getInputBuffer(inputBufferId);
byte[] data = audioData.data;
inputBuffer.put(data, 0, audioData.readSize);
mCodec.queueInputBuffer(inputBufferId, 0, audioData.readSize, 0,
0);
mBufferPool.deque(audioData);
else if (audioData == null && isRecording)
mCodec.queueInputBuffer(inputBufferId, 0, 0, 0,
0);
else
mCodec.queueInputBuffer(inputBufferId, 0, 0, 0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
int outputBufferId = mCodec.dequeueOutputBuffer(bufferInfo, TIME_OUT);
if (outputBufferId >= 0)
ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufferId);
if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM)
Log.i(TAG, "BUFFER_FLAG_END_OF_STREAM");
isEncodeEnd = true;
else
if (outputBuffer != null && bufferInfo.size > 0)
int len = bufferInfo.size;
outputBuffer.get(aacData, 0, bufferInfo.size);
//设置header
AacUtil.setADTSPacketLen(channelCount, aacHeader,
len + AAC_HEADER_SIZE);
totalSize += len;
out.write(aacHeader);
out.write(aacData, 0, len);
// bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.
mCodec.releaseOutputBuffer(outputBufferId, false);
else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
mediaFormat = mCodec.getOutputFormat(); // option B
catch (IOException | InterruptedException e)
e.printStackTrace();
finally
try
out.close();
catch (IOException e)
e.printStackTrace();
mBufferPool.clear();
mBufferPool = null;
mCodec.stop();
mCodec.release();
mCodec = null;
6 停止编码,释放
mCodec.stop();
mCodec.release();
mCodec = null;
其中上述的 mBufferPool用来管理pcm数据,如果嫌麻烦可以在AudioRecord读取后,直接进行操作。
源代码路径:https://github.com/pyzhangfan/audio-sample-app
以上是关于Android 多媒体——MediaCodec编码AAC的主要内容,如果未能解决你的问题,请参考以下文章
Android媒体解码MediaCodec MediaExtractor学习
Android 音视频 - MediaCodec 编解码音视频
简单高效易用Windows/Linux/ARM/Android/iOS平台实现RTMP推送组件EasyRTMP-Android MediaCodec硬编码流程介绍