音声合成マークアップ言語 (Speech Synthesis Markup Language、SSML) は、音声合成のための XML ベースのマークアップ言語です。これにより、大規模な音声合成モデルでリッチテキストコンテンツを処理できるようになり、話速、ピッチ、ポーズ、音量などの音声の特徴を詳細に制御できます。また、背景音楽を追加して、より表現力豊かな音声効果を作成することも可能です。このトピックでは、CosyVoice の SSML の特徴とその使用方法について説明します。
中国 (北京) リージョンのモデルを使用するには、中国 (北京) リージョンの API キーページに移動してください。
制限事項
モデル:SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのみをサポートします。
音声:SSML は、クローン音声と、音声リストで SSML をサポートするとマークされているシステム音声のみをサポートします。
API:SSML は一部の API のみをサポートします。
Java SDK (バージョン 2.20.3 以降):SSML は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。詳細については、「SSML サポート - Java SDK」をご参照ください。
Python SDK (バージョン 1.23.4 以降):SSML は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。詳細については、「SSML サポート - Python SDK」をご参照ください。
WebSocket API:run-task 命令を送信する際に、
enable_ssmlパラメーターをtrueに設定し、continue-task 命令を 1 回だけ送信します。詳細については、「SSML サポート - WebSocket API」をご参照ください。
利用開始
コードを実行する前に、次の手順を完了してください:
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. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
* および音声リストで 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()
// 環境変数を設定していない場合は、次の行を .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\">My speech rate is faster than normal.</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] Request ID: "
+ synthesizer.getLastRequestId()
+ ", First packet latency (ms): "
+ 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. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
* および音声リストで 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("Audio saved to 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("Failed to create output file: " + 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("Failed to write audio data: " + e.getMessage());
}
}
}
@Override
public void onComplete() {
System.out.println("Received Complete; speech synthesis finished");
closeFileOutputStream(fileOutputStream[0]);
latch.countDown();
}
@Override
public void onError(Exception e) {
System.out.println("Error occurred: " + e.toString());
closeFileOutputStream(fileOutputStream[0]);
latch.countDown();
}
};
SpeechSynthesisParam param =
SpeechSynthesisParam.builder()
// 環境変数を設定していない場合は、次の行を .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 を返します (結果はコールバックを通じて非同期に配信されます)
// 特殊文字をエスケープします
synthesizer.call("<speak rate=\"2\">My speech rate is faster than normal.</speak>");
// 合成が完了するのを待ちます
latch.await();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// タスク終了後、WebSocket 接続を閉じます
try {
synthesizer.getDuplexApi().close(1000, "bye");
} catch (Exception e) {
System.err.println("Failed to close WebSocket connection: " + e.getMessage());
}
// ファイルストリームが閉じられていることを確認します
closeFileOutputStream(fileOutputStream[0]);
}
// 最初のパケットのレイテンシには、WebSocket 接続の確立に必要な時間が含まれます
System.out.println(
"[Metric] Request ID: "
+ synthesizer.getLastRequestId()
+ ", First packet latency (ms): "
+ synthesizer.getFirstPackageDelay());
}
private static void closeFileOutputStream(FileOutputStream fileOutputStream) {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
System.err.println("Failed to close file stream: " + e.getMessage());
}
}
}Python SDK
非ストリーミング呼び出し
# coding=utf-8
# SSML 機能の注意事項:
# 1. SSML は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。
# 2. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
# および音声リストで SSML サポートとマークされたシステム音声 (例:cosyvoice-v3-flash の longanyang 音声) でのみサポートされます。
import dashscope
from dashscope.audio.tts_v2 import *
import os
# 環境変数を設定していない場合は、次の行を dashscope.api_key = "sk-xxx" に置き換えてください
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# モデル
model = "cosyvoice-v3-flash"
# 音声
voice = "longanyang"
# SpeechSynthesizer をインスタンス化し、モデル、音声、その他のリクエストパラメーターをコンストラクターに渡します
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# 非ストリーミング呼び出し。音声が返されるまでブロックします
# 特殊文字をエスケープします
audio = synthesizer.call("<speak rate=\"2\">My speech rate is faster than normal.</speak>")
# 音声をローカルに保存します
with open('output.mp3', 'wb') as f:
f.write(audio)
# 最初のパケットのレイテンシには、WebSocket 接続の確立に必要な時間が含まれます
print('[Metric] Request ID: {}, First packet latency: {} ms'.format(
synthesizer.get_last_request_id(),
synthesizer.get_first_package_delay()))単方向ストリーミング呼び出し
# coding=utf-8
# SSML 機能の注意事項:
# 1. SSML は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。
# 2. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
# および音声リストで 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
# 環境変数を設定していない場合は、次の行を 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("Connection established: " + get_timestamp())
def on_complete(self):
print("Speech synthesis completed; all results received: " + get_timestamp())
if hasattr(self, 'file') and self.file:
self.file.close()
self
# 最初のパケットのレイテンシには、WebSocket 接続の確立に必要な時間が含まれます
print('[Metric] Request ID: {}, First packet latency: {} ms'.format(
self.synthesizer.get_last_request_id(),
self.synthesizer.get_first_package_delay()))
def on_error(self, message: str):
print(f"Speech synthesis error: {message}")
if hasattr(self, 'file') and self.file:
self.file.close()
def on_close(self):
print("Connection closed: " + 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() + " Binary audio length: " + str(len(data)))
# 音声データをファイルに書き込みます
self.file.write(data)
callback = Callback()
# SpeechSynthesizer をインスタンス化し、モデル、音声、その他のリクエストパラメーターをコンストラクターに渡します
synthesizer = SpeechSynthesizer(
model=model,
voice=voice,
callback=callback,
)
# on_complete で使用するために、synthesizer インスタンスをコールバックに割り当てます
callback.synthesizer = synthesizer
# 単方向ストリーミング呼び出し。合成するテキストを送信し、コールバックの on_data メソッドを介してリアルタイムでバイナリ音声を受信します
# 特殊文字をエスケープします
synthesizer.call("<speak rate=\"2\">My speech rate is faster than normal.</speak>")WebSocket API
Go
// SSML 機能の注意事項:
// 1. run-task コマンドで enable_ssml パラメーターを true に設定して SSML を有効にします。
// 2. continue-task コマンドを使用して SSML テキストを送信します。このコマンドは 1 回しか送信できません。
// 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
// および音声リストで 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 キーを環境変数として設定していない場合は、次の行を apiKey := "your_api_key" に置き換えてください。
// 漏洩のリスクを減らすため、本番コードに API キーを直接ハードコーディングしないでください。
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("Connection failed. HTTP status code: %d\n", resp.StatusCode)
}
fmt.Println("Connection failed:", err)
return
}
defer conn.Close()
// タスク ID を生成します
taskID := uuid.New().String()
fmt.Printf("Generated task 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 コマンドは 1 回しか送信できません。
// そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
"enable_ssml": true,
},
"input": map[string]interface{}{},
},
}
runTaskJSON, _ := json.Marshal(runTaskCmd)
fmt.Printf("Sending run-task command: %s\n", string(runTaskJSON))
err = conn.WriteMessage(websocket.TextMessage, runTaskJSON)
if err != nil {
fmt.Println("Failed to send run-task:", err)
return
}
textSent := false
// メッセージを処理します
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
fmt.Println("Failed to read message:", err)
break
}
// バイナリメッセージを処理します
if messageType == websocket.BinaryMessage {
fmt.Printf("Received binary message, length: %d\n", len(message))
file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
file.Write(message)
file.Close()
continue
}
// テキストメッセージを処理します
messageStr := string(message)
fmt.Printf("Received text message: %s\n", strings.ReplaceAll(messageStr, "\n", ""))
// JSON を解析してイベントタイプを取得します
var msgMap map[string]interface{}
if json.Unmarshal(message, &msgMap) == nil {
if header, ok := msgMap["header"].(map[string]interface{}); ok {
if event, ok := header["event"].(string); ok {
fmt.Printf("Event type: %s\n", event)
switch event {
case "task-started":
fmt.Println("=== Received task-started event ===")
if !textSent {
// continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
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\">My speech rate is faster than normal.</speak>",
},
},
}
continueTaskJSON, _ := json.Marshal(continueTaskCmd)
fmt.Printf("Sending continue-task command: %s\n", string(continueTaskJSON))
err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON)
if err != nil {
fmt.Println("Failed to send 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("Sending finish-task command: %s\n", string(finishTaskJSON))
err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON)
if err != nil {
fmt.Println("Failed to send finish-task:", err)
return
}
}
case "task-finished":
fmt.Println("=== Task finished ===")
return
case "task-failed":
fmt.Println("=== Task failed ===")
if header["error_message"] != nil {
fmt.Printf("Error message: %s\n", header["error_message"])
}
return
case "result-generated":
fmt.Println("Received result-generated event")
}
}
}
}
}
}
C#
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
// SSML 機能の注意事項:
// 1. run-task コマンドで enable_ssml パラメーターを true に設定して SSML を有効にします。
// 2. continue-task コマンドを使用して SSML テキストを送信します。このコマンドは 1 回しか送信できません。
// 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
// および音声リストで SSML サポートとマークされたシステム音声 (例:cosyvoice-v3-flash の longanyang 音声) でのみサポートされます。
class Program {
// API キーを環境変数として設定していない場合は、次の行を private const string ApiKey="your_api_key" に置き換えてください。
// 漏洩のリスクを減らすため、本番コードに API キーを直接ハードコーディングしないでください。
private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");
// WebSocket サーバーの URL
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 を使用する場合、このコマンドは 1 回しか送信できません
// 特殊文字をエスケープします
await SendContinueTaskCommandAsync("<speak rate=\"2\">My speech rate is faster than normal.</speak>");
// finish-task コマンドを送信します
await SendFinishTaskCommandAsync(_taskId);
// 受信タスクが完了するのを待ちます
await receiveTask;
Console.WriteLine("Task completed; connection closed.");
} catch (OperationCanceledException) {
Console.WriteLine("Task canceled.");
} catch (Exception ex) {
Console.WriteLine($"Error occurred: {ex.Message}");
} finally {
_cancellationTokenSource.Cancel();
_webSocket.Dispose();
}
}
private static void ClearOutputFile(string filePath) {
if (File.Exists(filePath)) {
File.WriteAllText(filePath, string.Empty);
Console.WriteLine("Output file cleared.");
} else {
Console.WriteLine("Output file does not exist; no need to clear.");
}
}
private static async Task ConnectToWebSocketAsync(string url) {
var uri = new Uri(url);
if (_webSocket.State == WebSocketState.Connecting || _webSocket.State == WebSocketState.Open) {
return;
}
// WebSocket 接続ヘッダーを設定します
_webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}");
_webSocket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable");
try {
await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token);
Console.WriteLine("Successfully connected to WebSocket service.");
} catch (OperationCanceledException) {
Console.WriteLine("WebSocket connection canceled.");
} catch (Exception ex) {
Console.WriteLine($"WebSocket connection failed: {ex.Message}");
throw;
}
}
private static async Task SendRunTaskCommandAsync(string taskId) {
var command = CreateCommand("run-task", taskId, "duplex", new {
task_group = "audio",
task = "tts",
function = "SpeechSynthesizer",
model = "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 コマンドは 1 回しか送信できません。
// そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
enable_ssml = true
},
input = new { }
});
await SendJsonMessageAsync(command);
Console.WriteLine("Sent run-task command.");
}
private static async Task SendContinueTaskCommandAsync(string text) {
if (_taskId == null) {
throw new InvalidOperationException("Task ID not initialized.");
}
var command = CreateCommand("continue-task", _taskId, "duplex", new {
input = new {
text
}
});
await SendJsonMessageAsync(command);
Console.WriteLine("Sent continue-task command.");
}
private static async Task SendFinishTaskCommandAsync(string taskId) {
var command = CreateCommand("finish-task", taskId, "duplex", new {
input = new { }
});
await SendJsonMessageAsync(command);
Console.WriteLine("Sent finish-task command.");
}
private static async Task SendJsonMessageAsync(string message) {
var buffer = Encoding.UTF8.GetBytes(message);
try {
await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
} catch (OperationCanceledException) {
Console.WriteLine("Message send canceled.");
}
}
private static async Task ReceiveMessagesAsync() {
while (_webSocket.State == WebSocketState.Open) {
var response = await ReceiveMessageAsync();
if (response != null) {
var eventStr = response.RootElement.GetProperty("header").GetProperty("event").GetString();
switch (eventStr) {
case "task-started":
Console.WriteLine("Task started.");
_taskStartedTcs.TrySetResult(true);
break;
case "task-finished":
Console.WriteLine("Task completed.");
_cancellationTokenSource.Cancel();
break;
case "task-failed":
Console.WriteLine("Task failed: " + response.RootElement.GetProperty("header").GetProperty("error_message").GetString());
_cancellationTokenSource.Cancel();
break;
default:
// 必要に応じてここで result-generated を処理します
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("Received binary data...");
// バイナリデータをファイルに保存します
using (var fileStream = new FileStream(OutputFilePath, FileMode.Append)) {
fileStream.Write(buffer, 0, result.Count);
}
return null;
}
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
return JsonDocument.Parse(message);
} catch (OperationCanceledException) {
Console.WriteLine("Message receive canceled.");
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 テキストを送信します。このコマンドは 1 回しか送信できません。 -->
<!-- 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
および音声リストで 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 キーを環境変数として設定していない場合は、次の行を $api_key="your_api_key" に置き換えてください。
// 漏洩のリスクを減らすため、本番コードに API キーを直接ハードコーディングしないでください。
$api_key = getenv("DASHSCOPE_API_KEY");
$websocket_url = 'wss://dashscope.aliyuncs.com/api-ws/v1/inference/'; // WebSocket サーバーの URL
$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 "Connected to WebSocket server\n";
// タスク ID を生成します
$taskId = generateTaskId();
// run-task コマンドを送信します
sendRunTaskMessage($conn, $taskId);
// continue-task コマンドを送信する関数を定義します
$sendContinueTask = function() use ($conn, $loop, $taskId) {
// continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
$continueTaskMessage = json_encode([
"header" => [
"action" => "continue-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"input" => [
// 特殊文字をエスケープします
"text" => "<speak rate=\"2\">My speech rate is faster than normal.</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 "Unknown message format\n";
}
}
});
// 接続クローズをリッスンします
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed\n";
if ($code !== null) {
echo "Close code: " . $code . "\n";
}
if ($reason !== null) {
echo "Close reason: " . $reason . "\n";
}
});
}, function ($e) {
echo "Connection failed: {$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 コマンドは 1 回しか送信できません。
// そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
"enable_ssml" => true
],
"input" => (object) []
]
]);
echo "Preparing to send run-task command: " . $runTaskMessage . "\n";
$conn->send($runTaskMessage);
echo "run-task command sent\n";
}
/**
* 音声ファイルを読み取ります
* @param string $filePath
* @return bool|string
*/
function readAudioFile(string $filePath) {
$voiceData = file_get_contents($filePath);
if ($voiceData === false) {
echo "Failed to read audio file\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 "Preparing to send finish-task command: " . $finishTaskMessage . "\n";
$conn->send($finishTaskMessage);
echo "finish-task command sent\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 "Task started; sending continue-task command...\n";
$taskStarted = true;
// continue-task コマンドを送信します
$sendContinueTask();
break;
case 'result-generated':
// result-generated イベントを無視します
break;
case 'task-finished':
echo "Task completed\n";
$conn->close();
break;
case 'task-failed':
echo "Task failed\n";
echo "Error code: " . $response['header']['error_code'] . "\n";
echo "Error message: " . $response['header']['error_message'] . "\n";
$conn->close();
break;
case 'error':
echo "Error: " . $response['payload']['message'] . "\n";
break;
default:
echo "Unknown event: " . $response['header']['event'] . "\n";
break;
}
// タスク完了後に接続を閉じます
if ($response['header']['event'] == 'task-finished') {
// すべてのデータが送信されるように 1 秒待機します
$loop->addTimer(1, function() use ($conn) {
$conn->close();
echo "Client closed connection\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 テキストを送信します。このコマンドは 1 回しか送信できません。
// 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
// および音声リストで SSML サポートとマークされたシステム音声 (例:cosyvoice-v3-flash の longanyang 音声) でのみサポートされます。
import fs from 'fs';
import WebSocket from 'ws';
import { v4 as uuid } from 'uuid'; // UUID を生成するため
// API キーを環境変数として設定していない場合は、次の行を apiKey = 'your_api_key' に置き換えてください。
// 漏洩のリスクを減らすため、本番コードに API キーを直接ハードコーディングしないでください。
const apiKey = process.env.DASHSCOPE_API_KEY;
// WebSocket サーバーの URL
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('Connected to WebSocket server');
// 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 を有効にします。true に設定した場合、continue-task コマンドは 1 回しか送信できません。
// そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
},
input: {}
}
});
ws.send(runTaskMessage);
console.log('Sent run-task message');
});
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('Task started');
// continue-task コマンドを送信します
sendContinueTasks(ws);
break;
case 'task-finished':
console.log('Task completed');
ws.close();
fileStream.end(() => {
console.log('File stream closed');
});
break;
case 'task-failed':
console.error('Task failed:', message.header.error_message);
ws.close();
fileStream.end(() => {
console.log('File stream closed');
});
break;
default:
// 必要に応じてここで result-generated を処理します
break;
}
}
});
function sendContinueTasks(ws) {
if (taskStarted) {
// continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
const continueTaskMessage = JSON.stringify({
header: {
action: 'continue-task',
task_id: taskId,
streaming: 'duplex'
},
payload: {
input: {
// 特殊文字をエスケープします
text: '<speak rate="2">My speech rate is faster than normal.</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('Disconnected from WebSocket server');
});Java
Java を使用する場合は、Java DashScope SDK の使用を推奨します。詳細については、「Java SDK」をご参照ください。
以下は Java WebSocket の呼び出し例です。サンプルを実行する前に、次の依存関係がインポートされていることを確認してください:
Java-WebSocketjackson-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 テキストを送信します。このコマンドは 1 回しか送信できません。
* 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
* および音声リストで 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("Connection successful");
// run-task コマンドを送信します
// enable_ssml が true に設定されている場合、continue-task コマンドは 1 回しか送信できません。
// そうしないと、「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("Received message from server: " + message);
try {
// JSON メッセージを解析します
Map<String, Object> messageMap = new ObjectMapper().readValue(message, Map.class);
if (messageMap.containsKey("header")) {
Map<String, Object> header = (Map<String, Object>) messageMap.get("header");
if (header.containsKey("event")) {
String event = (String) header.get("event");
if ("task-started".equals(event)) {
System.out.println("Received task-started event from server");
// continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
// 特殊文字をエスケープします
sendContinueTask("<speak rate=\\\"2\\\">My speech rate is faster than normal.</speak>");
// finish-task コマンドを送信します
sendFinishTask();
} else if ("task-finished".equals(event)) {
System.out.println("Received task-finished event from server");
taskFinished = true;
closeConnection();
} else if ("task-failed".equals(event)) {
System.out.println("Task failed: " + message);
closeConnection();
}
}
}
} catch (Exception e) {
System.err.println("Error occurred: " + e.getMessage());
}
}
@Override
public void onMessage(ByteBuffer message) {
System.out.println("Received binary audio data size: " + message.remaining());
try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
byte[] buffer = new byte[message.remaining()];
message.get(buffer);
fos.write(buffer);
System.out.println("Audio data written to local file " + outputFile);
} catch (IOException e) {
System.err.println("Failed to write audio data to local file: " + e.getMessage());
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
System.out.println("Connection closed: " + reason + " (" + code + ")");
}
@Override
public void onError(Exception ex) {
System.err.println("Error: " + ex.getMessage());
ex.printStackTrace();
}
private void sendContinueTask(String text) {
String command = "{ \"header\": { \"action\": \"continue-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": { \"text\": \"" + text + "\" } }}";
send(command);
}
private void sendFinishTask() {
String command = "{ \"header\": { \"action\": \"finish-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": {} }}";
send(command);
}
private void closeConnection() {
if (!isClosed()) {
close();
}
}
public static void main(String[] args) {
try {
String apiKey = System.getenv("DASHSCOPE_API_KEY");
if (apiKey == null || apiKey.isEmpty()) {
System.err.println("Please set the DASHSCOPE_API_KEY environment variable");
return;
}
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "bearer " + apiKey);
TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://dashscope.aliyuncs.com/api-ws/v1/inference/"), headers);
client.connect();
while (!client.isClosed() && !client.taskFinished) {
Thread.sleep(1000);
}
} catch (Exception e) {
System.err.println("Failed to connect to WebSocket service: " + e.getMessage());
e.printStackTrace();
}
}
}Python
Python を使用する場合は、Python DashScope SDK の使用を推奨します。詳細については、「Python SDK」をご参照ください。
以下は Python WebSocket の呼び出し例です。サンプルを実行する前に、次のように依存関係をインストールしてください:
pip uninstall websocket-client
pip uninstall websocket
pip install websocket-clientPython スクリプトに "websocket.py" という名前を付けないでください。そうしないと、AttributeError が発生します。
# SSML 機能の注意事項:
# 1. run-task コマンドで enable_ssml パラメーターを true に設定して SSML を有効にします。
# 2. continue-task コマンドを使用して SSML テキストを送信します。このコマンドは 1 回しか送信できません。
# 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
# および音声リストで SSML サポートとマークされたシステム音声 (例:cosyvoice-v3-flash の longanyang 音声) でのみサポートされます。
import websocket
import json
import uuid
import os
import time
class TTSClient:
def __init__(self, api_key, uri):
"""
TTSClient インスタンスを初期化します
Args:
api_key (str): 認証用の API キー
uri (str): WebSocket サービス URL
"""
self.api_key = api_key # ご利用の API キーに置き換えてください
self.uri = uri # ご利用の WebSocket URL に置き換えてください
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 connected")
# 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 コマンドは 1 回しか送信できません。
# そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
"enable_ssml": True
},
"input": {}
}
}
# run-task コマンドを送信します
ws.send(json.dumps(run_task_cmd))
print("Sent run-task command")
def on_message(self, ws, message):
"""
メッセージが受信されたときのコールバック
テキストメッセージとバイナリメッセージの両方を処理します
"""
if isinstance(message, str):
# JSON テキストメッセージを処理します
try:
msg_json = json.loads(message)
print(f"Received JSON message: {msg_json}")
if "header" in msg_json:
header = msg_json["header"]
if "event" in header:
event = header["event"]
if event == "task-started":
print("Task started")
self.task_started = True
# continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
# 特殊文字をエスケープします
self.send_continue_task("<speak rate=\"2\">My speech rate is faster than normal.</speak>")
# continue-task の後に finish-task を送信します
self.send_finish_task()
elif event == "task-finished":
print("Task completed")
self.task_finished = True
self.close(ws)
elif event == "task-failed":
error_msg = msg_json.get("error_message", "Unknown error")
print(f"Task failed: {error_msg}")
self.task_finished = True
self.close(ws)
except json.JSONDecodeError as e:
print(f"JSON parsing failed: {e}")
else:
# バイナリメッセージ (音声データ) を処理します
print(f"Received binary message, size: {len(message)} bytes")
with open(self.output_file, "ab") as f:
f.write(message)
print(f"Audio data written to local file {self.output_file}")
def on_error(self, ws, error):
"""エラーが発生したときのコールバック"""
print(f"WebSocket error: {error}")
def on_close(self, ws, close_status_code, close_msg):
"""接続が閉じたときのコールバック"""
print(f"WebSocket closed: {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"Sent continue-task command, text: {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("Sent finish-task command")
def close(self, ws):
"""接続を能動的に閉じます"""
if ws and ws.sock and ws.sock.connected:
ws.close()
print("Connection closed actively")
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("Listening for WebSocket messages...")
self.ws.run_forever() # 長時間実行される接続を開始します
# 使用例
if __name__ == "__main__":
API_KEY = os.environ.get("DASHSCOPE_API_KEY") # 環境変数として設定されていない場合は、API_KEY をご利用の API キーに設定してください
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 特殊文字をエスケープする必要があります。一般的な特殊文字とそのエスケープ形式は次のとおりです:
"(二重引用符) →"'(単一引用符/アポストロフィ) →'&(アンパサンド) →&<(不等号 (より小)) → <>(不等号 (より大)) → >
<speak>:ルートノード
説明
<speak>タグは、すべての SSML ドキュメントのルートノードです。SSML 機能を使用するすべてのテキストは、<speak></speak>タグで囲む必要があります。構文
<speak>SSML 機能を必要とするテキスト</speak>プロパティ
プロパティ
タイプ
必須
説明
voice
String
いいえ
音声を指定します。
このプロパティは、API リクエストの
voiceパラメーターよりも優先度が高くなります。有効値:特定の音声の詳細については、「cosyvoice-v2 の音声」をご参照ください。
例:
<speak voice="longcheng_v2"> 私は男性の声です。 </speak>
rate
String
いいえ
話速を指定します。このプロパティは、API リクエストの
speech_rateパラメーターよりも優先度が高くなります。有効値:0.5 から 2 までの 10 進数。
デフォルト値:1
1 より大きい値は、話速が速いことを示します。
1 より小さい値は、話速が遅いことを示します。
例:
<speak rate="2"> 私の話速は通常より速いです。 </speak>
pitch
String
いいえ
ピッチを指定します。このプロパティは、API リクエストの
pitch_rateパラメーターよりも優先度が高くなります。有効値:0.5 から 2 までの 10 進数。
デフォルト値:1
1 より大きい値は、ピッチが高いことを示します。
1 より小さい値は、ピッチが低いことを示します。
例:
<speak pitch="0.5"> しかし、私のピッチは他の人より低いです。 </speak>
volume
String
いいえ
音量を指定します。このプロパティは、API リクエストの
volumeパラメーターよりも優先度が高くなります。有効値:0 から 100 までの整数。
デフォルト値:50
50 より大きい値は、音量が大きいことを示します。
50 より小さい値は、音量が小さいことを示します。
例:
<speak volume="80"> 私の音量も非常に大きいです。 </speak>
effect
String
いいえ
効果音を指定します。
有効値:
robot:ロボットの効果音
lolita:生き生きとした女性の声の効果
lowpass:ローパスの効果音
echo:エコーの効果音
eq:イコライザー (高度)
lpfilter:ローパスフィルター (高度)
hpfilter:ハイパスフィルター (高度)
説明eq、lpfilter、hpfilter は高度な効果音タイプです。
effectValueパラメーターを使用して、特定の効果をカスタマイズできます。各 SSML タグは 1 つの効果音のみをサポートします。複数の
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.0 q です。
この効果を使用する場合、
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
いいえ
合成された音声に指定された背景音楽を追加します。背景音楽ファイルは Alibaba Cloud OSS (Object Storage Service) に保存する必要があり (「ファイルのアップロード」をご参照ください)、そのバケットには少なくとも公開読み取り権限が必要です。
背景音楽の URL に
&、<、>などの XML 特殊文字が含まれている場合は、エスケープする必要があります。音声要件:
音声ファイルのサイズに上限はありませんが、ファイルサイズが大きいとダウンロード時間が増加する可能性があります。合成されたコンテンツの長さが背景音楽の長さを超える場合、背景音楽は合成された音声の長さに合わせて自動的にループします。
サンプルレート:16 kHz
サウンドチャンネル数:モノラル
ファイル形式:WAV
元の音声が WAV 形式でない場合は、
ffmpegツールを使用して変換します:ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.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> タグを属性なしで使用する場合、デフォルトのポーズ時間は 1 秒です。
プロパティ
タイプ
必須
説明
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">text</phoneme>プロパティ
プロパティ
タイプ
必須
説明
alphabet
String
はい
発音の種類を指定します:ピンイン (中国語用) または表音アルファベット (英語用)。
有効値:
"py":ピンイン
"cmu":表音アルファベット。詳細については、「The CMU Pronouncing Dictionary」をご参照ください。
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 に保存する必要があり (「ファイルのアップロード」をご参照ください)、そのバケットには少なくとも公開読み取り権限が必要です。URL に
&、<、>などの XML 特殊文字が含まれている場合は、エスケープする必要があります。音声要件:
サンプルレート:16 kHz
サウンドチャンネル数:モノラル
ファイル形式:WAV
元の音声が WAV 形式でない場合は、
ffmpegツールを使用して変換します:ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav
ファイルサイズ:2 MB 以下
ビット深度:16 ビット
例:
<speak> 馬が驚き<soundEvent src="http://nls.alicdn.com/sound-event/horse-neigh.wav"/>、人々はそれを避けるために散り散りになった。 </speak>
重要アップロードされた音声の著作権については、お客様が法的な責任を負います。
タグの関係
<soundEvent> は空のタグであり、他のタグを含むことはできません。
<say-as>:テキストの読み上げ方の設定 (数字、日付、電話番号など)
説明
テキスト文字列のコンテンツタイプを示し、モデルが適切な形式でテキストを読み上げられるようにします。このタグはオプションです。
構文
<say-as interpret-as="string">text</say-as>プロパティ
プロパティ
タイプ
必須
説明
interpret-as
String
はい
タグ内のテキストの情報タイプを示します。
有効値:
cardinal:基数 (整数または 10 進数) として読み上げます。
digits:個々の数字として読み上げます。例えば、123 は「いち に さん」と読み上げられます。
telephone:電話番号として読み上げます。
name:名前として読み上げます。
address:住所として読み上げます。
id:アカウント名やニックネームに適しています。従来の方法で読み上げます。
characters:タグ内のテキストを 1 文字ずつ読み上げます。
punctuation:タグ内のテキストを句読点として読み上げます。
date:日付として読み上げます。
time:時刻として読み上げます。
currency:通貨額として読み上げます。
measure:測定単位として読み上げます。
各 <say-as> タイプのサポート形式
cardinal
フォーマット
例
英語出力
説明
数字列
145
one hundred forty five
整数入力範囲:13 桁以内の正または負の整数、[-999999999999, 999999999999]。
10 進数入力範囲:小数点以下の桁数に特別な制限はありませんが、10 桁を超えないことを推奨します。
ゼロで始まる数字列
0145
one hundred forty five
負の符号 + 数字列
-145
minus hundred forty five
カンマで区切られた 3 桁の数字列
60,000
sixty thousand
負の符号 + カンマで区切られた 3 桁の数字列
-208,000
minus two hundred eight thousand
数字列 + 小数点 + ゼロ
12.00
twelve
数字列 + 小数点 + 数字列
12.34
twelve point three four
カンマで区切られた 3 桁の数字列 + 小数点 + 数字列
1,000.1
one thousand point one
負の符号 + 数字列 + 小数点 + 数字列
-12.34
minus twelve point three four
負の符号 + カンマで区切られた 3 桁の数字列 + 小数点 + 数字列
-1,000.1
minus one thousand point one
(3 桁のカンマ区切り) 数字列 + ハイフン + (3 桁のカンマ区切り) 数字
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
フォーマット
例
英語出力
説明
数字列
12034
one two zero three four
数字列の長さに特別な制限はありませんが、20 桁を超えないことを推奨します。
数字列がスペースまたはハイフンでグループ化されている場合、グループ間にカンマが挿入され、適切なポーズが作成されます。最大 5 グループまでサポートされます。
数字列 + スペースまたはハイフン + 数字列 + スペースまたはハイフン + 数字列 + スペースまたはハイフン + 数字列
1-23-456 7890
one, two three, four five six, seven eight nine zero
telephone
フォーマット
例
英語出力
説明
数字列
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
このタグは英語テキストではサポートされていません。
id
英語テキストの場合、このタグは characters タグと同じように機能します。
characters
フォーマット
例
英語出力
説明
string
*b+3$.c-0'=α
asterisk B plus three dollar dot C dash zero apostrophe equals alpha
漢字、大文字と小文字の英字、アラビア数字 0-9、および一部の全角および半角文字をサポートします。
出力のスペースは、各文字の間にポーズが挿入されることを示し、文字が 1 つずつ読み上げられることを意味します。
タグ内のテキストに XML 特殊文字が含まれている場合は、エスケープする必要があります。
punctuation
英語テキストの場合、このタグは characters タグと同じように機能します。
date
フォーマット
例
英語出力
説明
4 桁/2 桁または 4 桁-2 桁
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 で始まる 4 桁の数字
2000
two thousand
4 桁の年。
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 は 2 桁の日を示します。MMM は月の 3 文字の略語またはフルネームを示します。YYYY は 1 または 2 で始まる 4 桁の年を示します。
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 は月の 3 文字の略語またはフルネームを示します。DD は 2 桁の日を示します。
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 は月の 3 文字の略語またはフルネームを示します。
Jul - Dec
july to dcember
sep&oct
september and october
YYYY-YYYY
または
YYYY~YYYY
1990 - 2000
nineteen ninety to two thousand
YYYY は 1 または 2 で始まる 4 桁の年を示します。
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 は曜日の 3 文字の略語またはフルネームです。DD は 2 桁の日です。MMM は月の 3 文字の略語またはフルネームです。MM は 2 桁の月 (または月の 3 文字の略語またはフルネーム) です。YYYY は 1 または 2 で始まる 4 桁の年です。
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
saturday 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
フォーマット
例
英語出力
説明
HH:MM AM または PM
09:00 AM
nine A M
HH は 1 桁または 2 桁の時間を示します。MM は 2 桁の分を示します。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
フォーマット
例
英語出力
説明
数字 + 通貨識別子
1.00 RMB
one yuan
サポートされている数字形式:整数、10 進数、およびカンマを桁区切り記号として使用する国際形式。
サポートされている通貨識別子:
CN¥ (元)
CNY (元)
RMB (元)
AUD (オーストラリアドル)
CAD (カナダドル)
CHF (スイスフラン)
DKK (デンマーククローネ)
EUR (ユーロ)
GBP (英ポンド)
HKD (香港 (中国) ドル)
JPY (日本円)
NOK (ノルウェークローネ)
SEK (スウェーデンクローナ)
SGD (シンガポールドル)
USD (米ドル)
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
サポートされている数字形式:整数、10 進数、およびカンマを桁区切り記号として使用する国際形式。
サポートされている通貨識別子:
US$ (米ドル)
CA$ (カナダドル)
AU$ (オーストラリアドル)
SG$ (シンガポールドル)
HK$ (香港 (中国) ドル)
C$ (カナダドル)
A$ (オーストラリアドル)
$ (ドル)
£ (ポンド)
€ (ユーロ)
CN¥ (元)
CNY (元)
RMB (元)
AUD (オーストラリアドル)
CAD (カナダドル)
CHF (スイスフラン)
DKK (デンマーククローネ)
EUR (ユーロ)
GBP (英ポンド)
HKD (香港 (中国) ドル)
JPY (日本円)
NOK (ノルウェークローネ)
SEK (スウェーデンクローナ)
SGD (シンガポールドル)
USD (米ドル)
$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
フォーマット
例
英語出力
説明
数字 + 測定単位
1.0 kg
one kilogram
整数、10 進数、およびカンマ区切り記号付きの国際表記をサポートします。
一般的な単位の略語をサポートします。
1,234.01 km
one thousand two hundred thirty-four point zero one kilometers
測定単位
mm2
square millimeter
次の表に、<say-as> の一般的な記号の発音を示します。
記号
英語の発音
!
exclamation mark
“
double quote
#
pound
$
dollar
%
percent
&
and
‘
left quote
(
left parenthesis
)
right parenthesis
*
asterisk
+
plus
,
comma
-
dash
.
dot
/
slash
:
Solon
;
semicolon
<
less than
=
equals
>
greater than
?
question mark
@
at
[
left bracket
\
backslash
]
right bracket
^
caret
_
underscore
`
backtick
{
left brace
|
vertical bar
}
right brace
~
tilde
!
感嘆符
“
左二重引用符
”
右二重引用符
‘
左引用符
’
右引用符
(
左括弧
)
右括弧
,
読点
。
句点
—
全角ダッシュ
:
コロン
;
セミコロン
?
疑問符
、
読点
…
三点リーダー
……
三点リーダー
《
左二重山括弧
》
右二重山括弧
¥
円
≥
以上
≤
以下
≠
等しくない
≈
ほぼ等しい
±
プラスマイナス
×
かける
π
パイ
Α
アルファ
Β
ベータ
Γ
ガンマ
Δ
デルタ
Ε
イプシロン
Ζ
ゼータ
Θ
シータ
Ι
イオタ
Κ
カッパ
∧
ラムダ
Μ
ミュー
Ν
ニュー
Ξ
ksi
Ο
オミクロン
∏
パイ
Ρ
ロー
∑
シグマ
Τ
タウ
Υ
ウプシロン
Φ
ファイ
Χ
chi
Ψ
psi
Ω
オメガ
α
アルファ
β
ベータ
γ
ガンマ
δ
デルタ
ε
イプシロン
ζ
ゼータ
η
イータ
θ
シータ
ι
イオタ
κ
カッパ
λ
ラムダ
μ
ミュー
ν
ニュー
ξ
ksi
ο
オミクロン
π
パイ
ρ
ロー
σ
シグマ
τ
タウ
υ
ウプシロン
φ
ファイ
χ
chi
ψ
プサイ
ω
オメガ
次の表に、<say-as> の一般的な測定単位を示します。
フォーマット
カテゴリ
英語の例
略語
長さ
nm (ナノメートル), μm (マイクロメートル), mm (ミリメートル), cm (センチメートル), m (メートル), km (キロメートル), ft (フィート), in (インチ)
面積
cm² (平方センチメートル), m² (平方メートル), km² (平方キロメートル), SqFt (平方フィート)
体積
cm³ (立方センチメートル), m³ (立方メートル), km3 (立方キロメートル), mL (ミリリットル), L (リットル), gal (ガロン)
重量
μg (マイクログラム), mg (ミリグラム), g (グラム), kg (キログラム)
時間
min (分), sec (秒), ms (ミリ秒)
電磁気
μA (マイクロアンペア), mA (ミリアンペア), Hz (ヘルツ), kHz (キロヘルツ), MHz (メガヘルツ), GHz (ギガヘルツ), V (ボルト), kV (キロボルト), kWh (キロワット時)
音
dB (デシベル)
気圧
Pa (パスカル), kPa (キロパスカル), MPa (メガパスカル)
その他の一般的な単位
上記のカテゴリに限定されない測定単位をサポートします。例:tsp (ティースプーン)、rpm (毎分回転数)、KB (キロバイト)、mmHg (水銀柱ミリメートル)。
関係
<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>