Qwen リアルタイム音声合成モデルは、ストリーミングテキスト入力とオーディオ出力により、低遅延の音声合成を提供します。人間のような様々な音声を提供し、複数の言語や方言に対応し、異なる言語間で一貫した音声を可能にします。また、このモデルは自動的にトーンを調整し、複雑なテキストをスムーズに処理します。
主な特徴
リアルタイムで高忠実度の音声を生成し、中国語や英語を含む複数の言語で自然な響きの音声に対応します。
音声クローン (参照オーディオから音声をクローンする) と音声デザイン (テキスト記述から音声を生成する) の 2 つの音声カスタマイズ方法を提供し、カスタム音声を迅速に作成します。
リアルタイム対話シナリオでの低遅延応答のために、ストリーミング入出力に対応します。
速度、ピッチ、音量、ビットレートを調整することで、音声パフォーマンスの詳細な制御を可能にします。
主要なオーディオフォーマットと互換性があり、最大 48 kHz のサンプルレートでのオーディオ出力に対応します。
適用範囲
サポートされているモデル:
国際 (シンガポール)
以下のモデルを呼び出す際は、シンガポールリージョンの API キー を選択してください:
Qwen3-TTS-VD-Realtime: qwen3-tts-vd-realtime-2025-12-16 (スナップショット)
Qwen3-TTS-VC-Realtime: 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 キー を選択してください:
Qwen3-TTS-VD-Realtime: qwen3-tts-vd-realtime-2025-12-16 (スナップショット)
Qwen3-TTS-VC-Realtime: 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-2025-12-16 | 音声デザインに対応しています。オーディオサンプルを必要とせず、テキスト記述からカスタム音声を作成するため、独自のブランド音声をゼロからデザインするのに最適です。 | システム音声または音声クローンには対応していません。 |
ブランドイメージ、専用音声、またはシステム音声の拡張のための音声カスタマイズ (オーディオサンプルに基づく) | qwen3-tts-vc-realtime-2025-11-27 | 音声クローンに対応しています。実際のオーディオサンプルから音声を迅速にクローンし、人間のようなブランドボイスプリントを作成し、高い忠実度と一貫性を保証します。 | 音声デザインおよびシステム音声には対応していません。 |
インテリジェントカスタマーサービスと対話型ボット | qwen3-tts-flash-realtime-2025-11-27 | ストリーミング入出力に対応しています。調整可能な速度とピッチにより、自然な対話体験を提供します。マルチフォーマットのオーディオ出力は、さまざまなデバイスに適応します。 | システム音声のみ対応しています。音声クローンまたは音声デザインには対応していません。 |
多言語コンテンツのブロードキャスト | qwen3-tts-flash-realtime-2025-11-27 | 複数の言語と中国語の方言に対応し、グローバルなコンテンツ配信のニーズに応えます。 | システム音声のみ対応しています。音声クローンおよびデザインには対応していません。 |
オーディオ読み上げとコンテンツ制作 | qwen3-tts-flash-realtime-2025-11-27 | 調整可能な音量、速度、ピッチは、オーディオブックやポッドキャストなどのコンテンツの詳細な制作要件を満たします。 | システム音声のみ対応しています。音声クローンも音声デザインも対応していません。 |
E コマースライブストリーミングと短編動画の吹き替え | qwen3-tts-flash-realtime-2025-11-27 | MP3 や Opus などの圧縮フォーマットに対応しており、帯域幅が制限されたシナリオに適しています。調整可能なパラメーターは、さまざまな吹き替えスタイルのニーズに応えます。 | システム音声のみ対応しています。音声クローンおよび音声デザインには対応していません。 |
詳細については、「機能比較」をご参照ください。
クイックスタート
コードを実行する前に、API キーを取得して設定する必要があります。SDK を使用してサービスを呼び出す場合は、最新バージョンの DashScope SDK をインストールする必要もあります。
システム音声を使用した音声合成
以下の例は、システム音声を使用して音声合成を行う方法を示しています。詳細については、「サポートされている音声」をご参照ください。
DashScope SDK の使用
Python
server_commit モード
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/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-flash-realtime',
callback=callback,
# 以下はシンガポールリージョンの URL です。北京リージョンのモデルを使用する場合は、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,
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(),
))
commit モード
import base64
import os
import threading
import dashscope
from dashscope.audio.qwen_tts_realtime import *
qwen_tts_realtime: QwenTtsRealtime = None
text_to_synthesize = [
'これは最初の文です。',
'これは 2 番目の文です。',
'これは 3 番目の文です。',
]
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/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-flash-realtime',
callback=callback,
# 以下はシンガポールリージョンの URL です。北京リージョンのモデルを使用する場合は、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,
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
サーバーコミットモード
// Dashscope SDK のバージョンは 2.21.16 以降である必要があります。
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-flash-realtime")
// 以下はシンガポールリージョンの URL です。中国 (北京) リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
.url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
// シンガポールリージョンと中国 (北京) リージョンでは API キーが異なります。API キーを取得するには、https://www.alibabacloud.com/help/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")
.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);
}
}Commit モード
// Dashscope SDK のバージョンは 2.21.16 以降である必要があります。
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-flash-realtime")
// 以下はシンガポールリージョンの URL です。中国 (北京) リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/realtime に置き換えてください。
.url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
// シンガポールリージョンと中国 (北京) リージョンでは API キーが異なります。API キーを取得するには、https://www.alibabacloud.com/help/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("[メトリック] 応答: " + qwenTtsRef.get().getResponseId() +
", 最初のオーディオ遅延: " + qwenTtsRef.get().getFirstAudioDelay() + " ms");
}
completeLatch.get().countDown();
default:
break;
}
}
@Override
public void onClose(int code, String reason) {
System.out.println("接続が閉じられました コード: " + 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")
.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 の使用
ランタイム環境の準備
お使いのオペレーティングシステムに pyaudio をインストールします。
macOS
brew install portaudio && pip install pyaudioDebian/Ubuntu
sudo apt-get install python3-pyaudio または pip install pyaudioCentOS
sudo yum install -y portaudio portaudio-devel && pip install pyaudioWindows
pip install pyaudioインストール後、pip を使用して WebSocket 依存関係をインストールします:
pip install websocket-client==1.8.0 websocketsクライアントの作成
ローカルに
tts_realtime_client.pyという名前の Python ファイルを作成し、以下のコードをファイルにコピーします:音声合成モードの選択
Realtime API は以下の 2 つのモードに対応しています:
server_commit モード
クライアントはテキストのみを送信します。サーバーはテキストをどのようにセグメント化し、いつ合成するかをインテリジェントに決定します。このモードは、GPS ナビゲーションなど、合成のタイミングを手動で制御する必要がない低遅延シナリオに適しています。
commit モード
クライアントはまずテキストをバッファーに追加し、その後、指定されたテキストを合成するようにサーバーを能動的にトリガーします。このモードは、ニュース放送など、文の区切りや間を詳細に制御する必要があるシナリオに適しています。
server_commit モード
tts_realtime_client.pyと同じディレクトリに、server_commit.pyという名前の別の Python ファイルを作成し、以下のコードをファイルにコピーします:server_commit.pyを実行して、Realtime API によってリアルタイムで生成されたオーディオを聞きます。commit モード
tts_realtime_client.pyと同じディレクトリに、commit.pyという名前の別の Python ファイルを作成し、以下のコードをファイルにコピーします:commit.pyを実行します。合成するテキストを複数回入力できます。Realtime API から返されたオーディオを聞くには、空の行で Enter キーを押します。
クローン音声を使用した音声合成
音声クローンサービスはオーディオプレビューを提供しません。クローンされた音声を試聴・評価するには、音声合成に適用する必要があります。
以下の例は、音声クローンによって生成されたカスタム音声を使用して音声合成を行い、元の音声に非常に類似した出力を生成する方法を示しています。この例は、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-2025-11-27" # 音声クローンと音声合成には同じモデルを使用する必要があります。
DEFAULT_PREFERRED_NAME = "guanyu"
DEFAULT_AUDIO_MIME_TYPE = "audio/mpeg"
VOICE_FILE_PATH = "voice.mp3" # 音声クローン用のローカルオーディオファイルの相対パス。
TEXT_TO_SYNTHESIZE = [
'そうでしょう?こういうスーパーが本当に好きなんです、',
'特に旧正月の時期は。',
'スーパーに行くと',
'なんだか',
'すごく、すごく幸せな気分になります!',
'たくさん買いたくなっちゃいます!'
]
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:
"""
音声を作成し、voice パラメーターを返します。
"""
# シンガポールリージョンと中国 (北京) リージョンでは API キーが異なります。API キーを取得するには、https://www.alibabacloud.com/help/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 です。中国 (北京) リージョンのモデルを使用する場合は、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/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] 接続が閉じられました code={close_status_code}, msg={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=DEFAULT_TARGET_MODEL,
callback=callback,
# 以下はシンガポールリージョンの URL です。中国 (北京) リージョンのモデルを使用する場合は、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), # 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()}, '
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-2025-11-27";
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/model-studio/get-api-key/ をご参照ください。
// 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: 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 です。中国 (北京) リージョンのモデルを使用する場合は、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/model-studio/get-api-key/ をご参照ください。
// 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: .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);
}
}デザイン音声を使用した音声合成
音声デザイン機能を使用すると、サービスはプレビューオーディオデータを返します。プレビューオーディオを聴いてニーズを満たしていることを確認してから、音声合成に使用できます。この方法は、呼び出しコストの削減に役立ちます。
カスタム音声を生成し、プレビューを聴きます。満足のいくものであれば次に進みます。そうでなければ、音声を再生成します。
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-2025-12-16", "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.text if 'response' in locals() else '応答なし'}") 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-2025-12-16\",\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(); } } }前のステップで生成されたカスタム音声を使用して音声合成を行います。
この例は、システム音声を使用した音声合成のための 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] 接続が閉じられました、code={close_status_code}, msg={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-2025-12-16", 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()}, ' f'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-2025-12-16") // シンガポールリージョンの 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 をご参照ください。
インタラクションフロー
Server_commit モード
session.update イベントの session.mode を "server_commit" に設定して、このモードを有効にします。サーバーはその後、テキストのセグメンテーションと合成のタイミングを自動的に管理します。
インタラクションフローは以下の通りです:
クライアントが
session.updateイベントを送信すると、サーバーはsession.createdおよびsession.updatedイベントで応答します。クライアントは
input_text_buffer.appendイベントを使用して、サーバー側のバッファーにテキストを追加します。サーバーはテキストのセグメンテーションと合成のタイミングをインテリジェントに管理し、
response.created、response.output_item.added、response.content_part.added、およびresponse.audio.deltaイベントを返します。サーバーは、応答が完了した後、
response.audio.done、response.content_part.done、response.output_item.done、およびresponse.doneイベントを送信します。サーバーは
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 応答が完了しました |
Commit モード
session.update イベントの session.mode を "commit" に設定して、このモードを有効にします。このモードでは、クライアントは応答を受信するためにテキストバッファーをサーバーに送信する必要があります。
インタラクションフローは以下の通りです:
クライアントが
session.updateイベントを送信すると、サーバーはsession.createdおよびsession.updatedイベントで応答します。クライアントは
input_text_buffer.appendイベントを送信して、サーバー側のバッファーにテキストを追加します。クライアントは
input_text_buffer.commitイベントを送信してバッファーをサーバーにコミットし、session.finishイベントを送信してテキスト入力が完了したことを示します。サーバーは
response.createdイベントを送信して応答生成を開始します。サーバーは
response.output_item.added、response.content_part.added、およびresponse.audio.deltaイベントを送信します。サーバーが応答した後、
response.audio.done、response.content_part.done、response.output_item.done、およびresponse.doneイベントを送信します。サーバーは
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 応答が完了しました |
API リファレンス
機能比較
特徴 | qwen3-tts-vd-realtime-2025-12-16 | qwen3-tts-vc-realtime-2025-11-27 | qwen3-tts-flash-realtime, qwen3-tts-flash-realtime-2025-11-27, qwen3-tts-flash-realtime-2025-09-18 | qwen-tts-realtime, qwen-tts-realtime-latest, qwen-tts-realtime-2025-07-15 |
サポートされている言語 | 中国語、英語、スペイン語、ロシア語、イタリア語、フランス語、韓国語、日本語、ドイツ語、ポルトガル語 | 中国語 (北京語、北京、上海、四川、南京、陝西、閩南、天津、広東語、音声によって異なる)、英語、スペイン語、ロシア語、イタリア語、フランス語、韓国語、日本語、ドイツ語、ポルトガル語 | 中国語と英語 | |
オーディオフォーマット | pcm、wav、mp3、opus | pcm | ||
オーディオサンプリングレート | 8 kHz、16 kHz、24 kHz、48 kHz | 24 kHz | ||
音声クローン | ||||
音声デザイン | ||||
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.143353 ドル 中国本土 (北京): 10,000 文字あたり 0.143353 ドル | 国際 (シンガポール): 10,000 文字あたり 0.13 ドル 中国本土 (北京): 10,000 文字あたり 0.143353 ドル | 中国本土 (北京):
| |
サポートされている音声
サポートされている音声はモデルによって異なります。リクエストパラメーター voice を、表のvoice パラメーター列の対応する値に設定します。