Skip to main content
Text-to-speech

Real-time speech synthesis

Stream text-to-speech conversion over WebSocket with low first-packet latency

Stream text-to-speech conversion over WebSocket with low first-packet latency. Real-time speech synthesis supports streaming input and output, voice cloning, voice design, and fine-grained audio controls for voice assistants, audiobooks, and intelligent customer service.

Overview

Convert text to speech in real time through a bidirectional WebSocket streaming protocol with low latency.
  • Streaming input and output with low first-packet latency
  • Adjustable speech rate, pitch, volume, and bitrate for fine-grained audio control
  • Compatible with mainstream audio formats (PCM, WAV, MP3, Opus) with up to 48 kHz sample rate output
  • Supports instruction control, which lets you control speech expressiveness through natural language instructions
  • Supports voice cloning and Voice Design for custom voice creation
  • Supports emotion and rich language tags, which lets you embed emotion or sound effect tags within text
For batch scenarios such as audiobooks and courseware voiceover, use non-real-time speech synthesis (HTTP API). For model selection guidance, see Speech synthesis models.

Prerequisites

Quick start

The following examples demonstrate speech synthesis for each model. For more examples and parameter details, see the API reference.
  • Qwen-Audio-TTS
  • CosyVoice
  • Qwen-TTS
The following example synthesizes speech using a system voice.To use the instruction control feature, set instructions through the instruction parameter.
  • Python
  • Java
# coding=utf-8
import os
import dashscope
from dashscope.audio.tts_v2 import *
# Obtain an API key from the Qwen Cloud console.
# If the environment variable is not configured, replace the following line with your Qwen Cloud API key: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
dashscope.base_websocket_api_url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference'
# Model
# qwen-audio-3.0-tts-flash/qwen-audio-3.0-tts-plus: Use voices such as longanhuan_v3.6.
# Each voice supports different languages. To synthesize non-Chinese languages such as Japanese or Korean, select a voice that supports the target language. See the voice list for details.
model = "qwen-audio-3.0-tts-flash"
# Voice
voice = "longanhuan_v3.6"
# Instantiate SpeechSynthesizer and pass request parameters such as model and voice in the constructor
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# Send the text to be synthesized and get the binary audio
audio = synthesizer.call("How is the weather today?")
# The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time
print('[Metric] requestId: {}, first-packet latency: {} ms'.format(
  synthesizer.get_last_request_id(),
  synthesizer.get_first_package_delay()))
# Save the audio to a local file
with open('output.mp3', 'wb') as f:
  f.write(audio)

Advanced features

Qwen-TTS interaction modes

Qwen-TTS Realtime API provides two interaction modes:
  • server_commit mode: The server handles text segmentation and synthesis timing automatically. Suited for continuous synthesis of large text blocks. The client appends text without managing segmentation or submission.
  • commit mode: The client explicitly submits the text buffer to trigger synthesis. Suited for scenarios that require precise control over synthesis timing, such as per-turn synthesis in conversational AI.
Switch interaction modes:
  • WebSocket: Set the session.update event's mode field.
{
    "type": "session.update",
    "session": {
        "mode": "server_commit"
    }
}
  • Python SDK: Set the mode parameter in the update_session method.
qwen_tts_realtime.update_session(
    voice='Cherry',
    response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
    mode='server_commit'
)
  • Java SDK: Set the mode parameter through QwenTtsRealtimeConfig.builder().
QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
        .voice("Cherry")
        .responseFormat(ttsFormat)
        .mode("server_commit")
        .build();
qwenTtsRealtime.updateSession(config);
For complete SDK code examples, see Python SDK and Java SDK. For WebSocket event lifecycle and connection reuse details, see the WebSocket API reference.

Instruction control

Instruction control uses natural language descriptions to adjust speech tone, speed, emotion, and timbre characteristics without configuring complex audio parameters. Instruction specifications by model:
  • Qwen-Audio-TTS
  • CosyVoice
  • Qwen-TTS
Supported models: qwen-audio-3.0-tts-plus, qwen-audio-3.0-tts-flashSystem voices and voice cloning voices: accept any instruction.
Use cases:
  • Audiobook and radio drama voiceover
  • Advertising and promotional voiceover
  • Game character and animation voiceover
  • Emotionally expressive voice assistants
  • Documentary narration and news broadcasting
Tips for writing high-quality voice descriptions:
  • Core principles:
    1. Be specific, not vague: Use words that describe concrete vocal qualities, such as "deep," "crisp," or "slightly fast." Avoid subjective or vague terms like "nice" or "normal."
    2. Be multidimensional, not single-faceted: A good description covers multiple dimensions (gender, age, emotion, etc.). Writing only "female voice" is too broad to produce a distinctive timbre.
    3. Be objective, not subjective: Focus on the physical and perceptual qualities of the voice. For example, use "slightly high pitch with energy" rather than "my favorite voice."
    4. Be original, not imitative: Describe the vocal qualities you want, rather than requesting imitation of specific public figures (such as celebrities or actors). The model doesn't support imitation, and it may involve copyright risks.
    5. Be concise, not redundant: Make every word count. Avoid repeating synonyms or stacking meaningless modifiers.
  • Description dimensions: Combining the following dimensions produces more accurate results. The more dimensions described, the more precise the output.
    DimensionExample descriptions
    GenderMale, female, neutral
    AgeChild (5-12), teenager (13-18), young adult (19-35), middle-aged (36-55), elderly (55+)
    PitchHigh, mid, low, slightly high, slightly low
    SpeedFast, moderate, slow, slightly fast, slightly slow
    EmotionCheerful, calm, gentle, serious, lively, composed, soothing
    TimbreMagnetic, crisp, husky, mellow, sweet, rich, powerful
    Use caseNews broadcasting, advertising, audiobook, animation character, voice assistant, documentary narration
  • Examples:
    • Standard broadcasting style: Clear and precise articulation with standard pronunciation
    • Young, lively female voice with a slightly fast pace and a noticeable rising intonation, suitable for introducing fashion products
    • Calm middle-aged male voice with a slow pace, deep and magnetic timbre, suitable for reading news or narrating documentaries
    • Gentle, intellectual female voice, around 30 years old, with a calm tone, suitable for audiobook reading
    • Cute child voice, about 8-year-old girl, slightly childish speech, suitable for animation character voiceover

Dialects

This section describes how to produce speech in Chinese dialects (such as Henan dialect, Sichuan dialect, and Cantonese). Configuration methods vary by model and voice type. Dialect configuration by model:
  • Qwen-Audio-TTS
  • CosyVoice
  • Qwen-TTS
  • System voices: Select one of the following voice types:
    • A system voice with built-in dialect support, which outputs the corresponding dialect without additional configuration.
    • A voice that supports instruction control and can be configured to output a specific dialect through instruction text.
  • Voice cloning voices: Configure through the instruction control feature. For example, set the instruction text to 请用河南话表达.
Supported dialects: See the "Supported languages" column for each model in the speech synthesis models documentation.

Emotion and rich language tags

Qwen-Audio-TTS series models support embedding emotion and rich language tags directly in the text to synthesize (the text parameter). These tags control emotional expression or insert vocal effects (such as laughter and sighs) at specified positions, producing more expressive speech without configuring complex audio parameters.
Supported models: qwen-audio-3.0-tts-plus and qwen-audio-3.0-tts-flash only.Limitation: Only unidirectional streaming mode is supported.
Control tags Control tags set the emotion or style of the speech. Place a tag in the text to affect all subsequent text until the next control tag appears or the sentence is automatically segmented due to length.
TagDescription
[sad]Sad
[amazed]Amazed
[deep and loud shouting]Deep, loud shouting
[trembling]Trembling
[angry]Angry
[excited]Excited
[sarcastic]Sarcastic
[curious]Curious
[like dracula]Dracula style (deep, eerie)
[bored]Bored
[tired]Tired
[singing]Singing
[scornful]Scornful
[shouting]Shouting
[asmr]ASMR soft whisper
[panicked]Panicked
[mischievously]Mischievous
[empathetic]Empathetic
[whispers]Whisper
[reluctantly]Reluctant
[crying]Crying
[serious]Serious
[very slowly]Very slow speech
[very fast]Very fast speech
Rich language tags Rich language tags insert a vocal effect at the current position in the text without affecting the emotional style of surrounding text.
TagDescription
[gasp]Gasp
[sighing]Sigh
[clears throat]Throat clearing
[giggles]Giggle
[laughing]Laughter
[cough]Cough
[snorts]Snort
Usage examples The following example shows how to combine control tags and rich language tags in the text parameter: [excited]What a beautiful day today![laughing]Let's go out and have fun together! In this text, [excited] is a control tag that applies an excited emotion to all subsequent text. [laughing] is a rich language tag that inserts a laugh at that position before continuing to synthesize the remaining text. You can also switch between different emotions within the same text: [serious]Please pay attention to the safety precautions.[excited]Alright, let's get started now! Here, [serious] sets the first sentence to a serious tone, and [excited] switches to an excited tone starting from the second sentence.

Raw WebSocket protocol

The following examples demonstrate how to connect directly to the server through the WebSocket raw protocol, suited for scenarios without the DashScope SDK. These are minimal runnable implementations. For WebSocket protocol details, see the API reference for each model.
  • Qwen-Audio-TTS/CosyVoice
  • Qwen-TTS
Qwen-Audio-TTS and CosyVoice use the same WebSocket protocol. The following examples use qwen-audio-3.0-tts-flash. To use CosyVoice, replace the model parameter with a CosyVoice model (such as cosyvoice-v3-flash) and the voice parameter with the desired 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 (
	wsURL      = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/"
	outputFile = "output.mp3"
)
func main() {
	// Obtain an API key from the Qwen Cloud console.
	// If the environment variable is not configured, replace the following line with your Qwen Cloud API key: apiKey := "sk-xxx"
	apiKey := os.Getenv("DASHSCOPE_API_KEY")
	// Clear the output file
	os.Remove(outputFile)
	os.Create(outputFile)
	// Connect to 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("Connection failed, HTTP status code: %d\n", resp.StatusCode)
		}
		fmt.Println("Connection failed:", err)
		return
	}
	defer conn.Close()
	// Generate task ID
	taskID := uuid.New().String()
	fmt.Printf("Generated task ID: %s\n", taskID)
	// Send run-task event
	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,
				// If enable_ssml is set to true, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur.
				"enable_ssml": false,
			},
			"input": map[string]interface{}{},
		},
	}
	runTaskJSON, _ := json.Marshal(runTaskCmd)
	fmt.Printf("Sending run-task event: %s\n", string(runTaskJSON))
	err = conn.WriteMessage(websocket.TextMessage, runTaskJSON)
	if err != nil {
		fmt.Println("Failed to send run-task:", err)
		return
	}
	textSent := false
	// Process messages
	for {
		messageType, message, err := conn.ReadMessage()
		if err != nil {
			fmt.Println("Failed to read message:", err)
			break
		}
		// Process binary messages
		if messageType == websocket.BinaryMessage {
			fmt.Printf("Received binary message, length: %d\n", len(message))
			file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
			file.Write(message)
			file.Close()
			continue
		}
		// Process text messages
		messageStr := string(message)
		fmt.Printf("Received text message: %s\n", strings.ReplaceAll(messageStr, "\n", ""))
		// Simple JSON parsing to get event type
		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("Event type: %s\n", event)
					switch event {
					case "task-started":
						fmt.Println("=== Received task-started event ===")
						if !textSent {
							// Send continue-task events
							texts := []string{"Before my bed, moonlight shines bright, I suspect it's frost upon the ground.", "I raise my eyes to gaze at the bright moon, then bow my head, thinking of home."}
							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("Sending continue-task event: %s\n", string(continueTaskJSON))
								err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON)
								if err != nil {
									fmt.Println("Failed to send continue-task:", err)
									return
								}
							}
							textSent = true
							// Delay before sending finish-task
							time.Sleep(500 * time.Millisecond)
							// Send finish-task event
							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("Sending finish-task event: %s\n", string(finishTaskJSON))
							err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON)
							if err != nil {
								fmt.Println("Failed to send finish-task:", err)
								return
							}
						}
					case "task-finished":
						fmt.Println("=== Task completed ===")
						return
					case "task-failed":
						fmt.Println("=== Task failed ===")
						if header["error_message"] != nil {
							fmt.Printf("Error message: %s\n", header["error_message"])
						}
						return
					case "result-generated":
						fmt.Println("Received result-generated event")
					}
				}
			}
		}
	}
}

Voice customization

  • CosyVoice
  • Qwen-TTS-Realtime

Voice cloning: input audio format requirements

High-quality input audio is the foundation for excellent cloning results.
ItemRequirement
Supported formatsWAV (16-bit), MP3, M4A
Audio durationRecommended: 10-20 seconds. Maximum: 60 seconds.
File size≤ 10 MB
Sample rate≥ 16 kHz
ChannelsMono or stereo. For stereo audio, only the first channel is processed, so make sure it contains clear speech.
ContentThe audio must contain at least 5 seconds of continuous, clear speech with no background sound. The rest may contain only brief pauses (≤ 2 seconds). The entire clip should be free of background music, noise, or other voices to ensure high-quality core speech content. Use audio of normal speech as the input; do not upload songs or singing, to ensure accurate and usable cloning results.

Voice design: writing high-quality voice descriptions

Constraints

When writing a voice description (voice_prompt), follow these technical constraints:
  • Length limit: The voice_prompt content must not exceed 500 characters.
  • Supported languages: The description text supports Chinese and English only.

Core principles

The voice_prompt guides the model to generate a voice with specific characteristics.When writing a voice description, follow these core principles:
  • Be specific, not vague: Use words that describe concrete vocal qualities, such as "deep," "crisp," or "slightly fast." Avoid subjective, low-information terms like "nice" or "normal."
  • Be multidimensional, not single-faceted: A good description usually combines multiple dimensions (such as gender, age, and emotion). A single-dimension description (such as just "female voice") is too broad to produce a distinctive result.
  • Be objective, not subjective: Focus on the physical and perceptual qualities of the voice itself, rather than personal preferences. For example, use "slightly high pitch with energy" instead of "my favorite voice."
  • Be original, not imitative: Describe the qualities of the voice, rather than requesting imitation of specific people (such as celebrities or actors). Such requests involve copyright risks, and the model doesn't support direct imitation.
  • Be concise, not redundant: Make sure every word is meaningful. Avoid repeating synonyms or meaningless intensifiers (such as "a very, very nice voice").

Description dimension reference

DimensionExamples
GenderMale, female, neutral
AgeChild (5-12), teenager (13-18), young adult (19-35), middle-aged (36-55), elderly (55+)
PitchHigh, mid, low, slightly high, slightly low
SpeedFast, moderate, slow, slightly fast, slightly slow
EmotionCheerful, composed, gentle, serious, lively, cool, soothing
TimbreMagnetic, crisp, husky, mellow, sweet, rich, powerful
Use caseNews broadcasting, advertising voiceover, audiobook, animation character, voice assistant, documentary narration

Example comparison

Good examples:
  • "A young, lively female voice with a fast pace and a noticeable rising intonation, suitable for introducing fashion products."
    • Analysis: This description combines age, personality, speed, and intonation, and specifies a use case, forming a clear voice profile.
  • "A composed middle-aged male voice with a slightly slow pace, deep and magnetic, suitable for news broadcasting or documentary narration."
    • Analysis: This description clearly defines gender, age range, speed, timbre, and use case.
  • "A cute child voice, about an 8-year-old girl, speaking with a slightly childish tone, suitable for animation character voiceover."
    • Analysis: This description precisely targets age and a vocal quality (childishness), with a clear use case.
  • "A gentle, intellectual female, around 30 years old, with a calm tone, suitable for audiobook reading."
    • Analysis: This description effectively conveys the emotion and style of the voice through words like "intellectual" and "calm."
Bad examples and suggested improvements:
Bad exampleMain problemSuggested improvement
"A nice voice"The description is too vague and subjective, lacking actionable detail.Add concrete dimensions, such as "a young female voice with a clear timbre and a soft intonation."
"A voice like a certain celebrity"Involves copyright risks, and the model doesn't support direct imitation.Extract and describe vocal characteristics, such as "a mature, magnetic male voice with a composed pace."
"A very, very, very nice female voice"The description is redundant; repeated words don't help define the voice.Remove repetition and add effective details, such as "a female voice aged 20-24 with a light timbre, lively intonation, and sweet quality."
123456Invalid input that can't be parsed into vocal characteristics.Provide a meaningful text description; see the recommended examples above.

Connection reuse (WebSocket)

WebSocket connections support reuse: after a synthesis task completes, the next task can start on the same connection without reconnecting. Reuse flow:
  • Qwen-Audio-TTS/CosyVoice: The client sends finish-task. After the server returns a task-finished event, the client can send a run-task event to start a new task.
  • Qwen-TTS: The client sends session.finish. After the server returns session.finished, the client can establish a new session for the next task.
  • Wait for the server to return the completion event (task-finished or session.finished) before starting a new task.
  • CosyVoice and Qwen-Audio-TTS require a different task_id for each task on a reused connection.
  • If a task fails, the server returns an error event and closes the connection. The connection cannot be reused.
  • If no new task starts within 60 seconds, the connection automatically disconnects.
For event details, see the corresponding API reference.

High-concurrency best practices

The DashScope SDK has built-in pooling that reuses WebSocket connections and synthesizer objects, eliminating the overhead of repeatedly creating and destroying them.
Qwen-Audio-TTS and CosyVoice use the same SDK interface. The following examples also apply to Qwen-Audio-TTS models -- simply replace the model and voice parameters.
Prerequisites:
  • Python SDK
  • Java SDK
The Python SDK uses SpeechSynthesizerObjectPool to manage and reuse SpeechSynthesizer objects.The pool creates a specified number of SpeechSynthesizer instances and establishes WebSocket connections at initialization. When you borrow an object, it's ready to send requests immediately, reducing first-packet latency. After the object is returned, the connection stays active for the next task.

Implementation steps

  1. Install dependencies: Install the DashScope dependency (pip install -U dashscope).
  2. Create and configure the object pool. Set the pool size to 1.5x-2x the peak concurrency, and don't exceed your account's QPS limit. Create a global singleton pool (connection establishment during initialization takes some time):
from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool
synthesizer_object_pool = SpeechSynthesizerObjectPool(max_size=20)
import dashscope
dashscope.base_http_api_url = "https://dashscope-intl.aliyuncs.com/api/v1"
  • In the object pool scenario, SpeechSynthesizerObjectPool establishes WebSocket connections with the server using the current global dashscope.api_key at initialization. The API key is written to the Authorization header only during the WebSocket handshake for authentication. Subsequent task messages (such as run-task) don't carry the API key. Modifying dashscope.api_key after pool creation doesn't affect existing connections -- objects borrowed via borrow_synthesizer (including those returned and re-borrowed) still use the API key from the original handshake. The new value is silently ignored, which may cause identity, quota, or billing attribution to differ from expectations. Note: borrow_synthesizer doesn't support specifying an API key as a parameter.
  • To use multiple API keys, maintain a separate SpeechSynthesizerObjectPool instance for each key.
  1. Borrow a SpeechSynthesizer object from the pool. If the number of unreturned objects exceeds the pool capacity, the system creates additional objects. These additional objects must establish new connections and don't benefit from pooling.
speech_synthesizer = connectionPool.borrow_synthesizer(
  model='cosyvoice-v3-flash',
  voice='longanyang',
  seed=12382,
  callback=synthesizer_callback
)
  1. Perform speech synthesis. Call the SpeechSynthesizer object's call or streaming_call method to synthesize speech.
  2. Return the SpeechSynthesizer object. Return the object after the task completes to make it available for reuse. Don't return objects with incomplete or failed tasks.
connectionPool.return_synthesizer(speech_synthesizer)
Before copying: SpeechSynthesizerObjectPool establishes WebSocket connections and authenticates using the current global dashscope.api_key at initialization. Modifying dashscope.api_key after pool creation doesn't affect existing connections; the new value is silently ignored. For multi-key scenarios, maintain a separate pool instance per API key. See the important note above for details.
# !/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 = [
  'Sentence 1: Welcome to Alibaba speech synthesis service.',
  'Sentence 2: Welcome to Alibaba speech synthesis service.',
  'Sentence 3: Welcome to Alibaba speech synthesis service.',
]
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
  '''
  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://help.aliyun.com/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 synthesis 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__':
  # You must set dashscope.api_key and base_websocket_api_url before creating SpeechSynthesizerObjectPool.
  # The pool establishes WebSocket connections using the current global dashscope.api_key at initialization time.
  # Modifying dashscope.api_key after pool creation will not affect existing connections in the pool.
  dashscope.base_websocket_api_url='wss://dashscope-intl.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()

Resource management and error handling

  • Task succeeded: After a synthesis task completes normally, call connectionPool.return_synthesizer(speech_synthesizer) to return the SpeechSynthesizer object to the pool for reuse.
    Don't return SpeechSynthesizer objects with incomplete or failed tasks.
  • Task failed: If an SDK internal error or business logic exception causes the task to abort, close the underlying WebSocket connection: speech_synthesizer.close().
  • After all synthesis tasks are complete, shut down the pool: connectionPool.shutdown().
  • When a TaskFailed error occurs on the server side, no additional handling is required.

Supported scope

To call the following models, use your Qwen Cloud API key:
  • CosyVoice: cosyvoice-v3-plus, cosyvoice-v3-flash
  • Qwen-TTS:
    • Qwen3-TTS-Instruct-Flash-Realtime: qwen3-tts-instruct-flash-realtime (stable, currently equivalent to qwen3-tts-instruct-flash-realtime-2026-01-22), qwen3-tts-instruct-flash-realtime-2026-01-22 (latest snapshot)
    • Qwen3-TTS-VD-Realtime: qwen3-tts-vd-realtime-2026-01-15 (latest snapshot), qwen3-tts-vd-realtime-2025-12-16 (snapshot)
    • Qwen3-TTS-VC-Realtime: qwen3-tts-vc-realtime-2026-01-15 (latest snapshot), qwen3-tts-vc-realtime-2025-11-27 (snapshot)
    • Qwen3-TTS-Flash-Realtime: qwen3-tts-flash-realtime (stable, currently equivalent to qwen3-tts-flash-realtime-2025-11-27), qwen3-tts-flash-realtime-2025-11-27 (latest snapshot), qwen3-tts-flash-realtime-2025-09-18 (snapshot)

Supported voices

Different models support different voices. Set the voice request parameter to the value in the voice parameter column of the voice list.

API reference

FAQ

Q: How do I fix incorrect pronunciation in speech synthesis? How do I control the pronunciation of polyphonic characters?

  • Replace the polyphonic character with a homophone to quickly fix the pronunciation issue.
  • Use SSML markup language to control pronunciation.

Q: How do I troubleshoot silent audio when using a cloned voice?

  1. Verify the voice status Call the voice cloning/design API and confirm that the voice status is OK.
  2. Check model version consistency Make sure the target_model parameter used during voice cloning matches the model parameter used during speech synthesis. For example:
    • If you used cosyvoice-v3-plus for cloning
    • You must also use cosyvoice-v3-plus for synthesis
  3. Verify source audio quality Check whether the source audio used for voice cloning meets the audio requirements and best practices:
    • Audio duration: 10-20 seconds
    • Clear audio quality
    • No background noise
  4. Check request parameters Confirm that the voice parameter in your speech synthesis request is set to the cloned voice ID.

Q: What should I do if the cloned voice produces unstable or incomplete speech?

If the synthesized speech from a cloned voice exhibits any of the following issues:
  • Incomplete playback that only reads part of the text
  • Inconsistent synthesis quality
  • Abnormal pauses or silent segments in the speech
Possible cause: The source audio quality doesn't meet the requirements. Solution: Check whether the source audio meets the Recording guide for voice cloning requirements. We recommend re-recording based on the recording guidelines.

Q: Why does the actual duration differ from the duration displayed in the WAV file header?

Speech synthesis uses a streaming mechanism that returns data progressively as it's generated. The duration in the saved WAV file header is an estimate and may contain inaccuracies. For precise duration, set format to pcm, wait for the complete synthesis result, and then add the WAV file header yourself.

Q: Why won't the audio play?

Troubleshoot based on the following scenarios:
  1. Audio saved as a complete file (such as xx.mp3)
    1. Audio format consistency: The audio format in the request parameters must match the file extension (for example, if the format is set to wav, the file must be saved as .wav).
    2. Player compatibility: Confirm that your player supports the audio format and sample rate.
  2. Streaming audio playback
    1. Save the audio stream as a complete file and try playing it with a media player. If the file won't play, refer to the troubleshooting steps in Scenario 1.
    2. If the file plays correctly, the issue is in the streaming playback implementation. Confirm that your player supports streaming playback (such as ffmpeg, pyaudio, AudioFormat, or MediaSource).

Q: Why is audio playback stuttering?

Troubleshoot with the following steps:
  1. Check text send rate: Make sure the interval between text segments is short enough that the next segment arrives before the previous audio finishes playing.
  2. Check callback function performance:
    • Confirm that the callback function has no blocking business logic.
    • The callback runs on the WebSocket thread. Blocking it delays data reception. Write audio data to a separate buffer and process it in a separate thread.
  3. Check network stability: Network fluctuations can cause audio transmission interruptions or delays.

Q: Why is speech synthesis taking a long time?

Troubleshoot with the following steps:
  1. Check input interval For streaming synthesis, confirm that the interval between text segments isn't too long. Long intervals increase total synthesis time.
  2. Analyze performance metrics
    • First-packet latency: typically around 500 ms.
    • RTF (Real-Time Factor = total synthesis time / audio duration): should be less than 1.0 under normal conditions.

Q: How do I restrict an API key to speech synthesis only (permission isolation)?

Create a new workspace and authorize only specific models to limit the scope of an API key. For details, see Manage workspaces.