Qwen TTS real-time melakukan streaming input teks dan output audio melalui WebSocket, menyediakan berbagai suara alami dalam berbagai bahasa dan dialek—bahkan dalam satu suara yang sama—serta menyesuaikan intonasi secara otomatis untuk menangani teks kompleks secara natural.
Fitur utama
Menghasilkan suara berkualitas tinggi secara real-time dengan output multibahasa alami, termasuk Mandarin dan Inggris
Menyediakan dua metode kustomisasi suara: kloning suara Qwen dan desain suara Qwen
Streaming input dan output untuk interaksi real-time berlatensi rendah
Kontrol detail halus atas laju ucapan, pitch, volume, dan bitrate
Format PCM, WAV, MP3, dan Opus dengan laju sampel hingga 48 kHz
Mendukung kontrol instruksi, yang membentuk ekspresivitas suara melalui instruksi bahasa alami
Model yang didukung
Tiongkok daratan
Jika Anda memilih cakupan penerapan Tiongkok daratan, sumber daya komputasi inferensi model hanya tersedia di Tiongkok daratan. Data statis disimpan di wilayah yang Anda pilih. Wilayah yang didukung: Tiongkok (Beijing).Untuk memanggil model berikut, gunakan Kunci API dari wilayah Beijing:
Qwen3-TTS-Instruct-Flash-Realtime: qwen3-tts-instruct-flash-realtime (versi stabil, saat ini setara dengan qwen3-tts-instruct-flash-realtime-2026-01-22), qwen3-tts-instruct-flash-realtime-2026-01-22 (snapshot terbaru)
Qwen3-TTS-VD-Realtime: qwen3-tts-vd-realtime-2026-01-15 (snapshot terbaru), qwen3-tts-vd-realtime-2025-12-16 (snapshot)
Qwen3-TTS-VC-Realtime: qwen3-tts-vc-realtime-2026-01-15 (snapshot terbaru), qwen3-tts-vc-realtime-2025-11-27 (snapshot)
Qwen3-TTS-Flash-Realtime: qwen3-tts-flash-realtime (versi stabil, saat ini setara dengan qwen3-tts-flash-realtime-2025-11-27), qwen3-tts-flash-realtime-2025-11-27 (snapshot terbaru), qwen3-tts-flash-realtime-2025-09-18 (snapshot)
Qwen-TTS-Realtime: qwen-tts-realtime (versi stabil, saat ini setara dengan qwen-tts-realtime-2025-07-15), qwen-tts-realtime-latest (versi terbaru, saat ini setara dengan qwen-tts-realtime-2025-07-15), qwen-tts-realtime-2025-07-15 (snapshot)
Internasional
Jika Anda memilih cakupan penerapan Internasional, sumber daya komputasi inferensi model dialokasikan secara dinamis di seluruh dunia, tidak termasuk Tiongkok daratan. Data statis disimpan di wilayah yang Anda pilih. Wilayah yang didukung: Singapura.Untuk memanggil model berikut, gunakan Kunci API dari wilayah Singapura:
Qwen3-TTS-Instruct-Flash-Realtime: qwen3-tts-instruct-flash-realtime (versi stabil, saat ini setara dengan qwen3-tts-instruct-flash-realtime-2026-01-22), qwen3-tts-instruct-flash-realtime-2026-01-22 (snapshot terbaru)
Qwen3-TTS-VD-Realtime: qwen3-tts-vd-realtime-2026-01-15 (snapshot terbaru), qwen3-tts-vd-realtime-2025-12-16 (snapshot)
Qwen3-TTS-VC-Realtime: qwen3-tts-vc-realtime-2026-01-15 (snapshot terbaru), qwen3-tts-vc-realtime-2025-11-27 (snapshot)
Qwen3-TTS-Flash-Realtime: qwen3-tts-flash-realtime (versi stabil, saat ini setara dengan qwen3-tts-flash-realtime-2025-11-27), qwen3-tts-flash-realtime-2025-11-27 (snapshot terbaru), qwen3-tts-flash-realtime-2025-09-18 (snapshot)
Internasional
Jika Anda memilih cakupan penerapan Internasional, sumber daya komputasi inferensi model dijadwalkan secara dinamis di seluruh dunia, kecuali di Tiongkok daratan. Data statis disimpan di wilayah yang Anda pilih. Wilayah yang didukung: Singapura.Untuk memanggil model berikut, gunakan Kunci API dari wilayah Singapura:
Qwen3-TTS-Instruct-Flash-Realtime: qwen3-tts-instruct-flash-realtime (versi stabil, saat ini setara dengan qwen3-tts-instruct-flash-realtime-2026-01-22), qwen3-tts-instruct-flash-realtime-2026-01-22 (versi snapshot terbaru)
Qwen3-TTS-VD-Realtime: qwen3-tts-vd-realtime-2026-01-15 (versi snapshot terbaru), qwen3-tts-vd-realtime-2025-12-16 (versi snapshot)
Qwen3-TTS-VC-Realtime: qwen3-tts-vc-realtime-2026-01-15 (versi snapshot terbaru), qwen3-tts-vc-realtime-2025-11-27 (versi snapshot)
Qwen3-TTS-Flash-Realtime: qwen3-tts-flash-realtime (versi stabil, saat ini setara dengan qwen3-tts-flash-realtime-2025-11-27), qwen3-tts-flash-realtime-2025-11-27 (versi snapshot terbaru), qwen3-tts-flash-realtime-2025-09-18 (versi snapshot)
Tiongkok daratan
Jika Anda memilih cakupan penerapan Tiongkok daratan, sumber daya komputasi inferensi model hanya tersedia di Tiongkok daratan. Data statis disimpan di wilayah yang Anda pilih. Wilayah yang didukung: Tiongkok (Beijing).Untuk memanggil model berikut, gunakan Kunci API dari wilayah Beijing:
Qwen3-TTS-Instruct-Flash-Realtime: qwen3-tts-instruct-flash-realtime (versi stabil, saat ini setara dengan qwen3-tts-instruct-flash-realtime-2026-01-22), qwen3-tts-instruct-flash-realtime-2026-01-22 (versi snapshot terbaru)
Qwen3-TTS-VD-Realtime: qwen3-tts-vd-realtime-2026-01-15 (versi snapshot terbaru), qwen3-tts-vd-realtime-2025-12-16 (versi snapshot)
Qwen3-TTS-VC-Realtime: qwen3-tts-vc-realtime-2026-01-15 (versi snapshot terbaru), qwen3-tts-vc-realtime-2025-11-27 (versi snapshot)
Qwen3-TTS-Flash-Realtime: qwen3-tts-flash-realtime (versi stabil, saat ini setara dengan qwen3-tts-flash-realtime-2025-11-27), qwen3-tts-flash-realtime-2025-11-27 (versi snapshot terbaru), qwen3-tts-flash-realtime-2025-09-18 (versi snapshot)
Qwen-TTS-Realtime: qwen-tts-realtime (versi stabil, saat ini setara dengan qwen-tts-realtime-2025-07-15), qwen-tts-realtime-latest (versi terbaru, saat ini setara dengan qwen-tts-realtime-2025-07-15), qwen-tts-realtime-2025-07-15 (versi snapshot)
Pilih model
|
Kasus penggunaan |
Model yang direkomendasikan |
Mengapa |
|
Suara kustom untuk identitas merek, suara eksklusif, atau perluasan suara sistem (berbasis teks) |
qwen3-tts-vd-realtime-2026-01-15 |
Desain suara: membuat suara kustom dari deskripsi teks tanpa sampel audio — ideal untuk membangun suara merek dari awal. |
|
Suara kustom untuk identitas merek, suara eksklusif, atau perluasan suara sistem (berbasis audio) |
qwen3-tts-vc-realtime-2026-01-15 |
Kloning suara: mereplikasi suara secara cepat dari sampel audio nyata untuk menghasilkan suara merek yang konsisten dan mirip manusia. |
|
Produksi konten ekspresif (audiobook, drama radio, pengisi suara game atau animasi) |
qwen3-tts-instruct-flash-realtime |
Kontrol instruksi: menentukan pitch, laju ucapan, emosi, dan karakteristik tokoh melalui bahasa alami untuk ekspresivitas kaya dan permainan peran. |
|
Narasi profesional (berita, dokumenter, iklan) |
qwen3-tts-instruct-flash-realtime |
Kontrol instruksi: mendeskripsikan gaya narasi dan nada (misalnya, "otoritatif dan formal" atau "kasual dan ramah") untuk produksi berkualitas profesional. |
|
Layanan pelanggan cerdas dan chatbot |
qwen3-tts-flash-realtime, qwen3-tts-instruct-flash-realtime |
Input/output streaming dengan laju ucapan dan pitch yang dapat disesuaikan. Varian Instruct memungkinkan penyesuaian nada dinamis (menenangkan, antusias, profesional) berdasarkan konteks percakapan. |
|
Pengiriman konten multibahasa |
qwen3-tts-flash-realtime, qwen3-tts-instruct-flash-realtime |
Banyak bahasa dan dialek Mandarin untuk distribusi konten global. |
|
Pembacaan audio dan produksi konten umum |
qwen3-tts-flash-realtime, qwen3-tts-instruct-flash-realtime |
Volume, laju ucapan, dan pitch yang dapat disesuaikan untuk produksi audiobook dan podcast. |
|
Livestreaming E-dagang dan pengisi suara video pendek |
qwen3-tts-flash-realtime, qwen3-tts-instruct-flash-realtime |
Format terkompresi MP3 dan Opus untuk skenario dengan bandwidth terbatas. |
Untuk informasi lebih lanjut, lihat Perbandingan fitur model
Memulai dengan cepat
Sebelum menjalankan kode, dapatkan dan konfigurasikan Kunci API Anda. Untuk integrasi berbasis SDK, instal SDK DashScope versi terbaru.
Sintesis suara dengan suara sistem
Contoh berikut mensintesis suara dengan suara sistem (lihat Suara yang didukung).
Untuk mengaktifkan kontrol instruksi, ganti model dengan qwen3-tts-instruct-flash-realtime dan atur parameter instructions.
SDK DashScope
Python
Mode commit server
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 = [
'Right? I love supermarkets like this.',
'Especially during Chinese New Year,',
'I go shopping at supermarkets.',
'And I feel',
'absolutely thrilled!',
'I want to buy so many things!'
]
DO_VIDEO_TEST = False
def init_dashscope_api_key():
"""
Setel Kunci API DashScope Anda. Informasi lebih lanjut:
https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
"""
# Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
if 'DASHSCOPE_API_KEY' in os.environ:
dashscope.api_key = os.environ[
'DASHSCOPE_API_KEY'] # Muat Kunci API dari variabel lingkungan DASHSCOPE_API_KEY
else:
dashscope.api_key = 'your-dashscope-api-key' # Setel Kunci API secara manual
class MyCallback(QwenTtsRealtimeCallback):
def __init__(self):
self.complete_event = threading.Event()
self.file = open('result_24k.pcm', 'wb')
def on_open(self) -> None:
print('koneksi dibuka, inisialisasi pemutar')
def on_close(self, close_status_code, close_msg) -> None:
self.file.close()
print('koneksi ditutup dengan kode: {}, pesan: {}, hancurkan pemutar'.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('mulai sesi: {}'.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'respons {qwen_tts_realtime.get_last_response_id()} selesai')
if 'session.finished' == type:
print('sesi selesai')
self.complete_event.set()
except Exception as e:
print('[Error] {}'.format(e))
return
def wait_for_finished(self):
self.complete_event.wait()
if __name__ == '__main__':
init_dashscope_api_key()
print('Menginisialisasi ...')
callback = MyCallback()
qwen_tts_realtime = QwenTtsRealtime(
# Untuk menggunakan kontrol instruksi, ganti model dengan qwen3-tts-instruct-flash-realtime
model='qwen3-tts-flash-realtime',
callback=callback,
# URL ini untuk wilayah Singapura. Jika Anda menggunakan wilayah Beijing, ganti dengan: 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,
# Untuk menggunakan kontrol instruksi, hapus komentar baris berikut dan ganti model dengan qwen3-tts-instruct-flash-realtime
# instructions='Berbicara cepat dengan intonasi naik, cocok untuk memperkenalkan produk fesyen.',
# optimize_instructions=True,
mode = 'server_commit'
)
for text_chunk in text_to_synthesize:
print(f'kirim teks: {text_chunk}')
qwen_tts_realtime.append_text(text_chunk)
time.sleep(0.1)
qwen_tts_realtime.finish()
callback.wait_for_finished()
print('[Metric] sesi: {}, delay audio pertama: {}'.format(
qwen_tts_realtime.get_session_id(),
qwen_tts_realtime.get_first_audio_delay(),
))
Mode commit
import base64
import os
import threading
import dashscope
from dashscope.audio.qwen_tts_realtime import *
qwen_tts_realtime: QwenTtsRealtime = None
text_to_synthesize = [
'This is the first sentence.',
'This is the second sentence.',
'This is the third sentence.',
]
DO_VIDEO_TEST = False
def init_dashscope_api_key():
"""
Setel Kunci API DashScope Anda. Informasi lebih lanjut:
https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
"""
# Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
if 'DASHSCOPE_API_KEY' in os.environ:
dashscope.api_key = os.environ[
'DASHSCOPE_API_KEY'] # Muat Kunci API dari variabel lingkungan DASHSCOPE_API_KEY
else:
dashscope.api_key = 'your-dashscope-api-key' # Setel Kunci API secara manual
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('koneksi dibuka, inisialisasi pemutar')
def on_close(self, close_status_code, close_msg) -> None:
print('koneksi ditutup dengan kode: {}, pesan: {}, hancurkan pemutar'.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('mulai sesi: {}'.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'respons {qwen_tts_realtime.get_last_response_id()} selesai')
self.complete_event.set()
self.file.close()
if 'session.finished' == type:
print('sesi selesai')
self.complete_event.set()
except Exception as e:
print('[Error] {}'.format(e))
return
def wait_for_response_done(self):
self.complete_event.wait()
if __name__ == '__main__':
init_dashscope_api_key()
print('Menginisialisasi ...')
callback = MyCallback()
qwen_tts_realtime = QwenTtsRealtime(
# Untuk menggunakan kontrol instruksi, ganti model dengan qwen3-tts-instruct-flash-realtime
model='qwen3-tts-flash-realtime',
callback=callback,
# URL ini untuk wilayah Singapura. Jika Anda menggunakan wilayah Beijing, ganti dengan: 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,
# Untuk menggunakan kontrol instruksi, hapus komentar baris berikut dan ganti model dengan qwen3-tts-instruct-flash-realtime
# instructions='Berbicara cepat dengan intonasi naik, cocok untuk memperkenalkan produk fesyen.',
# optimize_instructions=True,
mode = 'commit'
)
print(f'kirim teks: {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'kirim teks: {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'kirim teks: {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('[Metric] sesi: {}, delay audio pertama: {}'.format(
qwen_tts_realtime.get_session_id(),
qwen_tts_realtime.get_first_audio_delay(),
))
Java
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;
// Pemutar audio PCM real-time
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();
// Inisialisasi format audio dan saluran audio.
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);
// Tulis data audio ke 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();
}
// Putar potongan audio dan blokir hingga pemutaran selesai.
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);
// Tunggu hingga audio dalam buffer selesai diputar.
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();
// Simpan file audio lengkap.
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()
// Untuk menggunakan kontrol instruksi, ganti model dengan qwen3-tts-instruct-flash-realtime.
.model("qwen3-tts-flash-realtime")
// Titik akhir Singapura. Untuk Tiongkok (Beijing), gunakan wss://dashscope.aliyuncs.com/api-ws/v1/realtime.
.url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
// Kunci API berbeda antara Singapura dan Tiongkok (Beijing). Lihat https://www.alibabacloud.com/help/zh/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);
// Buat instance pemutar audio real-time.
RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);
QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
@Override
public void onOpen() {
// Tangani pembentukan koneksi.
}
@Override
public void onEvent(JsonObject message) {
String type = message.get("type").getAsString();
switch(type) {
case "session.created":
// Tangani pembuatan sesi.
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();
// Putar audio secara real-time.
audioPlayer.write(recvAudioB64);
break;
case "response.done":
// Tangani penyelesaian respons.
break;
case "session.finished":
// Tangani penghentian sesi.
completeLatch.get().countDown();
default:
break;
}
}
@Override
public void onClose(int code, String reason) {
// Tangani penutupan koneksi.
}
});
qwenTtsRef.set(qwenTtsRealtime);
try {
qwenTtsRealtime.connect();
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
}
QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
.voice("Cherry")
.responseFormat(ttsFormat)
.mode("server_commit")
// Untuk menggunakan kontrol instruksi, hapus komentar baris berikut dan ganti model dengan 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();
// Tunggu hingga pemutaran audio selesai, lalu matikan pemutar.
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;
// Pemutar audio PCM real-time
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();
// Inisialisasi format audio dan saluran audio.
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);
// Tulis data audio ke 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();
}
// Putar potongan audio dan blokir hingga pemutaran selesai.
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);
// Tunggu hingga audio dalam buffer selesai diputar.
Thread.sleep(audioLength - 10);
}
public void write(String b64Audio) {
b64AudioBuffer.add(b64Audio);
}
public void cancel() {
b64AudioBuffer.clear();
RawAudioBuffer.clear();
}
public void waitForComplete() throws InterruptedException {
// Tunggu hingga semua data audio dalam buffer selesai diputar.
while (!b64AudioBuffer.isEmpty() || !RawAudioBuffer.isEmpty()) {
Thread.sleep(100);
}
// Tunggu hingga saluran audio dikosongkan.
line.drain();
}
public void shutdown() throws InterruptedException {
stopped.set(true);
decoderThread.join();
playerThread.join();
// Simpan file audio lengkap.
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()
// Untuk menggunakan kontrol instruksi, ganti model dengan qwen3-tts-instruct-flash-realtime.
.model("qwen3-tts-flash-realtime")
// Titik akhir Singapura. Untuk Tiongkok (Beijing), gunakan wss://dashscope.aliyuncs.com/api-ws/v1/realtime.
.url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
// Kunci API berbeda antara Singapura dan Tiongkok (Beijing). Lihat https://www.alibabacloud.com/help/zh/model-studio/get-api-key.
.apikey(System.getenv("DASHSCOPE_API_KEY"))
.build();
AtomicReference<CountDownLatch> completeLatch = new AtomicReference<>(new CountDownLatch(1));
// Buat instance pemutar real-time.
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("koneksi dibuka");
System.out.println("Masukkan teks dan tekan Enter untuk mengirim. Masukkan 'quit' untuk keluar dari program.");
}
@Override
public void onEvent(JsonObject message) {
String type = message.get("type").getAsString();
switch(type) {
case "session.created":
System.out.println("mulai sesi: " + message.get("session").getAsJsonObject().get("id").getAsString());
break;
case "response.audio.delta":
String recvAudioB64 = message.get("delta").getAsString();
byte[] rawAudio = Base64.getDecoder().decode(recvAudioB64);
// Putar audio secara real-time.
audioPlayer.write(recvAudioB64);
break;
case "response.done":
System.out.println("respons selesai");
// Tunggu hingga pemutaran audio selesai.
try {
audioPlayer.waitForComplete();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// Siapkan untuk input berikutnya.
completeLatch.get().countDown();
break;
case "session.finished":
System.out.println("sesi selesai");
if (qwenTtsRef.get() != null) {
System.out.println("[Metric] respons: " + qwenTtsRef.get().getResponseId() +
", delay audio pertama: " + qwenTtsRef.get().getFirstAudioDelay() + " ms");
}
completeLatch.get().countDown();
default:
break;
}
}
@Override
public void onClose(int code, String reason) {
System.out.println("koneksi ditutup kode: " + code + ", alasan: " + reason);
try {
// Tunggu hingga pemutaran selesai, lalu matikan pemutar.
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")
// Untuk menggunakan kontrol instruksi, hapus komentar baris berikut dan ganti model dengan qwen3-tts-instruct-flash-realtime.
// .instructions("")
// .optimizeInstructions(true)
.build();
qwenTtsRealtime.updateSession(config);
// Baca input pengguna dalam loop.
while (true) {
System.out.print("Masukkan teks untuk disintesis: ");
String text = scanner.nextLine();
// Keluar ketika pengguna memasukkan 'quit'.
if ("quit".equalsIgnoreCase(text.trim())) {
System.out.println("Menutup koneksi...");
qwenTtsRealtime.finish();
completeLatch.get().await();
break;
}
// Lewati input kosong.
if (text.trim().isEmpty()) {
continue;
}
// Inisialisasi ulang latch countdown.
completeLatch.set(new CountDownLatch(1));
// Kirim teks.
qwenTtsRealtime.appendText(text);
qwenTtsRealtime.commit();
// Tunggu hingga sintesis saat ini selesai.
completeLatch.get().await();
}
// Bersihkan sumber daya.
audioPlayer.waitForComplete();
audioPlayer.shutdown();
scanner.close();
System.exit(0);
}
}WebSocket API
-
Siapkan lingkungan
Python
Instal pyaudio sesuai sistem operasi Anda.
macOS
brew install portaudio && pip install pyaudioDebian/Ubuntu
sudo apt-get install python3-pyaudio # atau pip install pyaudioCentOS
sudo yum install -y portaudio portaudio-devel && pip install pyaudioWindows
pip install pyaudioSetelah instalasi, instal dependensi WebSocket melalui pip:
pip install websocket-client==1.8.0 websocketsJava
Tambahkan dependensi berikut ke proyek Anda:
Maven
Tambahkan berikut ke
pom.xml:<!-- Library Java-WebSocket --> <dependency> <groupId>org.java-websocket</groupId> <artifactId>Java-WebSocket</artifactId> <version>1.5.7</version> </dependency> <!-- Gson untuk pemrosesan JSON --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.13.1</version> </dependency>Gradle
Tambahkan berikut ke
build.gradle:// Library Java-WebSocket implementation("org.java-websocket:Java-WebSocket:1.5.7") // Gson untuk pemrosesan JSON implementation("com.google.code.gson:gson:2.13.1") -
Buat klien
Python
Buat file Python bernama
tts_realtime_client.pydan salin kode berikut ke dalamnya:Java
Buat file Java bernama
TTSRealtimeClient.javadan salin kode berikut ke dalamnya:import com.google.gson.Gson; import com.google.gson.JsonObject; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import java.net.URI; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; /** * Klien untuk berinteraksi dengan API TTS Realtime. * * Kelas ini menyediakan metode untuk terhubung ke API TTS Realtime, mengirim data teks, menerima output audio, dan mengelola koneksi WebSocket. */ public class TTSRealtimeClient { public enum SessionMode { SERVER_COMMIT("server_commit"), COMMIT("commit"); private final String value; SessionMode(String value) { this.value = value; } public String getValue() { return value; } } /** * Antarmuka callback audio */ public interface AudioCallback { void onAudio(byte[] audioData); } private final String baseUrl; private final String apiKey; private final String voice; private final SessionMode mode; private final String languageType; private final AudioCallback audioCallback; private final Gson gson = new Gson(); private WebSocketClient ws; private CountDownLatch responseDoneLatch; private CountDownLatch sessionFinishedLatch; public TTSRealtimeClient(String baseUrl, String apiKey, String voice, SessionMode mode, AudioCallback audioCallback, String languageType) { this.baseUrl = baseUrl; this.apiKey = apiKey; this.voice = voice; this.mode = mode; this.audioCallback = audioCallback; this.languageType = languageType; } public TTSRealtimeClient(String baseUrl, String apiKey, String voice, SessionMode mode, AudioCallback audioCallback) { this(baseUrl, apiKey, voice, mode, audioCallback, "Auto"); } /** * Membuat koneksi WebSocket ke API TTS Realtime. */ public void connect() throws Exception { Map<String, String> headers = new HashMap<>(); headers.put("Authorization", "Bearer " + apiKey); responseDoneLatch = new CountDownLatch(0); sessionFinishedLatch = new CountDownLatch(1); ws = new WebSocketClient(new URI(baseUrl), headers) { @Override public void onOpen(ServerHandshake handshake) { System.out.println("Koneksi WebSocket dibentuk"); // Kirim konfigurasi sesi default JsonObject session = new JsonObject(); session.addProperty("mode", mode.getValue()); session.addProperty("voice", TTSRealtimeClient.this.voice); // Untuk menggunakan fitur kontrol instruksi, hapus komentar baris di bawah dan ganti model dengan qwen3-tts-instruct-flash-realtime // session.addProperty("instructions", "Berbicara cepat dengan intonasi naik yang terlihat jelas, cocok untuk memperkenalkan produk fesyen."); // session.addProperty("optimize_instructions", true); session.addProperty("language_type", languageType); session.addProperty("response_format", "pcm"); session.addProperty("sample_rate", 24000); updateSession(session); } @Override public void onMessage(String message) { JsonObject event = gson.fromJson(message, JsonObject.class); String eventType = event.has("type") ? event.get("type").getAsString() : ""; if (!"response.audio.delta".equals(eventType)) { System.out.println("Menerima event: " + eventType); } switch (eventType) { case "error": System.err.println("Error: " + event.get("error")); break; case "session.created": System.out.println("Sesi dibuat, ID: " + event.getAsJsonObject("session").get("id").getAsString()); break; case "session.updated": System.out.println("Sesi diperbarui, ID: " + event.getAsJsonObject("session").get("id").getAsString()); break; case "input_text_buffer.committed": System.out.println("Buffer teks dikomit, ID item: " + event.get("item_id")); break; case "input_text_buffer.cleared": System.out.println("Buffer teks dihapus"); break; case "response.created": System.out.println("Respons dibuat, ID: " + event.getAsJsonObject("response").get("id").getAsString()); responseDoneLatch = new CountDownLatch(1); break; case "response.output_item.added": System.out.println("Item output ditambahkan, ID: " + event.getAsJsonObject("item").get("id").getAsString()); break; case "response.audio.delta": if (audioCallback != null) { byte[] audioBytes = Base64.getDecoder().decode( event.get("delta").getAsString()); audioCallback.onAudio(audioBytes); } break; case "response.audio.done": System.out.println("Generasi audio selesai"); break; case "response.done": System.out.println("Respons selesai"); responseDoneLatch.countDown(); break; case "session.finished": System.out.println("Sesi berakhir"); sessionFinishedLatch.countDown(); break; } } @Override public void onClose(int code, String reason, boolean remote) { System.out.println("Koneksi ditutup: " + reason); } @Override public void onError(Exception ex) { System.err.println("Error WebSocket: " + ex.getMessage()); } }; ws.connectBlocking(); } /** * Mengirim event ke server. */ public void sendEvent(JsonObject event) { String eventId = "event_" + System.currentTimeMillis(); event.addProperty("event_id", eventId); System.out.println("Mengirim event: type=" + event.get("type").getAsString() + ", event_id=" + eventId); ws.send(gson.toJson(event)); } /** * Memperbarui konfigurasi sesi. */ public void updateSession(JsonObject config) { JsonObject event = new JsonObject(); event.addProperty("type", "session.update"); event.add("session", config); System.out.println("Memperbarui konfigurasi sesi: " + event); sendEvent(event); } /** * Mengirim data teks ke API. */ public void appendText(String text) { JsonObject event = new JsonObject(); event.addProperty("type", "input_text_buffer.append"); event.addProperty("text", text); sendEvent(event); } /** * Commit buffer teks untuk memicu pemrosesan. */ public void commitTextBuffer() { JsonObject event = new JsonObject(); event.addProperty("type", "input_text_buffer.commit"); sendEvent(event); } /** * Menghapus buffer teks. */ public void clearTextBuffer() { JsonObject event = new JsonObject(); event.addProperty("type", "input_text_buffer.clear"); sendEvent(event); } /** * Mengakhiri sesi. */ public void finishSession() { JsonObject event = new JsonObject(); event.addProperty("type", "session.finish"); sendEvent(event); } /** * Menunggu event response.done. */ public void waitForResponseDone() throws InterruptedException { responseDoneLatch.await(); } /** * Menunggu event session.finished. */ public void waitForSessionFinished() throws InterruptedException { sessionFinishedLatch.await(); } /** * Menutup koneksi WebSocket. */ public void close() { if (ws != null) { ws.close(); } } } -
Pilih mode sintesis
API Realtime mendukung dua mode:
-
server_commit mode
Klien hanya mengirim teks. Server melakukan segmentasi dan menentukan waktu sintesis secara otomatis. Mode ini paling cocok untuk skenario latensi rendah tanpa kontrol pacing manual, seperti navigasi GPS.
-
commit mode
Klien menambahkan teks ke buffer, lalu secara eksplisit melakukan commit untuk memicu sintesis. Mode ini paling cocok untuk skenario yang membutuhkan kontrol detail halus atas segmentasi kalimat dan jeda, seperti siaran berita.
server_commit mode
Python
Buat file Python bernama
server_commit.pydi direktori yang sama dengantts_realtime_client.py, lalu salin kode berikut ke dalamnya:Jalankan
server_commit.pyuntuk mendengar audio yang dihasilkan oleh API Realtime secara real-time.Java
Buat file Java bernama
ServerCommit.javadi direktori yang sama denganTTSRealtimeClient.java, lalu salin kode berikut ke dalamnya:import javax.sound.sampled.*; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class ServerCommit { // Berikut ini adalah URL untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti URL dengan: wss://dashscope.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime private static final String URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"; // Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key // Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: private static final String API_KEY = "sk-xxx"; private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY"); private static final int SAMPLE_RATE = 24000; // Cache data audio private static final List<byte[]> audioChunks = new ArrayList<>(); // Antrian pemutaran real-time private static final ConcurrentLinkedQueue<byte[]> playbackQueue = new ConcurrentLinkedQueue<>(); private static final AtomicBoolean playing = new AtomicBoolean(true); public static void main(String[] args) throws Exception { if (API_KEY == null || API_KEY.isEmpty()) { throw new IllegalStateException("Harap setel variabel lingkungan DASHSCOPE_API_KEY"); } // Inisialisasi pemutaran audio AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info); audioLine.open(format); audioLine.start(); // Mulai thread pemutaran Thread playerThread = new Thread(() -> { while (playing.get() || !playbackQueue.isEmpty()) { byte[] chunk = playbackQueue.poll(); if (chunk != null) { audioLine.write(chunk, 0, chunk.length); } else { try { Thread.sleep(10); } catch (InterruptedException ignored) {} } } }); playerThread.start(); // Buat klien TTS // Untuk menggunakan fitur kontrol instruksi, ganti model dengan qwen3-tts-instruct-flash-realtime dan hapus komentar baris instructions di TTSRealtimeClient.java TTSRealtimeClient client = new TTSRealtimeClient( URL, API_KEY, "Cherry", TTSRealtimeClient.SessionMode.SERVER_COMMIT, audioData -> { playbackQueue.add(audioData); audioChunks.add(audioData); System.out.println("Menerima data audio: " + audioData.length + " byte"); } ); client.connect(); // Kirim fragmen teks String[] textFragments = { "Platform model bahasa besar Alibaba Cloud, Model Studio, adalah platform all-in-one untuk mengembangkan dan membangun aplikasi model bahasa besar.", "Baik pengembang maupun pengguna bisnis dapat berpartisipasi secara mendalam dalam desain dan pengembangan aplikasi model bahasa besar.", "Anda dapat mengembangkan aplikasi model bahasa besar dalam lima menit menggunakan antarmuka sederhana,", "atau melatih model khusus dalam beberapa jam, sehingga Anda dapat lebih fokus pada inovasi aplikasi.", }; System.out.println("Mulai mengirim teks..."); for (String text : textFragments) { System.out.println("Mengirim fragmen: " + text); client.appendText(text); Thread.sleep(100); } Thread.sleep(1000); client.finishSession(); // Tunggu hingga respons selesai client.waitForResponseDone(); client.waitForSessionFinished(); client.close(); // Tunggu hingga pemutaran selesai playing.set(false); playerThread.join(); audioLine.drain(); audioLine.close(); // Simpan file audio saveWav("output.wav"); System.out.println("Selesai"); } private static void saveWav(String filename) throws IOException { if (audioChunks.isEmpty()) { System.out.println("Tidak ada data audio untuk disimpan"); return; } ByteArrayOutputStream bos = new ByteArrayOutputStream(); for (byte[] chunk : audioChunks) { bos.write(chunk); } byte[] allAudio = bos.toByteArray(); AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); AudioInputStream ais = new AudioInputStream( new ByteArrayInputStream(allAudio), format, allAudio.length / 2); new File("outputs").mkdirs(); AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File("outputs/" + filename)); System.out.println("Audio disimpan ke: outputs/" + filename); } }Kompilasi dan jalankan
ServerCommit.javauntuk mendengar audio yang dihasilkan oleh API Realtime secara real-time.commit mode
Python
Buat file Python bernama
commit.pydi direktori yang sama dengantts_realtime_client.py, lalu salin kode berikut ke dalamnya:Jalankan
commit.py. Anda dapat memasukkan teks untuk disintesis beberapa kali. Tekan Enter tanpa mengetik teks apa pun untuk mendengar audio yang dikembalikan oleh API Realtime melalui speaker Anda.Java
Buat file Java bernama
Commit.javadi direktori yang sama denganTTSRealtimeClient.java, lalu salin kode berikut ke dalamnya:import javax.sound.sampled.*; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class Commit { // Berikut ini adalah URL untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti URL dengan: wss://dashscope.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime private static final String URL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"; // Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key // Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: private static final String API_KEY = "sk-xxx"; private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY"); private static final int SAMPLE_RATE = 24000; private static final List<byte[]> audioChunks = new ArrayList<>(); private static final ConcurrentLinkedQueue<byte[]> playbackQueue = new ConcurrentLinkedQueue<>(); private static final AtomicBoolean playing = new AtomicBoolean(true); public static void main(String[] args) throws Exception { if (API_KEY == null || API_KEY.isEmpty()) { throw new IllegalStateException("Harap setel variabel lingkungan DASHSCOPE_API_KEY"); } // Inisialisasi pemutaran audio AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info); audioLine.open(format); audioLine.start(); // Mulai thread pemutaran Thread playerThread = new Thread(() -> { while (playing.get() || !playbackQueue.isEmpty()) { byte[] chunk = playbackQueue.poll(); if (chunk != null) { audioLine.write(chunk, 0, chunk.length); } else { try { Thread.sleep(10); } catch (InterruptedException ignored) {} } } }); playerThread.start(); // Buat klien TTS (mode commit) // Untuk menggunakan fitur kontrol instruksi, ganti model dengan qwen3-tts-instruct-flash-realtime dan hapus komentar baris instructions di TTSRealtimeClient.java TTSRealtimeClient client = new TTSRealtimeClient( URL, API_KEY, "Cherry", TTSRealtimeClient.SessionMode.COMMIT, audioData -> { playbackQueue.add(audioData); audioChunks.add(audioData); System.out.println("Menerima data audio: " + audioData.length + " byte"); } ); client.connect(); // Input interaktif System.out.println("Masukkan teks (tekan Enter langsung untuk mengirim event commit dan mengakhiri sesi saat ini, tekan Ctrl+D untuk keluar dari program):"); Scanner scanner = new Scanner(System.in); while (true) { System.out.print("> "); if (!scanner.hasNextLine()) { client.finishSession(); break; } String userText = scanner.nextLine(); if (userText.isEmpty()) { // Input kosong: commit buffer dan akhiri sesi System.out.println("Input kosong, mengirim event commit dan mengakhiri sesi saat ini"); client.commitTextBuffer(); Thread.sleep(300); client.finishSession(); break; } else { System.out.println("Mengirim teks: " + userText); client.appendText(userText); } } scanner.close(); // Tunggu hingga respons selesai client.waitForResponseDone(); client.waitForSessionFinished(); client.close(); // Tunggu hingga pemutaran selesai playing.set(false); playerThread.join(); audioLine.drain(); audioLine.close(); // Simpan file audio saveWav("output.wav"); System.out.println("Selesai"); } private static void saveWav(String filename) throws IOException { if (audioChunks.isEmpty()) { System.out.println("Tidak ada data audio untuk disimpan"); return; } ByteArrayOutputStream bos = new ByteArrayOutputStream(); for (byte[] chunk : audioChunks) { bos.write(chunk); } byte[] allAudio = bos.toByteArray(); AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); AudioInputStream ais = new AudioInputStream( new ByteArrayInputStream(allAudio), format, allAudio.length / 2); new File("outputs").mkdirs(); AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File("outputs/" + filename)); System.out.println("Audio disimpan ke: outputs/" + filename); } }Kompilasi dan jalankan
Commit.java. Masukkan teks untuk disintesis beberapa kali, lalu tekan Enter tanpa teks untuk memutar audio melalui speaker Anda. -
Sintesis suara dengan suara yang dikloning
Kloning suara tidak menyediakan audio pratinjau. Gunakan API sintesis suara untuk mengevaluasi output. Mulailah dengan teks pendek untuk pengujian awal.
Contoh berikut mensintesis suara menggunakan suara yang dikloning dari sampel audio. Contoh ini memperluas kode mode server_commit SDK DashScope dari tab suara sistem, dengan voice diatur ke suara yang dikloning.
Prinsip utama: Model kloning suara (
target_model) harus sesuai dengan model sintesis suara (model). Ketidakcocokan menyebabkan sintesis gagal.Contoh ini menggunakan file audio lokal
voice.mp3untuk kloning suara. Gantilah dengan file audio Anda sendiri saat menjalankan kode.
Python
# coding=utf-8
# Petunjuk instalasi pyaudio:
# APPLE Mac OS X
# brew install portaudio
# pip install pyaudio
# Debian/Ubuntu
# sudo apt-get install python-pyaudio python3-pyaudio
# atau
# 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 # Versi SDK Python DashScope harus 1.23.9 atau lebih baru
from dashscope.audio.qwen_tts_realtime import QwenTtsRealtime, QwenTtsRealtimeCallback, AudioFormat
# ======= Konfigurasi konstan =======
DEFAULT_TARGET_MODEL = "qwen3-tts-vc-realtime-2026-01-15" # Kloning suara dan sintesis suara harus menggunakan model yang sama
DEFAULT_PREFERRED_NAME = "guanyu"
DEFAULT_AUDIO_MIME_TYPE = "audio/mpeg"
VOICE_FILE_PATH = "voice.mp3" # Jalur relatif ke file audio lokal yang digunakan untuk kloning suara
TEXT_TO_SYNTHESIZE = [
'Right? I love supermarkets like this.',
'Especially during Chinese New Year',
'When I go shopping',
'I feel',
'Extremely happy!',
'And want to buy so many things!'
]
def create_voice(file_path: str,
target_model: str = DEFAULT_TARGET_MODEL,
preferred_name: str = DEFAULT_PREFERRED_NAME,
audio_mime_type: str = DEFAULT_AUDIO_MIME_TYPE) -> str:
"""
Buat suara dan kembalikan parameter suara
"""
# Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: 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 audio tidak ditemukan: {file_path}")
base64_str = base64.b64encode(file_path_obj.read_bytes()).decode()
data_uri = f"data:{audio_mime_type};base64,{base64_str}"
# Berikut ini adalah URL untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti URL dengan: 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", # Jangan ubah nilai ini
"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"Gagal membuat suara: {resp.status_code}, {resp.text}")
try:
return resp.json()["output"]["voice"]
except (KeyError, ValueError) as e:
raise RuntimeError(f"Gagal mengurai respons suara: {e}")
def init_dashscope_api_key():
"""
Inisialisasi Kunci API SDK DashScope
"""
# Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
# ======= Kelas callback =======
class MyCallback(QwenTtsRealtimeCallback):
"""
Callback streaming TTS kustom
"""
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] Koneksi dibentuk')
def on_close(self, close_status_code, close_msg) -> None:
self._stream.stop_stream()
self._stream.close()
self._player.terminate()
print(f'[TTS] Koneksi ditutup kode={close_status_code}, pesan={close_msg}')
def on_event(self, response: dict) -> None:
try:
event_type = response.get('type', '')
if event_type == 'session.created':
print(f'[TTS] Sesi dimulai: {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] Respons selesai, ID Respons: {qwen_tts_realtime.get_last_response_id()}')
elif event_type == 'session.finished':
print('[TTS] Sesi berakhir')
self.complete_event.set()
except Exception as e:
print(f'[Error] Pengecualian menangani event callback: {e}')
def wait_for_finished(self):
self.complete_event.wait()
# ======= Logika eksekusi utama =======
if __name__ == '__main__':
init_dashscope_api_key()
print('[Sistem] Menginisialisasi Qwen TTS Realtime ...')
callback = MyCallback()
qwen_tts_realtime = QwenTtsRealtime(
model=DEFAULT_TARGET_MODEL,
callback=callback,
# Berikut ini adalah URL untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti URL dengan: 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), # Ganti parameter suara dengan suara kustom yang dihasilkan oleh kloning suara
response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
mode='server_commit'
)
for text_chunk in TEXT_TO_SYNTHESIZE:
print(f'[Mengirim teks]: {text_chunk}')
qwen_tts_realtime.append_text(text_chunk)
time.sleep(0.1)
qwen_tts_realtime.finish()
callback.wait_for_finished()
print(f'[Metric] session_id={qwen_tts_realtime.get_session_id()}, '
f'first_audio_delay={qwen_tts_realtime.get_first_audio_delay()}s')
Java
Impor dependensi Gson. Jika Anda menggunakan Maven atau Gradle, tambahkan dependensi sebagai berikut:
Maven
Tambahkan berikut ke 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
Tambahkan berikut ke 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 {
// ===== Definisi konstanta =====
// Kloning suara dan sintesis suara harus menggunakan model yang sama
private static final String TARGET_MODEL = "qwen3-tts-vc-realtime-2026-01-15";
private static final String PREFERRED_NAME = "guanyu";
// Jalur relatif ke file audio lokal yang digunakan untuk kloning suara
private static final String AUDIO_FILE = "voice.mp3";
private static final String AUDIO_MIME_TYPE = "audio/mpeg";
private static String[] textToSynthesize = {
"Right? I love supermarkets like this.",
"Especially during Chinese New Year",
"When I go shopping",
"I feel",
"Extremely happy!",
"And want to buy so many things!"
};
// Hasilkan URI data
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;
}
// Panggil API untuk membuat suara
public static String createVoice() throws Exception {
// Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: String apiKey = "sk-xxx"
String apiKey = System.getenv("DASHSCOPE_API_KEY");
String jsonPayload =
"{"
+ "\"model\": \"qwen-voice-enrollment\"," // Jangan ubah nilai ini
+ "\"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("Kode status 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("Isi respons: " + response);
if (status == 200) {
JsonObject jsonObj = new Gson().fromJson(response.toString(), JsonObject.class);
return jsonObj.getAsJsonObject("output").get("voice").getAsString();
}
throw new IOException("Gagal membuat suara: " + status + " - " + response);
}
}
// Kelas pemutar audio PCM real-time
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<>();
// Konstruktor: inisialisasi format audio dan saluran audio
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();
}
// Putar potongan audio dan blokir hingga pemutaran selesai
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);
// Tunggu hingga audio dalam buffer selesai diputar
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)
// Berikut ini adalah URL untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti URL dengan: wss://dashscope.aliyuncs.com/api-ws/v1/realtime
.url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime")
// Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: .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);
// Buat instance pemutar audio real-time
RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000);
QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() {
@Override
public void onOpen() {
// Tangani pembentukan koneksi
}
@Override
public void onEvent(JsonObject message) {
String type = message.get("type").getAsString();
switch(type) {
case "session.created":
// Tangani pembuatan sesi
break;
case "response.audio.delta":
String recvAudioB64 = message.get("delta").getAsString();
// Putar audio secara real-time
audioPlayer.write(recvAudioB64);
break;
case "response.done":
// Tangani penyelesaian respons
break;
case "session.finished":
// Tangani penghentian sesi
completeLatch.get().countDown();
default:
break;
}
}
@Override
public void onClose(int code, String reason) {
// Tangani penutupan koneksi
}
});
qwenTtsRef.set(qwenTtsRealtime);
try {
qwenTtsRealtime.connect();
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
}
QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder()
.voice(createVoice()) // Ganti parameter suara dengan suara kustom yang dihasilkan oleh kloning suara
.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();
// Tunggu hingga pemutaran audio selesai dan matikan pemutar
audioPlayer.waitForComplete();
audioPlayer.shutdown();
System.exit(0);
}
}
Sintesis suara dengan suara yang dirancang
Fitur desain suara menghasilkan pratinjau audio. Dengarkan pratinjau tersebut dan pastikan outputnya sesuai harapan sebelum melakukan sintesis suara—langkah ini membantu menghindari panggilan API yang tidak perlu.
-
Buat suara kustom dan dengarkan pratinjaunya. Jika hasilnya memuaskan, lanjutkan. Jika tidak, buat ulang suara tersebut.
Python
import requests import base64 import os def create_voice_and_play(): # Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key # Jika variabel lingkungan tidak disetel, ganti baris berikut dengan Kunci API Model Studio Anda: api_key = "sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") if not api_key: print("Error: Variabel lingkungan DASHSCOPE_API_KEY tidak ditemukan. Harap setel Kunci API terlebih dahulu.") return None, None, None # Siapkan data permintaan headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } data = { "model": "qwen-voice-design", "input": { "action": "create", "target_model": "qwen3-tts-vd-realtime-2026-01-15", "voice_prompt": "A composed middle-aged male announcer with a deep, rich and magnetic voice, a steady speaking speed and clear articulation, is suitable for news broadcasting or documentary commentary.", "preview_text": "Dear listeners, hello everyone. Welcome to the evening news.", "preferred_name": "announcer", "language": "en" }, "parameters": { "sample_rate": 24000, "response_format": "wav" } } # Berikut ini adalah URL untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti URL dengan: https://dashscope.aliyuncs.com/api/v1/services/audio/tts/customization url = "https://dashscope-intl.aliyuncs.com/api/v1/services/audio/tts/customization" try: # Kirim permintaan response = requests.post( url, headers=headers, json=data, timeout=60 # Tambahkan pengaturan timeout ) if response.status_code == 200: result = response.json() # Dapatkan nama suara voice_name = result["output"]["voice"] print(f"Nama suara: {voice_name}") # Dapatkan data audio pratinjau base64_audio = result["output"]["preview_audio"]["data"] # Dekode data audio Base64 audio_bytes = base64.b64decode(base64_audio) # Simpan file audio secara lokal filename = f"{voice_name}_preview.wav" # Tulis data audio ke file lokal with open(filename, 'wb') as f: f.write(audio_bytes) print(f"Audio disimpan ke file lokal: {filename}") print(f"Jalur file: {os.path.abspath(filename)}") return voice_name, audio_bytes, filename else: print(f"Permintaan gagal dengan kode status: {response.status_code}") print(f"Isi respons: {response.text}") return None, None, None except requests.exceptions.RequestException as e: print(f"Terjadi error permintaan jaringan: {e}") return None, None, None except KeyError as e: print(f"Error format data respons, bidang yang diperlukan tidak ada: {e}") print(f"Isi respons: {response.text if 'response' in locals() else 'Tidak ada respons'}") return None, None, None except Exception as e: print(f"Terjadi error tidak dikenal: {e}") return None, None, None if __name__ == "__main__": print("Mulai membuat suara...") voice_name, audio_data, saved_filename = create_voice_and_play() if voice_name: print(f"\nBerhasil membuat suara '{voice_name}'") print(f"File audio disimpan sebagai: '{saved_filename}'") print(f"Ukuran file: {os.path.getsize(saved_filename)} byte") else: print("\nPembuatan suara gagal")Java
Tambahkan dependensi Gson ke proyek Anda:
Maven
Tambahkan berikut ke
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
Tambahkan berikut ke
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() { // Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key // Jika variabel lingkungan tidak disetel, ganti baris berikut dengan Kunci API Model Studio Anda: String apiKey = "sk-xxx" String apiKey = System.getenv("DASHSCOPE_API_KEY"); // Buat string body permintaan JSON String jsonBody = "{\n" + " \"model\": \"qwen-voice-design\",\n" + " \"input\": {\n" + " \"action\": \"create\",\n" + " \"target_model\": \"qwen3-tts-vd-realtime-2026-01-15\",\n" + " \"voice_prompt\": \"A composed middle-aged male announcer with a deep, rich and magnetic voice, a steady speaking speed and clear articulation, is suitable for news broadcasting or documentary commentary.\",\n" + " \"preview_text\": \"Dear listeners, hello everyone. Welcome to the evening news.\",\n" + " \"preferred_name\": \"announcer\",\n" + " \"language\": \"en\"\n" + " },\n" + " \"parameters\": {\n" + " \"sample_rate\": 24000,\n" + " \"response_format\": \"wav\"\n" + " }\n" + "}"; HttpURLConnection connection = null; try { // Berikut ini adalah URL untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti URL dengan: 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(); // Atur metode permintaan dan header connection.setRequestMethod("POST"); connection.setRequestProperty("Authorization", "Bearer " + apiKey); connection.setRequestProperty("Content-Type", "application/json"); connection.setDoOutput(true); connection.setDoInput(true); // Kirim body permintaan try (OutputStream os = connection.getOutputStream()) { byte[] input = jsonBody.getBytes("UTF-8"); os.write(input, 0, input.length); os.flush(); } // Dapatkan respons int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { // Baca isi respons 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()); } } // Uraikan respons JSON JsonObject jsonResponse = JsonParser.parseString(response.toString()).getAsJsonObject(); JsonObject outputObj = jsonResponse.getAsJsonObject("output"); JsonObject previewAudioObj = outputObj.getAsJsonObject("preview_audio"); // Dapatkan nama suara String voiceName = outputObj.get("voice").getAsString(); System.out.println("Nama suara: " + voiceName); // Dapatkan data audio yang dienkripsi Base64 String base64Audio = previewAudioObj.get("data").getAsString(); // Dekode data audio Base64 byte[] audioBytes = Base64.getDecoder().decode(base64Audio); // Simpan audio ke file lokal String filename = voiceName + "_preview.wav"; saveAudioToFile(audioBytes, filename); System.out.println("Audio disimpan ke file lokal: " + filename); } else { // Baca respons error 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("Permintaan gagal dengan kode status: " + responseCode); System.out.println("Respons error: " + errorResponse.toString()); } } catch (Exception e) { System.err.println("Terjadi error selama permintaan: " + 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("Audio disimpan ke: " + file.getAbsolutePath()); } catch (IOException e) { System.err.println("Terjadi error saat menyimpan file audio: " + e.getMessage()); e.printStackTrace(); } } } -
Gunakan suara kustom yang dibuat pada langkah sebelumnya untuk sintesis suara.
Contoh ini mengikuti kode contoh mode "server commit" untuk suara sistem di SDK DashScope. Ganti parameter
voicedengan suara kustom yang dihasilkan melalui desain suara.Prinsip utama: Model yang digunakan untuk desain suara (
target_model) harus sama dengan model yang digunakan untuk sintesis suara selanjutnya (model). Jika tidak, sintesis akan gagal.Python
# coding=utf-8 # Instruksi instalasi untuk pyaudio: # APPLE Mac OS X # brew install portaudio # pip install pyaudio # Debian/Ubuntu # sudo apt-get install python-pyaudio python3-pyaudio # atau # 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 # Versi DashScope Python SDK harus 1.23.9 atau yang lebih baru from dashscope.audio.qwen_tts_realtime import QwenTtsRealtime, QwenTtsRealtimeCallback, AudioFormat # ======= Konfigurasi konstan ======= TEXT_TO_SYNTHESIZE = [ 'Benar kan? Saya sangat suka supermarket seperti ini,', 'terutama saat Tahun Baru.', 'Pergi ke supermarket', 'benar-benar membuat saya merasa', 'sangat, sangat bahagia!', 'Saya ingin membeli banyak sekali barang!' ] def init_dashscope_api_key(): """ Inisialisasi kunci API untuk SDK DashScope. """ # Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key # Jika variabel lingkungan tidak diatur, ganti baris berikut dengan kunci API Model Studio Anda: dashscope.api_key = "sk-xxx" dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") # ======= Kelas callback ======= class MyCallback(QwenTtsRealtimeCallback): """ Callback streaming TTS kustom. """ 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] Koneksi berhasil dibuat') def on_close(self, close_status_code, close_msg) -> None: self._stream.stop_stream() self._stream.close() self._player.terminate() print(f'[TTS] Koneksi ditutup, kode={close_status_code}, pesan={close_msg}') def on_event(self, response: dict) -> None: try: event_type = response.get('type', '') if event_type == 'session.created': print(f'[TTS] Sesi dimulai: {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] Tanggapan selesai, ID Tanggapan: {qwen_tts_realtime.get_last_response_id()}') elif event_type == 'session.finished': print('[TTS] Sesi selesai') self.complete_event.set() except Exception as e: print(f'[Error] Pengecualian saat memproses event callback: {e}') def wait_for_finished(self): self.complete_event.wait() # ======= Logika eksekusi utama ======= if __name__ == '__main__': init_dashscope_api_key() print('[Sistem] Menginisialisasi Qwen TTS Realtime ...') callback = MyCallback() qwen_tts_realtime = QwenTtsRealtime( # Gunakan model yang sama untuk desain suara dan sintesis suara model="qwen3-tts-vd-realtime-2026-01-15", callback=callback, # Berikut ini adalah URL untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti URL dengan: 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", # Ganti parameter voice dengan suara kustom yang dihasilkan oleh desain suara response_format=AudioFormat.PCM_24000HZ_MONO_16BIT, mode='server_commit' ) for text_chunk in TEXT_TO_SYNTHESIZE: print(f'[Mengirim teks]: {text_chunk}') qwen_tts_realtime.append_text(text_chunk) time.sleep(0.1) qwen_tts_realtime.finish() callback.wait_for_finished() print(f'[Metrik] 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 { // ===== Definisi konstanta ===== private static String[] textToSynthesize = { "Right? I really like this kind of supermarket,", "especially during the New Year.", "Going to the supermarket", "just makes me feel", "super, super happy!", "I want to buy so many things!" }; // Kelas pemutar audio real-time 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<>(); // Konstruktor menginisialisasi format audio dan saluran audio 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(); } // Memutar potongan audio dan memblokir hingga pemutaran selesai 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); // Tunggu hingga audio dalam buffer selesai diputar 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() // Gunakan model yang sama untuk desain suara dan sintesis suara .model("qwen3-tts-vd-realtime-2026-01-15") // Berikut ini adalah URL untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti URL dengan: wss://dashscope.aliyuncs.com/api-ws/v1/realtime .url("wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime") // Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key // Jika variabel lingkungan tidak disetel, ganti baris berikut dengan Kunci API Model Studio Anda: .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); // Buat instance pemutar audio real-time RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000); QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() { @Override public void onOpen() { // Penanganan saat koneksi dibentuk } @Override public void onEvent(JsonObject message) { String type = message.get("type").getAsString(); switch(type) { case "session.created": // Penanganan saat sesi dibuat break; case "response.audio.delta": String recvAudioB64 = message.get("delta").getAsString(); // Putar audio secara real-time audioPlayer.write(recvAudioB64); break; case "response.done": // Penanganan saat respons selesai break; case "session.finished": // Penanganan saat sesi selesai completeLatch.get().countDown(); default: break; } } @Override public void onClose(int code, String reason) { // Penanganan saat koneksi ditutup } }); qwenTtsRef.set(qwenTtsRealtime); try { qwenTtsRealtime.connect(); } catch (NoApiKeyException e) { throw new RuntimeException(e); } QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder() .voice("myvoice") // Ganti parameter suara dengan suara kustom yang dihasilkan oleh desain suara .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(); // Tunggu hingga pemutaran audio selesai dan matikan pemutar audioPlayer.waitForComplete(); audioPlayer.shutdown(); System.exit(0); } }
Untuk contoh kode lainnya, lihat alibabacloud-bailian-speech-demo di GitHub.
Alur interaksi
Mode server_commit
Atur session.mode ke "server_commit" dalam event session.update. Dalam mode ini, server secara otomatis menangani segmentasi teks dan penentuan waktu sintesis.
Klien mengirim event
session.update. Server merespons dengan eventsession.createddansession.updated.Klien mengirim event
input_text_buffer.appenduntuk menambahkan teks ke buffer sisi server.Server secara otomatis melakukan segmentasi teks dan menentukan waktu sintesis, lalu mengembalikan event
response.created,response.output_item.added,response.content_part.added, danresponse.audio.delta.Setelah respons selesai, server mengirim event
response.audio.done,response.content_part.done,response.output_item.done, danresponse.done.Server mengirim event
session.finisheduntuk mengakhiri sesi.
|
Siklus hidup |
Event klien |
Event server |
|
Inisialisasi sesi |
session.update Konfigurasi sesi |
session.created Sesi dibuat session.updated Konfigurasi sesi diperbarui |
|
Input teks pengguna |
input_text_buffer.append Tambahkan teks ke buffer sisi server input_text_buffer.commit Sintesis langsung teks dalam buffer di server session.finish Beritahu server bahwa tidak ada input teks lagi |
input_text_buffer.committed Server mengonfirmasi teks yang dikomit |
|
Output audio server |
Tidak ada |
response.created Server mulai menghasilkan respons response.output_item.added Konten output baru ditambahkan ke respons response.content_part.added Bagian konten baru ditambahkan ke pesan asisten response.audio.delta Data audio inkremental dari model response.content_part.done Streaming konten teks atau audio untuk pesan asisten selesai response.output_item.done Streaming item output lengkap untuk pesan asisten selesai response.audio.done Generasi audio selesai response.done Respons selesai |
commit mode
Atur session.mode ke "commit" dalam event session.update. Dalam mode ini, klien secara eksplisit melakukan commit buffer teks ke server untuk memicu sintesis.
Klien mengirim event
session.update. Server merespons dengan eventsession.createddansession.updated.Klien mengirim event
input_text_buffer.appenduntuk menambahkan teks ke buffer sisi server.Klien mengirim event
input_text_buffer.commituntuk melakukan commit buffer ke server, lalu mengirim eventsession.finishguna menandakan bahwa tidak ada input teks tambahan.Server merespons dengan
response.createddan mulai menghasilkan respons.Server mengirim event
response.output_item.added,response.content_part.added, danresponse.audio.delta.Setelah respons selesai, server mengembalikan
response.audio.done,response.content_part.done,response.output_item.done, danresponse.done.Server mengirim event
session.finisheduntuk mengakhiri sesi.
|
Siklus hidup |
Event klien |
Event server |
|
Inisialisasi sesi |
session.update Konfigurasi sesi |
session.created Sesi dibuat session.updated Konfigurasi sesi diperbarui |
|
Input teks pengguna |
input_text_buffer.append Tambahkan teks ke buffer input_text_buffer.commit Commit buffer ke server input_text_buffer.clear Kosongkan buffer |
input_text_buffer.committed Server mengonfirmasi teks yang dikomit |
|
Output audio server |
Tidak ada |
response.created Server mulai menghasilkan respons response.output_item.added Konten output baru ditambahkan ke respons response.content_part.added Bagian konten baru ditambahkan ke pesan asisten response.audio.delta Data audio inkremental dari model response.content_part.done Streaming konten teks atau audio untuk pesan asisten selesai response.output_item.done Streaming item output lengkap untuk pesan asisten selesai response.audio.done Generasi audio selesai response.done Respons selesai |
Kontrol instruksi
Kontrol instruksi memungkinkan Anda membentuk ekspresivitas suara melalui bahasa alami—cukup jelaskan nada, laju ucapan, emosi, atau karakteristik suara secara langsung tanpa perlu menyesuaikan parameter audio.
Model yang didukung: Hanya seri Qwen3-TTS-Instruct-Flash-Realtime.
Cara menggunakan: Masukkan instruksi dalam parameter instructions, misalnya: "Berbicara cepat dengan intonasi naik, cocok untuk memperkenalkan produk fesyen."
Bahasa yang didukung: Teks instruksi hanya mendukung Mandarin dan Inggris.
Batas panjang: Instruksi tidak boleh melebihi 1.600 token.
Kasus penggunaan:
Pengisi suara audiobook dan drama radio
Pengisi suara iklan dan promosi
Pengisi suara karakter game dan animasi
Asisten suara cerdas yang peka emosi
Narasi siaran berita dan dokumenter
Kiat menulis deskripsi suara yang efektif:
-
Prinsip inti:
Bersifat spesifik: Gunakan deskriptor konkret seperti "dalam", "jernih", atau "sedikit cepat". Hindari kata-kata samar seperti "menyenangkan" atau "normal".
Cakup beberapa dimensi: Gabungkan pitch, laju ucapan, dan emosi. Deskripsi satu dimensi seperti "pitch tinggi" menghasilkan output generik.
Bersifat objektif: Jelaskan karakteristik suara secara fisik dan persepsi, bukan preferensi pribadi. Misalnya, gunakan "pitch sedikit tinggi dengan nada energik" daripada "suara favorit saya".
Jelaskan kualitas, jangan meniru: Fokus pada karakteristik suara, bukan permintaan peniruan individu tertentu (seperti selebriti atau aktor). Model tidak mendukung peniruan langsung, dan permintaan semacam itu berisiko melanggar hak cipta.
Bersifat ringkas: Pastikan setiap kata memberikan makna. Hindari pengulangan sinonim atau penguat yang tidak perlu (misalnya, "suara yang benar-benar hebat").
-
Referensi dimensi: Menggabungkan beberapa dimensi guna menghasilkan output yang lebih kaya dan ekspresif.
Dimensi
Contoh
Pitch
Tinggi, sedang, rendah, sedikit tinggi, sedikit rendah
Laju ucapan
Cepat, sedang, lambat, sedikit cepat, sedikit lambat
Emosi
Ceria, tenang, lembut, serius, hidup, tenang, menenangkan
Kualitas suara
Magnetis, jernih, serak, lembut, manis, resonan, kuat
Kasus penggunaan
Siaran berita, pengisi suara iklan, audiobook, karakter animasi, asisten suara, narasi dokumenter
-
Contoh:
Gaya siaran standar: Artikulasi jelas dan tepat dengan pengucapan bulat sempurna
Eskalasi emosional: Volume meningkat cepat dari percakapan biasa hingga berteriak, dengan kepribadian langsung dan emosi yang diekspresikan secara terbuka
Keadaan emosional khusus: Ucapan sedikit cadel karena napas tersengal-sengal, disertai sedikit serak dan ketegangan yang jelas akibat menangis
Gaya pengisi suara iklan: Pitch sedikit tinggi, laju ucapan sedang, penuh energi dan karisma—ideal untuk pengisi suara iklan
Gaya lembut dan menenangkan: Laju ucapan sedikit lambat, pitch lembut dan manis, nada hangat dan menghibur, seperti teman yang peduli
Referensi API
Perbandingan fitur model
|
Fitur |
Qwen3-TTS-Instruct-Flash-Realtime |
Qwen3-TTS-VD-Realtime |
Qwen3-TTS-VC-Realtime |
Qwen3-TTS-Flash-Realtime |
Qwen-TTS-Realtime |
|
Bahasa yang didukung |
Mandarin, Inggris, Spanyol, Rusia, Italia, Prancis, Korea, Jepang, Jerman, Portugis |
Mandarin, Inggris, Spanyol, Rusia, Italia, Prancis, Korea, Jepang, Jerman, Portugis |
Mandarin (dialek Beijing, Shanghai, Sichuan, Nanjing, Shaanxi, Hokkien, Tianjin, dan Kanton, tergantung pada suara), Inggris, Spanyol, Rusia, Italia, Prancis, Korea, Jepang, Jerman, Portugis |
Mandarin, Inggris |
|
|
Format audio |
PCM, WAV, MP3, Opus |
PCM |
|||
|
Laju sampel |
8 kHz, 16 kHz, 24 kHz, 48 kHz |
24 kHz |
|||
|
Kloning suara |
|
|
|
||
|
Desain suara |
|
|
|
||
|
SSML |
|
||||
|
LaTeX |
|
||||
|
Kontrol volume |
|
|
|||
|
Kontrol laju ucapan |
|
|
|||
|
Kontrol pitch |
|
|
|||
|
Kontrol bitrate |
|
|
|||
|
Timestamp |
|
||||
|
Kontrol instruksi |
|
|
|||
|
Input streaming |
|
||||
|
Streaming Output |
|
||||
|
Pembatasan laju |
Permintaan per menit (RPM): 180 |
qwen3-tts-flash-realtime dan qwen3-tts-flash-realtime-2025-11-27: Permintaan per menit (RPM): 180 qwen3-tts-flash-realtime-2025-09-18: Permintaan per menit (RPM): 10 |
Permintaan per menit (RPM): 10 Token per menit (TPM): 100.000 |
||
|
Metode akses |
SDK Java/Python, API WebSocket |
||||
|
Harga |
Internasional: $0,143/10.000 karakter Tiongkok daratan: $0,143/10.000 karakter |
Internasional: $0,143353/10.000 karakter Tiongkok daratan: $0,143353/10.000 karakter |
Internasional: $0,13/10.000 karakter Tiongkok daratan: $0,143353/10.000 karakter |
Tiongkok daratan:
|
|
Suara yang didukung
Model yang berbeda mendukung suara yang berbeda. Untuk menggunakan suara tertentu, atur parameter voice ke nilai yang tercantum dalam kolom parameter suara pada tabel di bawah ini.
| Detail | Bahasa yang Didukung | Model yang Didukung |
| Nama suara: Cherry Deskripsi: Wanita muda ceria, positif, ramah, dan natural (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Serena Deskripsi: Wanita muda lembut (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Ethan Deskripsi: Mandarin standar dengan sedikit aksen utara; ceria, hangat, energik, dan bersemangat (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Chelsie Deskripsi: Pacar virtual dua dimensi (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Momo Deskripsi: Main-main dan nakal, menghibur Anda (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Vivian Deskripsi: Percaya diri, manis, dan sedikit galak (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Moon Deskripsi: Pria tampan dan berani bernama Yuebai (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Maia Deskripsi: Perpaduan kecerdasan dan kelembutan (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Kai Deskripsi: Spa audio yang menenangkan untuk telinga Anda (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Nofish Deskripsi: Desainer yang tidak bisa mengucapkan bunyi retrofleks (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Bella Deskripsi: Gadis kecil yang minum tapi tidak pernah memukul saat mabuk (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Jennifer Deskripsi: Suara perempuan berkualitas premium sinematik berbahasa Inggris Amerika (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Ryan Deskripsi: Penuh ritme, penuh gaya dramatis, menyeimbangkan keaslian dan ketegangan (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Katerina Deskripsi: Suara wanita dewasa dengan ritme kaya dan mudah diingat (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Aiden Deskripsi: Pria muda berbahasa Inggris Amerika yang mahir memasak (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Eldric Sage Deskripsi: Orang tua yang tenang dan bijaksana—teruji seperti pohon pinus, namun pikiran jernih seperti cermin (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Mia Deskripsi: Lembut seperti air musim semi, patuh seperti salju segar (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Mochi Deskripsi: Pemuda cerdas dan cepat tanggap—kepolosan kekanak-kanakan tetap ada, namun kebijaksanaan bersinar (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Bellona Deskripsi: Suara kuat dan jelas yang menghidupkan karakter—begitu mengharukan hingga membuat darah mendidih. Dengan keagungan heroik dan diksi sempurna, suara ini menangkap seluruh spektrum ekspresi manusia. | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Vincent Deskripsi: Suara unik serak dan berasap—hanya satu kalimat membangkitkan pasukan dan kisah kepahlawanan (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Bunny Deskripsi: Gadis kecil yang penuh "kelucuan" (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Neil Deskripsi: Intonasi dasar datar dengan pengucapan tepat dan jelas—pembaca berita profesional (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Elias Deskripsi: Menjaga ketelitian akademis sambil menggunakan teknik bercerita untuk mengubah pengetahuan kompleks menjadi modul pembelajaran yang mudah dicerna (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Arthur Deskripsi: Suara sederhana dan bersahaja yang dipenuhi waktu dan asap tembakau—perlahan menceritakan kisah desa dan keanehan (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Nini Deskripsi: Suara lembut dan manja seperti kue beras manis—panggilan panjang "Kakak" begitu manis hingga melelehkan tulang (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Seren Deskripsi: Suara lembut dan menenangkan untuk membantu Anda tertidur lebih cepat. Selamat malam, mimpi indah (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Pip Deskripsi: Anak laki-laki main-main dan nakal penuh keajaiban kekanak-kanakan—apakah ini kenangan Anda tentang Shin-chan? (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Stella Deskripsi: Biasanya suara remaja perempuan manis dan linglung—tetapi saat berteriak "Aku mewakili bulan untuk mengalahkanmu!", ia langsung memancarkan cinta dan keadilan yang tak tergoyahkan (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Bodega Deskripsi: Pria Spanyol yang penuh gairah (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Sonrisa Deskripsi: Wanita Latin Amerika ceria dan terbuka (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Alek Deskripsi: Dingin seperti semangat Rusia, namun hangat seperti lapisan dalam mantel wol (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Dolce Deskripsi: Pria Italia santai (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Sohee Deskripsi: Unnie Korea yang hangat, ceria, dan ekspresif secara emosional (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Ono Anna Deskripsi: Teman masa kecil yang cerdas dan bersemangat (perempuan) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Lenn Deskripsi: Rasional dalam hati, memberontak dalam detail—pemuda Jerman yang mengenakan setelan jas dan mendengarkan post-punk | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Emilien Deskripsi: Kakak laki-laki Prancis yang romantis (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Andre Deskripsi: Suara laki-laki magnetis, alami, dan stabil | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Radio Gol Deskripsi: Penyiar sepak bola Radio Gol! Hari ini saya akan berkomentar tentang sepak bola menggunakan nama saya (laki-laki) | Mandarin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Shanghai - Jada Deskripsi: Bibi Shanghai yang berbicara cepat dan energetik (perempuan) | Dialek Shanghainese, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Beijing - Dylan Deskripsi: Pemuda yang dibesarkan di hutong Beijing (laki-laki) | Dialek Beijing, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Nanjing - Li Deskripsi: Guru yoga yang sabar (laki-laki) | Dialek Nanjing, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Shaanxi - Marcus Deskripsi: Wajah lebar, sedikit bicara, hati tulus, suara dalam—rasa autentik Shaanxi (laki-laki) | Dialek Shaanxi, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Southern Min - Roy Deskripsi: Pria Taiwan yang humoris, langsung, dan hidup (laki-laki) | Dialek Southern Min, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Tianjin - Peter Deskripsi: Crosstalk ala Tianjin, pendukung profesional (laki-laki) | Dialek Tianjin, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Sichuan - Sunny Deskripsi: Gadis Sichuan yang manisnya bisa melelehkan hati (perempuan) | Dialek Sichuan, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Sichuan - Eric Deskripsi: Pria Sichuan dari Chengdu yang menonjol dalam kehidupan sehari-hari (laki-laki) | Dialek Sichuan, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Kanton - Rocky Deskripsi: A Qiang yang humoris dan cerdas memberikan obrolan langsung (laki-laki) | Dialek Kanton, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|
| Nama suara: Kanton - Kiki Deskripsi: Sahabat perempuan Hong Kong yang manis (perempuan) | Dialek Kanton, Inggris, Prancis, Jerman, Rusia, Italia, Spanyol, Portugis, Jepang, Korea |
|