FMODE DEVELOPERS / VOICE

飞马语音合成 API

使用参考音频和文本生成 WAV 音频。本文档对应生产接口当前契约。

APIv1

概览

飞马语音合成接收一段说话人参考音频、合成文本和可选情感参数,成功时直接返回 WAV 二进制数据。客户端无需接触上游服务地址或凭据。

POSThttps://server.fmode.cn/api/voice/indextts2
请求格式multipart/form-data
响应格式audio/wav
文本上限-- 字符
音频上限-- MB / 文件

鉴权

公开语音 API 使用 Fmode API Token。Token 必须通过 Bearer 请求头发送,不能放入查询参数、表单字段或文件名。

HTTP Header
Authorization: Bearer FMODE_API_TOKEN
Token 类型

使用 Fmode API 控制台创建的 sk- Token。Fmode 网站登录会话用于账户管理,不作为公开语音 API 的计费凭据。

合成接口

POST/api/voice/indextts2

根据说话人参考音频和 payload 生成单个 WAV 文件。请求开始前检查余额,上游成功后扣费。

请求头

名称必填说明
AuthorizationBearer FMODE_API_TOKEN
Idempotency-Key建议8-128 位字母、数字或 ._:-,用于避免超时重试重复扣费。
Content-Type自动由 HTTP 客户端为 multipart 自动生成并携带 boundary,不要手工固定。

Multipart 字段

字段类型必填限制
spk_audio_fileFile说话人参考音频,仅 MP3 / WAV,单文件最大 -- MB。
emo_audio_fileFile条件emo_control_method=1 时必填,限制同上。
payloadJSON string将下方 JSON 对象序列化为字符串后作为普通表单字段提交。

服务端会同时校验扩展名、MIME 类型和文件内容签名,修改扩展名不能绕过音频格式检查。

Payload 字段

字段类型默认值约束
inputstring-必填;去除首尾空白后不能为空;最多 -- 个 Unicode code point。
emo_control_method0 | 1 | 2 | 30选择情感控制方式。
emo_alphanumber-可选;0-1。
emo_vecnumber[8]-模式 2 必填;每项 0-1.2,总和不超过 1.5。
emo_textstring-模式 3 必填;最多 200 个 Unicode code point。
use_randombooleanfalse仅严格布尔值 true 会启用;生产请求建议固定为 false。
文本情感示例
{
  "input": "非常感谢您的耐心等待。",
  "emo_control_method": 3,
  "emo_alpha": 0.45,
  "emo_text": "自然、友好、耐心,表达清晰",
  "use_random": false
}

四种情感模式

0

自然表达

只使用说话人参考音频,不附加情感控制字段。

1

情感参考音频

额外上传 emo_audio_file,从第二段音频中提取情感表达。

2

八维情感向量

通过 emo_vec 传入八个数值。接口只固定维度和数值边界,不替客户端猜测向量语义。

3

情感描述文本

通过 emo_text 描述期望语气,可配合 emo_alpha 调整强度。

WAV 二进制响应

成功响应状态为 200Content-Type 固定为 audio/wav,响应体是音频二进制。不要对成功响应调用 JSON 解析。

200 OKContent-Type: audio/wavCache-Control: no-store

浏览器中可用 response.blob() 创建试听 URL;Node.js 使用 response.arrayBuffer() 写入文件;Python 直接写入 response.content

计费与余额

当前客户价¥--/ -- Unicode 字符
  • 字符数按 Unicode code point 计算,表情符号按一个字符计费。
  • 每次请求独立换算 quota 并向上取整,最低扣除 1 quota。
  • 上游成功返回有效 WAV 后才扣费;上游失败、超时或响应格式错误不扣费。
  • 语音合成与其他 Fmode API 共用账户余额,不建立语音专属钱包或包月订阅。

余额查询

GEThttps://server.fmode.cn/api/voice/product/balance

携带同一个 Bearer Token,返回 balanceCny 和按当前语音价格估算的 estimatedCharacters。该估算受逐请求取整影响,仅用于展示。

代码示例

示例均从本地读取参考音频,通过环境变量或占位值使用 Token,并将响应保存为 speech.wav

curl

Terminal
curl --request POST \
  'https://server.fmode.cn/api/voice/indextts2' \
  --header 'Authorization: Bearer FMODE_API_TOKEN' \
  --header 'Idempotency-Key: voice-20260804-example-001' \
  --form 'spk_audio_file=@./speaker.wav;type=audio/wav' \
  --form 'payload={"input":"您好,欢迎使用飞马语音合成。","emo_control_method":0,"use_random":false}' \
  --output speech.wav

Node.js 18+

voice.mjs
import { readFile, writeFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';

const token = process.env.FMODE_API_TOKEN;
const reference = await readFile('./speaker.wav');
const form = new FormData();

form.append(
  'spk_audio_file',
  new Blob([reference], { type: 'audio/wav' }),
  'speaker.wav',
);
form.append('payload', JSON.stringify({
  input: '您好,欢迎使用飞马语音合成。',
  emo_control_method: 0,
  use_random: false,
}));

const response = await fetch(
  'https://server.fmode.cn/api/voice/indextts2',
  {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + token,
      'Idempotency-Key': 'voice-' + randomUUID(),
    },
    body: form,
  },
);

if (!response.ok) {
  throw new Error(await response.text());
}
await writeFile('./speech.wav', Buffer.from(await response.arrayBuffer()));

Python 3

voice.py
import os
import uuid
import requests

token = os.environ['FMODE_API_TOKEN']
payload = {
    'input': '您好,欢迎使用飞马语音合成。',
    'emo_control_method': 0,
    'use_random': False,
}

with open('./speaker.wav', 'rb') as speaker:
    response = requests.post(
        'https://server.fmode.cn/api/voice/indextts2',
        headers={
            'Authorization': f'Bearer {token}',
            'Idempotency-Key': f'voice-{uuid.uuid4()}',
        },
        files={'spk_audio_file': ('speaker.wav', speaker, 'audio/wav')},
        data={'payload': __import__('json').dumps(payload, ensure_ascii=False)},
        timeout=240,
    )

response.raise_for_status()
with open('./speech.wav', 'wb') as output:
    output.write(response.content)

错误码

失败响应为 JSON:{"code": 400, "mess": "错误说明"}

HTTP含义处理建议
400payload、情感参数或音频格式无效根据 mess 修正请求,不要原样重试。
401Token 缺失、无效或过期检查 Bearer Token,必要时创建新 Token。
402用户或 Token 可用额度不足充值共享余额,或调整 Token 的额度限制。
403使用了网站登录会话而非 API Token改用 sk- Fmode API Token。
409Idempotency-Key 已用于不同输入或金额保持原请求不变,或为新输入生成新键。
413上传文件超过大小限制压缩或裁剪参考音频。
502 / 503语音服务响应无效、暂不可用或繁忙使用同一幂等键退避重试。
504语音合成超时使用同一幂等键重试并延长客户端超时。

幂等与重试

每个逻辑合成请求应生成一个唯一的 Idempotency-Key,并在网络超时或响应丢失时复用同一个键。输入指纹覆盖文本、情感与输出参数、说话人参考音频内容以及可选情感音频内容。

同键 + 同输入

允许恢复。服务会重新生成 WAV,但识别已有消费记录,不重复扣客户余额。

同键 + 不同输入

返回 409。即使字符数和费用相同,也不能复用原键。

当前版本不持久化缓存 WAV 二进制。若已成功扣费但客户端丢失响应,复用同键会重新合成并返回一份新的 WAV,客户侧不会产生第二次扣费。

安全建议

  • Token 存放在服务端环境变量或密钥管理系统,不提交到 Git。
  • 浏览器直调仅用于人工 Demo;正式应用通过自己的服务端转发,避免向最终用户暴露 Token。
  • 按应用创建独立 Token,设置额度、有效期,并定期轮换。
  • 不要把 Token 写入 URL、文件名、日志、埋点、错误上报或截图。
  • 只上传有权使用的声音参考音频,并为声音档案提供删除和访问控制。