MiniMax Speech-2.8-HD · 接入 + 文稿准备

让 TTS 说出你想要的声音

接进来只是开始。MiniMax TTS 的"自然度",八成来自文稿准备——停顿节奏、情绪音效、多音字替词、数字读法。 这份指南先讲怎么接,再把一年多的实战坑一次讲透。

模型speech-2.8-hd 国内端点api.minimaxi.com 海外端点api.minimax.io 更新2026-06
01

接入与配置

拿钥匙、填端点、选模型——三步就能让程序开口

三步接入

  1. 开通语音服务,拿到 API Key 在 MiniMax 开放平台开通文本转语音(t2a),生成一把 API Key。这把钥匙代表你的账号和额度,泄露等于别人花你的钱。
  2. 把 Key 存进配置,不要写死在代码里 Key 单独放一份本地配置文件或环境变量,代码运行时读出来。源码里只留占位符 <你的 MINIMAX_API_KEY>,这样脚本分享出去也不会带出密钥。
  3. 填端点 + 选模型 端点用 /v1/t2a_v2,模型选 speech-2.8-hd(支持停顿标记和拟声词)。请求头带 Authorization: Bearer <Key> 即可。

去哪申请 · 控制台入口

注册账号 + 实名认证后,进控制台 账户管理 → 接口密钥 创建 API Key。国内、海外是两套独立站点(账号不互通),按你的网络环境选一套:

版本申请控制台对应 API 端点
国内 platform.minimaxi.com api.minimaxi.com
海外 platform.minimax.io api.minimax.io
带不带 i 别记混 国内站全程带 i(minimaxi.com)、海外站不带 i(minimax.io)。 旧国内域名 api.minimaxi.chat 目前仍可用;但 api.minimax.chat(没 i 又是 .chat)是常见笔误,连不上。
钥匙怎么存 我自己的做法:Key 放在一份本地配置文件里,脚本启动时读 credentials.minimax.MINIMAX_API_KEY。 代码仓库里看不到任何明文,分享脚本零风险。新版 Key 是 sk-api- 开头,不需要再单独填 GroupId

最小可跑的请求骨架

import requests, json
from pathlib import Path

# Key 从本地配置读,不硬编码
API_KEY = json.loads(Path.home().joinpath(".config/minimax.json").read_text())["key"]
ENDPOINT = "https://api.minimaxi.com/v1/t2a_v2"   # 国内站,带 i

payload = {
    "model": "speech-2.8-hd",
    "text": "朋友们,今天聊个重要的话题。",
    "voice_setting": {"voice_id": "<你的克隆音色 id>", "speed": 1.3},
    "audio_setting": {"sample_rate": 24000, "format": "wav"},
}
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

resp = requests.post(ENDPOINT, json=payload, headers=headers, timeout=180).json()

# ⚠️ 返回的 data.audio 是 hex 编码,不是 base64
Path("out.wav").write_bytes(bytes.fromhex(resp["data"]["audio"]))
两个一上来就会卡的点 ① 返回的声音是 hex 编码,要用 bytes.fromhex() 解,当成 base64 解出来全是杂音。 ② 国内直连 api.minimaxi.chat 一般秒通;若本机超时,给请求挂一个 HTTPS 代理即可。
02

停顿控制

<#秒数#> 插入显式停顿,替你控制节奏

基础语法

在文本任意位置插入 <#0.3#>,数字单位为秒。引擎会在该点插入对应时长的静音。

朋友们<#0.3#>今天聊个重要的话题<#0.5#>AI 正在改变什么。

口播推荐区间

效果适用场景
<#0.2#>换气感逗号位置、短语之间
<#0.3#>自然停顿句间过渡、列举之间
<#0.5#>强调停顿话题转换、关键词前
<#0.8#> 以上拖沓基本不用,会显得尬
最反直觉的经验 定稿音色用了 1.3x 语速,测试下来完全不加停顿标记效果最好——自然语速已经给足节奏。 只有需要刻意强调时才补一个 <#0.3#>。标记用得越省越自然。

不要加停顿的位置

03

情绪与音效

预设 emotion 决定基调,(音效词) 点缀真实感

预设情绪

API 参数 emotion 可选:happy / sad / angry / fearful / disgusted / surprised / calm / neutral。口播场景推荐 calm,最稳。

踩坑 不要传 emotion: "auto"——文档里写过但实际会报错。不想指定就干脆不传这个字段,比传 auto 稳。另外:克隆音色不支持 emotion,只有系统音色支持。

嵌入式音效词

在文本中用 (keyword) 格式嵌入,引擎会在对应位置合成真实音效。这是提升"人味"的关键手段。

✓ 推荐使用

(breath)呼吸 (chuckle)轻笑 (inhale)吸气 (exhale)呼气 (gasp)惊讶 (pant)喘气 (snorts)不屑哼 (lip-smacking)咂嘴

✗ 避免使用

(laughs)不自然 (humming)哼唱怪 (coughs)出戏 (emm)效果怪异 (sighs)中文像"哎" (crying)不适合 (clear-throat)出戏 (applause)出戏

实战用法

// 对话开头给呼吸,调侃句配轻笑
(breath)说真的<#0.3#>这个需求(chuckle)我第一次听的时候也愣住了。
定稿配方 (breath) + (chuckle) 两个标记足够覆盖 95% 场景。其他的不用。越简单越不容易出戏。
04

替词与发音控制

pronunciation_dict 参数治多音字、生僻字、人名

方式一 · 括号拼音(推荐)

每个字用 (拼音+声调数字),声调 1-5 分别对应阴平/阳平/上声/去声/轻声。

"pronunciation_dict": {
  "tone": [
    "单老师/(shan4)(lao3)(shi1)",
    "曾国藩/(zeng1)(guo2)(fan1)",
    "欧阳娜娜/(ou1)(yang2)(na4)(na4)"
  ]
}

方式二 · 同音字替代

更简单,用字典里已有的字替换。适合单字多音。

"tone": [
  "仇/求",
  "仇雪岑/求雪岑"
]

无效格式(别踩)

✗ 不生效 "仇/qiu2"
"仇/qiú"
"仇/qiu2xue3cen2"
✓ 有效 "仇/(qiu2)"
"仇雪岑/(qiu2)(xue3)(cen2)"
"仇/求"

实战技巧:静态人名表

每次调 TTS 都携带一份通用人名字典,引擎自动匹配文中出现的名字。遇到新的读错名字,往表里加一条即可。

_PRONUNCIATION_DICT = {"tone": [
    # 多音字人名(单、仇、查、曾、解、朴、纪...)
    "单雄信/(shan4)(xiong2)(xin4)",
    "曾国藩/(zeng1)(guo2)(fan1)",

    # 复杂人名(全拼音确保连读不停顿)
    "欧阳娜娜/(ou1)(yang2)(na4)(na4)",
    "迪丽热巴/(di2)(li4)(re4)(ba1)",
]}
05

词组连读 —— ## 标记

防止生僻长词被引擎切断、或被分块切成两半

用双井号 ##词## 包裹,告诉引擎"这几个字作为一个整体连续发音"。

最近##司美格鲁肽##非常火。
##奥美拉唑肠溶胶囊##主要用于治疗胃食管反流病。

标记方式对比 (2026-02 实测)

标记结果说明
##词##✓ 稳定各种长度词组均表现好
{词}△ 不稳部分词有效部分无效
[词]✗ 差效果不好
|词|✗ 差效果不好
<词>✗ 冲突与停顿标记 <#x#> 冲突
关键:发送前必须剥离 ## 虽让引擎连读,但标记前后会产生明显不自然停顿。 正确流程:LLM 输出带 ##(用于分块时保护)→ 分块逻辑检查 ## 是否闭合 → 发 TTS 前 text.replace("##", "") 剥离 → 前端显示也要过滤。

使用边界

06

数字转中文 · 六步流水线

前端显示阿拉伯数字,发 TTS 前预处理为中文读法

LLM 输出 13800138000,引擎会读成"一百三十八亿零一十三万八千"——荒谬。规则是:前端始终显示阿拉伯数字,仅在发送 TTS 前转换,按以下顺序处理:

  1. 手机号(11 位以 1 开头)→ 逐字读
    Before13800138000
    After一三八零零一三八零零零
    正则:(?<!\d)1\d{10}(?!\d)
  2. 超长编码(12+ 位)→ 逐字读 身份证、订单号、单据号。正则:\d{12,}
  3. 年份(19xx / 20xx)→ 逐字读
    保持2000 多人
    3000 人(非年份)
    转换1922 横空出世 → 一九二二
    2000 年 → 二零零零年
  4. 数字 + 万/亿后缀 → 乘法合并
    12384 万 → 一万两千三百八十四万
    12384 万 → 一亿两千三百八十四万
    正则:(?<![.\d])(\d+)(万|亿),小数不匹配(2.5万 不动)。
  5. 单独的"2"+ 量词 → "两" 2个两个2人两人2倍两倍。 排除:第2名(前有"第")、12人(前有数字)。
    易漏量词 人 / 倍 / 元 / 岁 / 年 / 月 / 日 / 米 / 度 —— 这些最常见单位容易遗漏。发现"2X"读成"二X",就往列表加一个字。
  6. 剩余数字 → 中文读法(看上下文)
    后跟中文 · 正式2500 人 → 两千五百人
    21600 元 → 两万一千六百元
    句尾 · 口语省略赚了 2500 → 赚了两千五
    赚了 21600 → 赚了两万一千六
    判断:数字后第一个字符是否为 CJK 字符。

"两"还是"二"

_formal 必须传递到所有递归 Python 实现 _num_to_cn(n, _formal=False, _sub=False) 时,_formal 必须穿透到所有余数递归调用,否则内部子调用会偷偷做口语化省略——结果就是 21600 元 被读成"两万一千六元"(缺"百")。
07

分块与流式

长文本切块降低首字延迟,切分规则决定音质

分块规则

规则理由
切分位置。!?!?句子边界,不按固定字数
最小块60 字符太短音质差、有拼接感
最大块200 字符太长首字延迟高
闭合检查( 和 <#未闭合标记留到下一块
时间切分✗ 禁用"1 秒后发送"会产生残句

整段 vs 分段

给视频配旁白时,整篇文稿一次提交(段间用 <#0.4#> 分隔)语气最连贯,分段拼接会有断裂感。只有需要实时流式播放时才分块。

流式场景的前端显示

流式输出时,<#0.5#> 这种标记可能被拆成多个 chunk(如 <#0.5#> 分开到达),普通正则匹配不到不完整的标记,UI 会闪现半截标记。

// 用两个存储:原始完整文本 vs 过滤后显示文本
val rawContent  = StringBuilder()  // 给 TTS 用
val content     = StringBuilder()  // 给 UI 用

// 完整标记 / 尾部不完整标记(避免流式闪现)
// 注意:不要用 <[^>]* 会误匹配中文书名号《》
val TTS_MARKER_REGEX  = Regex("""<#[\d.]+#>|\([a-z-]+\)""")
val TTS_PARTIAL_TAIL = Regex("""(<#[\d.]*#?>?|\([a-z-]*)$""")
08

完整调用示例

所有经验汇总到一次请求

Python 调用

import requests, json
from pathlib import Path

API_KEY = json.loads(Path.home().joinpath(".config/minimax.json").read_text())["key"]
ENDPOINT = "https://api.minimaxi.com/v1/t2a_v2"

# 文稿:已做过数字预处理 + pronunciation_dict + 情绪标记
text = (
    "(breath)朋友们<#0.3#>今天聊个重要的话题"
    "<#0.5#>AI 正在改变什么(chuckle)。"
)

payload = {
    "model": "speech-2.8-hd",
    "text": text,
    "voice_setting": {
        "voice_id": "<你的克隆音色 id>",
        "speed": 1.3,
        "vol": 2.0,
        "pitch": 0,
    },
    "audio_setting": {
        "sample_rate": 24000,
        "format": "wav",
    },
    "subtitle_enable": True,  # 顺手要字级时间码,省掉后期对轴
    "pronunciation_dict": {
        "tone": [
            "单雄信/(shan4)(xiong2)(xin4)",
            "曾国藩/(zeng1)(guo2)(fan1)",
        ]
    },
}
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

resp = requests.post(ENDPOINT, json=payload, headers=headers, timeout=180).json()

# ⚠️ data.audio 是 hex 编码,不是 base64
audio_bytes = bytes.fromhex(resp["data"]["audio"])
Path("out.wav").write_bytes(audio_bytes)

容易翻车的五个点

  1. 域名带 i 国内站 api.minimaxi.com(带 i)、海外站 api.minimax.io 都可用。旧域名 api.minimaxi.chat 也通;但 api.minimax.chat(没 i)是常见笔误,连不上。
  2. audio 是 hex 不是 base64 bytes.fromhex(resp["data"]["audio"])。拿 base64.b64decode 解出来是噪声文件。
  3. emotion: "auto" 会报错 文档有但实际不支持。不指定就删字段,别传 auto。克隆音色不支持 emotion。
  4. 新版 Key 不需要 GroupId sk-api- 开头的新 Key,query string 留空即可;老 JWT key 才要填 GroupId。
  5. 克隆音色 7 天过期 克隆的 voice_id 如果 7 天没被调用会被自动删除。生产环境必须部署 keepalive 定时任务(每 5 天调一次保活)。