Android 11视频音频样本提取播放异常问题求助
Android 11视频音频提取播放异常:仅播放短时长噪音
在Android 11环境下,从15秒视频文件提取音频样本并播放时,仅能听到短时长随机噪音,getAudio()方法仅获取到487,831字节数据,以下是问题详情及解决方案:
问题代码
// 获取音频样本的方式 public ArrayList<byte[]> getAudio() { int index = 0; ArrayList<byte[]> audio = new ArrayList<>(); ByteBuffer buffer = ByteBuffer.allocateDirect(format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)); for (;;) { int size = extractor.readSampleData(buffer, 0); total_size += size; if (size < 0) { // 无更多数据可读 break; } for (int i = 0; i < size / 2; i++) { byte[] sample = new byte[2]; for (int j = 0; j < sample.length; j++) { sample[j] = buffer.get(); } audio.add(sample); } extractor.advance(); } return audio; } Log.d("AudioTest", "total audio data size in bytes: " + total_size); // 播放音频的方式 int SIZE = AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, SIZE, AudioTrack.MODE_STREAM); ArrayList<byte[]> audio = getAudio(); long start = System.currentTimeMillis(); track.play(); byte[] data = new byte[audio.size() * 2]; for (int i = 0, k = 0; i < audio.size(); i++) { byte[] sample = audio.get(i); for (int j = 0; j < sample.length; j++, k++) { data[k] = sample[j]; } } track.write(data, 0, data.length); long duration = System.currentTimeMillis() - start; Log.d("AudioTest", "that took: " + (duration / 1000) + "s");
视频文件信息
帧信息(视频轨道)
- has-sdtp: 1
- track-id: 1
- level: 2048
- mime: video/avc
- frame-count: 461
- profile: 8
- language: und
- color-standard: 1
- display-width: 1920
- track-fourcc: 828601953
- csd-1: java.nio.HeapByteBuffer[pos=0 lim=9 cap=9]
- color-transfer: 3
- durationUs: 15,382,033
- display-height: 1080
- width: 1920
- color-range: 2
- max-input-size: 3,145,748
- frame-rate: 30
- height: 1080
- csd-0: java.nio.HeapByteBuffer[pos=0 lim=33 cap=33]
样本信息(音频轨道)
- max-bitrate: 262,664
- isDMCMMExtractor: 1
- sample-rate: 48,000
- track-id: 2
- mime: audio/mp4a-latm
- profile: 2
- language: und
- aac-profile: 2
- track-fourcc: -1
- encoder-delay: 0
- durationUs: 15,402,666
- channel-count: 2
- bits-per-sample: 16
- encoder-padding: 48
- max-input-size: 524,308
- csd-0: java.nio.HeapByteBuffer[pos=0 lim=2 cap=2]
核心问题分析
- 未选择正确的音频轨道:从文件信息看,track-id=2是音频轨道,但代码中未明确让
MediaExtractor切换到该轨道,可能默认读取了视频轨道的数据,导致获取的音频数据量不足。 - 直接播放编码后的AAC数据:音频轨道的mime类型是
audio/mp4a-latm(即AAC编码),MediaExtractor读取的是编码后的压缩数据,并非可直接播放的PCM原始音频,直接用AudioTrack播放必然产生噪音。 - 音频参数不匹配:原音频是双声道(channel-count=2),但代码中
AudioTrack使用了单声道(CHANNEL_OUT_MONO),即使解码正确也会导致播放异常。 - 音频数据处理逻辑错误:
getAudio()方法中把编码数据按2字节分割成“样本”的操作完全错误,AAC编码数据的结构与PCM样本无关。
解决方案:解码AAC为PCM后播放
需要使用MediaCodec对AAC数据进行解码,得到PCM原始音频后再用AudioTrack播放,以下是修正后的示例代码:
private void extractAndPlayAudio(String videoPath) { MediaExtractor extractor = new MediaExtractor(); MediaCodec codec = null; AudioTrack audioTrack = null; try { extractor.setDataSource(videoPath); // 找到音频轨道 int audioTrackIndex = -1; MediaFormat audioFormat = null; for (int i = 0; i < extractor.getTrackCount(); i++) { MediaFormat format = extractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (mime.startsWith("audio/")) { audioTrackIndex = i; audioFormat = format; break; } } if (audioTrackIndex == -1) { Log.e("AudioTest", "未找到音频轨道"); return; } extractor.selectTrack(audioTrackIndex); // 初始化MediaCodec解码AAC String mime = audioFormat.getString(MediaFormat.KEY_MIME); codec = MediaCodec.createDecoderByType(mime); codec.configure(audioFormat, null, null, 0); codec.start(); // 初始化AudioTrack,匹配原音频参数 int sampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); int channelCount = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int audioFormatType = AudioFormat.ENCODING_PCM_16BIT; int channelConfig = channelCount == 2 ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO; int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormatType); audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormatType, minBufferSize * 2, AudioTrack.MODE_STREAM); audioTrack.play(); // 解码并播放流程 ByteBuffer[] inputBuffers = codec.getInputBuffers(); ByteBuffer[] outputBuffers = codec.getOutputBuffers(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); boolean isDone = false; while (!isDone) { // 填充输入数据到MediaCodec int inputBufferIndex = codec.dequeueInputBuffer(10000); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; int sampleSize = extractor.readSampleData(inputBuffer, 0); if (sampleSize < 0) { // 输入数据结束,发送结束标记 codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); isDone = true; } else { long presentationTimeUs = extractor.getSampleTime(); codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0); extractor.advance(); } } // 获取解码后的PCM数据 int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 10000); switch (outputBufferIndex) { case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: outputBuffers = codec.getOutputBuffers(); break; case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: // 输出格式变化,这里可以重新确认参数 break; case MediaCodec.INFO_TRY_AGAIN_LATER: break; default: ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; // 把PCM数据写入AudioTrack播放 byte[] pcmData = new byte[bufferInfo.size]; outputBuffer.get(pcmData); outputBuffer.clear(); audioTrack.write(pcmData, 0, pcmData.length); codec.releaseOutputBuffer(outputBufferIndex, false); if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { isDone = true; } break; } } } catch (IOException e) { e.printStackTrace(); } finally { if (audioTrack != null) { audioTrack.stop(); audioTrack.release(); } if (codec != null) { codec.stop(); codec.release(); } extractor.release(); } }
关键修正点
- 明确选择音频轨道,避免读取视频数据
- 使用
MediaCodec解码AAC压缩数据为PCM原始音频 - 确保
AudioTrack的参数(采样率、声道数、编码格式)与原音频完全匹配 - 采用流式播放逻辑,边解码边写入
AudioTrack,避免一次性加载大量数据导致的异常
内容的提问来源于stack exchange,提问作者EinStyn
相关产品推荐
相关产品推荐

