45 KiB
科大讯飞 SparkChain —— 第三方语音与模型全面替换落地文档
2026-10-15 起草,同日按功能、测试服边界、两端 SDK 差异和项目入口再补过。本文只做设计,未改任何代码。 真机怎么测、失败了改哪一行,见 科大讯飞SparkChain-真机自测与回改.md。 SDK 版本:
SparkChain_Android_SDK_overseas_2.1.4_rc4、SparkChain_IOS_SDK_overseas_2.1.4_rc4。 目标:语音识别、合成、机器翻译、同传、文本大模型、会议转写改走讯飞。微软语种(含爱尔兰语)走 SDK 里的微软引擎。服务器只动测试服。 正式机
47.116.104.181、正式接口https://ym.ymaikj.com、正式后台库一律不改。 测试服是阿龙8.133.166.29,业务域名https://ym-dev.ymaikj.com。发布只用各目录的./dev-deploy.sh along。 禁止prod-build.sh/prod-deploy.sh。客户端联调.env保持SERVER_URL=https://ym-dev.ymaikj.com,不要改去正式域名。
0. 一句话结论
SparkChain 是装在手机里的原生 SDK(安卓 AAR、iOS framework),不是 Go 能链接的库。 所以「全面换成它」要拆成两截,缺一截都不算换完:
- 手机上的实时链路(通话翻译、同声传译、面对面、文本翻译、播报)直接调 SDK。
- 服务器上的离线链路(
user_translate兜底、会议转写、会议总结)留在 Go 里,调讯飞开放平台 HTTP。 这套代码和后台配置只发布、只改测试服。正式服继续跑现在的阿里/微软/字节,直到以后单独决定上线。
现在工程里那套讯飞(ntrans.xfyun.cn、IflytekIntegratedSpeechService)是旧的听写/翻译接口,
不是这份 SparkChain。不要在旧类上打补丁,新开一个原生插件。
有三块这份 SDK 不能原样替换,落地时按第 5 节的写法处理,不要假装是同一条接口:
- 耳机 EMAI。海外 SDK 没有百炼那种「持续送 PCM、边说边回」的全双工对话。落地是听写 + 文本 LLM + 合成三段接进现有会话服务,工具调用和设备协议留在原处。
- 中国移动灵犀、对象存储、短信、实名、微信、地图。它们不是语音模型。
- 声音复刻、超拟人合成、图片 OCR 翻译。只在安卓国内文档里,海外包没有。
1. SDK 本身怎么用
1.1 初始化只做一次
两端都是 appId + apiKey + apiSecret,全局 init 一次,退出时 unInit。
| 节点 | 安卓 apiType |
iOS apiType |
什么时候用 |
|---|---|---|---|
| 国内 | ApiType.TPYE_CH |
0 |
不用。国内文档的星火/DeepSeek 和海外这套不是同一条 |
| 新加坡 | ApiType.TPYE_SG |
1 |
默认 |
| 欧洲 | ApiType.TPYE_EU |
2 |
2026-09-02 起是白名单节点。账号没加白之前不要切 |
枚举名里的 TPYE 是 SDK 自己的拼写,照抄,不要「改对」。
单次能力还可以再选引擎,和全局节点是两层:
- 讯飞自研:听写、大模型识别、同声传译新加坡语种表、新加坡发音人。
- 微软(文档里的
msft/ iOSRegionMSFT):实时转写、录音文件转写、在线合成、同声传译的另一张语种表。 爱尔兰语走这一层,不要为了它把全局节点改成欧洲。
兼容范围:安卓 5.0+,armeabi-v7a / arm64-v8a;iOS 9.0+,arm64。 音频入口统一是 16 kHz、16 bit、单声道 PCM,和现在耳机插件出口一致,中间不用再转格式。
1.2 海外包里两端都有的 10 项
| SDK 能力 | 类 | 对应我们现在的事 |
|---|---|---|
| 语音听写 | ASR |
短句识别(1 分钟内)。同传/面对面的识别段可以先用它 |
| 大模型识别 | ASR(语种码 *_cbm) |
识别效果更好的听写。新加坡约 27 语,含自动切语 mq_cbm。欧洲大模型文档写了「暂不提供」 |
| 实时转写 | RTASR |
长语音流。会议进行中的实时字幕、以及爱尔兰语识别(欧洲 ga_IE,微软 ga-IE) |
| 录音文件转写 | RAASR |
已录好的长音频,最长 5 小时,创建任务再查询。微软任务类型 iflyrec_voice_mul_cbm_msft_24h 含 ga-IE |
| 在线合成 | OnlineTTS |
译文播报,微软引擎只出 raw PCM。安卓文档有 ga-IE-OrlaNeural;iOS 发音人表没有爱尔兰语,见第 4.5 节 |
| 在线翻译 | ITS |
文本翻译。小语种表两边都有爱尔兰语 ga,但引擎枚举不同:安卓是 GTRANS,iOS 没有这个名字 |
| 同声传译 | AST |
识别 + 翻译 + 可选合成,一条链路。给中英这类已覆盖的通话/同传 |
| 文本对话 | LLM |
海外走 OpenAI 兼容接口,经讯飞私有地址。文档点名 gpt-5.3-*、gpt-5.4、gpt-5.4-pro |
| 图片理解 | LLM |
一张图加一段文字提问。不是「图中文字 OCR 再贴回原图」 |
| 图片生成 | LLM(domain=nrt) |
文生图,gpt-image-1.5。本产品现在没有这个功能,不接 |
1.3 只在安卓国内文档里、这次不接
声纹特征、声音复刻、超拟人合成、图片 OCR 翻译、Embedding、实时转写大模型、录音文件转写大模型、极速转写。 iOS 这包没有对应文档。产品若以后要「译文用说话人自己的声音」,那是国内声音复刻,和现在阿里 3.5 的实时声音复刻不是同一个东西,本次范围外。
2. 现在第三方实际落在哪
后台模板在 apps/services/modules/console/api_svctemplate.go。下表是测试服当前会走到的,换成一列是测试服上的去向。正式服配置不动,所以正式用户仍走旧服务商。
2.1 客户端实时
| 功能 | 现在 | 代码入口 | 换成 |
|---|---|---|---|
| 通话翻译选路 | 阿里 3.5 声音复刻 > 阿里旧版 > 微软三段 > 讯飞旧接口 | language_manager.dart 的 findBestMatchingProvider |
语种在 AST 表里走 SparkChain AST;不在表里走三段(RTASR/ASR + ITS + OnlineTTS) |
| 通话双路音频 | 耳机 PCM 不经 Dart,原生直连 azure_speech |
BesCallPcmBridge(iOS 通知 / Android 静态槽) |
桥的契约不动,消费方从 azure_speech 改到新插件 |
| 同声传译 / 面对面 | 微软听写 + user_translate + 合成 |
translation_controller.dart |
识别和合成改 SDK;翻译改端上 ITS,不再打 user_translate |
| 文本翻译页 | ServerTranslationService → user_translate,阿里覆盖不了再直连微软翻译 |
server_translation_service.dart |
端上 ITS。安卓 GTRANS,iOS 用第 4.5 节确定的类型 |
| 图片翻译 | 百炼视觉模型,只出原文/译文,不回贴图 | alibaba_image_translation_service.dart |
端上图片理解,提示词要求抽出图中文字并翻译。坐标和回贴图本来就没有,不用补 |
| 耳机 EMAI | 百炼多模态,原生 WebSocket,工具调用留在 Dart | BailianDialogClient / DeviceAiSessionService |
听写 + 文本 LLM + 合成接进同一条唤醒,见第 5.5 节。百炼密钥在测试服停用前保留,便于切回 |
| 旧讯飞 | stt_iflytek 的 appId / accessKey,host 默认 ntrans.xfyun.cn |
azure_speech 里 IflytekIntegratedSpeechService |
废弃。SparkChain 用另一套 appId / apiKey / apiSecret |
通话翻译现在是端到端直连,会员拦不住那一跳,靠「不下发凭据」关。换 SDK 之后凭据在端上,会员闸门要在开桥之前的 Dart 里拦,不能再指望密钥不下发。
2.2 服务端
| 功能 | 现在 | 代码入口 | 换成 |
|---|---|---|---|
| 实时机器翻译 | 只实现了阿里翻译。客户端对阿里没有的语种改直连微软 | modules/user/api_translate.go、model_translate.go |
端上改走 ITS 之后,这个接口变成兜底。兜底也改讯飞机器翻译 HTTP,不再留阿里/微软两套 |
| 会议转写 | 字节 / 阿里百炼 / Google / Azure 四选一 | modules/echomeet/provider_registry.go |
讯飞录音文件转写 HTTP(和 SDK 的 RAASR 是同一个云能力) |
| 会议总结 | 豆包 / 通义 / Gemini / Azure OpenAI | 同上,Summarizer |
讯飞文本大模型 HTTP。海外 SDK 的 LLM 是 OpenAI 兼容私有地址,服务端按讯飞开放平台文档接,不要自己猜 SDK 内部签名 |
| 会议翻译 | 2026-09-26 起转写不再翻译,翻译只决定总结用什么语言写 | tasks.go 的 TranslateProcess |
不恢复「转写再翻译一遍」。总结语言仍由提示词决定 |
2.3 明确不动
| 东西 | 原因 |
|---|---|
| 阿里云 OSS、短信 | 存储和短信,SDK 没有 |
| 实名(阿里 / 腾讯 / 创蓝) | 身份核验,SDK 没有 |
| 微信登录、地图、内购 | 与语音无关 |
中国移动灵犀 llm_mobile_elf |
另一份合同和原生 SDK,AGENT_TYPE=yidong 才走进去 |
| 耳机协议、通话下行、后台保活 | 音频通路已定,这次只换「PCM 送去哪家云」 |
3. 语种怎么选引擎
原则:能一条链路走完的用 AST,表里没有的拆成三段。 不要再按「阿里 > 微软 > 讯飞」排四家。
| 场景 | 引擎 | 说明 |
|---|---|---|
| 中英日韩和 AST 新加坡表里的约 30 个语种 | AST,讯飞自研 |
识别、翻译、合成一次会话。合成默认关,要播报再开 use_tts |
| AST 新加坡没有、微软同传表里有的 | AST,msft |
音色是 *Neural。同传表里没有爱尔兰语 |
| 爱尔兰语,以及两张 AST 表都没有的语种 | 三段 | 识别、翻译、合成的类和语种码按第 4.5 节分端写。安卓文档写明了微软 ga-IE 和 ga-IE-OrlaNeural;iOS 文档没有把这两项列全,不能把安卓参数原样抄过去 |
| 同传/面对面只需要文字、句子短 | 听写 ASR + ITS |
听写限制约 1 分钟。超时或长会改 RTASR |
| 会议文件 | 服务端录音文件转写 HTTP | 语种用微软 24 小时任务时与 RAASR 的 ga-IE 对齐 |
爱尔兰语是这次换厂商的验收样本,因为现在中文→爱尔兰语的通话翻译,远端爱尔兰语识别走的是微软三段,而阿里端到端覆盖不了它。换完之后这一对必须仍然是:近端中文能识别、译成爱尔兰语、远端听得到;远端说爱尔兰语,本端能出原文、中文译文和中文声音。
声音复刻(对方听到的是说话人自己的声音)这次会丢掉。那是阿里 3.5 的能力,海外 SparkChain 没有。中英语种改走 AST 之后,音色是发音人表里的固定音色,按路选,不再按说话人现场刻。
4. 客户端怎么接
4.1 新插件,不塞进 azure_speech
新建 apps/client/local_plugins/sparkchain/:
- 安卓依赖
SparkChain.aar。编解码需要的话再带Codec.aar(SDK 包里有)。 - iOS 用
sdk/overseas-uid/SparkChain.framework。不要链domestic-uid。 - Dart 侧只暴露和现有服务同形的方法:初始化、听写开始/送 PCM/结束、同传开始/送 PCM/结束、翻译一句、合成一段、释放。
- 回调进现在的
AstService/ 翻译控制器已经在听的事件:识别中间结果、最终结果、译文、TTS PCM、错误。
azure_speech 先留着,用开关切。新插件稳定之前,旧的阿里/微软路径还能回退。确认全量切走之后再删旧依赖,不要在同一次改动里既接新 SDK 又拆旧插件。
iOS 本地插件的 Package.swift 若引用兄弟插件,按仓库里已有的做法:flutter pub get 之后跑 apps/client/ios/sync_spm_platform.sh。
4.2 通话双路 PCM 的契约不变
现在的约定(改一处必须同改另一处,改漏了编译能过、但一帧都到不了):
- iOS 通知
com.eaimar.bes.callpcm.uplink/downlink,键leg、pcm。A = 本端麦,B = 对端通话音。 - 安卓
bluetooth_manager的静态槽,azure_speech现在来接。
三条桥不要接错。通话翻译、面对面、耳机 EMAI 各有一条,通知名和槽名改漏了编译能过,但一帧都到不了:
| 功能 | iOS 通知 | 安卓槽 | 现在谁在听 |
|---|---|---|---|
| 通话翻译 | com.eaimar.bes.callpcm.uplink / downlink,键 leg、pcm。A = 本端麦,B = 对端 |
BluetoothManager.callPcmTap |
BesCallPcmBridge |
| 面对面 | com.eaimar.bes.f2fpcm.uplink / downlink,同样的键。A/B 是左右耳 |
BluetoothManager.f2fPcmTap |
BesF2fPcmBridge |
| 耳机 EMAI | AiPcmBridge |
BluetoothManager 的 AI 上行槽(与 callPcmTap 分开) |
BesAiPcmBridge |
SparkChain 插件去接现在这条桥,门限、积压丢帧、ready 留在桥上。不要让 SDK 自己开麦克风。手机麦、录屏、广播扩展已经在现有代码里产出 PCM,继续推进插件的 write。SDK 再开一次麦,会和前台服务、AVAudioSession、耳机麦抢设备,表现为没声音或声音跑到扬声器。
AST 要两路各一个实例:A 路源语种是用户语言、目标是对方语言;B 路相反。和现在阿里/微软双路的方向一致。
下行译文 PCM 仍走现有 CallTranslationDownlink(16 kHz PCM → G.722 → 耳机)。SDK 合成如果给出别的采样率,在插件里重采样到 16 kHz 再交出,耳机侧不改。
4.3 初始化与线程
- App 启动、配置拉下来之后初始化一次。重复
init先unInit。unInit只在退出登录或密钥变了的时候做,不要在每句结束时做。 workDir文档标了必填,且必须可写。用应用私有目录(安卓filesDir,iOS Documents 下单独一个子目录)。不要写/sdcard,也不要为此申请MANAGE_EXTERNAL_STORAGE。测试机华为是 Android 12,requestLegacyExternalStorage已经不起作用。logPath不设,日志走 logcat / 控制台。release 的logLevel用 WARN,避免 SDK 把音频路径打进日志。- 听写/转写/同传的
write不要在主线程上堵。桥上的 PCM 已经在串行队列里,插件里直接write。 - 同一时刻只跑一条流式会话(一路
AST,或通话/面对面的两路AST)。翻译四个模式和 EMAI 本来就会suspendOtherServices,保持这个互斥。不要通话翻译开着再把 EMAI 的听写叠上去。 - 两路同时
start时按 SDK 文档错开(现有微软实现是 A 起来再等 1.2 秒再起 B)。SparkChain 是否必须错开,以第一版真机日志为准;若两路并行会让第二路无回调,就沿用这个间隔。 - 回调对象在插件里强引用,持有到
stop。不要只留在局部变量里——iOS 上弱引用会在初始化返回后被释放,表现就是一路完全没有识别结果。 - 合成采样率按 16 kHz 交给现有播放器。耳机 EMAI 的
_deviceRate就是 16000。百炼下行是 24000,那是百炼自己的出口,不要套到 SparkChain 上。SDK 若给出别的采样率,在插件里重采样到 16 kHz 再交。
4.4 凭据
SDK 要求密钥在手机上。这和 2026-08 把机器翻译改成服务端代调、避免密钥进包的决定相反,是用这家 SDK 的代价,单独接受:
- 后台新增一个第三方服务
sparkchain。全局init用app_id、api_key、api_secret、api_type(默认新加坡)。 - 新加坡同传文档要求再传一套
ast_appid/ast_apikey/ast_apisecret。模板里单独三个字段。和上面相同就填同一份,不要在客户端写死「肯定相同」。 - 经现有
user_getappconfig_v3下发。密钥字段标加密。 - 客户端用
AppConfig.cred读取,空则该能力不可用,不要回退到旧的 Azure/阿里密钥。 - release 日志禁止打密钥。可以打 init 返回码和会话
sid,方便真机对账。 - 类别不要标成端到端(类别 4)。
filterSvcsByVip会把类别 4 整份从下发列表拿掉。sparkchain一份密钥同时给文本翻译和 EMAI 用,标成 4 之后,非会员连这两项都初始化不了。
会员:通话、同声、面对面、音视频在点「开始」时拦。现在 translation_controller.dart 的 _hasTranslationQuota() 恒为 true,不能当成已经有闸门。落地时改成读现有会员状态。EMAI 不新加会员限制。user_translate 兜底仍走服务端原来的会员判断。
4.6 什么时候选讯飞节点
有两套「节点」,不要混成一件事。
我们自己的接口(ym-dev / 正式域名)不在这次按地区新开机器。DioManager.baseUrl 已经在进程启动时用手机系统地区选 SERVER_URL 或 HW_SERVER_URL。现在这两个值都是 https://ym-dev.ymaikj.com。登录页上选的国家是短信区号,不是人所在的地方,不能拿来切服务器。这次也不要为欧洲用户再部署一台业务服务器。
讯飞云节点才按用户所在地选。选的是 SDK 的 apiType,不是我们 Go 服务的地址。
时机不要等到进翻译页,也不要只绑在登录按钮上:
- 启动拉
user_getappconfig时,服务端已经用请求 IP 做过ipinfo→ToRegion(modules/user/core.go)。这个结果今天只用于 MCP 和配置作用域,没有回给客户端。测试服在这份响应里多带一个讯飞节点建议。 - 客户端拿到之后再
init。同一进程里节点不变就不unInit。 - 登录(
api_sgin)也会按 IP 写下CountryCode。若登录时的地区算出的节点和启动时不同(人换了网络),登录成功后unInit再init一次。一场翻译或 EMAI 进行中不要换节点。 - IP 查不到时用手机系统地区兜底,再不行用新加坡。不要用登录页的短信区号。
节点对照:
服务端 ToRegion |
讯飞全局节点 | 说明 |
|---|---|---|
欧洲 RegionEurope |
欧洲 TPYE_EU / iOS 2 |
只有账号已加白才切。没加白之前欧洲用户也用新加坡,否则 init 失败,整份语音都不可用 |
| 其他地区,含中国、东南亚、美洲 | 新加坡 TPYE_SG / iOS 1 |
中国用户也走海外这套 SDK 的新加坡节点。不要切 TPYE_CH,那是另一份国内文档,星火和 DeepSeek 不在这次的包里 |
| 未知 | 新加坡 | 失败方向是能用,不是切到一个没加白的节点 |
人在哪,只决定上面这个全局节点。一句爱尔兰语仍然走第 4.5 节的微软引擎,不因为人在新加坡就把 ga 送进新加坡同传表。人在欧洲,也是先加白再用欧洲节点,识别码仍按第 4.5 节,不把两层合成一个 if。
测试服改的是「响应里多一个节点字段」。正式服的 user_getappconfig 不动,正式包也还不会读这个字段。
4.5 安卓和 iOS 不是同一份 SDK
Dart 只保留一套方法。Kotlin 和 Swift 各写一份,不要共用一份 JNI,也不要把一边的枚举名写到另一边。插件对外只收我们自己的语种短码(zh、en、ga),各端自己换成该能力在该平台文档里的码。
| 安卓 | iOS | |
|---|---|---|
| 包 | SparkChain.aar,需要时再加 Codec.aar。混淆保留 com.iflytek.sparkchain.**。只加 INTERNET。workDir 用应用私有目录,不申请存储权限 |
sdk/overseas-uid/SparkChain.framework,Embed & Sign。不要链 domestic-uid。workDir 用 Documents 子目录 |
| 全局节点 | ApiType.TPYE_SG(拼写照抄) |
apiType = 1 |
| 选微软引擎 | 各能力自己的 region / 参数,见下表。不要传 iOS 的 RegionMSFT 这个名字 |
RegionMSFT |
| 参数写法 | setParams(key, value),registerCallbacks |
addStringParam / addIntParam / addBoolParam,回调是构造时传入的 delegate,必须强引用到 stop |
同传 AST |
new AST(RegionType.SG_TYPE),新加坡要再设 ast_appid / ast_apikey / ast_apisecret。start(lang, "pcm_s16le", 16000, tag),write(byte[]) |
initWithregion:RegionSG delegate:,同样再设那三个 ast_*。start:audioEncoding:sampleRate:userInfo:,write: 收 NSData。文档写 pcm 建议每 40ms 送 1280 字节,送太快会报错 |
| 同传语种码 | 中文是 cn,英文是 en,不是我们现在的 zh-CN / zh-Hans。微软同传表两端都没有爱尔兰语 |
同左。tts_target_lang 传字符串 |
在线翻译 ITS |
TransType.ITRANS 只有中英日韩等约 9 个,中文码 cn。一百多种(含爱尔兰语 ga,中文码 zh-CN 或 zh)走 TransType.GTRANS |
没有 GTRANS 这个枚举。 类型是 ITRANS、NIUTRANS、ITRANS_SG、GOOGLETRANS。ITRANS 对上那张 9 语表。含 ga 的那张大表标题是「讯飞机器翻译」,文档没写它对应哪个 TransType。接的时候用爱尔兰语 ga 把四个类型各打一次,用能返回译文的那个,不要在 Swift 里写一个不存在的 GTRANS |
爱尔兰语识别 RTASR |
欧洲节点 ga_IE(ist_mul_sp)。微软引擎语种表有 ga-IE(ist_mul_msft),不依赖欧洲加白 |
欧洲节点表有 ga_IE。微软一节只给了 language_name 参数,没有语种表,没有 ga-IE 这一行。先用 RegionMSFT + language_name=ga-IE 在真机打一次;没有识别结果就不要假装和安卓一样,爱尔兰语识别改走已加白的欧洲 ga_IE,或向讯飞要 iOS 微软语种表后再接 |
| 爱尔兰语合成 | OnlineTTS 微软表写了 ga-IE-OrlaNeural、ga-IE-ColmNeural。微软只出 aue=raw 的 PCM |
发音人表是讯飞的 x_xiaoyan 这一套,没有爱尔兰语。文档示例是 initWithRegion:RegionMSFT vcn:@"en-US-JennyNeural"。爱尔兰语先按安卓那个音色名、用 RegionMSFT 打一次;不出 PCM 就不要在 iOS 上报「已支持爱尔兰语播报」 |
| 听写推流 | write 在 SDK 回调线程之外调用,不要堵主线程 |
外部 PCM 不要让 SDK 再配 AVAudioSession。会话类别仍由现在的 EmaiAudioSession / 翻译页负责,否则会把耳机路由或后台音频踩掉 |
合成结果两边都只取 PCM 字节,交给现在的播放器或 CallTranslationDownlink。插件里不要再 new AudioTrack,也不要让 SDK 自己播。
验收按端分开记,不能只测一台:
- 安卓:中英
AST有字有声;中文→爱尔兰语识别用微软ga-IE、翻译用GTRANS的ga、合成用ga-IE-OrlaNeural。 - iOS:中英
AST同样有字有声。爱尔兰语三段以真机结果为准,写明实际用的TransType、识别 region 和音色名。和安卓不一致就在插件里分端,不要改 Dart 的语种短码。
5. 分功能落地
六个功能都挂在现有页面和设备协议上,只换「PCM / 文本送去哪家云」。
模式字符串不要改:simultaneous、faceToFace、call、audioVideo。
translation_controller.dart 的 changeTranslationMode 仍是翻译四个模式的总入口。
共用约定:
- 新提供商字符串用
sparkchain,不要复用已经绑在ntrans.xfyun.cn上的iflytek。 - 语种对在
AST表里就走一条同传会话;不在表里(爱尔兰语)走三段。三段的引擎枚举和语种码按第 4.5 节分端,不要两端都写GTRANS。 - 端上
ITS不能取消在途请求时,用序号丢掉过期的中间译文。最终那一次不能丢。 - 会员在点「开始」时拦,不要只留在
user_translate。 - 切后台的前台服务 / iOS
BackgroundKeepalive、译文路由(pinSpeaker: false)保持现有调用点,不要为了换 SDK 重写。
5.1 同声翻译(simultaneous)
现在的路径:单向。连上 Echo-one 就 DeviceFeatures.recordStart + openMic(AudioRoute.mic),PCM 推进现在的 ASR;没连耳机就用手机麦。识别出整句后走 ServerTranslationService(阿里,覆盖不了再直连微软),再 TTS。安卓进页就 AudioForegroundService.start(keepalive: true, pinSpeaker: false),iOS 再 BackgroundKeepalive.acquire('simultaneous')。默认录音,结束经 RecordingArchive 进语音纪要。
换成 SparkChain 时只动识别、翻译、合成三步:
changeTranslationMode('simultaneous')里现在的_reinitializeAsrService改为按第 3 节选引擎。在AST表内就初始化一条AST(源 = 用户选的源语言,目标 = 目标语言,use_tts=true)。不在表内就初始化听写(只传源语言一种,保持现在的单向,不要开连续语种识别)加端上ITS加OnlineTTS。_armSimultaneousInput/_attachSimultaneousHeadsetMic不动。耳机麦和手机麦的 PCM 都改送到新插件的write。SDK 不自己开麦。手机麦类别保持现在的phone_call。- 译文 PCM 继续交给现在的播放器。不要在插件里另开一路
AudioTrack去抢路由,否则又会把声音从耳机拽到手机扬声器。 - 中间结果:现在 700ms 节流、增长不足 4 字不发、可取消。
AST自带流式译文时直接上屏、不播报。三段路径仍按这个节流去调ITS。 - 停:先停推流和 SDK 会话,再
recordStop关耳机麦,再释放保活。顺序反了,耳机会继续推麦。 - 方向固定为源 → 目标。不要用识别回来的语种去对调方向,现在单向模式就是为了避开这个坑。
验收:连耳机时日志仍是「同声翻译收音:耳机麦」,首帧到达;译文从耳机出,不从手机扬声器出。切后台仍能出字。中英走 AST。中文→爱尔兰语走三段,译文是爱尔兰语文字和声音。
5.2 面对面翻译(faceToFace)
现在有两条收音,不要合成一条:
| 条件 | 现在 | 换 SDK 之后 |
|---|---|---|
Echo-one 已连接且有 faceToFace 能力 |
_startBesFaceToFace:f2fBleStart,双耳 PCM 经 setNativeF2fBridge 进端到端。左右耳按键走 setActiveSpeaker(1/2/0)。失败不退回手机麦 |
桥的通知/槽改由 SparkChain 插件接收,协议帧(AA 6B 等)一个字节不改。语种在 AST 表内:左耳、右耳各一条 AST,方向相反(左耳源→目标,右耳目标→源)。不在表内:两路各一个听写,方向由 activeSpeaker 决定,翻译走 ITS |
| 没连这台耳机 | 手机麦,连续识别,ASR normal,TTS 模式 push_to_talk |
同样手机麦。双向仍靠现在的说话人判断,不要改成单向 |
按住说话只在设备触发时成立(FaceToFaceTrigger.device 且松手 activeSpeaker==0 停识别)。App 里点开始是连续识别,松手逻辑不能套到这条上。
保活:安卓仍是 AudioForegroundService.start()(面对面这条今天没有 keepalive 参数,不要顺手改成和同传一样,除非单独验证双耳 BLE 不被静音轨抢走)。iOS 仍是 BackgroundKeepalive.acquire('faceToFace')。
停:f2fBleStop、关桥、stopContinuousTranslation、释放 iOS 保活。现在的 _stopBesFaceToFace 顺序保留。
验收:双耳都戴上才能开始;左耳说中文、右耳说英文(或爱尔兰语)方向相反且都有声音;来电退出仍弹现有文案并停会话;不连耳机时手机麦仍能翻。
5.3 通话翻译(call)
现在:必须有 DeviceCapability.callAudioTap。callStart + 两路扬声器 + asrStarted,PCM 经 BesCallPcmBridge 直连,不经 Dart。A = 本端麦 → 对方语种,声音给对方;B = 对端通话音 → 用户语种,声音给用户。AudioForegroundService.start(keepalive: true, pinSpeaker: false)。iOS 再保活 callTranslation。initialize 已经拉起会话时,点开始不要再 start 一次。
换成 SparkChain:
- 选路改
findBestMatchingProvider。中英等在AST表内的,A/B 各一个AST实例,打开合成。爱尔兰语 A/B 各走三段:B 路识别对方的爱尔兰语、译成中文、合成中文;A 路相反。识别码、翻译引擎、音色按第 4.5 节分端,安卓用微软ga-IE和ga-IE-OrlaNeural。不要做中文/爱尔兰语自动检测。 - 桥的契约不变(iOS 通知名和键、安卓静态槽)。
ready只在会话销毁时清掉,disable不清,否则同页第二场没有声音。 - 两路
start若并行导致第二路没有回调,沿用现在的间隔:A 起来后约 1.2 秒再起 B。回调对象在插件里强引用到stop。 - 译文 PCM 仍进
CallTranslationDownlink,16 kHz。SDK 若不是 16 kHz,在插件里重采样后再交。耳机协议和 G.722 打包不改。 broadcastpeertranslate仍只决定 B 路要不要在手机上再播一遍,默认 false。- 点开始时若
initialize已拉起,保持_astAutoStartPending,不要再开一路把前一路 generation 冲掉。
验收(不退出页面):中英双方都有字和声音;停掉再开一场仍有;中文→爱尔兰语时近端中文能识别、远端听到爱尔兰语,远端说爱尔兰语时本端有原文、中文译文和耳机里的中文。
5.4 音视频翻译(audioVideo)
现在是单向,和同声一样「听到什么就翻成目标语言」,但音源不是麦:
- 安卓:
MediaProjection抓系统播放。先开悬浮窗,识别就绪后再弹录屏授权。浮窗被拒绝就不起识别。 - iOS:ReplayKit 广播扩展抓系统音频,同时默认拉字幕小窗。小窗起不来不中断翻译。不要再走杰理
openDecoder(),那条已经没有 PCM。 - 两端都把
_audioSourceType设为外部 PCM。TTS 默认关(isTtsEnabled=false),这个模式是看字幕,不是把译文再播出来盖住原视频。 - 小窗只在
call和audioVideo显示。切到同声或面对面必须关掉,否则会盖住页面。 - 录音不进语音纪要。这条先保持。
换成 SparkChain:
- 捕获链路(录屏、广播扩展、悬浮窗、画中画)不动。外部 PCM 改推进新插件。
- 引擎与同声相同:表内
AST但 不要开合成(use_tts=false),只要识别和翻译文字。表外用听写 +ITS,不调OnlineTTS。 - 识别只传源语言一种。方向用现在的
_isOneWayMode,不要按检测到的语种对调。 - 字幕小窗继续吃现在的历史列表。不要为 SDK 再做一套浮窗数据。
验收:安卓授权录屏后切到别的 App,悬浮窗里有译文;拒绝悬浮窗权限则不开始。iOS 广播开始后切后台,小窗能出字,原视频声音不被译文盖住。
5.5 EMAI(耳机唤醒对话)
现在:DeviceAiSessionService._handleStart。3 秒内先 aiAccept。耳机走原生百炼(BailianNativeDialog + 设备 PCM 桥),车载香薰走 Dart 百炼、回复用手机 A2DP。问候语、工具调用解析、切后台保活、音乐焦点都在这条服务里。手机里按住说话的那一页不在这条链路里,本次不动。
海外 SDK 没有对等的全双工对话。落地用三段接进同一个 _handleStart,不新做一套唤醒:
- 设备协议不动:
aiAccept、退出命令、PCM 进出耳机的桥。桥的消费方从百炼客户端改成 SparkChain 听写推流。 - 会话建立后先播现在的随机问候(「我在 / 回来了 / 有什么可以帮到你」),用
OnlineTTS合成,走现在的喇叭(耳机私有下行,或香薰的 A2DP 播放器)。不要等用户先说话。 - 上行 PCM 进听写。一句最终结果出来后,调文本
LLM。location、date 仍在 Dart 里拼进提示词(地址是「未知」时不要把这两个字写进去),再把整段提示词交给 SDK。回复 PCM 按 16 kHz 进现在的喇叭。百炼那路 24000 不要照搬。 - 工具调用:安卓
LLM有LLMTools,iOS 用同一份工具 JSON 的对应写法。返回仍交给assistant_directive_service.dart,金测试不挪到原生。第一版先打通「问一句有回答」;工具不通时记下来,不要为了工具去改待办的解析格式。quit仍走现在的退出命令。SDK 不支持工具时,定闹钟会失败,问答不能因此被拿掉。 - 打断:香薰在会话中再按唤醒键,保持现在的「扔掉待播、听新的一句」。耳机原生路径保持现在的门控(播报时上行换静音),开关时机仍由
DeviceAiSessionService决定,不要搬进 SDK。 - 保活和音乐焦点调用点不动:安卓先前台服务再抢焦点再静音;耳机静音钉扬声器,香薰不钉。iOS 仍是
BackgroundKeepalive。 - 百炼配置缺失时现在会拒唤醒。改完后改为 SparkChain 未初始化就拒,仍要
ai退出,避免设备空等。
做不到的要写进验收说明:没有百炼那种「模型边听边说」的同传式回复,是说完一句再答。延迟会比现在大。这是 SDK 能力边界,不是实现遗漏。
验收:耳机按键能听到随机问候;问一句有文字和声音;定闹钟仍进待办;切后台不断;没在播的音乐不会被拉起来;香薰有音乐在播时,结束对话后音乐恢复。
5.6 语音纪要
两条入口,换的地方不同。
翻译页顺带归档(通话、同声、面对面;音视频不归档):
停录音时 RecordingArchive.archive 把文件和已经翻译好的句子交给 echomeet_addrecord。服务端不再对这段转写做识别。所以这三条换 SDK 之后,纪要里的字来自端上识别结果,服务端只负责存和总结。总结模型改讯飞 HTTP 时,这条归档不用再传音频给转写服务商。时间轴、暂停区间、文件名前缀(trans_call / trans_holder)保持。
纪要首页自己录或从设备导入:
meeting_home_controller 调 echomeet_addrecord,音频在服务端排队转写再总结。这里换的是 echomeet 的服务商,不是手机 SDK:
- 新增
ServiceType(例如Iflytek),实现现有Transcriber和Summarizer。状态机、轮询、卡住扫描、失败退算力都不改。 - 转写走讯飞开放平台「录音文件转写」HTTP,与 SDK 的
RAASR是同一类云能力,但是 HTTP,不要在容器里跑 AAR。空语言仍是自动识别,不要退回中文。转写结果不再翻译;总结用什么语言仍由现有提示词决定。 - 总结走讯飞文本大模型 HTTP。
<remark>/<transcript>的包法不动。 - 支架 TF 卡导入只负责把文件送进同一条
addrecord,导入逻辑不改。 - 客户端能力接口不改。语言列表跟着新服务的
languages。
开放平台 appId 若和 SparkChain 不是同一个,后台配两套字段,对用户仍是一个「讯飞」。
验收:同声或通话翻完,纪要里能看到音频、端上那份转写、以及总结。首页新录一条中文、一条非中文,转写和总结都能完成;失败退算力只退一次。
5.7 文本翻译页和图片翻译
文本页走 ServerTranslationService。改为优先端上 ITS,爱尔兰语码 ga。安卓用 GTRANS;iOS 用第 4.5 节试出来的那个 TransType。失败再调 user_translate,服务端内部改讯飞 HTTP,路径名不变,给没升级的包兜底。
图片翻译继续只出原文和译文,不回贴图。用图片理解加提示词抽出图中文字并翻译。国内「在线图片翻译」不接。
6. 只改测试服:服务器接口和管理后台
测试服和正式服是两套机器、两套库、两个域名。改测试服的 svc_config / echomeet_orch 不会同步到正式服。反过来,对正式后台点保存会立刻影响正式用户。本次所有后台操作都在测试后台里、应用选 ym,看请求是不是打到 ym-dev。
| 测试服(本次唯一允许动的) | 正式服(禁止) | |
|---|---|---|
| 机器 | 8.133.166.29 |
47.116.104.181 |
| 业务接口 | https://ym-dev.ymaikj.com |
https://ym.ymaikj.com |
| 发布 | deploy/app、deploy/console、deploy/admin 下 ./dev-deploy.sh along |
prod-build.sh、prod-deploy.sh |
| 客户端 | apps/client/.env 的 SERVER_URL 保持 ym-dev |
不要把联调包的服务器改成正式域名,也不要把这版上架 |
| 密钥 | 本机 DEV_KEY=$HOME/Documents/keys/loginscre.pem,不改脚本里的默认路径 |
同一把钥匙也能登上正式机,所以上面这条禁止要靠人不执行正式脚本,不是网络隔离 |
dev-deploy.sh 只换镜像,不上传服务器上的 .env 和 confs/*.yaml。测试机上已有的网关白名单、数据库不要用正式机文件覆盖。新接口若要进网关白名单,只改测试机那份 confs/gateway.yaml 然后重启测试容器。
6.1 后台改哪些页
管理后台登录测试环境,右上角应用固定为 ym。凭据按应用隔离,改错应用等于改了另一套产品。页面只动这两个:
第三方服务配置(/serviceconfig)
模板写在 api_svctemplate.go,要先把 console 镜像发到测试服,页面上才出新行。然后只在 ym 上启用并填值,不要点到别的应用。
| 新服务 id | 类别 | 谁读 | 字段 |
|---|---|---|---|
sparkchain |
不标类别 4。随 user_getappconfig_v3 下发 |
客户端 AppConfig.cred |
app_id、api_key、api_secret(后两个加密)。api_type 默认新加坡,只是 IP 查不到时的兜底,真正节点见第 4.6 节。另三个可选:ast_app_id、ast_api_key、ast_api_secret,空则原生用上面那一套 |
asrfile_iflytek |
录音文件识别 | 会议配置 → 转写 | 开放平台的 appId / secret,以及任务用的引擎(微软长音频含 ga-IE 时写在这里,不要写进客户端) |
llm_iflytek |
文本大模型 | 会议配置 → 总结,以及 EMAI 若改走服务端代答时 | 模型名、接口地址。地址以讯飞开放平台文档为准,不要抄 SDK 里那个私有 path 自己猜签名 |
mt_iflytek |
机器翻译 | user_translate 兜底 |
这是服务端 HTTP,不是手机 SDK 的枚举。爱尔兰语短码 ga。中文用开放平台要求的码,不要写成安卓 GTRANS 或 iOS GOOGLETRANS |
api_key / api_secret 必须标加密。下发仍走已有的 v3,测试机网关白名单里已经要有 user_getappconfig_v3。缺这一行时未登录拉配置会 code:18,客户端静默拿不到密钥,表现是「后台填了手机却初始化失败」。
旧服务在测试服由 AI_ONLY_IFLYTEK=1 整段拦住:识别、合成、翻译、端到端、大模型、录音转写、灵犀都不许再打到微软、阿里、字节、谷歌。库里的行先留着,但接口不会去调,凭据也不再下发给连着测试服的包。正式服不设这个变量。允许的只有 sparkchain、mt_iflytek、asrfile_iflytek、llm_iflytek。
会议配置(/meetconfig)
这一页不存密钥,只引用上面配好的服务 id,写在该应用的 echomeet_orch(region=0)。
- 转写:把
asrfile_iflytek设为优先级最高。旧的录音识别先留在后面当兜底,测试通过后再在测试服停用。选路是「已启用且支持该语言的第一个」,所以旧服务如果优先级更高,讯飞永远不会被叫到。 - 总结:默认模型改成
llm_iflytek。允许客户端选模型时,列表里不要再出现正式环境才有的模型名,避免测试包选了一个测试库里没有的 id。 - 不要改会议模板正文,也不要改「转写不翻译、翻译只决定总结语言」的开关语义。
- 保存后看测试服 home 日志有没有
ConfigChanged。没有就等最多 10 分钟的定时重载,或重启测试 home。不要为了生效去重启正式机。
6.2 服务器接口怎么改(代码进仓库,镜像只上测试服)
| 接口 | 现在 | 测试服上的新行为 | 正式服 |
|---|---|---|---|
POST /api/home/user_translate |
model_translate.go 只构造阿里翻译。阿里没有的语种,客户端自己直连微软 |
增加 mt_iflytek 分支。测试服启用它、停用阿里 MT 后,这个接口只打讯飞。路径、请求字段、VIP 判断不改,老包还能调 |
代码不到正式机,正式仍是阿里 |
user_getappconfig_v3 |
下发 thirdsvcs |
多一个 sparkchain。客户端 cred() 读它。空则该能力不可用,不回退旧密钥 |
正式不下发这套密钥 |
echomeet_addrecord / 转写轮询 / 总结 |
四家服务商 | provider_registry.go 增加讯飞 Transcriber + Summarizer。状态机、退算力、空语言自动识别不改。翻译页归档带来的句子仍然不重转写 |
正式仍是原来的四家 |
echomeet_getcapabilities |
语言列表来自编排里启用的服务 | 协议不改。测试服换成讯飞后,列表跟着新服务的 languages |
正式列表不变 |
resolveTranslator 今天认不出的 MT 会打日志并跳过。接 mt_iflytek 时要在构造分支里真正返回翻译客户端,不能只在模板里加一行却让运行时跳过。跳过的表现是测试服 user_translate 返回「未配置机器翻译服务」,客户端再去直连微软,等于没换。
会议转写 HTTP 与手机 SDK 的 init 不是同一段鉴权。开放平台 appId 若和 SparkChain 不是同一个,测试后台配两套字段,对测试用户仍显示一个「讯飞」。
部署顺序(都在测试服):
deploy/console && DEV_KEY=$HOME/Documents/keys/loginscre.pem ./dev-deploy.sh along,让新模板出现。- 测试后台按 6.1 填
ym的服务,先不要停旧的。 deploy/app同样./dev-deploy.sh along,home 里的讯飞分支才在ym-dev生效。- 用测试包打
user_translate和一条纪要,确认日志里的服务 id 是讯飞。 - 再在测试后台停用旧的语音服务。
- 管理后台本身若改了页面,
deploy/admin也只./dev-deploy.sh along。
回退只回测试服:测试后台把旧服务重新启用、讯飞编排优先级放后;必要时把测试机镜像换回上一版 dev-latest 之前的镜像。正式服没有这次的镜像和配置,不需要回退。
版本号:实施时再升客户端 pubspec 的构建号。本文不改版本。联调包不要提交商店。
7. 实施顺序
| 步 | 内容 | 完成的标志 |
|---|---|---|
| 1 | 原生插件:init、听写一句、翻译一句、合成一句 | 安卓、iOS 各通一次。爱尔兰语按第 4.5 节各用各的枚举,日志无密钥 |
| 2 | 文本翻译页改 ITS |
中英、中文→爱尔兰语出字 |
| 3 | 同声翻译 | 第 5.1 节:耳机麦、译文走耳机、切后台、爱尔兰语三段 |
| 4 | 面对面 | 第 5.2 节:双耳方向相反;无耳机时手机麦仍可用 |
| 5 | 音视频 | 第 5.4 节:字幕出来,不播译文盖住原视频 |
| 6 | 通话翻译 | 第 5.3 节三条验收 |
| 7 | EMAI | 第 5.5 节:问候、问答、待办、切后台、音乐 |
| 8 | 语音纪要 | 第 5.6 节。服务端只部署测试服;首页新录音在 ym-dev 上走讯飞 |
| 9 | 测试后台停用旧语音服务 | 只停测试库 ym。正式库不停。旧密钥不再下发给连着 ym-dev 的包 |
灵犀、存储、短信、实名不在表里。
8. 不要一起改的东西
- 正式服:机器
47.116.104.181、域名ym.ymaikj.com、正式后台库、prod-deploy.sh。 - Echo-one 协议字节、通话下行的 G.722 打包、
pinSpeaker的策略(同传和通话翻译保持不钉死手机扬声器)。 - 面对面的双耳 BLE。
- 设备 AI 对话的音乐焦点和后台保活。
user_translate的路由名。老包还在调。新实现可以换内部服务商,不能改路径。- 会议「转写不翻译、翻译只影响总结语言」的口径。
9. 风险
| 风险 | 处理 |
|---|---|
| 密钥重新进客户端 | 只经测试服 v3 下发这一套讯飞密钥;日志不打;混淆沿用现有 release 包 |
| 误发到正式服 | 发布命令只允许 dev-deploy.sh along。正式机和正式后台本次不登录去改配置 |
| 同传微软表没有爱尔兰语 | 该语种强制三段,不走 AST |
| 两端 SDK 接口不一致 | 按第 4.5 节分端实现。iOS 没有 GTRANS,微软爱尔兰语识别和 ga-IE-OrlaNeural 只在安卓文档里写全。iOS 用真机打通后再写死参数,不要抄安卓枚举 |
workDir 指向外部存储 |
init 在华为 Android 12 上直接失败。用应用私有目录,不申请存储权限 |
sparkchain 标成类别 4 |
非会员整份密钥被拿掉,文本翻译和 EMAI 一起不能用。类别不要用 4,翻译会员在点开始时拦 |
| SDK 自己开麦 | 和耳机麦、前台服务抢设备。PCM 一律由现有采集推进 write |
语种不在 AST 表里却仍走 AST |
按我们的语种表显式映射。表里没有的走三段;三段也没有的,沿用现在的不支持提示,不要猜一个相近语种码 |
| 按手机系统语言切到欧洲节点 | 系统地区不是人所在地。节点用测试服 IP 区域,欧洲未加白就保持新加坡。中国用户也不切国内节点 |
| 欧洲节点未加白 | 识别用微软引擎,全局节点保持新加坡 |
| 两路会话第二路无回调 | 强引用回调;必要时两路启动间隔 1.2 秒 |
| 会议 HTTP 与 SDK 不是同一个 appId | 实施前向讯飞确认;不能共用就后台两套字段 |
| 声音复刻消失 | 对用户说明中英通话不再用说话人本人音色,改固定发音人。不在本次用国内声音复刻补 |
| EMAI 没有全双工 | 按第 5.5 节接成说完再答;工具调用和设备协议不搬进 SDK |