Android MediaCodec编码PCM转AAC仅44100采样率可正常解码问题
问题分析与解决方案
你的问题核心在于硬编码的AAC配置参数(csd-0和ADTS头中的采样率索引)没有随采样率变化而动态调整,导致解码器接收到的编码元数据和实际音频流参数不匹配——只有44100Hz采样率刚好匹配你固定的参数,所以能正常解码,其他采样率自然失败。
具体问题点拆解
固定的
csd-0参数
你直接使用了byte[] data = new byte[]{(byte) 0x11, (byte) 0x90};作为csd-0,这个值对应的是:- AAC LC profile
- 采样率索引4(仅对应44100Hz)
- 通道数1(但你的编码配置是双通道
KEY_CHANNEL_COUNT=2)
换采样率时这个参数完全没变化,解码器拿到的配置信息和实际编码的音频参数严重不符,根本无法正确解析。
ADTS头中硬编码的采样率索引
在addADTStoPacket方法里,你固定了int freqIdx = FREQ_IDX;且FREQ_IDX=4,这只对应44100Hz。切换到其他采样率时,ADTS头里的采样率信息还是44100Hz,解码器用错误的参数去解析,必然失败。输入缓冲区的标记错误
你在queueInputBuffer时给每帧PCM数据都加了BUFFER_FLAG_CODEC_CONFIG标记,这个标记只应该用于发送配置数据(比如csd-0),重复发送会干扰编码器的正常工作逻辑。
修复后的代码实现
1. 动态生成适配当前参数的csd-0
根据当前的profile、采样率、通道数计算符合标准的csd-0:
// 替换原来固定的csd-0生成逻辑 private ByteBuffer generateCsd0(int profile, int sampleRate, int channelCount) { int freqIdx = getSampleRateIndex(sampleRate); if (freqIdx == -1) { throw new IllegalArgumentException("Unsupported sample rate: " + sampleRate); } byte[] csd0 = new byte[2]; csd0[0] = (byte) ((profile << 3) | (freqIdx >> 1)); csd0[1] = (byte) (((freqIdx & 0x01) << 7) | (channelCount << 3)); return ByteBuffer.wrap(csd0); } // 映射采样率到AAC标准规定的索引值 private int getSampleRateIndex(int sampleRate) { switch (sampleRate) { case 96000: return 0; case 88200: return 1; case 64000: return 2; case 48000: return 3; case 44100: return 4; case 32000: return 5; case 24000: return 6; case 22050: return 7; case 16000: return 8; case 12000: return 9; case 11025: return 10; case 8000: return 11; case 7350: return 12; default: return -1; } }
然后在prepare方法中替换原来的csd-0设置:
// 替换原来的byte[] data = ...和setByteBuffer部分 ByteBuffer csd_0 = generateCsd0(KEY_AAC_PROFILE, KEY_SAMPLE_RATE, KEY_CHANNEL_COUNT); mediaFormat.setByteBuffer("csd-0", csd_0);
2. 动态生成ADTS头的采样率索引
修改addADTStoPacket方法,根据当前采样率获取对应的索引:
private void addADTStoPacket(byte[] packet, int packetLen) { int profile = KEY_AAC_PROFILE; // AAC LC int freqIdx = getSampleRateIndex(KEY_SAMPLE_RATE); // 动态匹配当前采样率 int chanCfg = KEY_CHANNEL_COUNT; // 直接用配置的通道数,不用硬编码1 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; }
3. 修正输入缓冲区的标记
在encode方法中,BUFFER_FLAG_CODEC_CONFIG只用于发送配置数据,普通PCM数据用0标记:
// 替换原来的queueInputBuffer行 mEncoder.queueInputBuffer(inputBufferIndex, 0, data.length, computePresentationTime(presentationTimeUs), 0);
额外注意事项
- 确保修改
KEY_SAMPLE_RATE时,AudioRecord的初始化参数也保持一致(你的代码已经做到了,但要注意不要遗漏)。 - 部分设备的编码器可能对特定采样率/通道数组合有兼容性限制,建议在选择编码器时,通过
MediaCodecInfo.CodecCapabilities确认当前编码器支持的参数范围。
内容的提问来源于stack exchange,提问作者YLC




