You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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 能链接的库。 所以「全面换成它」要拆成两截,缺一截都不算换完:

  1. 手机上的实时链路(通话翻译、同声传译、面对面、文本翻译、播报)直接调 SDK。
  2. 服务器上的离线链路(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 / iOS RegionMSFT):实时转写、录音文件转写、在线合成、同声传译的另一张语种表。 爱尔兰语走这一层,不要为了它把全局节点改成欧洲。

兼容范围:安卓 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 服务的地址。

时机不要等到进翻译页,也不要只绑在登录按钮上:

  1. 启动拉 user_getappconfig 时,服务端已经用请求 IP 做过 ipinfo → ToRegion(modules/user/core.go)。这个结果今天只用于 MCP 和配置作用域,没有回给客户端。测试服在这份响应里多带一个讯飞节点建议。
  2. 客户端拿到之后再 init。同一进程里节点不变就不 unInit。
  3. 登录(api_sgin)也会按 IP 写下 CountryCode。若登录时的地区算出的节点和启动时不同(人换了网络),登录成功后 unInit 再 init 一次。一场翻译或 EMAI 进行中不要换节点。
  4. 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 时只动识别、翻译、合成三步:

  1. changeTranslationMode('simultaneous') 里现在的 _reinitializeAsrService 改为按第 3 节选引擎。在 AST 表内就初始化一条 AST(源 = 用户选的源语言,目标 = 目标语言,use_tts=true)。不在表内就初始化听写(只传源语言一种,保持现在的单向,不要开连续语种识别)加端上 ITS 加 OnlineTTS。
  2. _armSimultaneousInput / _attachSimultaneousHeadsetMic 不动。耳机麦和手机麦的 PCM 都改送到新插件的 write。SDK 不自己开麦。手机麦类别保持现在的 phone_call。
  3. 译文 PCM 继续交给现在的播放器。不要在插件里另开一路 AudioTrack 去抢路由,否则又会把声音从耳机拽到手机扬声器。
  4. 中间结果:现在 700ms 节流、增长不足 4 字不发、可取消。AST 自带流式译文时直接上屏、不播报。三段路径仍按这个节流去调 ITS。
  5. 停:先停推流和 SDK 会话,再 recordStop 关耳机麦,再释放保活。顺序反了,耳机会继续推麦。
  6. 方向固定为源 → 目标。不要用识别回来的语种去对调方向,现在单向模式就是为了避开这个坑。

验收:连耳机时日志仍是「同声翻译收音:耳机麦」,首帧到达;译文从耳机出,不从手机扬声器出。切后台仍能出字。中英走 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:

  1. 选路改 findBestMatchingProvider。中英等在 AST 表内的,A/B 各一个 AST 实例,打开合成。爱尔兰语 A/B 各走三段:B 路识别对方的爱尔兰语、译成中文、合成中文;A 路相反。识别码、翻译引擎、音色按第 4.5 节分端,安卓用微软 ga-IE 和 ga-IE-OrlaNeural。不要做中文/爱尔兰语自动检测。
  2. 桥的契约不变(iOS 通知名和键、安卓静态槽)。ready 只在会话销毁时清掉,disable 不清,否则同页第二场没有声音。
  3. 两路 start 若并行导致第二路没有回调,沿用现在的间隔:A 起来后约 1.2 秒再起 B。回调对象在插件里强引用到 stop。
  4. 译文 PCM 仍进 CallTranslationDownlink,16 kHz。SDK 若不是 16 kHz,在插件里重采样后再交。耳机协议和 G.722 打包不改。
  5. broadcastpeertranslate 仍只决定 B 路要不要在手机上再播一遍,默认 false。
  6. 点开始时若 initialize 已拉起,保持 _astAutoStartPending,不要再开一路把前一路 generation 冲掉。

验收(不退出页面):中英双方都有字和声音;停掉再开一场仍有;中文→爱尔兰语时近端中文能识别、远端听到爱尔兰语,远端说爱尔兰语时本端有原文、中文译文和耳机里的中文。

5.4 音视频翻译(audioVideo)

现在是单向,和同声一样「听到什么就翻成目标语言」,但音源不是麦:

  • 安卓:MediaProjection 抓系统播放。先开悬浮窗,识别就绪后再弹录屏授权。浮窗被拒绝就不起识别。
  • iOS:ReplayKit 广播扩展抓系统音频,同时默认拉字幕小窗。小窗起不来不中断翻译。不要再走杰理 openDecoder(),那条已经没有 PCM。
  • 两端都把 _audioSourceType 设为外部 PCM。TTS 默认关(isTtsEnabled=false),这个模式是看字幕,不是把译文再播出来盖住原视频。
  • 小窗只在 call 和 audioVideo 显示。切到同声或面对面必须关掉,否则会盖住页面。
  • 录音不进语音纪要。这条先保持。

换成 SparkChain:

  1. 捕获链路(录屏、广播扩展、悬浮窗、画中画)不动。外部 PCM 改推进新插件。
  2. 引擎与同声相同:表内 AST 但 不要开合成(use_tts=false),只要识别和翻译文字。表外用听写 + ITS,不调 OnlineTTS。
  3. 识别只传源语言一种。方向用现在的 _isOneWayMode,不要按检测到的语种对调。
  4. 字幕小窗继续吃现在的历史列表。不要为 SDK 再做一套浮窗数据。

验收:安卓授权录屏后切到别的 App,悬浮窗里有译文;拒绝悬浮窗权限则不开始。iOS 广播开始后切后台,小窗能出字,原视频声音不被译文盖住。

5.5 EMAI(耳机唤醒对话)

现在:DeviceAiSessionService._handleStart。3 秒内先 aiAccept。耳机走原生百炼(BailianNativeDialog + 设备 PCM 桥),车载香薰走 Dart 百炼、回复用手机 A2DP。问候语、工具调用解析、切后台保活、音乐焦点都在这条服务里。手机里按住说话的那一页不在这条链路里,本次不动。

海外 SDK 没有对等的全双工对话。落地用三段接进同一个 _handleStart,不新做一套唤醒:

  1. 设备协议不动:aiAccept、退出命令、PCM 进出耳机的桥。桥的消费方从百炼客户端改成 SparkChain 听写推流。
  2. 会话建立后先播现在的随机问候(「我在 / 回来了 / 有什么可以帮到你」),用 OnlineTTS 合成,走现在的喇叭(耳机私有下行,或香薰的 A2DP 播放器)。不要等用户先说话。
  3. 上行 PCM 进听写。一句最终结果出来后,调文本 LLM。location、date 仍在 Dart 里拼进提示词(地址是「未知」时不要把这两个字写进去),再把整段提示词交给 SDK。回复 PCM 按 16 kHz 进现在的喇叭。百炼那路 24000 不要照搬。
  4. 工具调用:安卓 LLM 有 LLMTools,iOS 用同一份工具 JSON 的对应写法。返回仍交给 assistant_directive_service.dart,金测试不挪到原生。第一版先打通「问一句有回答」;工具不通时记下来,不要为了工具去改待办的解析格式。quit 仍走现在的退出命令。SDK 不支持工具时,定闹钟会失败,问答不能因此被拿掉。
  5. 打断:香薰在会话中再按唤醒键,保持现在的「扔掉待播、听新的一句」。耳机原生路径保持现在的门控(播报时上行换静音),开关时机仍由 DeviceAiSessionService 决定,不要搬进 SDK。
  6. 保活和音乐焦点调用点不动:安卓先前台服务再抢焦点再静音;耳机静音钉扬声器,香薰不钉。iOS 仍是 BackgroundKeepalive。
  7. 百炼配置缺失时现在会拒唤醒。改完后改为 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)。

  1. 转写:把 asrfile_iflytek 设为优先级最高。旧的录音识别先留在后面当兜底,测试通过后再在测试服停用。选路是「已启用且支持该语言的第一个」,所以旧服务如果优先级更高,讯飞永远不会被叫到。
  2. 总结:默认模型改成 llm_iflytek。允许客户端选模型时,列表里不要再出现正式环境才有的模型名,避免测试包选了一个测试库里没有的 id。
  3. 不要改会议模板正文,也不要改「转写不翻译、翻译只决定总结语言」的开关语义。
  4. 保存后看测试服 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 不是同一个,测试后台配两套字段,对测试用户仍显示一个「讯飞」。

部署顺序(都在测试服):

  1. deploy/console && DEV_KEY=$HOME/Documents/keys/loginscre.pem ./dev-deploy.sh along,让新模板出现。
  2. 测试后台按 6.1 填 ym 的服务,先不要停旧的。
  3. deploy/app 同样 ./dev-deploy.sh along,home 里的讯飞分支才在 ym-dev 生效。
  4. 用测试包打 user_translate 和一条纪要,确认日志里的服务 id 是讯飞。
  5. 再在测试后台停用旧的语音服务。
  6. 管理后台本身若改了页面,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