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
相关产品推荐
相关产品推荐

