Java聊天应用FreeTTS多语音同时播放异常问题求助
解决FreeTTS多线程同时语音播放的问题
你遇到的核心问题是FreeTTS的底层设计限制——默认的语音合成组件(比如CMU的Kevin语音)并非线程安全,且音频输出通道默认共享,这就导致多实例并发运行时,不仅会出现音频抢占(只能一个声音播放),还会触发Syllable relation has already been set这类资源冲突错误。下面给你两个可行的解决方案:
方案1:修复现有代码,确保合成器实例完全隔离并正确管理资源
你的当前代码有几个可以优化的点,调整后能有效缓解甚至解决问题:
1.1 只初始化一次引擎中心
Central.registerEngineCentral只需要在应用启动时执行一次,重复注册会导致不必要的资源冲突。可以把这段初始化逻辑放到枚举的静态块中:
@Slf4j public enum Speech implements SpeechAgent { Instance; private static Random pool = new Random(); // 静态块初始化引擎,仅执行一次 static { System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory"); Central.registerEngineCentral("com.sun.speech.freetts.jsapi.FreeTTSEngineCentral"); } private static Synthesizer createSynthesizer() { try { SynthesizerModeDesc desc = new SynthesizerModeDesc(Locale.US); Synthesizer synthesizer = Central.createSynthesizer(desc); synthesizer.allocate(); synthesizer.resume(); Voice[] voices = null; while (voices == null) { voices = ((SynthesizerModeDesc) synthesizer.getEngineModeDesc()).getVoices(); } synthesizer.getSynthesizerProperties().setVoice(voices[pool.nextInt(voices.length)]); // 为每个合成器配置独立的音频播放器,避免音频抢占 com.sun.speech.freetts.jsapi.FreeTTSSynthesizer freeTtsSyn = (com.sun.speech.freetts.jsapi.FreeTTSSynthesizer) synthesizer; freeTtsSyn.getVoice().setAudioPlayer(new com.sun.speech.freetts.audio.JavaAudioPlayer()); return synthesizer; } catch (EngineException | AudioException | EngineStateError | PropertyVetoException e) { log.error("Failed to load synthesizer: {}", e.toString()); } return null; } public void speak(String input) { new Thread(new Runnable() { public void run() { Synthesizer synthesizer = createSynthesizer(); if (synthesizer == null) { return; } try { String cleanInput = input.replaceAll("[^a-zA-Z ]+", ""); synthesizer.speakPlainText(cleanInput, null); // 等待语音合成完成再释放资源 synthesizer.waitEngineState(Synthesizer.QUEUE_EMPTY); } catch (InterruptedException | EngineStateError e) { log.error("Error during speech synthesis: {}", e.toString()); } finally { // 必须释放合成器资源,避免内存泄漏 try { synthesizer.deallocate(); } catch (EngineException | EngineStateError e) { log.error("Failed to deallocate synthesizer: {}", e.toString()); } } } }).start(); } }
1.2 关键优化点说明
- 把引擎注册逻辑移到静态块,避免重复初始化导致的资源冲突
- 为每个合成器实例配置独立的
JavaAudioPlayer,解决音频输出抢占问题 - 添加
waitEngineState(Synthesizer.QUEUE_EMPTY)确保语音播放完成后再释放资源 - 在finally块中强制释放合成器,避免内存泄漏
方案2:切换到更现代的TTS引擎(推荐)
FreeTTS是比较老旧的项目,对多线程并发的支持有限。如果方案1效果达不到预期,建议切换到更适合并发场景的开源TTS引擎:
- MaryTTS:纯Java实现的开源TTS引擎,原生支持多实例并发,线程安全保障更完善,还支持多语言和多种语音音色
- PicoTTS:轻量级嵌入式TTS引擎,Java可以通过JNI调用,资源占用低,并发表现稳定
关于错误的补充解释
java.lang.IllegalStateException: Syllable relation has already been set这个错误,是因为FreeTTS的Segmenter音节处理组件内部使用了共享状态变量,当多个线程同时调用时,会导致状态被重复修改,从而触发异常。通过确保每个合成器实例完全独立(包括底层的Voice和处理组件),就能避免这类问题。
内容的提问来源于stack exchange,提问作者Paradoxis
相关产品推荐
相关产品推荐

