Todos os produtos
Search
Central de documentação

Alibaba Cloud Model Studio:Síntese de fala em tempo real

Última atualização: Jul 14, 2026

Converta texto em fala via WebSocket 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 em tempo real usando um protocolo de streaming bidirecional via WebSocket com baixa latência.

  • Entrada e saída em streaming com baixa latência no primeiro pacote

  • Controle refinado 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 saída com taxa de amostragem de até 48 kHz

  • Suporte a Controle por instruções, que permite controlar a expressividade da fala por meio de instruções em linguagem natural

  • Suporte a Clonagem de voz e Design de voz para criação de vozes personalizadas

  • Suporte a Tags de emoção e linguagem rica, que permitem inserir 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 a Síntese de fala não em tempo real. Para orientações sobre escolha de modelos, consulte Síntese de fala.

Pré-requisitos

Início rápido

Os exemplos a seguir demonstram a síntese de fala para cada modelo. Para mais exemplos e detalhes dos parâmetros, consulte a Referência da API.

Qwen-Audio-TTS

O exemplo abaixo sintetiza fala utilizando uma voz do sistema.

Para usar o recurso de Controle por instruções, defina as instruções por meio do parâmetro instruction.

Python

# 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 longanlingxi.
# 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 = "longanlingxi"

# 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)

Java

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 longanlingxi.
    // 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 = "longanlingxi";

    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

Importante

Os 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 (sem vozes do sistema). Antes de utilizá-los, crie uma voz personalizada por meio de Clonagem de voz ou Design de voz. Em seguida, defina voice com o ID da voz e model com o nome do modelo correspondente no seu código.

O exemplo a seguir sintetiza fala usando uma voz do sistema (consulte a Lista de vozes do CosyVoice).

Para utilizar o recurso de Controle por instruções, configure as instruções através do parâmetro instruction.

Python

# 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)

Java

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

Este exemplo sintetiza fala utilizando uma voz do sistema (consulte as Vozes suportadas).

Para habilitar o recurso de Controle por instruções, substitua model por qwen3-tts-instruct-flash-realtime e configure as instruções usando o parâmetro instructions.

Python

Server commit mode

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(),
                    ))

Commit mode

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, pois o cliente apenas adiciona o conteúdo sem precisar controlar 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 interações por turno em IA conversacional.

Alternar entre os 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 SDK Python e SDK Java. Para detalhes sobre o ciclo de vida de eventos WebSocket e reutilização de conexões, acesse a Referência da API WebSocket.

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 projetadas: 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 projetadas: não oferecem suporte ao controle por instruções.

    • Vozes do sistema: as instruções devem seguir um formato e conteúdo fixos. Consulte Lista de vozes do CosyVoice.

  • cosyvoice-v3-flash:

    • Vozes clonadas ou projetadas: aceitam qualquer instrução.

    • Vozes do sistema: as instruções devem seguir um formato e conteúdo fixos. Consulte Lista de vozes do CosyVoice.

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 projetadas: 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 projetadas: 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 Lista de vozes do CosyVoice.

  • cosyvoice-v3-flash:

    • Vozes clonadas ou projetadas: 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 outros 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 rápido". 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" é muito genérico 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 e enérgico" 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 descritivas:

    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, poderoso

    Caso de uso

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

  • Exemplos:

    • Estilo de locução padrão: articulação clara e precisa com pronúncia perfeita

    • Uma voz feminina jovem e animada, com ritmo acelerado e entonação ascendente marcante, ideal 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 intelectual e gentil, de cerca de 30 anos, com tom uniforme, apropriada para narração de audiolivros

    • Uma voz infantil fofa, aproximadamente de uma menina de 8 anos, falando com um leve tom 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 dialetos por modelo:

Qwen-Audio-TTS

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

    • Uma voz do sistema com suporte nativo a dialetos, que gera o dialeto correspondente sem configurações adicionais.

    • Uma voz compatível com Controle por instruções, configurável para gerar um dialeto específico por meio de texto de instrução.

  • Vozes clonadas: Configure através do recurso de Controle por instruções. 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: Escolha um dos seguintes tipos de voz na Lista de vozes do CosyVoice:

    • Uma voz do sistema com suporte nativo a dialetos (como longshange_v3), que gera o dialeto correspondente sem configurações adicionais.

    • Uma voz compatível com Controle por instruções e configurável para gerar um dialeto específico (como longanhuan_v3), definido via texto de instrução.

  • Vozes clonadas: Configure através do recurso de Controle por instruções. Por exemplo, defina o texto da instrução como 请用河南话表达.

  • Vozes projetadas: 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 dialetos. Consulte a lista de vozes do Qwen-TTS em Vozes suportadas.

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

  • Vozes projetadas: 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.

Importante

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

Limitação: Somente 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 seu tamanho.

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 e sombrio)

[bored]

Entediado

[tired]

Cansado

[singing]

Cantando

[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 nessa 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 um tom sério para a primeira frase, enquanto [excited] muda para um tom empolgado a partir da segunda frase.

Chamadas diretas ao protocolo WebSocket

Os exemplos a seguir demonstram como se conectar diretamente ao servidor usando o protocolo WebSocket nativo, 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

Instruções

  1. Leia o conteúdo em inglês para compreender O QUE precisa ser comunicado

  2. Escreva o texto em português do Brasil DO ZERO — esqueça a estrutura das frases em inglês

  3. Preserve toda a formatação markdown, blocos de código, links e imagens exatamente como estão

  4. Tags xref (<a data-tag="xref" ...>texto</a>) — preserve a tag INTEIRA com todos os atributos na ordem e caixa originais, traduza APENAS o texto visível entre > e

  5. Aplique todas as regras específicas do idioma rigorosamente

  6. Aplique as regras de stopwords com tolerância zero

  7. Use o modo imperativo em passos numerados e listas de procedimentos

  8. Garanta a consistência terminológica — o mesmo termo deve ter a mesma tradução em todo o documento

  9. Varie os inícios de frase em listas e tabelas — nenhum início deve se repetir 3 ou mais vezes

  10. Retorne APENAS o documento markdown em português do Brasil, sem explicações

Qwen-Audio-TTS/CosyVoice

O Qwen-Audio-TTS e o Qwen-Audio-TTS/CosyVoice compartilham o mesmo protocolo WebSocket. Os exemplos a seguir utilizam qwen-audio-3.0-tts-flash. Para usar o Qwen-Audio-TTS/CosyVoice, substitua o parâmetro model por um modelo do Qwen-Audio-TTS/CosyVoice (como cosyvoice-v3-flash) e defina o parâmetro voice com a voz desejada.

Instruções

  1. Leia o conteúdo em inglês para compreender O QUE precisa ser comunicado

  2. Escreva o texto em português do Brasil DO ZERO — esqueça a estrutura das frases em inglês

  3. Preserve toda a formatação markdown, blocos de código, links e imagens exatamente como estão

  4. Tags xref (<a data-tag="xref" ...>texto</a>) — preserve A TAG INTEIRA com todos os atributos na ordem e capitalização originais, traduza APENAS o texto visível entre > e

  5. Aplique todas as regras específicas do idioma rigorosamente

  6. Aplique as regras de stopwords com tolerância zero

  7. Use o modo imperativo em passos numerados e listas de procedimentos

  8. Garanta a consistência terminológica — o mesmo termo deve ter a mesma tradução em todo o documento

  9. Varie os inícios de frase em listas e tabelas — nenhum início deve se repetir 3 ou mais vezes

  10. Retorne APENAS o documento markdown em português do Brasil, sem explicações

    Go

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"net/http"
    	"os"
    	"strings"
    	"time"
    
    	"github.com/google/uuid"
    	"github.com/gorilla/websocket"
    )
    
    const (
    	// The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
    	wsURL      = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"
    	outputFile = "output.mp3"
    )
    
    func main() {
    	// 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 := os.Getenv("DASHSCOPE_API_KEY")
    
    	// Clear the output file
    	os.Remove(outputFile)
    	os.Create(outputFile)
    
    	// Connect to WebSocket
    	header := make(http.Header)
    	header.Add("X-DashScope-DataInspection", "enable")
    	header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey))
    
    	conn, resp, err := websocket.DefaultDialer.Dial(wsURL, header)
    	if err != nil {
    		if resp != nil {
    			fmt.Printf("Connection failed, HTTP status code: %d\n", resp.StatusCode)
    		}
    		fmt.Println("Connection failed:", err)
    		return
    	}
    	defer conn.Close()
    
    	// Generate task ID
    	taskID := uuid.New().String()
    	fmt.Printf("Generated task ID: %s\n", taskID)
    
    	// Sending run-task event
    	runTaskCmd := map[string]interface{}{
    		"header": map[string]interface{}{
    			"action":    "run-task",
    			"task_id":   taskID,
    			"streaming": "duplex",
    		},
    		"payload": map[string]interface{}{
    			"task_group": "audio",
    			"task":       "tts",
    			"function":   "SpeechSynthesizer",
    			"model":      "qwen-audio-3.0-tts-flash",
    			"parameters": map[string]interface{}{
    				"text_type":   "PlainText",
    				"voice":       "longanlingxi",
    				"format":      "mp3",
    				"sample_rate": 22050,
    				"volume":      50,
    				"rate":        1,
    				"pitch":       1,
    				// If enable_ssml is set to true, only one continue-task event can be sent; otherwise the error "Text request limit violated, expected 1." will be returned
    				"enable_ssml": false,
    			},
    			"input": map[string]interface{}{},
    		},
    	}
    
    	runTaskJSON, _ := json.Marshal(runTaskCmd)
    	fmt.Printf("Sending run-task event: %s\n", string(runTaskJSON))
    
    	err = conn.WriteMessage(websocket.TextMessage, runTaskJSON)
    	if err != nil {
    		fmt.Println("Failed to send run-task:", err)
    		return
    	}
    
    	textSent := false
    
    	// Process messages
    	for {
    		messageType, message, err := conn.ReadMessage()
    		if err != nil {
    			fmt.Println("Failed to read message:", err)
    			break
    		}
    
    		// Process binary message
    		if messageType == websocket.BinaryMessage {
    			fmt.Printf("Received binary message, length: %d\n", len(message))
    			file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
    			file.Write(message)
    			file.Close()
    			continue
    		}
    
    		// Process text message
    		messageStr := string(message)
    		fmt.Printf("Received text message: %s\n", strings.ReplaceAll(messageStr, "\n", ""))
    
    		// Parse JSON to get event type
    		var msgMap map[string]interface{}
    		if json.Unmarshal(message, &msgMap) == nil {
    			if header, ok := msgMap["header"].(map[string]interface{}); ok {
    				if event, ok := header["event"].(string); ok {
    					fmt.Printf("Event type: %s\n", event)
    
    					switch event {
    					case "task-started":
    						fmt.Println("=== Received task-started event ===")
    
    						if !textSent {
    							// Sending continue-task event
    
    							texts := []string{"Before my bed, moonlight shines bright, I suspect it's frost upon the ground.", "I raise my eyes to gaze at the bright moon, then bow my head, thinking of home."}
    
    							for _, text := range texts {
    								continueTaskCmd := map[string]interface{}{
    									"header": map[string]interface{}{
    										"action":    "continue-task",
    										"task_id":   taskID,
    										"streaming": "duplex",
    									},
    									"payload": map[string]interface{}{
    										"input": map[string]interface{}{
    											"text": text,
    										},
    									},
    								}
    
    								continueTaskJSON, _ := json.Marshal(continueTaskCmd)
    								fmt.Printf("Sending continue-task event: %s\n", string(continueTaskJSON))
    
    								err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON)
    								if err != nil {
    									fmt.Println("Failed to send continue-task:", err)
    									return
    								}
    							}
    
    							textSent = true
    
    							// Delay before sending finish-task
    							time.Sleep(500 * time.Millisecond)
    
    							// Sending finish-task event
    							finishTaskCmd := map[string]interface{}{
    								"header": map[string]interface{}{
    									"action":    "finish-task",
    									"task_id":   taskID,
    									"streaming": "duplex",
    								},
    								"payload": map[string]interface{}{
    									"input": map[string]interface{}{},
    								},
    							}
    
    							finishTaskJSON, _ := json.Marshal(finishTaskCmd)
    							fmt.Printf("Sending finish-task event: %s\n", string(finishTaskJSON))
    
    							err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON)
    							if err != nil {
    								fmt.Println("Failed to send finish-task:", err)
    								return
    							}
    						}
    
    					case "task-finished":
    						fmt.Println("=== Task completed ===")
    						return
    
    					case "task-failed":
    						fmt.Println("=== Task failed ===")
    						if header["error_message"] != nil {
    							fmt.Printf("Error message: %s\n", header["error_message"])
    						}
    						return
    
    					case "result-generated":
    						fmt.Println("Received result-generated event")
    					}
    				}
    			}
    		}
    	}
    }

    C#

    using System.Net.WebSockets;
    using System.Text;
    using System.Text.Json;
    
    class Program {
        // 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 readonly string ApiKey = "sk-xxx"
        private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");
    
        // The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
        private const string WebSocketUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/";
        // Output file path
        private const string OutputFilePath = "output.mp3";
    
        // WebSocket client
        private static ClientWebSocket _webSocket = new ClientWebSocket();
        // Cancellation token source
        private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
        // Task ID
        private static string? _taskId;
        // Whether the task has started
        private static TaskCompletionSource<bool> _taskStartedTcs = new TaskCompletionSource<bool>();
    
        static async Task Main(string[] args) {
            try {
                // Clear the output file
                ClearOutputFile(OutputFilePath);
    
                // Connect to the WebSocket service
                await ConnectToWebSocketAsync(WebSocketUrl);
    
                // Start the message receiving task
                Task receiveTask = ReceiveMessagesAsync();
    
                // Send the run-task event
                _taskId = GenerateTaskId();
                await SendRunTaskCommandAsync(_taskId);
    
                // Wait for the task-started event
                await _taskStartedTcs.Task;
    
                // Send continue-task events
                string[] texts = {
                    "Before my bed, moonlight shines bright,",
                    "I suspect it\'s frost upon the ground.",
                    "I raise my eyes to gaze at the bright moon,",
                    "then bow my head, thinking of home."
                };
                foreach (string text in texts) {
                    await SendContinueTaskCommandAsync(text);
                }
    
                // Send the finish-task event
                await SendFinishTaskCommandAsync(_taskId);
    
                // Wait for the receiving task to complete
                await receiveTask;
    
                Console.WriteLine("Task completed, connection closed.");
            } catch (OperationCanceledException) {
                Console.WriteLine("Task cancelled.");
            } catch (Exception ex) {
                Console.WriteLine($"Error occurred: {ex.Message}");
            } finally {
                _cancellationTokenSource.Cancel();
                _webSocket.Dispose();
            }
        }
    
        private static void ClearOutputFile(string filePath) {
            if (File.Exists(filePath)) {
                File.WriteAllText(filePath, string.Empty);
                Console.WriteLine("Output file cleared.");
            } else {
                Console.WriteLine("Output file does not exist, no need to clear.");
            }
        }
    
        private static async Task ConnectToWebSocketAsync(string url) {
            var uri = new Uri(url);
            if (_webSocket.State == WebSocketState.Connecting || _webSocket.State == WebSocketState.Open) {
                return;
            }
    
            // Set the WebSocket connection headers
            _webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}");
            _webSocket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable");
    
            try {
                await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token);
                Console.WriteLine("Successfully connected to the WebSocket service.");
            } catch (OperationCanceledException) {
                Console.WriteLine("WebSocket connection cancelled.");
            } catch (Exception ex) {
                Console.WriteLine($"WebSocket connection failed: {ex.Message}");
                throw;
            }
        }
    
        private static async Task SendRunTaskCommandAsync(string taskId) {
            var command = CreateCommand("run-task", taskId, "duplex", new {
                task_group = "audio",
                task = "tts",
                function = "SpeechSynthesizer",
                model = "qwen-audio-3.0-tts-flash",
                parameters = new
                {
                    text_type = "PlainText",
                    voice = "longanlingxi",
                    format = "mp3",
                    sample_rate = 22050,
                    volume = 50,
                    rate = 1,
                    pitch = 1,
                    // If enable_ssml is set to true, only one continue-task event can be sent; otherwise the error "Text request limit violated, expected 1." will be returned
                    enable_ssml = false
                },
                input = new { }
            });
    
            await SendJsonMessageAsync(command);
            Console.WriteLine("run-task event sent.");
        }
    
        private static async Task SendContinueTaskCommandAsync(string text) {
            if (_taskId == null) {
                throw new InvalidOperationException("Task ID not initialized.");
            }
    
            var command = CreateCommand("continue-task", _taskId, "duplex", new {
                input = new {
                    text
                }
            });
    
            await SendJsonMessageAsync(command);
            Console.WriteLine("continue-task event sent.");
        }
    
        private static async Task SendFinishTaskCommandAsync(string taskId) {
            var command = CreateCommand("finish-task", taskId, "duplex", new {
                input = new { }
            });
    
            await SendJsonMessageAsync(command);
            Console.WriteLine("finish-task event sent.");
        }
    
        private static async Task SendJsonMessageAsync(string message) {
            var buffer = Encoding.UTF8.GetBytes(message);
            try {
                await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
            } catch (OperationCanceledException) {
                Console.WriteLine("Message sending cancelled.");
            }
        }
    
        private static async Task ReceiveMessagesAsync() {
            while (_webSocket.State == WebSocketState.Open) {
                var response = await ReceiveMessageAsync();
                if (response != null) {
                    var eventStr = response.RootElement.GetProperty("header").GetProperty("event").GetString();
                    switch (eventStr) {
                        case "task-started":
                            Console.WriteLine("Task started.");
                            _taskStartedTcs.TrySetResult(true);
                            break;
                        case "task-finished":
                            Console.WriteLine("Task completed.");
                            _cancellationTokenSource.Cancel();
                            break;
                        case "task-failed":
                            Console.WriteLine("Task failed: " + response.RootElement.GetProperty("header").GetProperty("error_message").GetString());
                            _cancellationTokenSource.Cancel();
                            break;
                        default:
                            // result-generated can be handled here
                            break;
                    }
                }
            }
        }
    
        private static async Task<JsonDocument?> ReceiveMessageAsync() {
            var buffer = new byte[1024 * 4];
            var segment = new ArraySegment<byte>(buffer);
    
            try {
                WebSocketReceiveResult result = await _webSocket.ReceiveAsync(segment, _cancellationTokenSource.Token);
    
                if (result.MessageType == WebSocketMessageType.Close) {
                    await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cancellationTokenSource.Token);
                    return null;
                }
    
                if (result.MessageType == WebSocketMessageType.Binary) {
                    // Process binary data
                    Console.WriteLine("Received binary data...");
    
                    // Save the binary data to file
                    using (var fileStream = new FileStream(OutputFilePath, FileMode.Append)) {
                        fileStream.Write(buffer, 0, result.Count);
                    }
    
                    return null;
                }
    
                string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
                return JsonDocument.Parse(message);
            } catch (OperationCanceledException) {
                Console.WriteLine("Message receiving cancelled.");
                return null;
            }
        }
    
        private static string GenerateTaskId() {
            return Guid.NewGuid().ToString("N").Substring(0, 32);
        }
    
        private static string CreateCommand(string action, string taskId, string streaming, object payload) {
            var command = new {
                header = new {
                    action,
                    task_id = taskId,
                    streaming
                },
                payload
            };
    
            return JsonSerializer.Serialize(command);
        }
    }

    PHP

    Estrutura de diretórios do código de exemplo:

    my-php-project/

    ├── composer.json

    ├── vendor/

    └── index.php

    Conteúdo do arquivo composer.json (ajuste as versões das dependências conforme necessário):

    {
        "require": {
            "react/event-loop": "^1.3",
            "react/socket": "^1.11",
            "react/stream": "^1.2",
            "react/http": "^1.1",
            "ratchet/pawl": "^0.4"
        },
        "autoload": {
            "psr-4": {
                "App\\": "src/"
            }
        }
    }

    Conteúdo do arquivo index.php:

    <?php
    
    require __DIR__ . '/vendor/autoload.php';
    
    use Ratchet\Client\Connector;
    use React\EventLoop\Loop;
    use React\Socket\Connector as SocketConnector;
    
    // 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 = getenv("DASHSCOPE_API_KEY");
    // The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
    $websocket_url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/'; // WebSocket server URL
    $output_file = 'output.mp3'; // Output file path
    
    $loop = Loop::get();
    
    if (file_exists($output_file)) {
        // Clear file content
        file_put_contents($output_file, '');
    }
    
    // Create a custom connector
    $socketConnector = new SocketConnector($loop, [
        'tcp' => [
            'bindto' => '0.0.0.0:0',
        ],
        'tls' => [
            'verify_peer' => false,
            'verify_peer_name' => false,
        ],
    ]);
    
    $connector = new Connector($loop, $socketConnector);
    
    $headers = [
        'Authorization' => 'bearer ' . $api_key,
        'X-DashScope-DataInspection' => 'enable'
    ];
    
    $connector($websocket_url, [], $headers)->then(function ($conn) use ($loop, $output_file) {
        echo "Connected to the WebSocket server\n";
    
        // Generate task ID
        $taskId = generateTaskId();
    
        // Send the run-task event
        sendRunTaskMessage($conn, $taskId);
    
        // Define the function to send continue-task events
        $sendContinueTask = function() use ($conn, $loop, $taskId) {
            // Text to be sent
            $texts = ["Before my bed, moonlight shines bright,", "I suspect it\'s frost upon the ground.", "I raise my eyes to gaze at the bright moon,", "then bow my head, thinking of home."];
            $continueTaskCount = 0;
            foreach ($texts as $text) {
                $continueTaskMessage = json_encode([
                    "header" => [
                        "action" => "continue-task",
                        "task_id" => $taskId,
                        "streaming" => "duplex"
                    ],
                    "payload" => [
                        "input" => [
                            "text" => $text
                        ]
                    ]
                ]);
                echo "Sending continue-task event: " . $continueTaskMessage . "\n";
                $conn->send($continueTaskMessage);
                $continueTaskCount++;
            }
            echo "Number of continue-task events sent: " . $continueTaskCount . "\n";
    
            // Send the finish-task event
            sendFinishTaskMessage($conn, $taskId);
        };
    
        // Flag for whether the task-started event was received
        $taskStarted = false;
    
        // Listen for messages
        $conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) {
            if ($msg->isBinary()) {
                // Write binary data to local file
                file_put_contents($output_file, $msg->getPayload(), FILE_APPEND);
            } else {
                // Process non-binary message
                $response = json_decode($msg, true);
    
                if (isset($response['header']['event'])) {
                    handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted);
                } else {
                    echo "Unknown message format\n";
                }
            }
        });
    
        // Listen for connection close
        $conn->on('close', function($code = null, $reason = null) {
            echo "Connection closed\n";
            if ($code !== null) {
                echo "Close code: " . $code . "\n";
            }
            if ($reason !== null) {
                echo "Close reason: " . $reason . "\n";
            }
        });
    }, function ($e) {
        echo "Unable to connect: {$e->getMessage()}\n";
    });
    
    $loop->run();
    
    /**
     * Generate task ID
     * @return string
     */
    function generateTaskId(): string {
        return bin2hex(random_bytes(16));
    }
    
    /**
     * Send the run-task event
     * @param $conn
     * @param $taskId
     */
    function sendRunTaskMessage($conn, $taskId) {
        $runTaskMessage = json_encode([
            "header" => [
                "action" => "run-task",
                "task_id" => $taskId,
                "streaming" => "duplex"
            ],
            "payload" => [
                "task_group" => "audio",
                "task" => "tts",
                "function" => "SpeechSynthesizer",
                "model" => "qwen-audio-3.0-tts-flash",
                "parameters" => [
                    "text_type" => "PlainText",
                    "voice" => "longanlingxi",
                    "format" => "mp3",
                    "sample_rate" => 22050,
                    "volume" => 50,
                    "rate" => 1,
                    "pitch" => 1,
                    // If enable_ssml is set to true, only one continue-task event can be sent; otherwise the error "Text request limit violated, expected 1." will be returned
                    "enable_ssml" => false
                ],
                "input" => (object) []
            ]
        ]);
        echo "Sending run-task event: " . $runTaskMessage . "\n";
        $conn->send($runTaskMessage);
        echo "run-task event sent\n";
    }
    
    /**
     * Read audio file
     * @param string $filePath
     * @return bool|string
     */
    function readAudioFile(string $filePath) {
        $voiceData = file_get_contents($filePath);
        if ($voiceData === false) {
            echo "Unable to read audio file\n";
        }
        return $voiceData;
    }
    
    /**
     * Split audio data
     * @param string $data
     * @param int $chunkSize
     * @return array
     */
    function splitAudioData(string $data, int $chunkSize): array {
        return str_split($data, $chunkSize);
    }
    
    /**
     * Send the finish-task event
     * @param $conn
     * @param $taskId
     */
    function sendFinishTaskMessage($conn, $taskId) {
        $finishTaskMessage = json_encode([
            "header" => [
                "action" => "finish-task",
                "task_id" => $taskId,
                "streaming" => "duplex"
            ],
            "payload" => [
                "input" => (object) []
            ]
        ]);
        echo "Sending finish-task event: " . $finishTaskMessage . "\n";
        $conn->send($finishTaskMessage);
        echo "finish-task event sent\n";
    }
    
    /**
     * Handle events
     * @param $conn
     * @param $response
     * @param $sendContinueTask
     * @param $loop
     * @param $taskId
     * @param $taskStarted
     */
    function handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, &$taskStarted) {
        switch ($response['header']['event']) {
            case 'task-started':
                echo "Task started, sending continue-task events...\n";
                $taskStarted = true;
                // Send continue-task events
                $sendContinueTask();
                break;
            case 'result-generated':
                // Received result-generated event
                break;
            case 'task-finished':
                echo "Task completed\n";
                $conn->close();
                break;
            case 'task-failed':
                echo "Task failed\n";
                echo "Error code: " . $response['header']['error_code'] . "\n";
                echo "Error message: " . $response['header']['error_message'] . "\n";
                $conn->close();
                break;
            case 'error':
                echo "Error: " . $response['payload']['message'] . "\n";
                break;
            default:
                echo "Unknown event: " . $response['header']['event'] . "\n";
                break;
        }
    
        // If the task is completed, close the connection
        if ($response['header']['event'] == 'task-finished') {
            // Wait 1 second to ensure all data has been transmitted
            $loop->addTimer(1, function() use ($conn) {
                $conn->close();
                echo "Client closing connection\n";
            });
        }
    
        // If the task-started event has not been received, close the connection
        if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) {
            $conn->close();
        }
    }

    Node.js

    Instale as dependências necessárias:

    npm install ws
    npm install uuid

    Código de exemplo:

    const WebSocket = require('ws');
    const fs = require('fs');
    const uuid = require('uuid').v4;
    
    // 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: const apiKey = "sk-xxx"
    const apiKey = process.env.DASHSCOPE_API_KEY;
    // The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
    const url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/';
    // Output file path
    const outputFilePath = 'output.mp3';
    
    // Clear output file
    fs.writeFileSync(outputFilePath, '');
    
    // Create WebSocket client
    const ws = new WebSocket(url, {
      headers: {
        Authorization: `bearer ${apiKey}`,
        'X-DashScope-DataInspection': 'enable'
      }
    });
    
    let taskStarted = false;
    let taskId = uuid();
    
    ws.on('open', () => {
      console.log('Connected to the WebSocket server');
    
      // Send the run-task event
      const runTaskMessage = JSON.stringify({
        header: {
          action: 'run-task',
          task_id: taskId,
          streaming: 'duplex'
        },
        payload: {
          task_group: 'audio',
          task: 'tts',
          function: 'SpeechSynthesizer',
          model: 'qwen-audio-3.0-tts-flash',
          parameters: {
            text_type: 'PlainText',
            voice: 'longanlingxi', // Voice
            format: 'mp3', // Audio format
            sample_rate: 22050, // Sample rate
            volume: 50, // Volume
            rate: 1, // Speech rate
            pitch: 1, // Pitch
            enable_ssml: false // Whether to enable SSML. If enable_ssml is set to true, only one continue-task event can be sent; otherwise the error "Text request limit violated, expected 1." will be returned
          },
          input: {}
        }
      });
      ws.send(runTaskMessage);
      console.log('run-task message sent');
    });
    
    const fileStream = fs.createWriteStream(outputFilePath, { flags: 'a' });
    ws.on('message', (data, isBinary) => {
      if (isBinary) {
        // Write binary data to file
        fileStream.write(data);
      } else {
        const message = JSON.parse(data);
    
        switch (message.header.event) {
          case 'task-started':
            taskStarted = true;
            console.log('Task started');
            // Send continue-task events
            sendContinueTasks(ws);
            break;
          case 'task-finished':
            console.log('Task completed');
            ws.close();
            fileStream.end(() => {
              console.log('File stream closed');
            });
            break;
          case 'task-failed':
            console.error('Task failed: ', message.header.error_message);
            ws.close();
            fileStream.end(() => {
              console.log('File stream closed');
            });
            break;
          default:
            // result-generated can be handled here
            break;
        }
      }
    });
    
    function sendContinueTasks(ws) {
      const texts = [
        'Before my bed, moonlight shines bright,',
        'I suspect it is frost upon the ground.',
        'I raise my eyes to gaze at the bright moon,',
        'then bow my head, thinking of home.'
      ];
    
      texts.forEach((text, index) => {
        setTimeout(() => {
          if (taskStarted) {
            const continueTaskMessage = JSON.stringify({
              header: {
                action: 'continue-task',
                task_id: taskId,
                streaming: 'duplex'
              },
              payload: {
                input: {
                  text: text
                }
              }
            });
            ws.send(continueTaskMessage);
            console.log(`continue-task sent, text: ${text}`);
          }
        }, index * 1000); // Send one every second
      });
    
      // Send the finish-task event
      setTimeout(() => {
        if (taskStarted) {
          const finishTaskMessage = JSON.stringify({
            header: {
              action: 'finish-task',
              task_id: taskId,
              streaming: 'duplex'
            },
            payload: {
              input: {}
            }
          });
          ws.send(finishTaskMessage);
          console.log('finish-task sent');
        }
      }, texts.length * 1000 + 1000); // Send 1 second after all continue-task events are sent
    }
    
    ws.on('close', () => {
      console.log('Disconnected from the WebSocket server');
    });

    Java

    Recomendamos o uso do SDK DashScope para Java no desenvolvimento. Consulte SDK para Java.

    A seguir, apresentamos um exemplo de conexão direta via WebSocket em Java. Importe estas dependências antes de executar:

    • Java-WebSocket

    • jackson-databind

    Utilize Maven ou Gradle para gerenciar as dependências:

    pom.xml

    <dependencies>
        <!-- WebSocket Client -->
        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.5.3</version>
        </dependency>
    
        <!-- JSON Processing -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.0</version>
        </dependency>
    </dependencies>

    build.gradle

    // Other code omitted
    dependencies {
      // WebSocket Client
      implementation 'org.java-websocket:Java-WebSocket:1.5.3'
      // JSON Processing
      implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
    }
    // Other code omitted

    Código Java:

    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.handshake.ServerHandshake;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.net.URI;
    import java.nio.ByteBuffer;
    import java.util.*;
    
    public class TTSWebSocketClient extends WebSocketClient {
        private final String taskId = UUID.randomUUID().toString();
        private final String outputFile = "output_" + System.currentTimeMillis() + ".mp3";
        private boolean taskFinished = false;
    
        public TTSWebSocketClient(URI serverUri, Map<String, String> headers) {
            super(serverUri, headers);
        }
    
        @Override
        public void onOpen(ServerHandshake serverHandshake) {
            System.out.println("Connection established");
    
            // Send the run-task event
            // If enable_ssml is set to true, only one continue-task event can be sent; otherwise the error "Text request limit violated, expected 1." will be returned
            String runTaskCommand = "{ \"header\": { \"action\": \"run-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"task_group\": \"audio\", \"task\": \"tts\", \"function\": \"SpeechSynthesizer\", \"model\": \"qwen-audio-3.0-tts-flash\", \"parameters\": { \"text_type\": \"PlainText\", \"voice\": \"longanlingxi\", \"format\": \"mp3\", \"sample_rate\": 22050, \"volume\": 50, \"rate\": 1, \"pitch\": 1, \"enable_ssml\": false }, \"input\": {} }}";
            send(runTaskCommand);
        }
    
        @Override
        public void onMessage(String message) {
            System.out.println("Received message from server: " + message);
            try {
                // Parse JSON message
                Map<String, Object> messageMap = new ObjectMapper().readValue(message, Map.class);
    
                if (messageMap.containsKey("header")) {
                    Map<String, Object> header = (Map<String, Object>) messageMap.get("header");
    
                    if (header.containsKey("event")) {
                        String event = (String) header.get("event");
    
                        if ("task-started".equals(event)) {
                            System.out.println("Received task-started event from server");
    
                            List<String> texts = Arrays.asList(
                                    "Before my bed, moonlight shines bright,I suspect it\'s frost upon the ground.",
                                    "I raise my eyes to gaze at the bright moon,then bow my head, thinking of home."
                            );
    
                            for (String text : texts) {
                                // Send the continue-task event
                                sendContinueTask(text);
                            }
    
                            // Send the finish-task event
                            sendFinishTask();
                        } else if ("task-finished".equals(event)) {
                            System.out.println("Received task-finished event from server");
                            taskFinished = true;
                            closeConnection();
                        } else if ("task-failed".equals(event)) {
                            System.out.println("Task failed: " + message);
                            closeConnection();
                        }
                    }
                }
            } catch (Exception e) {
                System.err.println("Exception occurred: " + e.getMessage());
            }
        }
    
        @Override
        public void onMessage(ByteBuffer message) {
            System.out.println("Received binary audio data of size: " + message.remaining());
    
            try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
                byte[] buffer = new byte[message.remaining()];
                message.get(buffer);
                fos.write(buffer);
                System.out.println("Audio data written to local file " + outputFile);
            } catch (IOException e) {
                System.err.println("Failed to write audio data to local file: " + e.getMessage());
            }
        }
    
        @Override
        public void onClose(int code, String reason, boolean remote) {
            System.out.println("Connection closed: " + reason + " (" + code + ")");
        }
    
        @Override
        public void onError(Exception ex) {
            System.err.println("Error: " + ex.getMessage());
            ex.printStackTrace();
        }
    
        private void sendContinueTask(String text) {
            String command = "{ \"header\": { \"action\": \"continue-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": { \"text\": \"" + text + "\" } }}";
            send(command);
        }
    
        private void sendFinishTask() {
            String command = "{ \"header\": { \"action\": \"finish-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": {} }}";
            send(command);
        }
    
        private void closeConnection() {
            if (!isClosed()) {
                close();
            }
        }
    
        public static void main(String[] args) {
            try {
                // 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: String apiKey = "sk-xxx"
                String apiKey = System.getenv("DASHSCOPE_API_KEY");
                if (apiKey == null || apiKey.isEmpty()) {
                    System.err.println("Please set the DASHSCOPE_API_KEY environment variable");
                    return;
                }
    
                Map<String, String> headers = new HashMap<>();
                headers.put("Authorization", "bearer " + apiKey);
                // The following is the configuration for the Singapore region. Replace "{WorkspaceId}" with your actual workspace ID. The configuration varies by region.
                TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"), headers);
    
                client.connect();
    
                while (!client.isClosed() && !client.taskFinished) {
                    Thread.sleep(1000);
                }
            } catch (Exception e) {
                System.err.println("Failed to connect to WebSocket service: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }

    Python

    Recomendamos o uso do SDK DashScope para Python no desenvolvimento. Consulte SDK para Python.

    A seguir, apresentamos um exemplo de conexão direta via WebSocket em Python. Instale estas dependências antes de executar:

    pip uninstall websocket-client
    pip uninstall websocket
    pip install websocket-client
    Importante

    Não nomeie o arquivo Python como "websocket.py", caso contrário, ocorrerá um erro (AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?).

    import websocket
    import json
    import uuid
    import os
    import time
    
    class TTSClient:
        def __init__(self, api_key, uri):
            """
        Initialize TTSClient instance
    
        Parameters:
            api_key (str): API Key for authentication
            uri (str): WebSocket service URL
        """
            self.api_key = api_key  # Replace with your API Key
            self.uri = uri  # Replace with your WebSocket URL
            self.task_id = str(uuid.uuid4())  # Generate unique task ID
            self.output_file = f"output_{int(time.time())}.mp3"  # Output audio file path
            self.ws = None  # WebSocketApp instance
            self.task_started = False  # Whether task-started has been received
            self.task_finished = False  # Whether task-finished / task-failed has been received
    
        def on_open(self, ws):
            """
        Callback when WebSocket connection is established
        Sends run-task event to start speech synthesis task
        """
            print("WebSocket connected")
    
            # Construct run-task event
            run_task_cmd = {
                "header": {
                    "action": "run-task",
                    "task_id": self.task_id,
                    "streaming": "duplex"
                },
                "payload": {
                    "task_group": "audio",
                    "task": "tts",
                    "function": "SpeechSynthesizer",
                    "model": "qwen-audio-3.0-tts-flash",
                    "parameters": {
                        "text_type": "PlainText",
                        "voice": "longanlingxi",
                        "format": "mp3",
                        "sample_rate": 22050,
                        "volume": 50,
                        "rate": 1,
                        "pitch": 1,
                        # If enable_ssml is set to true, only one continue-task event can be sent; otherwise an error will be returned
                        "enable_ssml": False
                    },
                    "input": {}
                }
            }
    
            # Send run-task event
            ws.send(json.dumps(run_task_cmd))
            print("Sent run-task event")
    
        def on_message(self, ws, message):
            """
        Callback when a message is received
        Handles text and binary messages differently
        """
            if isinstance(message, str):
                # Handle JSON text messages
                try:
                    msg_json = json.loads(message)
                    print(f"Received JSON message: {msg_json}")
    
                    if "header" in msg_json:
                        header = msg_json["header"]
    
                        if "event" in header:
                            event = header["event"]
    
                            if event == "task-started":
                                print("Task started")
                                self.task_started = True
    
                                # Send continue-task events
                                texts = [
                                    "Before my bed, moonlight shines bright,I suspect it\'s frost upon the ground.",
                                    "I raise my eyes to gaze at the bright moon,then bow my head, thinking of home."
                                ]
    
                                for text in texts:
                                    self.send_continue_task(text)
    
                                # Send finish-task after all continue-task events are sent
                                self.send_finish_task()
    
                            elif event == "task-finished":
                                print("Task completed")
                                self.task_finished = True
                                self.close(ws)
    
                            elif event == "task-failed":
                                error_msg = msg_json.get("error_message", "Unknown error")
                                print(f"Task failed: {error_msg}")
                                self.task_finished = True
                                self.close(ws)
    
                except json.JSONDecodeError as e:
                    print(f"JSON parsing failed: {e}")
            else:
                # Handle binary messages (audio data)
                print(f"Received binary message, size: {len(message)} bytes")
                with open(self.output_file, "ab") as f:
                    f.write(message)
                print(f"Audio data written to local file {self.output_file}")
    
        def on_error(self, ws, error):
            """Callback when an error occurs"""
            print(f"WebSocket error: {error}")
    
        def on_close(self, ws, close_status_code, close_msg):
            """Callback when connection is closed"""
            print(f"WebSocket closed: {close_msg} ({close_status_code})")
    
        def send_continue_task(self, text):
            """Send continue-task event with text content to be synthesized"""
            cmd = {
                "header": {
                    "action": "continue-task",
                    "task_id": self.task_id,
                    "streaming": "duplex"
                },
                "payload": {
                    "input": {
                        "text": text
                    }
                }
            }
    
            self.ws.send(json.dumps(cmd))
            print(f"Sent continue-task event, text content: {text}")
    
        def send_finish_task(self):
            """Send finish-task event to end the speech synthesis task"""
            cmd = {
                "header": {
                    "action": "finish-task",
                    "task_id": self.task_id,
                    "streaming": "duplex"
                },
                "payload": {
                    "input": {}
                }
            }
    
            self.ws.send(json.dumps(cmd))
            print("Sent finish-task event")
    
        def close(self, ws):
            """Actively close the connection"""
            if ws and ws.sock and ws.sock.connected:
                ws.close()
                print("Connection closed actively")
    
        def run(self):
            """Start the WebSocket client"""
            # Set request headers (authentication)
            header = {
                "Authorization": f"bearer {self.api_key}",
                "X-DashScope-DataInspection": "enable"
            }
    
            # Create WebSocketApp instance
            self.ws = websocket.WebSocketApp(
                self.uri,
                header=header,
                on_open=self.on_open,
                on_message=self.on_message,
                on_error=self.on_error,
                on_close=self.on_close
            )
    
            print("Listening for WebSocket messages...")
            self.ws.run_forever()  # Start persistent connection listener
    
    # Example usage
    if __name__ == "__main__":
        # 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.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.
        SERVER_URI = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"  # Replace with your WebSocket URL
    
        client = TTSClient(API_KEY, SERVER_URI)
        client.run()

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 a seguir 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 cenários que exigem controle preciso da segmentação de frases, como transmissão 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 a seguir para o arquivo:

    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 a seguir para o arquivo:

    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 a seguir para o arquivo:

    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, é possível iniciar 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, depois que o servidor retorna task-finished, o cliente pode enviar run-task para iniciar uma nova tarefa.

  • Qwen-TTS: O cliente envia session.finish e, após o servidor retornar session.finished, o cliente pode criar uma nova sessão para iniciar a próxima tarefa.

Importante
  1. Aguarde o servidor retornar o evento de conclusão (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 fecha a conexão. A conexão não pode ser reutilizada.

  4. Caso nenhuma nova tarefa seja iniciada dentro de 60 segundos após o término da tarefa anterior, a conexão será fechada automaticamente.

Para obter detalhes sobre os eventos de cada modelo, consulte a referência da API correspondente.

Melhores práticas para alta concorrência

O DashScope SDK possui um pool 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

O Qwen-Audio-TTS e o Qwen-Audio-TTS/CosyVoice utilizam a mesma interface do SDK. Os exemplos a seguir 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.

O 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 como 1,5x a 2x a concorrência de pico e não exceda 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, o 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. As mensagens de tarefas subsequentes (como run-task) não carregam a chave de API. Modificar a dashscope.api_key após a criação do pool não afeta as conexões existentes — os 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 atribuição de identidade, cota ou faturamento difira do esperado. Observação: o método borrow_synthesizer não suporta a especificação de uma chave de API como parâmetro.

    • Para usar várias chaves de API, mantenha uma instância separada de SpeechSynthesizerObjectPool para cada chave.

  3. Solicite um objeto SpeechSynthesizer do pool

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

    Esses objetos adicionais precisam estabelecer novas conexões e não se beneficiam do uso do pool.

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

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

  5. 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 com falha.

    connectionPool.return_synthesizer(speech_synthesizer)
Código completo
Importante

Antes de usar este código: O SpeechSynthesizerObjectPool estabelece conexões WebSocket e realiza a autenticação usando a dashscope.api_key global atual na inicialização. Modificar a 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.

    Importante

    Não devolva objetos SpeechSynthesizer com tarefas incompletas ou com falha.

  • Falha na tarefa: Se um erro interno do SDK ou uma exceção de lógica de negócios 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 por meio 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 as conexões WebSocket subjacentes, reduzindo a sobrecarga de handshake de rede. Este recurso é habilitado por padrão.

  • Pool de objetos: Construído sobre o 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

    Adicione dashscope-sdk-java e commons-pool2 à configuração de dependências do seu projeto com base na 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'
      }
    3. Salve o arquivo build.gradle.

    4. 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

    Configure 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

    Configure o tamanho do pool de objetos por meio 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;
        }
    }
  4. 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 uso do pool.

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

    Após solicitar 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, o método updateParamAndCallback é chamado várias vezes (uma vez sempre que um objeto é solicitado, para definir o callback e parâmetros no nível da tarefa, como voice e format). A apiKey passada em cada chamada deve permanecer a mesma. O método updateParamAndCallback atualiza apenas 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 apenas durante o handshake WebSocket para autenticação. As mensagens de tarefas subsequentes (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 atribuição de identidade, cota ou faturamento difira do esperado.

    • Para usar várias chaves de API, mantenha instâncias de pool de objetos separadas para cada chave.

  6. Devolva o objeto SpeechSynthesizer

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

    Não devolva objetos com tarefas incompletas ou com falha.

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

Antes 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 do 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 service de síntese de fala Qwen-Audio-TTS/CosyVoice em instâncias ECS da Alibaba Cloud com as especificações indicadas. Concorrência excessiva pode aumentar a latência de processamento das tarefas.

O termo "concorrência por máquina" refere-se ao número de tarefas de síntese Qwen-Audio-TTS/CosyVoice executadas simultaneamente, equivalente 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)

    Importante

    Não devolva objetos SpeechSynthesizer com tarefas incompletas ou que falharam.

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

    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 service 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 service 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 alta latência 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 o 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 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 para expirar 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: A 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 para que conexões sem referência expirem antes que uma conexão fique disponível.

Exceção 3: Tarefas lentas na inicialização do service, mas com recuperação gradual

Causa raiz:

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

Solução:

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

Exceção 4: O 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 service

  • Anomalias de rede causando a desconexão e reconexão simultânea de muitas conexões WebSocket

  • Surto 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 um surto 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 dos pools.

  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 seguintes modelos, 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 seguintes modelos, 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 da API de clonagem/design de voz 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 da voz corresponda ao parâmetro model usado para a síntese de fala. Por exemplo:

    • A clonagem usou cosyvoice-v3-plus

    • A síntese também deve usar cosyvoice-v3-plus

  3. Verifique a qualidade do áudio de origem

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

    • Duração do áudio: 10-20 segundos

    • Qualidade de áudio clara

    • 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 seguintes problemas:

  • 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 no Guia de gravação para clonagem de voz. Grave novamente 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 usa 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 precisa, defina o formato como pcm, aguarde o resultado completo da síntese e, em seguida, adicione o cabeçalho do arquivo WAV manualmente.

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

Solucione o problema 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?

Solucione o problema 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 que o próximo texto chegue.

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

    • Confirme que não existe lógica de bloqueio na função de callback.

    • Callbacks são executados na thread WebSocket. Operações de bloqueio afetam a recepção 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?

Solucione o problema 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 Gerenciar workspaces.