长文本语音合成 RESTful API 通过 POST 请求提交文本,异步生成音频文件。任务提交后,可轮询查询结果或通过回调接收结果,再使用返回的音频地址下载或播放文件。
功能介绍
支持 PCM、WAV、MP3 音频格式。
支持 8000 Hz、16000 Hz 采样率;其他采样率的支持范围取决于音色。
支持选择音色,调整语速、语调和音量。
支持返回句级时间戳,用于音频播放与字幕同步。
支持轮询和回调两种结果获取方式。
完整的音色、采样率和参数说明,请参见长文本语音合成接口说明。
建议在服务端调用接口,避免在浏览器端暴露鉴权 Token。Node.js 服务端调用示例见下文。
前提条件
服务地址
|
访问方式 |
适用条件 |
URL |
|
公网访问 |
可通过互联网访问服务。 |
|
|
VPC 内网访问 |
使用华东 2(上海)地域的阿里云 ECS。 |
|
公网支持 HTTP 和 HTTPS,建议使用 HTTPS;VPC 内网仅支持 HTTP。下文示例使用公网 HTTPS 地址。
代码示例
示例从环境变量 NLS_APP_KEY 和 NLS_TOKEN 读取 Appkey 和 NLS Token,提交合成任务后轮询结果,输出任务 ID 和音频下载地址。将 text 替换为待合成的文本。
Java
示例使用 JDK 11 及以上版本的 HTTP 客户端和 Fastjson。Maven 依赖如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;
public class LongTts {
private static final String URL = "https://nls-gateway-cn-shanghai.aliyuncs.com/rest/v1/tts/async";
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30)).build();
private static JSONObject call(HttpRequest request) throws Exception {
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException("HTTP " + response.statusCode());
}
JSONObject result = JSON.parseObject(response.body());
if (result.getIntValue("error_code") != 20000000) {
throw new IllegalStateException("NLS error " + result.get("error_code")
+ "; request_id=" + result.getString("request_id"));
}
return result;
}
private static String encode(String value) {
return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
public static void main(String[] args) throws Exception {
String appkey = System.getenv("NLS_APP_KEY");
String token = System.getenv("NLS_TOKEN");
if (appkey == null || token == null) {
throw new IllegalArgumentException("Set NLS_APP_KEY and NLS_TOKEN");
}
Map<String, Object> body = Map.of(
"header", Map.of("appkey", appkey, "token", token),
"payload", Map.of("enable_notify", false, "tts_request", Map.of(
"text", "今天天气好晴朗。欢迎使用语音合成。",
"voice", "xiaoyun", "format", "wav", "sample_rate", 16000,
"enable_subtitle", true)));
JSONObject result = call(HttpRequest.newBuilder(URI.create(URL))
.timeout(Duration.ofSeconds(30)).header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(JSON.toJSONString(body))).build());
String taskId = result.getJSONObject("data").getString("task_id");
System.out.println("task_id: " + taskId);
String query = "?appkey=" + encode(appkey) + "&token=" + encode(token)
+ "&task_id=" + encode(taskId);
while (true) {
result = call(HttpRequest.newBuilder(URI.create(URL + query))
.timeout(Duration.ofSeconds(30)).GET().build());
String state = result.getString("error_message");
if ("QUEUEING".equals(state) || "RUNNING".equals(state)) {
Thread.sleep(10000);
continue;
}
JSONObject data = result.getJSONObject("data");
String address = data == null ? null : data.getString("audio_address");
if (address == null || address.isEmpty()) {
throw new IllegalStateException("No audio URL; request_id=" + result.getString("request_id"));
}
System.out.println(address);
break;
}
}
}
更多 Java 示例,请下载nls-restful-java-demo。
Python
示例使用 Python 3 标准库,无需安装第三方依赖。
import json
import os
import time
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError
URL = "https://nls-gateway-cn-shanghai.aliyuncs.com/rest/v1/tts/async"
appkey = os.environ["NLS_APP_KEY"]
token = os.environ["NLS_TOKEN"]
def call(request):
try:
with urlopen(request, timeout=30) as response:
result = json.load(response)
except HTTPError as error:
raise RuntimeError(f"HTTP {error.code}") from None
if result.get("error_code") != 20000000:
raise RuntimeError(f"NLS error {result.get('error_code')}; "
f"request_id={result.get('request_id')}")
return result
body = {
"header": {"appkey": appkey, "token": token},
"payload": {
"tts_request": {
"text": "今天天气好晴朗。欢迎使用语音合成。",
"voice": "xiaoyun", "format": "wav", "sample_rate": 16000,
"enable_subtitle": True
},
"enable_notify": False
}
}
result = call(Request(URL, data=json.dumps(body).encode("utf-8"),
headers={"Content-Type": "application/json"}))
task_id = result["data"]["task_id"]
print("task_id:", task_id)
query = urlencode({"appkey": appkey, "token": token, "task_id": task_id})
while True:
result = call(Request(URL + "?" + query))
if result.get("error_message") in ("QUEUEING", "RUNNING"):
time.sleep(10)
continue
address = (result.get("data") or {}).get("audio_address")
if not address:
raise RuntimeError("No audio URL; request_id=" + str(result.get("request_id")))
print(address)
break
PHP
示例使用 PHP 8.5,需启用 cURL 扩展。
<?php
$url = 'https://nls-gateway-cn-shanghai.aliyuncs.com/rest/v1/tts/async';
$appkey = getenv('NLS_APP_KEY');
$token = getenv('NLS_TOKEN');
if (!$appkey || !$token) {
throw new RuntimeException('Set NLS_APP_KEY and NLS_TOKEN');
}
function callApi($url, $body = null) {
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30]);
if ($body !== null) {
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($body, JSON_THROW_ON_ERROR)
]);
}
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($raw === false || $status < 200 || $status >= 300) {
throw new RuntimeException('HTTP request failed; status=' . $status);
}
$result = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
if (($result['error_code'] ?? null) !== 20000000) {
throw new RuntimeException('NLS error ' . ($result['error_code'] ?? '') .
'; request_id=' . ($result['request_id'] ?? ''));
}
return $result;
}
$body = [
'header' => ['appkey' => $appkey, 'token' => $token],
'payload' => [
'tts_request' => [
'text' => '今天天气好晴朗。欢迎使用语音合成。',
'voice' => 'xiaoyun', 'format' => 'wav', 'sample_rate' => 16000,
'enable_subtitle' => true
],
'enable_notify' => false
]
];
$result = callApi($url, $body);
$taskId = $result['data']['task_id'];
echo 'task_id: ' . $taskId . PHP_EOL;
$query = http_build_query(['appkey' => $appkey, 'token' => $token, 'task_id' => $taskId]);
while (true) {
$result = callApi($url . '?' . $query);
if (in_array($result['error_message'] ?? '', ['QUEUEING', 'RUNNING'], true)) {
sleep(10);
continue;
}
if (empty($result['data']['audio_address'])) {
throw new RuntimeException('No audio URL; request_id=' . ($result['request_id'] ?? ''));
}
echo $result['data']['audio_address'] . PHP_EOL;
break;
}
Node.js
示例使用 Node.js 22 的内置 fetch,采用 ES 模块格式。
const url = 'https://nls-gateway-cn-shanghai.aliyuncs.com/rest/v1/tts/async';
const appkey = process.env.NLS_APP_KEY;
const token = process.env.NLS_TOKEN;
if (!appkey || !token) throw new Error('Set NLS_APP_KEY and NLS_TOKEN');
async function call(address, options = {}) {
const response = await fetch(address, {
...options, signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
if (result.error_code !== 20000000) {
throw new Error(`NLS error ${result.error_code}; request_id=${result.request_id}`);
}
return result;
}
const body = {
header: { appkey, token },
payload: {
tts_request: {
text: '今天天气好晴朗。欢迎使用语音合成。',
voice: 'xiaoyun', format: 'wav', sample_rate: 16000,
enable_subtitle: true
},
enable_notify: false
}
};
let result = await call(url, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const taskId = result.data.task_id;
console.log('task_id:', taskId);
const query = new URLSearchParams({ appkey, token, task_id: taskId });
while (true) {
result = await call(`${url}?${query}`);
if (['QUEUEING', 'RUNNING'].includes(result.error_message)) {
await new Promise(resolve => setTimeout(resolve, 10000));
continue;
}
if (!result.data?.audio_address) {
throw new Error(`No audio URL; request_id=${result.request_id}`);
}
console.log(result.data.audio_address);
break;
}
Go
示例仅使用 Go 标准库。
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"time"
)
type Result struct {
ErrorCode int `json:"error_code"`
Message string `json:"error_message"`
RequestID string `json:"request_id"`
Data struct {
TaskID string `json:"task_id"`
AudioAddress string `json:"audio_address"`
} `json:"data"`
}
var client = &http.Client{Timeout: 30 * time.Second}
func call(method, address string, body []byte) (Result, error) {
var result Result
request, err := http.NewRequest(method, address, bytes.NewReader(body))
if err != nil { return result, err }
if body != nil { request.Header.Set("Content-Type", "application/json") }
response, err := client.Do(request)
if err != nil { return result, fmt.Errorf("HTTP request failed") }
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 {
return result, fmt.Errorf("HTTP %d", response.StatusCode)
}
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
return result, err
}
if result.ErrorCode != 20000000 {
return result, fmt.Errorf("NLS error %d; request_id=%s", result.ErrorCode, result.RequestID)
}
return result, nil
}
func run() error {
endpoint := "https://nls-gateway-cn-shanghai.aliyuncs.com/rest/v1/tts/async"
appkey, token := os.Getenv("NLS_APP_KEY"), os.Getenv("NLS_TOKEN")
if appkey == "" || token == "" { return fmt.Errorf("set NLS_APP_KEY and NLS_TOKEN") }
body, err := json.Marshal(map[string]interface{}{
"header": map[string]string{"appkey": appkey, "token": token},
"payload": map[string]interface{}{
"enable_notify": false,
"tts_request": map[string]interface{}{
"text": "今天天气好晴朗。欢迎使用语音合成。",
"voice": "xiaoyun", "format": "wav", "sample_rate": 16000,
"enable_subtitle": true,
},
},
})
if err != nil { return err }
result, err := call("POST", endpoint, body)
if err != nil { return err }
fmt.Println("task_id:", result.Data.TaskID)
query := url.Values{"appkey": {appkey}, "token": {token}, "task_id": {result.Data.TaskID}}
for {
result, err = call("GET", endpoint + "?" + query.Encode(), nil)
if err != nil { return err }
if result.Message == "QUEUEING" || result.Message == "RUNNING" {
time.Sleep(10 * time.Second)
continue
}
if result.Data.AudioAddress == "" {
return fmt.Errorf("no audio URL; request_id=%s", result.RequestID)
}
fmt.Println(result.Data.AudioAddress)
return nil
}
}
func main() {
if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) }
}
提交任务
请求说明
向服务地址发送 POST 请求,将请求头 Content-Type 设置为 application/json。请求体使用 UTF-8 编码的 JSON。
|
字段 |
类型 |
说明 |
|
header |
Object |
包含项目 |
|
payload.tts_request |
Object |
合成参数,包含必填的 |
|
payload.enable_notify |
Boolean |
是否通过回调接收结果。默认为 |
|
payload.notify_url |
String |
回调地址。 |
|
context |
Object |
客户端上下文,例如 |
各参数的取值、默认值和限制,请参见长文本语音合成接口说明。
{
"header": {
"appkey": "yourAppkey",
"token": "yourToken"
},
"payload": {
"tts_request": {
"text": "今天天气好晴朗。欢迎使用语音合成。",
"voice": "xiaoyun",
"format": "wav",
"sample_rate": 16000,
"enable_subtitle": true
},
"enable_notify": false
},
"context": {
"device_id": "my_device_id"
}
}
响应说明
响应体为 JSON。请求成功时,status 为 200,error_code 为 20000000,data.task_id 为后续查询所需的任务 ID。任务提交成功不代表音频已合成完成。
|
字段 |
类型 |
说明 |
|
status |
Integer |
请求状态。 |
|
error_code |
Integer |
服务状态码, |
|
error_message |
String |
状态或错误说明。 |
|
request_id |
String |
请求 ID,可用于排查问题。 |
|
data.task_id |
String |
合成任务 ID,用于查询合成结果。 |
提交成功的响应示例:
{
"status": 200,
"error_code": 20000000,
"error_message": "SUCCESS",
"request_id": "a3370c49a29148e78b39978f98ba****",
"data": {
"task_id": "35d9f813e00b11e9a2ce9ba0d6a2****"
}
}
请求失败时,根据 HTTP 状态码及响应中的 error_code、error_message 排查问题。例如,缺少鉴权 Token 时返回:
{
"status": 400,
"error_code": 40000001,
"error_message": "Gateway:ACCESS_DENIED:Missing authorization header!",
"request_id": "a3370c49a29148e78b39978f98ba****",
"url": "/rest/v1/tts/async"
}
获取合成结果
轮询
向同一服务地址发送 GET 请求,查询参数如下。
|
参数 |
说明 |
|
appkey |
项目的 Appkey。 |
|
token |
有效的 NLS Token。 |
|
task_id |
提交任务时返回的任务 ID。 |
https://nls-gateway-cn-shanghai.aliyuncs.com/rest/v1/tts/async?appkey={Appkey}&task_id={task_id}&token={Token}
error_message 为 QUEUEING 或 RUNNING 时,任务尚未完成,稍后再次查询。以下为处理中响应的示例:
{
"status": 200,
"error_code": 20000000,
"error_message": "RUNNING",
"request_id": "a3370c49a29148e78b39978f98ba****",
"data": {
"task_id": "35d9f813e00b11e9a2ce9ba0d6a2****",
"audio_address": null,
"notify_custom": null
}
}
合成完成后,data.audio_address 为音频下载地址。使用支持句级时间戳的音色并设置 enable_subtitle 为 true 时,data.sentences 包含句级时间戳,其中 begin_time 和 end_time 的单位为毫秒。
{
"status": 200,
"error_code": 20000000,
"error_message": "SUCCESS",
"request_id": "c541eae489af48d69dae2d2e203a****",
"data": {
"task_id": "35d9f813e00b11e9a2ce9ba0d6a2****",
"audio_address": "https://example.com/audio.wav",
"sentences": [
{"text": "今天天气好晴朗。", "begin_time": "0", "end_time": "2315"},
{"text": "欢迎使用语音合成。", "begin_time": "2315", "end_time": "4355"}
],
"notify_custom": ""
}
}
回调
提交任务时,将 payload.enable_notify 设置为 true,并通过 payload.notify_url 指定接收结果的地址。回调地址支持 HTTP 和 HTTPS,需使用域名,不支持直接使用 IP 地址。
服务端完成合成后,将结果发送到回调地址。回调结果示例:
{
"data": {
"audio_address": "https://example.com/audio.wav",
"task_id": "e3a06366d**************e1257693b"
},
"request_id": "edc6b5ab4***********223c8ace71",
"status": 20000000,
"error_code": 20000000,
"error_message": "SUCCESS"
}
audio_address 的有效期最长为 7 天,请及时下载并保存音频文件。示例中的 https://example.com/audio.wav 仅为占位地址,使用时以接口返回的地址为准。
错误码
错误码含义及处理方法,请参见错误码查询。