Skip to main content

Qwen-Audio-ASR-Streaming WebSocket 接続ガイド

このトピックでは、WebSocket 接続を介して Qwen-Audio-ASR-Streaming リアルタイム音声認識サービスにアクセスするためのサービスエンドポイント、リクエストヘッダー、インタラクションフローについて説明します。

サービスエンドポイント

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 へ

リクエストヘッダー

リクエストヘッダーに次の情報を追加します:
パラメータータイプ必須説明
AuthorizationstringはいBearer <your_api_key> 形式の認証トークン。<your_api_key> を実際の API キーに置き換えてください。
user-agentstringいいえサーバーがリクエスト元を追跡するのに役立つクライアント識別子です。
X-DashScope-WorkSpacestringいいえModel Studio の ワークスペース ID です。
X-DashScope-DataInspectionstringいいえデータコンプライアンス検査を有効にするかどうかを指定します。このパラメーターは、省略した場合、デフォルトで enable に設定されます。必要がない限り、このパラメーターは有効にしないでください。
Authorization は WebSocket ハンドシェイク時に検証されます。API キーが無効または設定されていない場合、ハンドシェイクは失敗し、HTTP 401 または 403 エラーが返されます。

インタラクションフロー

クライアントイベントとサーバーサイドイベントの詳細については、「クライアントイベント」および「サーバーサイドイベント」をご参照ください。
WebSocket speech recognition sequence
クライアントとサーバーは、次の順序でやり取りします:
  1. 接続を確立します:クライアントはサーバーへの WebSocket 接続を確立します。
  2. タスクを開始します:クライアントは run-task 命令を送信してタスクを開始し、サーバーから task-started イベントを受信します。このイベントは、タスクが開始され、後続の手順に進めることを確認します。
  3. オーディオストリームを送信します:クライアントはバイナリオーディオ (モノラルである必要があります) の送信を開始し、同時にサーバーから result-generated イベントのストリームを受信します。各イベントには音声認識結果が含まれます。
  4. タスク終了をサーバーに通知します:クライアントは finish-task 命令を送信してタスク終了をサーバーに通知し、その後もサーバーから result-generated イベントを受信し続けます。
  5. タスクを終了します:クライアントはサーバーから task-finished イベントを受信します。これはタスクが終了したことを示します。
  6. 接続を閉じます:クライアントは WebSocket 接続を閉じます。

呼び出し例

以下の例では qwen-audio-3.1-asr-flash-streaming を使用します。コンソールで API キーと Base URL を取得し、環境変数 DASHSCOPE_API_KEY と DASHSCOPE_BASE_URL に設定します。 サンプル音声を asr_example.wav として保存してください。16 kHz、モノラル、16-bit PCM エンコードの WAV ファイルです。
  • Java
  • Python
  • Go
  • Node.js
  • C#
  • PHP
例を実行する前に、Java-WebSocket 依存関係をインストールしてください:
Maven
<dependency>
    <groupId>org.java-websocket</groupId>
    <artifactId>Java-WebSocket</artifactId>
    <version>1.5.6</version>
</dependency>
<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20240303</version>
</dependency>
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONObject;

import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

public class FunASRRealtimeClient {

    private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY");
    private static final String URL = "wss://" + URI.create(System.getenv("DASHSCOPE_BASE_URL")).getHost() + "/api-ws/v1/inference";
    private static final String AUDIO_FILE = "asr_example.wav"; // Replace with the path to your audio file
    private static final String MODEL = "qwen-audio-3.1-asr-flash-streaming";

    // Generate a 32-character random ID
    private static final String TASK_ID = UUID.randomUUID().toString().replace("-", "").substring(0, 32);

    private static final AtomicBoolean taskStarted = new AtomicBoolean(false);
    private static WebSocketClient client;

    public static void main(String[] args) throws Exception {
        client = new WebSocketClient(new URI(URL)) {
            @Override
            public void onOpen(ServerHandshake handshake) {
                System.out.println("Connected to server");
                sendRunTask();
            }

            @Override
            public void onMessage(String data) {
                JSONObject message = new JSONObject(data);
                String event = message.getJSONObject("header").getString("event");
                switch (event) {
                    case "task-started":
                        System.out.println("Task started");
                        taskStarted.set(true);
                        new Thread(FunASRRealtimeClient::sendAudioStream).start();
                        break;
                    case "result-generated":
                        JSONObject payload = message.getJSONObject("payload");
                        String text = payload.getJSONObject("output").getJSONObject("sentence").getString("text");
                        System.out.println("Recognition result: " + text);
                        if (payload.has("usage")) {
                            System.out.println("Audio duration (seconds): " + payload.getJSONObject("usage").get("duration"));
                        }
                        break;
                    case "task-finished":
                        System.out.println("Task finished");
                        close();
                        break;
                    case "task-failed":
                        String errMsg = message.getJSONObject("header").optString("error_message");
                        System.err.println("Task failed: " + errMsg);
                        close();
                        break;
                    default:
                        System.out.println("Unknown event: " + event);
                }
            }

            @Override
            public void onClose(int code, String reason, boolean remote) {
                if (!taskStarted.get()) {
                    System.err.println("Task not started, closing connection");
                }
            }

            @Override
            public void onError(Exception ex) {
                System.err.println("WebSocket error: " + ex.getMessage());
            }
        };
        client.addHeader("Authorization", "bearer " + API_KEY);
        client.connectBlocking();
    }

    // Send the run-task instruction
    private static void sendRunTask() {
        JSONObject runTask = new JSONObject()
                .put("header", new JSONObject()
                        .put("action", "run-task")
                        .put("task_id", TASK_ID)
                        .put("streaming", "duplex"))
                .put("payload", new JSONObject()
                        .put("task_group", "audio")
                        .put("task", "asr")
                        .put("function", "recognition")
                        .put("model", MODEL)
                        .put("parameters", new JSONObject()
                                .put("sample_rate", 16000)
                                .put("format", "wav"))
                        .put("input", new JSONObject()));
        client.send(runTask.toString());
    }

    // Send the audio stream (send one binary chunk every 100ms)
    private static void sendAudioStream() {
        int chunkSize = 3200; // 100ms @ 16kHz 16bit mono
        try {
            byte[] audio = Files.readAllBytes(Paths.get(AUDIO_FILE));
            int offset = 0;
            while (offset < audio.length) {
                int end = Math.min(offset + chunkSize, audio.length);
                byte[] chunk = new byte[end - offset];
                System.arraycopy(audio, offset, chunk, 0, end - offset);
                client.send(ByteBuffer.wrap(chunk));
                offset = end;
                Thread.sleep(100);
            }
            System.out.println("Audio stream ended");
            sendFinishTask();
        } catch (Exception e) {
            System.err.println("Error reading audio file: " + e.getMessage());
            client.close();
        }
    }

    // Send the finish-task instruction
    private static void sendFinishTask() {
        JSONObject finishTask = new JSONObject()
                .put("header", new JSONObject()
                        .put("action", "finish-task")
                        .put("task_id", TASK_ID)
                        .put("streaming", "duplex"))
                .put("payload", new JSONObject()
                        .put("input", new JSONObject()));
        client.send(finishTask.toString());
    }
}