# 科大讯飞 SparkChain —— 第三方语音与模型全面替换落地文档 > 2026-10-15 起草,同日按功能、测试服边界、两端 SDK 差异和项目入口再补过。**本文只做设计,未改任何代码。** > 真机怎么测、失败了改哪一行,见 [科大讯飞SparkChain-真机自测与回改.md](科大讯飞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。`` / `` 的包法不动。 - 支架 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 |