双向流式API实现实时语音合成

5 阅读3分钟

传统文本转语音的挑战

传统文本转语音API采用请求-响应模式。这要求您在发起合成请求之前收集完整的文本。某机构Polly虽然能在请求后增量式流式返回音频,但瓶颈在于输入端——您必须等到文本完全可用才能开始发送。在由大语言模型驱动的对话应用中,文本是逐令牌生成的,这意味着需要等待整个响应生成完毕才能开始合成。

新功能:双向流式传输

StartSpeechSynthesisStream API 引入了一种根本不同的方法:

  • 增量发送文本:文本一旦可用即可流式传输给某机构Polly,无需等待完整句子或段落
  • 即时接收音频:实时获取生成的合成音频字节
  • 控制合成时机:使用flush配置触发对已缓冲文本的立即合成
  • 真正的双工通信:通过单一连接同时发送和接收

关键组件

组件方向用途
TextEvent客户端 → 某机构Polly发送待合成的文本
CloseStreamEvent客户端 → 某机构Polly表示文本输入结束
AudioEvent某机构Polly → 客户端接收合成的音频块
StreamClosedEvent某机构Polly → 客户端确认流已完成

性能基准测试

使用相同输入(7,045个字符、970字的散文,Matthew声音+Generative引擎,MP3输出,24kHz,us-west-2区域)进行对比:

指标传统SynthesizeSpeech双向流式改进
总处理时间115,226 ms (~115秒)70,071 ms (~70秒)快39%
API调用次数271减少27倍
发送的句子数27(顺序)27(逐词流式)
总音频字节2,354,2922,324,636

技术实现

创建异步Polly客户端

PollyAsyncClient pollyClient = PollyAsyncClient.builder()
    .region(Region.US_WEST_2)
    .credentialsProvider(DefaultCredentialsProvider.create())
    .build();

StartSpeechSynthesisStreamRequest request = StartSpeechSynthesisStreamRequest.builder()
    .voiceId(VoiceId.JOANNA)
    .engine(Engine.GENERATIVE)
    .outputFormat(OutputFormat.MP3)
    .sampleRate("24000")
    .build();

发送文本事件

TextEvent textEvent = TextEvent.builder()
    .text("Hello, this is streaming text-to-speech!")
    .build();

处理音频事件

StartSpeechSynthesisStreamResponseHandler responseHandler =
    StartSpeechSynthesisStreamResponseHandler.builder()
        .onResponse(response -> System.out.println("Stream connected"))
        .onError(error -> handleError(error))
        .subscriber(StartSpeechSynthesisStreamResponseHandler.Visitor.builder()
            .onAudioEvent(audioEvent -> {
                byte[] audioData = audioEvent.audioChunk().asByteArray();
                playOrBufferAudio(audioData);
            })
            .onStreamClosedEvent(event -> {
                System.out.println("Synthesis complete. Characters processed: "
                    + event.requestCharacters());
            })
            .build())
        .build();

LLM流式集成模式

// 启动Polly流
pollyStreamer.startStream(VoiceId.JOANNA, audioPlayer::playChunk);

// 当LLM生成令牌时...
llmClient.streamCompletion(prompt, token -> {
    boolean isSentenceEnd = token.endsWith(".") || token.endsWith("!") || token.endsWith("?");
    pollyStreamer.sendText(token, isSentenceEnd);
});

// LLM完成时
pollyStreamer.closeStream();

业务收益

改善用户体验:音频在LLM仍在生成时就开始播放,掩盖了后端处理时间。更快速、响应更及时的交互可提高用户留存率和满意度。

降低运营成本

成本因素传统分块方法双向流式
基础设施WebSocket服务器、负载均衡器、分块中间件客户端直连某机构Polly
开发工作自定义分块逻辑、音频重组、错误处理SDK处理复杂性
维护成本多个组件需要监控和更新单一集成点
API调用每个请求多次调用(每块一次)单个流会话

推荐使用场景

  • 对话式AI助手 – 将LLM响应直接流式合成语音
  • 实时翻译 – 在生成翻译文本的同时进行合成
  • 交互式语音应答 – 动态响应的电话系统
  • 无障碍工具 – 实时屏幕阅读器和文本转语音
  • 游戏 – 动态NPC对话和旁白
  • 实时字幕 – 实时转录系统的音频输出FINISHED