Qwen-Audio-TTS Python SDK

Updated at:

Use the DashScope Python SDK to integrate Qwen-Audio-TTS real-time speech synthesis into your application through non-streaming, one-way streaming, or bidirectional streaming modes.

Service endpoint

The SDK service endpoint is determined by the workspace/region parameters of the Client constructor (corresponding to the addresses below, which include the WorkspaceId). To switch to a different region, change the Client constructor parameters to the values of the target region.

China (Beijing)

wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference

Replace {WorkspaceId} with your actual workspace ID.

Singapore

wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference

Replace {WorkspaceId} with your actual workspace ID.

ImportantAlibaba Cloud Model Studio has released workspace-specific domains for the China (Beijing) and Singapore regions. The new dedicated domains deliver superior performance and higher stability for inference requests. We recommend migrating to the new domains:

  • China (Beijing): from dashscope.aliyuncs.com to {WorkspaceId}.cn-beijing.maas.aliyuncs.com
  • Singapore: from dashscope-intl.aliyuncs.com to {WorkspaceId}.ap-southeast-1.maas.aliyuncs.com

Core resource (client.audio.tts)

Resource path: client.audio.tts

Function: WebSocket-based speech synthesis that offers three invocation modes: one-shot synthesis (synthesize), streaming synthesis (stream), and bidirectional streaming sessions (connect).

Invocation entry points

# One-shot synthesis: returns the complete audio as bytes
audio = client.audio.tts.synthesize(
    text,                      # text is required (positional)
    model="qwen-audio-3.0-tts-flash", # model the voice belongs to
    voice="longanhuan_v3.6",   # voice
    format="mp3",              # audio encoding format, default "mp3"
    sample_rate=22050,         # sample rate, default 22050
)

# Streaming synthesis: yields audio bytes chunk by chunk (write or play as you receive)
for chunk in client.audio.tts.stream(text, model=..., voice=...):
    ...

# Bidirectional streaming session: submit text in segments; the connection is established automatically when entering the with context
with client.audio.tts.connect(model=..., voice=...) as session:
    session.send_text(text)    # can be called multiple times to append text
    session.stop()             # notifies the server that the input is complete
    for chunk in session.audio_chunks():
        ...

synthesize() - non-streaming

Method signature:

def synthesize(self, text: str, *, model: str,
               voice: str, format: str = "mp3",
               sample_rate: int = 22050, volume: int = None,
               rate: float = None, pitch: float = None,
               language_hints: list = None, enable_ssml: bool = None,
               parameters: Dict = None, ...) -> bytes

Parameters:

ParameterTypeRequiredDescription
textstrYesThe full text to synthesize. Maximum length: 20,000 characters.

Return value: bytes containing the complete audio data.

Description: This blocking call waits for the synthesis to complete and returns the full audio data at once. It is best suited for short text where real-time streaming is not required. Each synthesize or stream call creates its own session; no instance reuse is needed.

stream() - streaming

Method signature:

def stream(self, text: str, *, model: str, voice: str,
           format: str = "mp3", sample_rate: int = 22050, ...) -> Iterator[bytes]

Parameters:

ParameterTypeRequiredDescription
textstrYesThe full text to synthesize. Maximum length: 20,000 characters.

Return value: Iterator[bytes] containing the audio data yielded chunk by chunk.

Description: Streaming synthesis sends the complete text at once and yields audio chunk by chunk (Iterator[bytes]). It is best suited for short-text speech synthesis that requires low latency. To submit text in segments, use a bidirectional streaming session (connect).

send_text() - streaming input (session method)

Submit text in segments within a bidirectional streaming session (client.audio.tts.connect(...)).

Method signature:

def send_text(self, text: str) -> None

Parameters: text is the current text segment to synthesize. Call send_text multiple times to append text. Maximum per call: 20,000 characters. Cumulative maximum: 200,000 characters. Sending text and receiving audio must proceed concurrently (use this together with audio_chunks()/events() and stop).

stop() - end streaming input (session method)

Method signature:

def stop(self) -> None

Description: Notifies the server that all text has been sent. This call does not block the current thread; the receiving thread must keep consuming audio_chunks()/events() until the remaining text is synthesized and all audio data is returned. Failing to call this method may result in trailing text not being converted to speech.

cancel() - cancel streaming synthesis (session method)

Method signature:

def cancel(self) -> None

Description: Cancels the current streaming speech synthesis turn. After calling this method, the SDK immediately ends the current task. After cancellation, the session can no longer send text; create a new session to continue synthesis.

Session metrics and responses (session attributes)

session.first_package_delay
session.usage
session.last_response
  • first_package_delay: float, the delay in milliseconds from sending text to receiving the first audio chunk. Read it after synthesis completes.
  • usage: dict, the usage information of the most recent synthesis task.
  • last_response: dict, the response message of the most recent synthesis task (containing header/payload keys). The request ID of the most recent request can be retrieved from the header of session.last_response, which is useful for troubleshooting and log correlation.

Request parameters

The following parameters are set through the synthesize/stream/connect methods of client.audio.tts to control the model, voice, format, and audio characteristics of the synthesis (model, voice, format, sample_rate, volume, rate, pitch, language_hints, and enable_ssml are named parameters; all other parameters are passed through the parameters dictionary).

ParameterTypeRequiredDescription
modelstrYesThe model name.
voicestrYesThe voice used for speech synthesis.
  • System voices: See Qwen-Audio-TTS voice list
  • Cloned voices: Custom voices created through voice cloning
  • Custom voices: Custom voices created through voice design
formatstrNoAudio encoding format.

Default: mp3.

Supports mp3, wav, pcm, and other formats; the sample rate is set through the sample_rate parameter, defaulting to 22050.

volumeintNoThe volume level.

Default value: 50.

Valid values: [0, 100].

ratefloatNoThe speech rate.

Default value: 1.0.

Valid values: [0.5, 2.0].

pitchfloatNoThe pitch.

Default value: 1.0.

Valid values: [0.5, 2.0].

bit_rateintNoThe audio bit rate in kbps. When the audio format is mp3 or opus, use bit_rate to adjust the bit rate.

Default value: 32.

Valid values: [6, 510].

Set bit_rate through the parameters dictionary:

audio = client.audio.tts.synthesize(
    "Text to synthesize",
    model="qwen-audio-3.0-tts-flash",
    voice="longanhuan_v3.6",
    parameters={"bit_rate": 128},
)
word_timestamp_enabledboolNoSpecifies whether to enable word-level timestamps.

Default value: false.

Available only in streaming output mode. Cloned voices are supported. For supported system voices, see Qwen-Audio-TTS voice list.

Set word_timestamp_enabled through the parameters dictionary:

audio = client.audio.tts.stream(
    "Text to synthesize",
    model="qwen-audio-3.0-tts-flash",
    voice="your_voice",  # A system or cloned voice that supports word-level timestamps
    parameters={"word_timestamp_enabled": True},
)
seedintNoA random seed for controlling variation in the synthesis output. When the model version, text, voice, and other parameters are unchanged, using the same seed produces identical results.

Default value: 0.

Valid values: [0, 65535].

language_hintslist[str]No

Important

  • This parameter is an array, but the current version only processes the first element. Pass a single value.
  • This parameter specifies the target language for speech synthesis. It's unrelated to the language of the audio sample used in voice cloning. To set the source language for a cloning task, see the voice cloning API reference.

Specifies the target language for speech synthesis to improve output quality.

When digit pronunciation, abbreviation expansion, symbol reading, or minority-language synthesis doesn't meet expectations, use this parameter. For example:

  • Unexpected digit pronunciation: "hello, this is 110" is read as "hello, this is one one zero" instead of the expected Chinese pronunciation
  • Inaccurate symbol pronunciation: "@" is read as the Chinese equivalent instead of "at"
  • Poor minor language synthesis quality with unnatural results

Valid values

  • zh: Chinese

  • en: English

  • fr: French

  • de: German

  • ja: Japanese

  • ko: Korean

  • ru: Russian

  • pt: Portuguese

  • th: Thai

  • id: Indonesian

  • vi: Vietnamese

  • es: Spanish

  • it: Italian

  • ms: Malaysian

  • fil: Filipino

  • ar: Arabic

  • other: Other languages (supported by qwen-audio-3.1-tts-flash)

    Bulgarian, Dutch, Hungarian, Romanian, Danish, Croatian, Greek, Czech, Norwegian, Swedish, Hindi, Slovak, Finnish, Urdu, Serbian, Mexican Spanish, Lithuanian, Latvian, Turkish, Persian, Hebrew, Mongolian, Slovenian, Azerbaijani, Polish, Catalan, Khmer, Brazilian Portuguese, Lao, Georgian, Afrikaans, Nepali, Belarusian, Malayalam, Luxembourgish, Albanian, Tamil, Sinhala, Telugu, Estonian, Cebuano, Bosnian, Burmese, Armenian, Latin, Icelandic, Maltese, Swahili, Kazakh, Seychellois Creole, Ukrainian.

instructionstrNoSets an instruction to control synthesis effects such as dialect, emotion, or role.

For usage details, see Instruction control.

enable_aigc_tagboolNoSpecifies whether to embed an AIGC watermark in the generated audio. When set to true, the watermark is embedded in audio files of supported formats (wav/mp3/opus).

Default value: false.

Set enable_aigc_tag, aigc_propagator, and aigc_propagate_id through the parameters dictionary:

audio = client.audio.tts.synthesize(
    "Text to synthesize",
    model="qwen-audio-3.0-tts-flash",
    voice="longanhuan_v3.6",
    parameters={
        "enable_aigc_tag": True,
        "aigc_propagator": "your_propagator",
        "aigc_propagate_id": "your_propagate_id"
    },
)
aigc_propagatorstrNoSets the ContentPropagator field in the AIGC watermark, identifying the content propagator. Takes effect only when enable_aigc_tag is true.

Default value: Alibaba Cloud UID.

Set through the parameters dictionary. See the enable_aigc_tag example.

aigc_propagate_idstrNoSets the PropagateID field in the AIGC watermark, uniquely identifying a specific propagation action. Takes effect only when enable_aigc_tag is true.

Default value: The request ID of the current speech synthesis request.

Set through the parameters dictionary. See the enable_aigc_tag example.

hot_fixdictNoConfigures pronunciation corrections and text replacements applied before synthesis.

Parameters:

  • pronunciation: Custom pronunciation. Specifies pinyin annotations for words to correct inaccurate default pronunciations.
  • replace: Text replacement. Replaces specified words with target text before synthesis. The replaced text is used as the actual synthesis input.

Example:

audio = client.audio.tts.synthesize(
    "Text to synthesize",
    model="qwen-audio-3.0-tts-flash",
    voice="longanhuan_v3.6", # Voice
    parameters={
        "hot_fix": {
            "pronunciation": [{"weather": "tian1 qi4"}],
            "replace": [{"today": "gold day"}]
        }
    },
)

Sessions and event iteration

Streaming synthesis no longer uses callback interfaces: audio and server events are received through the session's iteration methods. stream()/synthesize() require no session management. The lifecycle of a bidirectional streaming session (client.audio.tts.connect(...)) is: entering the with context opens the connection automatically → multiple send_text() calls → stop() → read all remaining results → exiting the context closes the connection automatically.

audio_chunks() - iterate audio data

Method signature:

def audio_chunks(self) -> Iterator[bytes]

Return value: Iterator[bytes] containing the audio binary data chunks yielded one by one, in the format specified by the format parameter.

Description: Iterate in the receiving thread to receive audio data chunk by chunk; multiple chunks are produced during synthesis. Write the data to a file or feed it to a playback device. Only one events() or audio_chunks() reader is allowed per session.

events() - iterate server events

Method signature:

def events(self) -> Generator[Union[Dict, bytes], None, None]

Return value: Yields both server response events (dict, containing header (request information) and payload (output information)) and audio bytes. payload.output contains the event type, original text, and other details. See output field in events() events.

Description: Iterate server replies in the receiving thread. Contains the output information of synthesis events (event type, original text, sentence information); access payload.output directly for details.

Error handling

When an error occurs during synthesis, the WebSocket layer raises a DashScopeError exception, and the connection closes automatically after the exception is raised. Use try/except at the sending or receiving side to catch and log errors for troubleshooting:

from dashscope.v2 import DashScopeError

try:
    ...
except DashScopeError as e:
    print(e.code, e.message, e.request_id)

Session lifecycle

  • Entering the with context opens the connection automatically (initialize audio output streams or open file resources beforehand).
  • After all text has been synthesized and all audio data has been produced, the events()/audio_chunks() iteration ends; performance metrics such as first_package_delay can then be read.
  • Exiting the with context closes the connection automatically (release resources such as audio playback devices afterwards).

output field in events() events

The event dict yielded by the session's events() iteration contains a payload.output field with synthesis event information. Use this field to track synthesis progress and retrieve per-sentence details. The output field structure is as follows:

FieldTypeDescription
typestrEvent type. Values: sentence-begin (sentence synthesis started), sentence-synthesis (sentence synthesis in progress), or sentence-end (sentence synthesis finished).
original_textstrThe original text of the current sentence. Returned in sentence-begin and sentence-end events.
sentencedictSentence information. Contains index (sentence sequence number) and words (word list with timestamp information when word_timestamp_enabled is on).

Message example:

{
  "header": {
    "task_id": "xxx",
    "event": "result-generated",
    "attributes": {}
  },
  "payload": {
    "output": {
      "type": "sentence-begin",
      "original_text": "How is the weather today?",
      "sentence": {
        "index": 0,
        "words": []
      }
    }
  }
}

Parsing example:

def handle_event(event):
    output = (event.get('payload') or {}).get('output') or {}
    event_type = output.get('type', '')
    original_text = output.get('original_text', '')
    if event_type:
        print(f'Event type: {event_type}, original text: {original_text}')

Code examples

The SDK provides the key interfaces for speech synthesis and supports the following invocation modes:

  • Non-streaming: A blocking call that sends the complete text at once and returns the full audio directly. Best suited for short-text speech synthesis.
  • Unidirectional streaming: A non-blocking call that sends the complete text at once and receives audio data by iterating over the chunks returned by stream. Best suited for short-text speech synthesis that requires low latency.
  • Bidirectional streaming: A non-blocking call that sends text in multiple segments and receives the incrementally synthesized audio stream in real time by iterating over the session's audio_chunks()/events(). Best suited for long-text speech synthesis that requires low latency.

For more examples, see GitHub.

Non-streaming

image

The text sent in a single call must not exceed 20,000 characters. Exceeding this limit causes an error.

ImportantEach synthesize or stream call creates its own session; no instance reuse is needed.

# coding=utf-8

import os

import dashscope

# The API Keys for the Singapore and Beijing regions are different. Get API Key: https://help.aliyun.com/zh/model-studio/get-api-key
# If the environment variable is not configured, replace the following line with your Model Studio API Key: api_key="sk-xxx"
# The following configuration is for the China (Beijing) region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
client = dashscope.Client(
    api_key=os.environ.get('DASHSCOPE_API_KEY'),
    workspace="{WorkspaceId}",
    region="cn-beijing",
)

# Model
model = "qwen-audio-3.0-tts-flash"
# Voice
voice = "longanhuan_v3.6"

# Send the text for synthesis and get the binary audio
audio = client.audio.tts.synthesize("How is the weather today?", model=model, voice=voice)

# Save the audio to a local file
with open('output.mp3', 'wb') as f:
    f.write(audio)

dashscope CLI

export DASHSCOPE_API_KEY="your-api-key"
# Replace {WorkspaceId} with your Workspace ID, and cn-beijing with the corresponding region (Singapore: ap-southeast-1, US East: us-east-1)
export DASHSCOPE_HTTP_BASE_URL="https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1"
dashscope speech-synthesis create -m qwen-audio-3.0-tts-flash -t "Hello world" --voice longanhuan_v3.6

ImportantThe SDK Expert interactive assistant can accomplish the same development and troubleshooting in natural language. See

DashScope SDK Expert.

For the complete region table, see Base URL overview.

One-way streaming

image

The text sent in a single call must not exceed 20,000 characters. Exceeding this limit causes an error.

ImportantEach synthesize or stream call creates its own session; no instance reuse is needed.

# coding=utf-8

import os

from datetime import datetime

import dashscope

def get_timestamp():
    now = datetime.now()
    formatted_timestamp = now.strftime("[%Y-%m-%d %H:%M:%S.%f]")
    return formatted_timestamp

# The API Keys for the Singapore and Beijing regions are different. Get API Key: https://help.aliyun.com/zh/model-studio/get-api-key
# If the environment variable is not configured, replace the following line with your Model Studio API Key: api_key="sk-xxx"
# The following configuration is for the China (Beijing) region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
client = dashscope.Client(
    api_key=os.environ.get('DASHSCOPE_API_KEY'),
    workspace="{WorkspaceId}",
    region="cn-beijing",
)

# Model
model = "qwen-audio-3.0-tts-flash"
# Voice
voice = "longanhuan_v3.6"

# Send the complete text at once and receive the binary audio chunk by chunk
with open("output.mp3", "wb") as f:
    for chunk in client.audio.tts.stream("How is the weather today?", model=model, voice=voice):
        print(get_timestamp() + " Binary audio length: " + str(len(chunk)))
        f.write(chunk)
print("Speech synthesis completed, all results received: " + get_timestamp())

Bidirectional streaming

image

The text sent per call must not exceed 20,000 characters. The cumulative text must not exceed 200,000 characters.

  • During streaming input, call send_text multiple times to submit text segments in sequence. The server automatically performs sentence segmentation on received text:

    • Complete sentences are synthesized immediately
    • Incomplete sentences are buffered until complete

    When stop is called, the server force-synthesizes all received but unprocessed text (including incomplete sentences).

  • The interval between text segments must not exceed 23 seconds. Otherwise, a "request timeout after 23 seconds" exception is raised.

    If there's no text to send, call stop promptly to end the task.

    ImportantAlways call stop. Failing to do so may cause trailing text to not be converted to speech.

    The server enforces a 23-second timeout. This value can't be modified on the client side.

# coding=utf-8
#
# pyaudio installation instructions:
# For macOS, run the following commands:
#   brew install portaudio
#   pip install pyaudio
# For Debian/Ubuntu, run the following commands:
#   sudo apt-get install python-pyaudio python3-pyaudio
#   or
#   pip install pyaudio
# For CentOS, run the following commands:
#   sudo yum install -y portaudio portaudio-devel && pip install pyaudio
# For Microsoft Windows, run the following command:
#   python -m pip install pyaudio

import os
import time
import pyaudio
from concurrent.futures import ThreadPoolExecutor

import dashscope

from datetime import datetime

def get_timestamp():
    now = datetime.now()
    formatted_timestamp = now.strftime("[%Y-%m-%d %H:%M:%S.%f]")
    return formatted_timestamp

# The API Keys for the Singapore and Beijing regions are different. Get API Key: https://help.aliyun.com/zh/model-studio/get-api-key
# If the environment variable is not configured, replace the following line with your Model Studio API Key: api_key="sk-xxx"
# The following configuration is for the China (Beijing) region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
client = dashscope.Client(
    api_key=os.environ.get('DASHSCOPE_API_KEY'),
    workspace="{WorkspaceId}",
    region="cn-beijing",
)

# Model
model = "qwen-audio-3.0-tts-flash"
# Voice
voice = "longanhuan_v3.6"

test_text = [
    "Streaming text-to-speech SDK,",
    "converts input text",
    "into binary audio data.",
    "Compared with non-streaming speech synthesis,",
    "streaming synthesis offers better real-time performance.",
    "Users hear near-synchronous audio output while typing,",
    "greatly improving interaction experience",
    "and reducing wait time.",
    "Ideal for large language model (LLM) integration,",
    "where text is streamed for speech synthesis.",
]

# Create a bidirectional streaming synthesis session: the connection is established automatically when entering the with context
with client.audio.tts.connect(
        model=model,
        voice=voice,
        format="pcm",
        sample_rate=22050,
) as session:
    # Initialize the player
    player = pyaudio.PyAudio()
    stream = player.open(format=pyaudio.paInt16, channels=1, rate=22050, output=True)

    # Receiving thread: iterate the audio data and play it in real time
    def receive_audio():
        for chunk in session.audio_chunks():
            print(get_timestamp() + " Binary audio length: " + str(len(chunk)))
            stream.write(chunk)

    with ThreadPoolExecutor(max_workers=1) as pool:
        receiver = pool.submit(receive_audio)
        # Send the text for streaming synthesis
        for text in test_text:
            session.send_text(text)
            time.sleep(0.1)
        # End streaming speech synthesis
        session.stop()
        receiver.result()

    print("Speech synthesis completed, all results received: " + get_timestamp())
    # Stop the player
    stream.stop_stream()
    stream.close()
    player.terminate()

    # The first text submission requires establishing a WebSocket connection, so the first packet latency includes the connection setup time
    print('[Metric] first packet latency: {} ms'.format(session.first_package_delay))