在 Android 上使用 AudioRecord 和 MediaCodec 编码 AAC 音频
Posted
技术标签:
【中文标题】在 Android 上使用 AudioRecord 和 MediaCodec 编码 AAC 音频【英文标题】:Encoding AAC Audio using AudioRecord and MediaCodec on Android 【发布时间】:2013-11-18 13:25:34 【问题描述】:我正在尝试使用 android AudioRecord 和 MediaCodec 对 aac 音频进行编码。我创建了一个与 (Encoding H.264 from camera with Android MediaCodec) 非常相似的编码器类。通过这个类,我创建了一个 AudioRecord 实例,并告诉它读取其 byte[] 数据到 AudioEncoder (audioEncoder.offerEncoder(Data))。
while(isRecording)
audioRecord.read(Data, 0, Data.length);
audioEncoder.offerEncoder(Data);
这是我的 AudioRecord 设置
int audiosource = MediaRecorder.AudioSource.MIC;
int sampleRateInHz = 44100;
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
我成功收集了一些 byte[] 数组数据并将其写入本地文件。不幸的是,该文件无法播放。我在网上做了一些搜索,找到了一个相关的帖子(How to generate the AAC ADTS elementary stream with Android MediaCodec)。因此,其他有类似问题的人说主要问题是“MediaCodec 编码器生成原始 AAC 流。原始 AAC 流需要转换为可播放格式,例如 ADTS 流”。所以我尝试添加 ADTS 标头。然而,在我添加了 ADTS 标头之后(我在下面的代码中注释掉了),我的 AudioEncoder 甚至不会写入输出音频文件。 有什么我想念的吗?我的设置正确吗?
欢迎提出任何建议、cmets 和意见,非常感谢。谢谢大家!
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Environment;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class AudioEncoder
private MediaCodec mediaCodec;
private BufferedOutputStream outputStream;
private String mediaType = "audio/mp4a-latm";
public AudioEncoder()
File f = new File(Environment.getExternalStorageDirectory(), "Download/audio_encoded.aac");
touch(f);
try
outputStream = new BufferedOutputStream(new FileOutputStream(f));
Log.e("AudioEncoder", "outputStream initialized");
catch (Exception e)
e.printStackTrace();
mediaCodec = MediaCodec.createEncoderByType(mediaType);
final int kSampleRates[] = 8000, 11025, 22050, 44100, 48000 ;
final int kBitRates[] = 64000, 128000 ;
MediaFormat mediaFormat = MediaFormat.createAudioFormat(mediaType,kSampleRates[3],1);
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[1]);
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
public void close()
try
mediaCodec.stop();
mediaCodec.release();
outputStream.flush();
outputStream.close();
catch (Exception e)
e.printStackTrace();
// called AudioRecord's read
public synchronized void offerEncoder(byte[] input)
Log.e("AudioEncoder", input.length + " is coming");
try
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0)
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);
////trying to add a ADTS
// while (outputBufferIndex >= 0)
// int outBitsSize = bufferInfo.size;
// int outPacketSize = outBitsSize + 7; // 7 is ADTS size
// ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
//
// outputBuffer.position(bufferInfo.offset);
// outputBuffer.limit(bufferInfo.offset + outBitsSize);
//
// byte[] outData = new byte[outPacketSize];
// addADTStoPacket(outData, outPacketSize);
//
// outputBuffer.get(outData, 7, outBitsSize);
// outputBuffer.position(bufferInfo.offset);
//
//// byte[] outData = new byte[bufferInfo.size];
// outputStream.write(outData, 0, outData.length);
// Log.e("AudioEncoder", outData.length + " bytes written");
//
// mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
// outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
//
//
//Without ADTS header
while (outputBufferIndex >= 0)
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
outputStream.write(outData, 0, outData.length);
Log.e("AudioEncoder", outData.length + " bytes written");
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
catch (Throwable t)
t.printStackTrace();
/**
* Add ADTS header at the beginning of each and every AAC packet.
* This is needed as MediaCodec encoder generates a packet of raw
* AAC data.
*
* Note the packetLen must count in the ADTS header itself.
**/
private void addADTStoPacket(byte[] packet, int packetLen)
int profile = 2; //AAC LC
//39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
int freqIdx = 4; //44.1KHz
int chanCfg = 2; //CPE
// fill in ADTS data
packet[0] = (byte)0xFF;
packet[1] = (byte)0xF9;
packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
packet[4] = (byte)((packetLen&0x7FF) >> 3);
packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
packet[6] = (byte)0xFC;
public void touch(File f)
try
if(!f.exists())
f.createNewFile();
catch (IOException e)
e.printStackTrace();
【问题讨论】:
最后,我能够使用 ADTS 技术播放音频。不幸的是,绝对没有声音...... 你有没有想过这个问题?除非 API >= 18,否则显然没有使用 MediaCodec 编码音频的工作示例。 【参考方案1】:检查“testEncoder”方法here了解如何正确使用MediaCodec作为编码器。
在那之后 在您的代码中,
您的输入(录音机)配置为单个音频通道,而您的输出(ADTS 数据包标头)设置为两个通道(chanCfg = 2)。
如果您更改输入采样率(当前为 44.1khz),您还必须更改 ADTS 数据包标头中的 freqIdx 标志。检查此link 以获取有效值。
并且 ADTS 标头配置文件标志设置为“AAC LC”,您也可以在下面找到它 MediaCodecInfo.CodecProfileLevel。 您已设置 profile = 2 即 MediaCodecInfo.CodecProfileLevel.AACObjectLC
【讨论】:
【参考方案2】:您可以使用 Android 的 MediaMuxer 将 MediaCodec 创建的原始流打包成 .mp4 文件。奖励:.mp4 中包含的 AAC 数据包不需要 ADTS 标头。
我有一个working example of this technique on Github。
【讨论】:
感谢您的回复,但是,我的目标是 api 16 (android 4.1).. MediaMuxer 仅适用于 android v 4.3 或 api 18。 我在配置音频编码器时遇到问题。我使用与上述解决方案中提供的音频 MediaEncoder 完全相同的参数收到错误android.media.MediaCodec$CodecException: Error 0x80001001
。我错过了什么明显的东西吗?
但是我需要.aac
扩展文件。我该怎么做?谢谢!
@ch271828n 对于原始 .aac 流,您可能需要原始 MediaCodec 输出,只需跳过 MediaMuxer(它将流打包成 .mp4 等容器格式)。以上是关于在 Android 上使用 AudioRecord 和 MediaCodec 编码 AAC 音频的主要内容,如果未能解决你的问题,请参考以下文章
无法在 Android 中播放使用 AudioRecord 录制的 pcm 文件
Android 调用未初始化的 AudioRecord 错误