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

Alibaba Cloud Model Studio:リアルタイム音声合成 - Qwen

最終更新日:Mar 01, 2026

Qwen のリアルタイム音声合成モデルは、ストリーミング形式のテキスト入力とオーディオ出力をサポートします。複数の臨場感あふれる音声オプションを提供し、多言語および方言の合成に対応。同一音声で複数言語の出力が可能であり、イントネーションを自動調整し、複雑なテキストもスムーズに処理します。

主な機能

  • 中国語や英語など複数言語で自然な発音を実現した、高忠実度のリアルタイム音声出力を生成します。

  • 音声カスタマイズには、ボイスクローン(Qwen) および ボイスデザイン(Qwen) の 2 つの方法を提供します。

  • リアルタイム対話シナリオ向けに、低遅延のストリーミング入出力をサポートします。

  • 話速、ピッチ、音量、ビットレートを調整可能で、音声表現を細かく制御できます。

  • 主要なオーディオフォーマットに対応し、最大 48 kHz のサンプルレートでの出力が可能です。

  • 命令制御 をサポートしており、自然言語による命令で音声の表現力を制御できます。

可用性

サポートされるモデル:

国際

国際デプロイメントモード」では、アクセスポイントおよびデータストレージの両方が シンガポール リージョンに配置されます。モデル推論の計算リソースは、中国本土を除くグローバル範囲で動的にスケジュールされます。

以下のモデルを呼び出す際は、シンガポール リージョンの API キー を選択してください:

  • Qwen3-TTS-Instruct-Flash-Realtime:qwen3-tts-instruct-flash-realtime(安定版、qwen3-tts-instruct-flash-realtime-2026-01-22 と同等)、qwen3-tts-instruct-flash-realtime-2026-01-22(最新スナップショット)

  • Qwen3-TTS-VD-Realtime:qwen3-tts-vd-realtime-2026-01-15(最新スナップショット)、qwen3-tts-vd-realtime-2025-12-16(スナップショット)

  • Qwen3-TTS-VC-Realtime:qwen3-tts-vc-realtime-2026-01-15(最新スナップショット)、qwen3-tts-vc-realtime-2025-11-27(スナップショット)

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime(安定版、qwen3-tts-flash-realtime-2025-11-27 と同等)、qwen3-tts-flash-realtime-2025-11-27(最新スナップショット)、qwen3-tts-flash-realtime-2025-09-18 (スナップショット)

中国本土

中国本土デプロイメントモード」では、アクセスポイントおよびデータストレージの両方が 中国 (北京) リージョンに配置されます。モデル推論の計算リソースは中国本土内に限定されます。

以下のモデルを呼び出す際は、中国 (北京) リージョンの API キーを選択してください:API キー

  • Qwen3-TTS-Instruct-Flash-Realtime:qwen3-tts-instruct-flash-realtime(安定版、qwen3-tts-instruct-flash-realtime-2026-01-22 と同等)、qwen3-tts-instruct-flash-realtime-2026-01-22(最新スナップショット)

  • Qwen3-TTS-VD-Realtime:qwen3-tts-vd-realtime-2026-01-15(最新スナップショット)、qwen3-tts-vd-realtime-2025-12-16(スナップショット)

  • Qwen3-TTS-VC-Realtime:qwen3-tts-vc-realtime-2026-01-15(最新スナップショット)、qwen3-tts-vc-realtime-2025-11-27(スナップショット)

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime(安定版、qwen3-tts-flash-realtime-2025-11-27 と同等)、qwen3-tts-flash-realtime-2025-11-27(最新スナップショット)、qwen3-tts-flash-realtime-2025-09-18 (スナップショット)

  • Qwen-TTS-Realtime:qwen-tts-realtime (安定版、qwen-tts-realtime-2025-07-15と同等)、qwen-tts-realtime-latest(最新版、qwen-tts-realtime-2025-07-15 と同等)、qwen-tts-realtime-2025-07-15 (スナップショット)

詳細については、「モデル一覧」をご参照ください。

モデル選択ガイド

シナリオ

推奨モデル

理由

ブランドアイデンティティ、専用音声、または拡張システム音声のための音声カスタマイズ(テキスト記述に基づく)

qwen3-tts-vd-realtime-2026-01-15

ボイスデザインをサポート。音声サンプルを必要とせず、テキスト記述からカスタマイズされた音声を作成できます。ブランド専用音声をゼロから設計するのに最適です。

ブランドアイデンティティ、専用音声、または拡張システム音声のための音声カスタマイズ(音声サンプルに基づく)

qwen3-tts-vc-realtime-2026-01-15

ボイスクローンをサポート。実際の音声サンプルから音声を迅速に再現し、高忠実度かつ一貫性のあるブランドボイスプリントを作成できます。

感情的なコンテンツ制作(オーディオブック、ラジオドラマ、ゲーム/アニメの吹き替え)

qwen3-tts-instruct-flash-realtime

命令制御をサポート。自然言語による説明でトーン、話速、感情、キャラクターの個性を正確に制御できます。豊かな表現力とキャラクター構築を要するシナリオに最適です。

プロフェッショナルな放送(ニュース、ドキュメンタリー、広告)

qwen3-tts-instruct-flash-realtime

命令制御をサポート。「権威的で厳粛」や「カジュアルでフレンドリー」などの放送スタイルおよびトーン特性を説明できます。プロフェッショナルなコンテンツ制作に適しています。

インテリジェントカスタマーサービスおよび会話型ボット

qwen3-tts-flash-realtime、qwen3-tts-instruct-flash-realtime

ストリーミング入出力をサポートし、話速およびピッチを調整可能です。命令制御対応バージョン(instruct)では、会話の文脈に応じてトーン(安心感を与える、熱意ある、プロフェッショナルなど)を動的に調整できます。

多言語コンテンツの放送

qwen3-tts-flash-realtime、qwen3-tts-instruct-flash-realtime

複数言語および中国語の方言をサポートし、グローバルなコンテンツ配信ニーズに対応します。

オーディオブックの朗読および一般コンテンツ制作

qwen3-tts-flash-realtime、qwen3-tts-instruct-flash-realtime

音量、話速、ピッチを調整可能で、オーディオブック、ポッドキャストなど、細かい制作要件に対応できます。

E コマースライブストリーミングおよび短尺動画の吹き替え

qwen3-tts-flash-realtime、qwen3-tts-instruct-flash-realtime

mp3/opus 圧縮フォーマットをサポートし、帯域幅が制限されたシナリオに適しています。

詳細については、「機能比較」をご参照ください。

クイックスタート

API キーの取得 および 最新版 DashScope SDK のインストール を、コード実行前に完了してください。

システム音声の使用

以下の例では、システム音声(「サポートされる音声」を参照)を用いた音声合成を行います。

model パラメーターを qwen3-tts-instruct-flash-realtime に置き換え、instructions パラメーターを設定することで、命令制御 機能を利用できます。

DashScope SDK

Python

サーバーコミットモード

import os
import base64
import threading
import time
import dashscope
from dashscope.audio.qwen_tts_realtime import *


qwen_tts_realtime: QwenTtsRealtime = None
text_to_synthesize = [
    'そうですね?こんなスーパーが大好きです。',
    '特に旧正月の時期には、',
    'スーパーでお買い物に行きます。',
    'そして私は、',
    'とてもワクワクします!',
    'たくさん買いたいものがたくさんあります!'
]

DO_VIDEO_TEST = False

def init_dashscope_api_key():
    """
        DashScope API キーを設定します。詳細については、以下を参照してください:
        https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
    """

    # API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    if 'DASHSCOPE_API_KEY' in os.environ:
        dashscope.api_key = os.environ[
            'DASHSCOPE_API_KEY']  # 環境変数 DASHSCOPE_API_KEY から API キーを読み込みます
    else:
        dashscope.api_key = 'your-dashscope-api-key'  # 手動で API キーを設定します



class MyCallback(QwenTtsRealtimeCallback):
    def __init__(self):
        self.complete_event = threading.Event()
        self.file = open('result_24k.pcm', 'wb')

    def on_open(self) -> None:
        print('接続が確立されました。プレーヤーを初期化します。')

    def on_close(self, close_status_code, close_msg) -> None:
        self.file.close()
        print('接続が終了しました。コード:{}、メッセージ:{}、プレーヤーを破棄します。'.format(close_status_code, close_msg))

    def on_event(self, response: str) -> None:
        try:
            global qwen_tts_realtime
            type = response['type']
            if 'session.created' == type:
                print('セッション開始:{}'.format(response['session']['id']))
            if 'response.audio.delta' == type:
                recv_audio_b64 = response['delta']
                self.file.write(base64.b64decode(recv_audio_b64))
            if 'response.done' == type:
                print(f'応答 {qwen_tts_realtime.get_last_response_id()} が完了しました。')
            if 'session.finished' == type:
                print('セッションが終了しました。')
                self.complete_event.set()
        except Exception as e:
            print('[エラー] {}'.format(e))
            return

    def wait_for_finished(self):
        self.complete_event.wait()


if __name__  == '__main__':
    init_dashscope_api_key()

    print('初期化中 ...')

    callback = MyCallback()

    qwen_tts_realtime = QwenTtsRealtime(
        # 命令制御を使用するには、model を qwen3-tts-instruct-flash-realtime に置き換えてください。
        model='qwen3-tts-flash-realtime',
        callback=callback,
        # この URL はシンガポールリージョン用です。北京リージョンを使用する場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
        url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
        )

    qwen_tts_realtime.connect()
    qwen_tts_realtime.update_session(
        voice = 'Cherry',
        response_format = AudioFormat.PCM_24000HZ_MONO_16BIT,
        # 命令制御を使用するには、以下の行のコメントを解除し、model を qwen3-tts-instruct-flash-realtime に置き換えてください。
        # instructions='ファッション製品の紹介に適した、上昇するイントネーションで素早く話す。',
        # optimize_instructions=True,
        mode = 'server_commit'        
    )
    for text_chunk in text_to_synthesize:
        print(f'テキストを送信:{text_chunk}')
        qwen_tts_realtime.append_text(text_chunk)
        time.sleep(0.1)
    qwen_tts_realtime.finish()
    callback.wait_for_finished()
    print('[メトリクス] セッション:{}、初回音声遅延:{}'.format(
                    qwen_tts_realtime.get_session_id(), 
                    qwen_tts_realtime.get_first_audio_delay(),
                    ))

コミットモード

import base64
import os
import threading
import dashscope
from dashscope.audio.qwen_tts_realtime import *


qwen_tts_realtime: QwenTtsRealtime = None
text_to_synthesize = [
    'これは最初の文です。',
    'これは二番目の文です。',
    'これは三番目の文です。',
]

DO_VIDEO_TEST = False

def init_dashscope_api_key():
    """
        DashScope API キーを設定します。詳細については、以下を参照してください:
        https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
    """

    # API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    if 'DASHSCOPE_API_KEY' in os.environ:
        dashscope.api_key = os.environ[
            'DASHSCOPE_API_KEY']  # 環境変数 DASHSCOPE_API_KEY から API キーを読み込みます
    else:
        dashscope.api_key = 'your-dashscope-api-key'  # 手動で API キーを設定します



class MyCallback(QwenTtsRealtimeCallback):
    def __init__(self):
        super().__init__()
        self.response_counter = 0
        self.complete_event = threading.Event()
        self.file = open(f'result_{self.response_counter}_24k.pcm', 'wb')

    def reset_event(self):
        self.response_counter += 1
        self.file = open(f'result_{self.response_counter}_24k.pcm', 'wb')
        self.complete_event = threading.Event()

    def on_open(self) -> None:
        print('接続が確立されました。プレーヤーを初期化します。')

    def on_close(self, close_status_code, close_msg) -> None:
        print('接続が終了しました。コード:{}、メッセージ:{}、プレーヤーを破棄します。'.format(close_status_code, close_msg))

    def on_event(self, response: str) -> None:
        try:
            global qwen_tts_realtime
            type = response['type']
            if 'session.created' == type:
                print('セッション開始:{}'.format(response['session']['id']))
            if 'response.audio.delta' == type:
                recv_audio_b64 = response['delta']
                self.file.write(base64.b64decode(recv_audio_b64))
            if 'response.done' == type:
                print(f'応答 {qwen_tts_realtime.get_last_response_id()} が完了しました。')
                self.complete_event.set()
                self.file.close()
            if 'session.finished' == type:
                print('セッションが終了しました。')
                self.complete_event.set()
        except Exception as e:
            print('[エラー] {}'.format(e))
            return

    def wait_for_response_done(self):
        self.complete_event.wait()


if __name__  == '__main__':
    init_dashscope_api_key()

    print('初期化中 ...')

    callback = MyCallback()

    qwen_tts_realtime = QwenTtsRealtime(
        # 命令制御を使用するには、model を qwen3-tts-instruct-flash-realtime に置き換えてください。
        model='qwen3-tts-flash-realtime',
        callback=callback, 
        # この URL はシンガポールリージョン用です。北京リージョンを使用する場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
        url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
        )

    qwen_tts_realtime.connect()
    qwen_tts_realtime.update_session(
        voice = 'Cherry',
        response_format = AudioFormat.PCM_24000HZ_MONO_16BIT,
        # 命令制御を使用するには、以下の行のコメントを解除し、model を qwen3-tts-instruct-flash-realtime に置き換えてください。
        # instructions='ファッション製品の紹介に適した、上昇するイントネーションで素早く話す。',
        # optimize_instructions=True,
        mode = 'commit'        
    )
    print(f'テキストを送信:{text_to_synthesize[0]}')
    qwen_tts_realtime.append_text(text_to_synthesize[0])
    qwen_tts_realtime.commit()
    callback.wait_for_response_done()
    callback.reset_event()
    
    print(f'テキストを送信:{text_to_synthesize[1]}')
    qwen_tts_realtime.append_text(text_to_synthesize[1])
    qwen_tts_realtime.commit()
    callback.wait_for_response_done()
    callback.reset_event()

    print(f'テキストを送信:{text_to_synthesize[2]}')
    qwen_tts_realtime.append_text(text_to_synthesize[2])
    qwen_tts_realtime.commit()
    callback.wait_for_response_done()
    
    qwen_tts_realtime.finish()
    print('[メトリクス] セッション:{}、初回音声遅延:{}'.format(
                    qwen_tts_realtime.get_session_id(), 
                    qwen_tts_realtime.get_first_audio_delay(),
                    ))

Java

サーバーコミットモード

import com.alibaba.dashscope.audio.qwen_tts_realtime.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.AudioSystem;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Base64;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class Main {
    static String[] textToSynthesize = {
            "そうですね?こんなスーパーが大好きです。",
            "特に旧正月の時期には、",
            "スーパーでお買い物に行きます。",
            "そして私は、",
            "とてもワクワクします!",
            "たくさん買いたいものがたくさんあります!"
    };

    // リアルタイム PCM オーディオプレーヤークラス
    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();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException, LineUnavailableException, FileNotFoundException {
        QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder()
                // 命令制御機能を使用するには、model を qwen3-tts-instruct-flash-realtime に置き換えてください。
                .model("qwen3-tts-flash-realtime")
                // 以下の URL はシンガポールリージョン用です。中国 (北京) リージョンのモデルを使用する場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
                .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                // シンガポールおよび中国 (北京) リージョンの API キーは異なります。API キーの取得先:https://www.alibabacloud.com/help/en/model-studio/get-api-key。
                .apikey(System.getenv("DASHSCOPE_API_KEY"))
                .build();
        AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));
        final AtomicReference<QwenTtsRealtime> qwenTtsRef = new AtomicReference<>(null);

        // リアルタイムオーディオプレーヤーインスタンスを作成します。
        RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);

        QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
            @Override
            public void onOpen() {
                // 接続確立時のイベントを処理します。
            }
            @Override
            public void onEvent(JsonObject message) {
                String type = message.get("type").getAsString();
                switch(type) {
                    case "session.created":
                        // セッション作成時のイベントを処理します。
                        break;
                    case "response.audio.delta":
                        String recvAudioB64 = message.get("delta").getAsString();
                        // オーディオをリアルタイムで再生します。
                        audioPlayer.write(recvAudioB64);
                        break;
                    case "response.done":
                        // 応答完了時のイベントを処理します。
                        break;
                    case "session.finished":
                        // セッション終了時のイベントを処理します。
                        completeLatch.get().countDown();
                    default:
                        break;
                }
            }
            @Override
            public void onClose(int code, String reason) {
                // 接続終了時のイベントを処理します。
            }
        });
        qwenTtsRef.set(qwenTtsRealtime);
        try {
            qwenTtsRealtime.connect();
        } catch (NoApiKeyException e) {
            throw new RuntimeException(e);
        }
        QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
                .voice("Cherry")
                .responseFormat(QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT)
                .mode("server_commit")
                // 命令制御機能を使用するには、以下の行のコメントを解除し、model を qwen3-tts-instruct-flash-realtime に置き換えてください。
                // .instructions("")
                // .optimizeInstructions(true)
                .build();
        qwenTtsRealtime.updateSession(config);
        for (String text:textToSynthesize) {
            qwenTtsRealtime.appendText(text);
            Thread.sleep(100);
        }
        qwenTtsRealtime.finish();
        completeLatch.get().await();
        qwenTtsRealtime.close();

        // オーディオ再生が完了するのを待ってからプレーヤーをシャットダウンします。
        audioPlayer.waitForComplete();
        audioPlayer.shutdown();
        System.exit(0);
    }
}

コミットモード

import com.alibaba.dashscope.audio.qwen_tts_realtime.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.AudioSystem;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class commit {
    // リアルタイム PCM オーディオプレーヤークラス
    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();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException, LineUnavailableException, FileNotFoundException {
        Scanner scanner = new Scanner(System.in);

        QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder()
                // 命令制御機能を使用するには、model を qwen3-tts-instruct-flash-realtime に置き換えてください。
                .model("qwen3-tts-flash-realtime")
                // 以下の URL はシンガポールリージョン用です。中国 (北京) リージョンのモデルを使用する場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
                .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                // シンガポールおよび中国 (北京) リージョンの API キーは異なります。API キーの取得先:https://www.alibabacloud.com/help/en/model-studio/get-api-key。
                .apikey(System.getenv("DASHSCOPE_API_KEY"))
                .build();

        AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));

        // リアルタイムプレーヤーインスタンスを作成します。
        RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);

        final AtomicReference<QwenTtsRealtime> qwenTtsRef = new AtomicReference<>(null);
        QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
//            File file = new File("result_24k.pcm");
//            FileOutputStream fos = new FileOutputStream(file);
            @Override
            public void onOpen() {
                System.out.println("接続が確立されました。");
                System.out.println("テキストを入力して Enter キーを押すと送信されます。「quit」と入力するとプログラムを終了します。");
            }
            @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 "response.audio.delta":
                        String recvAudioB64 = message.get("delta").getAsString();
                        byte[] rawAudio = Base64.getDecoder().decode(recvAudioB64);
                        //                            fos.write(rawAudio);
                        // オーディオをリアルタイムで再生します。
                        audioPlayer.write(recvAudioB64);
                        break;
                    case "response.done":
                        System.out.println("応答が完了しました。");
                        // オーディオ再生が完了するのを待機します。
                        try {
                            audioPlayer.waitForComplete();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        // 次の入力の準備をします。
                        completeLatch.get().countDown();
                        break;
                    case "session.finished":
                        System.out.println("セッションが終了しました。");
                        if (qwenTtsRef.get() != null) {
                            System.out.println("[メトリクス] 応答:{}、初回音声遅延:{} ms".formatted(qwenTtsRef.get().getResponseId(), qwenTtsRef.get().getFirstAudioDelay()));
                        }
                        completeLatch.get().countDown();
                    default:
                        break;
                }
            }
            @Override
            public void onClose(int code, String reason) {
                System.out.println("接続が終了しました。コード:{}、理由:{}".formatted(code, reason));
                try {
//                    fos.close();
                    // 再生が完了するのを待ってからプレーヤーをシャットダウンします。
                    audioPlayer.waitForComplete();
                    audioPlayer.shutdown();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        qwenTtsRef.set(qwenTtsRealtime);
        try {
            qwenTtsRealtime.connect();
        } catch (NoApiKeyException e) {
            throw new RuntimeException(e);
        }
        QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
                .voice("Cherry")
                .responseFormat(QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT)
                .mode("commit")
                // 命令制御機能を使用するには、以下の行のコメントを解除し、model を qwen3-tts-instruct-flash-realtime に置き換えてください。
                // .instructions("")
                // .optimizeInstructions(true)
                .build();
        qwenTtsRealtime.updateSession(config);

        // ユーザー入力をループで読み取ります。
        while (true) {
            System.out.print("合成するテキストを入力してください:");
            String text = scanner.nextLine();

            // ユーザーが「quit」と入力した場合、プログラムを終了します。
            if ("quit".equalsIgnoreCase(text.trim())) {
                System.out.println("接続を終了しています...");
                qwenTtsRealtime.finish();
                completeLatch.get().await();
                break;
            }

            // ユーザー入力が空の場合、スキップします。
            if (text.trim().isEmpty()) {
                continue;
            }

            // カウントダウンラッチを再初期化します。
            completeLatch.set(new CountDownLatch(1));

            // テキストを送信します。
            qwenTtsRealtime.appendText(text);
            qwenTtsRealtime.commit();

            // 現在の合成が完了するのを待機します。
            completeLatch.get().await();
        }

        // リソースをクリーンアップします。
        audioPlayer.waitForComplete();
        audioPlayer.shutdown();
        scanner.close();
        System.exit(0);
    }
}

WebSocket API

  1. ランタイム環境の準備

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

    macOS

    brew install portaudio && pip install pyaudio

    Debian/Ubuntu

    sudo apt-get install python3-pyaudio
    
    または
    
    pip install pyaudio

    CentOS

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

    Windows

    pip install pyaudio

    次に、pip を使用して WebSocket 依存関係をインストールします:

    pip install websocket-client==1.8.0 websockets
  2. クライアントの作成

    ローカルに tts_realtime_client.py という名前の新しい Python ファイルを作成し、以下のコードをファイルにコピーしてください:

    tts_realtime_client.py

    # -- coding: utf-8 --
    
    import asyncio
    import websockets
    import json
    import base64
    import time
    from typing import Optional, Callable, Dict, Any
    from enum import Enum
    
    
    class SessionMode(Enum):
        SERVER_COMMIT = "server_commit"
        COMMIT = "commit"
    
    
    class TTSRealtimeClient:
        """
        TTS Realtime API と連携するためのクライアント。
    
        このクラスは、TTS Realtime API への接続、テキストデータの送信、オーディオ出力の受信、WebSocket 接続の管理を行うためのメソッドを提供します。
    
        属性:
            base_url (str):
                Realtime API のベース URL。
            api_key (str):
                認証用の API キー。
            voice (str):
                音声合成に使用されるサーバー側の音声。
            mode (SessionMode):
                セッションモード(server_commit または commit)。
            audio_callback (Callable[[bytes], None]):
                オーディオデータを受信するためのコールバック関数。
            language_type(str)
                合成される音声の言語。オプション:中国語、英語、ドイツ語、イタリア語、ポルトガル語、スペイン語、日本語、韓国語、フランス語、ロシア語、Auto
        """
    
        def __init__(
                self,
                base_url: str,
                api_key: str,
                voice: str = "Cherry",
                mode: SessionMode = SessionMode.SERVER_COMMIT,
                audio_callback: Optional[Callable[[bytes], None]] = None,
            language_type: str = "Auto"):
            self.base_url = base_url
            self.api_key = api_key
            self.voice = voice
            self.mode = mode
            self.ws = None
            self.audio_callback = audio_callback
            self.language_type = language_type
    
            # 現在の応答ステータス
            self._current_response_id = None
            self._current_item_id = None
            self._is_responding = False
            self._response_done_future = None
    
    
        async def connect(self) -> None:
            """TTS Realtime API との WebSocket 接続を確立します。"""
            headers = {
                "Authorization": f"Bearer {self.api_key}"
            }
    
            self.ws = await websockets.connect(self.base_url, additional_headers=headers)
    
            # デフォルトのセッション構成を設定します
            await self.update_session({
                "mode": self.mode.value,
                "voice": self.voice,
                # server_commit.py または commit.py で model を qwen3-tts-instruct-flash-realtime に置き換え、命令制御を使用するには、以下の行のコメントを解除してください。
                # "instructions": "ファッション製品の紹介に適した、明確に上昇するイントネーションで素早く話す。",
                # "optimize_instructions": true
                "language_type": self.language_type,
                "response_format": "pcm",
                "sample_rate": 24000
            })
    
    
        async def send_event(self, event) -> None:
            """サーバーにイベントを送信します。"""
            event['event_id'] = "event_" + str(int(time.time() * 1000))
            print(f"イベントを送信中:type={event['type']}、event_id={event['event_id']}")
            await self.ws.send(json.dumps(event))
    
    
        async def update_session(self, config: Dict[str, Any]) -> None:
            """セッション構成を更新します。"""
            event = {
                "type": "session.update",
                "session": config
            }
            print("セッション構成を更新中:", event)
            await self.send_event(event)
    
    
        async def append_text(self, text: str) -> None:
            """API にテキストデータを送信します。"""
            event = {
                "type": "input_text_buffer.append",
                "text": text
            }
            await self.send_event(event)
    
    
        async def commit_text_buffer(self) -> None:
            """テキストバッファーを送信して処理を開始します。"""
            event = {
                "type": "input_text_buffer.commit"
            }
            await self.send_event(event)
    
    
        async def clear_text_buffer(self) -> None:
            """テキストバッファーをクリアします。"""
            event = {
                "type": "input_text_buffer.clear"
            }
            await self.send_event(event)
    
    
        async def finish_session(self) -> None:
            """セッションを終了します。"""
            event = {
                "type": "session.finish"
            }
            await self.send_event(event)
    
    
        async def wait_for_response_done(self):
            """response.done イベントを待機します"""
            if self._response_done_future:
                await self._response_done_future
    
    
        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 != "response.audio.delta":
                        print(f"受信イベント:{event_type}")
    
                    if event_type == "error":
                        print("エラー:", event.get('error', {}))
                        continue
                    elif event_type == "session.created":
                        print("セッションが作成されました。ID:", event.get('session', {}).get('id'))
                    elif event_type == "session.updated":
                        print("セッションが更新されました。ID:", event.get('session', {}).get('id'))
                    elif event_type == "input_text_buffer.committed":
                        print("テキストバッファーが送信されました。アイテム ID:", event.get('item_id'))
                    elif event_type == "input_text_buffer.cleared":
                        print("テキストバッファーがクリアされました")
                    elif event_type == "response.created":
                        self._current_response_id = event.get("response", {}).get("id")
                        self._is_responding = True
                        # response.done を待機するための新しい future を作成します
                        self._response_done_future = asyncio.Future()
                        print("応答が作成されました。ID:", self._current_response_id)
                    elif event_type == "response.output_item.added":
                        self._current_item_id = event.get("item", {}).get("id")
                        print("出力アイテムが追加されました。ID:", self._current_item_id)
                    # オーディオデルタを処理
                    elif event_type == "response.audio.delta" and self.audio_callback:
                        audio_bytes = base64.b64decode(event.get("delta", ""))
                        self.audio_callback(audio_bytes)
                    elif event_type == "response.audio.done":
                        print("オーディオ生成が完了しました")
                    elif event_type == "response.done":
                        self._is_responding = False
                        self._current_response_id = None
                        self._current_item_id = None
                        # future を完了状態に設定します
                        if self._response_done_future and not self._response_done_future.done():
                            self._response_done_future.set_result(True)
                        print("応答が完了しました")
                    elif event_type == "session.finished":
                        print("セッションが終了しました")
    
            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()
  3. 音声合成モードの選択

    Realtime API は、以下の 2 つのモードをサポートしています:

    • サーバーコミットモード

      クライアントはテキストのみを送信します。サーバーがテキストの区切りと合成タイミングを知能的に判断します。GPS ナビゲーションなど、手動による合成制御を必要としない低遅延シナリオに適しています。

    • コミットモード

      まずテキストをバッファーに追加し、その後サーバーにそのテキストの合成を指示します。ニュース放送など、一時停止や文の区切りを細かく制御する必要があるシナリオに適しています。

    サーバーコミットモード

    server_commit.py という名前の別の Python ファイルを tts_realtime_client.py と同じディレクトリに作成し、以下のコードをファイルにコピーしてください:

    server_commit.py

    import os
    import asyncio
    import logging
    import wave
    from tts_realtime_client import TTSRealtimeClient, SessionMode
    import pyaudio
    
    # QwenTTS サービス構成
    # 命令制御を使用するには、model を qwen3-tts-instruct-flash-realtime に置き換え、tts_realtime_client.py の instructions のコメントを解除してください。
    # シンガポールリージョンの URL。北京リージョンのモデルを使用する場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime に置き換えてください。
    URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"
    # API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    # 環境変数が設定されていない場合は、API_KEY="sk-xxx" として直接設定してください。
    API_KEY = os.getenv("DASHSCOPE_API_KEY")
    
    if not API_KEY:
        raise ValueError("DASHSCOPE_API_KEY 環境変数を設定してください")
    
    # オーディオデータを収集
    _audio_chunks = []
    # リアルタイム再生設定
    _AUDIO_SAMPLE_RATE = 24000
    _audio_pyaudio = pyaudio.PyAudio()
    _audio_stream = None  # 実行時にオープンされます
    
    def _audio_callback(audio_bytes: bytes):
        """TTSRealtimeClient のオーディオコールバック:リアルタイムで再生およびキャッシュ"""
        global _audio_stream
        if _audio_stream is not None:
            try:
                _audio_stream.write(audio_bytes)
            except Exception as exc:
                logging.error(f"PyAudio 再生エラー:{exc}")
        _audio_chunks.append(audio_bytes)
        logging.info(f"オーディオチャンクを受信:{len(audio_bytes)} バイト")
    
    def _save_audio_to_file(filename: str = "output.wav", sample_rate: int = 24000) -> bool:
        """収集したオーディオデータを WAV ファイルに保存"""
        if not _audio_chunks:
            logging.warning("保存するオーディオデータがありません")
            return False
    
        try:
            audio_data = b"".join(_audio_chunks)
            with wave.open(filename, 'wb') as wav_file:
                wav_file.setnchannels(1)  # モノラル
                wav_file.setsampwidth(2)  # 16 ビット
                wav_file.setframerate(sample_rate)
                wav_file.writeframes(audio_data)
            logging.info(f"オーディオを保存しました:{filename}")
            return True
        except Exception as exc:
            logging.error(f"オーディオの保存に失敗しました:{exc}")
            return False
    
    async def _produce_text(client: TTSRealtimeClient):
        """サーバーにテキストフラグメントを送信"""
        text_fragments = [
            "Alibaba Cloud の Model Studio は、モデル開発およびアプリケーション構築のためのオールインワンプラットフォームです。",
            "開発者およびビジネス担当者が、モデルアプリケーションの設計・構築に深く関与できます。",
            "シンプルな UI 操作でわずか 5 分でモデルアプリケーションを開発できるほか、",
            "数時間でカスタムモデルをトレーニングでき、アプリケーションのイノベーションに集中できます。",
        ]
    
        logging.info("テキストフラグメントを送信中…")
        for text in text_fragments:
            logging.info(f"フラグメントを送信:{text}")
            await client.append_text(text)
            await asyncio.sleep(0.1)  # フラグメント間の短い遅延
    
        # セッション終了前にサーバーが内部処理を完了するのを待機
        await asyncio.sleep(1.0)
        await client.finish_session()
    
    async def _run_demo():
        """完全なデモを実行"""
        global _audio_stream
        # PyAudio 出力ストリームを開きます
        _audio_stream = _audio_pyaudio.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=_AUDIO_SAMPLE_RATE,
            output=True,
            frames_per_buffer=1024
        )
    
        client = TTSRealtimeClient(
            base_url=URL,
            api_key=API_KEY,
            voice="Cherry",
            mode=SessionMode.SERVER_COMMIT,
            audio_callback=_audio_callback
        )
    
        # 接続を確立
        await client.connect()
    
        # メッセージ処理とテキスト送信を並列に実行
        consumer_task = asyncio.create_task(client.handle_messages())
        producer_task = asyncio.create_task(_produce_text(client))
    
        await producer_task  # テキスト送信の完了を待機
    
        # response.done を待機
        await client.wait_for_response_done()
    
        # 接続を閉じ、コンシューマータスクをキャンセル
        await client.close()
        consumer_task.cancel()
    
        # オーディオストリームを閉じる
        if _audio_stream is not None:
            _audio_stream.stop_stream()
            _audio_stream.close()
        _audio_pyaudio.terminate()
    
        # オーディオデータを保存
        os.makedirs("outputs", exist_ok=True)
        _save_audio_to_file(os.path.join("outputs", "qwen_tts_output.wav"))
    
    def main():
        """同期エントリーポイント"""
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s [%(levelname)s] %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        logging.info("QwenTTS Realtime Client デモを開始しています…")
        asyncio.run(_run_demo())
    
    if __name__ == "__main__":
        main() 

    server_commit.py を実行して、Realtime API によって生成されたリアルタイム音声を聴取します。

    コミットモード

    commit.py という名前の別の Python ファイルを tts_realtime_client.py と同じディレクトリに作成し、以下のコードをファイルにコピーしてください:

    commit.py

    import os
    import asyncio
    import logging
    import wave
    from tts_realtime_client import TTSRealtimeClient, SessionMode
    import pyaudio
    
    # QwenTTS サービス構成
    # 命令制御を使用するには、model を qwen3-tts-instruct-flash-realtime に置き換え、tts_realtime_client.py の instructions のコメントを解除してください。
    # シンガポールリージョンの URL。北京リージョンのモデルを使用する場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime に置き換えてください。
    URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"
    # API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    # 環境変数が設定されていない場合は、API_KEY="sk-xxx" として直接設定してください。
    API_KEY = os.getenv("DASHSCOPE_API_KEY")
    
    if not API_KEY:
        raise ValueError("DASHSCOPE_API_KEY 環境変数を設定してください")
    
    # オーディオデータを収集
    _audio_chunks = []
    _AUDIO_SAMPLE_RATE = 24000
    _audio_pyaudio = pyaudio.PyAudio()
    _audio_stream = None
    
    def _audio_callback(audio_bytes: bytes):
        """TTSRealtimeClient のオーディオコールバック:リアルタイムで再生およびキャッシュ"""
        global _audio_stream
        if _audio_stream is not None:
            try:
                _audio_stream.write(audio_bytes)
            except Exception as exc:
                logging.error(f"PyAudio 再生エラー:{exc}")
        _audio_chunks.append(audio_bytes)
        logging.info(f"オーディオチャンクを受信:{len(audio_bytes)} バイト")
    
    def _save_audio_to_file(filename: str = "output.wav", sample_rate: int = 24000) -> bool:
        """収集したオーディオデータを WAV ファイルに保存"""
        if not _audio_chunks:
            logging.warning("保存するオーディオデータがありません")
            return False
    
        try:
            audio_data = b"".join(_audio_chunks)
            with wave.open(filename, 'wb') as wav_file:
                wav_file.setnchannels(1)  # モノラル
                wav_file.setsampwidth(2)  # 16 ビット
                wav_file.setframerate(sample_rate)
                wav_file.writeframes(audio_data)
            logging.info(f"オーディオを保存しました:{filename}")
            return True
        except Exception as exc:
            logging.error(f"オーディオの保存に失敗しました:{exc}")
            return False
    
    async def _user_input_loop(client: TTSRealtimeClient):
        """ユーザー入力を継続的に取得し、テキストを送信します。ユーザーが空の入力をした場合、コミットイベントを送信して現在のセッションを終了します。"""
        print("テキストを入力してください(空のまま Enter キーを押すとコミットイベントを送信し、現在のセッションを終了します。Ctrl+C または Ctrl+D を押すとプログラム全体を終了します):")
        
        while True:
            try:
                user_text = input("> ")
                if not user_text:  # ユーザーが空の入力をした場合
                    # 空の入力は会話の終了を意味します:バッファーを送信 → セッションを終了 → ループを抜ける
                    logging.info("空の入力を受け付けました。コミットイベントを送信し、現在のセッションを終了します。")
                    await client.commit_text_buffer()
                    # サーバーがコミットを処理するのを待機し、過早なセッション終了による音声の喪失を防ぎます。
                    await asyncio.sleep(0.3)
                    await client.finish_session()
                    break  # ユーザー入力ループを直接抜けるため、再度 Enter キーを押す必要はありません。
                else:
                    logging.info(f"テキストを送信:{user_text}")
                    await client.append_text(user_text)
                    
            except EOFError:  # ユーザーが Ctrl+D を押した場合
                break
            except KeyboardInterrupt:  # ユーザーが Ctrl+C を押した場合
                break
        
        # セッションを終了
        logging.info("セッションを終了しています...")
    async def _run_demo():
        """完全なデモを実行"""
        global _audio_stream
        # PyAudio 出力ストリームを開きます
        _audio_stream = _audio_pyaudio.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=_AUDIO_SAMPLE_RATE,
            output=True,
            frames_per_buffer=1024
        )
    
        client = TTSRealtimeClient(
            base_url=URL,
            api_key=API_KEY,
            voice="Cherry",
            mode=SessionMode.COMMIT,  # COMMIT モードに変更
            audio_callback=_audio_callback
        )
    
        # 接続を確立
        await client.connect()
    
        # メッセージ処理とユーザー入力を並列に実行
        consumer_task = asyncio.create_task(client.handle_messages())
        producer_task = asyncio.create_task(_user_input_loop(client))
    
        await producer_task  # ユーザー入力の完了を待機
    
        # response.done を待機
        await client.wait_for_response_done()
    
        # 接続を閉じ、コンシューマータスクをキャンセル
        await client.close()
        consumer_task.cancel()
    
        # オーディオストリームを閉じる
        if _audio_stream is not None:
            _audio_stream.stop_stream()
            _audio_stream.close()
        _audio_pyaudio.terminate()
    
        # オーディオデータを保存
        os.makedirs("outputs", exist_ok=True)
        _save_audio_to_file(os.path.join("outputs", "qwen_tts_output.wav"))
    
    def main():
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s [%(levelname)s] %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        logging.info("QwenTTS Realtime Client デモを開始しています…")
        asyncio.run(_run_demo())
    
    if __name__ == "__main__":
        main() 

    commit.py を実行して、複数のテキストを合成します。空のまま Enter キーを押すと、Realtime API から返された音声をスピーカーで聴取できます。

クローン音声の使用

ボイスクローンサービスは、プレビュー音声を提供しません。効果の確認および評価は、音声合成インターフェイスを通じて行ってください。初期テストには短いテキストを使用してください。

以下の例では、ボイスクローンによって生成されたカスタム音声を音声合成で使用し、元の音声に非常に近い出力を生成する方法を示します。この例は、「システム音声の使用」における DashScope SDK の「サーバーコミットモード」のサンプルコードを基にしており、voice パラメーターをクローンされたカスタム音声に置き換えています。

  • 基本原則:ボイスクローンモデル(target_model)と音声合成モデル(model)を一致させる必要があります。一致していない場合、合成は失敗します。

  • この例では、ローカルの音声ファイル voice.mp3 をボイスクローンに使用しています。コードを実行する際に、このファイルを置き換えてください。

Python

# coding=utf-8
# pyaudio のインストール手順:
# APPLE Mac OS X
#   brew install portaudio
#   pip install pyaudio
# Debian/Ubuntu
#   sudo apt-get install python-pyaudio python3-pyaudio
#   または
#   pip install pyaudio
# CentOS
#   sudo yum install -y portaudio portaudio-devel && pip install pyaudio
# Microsoft Windows
#   python -m pip install pyaudio

import pyaudio
import os
import requests
import base64
import pathlib
import threading
import time
import dashscope  # DashScope Python SDK のバージョンは 1.23.9 以降である必要があります
from dashscope.audio.qwen_tts_realtime import QwenTtsRealtime, QwenTtsRealtimeCallback, AudioFormat

# ======= 定数 =======
DEFAULT_TARGET_MODEL = "qwen3-tts-vc-realtime-2026-01-15"  # 音声クローンと音声合成で同じモデルを使用します
DEFAULT_PREFERRED_NAME = "guanyu"
DEFAULT_AUDIO_MIME_TYPE = "audio/mpeg"
VOICE_FILE_PATH = "voice.mp3"  # 音声クローン用のローカル音声ファイルへの相対パス

TEXT_TO_SYNTHESIZE = [
    'Right? I really love this kind of supermarket,',
    'especially during Chinese New Year',
    'when I go shopping',
    'I feel',
    'super super happy!',
    'I want to buy so many things!'
]

def create_voice(file_path: str,
                 target_model: str = DEFAULT_TARGET_MODEL,
                 preferred_name: str = DEFAULT_PREFERRED_NAME,
                 audio_mime_type: str = DEFAULT_AUDIO_MIME_TYPE) -> str:
    """
    音声を作成し、音声パラメーターを返します
    """
    # API キーはシンガポールリージョンと北京リージョンで異なります。API キーの取得方法: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    # 環境変数が設定されていない場合は、Model Studio の API キーに置き換えてください: api_key = "sk-xxx"
    api_key = os.getenv("DASHSCOPE_API_KEY")

    file_path_obj = pathlib.Path(file_path)
    if not file_path_obj.exists():
        raise FileNotFoundError(f"音声ファイルが見つかりません: {file_path}")

    base64_str = base64.b64encode(file_path_obj.read_bytes()).decode()
    data_uri = f"data:{audio_mime_type};base64,{base64_str}"

    # シンガポールリージョンの URL。北京リージョンのモデルの場合は、https://dashscope.aliyuncs.com/api/v1/services/audio/tts/customization に置き換えてください
    url = "https://dashscope-intl.aliyuncs.com/api/v1/services/audio/tts/customization"
    payload = {
        "model": "qwen-voice-enrollment", # この値は変更しないでください
        "input": {
            "action": "create",
            "target_model": target_model,
            "preferred_name": preferred_name,
            "audio": {"data": data_uri}
        }
    }
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }

    resp = requests.post(url, json=payload, headers=headers)
    if resp.status_code != 200:
        raise RuntimeError(f"音声作成に失敗しました: {resp.status_code}, {resp.text}")

    try:
        return resp.json()["output"]["voice"]
    except (KeyError, ValueError) as e:
        raise RuntimeError(f"音声応答の解析に失敗しました: {e}")

def init_dashscope_api_key():
    """
    DashScope SDK の API キーを初期化します
    """
    # API キーはシンガポールリージョンと北京リージョンで異なります。API キーの取得方法: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    # 環境変数が設定されていない場合は、Model Studio の API キーに置き換えてください: dashscope.api_key = "sk-xxx"
    dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")

# ======= コールバッククラス =======
class MyCallback(QwenTtsRealtimeCallback):
    """
    カスタム TTS ストリーミングコールバック
    """
    def __init__(self):
        self.complete_event = threading.Event()
        self._player = pyaudio.PyAudio()
        self._stream = self._player.open(
            format=pyaudio.paInt16, channels=1, rate=24000, output=True
        )

    def on_open(self) -> None:
        print('[TTS] 接続が確立されました')

    def on_close(self, close_status_code, close_msg) -> None:
        self._stream.stop_stream()
        self._stream.close()
        self._player.terminate()
        print(f'[TTS] 接続が終了しました。コード={close_status_code}、メッセージ={close_msg}')

    def on_event(self, response: dict) -> None:
        try:
            event_type = response.get('type', '')
            if event_type == 'session.created':
                print(f'[TTS] セッションが開始されました: {response["session"]["id"]}')
            elif event_type == 'response.audio.delta':
                audio_data = base64.b64decode(response['delta'])
                self._stream.write(audio_data)
            elif event_type == 'response.done':
                print(f'[TTS] 応答が完了しました。応答 ID: {qwen_tts_realtime.get_last_response_id()}')
            elif event_type == 'session.finished':
                print('[TTS] セッションが終了しました')
                self.complete_event.set()
        except Exception as e:
            print(f'[エラー] コールバックイベントの処理中にエラーが発生しました: {e}')

    def wait_for_finished(self):
        self.complete_event.wait()

# ======= メイン実行ロジック =======
if __name__ == '__main__':
    init_dashscope_api_key()
    print('[System] Qwen TTS Realtime を初期化しています...')

    callback = MyCallback()
    qwen_tts_realtime = QwenTtsRealtime(
        model=DEFAULT_TARGET_MODEL,
        callback=callback,
        # シンガポールリージョンの URL。北京リージョンのモデルの場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください
        url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
    )
    qwen_tts_realtime.connect()
    
    qwen_tts_realtime.update_session(
        voice=create_voice(VOICE_FILE_PATH), # 音声パラメーターをクローンしたカスタム音声に置き換えます
        response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
        mode='server_commit'
    )

    for text_chunk in TEXT_TO_SYNTHESIZE:
        print(f'[テキスト送信中]: {text_chunk}')
        qwen_tts_realtime.append_text(text_chunk)
        time.sleep(0.1)

    qwen_tts_realtime.finish()
    callback.wait_for_finished()

    print(f'[メトリクス] session_id={qwen_tts_realtime.get_session_id()}, '
          f'first_audio_delay={qwen_tts_realtime.get_first_audio_delay()}s')

Java

Gson 依存関係をインポートする必要があります。Maven または Gradle を使用している場合は、以下のように依存関係を追加してください:

Maven

pom.xml に以下の内容を追加します:

<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.13.1</version>
</dependency>

Gradle

build.gradle に以下の内容を追加します:

// https://mvnrepository.com/artifact/com.google.code.gson/gson
implementation("com.google.code.gson:gson:2.13.1")
import com.alibaba.dashscope.audio.qwen_tts_realtime.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

import javax.sound.sampled.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class Main {
    // ===== 定数 =====
    // ボイスクローンと音声合成で同じモデルを使用
    private static final String TARGET_MODEL = "qwen3-tts-vc-realtime-2026-01-15";
    private static final String PREFERRED_NAME = "guanyu";
    // ボイスクローン用のローカル音声ファイルの相対パス
    private static final String AUDIO_FILE = "voice.mp3";
    private static final String AUDIO_MIME_TYPE = "audio/mpeg";
    private static String[] textToSynthesize = {
            "そうですね?こんなスーパーが大好きです。",
            "特に旧正月の時期には、",
            "スーパーでお買い物に行きます。",
            "そして私は、",
            "とてもワクワクします!",
            "たくさん買いたいものがたくさんあります!"
    };

    // データ URI を生成
    public static String toDataUrl(String filePath) throws IOException {
        byte[] bytes = Files.readAllBytes(Paths.get(filePath));
        String encoded = Base64.getEncoder().encodeToString(bytes);
        return "data:" + AUDIO_MIME_TYPE + ";base64," + encoded;
    }

    // API を呼び出して音声を作成
    public static String createVoice() throws Exception {
        // API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
        // 環境変数が設定されていない場合は、String apiKey = "sk-xxx" として直接設定してください。
        String apiKey = System.getenv("DASHSCOPE_API_KEY");

        String jsonPayload =
                "{"
                        + "\"model\": \"qwen-voice-enrollment\"," // この値は変更しないでください。
                        + "\"input\": {"
                        +     "\"action\": \"create\","
                        +     "\"target_model\": \"" + TARGET_MODEL + "\","
                        +     "\"preferred_name\": \"" + PREFERRED_NAME + "\","
                        +     "\"audio\": {"
                        +         "\"data\": \"" + toDataUrl(AUDIO_FILE) + "\""
                        +     "}"
                        + "}"
                        + "}";

        HttpURLConnection con = (HttpURLConnection) new URL("https://dashscope.aliyuncs.com/api/v1/services/audio/tts/customization").openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("Authorization", "Bearer " + apiKey);
        con.setRequestProperty("Content-Type", "application/json");
        con.setDoOutput(true);

        try (OutputStream os = con.getOutputStream()) {
            os.write(jsonPayload.getBytes(StandardCharsets.UTF_8));
        }

        int status = con.getResponseCode();
        System.out.println("HTTP ステータスコード:" + status);

        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(status >= 200 && status < 300 ? con.getInputStream() : con.getErrorStream(),
                        StandardCharsets.UTF_8))) {
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                response.append(line);
            }
            System.out.println("応答内容:" + response);

            if (status == 200) {
                JsonObject jsonObj = new Gson().fromJson(response.toString(), JsonObject.class);
                return jsonObj.getAsJsonObject("output").get("voice").getAsString();
            }
            throw new IOException("音声作成に失敗しました:" + status + " - " + response);
        }
    }

    // リアルタイム PCM オーディオプレーヤークラス
    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();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder()
                .model(TARGET_MODEL)
                // シンガポールリージョンの URL。北京リージョンのモデルを使用する場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
                .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                // API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
                // 環境変数が設定されていない場合は、.apikey("sk-xxx") として直接設定してください。
                .apikey(System.getenv("DASHSCOPE_API_KEY"))
                .build();
        AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));
        final AtomicReference<QwenTtsRealtime> qwenTtsRef = new AtomicReference<>(null);

        // リアルタイムオーディオプレーヤーインスタンスを作成
        RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);

        QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
            @Override
            public void onOpen() {
                // 接続確立時の処理
            }
            @Override
            public void onEvent(JsonObject message) {
                String type = message.get("type").getAsString();
                switch(type) {
                    case "session.created":
                        // セッション作成時の処理
                        break;
                    case "response.audio.delta":
                        String recvAudioB64 = message.get("delta").getAsString();
                        // オーディオをリアルタイムで再生
                        audioPlayer.write(recvAudioB64);
                        break;
                    case "response.done":
                        // 応答完了時の処理
                        break;
                    case "session.finished":
                        // セッション終了時の処理
                        completeLatch.get().countDown();
                    default:
                        break;
                }
            }
            @Override
            public void onClose(int code, String reason) {
                // 接続終了時の処理
            }
        });
        qwenTtsRef.set(qwenTtsRealtime);
        try {
            qwenTtsRealtime.connect();
        } catch (NoApiKeyException e) {
            throw new RuntimeException(e);
        }
        QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
                .voice(createVoice()) // voice パラメーターをクローンされたカスタム音声に置き換えます。
                .responseFormat(QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT)
                .mode("server_commit")
                .build();
        qwenTtsRealtime.updateSession(config);
        for (String text:textToSynthesize) {
            qwenTtsRealtime.appendText(text);
            Thread.sleep(100);
        }
        qwenTtsRealtime.finish();
        completeLatch.get().await();

        // オーディオ再生が完了するのを待ってからプレーヤーをシャットダウン
        audioPlayer.waitForComplete();
        audioPlayer.shutdown();
        System.exit(0);
    }
}

デザイン音声の使用

ボイスデザイン機能はプレビュー音声データを返します。まずこのプレビュー音声を聴いて、期待通りの効果が得られるか確認してから、音声合成に使用してください。

  1. カスタム音声を生成し、プレビューを聴きます。満足できる場合は次に進み、そうでない場合は音声を再生成します。

    Python

    import requests
    import base64
    import os
    
    def create_voice_and_play():
        # API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
        # 環境変数を設定していない場合は、以下の行を api_key = "sk-xxx" に置き換えてください。
        api_key = os.getenv("DASHSCOPE_API_KEY")
        
        if not api_key:
            print("エラー:DASHSCOPE_API_KEY 環境変数が見つかりません。API キーを設定してください。")
            return None, None, None
        
        # リクエストデータを準備
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        
        data = {
            "model": "qwen-voice-design",
            "input": {
                "action": "create",
                "target_model": "qwen3-tts-vd-realtime-2026-01-15",
                "voice_prompt": "落ち着いた中年の男性アナウンサーで、深く豊かで魅力的な声、安定した話速、明瞭な発音で、ニュース放送やドキュメンタリーのナレーションに適しています。",
                "preview_text": "リスナーの皆様、こんにちは。イブニングニュースへようこそ。",
                "preferred_name": "announcer",
                "language": "en"
            },
            "parameters": {
                "sample_rate": 24000,
                "response_format": "wav"
            }
        }
        
        # シンガポールリージョンの URL。北京リージョンの場合は、https://dashscope.aliyuncs.com/api/v1/services/audio/tts/customization を使用してください。
        url = "https://dashscope-intl.aliyuncs.com/api/v1/services/audio/tts/customization"
        
        try:
            # リクエストを送信
            response = requests.post(
                url,
                headers=headers,
                json=data,
                timeout=60  # タイムアウト設定を追加
            )
            
            if response.status_code == 200:
                result = response.json()
                
                # 音声名を取得
                voice_name = result["output"]["voice"]
                print(f"音声名:{voice_name}")
                
                # プレビュー音声データを取得
                base64_audio = result["output"]["preview_audio"]["data"]
                
                # Base64 音声データをデコード
                audio_bytes = base64.b64decode(base64_audio)
                
                # 音声ファイルをローカルに保存
                filename = f"{voice_name}_preview.wav"
                
                # 音声データをローカルファイルに書き込み
                with open(filename, 'wb') as f:
                    f.write(audio_bytes)
                
                print(f"音声がローカルファイルに保存されました:{filename}")
                print(f"ファイルパス:{os.path.abspath(filename)}")
                
                return voice_name, audio_bytes, filename
            else:
                print(f"リクエストに失敗しました。ステータスコード:{response.status_code}")
                print(f"応答:{response.text}")
                return None, None, None
                
        except requests.exceptions.RequestException as e:
            print(f"ネットワークリクエストエラー:{e}")
            return None, None, None
        except KeyError as e:
            print(f"応答フォーマットエラー:必須フィールドがありません:{e}")
            print(f"応答:{'response' in locals() and response.text or '応答なし'}")
            return None, None, None
        except Exception as e:
            print(f"予期せぬエラー:{e}")
            return None, None, None
    
    if __name__ == "__main__":
        print("音声を作成中...")
        voice_name, audio_data, saved_filename = create_voice_and_play()
        
        if voice_name:
            print(f"\n音声 '{voice_name}' の作成に成功しました")
            print(f"音声ファイルが保存されました:'{saved_filename}'")
            print(f"ファイルサイズ:{os.path.getsize(saved_filename)} バイト")
        else:
            print("\n音声の作成に失敗しました")

    Java

    Gson 依存関係をインポートする必要があります。Maven または Gradle を使用している場合は、依存関係を追加してください:

    Maven

    pom.xml に以下の内容を追加します:

    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.13.1</version>
    </dependency>

    Gradle

    build.gradle に以下の内容を追加します:

    // https://mvnrepository.com/artifact/com.google.code.gson/gson
    implementation("com.google.code.gson:gson:2.13.1")
    import com.google.gson.JsonObject;
    import com.google.gson.JsonParser;
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.Base64;
    
    public class Main {
        public static void main(String[] args) {
            Main example = new Main();
            example.createVoice();
        }
    
        public void createVoice() {
            // API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
            // 環境変数を設定していない場合は、以下の行を String apiKey = "sk-xxx" に置き換えてください。
            String apiKey = System.getenv("DASHSCOPE_API_KEY");
    
            // JSON リクエストボディ文字列を作成
            String jsonBody = "{\n" +
                    "    \"model\": \"qwen-voice-design\",\n" +
                    "    \"input\": {\n" +
                    "        \"action\": \"create\",\n" +
                    "        \"target_model\": \"qwen3-tts-vd-realtime-2026-01-15\",\n" +
                    "        \"voice_prompt\": \"落ち着いた中年の男性アナウンサーで、深く豊かで魅力的な声、安定した話速、明瞭な発音で、ニュース放送やドキュメンタリーのナレーションに適しています。\",\n" +
                    "        \"preview_text\": \"リスナーの皆様、こんにちは。イブニングニュースへようこそ。\",\n" +
                    "        \"preferred_name\": \"announcer\",\n" +
                    "        \"language\": \"en\"\n" +
                    "    },\n" +
                    "    \"parameters\": {\n" +
                    "        \"sample_rate\": 24000,\n" +
                    "        \"response_format\": \"wav\"\n" +
                    "    }\n" +
                    "}";
    
            HttpURLConnection connection = null;
            try {
                // シンガポールリージョンの URL。北京リージョンの場合は、https://dashscope.aliyuncs.com/api/v1/services/audio/tts/customization を使用してください。
                URL url = new URL("https://dashscope-intl.aliyuncs.com/api/v1/services/audio/tts/customization");
                connection = (HttpURLConnection) url.openConnection();
    
                // リクエストメソッドとヘッダーを設定
                connection.setRequestMethod("POST");
                connection.setRequestProperty("Authorization", "Bearer " + apiKey);
                connection.setRequestProperty("Content-Type", "application/json");
                connection.setDoOutput(true);
                connection.setDoInput(true);
    
                // リクエストボディを送信
                try (OutputStream os = connection.getOutputStream()) {
                    byte[] input = jsonBody.getBytes("UTF-8");
                    os.write(input, 0, input.length);
                    os.flush();
                }
    
                // 応答を取得
                int responseCode = connection.getResponseCode();
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    // 応答内容を読み取り
                    StringBuilder response = new StringBuilder();
                    try (BufferedReader br = new BufferedReader(
                            new InputStreamReader(connection.getInputStream(), "UTF-8"))) {
                        String responseLine;
                        while ((responseLine = br.readLine()) != null) {
                            response.append(responseLine.trim());
                        }
                    }
    
                    // JSON 応答を解析
                    JsonObject jsonResponse = JsonParser.parseString(response.toString()).getAsJsonObject();
                    JsonObject outputObj = jsonResponse.getAsJsonObject("output");
                    JsonObject previewAudioObj = outputObj.getAsJsonObject("preview_audio");
    
                    // 音声名を取得
                    String voiceName = outputObj.get("voice").getAsString();
                    System.out.println("音声名:" + voiceName);
    
                    // Base64 エンコードされた音声データを取得
                    String base64Audio = previewAudioObj.get("data").getAsString();
    
                    // Base64 音声データをデコード
                    byte[] audioBytes = Base64.getDecoder().decode(base64Audio);
    
                    // 音声をローカルファイルに保存
                    String filename = voiceName + "_preview.wav";
                    saveAudioToFile(audioBytes, filename);
    
                    System.out.println("音声がローカルファイルに保存されました:" + filename);
    
                } else {
                    // エラー応答を読み取り
                    StringBuilder errorResponse = new StringBuilder();
                    try (BufferedReader br = new BufferedReader(
                            new InputStreamReader(connection.getErrorStream(), "UTF-8"))) {
                        String responseLine;
                        while ((responseLine = br.readLine()) != null) {
                            errorResponse.append(responseLine.trim());
                        }
                    }
    
                    System.out.println("リクエストに失敗しました。ステータスコード:" + responseCode);
                    System.out.println("エラー応答:" + errorResponse.toString());
                }
    
            } catch (Exception e) {
                System.err.println("リクエストエラー:" + e.getMessage());
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }
    
        private void saveAudioToFile(byte[] audioBytes, String filename) {
            try {
                File file = new File(filename);
                try (FileOutputStream fos = new FileOutputStream(file)) {
                    fos.write(audioBytes);
                }
                System.out.println("音声が保存されました:" + file.getAbsolutePath());
            } catch (IOException e) {
                System.err.println("音声ファイルの保存エラー:" + e.getMessage());
                e.printStackTrace();
            }
        }
    }
  2. 前のステップで生成したカスタム音声を音声合成に使用します。

    この例は、システム音声を使用した DashScope SDK の「サーバーコミットモード」での音声合成に基づいています。voice パラメーターをボイスデザインで生成したカスタム音声に置き換えます。

    基本原則:ボイスデザイン時に使用したモデル(target_model)と、その後の音声合成で使用するモデル(model)は、同一である必要があります。そうでない場合、合成は失敗します。

    Python

    # coding=utf-8
    # pyaudio のインストール手順:
    # APPLE Mac OS X
    #   brew install portaudio
    #   pip install pyaudio
    # Debian/Ubuntu
    #   sudo apt-get install python-pyaudio python3-pyaudio
    #   または
    #   pip install pyaudio
    # CentOS
    #   sudo yum install -y portaudio portaudio-devel && pip install pyaudio
    # Microsoft Windows
    #   python -m pip install pyaudio
    
    import pyaudio
    import os
    import base64
    import threading
    import time
    import dashscope  # DashScope Python SDK バージョン 1.23.9 以降が必要です
    from dashscope.audio.qwen_tts_realtime import QwenTtsRealtime, QwenTtsRealtimeCallback, AudioFormat
    
    # ======= 定数設定 =======
    TEXT_TO_SYNTHESIZE = [
        'そうですね?こんなスーパーが大好きです。',
        '特に旧正月の時期には、',
        'スーパーでお買い物に行きます。',
        'そして私は、',
        'とてもワクワクします!',
        'たくさん買いたいものがたくさんあります!'
    ]
    
    def init_dashscope_api_key():
        """
        DashScope SDK API キーを初期化します
        """
        # API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
        # 環境変数を設定していない場合は、以下の行を dashscope.api_key = "sk-xxx" に置き換えてください。
        dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
    
    # ======= コールバッククラス =======
    class MyCallback(QwenTtsRealtimeCallback):
        """
        カスタム TTS ストリーミングコールバック
        """
        def __init__(self):
            self.complete_event = threading.Event()
            self._player = pyaudio.PyAudio()
            self._stream = self._player.open(
                format=pyaudio.paInt16, channels=1, rate=24000, output=True
            )
    
        def on_open(self) -> None:
            print('[TTS] 接続が確立されました')
    
        def on_close(self, close_status_code, close_msg) -> None:
            self._stream.stop_stream()
            self._stream.close()
            self._player.terminate()
            print(f'[TTS] 接続が終了しました。コード={close_status_code}、メッセージ={close_msg}')
    
        def on_event(self, response: dict) -> None:
            try:
                event_type = response.get('type', '')
                if event_type == 'session.created':
                    print(f'[TTS] セッションが開始されました:{response["session"]["id"]}')
                elif event_type == 'response.audio.delta':
                    audio_data = base64.b64decode(response['delta'])
                    self._stream.write(audio_data)
                elif event_type == 'response.done':
                    print(f'[TTS] 応答が完了しました。応答 ID:{qwen_tts_realtime.get_last_response_id()}')
                elif event_type == 'session.finished':
                    print('[TTS] セッションが終了しました')
                    self.complete_event.set()
            except Exception as e:
                print(f'[エラー] コールバックイベント処理中に例外が発生しました:{e}')
    
        def wait_for_finished(self):
            self.complete_event.wait()
    
    # ======= メイン実行ロジック =======
    if __name__ == '__main__':
        init_dashscope_api_key()
        print('[システム] Qwen TTS Realtime を初期化中 ...')
    
        callback = MyCallback()
        qwen_tts_realtime = QwenTtsRealtime(
            # ボイスデザインと音声合成は同じモデルを使用する必要があります
            model="qwen3-tts-vd-realtime-2026-01-15",
            callback=callback,
            # シンガポールリージョンの URL。北京リージョンの場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime を使用してください。
            url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
        )
        qwen_tts_realtime.connect()
        
        qwen_tts_realtime.update_session(
            voice="myvoice", # voice パラメーターをボイスデザインで生成したカスタム音声に置き換えます
            response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
            mode='server_commit'
        )
    
        for text_chunk in TEXT_TO_SYNTHESIZE:
            print(f'[テキストを送信]:{text_chunk}')
            qwen_tts_realtime.append_text(text_chunk)
            time.sleep(0.1)
    
        qwen_tts_realtime.finish()
        callback.wait_for_finished()
    
        print(f'[メトリクス] session_id={qwen_tts_realtime.get_session_id()}, first_audio_delay={qwen_tts_realtime.get_first_audio_delay()}s')

    Java

    import com.alibaba.dashscope.audio.qwen_tts_realtime.*;
    import com.alibaba.dashscope.exception.NoApiKeyException;
    import com.google.gson.JsonObject;
    
    import javax.sound.sampled.*;
    import java.io.*;
    import java.util.Base64;
    import java.util.Queue;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    public class Main {
        // ===== 定義 =====
        private static String[] textToSynthesize = {
                "そうですね?こんなスーパーが大好きです。",
                "特に旧正月の時期には、",
                "スーパーでお買い物に行きます。",
                "そして私は、",
                "とてもワクワクします!",
                "たくさん買いたいものがたくさんあります!"
        };
    
        // リアルタイムオーディオプレーヤークラス
        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();
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder()
                    // ボイスデザインと音声合成は同じモデルを使用する必要があります
                    .model("qwen3-tts-vd-realtime-2026-01-15")
                    // シンガポールリージョンの URL。北京リージョンの場合は、wss://dashscope.aliyuncs.com/api-ws/v1/realtime を使用してください。
                    .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
                    // API キーはシンガポールと北京の各リージョンで異なります。API キーの取得先:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
                    // 環境変数を設定していない場合は、以下の行を .apikey("sk-xxx") に置き換えてください。
                    .apikey(System.getenv("DASHSCOPE_API_KEY"))
                    .build();
            AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));
            final AtomicReference<QwenTtsRealtime> qwenTtsRef = new AtomicReference<>(null);
    
            // リアルタイムオーディオプレーヤーインスタンスを作成
            RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);
    
            QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
                @Override
                public void onOpen() {
                    // 接続開始時の処理
                }
                @Override
                public void onEvent(JsonObject message) {
                    String type = message.get("type").getAsString();
                    switch(type) {
                        case "session.created":
                            // セッション作成時の処理
                            break;
                        case "response.audio.delta":
                            String recvAudioB64 = message.get("delta").getAsString();
                            // オーディオをリアルタイムで再生
                            audioPlayer.write(recvAudioB64);
                            break;
                        case "response.done":
                            // 応答完了時の処理
                            break;
                        case "session.finished":
                            // セッション終了時の処理
                            completeLatch.get().countDown();
                        default:
                            break;
                    }
                }
                @Override
                public void onClose(int code, String reason) {
                    // 接続終了時の処理
                }
            });
            qwenTtsRef.set(qwenTtsRealtime);
            try {
                qwenTtsRealtime.connect();
            } catch (NoApiKeyException e) {
                throw new RuntimeException(e);
            }
            QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
                    .voice("myvoice") // voice パラメーターをボイスデザインで生成したカスタム音声に置き換えます
                    .responseFormat(QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT)
                    .mode("server_commit")
                    .build();
            qwenTtsRealtime.updateSession(config);
            for (String text:textToSynthesize) {
                qwenTtsRealtime.appendText(text);
                Thread.sleep(100);
            }
            qwenTtsRealtime.finish();
            completeLatch.get().await();
    
            // オーディオ再生が完了するのを待ってからプレーヤーをシャットダウンします
            audioPlayer.waitForComplete();
            audioPlayer.shutdown();
            System.exit(0);
        }
    }

その他のサンプルコードについては、github をご参照ください。

インタラクションフロー

サーバーコミットモード

session.update イベントの session.mode プロパティを "server_commit" に設定すると、このモードが有効になります。サーバーはテキストのセグメント化と合成タイミングをインテリジェントに処理します。

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

  1. クライアントが session.update イベントを送信します。サーバーは session.created および session.updated イベントで応答します。

  2. クライアントが input_text_buffer.append イベントを送信して、サーバーのバッファーにテキストを追加します。

  3. サーバーはテキストのセグメント化と合成タイミングをインテリジェントに処理し、response.createdresponse.output_item.addedresponse.content_part.added、および response.audio.delta イベントを返します。

  4. 応答が完了すると、サーバーは response.audio.doneresponse.content_part.doneresponse.output_item.done、および response.done を返します。

  5. サーバーは session.finished で応答し、セッションを終了します。

ライフサイクル

クライアントイベント

サーバーイベント

セッションの初期化

session.update

セッション構成

session.created

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

session.updated

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

ユーザーテキスト入力

input_text_buffer.append

サーバーにテキストを追加

input_text_buffer.commit

サーバーにキャッシュされたテキストを即時合成

session.finish

これ以上テキスト入力がないことをサーバーに通知

input_text_buffer.committed

サーバーが送信されたテキストを受信しました

サーバーのオーディオ出力

なし

response.created

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

response.output_item.added

応答に新しい出力コンテンツ

response.content_part.added

アシスタントメッセージに新しい出力コンテンツが追加されました

response.audio.delta

モデルによって生成された増分オーディオ

response.content_part.done

アシスタントメッセージのテキストまたはオーディオコンテンツストリームが完了しました

response.output_item.done

アシスタントメッセージの出力アイテムストリーム全体が完了しました

response.audio.done

オーディオ生成が完了しました

response.done

応答が完了しました

コミットモード

session.update イベントの session.mode プロパティを "commit" に設定すると、このモードが有効になります。クライアントは、応答を得るためにテキストバッファーをサーバーに能動的に送信する必要があります。

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

  1. クライアントが session.update イベントを送信します。サーバーは session.created および session.updated イベントで応答します。

  2. クライアントが input_text_buffer.append イベントを送信して、サーバーのバッファーにテキストを追加します。

  3. クライアントが input_text_buffer.commit イベントを送信してバッファーをサーバーに送信し、session.finish イベントを送信してこれ以上テキスト入力がないことを示します。

  4. サーバーは response.created で応答し、応答の生成を開始します。

  5. サーバーは response.output_item.addedresponse.content_part.added、および response.audio.delta イベントで応答します。

  6. 応答が完了すると、サーバーは response.audio.doneresponse.content_part.doneresponse.output_item.done、および response.done を返します。

  7. サーバーは session.finished で応答し、セッションを終了します。

ライフサイクル

クライアントイベント

サーバーイベント

セッションの初期化

session.update

セッション構成

session.created

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

session.updated

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

ユーザーテキスト入力

input_text_buffer.append

バッファーにテキストを追加

input_text_buffer.commit

バッファーをサーバーに送信

input_text_buffer.clear

バッファーをクリア

input_text_buffer.committed

サーバーが送信されたテキストを受信しました

サーバーのオーディオ出力

なし

response.created

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

response.output_item.added

応答に新しい出力コンテンツ

response.content_part.added

アシスタントメッセージに新しい出力コンテンツが追加されました

response.audio.delta

モデルによって生成された増分オーディオ

response.content_part.done

アシスタントメッセージのテキストまたはオーディオコンテンツストリームが完了しました

response.output_item.done

アシスタントメッセージの出力アイテムストリーム全体が完了しました

response.audio.done

オーディオ生成が完了しました

response.done

応答が完了しました

命令制御

命令制御は、自然言語による記述を通じて音声表現を正確に制御する高度な音声合成機能です。複雑な音声パラメーターを調整することなく、簡単なテキスト記述で合成音声に特定のトーン、速度、感情、声の特徴を持たせることができます。

サポートされるモデル:Qwen3-TTS-Instruct-Flash-Realtime モデルでのみサポートされます。

使用方法instructions パラメーターを使用して命令内容を指定します。例:「ファッション製品の紹介に適した、明確に上昇するイントネーションで素早く話す。」

サポートされる言語:記述テキストは中国語と英語のみをサポートします。

長さ制限:1600 トークンを超えてはなりません。

適用シナリオ

  • オーディオブックおよびラジオドラマの吹き替え

  • 広告およびプロモーションビデオの吹き替え

  • ゲームキャラクターおよびアニメーションの吹き替え

  • 感情的にインテリジェントな音声アシスタント

  • ドキュメンタリーおよびニュース放送

高品質な音声記述の書き方:

  • 基本原則:

    1. 具体的であること、曖昧でないこと:「深い」「歯切れの良い」「速いペース」など、具体的な音声特性を表す言葉を使用します。「良い響き」や「普通」など、情報量の少ない主観的な用語は避けてください。

    2. 多次元的であること、単一次元的でないこと:良い記述は通常、複数の次元(後述のピッチ、速度、感情など)を組み合わせます。「高音」だけのような単一次元的な記述は、特徴的な効果を生み出すには広すぎます。

    3. 客観的であること、主観的でないこと:個人の好みではなく、音声自体の物理的および知覚的特性に焦点を当てます。例えば、「私のお気に入りの声」ではなく、「少し高めでエネルギッシュ」を使用できます。

    4. 独創的であること、模倣でないこと:特定の人(有名人や俳優など)の模倣を要求するのではなく、音声の特性を記述します。そのような要求は著作権リスクを伴い、モデルは直接の模倣をサポートしていません。

    5. 簡潔であること、冗長でないこと:すべての単語に意味があることを確認します。同義語を繰り返したり、「非常に素晴らしい声」のような無意味な強調語を使用したりすることは避けてください。

  • 記述の次元リファレンス:複数の次元を組み合わせて、より豊かな表現効果を生み出します。

    次元

    記述例

    ピッチ

    高い、中程度、低い、やや高い、やや低い

    速度

    速い、中程度、遅い、やや速い、やや遅い

    感情

    陽気、落ち着いた、優しい、真面目、活発、穏やか、癒し系

    特徴

    磁力的、歯切れの良い、ハスキー、まろやか、甘い、豊か、力強い

    目的

    ニュース放送、広告ナレーション、オーディオブック、アニメキャラクター、音声アシスタント、ドキュメンタリーナレーション

  • 例:

    • 標準的な放送スタイル:明瞭で正確な発音、完璧なアーティキュレーション

    • 感情の進行効果:通常の会話から叫び声へと音量が急速に増加し、率直な性格で、興奮しやすく表現豊か

    • 特殊な感情状態:泣いているために発音が少しこもり、わずかにかすれ、泣いていることによる明らかな緊張感がある

    • 広告ナレーションスタイル:やや高めのピッチ、中程度の速度、エネルギーと魅力に満ち、広告に適している

    • 優しく癒し系のスタイル:やや遅い速度、優しく甘いトーン、親しい友人のように思いやりがあり温かい

API リファレンス

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

ボイスクローン - API リファレンス

ボイスデザイン - API リファレンス

機能比較

機能

Qwen3-TTS-Instruct-Flash-Realtime

Qwen3-TTS-VD-Realtime

Qwen3-TTS-VC-Realtime

Qwen3-TTS-Flash-Realtime

Qwen-TTS-Realtime

サポート言語

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

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

中国語(標準語、北京語、上海語、四川語、南京語、陝西語、閩南語、天津語、広東語、音声により異なる)、英語、スペイン語、ロシア語、イタリア語、フランス語、韓国語、日本語、ドイツ語、ポルトガル語

中国語、英語

オーディオフォーマット

pcm、wav、mp3、opus

pcm

オーディオサンプルレート

8kHz、16kHz、24kHz、48kHz

24kHz

ボイスクローン

非サポート

サポート

非サポート

ボイスデザイン

非サポート

サポート

非サポート

SSML

非サポート

LaTeX

非サポート

音量調整

サポート

非サポート

速度調整

サポート

非サポート

ピッチ調整

サポート

非サポート

ビットレート調整

サポート

非サポート

タイムスタンプ

非サポート

命令

サポート

非サポート

ストリーミング入力

サポート

ストリーミング出力

サポート

レート制限

1分あたりのリクエスト数(RPM):180

qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27 RPM:180

qwen3-tts-flash-realtime-2025-09-18 RPM:10

RPM:10

1分あたりのトークン数(TPM):100,000

アクセス方法

Java/Python SDK、WebSocket API

価格

国際:10,000 文字あたり 0.143 ドル

中国本土:10,000 文字あたり 0.143 ドル

国際:10,000 文字あたり 0.143353 ドル

中国本土:10,000 文字あたり 0.143353 ドル

国際:10,000 文字あたり 0.13 ドル

中国本土:10,000 文字あたり 0.143353 ドル

中国本土:

  • 入力コスト:1,000 トークンあたり 0.345 ドル

  • 出力コスト:1,000 トークンあたり 1.721 ドル

サポートされる音声

モデルによってサポートされる音声は異なります。リクエストを行う際は、voice リクエストパラメーターを音声リストの音声パラメーター列に記載されている値に設定してください。

voice パラメーター

詳細

サポート言語

サポートモデル

Cherry

名前:Cherry

説明:陽気で前向き、フレンドリーで自然な若い女性。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

  • Qwen-TTS-Realtime:qwen-tts-realtime、qwen-tts-realtime-latest、qwen-tts-realtime-2025-07-15

Serena

名前:Serena

説明:優しい女性

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

  • Qwen-TTS-Realtime:qwen-tts-realtime、qwen-tts-realtime-latest、qwen-tts-realtime-2025-07-15

Ethan

名前:Ethan

説明:明るく、温かく、エネルギッシュで活気のある男性の声。標準的な中国語の発音で、わずかに北方のアクセントがあります。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

  • Qwen-TTS-Realtime:qwen-tts-realtime、qwen-tts-realtime-latest、qwen-tts-realtime-2025-07-15

Chelsie

名前:Chelsie

説明:2D バーチャル彼女

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

  • Qwen-TTS-Realtime:qwen-tts-realtime、qwen-tts-realtime-latest、qwen-tts-realtime-2025-07-15

Momo

名前:Momo

説明:陽気で可愛らしい女性の声。明るくデザインされています。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Vivian

名前:Vivian

説明:クールで可愛らしく、少し気の強い女性の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Moon

名前:Moon

説明:月白(男性)、元気でハンサム

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Maia

名前:Maia

説明:知性と優しさを兼ね備えた女性の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Kai

名前:Kai

説明:耳のスパのような癒しの声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Nofish

名前:Nofish

説明:「sh」や「zh」の音を発音できない男性デザイナー。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Bella

名前:Bella

説明:お酒は飲むが酔拳は使わない若い女の子。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Jennifer

名前:Jennifer

説明:プレミアムで映画のようなアメリカ英語の女性の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Ryan

名前:Ryan

説明:リズミカルでドラマチックな声で、リアリズムと緊張感があります。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Katerina

名前:Katerina

説明:豊かなリズムと余韻のある成熟した女性の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Aiden

名前:Aiden

説明:料理が得意な若いアメリカ人男性の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Eldric Sage

名前:Eldric Sage

説明:落ち着いて賢明な老人で、松の木のように風化した外見だが、心は鏡のように澄んでいる(男性)

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Mia

名前:Mia

説明:春の水のように優しく、初雪のように純粋(女性)

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Mochi

名前:Mochi

説明:子供らしい無邪気さを持ちながらも禅のような知恵を持つ、賢くて明るい「小さな大人」の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Bellona

名前:Bellona

説明:力強く響き渡る声で、明瞭な発音がキャラクターに命を吹き込み、リスナーの情熱をかき立てます。剣のぶつかり合う音と蹄の雷鳴が夢の中で響き、完璧にクリアで共鳴するトーンを通じて無数の声の世界を明らかにします。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Vincent

名前:Vincent

説明:独特のかすれたスモーキーな声で、広大な軍隊と英雄的な冒険の物語を即座に思い起こさせます。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Bunny

名前:Bunny

説明:「萌え」要素にあふれた女性キャラクター。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Neil

名前:Neil

説明:フラットなベースラインのイントネーションと、正確で明瞭な発音を持つプロのニュースアンカーの声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Elias

名前:Elias

説明:学術的な厳密さを保ちつつ、物語的な手法を用いて複雑なトピックを消化しやすいモジュールに分解します(女性)。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Arthur

名前:Arthur

説明:時間と乾いたタバコによって風化した素朴な声で、村の物語や奇妙な出来事をのんびりと語ります。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Nini

名前:Nini

説明:餅のように柔らかく粘りのある声で、「お兄ちゃん」という長引く呼び声は骨まで溶けるほど甘い。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Ebona

名前:Ebona

説明:ささやくような声で、あなたの内面の最も暗い隅で錆びた鍵がゆっくりと回るような声。そこには、認められていない子供時代の影や未知の恐怖が隠されています。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Seren

名前:Seren

説明:優しく癒される声で、より早く眠りにつくのを助けます。おやすみなさい、良い夢を。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Pip

名前:Pip

説明:いたずら好きでやんちゃだが、子供らしい無邪気さを残している。これはあなたの覚えているしんちゃんですか?(男性)

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Stella

名前:Stella

説明:普段は甘ったるくぼんやりした声だが、「月に代わってお仕置きよ!」と叫ぶと、否定できない愛と正義に満ち溢れます。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Bodega

名前:Bodega

説明:情熱的なスペインのおじさん

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Sonrisa

名前:Sonrisa

説明:温かく社交的なラテンアメリカの女性。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Alek

名前:Alek

説明:最初はロシアのように冷たく聞こえるが、ウールのコートの下は温かい声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Dolce

名前:Dolce

説明:のんびりした中年のイタリア人男性

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Sohee

名前:Sohee

説明:優しく、陽気で、感情表現豊かな韓国のお姉さん的存在。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Ono Anna

名前:Ono Anna

説明:元気でいたずら好きな若い女性で、幼なじみ。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Lenn

名前:Lenn

説明:核心は合理的だが、細部では反抗的—スーツを着てポストパンクを聴く若いドイツ人男性。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Emilien

名前:Emilien

説明:ロマンチックで成熟したフランス人男性

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Andre

名前:Andre

説明:魅力的で、自然で、心地よく、落ち着いた男性の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Radio Gol

名前:Radio Gol

説明:サッカーの詩人、Rádio Gol の声!「今日は名前を使ってサッカーの試合を実況します。」

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27

Jada

名前:Shanghai-Jada

説明:エネルギッシュな上海出身の女性

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Dylan

名前:Beijing-Dylan

説明:北京の胡同で育ったティーンエイジャーの少年。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Li

名前:Nanjing-Li

説明: 患者、男性のヨガ講師。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Marcus

名前:Shaanxi-Marcus

説明:顔が広く口数が少なく、心が誠実で声が低い—本物の陝西の味。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Roy

名前:Minnan-Roy

説明:ユーモラスで、率直で、活発な台湾の若い男性の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Peter

名前:Tianjin-Peter

説明: 天津クロストークのプロのツッコミ役の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Sunny

名前:Sichuan-Sunny

説明:心がとろけるような甘さの四川の女の子の声。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Eric

名前:Sichuan-Eric

説明:世俗から離れた四川省成都出身の男性。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Rocky

名前:Cantonese-Rocky

説明:ユーモラスで機知に富んだロッキーの声、オンラインチャットのためにここにいます。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18

Kiki

名前:Cantonese-Kiki

説明:香港出身の甘い親友の女性。

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

  • Qwen3-TTS-Flash-Realtime:qwen3-tts-flash-realtime、qwen3-tts-flash-realtime-2025-11-27、qwen3-tts-flash-realtime-2025-09-18