すべてのプロダクト
Search
ドキュメントセンター

Alibaba Cloud Model Studio:リアルタイム音声認識 - Qwen

最終更新日:Dec 14, 2025

ライブストリーミング、オンライン会議、音声チャット、スマートアシスタントなどのシナリオでは、連続したオーディオストリームをリアルタイムでテキストに変換する必要があります。これにより、即時の字幕を提供したり、会議の議事録を生成したり、音声コマンドに応答したりできます。Qwen リアルタイム音声認識サービスは、WebSocket プロトコルを介してリアルタイムでオーディオストリームを受信し、文字起こしを行います。

対応モデル

このモデルは、多言語認識やノイズ除去などの特徴をサポートしています。以下の利点があります:

  • 高精度な多言語認識:標準中国語や、広東語、四川語などのさまざまな中国語の方言を含む、複数の言語を高精度で認識します。

  • 複雑な環境への適応:複雑な音響環境に適応し、自動言語検出と非人声のインテリジェントなフィルタリングをサポートします。

  • コンテキスト強調:コンテキスト構成を使用して認識精度を向上させます。

  • 感情認識:驚き、平静、喜び、悲しみ、嫌悪、怒り、恐怖など、複数の感情状態を高精度で認識します。

国際 (シンガポール)

モデル

バージョン

対応言語

対応サンプルレート

単価

無料クォータ (注)

qwen3-asr-flash-realtime

qwen3-asr-flash-realtime-2025-10-27 と同等です。

安定版

中国語 (標準中国語、四川語、閩南語、呉語)、広東語、英語、日本語、ドイツ語、韓国語、ロシア語、フランス語、ポルトガル語、アラビア語、イタリア語、スペイン語、ヒンディー語、インドネシア語、タイ語、トルコ語、ウクライナ語、ベトナム語

8 kHz、16 kHz

$0.000090/秒

36,000 秒 (10 時間)

Model Studio を有効化してから 90 日間有効です。

qwen3-asr-flash-realtime-2025-10-27

スナップショット

中国 (北京)

モデル

バージョン

対応言語

対応サンプルレート

単価

qwen3-asr-flash-realtime

このモデルは qwen3-asr-flash-realtime-2025-10-27 のエイリアスです。

安定版

中国語 (標準中国語、四川語、閩南語、呉語)、広東語、英語、日本語、ドイツ語、韓国語、ロシア語、フランス語、ポルトガル語、アラビア語、イタリア語、スペイン語、ヒンディー語、インドネシア語、タイ語、トルコ語、ウクライナ語、ベトナム語

8 kHz、16 kHz

$0.000047/秒

qwen3-asr-flash-realtime-2025-10-27

スナップショット

特徴

特徴

説明

接続タイプ

Java/Python SDK、WebSocket API

多言語

中国語 (標準中国語、四川語、閩南語、呉語)、広東語、英語、日本語、ドイツ語、韓国語、ロシア語、フランス語、ポルトガル語、アラビア語、イタリア語、スペイン語、ヒンディー語、インドネシア語、タイ語、トルコ語、ウクライナ語、ベトナム語

言語検出

感情認識

認識言語の指定

✅ 音声の言語がわかっている場合は、リクエストパラメーター language を使用して指定することで、認識精度を向上させることができます。

句読点予測

ストリーミング入出力

VAD (音声アクティビティ検出)

コンテキスト強調

話者分離

音声入力方式

バイナリオーディオストリーム

検出用音声フォーマット

pcm、opus

検出用音声チャンネル

モノラル

検出用音声サンプルレート

8000 Hz、16000 Hz

はじめに

DashScope SDK の使用

Java

  1. SDK のインストール。DashScope SDK のバージョンが 2.21.14 以降であることを確認してください。

  2. API キーの取得。コードにハードコーディングすることを避けるため、API キーを環境変数として設定します。

  3. サンプルコードの実行。

    import com.alibaba.dashscope.audio.omni.*;
    import com.alibaba.dashscope.exception.NoApiKeyException;
    import com.google.gson.JsonObject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.sound.sampled.LineUnavailableException;
    import java.io.File;
    import java.io.FileInputStream;
    import java.util.Base64;
    import java.util.Collections;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.atomic.AtomicReference;
    
    public class Qwen3AsrRealtimeUsage {
        private static final Logger log = LoggerFactory.getLogger(Qwen3AsrRealtimeUsage.class);
        private static final int AUDIO_CHUNK_SIZE = 1024; // オーディオチャンクサイズ (バイト)
        private static final int SLEEP_INTERVAL_MS = 30;  // スリープ間隔 (ミリ秒)
    
        public static void main(String[] args) throws InterruptedException, LineUnavailableException {
            CountDownLatch finishLatch = new CountDownLatch(1);
    
            OmniRealtimeParam param = OmniRealtimeParam.builder()
                    .model("qwen3-asr-flash-realtime")
                    // 次の URL はシンガポールリージョン用です。北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
                    .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                    // シンガポールリージョンと北京リージョンの API キーは異なります。API キーを取得するには、https://www.alibabacloud.com/help/ja/model-studio/get-api-key をご参照ください。
                    // 環境変数を設定していない場合は、次の行をご利用の Model Studio API キーに置き換えてください:.apikey("sk-xxx")
                    .apikey(System.getenv("DASHSCOPE_API_KEY"))
                    .build();
    
            OmniRealtimeConversation conversation = null;
            final AtomicReference<OmniRealtimeConversation> conversationRef = new AtomicReference<>(null);
            conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() {
                @Override
                public void onOpen() {
                    System.out.println("connection opened");
                }
                @Override
                public void onEvent(JsonObject message) {
                    String type = message.get("type").getAsString();
                    switch(type) {
                        case "session.created":
                            System.out.println("start session: " + message.get("session").getAsJsonObject().get("id").getAsString());
                            break;
                        case "conversation.item.input_audio_transcription.completed":
                            System.out.println("transcription: " + message.get("transcript").getAsString());
                            finishLatch.countDown();
                            break;
                        case "input_audio_buffer.speech_started":
                            System.out.println("======VAD Speech Start======");
                            break;
                        case "input_audio_buffer.speech_stopped":
                            System.out.println("======VAD Speech Stop======");
                            break;
                        case "conversation.item.input_audio_transcription.text":
                            System.out.println("transcription: " + message.get("text").getAsString());
                            break;
                        default:
                            break;
                    }
                }
                @Override
                public void onClose(int code, String reason) {
                    System.out.println("connection closed code: " + code + ", reason: " + reason);
                }
            });
            conversationRef.set(conversation);
            try {
                conversation.connect();
            } catch (NoApiKeyException e) {
                throw new RuntimeException(e);
            }
    
            OmniRealtimeTranscriptionParam transcriptionParam = new OmniRealtimeTranscriptionParam();
            transcriptionParam.setLanguage("zh");
            transcriptionParam.setInputAudioFormat("pcm");
            transcriptionParam.setInputSampleRate(16000);
            // コーパス、オプション。コーパスがある場合は、認識を強化するために設定します。
            // transcriptionParam.setCorpusText("This is a stand-up comedy show");
    
            OmniRealtimeConfig config = OmniRealtimeConfig.builder()
                    .modalities(Collections.singletonList(OmniRealtimeModality.TEXT))
                    .transcriptionConfig(transcriptionParam)
                    .build();
            conversation.updateSession(config);
    
            String filePath = "your_audio_file.pcm";
            File audioFile = new File(filePath);
            if (!audioFile.exists()) {
                log.error("Audio file not found: {}", filePath);
                return;
            }
    
            try (FileInputStream audioInputStream = new FileInputStream(audioFile)) {
                byte[] audioBuffer = new byte[AUDIO_CHUNK_SIZE];
                int bytesRead;
                int totalBytesRead = 0;
    
                log.info("Starting to send audio data from: {}", filePath);
    
                // オーディオデータをチャンクで読み取り、送信します
                while ((bytesRead = audioInputStream.read(audioBuffer)) != -1) {
                    totalBytesRead += bytesRead;
                    String audioB64 = Base64.getEncoder().encodeToString(audioBuffer);
                    // オーディオチャンクを会話に送信します
                    conversation.appendAudio(audioB64);
    
                    // リアルタイムのオーディオストリーミングをシミュレートするために小さな遅延を追加します
                    Thread.sleep(SLEEP_INTERVAL_MS);
                }
    
                log.info("Finished sending audio data. Total bytes sent: {}", totalBytesRead);
    
                for (int i = 0; i < 30; i++) {
                    byte[] silence = new byte[1024];
                    // 音声ファイルの末尾に無音がないことによる認識失敗を防ぐために、無音音声を追加して送信します。
                    String audioB64 = Base64.getEncoder().encodeToString(silence);
                    conversation.appendAudio(audioB64);
                    Thread.sleep(20);
                }
            } catch (Exception e) {
                log.error("Error sending audio from file: {}", filePath, e);
            }
    
            // enableTurnDetection が false の場合は、次のコード行のコメントを解除してください。
            // conversation.commit();
            finishLatch.await(10, java.util.concurrent.TimeUnit.SECONDS);
            log.info("task finished");
    
            conversation.close(1000, "bye");
            System.exit(0);
        }
    }

Python

  1. SDK のインストール。DashScope SDK のバージョンが 1.25.3 以降であることを確認してください。

  2. API キーの取得。コードにハードコーディングすることを避けるため、API キーを環境変数として設定します。

  3. サンプルコードの実行。

    import logging
    import os
    import base64
    import signal
    import sys
    import time
    import dashscope
    from dashscope.audio.qwen_omni import *
    from dashscope.audio.qwen_omni.omni_realtime import TranscriptionParams
    
    
    def setup_logging():
        """ロギングを設定します。"""
        logger = logging.getLogger('dashscope')
        logger.setLevel(logging.DEBUG)
        handler = logging.StreamHandler(sys.stdout)
        handler.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        logger.propagate = False
        return logger
    
    
    def init_api_key():
        """API キーを初期化します。"""
        # シンガポールリージョンと北京リージョンの API キーは異なります。API キーを取得するには、https://www.alibabacloud.com/help/ja/model-studio/get-api-key をご参照ください。
        # 環境変数を設定していない場合は、次の行をご利用の Model Studio API キーに置き換えてください:dashscope.api_key = "sk-xxx"
        dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY', 'YOUR_API_KEY')
        if dashscope.api_key == 'YOUR_API_KEY':
            print('[Warning] Using placeholder API key, set DASHSCOPE_API_KEY environment variable.')
    
    
    class MyCallback(OmniRealtimeCallback):
        """リアルタイム認識のコールバックを処理します。"""
        def __init__(self, conversation):
            self.conversation = conversation
            self.handlers = {
                'session.created': self._handle_session_created,
                'conversation.item.input_audio_transcription.completed': self._handle_final_text,
                'conversation.item.input_audio_transcription.text': self._handle_stash_text,
                'input_audio_buffer.speech_started': lambda r: print('======Speech Start======'),
                'input_audio_buffer.speech_stopped': lambda r: print('======Speech Stop======')
            }
    
        def on_open(self):
            print('Connection opened')
    
        def on_close(self, code, msg):
            print(f'Connection closed, code: {code}, msg: {msg}')
    
        def on_event(self, response):
            try:
                handler = self.handlers.get(response['type'])
                if handler:
                    handler(response)
            except Exception as e:
                print(f'[Error] {e}')
    
        def _handle_session_created(self, response):
            print(f"Start session: {response['session']['id']}")
    
        def _handle_final_text(self, response):
            print(f"Final recognized text: {response['transcript']}")
    
        def _handle_stash_text(self, response):
            print(f"Got stash result: {response['stash']}")
    
    
    def read_audio_chunks(file_path, chunk_size=3200):
        """音声ファイルをチャンクで読み取ります。"""
        with open(file_path, 'rb') as f:
            while chunk := f.read(chunk_size):
                yield chunk
    
    
    def send_audio(conversation, file_path, delay=0.1):
        """音声データを送信します。"""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"Audio file {file_path} does not exist.")
    
        print("Processing audio file... Press 'Ctrl+C' to stop.")
        for chunk in read_audio_chunks(file_path):
            audio_b64 = base64.b64encode(chunk).decode('ascii')
            conversation.append_audio(audio_b64)
            time.sleep(delay)
        # enable_turn_detection が False の場合は、次のコード行のコメントを解除してください。
        # conversation.commit()
        # print("Audio commit sent.")
    
    def send_silence_data(conversation, cycles=30, bytes_per_cycle=1024):
        # 1024 バイトの無音データ (すべてゼロ) を作成します。
        silence_data = bytes(bytes_per_cycle)
    
        for i in range(cycles):
            # バイトデータを base64 にエンコードします。
            audio_b64 = base64.b64encode(silence_data).decode('ascii')
            # 無音データを送信します。
            conversation.append_audio(audio_b64)
            time.sleep(0.01)  # 10 ms の遅延
        print(f"Sent silent data {cycles} times, {bytes_per_cycle} bytes each time")
    
    def main():
        setup_logging()
        init_api_key()
    
        audio_file_path = "./your_audio_file.pcm"
        conversation = OmniRealtimeConversation(
            model='qwen3-asr-flash-realtime',
            # 次の URL はシンガポールリージョン用です。北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
            url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime',
            callback=MyCallback(conversation=None)  # 一時的に None を渡し、後で注入します。
        )
    
        # コールバックに self を注入します。
        conversation.callback.conversation = conversation
    
        def handle_exit(sig, frame):
            print('Ctrl+C pressed, exiting...')
            conversation.close()
            sys.exit(0)
    
        signal.signal(signal.SIGINT, handle_exit)
    
        conversation.connect()
    
        transcription_params = TranscriptionParams(
            language='zh',
            sample_rate=16000,
            input_audio_format="pcm"
            # 認識を補助するための入力音声のコーパス。
            # corpus_text=""
        )
    
        conversation.update_session(
            output_modalities=[MultiModality.TEXT],
            enable_input_audio_transcription=True,
            transcription_params=transcription_params
        )
    
        try:
            send_audio(conversation, audio_file_path)
            # 音声ファイルの末尾に無音がないことによる認識失敗を防ぐために、無音音声を追加して送信します。
            send_silence_data(conversation)
            time.sleep(3)  # 応答を待ちます。
        except Exception as e:
            print(f"Error occurred: {e}")
        finally:
            conversation.close()
            print("Audio processing completed.")
    
    
    if __name__ == '__main__':
        main()

WebSocket API の使用

以下の例は、ローカルの音声ファイルを送信し、WebSocket 接続を介して認識結果を取得する方法を示しています。

  1. API キーの作成:API キーを作成して設定します。セキュリティのため、API キーを環境変数としてエクスポートしてください。

  2. コードの記述と実行:認証、接続、音声送信、結果受信の完全なプロセスを実装します。詳細については、「対話フロー」をご参照ください。

    Python

    この例を実行する前に、次のコマンドを実行して依存関係がインストールされていることを確認してください:

    pip uninstall websocket-client
    pip uninstall websocket
    pip install websocket-client

    サンプルコードのファイル名を websocket.py にしないでください。そうしないと、次のエラーが発生する可能性があります:AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?

    # pip install websocket-client
    import os
    import time
    import json
    import threading
    import base64
    import websocket
    import logging
    import logging.handlers
    from datetime import datetime
    
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    
    # シンガポールリージョンと北京リージョンの API キーは異なります。API キーを取得するには、https://www.alibabacloud.com/help/ja/model-studio/get-api-key をご参照ください。
    # 環境変数を設定していない場合は、次の行をご利用の Model Studio API キーに置き換えてください:API_KEY="sk-xxx"
    API_KEY = os.environ.get("DASHSCOPE_API_KEY", "sk-xxx")
    QWEN_MODEL = "qwen3-asr-flash-realtime"
    # 次の URL はシンガポールリージョンのベース URL です。北京リージョンのモデルを使用する場合は、ベース URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
    baseUrl = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime"
    url = f"{baseUrl}?model={QWEN_MODEL}"
    print(f"Connecting to server: {url}")
    
    # 注:VAD モードを使用しない場合、連続して送信される音声の合計時間は 60 秒を超えてはなりません。
    enableServerVad = True
    is_running = True  # 実行フラグを追加します。
    
    headers = [
        "Authorization: Bearer " + API_KEY,
        "OpenAI-Beta: realtime=v1"
    ]
    
    def init_logger():
        formatter = logging.Formatter('%(asctime)s|%(levelname)s|%(message)s')
        f_handler = logging.handlers.RotatingFileHandler(
            "omni_tester.log", maxBytes=100 * 1024 * 1024, backupCount=3
        )
        f_handler.setLevel(logging.DEBUG)
        f_handler.setFormatter(formatter)
    
        console = logging.StreamHandler()
        console.setLevel(logging.DEBUG)
        console.setFormatter(formatter)
    
        logger.addHandler(f_handler)
        logger.addHandler(console)
    
    def on_open(ws):
        logger.info("Connected to server.")
    
        # セッション更新イベント。
        event_manual = {
            "event_id": "event_123",
            "type": "session.update",
            "session": {
                "modalities": ["text"],
                "input_audio_format": "pcm",
                "sample_rate": 16000,
                "input_audio_transcription": {
                    # 言語識別子、オプション。明確な言語情報がある場合は設定します。
                    "language": "zh"
                    # コーパス、オプション。コーパスがある場合は、認識を強化するために設定します。
                    # "corpus": {
                    #     "text": ""
                    # }
                },
                "turn_detection": None
            }
        }
        event_vad = {
            "event_id": "event_123",
            "type": "session.update",
            "session": {
                "modalities": ["text"],
                "input_audio_format": "pcm",
                "sample_rate": 16000,
                "input_audio_transcription": {
                    "language": "zh"
                },
                "turn_detection": {
                    "type": "server_vad",
                    "threshold": 0.2,
                    "silence_duration_ms": 800
                }
            }
        }
        if enableServerVad:
            logger.info(f"Sending event: {json.dumps(event_vad, indent=2)}")
            ws.send(json.dumps(event_vad))
        else:
            logger.info(f"Sending event: {json.dumps(event_manual, indent=2)}")
            ws.send(json.dumps(event_manual))
    
    def on_message(ws, message):
        global is_running
        try:
            data = json.loads(message)
            logger.info(f"Received event: {json.dumps(data, ensure_ascii=False, indent=2)}")
            if data.get("type") == "conversation.item.input_audio_transcription.completed":
                logger.info(f"Final transcript: {data.get('transcript')}")
                logger.info("Closing WebSocket connection after completion...")
                is_running = False  # 音声送信スレッドを停止します。
                ws.close()
        except json.JSONDecodeError:
            logger.error(f"Failed to parse message: {message}")
    
    def on_error(ws, error):
        logger.error(f"Error: {error}")
    
    def on_close(ws, close_status_code, close_msg):
        logger.info(f"Connection closed: {close_status_code} - {close_msg}")
    
    def send_audio(ws, local_audio_path):
        time.sleep(3)  # セッションの更新が完了するのを待ちます。
        global is_running
    
        with open(local_audio_path, 'rb') as audio_file:
            logger.info(f"Start reading file: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}")
            while is_running:
                audio_data = audio_file.read(3200)  # ~0.1s PCM16/16kHz
                if not audio_data:
                    logger.info(f"Finished reading file: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}")
                    if not enableServerVad and ws.sock and ws.sock.connected:
                        commit_event = {
                            "event_id": "event_789",
                            "type": "input_audio_buffer.commit"
                        }
                        ws.send(json.dumps(commit_event))
                    break
    
                if not ws.sock or not ws.sock.connected:
                    logger.info("WebSocket is closed. Stop sending audio.")
                    break
    
                encoded_data = base64.b64encode(audio_data).decode('utf-8')
                eventd = {
                    "event_id": f"event_{int(time.time() * 1000)}",
                    "type": "input_audio_buffer.append",
                    "audio": encoded_data
                }
                ws.send(json.dumps(eventd))
                logger.info(f"Sending audio event: {eventd['event_id']}")
                time.sleep(0.1)  # リアルタイム収集をシミュレートします。
    
    # ロガーを初期化します。
    init_logger()
    logger.info(f"Connecting to WebSocket server at {url}...")
    
    local_audio_path = "your_audio_file.pcm"
    ws = websocket.WebSocketApp(
        url,
        header=headers,
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )
    
    thread = threading.Thread(target=send_audio, args=(ws, local_audio_path))
    thread.start()
    ws.run_forever()

    Java

    この例を実行する前に、Java-WebSocket 依存関係がインストールされていることを確認してください:

    Maven

    <dependency>
        <groupId>org.java-websocket</groupId>
        <artifactId>Java-WebSocket</artifactId>
        <version>1.5.6</version>
    </dependency>

    Gradle

    implementation 'org.java-websocket:Java-WebSocket:1.5.6'
    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.handshake.ServerHandshake;
    import org.json.JSONObject;
    
    import java.net.URI;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.Base64;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.logging.*;
    
    public class QwenASRRealtimeClient {
    
        private static final Logger logger = Logger.getLogger(QwenASRRealtimeClient.class.getName());
        // シンガポールリージョンと北京リージョンの API キーは異なります。API キーを取得するには、https://www.alibabacloud.com/help/ja/model-studio/get-api-key をご参照ください。
        // 環境変数を設定していない場合は、次の行をご利用の Model Studio API キーに置き換えてください:private static final String API_KEY = "sk-xxx"
        private static final String API_KEY = System.getenv().getOrDefault("DASHSCOPE_API_KEY", "sk-xxx");
        private static final String MODEL = "qwen3-asr-flash-realtime";
    
        // VAD モードを使用するかどうかを制御します。
        private static final boolean enableServerVad = true;
    
        private static final AtomicBoolean isRunning = new AtomicBoolean(true);
        private static WebSocketClient client;
    
        public static void main(String[] args) throws Exception {
            initLogger();
    
            // 次の URL はシンガポールリージョンのベース URL です。北京リージョンのモデルを使用する場合は、ベース URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
            String baseUrl = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime";
            String url = baseUrl + "?model=" + MODEL;
            logger.info("Connecting to server: " + url);
    
            client = new WebSocketClient(new URI(url)) {
                @Override
                public void onOpen(ServerHandshake handshake) {
                    logger.info("Connected to server.");
                    sendSessionUpdate();
                }
    
                @Override
                public void onMessage(String message) {
                    try {
                        JSONObject data = new JSONObject(message);
                        String eventType = data.optString("type");
    
                        logger.info("Received event: " + data.toString(2));
    
                        // 最終的な認識結果イベントを受信 -> 送信スレッドを停止し、接続を閉じます。
                        if ("conversation.item.input_audio_transcription.completed".equals(eventType)) {
                            logger.info("Final transcript: " + data.optString("transcript"));
                            logger.info("Closing WebSocket connection after completion...");
    
                            isRunning.set(false); // 音声送信スレッドを停止します。
                            if (this.isOpen()) {
                                this.close(1000, "ASR completed");
                            }
                        }
                    } catch (Exception e) {
                        logger.severe("Failed to parse message: " + message);
                    }
                }
    
                @Override
                public void onClose(int code, String reason, boolean remote) {
                    logger.info("Connection closed: " + code + " - " + reason);
                }
    
                @Override
                public void onError(Exception ex) {
                    logger.severe("Error: " + ex.getMessage());
                }
            };
    
            // リクエストヘッダーを追加します。
            client.addHeader("Authorization", "Bearer " + API_KEY);
            client.addHeader("OpenAI-Beta", "realtime=v1");
    
            client.connectBlocking(); // 接続が確立されるまでブロックします。
    
            // 認識する音声ファイルのパスに置き換えます。
            String localAudioPath = "your_audio_file.pcm";
            Thread audioThread = new Thread(() -> {
                try {
                    sendAudio(localAudioPath);
                } catch (Exception e) {
                    logger.severe("Audio sending thread error: " + e.getMessage());
                }
            });
            audioThread.start();
        }
    
        /** セッション更新イベント (VAD の有効化/無効化)。 */
        private static void sendSessionUpdate() {
            JSONObject eventNoVad = new JSONObject()
                    .put("event_id", "event_123")
                    .put("type", "session.update")
                    .put("session", new JSONObject()
                            .put("modalities", new String[]{"text"})
                            .put("input_audio_format", "pcm")
                            .put("sample_rate", 16000)
                            .put("input_audio_transcription", new JSONObject()
                                    .put("language", "zh"))
                            .put("turn_detection", JSONObject.NULL) // 手動モード。
                    );
    
            JSONObject eventVad = new JSONObject()
                    .put("event_id", "event_123")
                    .put("type", "session.update")
                    .put("session", new JSONObject()
                            .put("modalities", new String[]{"text"})
                            .put("input_audio_format", "pcm")
                            .put("sample_rate", 16000)
                            .put("input_audio_transcription", new JSONObject()
                                    .put("language", "zh"))
                            .put("turn_detection", new JSONObject()
                                    .put("type", "server_vad")
                                    .put("threshold", 0.2)
                                    .put("silence_duration_ms", 800))
                    );
    
            if (enableServerVad) {
                logger.info("Sending event (VAD):\n" + eventVad.toString(2));
                client.send(eventVad.toString());
            } else {
                logger.info("Sending event (Manual):\n" + eventNoVad.toString(2));
                client.send(eventNoVad.toString());
            }
        }
    
        /** 音声ファイルストリームを送信します。 */
        private static void sendAudio(String localAudioPath) throws Exception {
            Thread.sleep(3000); // セッションの準備が整うのを待ちます。
            byte[] allBytes = Files.readAllBytes(Paths.get(localAudioPath));
            logger.info("Start reading file.");
    
            int offset = 0;
            while (isRunning.get() && offset < allBytes.length) {
                int chunkSize = Math.min(3200, allBytes.length - offset);
                byte[] chunk = new byte[chunkSize];
                System.arraycopy(allBytes, offset, chunk, 0, chunkSize);
                offset += chunkSize;
    
                if (client != null && client.isOpen()) {
                    String encoded = Base64.getEncoder().encodeToString(chunk);
                    JSONObject eventd = new JSONObject()
                            .put("event_id", "event_" + System.currentTimeMillis())
                            .put("type", "input_audio_buffer.append")
                            .put("audio", encoded);
    
                    client.send(eventd.toString());
                    logger.info("Sending audio event: " + eventd.getString("event_id"));
                } else {
                    break; // 切断後の送信を避けます。
                }
    
                Thread.sleep(100); // リアルタイム送信をシミュレートします。
            }
    
            logger.info("Finished reading file.");
    
            // 非 VAD モードでは commit が必要です。
            if (!enableServerVad && client != null && client.isOpen()) {
                JSONObject commitEvent = new JSONObject()
                        .put("event_id", "event_789")
                        .put("type", "input_audio_buffer.commit");
                client.send(commitEvent.toString());
                logger.info("Sent commit event for manual mode.");
            }
        }
    
        /** ロガーを初期化します。 */
        private static void initLogger() {
            logger.setLevel(Level.ALL);
            Logger rootLogger = Logger.getLogger("");
            for (Handler h : rootLogger.getHandlers()) {
                rootLogger.removeHandler(h);
            }
    
            Handler consoleHandler = new ConsoleHandler();
            consoleHandler.setLevel(Level.ALL);
            consoleHandler.setFormatter(new SimpleFormatter());
            logger.addHandler(consoleHandler);
        }
    }

    Node.js

    この例を実行する前に、次のコマンドを使用して依存関係がインストールされていることを確認してください:

    npm install ws
    /**
     * Qwen-ASR リアルタイム WebSocket クライアント (Node.js 版)
     * 特徴:
     * - VAD モードと手動モードをサポート
     * - session.update を送信してセッションを開始
     * - input_audio_buffer.append オーディオチャンクを継続的に送信
     * - 手動モードで input_audio_buffer.commit を送信
     * - completed イベントを受信後、接続を閉じて送信を停止
     */
    
    const WebSocket = require('ws');
    const fs = require('fs');
    
    // ===== 設定 =====
    // シンガポールリージョンと北京リージョンの API キーは異なります。API キーを取得するには、https://www.alibabacloud.com/help/ja/model-studio/get-api-key をご参照ください。
    // 環境変数を設定していない場合は、次の行をご利用の Model Studio API キーに置き換えてください:const API_KEY = "sk-xxx"
    const API_KEY = process.env.DASHSCOPE_API_KEY || 'sk-xxx';
    const MODEL = 'qwen3-asr-flash-realtime';
    const enableServerVad = true; // VAD モードの場合は true、手動モードの場合は false
    const localAudioPath = 'your_audio_file.pcm'; // PCM16、16 kHz の音声ファイルのパス
    
    // 次の URL はシンガポールリージョンのベース URL です。北京リージョンのモデルを使用する場合は、ベース URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
    const baseUrl = 'wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime';
    const url = `${baseUrl}?model=${MODEL}`;
    
    console.log(`Connecting to server: ${url}`);
    
    // ===== 状態制御 =====
    let isRunning = true;
    
    // ===== 接続確立 =====
    const ws = new WebSocket(url, {
        headers: {
            'Authorization': `Bearer ${API_KEY}`,
            'OpenAI-Beta': 'realtime=v1'
        }
    });
    
    // ===== イベントバインディング =====
    ws.on('open', () => {
        console.log('[WebSocket] Connected to server.');
        sendSessionUpdate();
        // 音声送信スレッドを開始します。
        sendAudio(localAudioPath);
    });
    
    ws.on('message', (message) => {
        try {
            const data = JSON.parse(message);
            console.log('[Received Event]:', JSON.stringify(data, null, 2));
    
            // 完了イベントを受信。
            if (data.type === 'conversation.item.input_audio_transcription.completed') {
                console.log(`[Final Transcript] ${data.transcript}`);
                console.log('[Action] Closing WebSocket connection after completion...');
                isRunning = false; // 音声送信を停止します。
                if (ws.readyState === WebSocket.OPEN) {
                    ws.close(1000, 'ASR completed');
                }
            }
        } catch (e) {
            console.error('[Error] Failed to parse message:', message);
        }
    });
    
    ws.on('close', (code, reason) => {
        console.log(`[WebSocket] Connection closed: ${code} - ${reason}`);
    });
    
    ws.on('error', (err) => {
        console.error('[WebSocket Error]', err);
    });
    
    // ===== セッション更新 =====
    function sendSessionUpdate() {
        const eventNoVad = {
            event_id: 'event_123',
            type: 'session.update',
            session: {
                modalities: ['text'],
                input_audio_format: 'pcm',
                sample_rate: 16000,
                input_audio_transcription: {
                    language: 'zh'
                },
                turn_detection: null
            }
        };
    
        const eventVad = {
            event_id: 'event_123',
            type: 'session.update',
            session: {
                modalities: ['text'],
                input_audio_format: 'pcm',
                sample_rate: 16000,
                input_audio_transcription: {
                    language: 'zh'
                },
                turn_detection: {
                    type: 'server_vad',
                    threshold: 0.2,
                    silence_duration_ms: 800
                }
            }
        };
    
        if (enableServerVad) {
            console.log('[Send Event] VAD Mode:\n', JSON.stringify(eventVad, null, 2));
            ws.send(JSON.stringify(eventVad));
        } else {
            console.log('[Send Event] Manual Mode:\n', JSON.stringify(eventNoVad, null, 2));
            ws.send(JSON.stringify(eventNoVad));
        }
    }
    
    // ===== 音声ファイルストリームの送信 =====
    function sendAudio(audioPath) {
        setTimeout(() => {
            console.log(`[File Read Start] ${audioPath}`);
            const buffer = fs.readFileSync(audioPath);
    
            let offset = 0;
            const chunkSize = 3200; // 約 0.1 秒の PCM16 音声
    
            function sendChunk() {
                if (!isRunning) return;
                if (offset >= buffer.length) {
                    console.log('[File Read End]');
                    if (!enableServerVad && ws.readyState === WebSocket.OPEN) {
                        const commitEvent = {
                            event_id: 'event_789',
                            type: 'input_audio_buffer.commit'
                        };
                        ws.send(JSON.stringify(commitEvent));
                        console.log('[Send Commit Event]');
                    }
                    return;
                }
    
                if (ws.readyState !== WebSocket.OPEN) {
                    console.log('[Stop] WebSocket is not open.');
                    return;
                }
    
                const chunk = buffer.slice(offset, offset + chunkSize);
                offset += chunkSize;
    
                const encoded = chunk.toString('base64');
                const appendEvent = {
                    event_id: `event_${Date.now()}`,
                    type: 'input_audio_buffer.append',
                    audio: encoded
                };
    
                ws.send(JSON.stringify(appendEvent));
                console.log(`[Send Audio Event] ${appendEvent.event_id}`);
    
                setTimeout(sendChunk, 100); // リアルタイム送信をシミュレートします。
            }
    
            sendChunk();
        }, 3000); // セッション設定が完了するのを待ちます。
    }
    

コア機能:コンテキストバイアス

コンテキストを提供して、名前、地名、製品用語などのドメイン固有の語彙の認識を最適化できます。これにより、文字起こしの精度が大幅に向上します。この機能は、従来のホットワードソリューションよりも柔軟で強力です。

長さの制限:コンテキストは 10,000 トークンを超えることはできません。

使用方法:

  • WebSocket API:session.update イベントで session.input_audio_transcription.corpus.text パラメーターを設定します。

  • Python SDK:corpus_text パラメーターを設定します。

  • Java SDK:corpusText パラメーターを設定します。

対応テキストタイプ:以下を含みますが、これらに限定されません:

  • ホットワードリスト (「ホットワード1、ホットワード2、ホットワード3、ホットワード4」など、さまざまな区切り文字形式)

  • 任意の形式と長さのテキスト段落または章

  • 混合コンテンツ:単語リストと段落の任意の組み合わせ

  • 無関係または無意味なテキスト (文字化けを含む)。モデルは無関係なテキストに対して高いフォールトトレランスを持ち、ほとんど悪影響を受けません。

例:

この例では、ある音声セグメントの正しい文字起こしは「投資銀行界隈の専門用語、何を知ってる?まずは九大外資、Bulge Bracket、BB...」です。

コンテキストバイアスなし

コンテキストバイアスがない場合、モデルは「Bulge Bracket」を「Bird Rock」と誤って認識します。

認識結果:「投資銀行界隈の専門用語、何を知ってる?まずは九大外資、Bird Rock、BB...」

コンテキストバイアスあり

コンテキストバイアスを使用すると、モデルは投資銀行の名前を正しく認識します。

認識結果:「投資銀行界隈の専門用語、何を知ってる?まずは九大外資、Bulge Bracket、BB...」

この結果を得るために、コンテキストに以下のいずれかのコンテンツを追加できます:

  • 単語リスト:

    • 単語リスト 1:

      Bulge Bracket, Boutique, Middle Market, 国内証券会社
    • 単語リスト 2:

      Bulge Bracket Boutique Middle Market 国内証券会社
    • 単語リスト 3:

      ['Bulge Bracket', 'Boutique', 'Middle Market', '国内証券会社']
  • 自然言語:

    投資銀行のカテゴリーの秘密を大公開!
    最近、オーストラリアの多くの友人から、投資銀行とは一体何なのかと尋ねられます。今日はそれを解説します。留学生にとって、投資銀行は主に4つのカテゴリーに分けられます:Bulge Bracket、Boutique、Middle Market、そして国内証券会社です。
    Bulge Bracket 投資銀行:これらは私たちがよく言う九大投資銀行のことで、ゴールドマン・サックスやモルガン・スタンレーなどが含まれます。これらの大手銀行は、事業範囲も規模も巨大です。
    Boutique 投資銀行:これらの銀行は比較的小規模ですが、事業分野が非常に専門的です。例えば、ラザードやエバーコアなどは、特定の分野で深い専門知識と経験を持っています。
    Middle Market 投資銀行:このタイプの銀行は主に中規模企業を対象とし、合併・買収や IPO などのサービスを提供します。大手銀行ほど大きくはありませんが、特定の市場で高い影響力を持っています。
    国内証券会社:中国市場の台頭に伴い、国内証券会社も国際市場でますます重要な役割を果たしています。
    その他、いくつかのポジションや事業部門については、関連するチャートをご参照ください。この情報が、投資銀行をよりよく理解し、将来のキャリアに備える助けとなれば幸いです!
  • 干渉ありの自然言語:コンテキストには、以下の例の名前のような無関係なテキストが含まれていても構いません。

    投資銀行のカテゴリーの秘密を大公開!
    最近、オーストラリアの多くの友人から、投資銀行とは一体何なのかと尋ねられます。今日はそれを解説します。留学生にとって、投資銀行は主に4つのカテゴリーに分けられます:Bulge Bracket、Boutique、Middle Market、そして国内証券会社です。
    Bulge Bracket 投資銀行:これらは私たちがよく言う九大投資銀行のことで、ゴールドマン・サックスやモルガン・スタンレーなどが含まれます。これらの大手銀行は、事業範囲も規模も巨大です。
    Boutique 投資銀行:これらの銀行は比較的小規模ですが、事業分野が非常に専門的です。例えば、ラザードやエバーコアなどは、特定の分野で深い専門知識と経験を持っています。
    Middle Market 投資銀行:このタイプの銀行は主に中規模企業を対象とし、合併・買収や IPO などのサービスを提供します。大手銀行ほど大きくはありませんが、特定の市場で高い影響力を持っています。
    国内証券会社:中国市場の台頭に伴い、国内証券会社も国際市場でますます重要な役割を果たしています。
    その他、いくつかのポジションや事業部門については、関連するチャートをご参照ください。この情報が、投資銀行をよりよく理解し、将来のキャリアに備える助けとなれば幸いです!
    田中太郎、鈴木一郎、佐藤花子、高橋健太、渡辺美咲、伊藤大輔、山本さくら、中村拓也、小林直樹、加藤愛子、吉田誠、山田優子、佐々木翔太、山口久美子、
    松本明、井上翼、木村遥、林直人、斎藤美穂、清水健一、橋本あゆみ、石川大樹、岡田真由、後藤拓海、
    阿部結衣、長谷川蓮、森田さやか、内田翔、藤田美緒、石井亮太、坂本優奈、遠藤和樹、
    近藤あかり、福田大輔、三浦結菜、岡本拓也、吉川美咲、野口翔平、松田ひかり、小川健太、前田さくら、藤井大輔、
    竹内美穂、渡部拓也、杉山愛子、増田誠、大塚優子、小島翔太、柴田久美子、宮崎明、安藤翼、上野遥

API リファレンス

リアルタイム音声認識 - Qwen API リファレンス

レート制限

国際 (シンガポール)

モデル

RPS

qwen3-asr-flash-realtime

20

qwen3-asr-flash-realtime-2025-10-27

中国本土 (北京)

モデル

RPS

qwen3-asr-flash-realtime

20

qwen3-asr-flash-realtime-2025-10-27