DashScope Java SDK のQwen リアルタイム音声合成における、主要なインターフェースとリクエストパラメーターです。
ユーザーガイド:モデルの紹介と選択の推奨事項については、「リアルタイム音声合成 – Qwen」または「音声合成 – Qwen」をご参照ください。
前提条件
DashScope Java SDK 2.22.7 以降が必要です。
クイックスタート
サーバーコミットモード
appendText()
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.*;
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 = {
"Right? I really love this kind of supermarket.",
"Especially during the Chinese New Year.",
"Going to the supermarket.",
"It just makes me feel.",
"Super, super happy!",
"I want to buy so many things!"
};
public static QwenTtsRealtimeAudioFormat ttsFormat = QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT;
// リアルタイム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<>();
private ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream();
// オーディオフォーマットとオーディオラインを初期化します。
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);
// オーディオデータをtotalAudioStreamに書き込みます。
try {
totalAudioStream.write(rawAudio);
} catch (IOException e) {
throw new RuntimeException(e);
}
} 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, IOException {
stopped.set(true);
decoderThread.join();
playerThread.join();
// 完全なオーディオファイルを保存します。
File file = new File("TotalAudio_"+ttsFormat.getSampleRate()+"."+ttsFormat.getFormat());
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(totalAudioStream.toByteArray());
}
if (line != null && line.isRunning()) {
line.drain();
line.close();
}
}
}
public static void main(String[] args) throws InterruptedException, LineUnavailableException, IOException {
QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder()
// 指示コントロールを使用する場合は、モデルをqwen3-tts-instruct-flash-realtimeに置き換えてください。
.model("qwen3-tts-flash-realtime")
// シンガポールリージョン
.url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
// 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":
// セッション作成を処理します。
if (message.has("session")) {
String eventId = message.get("event_id").getAsString();
String sessionId = message.get("session").getAsJsonObject().get("id").getAsString();
System.out.println("[onEvent] session.created, session_id: "
+ sessionId + ", event_id: " + eventId);
}
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(ttsFormat)
.mode("server_commit")
// 指示コントロールを使用する場合は、以下の行のコメントを解除し、モデルを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);
}
}
コミットモード
commit()
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.*;
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 Main {
public static QwenTtsRealtimeAudioFormat ttsFormat = QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT;
// リアルタイム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<>();
private ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream();
// オーディオフォーマットとオーディオラインを初期化します。
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);
// オーディオデータをtotalAudioStreamに書き込みます。
try {
totalAudioStream.write(rawAudio);
} catch (IOException e) {
throw new RuntimeException(e);
}
} 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();
// 完全なオーディオファイルを保存します。
File file = new File("TotalAudio_"+ttsFormat.getSampleRate()+"."+ttsFormat.getFormat());
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(totalAudioStream.toByteArray());
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
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()
// 指示コントロールを使用する場合は、モデルをqwen3-tts-instruct-flash-realtimeに置き換えてください。
.model("qwen3-tts-flash-realtime")
// シンガポールリージョン
.url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
// 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() {
@Override
public void onOpen() {
System.out.println("connection opened");
System.out.println("Enter text and press Enter to send. Enter 'quit' to exit the program.");
}
@Override
public void onEvent(JsonObject message) {
String type = message.get("type").getAsString();
switch(type) {
case "session.created":
System.out.println("start session: " + message.get("session").getAsJsonObject().get("id").getAsString());
break;
case "response.audio.delta":
String recvAudioB64 = message.get("delta").getAsString();
byte[] rawAudio = Base64.getDecoder().decode(recvAudioB64);
// オーディオをリアルタイムで再生します。
audioPlayer.write(recvAudioB64);
break;
case "response.done":
System.out.println("response done");
// オーディオ再生が完了するまで待機します。
try {
audioPlayer.waitForComplete();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 次のユーザー入力に備えます。
completeLatch.get().countDown();
break;
case "session.finished":
System.out.println("session finished");
if (qwenTtsRef.get() != null) {
System.out.println("[Metric] response: " + qwenTtsRef.get().getResponseId() +
", first audio delay: " + qwenTtsRef.get().getFirstAudioDelay() + " ms");
}
completeLatch.get().countDown();
default:
break;
}
}
@Override
public void onClose(int code, String reason) {
System.out.println("connection closed code: " + code + ", reason: " + reason);
try {
// 再生が完了するまで待機し、その後プレーヤーをシャットダウンします。
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(ttsFormat)
.mode("commit")
// 指示コントロールを使用する場合は、以下の行のコメントを解除し、モデルをqwen3-tts-instruct-flash-realtimeに置き換えてください。
// .instructions("")
// .optimizeInstructions(true)
.build();
qwenTtsRealtime.updateSession(config);
// ユーザー入力をループで読み取ります。
while (true) {
System.out.print("Enter the text to synthesize: ");
String text = scanner.nextLine();
// ユーザーが 'quit' を入力したら終了します。
if ("quit".equalsIgnoreCase(text.trim())) {
System.out.println("Closing the connection...");
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);
}
}
その他のサンプルについては、「GitHub リポジトリ」をご参照ください。
リクエストパラメーター
QwenTtsRealtimeParam オブジェクトのメソッドチェーンまたはセッターを使用して以下のリクエストパラメーターを設定し、そのオブジェクトを QwenTtsRealtime コンストラクターに渡します。
|
パラメーター |
タイプ |
必須 |
説明 |
|
model |
文字列 |
はい |
モデル名 (「サポートされているモデル」をご参照ください)。 |
|
url |
文字列 |
はい |
China (Beijing): Singapore: |
QwenTtsRealtimeConfig オブジェクトのメソッドチェーンまたはセッターを使用して以下のリクエストパラメーターを設定し、そのオブジェクトを updateSession メソッドに渡します。
|
パラメーター |
タイプ |
必須 |
説明 |
|
voice |
文字列 |
はい |
音声合成に使用する音声です。詳細については、「サポートされる音声」をご参照ください。 システム音声とカスタム音声がサポートされています。
|
|
languageType |
文字列 |
いいえ |
合成オーディオの言語です。デフォルト値は
|
|
mode |
文字列 |
いいえ |
インタラクションパターンです。有効な値は以下のとおりです。
|
|
format |
文字列 |
いいえ |
モデルからのオーディオ出力フォーマットです。 サポートされるフォーマット:
Qwen-TTS-Realtime (「サポートされるモデル」をご参照ください) は |
|
sampleRate |
int |
いいえ |
モデルからのオーディオ出力のサンプルレート (Hz) です。 サポートされるサンプルレート:
Qwen-TTS-Realtime (「サポートされるモデル」をご参照ください) は 24000 のみをサポートします。 |
|
speechRate |
float |
いいえ |
オーディオの話速です。1.0 が通常速度です。1.0 未満は低速、1.0 より大きい値は高速になります。 デフォルト値:1.0 有効範囲:[0.5, 2.0] Qwen-TTS-Realtime (「サポートされるモデル」をご参照ください) はこのパラメーターをサポートしません。 |
|
volume |
int |
いいえ |
オーディオのボリュームです。 デフォルト値:50 有効範囲:[0, 100] Qwen-TTS-Realtime (「サポートされるモデル」をご参照ください) はこのパラメーターをサポートしません。 |
|
pitchRate |
float |
いいえ |
合成オーディオのピッチです。 デフォルト値:1.0 有効範囲:[0.5, 2.0] Qwen-TTS-Realtime (「サポートされるモデル」をご参照ください) はこのパラメーターをサポートしません。 |
|
bitRate |
int |
いいえ |
ビットレート (kbps) です。ビットレートが高いほどオーディオ品質が向上し、ファイルサイズも大きくなります。このパラメーターは、オーディオフォーマット ( デフォルト値:128 有効範囲:[6, 510] Qwen-TTS-Realtime (「サポートされるモデル」をご参照ください) はこのパラメーターをサポートしません。 |
|
instructions |
文字列 |
いいえ |
命令を設定します。「リアルタイム音声合成 - Qwen」をご参照ください。 デフォルト値:なし。設定されていない場合、このパラメーターは効果を持ちません。 長さ制限:長さは 1600 トークンを超えてはなりません。 サポート言語:中国語と英語のみサポートされます。 適用範囲:この機能は Qwen3-TTS-Instruct-Flash-Realtime モデルシリーズでのみ利用可能です。 |
|
optimizeInstructions |
ブール値 |
いいえ |
デフォルト値:false 動作:true に設定すると、システムはセマンティクスを強化し、 適用シナリオ:高品質かつ詳細な音声表現が求められるシナリオでこの機能を有効にしてください。 依存関係:このパラメーターは 適用範囲:この機能は Qwen3-TTS-Instruct-Flash-Realtime モデルシリーズでのみ利用可能です。 |
主要なインターフェイス
QwenTtsRealtime クラス
インポート:
import com.alibaba.dashscope.audio.qwen_tts_realtime.QwenTtsRealtime;
|
メソッド |
シグネチャ |
サーバー イベント |
説明 |
|
connect |
|
セッションが作成されました セッション設定が更新されました |
サーバーへの WebSocket 接続を開きます。 |
|
updateSession |
|
セッション設定が更新されました |
セッション設定を更新します。リクエストパラメーターをご参照ください。 接続後、サーバーはセッションのデフォルトの入力および出力設定を返します。デフォルト値をオーバーライドするには、 サーバーは、 |
|
appendText |
|
なし |
テキストセグメントをサーバーサイドの入力バッファーに追加します。バッファーは、コミットされるまでテキストを保持します。
|
|
clearAppendedText |
|
サーバーが受信したテキストがクリアされました |
サーバーサイドの入力バッファー内のすべてのテキストをクリアします。 |
|
commit |
|
テキストがコミットされ、音声合成がトリガーされました 応答に新しい出力コンテンツが表示されました アシスタントメッセージアイテムに新しい出力コンテンツが追加されました モデルがオーディオを段階的に生成しました オーディオ生成が完了しました アシスタントメッセージのオーディオコンテンツのストリーミングが完了しました アシスタントメッセージの出力アイテム全体のストリーミングが完了しました 応答が完了しました |
以前にサーバーサイドのバッファーに追加されたテキストをコミットし、すべてのテキストを直ちに合成します。バッファーが空の場合はエラーを返します。
|
|
finish |
|
セッションが終了しました |
現在のタスクを停止します。 |
|
close |
|
なし |
接続を閉じます。 |
|
getSessionId |
|
なし |
現在のタスクのセッション ID を返します。 |
|
getResponseId |
|
なし |
最新の応答のレスポンス ID を返します。 |
|
getFirstAudioDelay |
|
なし |
最初のオーディオパケットのレイテンシーをミリ秒単位で返します。 |
コールバックインターフェイス (QwenTtsRealtimeCallback)
|
メソッド |
パラメーター |
戻り値 |
説明 |
|
なし |
なし |
WebSocket 接続が確立された直後に呼び出されます。 |
|
message:サーバーサイドの応答イベント。 |
なし |
サーバーがイベントを送信したときに呼び出されます。API 呼び出しの応答やモデルが生成したオーディオが含まれます。「サーバーサイド イベント」をご参照ください。 |
|
code:WebSocket 終了ステータスコード。 reason:終了理由。 |
なし |
サーバーが接続を閉じた後に呼び出されます。 |