Skip to main content

Qwen-TTS リアルタイム音声合成の WebSocket API

WebSocket を使用して Qwen-TTS リアルタイム音声合成サービスに接続します。本ドキュメントでは、サービスエンドポイント、リクエストヘッダー、インタラクションフローについて説明します。

モデルの概要と選択ガイドについては、「リアルタイム音声合成」をご参照ください。 Qwen-TTS Realtime API は WebSocket プロトコルを使用します。Java および Python アプリケーションは、DashScope SDK を使用することで WebSocket 処理をスキップできます。その他の言語では、WebSocket ライブラリを使用して直接接続できます。

サービスエンドポイント

以下の URL を使用します。model クエリパラメーターでモデルを指定します。
  • Singapore
  • China (Beijing)
WebSocket URL:wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime
URL は wss:// プロトコルを使用する必要があります。Authorization ヘッダーを設定し (「リクエストヘッダー」をご参照ください)、model クエリパラメーターを使用してモデルを指定します。

リクエストヘッダー

以下のリクエストヘッダーを設定します。

パラメーター

型

必須

説明

Authorization

string

はい

形式:Bearer <your_api_key>。<your_api_key> を API キーに置き換えます。

user-agent

string

いいえ

サーバー側のソース追跡用のクライアント識別子です。

X-DashScope-WorkSpace

string

いいえ

Alibaba Cloud Model Studio の ワークスペース ID です。

Authorization は WebSocket ハンドシェイク時に検証されます。API キーが無効またはない場合、ハンドシェイクは HTTP 401/403 で失敗します。

インタラクションフロー

クライアントイベントとサーバーイベントの詳細については、「クライアントイベント」および「サーバーイベント」をご参照ください。 API は 2 つのモードをサポートします。
  • ServerCommit モード:サーバーがテキストセグメンテーションと合成タイミングを自動的に決定します。
  • Commit モード:クライアントが各テキストセグメントを送信するタイミングを制御します。合成をトリガーするには、input_text_buffer.commit を明示的に呼び出す必要があります。

モードの詳細

  • ServerCommit モードでは、input_text_buffer.append を呼び出してテキストをバッファリングします。システムは合成タイミングを自動的に決定します。
  • ServerCommit モードで input_text_buffer.commit を呼び出すと、現在のバッファが即座に合成されます。その後、セッションは ServerCommit モードに戻ります。
  • Commit モードでは、input_text_buffer.append のみでは合成はトリガーされません。合成を開始するには、input_text_buffer.commit を呼び出す必要があります。
qwen-tts
主な手順:
  1. 接続:クライアントが WebSocket 接続を開きます。サーバーは準備が完了すると session.created を返します。
  2. セッション設定:クライアントは session.update を送信して、音声、フォーマット、モードを設定します。
  3. テキスト入力:クライアントは input_text_buffer.append を送信して、テキストをバッファに追加します。
  4. 合成トリガー:
    • ServerCommit モードでは、合成が自動的に開始されます。クライアントは input_text_buffer.commit を呼び出して、即座に合成を強制できます。
    • Commit モードでは、input_text_buffer.commit のみが合成をトリガーします。
  5. 音声生成:サーバーは response.created を送信し、response.audio.done が送信されるまで、response.audio.delta イベントとして base64 エンコードされた音声をストリーミングします。
  6. セッションの終了:クライアントは session.finish を送信してリソースを解放します。サーバーは session.finished を返し、接続を閉じます。
接続後、サーバーは以下の session.created イベントを返します。
{
    "event_id": "event_xxx",
    "type": "session.created",
    "session": {
        "object": "realtime.session",
        "mode": "server_commit",
        "model": "qwen3-tts-flash-realtime",
        "voice": "Cherry",
        "response_format": "pcm",
        "sample_rate": 24000,
        "id": "sess_xxx"
    }
}

コード例

Python と Java のクライアント、および 2 種類のコミットモードの例です。エンドポイントのリージョンに対応する DASHSCOPE_API_KEY を設定してください。Python は websockets と pyaudio、Java は Java-WebSocket と Gson を必要とし、Java Sound で再生します。各クライアントイベントに一意の UUID を使用します。
Java
<dependencies>
    <dependency>
        <groupId>org.java-websocket</groupId>
        <artifactId>Java-WebSocket</artifactId>
        <version>1.5.3</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>
</dependencies>
  1. クライアントの作成
    • Java
    • Python
    TTSRealtimeClient.java という名前の Java ファイルを作成し、以下のコードをファイルにコピーします:
    import com.google.gson.Gson;
    import com.google.gson.JsonObject;
    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.handshake.ServerHandshake;
    
    import java.net.URI;
    import java.util.Base64;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.CountDownLatch;
    import java.util.function.Consumer;
    
    /**
     * TTS リアルタイム API とやり取りするためのクライアント。
     *
     * このクラスは、TTS リアルタイム API への接続、テキストデータの送信、オーディオ出力の取得、WebSocket 接続の管理を行うメソッドを提供します。
     */
    public class TTSRealtimeClient {
    
        public enum SessionMode {
            SERVER_COMMIT("server_commit"),
            COMMIT("commit");
            private final String value;
            SessionMode(String value) { this.value = value; }
            public String getValue() { return value; }
        }
    
        /**
         * オーディオコールバックインターフェイス
         */
        public interface AudioCallback {
            void onAudio(byte[] audioData);
        }
    
        private final String baseUrl;
        private final String apiKey;
        private final String voice;
        private final SessionMode mode;
        private final String languageType;
        private final AudioCallback audioCallback;
        private final Gson gson = new Gson();
    
        private WebSocketClient ws;
        private CountDownLatch responseDoneLatch;
        private CountDownLatch sessionFinishedLatch;
    
        public TTSRealtimeClient(String baseUrl, String apiKey, String voice,
                                 SessionMode mode, AudioCallback audioCallback,
                                 String languageType) {
            this.baseUrl = baseUrl;
            this.apiKey = apiKey;
            this.voice = voice;
            this.mode = mode;
            this.audioCallback = audioCallback;
            this.languageType = languageType;
        }
    
        public TTSRealtimeClient(String baseUrl, String apiKey, String voice,
                                 SessionMode mode, AudioCallback audioCallback) {
            this(baseUrl, apiKey, voice, mode, audioCallback, "Auto");
        }
    
        /**
         * TTS リアルタイム API への WebSocket 接続を確立します。
         */
        public void connect() throws Exception {
            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "Bearer " + apiKey);
    
            responseDoneLatch = new CountDownLatch(0);
            sessionFinishedLatch = new CountDownLatch(1);
    
            ws = new WebSocketClient(new URI(baseUrl), headers) {
                @Override
                public void onOpen(ServerHandshake handshake) {
                    System.out.println("WebSocket connection established");
                    // デフォルトのセッション構成を送信します
                    JsonObject session = new JsonObject();
                    session.addProperty("mode", mode.getValue());
                    session.addProperty("voice", TTSRealtimeClient.this.voice);
                    session.addProperty("language_type", languageType);
                    session.addProperty("response_format", "pcm");
                    session.addProperty("sample_rate", 24000);
                    updateSession(session);
                }
    
                @Override
                public void onMessage(String message) {
                    JsonObject event = gson.fromJson(message, JsonObject.class);
                    String eventType = event.has("type") ? event.get("type").getAsString() : "";
    
                    if (!"response.audio.delta".equals(eventType)) {
                        System.out.println("Received event: " + eventType);
                    }
    
                    switch (eventType) {
                        case "error":
                            System.err.println("Error: " + event.get("error"));
                            break;
                        case "session.created":
                            System.out.println("Session created, ID: " +
                                event.getAsJsonObject("session").get("id").getAsString());
                            break;
                        case "session.updated":
                            System.out.println("Session updated, ID: " +
                                event.getAsJsonObject("session").get("id").getAsString());
                            break;
                        case "input_text_buffer.committed":
                            System.out.println("Text buffer committed, item ID: " + event.get("item_id"));
                            break;
                        case "input_text_buffer.cleared":
                            System.out.println("Text buffer cleared");
                            break;
                        case "response.created":
                            System.out.println("Response created, ID: " +
                                event.getAsJsonObject("response").get("id").getAsString());
                            responseDoneLatch = new CountDownLatch(1);
                            break;
                        case "response.output_item.added":
                            System.out.println("Output item added, ID: " +
                                event.getAsJsonObject("item").get("id").getAsString());
                            break;
                        case "response.audio.delta":
                            if (audioCallback != null) {
                                byte[] audioBytes = Base64.getDecoder().decode(
                                    event.get("delta").getAsString());
                                audioCallback.onAudio(audioBytes);
                            }
                            break;
                        case "response.audio.done":
                            System.out.println("Audio generation completed");
                            break;
                        case "response.done":
                            System.out.println("Response completed");
                            responseDoneLatch.countDown();
                            break;
                        case "session.finished":
                            System.out.println("Session finished");
                            sessionFinishedLatch.countDown();
                            break;
                    }
                }
    
                @Override
                public void onClose(int code, String reason, boolean remote) {
                    System.out.println("Connection closed: " + reason);
                }
    
                @Override
                public void onError(Exception ex) {
                    System.err.println("WebSocket error: " + ex.getMessage());
                }
            };
            ws.connectBlocking();
        }
    
        /**
         * サーバーにイベントを送信します。
         */
        public void sendEvent(JsonObject event) {
            String eventId = "event_" + java.util.UUID.randomUUID().toString();
            event.addProperty("event_id", eventId);
            System.out.println("Sending event: type=" + event.get("type").getAsString()
                + ", event_id=" + eventId);
            ws.send(gson.toJson(event));
        }
    
        /**
         * セッション構成を更新します。
         */
        public void updateSession(JsonObject config) {
            JsonObject event = new JsonObject();
            event.addProperty("type", "session.update");
            event.add("session", config);
            System.out.println("Updating session configuration: " + event);
            sendEvent(event);
        }
    
        /**
         * API にテキストデータを送信します。
         */
        public void appendText(String text) {
            JsonObject event = new JsonObject();
            event.addProperty("type", "input_text_buffer.append");
            event.addProperty("text", text);
            sendEvent(event);
        }
    
        /**
         * テキストバッファーをコミットして処理をトリガーします。
         */
        public void commitTextBuffer() {
            JsonObject event = new JsonObject();
            event.addProperty("type", "input_text_buffer.commit");
            sendEvent(event);
        }
    
        /**
         * テキストバッファーをクリアします。
         */
        public void clearTextBuffer() {
            JsonObject event = new JsonObject();
            event.addProperty("type", "input_text_buffer.clear");
            sendEvent(event);
        }
    
        /**
         * セッションを終了します。
         */
        public void finishSession() {
            JsonObject event = new JsonObject();
            event.addProperty("type", "session.finish");
            sendEvent(event);
        }
    
        /**
         * response.done イベントを待ちます。
         */
        public void waitForResponseDone() throws InterruptedException {
            responseDoneLatch.await();
        }
    
        /**
         * session.finished イベントを待ちます。
         */
        public void waitForSessionFinished() throws InterruptedException {
            sessionFinishedLatch.await();
        }
    
        /**
         * WebSocket 接続をクローズします。
         */
        public void close() {
            if (ws != null) {
                ws.close();
            }
        }
    }
    
  2. 音声合成モードの選択 リアルタイム API は 2 つのモードをサポートしています:
    • サーバーコミットモード サーバーがテキスト分割と合成タイミングを自動的に処理します。クライアントはテキストを送信するだけです。低遅延が求められるシナリオ(GPS ナビゲーションなど)に適しています。
    • コミットモード クライアントがバッファーにテキストを追加し、明示的に合成をトリガーします。ニュース放送など、文の分割制御を正確に行う必要があるシナリオに適しています。
    • サーバーコミットモード
    • コミットモード
    • Java
    • Python
    TTSRealtimeClient.java と同じディレクトリに、ServerCommit.java という名前の別の Java ファイルを作成し、以下のコードをファイルにコピーします:
    import javax.sound.sampled.*;
    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    public class ServerCommit {
        // 以下はシンガポールリージョンの構成です。
        private static final String URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime";
        // API キーはシンガポールリージョンと中国 (北京) リージョンで異なります。API キーの取得: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
        // 環境変数を設定していない場合は、次の行を中国 Model Studio API キーに置き換えてください: private static final String API_KEY = "sk-xxx";
        private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY");
        private static final int SAMPLE_RATE = 24000;
    
        // オーディオデータキャッシュ
        private static final List<byte[]> audioChunks = new ArrayList<>();
        // リアルタイム再生キュー
        private static final ConcurrentLinkedQueue<byte[]> playbackQueue = new ConcurrentLinkedQueue<>();
        private static final AtomicBoolean playing = new AtomicBoolean(true);
    
        public static void main(String[] args) throws Exception {
            if (API_KEY == null || API_KEY.isEmpty()) {
                throw new IllegalStateException("Please set the DASHSCOPE_API_KEY environment variable");
            }
    
            // オーディオ再生を初期化します
            AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false);
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
            SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info);
            audioLine.open(format);
            audioLine.start();
    
            // 再生スレッドを開始します
            Thread playerThread = new Thread(() -> {
                while (playing.get() || !playbackQueue.isEmpty()) {
                    byte[] chunk = playbackQueue.poll();
                    if (chunk != null) {
                        audioLine.write(chunk, 0, chunk.length);
                    } else {
                        try { Thread.sleep(10); } catch (InterruptedException ignored) {}
                    }
                }
            });
            playerThread.start();
    
            // TTS クライアントを作成します
            TTSRealtimeClient client = new TTSRealtimeClient(
                URL, API_KEY, "Cherry",
                TTSRealtimeClient.SessionMode.SERVER_COMMIT,
                audioData -> {
                    playbackQueue.add(audioData);
                    audioChunks.add(audioData);
                    System.out.println("Received audio data: " + audioData.length + " bytes");
                }
            );
    
            client.connect();
    
            // テキストフラグメントを送信します
            String[] textFragments = {
                "Alibaba Cloud の大規模言語モデルプラットフォームである Model Studio は、大規模言語モデルアプリケーションを開発・構築するためのオールインワンプラットフォームです。",
                "開発者とビジネスユーザーの両方が、大規模言語モデルアプリケーションの設計と開発に深く関与できます。",
                "シンプルなインターフェイスを使用して、5 分で大規模言語モデルアプリケーションを開発できます。",
                "または、数時間で専用モデルをトレーニングでき、アプリケーションの革新により多くのエネルギーを注げます。"
            };
    
            System.out.println("Starting to send text...");
            for (String text : textFragments) {
                System.out.println("Sending fragment: " + text);
                client.appendText(text);
                Thread.sleep(100);
            }
    
            Thread.sleep(1000);
            client.finishSession();
    
            // 応答の完了を待ちます
            client.waitForResponseDone();
            client.waitForSessionFinished();
            client.close();
    
            // 再生の完了を待ちます
            playing.set(false);
            playerThread.join();
            audioLine.drain();
            audioLine.close();
    
            // オーディオファイルを保存します
            saveWav("output.wav");
            System.out.println("Done");
        }
    
        private static void saveWav(String filename) throws IOException {
            if (audioChunks.isEmpty()) {
                System.out.println("No audio data to save");
                return;
            }
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            for (byte[] chunk : audioChunks) {
                bos.write(chunk);
            }
            byte[] allAudio = bos.toByteArray();
            AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false);
            AudioInputStream ais = new AudioInputStream(
                new ByteArrayInputStream(allAudio), format, allAudio.length / 2);
            new File("outputs").mkdirs();
            AudioSystem.write(ais, AudioFileFormat.Type.WAVE,
                new File("outputs/" + filename));
            System.out.println("Audio saved to: outputs/" + filename);
        }
    }
    
    ServerCommit.java をコンパイルして実行し、リアルタイム API によって生成されたオーディオをリアルタイムで聞きます。