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

Alibaba Cloud Model Studio:リアルタイム(Qwen-Omni-Realtime)

最終更新日:Mar 31, 2026

Qwen-Omni-Realtime は、Qwen シリーズに属するリアルタイム音声・映像チャットモデルです。このモデルは、ストリーミング音声および画像(例:ビデオストリームからリアルタイムで抽出された連続する画像フレームなど)を入力として処理し、高品質なテキストおよび音声出力をリアルタイムで生成します。

サポートされるリージョン: シンガポール、中国 (北京)。各リージョンごとに、API キー を使用する必要があります。

使い方

1. 接続の確立

WebSocket プロトコルを使用して Qwen-Omni-Realtime モデルに接続します。以下の Python コード例または DashScope SDK をご利用ください。

説明

単一の WebSocket セッションは、自動的に終了するまで最大 120 分間 継続します。

ネイティブ WebSocket 接続

以下の設定項目が必要です。

設定項目

説明

エンドポイント

中国 (北京):wss://dashscope.aliyuncs.com/api-ws/v1/realtime

国際 (シンガポール):wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime

クエリパラメーター

クエリパラメーターは model です。アクセスしたいモデル名を指定します。例:?model=qwen3.5-omni-plus-realtime

リクエストヘッダー

Bearer トークンによる認証:Authorization: Bearer DASHSCOPE_API_KEY

DASHSCOPE_API_KEY は、Model Studio で取得した API キー です。
# pip install websocket-client
import json
import websocket
import os

API_KEY=os.getenv("DASHSCOPE_API_KEY")
API_URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3.5-omni-plus-realtime"

headers = [
    "Authorization: Bearer " + API_KEY
]

def on_open(ws):
    print(f"サーバーに接続しました:{API_URL}")
def on_message(ws, message):
    data = json.loads(message)
    print("受信イベント:", json.dumps(data, indent=2))
def on_error(ws, error):
    print("エラー:", error)

ws = websocket.WebSocketApp(
    API_URL,
    header=headers,
    on_open=on_open,
    on_message=on_message,
    on_error=on_error
)

ws.run_forever()

DashScope SDK

# SDK バージョン 1.23.9 以降
import os
import json
from dashscope.audio.qwen_omni import OmniRealtimeConversation,OmniRealtimeCallback
import dashscope
# シンガポールと北京の API キーは異なります。API キーの取得方法については、https://www.alibabacloud.com/help/ja/model-studio/get-api-key をご参照ください。
# API キーを設定していない場合は、次の行を dashscope.api_key = "sk-xxx" に置き換えてください。
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")

class PrintCallback(OmniRealtimeCallback):
    def on_open(self) -> None:
        print("接続が完了しました")
    def on_event(self, response: dict) -> None:
        print("受信イベント:")
        print(json.dumps(response, indent=2, ensure_ascii=False))
    def on_close(self, close_status_code: int, close_msg: str) -> None:
        print(f"接続が終了しました(コード={close_status_code}、メッセージ={close_msg})。")

callback = PrintCallback()
conversation = OmniRealtimeConversation(
    model="qwen3.5-omni-plus-realtime",
    callback=callback,
    # 以下はシンガポールリージョンの URL です。北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
    url="wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime"
)
try:
    conversation.connect()
    print("会話が開始されました。Ctrl+C を押して終了してください。")
    conversation.thread.join()
except KeyboardInterrupt:
    conversation.close()
// SDK バージョン 2.20.9 以降
import com.alibaba.dashscope.audio.omni.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) throws InterruptedException, NoApiKeyException {
        CountDownLatch latch = new CountDownLatch(1);
        OmniRealtimeParam param = OmniRealtimeParam.builder()
                .model("qwen3.5-omni-plus-realtime")
                .apikey(System.getenv("DASHSCOPE_API_KEY"))
                // 以下はシンガポールリージョンの URL です。北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
                .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                .build();

        OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() {
            @Override
            public void onOpen() {
                System.out.println("接続が完了しました");
            }
            @Override
            public void onEvent(JsonObject message) {
                System.out.println(message);
            }
            @Override
            public void onClose(int code, String reason) {
                System.out.println("接続が終了しました(コード:" + code + "、理由:" + reason + ")");
                latch.countDown();
            }
        });
        conversation.connect();
        latch.await();
        conversation.close(1000, "さようなら");
        System.exit(0);
    }
}

2. セッションの構成

クライアントイベント session.update を送信します。

{
    // このイベントの ID。クライアントが生成します。
    "event_id": "event_ToPZqeobitzUJnt3QqtWg",
    // イベントタイプ。固定値「session.update」です。
    "type": "session.update",
    // セッション構成。
    "session": {
        // 出力モダリティ。サポートされる値は ["text"](テキストのみ)または ["text","audio"](テキストおよび音声)です。
        "modalities": [
            "text",
            "audio"
        ],
        // 出力音声のボイス。
        "voice": "Cherry",
        // 入力音声フォーマット。pcm のみサポートされます。
        "input_audio_format": "pcm",
        // 出力音声フォーマット。pcm のみサポートされます。
        "output_audio_format": "pcm",
        // システムメッセージ。モデルの目的または役割を設定します。
        "instructions": "あなたは五つ星ホテルの AI 顧客サービス担当者です。客室タイプ、施設、料金、予約ポリシーに関するお客様のお問い合わせに正確かつ親切に回答してください。常にプロフェッショナルで親切な態度で応答してください。未確認の情報やホテルのサービス範囲を超えた情報を提供しないでください。",
        // 音声区間検出(VAD)を有効化します。有効化するには、構成オブジェクトを渡します。サーバーは、このオブジェクトに基づいて音声の開始/終了を自動検出します。
        // クライアントがモデル応答の開始タイミングを決定する場合は、null を設定します。
        "turn_detection": {
            // VAD のタイプ。server_vad に設定する必要があります。
            "type": "server_vad",
            // VAD 検出しきい値。騒音環境では増加させ、静かな環境では減少させます。
            "threshold": 0.5,
            // 音声終了を検出するための無音期間(ミリ秒単位)。この値を超えると、モデル応答がトリガーされます。
            "silence_duration_ms": 800
        }
    }
}

3. 音声および画像の入力

input_audio_buffer.append イベントおよび input_image_buffer.append イベントを使用して、Base64 エンコード済みの音声(必須)および画像(任意)データをサーバーバッファーに送信します。

画像は、ローカルファイルから取得することも、ビデオストリームからリアルタイムでキャプチャすることもできます。
サーバー側 VAD が有効な場合、サーバーは音声の終了を自動検出し、データを自動的に送信して応答をトリガーします。VAD が無効(手動モード)の場合、クライアントは input_audio_buffer.commit イベントを呼び出してデータを送信する必要があります。

4. モデル応答の受信

モデル応答のフォーマットは、構成された出力モダリティによって異なります。

モデル選択

Qwen3.5-Omni-Realtime は、Qwen シリーズの最新リアルタイムマルチモーダルモデルです。前世代の Qwen3-Omni-Flash-Realtime と比較して、以下の点が向上しています。

  • 知能レベル

    知能が大幅に向上し、Qwen3.5-Plus と同等の性能を実現しています。

  • Web 検索

    Web 検索をネイティブでサポートしています。モデルがリアルタイム質問に対する回答を検索するかどうかを自律的に判断します。「Web 検索」をご参照ください。

  • セマンティックな中断

    会話の意図を自動的に識別し、フィラー音や意味のないバックグラウンドノイズによる中断を回避します。

  • 音声制御

    「もっと速く話して」「もっと大きく話して」「明るく話して」などの音声コマンドで、音量、話す速度、感情を制御できます。

  • サポート言語

    113 の言語および方言で音声認識をサポートし、36 の言語および方言で音声合成をサポートします。

  • ボイスオプション

    55 種類のボイス(47 種類の多言語対応ボイス+8 種類の方言専用ボイス)を提供します。「ボイス一覧」をご参照ください。

モデル名、コンテキスト、課金、スナップショットバージョンについては、「モデル一覧」をご参照ください。同時実行数の制限については、「レート制限」をご参照ください。

はじめに

API キー を取得し、API キーを環境変数として設定 します。

お好みのプログラミング言語を選択し、以下の手順に従って、リアルタイムモデルとのリアルタイム会話をすぐに開始してください。

DashScope Python SDK

  • 実行環境の準備

Python のバージョンは 3.10 以降である必要があります。

まず、お使いのオペレーティングシステムに応じて pyaudio をインストールします。

macOS

brew install portaudio && pip install pyaudio

Debian/Ubuntu

  • 仮想環境を使用していない場合、システムパッケージマネージャーで直接インストールします。

    sudo apt-get install python3-pyaudio
  • 仮想環境を使用している場合、まずコンパイル依存関係をインストールします。

    sudo apt update
    sudo apt install -y python3-dev portaudio19-dev

    その後、アクティブ化された仮想環境で pip を使用してパッケージをインストールできます。

    pip install pyaudio

CentOS

sudo yum install -y portaudio portaudio-devel && pip install pyaudio

Windows

pip install pyaudio

インストール後、pip を使用して依存関係をインストールします。

pip install websocket-client dashscope
  • 対話モードの選択

    • VAD モード(音声区間検出、音声の開始/終了を自動検出)

      サーバーがユーザーの発話の開始および終了を自動検出し、応答します。

    • 手動モード(プッシュ・トゥ・トーク、リリース・トゥ・センド)

      クライアントが発話タイミングを制御します。ユーザーの発話終了後に、クライアントがサーバーにメッセージを送信します。

    VAD モード

    新しい Python ファイル vad_dash.py を作成し、以下のコードをコピーして貼り付けます。

    vad_dash.py

    # 依存関係:dashscope >= 1.23.9、pyaudio
    import os
    import base64
    import time
    import pyaudio
    from dashscope.audio.qwen_omni import MultiModality, AudioFormat,OmniRealtimeCallback,OmniRealtimeConversation
    import dashscope
    
    # 設定パラメーター:エンドポイント、API キー、ボイス、モデル、モデルの役割
    # リージョンを指定します。「intl」は国際(シンガポール)、"cn" は中国(北京)です。
    region = 'intl'
    base_domain = 'dashscope-intl.aliyuncs.com' if region == 'intl' else 'dashscope.aliyuncs.com'
    url = f'wss://{base_domain}/api-ws/v1/realtime'
    # API キーを設定します。環境変数を設定していない場合は、次の行を dashscope.api_key = "sk-xxx" に置き換えてください。
    dashscope.api_key = os.getenv('DASHSCOPE_API_KEY')
    # ボイスを指定します。
    voice = 'Cherry'
    # モデルを指定します。
    model = 'qwen3.5-omni-plus-realtime'
    # モデルの役割を指定します。
    instructions = "あなたは個人アシスタントの「小雲」です。ユーザーの質問にユーモアと機知を交えてお答えください。"
    class SimpleCallback(OmniRealtimeCallback):
        def __init__(self, pya):
            self.pya = pya
            self.out = None
        def on_open(self):
            # 音声出力ストリームを初期化します。
            self.out = self.pya.open(
                format=pyaudio.paInt16,
                channels=1,
                rate=24000,
                output=True
            )
        def on_event(self, response):
            if response['type'] == 'response.audio.delta':
                # 音声を再生します。
                self.out.write(base64.b64decode(response['delta']))
            elif response['type'] == 'conversation.item.input_audio_transcription.completed':
                # 文字起こしされたテキストを表示します。
                print(f"[ユーザー] {response['transcript']}")
            elif response['type'] == 'response.audio_transcript.done':
                # アシスタントの返答テキストを表示します。
                print(f"[LLM] {response['transcript']}")
    
    # 1. 音声デバイスを初期化します。
    pya = pyaudio.PyAudio()
    # 2. コールバック関数とセッションを作成します。
    callback = SimpleCallback(pya)
    conv = OmniRealtimeConversation(model=model, callback=callback, url=url)
    # 3. 接続を確立し、セッションを構成します。
    conv.connect()
    conv.update_session(output_modalities=[MultiModality.AUDIO, MultiModality.TEXT], voice=voice, instructions=instructions)
    # 4. 音声入力ストリームを初期化します。
    mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
    # 5. 音声入力を処理するメインループです。
    print("会話が開始されました。マイクに向かって話してください(Ctrl+C で終了)…")
    try:
        while True:
            audio_data = mic.read(3200, exception_on_overflow=False)
            conv.append_audio(base64.b64encode(audio_data).decode())
            time.sleep(0.01)
    except KeyboardInterrupt:
        # リソースをクリーンアップします。
        conv.close()
        mic.close()
        callback.out.close()
        pya.terminate()
        print("\n会話が終了しました")

    vad_dash.py を実行して、マイク経由で Qwen-Omni-Realtime とリアルタイム会話を行います。システムが音声の開始/終了を検出し、手動介入なしで自動的にデータをサーバーに送信します。

    手動モード

    新しい Python ファイル manual_dash.py を作成し、以下のコードをコピーして貼り付けます。

    manual_dash.py

    # 依存関係:dashscope >= 1.23.9、pyaudio。
    import os
    import base64
    import sys
    import threading
    import pyaudio
    from dashscope.audio.qwen_omni import *
    import dashscope
    
    # 環境変数を設定していない場合は、次の行を API キーに置き換えてください:dashscope.api_key = "sk-xxx"
    dashscope.api_key = os.getenv('DASHSCOPE_API_KEY')
    voice = 'Cherry'
    
    class MyCallback(OmniRealtimeCallback):
        """最小限のコールバック:接続時にスピーカーを初期化し、返された音声を直接再生します。"""
        def __init__(self, ctx):
            super().__init__()
            self.ctx = ctx
    
        def on_open(self) -> None:
            # 接続後に PyAudio およびスピーカー(24k/モノラル/16bit)を初期化します。
            print('接続が確立されました')
            try:
                self.ctx['pya'] = pyaudio.PyAudio()
                self.ctx['out'] = self.ctx['pya'].open(
                    format=pyaudio.paInt16,
                    channels=1,
                    rate=24000,
                    output=True
                )
                print('音声出力が初期化されました')
            except Exception as e:
                print('[エラー] 音声初期化に失敗しました:{}'.format(e))
    
        def on_close(self, close_status_code, close_msg) -> None:
            print('接続が終了しました(コード:{}、メッセージ:{})'.format(close_status_code, close_msg))
            sys.exit(0)
    
        def on_event(self, response: str) -> None:
            try:
                t = response['type']
                handlers = {
                    'session.created': lambda r: print('セッションを開始しました:{}'.format(r['session']['id'])),
                    'conversation.item.input_audio_transcription.completed': lambda r: print('質問:{}'.format(r['transcript'])),
                    'response.audio_transcript.delta': lambda r: print('LLM のテキスト:{}'.format(r['delta'])),
                    'response.audio.delta': self._play_audio,
                    'response.done': self._response_done,
                }
                h = handlers.get(t)
                if h:
                    h(response)
            except Exception as e:
                print('[エラー] {}'.format(e))
    
        def _play_audio(self, response):
            # Base64 をデコードし、再生用に出力ストリームに書き込みます。
            if self.ctx['out'] is None:
                return
            try:
                data = base64.b64decode(response['delta'])
                self.ctx['out'].write(data)
            except Exception as e:
                print('[エラー] 音声再生に失敗しました:{}'.format(e))
    
        def _response_done(self, response):
            # メインループが待機できるように、現在のターンを完了としてマークします。
            if self.ctx['conv'] is not None:
                print('[メトリック] 応答:{}、最初のテキスト遅延:{}、最初の音声遅延:{}'.format(
                    self.ctx['conv'].get_last_response_id(),
                    self.ctx['conv'].get_last_first_text_delay(),
                    self.ctx['conv'].get_last_first_audio_delay(),
                ))
            if self.ctx['resp_done'] is not None:
                self.ctx['resp_done'].set()
    
    def shutdown_ctx(ctx):
        """音声および PyAudio リソースを安全に解放します。"""
        try:
            if ctx['out'] is not None:
                ctx['out'].close()
                ctx['out'] = None
        except Exception:
            pass
        try:
            if ctx['pya'] is not None:
                ctx['pya'].terminate()
                ctx['pya'] = None
        except Exception:
            pass
    
    
    def record_until_enter(pya_inst: pyaudio.PyAudio, sample_rate=16000, chunk_size=3200):
        """Enter キーを押すと録音を停止し、PCM バイトを返します。"""
        frames = []
        stop_evt = threading.Event()
    
        stream = pya_inst.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=sample_rate,
            input=True,
            frames_per_buffer=chunk_size
        )
    
        def _reader():
            while not stop_evt.is_set():
                try:
                    frames.append(stream.read(chunk_size, exception_on_overflow=False))
                except Exception:
                    break
    
        t = threading.Thread(target=_reader, daemon=True)
        t.start()
        input()  # ユーザーが再度 Enter キーを押すと録音を停止します。
        stop_evt.set()
        t.join(timeout=1.0)
        try:
            stream.close()
        except Exception:
            pass
        return b''.join(frames)
    
    
    if __name__  == '__main__':
        print('初期化中 …')
        # 実行時コンテキスト:音声およびセッションハンドルを格納します。
        ctx = {'pya': None, 'out': None, 'conv': None, 'resp_done': threading.Event()}
        callback = MyCallback(ctx)
        conversation = OmniRealtimeConversation(
            model='qwen3.5-omni-plus-realtime',
            callback=callback,
            # 以下は国際(シンガポール)リージョンの URL です。中国(北京)リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
            url="wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime",
        )
        try:
            conversation.connect()
        except Exception as e:
            print('[エラー] 接続に失敗しました:{}'.format(e))
            sys.exit(1)
    
        ctx['conv'] = conversation
        # セッション構成:テキストおよび音声出力を有効化(サーバー側 VAD を無効化し、手動録音に切り替えます)。
        conversation.update_session(
            output_modalities=[MultiModality.AUDIO, MultiModality.TEXT],
            voice=voice,
            enable_input_audio_transcription=True,
            # 入力音声を文字起こすモデル。gummy-realtime-v1 のみサポートされます。
            input_audio_transcription_model='gummy-realtime-v1',
            enable_turn_detection=False,
            instructions="あなたは個人アシスタントの「小雲」です。ユーザーの質問に正確かつ親切に回答し、常に親切な態度で応答してください。"
        )
    
        try:
            turn = 1
            while True:
                print(f"\n--- ターン {turn} ---")
                print("Enter キーを押して録音を開始します(q を入力すると終了します)…")
                user_input = input()
                if user_input.strip().lower() in ['q', 'quit']:
                    print("ユーザーが終了を要求しました…")
                    break
                print("録音中… 再度 Enter キーを押して停止します。")
                if ctx['pya'] is None:
                    ctx['pya'] = pyaudio.PyAudio()
                recorded = record_until_enter(ctx['pya'])
                if not recorded:
                    print("有効な音声が録音されませんでした。もう一度お試しください。")
                    continue
                print(f"音声を {len(recorded)} バイト録音しました。送信中…")
    
                # 3200 バイトずつのチャンクで送信します(16k/16bit/100ms に対応)。
                chunk_size = 3200
                for i in range(0, len(recorded), chunk_size):
                    chunk = recorded[i:i+chunk_size]
                    conversation.append_audio(base64.b64encode(chunk).decode('ascii'))
    
                print("送信が完了しました。モデルの応答を待っています…")
                ctx['resp_done'].clear()
                conversation.commit()
                conversation.create_response()
                ctx['resp_done'].wait()
                print('音声再生が完了しました。')
                turn += 1
        except KeyboardInterrupt:
            print("\nユーザーによりプログラムが中断されました。")
        finally:
            shutdown_ctx(ctx)
            print("プログラムが終了しました。")

    manual_dash.py を実行します。Enter キーを押して話しかけ、再度 Enter キーを押してモデルの音声応答を受信します。

DashScope Java SDK

対話モードの選択

  • VAD モード(音声区間検出、音声の開始/終了を自動検出)

    リアルタイム API が音声タイミングを自動検出し、応答します。

  • 手動モード(プッシュ・トゥ・トーク、リリース・トゥ・センド)

    クライアントが音声タイミングを制御します。ユーザーの発話終了後に、クライアントがサーバーにメッセージを送信します。

VAD モード

OmniServerVad.java

import com.alibaba.dashscope.audio.omni.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.*;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class OmniServerVad {
    static class SequentialAudioPlayer {
        private final SourceDataLine line;
        private final Queue<byte[]> audioQueue = new ConcurrentLinkedQueue<>();
        private final Thread playerThread;
        private final AtomicBoolean shouldStop = new AtomicBoolean(false);

        public SequentialAudioPlayer() throws LineUnavailableException {
            AudioFormat format = new AudioFormat(24000, 16, 1, true, false);
            line = AudioSystem.getSourceDataLine(format);
            line.open(format);
            line.start();

            playerThread = new Thread(() -> {
                while (!shouldStop.get()) {
                    byte[] audio = audioQueue.poll();
                    if (audio != null) {
                        line.write(audio, 0, audio.length);
                    } else {
                        try { Thread.sleep(10); } catch (InterruptedException ignored) {}
                    }
                }
            }, "AudioPlayer");
            playerThread.start();
        }

        public void play(String base64Audio) {
            try {
                byte[] audio = Base64.getDecoder().decode(base64Audio);
                audioQueue.add(audio);
            } catch (Exception e) {
                System.err.println("音声デコードに失敗しました: " + e.getMessage());
            }
        }

        public void cancel() {
            audioQueue.clear();
            line.flush();
        }

        public void close() {
            shouldStop.set(true);
            try { playerThread.join(1000); } catch (InterruptedException ignored) {}
            line.drain();
            line.close();
        }
    }

    public static void main(String[] args) {
        try {
            SequentialAudioPlayer player = new SequentialAudioPlayer();
            AtomicBoolean userIsSpeaking = new AtomicBoolean(false);
            AtomicBoolean shouldStop = new AtomicBoolean(false);

            OmniRealtimeParam param = OmniRealtimeParam.builder()
                    .model("qwen3.5-omni-plus-realtime")
                    .apikey(System.getenv("DASHSCOPE_API_KEY"))
                    // 以下は国際(シンガポール)リージョンの URL です。中国(北京)リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
                    .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                    .build();

            OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() {
                @Override public void onOpen() {
                    System.out.println("接続が確立されました");
                }
                @Override public void onClose(int code, String reason) {
                    System.out.println("接続が終了しました (" + code + "): " + reason);
                    shouldStop.set(true);
                }
                @Override public void onEvent(JsonObject event) {
                    handleEvent(event, player, userIsSpeaking);
                }
            });

            conversation.connect();
            conversation.updateSession(OmniRealtimeConfig.builder()
                    .modalities(Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT))
                    .voice("Cherry")
                    .enableTurnDetection(true)
                    .enableInputAudioTranscription(true)
                    .parameters(Map.of("instructions",
                            "あなたは五つ星ホテルの AI 顧客サービス担当者です。客室タイプ、施設、料金、予約ポリシーに関するお客様のお問い合わせに正確かつ親切に回答してください。常にプロフェッショナルで親切な態度で応答してください。未確認の情報やホテルのサービス範囲を超えた情報を提供しないでください。"))
                    .build()
            );

            System.out.println("話しかけてください(音声の開始/終了を自動検出、Ctrl+C で終了)…");
            AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
            TargetDataLine mic = AudioSystem.getTargetDataLine(format);
            mic.open(format);
            mic.start();

            ByteBuffer buffer = ByteBuffer.allocate(3200);
            while (!shouldStop.get()) {
                int bytesRead = mic.read(buffer.array(), 0, buffer.capacity());
                if (bytesRead > 0) {
                    try {
                        conversation.appendAudio(Base64.getEncoder().encodeToString(buffer.array()));
                    } catch (Exception e) {
                        if (e.getMessage() != null && e.getMessage().contains("closed")) {
                            System.out.println("会話が終了しました。録音を停止します。");
                            break;
                        }
                    }
                }
                Thread.sleep(20);
            }

            conversation.close(1000, "通常終了");
            player.close();
            mic.close();
            System.out.println("\nプログラムが終了しました。");

        } catch (NoApiKeyException e) {
            System.err.println("API KEY が見つかりません:DASHSCOPE_API_KEY 環境変数を設定してください。");
            System.exit(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void handleEvent(JsonObject event, SequentialAudioPlayer player, AtomicBoolean userIsSpeaking) {
        String type = event.get("type").getAsString();
        switch (type) {
            case "input_audio_buffer.speech_started":
                System.out.println("\n[ユーザーが話始めました]");
                player.cancel();
                userIsSpeaking.set(true);
                break;
            case "input_audio_buffer.speech_stopped":
                System.out.println("[ユーザーが話終わりました]");
                userIsSpeaking.set(false);
                break;
            case "response.audio.delta":
                if (!userIsSpeaking.get()) {
                    player.play(event.get("delta").getAsString());
                }
                break;
            case "conversation.item.input_audio_transcription.completed":
                System.out.println("ユーザー: " + event.get("transcript").getAsString());
                break;
            case "response.audio_transcript.delta":
                System.out.print(event.get("delta").getAsString());
                break;
            case "response.done":
                System.out.println("応答が完了しました");
                break;
        }
    }
}

OmniServerVad.main() メソッドを実行して、マイク経由でリアルタイムモデルとリアルタイム会話を行います。システムが音声の開始/終了を検出し、手動介入なしで自動的にデータをサーバーに送信します。

手動モード

OmniWithoutServerVad.java

// DashScope Java SDK バージョン 2.20.9 以降

import com.alibaba.dashscope.audio.omni.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

public class Main {
    // RealtimePcmPlayer クラス定義開始
    public static class RealtimePcmPlayer {
        private int sampleRate;
        private SourceDataLine line;
        private AudioFormat audioFormat;
        private Thread decoderThread;
        private Thread playerThread;
        private AtomicBoolean stopped = new AtomicBoolean(false);
        private Queue<String> b64AudioBuffer = new ConcurrentLinkedQueue<>();
        private Queue<byte[]> RawAudioBuffer = new ConcurrentLinkedQueue<>();

        // コンストラクターは、オーディオフォーマットとオーディオラインを初期化します。
        public RealtimePcmPlayer(int sampleRate) throws LineUnavailableException {
            this.sampleRate = sampleRate;
            this.audioFormat = new AudioFormat(this.sampleRate, 16, 1, true, false);
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
            line = (SourceDataLine) AudioSystem.getLine(info);
            line.open(audioFormat);
            line.start();
            decoderThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!stopped.get()) {
                        String b64Audio = b64AudioBuffer.poll();
                        if (b64Audio != null) {
                            byte[] rawAudio = Base64.getDecoder().decode(b64Audio);
                            RawAudioBuffer.add(rawAudio);
                        } else {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            });
            playerThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!stopped.get()) {
                        byte[] rawAudio = RawAudioBuffer.poll();
                        if (rawAudio != null) {
                            try {
                                playChunk(rawAudio);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        } else {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            });
            decoderThread.start();
            playerThread.start();
        }

        // 音声チャンクを再生し、再生完了までブロックします。
        private void playChunk(byte[] chunk) throws IOException, InterruptedException {
            if (chunk == null || chunk.length == 0) return;

            int bytesWritten = 0;
            while (bytesWritten < chunk.length) {
                bytesWritten += line.write(chunk, bytesWritten, chunk.length - bytesWritten);
            }
            int audioLength = chunk.length / (this.sampleRate*2/1000);
            // バッファー内の音声の再生が完了するまで待機します。
            Thread.sleep(audioLength - 10);
        }

        public void write(String b64Audio) {
            b64AudioBuffer.add(b64Audio);
        }

        public void cancel() {
            b64AudioBuffer.clear();
            RawAudioBuffer.clear();
        }

        public void waitForComplete() throws InterruptedException {
            while (!b64AudioBuffer.isEmpty() || !RawAudioBuffer.isEmpty()) {
                Thread.sleep(100);
            }
            line.drain();
        }

        public void shutdown() throws InterruptedException {
            stopped.set(true);
            decoderThread.join();
            playerThread.join();
            if (line != null && line.isRunning()) {
                line.drain();
                line.close();
            }
        }
    } // RealtimePcmPlayer クラス定義終了
    // 録音メソッドを追加
    private static void recordAndSend(TargetDataLine line, OmniRealtimeConversation conversation) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[3200];
        AtomicBoolean stopRecording = new AtomicBoolean(false);

        // Enter キーを監視するスレッドを開始します。
        Thread enterKeyListener = new Thread(() -> {
            try {
                System.in.read();
                stopRecording.set(true);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        enterKeyListener.start();

        // 録音ループ
        while (!stopRecording.get()) {
            int count = line.read(buffer, 0, buffer.length);
            if (count > 0) {
                out.write(buffer, 0, count);
            }
        }

        // 録音されたデータを送信します。
        byte[] audioData = out.toByteArray();
        String audioB64 = Base64.getEncoder().encodeToString(audioData);
        conversation.appendAudio(audioB64);
        out.close();
    }

    public static void main(String[] args) throws InterruptedException, LineUnavailableException {
        OmniRealtimeParam param = OmniRealtimeParam.builder()
                .model("qwen3.5-omni-plus-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"))
                // 以下はシンガポールリージョンの URL です。北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
                .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                .build();
        AtomicReference<CountDownLatch> responseDoneLatch = new AtomicReference<>(null);
        responseDoneLatch.set(new CountDownLatch(1));

        RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);
        final AtomicReference<OmniRealtimeConversation> conversationRef = new AtomicReference<>(null);
        OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() {
            @Override
            public void onOpen() {
                System.out.println("接続が確立されました");
            }
            @Override
            public void onEvent(JsonObject message) {
                String type = message.get("type").getAsString();
                switch(type) {
                    case "session.created":
                        System.out.println("セッションを開始しました: " + message.get("session").getAsJsonObject().get("id").getAsString());
                        break;
                    case "conversation.item.input_audio_transcription.completed":
                        System.out.println("質問: " + message.get("transcript").getAsString());
                        break;
                    case "response.audio_transcript.delta":
                        System.out.println("LLM の応答の差分を取得しました: " + message.get("delta").getAsString());
                        break;
                    case "response.audio.delta":
                        String recvAudioB64 = message.get("delta").getAsString();
                        audioPlayer.write(recvAudioB64);
                        break;
                    case "response.done":
                        System.out.println("======応答完了======");
                        if (conversationRef.get() != null) {
                            System.out.println("[メトリック] 応答: " + conversationRef.get().getResponseId() +
                                    "、最初のテキスト遅延: " + conversationRef.get().getFirstTextDelay() +
                                    " ms、最初の音声遅延: " + conversationRef.get().getFirstAudioDelay() + " ms");
                        }
                        responseDoneLatch.get().countDown();
                        break;
                    default:
                        break;
                }
            }
            @Override
            public void onClose(int code, String reason) {
                System.out.println("接続が終了しました(コード: " + code + "、理由: " + reason + ")");
            }
        });
        conversationRef.set(conversation);
        try {
            conversation.connect();
        } catch (NoApiKeyException e) {
            throw new RuntimeException(e);
        }
        OmniRealtimeConfig config = OmniRealtimeConfig.builder()
                .modalities(Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT))
                .voice("Cherry")
                .enableTurnDetection(false)
                // モデルの役割を設定します。
                .parameters(new HashMap<String, Object>() {{
                    put("instructions","あなたは個人アシスタントの「小雲」です。ユーザーの質問に正確かつ親切に回答し、常に親切な態度で応答してください。");
                }})
                .build();
        conversation.updateSession(config);

        // マイク録音機能を追加します。
        AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
        DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);

        if (!AudioSystem.isLineSupported(info)) {
            System.out.println("ラインがサポートされていません");
            return;
        }

        TargetDataLine line = null;
        try {
            line = (TargetDataLine) AudioSystem.getLine(info);
            line.open(format);
            line.start();

            while (true) {
                System.out.println("Enter キーを押して録音を開始します…");
                System.in.read();
                System.out.println("録音を開始しました。話しかけてください… Enter キーを再度押して録音を停止し、送信します。");
                recordAndSend(line, conversation);
                conversation.commit();
                conversation.createResponse(null, null);
                // 次の待機のためにラッチをリセットします。
                responseDoneLatch.set(new CountDownLatch(1));
            }
        } catch (LineUnavailableException | IOException e) {
            e.printStackTrace();
        } finally {
            if (line != null) {
                line.stop();
                line.close();
            }
        }
    }}

OmniWithoutServerVad.main() メソッドを実行します。Enter キーを押して録音を開始し、再度 Enter キーを押して停止および送信を行います。その後、モデルの応答を受信および再生します。

WebSocket(Python)

  • 実行環境の準備

    Python のバージョンは 3.10 以降である必要があります。

    まず、お使いのオペレーティングシステムに応じて pyaudio をインストールします。

    macOS

    brew install portaudio && pip install pyaudio

    Debian/Ubuntu

    sudo apt-get install python3-pyaudio
    
    または
    
    pip install pyaudio
    pip install pyaudio の使用を推奨します。インストールに失敗した場合は、まず OS 用の portaudio 依存関係をインストールしてください。

    CentOS

    sudo yum install -y portaudio portaudio-devel && pip install pyaudio

    Windows

    pip install pyaudio

    インストール後、pip を使用して WebSocket 関連の依存関係をインストールします。

    pip install websockets==15.0.1
  • クライアントの作成

    ローカルディレクトリに omni_realtime_client.py という新しい Python ファイルを作成し、以下のコードをコピーして貼り付けます。

    omni_realtime_client.py

    import asyncio
    import websockets
    import json
    import base64
    import time
    from typing import Optional, Callable, List, Dict, Any
    from enum import Enum
    
    class TurnDetectionMode(Enum):
        SERVER_VAD = "server_vad"
        MANUAL = "manual"
    
    class OmniRealtimeClient:
    
        def __init__(
                self,
                base_url,
                api_key: str,
                model: str = "",
                voice: str = "Ethan",
                instructions: str = "あなたは親切なアシスタントです。",
                turn_detection_mode: TurnDetectionMode = TurnDetectionMode.SERVER_VAD,
                on_text_delta: Optional[Callable[[str], None]] = None,
                on_audio_delta: Optional[Callable[[bytes], None]] = None,
                on_input_transcript: Optional[Callable[[str], None]] = None,
                on_output_transcript: Optional[Callable[[str], None]] = None,
                extra_event_handlers: Optional[Dict[str, Callable[[Dict[str, Any]], None]]] = None
        ):
            self.base_url = base_url
            self.api_key = api_key
            self.model = model
            self.voice = voice
            self.instructions = instructions
            self.ws = None
            self.on_text_delta = on_text_delta
            self.on_audio_delta = on_audio_delta
            self.on_input_transcript = on_input_transcript
            self.on_output_transcript = on_output_transcript
            self.turn_detection_mode = turn_detection_mode
            self.extra_event_handlers = extra_event_handlers or {}
    
            # 現在の応答ステータス
            self._current_response_id = None
            self._current_item_id = None
            self._is_responding = False
            # 入力/出力文字起こしの表示ステータス
            self._print_input_transcript = True
            self._output_transcript_buffer = ""
    
        async def connect(self) -> None:
            """リアルタイム API との WebSocket 接続を確立します。"""
            url = f"{self.base_url}?model={self.model}"
            headers = {
                "Authorization": f"Bearer {self.api_key}"
            }
            self.ws = await websockets.connect(url, additional_headers=headers)
    
            # セッション構成
            session_config = {
                "modalities": ["text", "audio"],
                "voice": self.voice,
                "instructions": self.instructions,
                "input_audio_format": "pcm",
                "output_audio_format": "pcm",
                "input_audio_transcription": {
                    "model": "gummy-realtime-v1"
                }
            }
    
            if self.turn_detection_mode == TurnDetectionMode.MANUAL:
                session_config['turn_detection'] = None
                await self.update_session(session_config)
            elif self.turn_detection_mode == TurnDetectionMode.SERVER_VAD:
                session_config['turn_detection'] = {
                    "type": "server_vad",
                    "threshold": 0.1,
                    "prefix_padding_ms": 500,
                    "silence_duration_ms": 900
                }
                await self.update_session(session_config)
            else:
                raise ValueError(f"無効なターン検出モード:{self.turn_detection_mode}")
    
        async def send_event(self, event) -> None:
            event['event_id'] = "event_" + str(int(time.time() * 1000))
            await self.ws.send(json.dumps(event))
    
        async def update_session(self, config: Dict[str, Any]) -> None:
            """セッション構成を更新します。"""
            event = {
                "type": "session.update",
                "session": config
            }
            await self.send_event(event)
    
        async def stream_audio(self, audio_chunk: bytes) -> None:
            """API に生音声データをストリーミングします。"""
            # 16 ビット、16 kHz、モノラル PCM のみサポートされます。
            audio_b64 = base64.b64encode(audio_chunk).decode()
            append_event = {
                "type": "input_audio_buffer.append",
                "audio": audio_b64
            }
            await self.send_event(append_event)
    
        async def commit_audio_buffer(self) -> None:
            """音声バッファーをコミットして処理をトリガーします。"""
            event = {
                "type": "input_audio_buffer.commit"
            }
            await self.send_event(event)
    
        async def append_image(self, image_chunk: bytes) -> None:
            """画像データを画像バッファーに追加します。
            画像データはローカルファイルまたはリアルタイムビデオストリームから取得できます。
            注意:
                - 画像フォーマットは JPG または JPEG のみです。推奨解像度は 480p または 720p です。最大サポート解像度は 1080p です。
                - 単一の画像は 500 KB を超えてはいけません。
                - 送信前に画像データを Base64 エンコードする必要があります。
                - 1 秒あたり最大 1 フレームの頻度で画像を送信することを推奨します。
                - 画像データを送信する前に、少なくとも 1 回は音声データを送信する必要があります。
            """
            image_b64 = base64.b64encode(image_chunk).decode()
            event = {
                "type": "input_image_buffer.append",
                "image": image_b64
            }
            await self.send_event(event)
    
        async def create_response(self) -> None:
            """API に応答の生成をリクエストします(手動モードでのみ必要です)。"""
            event = {
                "type": "response.create"
            }
            await self.send_event(event)
    
        async def cancel_response(self) -> None:
            """現在の応答をキャンセルします。"""
            event = {
                "type": "response.cancel"
            }
            await self.send_event(event)
    
        async def handle_interruption(self):
            """現在の応答に対するユーザーの中断を処理します。"""
            if not self._is_responding:
                return
            # 1. 現在の応答をキャンセルします。
            if self._current_response_id:
                await self.cancel_response()
    
            self._is_responding = False
            self._current_response_id = None
            self._current_item_id = None
    
        async def handle_messages(self) -> None:
            try:
                async for message in self.ws:
                    event = json.loads(message)
                    event_type = event.get("type")
                    if event_type == "error":
                        print(" エラー: ", event['error'])
                        continue
                    elif event_type == "response.created":
                        self._current_response_id = event.get("response", {}).get("id")
                        self._is_responding = True
                    elif event_type == "response.output_item.added":
                        self._current_item_id = event.get("item", {}).get("id")
                    elif event_type == "response.done":
                        self._is_responding = False
                        self._current_response_id = None
                        self._current_item_id = None
                    elif event_type == "input_audio_buffer.speech_started":
                        print("音声の開始を検出しました")
                        if self._is_responding:
                            print("中断を処理しています")
                            await self.handle_interruption()
                    elif event_type == "input_audio_buffer.speech_stopped":
                        print("音声の終了を検出しました")
                    elif event_type == "response.text.delta":
                        if self.on_text_delta:
                            self.on_text_delta(event["delta"])
                    elif event_type == "response.audio.delta":
                        if self.on_audio_delta:
                            audio_bytes = base64.b64decode(event["delta"])
                            self.on_audio_delta(audio_bytes)
                    elif event_type == "conversation.item.input_audio_transcription.completed":
                        transcript = event.get("transcript", "")
                        print(f"ユーザー: {transcript}")
                        if self.on_input_transcript:
                            await asyncio.to_thread(self.on_input_transcript, transcript)
                            self._print_input_transcript = True
                    elif event_type == "response.audio_transcript.delta":
                        if self.on_output_transcript:
                            delta = event.get("delta", "")
                            if not self._print_input_transcript:
                                self._output_transcript_buffer += delta
                            else:
                                if self._output_transcript_buffer:
                                    await asyncio.to_thread(self.on_output_transcript, self._output_transcript_buffer)
                                    self._output_transcript_buffer = ""
                                await asyncio.to_thread(self.on_output_transcript, delta)
                    elif event_type == "response.audio_transcript.done":
                        print(f"LLM: {event.get('transcript', '')}")
                        self._print_input_transcript = False
                    elif event_type in self.extra_event_handlers:
                        self.extra_event_handlers[event_type](event)
            except websockets.exceptions.ConnectionClosed:
                print(" 接続が閉じられました")
            except Exception as e:
                print(" メッセージ処理中のエラー: ", str(e))
        async def close(self) -> None:
            """WebSocket 接続を閉じます。"""
            if self.ws:
                await self.ws.close()
  • 対話モードの選択

    • VAD モード(音声区間検出、音声の開始/終了を自動検出)

      リアルタイム API が音声タイミングを自動検出し、応答します。

    • 手動モード(プッシュ・トゥ・トーク、リリース・トゥ・センド)

      クライアントが音声タイミングを制御します。ユーザーの発話終了後に、クライアントがサーバーにメッセージを送信します。

    VAD モード

    omni_realtime_client.py と同じディレクトリに、別の Python ファイル vad_mode.py を作成し、以下のコードをコピーして貼り付けます。

    vad_mode.py

    # -- coding: utf-8 --
    import os, asyncio, pyaudio, queue, threading
    from omni_realtime_client import OmniRealtimeClient, TurnDetectionMode
    
    # 音声プレーヤークラス(中断処理対応)
    class AudioPlayer:
        def __init__(self, pyaudio_instance, rate=24000):
            self.stream = pyaudio_instance.open(format=pyaudio.paInt16, channels=1, rate=rate, output=True)
            self.queue = queue.Queue()
            self.stop_evt = threading.Event()
            self.interrupt_evt = threading.Event()
            threading.Thread(target=self._run, daemon=True).start()
    
        def _run(self):
            while not self.stop_evt.is_set():
                try:
                    data = self.queue.get(timeout=0.5)
                    if data is None: break
                    if not self.interrupt_evt.is_set(): self.stream.write(data)
                    self.queue.task_done()
                except queue.Empty: continue
    
        def add_audio(self, data): self.queue.put(data)
        def handle_interrupt(self): self.interrupt_evt.set(); self.queue.queue.clear()
        def stop(self): self.stop_evt.set(); self.queue.put(None); self.stream.stop_stream(); self.stream.close()
    
    # マイクからの録音および送信
    async def record_and_send(client):
        p = pyaudio.PyAudio()
        stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=3200)
        print("録音を開始しました。話しかけてください…")
        try:
            while True:
                audio_data = stream.read(3200)
                await client.stream_audio(audio_data)
                await asyncio.sleep(0.02)
        finally:
            stream.stop_stream(); stream.close(); p.terminate()
    
    async def main():
        p = pyaudio.PyAudio()
        player = AudioPlayer(pyaudio_instance=p)
    
        client = OmniRealtimeClient(
            # 以下は国際(シンガポール)リージョンの base_url です。中国(北京)リージョンの base_url は wss://dashscope.aliyuncs.com/api-ws/v1/realtime です。
            base_url="wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime",
            api_key=os.environ.get("DASHSCOPE_API_KEY"),
            model="qwen3.5-omni-plus-realtime",
            voice="Cherry",
            instructions="あなたはユーモアと機知に富んだアシスタント「小雲」です。",
            turn_detection_mode=TurnDetectionMode.SERVER_VAD,
            on_text_delta=lambda t: print(f"\nアシスタント: {t}", end="", flush=True),
            on_audio_delta=player.add_audio,
        )
    
        await client.connect()
        print("接続に成功しました。リアルタイム会話を開始します…")
    
        # 並列実行
        await asyncio.gather(client.handle_messages(), record_and_send(client))
    
    if __name__ == "__main__":
        try:
            asyncio.run(main())
        except KeyboardInterrupt:
            print("\nプログラムが終了しました。")

    vad_mode.py を実行して、マイク経由でリアルタイムモデルとリアルタイム会話を行います。システムが音声の開始/終了を検出し、手動介入なしで自動的にデータをサーバーに送信します。

    手動モード

    omni_realtime_client.py と同じディレクトリに、別の Python ファイル manual_mode.py を作成し、以下のコードをコピーして貼り付けます。

    manual_mode.py

    # -- coding: utf-8 --
    import os
    import asyncio
    import time
    import threading
    import queue
    import pyaudio
    from omni_realtime_client import OmniRealtimeClient, TurnDetectionMode
    
    
    class AudioPlayer:
        """リアルタイム音声プレーヤークラス"""
    
        def __init__(self, sample_rate=24000, channels=1, sample_width=2):
            self.sample_rate = sample_rate
            self.channels = channels
            self.sample_width = sample_width  # 2 バイト(16 ビット)
            self.audio_queue = queue.Queue()
            self.is_playing = False
            self.play_thread = None
            self.pyaudio_instance = None
            self.stream = None
            self._lock = threading.Lock()  # 同期アクセスのためのロック
            self._last_data_time = time.time()  # 最後にデータを受信した時刻を記録
            self._response_done = False  # 応答完了を示すフラグ
            self._waiting_for_response = False  # サーバーからの応答待ちを示すフラグ
            # 最後に音声ストリームに書き込まれたデータの時刻と、直近の音声チャンクの持続時間を記録し、より正確な再生終了検出を行う
            self._last_play_time = time.time()
            self._last_chunk_duration = 0.0
    
        def start(self):
            """音声プレーヤーを開始します"""
            with self._lock:
                if self.is_playing:
                    return
    
                self.is_playing = True
    
                try:
                    self.pyaudio_instance = pyaudio.PyAudio()
    
                    # 音声出力ストリームを作成します
                    self.stream = self.pyaudio_instance.open(
                        format=pyaudio.paInt16,  # 16 ビット
                        channels=self.channels,
                        rate=self.sample_rate,
                        output=True,
                        frames_per_buffer=1024
                    )
    
                    # 再生スレッドを開始します
                    self.play_thread = threading.Thread(target=self._play_audio)
                    self.play_thread.daemon = True
                    self.play_thread.start()
    
                    print("音声プレーヤーを開始しました")
                except Exception as e:
                    print(f"音声プレーヤーの開始に失敗しました:{e}")
                    self._cleanup_resources()
                    raise
    
        def stop(self):
            """音声プレーヤーを停止します"""
            with self._lock:
                if not self.is_playing:
                    return
    
                self.is_playing = False
    
            # キューをクリアします
            while not self.audio_queue.empty():
                try:
                    self.audio_queue.get_nowait()
                except queue.Empty:
                    break
    
            # 再生スレッドの終了を待機します(デッドロックを避けるためロック外で待機)
            if self.play_thread and self.play_thread.is_alive():
                self.play_thread.join(timeout=2.0)
    
            # ロックを再取得してリソースをクリーンアップ
            with self._lock:
                self._cleanup_resources()
    
            print("音声プレーヤーを停止しました")
    
        def _cleanup_resources(self):
            """音声リソースをクリーンアップします(ロック内で呼び出す必要があります)"""
            try:
                # 音声ストリームを閉じます
                if self.stream:
                    if not self.stream.is_stopped():
                        self.stream.stop_stream()
                    self.stream.close()
                    self.stream = None
            except Exception as e:
                print(f"音声ストリームの閉じ方に失敗しました:{e}")
    
            try:
                if self.pyaudio_instance:
                    self.pyaudio_instance.terminate()
                    self.pyaudio_instance = None
            except Exception as e:
                print(f"PyAudio インスタンスの終了に失敗しました:{e}")
    
        def add_audio_data(self, audio_data):
            """再生キューに音声データを追加します"""
            if self.is_playing and audio_data            with self._lock:
                    self._last_data_time = time.time()  # 最後にデータを受信した時刻を更新
                    self._waiting_for_response = False  # データ受信中なので、応答待ちではない
    
        def stop_receiving_data(self):
            """これ以上新しい音声データを受信しないことを示します"""
            with self._lock:
                self._response_done = True
                self._waiting_for_response = False  # 応答完了なので、応答待ちではない
    
        def prepare_for_next_turn(self):
            """次の会話ターンのためにプレーヤーの状態をリセットします。"""
            with self._lock:
                self._response_done = False
                self._last_data_time = time.time()
                self._last_play_time = time.time()
                self._last_chunk_duration = 0.0
                self._waiting_for_response = True  # 次の応答を待機開始
    
            # 前のターンの残りの音声データをクリア
            while not self.audio_queue.empty():
                try:
                    self.audio_queue.get_nowait()
                except queue.Empty:
                    break
    
        def is_finished_playing(self):
            """すべての音声データが再生されたかどうかを確認します"""
            with self._lock:
                queue_size = self.audio_queue.qsize()
                time_since_last_data = time.time() - self._last_data_time
                time_since_last_play = time.time() - self._last_play_time
    
                # ---------------------- スマートな終了検出 ----------------------
                # 1. 優先:サーバーが完了を通知し、再生キューが空の場合。
                #    直近の音声チャンクの再生が完了するまで待機(チャンク持続時間+0.1 秒の許容誤差)。
                if self._response_done and queue_size == 0:
                    min_wait = max(self._last_chunk_duration + 0.1, 0.5)  # 少なくとも 0.5 秒待機
                    if time_since_last_play >= min_wait:
                        return True
    
                # 2. フォールバック:新しいデータを一定時間受信しなかった場合、かつ再生キューが空の場合。
                #    このロジックは、サーバーが明示的に `response.done` を送信しない場合の保険です。
                if not self._waiting_for_response and queue_size == 0 and time_since_last_data > 1.0:
                    print("\n(しばらく新しい音声を受信しなかったため、再生が完了したと見なします)")
                    return True
    
                return False
    
        def _play_audio(self):
            """音声データを再生するワーカースレッド"""
            while True:
                # 停止すべきか確認
                with self._lock:
                    if not self.is_playing:
                        break
                    stream_ref = self.stream  # ストリームへの参照を取得
    
                try:
                    # キューから音声データを取得(タイムアウト 0.1 秒)
                    audio_data = self.audio_queue.get(timeout=0.1)
    
                    # ステータスとストリームの有効性を再度確認
                    with self._lock:
                        if self.is_playing and stream_ref and not stream_ref.is_stopped():
                            try:
                                # 音声データを再生
                                stream_ref.write(audio_data)
                                # 最新の再生情報を更新
                                self._last_play_time = time.time()
                                self._last_chunk_duration = len(audio_data) / (
                                            self.channels * self.sample_width) / self.sample_rate
                            except Exception as e:
                                print(f"音声ストリームへの書き込みに失敗しました:{e}")
                                break
    
                    # このデータブロックの処理を完了としてマーク
                    self.audio_queue.task_done()
    
                except queue.Empty:
                    # キューが空の場合は継続待機
                    continue
                except Exception as e:
                    print(f"音声再生中にエラーが発生しました:{e}")
                    break
    
    
    class MicrophoneRecorder:
        """リアルタイムマイクレコーダー"""
    
        def __init__(self, sample_rate=16000, channels=1, chunk_size=3200):
            self.sample_rate = sample_rate
            self.channels = channels
            self.chunk_size = chunk_size
            self.pyaudio_instance = None
            self.stream = None
            self.frames = []
            self._is_recording = False
            self._record_thread = None
    
        def _recording_thread(self):
            """録音ワーカースレッド"""
            # _is_recording が True の間、音声ストリームからデータを継続的に読み取ります
            while self._is_recording:
                try:
                    # バッファオーバーフローによるクラッシュを避けるために exception_on_overflow=False を使用
                    data = self.stream.read(self.chunk_size, exception_on_overflow=False)
                    self.frames.append(data)
                except (IOError, OSError) as e:
                    # ストリームが閉じられた場合、読み取りでエラーが発生することがあります
                    print(f"録音ストリームからの読み取りエラー、おそらく閉じられています:{e}")
                    break
    
        def start(self):
            """録音を開始します"""
            if self._is_recording:
                print("すでに録音中です。")
                return
    
            self.frames = []
            self._is_recording = True
    
            try:
                self.pyaudio_instance = pyaudio.PyAudio()
                self.stream = self.pyaudio_instance.open(
                    format=pyaudio.paInt16,
                    channels=self.channels,
                    rate=self.sample_rate,
                    input=True,
                    frames_per_buffer=self.chunk_size
                )
    
                self._record_thread = threading.Thread(target=self._recording_thread)
                self._record_thread.daemon = True
                self._record_thread.start()
                print("マイク録音を開始しました…")
            except Exception as e:
                print(f"マイクの開始に失敗しました:{e}")
                self._is_recording = False
                self._cleanup()
                raise
    
        def stop(self):
            """録音を停止し、音声データを返します"""
            if not self._is_recording:
                return None
    
            self._is_recording = False
    
            # 録音スレッドが安全に終了するまで待機
            if self._record_thread:
                self._record_thread.join(timeout=1.0)
    
            self._cleanup()
    
            print("マイク録音を停止しました。")
            return b''.join(self.frames)
    
        def _cleanup(self):
            """PyAudio リソースを安全にクリーンアップします"""
            if self.stream:
                try:
                    if self.stream.is_active():
                        self.stream.stop_stream()
                    self.stream.close()
                except Exception as e:
                    print(f"音声ストリームの閉じ方に失敗しました:{e}")
    
            if self.pyaudio_instance:
                try:
                    self.pyaudio_instance.terminate()
                except Exception as e:
                    print(f"PyAudio インスタンスの終了に失敗しました:{e}")
    
            self.stream = None
            self.pyaudio_instance = None
    
    
    async def interactive_test():
        """
        対話式テストスクリプト:各ターンで音声および画像を送信するマルチターン会話が可能です。
        """
        # ------------------- 1. 初期化および接続(1 回のみ) -------------------
        # シンガポールと北京の API キーは異なります。API キーの取得方法については、https://www.alibabacloud.com/help/ja/model-studio/get-api-key をご参照ください。
        api_key = os.environ.get("DASHSCOPE_API_KEY")
        if not api_key:
            print("DASHSCOPE_API_KEY 環境変数を設定してください。")
            return
    
        print("--- リアルタイムマルチモーダル音声/映像チャットクライアント ---")
        print("音声プレーヤーおよびクライアントを初期化しています…")
    
        audio_player = AudioPlayer()
        audio_player.start()
    
        def on_audio_received(audio_data):
            audio_player.add_audio_data(audio_data)
    
        def on_response_done(event):
            print("\n(応答終了マーカーを受信しました)")
            audio_player.stop_receiving_data()
    
        realtime_client = OmniRealtimeClient(
            # 以下はシンガポールリージョンの base_url です。北京リージョンの base_url は wss://dashscope.aliyuncs.com/api-ws/v1/realtime です。
            base_url="wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime",
            api_key=api_key,
            model="qwen3.5-omni-plus-realtime",
            voice="Ethan",
            instructions="あなたは個人アシスタントの「小雲」です。ユーザーの質問に正確かつ親切に回答し、常に親切な態度で応答してください。", # モデルの役割を設定
            on_text_delta=lambda text: print(f"アシスタントの返答:{text}", end="", flush=True),
            on_audio_delta=on_audio_received,
            turn_detection_mode=TurnDetectionMode.MANUAL,
            extra_event_handlers={"response.done": on_response_done}
        )
    
        message_handler_task = None
        try:
            await realtime_client.connect()
            print("サーバーに接続しました。いつでも 'q' または 'quit' を入力して終了できます。")
            message_handler_task = asyncio.create_task(realtime_client.handle_messages())
            await asyncio.sleep(0.5)
    
            turn_counter = 1
            # ------------------- 2. マルチターン会話ループ -------------------
            while True:
                print(f"\n--- ターン {turn_counter} ---")
                audio_player.prepare_for_next_turn()
    
                recorded_audio = None
                image_paths = []
    
                # --- ユーザー入力の取得:マイクから録音 ---
                loop = asyncio.get_event_loop()
                recorder = MicrophoneRecorder(sample_rate=16000)  # 16k サンプルレートは音声認識に推奨
    
                print("録音を開始する準備ができました。Enter キーを押して録音を開始します(または 'q' を入力して終了)…")
                user_input = await loop.run_in_executor(None, input)
                if user_input.strip().lower() in ['q', 'quit']:
                    print("ユーザーが終了を要求しました…")
                    return
    
                try:
                    recorder.start()
                except Exception:
                    print("録音を開始できませんでした。マイクの権限およびデバイスを確認してください。このターンはスキップします。")
                    continue
    
                print("録音中… 再度 Enter キーを押して停止します。")
                await loop.run_in_executor(None, input)
    
                recorded_audio = recorder.stop()
    
                if not recorded_audio or len(recorded_audio) == 0:
                    print("有効な音声が録音されませんでした。このターンをもう一度やり直してください。")
                    continue
    
                # --- 画像入力の取得(任意) ---
                # 以下の画像入力機能はコメントアウトされており、一時的に無効化されています。有効化するには、下記のコードをアンコメントしてください。
                # print("\n各行に [画像ファイル] の絶対パスを入力してください(任意)。終了したら 's' を入力するか、Enter キーを押してリクエストを送信します。")
                # while True:
                #     path = input("画像パス: ").strip()
                #     if path.lower() == 's' or path == '':
                #         break
                #     if path.lower() in ['q', 'quit']:
                #         print("ユーザーが終了を要求しました…")
                #         return
                #
                #     if not os.path.isabs(path):
                #         print("エラー:絶対パスを入力してください。")
                #         continue
                #     if not os.path.exists(path):
                #         print(f"エラー:ファイルが見つかりません -> {path}")
                #         continue
                #     image_paths.append(path)
                #     print(f"画像を追加しました:{os.path.basename(path)}")
    
                # --- 3. データの送信および応答の取得 ---
                print("\n--- 入力の確認 ---")
                print(f"処理する音声:1(マイクから)、画像:{len(image_paths)}")
                print("------------------")
    
                # 3.1 録音された音声を送信
                try:
                    print(f"マイク録音を送信中 ({len(recorded_audio)} バイト)")
                    await realtime_client.stream_audio(recorded_audio)
                    await asyncio.sleep(0.1)
                except Exception as e:
                    print(f"マイク録音の送信に失敗しました:{e}")
                    continue
    
                # 3.2 すべての画像ファイルを送信
                # 以下の画像送信コードはコメントアウトされており、一時的に無効化されています。
                # for i in range(0, len(image_paths)):
                #     path = image_paths[i]
                #     try:
                #         with open(path, "rb") as f:
                #             data = f.read()
                #         print(f"画像 {i+1} を送信中:{os.path.basename(path)} ({len(data)} バイト)")
                #         await realtime_client.append_image(data)
                #         await asyncio.sleep(0.1)
                #     except Exception as e:
                #         print(f"画像 {os.path.basename(path)} の送信に失敗しました:{e}")
    
                # 3.3 送信およびサーバー応答のリクエスト
                print("すべての入力を送信し、サーバー応答をリクエストしています…")
                await realtime_client.commit_audio_buffer()
                await realtime_client.create_response()
    
                print("サーバー応答音声の待機および再生中…")
                start_time = time.time()
                max_wait_time = 60
                while not audio_player.is_finished_playing():
                    if time.time() - start_time > max_wait_time:
                        print(f"\n待機時間がタイムアウトしました({max_wait_time} 秒)。次のターンに移ります。")
                        break
                    await asyncio.sleep(0.2)
    
                print("\nこのターンの音声再生が完了しました!")
                turn_counter += 1
    
        except (asyncio.CancelledError, KeyboardInterrupt):
            print("\nプログラムが中断されました。")
        except Exception as e:
            print(f"予期せぬエラーが発生しました:{e}")
        finally:
            # ------------------- 4. リソースのクリーンアップ -------------------
            print("\n接続を閉じ、リソースをクリーンアップしています…")
            if message_handler_task and not message_handler_task.done():
                message_handler_task.cancel()
    
            if 'realtime_client' in locals() and realtime_client.ws and not realtime_client.ws.close:
                await realtime_client.close()
                print("接続を閉じました。")
    
            audio_player.stop()
            print("プログラムが終了しました。")
    
    
    if __name__ == "__main__":
        try:
            asyncio.run(interactive_test())
        except KeyboardInterrupt:
            print("\nユーザーによりプログラムが強制終了されました。")

    manual_mode.py を実行します。Enter キーを押して話しかけ、再度 Enter キーを押してモデルの音声応答を受信します。

インタラクションフロー

VAD モード

session.update イベントにおいて、session.turn_detection"server_vad" に設定することで、VAD モードを有効化します。このモードでは、サーバーが音声の開始および終了を自動検出し、それに応じて応答します。音声通話などのユースケースに適しています。

インタラクションフローは以下のとおりです:

  1. サーバーが音声の開始を検出し、input_audio_buffer.speech_started イベントを送信します。

  2. クライアントは任意のタイミングで、input_audio_buffer.append イベントおよび input_image_buffer.append イベントを送信し、オーディオおよびイメージをバッファーに追加できます。

    input_image_buffer.append イベントを送信する前に、少なくとも 1 回の input_audio_buffer.append イベントを送信する必要があります。
  3. サーバーが音声の終了を検出し、input_audio_buffer.speech_stopped イベントを送信します。

  4. サーバーがオーディオバッファーを確定するために、input_audio_buffer.committed イベントを送信します。

  5. サーバーが、バッファーから生成されたユーザーのメッセージ項目を含む conversation.item.created イベントを送信します。

ライフサイクル

クライアントイベント

サーバーイベント

セッション初期化

session.update

セッション構成

session.created

セッションが作成されました

session.updated

セッション構成が更新されました

ユーザー音声入力

input_audio_buffer.append

音声をバッファーに追加

input_image_buffer.append

イメージをバッファーに追加

input_audio_buffer.speech_started

音声の開始が検出されました

input_audio_buffer.speech_stopped

音声の終了が検出されました

input_audio_buffer.committed

サーバーが送信された音声を受信しました

サーバー音声出力

なし

response.created

サーバーが応答の生成を開始しました

response.output_item.added

応答中の新しい出力コンテンツ

conversation.item.created

会話項目が作成されました

response.content_part.added

アシスタントメッセージへの新しい出力コンテンツの追加

response.audio_transcript.delta

逐次生成される文字起こしテキスト

response.audio.delta

モデルから逐次生成される音声

response.audio_transcript.done

文字起こし処理が完了しました

response.audio.done

音声生成が完了しました

response.content_part.done

アシスタントメッセージのテキストまたは音声コンテンツのストリーミングが完了しました

response.output_item.done

アシスタントメッセージの出力項目全体のストリーミングが完了しました

response.done

応答が完了しました

手動モード

session.update イベントにおいて、session.turn_detectionnull に設定することで、手動モードを有効化します。このモードでは、クライアントが明示的に input_audio_buffer.commit イベントおよび response.create イベントを送信し、サーバーからの応答を要求します。プッシュ・トゥ・トーク(PTT)シナリオ(例:チャットアプリケーションにおける音声メッセージ送信)に適しています。

インタラクションフローは以下のとおりです:

  1. クライアントは任意のタイミングで、input_audio_buffer.append イベントおよび input_image_buffer.append イベントを送信し、オーディオおよびイメージをバッファーに追加できます。

    input_image_buffer.append イベントを送信する前に、少なくとも 1 回の input_audio_buffer.append イベントを送信する必要があります。
  2. クライアントが input_audio_buffer.commit イベントを送信し、音声およびイメージのバッファーを確定します。これにより、現在のターンにおけるすべてのユーザー入力(音声およびイメージ)が送信済みであることをサーバーに通知します。

  3. サーバーが input_audio_buffer.committed イベントで応答します。

  4. クライアントが response.create イベントを送信し、モデルの出力を待機します。

  5. サーバーが conversation.item.created イベントで応答します。

ライフサイクル

クライアントイベント

サーバーイベント

セッション初期化

session.update

セッション構成

session.created

セッションが作成されました

session.updated

セッション構成が更新されました

ユーザー音声入力

input_audio_buffer.append

音声をバッファーに追加

input_image_buffer.append

イメージをバッファーに追加

input_audio_buffer.commit

音声およびイメージをサーバーに送信

response.create

モデル応答を作成

input_audio_buffer.committed

サーバーが送信された音声を受信しました

サーバー音声出力

input_audio_buffer.clear

バッファー内の音声をクリア

response.created

サーバーが応答の生成を開始しました

response.output_item.added

応答中の新しい出力コンテンツ

conversation.item.created

会話項目が作成されました

response.content_part.added

アシスタントメッセージ項目への新しい出力コンテンツの追加

response.audio_transcript.delta

逐次生成される文字起こしテキスト

response.audio.delta

モデルから逐次生成される音声

response.audio_transcript.done

文字起こし処理が完了しました

response.audio.done

音声生成が完了しました

response.content_part.done

アシスタントメッセージのテキストまたは音声コンテンツのストリーミングが完了しました

response.output_item.done

アシスタントメッセージの出力項目全体のストリーミングが完了しました

response.done

応答が完了しました

Web 検索

Web 検索機能により、モデルはリアルタイムで取得したデータを用いて応答します。株価や天気予報など、最新の情報を必要とするシナリオにご利用ください。モデルがユーザーの質問に応じて Web 検索を実行するかどうかは、自動的に判断されます。

Qwen3.5-Omni-Realtime モデルのみが Web 検索をサポートしています。デフォルトでは無効化されています。session.update イベントを使用して有効化してください。
課金の詳細については、エージェント ポリシーを、課金ドキュメント

有効化方法

session.update イベントで、以下のパラメーターを追加します:

  • enable_search:Web 検索を有効化するには、true を設定します。

  • search_options.enable_source:検索結果のソース一覧を返すには、true を設定します。

パラメーターの詳細については、「session.update」をご参照ください。

応答フォーマット

Web 検索を有効化すると、response.done イベントの usage オブジェクトに新しい plugins フィールドが含まれるようになります。このフィールドには、検索の使用状況を示すメトリックが記録されます:

{
    "usage": {
        "total_tokens": 2937,
        "input_tokens": 2554,
        "output_tokens": 383,
        "input_tokens_details": {
            "text_tokens": 2512,
            "audio_tokens": 42
        },
        "output_tokens_details": {
            "text_tokens": 90,
            "audio_tokens": 293
        },
        "plugins": {
            "search": {
                "count": 1,
                "strategy": "agent"
            }
        }
    }
}

コード例

以下に、リアルタイム会話で Web 検索を有効化する方法を示します。

DashScope Python SDK

update_session 呼び出し時に、enable_search および search_options パラメーターを渡します:

import os
import base64
import time
import json
import pyaudio
from dashscope.audio.qwen_omni import MultiModality, AudioFormat, OmniRealtimeCallback, OmniRealtimeConversation
import dashscope

dashscope.api_key = os.getenv('DASHSCOPE_API_KEY')
url = 'wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
model = 'qwen3.5-omni-plus-realtime'
voice = 'Tina'

class SearchCallback(OmniRealtimeCallback):
    def __init__(self, pya):
        self.pya = pya
        self.out = None
    def on_open(self):
        self.out = self.pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True)
    def on_event(self, response):
        if response['type'] == 'response.audio.delta':
            self.out.write(base64.b64decode(response['delta']))
        elif response['type'] == 'conversation.item.input_audio_transcription.completed':
            print(f"[User] {response['transcript']}")
        elif response['type'] == 'response.audio_transcript.done':
            print(f"[LLM] {response['transcript']}")
        elif response['type'] == 'response.done':
            usage = response.get('response', {}).get('usage', {})
            plugins = usage.get('plugins', {})
            if plugins.get('search'):
                print(f"[Search] count={plugins['search']['count']}, strategy={plugins['search']['strategy']}")

pya = pyaudio.PyAudio()
callback = SearchCallback(pya)
conv = OmniRealtimeConversation(model=model, callback=callback, url=url)
conv.connect()
conv.update_session(
    output_modalities=[MultiModality.AUDIO, MultiModality.TEXT],
    voice=voice,
    instructions="You are Xiao Yun, a personal assistant",
    enable_search=True,
    search_options={'enable_source': True}
)
mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
print("Web 検索が有効化されました。マイクに向かって話してください(Ctrl+C で終了)...")
try:
    while True:
        audio_data = mic.read(3200, exception_on_overflow=False)
        conv.append_audio(base64.b64encode(audio_data).decode())
        time.sleep(0.01)
except KeyboardInterrupt:
    conv.close()
    mic.close()
    callback.out.close()
    pya.terminate()
    print("\n会話が終了しました")

DashScope Java SDK

updateSession で、parameters マップを通じて Web 検索設定を渡します:

import com.alibaba.dashscope.audio.omni.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class OmniSearch {
    static class SequentialAudioPlayer {
        private final SourceDataLine line;
        private final Queue<byte[]> audioQueue = new ConcurrentLinkedQueue<>();
        private final Thread playerThread;
        private final AtomicBoolean shouldStop = new AtomicBoolean(false);

        public SequentialAudioPlayer() throws LineUnavailableException {
            AudioFormat format = new AudioFormat(24000, 16, 1, true, false);
            line = AudioSystem.getSourceDataLine(format);
            line.open(format);
            line.start();
            playerThread = new Thread(() -> {
                while (!shouldStop.get()) {
                    byte[] audio = audioQueue.poll();
                    if (audio != null) {
                        line.write(audio, 0, audio.length);
                    } else {
                        try { Thread.sleep(10); } catch (InterruptedException ignored) {}
                    }
                }
            }, "AudioPlayer");
            playerThread.start();
        }

        public void play(String base64Audio) {
            audioQueue.add(Base64.getDecoder().decode(base64Audio));
        }
        public void close() {
            shouldStop.set(true);
            try { playerThread.join(1000); } catch (InterruptedException ignored) {}
            line.drain();
            line.close();
        }
    }

    public static void main(String[] args) {
        try {
            SequentialAudioPlayer player = new SequentialAudioPlayer();
            AtomicBoolean shouldStop = new AtomicBoolean(false);

            OmniRealtimeParam param = OmniRealtimeParam.builder()
                    .model("qwen3.5-omni-plus-realtime")
                    .apikey(System.getenv("DASHSCOPE_API_KEY"))
                    .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                    .build();

            OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() {
                @Override public void onOpen() {
                    System.out.println("接続が確立されました");
                }
                @Override public void onClose(int code, String reason) {
                    System.out.println("接続が終了しました");
                    shouldStop.set(true);
                }
                @Override public void onEvent(JsonObject event) {
                    String type = event.get("type").getAsString();
                    if ("response.audio.delta".equals(type)) {
                        player.play(event.get("delta").getAsString());
                    } else if ("response.audio_transcript.done".equals(type)) {
                        System.out.println("[LLM] " + event.get("transcript").getAsString());
                    } else if ("response.done".equals(type)) {
                        JsonObject response = event.getAsJsonObject("response");
                        if (response != null && response.has("usage")) {
                            JsonObject usage = response.getAsJsonObject("usage");
                            if (usage.has("plugins")) {
                                JsonObject plugins = usage.getAsJsonObject("plugins");
                                if (plugins.has("search")) {
                                    JsonObject search = plugins.getAsJsonObject("search");
                                    System.out.println("[Search] count=" + search.get("count").getAsInt()
                                            + ", strategy=" + search.get("strategy").getAsString());
                                }
                            }
                        }
                    }
                }
            });

            conversation.connect();
            conversation.updateSession(OmniRealtimeConfig.builder()
                    .modalities(Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT))
                    .voice("Tina")
                    .enableTurnDetection(true)
                    .enableInputAudioTranscription(true)
                    .parameters(Map.of(
                            "instructions", "You are Xiao Yun, a personal assistant",
                            "enable_search", true,
                            "search_options", Map.of("enable_source", true)
                    ))
                    .build()
            );

            System.out.println("Web 検索が有効化されました。話しかけてください(Ctrl+C で終了)...");
            AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
            TargetDataLine mic = AudioSystem.getTargetDataLine(format);
            mic.open(format);
            mic.start();

            ByteBuffer buffer = ByteBuffer.allocate(3200);
            while (!shouldStop.get()) {
                int bytesRead = mic.read(buffer.array(), 0, buffer.capacity());
                if (bytesRead > 0) {
                    conversation.appendAudio(Base64.getEncoder().encodeToString(buffer.array()));
                }
                Thread.sleep(20);
            }

            conversation.close(1000, "通常終了");
            player.close();
            mic.close();
        } catch (NoApiKeyException e) {
            System.err.println("API キーが見つかりません:環境変数 DASHSCOPE_API_KEY を設定してください");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

WebSocket(Python)

session.update の JSON ペイロードに、enable_search および search_options フィールドを追加します:

import json
import os
import websocket
import base64
import pyaudio
import threading

API_KEY = os.getenv("DASHSCOPE_API_KEY")
API_URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3.5-omni-plus-realtime"

pya = pyaudio.PyAudio()
out_stream = pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True)

def on_open(ws):
    ws.send(json.dumps({
        "type": "session.update",
        "session": {
            "modalities": ["text", "audio"],
            "voice": "Tina",
            "instructions": "You are Xiao Yun, a personal assistant",
            "input_audio_format": "pcm",
            "output_audio_format": "pcm",
            "enable_search": True,
            "search_options": {
                "enable_source": True
            }
        }
    }))
    print("Web 検索が有効化されました。マイクに向かって話してください...")
    def send_audio():
        mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
        try:
            while True:
                audio = mic.read(3200, exception_on_overflow=False)
                ws.send(json.dumps({
                    "type": "input_audio_buffer.append",
                    "audio": base64.b64encode(audio).decode()
                }))
        except Exception:
            mic.close()
    threading.Thread(target=send_audio, daemon=True).start()

def on_message(ws, message):
    event = json.loads(message)
    if event["type"] == "response.audio.delta":
        out_stream.write(base64.b64decode(event["delta"]))
    elif event["type"] == "response.audio_transcript.done":
        print(f"[LLM] {event['transcript']}")
    elif event["type"] == "response.done":
        usage = event.get("response", {}).get("usage", {})
        plugins = usage.get("plugins", {})
        if plugins.get("search"):
            print(f"[Search] count={plugins['search']['count']}, strategy={plugins['search']['strategy']}")

def on_error(ws, error):
    print(f"エラー: {error}")

headers = ["Authorization: Bearer " + API_KEY]
ws = websocket.WebSocketApp(API_URL, header=headers, on_open=on_open, on_message=on_message, on_error=on_error)
ws.run_forever()

関連 API

課金とレート制限

課金ルール

Qwen-Omni-Realtime モデルは、音声および画像という各モダリティにおけるトークン使用量に基づいて課金されます。詳細については、「モデル一覧」をご参照ください。

音声および画像のトークン換算ルール

音声

  • Qwen3.5-Omni-Realtime:総トークン数 = 音声の持続時間(秒単位) × 7

  • Qwen3-Omni-Flash-Realtime:総トークン数 = 音声の持続時間(秒単位) × 12.5

  • Qwen-Omni-Turbo-Realtime:総トークン数 = 音声の持続時間(秒単位) × 25。ただし、音声の持続時間が 1 秒未満の場合、1 秒として計算します。

画像

  • Qwen3.5-Omni-Plus-Realtime モデル:1 トークンあたり 32×32 ピクセル

  • Qwen3-Omni-Flash-Realtime モデル:1 トークンあたり 32×32 ピクセル

  • Qwen-Omni-Turbo-Realtime モデル:1 トークンあたり 28×28 ピクセル

画像は最低 4 トークンを必要とし、最大 1,280 トークンまでサポートします。以下のコードを使用して、画像が消費するトークン総数を概算できます:

# Pillow ライブラリを次のコマンドでインストールします:pip install Pillow
from PIL import Image
import math

# Qwen-Omni-Turbo-Realtime モデルの場合、ズーム係数は 28 です。
# factor = 28
# Qwen3-Omni-Flash-Realtime および Qwen3.5-Omni-Realtime モデルの場合、ズーム係数は 32 です。
factor = 32

def token_calculate(image_path='', duration=10):
    """
    :param image_path: イメージのパス。
    :param duration: 会話接続の持続時間。
    :return: イメージのトークン数。
    """
    if len(image_path) > 0:
        # 指定された PNG イメージファイルを開きます。
        image = Image.open(image_path)
        # イメージの元のディメンションを取得します。
        height = image.height
        width = image.width
        print(f"スケーリング前のイメージサイズ:height={height}, width={width}")
        # 高さをズーム係数の整数倍になるよう調整します。
        h_bar = round(height / factor) * factor
        # 幅をズーム係数の整数倍になるよう調整します。
        w_bar = round(width / factor) * factor
        # イメージトークンの下限:4 トークン。
        min_pixels = factor * factor * 4
        # イメージトークンの上限:1,280 トークン。
        max_pixels = 1280 * factor * factor
        # スケーリング後のピクセル総数が [min_pixels, max_pixels] の範囲内になるよう、イメージをスケーリングします。
        if h_bar * w_bar > max_pixels:
            # スケーリング後のイメージのピクセル総数が max_pixels を超えないよう、スケーリング係数 beta を計算します。
            beta = math.sqrt((height * width) / max_pixels)
            # 高さを再計算し、ズーム係数の整数倍になるよう調整します。
            h_bar = math.floor(height / beta / factor) * factor
            # 幅を再計算し、ズーム係数の整数倍になるよう調整します。
            w_bar = math.floor(width / beta / factor) * factor
        elif h_bar * w_bar < min_pixels:
            # スケーリング後のイメージのピクセル総数が min_pixels 未満にならないよう、スケーリング係数 beta を計算します。
            beta = math.sqrt(min_pixels / (height * width))
            # 高さを再計算し、ズーム係数の整数倍になるよう調整します。
            h_bar = math.ceil(height * beta / factor) * factor
            # 幅を再計算し、ズーム係数の整数倍になるよう調整します。
            w_bar = math.ceil(width * beta / factor) * factor
        print(f"スケーリング後のイメージサイズ:height={h_bar}, width={w_bar}")
        # イメージのトークン数を計算します:総ピクセル数 ÷ (factor × factor)。
        token = int((h_bar * w_bar) / (factor * factor))
        print(f"スケーリング後のトークン数:{token}")
        total_token = token * math.ceil(duration / 2)
        print(f"総トークン数:{total_token}")
        return total_token
    else:
        print("エラー:image_path が空です。トークン数を計算できません。")
        return 0

if __name__ == "__main__":
    total_token = token_calculate(image_path="xxx/test.jpg", duration=10)

レート制限

モデルのレート制限ルールについて詳しくは、「レート制限」をご参照ください。

エラーコード

モデルの呼び出しでエラーが発生し、エラーメッセージが返された場合は、解決方法については、「エラーメッセージ」をご参照ください。

音声一覧

voice リクエストパラメーターに、voice パラメーター 列の値を設定します。

qwen3.5-omni-realtime

voice パラメーター

詳細

対応言語

Tina

音声名: Tina

説明: 温かみのあるミルクティーのような声——甘く、居心地がよく、問題解決時には鋭さも兼ね備えています。

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Cindy

音声名: Cindy

説明: 台湾出身の、愛らしい話し方をする若い女性

中国語(台湾アクセント)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Liora Mira

音声名: Qinghuan Liora Mira

説明: 日常生活に温かみを織り込む、優しい声

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Sunnybobi

音声名: Sunnybobi

説明: 明るく、ちょっと空気を読めない近所の女の子

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Raymond

音声名: Lin Chuanye (Raymond)

説明: 声がはっきりとしており、出前が大好きな引きこもり

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Ethan

音声名: Chenxu Ethan

説明: 標準中国語(やや北部アクセントあり)。明るく、温かく、エネルギッシュで若々しい

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Theo Calm

音声名: Theo Calm

説明: 沈黙を通じて理解を伝え、言葉によって癒しを届ける

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Serena

音声名: Serena

説明: 優しい若い女性

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Harvey

音声名: Harvey

説明: 時間の重みを宿した声——深みがあり、まろやかで、コーヒーと古びた本の香りがする

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Maia

音声名: Maia

説明: 知性と優しさの融合

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Evan

音声名: Evan

説明: 大学生——若々しく、愛らしい

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Qiao

音声名: Qiao

説明: 単に可愛いだけではなく、表面は甘く、内面には個性が満ちている

中国語(台湾アクセント)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Momo

音声名: Momo

説明: いたずら好きで、あなたを元気づけるためにここにいます

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Wil

音声名: Wil

説明: 深セン出身の若者。香港・台湾アクセントで話します

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Angel

音声名: Tai Pu – An Qi Angel

説明: やや台湾アクセントあり——とても甘い声

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Li Cassian

音色名: Dongchang—Grand Eunuch Li Cassian

説明: 抑えた話し方——沈黙が三分、場の空気を読むのが七分

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Mia

音声名: Gentle Lifestyle Blogger - Shuran Mia

説明: 安らぎのある声で、ゆっくりとした暮らしの美学と日常の快適さを発信するライフスタイルアーティスト

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Joyner

音声名: Comedy Specialist—Adou Joyner

説明: 面白く、誇張され、そして地に足のついた話し方

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Gold

音声名: Gold

説明: 米国西海岸出身のブラックラッパー

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Katerina

音声名: Katerina

説明: 成熟した、威厳ある声。豊かなリズムと響きを持つ

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Ryan

音声名: Sweet Tea Ryan

説明: 高エネルギーなトーンと強いドラマチックな存在感——リアルさと迫力の融合

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Jennifer

音声名: Jennifer

説明: 高品質で、映画のような質感を持つアメリカ英語の女性ボイス

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Aiden

音声名: Aiden

説明: 料理に長けたアメリカの若者

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Mione

音声名: Mione

説明: 成熟した知的なイギリス出身の近所の女の子

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Sunny

音声名: Sichuan - Qing'er Sunny

説明: 四川出身の甘い女の子。あなたの心を温めてくれます

中国語(四川方言)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Dylan

音声名: Beijing–Xiaodong (Dylan)

説明: 北京の胡同で育った若者

中国語(北京方言)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Eric

音声名: Sichuan - Cheng Chuan Eric

説明: 四川成都出身の活気あふれる男性

中国語(四川方言)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Peter

音声名: Tianjin–Li Bide (Peter)

説明: 天津風の相声(シャンション)のプロ。サポート役の達人

中国語(天津方言)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Joseph Chen

音声名: Joseph Chen

説明: 私はプーおじさんです。本名は陳志甫——東南アジア在住の華僑です

中国語(閩南語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Marcus

音色名: Shaanxi–Qinchuan Marcus

説明: 顔は広く、口数は少なく、心は誠実、声は低く——陝西省の真髄そのもの

中国語(陝西方言)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Li

音声名: Nanjing–Lao Li

説明: 怒りっぽいおじさん

中国語(南京方言)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Rocky

音声名: Cantonese – Ah Qiang Rocky

説明: アー・チャンは、ユーモアと機知をもってオンラインチャットでの会話をお手伝いします。

中国語(広東語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Sohee

音声名: Sohee

説明: 温かく、明るく、感情表現豊かな韓国の姉さん

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Lenn

音声名: Lenn

説明: 核心は合理的だが、細部では反骨精神に富む——スーツを着てポストパンクを聴くドイツの若者

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Ono Anna

音声名: Ono Anna

説明: 賢くて、遊び心のある幼なじみ

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Sonrisa

音声名: Sonrisa

説明: 温かく、陽気なラテンアメリカ出身の女性

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Bodega

音色名: Bodega

説明: 温かく、情熱的なスペイン人の男性

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Emilien

音声名: Emilien

説明: ロマンチックなフランス人の兄貴

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Andre

音声名: Andre

説明: 魅力的で、自然で、安定感のある男性の声

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Radio Gol

音声名: Radio Gol

説明: 私はRádio Golのフットボール詩人です! 今日は、選手の名前のみを使って試合を実況します。

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Alek

音声名: Alek

説明: ロシアの精神のように冷たく——しかし、コートの裏地のように暖かい

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Rizky

音声名: Rizky

説明: 特徴的な声を持つ若いインドネシア人男性

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Roya

音声名: Roya

説明: スポーティで、自由奔放な心を持つ女の子

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Arda

音声名: Arda

説明: 高くも低くもなく——クリアで、シャープ、そしてほんのり温かみがある

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Hana

音声名: Hana

説明: 犬が大好きなベトナム出身の成熟した女性

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Dolce

トーン名: Dolce

説明: のんびり屋のイタリア人男性

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Jakub

音声名: Jakub

説明: ポーランドの町から来た、カリスマと芸術性を兼ね備えた若者

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Griet

音声名: Griet

説明: 成熟した芸術性のあるオランダ出身の女性

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Eliška

音声名: Eliška

説明: すべての言葉に中欧の職人技と温かみが込められています

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Marina

音声名: Marina

説明: 多文化都市で育った女の子

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Siiri

音声名: Siiri

説明: 控えめで優しく——静かで湖のような話し方のペース

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Ingrid

音声名: Ingrid

説明: ノルウェーの田舎出身の女性

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Sigga

音声名: Sigga

説明: アイスランドの町から来た、知的な若い女性

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Bea

音声名: Bea

説明: コーヒーが大好きなフィリピン出身の甘い女性

中国語(標準中国語)、中国語、英語

フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

Chloe

音声名: Chloe

説明: マレーシアのオフィスワーカー

中国語(標準中国語)、中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語、タイ語、インドネシア語、アラビア語、ベトナム語、トルコ語、フィンランド語、ポーランド語、ヒンディー語、オランダ語、チェコ語、ウルドゥー語、タガログ語、スウェーデン語、デンマーク語、ヘブライ語、アイスランド語、マレー語、ノルウェー語、ペルシャ語

qwen3-omni-flash-realtime-2025-12-01

音声名

voiceパラメーター

音色効果

説明

対応言語

Qianyue

Cherry

明るく、前向きで、フレンドリーかつ自然な若い女性

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Suyao

Serena

優しい若い女性

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Chenxu

Ethan

標準中国語(やや北部アクセントあり)。明るく、温かく、エネルギッシュで、躍動感がある

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Qianxue

Chelsie

二次元のバーチャルガールフレンド

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Motu

Momo

いたずら好きで、あなたを元気づけるように設計された声

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

13

Vivian

生意気で、かわいく、ちょっとイライラしている若い女性

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Yuebai

Moon

即興的で、ハンサムな岳白

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

April

Maia

知性と優しさの融合

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Kai

Kai

耳に優しい体験

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

魚を食べないでください。

Nofish

巻き舌音を発音できないデザイナー

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Mengbao

Bella

酔っ払っても、決して乱暴なパンチをしない小さな女の子

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Jennifer

Jennifer

高品質で、映画のような質感を持つアメリカ英語の女性ボイス

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Tiancha

Ryan

リズム感に富み、劇的な魅力に満ちており、リアルさと迫力を両立させる

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Kajielina

Katerina

豊かで、印象に残るリズムを持つ成熟した女性の声

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Aideng

Aiden

アメリカ英語を話す料理が得意な若い男性

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Cangmingzi

Eldric Sage

落ち着いて賢い長老の声。松の木のしなやかさと、澄んだ心の明晰さを想起させる

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

良い妹

Mia

春の水のように柔らかく、新雪のように素直

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Sha Xiaomi

Mochi

賢く、明るい大人。子供のような無邪気さと、年齢を超えた知性を併せ持つ

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Yan Zhengying

Bellona

力強く、はっきりとした声で、登場人物を生き生きと描き、 excitement を呼び起こす。

英雄譚や鮮烈な声の表現を想起させる

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Tianshu

Vincent

独特のガラガラで、スモーキーな声で、軍隊と騎士道の物語を語る

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Meng Xiao Ji

Bunny

「かわいい」魅力が溢れる小さな女の子

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

A Wen

Neil

プロフェッショナルなニュースアナウンサー。基調となるトーンは安定しており、発音は正確

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Mo Lecturer

Elias

学術的厳密性を維持しつつ、物語を通して複雑な知識を消化しやすい認知モジュールへと変換する

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Xu Da Ye

Arthur

シンプルで、歳を重ねた声。時間とタバコの煙に染まり、ゆっくりと村の伝説や不思議な話を語る

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Linjia Meimei

Nini

もちもちした甘い声。伸ばした「お兄ちゃん」は、骨まで溶けてしまうほど甘い

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Gui Po Po

Ebona

囁きは錆びた鍵のよう。あなたの内なる闇の最も奥深く——子供時代の影と未知の恐怖が隠れている場所——をゆっくりと開く

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Xiao Wan

Seren

安らぎを与える優しい声で、より早く眠りにつくお手伝いをします。おやすみなさい、良い夢を

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

いたずらっ子

Pip

遊び心といたずら心に富みながらも、純粋無垢な男の子——シンちゃんを思い出させませんか?

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Shao Nü A Yue

Stella

普段は甘ったるく、ぼんやりとした十代の女の子の声ですが、「私は月を代表して君を倒す!」と叫ぶと、愛と正義に満ちた声になる

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Bodega

Bodega

情熱的なスペイン人の男性

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Sonisha

Sonrisa

明るく、陽気なラテンアメリカ出身の女性

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Aleke

Alek

ロシアの精神のように冷たく——そしてウールのコートの裏地のように暖かい声

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Duorche

Dolce

のんびり屋のイタリア人男性

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Suxi

Sohee

親切で、明るく、感情表現豊かな韓国の姉さん

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Xiao Ye Xing

Ono Anna

賢く、活気のある幼なじみ

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Lai En

Lenn

核は合理的だが、細部では反骨精神に富む——スーツを着てポストパンクを聴くドイツの若者

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Aimi’er’an

Emilien

ロマンチックなフランス人の兄貴

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Andele

Andre

魅力的で、自然で、安定感のある男性の声

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Ladio Ge Er

Radio Gol

フットボール詩人Rádio Gol! 今日の試合実況は、私の名前だけで行います。

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

上海 – A Zhen

Jada

キビキビとしてエネルギッシュな上海のおばさん

上海語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

北京 – Xiao Dong

Dylan

北京の胡同で育った若者

北京方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

南京 – Lao Li

Li

忍耐強いヨガインストラクター

南京方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

陝西 – Qin Chuan

Marcus

顔は広く、口数は少なく、心は誠実、声は低く——本格的な陝西省の味わい

陝西方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

閩南 – A Jie

Roy

ユーモアと率直さ、活気と地に足のついた話し方をする台湾出身の男性

閩南語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

天津 – Li Peter

Peter

天津の相声(シャンション)のプロ。サポート役の達人

天津方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

四川 – Qing Er

Sunny

四川出身の甘い女の子。あなたの心を温めてくれます

四川方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

四川 – Cheng Chuan

Eric

群衆から目立つ、成都出身の四川人

四川方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

広東語 – A Qiang

Rocky

ユーモアと機知に富むアー・チャン。オンラインチャットでお手伝いします

広東語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

広東語 – A Qing

Kiki

甘い香港出身の女の子。親しい友達

広東語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

qwen3-omni-flash-realtime、qwen3-omni-flash-realtime-2025-09-15

音声名

voiceパラメーター

音色効果

説明

対応言語

Qianyue

Cherry

明るく、前向きで、フレンドリーかつ自然な若い女性

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Chenxu

Ethan

標準中国語(やや北部アクセントあり)。明るく、温かく、エネルギッシュで、躍動感がある

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

魚を食べないでください。

Nofish

巻き舌音を発音できないデザイナー

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Jennifer

Jennifer

高品質で、映画のような質感を持つアメリカ英語の女性ボイス

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Tiancha

Ryan

リズム感に富み、劇的な魅力に満ちており、リアルさと迫力を両立させる

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Kajielina

Katerina

豊かで、印象に残るリズムを持つ成熟した女性の声

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Mo Lecturer

Elias

学術的厳密性を維持しつつ、物語を通して複雑な知識を消化しやすい認知モジュールへと変換する

中国語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

上海 – A Zhen

Jada

キビキビとしてエネルギッシュな上海のおばさん

上海語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

北京 – Xiao Dong

Dylan

北京の胡同で育った若者

北京方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

四川 – Qing Er

Sunny

四川出身の甘い女の子。あなたの心を温めてくれます

四川方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

南京 – Lao Li

Li

忍耐強いヨガインストラクター

南京方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

陝西 – Qin Chuan

Marcus

顔は広く、口数は少なく、心は誠実、声は低く——本格的な陝西省の味わい

陝西方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

閩南 – A Jie

Roy

ユーモアと率直さ、活気と地に足のついた話し方をする台湾出身の男性

閩南語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

天津 – Li Peter

Peter

天津の相声(シャンション)のプロ。サポート役の達人

天津方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

広東語 – A Qiang

Rocky

ユーモアと機知に富むアー・チャン。オンラインチャットでお手伝いします

広東語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

広東語 – A Qing

Kiki

甘い香港出身の女の子。親しい友達

広東語、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

四川 – チェン・チュアン

Eric

群衆から目立つ、成都出身の四川人

四川方言、英語、フランス語、ドイツ語、ロシア語、イタリア語、スペイン語、ポルトガル語、日本語、韓国語

Qwen-Omni-Turbo-Realtime

音声名

voiceパラメーター

音色効果

説明

対応言語

Qianyue

Cherry

明るく、前向きで、フレンドリーかつ自然な若い女性

中国語、英語

Suyao

Serena

優しい若い女性

中国語、英語

Chenxu

Ethan

標準中国語(やや北部アクセントあり)。明るく、温かく、エネルギッシュで、躍動感がある

中国語、英語

Qianxue

Chelsie

二次元のバーチャルガールフレンド

中国語、英語