You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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]

核心问题分析

  1. 未选择正确的音频轨道:从文件信息看,track-id=2是音频轨道,但代码中未明确让MediaExtractor切换到该轨道,可能默认读取了视频轨道的数据,导致获取的音频数据量不足。
  2. 直接播放编码后的AAC数据:音频轨道的mime类型是audio/mp4a-latm(即AAC编码),MediaExtractor读取的是编码后的压缩数据,并非可直接播放的PCM原始音频,直接用AudioTrack播放必然产生噪音。
  3. 音频参数不匹配:原音频是双声道(channel-count=2),但代码中AudioTrack使用了单声道(CHANNEL_OUT_MONO),即使解码正确也会导致播放异常。
  4. 音频数据处理逻辑错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 12:27:01