RESTful API

更新时间:
复制 MD 格式

长文本语音合成 RESTful API 通过 POST 请求提交文本,异步生成音频文件。任务提交后,可轮询查询结果或通过回调接收结果,再使用返回的音频地址下载或播放文件。

功能介绍

  • 支持 PCM、WAV、MP3 音频格式。

  • 支持 8000 Hz、16000 Hz 采样率;其他采样率的支持范围取决于音色。

  • 支持选择音色,调整语速、语调和音量。

  • 支持返回句级时间戳,用于音频播放与字幕同步。

  • 支持轮询和回调两种结果获取方式。

完整的音色、采样率和参数说明,请参见长文本语音合成接口说明。

说明

建议在服务端调用接口,避免在浏览器端暴露鉴权 Token。Node.js 服务端调用示例见下文。

前提条件

  • 已开通长文本语音合成商用版。该功能不提供试用版。

  • 已创建项目并获取 Appkey,具体操作请参见管理项目。

  • 已获取有效的 NLS Token,具体操作请参见获取 Token。

服务地址

访问方式

适用条件

URL

公网访问

可通过互联网访问服务。

https://nls-gateway-cn-shanghai.aliyuncs.com/rest/v1/tts/async

VPC 内网访问

使用华东 2(上海)地域的阿里云 ECS。

http://nls-gateway-cn-shanghai-internal.aliyuncs.com/rest/v1/tts/async

公网支持 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

包含项目 appkey 和鉴权 token,两者均必填。

payload.tts_request

Object

合成参数,包含必填的 text,以及音色、音频格式、采样率等可选参数。

payload.enable_notify

Boolean

是否通过回调接收结果。默认为 false。

payload.notify_url

String

回调地址。enable_notify 为 true 时必填。

context

Object

客户端上下文,例如 device_id。

各参数的取值、默认值和限制,请参见长文本语音合成接口说明。

{
  "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

服务状态码,20000000 表示请求成功。

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 仅为占位地址,使用时以接口返回的地址为准。

错误码

错误码含义及处理方法,请参见错误码查询。