全部產品
Search
文件中心

Alibaba Cloud Model Studio:SSML標記語言介紹

更新時間:Dec 25, 2025

SSML(Speech Synthesis Markup Language) 是一種基於 XML 的語音合成標記語言。它不僅能讓語音合成大模型讀出更豐富的常值內容,還支援對語速、語調、停頓、音量等語音特徵進行精細控制,甚至可以添加背景音樂,帶來更具表現力的語音效果。本文介紹CosyVoice的SSML功能及使用。

重要

如需使用“中國大陸(北京)”地區的模型,請前往“中國大陸(北京)”地區的API-KEY頁面

限制與約束

快速開始

運行代碼前,請完成以下準備工作:

  1. 擷取與配置 API Key

  2. 安裝SDK(如需運行Java/Python SDK樣本)

Java SDK

非流式調用

import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;

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

/**
 * SSML功能說明:
 *     1. 只有非流式調用和單向流式調用支援SSML功能
 *     2. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色)
 */
public class Main {
    private static String model = "cosyvoice-v3-flash";
    private static String voice = "longanyang";

    public static void main(String[] args) {
        streamAudioDataToSpeaker();
        System.exit(0);
    }

    public static void streamAudioDataToSpeaker() {
        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // 若沒有配置環境變數,請用百鍊API Key將下行替換為:.apikey("sk-xxx")
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .model(model)
                        .voice(voice)
                        .build();

        SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
        ByteBuffer audio = null;
        try {
            // 非流式調用,阻塞直至音頻返回
            // 特殊字元需要進行轉義
            audio = synthesizer.call("<speak rate=\"2\">我的語速比正常人快。</speak>");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 任務結束關閉websocket串連
            synthesizer.getDuplexApi().close(1000, "bye");
        }
        if (audio != null) {
            // 將音頻資料儲存到本地檔案“output.mp3”中
            File file = new File("output.mp3");
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(audio.array());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        
        // 首次發送文本時需建立 WebSocket 串連,因此首包延遲會包含串連建立的耗時
        System.out.println(
                "[Metric] requestId為:"
                        + synthesizer.getLastRequestId()
                        + "首包延遲(毫秒)為:"
                        + synthesizer.getFirstPackageDelay());
    }
}

單向流式調用

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 java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;

/**
 * SSML功能說明:
 *     1. 只有非流式調用和單向流式調用支援SSML功能
 *     2. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色)
 */
public class Main {
    private static String model = "cosyvoice-v3-flash";
    private static String voice = "longanyang";

    public static void main(String[] args) {
        streamAudioDataToSpeaker();
        System.out.println("音頻已儲存到 output.mp3 檔案中");
        System.exit(0);
    }

    public static void streamAudioDataToSpeaker() {
        CountDownLatch latch = new CountDownLatch(1);
        final FileOutputStream[] fileOutputStream = new FileOutputStream[1];

        try {
            fileOutputStream[0] = new FileOutputStream("output.mp3");
        } catch (IOException e) {
            System.err.println("無法建立輸出檔案: " + e.getMessage());
            return;
        }

        // 實現回調介面ResultCallback
        ResultCallback<SpeechSynthesisResult> callback = new ResultCallback<SpeechSynthesisResult>() {
            @Override
            public void onEvent(SpeechSynthesisResult result) {
                if (result.getAudioFrame() != null) {
                    // 將音頻資料寫入本地檔案
                    try {
                        byte[] audioData = result.getAudioFrame().array();
                        fileOutputStream[0].write(audioData);
                        fileOutputStream[0].flush();
                    } catch (IOException e) {
                        System.err.println("寫入音頻資料失敗: " + e.getMessage());
                    }
                }
            }

            @Override
            public void onComplete() {
                System.out.println("收到Complete,語音合成結束");
                closeFileOutputStream(fileOutputStream[0]);
                latch.countDown();
            }

            @Override
            public void onError(Exception e) {
                System.out.println("出現異常:" + e.toString());
                closeFileOutputStream(fileOutputStream[0]);
                latch.countDown();
            }
        };

        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // 若沒有配置環境變數,請用百鍊API Key將下行替換為:.apikey("sk-xxx")
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .model(model)
                        .voice(voice)
                        .format(SpeechSynthesisAudioFormat.MP3_22050HZ_MONO_256KBPS)
                        .build();

        SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, callback);

        try {
            // 單向流式調用,立即返回null(實際結果通過回調介面非同步傳遞),在回調介面的onEvent方法中即時擷取二進位音頻
            // 特殊字元需要進行轉義
            synthesizer.call("<speak rate=\"2\">我的語速比正常人快。</speak>");
            // 等待合成完成
            latch.await();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 任務結束後關閉websocket串連
            try {
                synthesizer.getDuplexApi().close(1000, "bye");
            } catch (Exception e) {
                System.err.println("關閉WebSocket串連失敗: " + e.getMessage());
            }

            // 確保檔案流被關閉
            closeFileOutputStream(fileOutputStream[0]);
        }
        
        // 首次發送文本時需建立 WebSocket 串連,因此首包延遲會包含串連建立的耗時
        System.out.println(
                "[Metric] requestId為:"
                        + synthesizer.getLastRequestId()
                        + ",首包延遲(毫秒)為:"
                        + synthesizer.getFirstPackageDelay());
    }

    private static void closeFileOutputStream(FileOutputStream fileOutputStream) {
        try {
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
        } catch (IOException e) {
            System.err.println("關閉檔案流失敗: " + e.getMessage());
        }
    }
}

Python SDK

非流式調用

# coding=utf-8
# SSML功能說明:
#     1. 只有非流式調用和單向流式調用支援SSML功能
#     2. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色)

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

# 若沒有配置環境變數,請用百鍊API Key將下行替換為:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# 模型
model = "cosyvoice-v3-flash"
# 音色
voice = "longanyang"

# 執行個體化SpeechSynthesizer,並在構造方法中傳入模型(model)、音色(voice)等請求參數
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# 非流式調用,阻塞直至音頻返回
# 特殊字元需要進行轉義
audio = synthesizer.call("<speak rate=\"2\">我的語速比正常人快。</speak>")

# 將音頻儲存至本地
with open('output.mp3', 'wb') as f:
    f.write(audio)

# 首次發送文本時需建立 WebSocket 串連,因此首包延遲會包含串連建立的耗時
print('[Metric] requestId為:{},首包延遲為:{}毫秒'.format(
    synthesizer.get_last_request_id(),
    synthesizer.get_first_package_delay()))

單向流式調用

# coding=utf-8
# SSML功能說明:
#     1. 只有非流式調用和單向流式調用支援SSML功能
#     2. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色)

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

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

# 若沒有配置環境變數,請用百鍊API Key將下行替換為:dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# 模型
model = "cosyvoice-v3-flash"
# 音色
voice = "longanyang"

# 定義回調介面
class Callback(ResultCallback):
    _player = None
    _stream = None

    def on_open(self):
        # 開啟輸出檔案,準備寫入音頻資料
        self.file = open("output.mp3", "wb")
        print("串連建立:" + get_timestamp())

    def on_complete(self):
        print("語音合成完成,所有合成結果已被接收:" + get_timestamp())
        if hasattr(self, 'file') and self.file:
            self.file.close()
        self
        # 首次發送文本時需建立 WebSocket 串連,因此首包延遲會包含串連建立的耗時
        print('[Metric] requestId為:{},首包延遲為:{}毫秒'.format(
            self.synthesizer.get_last_request_id(),
            self.synthesizer.get_first_package_delay()))

    def on_error(self, message: str):
        print(f"語音合成出現異常:{message}")
        if hasattr(self, 'file') and self.file:
            self.file.close()

    def on_close(self):
        print("串連關閉:" + get_timestamp())
        if hasattr(self, 'file') and self.file:
            self.file.close()

    def on_event(self, message):
        pass

    def on_data(self, data: bytes) -> None:
        print(get_timestamp() + " 二進位音頻長度為:" + str(len(data)))
        # 將音頻資料寫入檔案
        self.file.write(data)

callback = Callback()

# 執行個體化SpeechSynthesizer,並在構造方法中傳入模型(model)、音色(voice)等請求參數
synthesizer = SpeechSynthesizer(
    model=model,
    voice=voice,
    callback=callback,
)

# 將synthesizer執行個體賦值給callback,以便在on_complete中使用
callback.synthesizer = synthesizer

# 單向流式調用,發送待合成文本,在回調介面的on_data方法中即時擷取二進位音頻
# 特殊字元需要進行轉義
synthesizer.call("<speak rate=\"2\">我的語速比正常人快。</speak>")

WebSocket API

Go

// SSML功能說明:
//     1. 在發送run-task指令時,將參數enable_ssml設定為true,以開啟SSML支援
//     2. 通過continue-task指令發送包含SSML的文本,且只允許發送一次continue-task指令
//     3. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色)

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
    "time"

    "github.com/google/uuid"
    "github.com/gorilla/websocket"
)

const (
    wsURL      = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/"
    outputFile = "output.mp3"
)

func main() {
    // 若沒有將API Key配置到環境變數,可將下行替換為:apiKey := "your_api_key"。不建議在生產環境中直接將API Key寫入程式碼到代碼中,以減少API Key泄露風險。
    apiKey := os.Getenv("DASHSCOPE_API_KEY")

    // 清空輸出檔案
    os.Remove(outputFile)
    os.Create(outputFile)

    // 串連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("串連失敗 HTTP狀態代碼: %d\n", resp.StatusCode)
        }
        fmt.Println("串連失敗:", err)
        return
    }
    defer conn.Close()

    // 產生任務ID
    taskID := uuid.New().String()
    fmt.Printf("產生任務ID: %s\n", taskID)

    // 發送run-task指令
    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":      "cosyvoice-v3-flash",
            "parameters": map[string]interface{}{
                "text_type":   "PlainText",
                "voice":       "longanyang",
                "format":      "mp3",
                "sample_rate": 22050,
                "volume":      50,
                "rate":        1,
                "pitch":       1,
                // 如果enable_ssml設為true,只允許發送一次continue-task指令,否則會報錯“Text request limit violated, expected 1.”
                "enable_ssml": true,
            },
            "input": map[string]interface{}{},
        },
    }

    runTaskJSON, _ := json.Marshal(runTaskCmd)
    fmt.Printf("發送run-task指令: %s\n", string(runTaskJSON))

    err = conn.WriteMessage(websocket.TextMessage, runTaskJSON)
    if err != nil {
        fmt.Println("發送run-task失敗:", err)
        return
    }

    textSent := false

    // 處理訊息
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            fmt.Println("讀取訊息失敗:", err)
            break
        }

        // 處理二進位訊息
        if messageType == websocket.BinaryMessage {
            fmt.Printf("收到二進位訊息,長度: %d\n", len(message))
            file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
            file.Write(message)
            file.Close()
            continue
        }

        // 處理簡訊
        messageStr := string(message)
        fmt.Printf("收到簡訊: %s\n", strings.ReplaceAll(messageStr, "\n", ""))

        // 簡單解析JSON擷取event類型
        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("事件類型: %s\n", event)

                    switch event {
                    case "task-started":
                        fmt.Println("=== 收到task-started事件 ===")

                        if !textSent {
                            // 發送 continue-task 指令,使用SSML功能時,該指令只允許發送一次
                            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": "<speak rate=\"2\">我的語速比正常人快。</speak>",
                                    },
                                },
                            }

                            continueTaskJSON, _ := json.Marshal(continueTaskCmd)
                            fmt.Printf("發送continue-task指令: %s\n", string(continueTaskJSON))

                            err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON)
                            if err != nil {
                                fmt.Println("發送continue-task失敗:", err)
                                return
                            }

                            textSent = true

                            // 延遲發送finish-task
                            time.Sleep(500 * time.Millisecond)

                            // 發送finish-task指令
                            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("發送finish-task指令: %s\n", string(finishTaskJSON))

                            err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON)
                            if err != nil {
                                fmt.Println("發送finish-task失敗:", err)
                                return
                            }
                        }

                    case "task-finished":
                        fmt.Println("=== 任務完成 ===")
                        return

                    case "task-failed":
                        fmt.Println("=== 任務失敗 ===")
                        if header["error_message"] != nil {
                            fmt.Printf("錯誤資訊: %s\n", header["error_message"])
                        }
                        return

                    case "result-generated":
                        fmt.Println("收到result-generated事件")
                    }
                }
            }
        }
    }
}

C#

using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

// SSML功能說明:
//     1. 在發送run-task指令時,將參數enable_ssml設定為true,以開啟SSML支援
//     2. 通過continue-task指令發送包含SSML的文本,且只允許發送一次continue-task指令
//     3. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色)
class Program {
    // 若沒有將API Key配置到環境變數,可將下行替換為:private const string ApiKey="your_api_key"。不建議在生產環境中直接將API Key寫入程式碼到代碼中,以減少API Key泄露風險。
    private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");

    // WebSocket伺服器位址
    private const string WebSocketUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/";
    // 輸出檔案路徑
    private const string OutputFilePath = "output.mp3";

    // WebSocket用戶端
    private static ClientWebSocket _webSocket = new ClientWebSocket();
    // 取消令牌源
    private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
    // 任務ID
    private static string? _taskId;
    // 任務是否已啟動
    private static TaskCompletionSource<bool> _taskStartedTcs = new TaskCompletionSource<bool>();

    static async Task Main(string[] args) {
        try {
            // 清空輸出檔案
            ClearOutputFile(OutputFilePath);

            // 串連WebSocket服務
            await ConnectToWebSocketAsync(WebSocketUrl);

            // 啟動接收訊息的任務
            Task receiveTask = ReceiveMessagesAsync();

            // 發送run-task指令
            _taskId = GenerateTaskId();
            await SendRunTaskCommandAsync(_taskId);

            // 等待task-started事件
            await _taskStartedTcs.Task;

            // 發送 continue-task 指令,使用SSML功能時,該指令只允許發送一次
            // 特殊字元需要進行轉義
            await SendContinueTaskCommandAsync("<speak rate=\"2\">我的語速比正常人快。</speak>");

            // 發送finish-task指令
            await SendFinishTaskCommandAsync(_taskId);

            // 等待接收任務完成
            await receiveTask;

            Console.WriteLine("任務完成,串連已關閉。");
        } catch (OperationCanceledException) {
            Console.WriteLine("任務被取消。");
        } catch (Exception ex) {
            Console.WriteLine($"發生錯誤:{ex.Message}");
        } finally {
            _cancellationTokenSource.Cancel();
            _webSocket.Dispose();
        }
    }

    private static void ClearOutputFile(string filePath) {
        if (File.Exists(filePath)) {
            File.WriteAllText(filePath, string.Empty);
            Console.WriteLine("輸出檔案已清空。");
        } else {
            Console.WriteLine("輸出檔案不存在,無需清空。");
        }
    }

    private static async Task ConnectToWebSocketAsync(string url) {
        var uri = new Uri(url);
        if (_webSocket.State == WebSocketState.Connecting || _webSocket.State == WebSocketState.Open) {
            return;
        }

        // 設定WebSocket串連的頭部資訊
        _webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}");
        _webSocket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable");

        try {
            await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token);
            Console.WriteLine("已成功串連到WebSocket服務。");
        } catch (OperationCanceledException) {
            Console.WriteLine("WebSocket串連被取消。");
        } catch (Exception ex) {
            Console.WriteLine($"WebSocket串連失敗: {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 = "cosyvoice-v3-flash",
            parameters = new
            {
                text_type = "PlainText",
                voice = "longanyang",
                format = "mp3",
                sample_rate = 22050,
                volume = 50,
                rate = 1,
                pitch = 1,
                // 如果enable_ssml設為true,只允許發送一次continue-task指令,否則會報錯“Text request limit violated, expected 1.”
                enable_ssml = true
            },
            input = new { }
        });

        await SendJsonMessageAsync(command);
        Console.WriteLine("已發送run-task指令。");
    }

    private static async Task SendContinueTaskCommandAsync(string text) {
        if (_taskId == null) {
            throw new InvalidOperationException("任務ID未初始化。");
        }

        var command = CreateCommand("continue-task", _taskId, "duplex", new {
            input = new {
                text
            }
        });

        await SendJsonMessageAsync(command);
        Console.WriteLine("已發送continue-task指令。");
    }

    private static async Task SendFinishTaskCommandAsync(string taskId) {
        var command = CreateCommand("finish-task", taskId, "duplex", new {
            input = new { }
        });

        await SendJsonMessageAsync(command);
        Console.WriteLine("已發送finish-task指令。");
    }

    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("訊息發送被取消。");
        }
    }

    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("任務已啟動。");
                        _taskStartedTcs.TrySetResult(true);
                        break;
                    case "task-finished":
                        Console.WriteLine("任務已完成。");
                        _cancellationTokenSource.Cancel();
                        break;
                    case "task-failed":
                        Console.WriteLine("任務失敗:" + response.RootElement.GetProperty("header").GetProperty("error_message").GetString());
                        _cancellationTokenSource.Cancel();
                        break;
                    default:
                        // result-generated可在此處理
                        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) {
                // 處理位元據
                Console.WriteLine("接收到位元據...");

                // 將位元據儲存到檔案
                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("訊息接收被取消。");
            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

範例程式碼目錄結構為:

my-php-project/

├── composer.json

├── vendor/

└── index.php

composer.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/"
        }
    }
}

index.php內容如下:

<!-- SSML功能說明: -->
<!--     1. 在發送run-task指令時,將參數enable_ssml設定為true,以開啟SSML支援 -->
<!--     2. 通過continue-task指令發送包含SSML的文本,且只允許發送一次continue-task指令 -->
<!--     3. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色) -->

<?php

require __DIR__ . '/vendor/autoload.php';

use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;

// 若沒有將API Key配置到環境變數,可將下行替換為:$api_key="your_api_key"。不建議在生產環境中直接將API Key寫入程式碼到代碼中,以減少API Key泄露風險。
$api_key = getenv("DASHSCOPE_API_KEY");
$websocket_url = 'wss://dashscope.aliyuncs.com/api-ws/v1/inference/'; // WebSocket伺服器位址
$output_file = 'output.mp3'; // 輸出檔案路徑

$loop = Loop::get();

if (file_exists($output_file)) {
    // 清空檔案內容
    file_put_contents($output_file, '');
}

// 建立自訂的連接器
$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 "串連到WebSocket伺服器\n";

    // 產生任務ID
    $taskId = generateTaskId();

    // 發送 run-task 指令
    sendRunTaskMessage($conn, $taskId);

    // 定義發送 continue-task 指令的函數
    $sendContinueTask = function() use ($conn, $loop, $taskId) {
        // 發送 continue-task 指令,使用SSML功能時,該指令只允許發送一次
        $continueTaskMessage = json_encode([
            "header" => [
                "action" => "continue-task",
                "task_id" => $taskId,
                "streaming" => "duplex"
            ],
            "payload" => [
                "input" => [
                    // 特殊字元需要進行轉義
                    "text" => "<speak rate=\"2\">我的語速比正常人快。</speak>"
                ]
            ]
        ]);
        $conn->send($continueTaskMessage);

        // 發送 finish-task 指令
        sendFinishTaskMessage($conn, $taskId);
    };

    // 標記是否收到 task-started 事件
    $taskStarted = false;

    // 監聽訊息
    $conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) {
        if ($msg->isBinary()) {
            // 寫入位元據到本地檔案
            file_put_contents($output_file, $msg->getPayload(), FILE_APPEND);
        } else {
            // 處理非二進位訊息
            $response = json_decode($msg, true);

            if (isset($response['header']['event'])) {
                handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted);
            } else {
                echo "未知的訊息格式\n";
            }
        }
    });

    // 監聽串連關閉
    $conn->on('close', function($code = null, $reason = null) {
        echo "串連已關閉\n";
        if ($code !== null) {
            echo "關閉代碼: " . $code . "\n";
        }
        if ($reason !== null) {
            echo "關閉原因:" . $reason . "\n";
        }
    });
}, function ($e) {
    echo "無法串連:{$e->getMessage()}\n";
});

$loop->run();

/**
 * 產生任務ID
 * @return string
 */
function generateTaskId(): string {
    return bin2hex(random_bytes(16));
}

/**
 * 發送 run-task 指令
 * @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" => "cosyvoice-v3-flash",
            "parameters" => [
                "text_type" => "PlainText",
                "voice" => "longanyang",
                "format" => "mp3",
                "sample_rate" => 22050,
                "volume" => 50,
                "rate" => 1,
                "pitch" => 1,
                // 如果enable_ssml設為true,只允許發送一次continue-task指令,否則會報錯“Text request limit violated, expected 1.”
                "enable_ssml" => true
            ],
            "input" => (object) []
        ]
    ]);
    echo "準備發送run-task指令: " . $runTaskMessage . "\n";
    $conn->send($runTaskMessage);
    echo "run-task指令已發送\n";
}

/**
 * 讀取音頻檔案
 * @param string $filePath
 * @return bool|string
 */
function readAudioFile(string $filePath) {
    $voiceData = file_get_contents($filePath);
    if ($voiceData === false) {
        echo "無法讀取音頻檔案\n";
    }
    return $voiceData;
}

/**
 * 分割音頻資料
 * @param string $data
 * @param int $chunkSize
 * @return array
 */
function splitAudioData(string $data, int $chunkSize): array {
    return str_split($data, $chunkSize);
}

/**
 * 發送 finish-task 指令
 * @param $conn
 * @param $taskId
 */
function sendFinishTaskMessage($conn, $taskId) {
    $finishTaskMessage = json_encode([
        "header" => [
            "action" => "finish-task",
            "task_id" => $taskId,
            "streaming" => "duplex"
        ],
        "payload" => [
            "input" => (object) []
        ]
    ]);
    echo "準備發送finish-task指令: " . $finishTaskMessage . "\n";
    $conn->send($finishTaskMessage);
    echo "finish-task指令已發送\n";
}

/**
 * 處理事件
 * @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 "任務開始,發送continue-task指令...\n";
            $taskStarted = true;
            // 發送 continue-task 指令
            $sendContinueTask();
            break;
        case 'result-generated':
            // 忽略result-generated事件
            break;
        case 'task-finished':
            echo "任務完成\n";
            $conn->close();
            break;
        case 'task-failed':
            echo "任務失敗\n";
            echo "錯誤碼:" . $response['header']['error_code'] . "\n";
            echo "錯誤資訊:" . $response['header']['error_message'] . "\n";
            $conn->close();
            break;
        case 'error':
            echo "錯誤:" . $response['payload']['message'] . "\n";
            break;
        default:
            echo "未知事件:" . $response['header']['event'] . "\n";
            break;
    }

    // 如果任務已完成,關閉串連
    if ($response['header']['event'] == 'task-finished') {
        // 等待1秒以確保所有資料都已傳輸完畢
        $loop->addTimer(1, function() use ($conn) {
            $conn->close();
            echo "用戶端關閉串連\n";
        });
    }

    // 如果沒有收到 task-started 事件,關閉串連
    if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) {
        $conn->close();
    }
}

Node.js

需安裝相關依賴:

npm install ws
npm install uuid

範例程式碼如下:

// SSML功能說明:
//     1. 在發送run-task指令時,將參數enable_ssml設定為true,以開啟SSML支援
//     2. 通過continue-task指令發送包含SSML的文本,且只允許發送一次continue-task指令
//     3. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色)

import fs from 'fs';
import WebSocket from 'ws';
import { v4 as uuid } from 'uuid'; // 用於產生UUID

// 若沒有將API Key配置到環境變數,可將下行替換為:apiKey = 'your_api_key'。不建議在生產環境中直接將API Key寫入程式碼到代碼中,以減少API Key泄露風險。
const apiKey = process.env.DASHSCOPE_API_KEY;
// WebSocket伺服器位址
const url = 'wss://dashscope.aliyuncs.com/api-ws/v1/inference/';
// 輸出檔案路徑
const outputFilePath = 'output.mp3';

// 清空輸出檔案
fs.writeFileSync(outputFilePath, '');

// 建立WebSocket用戶端
const ws = new WebSocket(url, {
  headers: {
    Authorization: `bearer ${apiKey}`,
    'X-DashScope-DataInspection': 'enable'
  }
});

let taskStarted = false;
let taskId = uuid();

ws.on('open', () => {
  console.log('已串連到WebSocket伺服器');

  // 發送run-task指令
  const runTaskMessage = JSON.stringify({
    header: {
      action: 'run-task',
      task_id: taskId,
      streaming: 'duplex'
    },
    payload: {
      task_group: 'audio',
      task: 'tts',
      function: 'SpeechSynthesizer',
      model: 'cosyvoice-v3-flash',
      parameters: {
        text_type: 'PlainText',
        voice: 'longanyang', // 音色
        format: 'mp3', // 音頻格式
        sample_rate: 22050, // 採樣率
        volume: 50, // 音量
        rate: 1, // 語速
        pitch: 1, // 音調
        enable_ssml: true // 是否開啟SSML功能。如果enable_ssml設為true,只允許發送一次continue-task指令,否則會報錯“Text request limit violated, expected 1.”
      },
      input: {}
    }
  });
  ws.send(runTaskMessage);
  console.log('已發送run-task訊息');
});

const fileStream = fs.createWriteStream(outputFilePath, { flags: 'a' });
ws.on('message', (data, isBinary) => {
  if (isBinary) {
    // 寫入位元據到檔案
    fileStream.write(data);
  } else {
    const message = JSON.parse(data);

    switch (message.header.event) {
      case 'task-started':
        taskStarted = true;
        console.log('任務已開始');
        // 發送continue-task指令
        sendContinueTasks(ws);
        break;
      case 'task-finished':
        console.log('任務已完成');
        ws.close();
        fileStream.end(() => {
          console.log('檔案流已關閉');
        });
        break;
      case 'task-failed':
        console.error('任務失敗:', message.header.error_message);
        ws.close();
        fileStream.end(() => {
          console.log('檔案流已關閉');
        });
        break;
      default:
        // 可以在這裡處理result-generated
        break;
    }
  }
});

function sendContinueTasks(ws) {
  
  if (taskStarted) {
    // 發送 continue-task 指令,使用SSML功能時,該指令只允許發送一次
    const continueTaskMessage = JSON.stringify({
      header: {
        action: 'continue-task',
        task_id: taskId,
        streaming: 'duplex'
      },
      payload: {
        input: {
          // 特殊字元需要進行轉義
          text: '<speak rate="2">我的語速比正常人快。</speak>'
        }
      }
    });
    ws.send(continueTaskMessage);
    
    // 發送finish-task指令
    const finishTaskMessage = JSON.stringify({
      header: {
        action: 'finish-task',
        task_id: taskId,
        streaming: 'duplex'
      },
      payload: {
        input: {}
      }
    });
    ws.send(finishTaskMessage);
  }
}

ws.on('close', () => {
  console.log('已斷開與WebSocket伺服器的串連');
});

Java

如您使用Java程式設計語言,建議採用Java DashScope SDK進行開發,詳情請參見Java SDK

以下是Java WebSocket的調用樣本。在運行樣本前,請確保已匯入以下依賴:

  • Java-WebSocket

  • jackson-databind

推薦您使用Maven或Gradle管理依賴包,其配置如下:

pom.xml

<dependencies>
    <!-- WebSocket Client -->
    <dependency>
        <groupId>org.java-websocket</groupId>
        <artifactId>Java-WebSocket</artifactId>
        <version>1.5.3</version>
    </dependency>

    <!-- JSON Processing -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.0</version>
    </dependency>
</dependencies>

build.gradle

// 省略其它代碼
dependencies {
  // WebSocket Client
  implementation 'org.java-websocket:Java-WebSocket:1.5.3'
  // JSON Processing
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
}
// 省略其它代碼

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.*;

/**
 * SSML功能說明:
 *     1. 在發送run-task指令時,將參數enable_ssml設定為true,以開啟SSML支援
 *     2. 通過continue-task指令發送包含SSML的文本,且只允許發送一次continue-task指令
 *     3. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色)
 */
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("串連成功");

        // 發送run-task指令
        // 如果enable_ssml設為true,只允許發送一次continue-task指令,否則會報錯“Text request limit violated, expected 1.”
        String runTaskCommand = "{ \"header\": { \"action\": \"run-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"task_group\": \"audio\", \"task\": \"tts\", \"function\": \"SpeechSynthesizer\", \"model\": \"cosyvoice-v3-flash\", \"parameters\": { \"text_type\": \"PlainText\", \"voice\": \"longanyang\", \"format\": \"mp3\", \"sample_rate\": 22050, \"volume\": 50, \"rate\": 1, \"pitch\": 1, \"enable_ssml\": true }, \"input\": {} }}";
        send(runTaskCommand);
    }

    @Override
    public void onMessage(String message) {
        System.out.println("收到服務端返回的訊息:" + 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("收到服務端返回的task-started事件");

                        // 發送 continue-task 指令,使用SSML功能時,該指令只允許發送一次
                        // 特殊字元需要進行轉義
                        sendContinueTask("<speak rate=\\\"2\\\">我的語速比正常人快。</speak>");

                        // 發送finish-task指令
                        sendFinishTask();
                    } else if ("task-finished".equals(event)) {
                        System.out.println("收到服務端返回的task-finished事件");
                        taskFinished = true;
                        closeConnection();
                    } else if ("task-failed".equals(event)) {
                        System.out.println("任務失敗:" + message);
                        closeConnection();
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("出現異常:" + e.getMessage());
        }
    }

    @Override
    public void onMessage(ByteBuffer message) {
        System.out.println("收到的二進位音頻資料大小為:" + message.remaining());

        try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
            byte[] buffer = new byte[message.remaining()];
            message.get(buffer);
            fos.write(buffer);
            System.out.println("音頻資料已寫入本地檔案" + outputFile + "中");
        } catch (IOException e) {
            System.err.println("音頻資料寫入本地檔案失敗:" + e.getMessage());
        }
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        System.out.println("串連關閉:" + reason + " (" + code + ")");
    }

    @Override
    public void onError(Exception ex) {
        System.err.println("報錯:" + 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 {
            String apiKey = System.getenv("DASHSCOPE_API_KEY");
            if (apiKey == null || apiKey.isEmpty()) {
                System.err.println("請設定 DASHSCOPE_API_KEY 環境變數");
                return;
            }

            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "bearer " + apiKey);
            TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://dashscope.aliyuncs.com/api-ws/v1/inference/"), headers);

            client.connect();

            while (!client.isClosed() && !client.taskFinished) {
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            System.err.println("串連WebSocket服務失敗:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

Python

如您使用Python程式設計語言,建議採用Python DashScope SDK進行開發,詳情請參見Python SDK

以下是Python WebSocket的調用樣本。在運行樣本前,請確保通過如下方式匯入依賴:

pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client
重要

請不要將運行範例程式碼的Python檔案命名為“websocket.py”,否則會報錯(AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?)。

# SSML功能說明:
#     1. 在發送run-task指令時,將參數enable_ssml設定為true,以開啟SSML支援
#     2. 通過continue-task指令發送包含SSML的文本,且只允許發送一次continue-task指令
#     3. 只有cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的複刻音色以及音色列表中標記為支援SSML的系統音色支援SSML功能(例如cosyvoice-v3-flash模型的longanyang音色)

import websocket
import json
import uuid
import os
import time


class TTSClient:
    def __init__(self, api_key, uri):
        """
    初始化 TTSClient 執行個體

    參數:
        api_key (str): 鑒權用的 API Key
        uri (str): WebSocket 服務地址
    """
        self.api_key = api_key  # 替換為你的 API Key
        self.uri = uri  # 替換為你的 WebSocket 地址
        self.task_id = str(uuid.uuid4())  # 產生唯一任務 ID
        self.output_file = f"output_{int(time.time())}.mp3"  # 輸出音頻檔案路徑
        self.ws = None  # WebSocketApp 執行個體
        self.task_started = False  # 是否收到 task-started
        self.task_finished = False  # 是否收到 task-finished / task-failed

    def on_open(self, ws):
        """
    WebSocket 串連建立時回呼函數
    發送 run-task 指令開啟語音合成任務
    """
        print("WebSocket 已串連")

        # 構造 run-task 指令
        run_task_cmd = {
            "header": {
                "action": "run-task",
                "task_id": self.task_id,
                "streaming": "duplex"
            },
            "payload": {
                "task_group": "audio",
                "task": "tts",
                "function": "SpeechSynthesizer",
                "model": "cosyvoice-v3-flash",
                "parameters": {
                    "text_type": "PlainText",
                    "voice": "longanyang",
                    "format": "mp3",
                    "sample_rate": 22050,
                    "volume": 50,
                    "rate": 1,
                    "pitch": 1,
                    # 如果enable_ssml設為True,只允許發送一次continue-task指令,否則會報錯“Text request limit violated, expected 1.”
                    "enable_ssml": True
                },
                "input": {}
            }
        }

        # 發送 run-task 指令
        ws.send(json.dumps(run_task_cmd))
        print("已發送 run-task 指令")

    def on_message(self, ws, message):
        """
    接收到訊息時的回呼函數
    區分文本和二進位訊息處理
    """
        if isinstance(message, str):
            # 處理 JSON 簡訊
            try:
                msg_json = json.loads(message)
                print(f"收到 JSON 訊息: {msg_json}")

                if "header" in msg_json:
                    header = msg_json["header"]

                    if "event" in header:
                        event = header["event"]

                        if event == "task-started":
                            print("任務已啟動")
                            self.task_started = True

                            # 發送 continue-task 指令,使用SSML功能時,該指令只允許發送一次
                            # 特殊字元需要進行轉義
                            self.send_continue_task("<speak rate=\"2\">我的語速比正常人快。</speak>")

                            # continue-task 發送完成後發送 finish-task
                            self.send_finish_task()

                        elif event == "task-finished":
                            print("任務已完成")
                            self.task_finished = True
                            self.close(ws)

                        elif event == "task-failed":
                            error_msg = msg_json.get("error_message", "未知錯誤")
                            print(f"任務失敗: {error_msg}")
                            self.task_finished = True
                            self.close(ws)

            except json.JSONDecodeError as e:
                print(f"JSON 解析失敗: {e}")
        else:
            # 處理二進位訊息(音頻資料)
            print(f"收到二進位訊息,大小: {len(message)} 位元組")
            with open(self.output_file, "ab") as f:
                f.write(message)
            print(f"已將音頻資料寫入本地檔案{self.output_file}中")

    def on_error(self, ws, error):
        """發生錯誤時的回調"""
        print(f"WebSocket 出錯: {error}")

    def on_close(self, ws, close_status_code, close_msg):
        """串連關閉時的回調"""
        print(f"WebSocket 已關閉: {close_msg} ({close_status_code})")

    def send_continue_task(self, text):
        """發送 continue-task 指令,附帶要合成的常值內容"""
        cmd = {
            "header": {
                "action": "continue-task",
                "task_id": self.task_id,
                "streaming": "duplex"
            },
            "payload": {
                "input": {
                    "text": text
                }
            }
        }

        self.ws.send(json.dumps(cmd))
        print(f"已發送 continue-task 指令,常值內容: {text}")

    def send_finish_task(self):
        """發送 finish-task 指令,結束語音合成任務"""
        cmd = {
            "header": {
                "action": "finish-task",
                "task_id": self.task_id,
                "streaming": "duplex"
            },
            "payload": {
                "input": {}
            }
        }

        self.ws.send(json.dumps(cmd))
        print("已發送 finish-task 指令")

    def close(self, ws):
        """主動關閉串連"""
        if ws and ws.sock and ws.sock.connected:
            ws.close()
            print("已主動關閉串連")

    def run(self):
        """啟動 WebSocket 用戶端"""
        # 佈建要求頭部(鑒權)
        header = {
            "Authorization": f"bearer {self.api_key}",
            "X-DashScope-DataInspection": "enable"
        }

        # 建立 WebSocketApp 執行個體
        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("正在監聽 WebSocket 訊息...")
        self.ws.run_forever()  # 啟動長串連監聽


# 樣本使用方式
if __name__ == "__main__":
    API_KEY = os.environ.get("DASHSCOPE_API_KEY")  # 如您未將API Key配置到環境變數,請將API_KEY 設定為您的 API Key
    SERVER_URI = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/"

    client = TTSClient(API_KEY, SERVER_URI)
    client.run()

標籤

說明

阿里巴巴語音合成服務在實現 SSML 時參考了 W3C SSML 1.0 規範,但在設計上更注重業務適配性。因此,並未完整支援所有標準標籤,而是結合實際使用情境,實現了最具實用價值的標籤集合。

  • 所有使用 SSML 功能的常值內容必須包含在 <speak></speak> 標籤內。

  • 支援多個 <speak> 標籤並列使用(如:<speak></speak><speak></speak>),但不支援嵌套結構(如:<speak><speak></speak></speak>)。

  • 編碼時,若標籤內的常值內容包含 XML 特殊字元,需進行相應的字元轉義。常見特殊字元及其轉義形式如下:

    • "(雙引號) → &quot;

    • '(單引號/撇號) → &apos;

    • &(表示“和”的符號) → &amp;

    • <(小於符號) → &lt;

    • >(大於符號) → &gt;

<speak>:根節點

  • 描述

    <speak> 標籤是所有 SSML 標籤的根節點,任何使用 SSML 功能的常值內容都必須包含在 <speak></speak> 標籤之間。

  • 文法

     <speak>需要使用SSML功能的文本</speak>
  • 屬性

    屬性名稱

    屬性類型

    是否必選

    描述

    voice

    String

    指定發音人(音色)。

    優先順序高於介面請求參數voice指定的發音人。

    • 取值範圍:具體的音色,詳情請參見cosyvoice-v2音色

    • 樣本:

      <speak voice="longcheng_v2">
        我是男聲。
      </speak>

    rate

    String

    指定語速。優先順序高於介面請求參數speech_rate指定的語速。

    • 取值範圍:[0.5,2]之間的小數

    • 預設值:1

      • 大於1表示加快語速

      • 小於1表示減慢語速

    • 樣本:

      <speak rate="2">
        我的語速比正常人快。
      </speak>

    pitch

    String

    指定音高(語調)。優先順序高於介面請求參數pitch_rate指定的音高(語調)。

    • 取值範圍:[0.5,2]之間的小數

    • 預設值:1

      • 大於1表示升高音高

      • 小於1表示降低音高

    • 樣本:

      <speak pitch="0.5">
        我的音高卻比別人低。
      </speak>

    volume

    String

    指定音量。優先順序高於介面請求參數volume指定的音量。

    • 取值範圍:[0,100]之間的整數

    • 預設值:50

      • 大於50表示增大音量

      • 小於50表示減小音量

    • 樣本:

      <speak volume="80">
        我的音量也很大。
      </speak>

    effect

    String

    指定音效。

    • 取值範圍:

      • robot:機器人音效

      • lolita:蘿莉音效

      • lowpass:低通音效

      • echo:回聲音效

      • eq:均衡器(進階)

      • lpfilter:低通濾波器(進階)

      • hpfilter:高通濾波器(進階)

      說明
      • eq、lpfilter、hpfilter是進階音效類型,您可以通過effectValue參數自訂其具體效果。

      • 每個 SSML 標籤僅支援配置一種音效,不允許多個 effect 屬性共存。

      • 使用音效功能會增加系統延時。

    • 樣本:

      <speak effect="robot">
        你喜歡機器人瓦力嗎?
      </speak>

    effectValue

    String

    指定音效(effect參數)的具體效果。

    • 取值範圍:

      • eq(均衡器):系統預設支援8個頻率等級,對應頻率如下:

        [“40 Hz”,“100 Hz”, “200 Hz”, “400 Hz”, “800 Hz”, “1600 Hz”, “4000 Hz”, “12000 Hz”]。

        每個頻段頻寬均為1.0q。

        使用時需通過 effectValue 參數指定每個頻段的增益值,該參數為一個由 8 個整數組成的字串,數值範圍為 [-20, 20],數字之間用空格分隔,數值為 0 表示不調整對應頻率的增益。

        例如:effectValue="1 1 1 1 1 1 1 1"

      • lpfilter(低通濾波器):輸入低通濾波器的頻率值。取值為(0, 目標採樣率/2]之間的整數。例如effectValue="800"。

      • hpfilter(高通濾波器):輸入高通濾波器的頻率值。取值為(0, 目標採樣率/2]之間的整數。例如effectValue="1200"。

    • 樣本:

      <speak effect="eq" effectValue="1 -20 1 1 1 1 20 1">
        你喜歡機器人瓦力嗎?
      </speak>
      
      <speak effect="lpfilter" effectValue="1200">
        你喜歡機器人瓦力嗎?
      </speak>
      
      <speak effect="hpfilter" effectValue="1200">
        你喜歡機器人瓦力嗎?
      </speak>

    bgm

    String

    為合成的語音添加指定的背景音樂。背景音檔案需儲存在阿里雲 OSS 上(請參見上傳檔案),且所在儲存空間(Bucket)應至少具有公用讀取許可權。

    背景音樂URL中若包含 XML 特殊字元(如 &<> 等),需進行字元轉義處理。

    • 音頻要求:

      音頻檔案大小無上限,但較大的檔案可能會增加下載耗時;若合成內容的時間長度超過背景音時間長度,背景音將自動迴圈播放以匹配合成音頻長度。

      • 採樣率:16kHz

      • 聲道數:單聲道

      • 檔案格式:WAV

        若原始音頻非 WAV 格式,可使用 ffmpeg 工具進行轉換:

        ffmpeg -i 輸入音頻 -acodec pcm_s16le -ac 1 -ar 16000 輸出.wav
      • 位元深度:16位

    • 樣本:

      <speak bgm="http://nls.alicdn.com/bgm/2.wav" backgroundMusicVolume="30" rate="-500" volume="40">
        <break time="2s"/>
        陰崖老木蒼蒼煙
        <break time="700ms"/>
        雨聲猶在竹林間
        <break time="700ms"/>
        綿蕝固知裨國計
        <break time="700ms"/>
        綿州風物總堪憐
        <break time="2s"/>
      </speak>
    重要

    您需要對上傳的音頻著作權承擔相應的法律責任。

    backgroundMusicVolume

    String

    指定背景音樂的音量。和backgroundMusicVolume屬性搭配使用。

  • 標籤關係

    <speak>標籤可以包含文本和其他標籤:

  • 更多樣本

    • 空屬性

      <speak>
        需要調用SSML標籤的文本
      </speak>
    • 屬性群組合(空格分隔)

      <speak rate="200" pitch="-100" volume="80">
        所以放在一起,我的聲音是這樣的。
      </speak>

<break>:控制停頓時間

  • 描述

    在語音合成過程中添加一段靜默時間,類比自然說話中的停頓效果。支援秒(s)或毫秒(ms)單位設定。該標籤是可選標籤。

  • 文法

    # 空屬性
    <break/>
    # 帶time屬性
    <break time="string"/>
  • 屬性

    說明

    使用無屬性的<break>標籤時,停頓時間長度為“1s”。

    屬性名稱

    屬性類型

    是否必選

    描述

    time

    String

    以秒/毫秒為單位設定停頓的時間長度 (如“2s”、“50ms”)。

    • 取值範圍:

      • 以秒(s)為單位,取值範圍為[1, 10]之間的整數

      • 以毫秒(ms)為單位,取值範圍為[50, 10000]之間的整數

    • 樣本:

      <speak>
        請閉上眼睛休息一下<break time="500ms"/>好了,請睜開眼睛。
      </speak>
    重要

    當連續使用多個 <break> 標籤時,總的停頓時間長度為各個標籤指定時間的累加。若總時間長度超過 10 秒,則僅生效前 10 秒。

    如下所示,該段 SSML 中 <break> 標籤累計時間長度為 15 秒,超過 10 秒限制,最終停頓時間長度將被截斷為 10 秒:

    <speak>
      請閉上眼睛休息一下<break time="5s"/><break time="5s"/><break time="5s"/>好了,請睜開眼睛。
    </speak>
  • 標籤關係

    <break>是空標籤,不能包含任何標籤。

<sub>:替換文本

  • 描述

    將某段文本替換為指定的更適合朗讀的文本。例如將 “W3C” 讀成 “網路通訊協定標準”。該標籤是可選標籤。

  • 文法

    <sub alias="string"></sub>
  • 屬性

    屬性名稱

    屬性類型

    是否必選

    描述

    alias

    String

    將某段文本替換為更適合朗讀的文本。

    樣本:

     <speak>
       <sub alias="網路通訊協定標準">W3C</sub>
     </speak>
  • 標籤關係

    <sub>標籤僅可以包括文本。

<phoneme>:指定發音(拼音/音標)

  • 描述

    控制某段文本的具體發音方式,中文可用拼音,英文可用音標(如 CMU),適用於需要精準發音的情境。該標籤是可選標籤。

  • 文法

    <phoneme alphabet="string" ph="string">文本</phoneme>
  • 屬性

    屬性名稱

    屬性類型

    是否必選

    描述

    alphabet

    String

    指定發音類型:拼音(對應中文)或音標(對應英文)。

    取值範圍:

    ph

    String

    指定具體的拼音或音標:

    • 字與字的拼音用空格分隔,拼音的數目必須與字數一致。

    • 每個拼音由發音部分和音調組成,其中音調為 1 到 5 的整數,5 表示輕聲。

    • 樣本:

      <speak>
        去<phoneme alphabet="py" ph="dian3 dang4 hang2">典當行</phoneme>把這個玩意<phoneme alphabet="py" ph="dang4 diao4">當掉</phoneme>
      </speak>
      
      <speak>
        How to spell <phoneme alphabet="cmu" ph="S AY N">sin</phoneme>?
      </speak>
  • 標籤關係

    <phoneme>標籤僅包括文本。

<soundEvent>:插入一段外部聲音(鈴聲、貓叫等)

  • 描述

    支援在語音中插入音效檔案,如提示音、環境音等,增強語音表達的豐富性。該標籤是可選標籤。

  • 文法

     <soundEvent src="URL"/>
  • 屬性

    屬性名稱

    屬性類型

    是否必選

    描述

    src

    String

    設定外部音頻URL。

    音頻檔案需儲存在阿里雲 OSS 上(請參見上傳檔案),且所在儲存空間(Bucket)應至少具有公用讀取許可權。URL中若包含 XML 特殊字元(如 &<> 等),需進行字元轉義處理。

    • 音頻要求:

      • 採樣率:16kHz

      • 聲道數:單聲道

      • 檔案格式:WAV

        若原始音頻非 WAV 格式,可使用 ffmpeg 工具進行轉換:

        ffmpeg -i 輸入音頻 -acodec pcm_s16le -ac 1 -ar 16000 輸出.wav
      • 檔案大小:不超過2MB

      • 位元深度:16位

    • 樣本:

      <speak>
        一匹馬受了驚嚇<soundEvent src="http://nls.alicdn.com/sound-event/horse-neigh.wav"/>人們四散躲避
      </speak>
    重要

    您需要對上傳的音頻著作權承擔相應的法律責任。

  • 標籤關係

    <soundEvent>是空標籤,不可以包含任何標籤。

<say-as>:設定文本的讀法(數字、日期、電話號碼等)

  • 描述

    告訴大模型文本是什麼類型,並按該類型的常規讀法進行朗讀。該標籤是可選標籤。

  • 文法

     <say-as interpret-as="string">文本</say-as>
  • 屬性

    屬性名稱

    屬性類型

    是否必選

    描述

    interpret-as

    String

    指示出標籤內文本的資訊類型。

    取值範圍:

    • cardinal:按整數或小數的常見讀法朗讀

    • digits:按數字逐個讀出(如:123 → 一二三)

    • telephone:按電話號碼的常用方式讀出

    • name:按人名的常規讀法朗讀

    • address:按地址的常見方式讀出

    • id:適用於賬戶名、暱稱等,按常規讀法處理

    • characters:將標籤內的文本按字元一一讀出

    • punctuation:將標籤內的文本按標點符號的方式讀出來

    • date:按日期格式的常見讀法朗讀

    • time:按時間格式的常見方式讀出

    • currency:按金額的常見讀法處理

    • measure:按計量單位的常見方式讀出

  • 各<say-as>類型支援範圍

    • cardinal

      格式

      樣本

      中文輸出

      說明

      數字串

      145

      一百四十五

      整數輸入範圍:20位以內的正負整數,[-99999999999999999999,99999999999999999999]。

      小數輸入範圍:對小數點後小數的位元沒有特殊限制,建議不超過10位。

      負號+數字串

      -145

      負一百四十五

      以逗號分隔3位元字串

      10,000

      一萬

      負號+以逗號分隔3位元字串

      -10,124

      負一萬一百二十四

      數字串+小數點+2個零

      10.00

      負號+數字串+小數點+2個零

      -110.00

      負一百一十

      數字串+小數點+數字串

      79.090

      七十九點零九零

      負號+數字串+小數點+數字串

      -79.001

      負七十九點零零一

      格式

      樣本

      英文輸出

      說明

      數字串

      145

      one hundred forty five

      整數輸入範圍:13位以內的正負整數,[-999999999999,999999999999]。

      小數輸入範圍:對小數點後小數的位元沒有特殊限制,建議不超過10位。

      以零開頭的數字串

      0145

      one hundred forty five

      負號+數字串

      -145

      minus hundred forty five

      以逗號分隔三位元字串

      60,000

      sixty thousand

      負號+以逗號分隔三位元字串

      -208,000

      minus two hundred eight thousand

      數字串+小數點+零

      12.00

      twelve

      數字串+小數點+數字串

      12.34

      twelve point three four

      以逗號分隔三位元字串+小數點+數字串

      1,000.1

      one thousand point one

      負號+數字串+小數點+數字串

      -12.34

      minus twelve point three four

      負號+以逗號分隔三位元字串+小數點+數字串

      -1,000.1

      minus one thousand point one

      (以逗號分隔三位)數字串+連詞符+(以逗號分隔三位)數字

      1-1,000

      one to one thousand

      其他預設讀法

      012.34

      twelve point three four

      1/2

      one half

      -3/4

      minus three quarters

      5.1/6

      five point one over six

      -3 1/2

      minus three and a half

      1,000.3^3

      one thousand point three to the power of three

      3e9.1

      three times ten to the power of nine point one

      23.10%

      twenty three point one percent

    • digits

      格式

      樣本

      中文輸出

      說明

      數字串

      129090909

      一二九零九零九零九

      對數字串的長度沒有特殊限制,建議不超過20位。

      當數字串超過10位時,每個數字後插入停頓。

      格式

      樣本

      英文輸出

      說明

      數字串

      12034

      one two zero three four

      對數字串的長度沒有特殊限制,建議不超過20位。

      當數字串以空格或連詞符分組時,分組之間會插入逗號而產生適當停頓,支援最長5個分組。

      數字串+空格或連詞符+數字串+空格或連詞符+數字串+空格或連詞符+數字串

      1-23-456 7890

      one, two three, four five six, seven eight nine zero

    • telephone

      格式

      樣本

      中文輸出

      說明

      有線電話號

      4930286

      四九三 零二八六

      支援7~8位有線電話號,支援空格和“-”作為分隔字元。

      其中,7位有線電話號支援“3-4”的數字分隔方式;8位有線電話號支援“4-4”的數字分隔方式。

      493 0286

      四九三 零二八六

      493-0286

      四九三 零二八六

      62552560

      六二五五 二五六零

      6255 2560

      六二五五 二五六零

      6255-2560

      六二五五 二五六零

      有線電話號+分機號

      4930286-109

      四九三 零二八六 轉么零九

      支援1~4位分機號。

      4930286轉109

      四九三 零二八六 轉么零九

      4930286分機109

      四九三 零二八六 分機么零九

      4930286分機號109

      四九三 零二八六 分機號么零九

      區號+有線電話號

      01062552560

      零么零 六二五五 二五六零

      支援區號:010、02x、03xx、04xx、05xx、07xx、08xx、09xx。

      010 62552560

      零么零 六二五五 二五六零

      010 6255 2560

      零么零 六二五五 二五六零

      010 6255-2560

      零么零 六二五五 二五六零

      010-62552560

      零么零 六二五五 二五六零

      010-6255-2560

      零么零 六二五五 二五六零

      (010)62552560

      零么零 六二五五 二五六零

      03198907098

      零三么九 八九零 七零九八

      0319-8907098

      三么九 八九零 七零九八

      區號+有線電話號+分機號

      010 62552560-109

      零么零 六二五五 二五六零 轉么零九

      010-62552560-109

      零么零 六二五五 二五六零 轉么零九

      (010)62552560-109

      零么零 六二五五 二五六零 轉么零九

      (010)62552560轉109

      零么零 六二五五 二五六零 轉么零九

      (010)62552560分機109

      零么零 六二五五 二五六零 分機么零九

      (010)62552560分機號109

      零么零 六二五五 二五六零 分機號么零九

      國家代碼+區號+有線電話號

      86-010-62791627

      八六 零么零 六二七九 么六二七

      支援國家代碼:86、 (86)、+86、(+86)、0086。並統一讀為“八六”。

      (86)10-62791627

      八六 么零 六二七九 么六二七

      +86-010-62791627

      八六 零么零 六二七九 么六二七

      0086-10-62791627

      八六 么零 六二七九 么六二七

      (+86)-10-6279 1627

      八六 么零 六二七九 么六二七

      國家代碼+區號+有線電話號+分機號

      (86)21-58118818-207

      八六 二么 五八么么 八八么八 轉二零七

      (86)021-5811-8818-207

      八六 零二么 五八么么 八八么八 轉二零七

      (86)021-58118818轉207

      八六 零二么 五八么么 八八么八 轉二零七

      (86)21-5811-8818分機207

      八六 二么 五八么么 八八么八 分機二零七

      +86-021-58118818分機號207

      八六 零二么 五八么么 八八么八分機號二零七

      手機號

      139 0000 5678

      么三九 零零零零 五六七八

      支援11位手機號,支援3-3-5、3-4-4兩種數字分隔方式

      139-000-05678

      么三九 零零零 零五六七八

      139 000 05678

      么三九 零零零 零五六七八

      國家代碼+手機號

      +86-13900005678

      八六 么三九 零零零零 五六七八

      (+86)-139-0000-5678

      八六 么三九 零零零零 五六七八

      +8613900005678

      八六 么三九 零零零零 五六七八

      0086-139 000 05678

      八六 么三九 零零零 零五六七八

      服務號

      123

      么二三

      • 支援常用的服務號。

      • 支援以400/800開頭的10位服務號,支援以“3-3-4”的數字分隔方式。

      • 支援以12530/17951/12593開頭的16位號碼。

      95678

      九五六七八

      4008110510

      四零零 八么么 零五么零

      800-810-8888

      八零零 八么零 八八八八

      1253013520638377

      么二五三零 么三五 二零六三 八三七七

      其他

      (86)(21)9899-80800-0909

      八六 二么 九八九九 八零八零零 零九零九

      支援“數字串+分隔字元(左右括弧、-)”方式。

      格式

      樣本

      英文輸出

      說明

      數字串

      12034

      one two oh three four

      對數字串的長度沒有特殊限制,建議不超過20位。當數字串以空格或連詞符分組時,分組之間會插入逗號而產生適當停頓,支援最長5個分組。

      數字串+空格或連詞符+數字串+空格或連詞符+數字串

      1-23-456 7890

      one, two three, four five six, seven eight nine oh

      加號+數字串+空格或連詞符+數字串

      +43-211-0567

      plus four three, two one one, oh five six seven

      左括弧+數字串+右括弧+空格+數字串+空格或連詞符+數字串

      (21) 654-3210

      (two one) six five four, three two one oh

    • address

      格式

      樣本

      中文輸出

      說明

      常用地址格式

      元和鎮嘉元30-9

      元和鎮嘉元三十杠九

      支援常用地址格式。此處地址指標準的郵寄地址。

      市台路388弄1107-1108號

      市台路三八八弄么么零七杠么么零八號

      華潤二十四城六期錦雲府3-1-3205

      華潤二十四城六期錦雲府三杠一杠三二零五

      聖華名都大廈2幢2006室

      聖華名都大廈二幢二零零六室

      五常街道庭院5幢4單元201

      五常街道庭院五幢四單元二零么

      芙蓉江路150弄19號

      芙蓉江路么五零弄十九號

      英文文本不支援該標籤。

    • id

      格式

      樣本

      輸出

      說明

      字串

      dell0101

      D E L L 零 一 零 一

      大小寫英文字元、阿拉伯數字0~9、底線。

      輸出的空格表示每個字元之間插入停頓,即字元一個一個地讀。

      myid_1998

      M Y I D 底線 一 九 九 八

      AiTest

      A I T E S T

      英文文本該標籤功能同標籤characters。

    • characters

      格式

      樣本

      中文輸出

      說明

      字串

      ISBN 1-001-099098-1

      I S B N 一 杠 零 零 一 杠 零 九 九 零 九 八 杠 一

      支援中文漢字、大小寫英文字元、阿拉伯數字0~9以及部分全形和半形字元。

      輸出的空格表示每個字元之間插入停頓,即字元一個一個地讀。標籤內的文本如果包含XML的特殊字元,需要做字元轉義。

      x10b2345_u

      x 一 零 b 二 三 四 五 底線 u

      v1.0.1

      v 一 點 零 點 一

      版本號碼2.0

      版本號碼二 點 零

      蘇M MA000

      蘇M M A 零 零 零

      空中巴士A330

      空中巴士A 三 三 零

      型號s01 s02和s03

      型號s 零 一 s 零二 和s 零 三

      空中巴士A330

      空中巴士A 三 三 零

      αβγ

      阿爾法 貝塔 伽瑪

      格式

      樣本

      英文輸出

      說明

      字串

      *b+3$.c-0'=α

      asterisk B plus three dollar dot C dash zero apostrophe equals alpha

      支援中文漢字、大小寫英文字元、阿拉伯數字0~9以及部分全形和半形字元。

      輸出的空格表示每個字元之間插入停頓,即字元一個一個地讀。

      標籤內的文本如果包含XML的特殊字元,需要做字元轉義。

    • punctuation

      格式

      樣本

      中文輸出

      說明

      標點符號

      省略符號

      支援常見中英文標點。輸出的空格表示每個字元之間插入停頓,即字元一個一個地讀。

      標籤內的文本如果包含XML的特殊字元,需要做字元轉義。

      ……

      省略符號

      !"#$%&

      歎號 雙引號 井號 dollar 百分比符號 and

      ‘()*+

      單引號 左括弧 右括弧 星號 加號

      ,-./:;

      逗號 杠 點 斜杠 冒號 分號

      <=>?@

      小於 等號 大於 問號 at

      [\]^_

      左方括弧 反斜線 右方括弧 脫字元 底線

      英文文本該標籤功能同標籤characters。

    • date

      格式

      樣本

      中文輸出

      說明

      xx年

      71年

      七一年

      支援2位和4位年份。其中:

      • 2位年份支援60年~99年、00年~09年、10年~19年。

      • 4位年份支援1000年~1999年、2000年~2099年。

      04年

      零四年

      19年

      一九年

      1011年

      一零一一年

      1998年

      一九九八年

      2008年

      二零零八年

      xx年xx月

      98年4月

      九八年四月

      當月份為1到9月時,支援開頭帶“0”和不帶“0”兩種寫法。例如“1908年4月”和“1908年04月”。

      1998年04月

      一九九八年四月

      08年8月

      零八年八月

      2008年8月

      二零零八年八月

      xx年xx月xx日xx年xx月xx號

      98年4月23日

      九八年四月二十三日

      當日期為1到9日時,支援開頭帶“0”和不帶“0”兩種寫法。例如“1908年4月8日”和“1908年04月08日”。

      1998年04月23日

      一九九八年四月二十三日

      08年8月8號

      零八年八月八號

      2008年08月08號

      二零零八年八月八號

      xx年xx月xx日xx年xx月xx號

      98年4月23日

      九八年四月二十三日

      當日期為1到9日時,支援開頭帶“0”和不“0”兩種寫法。例如“1908年4月8日”和“1908年04月08日”。

      1998年04月23日

      一九九八年四月二十三日

      08年8月8號

      零八年八月八號

      2008年08月08號

      二零零八年八月八號

      xx月xx號

      3月20日

      三月二十日

      08月07號

      八月七號

      年月縮寫

      2018/08

      二零一八年八月

      支援“/”、“-”、“.”作為縮寫的分隔字元。

      2018-08

      二零一八年八月

      2018.08

      二零一八年八月

      年月日縮寫

      2018/08/08

      二零一八年八月八日

      2018-8-8

      二零一八年八月八日

      2018.08.08

      二零一八年八月八日

      xx年xx月xx日~xx年xx月xx日xx年xx月xx號~xx年xx月xx號

      04年9月1日~30日

      零四年九月一日至三十日

      支援“~”、“-”作為“至”的縮寫標誌。

      2004年09月01號-2008年06月08號

      二零零四年九月一號至二零零八年六月八號

      xx年xx月xx日~xx日xx年xx月xx號~xx號

      04年9月1日~30日

      零四年九月一日至三十日

      2004年09月01號-2008年06月08號

      二零零四年九月一號至二零零八年六月八號

      xx年xx月~xx年xx月

      01年04月~10年04月

      零一年四月至一零年四月

      2001年04月~2010年04月

      二零零一年四月至二零一零年四月

      xx月xx日~xx月xx日xx月xx號~xx月xx號

      10月1日~10月7日

      十月一日至十月七日

      10月01號~10月07號

      十月一號至十月七號

      xx月xx日~xx日xx月xx號~xx號

      10月1日~7日

      十月一日至七日

      10月01號~07號

      十月一號至七號

      年月日縮寫~年月日縮寫

      2018/03/03~2019/01/01

      二零一八年三月三日至二零一九年一月一日

      支援“/”、“.”作為縮寫的分隔字元,支援“~”、“-”作為“至”的縮寫標誌。

      1997.9.9~1998.9.9

      一九九七年九月九日至一九九八年九月九日

      月日縮寫~月日縮寫

      10/20~10/31

      十月二十日至十月三十一日

      xx~xx月xx月~xx月

      1~10月

      一至十月

      1月~10月

      一月至十月

      月日年縮寫

      10/20/2018

      二零一八年十月二十日

      僅支援4位的年份,僅支援“/”作為日期的分隔字元,僅支援“月/日/年”的書寫方式。

      格式

      樣本

      英文輸出

      說明

      四位元字/兩位元字或四位元字-兩位元字

      2000/01

      two thousand, oh one

      跨年度。

      1900-01

      nineteen hundred, oh one

      2001-02

      twenty oh one, oh two

      2019-20

      twenty nineteen, twenty

      1998-99

      nineteen ninety eight, ninety nine

      1999-00

      nineteen ninety nine, oh oh

      以1或2開頭的四位元字

      2000

      two thousand

      四位元字年份。

      1900

      nineteen hundred

      1905

      nineteen oh five

      2021

      twenty twenty one

      星期幾-星期幾

      星期幾~星期幾

      星期幾&星期幾

      mon-wed

      monday to wednesday

      星期幾的範圍標籤內的文本如果包含XML的特殊字元,需要做字元轉義。

      tue~fri

      tuesday to friday

      sat&sun

      saturday and sunday

      DD-DD MMM, YYYY

      DD~DD MMM, YYYY

      DD&DD MMM, YYYY

      19-20 Jan, 2000

      the nineteen to the twentieth of january two thousand

      DD表示兩位元字日期,MMM表示月份的三字母縮寫或完整單詞,YYYY表示以1或2開頭的四位元字年份。

      01 ~ 10 Jul, 2020

      the first to the tenth of july twenty twenty

      05&06 Apr, 2009

      the fifth and the sixth of april two thousand nine

      MMM DD-DD

      MMM DD~DD

      MMM DD&DD

      Feb 01 - 03

      feburary the first to the third

      MMM表示月份的三字母縮寫或完整單詞,DD表示兩位元字日期。

      Aug 10~20

      august the tenth to the twentieth

      Dec 11&12

      december the eleventh and the twelfth

      MMM-MMM

      MMM~MMM

      MMM&MMM

      Jan-Jun

      january to june

      MMM表示月份的三字母縮寫或完整單詞。

      jul ~ dec

      july to december

      sep&oct

      september and october

      YYYY-YYYY

      YYYY~YYYY

      1990 - 2000

      nineteen ninety to two thousand

      YYYY表示以1或2開頭的四位元字年份。

      2001~2021

      two thousand one to twenty twenty one

      WWW DD MMM YYYY

      Sun 20 Nov 2011

      sunday the twentieth of november twenty eleven

      WWW表示星期幾的三字母縮寫或完整單詞,DD表示兩位元字日期,MMM表示月份的三字母縮寫或完整單詞,MM表示兩位元字月份(或三字母縮寫或完整單詞),YYYY表示以1或2開頭的四位元字年份。

      WWW DD MMM

      Sun 20 Nov

      sunday the twentieth of november

      WWW MMM DD YYYY

      Sun Nov 20 2011

      sunday november the twentieth twenty eleven

      WWW MMM DD

      Sun Nov 20

      sunday november the twentieth

      WWW YYYY-MM-DD

      Sat 2010-10-01

      aturday october the first twenty ten

      WWW YYYY/MM/DD

      Sat 2010/10/01

      saturday october the first twenty ten

      WWW MM/DD/YYYY

      Sun 11/20/2011

      sunday november the twentieth twenty eleven

      MM/DD/YYYY

      11/20/2011

      november the twentieth twenty eleven

      YYYY

      1998

      nineteen ninety eight

      其他預設讀法

      10 Mar, 2001

      the tenth of march two thousand one

      10 Mar

      the tenth of march

      Mar 2001

      march two thousand one

      Fri. 10/Mar/2001

      friday the tenth of march two thousand one

      Mar 10th, 2001

      march the tenth two thousand one

      Mar 10

      march the tenth

      2001/03/10

      march the tenth two thousand one

      2001-03-10

      march the tenth two thousand one

      2000s

      two thousands

      2010's

      twenty tens

      1900's

      nineteen hundreds

      1990s

      nineteen nineties

    • time

      格式

      樣本

      中文輸出

      說明

      時刻

      12:00

      十二點

      支援常用時間和時間範圍格式。

      12:00:00點

      十二點

      10:20分

      十點二十分

      10:20:30

      十點二十分三十秒

      09:18:14

      九點十八分十四秒

      時刻~時刻

      11:00~12:00

      十一點到十二點

      09:00-14:00

      九點到十四點

      11:00~11:30

      十一點到十一點三十分

      11:00-12:18

      十一點到十二點十八分

      10:30~11:00

      十點三十分到十一點

      09:28-10:00

      九點二十八分到十點

      10:20~11:20

      十點二十分到十一點二十分

      06:00~08:00

      六點到八點

      上午10:20~下午13:30

      上午十點二十分到下午十三點三十分

      時間縮寫

      5:00 am

      淩晨五點整

      5:30 am

      淩晨五點半

      5:20:12 am

      淩晨五點二十分十二秒

      7:00 am

      上午七點整

      7:30 AM

      上午七點半

      7:20:12 a.m.

      上午七點二十分十二秒

      07:08:12 A.M.

      上午七點零八分十二秒

      5:00 pm

      下午五點整

      5:30 PM

      下午五點半

      5:20:12 p.m.

      下午五點二十分十二秒

      05:09:12 P.M.

      下午五點零九分十二秒

      9:00 pm

      晚上九點整

      9:30 pm

      晚上九點半

      9:20:12 PM

      晚上九點二十分十二秒

      9:02:12 P.M.

      晚上九點零二分十二秒

      12:00 pm

      中午十二點整

      12:30 p.m.

      中午十二點半

      12:20:12 PM

      中午十二點二十分十二秒

      格式

      樣本

      英文輸出

      說明

      HH:MM AM或PM

      09:00 AM

      nine A M

      HH表示一或兩位元字小時,MM表示兩位元字分鐘,AM/PM表示上/下午。

      09:03 PM

      nine oh three P M

      09:13 p.m.

      nine thirteen p m

      HH:MM

      21:00

      twenty one hundred

      HHMM

      100

      one oclock

      時刻-時刻

      8:00 am - 05:30 pm

      eight a m to five p m

      支援常見時間格式和範圍。

      7:05~10:15 AM

      seven oh five to ten fifteen A M

      09:00-13:00

      nine oclock to thirteen hundred

    • currency

      格式

      樣本

      中文輸出

      說明

      數字+金額標識符

      12.00 RMB

      十二人民幣

      支援AUD(澳幣) 、CAD(加元)、 HKD(港幣)、JPY(日元)、USD(美元)、CHF(瑞士法郎)、NOK(挪威克朗)、SEK(瑞典克朗)、GBP(英鎊)、 RMB(人民幣)、CNY(元)和EUR(歐元)。

      支援的數字格式包括:整數、小數以及以逗號分隔的國際寫法。

      12.50 RMB

      十二點五零人民幣

      12,000,000 RMB

      一千二百萬人民幣

      12,000,000.00 RMB

      一千二百萬人民幣

      12,000.35 RMB

      一萬兩千點三五人民幣

      金額標識符+數字

      $12

      十二美元

      支援 CAD(加元)、 $(美元)、Fr(法郎)、kr(丹麥幣)、 £(英鎊)、¥(元)和 €(歐元)。

      支援的數字格式包括:整數、小數以及以逗號分隔的國際寫法。

      $12.00

      十二美元

      $12.12

      二點一二美元

      $12,000

      一萬兩千美元

      $12,000.00

      一萬兩千美元

      $12,000.99

      一萬兩千點九九美元

      其他預設讀法

      1213

      一千二百一十三

      1213 KML

      一千二百一十三K M L

      1213.00 KML

      一千二百一十三K M L

      1213.9 KML

      一千二百一十三點九K M L

      1,000 KML

      一千K M L

      1,000.00 KML

      一千K M L

      1,000.98 KML

      一千點九八K M L

      12,000

      一萬兩千

      格式

      樣本

      英文輸出

      說明

      數字+金額識別符

      1.00 RMB

      one yuan

      支援的數字格式:整數、小數以及以逗號分隔的國際寫法。

      支援的金額識別符:

      CN¥ (yuan)

      CNY (yuan)

      RMB (yuan)

      AUD (australian dollar)

      CAD (canadian dollar)

      CHF (swiss franc)

      DKK (danish krone)

      EUR (euro)

      GBP (british pound)

      HKD (Hong Kong(China) dollar)

      JPY (japanese yen)

      NOK (norwegian krone)

      SEK (swedish krona)

      SGD (singapore dollar)

      USD (united states dollar)

      2.02 CNY

      two point zero two yuan

      1,000.23 CN¥

      one thousand point two three yuan

      1.01 SGD

      one singapore dollar and one cent

      2.01 CAD

      two canadian dollars and one cent

      3.1 HKD

      three hong kong dollars and ten cents

      1,000.00 EUR

      one thousand euros

      金額識別符+數字

      US$ 1.00

      one US dollar

      支援的數字格式:整數、小數以及以逗號分隔的國際寫法。

      支援的金額識別符:

      US$ (US dollar)

      CA$ (Canadian dollar)

      AU$ (Australian dollar)

      SG$ (Singapore dollar)

      HK$ (Hong Kong dollar)

      C$ (Canadian dollar)

      A$ (Australian dollar)

      $ (dollar)

      £ (pound)

      € (euro)

      CN¥ (yuan)

      CNY (yuan)

      RMB (yuan)

      AUD (australian dollar)

      CAD (canadian dollar)

      CHF (swiss franc)

      DKK (danish krone)

      EUR (euro)

      GBP (british pound)

      HKD (Hong Kong(China) dollar)

      JPY (japanese yen)

      NOK (norwegian krone)

      SEK (swedish krona)

      SGD (singapore dollar)

      USD (united states dollar)

      $0.01

      one cent

      JPY 1.01

      one japanese yen and one sen

      £1.1

      one pound and ten pence

      €2.01

      two euros and one cent

      USD 1,000

      one thousand united states dollars

      數字+量詞+金額識別符

      金額識別符+數字+量詞

      1.23 Tn RMB

      one point two three trillion yuan

      支援的量詞格式包括:

      thousand

      million

      billion

      trillion

      Mil (million)

      mil (million)

      Bil (billion)

      bil (billion)

      MM (million)

      Bn (billion)

      bn (billion)

      Tn (trillion)

      tn (trillion)

      K(thousand)

      k (thousand)

      M (million)

      m (million)

      $1.2 K

      one point two thousand dollars

    • measure

      格式

      樣本

      中文輸出

      說明

      數字+中文單位

      2片

      兩片

      支援常見中文單位及單位縮寫。

      120公頃

      一百二十公頃

      100多毫克

      一百多毫克

      100來米

      一百來米

      100餘人

      一百餘人

      1厘米20毫米

      一厘米二十毫米

      120.00平方公裡

      一百二十平方公裡

      數字+單位縮寫

      120.56 cm²

      一百二十點五六平方厘米

      120 ㎡ 56 cm²

      一百二十平方米五十六平方厘米

      100 m 12 cm 6 mm

      一百米十二厘米六毫米

      範圍

      10~15 kg

      十至十五千克

      10.24~789.82畝

      十點二四至七百八十九點八二畝

      10米~15米

      十米至十五米

      10.24 cm~19.08 cm

      十點二四厘米至十九點零八厘米

      數字+單位+"/"+單位

      10元/斤

      十元每斤

      199~299元/件

      一百九十九至二百九十九元每件

      299.99元/g~399.99元/g

      二百九十九點九九元每克至三百九十九點九九元每克

      其他預設讀法

      12紮

      十二紮

      30 rm

      三十r m

      4萬萬同胞

      四萬萬同胞

      12.897微克

      十二點八九七微克

      格式

      樣本

      英文輸出

      說明

      數字+計量單位

      1.0 kg

      one kilogram

      支援的數字格式:整數、小數以及以逗號分隔的國際寫法。

      支援常見單位縮寫。

      1,234.01 km

      one thousand two hundred thirty four point zero one kilometres.

      計量單位

      mm2

      square millimetre

    • <say-as>常見符號讀法如下表所示。

      符號

      中文讀法

      英文讀法

      !

      歎號

      exclamation mark

      雙引號

      double quote

      #

      井號

      pound

      $

      dollar

      dollar

      %

      百分比符號

      percent

      &

      and

      and

      單引號

      left quote

      左括弧

      left parenthesis

      右括弧

      right parenthesis

      *

      asterisk

      +

      plus

      ,

      逗號

      comma

      -

      dash

      .

      dot

      /

      斜杠

      slash

      零冒號

      solon

      分號

      semicolon

      <

      小於

      less than

      =

      等號

      equals

      >

      大於

      greater than

      ?

      問號

      question mark

      @

      at

      at

      [

      左方括弧

      left bracket

      \

      反斜線

      back slash

      ]

      右方括弧

      right bracket

      ^

      脫字元

      caret

      _

      底線

      underscore

      `

      反引號

      back quote

      {

      左花括弧

      left brace

      |

      豎線

      vertical bar

      }

      右花括弧

      right brace

      ~

      波浪線

      tilde

      歎號

      exclamation mark

      左雙引號

      left double quote

      右雙引號

      right double qute

      左單引號

      left quote

      右單引號

      right quote

      左括弧

      left parenthesis

      右括弧

      right parenthesis

      逗號

      comma

      句號

      full stop

      em dash

      冒號

      colon

      分號

      semicolon

      問號

      question mark

      頓號

      enumeration comma

      省略符號

      ellipsis

      ……

      省略符號

      ellipsis

      左>形箭號

      left guillemet

      右>形箭號

      right guillemet

      人民幣符號

      yuan

      大於等於

      greater than or equal to

      小於等於

      less than or equal to

      不等於

      not equal

      約等於

      approximately equal

      ±

      加減

      plus or minus

      ×

      times

      π

      pi

      Α

      阿爾法

      alpha

      Β

      貝塔

      beta

      Γ

      伽瑪

      gamma

      Δ

      德爾塔

      delta

      Ε

      艾普西龍

      epsilon

      Ζ

      捷塔

      zeta

      Θ

      西塔

      theta

      Ι

      艾歐塔

      iota

      Κ

      喀帕

      kappa

      拉姆達

      lambda

      Μ

      mu

      Ν

      nu

      Ξ

      克西

      ksi

      Ο

      歐麥克輪

      omicron

      pi

      Ρ

      rho

      西格瑪

      sigma

      Τ

      tau

      Υ

      宇普西龍

      upsilon

      Φ

      fai

      phi

      Χ

      chi

      Ψ

      普賽

      psi

      Ω

      歐米伽

      omega

      α

      阿爾法

      alpha

      β

      貝塔

      beta

      γ

      伽瑪

      gamma

      δ

      德爾塔

      delta

      ε

      艾普西龍

      epsilon

      ζ

      捷塔

      zeta

      η

      依塔

      eta

      θ

      西塔

      theta

      ι

      艾歐塔

      iota

      κ

      喀帕

      kappa

      λ

      拉姆達

      lambda

      μ

      mu

      ν

      nu

      ξ

      克西

      ksi

      ο

      歐麥克輪

      omicron

      π

      pi

      ρ

      rho

      σ

      西格瑪

      sigma

      τ

      tau

      υ

      宇普西龍

      upsilon

      φ

      fai

      phi

      χ

      chi

      ψ

      普賽

      psi

      ω

      歐米伽

      omega

    • <say-as>常見計量單位如下表所示。

      格式

      類別

      中文樣本

      英文樣本

      縮寫

      長度

      nm(納米)、μm(微米)、 mm(毫米)、cm(厘米)、m(米)、km(千米)、ft(英尺)、in(英寸)

      nm (nanometre), μm (micrometre), mm (millimetre), cm (centimetre), m (metre), km (kilometre), ft (foot), in (inch)

      面積

      cm²(平方厘米)、㎡(平方米)、km²(平方千米)、SqFt(平方英尺)

      cm² (square centimetre), ㎡ (square metre), km2 (square kilometre), SqFt (square foot)

      體積

      cm³(立方厘米)、m³(立方米)、km³(立方千米)、mL(毫升)、L(升)、gallon(加侖)

      cm³ (cubic centimetre), m³ (cubic metre), km3 (cubic kilometre), mL (millilitre), L (millilitre), gal (gallon)

      重量

      μg(微克)、mg(毫克)、g(克)、kg(千克)

      μg (microgram), mg (microgram), g (gram), kg (kilogram)

      時間

      min(分)、sec(秒)、ms(毫秒)

      min (minute), sec (second), ms (millisecond)

      電磁

      μA(微安)、mA(毫安)、Ω(歐姆)、Hz(赫茲)、kHz(千赫茲)、MHz(兆赫茲)、GHz(吉赫茲)、V(伏)、kV(千伏)、kWh(千瓦時)

      μA (microamp), mA (milliamp), Hz (hertz), kHz (kilohertz), MHz (megahertz), GHz (gigahertz), V (volt), kV (kilovolt), kWh (kilowatt hour)

      聲音

      dB(分貝)

      dB (decibel)

      氣壓

      Pa(帕)、kPa(千帕)、Mpa(兆帕)

      Pa (pascal), kPa (kilopascal), MPa (megapascal)

      其他常見單位

      支援不限於上述類別的中文單位,例如“米”、“秒”、“美元”、“毫升每瓶”等。以及中文量詞,例如“架”、“場”、“頭”、“部”、“盆”等。

      支援不限於上述類別的計量單位,例如 tsp (teaspoon), rpm (round per minute), KB (kilobyte), mmHg (milimetre of mercury) 等。

  • 標籤關係

    <say-as>標籤可以包括文本及<vhml/>。

  • 樣本

    • cardinal

      <speak>
        <say-as interpret-as="cardinal">12345</say-as>
      </speak>
      <speak>
        <say-as interpret-as="cardinal">10234</say-as>
      </speak>
    • digits

      <speak>
        <say-as interpret-as="digits">12345</say-as>
      </speak>
      <speak>
        <say-as interpret-as="digits">10234</say-as>
      </speak>
    • telephone

      <speak>
        <say-as interpret-as="telephone">12345</say-as>
      </speak>
      <speak>
        <say-as interpret-as="telephone">10234</say-as>
      </speak>
    • name

      <speak>
        她的曾用名是<say-as interpret-as="name">曾小凡</say-as>
      </speak>
    • address

      <speak>
        <say-as interpret-as="address">富路國際1號樓3單元304</say-as>
      </speak>
    • id

      <speak>
        <say-as interpret-as="id">myid_1998</say-as>
      </speak>
    • characters

      <speak>
        <say-as interpret-as="characters">希臘字母αβ</say-as>
      </speak>
      <speak>
        <say-as interpret-as="characters">*b+3.c$=α</say-as>
      </speak>
    • punctuation

      <speak>
        <say-as interpret-as="punctuation"> -./:;</say-as>
      </speak>
    • date

      <speak>
        <say-as interpret-as="date">1000-10-10</say-as>
      </speak>
      <speak>
        <say-as interpret-as="date">10-01-2020</say-as>
      </speak>
    • time

      <speak>
        <say-as interpret-as="time">5:00am</say-as>
      </speak>
      <speak>
        <say-as interpret-as="time">0500</say-as>
      </speak>
    • currency

      <speak>
        <say-as interpret-as="currency">13,000,000.00RMB</say-as>
      </speak>
      <speak>
        <say-as interpret-as="currency">$1,000.01</say-as>
      </speak>
    • measure

      <speak>
        <say-as interpret-as="measure">100m12cm6mm</say-as>
      </speak>
      <speak>
        <say-as interpret-as="measure">1,000.01kg</say-as>
      </speak>