
android 11 中使用 mediaextractor 直接读取音频样本后无法正常播放,是因为 mp4/aac 音频为压缩格式,而 audiotrack 仅接受 pcm 解码后的原始数据,必须通过 mediacodec 完成解码流程。
android 11 中使用 mediaextractor 直接读取音频样本后无法正常播放,是因为 mp4/aac 音频为压缩格式,而 audiotrack 仅接受 pcm 解码后的原始数据,必须通过 mediacodec 完成解码流程。
在 Android 平台上从视频容器(如 MP4)中提取并播放音频,绝不能跳过解码环节。您提供的日志明确显示音频轨道的 MIME 类型为 audio/mp4a-latm——这是 AAC 编码的标准标识。MediaExtractor.readSampleData() 返回的是压缩的 AAC 帧数据(通常为 ADTS 或 LOAS 封装),而非 PCM 样本;若直接将其喂给 AudioTrack(配置为 ENCODING_PCM_16BIT),将导致严重失真、杂音或极短播放(如您观察到的 5 秒噪声),根本原因正是数据格式不匹配。
✅ 正确做法是构建完整的「Extractor → Decoder → AudioTrack」流水线:
- 使用 MediaExtractor 定位音频轨道(根据 mime 类型筛选)
-
用 MediaCodec 创建 AAC 解码器,配置其输入格式(从 extractor 获取
MediaFormat) - 循环:拉取压缩帧 → 推入解码器输入缓冲区 → 从输出缓冲区获取解码后的 PCM 数据 → 写入 AudioTrack
- 注意同步时间戳、缓冲区释放与 EOS 处理
以下是关键代码片段(已适配 Android 11+,含错误检查与资源管理):
private void playExtractedAudio(String videoPath) throws IOException {
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(videoPath);
// 查找音频轨道索引
int audioTrackIndex = -1;
for (int i = 0; i < extractor.getTrackCount(); i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime != null && mime.startsWith("audio/")) {
audioTrackIndex = i;
break;
}
}
if (audioTrackIndex == -1) throw new IllegalStateException("No audio track found");
extractor.selectTrack(audioTrackIndex);
MediaFormat audioFormat = extractor.getTrackFormat(audioTrackIndex);
// 创建 AAC 解码器
MediaCodec decoder = MediaCodec.createDecoderByType(audioFormat.getString(MediaFormat.KEY_MIME));
decoder.configure(audioFormat, null, null, 0);
decoder.start();
// 初始化 AudioTrack(参数需与解码输出一致!)
int sampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
int channelCount = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int channelConfig = (channelCount == 1) ? AudioFormat.CHANNEL_OUT_MONO
: AudioFormat.CHANNEL_OUT_STEREO;
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
sampleRate,
channelConfig,
AudioFormat.ENCODING_PCM_16BIT,
minBufferSize,
AudioTrack.MODE_STREAM
);
audioTrack.play();
ByteBuffer[] inputBuffers = decoder.getInputBuffers();
ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
boolean isInputDone = false;
boolean isOutputDone = false;
long startTime = System.nanoTime();
while (!isOutputDone) {
// 1. 填充输入缓冲区(压缩 AAC 帧)
if (!isInputDone) {
int inputBufIndex = decoder.dequeueInputBuffer(10000);
if (inputBufIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufIndex];
inputBuffer.clear();
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) {
decoder.queueInputBuffer(inputBufIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
isInputDone = true;
} else {
long presentationTimeUs = extractor.getSampleTime();
decoder.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTimeUs, 0);
extractor.advance();
}
}
}
// 2. 拉取解码后的 PCM 输出
int outputBufIndex = decoder.dequeueOutputBuffer(info, 10000);
if (outputBufIndex >= 0) {
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
isOutputDone = true;
}
if (info.size > 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufIndex];
outputBuffer.position(info.offset);
outputBuffer.limit(info.offset + info.size);
// 直接写入 AudioTrack(无需额外拷贝!)
byte[] pcmData = new byte[info.size];
outputBuffer.get(pcmData);
audioTrack.write(pcmData, 0, info.size);
}
decoder.releaseOutputBuffer(outputBufIndex, false);
}
}
// 清理资源
audioTrack.stop();
audioTrack.release();
decoder.stop();
decoder.release();
extractor.release();
}⚠️ 关键注意事项:
-
采样率/声道数必须严格匹配:
AudioTrack的sampleRate和channelConfig必须与MediaFormat中的KEY_SAMPLE_RATE/KEY_CHANNEL_COUNT一致,否则播放异常; -
避免内存拷贝:示例中仍使用
get()提取字节数组以保持清晰性,生产环境建议用audioTrack.write(outputBuffer, info.size, AudioTrack.WRITE_BLOCKING)直接传递ByteBuffer,减少 GC 压力; -
AAC 特殊处理:若视频含 ADTS 头,
MediaExtractor通常已剥离,但若遇到解码失败,需检查csd-0(codec-specific data)是否已正确传入MediaFormat(本例中 extractor 已自动处理); -
权限与后台限制:Android 11 要求
READ_EXTERNAL_STORAGE(若访问外部存储),且前台服务需声明FOREGROUND_SERVICE权限以保障音频持续播放。
总结:没有“绕过解码”的捷径。MediaExtractor 是容器解析器,MediaCodec 是解码核心,AudioTrack 是音频输出终端——三者协同才是 Android 媒体处理的黄金路径。忽略任一环节,都将导致不可预测的播放故障。


















