定制热词HTTP API参考 - 千问云
跳转到主要内容
Sambert

Sambert WebSocket API 参考

通过 WebSocket 长连接实时合成 Sambert 语音,支持流式音频输出与字/音素级时间戳

本文介绍通过 WebSocket 连接访问 Sambert 实时语音合成服务的交互流程、服务端点和请求头。 DashScope SDK 目前仅支持 Java 和 Python。使用其他编程语言时,可通过 WebSocket 连接与服务进行通信。 用户指南: 关于模型介绍和选型建议请参见语音合成

服务端点

WebSocket 服务端点固定为:wss://dashscope.aliyuncs.com/api-ws/v1/inference
URL 必须使用 wss:// 协议,且固定不变。Authorization 在请求头中设置(参见请求头)。

请求头

请求头中需添加如下信息:
参数类型是否必选说明
Authorizationstring鉴权令牌,格式为 Bearer <your_api_key>,将 <your_api_key> 替换为实际的 API Key。
user-agentstring客户端标识,便于服务端追踪来源。
X-DashScope-WorkSpacestring千问云业务空间 ID。
X-DashScope-DataInspectionstring是否启用数据合规检测功能。默认不传或设为 enable。如非必要,请勿启用该参数。
Authorization 鉴权在 WebSocket 握手阶段验证。如果 API Key 无效或缺失,握手将失败并返回 HTTP 401/403 错误。

交互流程

客户端事件和服务端事件的详细说明,请参见客户端事件服务端事件 按时间顺序,客户端与服务端的交互流程如下:
  1. 建立连接:客户端与服务端建立 WebSocket 连接。
  2. 开启任务:客户端发送 run-task 事件以开启任务。Sambert 在 run-task 中一次性发送全部待合成文本。
  3. 等待确认:客户端收到服务端返回的 task-started 事件,标志着任务已成功开启。
  4. 接收音频:客户端通过 binary 通道接收服务端持续返回的音频流,同时收到 result-generated 事件(携带时间戳等附加信息)。
  5. 任务结束:客户端收到服务端返回的 task-finished 事件,标志着任务结束。
  6. 关闭连接:客户端关闭 WebSocket 连接。
为提高资源利用率,建议复用 WebSocket 连接处理多个任务,而非为每个任务建立新连接。
Sambert 不支持流式输入(streaming 为 out 而非 duplex),所有待合成文本必须在 run-task 事件中一次性发送。不支持 continue-task 和 finish-task 指令。

前提条件

  • 已获取 DashScope API Key 并配置为环境变量 DASHSCOPE_API_KEY
  • 网络环境支持访问 dashscope.aliyuncs.com(国内访问)。

约束

  • WebSocket 连接建立后,需先发送 run-task 指令,服务端才会开始合成并推送音频。
  • 一次 WebSocket 连接只对应一次合成任务(一个 task_id)。任务完成(task-finished)后可复用同一连接发起新任务;任务失败(task-failed)后连接将被关闭,需重新建立连接。
  • 待合成文本长度上限:一次 run-task 请求不超过 10,000 个字符(含标点)。

二、异步监听服务器返回的消息

服务端返回两种类型的消息:
  • 二进制消息:音频数据流,客户端直接写入文件或播放缓冲区。
  • 文本消息(JSON):事件通知,包含以下类型:
服务端消息 header 字段说明
字段类型说明
headerObject消息头
header.eventString事件类型:task-startedresult-generatedtask-finishedtask-failed
header.task_idString任务 ID,与发送 run-task 时的 task_id 一致
task-started 事件
{
    "header": {
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "event": "task-started",
        "attributes": {}
    },
    "payload": {}
}
result-generated 事件(含时间戳示例)
{
    "header": {
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "event": "result-generated",
        "attributes": {}
    },
    "payload": {
        "output": {
            "sentence": {
                "begin_time": 0,
                "end_time": 1162,
                "words": [
                    {
                        "text": "床",
                        "begin_time": 0,
                        "end_time": 263,
                        "phonemes": [
                            {
                                "begin_time": 0,
                                "end_time": 119,
                                "text": "ch_c",
                                "tone": 2
                            },
                            {
                                "begin_time": 119,
                                "end_time": 263,
                                "text": "uang_c",
                                "tone": 2
                            }
                        ]
                    }
                ]
            }
        },
        "usage": null
    }
}
output.sentence 字段说明:
字段类型说明
begin_timeInteger句子开始时间(毫秒)
end_timeInteger句子结束时间(毫秒)
wordsArray字级别时间戳列表(开启 word_timestamp_enabled 后返回)
words 中每个元素的字段:
字段类型说明
textString汉字文本
begin_timeInteger开始时间(毫秒)
end_timeInteger结束时间(毫秒)
phonemesArray音素级时间戳列表(开启 phoneme_timestamp_enabled 后返回)
phonemes 中每个元素的字段:
字段类型说明
begin_timeInteger开始时间(毫秒)
end_timeInteger结束时间(毫秒)
textString音素文本
toneInteger声调(1–4,0 表示轻声)
task-finished 事件
{
    "header": {
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "event": "task-finished",
        "attributes": {}
    },
    "payload": {
        "output": null,
        "usage": {
            "characters": 6
        }
    }
}
task-failed 事件
{
    "header": {
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "event": "task-failed",
        "error_code": "CLIENT_ERROR",
        "error_message": "request timeout after 23 seconds.",
        "attributes": {}
    },
    "payload": {}
}
task-failed 错误字段说明:
字段类型说明
header.error_codeString错误码,如 CLIENT_ERRORSERVER_ERROR
header.error_messageString错误详情描述

三、给服务器发送消息

客户端只需发送一条 JSON 消息:run-task 指令。 消息结构
{
    "header": {
        "action": "run-task",
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "streaming": "out"
    },
    "payload": {
        "model": "sambert-zhichu-v1",
        "task_group": "audio",
        "task": "tts",
        "function": "SpeechSynthesizer",
        "input": {
            "text": "床前明月光,"
        },
        "parameters": {
            "text_type": "PlainText",
            "format": "mp3",
            "sample_rate": 16000,
            "volume": 50,
            "rate": 1,
            "pitch": 1,
            "word_timestamp_enabled": true,
            "phoneme_timestamp_enabled": true
        }
    }
}
run-task header 字段说明
字段类型必填说明
header.actionString固定值 run-task
header.task_idString任务 ID,格式为随机 UUID,同一连接内不可重复
header.streamingString固定值 out,表示流式输出
Python 生成 UUID 示例:
import uuid

def generate_task_id():
  # 生成随机UUID
  return uuid.uuid4().hex
run-task payload 参数说明
参数类型必填说明
task_groupString固定值 audio
taskString固定值 tts
functionString固定值 SpeechSynthesizer
modelString模型名称,详见模型列表
input.textString待合成文本,最长 10,000 个字符
parameters.text_typeString文本类型。PlainText(默认):纯文本;SSML:SSML 标记语言
parameters.formatString音频格式。支持 pcmwavmp3(默认 pcm
parameters.sample_rateInteger采样率(Hz)。默认值由模型决定,常见值为 1600048000
parameters.volumeInteger音量,范围 0100,默认 50
parameters.rateFloat语速,范围 0.52.0,默认 1.0(1.0 为正常语速)
parameters.pitchFloat音调,范围 0.52.0,默认 1.0(1.0 为正常音调)
parameters.word_timestamp_enabledBoolean是否返回字级别时间戳,默认 false
parameters.phoneme_timestamp_enabledBoolean是否返回音素级别时间戳,默认 false

四、关闭 WebSocket 连接

收到 task-finished 事件后,客户端可选择:
  • 关闭连接:调用 WebSocket 关闭接口(推荐在不再需要合成时关闭)。
  • 复用连接:使用新的 task_id 发送下一个 run-task 指令,继续合成新文本,无需重新建立连接(节省握手开销)。
收到 task-failed 事件后,连接由服务端关闭,客户端需要重新建立连接。

关于建连开销和连接复用

建立 WebSocket 连接需要 TLS 握手,有一定延迟开销。对于需要高频合成的场景,建议:
  • 维持长连接,通过切换 task_id 复用同一连接。
  • 对连接进行池化管理,避免频繁断连重连。

示例代码

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/google/uuid"
	"github.com/gorilla/websocket"
)

const (
	wsURL      = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/" // WebSocket服务器地址
	outputFile = "output.mp3"                                        // 输出文件路径
)

func main() {
	// 若没有将API Key配置到环境变量,可将下行替换为:apiKey := "your_api_key"。不建议在生产环境中直接将API Key硬编码到代码中,以减少API Key泄露风险。
	apiKey := os.Getenv("DASHSCOPE_API_KEY")

	if err := clearOutputFile(outputFile); err != nil {
		fmt.Println("清空输出文件失败:", err)
		return
	}

	conn, err := connectWebSocket(apiKey)
	if err != nil {
		fmt.Println("连接WebSocket失败:", err)
		return
	}
	defer closeConnection(conn)

	done := make(chan struct{})
	go receiveMessage(conn, done)

	if err := sendRunTaskMsg(conn); err != nil {
		fmt.Println("发送run-task指令失败:", err)
		return
	}

	select {
	case <-done:
		fmt.Println("任务结束")
	case <-time.After(5 * time.Minute):
		fmt.Println("任务超时")
	}
}

type Message struct {
	Header  Header  `json:"header"`
	Payload Payload `json:"payload"`
}

type Header struct {
	Action       string                 `json:"action,omitempty"`
	TaskID       string                 `json:"task_id"`
	Streaming    string                 `json:"streaming,omitempty"`
	Event        string                 `json:"event,omitempty"`
	ErrorCode    string                 `json:"error_code,omitempty"`
	ErrorMessage string                 `json:"error_message,omitempty"`
	Attributes   map[string]interface{} `json:"attributes"`
}

type Payload struct {
	Model      string     `json:"model,omitempty"`
	TaskGroup  string     `json:"task_group,omitempty"`
	Task       string     `json:"task,omitempty"`
	Function   string     `json:"function,omitempty"`
	Input      Input      `json:"input,omitempty"`
	Parameters Parameters `json:"parameters,omitempty"`
	Output     Output     `json:"output,omitempty"`
	Usage      Usage      `json:"usage,omitempty"`
}

type Input struct {
	Text string `json:"text"`
}

type Parameters struct {
	TextType                string  `json:"text_type"`
	Format                  string  `json:"format"`
	SampleRate              int     `json:"sample_rate"`
	Volume                  int     `json:"volume"`
	Rate                    float64 `json:"rate"`
	Pitch                   float64 `json:"pitch"`
	WordTimestampEnabled    bool    `json:"word_timestamp_enabled"`
	PhonemeTimestampEnabled bool    `json:"phoneme_timestamp_enabled"`
}

type Output struct {
	Sentence Sentence `json:"sentence"`
}

type Sentence struct {
	BeginTime int    `json:"begin_time"`
	EndTime   int    `json:"end_time"`
	Words     []Word `json:"words"`
}

type Word struct {
	Text      string    `json:"text"`
	BeginTime int       `json:"begin_time"`
	EndTime   int       `json:"end_time"`
	Phonemes  []Phoneme `json:"phonemes"`
}

type Phoneme struct {
	BeginTime int    `json:"begin_time"`
	EndTime   int    `json:"end_time"`
	Text      string `json:"text"`
	Tone      int    `json:"tone"`
}

type Usage struct {
	Characters int `json:"characters"`
}

func receiveMessage(conn *websocket.Conn, done chan struct{}) {
	for {
		msgType, message, err := conn.ReadMessage()
		if err != nil {
			fmt.Println("解析服务器消息失败:", err)
			close(done)
			break
		}

		if msgType == websocket.BinaryMessage {
			if err := writeBinaryDataToFile(message, outputFile); err != nil {
				fmt.Println("写入二进制数据失败:", err)
				close(done)
				break
			}
			fmt.Println("音频片段已写入本地文件")
		} else {
			var msg Message
			if err := json.Unmarshal(message, &msg); err != nil {
				fmt.Println("解析事件失败:", err)
				continue
			}
			if handleMessage(conn, msg, done) {
				break
			}
		}
	}
}

func handleMessage(conn *websocket.Conn, msg Message, done chan struct{}) bool {
	switch msg.Header.Event {
	case "task-started":
		fmt.Println("任务已启动")
	case "result-generated":
		// 如需获取附加消息,可在此处添加相应代码
	case "task-finished":
		fmt.Println("任务已完成")
		close(done)
		return true
	case "task-failed":
		if msg.Header.ErrorMessage != "" {
			fmt.Printf("任务失败:%s\n", msg.Header.ErrorMessage)
		} else {
			fmt.Println("未知原因导致任务失败")
		}
		close(done)
		return true
	default:
		fmt.Printf("预料之外的事件:%v\n", msg)
		close(done)
	}
	return false
}

func sendRunTaskMsg(conn *websocket.Conn) error {
	runTaskMsg, err := generateRunTaskMsg()
	if err != nil {
		return err
	}
	return conn.WriteMessage(websocket.TextMessage, []byte(runTaskMsg))
}

func generateRunTaskMsg() (string, error) {
	runTaskMessage := Message{
		Header: Header{
			Action:    "run-task",
			TaskID:    uuid.New().String(),
			Streaming: "out",
		},
		Payload: Payload{
			Model:     "sambert-zhichu-v1",
			TaskGroup: "audio",
			Task:      "tts",
			Function:  "SpeechSynthesizer",
			Input: Input{
				Text: "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。",
			},
			Parameters: Parameters{
				TextType:                "PlainText",
				Format:                  "mp3",
				SampleRate:              16000,
				Volume:                  50,
				Rate:                    1.0,
				Pitch:                   1.0,
				WordTimestampEnabled:    true,
				PhonemeTimestampEnabled: true,
			},
		},
	}
	runTaskMsgJSON, err := json.Marshal(runTaskMessage)
	return string(runTaskMsgJSON), err
}

func connectWebSocket(apiKey string) (*websocket.Conn, error) {
	header := make(http.Header)
	header.Add("X-DashScope-DataInspection", "enable")
	header.Add
DashScope
图像生成 API

错误码

通用错误码请参阅错误信息 WebSocket 特有错误通过 task-failed 事件返回,错误码位于 header.error_code,错误信息位于 header.error_message

常见问题

Q:连接建立后没有收到任何音频数据? A:请确认已发送 run-task 指令,且指令格式正确。检查 payload.input.text 是否非空,model 是否为有效的模型名称。 Q:task-failed 错误码为 CLIENT_ERROR,错误信息包含 timeout A:连接建立后需在较短时间内(通常 23 秒内)发送 run-task 指令,否则服务端会超时并关闭连接。 Q:能否在同一连接内合成多段文本? A:可以。收到 task-finished 后,使用不同的 task_id 再次发送 run-task 指令即可。注意 task-failed 后连接已关闭,需重新建连。

模型列表

中文音色(48 kHz)

音色名model 参数时间戳支持适用场景特色默认采样率(Hz)
广告男声sambert-zhinan-v1广告配音磁性、自信48000
温柔女声sambert-zhiqi-v1情感类内容柔和、亲切48000
舌尖男声sambert-zhichu-v1通用清晰、标准48000
新闻男声sambert-zhide-v1新闻播报沉稳、权威48000
标准女声sambert-zhijia-v1通用标准、清晰48000
新闻女声sambert-zhiru-v1新闻播报专业、流畅48000
资讯女声sambert-zhiqian-v1资讯播报干练、利落48000
磁性男声sambert-zhixiang-v1有声读物磁性、浑厚48000
萝莉女声sambert-zhiwei-v1动漫、娱乐活泼、可爱48000

中文音色(16 kHz)

音色名model 参数时间戳支持适用场景语言默认采样率(Hz)
sambert-zhihao-v1通用中文16000
sambert-zhijing-v1通用中文16000
sambert-zhiming-v1通用中文16000
sambert-zhimo-v1通用中文16000
sambert-zhina-v1通用中文16000
sambert-zhishu-v1通用中文16000
sambert-zhistella-v1通用中文16000
sambert-zhiting-v1通用中文16000
sambert-zhixiao-v1通用中文16000
sambert-zhiya-v1通用中文16000
sambert-zhiye-v1通用中文16000
sambert-zhiying-v1通用中文16000
sambert-zhiyuan-v1通用中文16000
sambert-zhiyue-v1通用中文16000
sambert-zhigui-v1通用中文16000
sambert-zhishuo-v1通用中文16000
sambert-zhimiao-emo-v1情感类中文16000
sambert-zhimao-v1通用中文16000
sambert-zhilun-v1通用中文16000
sambert-zhifei-v1通用中文16000
sambert-zhida-v1通用中文16000

多语种音色(16 kHz)

音色名model 参数时间戳支持语言默认采样率(Hz)
sambert-camila-v1西班牙语16000
sambert-perla-v1意大利语16000
sambert-indah-v1印尼语16000
sambert-clara-v1法语16000
sambert-hanna-v1德语16000

英语音色(美式,16 kHz)

音色名model 参数时间戳支持语言默认采样率(Hz)
sambert-beth-v1英语(美式)16000
sambert-betty-v1英语(美式)16000
sambert-cally-v1英语(美式)16000
sambert-cindy-v1英语(美式)16000
sambert-eva-v1英语(美式)16000
sambert-donna-v1英语(美式)16000
sambert-brian-v1英语(美式)16000

泰语音色(16 kHz)

音色名model 参数时间戳支持语言默认采样率(Hz)
sambert-waan-v1泰语16000