OpenMAIC 接入小米 MiMo TTS 语音合成


tags:

  • OpenMAIC
  • 语音合成
  • MiMo
  • TTS
  • 工具使用
    date: 2026-09-10

OpenMAIC 接入小米 MiMo TTS 语音合成

记录给 OpenMAIC 接入小米 MiMo-V2.5-TTS 的完整过程:协议调研 → 架构定位 → 代码改造 → 配置启用 → 验证与限制。

一、背景与目标

  • OpenMAIC 内置 TTS 服务商为 OpenAI / Azure / GLM / Qwen / Doubao / MiniMax / ElevenLabs / VoxCPM2 / Lemonade / 浏览器原生,没有小米 MiMo
  • OpenMAIC 提供"添加自定义语音合成"入口,但该入口只实现 OpenAI 兼容的 POST {baseUrl}/audio/speech 协议。
  • 小米 MiMo TTS 表面上是"OpenAI 兼容",实际走的是 chat/completions + audio 参数 ,并非 /audio/speech,因此光靠界面配置无法接入,必须写原生 provider。

目标:让 OpenMAIC 里出现 "Xiaomi MiMo TTS",可选 mimo-v2.5-tts 模型与官方 9 个预置音色,并能在设置页试听、在课堂生成中作为语音讲解使用。

二、MiMo TTS 接口协议调研

来源:https://mimo.mi.com/docs (MiMo-TTS 系列 - OpenAI API 兼容)

2.1 请求

复制代码
POST https://api.xiaomimimo.com/v1/chat/completions
api-key: $MIMO_API_KEY
Content-Type: application/json
json 复制代码
{
  "model": "mimo-v2.5-tts",
  "messages": [
    { "role": "assistant", "content": "要朗读的文本" }
  ],
  "audio": { "format": "wav", "voice": "mimo_default" },
  "stream": false
}

关键点:

说明
待合成文本位置 必须在 role: "assistant" 的 message 中,不能放 user
user 消息 可选,用于传入自然语言风格/语气指令(不朗读其内容);voicedesign 模型必填
认证 官方示例用 api-key 头;用 OpenAI SDK(传 api_key)时实际发的是 Authorization: Bearer,两种都可用
音频参数 audio.format 支持 wav / mp3 / pcm / pcm16;audio.voice 传预置音色 ID
流式 stream: true 时输出 pcm16,自行拼接;非流式直接拿完整 base64

2.2 响应

json 复制代码
{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "audio": { "id": "...", "data": "base64Data" }
      }
    }
  ]
}

音频为 base64 ,位于 choices[0].message.audio.data,需自行解码成字节。

2.3 模型与预置音色

Model ID 用途
mimo-v2.5-tts 预置精品音色(本次接入的模型)
mimo-v2.5-tts-voicedesign 用文本描述设计音色
mimo-v2.5-tts-voiceclone 用音频样本复刻音色

mimo-v2.5-tts 预置音色:

音色名 Voice ID 语言 性别
MiMo 默认 mimo_default 视集群(中国集群=冰糖,其他=Mia) -
冰糖 冰糖 中文
茉莉 茉莉 中文
苏打 苏打 中文
白桦 白桦 中文
Mia Mia 英文
Chloe Chloe 英文
Milo Milo 英文
Dean Dean 英文

!note 中文音色的 Voice ID 就是中文词本身(如 冰糖),这是官方定义,不是笔误。

2.4 风格控制方式(后续可用)

  • 自然语言控制:写在 user 消息里,例如"用轻快上扬的语调朗读,语速稍快"。
  • 标签控制:写在 assistant 文本开头或句中,例如 (磁性)...(东北话)...(唱歌)歌词(叹气)(语速加快)
  • 因此 MiMo 没有独立的语速数值参数,语速/情绪都通过文本指令表达(见第五节限速映射实现)。

三、OpenMAIC 的 TTS 架构速览

接入前必须知道这几层:

文件 作用
类型定义 lib/audio/types.ts BuiltInTTSProviderId 联合类型 + TTSProviderConfig 结构
服务商注册表 lib/audio/constants.ts TTS_PROVIDERSDEFAULT_TTS_VOICESDEFAULT_TTS_MODELS
合成实现 lib/audio/tts-providers.ts generateTTS() switch 分发到各服务商实现
服务端配置 lib/server/provider-config.ts 环境变量前缀 → provider id 映射、key/baseUrl/model 解析、禁用开关
设置存储 lib/store/settings.ts ttsProvidersConfig 默认条目(apiKey/baseUrl/modelId/enabled)
显示名 lib/audio/provider-display.ts + lib/i18n/locales/*.json provider 多语言名称
设置界面 components/settings/tts-settings.tsx 服务商列表、模型/音色选择、试听、请求地址提示
请求链路 app/api/generate/tts/route.ts 客户端配置 → resolveTTSApiKey/BaseUrl/ModelgenerateTTS()

一次语音合成的实际流程:

  1. 客户端把 ttsProviderId / ttsVoice / ttsModelId / ttsApiKey / ttsBaseUrl 发给 /api/generate/tts
  2. 服务端判断该 provider 是否"服务端托管"(env/YAML 配了 key),托管则忽略客户端 key。
  3. resolveTTSApiKey() 取 key,resolveTTSBaseUrl() 取 baseUrl(没有则回退注册表 defaultBaseUrl),resolveTTSModel() 取模型。
  4. generateTTS(config, text) 按 provider 分发到具体实现,返回 { audio: Uint8Array, format }

四、接入步骤(逐文件)

4.1 类型:新增 provider id

lib/audio/types.ts:

ts 复制代码
export type BuiltInTTSProviderId =
  | 'openai-tts'
  | 'azure-tts'
  | 'glm-tts'
  | 'qwen-tts'
  | 'voxcpm-tts'
  | 'doubao-tts'
  | 'elevenlabs-tts'
  | 'minimax-tts'
  | 'xiaomi-tts'      // 新增
  | 'lemonade-tts'
  | 'browser-native-tts';

4.2 注册表:模型与音色

lib/audio/constants.tsTTS_PROVIDERS 中新增:

ts 复制代码
'xiaomi-tts': {
  id: 'xiaomi-tts',
  name: 'Xiaomi MiMo TTS',
  requiresApiKey: true,
  defaultBaseUrl: 'https://api.xiaomimimo.com/v1',
  icon: '/logos/xiaomi.svg',
  models: [{ id: 'mimo-v2.5-tts', name: 'MiMo-V2.5-TTS' }],
  defaultModelId: 'mimo-v2.5-tts',
  voices: [
    { id: 'mimo_default', name: 'MiMo 默认', language: 'zh-CN', gender: 'neutral' },
    { id: '冰糖', name: '冰糖', language: 'zh-CN', gender: 'female' },
    { id: '茉莉', name: '茉莉', language: 'zh-CN', gender: 'female' },
    { id: '苏打', name: '苏打', language: 'zh-CN', gender: 'male' },
    { id: '白桦', name: '白桦', language: 'zh-CN', gender: 'male' },
    { id: 'Mia', name: 'Mia', language: 'en-US', gender: 'female' },
    { id: 'Chloe', name: 'Chloe', language: 'en-US', gender: 'female' },
    { id: 'Milo', name: 'Milo', language: 'en-US', gender: 'male' },
    { id: 'Dean', name: 'Dean', language: 'en-US', gender: 'male' },
  ],
  supportedFormats: ['wav', 'mp3', 'pcm'],
  speedRange: { min: 0.5, max: 2.0, default: 1.0 },
},

同时补两个 Record<BuiltInTTSProviderId, string> 映射(缺一个就会类型报错):

ts 复制代码
export const DEFAULT_TTS_VOICES = { /* ... */ 'xiaomi-tts': 'mimo_default' };
export const DEFAULT_TTS_MODELS = { /* ... */ 'xiaomi-tts': 'mimo-v2.5-tts' };

4.3 合成实现:chat/completions + base64 解码

lib/audio/tts-providers.ts 的 switch 增加分支,并实现:

ts 复制代码
case 'xiaomi-tts':
  return await generateXiaomiMiMoTTS(config, text, signal);
ts 复制代码
async function generateXiaomiMiMoTTS(
  config: TTSModelConfig,
  text: string,
  signal: AbortSignal,
): Promise<TTSGenerationResult> {
  const baseUrl = (config.baseUrl || TTS_PROVIDERS['xiaomi-tts'].defaultBaseUrl || '').replace(
    /\/$/,
    '',
  );
  if (!baseUrl) throw new Error('Xiaomi MiMo TTS base URL is required');

  const apiKey = config.apiKey?.trim();
  if (!apiKey) throw new Error('API key required for Xiaomi MiMo TTS');

  const format = config.format || 'wav';
  const voice = config.voice?.trim() || 'mimo_default';

  const messages: Array<{ role: 'user' | 'assistant'; content: string }> = [];
  const speed = config.speed && config.speed !== 1 ? config.speed : undefined;
  if (speed) {
    // MiMo 没有数值语速参数,用自然语言 user 指令表达
    const factor = Math.round(speed * 100) / 100;
    messages.push({
      role: 'user',
      content:
        factor < 1
          ? `请用更慢的语速朗读,约为原来的 ${factor} 倍。`
          : `请用更快的语速朗读,约为原来的 ${factor} 倍。`,
    });
  }
  messages.push({ role: 'assistant', content: text });

  const response = await fetch(`${baseUrl}/chat/completions`, {
    method: 'POST',
    headers: {
      'api-key': apiKey,                    // 官方文档写法
      Authorization: `Bearer ${apiKey}`,    // OpenAI SDK 风格,兼容两种 key
      'Content-Type': 'application/json; charset=utf-8',
    },
    body: JSON.stringify({
      model: config.modelId || TTS_PROVIDERS['xiaomi-tts'].defaultModelId,
      messages,
      audio: { format, voice },
      stream: false,
    }),
    signal,
  });

  if (!response.ok) {
    throwIfTtsRateLimited('Xiaomi MiMo', response.status);
    throw new Error(`Xiaomi MiMo TTS API error: ${await readTTSApiError(response)}`);
  }

  const data = (await response.json().catch(() => null)) as {
    choices?: Array<{ message?: { audio?: { data?: string } } }>;
  } | null;
  const audioBase64 = data?.choices?.[0]?.message?.audio?.data;
  if (!audioBase64 || typeof audioBase64 !== 'string') {
    throw new Error(`Xiaomi MiMo TTS error: no audio returned. Response: ${JSON.stringify(data ?? null)}`);
  }

  return {
    audio: new Uint8Array(Buffer.from(audioBase64, 'base64')),
    format,
  };
}

4.4 服务端环境变量映射

lib/server/provider-config.tsTTS_ENV_MAP:

ts 复制代码
TTS_XIAOMI: 'xiaomi-tts',
TTS_MIMO: 'xiaomi-tts',

映射后自动获得:

  • TTS_XIAOMI_API_KEY / TTS_XIAOMI_BASE_URL / TTS_XIAOMI_MODELS
  • TTS_XIAOMI_ENABLED=false 运营级禁用开关
  • 同名别名 TTS_MIMO_*

4.5 显示名与国际化

lib/audio/provider-display.ts:

ts 复制代码
const TTS_PROVIDER_NAME_KEYS = {
  // ...
  'xiaomi-tts': 'settings.providerXiaomiTTS',
};

lib/i18n/locales/*.json(12 个语言文件)在 providerMiniMaxTTS 后新增:

json 复制代码
"providerXiaomiTTS": "小米 MiMo TTS"

英文等语言用 "Xiaomi MiMo TTS"。改完执行 node scripts/check-i18n-keys.mjs 校验多语言键对齐。

4.6 客户端默认配置

lib/store/settings.tsgetDefaultAudioConfig().ttsProvidersConfig(每个内置 provider 都要有条目):

ts 复制代码
'xiaomi-tts': { apiKey: '', baseUrl: '', modelId: 'mimo-v2.5-tts', enabled: true },

4.7 设置页请求地址提示

components/settings/tts-settings.tsxendpointPath 增加分支,让界面显示正确的请求路径:

ts 复制代码
case 'xiaomi-tts':
  return '/chat/completions';

4.8 环境变量文档

.env.example 的 TTS 段落新增:

env 复制代码
# Xiaomi MiMo TTS (mimo-v2.5-tts, chat-completions based)
TTS_XIAOMI_API_KEY=
TTS_XIAOMI_BASE_URL=https://api.xiaomimimo.com/v1
# Token Plan 区域端点:
# TTS_XIAOMI_BASE_URL=https://token-plan-cn.xiaomimimo.com/v1
# TTS_XIAOMI_BASE_URL=https://token-plan-sgp.xiaomimimo.com/v1
# TTS_XIAOMI_BASE_URL=https://token-plan-ams.xiaomimimo.com/v1
# TTS_MIMO_* 是 TTS_XIAOMI_* 的别名

五、配置与启用

.env.local:

env 复制代码
TTS_XIAOMI_API_KEY=你的MiMo密钥
TTS_XIAOMI_BASE_URL=https://api.xiaomimimo.com/v1

使用 Token Plan(tp- 开头密钥)时把 BASE_URL 换成本区域的 token-plan-{cn,sgp,ams}.xiaomimimo.com/v1

然后:

  1. 重启 pnpm dev / 重新 pnpm build
  2. 打开 设置 → 语音合成
  3. 选择 "Xiaomi MiMo TTS",模型 MiMo-V2.5-TTS,选音色(推荐中文用 冰糖,英文用 Mia)。
  4. 点试听按钮确认出声;在课堂生成中该 provider 会自动进入可选音色池。

!warning 常见误区

对话模型的 XIAOMI_API_KEY 不会自动用于语音合成,必须单独配 TTS_XIAOMI_API_KEY

也不要再走"添加自定义语音合成"入口,那个入口打的是 /audio/speech,MiMo 不支持。

六、验证清单

验证项 命令 / 操作 期望结果
类型检查 pnpm exec tsc --noEmit -p tsconfig.json 0 错误
多语言键 node scripts/check-i18n-keys.mjs key alignment check passed
服务商识别 启动后访问 /api/server-providers 配置 key 后出现 xiaomi-tts
界面 设置 → 语音合成 出现 Xiaomi MiMo TTS 与 9 个音色
试听 设置页试听按钮 正常出声,无 401/500
生产构建 pnpm build 编译 + 类型检查通过

排查要点:

  • 返回 MISSING_API_KEY.env.local 里没配 TTS_XIAOMI_API_KEY,或改了没重启。
  • 返回 401/403 → key 类型与 baseUrl 不匹配(按量付费 sk- 配 api.xiaomimimo.com;Token Plan tp- 配区域端点)。
  • 返回 "no audio returned" → 模型名写错,或把待合成文本放进了 user 消息。
  • 想直连排查协议,可用官方 Python/OpenAI SDK 按第二节的请求体先验证 key 是否可用。

七、已知限制与后续扩展

  1. 只接入了预置音色模型 mimo-v2.5-tts;voicedesign(文本设计音色)与 voiceclone(音色复刻)未暴露到界面。
  2. 语速用自然语言 user 指令近似映射(0.5--2.0 倍),并非官方数值参数。
  3. 风格/情绪 目前依赖文本标签:需要在朗读文本里自带 (磁性)(东北话) 这类标签,或在 user 消息里写风格描述。
  4. 单说话人模型;多人对话场景由 OpenMAIC 逐条文本分别合成。

如需继续扩展:

  • voicedesign :注册第二个模型,把"音色描述"作为 user 消息内容、不传 audio.voice,可在 providerOptions 里增加 stylePrompt 字段,并在设置页做输入框。
  • voiceclone :audio.voicedata:audio/wav;base64,<样本>(样本 ≤ 10MB,支持 mp3/wav),需要在设置页增加样本上传与 base64 编码,并注意不要把样本写进日志。

八、经验总结

  1. 先核对协议再动手 :很多"OpenAI 兼容"的语音接口其实走 chat/completions 的 audio 通道,通用 /audio/speech 适配器一定失败。
  2. 认证头两手准备 :官方文档用 api-key,OpenAI SDK 发 Authorization: Bearer,两个都带上最省事(实测互不冲突)。
  3. 默认输出 wav:非流式场景下 wav 最省事,不需要自己拼 PCM。
  4. 改 provider 是"多点同步":类型、注册表、默认值映射、实现、env 映射、显示名、i18n、客户端默认配置,漏一处就编译不过或界面不显示。
  5. 编译期与运行期分开 :NEXT_PUBLIC_* 之外的服务端 Key 放 .env.local 即可,但改完必须重启进程。
  6. 接入完成必做验证:类型检查 + i18n 校验 + 界面试听 + 真实合成,四步都过才算完成。
相关推荐
Jia ming1 天前
DeepSeek API配置踩坑记:OpenMAIC部署全流程
deepseek·openmaic
@嵌入式扫地僧8 天前
车载毫米波雷达分类芯驰 D9-Pro 部署全流程
雷达点云·mimo·npu·tiny-asil·tflite micro
EW Frontier24 天前
【雷达信号处理】5 个阵元追平 16 个阵元:稀疏阵列 STAP 的实测报告【附python+matlab代码】
python·matlab·信号处理·雷达·mimo·稀疏阵列·stap
通信仿真爱好者2 个月前
第【89】期-- MIMO干扰广播信道中的加权和速率最大化:WMMSE算法推导、实现与仿真 --MATLAB完整代码
mimo·wmmse·交替优化·加权和速率最大化
带娃的IT创业者2 个月前
当推理速度突破物理极限:深度解析 MiMo-v2.5-Pro-UltraSpeed 的 1000 TPS 架构革命
人工智能·架构·大模型·架构优化·mimo·tps·推理速度
通信仿真爱好者2 个月前
第【60期】--大规模MIMO系统信号检测算法误码率比较 --matlab完整代码+参考文章
算法·matlab·mimo·信号检测
wj3055853783 个月前
Claude Code接入MiMo缓存失效?1个变量秒修复
缓存·mimo·claude code
通信仿真爱好者3 个月前
第【11】期--基于智能反射面的MIMO安全速率最大化研究-maltab完整代码+完整报告
mimo·matlab仿真·物理层安全·智能反射面
人道领域3 个月前
新项目该怎么入手?我用Claude code 接入小米mimo复盘黑马点评,看他的思路是什么。
java·人工智能·后端·mimo·claude code