当前位置: 首页 > news >正文

CosyVoice 双向流式 streamingCall() — 前后端总体方案

CosyVoice 双向流式streamingCall()— 前后端总体方案

在保留现有LLM 流式type=content的前提下,把 TTS 从「整段call()+ OSS URL」升级为CosyVoice 双向 WebSocket + 音频帧直推前端,并保证语音失败不影响文字


一、现状 vs 目标

维度现状目标
文字百炼streamCalltype=content不变
TTS APIcall(整段)阻塞streamingCall(delta)+streamingComplete()
分句本地VoiceStreamingSegmentBufferCosyVoice 服务端自动分句(可去掉本地缓冲)
推前端voiceChunk.voiceUrl(OSS)音频帧(Base64 或 WS Binary)+ 可选短 URL 降级
落库段级 OSS + merge →ext.voiceUrl内存攒帧 → 结束 merge 上传一次
complete等 TTS 队列finishAndAwait文字结束即发 complete,TTS 异步收尾

二、总体架构

CosyVoice WSS streamingCall百炼 Agent streamCallMobileImControllerask-ai WebSocket小程序/H5CosyVoice WSS streamingCall百炼 Agent streamCallMobileImControllerask-ai WebSocket小程序/H5loop[LLM 流式]promptprocessWebSocketAskAi创建 SpeechSynthesizer+callbackchatStreamForWebSocketdeltatype=contentstreamingCall(delta)onEvent audioFrametype=voiceFrame全文结束type=textComplete 或 complete(无语音)streamingComplete()onCompletemerge帧→WAV→OSS→exttype=voiceComplete

原则

  1. 文字与语音解耦:content 先推;TTS 异常只 catch 打日志,不中断 LLM。
  2. 一问一 CosyVoice 连接:每问答一个SpeechSynthesizer,禁止单例共享。
  3. 文本单线程喂 TTS:LLM delta 入队,单线程streamingCall,避免多线程同实例。
  4. 出站串行:同一WebSocketSession的 content / voiceFrame 走per-connection 发送队列

三、后端方案

3.1 模块划分

模块职责
AliyunAgentServiceImpl不变:推content+onContentDelta
CosyVoiceStreamingSession(新)管理一条 WSS:streamingCall/streamingComplete/ callback / close
VoiceSynthesisService新增openStreamingSession(voiceId, callback),配置 WSS 端点
ImStreamingVoicePushSession(重构)去掉call()+段级 OSS;改为转发 delta + 收帧推前端 + 攒帧 merge
WebSocketOutboundQueue(新)同一连接 Text/Binary 串行 send
MobileImController创建/销毁 session;LLM 结束后先发 complete,TTS 异步 finalize

3.2 CosyVoice 会话生命周期

// 问答开始(有 doctorId + voiceId + 连接可用)SpeechSynthesisParamparam=builder().apiKey(...).model("cosyvoice-v2")// 与复刻音色一致.voice(voiceId).format(PCM_22050HZ_MONO_16BIT)// 或 MP3,流式推荐 PCM/MP3.build();SpeechSynthesizersynthesizer=newSpeechSynthesizer(param,callback);// LLM 每个 delta(onContentDelta,try-catch)textFeederQueue.offer(delta);// 单线程 consumer → synthesizer.streamingCall(delta)// LLM 结束synthesizer.streamingComplete();await onComplete/latch;// merge + 落库 + closesynthesizer.getDuplexApi().close(1000,"bye");

配置新增

aliyun:voice:api-key:sk-xxxmodel:cosyvoice-v2websocket-url:wss://{workspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference

SDK 建议≥ 2.22.0getOutput()句子事件);当前 2.19.4 可先 PoC。

3.3 与 LLM 的衔接

onContentDelta(delta): 1. outboundQueue.send(content) // 已在 Agent 层完成 2. cosyVoiceSession.feedText(delta) // 非阻塞入队 chatStreamForWebSocket 返回后: 1. updateBotMessageAfterAiReply 2. outboundQueue.send({ type: "textComplete" }) // 或带 complete 3. cosyVoiceSession.finishAsync() // streamingComplete + 不阻塞主线程 4. 环信 / 告警 等(不依赖 TTS)

3.4 推前端协议(建议)

文字(不变)

{"type":"content","content":"增量","messageId":"...","timestamp":123}

音频帧(新增,推荐 JSON+Base64 便于小程序)

{"type":"voiceFrame","messageId":"429476821505122304","seq":12,"format":"pcm","sampleRate":22050,"channels":1,"bitDepth":16,"sentenceIndex":0,"event":"sentence-synthesis","data":"base64...","timestamp":123}

可选:句子边界(来自result.getOutput()

{"type":"voiceSentence","messageId":"...","sentenceIndex":0,"text":"...","event":"sentence-end"}

结束

{"type":"voiceComplete","messageId":"...","hasVoice":true,"voiceUrl":"https://.../merged.wav"}{"type":"complete","messageId":"...","timestamp":123}
消息时机
contentLLM 流式
voiceGeneratingTTS 连接建立(可选)
voiceFrameCosyVoiceonEventaudioFrame
textCompleteLLM 结束(不等 TTS
voiceCompleteTTSonComplete+ merge 落库后
completetextComplete同发,或 voice 可选

降级:帧推送失败或小程序不支持流式播放时,保留短 MP3 分片 URLvoiceChunk兼容。

3.5 落库

onEvent: audioFrames.add(frame) onComplete: bytes = concat(frames) 或 decode PCM → WAV mergedUrl = upload OSS voice/merged ext: { hasVoice, voiceUrl, voiceFormat, voiceId }

段级 OSS 可取消,只保留最终 merge 一次

3.6 失败与隔离

失败处理
无音色 / 无 doctorId不建 TTS,仅 content + complete
streamingCall/ CosyVoice 报错日志 +voiceComplete(hasVoice=false)不影响已推 content
单帧推送失败日志,继续后续帧
merge/OSS 失败ext.voiceUrl,实时播放仍可能完整
23s 无新文本超时关 TTS;LLM 若仍输出需续连或整问重建 session
同连接连发两问in-flight 锁或拒绝第二问

3.7 并发

  • 每用户每问:1× SpeechSynthesizer + 1× WSS
  • 全局:有界 TTS 连接池(如 32~64),超出排队
  • 禁止Spring 单例SpeechSynthesizer
  • WebSocketConnectionManager:增加sendBinary(connectionId, bytes)+ 与 Text 共用 outbound 队列

四、前端方案

4.1 状态机

IDLE → CONNECTED → AI_STREAMING → TEXT_DONE → VOICE_STREAMING → DONE ↓ content ↓ voiceFrame ↓ ↓ 追加播放队列
  • 文字:按序拼接content
  • 语音:按messageId+seq维护播放队列

4.2 播放(微信小程序)

方案做法适用
A. Base64 → 临时文件每句/每 N 帧攒成 WAV →wx.getFileSystemManager写 temp →InnerAudioContext.src改造小,延迟略高于 H5
B. 句子级 WAVsentence-end后拼帧写文件再播与 CosyVoice 分句对齐,推荐
C. 降级 URL仍收voiceChunk.voiceUrl兼容旧版

不建议小程序裸 PCM 逐帧直播(无 Web Audio,实现成本高)。

4.3 前端伪代码

consttextBuf={};constaudioQueue=[];// { messageId, seq, pcmChunks[] }letplaying=false;onMessage(msg){switch(msg.type){case'content':appendText(msg.messageId,msg.content);break;case'voiceFrame':enqueueFrame(msg);tryPlayNext();break;case'textComplete':markTextDone(msg.messageId);break;case'voiceComplete':case'complete':finishSession(msg.messageId);break;}}

4.4 与旧协议兼容

  • 检测首包:有voiceFrame走流式;仅有voiceChunk走 URL 队列
  • 版本号:连接时?voiceProtocol=2connected里带features: ["voiceStream"]

五、分阶段实施

阶段内容风险
P0SDK 升级 +CosyVoiceStreamingSessionPoC;服务端收帧落日志
P1voiceFrameBase64 推 WS;小程序句子级 temp 文件播放
P2去掉段级 OSS + 本地VoiceStreamingSegmentBuffer;complete 与 TTS 解耦
P3outbound 队列、有界连接池、in-flight 锁
P4可选 Binary 帧、H5 Web Audio 低延迟路径

六、和现有voiceChunk对比

现在voiceChunk+ OSS双向streamingCall
首包延迟整句合成 + 上传更低(帧级)
带宽客户端拉 OSSWS 直推(Base64 更大)
小程序InnerAudio + URL成熟需 temp 文件或句子 WAV
服务端简单WSS 长连接 + 队列 + 协议
历史回放段级 + merge仅 merge 一次即可

七、推荐结论

推荐路径LLM 文字协议不动 + CosyVoice 双向流式 +voiceFrame(句子边界拼 WAV)+ 结束 merge 一次 OSS + 文字结束立即complete+ 语音失败可降级无 voice。

  • 后端核心CosyVoiceStreamingSession+ 文本单线程 feeder + outbound 串行队列
  • 前端核心:按messageId/seq攒帧,按句写 temp WAV 播放
  • 兼容:保留voiceChunk作降级开关

若要落地 P0/P1,切Agent 模式可从VoiceSynthesisService+ImStreamingVoicePushSession改造起笔。

http://www.gsyq.cn/news/1612092.html

相关文章:

  • 基于改进YOLOv8与无人机的电动自行车违规行为智能检测系统
  • 从零到一:如何用Citizens2打造沉浸式Minecraft服务器体验
  • GitLab架构演进:应对AI时代代码分析与高并发挑战
  • 按位取反是对补码的取反,和之前的求反码的规则类似,但是首位的符号位是改变的,剩下的位数0和1互换,说白了就是每一位都取反
  • 基于改进YOLOv8的无人机航拍电动自行车违规行为检测实践指南
  • 叉车采购选哪家?这几点帮你精准锁定
  • AI Agent实战指南:从核心能力到本地部署的完整路径
  • 影刀RPA新手教程:电商评论挖掘完全指南——批量采集用户评论、情感分析与词云生成
  • vivo X Fold6开售:稳健策略下,能否跨越折叠屏与AI生态门槛?
  • WorkBuddy AI助手:自然语言查询数据库实战指南与安全实践
  • DTSS认证咨询机构哪家值得推荐
  • Linux strip 命令 | 详解及在 Linaro 交叉编译工具链中的使用
  • 第49期 | 求职策略与渠道——AI时代的前端求职指南
  • MySQL零基础入门:从核心概念到实战应用的全链路学习指南
  • 终极图片去重解决方案:AntiDupl.NET免费开源工具完全指南
  • RAG 看起来简单,一上线就翻车?逐个排查 5 个环节
  • LyricsX:macOS歌词同步终极指南 - 打造完美音乐体验
  • DeepSeek V3技术深扒!MoE+MLA如何让AI推理快如闪电?
  • 国产大模型的具象交互补全:魔珐星云让 Qwen/DeepSeek Agent 拥有 3D 身体
  • YOLOv8知识蒸馏实战:从37%到42% mAP的模型压缩与性能提升
  • MySQL从零到一:Windows/Linux环境搭建与核心操作实战指南
  • 企业级Agentic AI落地指南:从概念到工程实践的五维拆解
  • 智能编码助手实战:从环境配置到视频理解与数据插件的进阶应用
  • 数据分析实战:Excel、SQL、Python与Power BI全流程项目指南
  • MySQL主从同步原理与实战:从一主一从到一主多从配置指南
  • 3步掌握QQ音乐加密音频转换,实现音乐自由播放
  • YOLOv8轻量化船舶检测:CA注意力与深度可分离卷积实战
  • 计算机毕业设计之服装信息管理系统的设计与实现
  • 数据分析技能树构建:Excel、SQL、Python与BI工具全链路实战指南
  • 终极指南:如何快速解密RPG Maker加密存档并提取游戏资源