> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modelstudio.console.alibabacloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Synthesize speech with qwen-audio-3.0-tts-flash over AOQ

> Use AOQ to connect to qwen-audio-3.0-tts-flash, send text in segments, and play synthesized speech in real time. The client code uses Android Java.

## Solution overview <span id="tts-overview-title" />

qwen-audio-3.0-tts-flash supports the AOQ Inference event protocol. This tutorial uses the model to demonstrate streaming speech synthesis over AOQ. The client sends run-task, continue-task, and finish-task over the Data track. The service streams audio over the Audio track and returns task events over the Data track.

A task can contain multiple continue-task events. Complete sentences are synthesized promptly. Incomplete sentences remain buffered until subsequent text completes them or the client sends finish-task. This approach is suitable for mobile playback, segmented long-text input, and low-latency speech output.

## Prerequisites <span id="tts-prerequisites-title" />

1. Activate Model Studio and follow [Obtain and configure an API key](/en/model-studio/get-api-key). Store the API key only on your application server. Do not include it in client code or commit it to a code repository.
2. Confirm the AOQ endpoint for the region in which your application is deployed. For selection guidance, see [Select a region, deployment scope, and endpoint](/en/model-studio/regions).
3. Download the latest AOQ Client SDK as described in [SDK download](/en/model-studio/realtime-sdk-download).
4. Build an application server and implement proxy authentication as described in [Token authentication](/en/model-studio/realtime-token-authentication). Before each new connection, the client must obtain new connection credentials from the application server.

### Import the SDK <span id="tts-import-sdk-title" />

Import the SDK for your development platform. The client implementation uses Android Java. Other platforms provide the same interfaces and event flow. This tutorial uses Opus audio streams. Import the corresponding plugin as described in the SDK download topic before proceeding.

<Tabs>
  <Tab title="Android">
    1. Place libPluginOpus.so from the Opus plugin in app/src/main/jniLibs/armeabi-v7a/ and app/src/main/jniLibs/arm64-v8a/ for the corresponding ABIs. Place AoqClientSdk-release.aar in app/libs, and configure the dependency and SDK-supported ABIs in app/build.gradle:

    ```groovy
    android {
        defaultConfig {
            minSdk 21
            ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
        }
    }

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.aar'])
    }
    ```

    2. Declare the following permissions in AndroidManifest.xml:

    ```xml
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    ```

    3. This scenario does not require microphone or camera permissions.
  </Tab>

  <Tab title="iOS">
    1. Add AoqClientSdk.framework and PluginOpus.framework to the Xcode project and select Embed & Sign under Target > General > Frameworks, Libraries, and Embedded Content. The SDK supports arm64 devices running iOS 13.0 or later.
    2. This scenario does not use the microphone or camera and requires no related permissions.
    3. Use import AoqClientSdk in Swift or #import \<AoqClientSdk/AoqClientSdk.h> in Objective-C.
  </Tab>

  <Tab title="HarmonyOS">
    1. Place libPluginOpus.so from the Opus plugin in entry/libs/arm64-v8a/. Place AoqClientSdk.har in entry/libs and declare the dependency in entry/oh-package.json5. The SDK is compatible with API 12 and supports arm64-v8a:

    ```json
    {
      "dependencies": {
        "@aoq/client-sdk": "file:./libs/AoqClientSdk.har"
      }
    }
    ```

    2. Declare the following permissions in entry/src/main/module.json5:

    ```json
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" }
    ]
    ```

    3. This scenario does not require microphone or camera permissions.
  </Tab>

  <Tab title="Linux (Python)">
    1. Extract the SDK and keep aoq\_client\_sdk.py, libAoqClientSdk.so, and libonnxruntime.so.1.16.3 in the same directory.
    2. Add the SDK directory to the Python and dynamic library search paths:

    ```bash
    export PYTHONPATH="$PWD/AoqClientSdk:$PYTHONPATH"
    export LD_LIBRARY_PATH="$PWD/AoqClientSdk:$LD_LIBRARY_PATH"
    ```

    3. Use import aoq\_client\_sdk in Python. You can also specify the absolute path of libAoqClientSdk.so by using AOQ\_CLIENT\_SDK\_LIB.
  </Tab>
</Tabs>

## Try the demo <span id="tts-experience-demo-title" />

Use the Android demo from Alibaba Cloud Model Studio to quickly verify AOQ connectivity. Download the APK and configure the API key and `workspaceId` to try selected models.

Scan the following QR code to download the demo:

<Image src="https://g-adoc.alcasset.com/media/maas_docs/sfm/common/images/6a4b3c2d1e0f92cc.png" alt="QR code for downloading the demo" width={100} height={100} />

## Implementation flow <span id="tts-flow-title" />

1. The application server obtains AOQ connection parameters for qwen-audio-3.0-tts-flash from the Inference token URL.
2. The client publishes the Data track, subscribes to the Audio and Data tracks, and configures the SDK audio decoder.
3. The client starts the local player and connects to AOQ. After the connection succeeds, it sends run-task with a new task\_id.
4. After task-started is received, the client sends one or more continue-task text segments at the pace required by the application.
5. After all text is sent, the client sends finish-task. The service returns the remaining audio and finally task-finished.
6. After task-finished is received, start another task over the same AOQ connection with a new task\_id, or disconnect and destroy the engine.

<Image src="https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/9418966871/p1094860.png" alt="Sequence diagram for streaming speech synthesis over AOQ" width={500} />

## Obtain a token from the application server <span id="tts-token-request-title" />

Set DASHSCOPE\_API\_KEY on the application server and send the request to the endpoint for the selected region. clientIp is the actual public IP address of the client. This field is optional, but specifying it helps the service allocate an appropriate relay endpoint.

```bash
curl -X POST \
  "https://{endpoint}/api/v1/webrtc/inference?model=qwen-audio-3.0-tts-flash" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${DASHSCOPE_API_KEY}" \
  -H "x-dashscope-rtc-transport: moq" \
  -d "{\"clientIp\": \"${CLIENT_REAL_IP}\"}"
```

<Note>
  If the application server cannot obtain the actual public IP address of the client, omit clientIp instead of passing an empty string.
</Note>

The application server returns the following response fields to the client. Never return the API key to a client in production. For all request and response fields, see [Token authentication](/en/model-studio/realtime-token-authentication).

<table style={{ display: "table", tableLayout: "fixed", width: "100%" }}><colgroup><col style={{ width: "40%" }} /><col style={{ width: "60%" }} /></colgroup><tbody><tr><td><p><strong>Response field</strong></p></td><td><p><strong>SDK field</strong></p></td></tr><tr><td><p>aoqTokenForClient</p></td><td><p>AoqConnectConfig.token</p></td></tr><tr><td><p>sid</p></td><td><p>AoqConnectConfig.sid</p></td></tr><tr><td><p>clientRelayCertFingerprint</p></td><td><p>AoqConnectConfig.certFingerprint</p></td></tr><tr><td><p>clientRelayEndpoints</p></td><td><p>AoqConnectConfig.relayEndpoints</p></td></tr><tr><td><p>extraInfo.workspaceIdHash</p></td><td><p>AoqConnectConfig.workspaceIdHash</p></td></tr></tbody></table>

## Implement the Android client <span id="tts-implementation-title" />

After the client obtains AoqConnectConfig from the application server, follow these steps to implement streaming speech synthesis on Android.

### 1. Create the engine and register callbacks <span id="tts-step-1-title" />

Create the singleton AOQ engine and register callbacks for connection and Data-track events. Maintain connection readiness in the connection callback and pass task events to the application state machine.

```java
AoqClientListener listener = new AoqClientListener() {
    @Override
    public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) {
        connected = status == AoqClientEngine.AoqConnectionStatus
                .AoqConnectionStatusConnected;
    }

    @Override
    public void onDataMsg(AoqClientEngine.AoqDataMsg msg) {
        handleServerEvent(msg);
    }
};

AoqClientEngine.AoqCreateConfig createConfig = new AoqClientEngine.AoqCreateConfig();
createConfig.workDir = context.getFilesDir().getAbsolutePath();
engine = AoqClientEngine.createEngine(context, createConfig, listener);
```

### 2. Start audio playback <span id="tts-step-2-title" />

TTS does not capture microphone audio. Initialize only the local player. Select the speaker or earpiece as the default output. The SDK automatically plays server audio from the Audio track.

```java
AoqClientEngine.AoqAudioPlaybackConfig playbackConfig =
        new AoqClientEngine.AoqAudioPlaybackConfig();
playbackConfig.channel = 1;
playbackConfig.isDefaultSpeaker = true;
engine.startAudioPlayer(playbackConfig);
```

### 3. Configure the decoder and tracks and connect <span id="tts-step-3-title" />

Configure the SDK audio decoder. Then publish the Data track and subscribe to the Audio and Data tracks. The following values are Opus examples for this tutorial. Populate AoqConnectConfig fields from the application-server token response.

```java
AoqClientEngine.AoqAudioCodecConfig audioDecoderConfig =
        new AoqClientEngine.AoqAudioCodecConfig();
audioDecoderConfig.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
audioDecoderConfig.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioOpus;
audioDecoderConfig.sampleRate = 24000; // Example. Match run-task.sample_rate.
audioDecoderConfig.channel = 1;
engine.setAudioDecoderConfig(audioDecoderConfig);

AoqClientEngine.AoqTrackParam publishDataTrack = new AoqClientEngine.AoqTrackParam();
publishDataTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeData;
connectConfig.publishTracks.add(publishDataTrack);

AoqClientEngine.AoqTrackParam subscribeAudioTrack = new AoqClientEngine.AoqTrackParam();
subscribeAudioTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
connectConfig.subscribeTracks.add(subscribeAudioTrack);

AoqClientEngine.AoqTrackParam subscribeDataTrack = new AoqClientEngine.AoqTrackParam();
subscribeDataTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeData;
connectConfig.subscribeTracks.add(subscribeDataTrack);
engine.connect(connectConfig);
```

### 4. Call sendDataMsg to send a run-task event <span id="tts-step-4-title" />

After the connection succeeds, generate a new UUID task\_id and configure the model, voice, text type, audio format, and sample rate. For optional parameters, see [Client events](/en/model-studio/cosyvoice-client-events).

```java
taskId = UUID.randomUUID().toString();
JSONObject header = new JSONObject()
        .put("action", "run-task")
        .put("task_id", taskId)
        .put("streaming", "duplex");
JSONObject parameters = new JSONObject()
        .put("text_type", "PlainText")
        .put("voice", voice)
        .put("format", "pcm")
        .put("sample_rate", 24000);
JSONObject payload = new JSONObject()
        .put("task_group", "audio")
        .put("task", "tts")
        .put("function", "SpeechSynthesizer")
        .put("model", "qwen-audio-3.0-tts-flash")
        .put("input", new JSONObject())
        .put("parameters", parameters);
JSONObject runTask = new JSONObject().put("header", header).put("payload", payload);
AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();
dataMessage.data = runTask.toString().getBytes(StandardCharsets.UTF_8);
engine.sendDataMsg(dataMessage);
```

### 5. Call sendDataMsg to send a continue-task event <span id="tts-step-5-title" />

Send continue-task only after task-started is received. A task can contain multiple segments. Each event supports up to 20,000 characters, and a task supports up to 200,000 characters in total. Send subsequent segments or finish the task promptly. Do not depend on a fixed connection-timeout value.

```java
JSONObject continueHeader = new JSONObject()
        .put("action", "continue-task")
        .put("task_id", taskId)
        .put("streaming", "duplex");
JSONObject payload = new JSONObject()
        .put("input", new JSONObject().put("text", text));
JSONObject continueTask = new JSONObject()
        .put("header", continueHeader)
        .put("payload", payload);
AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();
dataMessage.data = continueTask.toString().getBytes(StandardCharsets.UTF_8);
engine.sendDataMsg(dataMessage);
```

### 6. Handle server events <span id="tts-step-6-title" />

In onDataMsg, read header.event to maintain task state and handle failures. result-generated indicates that a sentence was synthesized, while the audio is still returned over the Audio track. For all fields, see [Server events](/en/model-studio/cosyvoice-server-events).

```java
JSONObject header = event.optJSONObject("header");
if (header == null) return;
String name = header.optString("event");
if ("task-started".equals(name)) {
    // The application can now send one or more continue-task events.
} else if ("result-generated".equals(name)) {
    // A sentence was synthesized. Audio is delivered over the Audio track.
} else if ("task-finished".equals(name)) {
    taskActive = false;
} else if ("task-failed".equals(name)) {
    taskActive = false;
    String message = header.optString("error_message");
    // Display or log the error.
}
```

### 7. Call sendDataMsg to send a finish-task event <span id="tts-step-7-title" />

Immediately after all text is sent, send finish-task to synthesize incomplete text buffered by the service, and wait for task-finished. For details, see [Client events](/en/model-studio/cosyvoice-client-events).

```java
JSONObject finishHeader = new JSONObject()
        .put("action", "finish-task")
        .put("task_id", taskId)
        .put("streaming", "duplex");
JSONObject finishTask = new JSONObject()
        .put("header", finishHeader)
        .put("payload", new JSONObject().put("input", new JSONObject()));
AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();
dataMessage.data = finishTask.toString().getBytes(StandardCharsets.UTF_8);
engine.sendDataMsg(dataMessage);
```

### 8. Disconnect and destroy the engine <span id="tts-step-8-title" />

Do not disconnect immediately after finish-task is sent. After task-finished or task-failed is received, disconnect and destroy the engine if no subsequent task will be started. The SDK automatically closes the audio player.

```java
engine.disconnect();
AoqClientEngine.destroy();
```

## Main server events <span id="tts-events-title" />

<table style={{ display: "table", tableLayout: "fixed", width: "100%" }}><colgroup><col style={{ width: "36%" }} /><col style={{ width: "64%" }} /></colgroup><tbody><tr><td><p><strong>Event</strong></p></td><td><p><strong>Description</strong></p></td></tr><tr><td><p>task-started</p></td><td><p>The task has started and continue-task can be sent</p></td></tr><tr><td><p>result-generated</p></td><td><p>A complete sentence was synthesized and its audio is returned over the Audio track</p></td></tr><tr><td><p>task-finished</p></td><td><p>All buffered text was processed and the task is complete</p></td></tr><tr><td><p>task-failed</p></td><td><p>The task failed. Read the error code and message</p></td></tr></tbody></table>

## Complete example <span id="tts-complete-title" />

The following class accepts an AoqConnectConfig mapped from the application-server token response. After the connection succeeds, call synthesize(text, voice). Add permissions, UI state, and reconnection logic in production.

```java height="500" expandable
import android.content.Context;

import com.alibaba.aoq.clientsdk.AoqClientEngine;
import com.alibaba.aoq.clientsdk.AoqClientListener;

import org.json.JSONException;
import org.json.JSONObject;

import java.nio.charset.StandardCharsets;
import java.util.UUID;

public final class TtsClient {
    private AoqClientEngine engine;
    private String taskId;
    private String pendingText;
    private String pendingVoice;
    private boolean connected;
    private boolean taskActive;

    public TtsClient(Context context, AoqClientEngine.AoqConnectConfig connectConfig) {
        AoqClientListener listener = new AoqClientListener() {
            @Override
            public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) {
                if (status == AoqClientEngine.AoqConnectionStatus.AoqConnectionStatusConnected) {
                    connected = true;
                } else if (status == AoqClientEngine.AoqConnectionStatus
                        .AoqConnectionStatusDisconnected) {
                    connected = false;
                }
            }

            @Override
            public void onDataMsg(AoqClientEngine.AoqDataMsg msg) {
                try {
                    JSONObject event = new JSONObject(
                            new String(msg.data, StandardCharsets.UTF_8));
                    String eventName = event.optJSONObject("header") == null
                            ? "" : event.optJSONObject("header").optString("event");
                    if ("task-started".equals(eventName)) {
                        sendContinueTask();
                        sendFinishTask();
                    } else if ("task-finished".equals(eventName)
                            || "task-failed".equals(eventName)) {
                        taskActive = false;
                    }
                } catch (JSONException e) {
                    throw new IllegalArgumentException("Invalid server event", e);
                }
            }
        };

        AoqClientEngine.AoqCreateConfig createConfig = new AoqClientEngine.AoqCreateConfig();
        createConfig.workDir = context.getFilesDir().getAbsolutePath();
        engine = AoqClientEngine.createEngine(context, createConfig, listener);

        // Example values. Match these settings to the output audio format in run-task.
        AoqClientEngine.AoqAudioCodecConfig audioDecoderConfig =
                new AoqClientEngine.AoqAudioCodecConfig();
        audioDecoderConfig.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
        audioDecoderConfig.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioOpus;
        audioDecoderConfig.sampleRate = 24000;
        audioDecoderConfig.channel = 1;
        engine.setAudioDecoderConfig(audioDecoderConfig);

        AoqClientEngine.AoqAudioPlaybackConfig playbackConfig =
                new AoqClientEngine.AoqAudioPlaybackConfig();
        playbackConfig.channel = 1;
        playbackConfig.isDefaultSpeaker = true;
        engine.startAudioPlayer(playbackConfig);

        AoqClientEngine.AoqTrackParam publishDataTrack =
                new AoqClientEngine.AoqTrackParam();
        publishDataTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeData;
        connectConfig.publishTracks.add(publishDataTrack);

        AoqClientEngine.AoqTrackParam subscribeAudioTrack =
                new AoqClientEngine.AoqTrackParam();
        subscribeAudioTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
        connectConfig.subscribeTracks.add(subscribeAudioTrack);

        AoqClientEngine.AoqTrackParam subscribeDataTrack =
                new AoqClientEngine.AoqTrackParam();
        subscribeDataTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeData;
        connectConfig.subscribeTracks.add(subscribeDataTrack);

        engine.connect(connectConfig);
    }

    public void synthesize(String text, String voice) {
        if (!connected || taskActive) {
            throw new IllegalStateException("The connection is not ready or a task is active.");
        }
        taskId = UUID.randomUUID().toString();
        pendingText = text;
        pendingVoice = voice;
        taskActive = true;
        sendRunTask();
    }

    private void sendRunTask() {
        try {
            JSONObject header = new JSONObject()
                    .put("action", "run-task")
                    .put("task_id", taskId)
                    .put("streaming", "duplex");
            JSONObject parameters = new JSONObject()
                    .put("text_type", "PlainText")
                    .put("voice", pendingVoice)
                    .put("format", "pcm")
                    .put("sample_rate", 24000);
            JSONObject payload = new JSONObject()
                    .put("task_group", "audio")
                    .put("task", "tts")
                    .put("function", "SpeechSynthesizer")
                    .put("model", "qwen-audio-3.0-tts-flash")
                    .put("input", new JSONObject())
                    .put("parameters", parameters);
            JSONObject runTask = new JSONObject().put("header", header).put("payload", payload);
            AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();
            dataMessage.data = runTask.toString().getBytes(StandardCharsets.UTF_8);
            engine.sendDataMsg(dataMessage);
        } catch (JSONException e) {
            throw new IllegalStateException("Failed to create run-task", e);
        }
    }

    private void sendContinueTask() {
        try {
            JSONObject header = new JSONObject()
                    .put("action", "continue-task")
                    .put("task_id", taskId)
                    .put("streaming", "duplex");
            JSONObject payload = new JSONObject()
                    .put("input", new JSONObject().put("text", pendingText));
            JSONObject continueTask = new JSONObject()
                    .put("header", header)
                    .put("payload", payload);
            AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();
            dataMessage.data = continueTask.toString().getBytes(StandardCharsets.UTF_8);
            engine.sendDataMsg(dataMessage);
        } catch (JSONException e) {
            throw new IllegalStateException("Failed to create continue-task", e);
        }
    }

    private void sendFinishTask() {
        try {
            JSONObject header = new JSONObject()
                    .put("action", "finish-task")
                    .put("task_id", taskId)
                    .put("streaming", "duplex");
            JSONObject finishTask = new JSONObject()
                    .put("header", header)
                    .put("payload", new JSONObject().put("input", new JSONObject()));
            AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();
            dataMessage.data = finishTask.toString().getBytes(StandardCharsets.UTF_8);
            engine.sendDataMsg(dataMessage);
        } catch (JSONException e) {
            throw new IllegalStateException("Failed to create finish-task", e);
        }
    }

    public void close() {
        engine.disconnect();
        AoqClientEngine.destroy();
    }
}
```

## Run and verify <span id="tts-run-title" />

1. Text is submitted only after task-started is received.
2. Audio for complete sentences is played continuously over the Audio track. Incomplete sentences are synthesized after finish-task.
3. task-finished is received after all audio is complete. Another task can then start with a new task\_id.

## Common scenarios <span id="tts-scenarios-title" />

### Multiple tasks over one connection <span id="tts-scenario-1-title" />

After task-finished is received, send another run-task with a new task\_id over the same AOQ connection. No new token is needed while the connection remains active. If the connection is closed, obtain new connection credentials.

### Change the voice <span id="tts-scenario-2-title" />

Each run-task can select a system voice or a valid voice\_id in parameters.voice. You can therefore change the voice between tasks over the same connection.

### Speaker or earpiece <span id="tts-scenario-3-title" />

Set the default output by using AoqAudioPlaybackConfig.isDefaultSpeaker, and call enableSpeakerphone to switch while the connection is active.

## Troubleshooting <span id="tts-troubleshooting-title" />

<table style={{ display: "table", tableLayout: "fixed", width: "100%" }}><colgroup><col style={{ width: "35%" }} /><col style={{ width: "65%" }} /></colgroup><tbody><tr><td><p><strong>Issue</strong></p></td><td><p><strong>Solution</strong></p></td></tr><tr><td><p>The connection succeeds but the task does not start</p></td><td><p>Make sure that credentials were obtained from the Inference token URL, and check the model name, task\_id, and Data-track publication in run-task.</p></td></tr><tr><td><p>continue-task is rejected</p></td><td><p>Wait for task-started, and use the same task\_id in run-task, continue-task, and finish-task.</p></td></tr><tr><td><p>The task succeeds but no audio is played</p></td><td><p>Make sure that the Audio track is subscribed and the player is running, and verify that the SDK decoder matches the output audio format selected in run-task.</p></td></tr><tr><td><p>The final text has no audio</p></td><td><p>Send finish-task after all text is sent, and wait for the remaining audio and task-finished before disconnecting.</p></td></tr></tbody></table>

## Related information <span id="tts-related-title" />

For all parameters, event fields, and interfaces for other platforms, see:

- [Obtain and configure an API key](/en/model-studio/get-api-key)
- [Select a region, deployment scope, and endpoint](/en/model-studio/regions)
- [AOQ Client SDK overview](/en/model-studio/realtime-api-aoq-sdk-desc)
- [SDK download](/en/model-studio/realtime-sdk-download)
- [Token authentication](/en/model-studio/realtime-token-authentication)
- [Qwen-Audio-TTS user guide](/en/model-studio/tts-model)
- [Client events](/en/model-studio/cosyvoice-client-events)
- [Server events](/en/model-studio/cosyvoice-server-events)
