Skip to main content
語音合成

即時語音合成

即時語音合成將文本即時轉換為自然語音,支援流式輸入與輸出,具備聲音複刻、聲音設計及精細化音頻控制能力,適用於語音助手、有聲讀物、智能客服等情境。

概述

實現低延遲文字轉換語音。
  • 支援流式輸入與輸出,首包延遲低
  • 可調節語速、語調、音量與碼率,實現精細的語音效果控制
  • 相容主流音頻格式(PCM、WAV、MP3、Opus),最高支援 48kHz 採樣率輸出
  • 支援指令控制,可通過自然語言指令控制語音表現力
  • 支援聲音複刻聲音設計音色定製
  • 支援情感與富語言標籤,可在文本中嵌入標籤控制情感表達或插入擬聲效果
批量情境(有聲讀物、課件配音等)可使用非即時語音合成。各模型選型建議請參見語音合成

前提條件

快速開始

以下是各模型的語音合成樣本。更多樣本和參數說明請參見各模型的API參考
  • Qwen-Audio-TTS
  • CosyVoice
  • Qwen-TTS
以下樣本示範如何使用系統音色進行語音合成。如需使用指令控制功能,請通過 instruction 參數設定指令。
Python
# coding=utf-8

import os
import dashscope
from dashscope.audio.tts_v2 import *

# 新加坡地區和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 若沒有配置環境變數,請用阿里雲百鍊API Key將下行替換為:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# 以下為新加坡地區的配置,調用時請將"{WorkspaceId}"替換為真實的業務空間ID,各地區的配置不同。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'

# 模型
# qwen-audio-3.0-tts-flash/qwen-audio-3.0-tts-plus:使用longanhuan_v3.6等音色。
# 每個音色支援的語言不同,合成日語、韓語等非中文語言時,需選擇支援對應語言的音色。詳見音色列表。
model = "qwen-audio-3.0-tts-flash"
# 音色
voice = "longanhuan_v3.6"

# 執行個體化SpeechSynthesizer,並在構造方法中傳入模型(model)、音色(voice)等請求參數
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# 發送待合成文本,擷取二進位音頻
audio = synthesizer.call("今天天氣怎麼樣?")
# 首次發送文本時需建立 WebSocket 串連,因此首包延遲會包含串連建立的耗時
print('[Metric] requestId為:{},首包延遲為:{}毫秒'.format(
    synthesizer.get_last_request_id(),
    synthesizer.get_first_package_delay()))

# 將音頻儲存至本地
with open('output.mp3', 'wb') as f:
    f.write(audio)

會話配置

Qwen-TTS 互動模式

Qwen-TTS Realtime API 提供兩種互動模式:
  • server_commit 模式:由服務端智能處理文本分段與合成時機,適合大段文本的連續合成情境。用戶端只需持續追加文本,無需關注分段和提交。
  • commit 模式:由用戶端主動提交文本緩衝區以觸發合成,適合需要精確控制合成時機的情境(如對話式 AI 逐輪合成)。
切換互動模式
  • WebSocket:通過 session.update 事件中的 mode 欄位設定。
{
    "type": "session.update",
    "session": {
        "mode": "server_commit"
    }
}
  • Python SDK:在 update_session 方法中通過 mode 參數設定。
qwen_tts_realtime.update_session(
    voice='Cherry',
    response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
    mode='server_commit'
)
  • Java SDK:通過 QwenTtsRealtimeConfig.builder() 設定 mode 參數。
QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
        .voice("Cherry")
        .responseFormat(ttsFormat)
        .mode("server_commit")
        .build();
qwenTtsRealtime.updateSession(config);
完整的 SDK 程式碼範例請參見Python SDKJava SDK。WebSocket 事件生命週期和串連複用說明請參見WebSocket API參考

進階功能

指令控制

指令控制通過自然語言描述控制語音的音調、語速、情感和音色特點,無需調整複雜的音頻參數。 各模型指令規格
  • Qwen-Audio-TTS
  • CosyVoice
  • Qwen-TTS
支援的模型qwen-audio-3.0-tts-plusqwen-audio-3.0-tts-flash系統音色和聲音複刻音色:均可輸入任意指令。
適用情境
  • 有聲書和廣播劇配音
  • 廣告和宣傳片配音
  • 遊戲角色和動畫配音
  • 情感化的智能語音助手
  • 紀錄片和新聞播報
如何編寫高品質的聲音描述
  • 核心原則
    1. 具體而非模糊:使用描繪聲音特質的詞語,如“低沉”、“清脆”、“語速偏快”,避免“好聽”、“普通”等主觀或模糊的表述。
    2. 多維而非單一:好的描述通常涵蓋多個維度(如性別、年齡、情感等)。僅寫“女聲”過於寬泛,難以產生有特色的音色。
    3. 客觀而非主觀:聚焦聲音的物理和感知特徵。例如,用”音調偏高,帶有活力“代替”我最喜歡的聲音”。
    4. 原創而非模仿:描述聲音的特質,而非要求模仿特定人物(如名人、演員)。模型不支援模仿,且可能涉及著作權風險。
    5. 簡潔而非冗餘:確保每個詞都有明確作用,避免重複的同義字或無意義的修飾。
  • 描述維度參考 建議組合以下維度描述聲音,維度越豐富,產生效果越精準。

    維度

    描述樣本

    性別

    男性、女性、中性

    年齡

    兒童(5-12 歲)、青少年(13-18 歲)、青年(19-35 歲)、中年(36-55 歲)、老年(55 歲以上)

    音調

    高音、中音、低音、偏高、偏低

    語速

    快速、中速、緩慢、偏快、偏慢

    情感

    開朗、沉穩、溫柔、嚴肅、活潑、冷靜、治癒

    特點

    有磁性、清脆、沙啞、圓潤、甜美、渾厚、有力

    用途

    新聞播報、廣告配音、有聲書、動畫角色、語音助手、紀錄片解說

  • 樣本
    • 標準播音風格:吐字清晰精準,字正腔圓
    • 年輕活潑的女性聲音,語速較快,帶有明顯的上揚語調,適合介紹時尚產品
    • 沉穩的中年男性,語速緩慢,音色低沉有磁性,適合朗讀新聞或紀錄片解說
    • 溫柔知性的女性,30 歲左右,語調平和,適合有聲書朗讀
    • 可愛的兒童聲音,大約 8 歲女孩,說話略帶稚氣,適合動畫角色配音

方言

本節介紹如何讓模型用中文方言(如河南話、四川話、粵語等)輸出語音。不同模型和音色類型的設定方式不同。 各模型方言設定方式
  • Qwen-Audio-TTS
  • CosyVoice
  • Qwen-TTS
  • 系統音色:在Qwen-Audio-TTS音色列表中選擇以下任一種音色:
    • 支援方言的系統音色,無需額外設定即可輸出對應方言。
    • 支援指令控制且可指定方言的音色,通過指令文本指定方言。
  • 聲音複刻音色:通過指令控制功能設定,例如指令文本寫 請用河南話表達
具體支援哪些方言:參見Qwen-Audio-TTS中各模型“支援的語言”。

情感與富語言標籤

Qwen-Audio-TTS 系列模型支援在待合成文本(text 參數)中直接嵌入情感與富語言標籤,用於控制語音的情感表達或在指定位置插入擬聲效果(如笑聲、歎息等),無需調整複雜的音頻參數即可產生更具表現力的語音。
支援的模型:僅 qwen-audio-3.0-tts-plusqwen-audio-3.0-tts-flash限制:僅支援單向流式模式。
控制類標籤 控制類標籤用於設定語音的情感或風格。將標籤寫在文本中,標籤會作用於其後的所有文本,直到遇到下一個控制類標籤,或因句子較長被自動切分為止。

標籤

說明

[sad]

悲傷

[amazed]

驚歎

[deep and loud shouting]

深沉大聲呐喊

[trembling]

顫抖

[angry]

憤怒

[excited]

興奮

[sarcastic]

諷刺

[curious]

好奇

[like dracula]

德古拉風格(低沉、陰森)

[bored]

無聊

[tired]

疲憊

[scornful]

輕蔑

[shouting]

大喊

[asmr]

ASMR 輕柔耳語

[panicked]

恐慌

[mischievously]

調皮

[empathetic]

共情

[whispers]

耳語

[reluctantly]

不情願

[crying]

哭泣

[serious]

嚴肅

[very slowly]

非常緩慢地說話

[very fast]

非常快速地說話

富語言類標籤 富語言類標籤用於在文本的當前位置插入一段擬聲效果,不影響前後文本的情感風格。

標籤

說明

[gasp]

倒吸一口氣

[sighing]

歎息

[clears throat]

清嗓

[giggles]

咯咯笑

[laughing]

大笑

[cough]

咳嗽

[snorts]

哼聲、嗤笑

使用樣本 以下樣本展示如何在 text 參數中組合使用控制類標籤和富語言類標籤: [excited]今天的天氣真不錯![laughing]我們一起出去玩吧! 上述文本中,[excited] 是控制類標籤,作用於其後的所有文本,使語音帶有興奮的情感;[laughing] 是富語言類標籤,在該位置插入一段笑聲效果後繼續合成後續文本。 您也可以在同一段文本中切換不同情感: [serious]請注意安全事項。[excited]好了,現在讓我們開始吧! 其中 [serious] 控制第一句為嚴肅語氣,[excited] 從第二句起切換為興奮語氣。

取消任務

在即時語音合成過程中,如果需要中斷當前輪次合成,可以發送取消指令。取消後服務端會立即結束當前任務並返回結束事件,您可在當前 WebSocket 串連上繼續發起新的合成任務,無需重建立立串連。 使用方式
  • Python SDK:1.26.4 及以上版本,調用 SpeechSynthesizer.streaming_cancel()
  • Java SDK:2.22.26 及以上版本,調用 SpeechSynthesizer.streamingCancel()
  • WebSocket 原始協議:發送 finish-task 事件,並在 input 中設定 directive=cancel
模型限制
  • 華北2(北京)地區:Qwen-Audio-TTS 系列模型的所有模型都支援該功能;CosyVoice 系列模型僅 v2 及以上版本支援該功能。
  • 新加坡地區:Qwen-Audio-TTS 系列模型的所有模型都支援該功能;CosyVoice 系列模型不支援該功能。

WebSocket 原始協議調用

以下樣本展示如何通過 WebSocket 原始協議直連服務端,適用於不使用 DashScope SDK 的情境。此為最小可運行實現,WebSocket 通訊協定請參見各模型的 API 參考。
  • Qwen-Audio-TTS/CosyVoice
  • Qwen-TTS
Qwen-Audio-TTS 和 CosyVoice 使用相同的 WebSocket 通訊協定,只需替換 modelvoice 參數。以下樣本以 qwen-audio-3.0-tts-flash 為例,使用 CosyVoice 時將 model 替換為 cosyvoice-v3-flash 等,voice 替換為對應音色即可。
  • Go
  • C#
  • PHP
  • Node.js
  • Java
  • Python
package main

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

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

const (
	// 以下為新加坡地區的配置,調用時請將"{WorkspaceId}"替換為真實的業務空間ID,各地區的配置不同。
	wsURL      = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference"
	outputFile = "output.mp3"
)

func main() {
	// 新加坡和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
	// 若沒有配置環境變數,請用阿里雲百鍊API Key將下行替換為:apiKey := "sk-xxx"
	apiKey := os.Getenv("DASHSCOPE_API_KEY")

	// 清空輸出檔案
	os.Remove(outputFile)
	os.Create(outputFile)

	// 串連WebSocket
	header := make(http.Header)
	header.Add("X-DashScope-DataInspection", "enable")
	header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey))

	conn, resp, err := websocket.DefaultDialer.Dial(wsURL, header)
	if err != nil {
		if resp != nil {
			fmt.Printf("串連失敗 HTTP狀態代碼: %d\n", resp.StatusCode)
		}
		fmt.Println("串連失敗:", err)
		return
	}
	defer conn.Close()

	// 產生任務ID
	taskID := uuid.New().String()
	fmt.Printf("產生任務ID: %s\n", taskID)

	// 發送run-task事件
	runTaskCmd := map[string]interface{}{
		"header": map[string]interface{}{
			"action":    "run-task",
			"task_id":   taskID,
			"streaming": "duplex",
		},
		"payload": map[string]interface{}{
			"task_group": "audio",
			"task":       "tts",
			"function":   "SpeechSynthesizer",
			"model":      "qwen-audio-3.0-tts-flash",
			"parameters": map[string]interface{}{
				"text_type":   "PlainText",
				"voice":       "longanhuan_v3.6",
				"format":      "mp3",
				"sample_rate": 22050,
				"volume":      50,
				"rate":        1,
				"pitch":       1,
				// 如果enable_ssml設為true,只允許發送一次continue-task事件,否則會報錯“Text request limit violated, expected 1.”
				"enable_ssml": false,
			},
			"input": map[string]interface{}{},
		},
	}

	runTaskJSON, _ := json.Marshal(runTaskCmd)
	fmt.Printf("發送run-task事件: %s\n", string(runTaskJSON))

	err = conn.WriteMessage(websocket.TextMessage, runTaskJSON)
	if err != nil {
		fmt.Println("發送run-task失敗:", err)
		return
	}

	textSent := false

	// 處理訊息
	for {
		messageType, message, err := conn.ReadMessage()
		if err != nil {
			fmt.Println("讀取訊息失敗:", err)
			break
		}

		// 處理二進位訊息
		if messageType == websocket.BinaryMessage {
			fmt.Printf("收到二進位訊息,長度: %d\n", len(message))
			file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
			file.Write(message)
			file.Close()
			continue
		}

		// 處理簡訊
		messageStr := string(message)
		fmt.Printf("收到簡訊: %s\n", strings.ReplaceAll(messageStr, "\n", ""))

		// 簡單解析JSON擷取event類型
		var msgMap map[string]interface{}
		if json.Unmarshal(message, &msgMap) == nil {
			if header, ok := msgMap["header"].(map[string]interface{}); ok {
				if event, ok := header["event"].(string); ok {
					fmt.Printf("事件類型: %s\n", event)

					switch event {
					case "task-started":
						fmt.Println("=== 收到task-started事件 ===")

						if !textSent {
							// 發送continue-task事件

							texts := []string{"床前明月光,疑是地上霜。", "舉頭望明月,低頭思故鄉。"}

							for _, text := range texts {
								continueTaskCmd := map[string]interface{}{
									"header": map[string]interface{}{
										"action":    "continue-task",
										"task_id":   taskID,
										"streaming": "duplex",
									},
									"payload": map[string]interface{}{
										"input": map[string]interface{}{
											"text": text,
										},
									},
								}

								continueTaskJSON, _ := json.Marshal(continueTaskCmd)
								fmt.Printf("發送continue-task事件: %s\n", string(continueTaskJSON))

								err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON)
								if err != nil {
									fmt.Println("發送continue-task失敗:", err)
									return
								}
							}

							textSent = true

							// 延遲發送finish-task
							time.Sleep(500 * time.Millisecond)

							// 發送finish-task事件
							finishTaskCmd := map[string]interface{}{
								"header": map[string]interface{}{
									"action":    "finish-task",
									"task_id":   taskID,
									"streaming": "duplex",
								},
								"payload": map[string]interface{}{
									"input": map[string]interface{}{},
								},
							}

							finishTaskJSON, _ := json.Marshal(finishTaskCmd)
							fmt.Printf("發送finish-task事件: %s\n", string(finishTaskJSON))

							err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON)
							if err != nil {
								fmt.Println("發送finish-task失敗:", err)
								return
							}
						}

					case "task-finished":
						fmt.Println("=== 任務完成 ===")
						return

					case "task-failed":
						fmt.Println("=== 任務失敗 ===")
						if header["error_message"] != nil {
							fmt.Printf("錯誤資訊: %s\n", header["error_message"])
						}
						return

					case "result-generated":
						fmt.Println("收到result-generated事件")
					}
				}
			}
		}
	}
}

應用於生產環境

串連複用(WebSocket)

WebSocket 串連支援複用:一個合成任務結束後,無需重建立立串連即可開啟下一個任務。 複用流程
  • Qwen-Audio-TTS / CosyVoice:用戶端發送 finish-task,服務端返回 task-finished 後,可重新發送 run-task 開啟新任務。
  • Qwen-TTS:用戶端發送 session.finish,服務端返回 session.finished 後,可建立新會話開啟下一個任務。
取消任務後複用:對於 Qwen-Audio-TTS / CosyVoice,如果使用 cancel 指令取消當前任務,服務端返回 task-finished 後,同樣可以在當前串連上重新發送 run-task 開啟新任務。詳情請參見取消任務
  1. 必須等服務端返回結束事件(task-finishedsession.finished)後才可發起新任務。
  2. Qwen-Audio-TTS、CosyVoice 在複用串連中的不同任務需要使用不同的 task_id
  3. 任務失敗時服務端返回錯誤事件並關閉串連,該串連不可複用。
  4. 任務結束後 60 秒無新任務,串連自動斷開。
各模型事件說明請參見對應的API參考

模型限流

模型調用受限流規則約束,超出限制時服務端返回 Requests rate limit exceeded, please try again later. 報錯,需降低調用頻率或並發數後重試。 各模型的限流條件請參見限流

高並發最佳實務

DashScope SDK 內建池化機制,可複用 WebSocket 串連和合成對象,避免頻繁建立銷毀帶來的開銷。
  • Qwen-Audio-TTS/CosyVoice
Qwen-Audio-TTS 和 CosyVoice 使用相同的 SDK 介面,以下樣本同樣適用於 Qwen-Audio-TTS 系列模型,只需替換 modelvoice 參數。

前提條件

  • Python SDK
  • Java SDK
Python SDK 通過 SpeechSynthesizerObjectPool 管理和複用 SpeechSynthesizer 對象。對象池在初始化時即建立指定數量的 SpeechSynthesizer 執行個體並建立 WebSocket 串連,擷取對象時可直接發起請求,降低首包延遲。歸還後串連保持活躍,等待下次複用。

實現步驟

  1. 安裝依賴:安裝DashScope依賴(pip install -U dashscope
  2. 建立並設定物件池 對象池大小推薦設為峰值並發數的 1.5~2 倍,且不應超過賬戶的 QPS 限制。 建立全域單例對象池(初始化時建立串連,有一定耗時):
from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool

connectionPool = SpeechSynthesizerObjectPool(max_size=20)
import dashscope
# 以下為華北2(北京)地區的配置,調用時請將"{WorkspaceId}"替換為真實的業務空間ID,各地區的配置不同。
dashscope.base_http_api_url = "https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1"
  • 在對象池情境中,SpeechSynthesizerObjectPool在初始化時即按當前全域dashscope.api_key與服務端建立 WebSocket 串連。apiKey 僅在 WebSocket 建連握手時寫入Authorization要求標頭用於鑒權,後續任務訊息(如run-task)本身不攜帶 apiKey。池建立後修改dashscope.api_key不會影響池內已建串連——borrow_synthesizer取出的對象(包括歸還後再次複用的對象)仍使用握手時的 apiKey,新值會被靜默忽略,可能導致身份、配額或計費歸屬與預期不一致。注意:borrow_synthesizer也不支援通過參數指定 apiKey。
  • 如確需使用多個不同的 API Key,請為每個 API Key 維護獨立的SpeechSynthesizerObjectPool執行個體
  1. 從對象池中擷取SpeechSynthesizer對象 如果當前未歸還的對象數已超過池容量,系統會額外建立新對象。 此類對象需重建立立串連,不具備複用效果。
speech_synthesizer = connectionPool.borrow_synthesizer(
    model='cosyvoice-v3-flash',
    voice='longanyang',
    seed=12382,
    callback=synthesizer_callback
)
  1. 進行語音合成 調用SpeechSynthesizer對象的call或streaming_call方法進行語音合成。
  2. 歸還SpeechSynthesizer對象 任務結束後歸還對象以供複用。 不要歸還未完成任務或任務失敗的對象。
connectionPool.return_synthesizer(speech_synthesizer)
完整代碼
複製使用前請注意:SpeechSynthesizerObjectPool在初始化時即按當前全域dashscope.api_key與服務端建立 WebSocket 串連並完成鑒權;池建立後再修改dashscope.api_key不會影響池內已建串連,新值會被靜默忽略。多 API Key 情境請為每個 API Key 維護獨立的池執行個體。詳見上文重要說明。
# !/usr/bin/env python3
# Copyright (C) Alibaba Group. All Rights Reserved.
# MIT License (https://opensource.org/licenses/MIT)

import os
import time
import threading

import dashscope
from dashscope.audio.tts_v2 import *

USE_CONNECTION_POOL = True
text_to_synthesize = [
    '第一句、歡迎使用阿里巴巴語音合成服務。',
    '第二句、歡迎使用阿里巴巴語音合成服務。',
    '第三句、歡迎使用阿里巴巴語音合成服務。',
]
connectionPool = None

def init_dashscope_api_key():
    '''
    Set your DashScope API-key. More information:
    https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
    '''
    # 新加坡地區和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    if 'DASHSCOPE_API_KEY' in os.environ:
        dashscope.api_key = os.environ[
            'DASHSCOPE_API_KEY']  # load API-key from environment variable DASHSCOPE_API_KEY
    else:
        dashscope.api_key = '<your-dashscope-api-key>'  # set API-key manually

def synthesis_text_to_speech_and_play_by_streaming_mode(text, task_id):
    global USE_CONNECTION_POOL, connectionPool
    '''
    Synthesize speech with given text by streaming mode, async call and play the synthesized audio in real-time.
    for more information, please refer to https://www.alibabacloud.com/help/document_detail/2712523.html
    '''

    complete_event = threading.Event()

    # Define a callback to handle the result

    class Callback(ResultCallback):
        def on_open(self):
            # when using object pool, on_open will be called after task start
            self.file = open(f'result_{task_id}.mp3', 'wb')
            print(f'[task_{task_id}] start')

        def on_complete(self):
            print(f'[task_{task_id}] speech synthesis task complete successfully.')
            complete_event.set()

        def on_error(self, message: str):
            print(f'[task_{task_id}] speech synthesis task failed, {message}')

        def on_close(self):
            # when using object pool, on_close will be called after task finished
            print(f'[task_{task_id}] finished')

        def on_event(self, message):
            # print(f'recv speech synthsis message {message}')
            pass

        def on_data(self, data: bytes) -> None:
            # send to player
            # save audio to file
            self.file.write(data)

    # Call the speech synthesizer callback
    synthesizer_callback = Callback()

    # Initialize the speech synthesizer
    # you can customize the synthesis parameters, like voice, format, sample_rate or other parameters
    if USE_CONNECTION_POOL:
        speech_synthesizer = connectionPool.borrow_synthesizer(
            model='cosyvoice-v3-flash',
            voice='longanyang',
            seed=12382,
            callback=synthesizer_callback
        )
    else:
        speech_synthesizer = SpeechSynthesizer(model='cosyvoice-v3-flash',
                                               voice='longanyang',
                                               seed=12382,
                                               callback=synthesizer_callback)
    try:
        speech_synthesizer.call(text)
    except Exception as e:
        print(f'[task_{task_id}] speech synthesis task failed, {e}')
        if USE_CONNECTION_POOL:
            # close the synthesizer connection manually if task failed when using connection pool.
            speech_synthesizer.close()
        return

    print('[task_{}] Synthesized text: {}'.format(task_id, text))
    complete_event.wait()
    print('[task_{}][Metric] requestId: {}, first package delay ms: {}'.format(
        task_id,
        speech_synthesizer.get_last_request_id(),
        speech_synthesizer.get_first_package_delay()))
    if USE_CONNECTION_POOL:
        connectionPool.return_synthesizer(speech_synthesizer)

# main function
if __name__ == '__main__':
    # 必須先設定 dashscope.api_key 和 base_websocket_api_url,再建立 SpeechSynthesizerObjectPool。
    # 池在初始化時即按當前全域 dashscope.api_key 建立 WebSocket 串連,
    # 池建立後再修改 dashscope.api_key 不會影響池內已建串連。
    # 以下為新加坡地區的配置,調用時請將"{WorkspaceId}"替換為真實的業務空間ID,各地區的配置不同。
    dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'
    init_dashscope_api_key()

    if USE_CONNECTION_POOL:
        print('creating connection pool')
        start_time = time.time() * 1000
        connectionPool = SpeechSynthesizerObjectPool(max_size=3)
        end_time = time.time() * 1000
        print('connection pool created, cost: {} ms'.format(end_time - start_time))

    task_thread_list = []
    for task_id in range(3):
        thread = threading.Thread(
            target=synthesis_text_to_speech_and_play_by_streaming_mode,
            args=(text_to_synthesize[task_id], task_id))
        task_thread_list.append(thread)

    for task_thread in task_thread_list:
        task_thread.start()

    for task_thread in task_thread_list:
        task_thread.join()

    if USE_CONNECTION_POOL:
        connectionPool.shutdown()

資源管理與異常處理

  • 任務成功:當語音合成任務正常完成時,必須調用 connectionPool.return_synthesizer(speech_synthesizer)SpeechSynthesizer 對象歸還到池中,以便複用。
    不要歸還未完成任務或任務失敗的SpeechSynthesizer對象。
  • 任務失敗:當 SDK 內部或商務邏輯拋出異常導致任務中斷時,主動關閉底層的 WebSocket 串連:speech_synthesizer.close()
  • 在所有語音合成任務完成後,要通過如下方式關閉對象池:connectionPool.shutdown()
  • 在服務出現TaskFailed報錯時,不需要額外處理。

支援的模型與地區

  • 新加坡
  • 華北2(北京)
調用以下模型時,請選擇新加坡地區的API Key
  • Qwen-Audio-TTS:qwen-audio-3.0-tts-plus、qwen-audio-3.0-tts-flash
  • CosyVoice:cosyvoice-v3-plus、cosyvoice-v3-flash
  • Qwen-TTS
    • Qwen3-TTS-Instruct-Flash-Realtime:qwen3-tts-instruct-flash-realtime(穩定版,當前等同qwen3-tts-instruct-flash-realtime-2026-01-22)、qwen3-tts-instruct-flash-realtime-2026-01-22(最新快照版)
    • Qwen3-TTS-VD-Realtime:qwen3-tts-vd-realtime-2026-01-15(最新快照版)、qwen3-tts-vd-realtime-2025-12-16(快照版)
    • Qwen3-TTS-VC-Realtime:qwen3-tts-vc-realtime-2026-01-15(最新快照版)、qwen3-tts-vc-realtime-2025-11-27(快照版)
    • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime(穩定版,當前等同qwen3-tts-flash-realtime-2025-11-27)、qwen3-tts-flash-realtime-2025-11-27(最新快照版)、qwen3-tts-flash-realtime-2025-09-18(快照版)

支援的音色

不同模型支援的音色不同。將請求參數 voice 設為音色列表中 voice參數 列的值即可。

API參考

常見問題

Q:語音合成發音錯誤怎麼辦?多音字如何控制發音?

  • 將多音字替換為同音的其他漢字,快速解決發音問題。
  • 使用 SSML 標記語言控制發音。

Q:使用複刻音色產生的音頻無聲音如何排查?

  1. 確認音色狀態 調用CosyVoice聲音複刻/設計API介面,確認音色的 status 是否為 OK
  2. 檢查模型版本一致性 確保複刻音色時使用的 target_model 參數與語音合成時的 model 參數完全一致。例如:
    • 複刻時使用 cosyvoice-v3-plus
    • 合成時也必須使用 cosyvoice-v3-plus
  3. 驗證源音頻品質 檢查複刻音色時使用的源音頻是否符合CosyVoice聲音複刻/設計API
    • 音頻時間長度:10-20秒
    • 音質清晰
    • 無背景雜音
  4. 檢查請求參數 確認語音合成請求中的 voice 參數已設定為複刻音色的 ID。

Q:聲音複刻後合成效果不穩定或語音不完整怎麼辦?

如果複刻音色後合成的語音出現以下問題:
  • 語音播放不完整,唯讀出部分文字
  • 合成效果不穩定,時好時壞
  • 語音中包含異常停頓或靜音段
可能原因:源音頻品質不符合要求。 解決方案:請檢查源音頻是否符合錄音操作指南中的音頻要求,建議按照錄音指南重新錄製。

Q:為什麼語音合成的實際時間長度與 WAV 檔案顯示的時間長度不一致?

語音合成採用流式機制,邊合成邊返回資料,因此儲存的 WAV 檔案頭中的時間長度是預估值,存在一定誤差。如需精確時間長度,可將 format 設定為 pcm,待擷取完整合成結果後自行添加 WAV 檔案頭資訊。

Q:為什麼音頻無法播放?

請按以下情境逐一排查:
  1. 音頻儲存為完整檔案(如 xx.mp3)的情況
    1. 音頻格式一致性:請求參數中的音頻格式須與檔案尾碼一致(如參數為 wav 則檔案須為 .wav)。
    2. 播放器相容性:確認播放器支援該音訊格式和採樣率。
  2. 流式播放音訊情況
    1. 將音頻流儲存為完整檔案,嘗試用播放器播放。如果檔案無法播放,請參考情境 1 的排查方法。
    2. 如果檔案可正常播放,則問題在流式播放實現。請確認播放器支援流式播放(如 ffmpeg、pyaudio、AudioFormat、MediaSource 等)。

Q:為什麼音頻播放卡頓?

請按以下步驟逐一排查:
  1. 檢查文本發送速度:確保發送間隔合理,避免上段音頻播完後下段文本尚未到達。
  2. 檢查回呼函數效能:
    • 確認回呼函數中無阻塞性商務邏輯。
    • 回調運行在 WebSocket 線程,阻塞會影響資料接收。建議將音頻資料寫入獨立緩衝區,在其他線程中處理。
  3. 檢查網路穩定性:網路波動可能導致音頻傳輸中斷或延遲。

Q:語音合成耗時較長是什麼原因?

請按以下步驟排查:
  1. 檢查輸入間隔 如果是流式合成,確認文本發送間隔是否過長,過長會導致合成總時間長度增加。
  2. 分析效能指標
    • 首包延遲:正常約 500ms。
    • RTF(即時率 = 合成總耗時 / 音頻時間長度):正常應小於 1.0。

Q:合成的音頻中讀出了文本裡的特殊符號怎麼辦?

Qwen-TTS 系列模型可能將文本中的部分特殊符號(如 Markdown 加粗標記 **)合成為語音。可通過以下方式處理:
  1. 調用前對文本進行預先處理,去除特殊符號。
  2. 改用 CosyVoice 模型。

Q:如何限制 API Key 僅用於語音合成服務(許可權隔離)?

通過建立業務空間並僅授權特定模型,可限制 API Key 的使用範圍。請參見業務空間管理
Token Plan
模型體驗
用量統計與效能監控
資產中心
服務支援