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

Android中MediaCodec解码FFServer推送的AAC音频无输出无声音问题求助

解决Android MediaCodec解码AAC RTP包输出缓冲区为空的问题

看起来你已经走对了大部分路,但解码输出空缓冲区的问题通常和原始数据格式不匹配或者RTP包处理不当有关,我来帮你一步步排查:

1. 先确认你拿到的是纯AAC帧(剥离RTP头)

RTP数据包包含固定的12字节头部(默认无扩展头),你必须先把这部分剥离,只保留后面的AAC payload。如果直接把整个RTP包喂给MediaCodec,解码器根本识别不了,自然输出空。

你可以这样处理:

// 假设rtpPacket是接收到的完整RTP字节数组
byte[] aacPayload = Arrays.copyOfRange(rtpPacket, 12, rtpPacket.length);

如果你的RTP流带扩展头部,需要先解析扩展长度再调整偏移,FFServer默认AAC流一般不会带,先按12字节处理试试。

2. 给RAW AAC帧添加ADTS头

libvo_aacenc编码出来的AAC是RAW AAC帧(不带ADTS头),而Android的MediaCodec AAC解码器默认需要带ADTS头的帧才能正确解析。你需要手动给每个AAC帧前面加上ADTS头,构造方法如下:

private byte[] addADTSHeader(byte[] rawAAC, int sampleRate, int channels, int bitrate) {
    int profile = 2; // AAC LC(libvo_aacenc默认是这个profile)
    int sampleRateIndex = getSampleRateIndex(sampleRate);
    int channelConfig = channels;

    int frameLength = rawAAC.length + 7; // ADTS头固定7字节
    byte[] adtsHeader = new byte[7];

    adtsHeader[0] = (byte) 0xFF;
    adtsHeader[1] = (byte) 0xF9;
    adtsHeader[2] = (byte) (((profile - 1) << 6) + (sampleRateIndex << 2) + (channelConfig >> 2));
    adtsHeader[3] = (byte) (((channelConfig & 3) << 6) + (frameLength >> 11));
    adtsHeader[4] = (byte) ((frameLength & 0x7FF) >> 3);
    adtsHeaderHeader[5] = (byte) (((frameLength & 7) << 5) + 0x1F);
    adtsHeader[6] = (byte) 0xFC;

    // 合并ADTS头和原始AAC帧
    byte[] result = new byte[frameLength];
    System.arraycopy(adtsHeader, 0, result, 0, 7);
    System.arraycopy(rawAAC, 0, result, 7, rawAAC.length);
    return result;
}

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; // 你的采样率对应索引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 4;
    }
}

这一步大概率是核心问题,把处理后的AAC数据再喂给MediaCodec试试。

3. 检查MediaCodec的配置是否正确

确保初始化MediaCodec时的MediaFormat参数完全匹配编码参数:

MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 44100, 2);
format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
// 初始化解码器
MediaCodec decoder = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
decoder.configure(format, null, null, 0);
decoder.start();

注意:MIME类型要用MediaFormat.MIMETYPE_AUDIO_AAC,AAC_PROFILE必须和libvo_aacenc的编码profile一致,默认是LC。

4. 确认输入缓冲区的提交逻辑

  • 提交数据时必须设置正确的时间戳(PTS),实时流可以用系统时间递增(比如System.nanoTime() / 1000),如果RTP包自带PTS,直接转换使用更准确。
  • 不要过早提交数据,确保解码器已经调用start()进入RUNNING状态。
  • 实时流不要设置BUFFER_FLAG_END_OF_STREAM,除非流结束。

示例提交代码:

int inputBufferIndex = decoder.dequeueInputBuffer(10000); // 等待10ms
if (inputBufferIndex >= 0) {
    ByteBuffer inputBuffer = decoder.getInputBuffer(inputBufferIndex);
    inputBuffer.clear();
    byte[] processedAAC = addADTSHeader(aacPayload, 44100, 2, 128000);
    inputBuffer.put(processedAAC);
    long pts = System.nanoTime() / 1000; // 转换为微秒
    decoder.queueInputBuffer(inputBufferIndex, 0, processedAAC.length, pts, 0);
}

5. 输出缓冲区的处理逻辑

确保你正确处理了dequeueOutputBuffer的返回值:

  • 如果返回INFO_OUTPUT_FORMAT_CHANGED,需要重新获取输出格式并配置AudioTrack。
  • 如果返回有效索引,要读取缓冲区数据,然后释放缓冲区给解码器复用。

示例处理代码:

MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 10000);
while (outputBufferIndex >= 0) {
    ByteBuffer outputBuffer = decoder.getOutputBuffer(outputBufferIndex);
    if (bufferInfo.size > 0) {
        // 拿到解码后的PCM数据,喂给AudioTrack播放
        byte[] pcmData = new byte[bufferInfo.size];
        outputBuffer.get(pcmData);
        audioTrack.write(pcmData, 0, pcmData.length);
    }
    decoder.releaseOutputBuffer(outputBufferIndex, false);
    outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0);
}
// 处理格式变化
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    MediaFormat outputFormat = decoder.getOutputFormat();
    // 重新配置AudioTrack
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
            outputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE),
            outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO,
            AudioFormat.ENCODING_PCM_16BIT,
            AudioTrack.getMinBufferSize(outputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE),
                    outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT),
            AudioTrack.MODE_STREAM);
    audioTrack.play();
}

最后排查点

  • 确认AudioTrack已正确初始化并处于播放状态(有时候解码有输出但AudioTrack没配置好也会没声音,但你当前问题是输出缓冲区空,先解决解码问题)。
  • 用日志打印每个步骤的字节数,比如处理后的AAC帧长度、输入缓冲区提交的长度,确认数据确实进入了解码器。

按照上面的步骤排查,应该能解决输出缓冲区为空的问题。

内容的提问来源于stack exchange,提问作者Nicolò

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:32:36