蓝牙连接时音频出现间隙问题的排查与解决求助
问题描述
我使用SSML实现应用的语音播报功能,本地播放完全正常,但手机连接蓝牙设备后,音频频繁出现间隙或延迟,甚至会丢失开头或中间的内容。比如原音频是Hello John, I am your assistant. How can I help you?,蓝牙播放时可能变成sistant. How can I help you?,偶尔播放流畅,但问题时常发生。
排除蓝牙距离问题,设备紧邻。由于Google SSML需要网络,不确定是蓝牙还是网络导致的问题,希望找到方法消除间隙,确保音频完全准备好再播放。
现有代码
播放音频的核心代码:
String myFile = context.getFilesDir() + "/output.mp3"; mMediaPlayer.reset(); mMediaPlayer.setDataSource(myFile); mMediaPlayer.prepare(); mMediaPlayer.start();
完整TTS实现类:
public class Tts { public Context context; private final MediaPlayer mMediaPlayer; public Tts(Context context, MediaPlayer mMediaPlayer) { this.context = context; this.mMediaPlayer = mMediaPlayer; } @SuppressLint({"NewApi", "ResourceType", "UseCompatLoadingForColorStateLists"}) public void say(String text) throws Exception { InputStream stream = context.getResources().openRawResource(R.raw.credential); // R.raw.credential是credential.json GoogleCredentials credentials = GoogleCredentials.fromStream(stream); TextToSpeechSettings textToSpeechSettings = TextToSpeechSettings.newBuilder() .setCredentialsProvider( FixedCredentialsProvider.create(credentials) ).build(); // 初始化客户端 try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create(textToSpeechSettings)) { // 替换{name}为目标名称 SharedPreferences sharedPreferences = context.getSharedPreferences("target", Context.MODE_PRIVATE); String target = sharedPreferences.getString("target", null); text = (target != null) ? text.replace("{name}", target) : text.replace("null", ""); // 设置待合成的文本输入 String myString = "<speak><prosody pitch=\"low\">" + text + "</prosody></speak>"; SynthesisInput input = SynthesisInput.newBuilder().setSsml(myString).build(); // 构建语音请求,选择语言代码和SSML语音性别 VoiceSelectionParams voice = VoiceSelectionParams.newBuilder() .setName("de-DE-Wavenet-E") .setLanguageCode("de-DE") .setSsmlGender(SsmlVoiceGender.MALE) .build(); // 选择返回的音频文件类型 AudioConfig audioConfig = AudioConfig.newBuilder().setAudioEncoding(AudioEncoding.MP3).build(); // 执行文本转语音请求 SynthesizeSpeechResponse response = textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); // 获取音频内容 ByteString audioContents = response.getAudioContent(); // 将音频写入本地文件 try (FileOutputStream out = new FileOutputStream(context.getFilesDir() + "/output.mp3")) { out.write(audioContents.toByteArray()); } String myFile = context.getFilesDir() + "/output.mp3"; mMediaPlayer.setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build()); mMediaPlayer.reset(); mMediaPlayer.setDataSource(myFile); mMediaPlayer.prepare(); mMediaPlayer.setOnPreparedListener(mediaPlayer -> mMediaPlayer.start()); } } }
已尝试的无效方案
- 设置音频类型为语音:
mMediaPlayer.setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SPEECH).build()); - 替换
mMediaPlayer.prepare()为mMediaPlayer.prepareAsync(),但音频无法播放 - 在
OnPreparedListener中调用start(),间隙问题仍存在
解决方案
1. 优化音频编码参数,适配蓝牙传输
Google TTS生成的默认MP3参数可能与部分蓝牙设备不兼容,导致解码丢包。调整AudioConfig使用更适合语音传输的配置:
// 方案1:改用无压缩PCM格式,减少蓝牙解码开销 AudioConfig audioConfig = AudioConfig.newBuilder() .setAudioEncoding(AudioEncoding.LINEAR16) .setSampleRateHertz(16000) // 语音常用采样率,降低传输压力 .build(); // 方案2:如果坚持用MP3,指定低比特率 AudioConfig audioConfig = AudioConfig.newBuilder() .setAudioEncoding(AudioEncoding.MP3) .setSampleRateHertz(16000) .setAudioBitrate(32000) // 32kbps足够语音清晰,减少数据量 .build();
2. 修正MediaPlayer播放逻辑,确保完全就绪后启动
当前代码中OnPreparedListener的注册时机错误,应在prepare()前设置,避免监听器未生效就完成准备:
String myFile = context.getFilesDir() + "/output.mp3"; mMediaPlayer.reset(); // 先注册准备完成监听器 mMediaPlayer.setOnPreparedListener(mediaPlayer -> { if (mediaPlayer.isPrepared()) { mediaPlayer.start(); } }); // 配置音频属性为助手语音,系统会优化蓝牙传输优先级 mMediaPlayer.setAudioAttributes(new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .setUsage(AudioAttributes.USAGE_ASSISTANT) .build()); mMediaPlayer.setDataSource(myFile); mMediaPlayer.prepare();
如果使用prepareAsync(),需确保监听器正确注册,且添加错误监听排查问题:
mMediaPlayer.reset(); mMediaPlayer.setOnPreparedListener(mediaPlayer -> mediaPlayer.start()); mMediaPlayer.setOnErrorListener((mediaPlayer, what, extra) -> { Log.e("TTS", "MediaPlayer错误: " + what + ", " + extra); return false; }); mMediaPlayer.setAudioAttributes(/* ... */); mMediaPlayer.setDataSource(myFile); mMediaPlayer.prepareAsync();
3. 避免音频文件覆盖冲突
每次生成音频都覆盖同一文件,若前一次播放未结束就写入新内容,可能导致文件损坏。改用唯一文件名:
// 生成带时间戳的唯一文件名 String uniqueFileName = context.getFilesDir() + "/output_" + System.currentTimeMillis() + ".mp3"; try (FileOutputStream out = new FileOutputStream(uniqueFileName)) { out.write(audioContents.toByteArray()); } // 使用唯一文件播放 mMediaPlayer.setDataSource(uniqueFileName);
4. 优化系统蓝牙设置
- 引导用户关闭蓝牙设备的"高清音频"模式(如AAC/SBC HD),部分设备在该模式下会增加延迟和丢包
- 在Manifest中添加权限,确保应用能修改音频设置:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
5. 排查网络因素
提前下载好音频文件,离线测试蓝牙播放。如果离线播放仍有问题,说明是蓝牙适配问题;若正常,则需优化TTS音频生成后的写入逻辑,确保文件完全写入后再触发播放。
内容的提问来源于stack exchange,提问作者xRay
相关产品推荐
相关产品推荐

