Qwen リアルタイム音声合成 DashScope Java SDK の主要インターフェイスおよびリクエストパラメーター。
ユーザーガイド:モデルの概要および選定に関する推奨事項については、「リアルタイム音声合成 – Qwen」または「音声合成 – Qwen」をご参照ください。
シンガポールリージョン向けのレガシドメイン https://dashscope-intl.aliyuncs.com はまもなく非推奨になります。できるだけ早く、新しいドメイン https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com に移行してください。
前提条件
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;
// Real-time PCM audio player
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();
// Initialize the audio format and audio line.
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);
// Write audio data to 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();
}
// Play an audio chunk and block until playback completes.
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);
// Wait for the buffered audio to finish playing.
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();
// Save the complete audio file.
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()
// To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime.
.model("qwen3-tts-flash-realtime")
// Singapore endpoint. For China (Beijing), use wss://dashscope.aliyuncs.com/api-ws/v1/realtime.
.url("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime")
// API keys differ between Singapore and China (Beijing). See 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);
// Create a real-time audio player instance.
RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);
QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
@Override
public void onOpen() {
// Handle connection establishment.
}
@Override
public void onEvent(JsonObject message) {
String type = message.get("type").getAsString();
switch(type) {
case "session.created":
// Handle session creation.
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();
// Play audio in real time.
audioPlayer.write(recvAudioB64);
break;
case "response.done":
// Handle response completion.
break;
case "session.finished":
// Handle session termination.
completeLatch.get().countDown();
default:
break;
}
}
@Override
public void onClose(int code, String reason) {
// Handle connection closure.
}
});
qwenTtsRef.set(qwenTtsRealtime);
try {
qwenTtsRealtime.connect();
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
}
QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
.voice("Cherry")
.responseFormat(ttsFormat)
.mode("server_commit")
// To use instruction control, uncomment the following lines and replace the model with 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();
// Wait for audio playback to complete, then shut down the player.
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;
// Real-time PCM audio player
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();
// Initialize the audio format and audio line.
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);
// Write audio data to 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();
}
// Play an audio chunk and block until playback completes.
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);
// Wait for the buffered audio to finish playing.
Thread.sleep(audioLength - 10);
}
public void write(String b64Audio) {
b64AudioBuffer.add(b64Audio);
}
public void cancel() {
b64AudioBuffer.clear();
RawAudioBuffer.clear();
}
public void waitForComplete() throws InterruptedException {
// Wait for all buffered audio data to finish playing.
while (!b64AudioBuffer.isEmpty() || !RawAudioBuffer.isEmpty()) {
Thread.sleep(100);
}
// Wait for the audio line to drain.
line.drain();
}
public void shutdown() throws InterruptedException {
stopped.set(true);
decoderThread.join();
playerThread.join();
// Save the complete audio file.
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()
// To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime.
.model("qwen3-tts-flash-realtime")
// Singapore endpoint. For China (Beijing), use wss://dashscope.aliyuncs.com/api-ws/v1/realtime.
.url("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime")
// API keys differ between Singapore and China (Beijing). See 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));
// Create a real-time player instance.
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);
// Play audio in real time.
audioPlayer.write(recvAudioB64);
break;
case "response.done":
System.out.println("response done");
// Wait for audio playback to complete.
try {
audioPlayer.waitForComplete();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// Prepare for the next input.
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 {
// Wait for playback to complete, then shut down the player.
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")
// To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime.
// .instructions("")
// .optimizeInstructions(true)
.build();
qwenTtsRealtime.updateSession(config);
// Read user input in a loop.
while (true) {
System.out.print("Enter the text to synthesize: ");
String text = scanner.nextLine();
// Exit when the user enters 'quit'.
if ("quit".equalsIgnoreCase(text.trim())) {
System.out.println("Closing the connection...");
qwenTtsRealtime.finish();
completeLatch.get().await();
break;
}
// Skip empty input.
if (text.trim().isEmpty()) {
continue;
}
// Re-initialize the countdown latch.
completeLatch.set(new CountDownLatch(1));
// Send the text.
qwenTtsRealtime.appendText(text);
qwenTtsRealtime.commit();
// Wait for the current synthesis to complete.
completeLatch.get().await();
}
// Clean up resources.
audioPlayer.waitForComplete();
audioPlayer.shutdown();
scanner.close();
System.exit(0);
}
}
その他のサンプルについては、GitHub リポジトリをご参照ください。
リクエストパラメーター
QwenTtsRealtimeParam オブジェクトのチェーンメソッドまたはセッターを使用して、以下のリクエストパラメーターを設定し、オブジェクトを QwenTtsRealtime コンストラクターに渡します。
|
パラメーター |
型 |
必須 |
説明 |
|
model |
String |
はい |
モデル名(「サポートされるモデル」をご参照ください)。 |
|
url |
String |
はい |
中国 (北京): シンガポール:
|
QwenTtsRealtimeConfig オブジェクトのチェーンメソッドまたはセッターを使用して、以下のリクエストパラメーターを設定し、オブジェクトを updateSession メソッドに渡します。
|
パラメーター |
型 |
必須 |
説明 |
|
voice |
String |
はい |
音声合成に使用する音声。詳細については、「サポートされる音声」をご参照ください。 システム音声とカスタム音声がサポートされています。
|
|
languageType |
String |
いいえ |
合成音声の言語を指定します。デフォルト値は
|
|
mode |
String |
いいえ |
インタラクションモード。有効な値:
|
|
format |
String |
いいえ |
モデルから出力されるオーディオのフォーマット。 サポートされるフォーマット:
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 |
String |
いいえ |
命令を設定します。詳細については、「リアルタイム音声合成 - Qwen」をご参照ください。 デフォルト値:なし。設定されていない場合は、このパラメーターはアクティブになりません。 長さ制限:長さは 1600 トークンを超えてはいけません。 サポート言語:中国語および英語のみ。 適用範囲:この機能は Qwen3-TTS-Instruct-Flash-Realtime モデルシリーズでのみ利用可能です。 |
|
optimizeInstructions |
boolean |
いいえ |
デフォルト値:false 動作:true に設定すると、システムは 適用シナリオ:高品質かつ詳細な音声表現が求められるシナリオで推奨されます。 依存関係:このパラメーターは 適用範囲:この機能は Qwen3-TTS-Instruct-Flash-Realtime モデルシリーズでのみ利用可能です。 |
主要インターフェイス
QwenTtsRealtime クラス
インポート方法:
import com.alibaba.dashscope.audio.qwen_tts_realtime.QwenTtsRealtime;
|
メソッド |
シグネチャー |
サーバーイベント |
説明 |
|
connect |
|
セッション作成完了 セッション構成更新完了 |
サーバーへの WebSocket 接続を開きます。 |
|
updateSession |
|
セッション構成更新完了 |
セッション構成を更新します。「リクエストパラメーター」をご参照ください。 接続後、サーバーはセッションのデフォルト入力および出力構成を返します。connect() 直後にこのメソッドを呼び出して、デフォルト値を上書きしてください。 サーバーは session.update イベントを受信した時点でパラメーターを検証します。いずれかのパラメーターが無効な場合、サーバーはエラーを返します。それ以外の場合は、サーバー側のセッション構成を更新します。 |
|
appendText |
|
なし |
テキストセグメントをサーバー側の入力バッファーに追加します。バッファーは、テキストを送信するまでテキストを保持します。
|
|
clearAppendedText |
|
サーバーが受信したテキストをクリア |
サーバー側の入力バッファー内のすべてのテキストをクリアします。 |
|
commit |
|
テキストをコミットして音声合成をトリガー 応答に新しい出力コンテンツが表示 アシスタントメッセージアイテムに新しい出力コンテンツを追加 モデルがオーディオを増分的に生成 オーディオ生成完了 アシスタントメッセージのオーディオコンテンツのストリーミング完了 アシスタントメッセージの出力アイテム全体のストリーミング完了 応答完了 |
以前にサーバー側バッファーに追加されたテキストをコミットし、すべてのテキストを即座に合成します。バッファーが空の場合、エラーを返します。
|
|
finish |
|
応答完了 |
現在のタスクを停止します。 |
|
close |
|
なし |
接続を閉じます。 |
|
getSessionId |
|
なし |
現在のタスクのセッション ID を返します。 |
|
getResponseId |
|
なし |
最新の応答の応答 ID を返します。 |
|
getFirstAudioDelay |
|
なし |
最初のオーディオパケットの遅延時間(ミリ秒)を返します。 |
コールバックインターフェイス (QwenTtsRealtimeCallback)
|
メソッド |
パラメーター |
戻り値 |
説明 |
|
なし |
なし |
WebSocket 接続が確立された直後に呼び出されます。 |
|
message:サーバー側の応答イベント。 |
なし |
サーバーがイベントを送信したときに呼び出されます。これには API 呼び出しの応答およびモデルが生成したオーディオが含まれます。「サーバー側イベント」をご参照ください。 |
|
code:WebSocket のクローズステータスコード。 reason:クローズ理由。 |
なし |
サーバーが接続を閉じた後に呼び出されます。 |