Antarmuka utama dan parameter permintaan untuk sintesis suara real-time Qwen pada DashScope Java SDK.
Panduan pengguna: Untuk informasi mengenai model dan rekomendasi pemilihan, lihat Sintesis suara real-time – Qwen atau Sintesis suara – Qwen.
Prasyarat
Diperlukan DashScope Java SDK versi 2.22.7 atau lebih baru.
Mulai menggunakan
Mode server commit
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")
// China (Beijing) region
.url("wss://dashscope-intl.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);
}
}
Mode commit
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")
// China (Beijing) region
.url("wss://dashscope-intl.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);
}
}
Untuk contoh lainnya, lihat repositori GitHub.
Parameter permintaan
Konfigurasikan parameter permintaan berikut menggunakan metode chaining atau setter dari objek QwenTtsRealtimeParam, lalu teruskan objek tersebut ke konstruktor QwenTtsRealtime.
|
Parameter |
Tipe |
Wajib |
Deskripsi |
|
model |
String |
Ya |
Nama model (lihat Model yang didukung). |
|
url |
String |
Ya |
China (Beijing): Singapura: |
Konfigurasikan parameter permintaan berikut menggunakan metode chaining atau setter dari objek QwenTtsRealtimeConfig, lalu teruskan objek tersebut ke metode updateSession.
|
Parameter |
Type |
Wajib |
Deskripsi |
|
voice |
String |
Ya |
Voice untuk sintesis suara. Untuk informasi selengkapnya, lihat Voice yang didukung. Voice sistem dan voice kustom didukung:
|
|
languageType |
String |
Tidak |
Menentukan bahasa audio hasil sintesis. Nilai default adalah
|
|
mode |
String |
Tidak |
Mode interaksi. Nilai yang valid:
|
|
format |
String |
Tidak |
Format output audio dari model. Format yang didukung:
Qwen-TTS-Realtime (lihat Model yang didukung) hanya mendukung |
|
sampleRate |
int |
Tidak |
Laju sampel output audio dari model, dalam Hz. Laju sampel yang didukung:
Qwen-TTS-Realtime (lihat Model yang didukung) hanya mendukung 24000. |
|
speechRate |
float |
Tidak |
Kecepatan bicara audio. Nilai 1.0 adalah kecepatan normal. Nilai kurang dari 1.0 lebih lambat, dan nilai lebih dari 1.0 lebih cepat. Nilai default: 1.0. Rentang valid: [0.5, 2.0]. Qwen-TTS-Realtime (lihat Model yang didukung) tidak mendukung parameter ini. |
|
volume |
int |
Tidak |
Volume audio. Nilai default: 50. Rentang valid: [0, 100]. Qwen-TTS-Realtime (lihat Model yang didukung) tidak mendukung parameter ini. |
|
pitchRate |
float |
Tidak |
Nada audio hasil sintesis. Nilai default: 1.0. Rentang valid: [0.5, 2.0]. Qwen-TTS-Realtime (lihat Model yang didukung) tidak mendukung parameter ini. |
|
bitRate |
int |
Tidak |
Menentukan bitrate audio dalam kbps. Bitrate yang lebih tinggi menghasilkan kualitas audio lebih baik dan ukuran file lebih besar. Parameter ini hanya tersedia ketika format audio ( Nilai default: 128. Rentang valid: [6, 510]. Qwen-TTS-Realtime (lihat Model yang didukung) tidak mendukung parameter ini. |
|
instructions |
String |
Tidak |
Mengatur instruksi. Untuk informasi selengkapnya, lihat Sintesis suara real-time - Qwen. Nilai default: Tidak ada. Parameter tidak aktif jika tidak diatur. Batas panjang: Panjang tidak boleh melebihi 1600 token. Bahasa yang didukung: Hanya bahasa Mandarin dan Inggris. Cakupan: Fitur ini hanya tersedia untuk seri model Qwen3-TTS-Instruct-Flash-Realtime. |
|
optimizeInstructions |
boolean |
Tidak |
Menentukan apakah akan mengoptimalkan Nilai default: false. Perilaku: Ketika diatur ke true, sistem meningkatkan dan menulis ulang konten Skenario: Direkomendasikan untuk skenario yang membutuhkan ekspresi suara berkualitas tinggi dan detail halus. Ketergantungan: Parameter ini bergantung pada parameter Cakupan: Fitur ini hanya tersedia untuk seri model Qwen3-TTS-Instruct-Flash-Realtime. |
Antarmuka utama
Kelas QwenTtsRealtime
Untuk mengimpor:
import com.alibaba.dashscope.audio.qwen_tts_realtime.QwenTtsRealtime;
|
Metode |
Signature |
Event server |
Deskripsi |
|
connect |
|
Sesi dibuat Konfigurasi sesi diperbarui |
Membuka koneksi WebSocket ke server. |
|
updateSession |
|
Konfigurasi sesi diperbarui |
Memperbarui konfigurasi sesi. Lihat Parameter permintaan. Setelah terhubung, server mengembalikan konfigurasi input dan output default untuk sesi. Panggil metode ini segera setelah connect() untuk mengganti nilai default. Server memvalidasi parameter saat menerima event session.update. Jika ada parameter yang tidak valid, server mengembalikan error. Jika tidak, server memperbarui konfigurasi sesi di sisi server. |
|
appendText |
|
Tidak ada |
Menambahkan segmen teks ke buffer input sisi server. Buffer menyimpan teks hingga Anda mengirimkannya.
|
|
clearAppendedText |
|
Menghapus teks yang diterima oleh server |
Menghapus semua teks dalam buffer input sisi server. |
|
commit |
|
Commit teks dan picu sintesis suara Konten output baru muncul dalam respons Konten output baru ditambahkan ke item pesan asisten Model menghasilkan audio secara inkremental Generasi audio selesai Streaming konten audio untuk pesan asisten selesai Streaming seluruh item output untuk pesan asisten selesai Respons selesai |
Commit teks yang sebelumnya ditambahkan ke buffer sisi server dan langsung mensintesis semua teks. Mengembalikan error jika buffer kosong.
|
|
finish |
|
Respons selesai |
Menghentikan tugas saat ini. |
|
close |
|
Tidak ada |
Menutup koneksi. |
|
getSessionId |
|
Tidak ada |
Mengembalikan ID sesi dari tugas saat ini. |
|
getResponseId |
|
Tidak ada |
Mengembalikan ID respons dari respons terbaru. |
|
getFirstAudioDelay |
|
Tidak ada |
Mengembalikan latensi paket audio pertama dalam milidetik. |
Antarmuka callback (QwenTtsRealtimeCallback)
|
Metode |
Parameter |
Nilai kembali |
Deskripsi |
|
Tidak ada |
Tidak ada |
Dipanggil segera setelah koneksi WebSocket terbentuk. |
|
message: event respons sisi server. |
Tidak ada |
Dipanggil ketika server mengirim event, termasuk respons panggilan API dan audio yang dihasilkan model. Lihat Event sisi server. |
|
code: kode status penutupan WebSocket. reason: alasan penutupan. |
Tidak ada |
Dipanggil setelah server menutup koneksi. |