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

Android使用AudioTrack播放PCM流出现失真问题排查

Android平台WebRTC PCM音频播放卡顿杂音问题解决

我在封装WebRTC原生库获取PCM音频流后,桌面JVM(Linux)环境下的播放逻辑完全正常,但移植到Android平台用AudioTrack播放时,出现严重的卡顿和杂音问题。

排查过程

  • 先后尝试直接写入ByteBuffer、转换为short数组后写入AudioTrack两种方式,杂音有轻微减少,但卡顿现象始终无法消除
  • 经过调试定位,问题根源是音频缓冲区欠载(underrun)——AudioTrack的回调线程和WebRTC的音频输出线程时序难以同步,导致AudioTrack请求数据时缓冲区为空,只能填充静音或旧数据,进而产生卡顿杂音
  • 尝试过调整AudioTrack的缓冲区大小、修改线程优先级、增加数据预存等多种同步方案,均无法彻底解决时序匹配问题

最终解决方案

改用Google官方的Oboe音频框架,结合C++实现低延迟音频渲染,完美解决了缓冲区欠载问题,播放流畅无杂音。

代码示例

1. C++端Oboe音频渲染实现

#include <oboe/Oboe.h>
#include <mutex>
#include <vector>

class AudioRenderer : public oboe::AudioStreamCallback {
public:
    oboe::DataCallbackResult onAudioReady(
        oboe::AudioStream *audioStream,
        void *audioData,
        int32_t numFrames
    ) override {
        std::lock_guard<std::mutex> lock(mBufferMutex);
        auto targetBuffer = static_cast<int16_t*>(audioData);
        const int channelCount = audioStream->getChannelCount();
        const int32_t totalSamplesNeeded = numFrames * channelCount;
        
        // 取可用数据和需求数据的最小值
        const int32_t samplesToCopy = std::min(totalSamplesNeeded, static_cast<int32_t>(mPcmBuffer.size()));
        
        if (samplesToCopy > 0) {
            memcpy(targetBuffer, mPcmBuffer.data(), samplesToCopy * sizeof(int16_t));
            // 移除已拷贝的数据
            mPcmBuffer.erase(mPcmBuffer.begin(), mPcmBuffer.begin() + samplesToCopy);
        } else {
            // 无数据时填充静音
            memset(targetBuffer, 0, totalSamplesNeeded * sizeof(int16_t));
        }
        return oboe::DataCallbackResult::Continue;
    }

    // 接收WebRTC传递的PCM数据
    void pushPcmData(const int16_t *data, size_t sampleCount) {
        std::lock_guard<std::mutex> lock(mBufferMutex);
        mPcmBuffer.insert(mPcmBuffer.end(), data, data + sampleCount);
        
        // 限制缓冲区最大长度,避免内存溢出(这里设为2秒双声道48kHz数据)
        const size_t maxBufferSize = 48000 * 2 * 2;
        if (mPcmBuffer.size() > maxBufferSize) {
            mPcmBuffer.erase(mPcmBuffer.begin(), mPcmBuffer.end() - maxBufferSize);
        }
    }

private:
    std::vector<int16_t> mPcmBuffer;
    std::mutex mBufferMutex;
};

// 全局音频渲染实例和流对象
std::shared_ptr<AudioRenderer> gAudioRenderer;
oboe::AudioStream *gAudioStream = nullptr;

// 初始化Oboe音频流
extern "C" void initAudioRenderer() {
    gAudioRenderer = std::make_shared<AudioRenderer>();
    oboe::AudioStreamBuilder builder;
    
    oboe::Result result = builder.setCallback(gAudioRenderer.get())
        .setDirection(oboe::Direction::Output)
        .setPerformanceMode(oboe::PerformanceMode::LowLatency)
        .setFormat(oboe::AudioFormat::I16)
        .setSampleRate(48000) // 匹配WebRTC输出采样率
        .setChannelCount(oboe::ChannelCount::Stereo) // 匹配WebRTC声道数
        .openStream(&gAudioStream);
    
    if (result == oboe::Result::OK) {
        gAudioStream->requestStart();
    }
}

2. JNI桥接代码(C++)

#include <jni.h>
#include "AudioRenderer.h"

extern "C" JNIEXPORT void JNICALL
Java_com_example_yourpackage_WebRtcAudioPlayer_pushPcmData(
    JNIEnv *env, jobject thiz, jshortArray pcmData, jint sampleCount
) {
    jshort *rawData = env->GetShortArrayElements(pcmData, nullptr);
    if (rawData && gAudioRenderer) {
        gAudioRenderer->pushPcmData(rawData, static_cast<size_t>(sampleCount));
    }
    env->ReleaseShortArrayElements(pcmData, rawData, 0);
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_yourpackage_WebRtcAudioPlayer_initAudio(
    JNIEnv *env, jobject thiz
) {
    initAudioRenderer();
}

3. Java层WebRTC音频回调对接

package com.example.yourpackage;

public class WebRtcAudioPlayer {
    static {
        System.loadLibrary("audio-renderer");
    }

    private native void initAudio();
    private native void pushPcmData(short[] pcmData, int sampleCount);

    public WebRtcAudioPlayer() {
        initAudio();
    }

    // WebRTC音频数据回调方法,需注册到WebRTC的音频输出接口
    public void onAudioFrameReceived(short[] pcmData, int sampleRate, int channels) {
        pushPcmData(pcmData, pcmData.length);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 20:38:25