QwenCloudBRASIL
Tradução de apoio para consulta. Código e identificadores técnicos preservados. Consultar referência original ↗

Síntese de fala em tempo real.

Transmita texto para fala com baixa latência de primeira embalagem.

A síntese de fala em tempo real suporta entrada e saída de streaming, clonagem de voz, design de voz e controles de áudio finos para assistentes de voz, audiolivros e atendimento ao cliente inteligente.

Visão geral

Converta texto para fala em tempo real com baixa latência.

Para cenários em lote, como audiolivros e voiceover de programas, use síntese de fala não em tempo real (A API de HTTP). Para orientação de seleção de modelos, veja [Modelos de síntese de voz] (/developer-guides/speech/tts-models).

Pré-requisitos

-Começo rápido.

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

Qwen-Audio-TTS

O exemplo a seguir sintetiza a fala usando uma voz do sistema.

Para usar o [controle de instruções] (#instruction-control), definir instruções através do `instruction`  parâmetro.

Python

    ```python
    # coding=utf-8
    import os
    import dashscope
    from dashscope.audio.tts_v2 import *
    # Obtain an API key from the QwenCloud console.
    # If the environment variable is not configured, replace the following line with your QwenCloud API key: dashscope.api_key = "sk-xxx"
    dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
    dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference'
    # Model
    # qwen-audio-3.0-tts-flash/qwen-audio-3.0-tts-plus: Use voices such as longanhuan_v3.6.
    # Each voice supports different languages. To synthesize non-Chinese languages such as Japanese or Korean, select a voice that supports the target language. See the voice list for details.
    model = "qwen-audio-3.0-tts-flash"
    # Voice
    voice = "longanhuan_v3.6"
    # Instantiate SpeechSynthesizer and pass request parameters such as model and voice in the constructor
    synthesizer = SpeechSynthesizer(model=model, voice=voice)
    # Send the text to be synthesized and get the binary audio
    audio = synthesizer.call("How is the weather today?")
    # The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time
    print('[Metric] requestId: {}, first-packet latency: {} ms'.format(
      synthesizer.get_last_request_id(),
      synthesizer.get_first_package_delay()))
    # Save the audio to a local file
    with open('output.mp3', 'wb') as f:
      f.write(audio)
    ```

Java

    ```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 longanhuan_v3.6.
      // Each voice supports different languages. To synthesize non-Chinese languages such as Japanese or Korean, select a voice that supports the target language. See the voice list for details.
      private static String model = "qwen-audio-3.0-tts-flash";
      // Voice
      private static String voice = "longanhuan_v3.6";
      public static void streamAudioDataToSpeaker() {
        // Request parameters
        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // Obtain an API key from the QwenCloud console.
                        // If the environment variable is not configured, replace the following line with your QwenCloud 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) {
        Constants.baseWebsocketApiUrl = "wss://maas.qwencloudapi.com/api-ws/v1/inference";
        streamAudioDataToSpeaker();
        System.exit(0);
      }
    }
    ```

CosyVoice

 Além do WebSocket, CosyVoice  Modelos também apoiam o protocolo de QOA. Para integração cliente-side que prioriza latência estável, resiliência em redes fracas, e integração de ruído e cancelamento de eco, o AOQ é recomendado. Para uma comparação de protocolo, veja [Realtime API overview] (/api-reference/realtime-api/overview).

O exemplo a seguir sintetiza a fala usando uma voz do sistema (ver [CosyVoice "Lista de voz" (/developer-guides/speech/voice-list/cosyvoice)).

Para usar o [controle de instruções] (#instruction-control), definir instruções através do `instruction`  parâmetro.

Python

    ```python
    # coding=utf-8
    import os
    import dashscope
    from dashscope.audio.tts_v2 import *
    # Obtain an API key from the QwenCloud console.
    # If the environment variable is not configured, replace the following line with your QwenCloud API key: dashscope.api_key = "sk-xxx"
    dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
    dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference'
    # Model
    # Different model versions require corresponding voice types:
    # cosyvoice-v3-flash/cosyvoice-v3-plus: Use voices such as longanyang.
    # Each voice supports different languages. When synthesizing non-Chinese languages such as Japanese or Korean, select a voice that supports the target language. For details, see the CosyVoice voice list.
    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 text for synthesis and obtain 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 package delay: {} ms'.format(
      synthesizer.get_last_request_id(),
      synthesizer.get_first_package_delay()))
    # Save audio to a local file
    with open('output.mp3', 'wb') as f:
      f.write(audio)
    ```

Java

    ```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 corresponding voice types:
        // cosyvoice-v3-flash/cosyvoice-v3-plus: Use voices such as longanyang.
        // Each voice supports different languages. When synthesizing non-Chinese languages such as Japanese or Korean, select a voice that supports the target language. For details, see the CosyVoice voice list.
        private static String model = "cosyvoice-v3-flash";
        // Voice
        private static String voice = "longanyang";
        public static void streamAudioDataToSpeaker() {
            // Request parameters
            SpeechSynthesisParam param =
                    SpeechSynthesisParam.builder()
                            // Obtain an API key from the QwenCloud console.
                            // If the environment variable is not configured, replace the following line with your QwenCloud 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 after the task completes
                synthesizer.getDuplexApi().close(1000, "bye");
            }
            if (audio != null) {
                // Save the audio data to a 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 package delay (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) {
            Constants.baseWebsocketApiUrl = "wss://maas.qwencloudapi.com/api-ws/v1/inference";
            streamAudioDataToSpeaker();
            System.exit(0);
        }
    }
    ```

Qwen3-TTS

O exemplo a seguir sintetiza a fala usando uma voz do sistema (ver [vozes suportadas])#supported-voices)).

Python

Server commit mode

        ```python
        import os
        import base64
        import threading
        import time
        import dashscope
        from dashscope.audio.qwen_tts_realtime import *
        qwen_tts_realtime: QwenTtsRealtime = None
        text_to_synthesize = [
            'Right? I love supermarkets like this.',
            'Especially during Chinese New Year,',
            'I go shopping at supermarkets.',
            'And I feel',
            'absolutely thrilled!',
            'I want to buy so many things!'
        ]
        DO_VIDEO_TEST = False
        def init_dashscope_api_key():
            """
                Set your DashScope API key. More information:
                https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
            """
            # Obtain an API key from the QwenCloud console.
            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(
                model='qwen3-tts-flash-realtime',
                callback=callback,
                url='wss://maas.qwencloudapi.com/api-ws/v1/realtime'
                )
            qwen_tts_realtime.connect()
            qwen_tts_realtime.update_session(
                voice = 'Cherry',
                response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
                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

        ```python
        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
            """
            # Obtain an API key from the QwenCloud console.
            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(
                model='qwen3-tts-flash-realtime',
                callback=callback,
                url='wss://maas.qwencloudapi.com/api-ws/v1/realtime'
                )
            qwen_tts_realtime.connect()
            qwen_tts_realtime.update_session(
                voice = 'Cherry',
                response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
                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

Server commit mode

        `appendText()`

        ```java
        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()
                        .model("qwen3-tts-flash-realtime")
                        .url("wss://maas.qwencloudapi.com/api-ws/v1/realtime")
                        // Obtain an API key from the QwenCloud console.
                        .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")                .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);
            }
        }
        ```

Commit mode

        `commit()`

        ```java
        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()
                        .model("qwen3-tts-flash-realtime")
                        .url("wss://maas.qwencloudapi.com/api-ws/v1/realtime")
                        // Obtain an API key from the QwenCloud console.
                        .apikey(System.getenv("DASHSCOPE_API_KEY"))
                        .build();
                AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));
                // Create a real-time audio 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")                .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);
            }
        }
        ```

Recursos avançados

Qwen3-TTS modos de interação

Qwen3-TTS API em tempo real fornece dois modos de interação:

** Mude de modos de interação**:

{
    "type": "session.update",
    "session": {
        "mode": "server_commit"
    }
}
qwen_tts_realtime.update_session(
    voice='Cherry',
    response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
    mode='server_commit'
)
QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
        .voice("Cherry")
        .responseFormat(ttsFormat)
        .mode("server_commit")
        .build();
qwenTtsRealtime.updateSession(config);

Para exemplos completos de código SDK, veja [Python SDK] (/api-reference/speech-synthesis/cosyvoice/python-sdk) e [Java SDK] (/api-reference/speech-synthesis/cosyvoice/java-sdk). Para o ciclo de vida do evento WebSocket e os detalhes de reutilização da conexão, veja a referência da API WebSocket.

Controle de instruções.

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

** Especificações de instrução por modelo**:

Qwen-Audio-TTS

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

Vozes de sistema e vozes clonadas: aceite qualquer instrução.

CosyVoice

 Modelos apoiados: cosyvoice-v3-plus, cosyvoice-v3-flash

 Os requisitos de formato de instrução variam de acordo com o modelo:

- cosyvoice-v3-plus:
  -  Não apoie controle de instruções.
  -  Instruções devem seguir um formato fixo. Para detalhes, veja CosyVoice  Lista de voz.
- cosyvoice-v3-flash:
  - Clonagem de voz/vozes de design: aceite qualquer instrução.
  -  Instruções devem seguir um formato fixo. Para detalhes, veja CosyVoice  Lista de voz.

Especifique o conteúdo de instrução através do parâmetro de instrução.

Linguagens suportadas para texto de instrução

- cosyvoice-v3-plus:
  - Clonagem de voz/vozes de design: chinês, inglês, francês, alemão, japonês, coreano e russo.
  -  Instruções devem seguir um formato fixo. Para detalhes, veja CosyVoice  Lista de voz.
- cosyvoice-v3-flash:
  - Clonagem de voz/vozes de design: chinês, inglês, francês, alemão, japonês, coreano e russo.
  -  Só chineses.

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

** Use casos**:

Dicas para escrever descrições de voz de alta qualidade

  1. Use palavras que descrevem qualidades vocais concretas, como "profundo", "perigoso", "raio", ou "ligeiramente rápido".
  2. Uma boa descrição abrange várias dimensões (gênero, idade, emoção, etc.). Escrever apenas "voz feminina" é muito amplo para produzir um timbre distinto.
  3. Foque nas qualidades físicas e perceptivas da voz. Por exemplo, use "um pouco alto com energia" ao invés de "minha voz favorita".
  4. Descreva as qualidades vocais que você tem para ser original e não imitativa. want, em vez de solicitar imitação de figuras públicas específicas (como celebridades ou atores). O modelo não suporta imitação, e pode envolver riscos de direitos autorais.
  5. Seja conciso, não redundante, faça cada palavra valer a pena. Evite repetir sinônimos ou empilhar modificadores sem sentido.
Dimensão Descrições de exemplo
Sexo Homem, mulher, neutro
Idade Criança (5-12), adolescente (13-18), jovem adulto (19-35), de meia idade (36-55), idoso (55+)
Pitch Alto, médio, baixo, ligeiramente alto, ligeiramente baixo
Velocidade Rápido, moderado, lento, ligeiramente rápido, ligeiramente lento
Emoção Alegre, calmo, gentil, sério, animado, composto, calmante
Timbre. Magnético, crocante, rouco, suave, doce, rico, poderoso
Use o caso. Notícias, publicidade, audiolivro, animação, assistente de voz, narração de documentários

Magnético, crocante, rouco, suave, doce, rico, poderoso

Use o caso, notícias de transmissão, publicidade, audiolivro, personagem de animação, assistente de voz, narração de documentários

Dialetos

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

** Configuração do diferencial por modelo**:

Qwen-Audio-TTS

-  Selecione um dos seguintes tipos de voz:
  -  Uma voz de sistema com suporte de dialeto embutido, que produz o dialeto correspondente sem configuração adicional.
  -  Uma voz que suporta [controle de instruções] (#instruction-control) e pode ser configurado para produzir um dialeto específico através de texto de instrução.
-  Configure através do controle de instruções#instruction-control) feature. Por exemplo, defina o texto de instruções para `请用河南话表达`.

 Veja a coluna "Línguas Suportadas" para cada modelo na documentação dos modelos de síntese de fala.

CosyVoice

- Use uma voz de sistema com suporte de dialeto embutido (como `longanyue_v3`, `longshange_v3`, ou `longanmin_v3`), que produz o dialeto correspondente sem configuração adicional.
-  Configure através do controle de instruções#instruction-control) feature. Por exemplo, defina o texto de instruções para `请用河南话表达`.
- Voz de design de vozes: não apoiem dialetos.

 Veja a coluna "Língua" de cada voz naCosyVoice "Lista de voz" (/developer-guides/speech/voice-list/cosyvoice).

**Exemplo**:Use `cosyvoice-v3-flash` com o `longshange_v3`  voz para o discurso de saída no dialeto Shaanxi.

```python
# coding=utf-8
import os
import dashscope
from dashscope.audio.tts_v2 import *
# Obtain an API key from the QwenCloud console.
# If the environment variable isn't configured, replace the following line with your QwenCloud API key: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference'
# Model
# Different model versions require corresponding voice types:
# cosyvoice-v3-flash/cosyvoice-v3-plus: Use voices such as longanyang.
# Pick a dialect-capable voice
model = "cosyvoice-v3-flash"
# Voice
voice = "longshange_v3"
# Instantiate SpeechSynthesizer and pass request parameters such as model and voice in the constructor
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# Send text for synthesis and obtain binary audio
audio = synthesizer.call("咥饭咧么?今黑来咱去逛咧!")  # Shaanxi dialect sample text
# The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time
print('[Metric] requestId: {}, first package delay: {} ms'.format(
    synthesizer.get_last_request_id(),
    synthesizer.get_first_package_delay()))
# Save audio to a local file
with open('output.mp3', 'wb') as f:
    f.write(audio)
```

Qwen3-TTS

- Use uma voz de sistema com suporte de dialeto embutido.  Veja o Qwen3-TTS  Lista de voz em [vozes apoiadas] (#supported-voices).
- Voz clonando vozes: não apoiem dialetos.
- Voz de design de vozes: não apoiem dialetos.

 Veja a coluna "Línguas Suportadas" para cada modelo em Qwen3-TTS.

Emoção e etiquetas de linguagem ricas

Qwen-Audio-TTS Modelos de séries suportam a incorporação de emoções e tags de linguagem ricas diretamente no texto para sintetizar (o text parâmetro). Essas etiquetas controlam a expressão emocional ou inserem efeitos vocais (como risos e suspiros) em posições especificadas, produzindo uma fala mais expressiva sem configurar parâmetros audio complexos.

Modelos apoiados: qwen-audio-3.0-tts-plus e qwen-audio-3.0-tts-flash Só.

Apenas o modo de transmissão unidirecional é suportado.

** Marcas de controle **

As etiquetas de controle definem a emoção ou o estilo do discurso. Coloque uma etiqueta no texto para afetar todo texto subsequente até que a próxima tag de controle apareça ou a sentença seja automaticamente segmentada devido ao comprimento.

Marca Descrição
[sad] Triste.
[amazed] Surpreendente.
[deep and loud shouting] Gritos profundos e altos
[trembling] Tremendo.
[angry] Zangado.
[excited] Animado
[sarcastic] Sarcástico.
[curious] Curioso.
[like dracula] Drácula estilo (profundamente, assustador)
[bored] Chato.
[tired] Cansada.
[scornful] Scorniful
[shouting] Gritando
[asmr] Sussurro suave ASMR
[panicked] Em pânico.
[mischievously] Mischievous
[empathetic] Empatia
[whispers] Sussurre.
[reluctantly] Relutante.
[crying] Chorando
[serious] Sério.
[very slowly] Muito lento discurso
[very fast] Discurso muito rápido.

**Rich tags de linguagem **

Marcas de linguagem ricas inserem um efeito vocal na posição atual no texto sem afetar o estilo emocional do texto circundante.

Marca Descrição
[gasp] Gasp.
[sighing] Suspiro
[clears throat] - A garganta está limpa.
[giggles] Risadinha.
[laughing] Riso
[cough] Tosse
[snorts] Snort

** Exemplos de uso**

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

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

Neste texto, [excited] é uma tag de controle que aplica uma emoção animada a todo texto subsequente. [laughing] é uma tag de linguagem rica que insere uma risada dessa posição antes de continuar a sintetizar o texto restante.

Você também pode trocar entre diferentes emoções dentro do mesmo texto:

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

Aqui. [serious] A primeira frase é um tom sério, e [excited] Muda para um tom animado a partir da segunda frase.

Cancelar tarefa

Se precisar interromper a síntese atual durante a síntese de fala em tempo real, envie um comando de cancelamento. Após o cancelamento, o servidor termina imediatamente a tarefa atual e retorna um evento de conclusão. Você pode começar uma nova tarefa de síntese na mesma conexão WebSocket sem reconectar.

Usagem:

Protocolo Raw WebSocket

Os exemplos a seguir demonstram como se conectar diretamente ao servidor através do protocolo de WebSocket, adequado para cenários sem o SDK DashScope. São implementações minimas executáveis. Para detalhes do protocolo WebSocket, veja a referência da API para cada modelo.

Veja exemplos de chamadas de protocolo brutos do WebSocket

Qwen-Audio-TTS/CosyVoice

  Qwen-Audio-TTS e CosyVoice Use o mesmo protocolo WebSocket.  Os exemplos a seguir usam `qwen-audio-3.0-tts-flash`. Para usar CosyVoice, substitua o `model`  parâmetro com um CosyVoice  modelo (como `cosyvoice-v3-flash`) e o `voice`  parâmetro com a voz desejada.

Go

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

C#

      ```csharp
      using System.Net.WebSockets;
      using System.Text;
      using System.Text.Json;
      class Program {
          // Obtain an API key from the QwenCloud console.
          // If the environment variable is not configured, replace the following line with your QwenCloud 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.");
          private const string WebSocketUrl = "wss://maas.qwencloudapi.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 run-task event
                  _taskId = GenerateTaskId();
                  await SendRunTaskCommandAsync(_taskId);
                  // Wait for the task-started event
                  await _taskStartedTcs.Task;
                  // Continuously 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 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 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 = "longanhuan_v3.6",
                      format = "mp3",
                      sample_rate = 22050,
                      volume = 50,
                      rate = 1,
                      pitch = 1,
                      // If enable_ssml is set to true, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur.
                      enable_ssml = false
                  },
                  input = 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 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

      A estrutura de diretórios de código exemplo:

      ```text
      my-php-project/
      ├── composer.json
      ├── vendor/
      └── index.php
      ```

      O conteúdo do compositor Json é o seguinte. Determine as versões de dependência adequadas com base em seus requisitos:

      ```json
      {
          "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/"
              }
          }
      }
      ```

      O índice.php conteúdo:

      ```php
      <?php
      require __DIR__ . '/vendor/autoload.php';
      use Ratchet\Client\Connector;
      use React\EventLoop\Loop;
      use React\Socket\Connector as SocketConnector;
      // Obtain an API key from the QwenCloud console.
      // If the environment variable is not configured, replace the following line with your QwenCloud API key: $api_key = "sk-xxx"
      $api_key = getenv("DASHSCOPE_API_KEY");
      $websocket_url = 'wss://maas.qwencloudapi.com/api-ws/v1/inference'; // WebSocket server address
      $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 WebSocket server\n";
          // Generate task ID
          $taskId = generateTaskId();
          // Send run-task event
          sendRunTaskMessage($conn, $taskId);
          // Define the function for sending continue-task events
          $sendContinueTask = function() use ($conn, $loop, $taskId) {
              // Text to send
              $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 finish-task event
              sendFinishTaskMessage($conn, $taskId);
          };
          // Flag for whether the task-started event has been 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 messages
                  $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 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" => "longanhuan_v3.6",
                      "format" => "mp3",
                      "sample_rate" => 22050,
                      "volume" => 50,
                      "rate" => 1,
                      "pitch" => 1,
                      // If enable_ssml is set to true, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur.
                      "enable_ssml" => false
                  ],
                  "input" => (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 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 closed connection\n";
              });
          }
          // If the task-started event was not received, close the connection
          if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) {
              $conn->close();
          }
      }
      ```

Node.js

       Instale as dependências necessárias:

      ```sh
      npm install ws
      npm install uuid
      ```

       Código de exemplo:

      ```javascript
      const WebSocket = require('ws');
      const fs = require('fs');
      const uuid = require('uuid').v4;
      // Obtain an API key from the QwenCloud console.
      // If the environment variable is not configured, replace the following line with your QwenCloud API key: const apiKey = "sk-xxx"
      const apiKey = process.env.DASHSCOPE_API_KEY;
      const url = 'wss://maas.qwencloudapi.com/api-ws/v1/inference';
      // Output file path
      const outputFilePath = 'output.mp3';
      // Clear the 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 WebSocket server');
        // Send 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: 'longanhuan_v3.6', // 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 is allowed; otherwise the error "Text request limit violated, expected 1." will occur.
            },
            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\'s 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(`Sent continue-task, text: ${text}`);
            }
          }, index * 1000); // Send one every second
        });
        // Send 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 WebSocket server');
      });
      ```

Java

      Para Java, recomendamos usar o Java DashScope SDK. Para detalhes, veja [Java SDK] (/api-reference/speech-synthesis/cosyvoice/java-sdk).

      O seguinte é um exemplo Java WebSocket.  Antes de executá-lo, certifique-se de ter importado essas dependências:

      - `Java-WebSocket`
      - `jackson-databind`

      Use Maven ou Gradle para gerenciar dependências. A configuração é a seguinte:

pom.xml

          ```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

          ```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:

      ```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 run-task event
              // If enable_ssml is set to true, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur.
              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\": \"longanhuan_v3.6\", \"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 continue-task event
                                  sendContinueTask(text);
                              }
                              // Send 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, 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 {
                  // Obtain an API key from the QwenCloud console.
                  // If the environment variable is not configured, replace the following line with your QwenCloud 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);
                  TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://maas.qwencloudapi.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

      Para Python, recomendamos usar o Python DashScope SDK. Para detalhes, veja [Python SDK] (/api-reference/speech-synthesis/cosyvoice/python-sdk).

      O seguinte é um exemplo Python WebSocket.  Antes de executá-lo, instale a dependência como segue:

      ```bash
      pip uninstall websocket-client
      pip uninstall websocket
      pip install websocket-client
      ```

      

         Não chame seu arquivo Python de "websocket.py".  Caso contrário, um erro ocorre (AtributoErro: módulo 'websocket' não tem atributo 'WebSocketApp'.  Você quis dizer: 'WebSocket'?).
      


      ```python
      import websocket
      import json
      import uuid
      import os
      import time
      class TTSClient:
          def __init__(self, api_key, uri):
              """
          Initialize a TTSClient instance.
          Parameters:
              api_key (str): API Key for authentication
              uri (str): WebSocket service address
          """
              self.api_key = api_key  # Replace with your API Key
              self.uri = uri  # Replace with your WebSocket address
              self.task_id = str(uuid.uuid4())  # Generate a 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 the run-task event to start the speech synthesis task.
          """
              print("WebSocket connected")
              # Construct the 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": "longanhuan_v3.6",
                          "format": "mp3",
                          "sample_rate": 22050,
                          "volume": 50,
                          "rate": 1,
                          "pitch": 1,
                          # If enable_ssml is set to True, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur.
                          "enable_ssml": False
                      },
                      "input": {}
                  }
              }
              # Send the run-task event
              ws.send(json.dumps(run_task_cmd))
              print("run-task event sent")
          def on_message(self, ws, message):
              """
          Callback when a message is received.
          Handles text and binary messages separately.
          """
              if isinstance(message, str):
                  # Process 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:
                  # Process 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 a continue-task event with the text content to synthesize"""
              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 a 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("finish-task event sent")
          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 a 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 long-lived connection listener
      # Example usage
      if __name__ == "__main__":
          # Obtain an API key from the QwenCloud console.
          # If the environment variable is not configured, replace the following line with your QwenCloud API key: API_KEY = "sk-xxx"
          API_KEY = os.environ.get("DASHSCOPE_API_KEY")
          SERVER_URI = "wss://maas.qwencloudapi.com/api-ws/v1/inference"  # Replace with your WebSocket address
          client = TTSClient(API_KEY, SERVER_URI)
          client.run()
      ```

Qwen3-TTS

  1. ? Crie o cliente ?

Python

       Criar um arquivo Python chamado `tts_realtime_client.py` e copie o seguinte código para ele:

      ```python
      # -- 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 by the server for 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 of the 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 a 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,            "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 the 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:
              """Process 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)
                      # Process 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 the 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 ended")
              except websockets.exceptions.ConnectionClosed:
                  print("Connection closed")
              except Exception as e:
                  print("Error processing 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 seguinte código para ele:

      ```java
      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;
      /**
       * 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.
       */
      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 with 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);                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 ended");
                              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 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.  Escolha um modo de síntese

  A API em tempo real suporta os seguintes dois modos:

  - **servidor\_commit mode**

    O cliente só manda mensagens. O servidor determina inteligentemente a segmentação e o tempo de síntese. Este modo se adapta a cenários de baixa latência que não requerem controle manual sobre o ritmo de síntese, como navegação por GPS.
  - **Comprometer modo**

    O cliente adiciona texto a um buffer e então ativa o servidor para sintetizar o texto especificado. Este modo se adapta a cenários que requerem controle de finos níveis sobre quebras de sentenças e pausas, como transmissão de notícias.

server_commit mode

Python

          No mesmo diretório que `tts_realtime_client.py`, criar outro arquivo Python chamado `server_commit.py` e copie o seguinte código para ele:

          ```python
          import os
          import asyncio
          import logging
          import wave
          from tts_realtime_client import TTSRealtimeClient, SessionMode
          import pyaudio
          # QwenTTS service configuration
          URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"
          # Obtain an API key from the QwenCloud console.
          # If the environment variable is not configured, replace the following line with your QwenCloud 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 = [
                  "QwenCloud 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 server to complete 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()
          ```

           Corra. `server_commit.py` para ouvir o áudio gerado pela API em tempo real em tempo real.

Java

          No mesmo diretório que `TTSRealtimeClient.java`, criar outro arquivo Java chamado `ServerCommit.java` e copie o seguinte código para ele:

          ```java
          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 {
              private static final String URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime";
              // Obtain an API key from the QwenCloud console.
              // If the environment variable is not configured, replace the following line with your QwenCloud 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 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 TTS client        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 = {
                      "QwenCloud 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("Sending text...");
                  for (String text : textFragments) {
                      System.out.println("Sending fragment: " + text);
                      client.appendText(text);
                      Thread.sleep(100);
                  }
                  Thread.sleep(1000);
                  client.finishSession();
                  // Wait for response to complete
                  client.waitForResponseDone();
                  client.waitForSessionFinished();
                  client.close();
                  // Wait for playback to complete
                  playing.set(false);
                  playerThread.join();
                  audioLine.drain();
                  audioLine.close();
                  // Save 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);
              }
          }
          ```

          Compilar e correr `ServerCommit.java` para ouvir o áudio gerado pela API em tempo real em tempo real.

commit mode

Python

          No mesmo diretório que `tts_realtime_client.py`, criar outro arquivo Python chamado `commit.py` e copie o seguinte código para ele:

          ```python
          import os
          import asyncio
          import logging
          import wave
          from tts_realtime_client import TTSRealtimeClient, SessionMode
          import pyaudio
          # QwenTTS service configuration
          URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"
          # Obtain an API key from the QwenCloud console.
          # If the environment variable is not configured, replace the following line with your QwenCloud 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 session."""
              print("Enter text (press Enter directly to send a commit event and end the 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 marks the end of a conversation: commit buffer -> end session -> exit loop
                          logging.info("Empty input, sending commit event and ending session")
                          await client.commit_text_buffer()
                          # Wait briefly for server to process 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()
          ```

           Corra. `commit.py`.  Você pode digitar SMS várias vezes.  Pressione Enter sem digitar o texto para ouvir o áudio retornado pela API em tempo real através de seus alto-falantes.

Java

          No mesmo diretório que `TTSRealtimeClient.java`, criar outro arquivo Java chamado `Commit.java` e copie o seguinte código para ele:

          ```java
          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 {
              private static final String URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime";
              // Obtain an API key from the QwenCloud console.
              // If the environment variable is not configured, replace the following line with your QwenCloud 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 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 TTS client (commit mode)        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 buffer and end session
                          System.out.println("Empty input, sending commit event and ending session");
                          client.commitTextBuffer();
                          Thread.sleep(300);
                          client.finishSession();
                          break;
                      } else {
                          System.out.println("Sending text: " + userText);
                          client.appendText(userText);
                      }
                  }
                  scanner.close();
                  // Wait for response to complete
                  client.waitForResponseDone();
                  client.waitForSessionFinished();
                  client.close();
                  // Wait for playback to complete
                  playing.set(false);
                  playerThread.join();
                  audioLine.drain();
                  audioLine.close();
                  // Save 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);
              }
          }
          ```

          Compilar e correr `Commit.java`.  Você pode digitar SMS várias vezes.  Pressione Enter sem digitar o texto para ouvir o áudio retornado pela API em tempo real através de seus alto-falantes.

Personalização de voz

CosyVoice

### Clonagem de voz: requisitos de áudio de entrada

 Áudio de entrada de alta qualidade é a base para excelentes resultados de clonagem.
Item Requisição
Formatos suportados. WAV (16 bits), MP3, M4A
Duração do áudio Recomendado: 10-20 segundos. Máximo: 60 segundos.
Tamanho do arquivo ≤ 10 MB
Taxa de amostragem ≥ 16 kHz
Canais Mono ou estéreo. Para áudio estéreo, só o primeiro canal é processado, então certifique-se de que contenha uma fala clara.
Conteúdo O áudio deve conter pelo menos 5 segundos de fala contínua e clara sem som de fundo. O resto pode conter apenas breves pausas (≤ 2 segundos). Todo o clipe deve estar livre de música de fundo, barulho ou outras vozes para garantir conteúdo de voz de alta qualidade. Use áudio de fala normal como entrada, não carregue músicas ou cantando, para garantir resultados precisos e utilizáveis de clonagem.
### Design de voz: escrever descrições de voz de alta qualidade

#### Restrições

 Quando escrever uma descrição de voz (`voice_prompt`), siga essas restrições técnicas:

- * Limite de comprimento * `voice_prompt` O conteúdo não deve exceder 500 caracteres.
- O texto da descrição suporta apenas chinês e inglês.

####  Princípios fundamentais

O `voice_prompt`  guia o modelo para gerar uma voz com características específicas.

 Ao escrever uma descrição de voz, siga estes princípios fundamentais:

- Use palavras que descrevem qualidades vocais concretas, como "profundas", "perigoso", "leasamente rápidas", evite termos subjetivos, de baixa informação, como "legais" ou "normais".
-  Uma boa descrição geralmente combina múltiplas dimensões (como sexo, idade e emoção).  Uma descrição de uma única dimensão (como apenas "voz feminina") é muito ampla para produzir um resultado distinto.
- Foque nas qualidades físicas e perceptuais da própria voz, ao invés de preferências pessoais. Por exemplo, use "um pouco alto com energia" em vez de "minha voz favorita".
- Descreva as qualidades da voz, ao invés de pedir imitação de pessoas específicas (como celebridades ou atores).  Tais pedidos envolvem riscos de direitos autorais, e o modelo não suporta imitação direta.
-  Seja conciso, não redundante, certifique-se de que cada palavra seja significativa.  Evite repetir sinônimos ou intensificadores sem sentido (como "uma voz muito, muito agradável").

####  Descrição, dimensão, referência.
Dimensão Exemplos
Sexo Homem, mulher, neutro
Idade Criança (5-12), adolescente (13-18), jovem adulto (19-35), de meia idade (36-55), idoso (55+)
Pitch Alto, médio, baixo, ligeiramente alto, ligeiramente baixo
Velocidade Rápido, moderado, lento, ligeiramente rápido, ligeiramente lento
Emoção Alegre, composto, gentil, sério, vivo, fresco, calmante
Timbre. Magnético, crocante, rouco, suave, doce, rico, poderoso
Use o caso. Notícias, publicidade, audiolivro, animação, assistente de voz, narração de documentários

Magnético, crocante, rouco, suave, doce, rico, poderoso

Use o caso, transmissão de notícias, publicidade, audiolivro, personagem de animação, assistente de voz, narração de documentários

####  Comparação de exemplo

Bons exemplos:

- "Uma voz feminina jovem e animada com um ritmo rápido e uma entonação crescente perceptível, adequada para introduzir produtos de moda."
  -  Esta descrição combina idade, personalidade, velocidade e entonação, e especifica um caso de uso, formando um perfil de voz claro.
- "Uma voz masculina composta de meia-idade com um ritmo ligeiramente lento, profundo e magnético, adequado para transmissão de notícias ou narração documental."
  -  Esta descrição define claramente sexo, faixa etária, velocidade, timbre e caso de uso.
- "Uma voz de criança bonita, sobre uma garota de 8 anos, falando com um tom um pouco infantil, adequado para a narração de personagens de animação."
  -  Esta descrição visa precisamente a idade e a qualidade vocal (criança), com um caso de uso claro.
- "Uma mulher gentil, intelectual, com cerca de 30 anos, com um tom calmo, adequado para leitura de audiolivro."
  -  Esta descrição efetivamente transmite a emoção e o estilo da voz através de palavras como "intelectual" e "calma".

Maus exemplos e sugestões de melhorias.
Mau exemplo. Problema principal. Sugestão de melhora
"Uma voz agradável" A descrição é muito vaga e subjetiva, sem detalhes acionáveis. Adicione dimensões concretas, como "uma voz jovem feminina com um timbre claro e uma entonação suave."
Uma voz como uma certa celebridade Envolve riscos de direitos autorais, e o modelo não suporta imitação direta. Extrair e descrever características vocais, como "uma voz masculina madura e magnética com um ritmo composto".
"Uma voz muito, muito, muito boa, feminina" A descrição é redundante, palavras repetidas não ajudam a definir a voz. Remova a repetição e adicione detalhes eficazes, como "uma voz feminina de 20 a 24 anos com um timbre leve, entonação viva e doce qualidade."
123456 Entrada inválida que não pode ser analisada em características vocais. Dê uma descrição significativa, veja os exemplos recomendados acima.

Qwen3-TTS-Realtime

Qwen3-TTS -Acredita na clonagem de voz.Qwen3-TTS-VC) e design de voz (Qwen3-TTS-VD).  Veja o [clonografia de voz] (/api-reference/speech-synthesis/voice-cloning/qwen/create-voice) guia para detalhes.

Reuso da conexão (WebSocket)

Conexões WebSocket suportam o reuso: após uma tarefa de síntese terminar, a próxima tarefa pode começar na mesma conexão sem reconectar.

** Fluxo de reaproveitamento**:

Por Qwen-Audio-TTSSe cancelar a tarefa atual usando o cancel Diretriz, você também pode enviar um novo run-task evento na mesma conexão depois que o servidor retorna task-finished. Veja [Cancelar tarefa] (#cancel-task).

Para detalhes do evento, veja a correspondente [referência API] (#api-reference).

Melhores práticas de alta concorrência.

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

Qwen-Audio-TTS e CosyVoice Use a mesma interface SDK. Os exemplos a seguir também se aplicam a Qwen-Audio-TTS Modelos -- simplesmente substitua o model e voice Parâmetros.

? Pré-requisitos ? :

Python SDK

O Python SDK usa `SpeechSynthesizerObjectPool` para gerenciar e reutilizar `SpeechSynthesizer`  objetos.

A piscina cria um número específico de `SpeechSynthesizer` E estabelece conexões WebSocket na inicialização.  Quando você pega um objeto emprestado, ele está pronto para enviar pedidos imediatamente, reduzindo a latência do primeiro pacote.  Depois que o objeto é devolvido, a conexão permanece ativa para a próxima tarefa.

####  Etapas de implementação

1.  Instalar dependências: Instalar dependência do DashScope (`pip install -U dashscope`).
2.  Crie e configure o conjunto de objetos.

    Ajuste o tamanho da piscina para 1,5x-2x a concordância máxima, e não exceda o limite de QPS da sua conta.

    Criar uma piscina global de singletons (estabelecimento de conexão durante a inicialização leva algum tempo):

```python
from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool
synthesizer_object_pool = SpeechSynthesizerObjectPool(max_size=20)
import dashscope
dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1"
```



  - No cenário de piscina de objetos, `SpeechSynthesizerObjectPool`  estabelece conexões WebSocket com o servidor usando o global atual `dashscope.api_key` na inicialização. A chave da API está escrita para o `Authorization`  Só cabeçalho durante o aperto de mão WebSocket para autenticação.  Mensagens de tarefas posteriores (como `run-task` Não leve a chave da API. `borrow_synthesizer` (incluindo os que voltaram e voltaram a pedir emprestado) ainda usam a chave API do aperto de mão original. O novo valor é ignorado silenciosamente, o que pode causar identidade, cota ou atribuição de faturamento para diferir das expectativas.  Nota: `borrow_synthesizer`  Não suporta especificar uma chave API como parâmetro.
  - Para usar várias chaves API, mantenha um separado. `SpeechSynthesizerObjectPool` **Instance** para cada chave.



3. Emprestar um `SpeechSynthesizer`  objeto da piscina.

   Se o número de objetos não retornados exceder a capacidade da piscina, o sistema cria objetos adicionais.  Esses objetos adicionais devem estabelecer novas conexões e não se beneficiar de juntar.

```python
speech_synthesizer = connectionPool.borrow_synthesizer(
  model='cosyvoice-v3-flash',
  voice='longanyang',
  seed=12382,
  callback=synthesizer_callback
)
```

4.  Faça a síntese da fala.  Chame o `SpeechSynthesizer` O método de chamada ou transmissão de objetos para sintetizar a fala.
5.  Devolva o `SpeechSynthesizer`  Protesto.  Devolva o objeto após a tarefa completa para torná-lo disponível para reutilização.  Não devolva objetos com tarefas incompletas ou falhadas.

```python
connectionPool.return_synthesizer(speech_synthesizer)
```

Complete code

     Antes de copiar: `SpeechSynthesizerObjectPool`  estabelece conexões WebSocket e autentica usando o atual global `dashscope.api_key` Na inicialização. **Modificar o Dashscope.api\_key após a criação do pool não afeta as conexões existentes**; o novo valor é ignorado silenciosamente. Para cenários multi-chave, mantenha uma instância de conjunto separada por chave API.  Veja a nota importante acima para detalhes.
  


  ```python
  # !/usr/bin/env python3
  # Copyright (C) Alibaba Group. All Rights Reserved.
  # MIT License (https://opensource.org/licenses/MIT)
  import os
  import time
  import threading
  import dashscope
  from dashscope.audio.tts_v2 import *
  USE_CONNECTION_POOL = True
  text_to_synthesize = [
    'Sentence 1: Welcome to Alibaba speech synthesis service.',
    'Sentence 2: Welcome to Alibaba speech synthesis service.',
    'Sentence 3: Welcome to Alibaba speech synthesis service.',
  ]
  connectionPool = None
  def init_dashscope_api_key():
    '''
    Set your DashScope API-key. More information:
    https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
    '''
    if 'DASHSCOPE_API_KEY' in os.environ:
      dashscope.api_key = os.environ[
        'DASHSCOPE_API_KEY']  # load API-key from environment variable DASHSCOPE_API_KEY
    else:
      dashscope.api_key = '<your-dashscope-api-key>'  # set API-key manually
  def synthesis_text_to_speech_and_play_by_streaming_mode(text, task_id):
    global USE_CONNECTION_POOL, connectionPool
    '''
    Synthesize speech with given text by streaming mode, async call and play the synthesized audio in real-time.
    for more information, please refer to https://www.alibabacloud.com/help/document_detail/2712523.html
    '''
    complete_event = threading.Event()
    # Define a callback to handle the result
    class Callback(ResultCallback):
      def on_open(self):
        # when using object pool, on_open will be called after task start
        self.file = open(f'result_{task_id}.mp3', 'wb')
        print(f'[task_{task_id}] start')
      def on_complete(self):
        print(f'[task_{task_id}] speech synthesis task complete successfully.')
        complete_event.set()
      def on_error(self, message: str):
        print(f'[task_{task_id}] speech synthesis task failed, {message}')
      def on_close(self):
        # when using object pool, on_close will be called after task finished
        print(f'[task_{task_id}] finished')
      def on_event(self, message):
        # print(f'recv speech synthesis message {message}')
        pass
      def on_data(self, data: bytes) -> None:
        # send to player
        # save audio to file
        self.file.write(data)
    # Call the speech synthesizer callback
    synthesizer_callback = Callback()
    # Initialize the speech synthesizer
    # you can customize the synthesis parameters, like voice, format, sample_rate or other parameters
    if USE_CONNECTION_POOL:
      speech_synthesizer = connectionPool.borrow_synthesizer(
        model='cosyvoice-v3-flash',
        voice='longanyang',
        seed=12382,
        callback=synthesizer_callback
      )
    else:
      speech_synthesizer = SpeechSynthesizer(model='cosyvoice-v3-flash',
                         voice='longanyang',
                         seed=12382,
                         callback=synthesizer_callback)
    try:
      speech_synthesizer.call(text)
    except Exception as e:
      print(f'[task_{task_id}] speech synthesis task failed, {e}')
      if USE_CONNECTION_POOL:
        # close the synthesizer connection manually if task failed when using connection pool.
        speech_synthesizer.close()
      return
    print('[task_{}] Synthesized text: {}'.format(task_id, text))
    complete_event.wait()
    print('[task_{}][Metric] requestId: {}, first package delay ms: {}'.format(
      task_id,
      speech_synthesizer.get_last_request_id(),
      speech_synthesizer.get_first_package_delay()))
    if USE_CONNECTION_POOL:
      connectionPool.return_synthesizer(speech_synthesizer)
  # main function
  if __name__ == '__main__':
    # You must set dashscope.api_key and base_websocket_api_url before creating SpeechSynthesizerObjectPool.
    # The pool establishes WebSocket connections using the current global dashscope.api_key at initialization time.
    # Modifying dashscope.api_key after pool creation will not affect existing connections in the pool.
    dashscope.base_websocket_api_url='wss://maas.qwencloudapi.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()
  ```



####  Gestão de recursos e manipulação de erros

- Tarefa bem sucedida: depois de uma tarefa de síntese terminar normalmente, ligue para `connectionPool.return_synthesizer(speech_synthesizer)` Para devolver o `SpeechSynthesizer`  objeto à piscina para reutilização.

  

     Não volte. `SpeechSynthesizer`  objetos com tarefas incompletas ou falhadas.
  


- Se um erro interno SDK ou exceção lógica de negócios faz a tarefa abortar, feche a conexão subjacente do WebSocket: `speech_synthesizer.close()`.

-  Depois que todas as tarefas de síntese estiverem completas, desliguem a piscina. `connectionPool.shutdown()`.

-  Quando um erro falha ocorre do lado do servidor, não é necessário nenhum tratamento adicional.

Java SDK

O Java SDK consegue um desempenho ideal através da coordenação de uma piscina de conexão integrada e uma piscina de objetos personalizada.

- A piscina de conexão OkHttp3 integrada ao SDK gerencia e reutiliza conexões WebSocket subjacentes, reduzindo o aperto de mão da rede.  Isso está ativado por padrão.
-  Piscina de objetos construída em `commons-pool2` Esta piscina mantém um conjunto de pré-ligados `SpeechSynthesizer`  objetos. Emprestar da piscina elimina a latência da configuração da conexão, reduzindo significativamente o atraso do primeiro pacote.

####  Etapas de implementação

1.  Adicione dependências.  Adicione Dashscope-sdk-java e Commons-pool2 à configuração de dependência do seu projeto baseado na ferramenta de construção que você usa. Maven e Gradle exemplos:



  ```xml Maven
  <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>dashscope-sdk-java</artifactId>
      <!-- Replace 'the-latest-version' with version 2.16.9 or later. Find the version 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. Find the version at: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
      <version>the-latest-version</version>
  </dependency>
  ```

  ```groovy Gradle
  dependencies {
      // Replace 'the-latest-version' with version 2.16.6 or later. Find the version 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. Find the version at: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2
      implementation group: 'org.apache.commons', name: 'commons-pool2', version: 'the-latest-version'
  }
  ```



2.  Configure a piscina de conexão.  Configure os parâmetros de conjunto de conexão através de variáveis de ambiente:
Variável ambiente Descrição
DASHSCOPE_CONNECTION_POOL_SIZE Tamanho da piscina de conexão. Valor recomendado: pelo menos 2x a concorrência máxima. Padrão: 32.
PEDIDOS DE DASHSCOPE MAX MÍNIMOS Número máximo de pedidos de sincronização. Valor recomendado: o mesmo queDASHSCOPE_CONNECTION_POOL_SIZE. Padrão: 32.
DASHSCOPE MAX MÍNIMAS PEDIDOS DE ASSINATURA Número máximo de pedidos de sincronização por host. Valor recomendado: o mesmo queDASHSCOPE_CONNECTION_POOL_SIZE. Padrão: 32.
3. Configura o conjunto de objetos.  Configure o tamanho do pool de objetos através de variáveis de ambiente:
Variável ambiente Descrição
COSYVOICE_OBJECTPOOL_SIZE Tamanho da piscina de objetos. Valor recomendado: 1,5x-2x a concorrência máxima. Predefinição: 500.
  - O tamanho da piscina de objetos (`COSYVOICE_OBJECTPOOL_SIZE` Deve ser menor ou igual ao tamanho da piscina de conexão.`DASHSCOPE_CONNECTION_POOL_SIZE`).  Caso contrário, quando o objeto do pool solicita objetos e o pool de conexão está cheio, chamando os fios de bloqueio enquanto espera por conexões disponíveis.
  - O tamanho do conjunto de objetos não deve exceder o limite de QPS da sua conta.



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

```java
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. Emprestar um `SpeechSynthesizer`  objeto da piscina. Se o número de objetos não retornados exceder a capacidade máxima da piscina, o sistema cria um adicional `SpeechSynthesizer`  Protesto.  Esses objetos recém-criados requerem uma reiniciação e uma nova conexão WebSocket, para que eles não se beneficiem de fazer uma piscina.

```java
synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();
```

5.  Faça a síntese da fala.  Depois de pedir emprestado um `SpeechSynthesizer`  objeto da piscina, ligue `updateParamAndCallback(param, callback)` para ligar os parâmetros e chamar de volta para a tarefa atual, em seguida, ligue `streamingCall`  Ou `call` para sintetizar o discurso.

   

     - No cenário de piscina de objetos, `updateParamAndCallback` é chamado de **multiple times** (uma vez que um objeto é emprestado, para definir o retorno e parâmetros de nível de tarefa, como `voice` e `format`A apiKey passou em cada chamada deve permanecer a mesma. `updateParamAndCallback`  Só atualiza os campos locais da corrente `SpeechSynthesizer` E não reconstrói a conexão subjacente do WebSocket. O SDK escreve o `apiKey` para o `Authorization`  Só cabeçalho durante o aperto de mão WebSocket para autenticação.  Mensagens de tarefas posteriores (como `run-task`) não carregue o `apiKey`.  Enquanto a conexão reutilizada não estiver desconectada, passar uma nova `apiKey`  Não será enviado para o servidor. O pedido ainda usa o `apiKey` do aperto de mão inicial, que pode causar identidade, cota ou atribuição de faturamento, diferente das expectativas.
     - Para usar várias chaves API, mantenha ** instâncias de conjunto de objetos separados** para cada chave.
   


6.  Devolva o `SpeechSynthesizer`  Protesto.  Devolva o objeto após a tarefa completa para torná-lo disponível para reutilização.  Não devolva objetos com tarefas incompletas ou falhadas.

```java
CosyvoiceObjectPool.getInstance().returnObject(synthesizer);
```

Complete code

     Antes de usar este código, no cenário do conjunto de objetos, a ApiKey passou para `updateParamAndCallback` Em várias chamadas ** deve permanecer o mesmo** -- o SDK não atualiza a chave API de conexões estabelecidas, e passar uma chave API diferente não tem efeito. Para várias chaves API, mantenha um exemplo de conjunto de objetos separados para cada chave. Para detalhes, veja a nota importante acima.
  


  ```java
  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")
                              // Get an API Key: https://home.qwencloud.com/api-keys
                              // If no environment variable is configured, replace the next line with: .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 {
          Constants.baseWebsocketApiUrl = "wss://maas.qwencloudapi.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 seguinte configuração é baseada nos resultados dos testes de execução apenas o Qwen-Audio-TTS/CosyVoice  Serviço de síntese de fala em instâncias da Alibaba Cloud ECS com as especificações especificadas.  Uma excessiva concorrência pode aumentar a latência do processamento de tarefas. A "concorrencia de uma máquina única" refere-se ao número de Qwen-Audio-TTS/CosyVoice  tarefas de síntese em execução simultânea, equivalente ao número de linhas de trabalho.
Especificações ECS (Alibaba Cloud) Max, uma única máquina. Tamanho da piscina de objetos Tamanho da piscina de conexão
4 VCPUs, 8 GiB 100 500 2000
8 VCPUs, 16 GiB 150 500 2000
16 VCPUs, 32 GiB 200 500 2000
####  Gestão de recursos e manipulação de erros

-  Quando uma tarefa de síntese de fala termina normalmente, você deve chamar o método de retorno de GenericObjectPool para retornar o método de retorno de uma linguagem. `SpeechSynthesizer`  Volte para a piscina para reutilização. No código atual, isso corresponde a `CosyvoiceObjectPool.getInstance().returnObject(synthesizer)`.

  

     Não volte. `SpeechSynthesizer`  objetos cujas tarefas estão incompletas ou falharam.
  


-  Quando o SDK internamente ou a lógica empresarial lança uma exceção que interrompe uma tarefa, você deve realizar as seguintes duas operações:
  1.  Feche proativamente a conexão WebSocket subjacente.
  2. Invalide o objeto do conjunto de objetos para evitar que ele seja reutilizado.

```java
// Close the connection
synthesizer.getDuplexApi().close(1000, "bye");
// Invalidate the synthesizer that encountered an exception from the object pool
CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
```

-  Quando o serviço reporta um erro de tarefa, não é necessário nenhum tratamento adicional.

####  Chame o aquecimento e as diretrizes de medição de latência.

 Ao avaliar o desempenho do DashScope Java SDK (como latência de chamada simultânea), realize operações de aquecimento suficientes primeiro.  Isso garante que as medições reflitam o desempenho do estado estacionário e evita que os dados sejam desviados causados pela sobrecarga inicial do estabelecimento de conexão.

** Mecanismo de reaproveitamento de conexão**: O DashScope Java SDK gerencia e reutiliza eficientemente conexões WebSocket através de uma piscina global de conexão singleton, projetada para reduzir a sobrecarga de estabelecimento de conexão frequente e desconexão e melhorar a capacidade de processamento em cenários de alta concorrência. Este mecanismo funciona como segue:

- O SDK não cria conexões WebSocket na inicialização de serviço.  Ao invés disso, as conexões são estabelecidas sob demanda quando a primeira ligação é feita.
-  Após um pedido completo, a conexão é mantida na piscina por até 60 segundos para reutilização.
  - Se um novo pedido chegar em 60 segundos, a conexão existente é reutilizada, evitando o aperto de mão repetido.
  - Se uma conexão permanecer ociosa por mais de 60 segundos, ela é automaticamente fechada para liberar recursos.

 Nos seguintes cenários, a piscina de conexão pode não ter conexões ativas reutilizáveis, fazendo pedidos para estabelecer novas conexões.

- A inscrição acabou de começar e nenhuma ligação foi feita ainda.
- O serviço está inativo há mais de 60 segundos, e as conexões na piscina foram fechadas devido ao tempo de espera.

 Nestes cenários, o primeiro pedido deve completar uma conexão completa com WebSocket (Aperto de mão TCP, negociação TLS e atualização de protocolo), resultando em latência significativamente maior do que as solicitações subsequentes em conexões reutilizadas. Sem aquecimento, os resultados dos testes de desempenho são distorcidos pela inclusão de conexões de cima.

O tempo para o primeiro áudio relatado no lado SDK (como o valor obtido através do primeiro áudio) `get_first_package_delay()`) inclui o tempo gasto no estabelecimento de conexão WebSocket e transmissão de rede, e não é igual ao tempo real para o primeiro áudio do serviço modelo. O tempo real para o primeiro áudio refere-se ao intervalo de tempo de quando o servidor recebe o `run-task`  Comando para quando retornar o primeiro `result-generated`  evento. Este valor pode ser visto em registros do servidor. Em cenários de alta concorrência, devido ao estabelecimento de um grande número de conexões e agendamento de recursos, a latência relatada no lado SDK pode ser significativamente maior do que o tempo real para o primeiro áudio no lado do servidor. Se o SDK reportar o tempo para o primeiro áudio:

- Compare o tempo com o primeiro áudio nos registros do servidor. `run-task` para o primeiro `result-generated`) para verificar se o desempenho de inferência do modelo é normal.
- Use o conjunto de objetos ou o mecanismo de conexão descrito acima para aquecer para eliminar o estabelecimento de conexão WebSocket, para que a latência relatada no lado SDK reflita mais de perto o tempo real para o primeiro áudio.

Para obter dados confiáveis de desempenho, siga estes passos de aquecimento antes de realizar a avaliação formal de desempenho ou medição de latência:

1. Simule o nível de concorrência do seu teste formal enviando um certo número de chamadas com antecedência (por exemplo, continuamente por 1-2 minutos) para preencher totalmente o pool de conexão.
2.  Após confirmar que o conjunto de conexões estabeleceu e manteve um número suficiente de conexões ativas, comece a coleta formal de dados de desempenho.

Com aquecimento adequado, o conjunto de conexões SDK entra em um estado de reutilização estável, permitindo medir métricas de latência mais representativas que refletem com precisão o desempenho do serviço durante a operação on-line em estado estacionário.

#### Excepções comuns do Java SDK

Exception 1: Server TCP connections keep increasing despite stable business traffic

    ? Porque: ?

     Cada objeto SDK cria uma conexão quando instanciado. Se um objeto não for usado, cada objeto será destruído após sua tarefa. A conexão entra em um estado não referenciado e permanece aberta até que o servidor desencadeie um tempo limite de conexão após 61 segundos, o que significa que a conexão não pode ser reutilizada durante esses 61 segundos.

    Em cenários de alta concorrência, novas tarefas criam novas conexões quando não há conexões reutilizáveis, o que leva às seguintes consequências:

    1. O número de conexões continua aumentando.
    2. Conexões excessivas esgotam os recursos do servidor, fazendo o servidor ficar sem resposta.
    3. O pool de conexão atinge o limite, e novas tarefas são bloqueadas enquanto aguardam as conexões disponíveis.

    **Tipo 2:** Quando MaxIdle é definido para um valor menor que MaxTotal na configuração do conjunto de objetos, objetos ociosos que excedem MaxIdle são destruídos, causando vazamentos de conexão. As conexões deixadas permanecem abertas até que o tempo limite de 61 segundos desencadeie uma desconexão.  Semelhante ao tipo 1, isso faz com que o número de conexões continue aumentando.

    **Solução**:

    Para o tipo 1, use uma piscina de objetos.

    Para o Tipo 2, verifique os parâmetros de configuração do conjunto de objetos. Defina MaxIdle igual a MaxTotal, e desativar a política automática de destruição de objetos do conjunto de objetos.

Exception 2: Task takes 60 seconds longer than a normal call

    O mesmo que "Excepção 1". O pool de conexão atingiu seu limite máximo de conexão, e novas tarefas devem esperar 61 segundos para conexões não referenciadas para parar antes que uma conexão fique disponível.

Exception 3: Tasks are slow at service startup but gradually return to normal

    **Causa**: Em cenários de alta concorrência, o mesmo objeto reutiliza a mesma conexão WebSocket, então conexões WebSocket só são criadas na inicialização de serviço. Note que se chamadas de alta concorrência começarem imediatamente durante a fase de inicialização, criar muitas conexões WebSocket simultaneamente pode causar bloqueio.

     Aumentar gradualmente o nível de concorrência após iniciar o serviço, ou adicionar tarefas de aquecimento.

Exception 4: Server error "Invalid action('run-task')! Please follow the protocol!"

     Isso ocorre quando um erro no lado do cliente acontece, mas o servidor não sabe, deixando a conexão em um estado de tarefa ativo.  Quando a conexão e o objeto são reutilizados para a próxima tarefa, um erro de protocolo ocorre e a próxima tarefa falha.

    Ativamente feche a conexão WebSocket após uma exceção ser lançada, e então devolva o objeto para o tanque de objetos.

Exception 5: Abnormal call volume spikes despite stable business traffic

    **Causa**: criar muitas conexões WebSocket simultaneamente causa bloqueio, mas o tráfego de negócios continua chegando, levando a um atraso de tarefas de curto prazo.  Após a resolução do bloqueio, todas as tarefas são executadas imediatamente.  Isso causa picos de volume de chamadas e pode momentaneamente exceder o limite de concordância da conta, resultando em falhas parciais de tarefas, falta de resposta do servidor e outros problemas.

     Esta situação de criar muitas conexões WebSocket simultaneamente ocorre muitas vezes durante:

    - A fase de inicialização do serviço
    -  Anomalias de rede que causam um grande número de conexões WebSocket para desconectar e reconectar simultaneamente
    - Um grande número de erros no lado do servidor ocorrendo ao mesmo tempo, desencadeando reconexão de WebSocket em massa. Um erro comum está excedendo o limite de concorrência da conta.

    **Solução**:

    1.  Verifique as condições da rede.
    2.  Investigar se um grande número de outros erros no servidor ocorreram antes do pico.
    3. Aumente o limite de concordância da conta.
    4.  Reduza o tamanho da piscina de objetos e da piscina de conexão para limitar o nível máximo de concorrência através do limite superior da piscina de objetos.
    5. Atualize configurações de servidor ou adicione mais máquinas.

Exception 6: All tasks slow down as the concurrency level increases

    **Solução**:

    1.  Verifique se o limite de largura de banda da rede foi alcançado.
    2.  Verifique se o nível de concordância é muito alto.

- Escopo apoiado.

Para chamar os seguintes modelos, use o seu QwenCloud Chave da API:

Vozes apoiadas

Modelos diferentes suportam vozes diferentes. Defina o voice Requisita parâmetro ao valor na coluna ** do parâmetro voz da lista de voz.

Referência da API

FAQ

Como posso corrigir pronúncia incorreta na síntese de fala? Como eu controlo a pronúncia de personagens polifônicos?

Como posso solucionar o áudio silencioso usando uma voz clonada?

  1. Verifique o status da voz

    Ligue para a API de clonagem de voz e confirme que a voz status É OK.

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

    Certifique-se de que o target_model O parâmetro usado durante a clonagem de voz combina com o model parâmetro usado durante a síntese da fala. Por exemplo:

    • Se você usasse cosyvoice-v3-plus para clonagem
    • Você também deve usar cosyvoice-v3-plus para síntese
  3. ** Verifique a qualidade de áudio fonte **

    Verifique se o áudio de origem usado para clonagem de voz atende aos requisitos de áudio e melhores práticas:

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

    Confirme que o voice O parâmetro na sua solicitação de síntese de fala está definido para a identificação de voz clonada.

O que devo fazer se a voz clonada produzir fala instável ou incompleta?

Se o discurso sintetizado de uma voz clonada exibir qualquer um dos seguintes problemas:

A qualidade do áudio de origem não atende aos requisitos.

Verifique se o áudio de origem encontra o guia de gravação para clonagem de voz (/developer-guides/speech/voice-cloning#audio-requirements) requisitos. Recomendamos regravar com base nas diretrizes de gravação.

P: Por que a duração real difere da duração exibida no cabeçalho do arquivo WAV?

A síntese de fala usa um mecanismo de transmissão que retorna dados progressivamente conforme é gerado. A duração no cabeçalho do arquivo WAV salvo é uma estimativa e pode conter imprecisões. Por uma duração precisa, definir format para pcm, espere pelo resultado completo da síntese, e depois adicione o cabeçalho do arquivo WAV você mesmo.

P: Por que o áudio não toca?

Solução de problemas baseada nos seguintes cenários:

  1. Áudio salvo como um arquivo completo (como xx.mp3)
  2. O formato de áudio nos parâmetros de solicitação deve corresponder à extensão do arquivo (por exemplo, se o formato for definido como wav, o arquivo deve ser salvo como .wav).
  3. Compatibilidade do jogador: confirme que seu jogador suporta o formato de áudio e taxa de amostragem.
  4. Estou transmitindo a reprodução de áudio.
  5. Salve o fluxo de áudio como um arquivo completo e tente tocá-lo com um player de mídia. Se o arquivo não tocar, consulte os passos de solução de problemas no cenário 1.
  6. Se o arquivo tocar corretamente, o problema é na implementação da reprodução. Confirme que seu player suporta streaming de reprodução (como ffmpeg, pyaudio, AudioFormat ou MediaSource).

P: Por que a reprodução de áudio está gaguejando?

Resolver problemas com os seguintes passos:

  1. Verifique a taxa de envio de texto**: certifique-se de que o intervalo entre os segmentos de texto seja curto o suficiente para que o próximo segmento chegue antes do áudio anterior terminar de tocar.
  2. ** Verifique o desempenho da função de retorno de chamada**:
    • Confirme que a função de retorno não tem lógica de bloqueio.
    • A ligação é feita na linha WebSocket. Bloqueando-o atrasa a recepção de dados. Escreva dados de áudio em um buffer separado e processá-los em um fio separado.
  3. As flutuações da rede podem causar interrupções ou atrasos na transmissão de áudio.

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

Resolver problemas com os seguintes passos:

  1. Verifique o intervalo de entrada.

Para a síntese de streaming, confirme que o intervalo entre os segmentos de texto não é muito longo. Intervalos longos aumentam o tempo total de síntese. 2. Analise as métricas de desempenho.

Como posso restringir uma chave API para síntese de fala apenas (isolação de permissão)?

Criar um novo espaço de trabalho e autorizar apenas modelos específicos para limitar o alcance de uma chave API. Para detalhes, consulte Gerenciar espaços de trabalho.