播报与声音库
列出音色并选择兼容组合
GET /v1/audio/voices
发起合成前先列出音色。请选择 available: true 的音色,并检查各模型的 compatibility 列表;音色可用并不代表适用于所有合成模型。以下响应节选展示了一个用户音色,ID 仅为示意。
请求示例:
curl --fail-with-body "$EDGESPEAK_BASE_URL/audio/voices"输出示例:
{
"voices": [
{
"id": "user:00000000-0000-4000-8000-000000000001",
"names": {
"en-US": "My voice"
},
"descriptions": {},
"supported_languages": [
"en-US"
],
"origin": "cloned",
"compatibility": [],
"available": true,
"created_by_user": true
}
]
}名称与描述按 locale 索引,并非单个 name 字符串。示例中的空兼容列表不能证明支持任何模型,发起后续请求时请填入实际兼容的模型与音色组合。
生成 WAV 播报音频
POST /v1/audio/speech
JSON 请求必填字段:model、voice、input(或非空 segments[])。language 可选,省略或传 auto 由服务自动选择;各模型支持的语言见播报语言。下列模型与音色组合仅为示例,请先确认在模型目录中存在且兼容。响应格式为 WAV。
请求示例:
curl --fail-with-body "$EDGESPEAK_BASE_URL/audio/speech" \
-H "Content-Type: application/json" \
-d '{"model":"Qwen/Qwen3-TTS-0.6B-Base","voice":"builtin:bright-girl","input":"Hello world.","response_format":"wav"}' \
-o speech.wav
file speech.wav输出示例:
speech.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 24000 Hz此处为 file 命令的输出示例,并非 JSON。HTTP 响应体为 audio/wav 二进制数据,默认流式输出。原生采样率取决于模型,请省略 sample_rate。桌面网关流式接口仅接受与原生采样率一致的显式值,Headless 服务则会拒绝显式 sample_rate。请求失败时 --fail-with-body 会以非零状态退出,应检查保存的错误内容而非直接播放文件。参阅各模型支持的语音控制参数。
通过 SSE 接收播报音频
需要事件流时传入 stream_format: "sse",并使用 curl -N 禁用缓冲。每个 delta 事件中的音频数据均为 Base64 编码,第一段解码后包含 WAV 文件头,按顺序拼接解码后的字节即可还原音频。
请求示例:
curl --fail-with-body "$EDGESPEAK_BASE_URL/audio/speech" \
-H "Content-Type: application/json" \
-d '{"model":"Qwen/Qwen3-TTS-0.6B-Base","voice":"builtin:bright-girl","input":"Hello world.","stream_format":"sse","response_format":"wav"}' \
-N输出示例:
event: speech.audio.delta
data: {"type":"speech.audio.delta","audio":"<base64 WAV header and PCM bytes>"}
event: speech.audio.done
data: {"type":"speech.audio.done","diagnostics":{},"usage":{"input_tokens":0,"output_tokens":0,"total_tokens":0}}上述事件节选省略了 diagnostics 内部字段,音频内容为占位符。合成成功以 speech.audio.done 事件结束;客户端同时需要处理 error 事件以及完成前的异常断线。此处 usage 中的 token 数为零,不能用来估算音频时长。
风格指令、克隆档位与声音设计
以下字段都放在 POST /v1/audio/speech 的 JSON 顶层,segments[] 的每一段也可以单独给 instructions 或 disable_style;段内两者都不写(含 ""、null、false)就继承顶层风格。
| 参数 | 说明 |
|---|---|
instructions | OpenAI 同名参数,字符串。不传、传 "" 或 null 沿用音色保存的风格;传非空文本则本次改用这段风格。只有 /v1/models 中 features 含 instruct 的模型接受非空文本,其他模型返回 400,不会静默忽略。voice: "builtin:design" 时它是声音描述,必填。FireRedTeam/FireRedTTS3-Instruct 与 k2-fsa/OmniVoice 的描述只在 voice: "builtin:design" 下生效:选了具体音色(预设音色、已保存音色或 builtin:auto)再给非空文本返回 400,不会悄悄换掉所选音色;不填则照常用所选音色。 |
disable_style | 布尔。true 表示本次不用任何风格;false 或不传无作用。与非空 instructions 同时出现返回 400 conflicting_speech_instructions。 |
clone_mode | 克隆档位:quick 起播更快,ultimate 相似度更好,所有克隆模型共用这一个参数。省略时用模型缺省档。点名了兑现不了的档位返回 400,不会静默回落到另一档。 |
各模型支持的克隆档位列在 /v1/models 中:有档位的播报模型带 clone_mode.values(可选档位)与 clone_mode.default_value(缺省档),没有档位的模型不带 clone_mode 字段。目前 Qwen3-TTS Base(0.6B 与 1.7B)和 openbmb/VoxCPM2 两档都有,缺省档为 quick。
{"id":"openbmb/VoxCPM2","clone_mode":{"values":["quick","ultimate"],"default_value":"quick"}}上例为模型目录条目的节选,省略了其他字段。
按描述设计声音
voice: "builtin:design" 不挂任何内置或用户音色,instructions 里的描述就是声音:
- 支持的模型:
openbmb/VoxCPM2、Qwen/Qwen3-TTS-1.7B-VoiceDesign、BreezeBlue/Breeze-TTS-2、FireRedTeam/FireRedTTS3-Instruct、k2-fsa/OmniVoice。其他模型直接拒绝,不会换成某个内置音色。 - 描述必填,上限 120 字;
FireRedTeam/FireRedTTS3-Instruct上限 1000 字,k2-fsa/OmniVoice上限 80 字。 FireRedTeam/FireRedTTS3-Instruct与k2-fsa/OmniVoice只能这样用描述:选了具体音色再给非空instructions返回 400,见下方错误表。- 不接受
clone_mode。 - Realtime 会话不接受
builtin:design,见请求语音回复。
k2-fsa/OmniVoice 的描述由属性短语组成,用逗号分隔(中文用全角逗号),每类至多一项:性别(男 / 女,male / female)、年龄(儿童、少年、青年、中年、老年;child、teenager、young adult、middle-aged、elderly)、音高(极低音调 … 极高音调;very low pitch … very high pitch)、耳语(耳语,whisper)、英语口音(如 british accent,仅英文)、汉语方言(如 四川话,仅中文)。声音设计只支持中文和英语,其他语言可能不稳定。
桌面 App 的播报页填了描述会自动改用 builtin:design,所选音色本次不生效。
curl --fail-with-body "$EDGESPEAK_BASE_URL/audio/speech" \
-H "Content-Type: application/json" \
-d '{"model":"FireRedTeam/FireRedTTS3-Instruct","voice":"builtin:design","instructions":"沉稳、低沉的男声旁白","input":"你好,这里是 EdgeSpeak。","response_format":"wav"}' \
-o designed.wav
curl --fail-with-body "$EDGESPEAK_BASE_URL/audio/speech" \
-H "Content-Type: application/json" \
-d '{"model":"k2-fsa/OmniVoice","voice":"builtin:design","instructions":"女,青年,低音调","input":"你好,这里是 EdgeSpeak。","response_format":"wav"}' \
-o omnivoice-designed.wav风格与档位错误
错误响应使用统一信封,按 error.code 分支,不要匹配 message 文案:
{"error":{"message":"...","type":"invalid_request_error","code":"conflicting_speech_instructions"}}| 情形 | error.code | 原因位置 |
|---|---|---|
disable_style: true 与非空 instructions 同时出现 | conflicting_speech_instructions | code 本身 |
请求里出现已删除的 style_instruction(顶层或 segments[] 内,任何取值) | bad_request | message 提示改用 instructions / disable_style |
非空 instructions 遇到不支持风格指令的模型(含 FireRedTeam/FireRedTTS3-Instruct、k2-fsa/OmniVoice 选了具体音色)/ 描述必填但缺失 / 描述超长 | bad_request | message 中的稳定键 errors.broadcast.instructUnsupported、errors.broadcast.instructRequired、errors.broadcast.instructTooLong |
| 模型没有点名的克隆档位 / 模型有这一档但本次音色没有 | bad_request | message 以 clone_mode_unsupported_by_model 或 clone_mode_unsupported_by_voice 开头,并列出支持的档位 |
这些都是请求本身的问题,需要改请求,原样重试不会成功。
查询模型专属播报示例
GET /v1/audio/speech/examples
可选查询参数:model、mode、language、id、recipe。先查询完整目录获取有效的 ID 与模式;传入无效过滤条件将返回 400。以下命令先保存完整目录,再通过 jq 展示可选模式。
请求示例:
curl --fail-with-body "$EDGESPEAK_BASE_URL/audio/speech/examples" \
-o speech-examples.json
jq '.examples | map(.mode) | unique' speech-examples.json输出示例:
[
"emotion-vector",
"style-control",
"voice-design"
]这里展示的是 jq 处理后的示例输出。保存的响应包含 schema_version、catalog_version、verified_at、resolved_language、models[] 和 examples[]。每个示例均包含 applicable 与 reason,调用时应仅使用当前模型支持的控制选项。例如追加 ?model=voxcpm2&mode=voice-design&language=en-US 即可按条件筛选。
创建可复用的用户音色
POST /v1/audio/voices
将声音保存到服务主机的声音库。使用 multipart 格式提交参数:
| 参数 | 说明 |
|---|---|
audio_sample | 必填,最多 10 MiB;使用简短清晰的参考音频。 |
ref_text / name | 必填,分别对应参考音频文稿与音色名称。 |
consent | 必填,固定为 true。 |
language | 可选,默认 zh-CN;其他语言需显式设置。以 en-US 这类 locale 形式保存,见语言代码。 |
speaker_description | 可选,说话人描述。 |
不要提供 model。
请求示例:
curl --fail-with-body "$EDGESPEAK_BASE_URL/audio/voices" \
-F audio_sample=@reference.wav -F ref_text="Hello, this is my reference recording." \
-F name="My voice" -F language=en-US -F consent=true \
-o created-voice.json
cat created-voice.json输出示例:
{
"success": true,
"voice": {
"id": "user:00000000-0000-4000-8000-000000000001",
"names": {
"en-US": "My voice"
},
"descriptions": {},
"supported_languages": [
"en-US"
],
"origin": "cloned",
"compatibility": [],
"available": true,
"created_by_user": true
}
}后续合成或删除时请使用实际返回的 voice.id,不要使用示意 UUID。发起合成前请检查返回的兼容性信息。
删除用户音色
DELETE /v1/audio/voices/:voice_id
从声音库中删除上一步创建的音色,仅在确认不再需要时执行。内置音色不可删除。以下示例使用 jq 读取保存的响应以获取音色 ID。
请求示例:
VOICE_ID=$(jq -er '.voice.id' created-voice.json)
curl --fail-with-body "$EDGESPEAK_BASE_URL/audio/voices/$VOICE_ID" \
-X DELETE输出示例:
{
"success": true,
"deleted": {
"id": "user:00000000-0000-4000-8000-000000000001",
"name": "My voice"
}
}桌面请求排队
桌面端的本机播报请求共用同一个队列。客户端可以并发提交,模型按需依次执行,排队等待期间不会提前占用模型。模型休眠设置只决定空闲回收,不等于多个播报模型同时驻留。
请求格式保持不变。若前面已有播报请求,本次首包时间将包含排队耗时:
curl --no-buffer http://127.0.0.1:1117/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{"model":"openbmb/VoxCPM2","voice":"builtin:bright-girl","input":"你好。","response_format":"wav","stream_format":"sse"}'桌面网关启用鉴权时需添加 Bearer 凭据。请求成功时同样通过 speech.audio.delta 与 speech.audio.done 发送 SSE 事件;音频传输开始后的异常仍按现有流内错误处理。
Headless 模型队列
CLI 与 Headless 服务里,REST 播报、Realtime 播报、MCP 合成、声音准备和 TTS 预加载共用同一个模型执行队列。请求依次执行;调大 --speech-concurrency 也不会在前一个请求准备或合成期间提前切换模型。排队中取消请求,服务端不会继续加载模型;运行中的请求被取消时,清理流程会等原生任务结束后再释放模型占用。原生故障或清理超时仍会返回错误。Realtime 握手达到并发上限时同样排队等待,详见会话排队。
直接使用上文的 WAV 或 SSE 请求示例即可,排队不新增请求字段、SSE 事件或响应字段。客户端超时需计入排队、模型加载与推理耗时,详见 CLI 并发参数。
生成计时与 IndexTTS 流式
diagnostics.infer_seconds 表示原生合成阶段的墙钟时间,包含声码器计算及阶段内等待,但并非纯 GPU 计算时间,也不包含模型加载耗时,其中含非流式长文的批量预生成时间。客户端首音延迟从发起请求开始计时,可能包含排队、加载和声音准备耗时。
以下请求沿用现有协议;将 input 替换为实际长文,并从模型目录中选择 IndexTTS 2.5。仅当长文切分为多段时才走流式,短句可能只返回一个音频事件。
{"model":"IndexTeam/IndexTTS-2.5","voice":"builtin:bright-girl","input":"你好,这是一条语音速度测试。","language":"zh-CN","response_format":"wav","stream_format":"sse"}成功终态的诊断字段节选如下(数值仅为示意;实际终态还含其他字段):
{"type":"speech.audio.done","diagnostics":{"infer_seconds":6.0,"duration_seconds":3.0}}该例生成的 RTF 为 6.0 / 3.0 = 2.0;总 RTF 需使用客户端完整的请求耗时单独计算。单次请求的模型加载耗时目前没有公开字段,不能用「总耗时减生成耗时」来充当加载时间。