Skip to main content

CosyVoice 音声合成 WebSocket API

WebSocket 接続を介して、CosyVoice のリアルタイム音声合成サービスにアクセスします。このドキュメントでは、サービスエンドポイント、リクエストヘッダー、およびクライアントとサーバー間のインタラクションワークフローについて説明します。 レート制限:モデルの呼び出しにはレート制限が適用されます。制限を超えると、サーバーはエラー Requests rate limit exceeded, please try again later. を返します。リクエストレートまたは同時実行数を減らしてリトライしてください。各モデルのレート制限については、「レート制限」をご参照ください。

サービスエンドポイント

固定の WebSocket URL:
  • シンガポール
  • 中国 (北京)
wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference{WorkspaceId} を実際のワークスペース ID に置き換えてください。
Alibaba Cloud Model Studio は、中国 (北京) およびシンガポールリージョン向けにワークスペース固有のドメインをリリースしました。新しい専用ドメインは、推論リクエストに対して優れたパフォーマンスと高い安定性を提供します。新しいドメインへの移行を推奨します:
  • 中国 (北京):dashscope.aliyuncs.com から {WorkspaceId}.cn-beijing.maas.aliyuncs.com へ
  • シンガポール:dashscope-intl.aliyuncs.com から {WorkspaceId}.ap-southeast-1.maas.aliyuncs.com へ

リクエストヘッダー

リクエストに次のヘッダーを含めてください:
パラメータータイプ必須説明
Authorization文字列はいフォーマット:Bearer <API key>。<API key> をご自身の API キーに置き換えてください。
user-agent文字列いいえリクエスト追跡用のクライアント識別子。
X-DashScope-WorkSpace文字列いいえAlibaba Cloud Model Studio のワークスペース ID。
X-DashScope-DataInspection文字列いいえデータインスペクションを有効にします。デフォルト:enable。特に必要がない限り、設定しないでください。
権限付与は WebSocket ハンドシェイク中に検証されます。API キーが無効または欠落している場合、ハンドシェイクは HTTP 401/403 エラーで失敗します。

インタラクションワークフロー

クライアント側およびサーバー側のイベントの詳細については、「クライアントイベント」と「サーバー側イベント」をご参照ください。 クライアントとサーバーのインタラクションシーケンス:
  1. 接続の確立:サーバーへの WebSocket 接続を作成します。
  2. タスクの開始:run-task イベントを送信します。
  3. 確認の待機:次に進む前に、サーバーから task-started イベントを受信します。
  4. 合成用テキストの送信: 合成するテキストを含む continue-task イベントを、順番に 1 つ以上送信します。サーバーは、完全な文ごとに音声ストリームを含む result-generated イベントを返します。テキスト長の制限については、continue-task イベントの text フィールドをご参照ください。
    テキストセグメントを含む複数の continue-task イベントを順番に送信します。サーバーはテキストを文に分割します:
    • 完全な文は直ちに音声に合成され、返されます。
    • 不完全な文は、完了するまでバッファリングされます。
    finish-task イベントにより、バッファリングされたすべてのコンテンツが強制的に合成されます。
  5. 音声の受信:binary チャンネルから音声ストリームを読み取ります。
  6. タスクの終了: すべてのテキストを送信した後、finish-task イベントを送信し、音声の受信を続けます。このステップは必須です。スキップすると、不完全な音声が生成される可能性があります。
  7. タスク完了の受信: サーバーからの task-finished イベントにより、タスクが完了したことが確認されます。
  8. 接続の終了:WebSocket 接続を切断します。
タスクごとに新しい接続を作成するのではなく、複数のタスクで WebSocket 接続を再利用してください。
1 つの合成タスク内のすべてのイベント (run-task、continue-task、finish-task) は、同じ task_id を共有する必要があります。タスクごとに新しい task_id (UUID など) を生成してください。値が一致しない場合、音声の破損やタスクの失敗を引き起こす可能性があります。

コード例

次の例は汎用 WebSocket ライブラリでイベントを直接送信し、cosyvoice-v3-flash と音声 longanyang を使用します。API キーを取得して環境変数 DASHSCOPE_API_KEY に設定し、{WorkspaceId} を対象リージョンのワークスペース ID に置き換えてください。リージョンを切り替える際は、エンドポイントと API キーの両方を変更します。
  • Java
  • Python
  • Go
  • C#
  • PHP
  • Node.js
開発には Java DashScope SDK の使用を推奨します。「CosyVoice 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>
Java コード:
import com.fasterxml.jackson.databind.ObjectMapper;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.*;

public class TTSWebSocketClient extends WebSocketClient {
    private final String taskId = UUID.randomUUID().toString();
    private final String outputFile = "output_" + System.currentTimeMillis() + ".mp3";
    private boolean taskFinished = false;

    public TTSWebSocketClient(URI serverUri, Map<String, String> headers) {
        super(serverUri, headers);
    }

    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        System.out.println("Connection established");

        // 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\": false }, \"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");

                        List<String> texts = Arrays.asList(
                                "Before my bed, moonlight shines bright,I suspect it\'s frost upon the ground.",
                                "I raise my eyes to gaze at the bright moon,then bow my head, thinking of home."
                        );

                        for (String text : texts) {
                            // continue-task イベントを送信します
                            sendContinueTask(text);
                        }

                        // 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("Exception occurred: " + e.getMessage());
        }
    }

    @Override
    public void onMessage(ByteBuffer message) {
        System.out.println("Received binary audio data of size: " + message.remaining());

        try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
            byte[] buffer = new byte[message.remaining()];
            message.get(buffer);
            fos.write(buffer);
            System.out.println("Audio data written to local file " + outputFile);
        } catch (IOException e) {
            System.err.println("Failed to write audio data to local file: " + e.getMessage());
        }
    }

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

    @Override
    public void onError(Exception ex) {
        System.err.println("Error: " + ex.getMessage());
        ex.printStackTrace();
    }

    private void sendContinueTask(String text) {
        String command = "{ \"header\": { \"action\": \"continue-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": { \"text\": \"" + text + "\" } }}";
        send(command);
    }

    private void sendFinishTask() {
        String command = "{ \"header\": { \"action\": \"finish-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": {} }}";
        send(command);
    }

    private void closeConnection() {
        if (!isClosed()) {
            close();
        }
    }

    public static void main(String[] args) {
        try {
            // API キーはシンガポールリージョンと中国 (北京) リージョンで異なります。API キーの取得: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
            // 環境変数を設定していない場合は、次の行を中国 Model Studio API キーに置き換えてください: String apiKey = "sk-xxx"
            String apiKey = System.getenv("DASHSCOPE_API_KEY");
            if (apiKey == null || apiKey.isEmpty()) {
                System.err.println("Please set the DASHSCOPE_API_KEY environment variable");
                return;
            }

            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "bearer " + apiKey);
            // 以下はシンガポールリージョンの構成です。{WorkspaceId} を実際のワークスペース ID に置き換えてください。リージョンによって構成は異なります。
            TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference"), headers);

            client.connect();

            while (!client.isClosed() && !client.taskFinished) {
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            System.err.println("Failed to connect to WebSocket service: " + e.getMessage());
            e.printStackTrace();
        }
    }
}