Todos os produtos
Search
Central de documentação

Alibaba Cloud Model Studio:Real-time speech synthesis

Última atualização: Sep 09, 2026

Converta texto em fala com baixa latência no primeiro pacote. A síntese de fala em tempo real oferece entrada e saída em streaming, clonagem de voz, design de voz e controles de áudio refinados para assistentes de voz, audiolivros e atendimento ao cliente inteligente.

Visão geral

Transforme texto em fala instantaneamente e com baixa latência.

  • Entrada e saída em streaming com baixa latência no primeiro pacote
  • Controle granular de áudio com ajustes de velocidade da fala, tom, volume e taxa de bits
  • Compatibilidade com os principais formatos de áudio (PCM, WAV, MP3, Opus) e taxa de amostragem de até 48 kHz
  • Suporte a Instruction control, que permite controlar a expressividade da fala por meio de instruções em linguagem natural
  • Recursos de Voice cloning e Voice Design para criação de vozes personalizadas
  • Integração com Emotion and rich language tags, possibilitando a inserção de tags de emoção ou efeitos sonoros diretamente no texto

Para cenários em lote, como narração de audiolivros e materiais didáticos, utilize Non-real-time speech synthesis. Para orientações sobre a escolha do modelo, consulte Speech synthesis.

Pré-requisitos

Início rápido

Os exemplos a seguir demonstram a síntese de fala para cada modelo. Para ver mais exemplos e detalhes dos parâmetros, acesse API reference.

Qwen-Audio-TTS

Este exemplo realiza a síntese de fala utilizando uma voz do sistema.

Para aproveitar o recurso Instruction control, defina as instruções por meio do parâmetro instruction.

# coding=utf-8

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

# The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.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)
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.utils.Constants;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

public class Main {
    // 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.
    private static String model = "qwen-audio-3.0-tts-flash";
    // Voice
    private static String voice = "longanhuan_v3.6";

    public static void streamAudioDataToSpeaker() {
        // Request parameters
        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
                        // If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: .apiKey("sk-xxx")
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .model(model) // Model
                        .voice(voice) // Voice
                        .build();

        // Synchronous mode: disable callback (second parameter is null)
        SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
        ByteBuffer audio = null;
        try {
            // Block until audio is returned
            audio = synthesizer.call("How is the weather today?");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // Close the WebSocket connection when the task is done
            synthesizer.getDuplexApi().close(1000, "bye");
        }
        if (audio != null) {
            // Save the audio data to the local file "output.mp3"
            File file = new File("output.mp3");
            // The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time
            // Note: getFirstPackageDelay() requires dashscope-sdk-java 2.18.0 or later
            System.out.println(
                    "[Metric] requestId: "
                            + synthesizer.getLastRequestId()
                            + ", first-packet latency (ms): "
                            + synthesizer.getFirstPackageDelay());
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(audio.array());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        // The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
        Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
        streamAudioDataToSpeaker();
        System.exit(0);
    }
}

CosyVoice

Além do WebSocket, este modelo também suporta o protocolo AOQ. Recomendamos o uso do AOQ para integrações no lado do cliente que exigem latência estável, resiliência em redes instáveis e supressão de ruído full-duplex com cancelamento de eco integrados. Para comparar os protocolos, veja Realtime API overview.

ImportanteOs modelos cosyvoice-v3.5-plus e cosyvoice-v3.5-flash estão disponíveis apenas na região de Beijing e suportam exclusivamente cenários de design de voz e clonagem de voz (não incluem vozes do sistema). Antes de utilizá-los, crie uma voz personalizada por meio de Voice cloning ou Voice Design. Em seguida, no seu código, defina voice com o ID da voz criada e model com o nome do modelo correspondente.

O exemplo abaixo sintetiza fala usando uma voz do sistema (consulte CosyVoice Voice list).

Para habilitar o recurso Instruction control, configure as instruções através do parâmetro instruction.

# coding=utf-8

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

# The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'

# Model
# Different model versions require their corresponding voices:
# cosyvoice-v3-flash/cosyvoice-v3-plus: Use voices such as longanyang.
# cosyvoice-v2: Use voices such as longxiaochun_v2.
# 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 Qwen-Audio-TTS/CosyVoice voice list for details.
model = "cosyvoice-v3-flash"
# Voice
voice = "longanyang"

# 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)
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
    import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
    import com.alibaba.dashscope.utils.Constants;

    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;

    public class Main {
        // Model
        // Different model versions require their corresponding voices:
        // cosyvoice-v3-flash/cosyvoice-v3-plus: Use voices such as longanyang.
        // cosyvoice-v2: Use voices such as longxiaochun_v2.
        // 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 Qwen-Audio-TTS/CosyVoice voice list for details.
        private static String model = "cosyvoice-v3-flash";
        // Voice
        private static String voice = "longanyang";

        public static void streamAudioDataToSpeaker() {
            // Request parameters
            SpeechSynthesisParam param =
                    SpeechSynthesisParam.builder()
                            // The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
                            // If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: .apiKey("sk-xxx")
                            .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                            .model(model) // Model
                            .voice(voice) // Voice
                            .build();

            // Synchronous mode: disable callback (second parameter is null)
            SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
            ByteBuffer audio = null;
            try {
                // Block until audio is returned
                audio = synthesizer.call("How is the weather today?");
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                // Close the WebSocket connection when the task is done
                synthesizer.getDuplexApi().close(1000, "bye");
            }
            if (audio != null) {
                // Save the audio data to the local file "output.mp3"
                File file = new File("output.mp3");
                // The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time
                // Note: getFirstPackageDelay() requires dashscope-sdk-java 2.18.0 or later
                System.out.println(
                        "[Metric] requestId: "
                                + synthesizer.getLastRequestId()
                                + ", first-packet latency (ms): "
                                + synthesizer.getFirstPackageDelay());
                try (FileOutputStream fos = new FileOutputStream(file)) {
                    fos.write(audio.array());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        public static void main(String[] args) {
            // The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
            Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
            streamAudioDataToSpeaker();
            System.exit(0);
        }
    }

Qwen-TTS

O exemplo a seguir sintetiza fala usando uma voz do sistema (veja Supported voices).

Para utilizar o recurso Instruction control, substitua model por qwen3-tts-instruct-flash-realtime e especifique as instruções no parâmetro instructions.

Python

import os
import base64
import threading
import time
import dashscope
from dashscope.audio.qwen_tts_realtime import *

qwen_tts_realtime: QwenTtsRealtime = None
text_to_synthesize = [
    'Right? I love supermarkets like this.',
    'Especially during Chinese New Year,',
    'I go shopping at supermarkets.',
    'And I feel',
    'absolutely thrilled!',
    'I want to buy so many things!'
]

DO_VIDEO_TEST = False

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 keys differ between the Singapore and Beijing regions. Get an API key: https://www.alibabacloud.com/help/en/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

class MyCallback(QwenTtsRealtimeCallback):
    def __init__(self):
        self.complete_event = threading.Event()
        self.file = open('result_24k.pcm', 'wb')

    def on_open(self) -> None:
        print('connection opened, init player')

    def on_close(self, close_status_code, close_msg) -> None:
        self.file.close()
        print('connection closed with code: {}, msg: {}, destroy player'.format(close_status_code, close_msg))

    def on_event(self, response: str) -> None:
        try:
            global qwen_tts_realtime
            type = response['type']
            if 'session.created' == type:
                print('start session: {}'.format(response['session']['id']))
            if 'response.audio.delta' == type:
                recv_audio_b64 = response['delta']
                self.file.write(base64.b64decode(recv_audio_b64))
            if 'response.done' == type:
                print(f'response {qwen_tts_realtime.get_last_response_id()} done')
            if 'session.finished' == type:
                print('session finished')
                self.complete_event.set()
        except Exception as e:
            print('[Error] {}'.format(e))
            return

    def wait_for_finished(self):
        self.complete_event.wait()

if __name__  == '__main__':
    init_dashscope_api_key()

    print('Initializing ...')

    callback = MyCallback()

    qwen_tts_realtime = QwenTtsRealtime(
        # To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime
        model='qwen3-tts-flash-realtime',
        callback=callback,
        # Singapore region
        url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
        )

    qwen_tts_realtime.connect()
    qwen_tts_realtime.update_session(
        voice = 'Cherry',
        response_format = AudioFormat.PCM_24000HZ_MONO_16BIT,
        # To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime
        # instructions='Speak quickly with a rising intonation, suitable for introducing fashion products.',
        # optimize_instructions=True,
        mode = 'server_commit'
    )
    for text_chunk in text_to_synthesize:
        print(f'send text: {text_chunk}')
        qwen_tts_realtime.append_text(text_chunk)
        time.sleep(0.1)
    qwen_tts_realtime.finish()
    callback.wait_for_finished()
    print('[Metric] session: {}, first audio delay: {}'.format(
                    qwen_tts_realtime.get_session_id(),
                    qwen_tts_realtime.get_first_audio_delay(),
                    ))
import base64
import os
import threading
import dashscope
from dashscope.audio.qwen_tts_realtime import *

qwen_tts_realtime: QwenTtsRealtime = None
text_to_synthesize = [
    'This is the first sentence.',
    'This is the second sentence.',
    'This is the third sentence.',
]

DO_VIDEO_TEST = False

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 keys differ between the Singapore and Beijing regions. Get an API key: https://www.alibabacloud.com/help/en/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

class MyCallback(QwenTtsRealtimeCallback):
    def __init__(self):
        super().__init__()
        self.response_counter = 0
        self.complete_event = threading.Event()
        self.file = open(f'result_{self.response_counter}_24k.pcm', 'wb')

    def reset_event(self):
        self.response_counter += 1
        self.file = open(f'result_{self.response_counter}_24k.pcm', 'wb')
        self.complete_event = threading.Event()

    def on_open(self) -> None:
        print('connection opened, init player')

    def on_close(self, close_status_code, close_msg) -> None:
        print('connection closed with code: {}, msg: {}, destroy player'.format(close_status_code, close_msg))

    def on_event(self, response: str) -> None:
        try:
            global qwen_tts_realtime
            type = response['type']
            if 'session.created' == type:
                print('start session: {}'.format(response['session']['id']))
            if 'response.audio.delta' == type:
                recv_audio_b64 = response['delta']
                self.file.write(base64.b64decode(recv_audio_b64))
            if 'response.done' == type:
                print(f'response {qwen_tts_realtime.get_last_response_id()} done')
                self.complete_event.set()
                self.file.close()
            if 'session.finished' == type:
                print('session finished')
                self.complete_event.set()
        except Exception as e:
            print('[Error] {}'.format(e))
            return

    def wait_for_response_done(self):
        self.complete_event.wait()

if __name__  == '__main__':
    init_dashscope_api_key()

    print('Initializing ...')

    callback = MyCallback()

    qwen_tts_realtime = QwenTtsRealtime(
        # To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime
        model='qwen3-tts-flash-realtime',
        callback=callback,
        # Singapore region
        url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
        )

    qwen_tts_realtime.connect()
    qwen_tts_realtime.update_session(
        voice = 'Cherry',
        response_format = AudioFormat.PCM_24000HZ_MONO_16BIT,
        # To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime
        # instructions='Speak quickly with a rising intonation, suitable for introducing fashion products.',
        # optimize_instructions=True,
        mode = 'commit'
    )
    print(f'send text: {text_to_synthesize[0]}')
    qwen_tts_realtime.append_text(text_to_synthesize[0])
    qwen_tts_realtime.commit()
    callback.wait_for_response_done()
    callback.reset_event()

    print(f'send text: {text_to_synthesize[1]}')
    qwen_tts_realtime.append_text(text_to_synthesize[1])
    qwen_tts_realtime.commit()
    callback.wait_for_response_done()
    callback.reset_event()

    print(f'send text: {text_to_synthesize[2]}')
    qwen_tts_realtime.append_text(text_to_synthesize[2])
    qwen_tts_realtime.commit()
    callback.wait_for_response_done()

    qwen_tts_realtime.finish()
    print('[Metric] session: {}, first audio delay: {}'.format(
                    qwen_tts_realtime.get_session_id(),
                    qwen_tts_realtime.get_first_audio_delay(),
                    ))

Java

Modo server commit

appendText()

import com.alibaba.dashscope.audio.qwen_tts_realtime.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.AudioSystem;
import java.io.*;
import java.util.Base64;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class Main {
    static String[] textToSynthesize = {
            "Right? I really love this kind of supermarket.",
            "Especially during the Chinese New Year.",
            "Going to the supermarket.",
            "It just makes me feel.",
            "Super, super happy!",
            "I want to buy so many things!"
    };
    public static QwenTtsRealtimeAudioFormat ttsFormat = QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT;

    // Real-time PCM audio player
    public static class RealtimePcmPlayer {
        private int sampleRate;
        private SourceDataLine line;
        private AudioFormat audioFormat;
        private Thread decoderThread;
        private Thread playerThread;
        private AtomicBoolean stopped = new AtomicBoolean(false);
        private Queue<String> b64AudioBuffer = new ConcurrentLinkedQueue<>();
        private Queue<byte[]> RawAudioBuffer = new ConcurrentLinkedQueue<>();
        private ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream();

        // Initialize the audio format and audio line.
        public RealtimePcmPlayer(int sampleRate) throws LineUnavailableException {
            this.sampleRate = sampleRate;
            this.audioFormat = new AudioFormat(this.sampleRate, 16, 1, true, false);
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
            line = (SourceDataLine) AudioSystem.getLine(info);
            line.open(audioFormat);
            line.start();
            decoderThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!stopped.get()) {
                        String b64Audio = b64AudioBuffer.poll();
                        if (b64Audio != null) {
                            byte[] rawAudio = Base64.getDecoder().decode(b64Audio);
                            RawAudioBuffer.add(rawAudio);
                            // Write audio data to totalAudioStream.
                            try {
                                totalAudioStream.write(rawAudio);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        } else {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            });
            playerThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!stopped.get()) {
                        byte[] rawAudio = RawAudioBuffer.poll();
                        if (rawAudio != null) {
                            try {
                                playChunk(rawAudio);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        } else {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            });
            decoderThread.start();
            playerThread.start();
        }

        // Play an audio chunk and block until playback completes.
        private void playChunk(byte[] chunk) throws IOException, InterruptedException {
            if (chunk == null || chunk.length == 0) return;

            int bytesWritten = 0;
            while (bytesWritten < chunk.length) {
                bytesWritten += line.write(chunk, bytesWritten, chunk.length - bytesWritten);
            }
            int audioLength = chunk.length / (this.sampleRate*2/1000);
            // Wait for the buffered audio to finish playing.
            Thread.sleep(audioLength - 10);
        }

        public void write(String b64Audio) {
            b64AudioBuffer.add(b64Audio);
        }

        public void cancel() {
            b64AudioBuffer.clear();
            RawAudioBuffer.clear();
        }

        public void waitForComplete() throws InterruptedException {
            while (!b64AudioBuffer.isEmpty() || !RawAudioBuffer.isEmpty()) {
                Thread.sleep(100);
            }
            line.drain();
        }

        public void shutdown() throws InterruptedException, IOException {
            stopped.set(true);
            decoderThread.join();
            playerThread.join();

            // Save the complete audio file.
            File file = new File("TotalAudio_"+ttsFormat.getSampleRate()+"."+ttsFormat.getFormat());
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(totalAudioStream.toByteArray());
            }

            if (line != null && line.isRunning()) {
                line.drain();
                line.close();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException, LineUnavailableException, IOException {
        QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder()
                // To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime.
                .model("qwen3-tts-flash-realtime")
                // China (Beijing) region
                .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                // API keys differ between Singapore and China (Beijing). See https://www.alibabacloud.com/help/en/model-studio/get-api-key.
                .apikey(System.getenv("DASHSCOPE_API_KEY"))
                .build();
        AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));
        final AtomicReference<QwenTtsRealtime> qwenTtsRef = new AtomicReference<>(null);

        // Create a real-time audio player instance.
        RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);

        QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
            @Override
            public void onOpen() {
                // Handle connection establishment.
            }
            @Override
            public void onEvent(JsonObject message) {
                String type = message.get("type").getAsString();
                switch(type) {
                    case "session.created":
                        // Handle session creation.
                        if (message.has("session")) {
                            String eventId = message.get("event_id").getAsString();
                            String sessionId = message.get("session").getAsJsonObject().get("id").getAsString();
                            System.out.println("[onEvent] session.created, session_id: "
                                    + sessionId + ", event_id: " + eventId);
                        }
                        break;
                    case "response.audio.delta":
                        String recvAudioB64 = message.get("delta").getAsString();
                        // Play audio in real time.
                        audioPlayer.write(recvAudioB64);
                        break;
                    case "response.done":
                        // Handle response completion.
                        break;
                    case "session.finished":
                        // Handle session termination.
                        completeLatch.get().countDown();
                    default:
                        break;
                }
            }
            @Override
            public void onClose(int code, String reason) {
                // Handle connection closure.
            }
        });
        qwenTtsRef.set(qwenTtsRealtime);
        try {
            qwenTtsRealtime.connect();
        } catch (NoApiKeyException e) {
            throw new RuntimeException(e);
        }
        QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
                .voice("Cherry")
                .responseFormat(ttsFormat)
                .mode("server_commit")
                // To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime.
                // .instructions("")
                // .optimizeInstructions(true)
                .build();
        qwenTtsRealtime.updateSession(config);
        for (String text:textToSynthesize) {
            qwenTtsRealtime.appendText(text);
            Thread.sleep(100);
        }
        qwenTtsRealtime.finish();
        completeLatch.get().await();
        qwenTtsRealtime.close();

        // Wait for audio playback to complete, then shut down the player.
        audioPlayer.waitForComplete();
        audioPlayer.shutdown();
        System.exit(0);
    }
}

Modo commit

commit()

import com.alibaba.dashscope.audio.qwen_tts_realtime.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.AudioSystem;
import java.io.*;
import java.util.Base64;
import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class Main {
    public static QwenTtsRealtimeAudioFormat ttsFormat = QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT;
    // Real-time PCM audio player
    public static class RealtimePcmPlayer {
        private int sampleRate;
        private SourceDataLine line;
        private AudioFormat audioFormat;
        private Thread decoderThread;
        private Thread playerThread;
        private AtomicBoolean stopped = new AtomicBoolean(false);
        private Queue<String> b64AudioBuffer = new ConcurrentLinkedQueue<>();
        private Queue<byte[]> RawAudioBuffer = new ConcurrentLinkedQueue<>();
        private ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream();

        // Initialize the audio format and audio line.
        public RealtimePcmPlayer(int sampleRate) throws LineUnavailableException {
            this.sampleRate = sampleRate;
            this.audioFormat = new AudioFormat(this.sampleRate, 16, 1, true, false);
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
            line = (SourceDataLine) AudioSystem.getLine(info);
            line.open(audioFormat);
            line.start();
            decoderThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!stopped.get()) {
                        String b64Audio = b64AudioBuffer.poll();
                        if (b64Audio != null) {
                            byte[] rawAudio = Base64.getDecoder().decode(b64Audio);
                            RawAudioBuffer.add(rawAudio);
                            // Write audio data to totalAudioStream.
                            try {
                                totalAudioStream.write(rawAudio);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        } else {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            });
            playerThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!stopped.get()) {
                        byte[] rawAudio = RawAudioBuffer.poll();
                        if (rawAudio != null) {
                            try {
                                playChunk(rawAudio);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        } else {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            });
            decoderThread.start();
            playerThread.start();
        }

        // Play an audio chunk and block until playback completes.
        private void playChunk(byte[] chunk) throws IOException, InterruptedException {
            if (chunk == null || chunk.length == 0) return;

            int bytesWritten = 0;
            while (bytesWritten < chunk.length) {
                bytesWritten += line.write(chunk, bytesWritten, chunk.length - bytesWritten);
            }
            int audioLength = chunk.length / (this.sampleRate*2/1000);
            // Wait for the buffered audio to finish playing.
            Thread.sleep(audioLength - 10);
        }

        public void write(String b64Audio) {
            b64AudioBuffer.add(b64Audio);
        }

        public void cancel() {
            b64AudioBuffer.clear();
            RawAudioBuffer.clear();
        }

        public void waitForComplete() throws InterruptedException {
            // Wait for all buffered audio data to finish playing.
            while (!b64AudioBuffer.isEmpty() || !RawAudioBuffer.isEmpty()) {
                Thread.sleep(100);
            }
            // Wait for the audio line to drain.
            line.drain();
        }

        public void shutdown() throws InterruptedException {
            stopped.set(true);
            decoderThread.join();
            playerThread.join();
            // Save the complete audio file.
            File file = new File("TotalAudio_"+ttsFormat.getSampleRate()+"."+ttsFormat.getFormat());
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(totalAudioStream.toByteArray());
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            if (line != null && line.isRunning()) {
                line.drain();
                line.close();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException, LineUnavailableException, FileNotFoundException {
        Scanner scanner = new Scanner(System.in);

        QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder()
                // To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime.
                .model("qwen3-tts-flash-realtime")
                // China (Beijing) region
                .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                // API keys differ between Singapore and China (Beijing). See https://www.alibabacloud.com/help/en/model-studio/get-api-key.
                .apikey(System.getenv("DASHSCOPE_API_KEY"))
                .build();

        AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));

        // Create a real-time player instance.
        RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);

        final AtomicReference<QwenTtsRealtime> qwenTtsRef = new AtomicReference<>(null);
        QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
            @Override
            public void onOpen() {
                System.out.println("connection opened");
                System.out.println("Enter text and press Enter to send. Enter 'quit' to exit the program.");
            }
            @Override
            public void onEvent(JsonObject message) {
                String type = message.get("type").getAsString();
                switch(type) {
                    case "session.created":
                        System.out.println("start session: " + message.get("session").getAsJsonObject().get("id").getAsString());
                        break;
                    case "response.audio.delta":
                        String recvAudioB64 = message.get("delta").getAsString();
                        byte[] rawAudio = Base64.getDecoder().decode(recvAudioB64);
                        // Play audio in real time.
                        audioPlayer.write(recvAudioB64);
                        break;
                    case "response.done":
                        System.out.println("response done");
                        // Wait for audio playback to complete.
                        try {
                            audioPlayer.waitForComplete();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        // Prepare for the next input.
                        completeLatch.get().countDown();
                        break;
                    case "session.finished":
                        System.out.println("session finished");
                        if (qwenTtsRef.get() != null) {
                            System.out.println("[Metric] response: " + qwenTtsRef.get().getResponseId() +
                                    ", first audio delay: " + qwenTtsRef.get().getFirstAudioDelay() + " ms");
                        }
                        completeLatch.get().countDown();
                    default:
                        break;
                }
            }
            @Override
            public void onClose(int code, String reason) {
                System.out.println("connection closed code: " + code + ", reason: " + reason);
                try {
                    // Wait for playback to complete, then shut down the player.
                    audioPlayer.waitForComplete();
                    audioPlayer.shutdown();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        qwenTtsRef.set(qwenTtsRealtime);
        try {
            qwenTtsRealtime.connect();
        } catch (NoApiKeyException e) {
            throw new RuntimeException(e);
        }
        QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
                .voice("Cherry")
                .responseFormat(ttsFormat)
                .mode("commit")
                // To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime.
                // .instructions("")
                // .optimizeInstructions(true)
                .build();
        qwenTtsRealtime.updateSession(config);

        // Read user input in a loop.
        while (true) {
            System.out.print("Enter the text to synthesize: ");
            String text = scanner.nextLine();

            // Exit when the user enters 'quit'.
            if ("quit".equalsIgnoreCase(text.trim())) {
                System.out.println("Closing the connection...");
                qwenTtsRealtime.finish();
                completeLatch.get().await();
                break;
            }

            // Skip empty input.
            if (text.trim().isEmpty()) {
                continue;
            }

            // Re-initialize the countdown latch.
            completeLatch.set(new CountDownLatch(1));

            // Send the text.
            qwenTtsRealtime.appendText(text);
            qwenTtsRealtime.commit();

            // Wait for the current synthesis to complete.
            completeLatch.get().await();
        }

        // Clean up resources.
        audioPlayer.waitForComplete();
        audioPlayer.shutdown();
        scanner.close();
        System.exit(0);
    }
}

Configuração de sessão

Modos de interação do Qwen-TTS

A API em tempo real do Qwen-TTS oferece dois modos de interação:

  • Modo server_commit: O servidor gerencia automaticamente a segmentação de texto e o tempo de síntese. Ideal para síntese contínua de grandes blocos de texto. O cliente adiciona texto sem precisar gerenciar a segmentação ou o envio.
  • Modo commit: O cliente envia explicitamente o buffer de texto para acionar a síntese. Recomendado para cenários que exigem controle preciso sobre o momento da síntese, como a síntese por turno em IA conversacional.

Alternar entre modos de interação:

  • WebSocket: Defina o campo mode no evento session.update.
{
    "type": "session.update",
    "session": {
        "mode": "server_commit"
    }
}
  • SDK Python: Configure o parâmetro mode no método update_session.
qwen_tts_realtime.update_session(
    voice='Cherry',
    response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
    mode='server_commit'
)
  • SDK Java: Especifique o parâmetro mode por meio de QwenTtsRealtimeConfig.builder().
QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
        .voice("Cherry")
        .responseFormat(ttsFormat)
        .mode("server_commit")
        .build();
qwenTtsRealtime.updateSession(config);

Para exemplos completos de código dos SDKs, consulte Python SDK e Java SDK. Para detalhes sobre o ciclo de vida de eventos WebSocket e reutilização de conexões, veja WebSocket API reference.

Recursos avançados

Controle por instruções

O controle por instruções utiliza descrições em linguagem natural para ajustar o tom, a velocidade, a emoção e as características do timbre da fala, sem a necessidade de configurar parâmetros de áudio complexos.

Especificações de instruções por modelo:

Qwen-Audio-TTS

Modelos suportados: qwen-audio-3.0-tts-plus, qwen-audio-3.0-tts-flash

Vozes do sistema e vozes clonadas: aceitam qualquer instrução.

CosyVoice

Modelos suportados: cosyvoice-v3.5-plus, cosyvoice-v3.5-flash, cosyvoice-v3-plus, cosyvoice-v3-flash

Os requisitos de formato de instrução variam conforme o modelo:

  • cosyvoice-v3.5-plus, cosyvoice-v3.5-flash:

    • Vozes clonadas ou personalizadas: aceitam qualquer instrução.
    • Vozes do sistema: a versão v3.5 não oferece suporte a vozes do sistema.
  • cosyvoice-v3-plus:

    • Vozes clonadas ou personalizadas: não oferecem suporte ao controle por instruções.
    • Vozes do sistema: as instruções devem seguir um formato e conteúdo fixos. Consulte CosyVoice Voice list.
  • cosyvoice-v3-flash:

    • Vozes clonadas ou personalizadas: aceitam qualquer instrução.
    • Vozes do sistema: as instruções devem seguir um formato e conteúdo fixos. Consulte CosyVoice Voice list.

Uso: Especifique o conteúdo da instrução por meio do parâmetro instruction.

Idiomas suportados para o texto da instrução:

  • cosyvoice-v3.5-plus, cosyvoice-v3.5-flash:

    • Vozes clonadas ou personalizadas: chinês, inglês, francês, alemão, japonês, coreano, russo, português, tailandês, indonésio e vietnamita.
    • Vozes do sistema: a versão v3.5 não oferece suporte a vozes do sistema.
  • cosyvoice-v3-plus:

    • Vozes clonadas ou personalizadas: chinês, inglês, francês, alemão, japonês, coreano e russo.
    • Vozes do sistema: as instruções devem seguir um formato e conteúdo fixos. Consulte CosyVoice Voice list.
  • cosyvoice-v3-flash:

    • Vozes clonadas ou personalizadas: chinês, inglês, francês, alemão, japonês, coreano e russo.
    • Vozes do sistema: apenas chinês.

Limite de comprimento do texto da instrução: máximo de 100 caracteres. Caracteres chineses (incluindo chinês simplificado/tradicional, kanji japonês e hanja coreano) contam como 2 caracteres cada. Todos os demais caracteres (como pontuação, letras, dígitos, kana japonês e hangul coreano) contam como 1 caractere cada.

Qwen-TTS

Modelos suportados: Apenas modelos da série Qwen3-TTS-Instruct-Flash-Realtime.

Uso: Especifique o conteúdo da instrução por meio do parâmetro instructions.

Idiomas suportados para o texto da instrução: Apenas chinês e inglês.

Limite de comprimento do texto da instrução: Máximo de 1.600 tokens.

Casos de uso:

  • Narração de audiolivros e radionovelas
  • Locução para publicidade e vídeos promocionais
  • Dublagem de personagens de jogos e animações
  • Assistentes de voz com expressividade emocional
  • Narração de documentários e telejornais

Como escrever descrições de voz eficazes:

  • Princípios fundamentais:

    1. Seja específico, não vago: Utilize termos que descrevam qualidades vocais, como "profundo", "nítido" ou "ritmo levemente acelerado". Evite palavras subjetivas ou imprecisas como "bonito" ou "normal".
    2. Aborde múltiplas dimensões: Uma boa descrição geralmente abrange vários aspectos (como gênero, idade e emoção). Escrever apenas "voz feminina" é amplo demais para gerar um timbre distinto.
    3. Mantenha a objetividade: Concentre-se nas características físicas e perceptivas da voz. Por exemplo, use "tom mais agudo com energia" em vez de "minha voz favorita".
    4. Priorize a originalidade: Descreva as qualidades vocais em vez de solicitar a imitação de pessoas específicas (como celebridades ou atores). O modelo não suporta imitações e isso pode acarretar riscos de direitos autorais.
    5. Seja conciso: Garanta que cada palavra tenha um propósito. Evite sinônimos repetitivos ou modificadores sem significado.
  • Referência de dimensões de descrição:

    Combine as dimensões abaixo para descrever uma voz. Quanto mais dimensões você incluir, mais precisa será a saída.

    Dimensão

    Exemplos de descrições

    Gênero

    Masculino, feminino, andrógino

    Idade

    Criança (5-12), adolescente (13-18), jovem adulto (19-35), meia-idade (36-55), idoso (55+)

    Tom

    Agudo, médio, grave, levemente agudo, levemente grave

    Velocidade

    Rápida, moderada, lenta, levemente rápida, levemente lenta

    Emoção

    Alegre, calmo, gentil, sério, animado, sereno, suave

    Características

    Magnético, nítido, rouco, aveludado, doce, encorpado, potente

    Caso de uso

    Telejornal, locução publicitária, audiolivro, personagem de animação, assistente de voz, narração de documentário

  • Exemplos:

    • Estilo de transmissão padrão: articulação clara e precisa com pronúncia perfeita
    • Uma voz feminina jovem e animada, com ritmo mais rápido e entonação ascendente notável, adequada para apresentações de produtos de moda
    • Um homem de meia-idade calmo, ritmo lento, voz profunda e magnética, adequado para leitura de notícias ou narração de documentários
    • Uma mulher gentil e intelectual, por volta dos 30 anos, com tom uniforme, adequada para narração de audiolivros
    • Uma voz infantil fofa, aproximadamente de uma menina de 8 anos, falando com uma qualidade levemente pueril, adequada para dublagem de personagens de animação

Dialetos

Esta seção descreve como produzir fala em dialetos chineses (como o dialeto de Henan, dialeto de Sichuan e cantonês). Os métodos de configuração variam conforme o modelo e o tipo de voz.

Configuração de dialeto por modelo:

Qwen-Audio-TTS

  • Vozes do sistema: Selecione um dos seguintes tipos de voz:

    • Uma voz do sistema com suporte nativo a dialeto, que gera o dialeto correspondente sem configuração adicional.
    • Uma voz compatível com Instruction control, configurável para gerar um dialeto específico por meio de texto de instrução.
  • Vozes clonadas: Configure por meio do recurso Instruction control. Por exemplo, defina o texto da instrução como 请用河南话表达.

Dialetos suportados: Consulte a coluna "Idiomas suportados" de cada modelo em Qwen-Audio-TTS.

CosyVoice

  • Vozes do sistema: Selecione um dos seguintes tipos de voz em CosyVoice Voice list:

    • Uma voz do sistema com suporte nativo a dialeto (como longshange_v3), que gera o dialeto correspondente sem configuração adicional.
    • Uma voz compatível com Instruction control, configurável para gerar um dialeto específico (como longanhuan_v3), especificado por meio de texto de instrução.
  • Vozes clonadas: Configure por meio do recurso Instruction control. Por exemplo, defina o texto da instrução como 请用河南话表达.

  • Vozes personalizadas: não oferecem suporte a dialetos.

Dialetos suportados: Consulte a coluna "Idiomas suportados" de cada modelo em CosyVoice.

Exemplo: Utilize o modelo cosyvoice-v3-flash com a voz longanhuan_v3 e defina o texto da instrução como "请用河南话表达。" para gerar fala no dialeto de Henan.

# coding=utf-8

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

# The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'

# Model
# Different model versions require their corresponding voices:
# cosyvoice-v3-flash/cosyvoice-v3-plus: Use voices such as longanyang.
# cosyvoice-v2: Use voices such as longxiaochun_v2.
# Select the appropriate voice for your target language
model = "cosyvoice-v3-flash"
# Voice
voice = "longanhuan_v3"

# Instantiate SpeechSynthesizer and pass request parameters such as model and voice in the constructor
synthesizer = SpeechSynthesizer(model=model, voice=voice, instruction="请用河南话表达。")
# Send the text to be synthesized and get the binary audio
audio = synthesizer.call("叫你去买盐,你买回来一袋面,这不是弄啥嘞吗!")
# 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)

Qwen-TTS

  • Vozes do sistema: Utilize uma voz do sistema com suporte nativo a dialeto. Consulte a lista de vozes do Qwen-TTS em Supported voices.
  • Vozes clonadas: não oferecem suporte a dialetos.
  • Vozes personalizadas: não oferecem suporte a dialetos.

Dialetos suportados: Consulte a coluna "Idiomas suportados" de cada modelo em Qwen3-TTS.

Tags de emoção e linguagem rica

Os modelos da série Qwen-Audio-TTS permitem incorporar tags de emoção e linguagem rica diretamente no texto a ser sintetizado (parâmetro text). Essas tags controlam a expressão emocional ou inserem efeitos vocais (como risadas e suspiros) em posições específicas, produzindo uma fala mais expressiva sem a necessidade de configurar parâmetros de áudio complexos.

ImportanteModelos suportados: Apenas qwen-audio-3.0-tts-plus e qwen-audio-3.0-tts-flash.

Limitação: Apenas o modo de streaming unidirecional é suportado.

Tags de controle

As tags de controle definem a emoção ou o estilo da fala. Insira uma tag no texto para afetar todo o conteúdo subsequente até que outra tag de controle apareça ou a frase seja segmentada automaticamente devido ao comprimento.

Tag

Descrição

[sad]

Triste

[amazed]

Espantado

[deep and loud shouting]

Grito profundo e alto

[trembling]

Trêmulo

[angry]

Com raiva

[excited]

Empolgado

[sarcastic]

Sarcástico

[curious]

Curioso

[like dracula]

Estilo Drácula (profundo, sombrio)

[bored]

Entediado

[tired]

Cansado

[scornful]

Desdenhoso

[shouting]

Gritando

[asmr]

Sussurro suave ASMR

[panicked]

Em pânico

[mischievously]

Malicioso

[empathetic]

Empático

[whispers]

Sussurro

[reluctantly]

Relutante

[crying]

Chorando

[serious]

Sério

[very slowly]

Fala muito lenta

[very fast]

Fala muito rápida

Tags de linguagem rica

As tags de linguagem rica inserem um efeito vocal na posição atual do texto, sem alterar o estilo emocional do conteúdo ao redor.

Tag

Descrição

[gasp]

Arquejo

[sighing]

Suspiro

[clears throat]

Limpeza de garganta

[giggles]

Risadinha

[laughing]

Risada

[cough]

Tosse

[snorts]

Bufada

Exemplos de uso

O exemplo a seguir demonstra como combinar tags de controle e tags de linguagem rica no parâmetro text:

[excited]What a beautiful day today![laughing]Let's go out and have fun together!

Neste texto, [excited] é uma tag de controle que aplica emoção de empolgação a todo o texto subsequente. Já [laughing] é uma tag de linguagem rica que insere uma risada naquela posição antes de continuar a síntese do restante do texto.

Também é possível alternar entre diferentes emoções no mesmo texto:

[serious]Please pay attention to the safety precautions.[excited]Alright, let's get started now!

Aqui, [serious] define a primeira frase com um tom sério, enquanto [excited] alterna para um tom empolgado a partir da segunda frase.

Cancelar tarefa

Caso precise interromper a rodada atual de síntese durante a geração de fala em tempo real, envie um comando de cancelamento. Após o cancelamento, o servidor encerra imediatamente a tarefa atual e retorna um evento de conclusão. É possível iniciar uma nova tarefa de síntese na mesma conexão WebSocket sem precisar reconectar.

Uso:

  • Python SDK: Versão 1.26.4 ou posterior, chame SpeechSynthesizer.streaming_cancel().
  • Java SDK: Versão 2.22.26 ou posterior, chame SpeechSynthesizer.streamingCancel().
  • Protocolo bruto WebSocket: Envie um evento finish-task e defina directive=cancel em input.

ImportanteLimitações de modelo:

  • China (Beijing): Todos os modelos Qwen-Audio-TTS suportam este recurso. Os modelos CosyVoice exigem a versão v2 ou posterior.
  • Singapore: Todos os modelos Qwen-Audio-TTS suportam este recurso. Os modelos CosyVoice não oferecem suporte a este recurso.

Chamadas diretas ao protocolo WebSocket

Os exemplos a seguir demonstram como se conectar diretamente ao servidor usando o protocolo nativo do WebSocket, ideal para cenários em que o DashScope SDK não está disponível. Estas são implementações mínimas e executáveis. Para obter detalhes sobre o protocolo WebSocket, consulte a referência da API de cada modelo.

Visualizar exemplos de chamada direta ao protocolo WebSocket

Qwen-TTS

  1. Crie o cliente

    Python

    Crie um arquivo Python chamado tts_realtime_client.py e copie o código a seguir para o arquivo:

    # -- coding: utf-8 --
    
    import asyncio
    import websockets
    import json
    import base64
    import time
    from typing import Optional, Callable, Dict, Any
    from enum import Enum
    
    class SessionMode(Enum):
        SERVER_COMMIT = "server_commit"
        COMMIT = "commit"
    
    class TTSRealtimeClient:
        """
        Client for interacting with the TTS Realtime API.
    
        This class provides methods for connecting to the TTS Realtime API, sending text data,
        receiving audio output, and managing WebSocket connections.
    
        Attributes:
            base_url (str):
                Base URL of the Realtime API.
            api_key (str):
                API Key for authentication.
            voice (str):
                Voice used for server-side speech synthesis.
            mode (SessionMode):
                Session mode, either server_commit or commit.
            audio_callback (Callable[[bytes], None]):
                Callback function for receiving audio data.
            language_type(str)
                Language for synthesized speech. Options: Chinese, English, German, Italian, Portuguese, Spanish, Japanese, Korean, French, Russian, Auto
        """
    
        def __init__(
                self,
                base_url: str,
                api_key: str,
                voice: str = "Cherry",
                mode: SessionMode = SessionMode.SERVER_COMMIT,
                audio_callback: Optional[Callable[[bytes], None]] = None,
            language_type: str = "Auto"):
            self.base_url = base_url
            self.api_key = api_key
            self.voice = voice
            self.mode = mode
            self.ws = None
            self.audio_callback = audio_callback
            self.language_type = language_type
    
            # Current response state
            self._current_response_id = None
            self._current_item_id = None
            self._is_responding = False
            self._response_done_future = None
    
        async def connect(self) -> None:
            """Establish WebSocket connection with the TTS Realtime API."""
            headers = {
                "Authorization": f"Bearer {self.api_key}"
            }
    
            self.ws = await websockets.connect(self.base_url, additional_headers=headers)
    
            # Set default session configuration
            await self.update_session({
                "mode": self.mode.value,
                "voice": self.voice,
                # To use the instruction control feature, uncomment the lines below and replace the model with qwen3-tts-instruct-flash-realtime in server_commit.py or commit.py
                # "instructions": "Speak quickly with a noticeable rising intonation, suitable for introducing fashion products.",
                # "optimize_instructions": true
                "language_type": self.language_type,
                "response_format": "pcm",
                "sample_rate": 24000
            })
    
        async def send_event(self, event) -> None:
            """Send an event to the server."""
            event['event_id'] = "event_" + str(int(time.time() * 1000))
            print(f"Sending event: type={event['type']}, event_id={event['event_id']}")
            await self.ws.send(json.dumps(event))
    
        async def update_session(self, config: Dict[str, Any]) -> None:
            """Update session configuration."""
            event = {
                "type": "session.update",
                "session": config
            }
            print("Updating session configuration: ", event)
            await self.send_event(event)
    
        async def append_text(self, text: str) -> None:
            """Send text data to the API."""
            event = {
                "type": "input_text_buffer.append",
                "text": text
            }
            await self.send_event(event)
    
        async def commit_text_buffer(self) -> None:
            """Commit text buffer to trigger processing."""
            event = {
                "type": "input_text_buffer.commit"
            }
            await self.send_event(event)
    
        async def clear_text_buffer(self) -> None:
            """Clear the text buffer."""
            event = {
                "type": "input_text_buffer.clear"
            }
            await self.send_event(event)
    
        async def finish_session(self) -> None:
            """End the session."""
            event = {
                "type": "session.finish"
            }
            await self.send_event(event)
    
        async def wait_for_response_done(self):
            """Wait for the response.done event"""
            if self._response_done_future:
                await self._response_done_future
    
        async def handle_messages(self) -> None:
            """Handle messages from the server."""
            try:
                async for message in self.ws:
                    event = json.loads(message)
                    event_type = event.get("type")
    
                    if event_type != "response.audio.delta":
                        print(f"Received event: {event_type}")
    
                    if event_type == "error":
                        print("Error: ", event.get('error', {}))
                        continue
                    elif event_type == "session.created":
                        print("Session created, ID: ", event.get('session', {}).get('id'))
                    elif event_type == "session.updated":
                        print("Session updated, ID: ", event.get('session', {}).get('id'))
                    elif event_type == "input_text_buffer.committed":
                        print("Text buffer committed, item ID: ", event.get('item_id'))
                    elif event_type == "input_text_buffer.cleared":
                        print("Text buffer cleared")
                    elif event_type == "response.created":
                        self._current_response_id = event.get("response", {}).get("id")
                        self._is_responding = True
                        # Create a new future to wait for response.done
                        self._response_done_future = asyncio.Future()
                        print("Response created, ID: ", self._current_response_id)
                    elif event_type == "response.output_item.added":
                        self._current_item_id = event.get("item", {}).get("id")
                        print("Output item added, ID: ", self._current_item_id)
                    # Handle audio delta
                    elif event_type == "response.audio.delta" and self.audio_callback:
                        audio_bytes = base64.b64decode(event.get("delta", ""))
                        self.audio_callback(audio_bytes)
                    elif event_type == "response.audio.done":
                        print("Audio generation completed")
                    elif event_type == "response.done":
                        self._is_responding = False
                        self._current_response_id = None
                        self._current_item_id = None
                        # Mark future as done
                        if self._response_done_future and not self._response_done_future.done():
                            self._response_done_future.set_result(True)
                        print("Response completed")
                    elif event_type == "session.finished":
                        print("Session finished")
    
            except websockets.exceptions.ConnectionClosed:
                print("Connection closed")
            except Exception as e:
                print("Error handling messages: ", str(e))
    
        async def close(self) -> None:
            """Close the WebSocket connection."""
            if self.ws:
                await self.ws.close()
    

    Java

    Crie um arquivo Java chamado TTSRealtimeClient.java e copie o código abaixo para o arquivo:

    import com.google.gson.Gson;
    import com.google.gson.JsonObject;
    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.handshake.ServerHandshake;
    
    import java.net.URI;
    import java.util.Base64;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.CountDownLatch;
    import java.util.function.Consumer;
    
    /**
     * A client for interacting with the TTS Realtime API.
     *
     * This class provides methods for connecting to the TTS Realtime API, sending text data, retrieving audio output, and managing WebSocket connections.
     */
    public class TTSRealtimeClient {
    
        public enum SessionMode {
            SERVER_COMMIT("server_commit"),
            COMMIT("commit");
            private final String value;
            SessionMode(String value) { this.value = value; }
            public String getValue() { return value; }
        }
    
        /**
         * Audio callback interface
         */
        public interface AudioCallback {
            void onAudio(byte[] audioData);
        }
    
        private final String baseUrl;
        private final String apiKey;
        private final String voice;
        private final SessionMode mode;
        private final String languageType;
        private final AudioCallback audioCallback;
        private final Gson gson = new Gson();
    
        private WebSocketClient ws;
        private CountDownLatch responseDoneLatch;
        private CountDownLatch sessionFinishedLatch;
    
        public TTSRealtimeClient(String baseUrl, String apiKey, String voice,
                                 SessionMode mode, AudioCallback audioCallback,
                                 String languageType) {
            this.baseUrl = baseUrl;
            this.apiKey = apiKey;
            this.voice = voice;
            this.mode = mode;
            this.audioCallback = audioCallback;
            this.languageType = languageType;
        }
    
        public TTSRealtimeClient(String baseUrl, String apiKey, String voice,
                                 SessionMode mode, AudioCallback audioCallback) {
            this(baseUrl, apiKey, voice, mode, audioCallback, "Auto");
        }
    
        /**
         * Establish a WebSocket connection to the TTS Realtime API.
         */
        public void connect() throws Exception {
            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "Bearer " + apiKey);
    
            responseDoneLatch = new CountDownLatch(0);
            sessionFinishedLatch = new CountDownLatch(1);
    
            ws = new WebSocketClient(new URI(baseUrl), headers) {
                @Override
                public void onOpen(ServerHandshake handshake) {
                    System.out.println("WebSocket connection established");
                    // Send default session configuration
                    JsonObject session = new JsonObject();
                    session.addProperty("mode", mode.getValue());
                    session.addProperty("voice", TTSRealtimeClient.this.voice);
                    // To use the instruction control feature, uncomment the lines below and replace the model with qwen3-tts-instruct-flash-realtime
                    // session.addProperty("instructions", "Speak quickly with a noticeable rising intonation, suitable for introducing fashion products.");
                    // session.addProperty("optimize_instructions", true);
                    session.addProperty("language_type", languageType);
                    session.addProperty("response_format", "pcm");
                    session.addProperty("sample_rate", 24000);
                    updateSession(session);
                }
    
                @Override
                public void onMessage(String message) {
                    JsonObject event = gson.fromJson(message, JsonObject.class);
                    String eventType = event.has("type") ? event.get("type").getAsString() : "";
    
                    if (!"response.audio.delta".equals(eventType)) {
                        System.out.println("Received event: " + eventType);
                    }
    
                    switch (eventType) {
                        case "error":
                            System.err.println("Error: " + event.get("error"));
                            break;
                        case "session.created":
                            System.out.println("Session created, ID: " +
                                event.getAsJsonObject("session").get("id").getAsString());
                            break;
                        case "session.updated":
                            System.out.println("Session updated, ID: " +
                                event.getAsJsonObject("session").get("id").getAsString());
                            break;
                        case "input_text_buffer.committed":
                            System.out.println("Text buffer committed, item ID: " + event.get("item_id"));
                            break;
                        case "input_text_buffer.cleared":
                            System.out.println("Text buffer cleared");
                            break;
                        case "response.created":
                            System.out.println("Response created, ID: " +
                                event.getAsJsonObject("response").get("id").getAsString());
                            responseDoneLatch = new CountDownLatch(1);
                            break;
                        case "response.output_item.added":
                            System.out.println("Output item added, ID: " +
                                event.getAsJsonObject("item").get("id").getAsString());
                            break;
                        case "response.audio.delta":
                            if (audioCallback != null) {
                                byte[] audioBytes = Base64.getDecoder().decode(
                                    event.get("delta").getAsString());
                                audioCallback.onAudio(audioBytes);
                            }
                            break;
                        case "response.audio.done":
                            System.out.println("Audio generation completed");
                            break;
                        case "response.done":
                            System.out.println("Response completed");
                            responseDoneLatch.countDown();
                            break;
                        case "session.finished":
                            System.out.println("Session finished");
                            sessionFinishedLatch.countDown();
                            break;
                    }
                }
    
                @Override
                public void onClose(int code, String reason, boolean remote) {
                    System.out.println("Connection closed: " + reason);
                }
    
                @Override
                public void onError(Exception ex) {
                    System.err.println("WebSocket error: " + ex.getMessage());
                }
            };
            ws.connectBlocking();
        }
    
        /**
         * Send an event to the server.
         */
        public void sendEvent(JsonObject event) {
            String eventId = "event_" + System.currentTimeMillis();
            event.addProperty("event_id", eventId);
            System.out.println("Sending event: type=" + event.get("type").getAsString()
                + ", event_id=" + eventId);
            ws.send(gson.toJson(event));
        }
    
        /**
         * Update the session configuration.
         */
        public void updateSession(JsonObject config) {
            JsonObject event = new JsonObject();
            event.addProperty("type", "session.update");
            event.add("session", config);
            System.out.println("Updating session configuration: " + event);
            sendEvent(event);
        }
    
        /**
         * Send text data to the API.
         */
        public void appendText(String text) {
            JsonObject event = new JsonObject();
            event.addProperty("type", "input_text_buffer.append");
            event.addProperty("text", text);
            sendEvent(event);
        }
    
        /**
         * Commit the text buffer to trigger processing.
         */
        public void commitTextBuffer() {
            JsonObject event = new JsonObject();
            event.addProperty("type", "input_text_buffer.commit");
            sendEvent(event);
        }
    
        /**
         * Clear the text buffer.
         */
        public void clearTextBuffer() {
            JsonObject event = new JsonObject();
            event.addProperty("type", "input_text_buffer.clear");
            sendEvent(event);
        }
    
        /**
         * End the session.
         */
        public void finishSession() {
            JsonObject event = new JsonObject();
            event.addProperty("type", "session.finish");
            sendEvent(event);
        }
    
        /**
         * Wait for the response.done event.
         */
        public void waitForResponseDone() throws InterruptedException {
            responseDoneLatch.await();
        }
    
        /**
         * Wait for the session.finished event.
         */
        public void waitForSessionFinished() throws InterruptedException {
            sessionFinishedLatch.await();
        }
    
        /**
         * Close the WebSocket connection.
         */
        public void close() {
            if (ws != null) {
                ws.close();
            }
        }
    }
    
  2. Selecione um modo de síntese de fala

    A Realtime API oferece suporte a dois modos:

    • Modo server_commit

      O servidor gerencia automaticamente a segmentação de texto e o tempo de síntese. O cliente apenas envia o texto. Ideal para cenários de baixa latência, como navegação GPS.

    • Modo commit

      O cliente adiciona texto a um buffer e aciona explicitamente a síntese. Recomendado para casos que exigem controle preciso da segmentação de frases, como transmissões de notícias.

    Modo server_commit

    Python

    No mesmo diretório do arquivo tts_realtime_client.py, crie outro arquivo Python chamado server_commit.py e copie o código abaixo para ele:

    import os
    import asyncio
    import logging
    import wave
    from tts_realtime_client import TTSRealtimeClient, SessionMode
    import pyaudio
    
    # QwenTTS service configuration
    # To use the instruction control feature, replace the model with qwen3-tts-instruct-flash-realtime and uncomment instructions in tts_realtime_client.py
    # The following is the configuration for the Singapore region.
    URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"
    # The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    # If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: API_KEY="sk-xxx"
    API_KEY = os.getenv("DASHSCOPE_API_KEY")
    
    if not API_KEY:
        raise ValueError("Please set DASHSCOPE_API_KEY environment variable")
    
    # Collect audio data
    _audio_chunks = []
    # Real-time playback related
    _AUDIO_SAMPLE_RATE = 24000
    _audio_pyaudio = pyaudio.PyAudio()
    _audio_stream = None  # Will be opened at runtime
    
    def _audio_callback(audio_bytes: bytes):
        """TTSRealtimeClient audio callback: real-time playback and caching"""
        global _audio_stream
        if _audio_stream is not None:
            try:
                _audio_stream.write(audio_bytes)
            except Exception as exc:
                logging.error(f"PyAudio playback error: {exc}")
        _audio_chunks.append(audio_bytes)
        logging.info(f"Received audio chunk: {len(audio_bytes)} bytes")
    
    def _save_audio_to_file(filename: str = "output.wav", sample_rate: int = 24000) -> bool:
        """Save collected audio data as a WAV file"""
        if not _audio_chunks:
            logging.warning("No audio data to save")
            return False
    
        try:
            audio_data = b"".join(_audio_chunks)
            with wave.open(filename, 'wb') as wav_file:
                wav_file.setnchannels(1)  # Mono
                wav_file.setsampwidth(2)  # 16-bit
                wav_file.setframerate(sample_rate)
                wav_file.writeframes(audio_data)
            logging.info(f"Audio saved to: {filename}")
            return True
        except Exception as exc:
            logging.error(f"Failed to save audio: {exc}")
            return False
    
    async def _produce_text(client: TTSRealtimeClient):
        """Send text fragments to the server"""
        text_fragments = [
            "Alibaba Cloud's large language model platform, Model Studio, is an all-in-one platform for developing and building large language model applications.",
            "Both developers and business users can deeply participate in the design and development of large language model applications.",
            "You can develop a large language model application in five minutes using a simple interface,",
            "or train a dedicated model in a few hours, allowing you to focus more energy on application innovation.",
        ]
    
        logging.info("Sending text fragments…")
        for text in text_fragments:
            logging.info(f"Sending fragment: {text}")
            await client.append_text(text)
            await asyncio.sleep(0.1)  # Brief delay between fragments
    
        # Wait for the server to finish internal processing before ending the session
        await asyncio.sleep(1.0)
        await client.finish_session()
    
    async def _run_demo():
        """Run the complete demo"""
        global _audio_stream
        # Open PyAudio output stream
        _audio_stream = _audio_pyaudio.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=_AUDIO_SAMPLE_RATE,
            output=True,
            frames_per_buffer=1024
        )
    
        client = TTSRealtimeClient(
            base_url=URL,
            api_key=API_KEY,
            voice="Cherry",
            mode=SessionMode.SERVER_COMMIT,
            audio_callback=_audio_callback
        )
    
        # Establish connection
        await client.connect()
    
        # Run message handling and text sending in parallel
        consumer_task = asyncio.create_task(client.handle_messages())
        producer_task = asyncio.create_task(_produce_text(client))
    
        await producer_task  # Wait for text sending to complete
    
        # Wait for response.done
        await client.wait_for_response_done()
    
        # Close connection and cancel consumer task
        await client.close()
        consumer_task.cancel()
    
        # Close audio stream
        if _audio_stream is not None:
            _audio_stream.stop_stream()
            _audio_stream.close()
        _audio_pyaudio.terminate()
    
        # Save audio data
        os.makedirs("outputs", exist_ok=True)
        _save_audio_to_file(os.path.join("outputs", "qwen_tts_output.wav"))
    
    def main():
        """Synchronous entry point"""
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s [%(levelname)s] %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        logging.info("Starting QwenTTS Realtime Client demo…")
        asyncio.run(_run_demo())
    
    if __name__ == "__main__":
        main()
    

    Execute o arquivo server_commit.py para ouvir em tempo real o áudio gerado pela Realtime API.

    Java

    No mesmo diretório do arquivo TTSRealtimeClient.java, crie outro arquivo Java chamado ServerCommit.java e copie o código abaixo para ele:

    import javax.sound.sampled.*;
    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    public class ServerCommit {
        // The following is the configuration for the Singapore region.
        private static final String URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime";
        // The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
        // If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: private static final String API_KEY = "sk-xxx";
        private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY");
        private static final int SAMPLE_RATE = 24000;
    
        // Audio data cache
        private static final List<byte[]> audioChunks = new ArrayList<>();
        // Real-time playback queue
        private static final ConcurrentLinkedQueue<byte[]> playbackQueue = new ConcurrentLinkedQueue<>();
        private static final AtomicBoolean playing = new AtomicBoolean(true);
    
        public static void main(String[] args) throws Exception {
            if (API_KEY == null || API_KEY.isEmpty()) {
                throw new IllegalStateException("Please set the DASHSCOPE_API_KEY environment variable");
            }
    
            // Initialize audio playback
            AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false);
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
            SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info);
            audioLine.open(format);
            audioLine.start();
    
            // Start the playback thread
            Thread playerThread = new Thread(() -> {
                while (playing.get() || !playbackQueue.isEmpty()) {
                    byte[] chunk = playbackQueue.poll();
                    if (chunk != null) {
                        audioLine.write(chunk, 0, chunk.length);
                    } else {
                        try { Thread.sleep(10); } catch (InterruptedException ignored) {}
                    }
                }
            });
            playerThread.start();
    
            // Create the TTS client
            // To use the instruction control feature, replace the model with qwen3-tts-instruct-flash-realtime and uncomment the instructions in TTSRealtimeClient.java
            TTSRealtimeClient client = new TTSRealtimeClient(
                URL, API_KEY, "Cherry",
                TTSRealtimeClient.SessionMode.SERVER_COMMIT,
                audioData -> {
                    playbackQueue.add(audioData);
                    audioChunks.add(audioData);
                    System.out.println("Received audio data: " + audioData.length + " bytes");
                }
            );
    
            client.connect();
    
            // Send text fragments
            String[] textFragments = {
                "Alibaba Cloud's large language model platform, Model Studio, is an all-in-one platform for developing and building large language model applications.",
                "Both developers and business users can deeply participate in the design and development of large language model applications.",
                "You can develop a large language model application in five minutes using a simple interface,",
                "or train a dedicated model in a few hours, allowing you to focus more energy on application innovation."
            };
    
            System.out.println("Starting to send text...");
            for (String text : textFragments) {
                System.out.println("Sending fragment: " + text);
                client.appendText(text);
                Thread.sleep(100);
            }
    
            Thread.sleep(1000);
            client.finishSession();
    
            // Wait for the response to complete
            client.waitForResponseDone();
            client.waitForSessionFinished();
            client.close();
    
            // Wait for playback to complete
            playing.set(false);
            playerThread.join();
            audioLine.drain();
            audioLine.close();
    
            // Save the audio file
            saveWav("output.wav");
            System.out.println("Done");
        }
    
        private static void saveWav(String filename) throws IOException {
            if (audioChunks.isEmpty()) {
                System.out.println("No audio data to save");
                return;
            }
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            for (byte[] chunk : audioChunks) {
                bos.write(chunk);
            }
            byte[] allAudio = bos.toByteArray();
            AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false);
            AudioInputStream ais = new AudioInputStream(
                new ByteArrayInputStream(allAudio), format, allAudio.length / 2);
            new File("outputs").mkdirs();
            AudioSystem.write(ais, AudioFileFormat.Type.WAVE,
                new File("outputs/" + filename));
            System.out.println("Audio saved to: outputs/" + filename);
        }
    }
    

    Compile e execute o arquivo ServerCommit.java para ouvir em tempo real o áudio gerado pela Realtime API.

    Modo commit

    Python

    No mesmo diretório do arquivo tts_realtime_client.py, crie outro arquivo Python chamado commit.py e copie o código abaixo para ele:

    import os
    import asyncio
    import logging
    import wave
    from tts_realtime_client import TTSRealtimeClient, SessionMode
    import pyaudio
    
    # QwenTTS service configuration
    # To use the instruction control feature, replace the model with qwen3-tts-instruct-flash-realtime and uncomment instructions in tts_realtime_client.py
    # The following is the configuration for the Singapore region.
    URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"
    # The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    # If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: API_KEY="sk-xxx"
    API_KEY = os.getenv("DASHSCOPE_API_KEY")
    
    if not API_KEY:
        raise ValueError("Please set DASHSCOPE_API_KEY environment variable")
    
    # Collect audio data
    _audio_chunks = []
    _AUDIO_SAMPLE_RATE = 24000
    _audio_pyaudio = pyaudio.PyAudio()
    _audio_stream = None
    
    def _audio_callback(audio_bytes: bytes):
        """TTSRealtimeClient audio callback: real-time playback and caching"""
        global _audio_stream
        if _audio_stream is not None:
            try:
                _audio_stream.write(audio_bytes)
            except Exception as exc:
                logging.error(f"PyAudio playback error: {exc}")
        _audio_chunks.append(audio_bytes)
        logging.info(f"Received audio chunk: {len(audio_bytes)} bytes")
    
    def _save_audio_to_file(filename: str = "output.wav", sample_rate: int = 24000) -> bool:
        """Save collected audio data as a WAV file"""
        if not _audio_chunks:
            logging.warning("No audio data to save")
            return False
    
        try:
            audio_data = b"".join(_audio_chunks)
            with wave.open(filename, 'wb') as wav_file:
                wav_file.setnchannels(1)  # Mono
                wav_file.setsampwidth(2)  # 16-bit
                wav_file.setframerate(sample_rate)
                wav_file.writeframes(audio_data)
            logging.info(f"Audio saved to: {filename}")
            return True
        except Exception as exc:
            logging.error(f"Failed to save audio: {exc}")
            return False
    
    async def _user_input_loop(client: TTSRealtimeClient):
        """Continuously get user input and send text. When user enters empty text, send a commit event and end the current session"""
        print("Enter text (press Enter directly to send a commit event and end the current session, press Ctrl+C or Ctrl+D to exit the program):")
    
        while True:
            try:
                user_text = input("> ")
                if not user_text:  # User input is empty
                    # Empty input is treated as end of a conversation: commit buffer -> end session -> break loop
                    logging.info("Empty input, sending commit event and ending current session")
                    await client.commit_text_buffer()
                    # Wait briefly for the server to process the commit, preventing premature session end that could lose audio
                    await asyncio.sleep(0.3)
                    await client.finish_session()
                    break  # Exit user input loop directly, no need to press Enter again
                else:
                    logging.info(f"Sending text: {user_text}")
                    await client.append_text(user_text)
    
            except EOFError:  # User pressed Ctrl+D
                break
            except KeyboardInterrupt:  # User pressed Ctrl+C
                break
    
        # End session
        logging.info("Ending session...")
    async def _run_demo():
        """Run the complete demo"""
        global _audio_stream
        # Open PyAudio output stream
        _audio_stream = _audio_pyaudio.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=_AUDIO_SAMPLE_RATE,
            output=True,
            frames_per_buffer=1024
        )
    
        client = TTSRealtimeClient(
            base_url=URL,
            api_key=API_KEY,
            voice="Cherry",
            mode=SessionMode.COMMIT,  # Changed to COMMIT mode
            audio_callback=_audio_callback
        )
    
        # Establish connection
        await client.connect()
    
        # Run message handling and user input in parallel
        consumer_task = asyncio.create_task(client.handle_messages())
        producer_task = asyncio.create_task(_user_input_loop(client))
    
        await producer_task  # Wait for user input to complete
    
        # Wait for response.done
        await client.wait_for_response_done()
    
        # Close connection and cancel consumer task
        await client.close()
        consumer_task.cancel()
    
        # Close audio stream
        if _audio_stream is not None:
            _audio_stream.stop_stream()
            _audio_stream.close()
        _audio_pyaudio.terminate()
    
        # Save audio data
        os.makedirs("outputs", exist_ok=True)
        _save_audio_to_file(os.path.join("outputs", "qwen_tts_output.wav"))
    
    def main():
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s [%(levelname)s] %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        logging.info("Starting QwenTTS Realtime Client demo…")
        asyncio.run(_run_demo())
    
    if __name__ == "__main__":
        main()
    

    Execute o arquivo commit.py. É possível inserir texto para síntese quantas vezes desejar. Pressione Enter sem digitar nenhum texto para ouvir, pelo alto-falante, o áudio retornado pela Realtime API.

    Java

    No mesmo diretório do arquivo TTSRealtimeClient.java, crie outro arquivo Java chamado Commit.java e copie o código abaixo para ele:

    import javax.sound.sampled.*;
    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Scanner;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    public class Commit {
        // The following is the configuration for the Singapore region.
        private static final String URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime";
        // The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
        // If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: private static final String API_KEY = "sk-xxx";
        private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY");
        private static final int SAMPLE_RATE = 24000;
    
        private static final List<byte[]> audioChunks = new ArrayList<>();
        private static final ConcurrentLinkedQueue<byte[]> playbackQueue = new ConcurrentLinkedQueue<>();
        private static final AtomicBoolean playing = new AtomicBoolean(true);
    
        public static void main(String[] args) throws Exception {
            if (API_KEY == null || API_KEY.isEmpty()) {
                throw new IllegalStateException("Please set the DASHSCOPE_API_KEY environment variable");
            }
    
            // Initialize audio playback
            AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false);
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
            SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info);
            audioLine.open(format);
            audioLine.start();
    
            // Start the playback thread
            Thread playerThread = new Thread(() -> {
                while (playing.get() || !playbackQueue.isEmpty()) {
                    byte[] chunk = playbackQueue.poll();
                    if (chunk != null) {
                        audioLine.write(chunk, 0, chunk.length);
                    } else {
                        try { Thread.sleep(10); } catch (InterruptedException ignored) {}
                    }
                }
            });
            playerThread.start();
    
            // Create a TTS client (commit mode)
            // To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime and uncomment the instructions in TTSRealtimeClient.java
            TTSRealtimeClient client = new TTSRealtimeClient(
                URL, API_KEY, "Cherry",
                TTSRealtimeClient.SessionMode.COMMIT,
                audioData -> {
                    playbackQueue.add(audioData);
                    audioChunks.add(audioData);
                    System.out.println("Received audio data: " + audioData.length + " bytes");
                }
            );
    
            client.connect();
    
            // Interactive input
            System.out.println("Enter text (press Enter directly to send a commit event and end the session, press Ctrl+D to exit the program):");
            Scanner scanner = new Scanner(System.in);
            while (true) {
                System.out.print("> ");
                if (!scanner.hasNextLine()) {
                    client.finishSession();
                    break;
                }
                String userText = scanner.nextLine();
                if (userText.isEmpty()) {
                    // Empty input: commit the buffer and end the session
                    System.out.println("Empty input, sending commit event and ending the session");
                    client.commitTextBuffer();
                    Thread.sleep(300);
                    client.finishSession();
                    break;
                } else {
                    System.out.println("Sending text: " + userText);
                    client.appendText(userText);
                }
            }
            scanner.close();
    
            // Wait for the response to complete
            client.waitForResponseDone();
            client.waitForSessionFinished();
            client.close();
    
            // Wait for playback to complete
            playing.set(false);
            playerThread.join();
            audioLine.drain();
            audioLine.close();
    
            // Save the audio file
            saveWav("output.wav");
            System.out.println("Done");
        }
    
        private static void saveWav(String filename) throws IOException {
            if (audioChunks.isEmpty()) {
                System.out.println("No audio data to save");
                return;
            }
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            for (byte[] chunk : audioChunks) {
                bos.write(chunk);
            }
            byte[] allAudio = bos.toByteArray();
            AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false);
            AudioInputStream ais = new AudioInputStream(
                new ByteArrayInputStream(allAudio), format, allAudio.length / 2);
            new File("outputs").mkdirs();
            AudioSystem.write(ais, AudioFileFormat.Type.WAVE,
                new File("outputs/" + filename));
            System.out.println("Audio saved to: outputs/" + filename);
        }
    }
    

    Compile e execute o arquivo Commit.java. É possível inserir texto para síntese quantas vezes desejar. Pressione Enter sem digitar nenhum texto para ouvir, pelo alto-falante, o áudio retornado pela Realtime API.

Aplicação em produção

Reutilização de conexão (WebSocket)

As conexões WebSocket são reutilizáveis. Após a conclusão de uma tarefa de síntese, inicie a próxima tarefa na mesma conexão sem precisar restabelecê-la.

Processo de reutilização:

  • Qwen-Audio-TTS / Qwen-Audio-TTS/CosyVoice: O cliente envia finish-task e, após o servidor retornar task-finished, o cliente pode enviar run-task para iniciar uma nova tarefa.
  • Qwen-TTS: O cliente envia session.finish e, depois que o servidor retorna session.finished, o cliente pode criar uma nova sessão para começar a próxima tarefa.

Reutilização após cancelamento: Para Qwen-Audio-TTS / Qwen-Audio-TTS/CosyVoice, caso você cancele a tarefa atual com a diretiva cancel, também é possível enviar um novo run-task na mesma conexão assim que o servidor retornar task-finished. Para mais detalhes, consulte Cancel task.

Importante

  1. Aguarde o evento de conclusão do servidor (task-finished ou session.finished) antes de iniciar uma nova tarefa.
  2. Qwen-Audio-TTS, Qwen-Audio-TTS/CosyVoice exigem um task_id diferente para cada tarefa em uma conexão reutilizada.
  3. Se uma tarefa falhar, o servidor retorna um evento de erro e encerra a conexão, impedindo sua reutilização.
  4. Caso nenhuma nova tarefa seja iniciada dentro de 60 segundos após o término da anterior, a conexão será fechada automaticamente.

Para obter detalhes sobre os eventos de cada modelo, consulte o API reference correspondente.

Limites de taxa

As chamadas aos modelos estão sujeitas a limites de taxa. Quando um limite é excedido, o servidor retorna o erro Requests rate limit exceeded, please try again later. Reduza a taxa de requisições ou a concorrência e tente novamente.

Para verificar os limites de taxa de cada modelo, consulte Rate limiting.

Melhores práticas para alta concorrência

O DashScope SDK possui pooling integrado que reutiliza conexões WebSocket e objetos sintetizadores, eliminando a sobrecarga de criá-los e destruí-los repetidamente.

Visualizar melhores práticas para alta concorrência

Qwen-Audio-TTS/CosyVoice

Qwen-Audio-TTS e Qwen-Audio-TTS/CosyVoice compartilham a mesma interface do SDK. Os exemplos abaixo também se aplicam aos modelos Qwen-Audio-TTS — basta substituir os parâmetros model e voice.

Pré-requisitos

Python SDK

O Python SDK utiliza SpeechSynthesizerObjectPool para gerenciar e reutilizar objetos SpeechSynthesizer.

Esse pool cria um número especificado de instâncias SpeechSynthesizer e estabelece conexões WebSocket durante a inicialização. Ao solicitar um objeto emprestado, ele já está pronto para enviar requisições imediatamente, reduzindo a latência do primeiro pacote. Após a devolução do objeto, a conexão permanece ativa para a próxima tarefa.

Etapas de implementação

  1. Instale as dependências: Instale a dependência do DashScope (pip install -U dashscope).

  2. Crie e configure o pool de objetos

    Defina o tamanho do pool entre 1,5x e 2x a concorrência de pico, sem exceder o limite de QPS da sua conta.

    Crie um pool singleton global (o estabelecimento da conexão durante a inicialização leva algum tempo):

from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool

synthesizer_object_pool = SpeechSynthesizerObjectPool(max_size=20)
import dashscope
# The following is the configuration for the China (Beijing) region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
dashscope.base_http_api_url = "https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1"

Importante

  • No cenário de pool de objetos, SpeechSynthesizerObjectPool estabelece conexões WebSocket com o servidor usando a dashscope.api_key global atual durante a inicialização. A chave de API é gravada no cabeçalho Authorization apenas durante o handshake WebSocket para autenticação. Mensagens subsequentes da tarefa (como run-task) não carregam a chave de API. Modificar dashscope.api_key após a criação do pool não afeta as conexões existentes — objetos obtidos via borrow_synthesizer (incluindo aqueles devolvidos e solicitados novamente) ainda usam a chave de API do handshake original. O novo valor é ignorado silenciosamente, o que pode fazer com que a identidade, cota ou atribuição de faturamento difira do esperado. Nota: borrow_synthesizer não suporta a especificação de uma chave de API como parâmetro.
  • Para usar múltiplas chaves de API, mantenha uma instância separada de SpeechSynthesizerObjectPool para cada chave.
  1. Solicite um objeto SpeechSynthesizer do pool

    Se a quantidade de objetos não devolvidos exceder a capacidade do pool, o sistema criará objetos adicionais.

    Esses objetos extras precisam estabelecer novas conexões e não se beneficiam do pooling.

speech_synthesizer = connectionPool.borrow_synthesizer(
    model='cosyvoice-v3-flash',
    voice='longanyang',
    seed=12382,
    callback=synthesizer_callback
)
  1. Execute a síntese de fala

    Chame o método call ou streaming_call do objeto SpeechSynthesizer para sintetizar a fala.

  2. Devolva o objeto SpeechSynthesizer

    Devolva o objeto após a conclusão da tarefa para disponibilizá-lo para reutilização.

    Não devolva objetos com tarefas incompletas ou falhas.

connectionPool.return_synthesizer(speech_synthesizer)
Código completo

ImportanteAntes de usar este código: SpeechSynthesizerObjectPool estabelece conexões WebSocket e realiza autenticação usando a dashscope.api_key global atual na inicialização. Modificar dashscope.api_key após a criação do pool não afeta as conexões existentes — o novo valor é ignorado silenciosamente. Para múltiplas chaves de API, mantenha uma instância de pool separada para cada chave. Para mais detalhes, consulte a nota importante acima.

# !/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
    '''
    # The API Key differs between the Singapore and Beijing regions. Get your 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_open 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__':
    # 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.
    # The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
    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()

Gerenciamento de recursos e tratamento de erros

  • Tarefa bem-sucedida: Após a conclusão normal de uma tarefa de síntese, chame connectionPool.return_synthesizer(speech_synthesizer) para devolver o objeto SpeechSynthesizer ao pool para reutilização.

    ImportanteNão devolva objetos SpeechSynthesizer com tarefas incompletas ou falhas.

  • Falha na tarefa: Se um erro interno do SDK ou uma exceção de lógica de negócio causar a interrupção da tarefa, feche a conexão WebSocket subjacente: speech_synthesizer.close()

  • Após a conclusão de todas as tarefas de síntese, encerre o pool: connectionPool.shutdown()

  • Quando ocorrer um erro TaskFailed no lado do servidor, nenhum tratamento adicional é necessário.

Java SDK

O Java SDK alcança desempenho ideal através da coordenação entre um pool de conexões integrado e um pool de objetos personalizado.

  • Pool de conexões: O pool de conexões OkHttp3 integrado ao SDK gerencia e reutiliza conexões WebSocket subjacentes, reduzindo a sobrecarga de handshake de rede. Este recurso é ativado por padrão.
  • Pool de objetos: Construído sobre commons-pool2, este pool mantém um conjunto de objetos SpeechSynthesizer pré-conectados. Solicitar um objeto do pool elimina a latência de configuração de conexão, reduzindo significativamente o atraso do primeiro pacote.

Etapas de implementação

  1. Adicione as dependências

    Inclua dashscope-sdk-java e commons-pool2 na configuração de dependências do seu projeto, conforme a ferramenta de build utilizada.

    Exemplos para Maven e Gradle:

    Maven

    1. Abra o arquivo pom.xml do seu projeto Maven.
    2. Adicione as seguintes dependências dentro da tag <dependencies>.
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>dashscope-sdk-java</artifactId>
        <!-- Replace 'the-latest-version' with version 2.16.9 or later. Check available versions at: https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
        <version>the-latest-version</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <!-- Replace 'the-latest-version' with the latest version. Check available versions at: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <version>the-latest-version</version>
    </dependency>
    
    1. Salve o arquivo pom.xml.
    2. Execute um comando Maven como mvn clean install ou mvn compile para atualizar as dependências do projeto.

    Gradle

    1. Abra o arquivo build.gradle do seu projeto Gradle.
    2. Adicione as seguintes dependências dentro do bloco dependencies.
    dependencies {
        // Replace 'the-latest-version' with version 2.16.6 or later. Check available versions at: https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java
        implementation group: 'com.alibaba', name: 'dashscope-sdk-java', version: 'the-latest-version'
    
        // Replace 'the-latest-version' with the latest version. Check available versions at: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2
        implementation group: 'org.apache.commons', name: 'commons-pool2', version: 'the-latest-version'
    }
    
    1. Salve o arquivo build.gradle.
    2. No terminal, navegue até o diretório raiz do projeto e execute o seguinte comando Gradle para atualizar as dependências.
    ./gradlew build --refresh-dependencies
    

    No Windows, utilize o seguinte comando:

    gradlew build --refresh-dependencies
    
  2. Configure o pool de conexões

    Defina os principais parâmetros do pool de conexões por meio de variáveis de ambiente:

    Variável de ambiente

    Descrição

    DASHSCOPE_CONNECTION_POOL_SIZE

    Tamanho do pool de conexões.

    Valor recomendado: pelo menos 2x a concorrência de pico.

    Padrão: 32.

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS

    Número máximo de requisições assíncronas.

    Valor recomendado: igual a DASHSCOPE_CONNECTION_POOL_SIZE.

    Padrão: 32.

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST

    Número máximo de requisições assíncronas por host.

    Valor recomendado: igual a DASHSCOPE_CONNECTION_POOL_SIZE.

    Padrão: 32.

  3. Configure o pool de objetos

    Defina o tamanho do pool de objetos através de variáveis de ambiente:

    Variável de ambiente

    Descrição

    COSYVOICE_OBJECTPOOL_SIZE

    Tamanho do pool de objetos.

    Valor recomendado: 1,5x a 2x a concorrência de pico.

    Padrão: 500.

    Importante

    • O tamanho do pool de objetos (COSYVOICE_OBJECTPOOL_SIZE) deve ser menor ou igual ao tamanho do pool de conexões (DASHSCOPE_CONNECTION_POOL_SIZE). Caso contrário, quando o pool de objetos solicitar objetos e o pool de conexões estiver cheio, as threads de chamada serão bloqueadas aguardando conexões disponíveis.
    • O tamanho do pool de objetos não deve exceder o limite de QPS (consultas por segundo) da sua conta.

    Crie o pool de objetos com o seguinte código:

class CosyvoiceObjectPool {
    // ... Other code is omitted here. For the complete example, see the full code.
    public static GenericObjectPool<SpeechSynthesizer> getInstance() {
        lock.lock();
        if (synthesizerPool == null) {
            // You can set the object pool size here, or set it in the environment variable COSYVOICE_OBJECTPOOL_SIZE.
            // It is recommended to set it to 1.5 to 2 times the maximum concurrent connections of the server.
            int objectPoolSize = getObjectivePoolSize();
            SpeechSynthesizerObjectFactory speechSynthesizerObjectFactory =
                    new SpeechSynthesizerObjectFactory();
            GenericObjectPoolConfig<SpeechSynthesizer> config =
                    new GenericObjectPoolConfig<>();
            config.setMaxTotal(objectPoolSize);
            config.setMaxIdle(objectPoolSize);
            config.setMinIdle(objectPoolSize);
            synthesizerPool =
                    new GenericObjectPool<>(speechSynthesizerObjectFactory, config);
        }
        lock.unlock();
        return synthesizerPool;
    }
}
  1. Solicite um objeto SpeechSynthesizer do pool

    Se o número de objetos não devolvidos exceder a capacidade máxima do pool, o sistema criará um objeto SpeechSynthesizer adicional.

    Esses objetos recém-criados exigem reinicialização e uma nova conexão WebSocket, portanto não se beneficiam do pooling.

synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();
  1. Execute a síntese de fala

    Após obter um objeto SpeechSynthesizer do pool, chame updateParamAndCallback(param, callback) para vincular os parâmetros e o callback da tarefa atual e, em seguida, chame streamingCall ou call para sintetizar a fala.

    Importante

    • No cenário de pool de objetos, updateParamAndCallback é chamado várias vezes (uma vez sempre que um objeto é solicitado, para definir o callback e parâmetros de nível de tarefa como voice e format). A apiKey passada em cada chamada deve permanecer a mesma. updateParamAndCallback apenas atualiza os campos locais da instância SpeechSynthesizer atual e não reconstrói a conexão WebSocket subjacente. O SDK grava a apiKey no cabeçalho Authorization somente durante o handshake WebSocket para autenticação. Mensagens subsequentes da tarefa (como run-task) não carregam a apiKey. Desde que a conexão reutilizada não seja desconectada, passar uma nova apiKey não a enviará ao servidor — a requisição ainda usará a apiKey do handshake inicial, o que pode fazer com que a identidade, cota ou atribuição de faturamento difira do esperado.
    • Para usar múltiplas chaves de API, mantenha instâncias de pool de objetos separadas para cada chave.
  2. Devolva o objeto SpeechSynthesizer

    Após a conclusão da tarefa de síntese, devolva o objeto SpeechSynthesizer para que tarefas subsequentes possam reutilizá-lo.

    Não devolva objetos com tarefas incompletas ou falhas.

CosyvoiceObjectPool.getInstance().returnObject(synthesizer);
Código completo

ImportanteAntes de usar este código: no cenário de pool de objetos, a apiKey passada para updateParamAndCallback em múltiplas chamadas deve permanecer a mesma — o SDK não atualiza a API key de conexões já estabelecidas, e passar uma API key diferente não surte efeito. Para utilizar múltiplas API keys, mantenha uma instância separada de pool de objetos para cada chave. Para mais detalhes, consulte a nota importante acima.

import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * You need to include the org.apache.commons.pool2 and DashScope packages in your project.
 *
 * DashScope SDK 2.16.6 and later versions are optimized for high-concurrency scenarios.
 * Versions earlier than DashScope SDK 2.16.6 are not recommended for high-concurrency use.
 *
 *
 * Before making high-concurrency calls to the TTS service,
 * please configure the connection pool parameters through the following environment variables.
 *
 * DASHSCOPE_MAXIMUM_ASYNC_REQUESTS
 * DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST
 * DASHSCOPE_CONNECTION_POOL_SIZE
 *
 */

class SpeechSynthesizerObjectFactory
        extends BasePooledObjectFactory<SpeechSynthesizer> {
    public SpeechSynthesizerObjectFactory() {
        super();
    }
    @Override
    public SpeechSynthesizer create() throws Exception {
        return new SpeechSynthesizer();
    }

    @Override
    public PooledObject<SpeechSynthesizer> wrap(SpeechSynthesizer obj) {
        return new DefaultPooledObject<>(obj);
    }
}

class CosyvoiceObjectPool {
    public static GenericObjectPool<SpeechSynthesizer> synthesizerPool;
    public static String COSYVOICE_OBJECTPOOL_SIZE_ENV = "COSYVOICE_OBJECTPOOL_SIZE";
    public static int DEFAULT_OBJECT_POOL_SIZE = 500;
    private static Lock lock = new java.util.concurrent.locks.ReentrantLock();
    public static int getObjectivePoolSize() {
        try {
            Integer n = Integer.parseInt(System.getenv(COSYVOICE_OBJECTPOOL_SIZE_ENV));
            System.out.println("Using Object Pool Size In Env: "+ n);
            return n;
        } catch (NumberFormatException e) {
            System.out.println("Using Default Object Pool Size: "+ DEFAULT_OBJECT_POOL_SIZE);
            return DEFAULT_OBJECT_POOL_SIZE;
        }
    }
    public static GenericObjectPool<SpeechSynthesizer> getInstance() {
        lock.lock();
        if (synthesizerPool == null) {
            // You can set the object pool size here or in the COSYVOICE_OBJECTPOOL_SIZE environment variable.
            // It is recommended to set it to 1.5 to 2 times your server's maximum concurrent connections.
            int objectPoolSize = getObjectivePoolSize();
            SpeechSynthesizerObjectFactory speechSynthesizerObjectFactory =
                    new SpeechSynthesizerObjectFactory();
            GenericObjectPoolConfig<SpeechSynthesizer> config =
                    new GenericObjectPoolConfig<>();
            config.setMaxTotal(objectPoolSize);
            config.setMaxIdle(objectPoolSize);
            config.setMinIdle(objectPoolSize);
            synthesizerPool =
                    new GenericObjectPool<>(speechSynthesizerObjectFactory, config);
        }
        lock.unlock();
        return synthesizerPool;
    }
}

class SynthesizeTaskWithCallback implements Runnable {
    String[] textArray;
    String requestId;
    long timeCost;
    public SynthesizeTaskWithCallback(String[] textArray) {
        this.textArray = textArray;
    }
    @Override
    public void run() {
        SpeechSynthesizer synthesizer = null;
        long startTime = System.currentTimeMillis();
        // if recv onError
        final boolean[] hasError = {false};
        try {
            class ReactCallback extends ResultCallback<SpeechSynthesisResult> {
                ReactCallback() {}

                @Override
                public void onEvent(SpeechSynthesisResult message) {
                    if (message.getAudioFrame() != null) {
                        try {
                            byte[] bytesArray = message.getAudioFrame().array();
                            System.out.println("Received audio, audio stream length: " + bytesArray.length);
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }

                @Override
                public void onComplete() {}

                @Override
                public void onError(Exception e) {
                    System.out.println(e.getMessage());
                    e.printStackTrace();
                    hasError[0] = true;
                }
            }

            SpeechSynthesisParam param =
                    SpeechSynthesisParam.builder()
                            .model("cosyvoice-v3-flash")
                            .voice("longanyang")
                            // The API Key differs between the Singapore and Beijing regions. Get your API Key: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
                            // If you have not configured the environment variable, replace the next line with your Chinese Model Studio API Key: .apiKey("sk-xxx")
                            .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                            .format(SpeechSynthesisAudioFormat
                                    .MP3_22050HZ_MONO_256KBPS) // Use PCM or MP3 for streaming synthesis
                            .build();

            try {
                synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();
                // Note: In an object pool scenario, the apiKey passed in multiple calls to updateParamAndCallback must always be the same. The SDK does not update the apiKey for an established connection, so passing a different apiKey will not take effect. See the important notes in the "Perform speech synthesis" step above.
                synthesizer.updateParamAndCallback(param, new ReactCallback());
                for (String text : textArray) {
                    synthesizer.streamingCall(text);
                }
                Thread.sleep(20);
                synthesizer.streamingComplete(60000);
                requestId = synthesizer.getLastRequestId();
            } catch (Exception e) {
                System.out.println("Exception e: " + e.toString());
                hasError[0] = true;
            }
        } catch (Exception e) {
            hasError[0] = true;
            throw new RuntimeException(e);
        }
        if (synthesizer != null) {
            try {
                if (hasError[0] == true) {
                    // If an exception occurs, close the connection and invalidate the object in the pool.
                    synthesizer.getDuplexApi().close(1000, "bye");
                    CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
                } else {
                    // If the task completes normally, return the object to the pool.
                    CosyvoiceObjectPool.getInstance().returnObject(synthesizer);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            long endTime = System.currentTimeMillis();
            timeCost = endTime - startTime;
            System.out.println("[Thread " + Thread.currentThread() + "] Speech synthesis task completed. Time cost: " + timeCost + " ms, RequestId " + requestId);
        }
    }
}

@Slf4j
public class SynthesizeTextToSpeechWithCallbackConcurrently {
    public static void checkoutEnv(String envName, int defaultSize) {
        if (System.getenv(envName) != null) {
            System.out.println("[ENV CHECK]: " + envName + " "
                    + System.getenv(envName));
        } else {
            System.out.println("[ENV CHECK]: " + envName
                    + " Using Default which is " + defaultSize);
        }
    }

    public static void main(String[] args)
            throws InterruptedException, NoApiKeyException {
        // The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
        Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
        // Check for connection pool env
        checkoutEnv("DASHSCOPE_CONNECTION_POOL_SIZE", 32);
        checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS", 32);
        checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST", 32);
        checkoutEnv(CosyvoiceObjectPool.COSYVOICE_OBJECTPOOL_SIZE_ENV, CosyvoiceObjectPool.DEFAULT_OBJECT_POOL_SIZE);

        int runTimes = 3;
        // Create the pool of SpeechSynthesis objects
        ExecutorService executorService = Executors.newFixedThreadPool(runTimes);

        for (int i = 0; i < runTimes; i++) {
            // Record the task submission time
            LocalDateTime submissionTime = LocalDateTime.now();
            executorService.submit(new SynthesizeTaskWithCallback(new String[] {
                    "Before my bed, moonlight shines bright,", "I wonder if it is frost on the ground,", "I raise my eyes to gaze at the bright moon,", "then bow my head, thinking of home."}));
        }

        // Shut down the ExecutorService and wait for all tasks to complete
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.MINUTES);
        System.exit(0);
    }
}

Configuração recomendada

A configuração abaixo baseia-se em testes executando exclusivamente o serviço de síntese de fala Qwen-Audio-TTS/CosyVoice em instâncias ECS do Alibaba Cloud com as especificações indicadas. Concorrência excessiva pode aumentar a latência no processamento das tarefas.

O termo "concorrência por máquina" refere-se à quantidade de tarefas de síntese Qwen-Audio-TTS/CosyVoice rodando simultaneamente, o que equivale ao número de threads de trabalho.

Especificações do ECS (Alibaba Cloud)

Concorrência máxima por máquina

Tamanho do pool de objetos

Tamanho do pool de conexões

4 vCPUs, 8 GiB

100

500

2000

8 vCPUs, 16 GiB

150

500

2000

16 vCPUs, 32 GiB

200

500

2000

Gerenciamento de recursos e tratamento de erros

  • Tarefa bem-sucedida: após a conclusão normal de uma tarefa de síntese, chame o método returnObject do GenericObjectPool para devolver o objeto SpeechSynthesizer ao pool e permitir sua reutilização.

    No código de exemplo, isso corresponde a CosyvoiceObjectPool.getInstance().returnObject(synthesizer)

    ImportanteNão devolva objetos SpeechSynthesizer cujas tarefas estejam incompletas ou tenham falhado.

  • Falha na tarefa: caso um erro interno do SDK ou uma exceção de lógica de negócio interrompa a tarefa, execute os passos a seguir:

    1. Feche a conexão WebSocket subjacente.
    2. Invalide o objeto no pool para impedir sua reutilização.
// In the current code, the corresponding content is as follows
// Close the connection
synthesizer.getDuplexApi().close(1000, "bye");
// Invalidate the synthesizer in the object pool when an exception occurs
CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
  • Quando ocorrer um erro TaskFailed no lado do servidor, nenhuma ação adicional é necessária.

Warmup e medição de latência

Ao avaliar o DashScope Java SDK quanto à latência de concorrência e desempenho, execute operações de warmup suficientes antes. Isso garante que as medições reflitam o desempenho em estado estacionário, e não a sobrecarga inicial de conexão.

Mecanismo de reutilização de conexões

O DashScope Java SDK utiliza um pool de conexões singleton global para gerenciar e reutilizar conexões WebSocket de forma eficiente, reduzindo a sobrecarga de criação e encerramento frequente de conexões em cargas de trabalho de alta concorrência.

Funcionamento desse mecanismo:

  • Criação sob demanda: o SDK não pré-cria conexões WebSocket na inicialização. As conexões são estabelecidas na primeira chamada.

  • Reutilização com limite de tempo: após a conclusão de uma requisição, a conexão permanece no pool por até 60 segundos para reutilização.

    • Se uma nova requisição chegar dentro de 60 segundos, a conexão existente é reutilizada, evitando a sobrecarga de handshake.
    • Caso a conexão fique ociosa por mais de 60 segundos, ela é fechada automaticamente para liberar recursos.
Importância do warmup

Nos cenários a seguir, o pool de conexões pode não ter conexões ativas reutilizáveis, forçando a criação de novas conexões:

  • A aplicação acabou de iniciar e ainda não fez nenhuma chamada.
  • O serviço ficou ocioso por mais de 60 segundos, e as conexões no pool expiraram e foram fechadas.

Nesses casos, a primeira requisição precisa concluir um handshake WebSocket completo (handshake TCP, negociação TLS, upgrade de protocolo), resultando em latência significativamente maior do que nas requisições subsequentes que reutilizam conexões. Sem warmup, os resultados dos testes de desempenho ficam distorcidos por essa sobrecarga inicial de conexão.

Latência reportada pelo SDK vs. latência real do primeiro pacote

A latência do primeiro pacote reportada pelo SDK (por exemplo, o valor de get_first_package_delay()) inclui o tempo de estabelecimento da conexão WebSocket e de transmissão pela rede, não correspondendo à latência real do primeiro pacote do serviço de modelo.

A latência real do primeiro pacote é o intervalo entre o momento em que o servidor recebe a instrução run-task e o momento em que retorna o primeiro evento result-generated. Esse valor pode ser encontrado nos logs do lado do servidor.

Em cenários de alta concorrência, devido à criação simultânea de conexões e ao agendamento de recursos, a latência reportada pelo SDK pode ser significativamente maior que a latência real do primeiro pacote no servidor. Se você observar uma latência alta do primeiro pacote reportada pelo SDK:

  • Compare com a latência do primeiro pacote nos logs do servidor (de run-task até o primeiro result-generated) para verificar se o desempenho da inferência do modelo está normal.
  • Utilize o mecanismo de pool de objetos ou pool de conexões descrito acima para realizar o warmup. Isso elimina a sobrecarga do handshake WebSocket, aproximando a latência reportada pelo SDK da latência real do primeiro pacote.
Práticas recomendadas

Para obter dados de desempenho confiáveis, siga estas etapas de warmup antes de executar testes de carga ou coletar métricas de latência:

  1. Simule o nível de concorrência alvo e envie previamente uma quantidade de requisições de warmup (por exemplo, mantenha tráfego por 1 a 2 minutos) para preencher completamente o pool de conexões.
  2. Confirme que o pool de conexões estabeleceu e manteve conexões ativas suficientes antes de iniciar a coleta formal de dados de desempenho.

Um warmup adequado leva o pool de conexões do SDK a um estado estável de reutilização, produzindo métricas de latência que refletem com precisão o desempenho online em estado estacionário.

Exceções comuns do Java SDK

Exceção 1: Conexões TCP do servidor continuam aumentando apesar do tráfego estável

Causa raiz:Tipo 1:

Cada objeto do SDK cria uma conexão ao ser inicializado. Sem um pool de objetos, o objeto é destruído após a conclusão de cada tarefa. A conexão entra então em um estado sem referência e permanece aberta até que o servidor a encerre por timeout após 61 segundos. Durante esse período, a conexão não pode ser reutilizada.

Sob alta concorrência, novas tarefas que não encontram conexões reutilizáveis criam novas conexões, levando a:

  1. Aumento contínuo na contagem de conexões.
  2. Esgotamento de recursos do servidor e degradação de desempenho devido ao excesso de conexões.
  3. Saturação do pool de conexões, fazendo com que novas tarefas bloqueiem enquanto aguardam conexões disponíveis.
Tipo 2:

O MaxIdle do pool de objetos está configurado com um valor inferior ao MaxTotal, causando a destruição de objetos ociosos além do limite de MaxIdle, o que vaza suas conexões. Essas conexões vazadas precisam aguardar 61 segundos até o timeout antes de serem desconectadas, assim como no Tipo 1.

Solução:

Para o Tipo 1: utilize um pool de objetos.

Para o Tipo 2: verifique a configuração do pool de objetos e defina MaxIdle igual a MaxTotal. Desative a política automática de evicção de objetos.

Exceção 2: Tarefa leva 60 segundos a mais do que o normal

Mesma causa raiz da Exceção 1: o pool de conexões atingiu seu limite máximo de conexões, e novas tarefas precisam esperar 61 segundos até que conexões sem referência expirem antes que uma conexão fique disponível.

Exceção 3: Tarefas são lentas na inicialização do serviço, mas recuperam gradualmente

Causa raiz:

Sob alta concorrência, um único objeto reutiliza a mesma conexão WebSocket. Conexões WebSocket só são criadas na inicialização do serviço. Se a alta concorrência começar imediatamente no lançamento, a criação simultânea de muitas conexões WebSocket causa bloqueio.

Solução:

Aumente a concorrência gradualmente após iniciar o serviço ou adicione tarefas de warmup.

Exceção 4: Servidor reporta "Invalid action('run-task')! Please follow the protocol!"

Causa raiz:

Ocorreu um erro no lado do cliente, mas o servidor não o detectou. A conexão permanece em um estado de tarefa em andamento. Quando essa conexão e esse objeto são reutilizados para a próxima tarefa, o fluxo do protocolo é quebrado e a tarefa seguinte falha.

Solução:

Após o lançamento de uma exceção, feche a conexão WebSocket antes de devolver o objeto ao pool.

Exceção 5: Picos de tráfego apesar da carga de negócios estável

Causa raiz:

A criação simultânea de muitas conexões WebSocket causa bloqueio. Enquanto bloqueado, o tráfego de negócios recebido se acumula. Assim que o bloqueio é resolvido, todas as tarefas acumuladas são disparadas de uma vez, criando um pico de tráfego que pode exceder o limite de concorrência da sua conta, causando falhas nas tarefas ou degradação do servidor.

Gatilhos comuns para a criação simultânea de muitas conexões WebSocket:

  • Fase de inicialização do serviço
  • Anomalias de rede causando a desconexão e reconexão simultânea de muitas conexões WebSocket
  • Explosão de erros no lado do servidor causando reconexões em massa de WebSocket. Erros comuns incluem a ultrapassagem do limite de concorrência da conta ("Requests rate limit exceeded, please try again later.").

Solução:

  1. Verifique as condições da rede.
  2. Investigue se houve uma explosão de outros erros no lado do servidor antes do pico.
  3. Aumente o limite de concorrência da conta.
  4. Reduza os tamanhos do pool de objetos e do pool de conexões para limitar a concorrência máxima por meio dos limites do pool.
  5. Atualize as especificações do servidor ou adicione mais máquinas.

Exceção 6: Todas as tarefas ficam mais lentas conforme a concorrência aumenta

Solução:

  1. Verifique se a largura de banda da rede atingiu seu limite.
  2. Verifique se a concorrência real está alta demais.

Modelos e regiões suportados

Singapore

Para chamar os modelos a seguir, selecione uma API Key da região de Singapore:

  • Qwen-Audio-TTS: qwen-audio-3.0-tts-plus, qwen-audio-3.0-tts-flash

  • Qwen-Audio-TTS/CosyVoice: cosyvoice-v3-plus, cosyvoice-v3-flash

  • Qwen-TTS:
    • Qwen3-TTS-Instruct-Flash-Realtime: qwen3-tts-instruct-flash-realtime (estável, atualmente equivalente a qwen3-tts-instruct-flash-realtime-2026-01-22), qwen3-tts-instruct-flash-realtime-2026-01-22 (snapshot mais recente)
    • Qwen3-TTS-VD-Realtime: qwen3-tts-vd-realtime-2026-01-15 (snapshot mais recente), qwen3-tts-vd-realtime-2025-12-16 (snapshot)
    • Qwen3-TTS-VC-Realtime: qwen3-tts-vc-realtime-2026-01-15 (snapshot mais recente), qwen3-tts-vc-realtime-2025-11-27 (snapshot)
    • Qwen3-TTS-Flash-Realtime: qwen3-tts-flash-realtime (estável, atualmente equivalente a qwen3-tts-flash-realtime-2025-11-27), qwen3-tts-flash-realtime-2025-11-27 (snapshot mais recente), qwen3-tts-flash-realtime-2025-09-18 (snapshot)

China (Beijing)

Para chamar os modelos a seguir, selecione uma API Key da região de Beijing:

  • Qwen-Audio-TTS: qwen-audio-3.0-tts-plus, qwen-audio-3.0-tts-flash

  • Qwen-Audio-TTS/CosyVoice: cosyvoice-v3.5-plus, cosyvoice-v3.5-flash, cosyvoice-v3-plus, cosyvoice-v3-flash, cosyvoice-v2

  • Qwen-TTS:
    • Qwen3-TTS-Instruct-Flash-Realtime: qwen3-tts-instruct-flash-realtime (estável, atualmente equivalente a qwen3-tts-instruct-flash-realtime-2026-01-22), qwen3-tts-instruct-flash-realtime-2026-01-22 (snapshot mais recente)
    • Qwen3-TTS-VD-Realtime: qwen3-tts-vd-realtime-2026-01-15 (snapshot mais recente), qwen3-tts-vd-realtime-2025-12-16 (snapshot)
    • Qwen3-TTS-VC-Realtime: qwen3-tts-vc-realtime-2026-01-15 (snapshot mais recente), qwen3-tts-vc-realtime-2025-11-27 (snapshot)
    • Qwen3-TTS-Flash-Realtime: qwen3-tts-flash-realtime (estável, atualmente equivalente a qwen3-tts-flash-realtime-2025-11-27), qwen3-tts-flash-realtime-2025-11-27 (snapshot mais recente), qwen3-tts-flash-realtime-2025-09-18 (snapshot)
    • Qwen-TTS-Realtime: qwen-tts-realtime (estável, atualmente equivalente a qwen-tts-realtime-2025-07-15), qwen-tts-realtime-latest (mais recente, atualmente equivalente a qwen-tts-realtime-2025-07-15), qwen-tts-realtime-2025-07-15 (snapshot)

Vozes suportadas

Diferentes modelos suportam diferentes vozes. Defina o parâmetro de requisição voice com o valor indicado na coluna parâmetro voice da lista de vozes correspondente.

Referência da API

FAQ

P: Como corrigir pronúncia incorreta na síntese de fala? Como controlar a pronúncia de caracteres polifônicos?

  • Substitua o caractere polifônico por um homófono para corrigir rapidamente o problema de pronúncia.
  • Use marcação SSML para controlar a pronúncia .

P: Como solucionar áudio mudo ao usar uma voz clonada?

  1. Verifique o status da voz

    Chame a interface Voice cloning/design API e confirme se o status da voz é OK.

  2. Verifique a consistência da versão do modelo

    Certifique-se de que o parâmetro target_model usado durante a clonagem de voz corresponda ao parâmetro model usado para a síntese de fala. Por exemplo:

    • A clonagem utilizou cosyvoice-v3-plus
    • A síntese também deve utilizar cosyvoice-v3-plus
  3. Verifique a qualidade do áudio de origem

    Confira se o áudio de origem usado para clonagem de voz atende aos requisitos em Voice cloning/design API:

    • Duração do áudio: 10-20 segundos
    • Qualidade de áudio nítida
    • Sem ruído de fundo
  4. Verifique os parâmetros da requisição

    Confirme se o parâmetro voice na requisição de síntese de fala está definido com o ID da voz clonada.

P: O que fazer se o áudio sintetizado de uma voz clonada estiver instável ou incompleto?

Se o áudio sintetizado de uma voz clonada apresentar algum dos problemas a seguir:

  • Reprodução de áudio incompleta, com apenas parte do texto falado
  • Qualidade de síntese inconsistente
  • Áudio contém pausas anormais ou segmentos silenciosos

Possível causa: O áudio de origem não atende aos requisitos de qualidade.

Solução: Verifique se o áudio de origem atende aos requisitos em Recording guide for voice cloning. Regrave o áudio seguindo as diretrizes de gravação.

P: Por que a duração real do áudio sintetizado difere da duração mostrada no arquivo WAV?

A síntese de fala utiliza um mecanismo de streaming que retorna dados à medida que são gerados. A duração no cabeçalho do arquivo WAV salvo é uma estimativa e pode ser imprecisa. Para obter a duração exata, defina o formato como pcm, aguarde o resultado completo da síntese e adicione o cabeçalho do arquivo WAV manualmente.

P: Por que o arquivo de áudio não reproduz?

Faça a solução de problemas com base no seu cenário:

  1. Áudio salvo como arquivo completo (como xx.mp3)

    1. Consistência do formato de áudio: O formato de áudio nos parâmetros da requisição deve corresponder à extensão do arquivo (por exemplo, se o parâmetro for wav, o arquivo deve ser .wav).
    2. Compatibilidade do player: Confirme se o player suporta o formato de áudio e a taxa de amostragem.
  2. Reprodução de áudio em streaming

    1. Salve o fluxo de áudio como um arquivo completo e tente reproduzi-lo com um media player. Se o arquivo não reproduzir, consulte o cenário 1 acima.
    2. Se o arquivo reproduzir corretamente, o problema está na implementação da reprodução em streaming. Confirme se o player suporta reprodução em streaming (como ffmpeg, pyaudio, AudioFormat ou MediaSource).

P: Por que a reprodução do áudio está travando?

Faça a solução de problemas seguindo estas etapas:

  1. Verifique a taxa de envio de texto: Certifique-se de que o intervalo de envio seja razoável para evitar que o segmento de áudio anterior termine antes da chegada do próximo texto.

  2. Verifique o desempenho da função de callback:

    • Confirme que não existe lógica de bloqueio na função de callback.
    • Os callbacks são executados na thread WebSocket. Operações de bloqueio afetam o recebimento de dados. Escreva os dados de áudio em um buffer separado e processe-os em outra thread.
  3. Verifique a estabilidade da rede: Flutuações na rede podem causar interrupções ou atrasos na transmissão de áudio.

P: Por que a síntese de fala está demorando muito?

Faça a solução de problemas seguindo estas etapas:

  1. Verifique os intervalos de entrada

    Para síntese em streaming, verifique se o intervalo de envio de texto está muito longo. Intervalos longos aumentam o tempo total de síntese.

  2. Analise as métricas de desempenho

    • Latência do primeiro pacote: normalmente em torno de 500 ms.
    • RTF (Fator de Tempo Real = tempo total de síntese / duração do áudio): deve ser menor que 1,0.

P: Como restringir uma API key apenas ao service de síntese de fala (isolamento de permissões)?

Crie um novo workspace e conceda acesso apenas a modelos específicos. Isso limita o escopo da API key. Para mais detalhes, consulte Manage workspaces.