Sintesis suara dengan qwen-audio-3.0-tts-flash melalui AOQ
Gunakan AOQ untuk terhubung ke qwen-audio-3.0-tts-flash, kirim teks dalam segmen, dan putar suara hasil sintesis secara real time. Kode klien menggunakan Android Java.
qwen-audio-3.0-tts-flash mendukung protokol event Inferensi AOQ. Tutorial ini menggunakan model tersebut untuk menunjukkan sintesis suara streaming melalui AOQ. Klien mengirim run-task, continue-task, dan finish-task melalui track Data. Layanan melakukan streaming audio melalui track Audio dan mengembalikan event tugas melalui track Data.Satu tugas dapat berisi beberapa event continue-task. Kalimat lengkap langsung disintesis, sedangkan kalimat yang belum lengkap tetap di-buffer hingga teks berikutnya melengkapinya atau klien mengirim finish-task. Pendekatan ini cocok untuk pemutaran di perangkat mobile, input teks panjang yang tersegmentasi, dan output suara berlatensi rendah.
Aktifkan Model Studio dan ikuti Mendapatkan dan mengonfigurasi Kunci API. Simpan Kunci API hanya di server aplikasi Anda. Jangan sertakan dalam kode klien atau commit ke repositori kode.
Unduh AOQ Client SDK versi terbaru seperti dijelaskan dalam Unduhan SDK.
Buat server aplikasi dan terapkan otentikasi proxy seperti dijelaskan dalam Otentikasi token. Sebelum setiap koneksi baru, klien harus memperoleh kredensial koneksi baru dari server aplikasi.
Impor SDK untuk platform pengembangan Anda. Implementasi klien menggunakan Android Java. Platform lain menyediakan antarmuka dan alur event yang sama. Tutorial ini menggunakan aliran audio PCM. Jika aplikasi Anda memilih Opus, impor plugin yang sesuai seperti dijelaskan dalam topik unduhan SDK.
Android
iOS
HarmonyOS
Linux (Python)
Letakkan AoqClientSdk-release.aar di app/libs, lalu konfigurasikan dependensi dan ABI yang didukung SDK di app/build.gradle:
Skenario ini tidak memerlukan izin mikrofon atau kamera.
Tambahkan AoqClientSdk.framework ke proyek Xcode dan pilih Embed & Sign di bawah Target > General > Frameworks, Libraries, and Embedded Content. SDK mendukung perangkat arm64 yang menjalankan iOS 13.0 atau lebih baru.
Skenario ini tidak menggunakan mikrofon atau kamera dan tidak memerlukan izin terkait.
Gunakan import AoqClientSdk di Swift atau #import <AoqClientSdk/AoqClientSdk.h> di Objective-C.
Letakkan AoqClientSdk.har di entry/libs dan deklarasikan dependensi di entry/oh-package.json5. SDK kompatibel dengan API 12 dan mendukung arm64-v8a:
Gunakan demo Android dari Alibaba Cloud Model Studio untuk memverifikasi konektivitas AOQ dengan cepat. Unduh APK dan konfigurasikan Kunci API serta workspaceId untuk mencoba model yang dipilih.Pindai kode QR berikut untuk mengunduh demo:
Atur DASHSCOPE_API_KEY di server aplikasi dan kirim permintaan ke titik akhir untuk wilayah yang dipilih. clientIp adalah Alamat IP publik aktual klien. Field ini opsional, tetapi menentukannya membantu layanan mengalokasikan titik akhir relay yang sesuai.
Jika server aplikasi tidak dapat memperoleh Alamat IP publik aktual klien, abaikan clientIp daripada mengirim string kosong.
Server aplikasi mengembalikan field respons berikut ke klien. Jangan pernah mengembalikan Kunci API ke klien di lingkungan produksi. Untuk semua field permintaan dan respons, lihat Otentikasi token.
Setelah klien memperoleh AoqConnectConfig dari server aplikasi, ikuti langkah-langkah berikut untuk mengimplementasikan sintesis suara streaming di Android.
Buat engine AOQ singleton dan daftarkan callback untuk status koneksi dan event track Data. Pertahankan kesiapan koneksi di callback koneksi dan teruskan event tugas ke mesin keadaan aplikasi.
Copy
AoqClientListener listener = new AoqClientListener() { @Override public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) { connected = status == AoqClientEngine.AoqConnectionStatus .AoqConnectionStatusConnected; } @Override public void onDataMsg(AoqClientEngine.AoqDataMsg msg) { handleServerEvent(msg); }};AoqClientEngine.AoqCreateConfig createConfig = new AoqClientEngine.AoqCreateConfig();createConfig.workDir = context.getFilesDir().getAbsolutePath();engine = AoqClientEngine.createEngine(context, createConfig, listener);
TTS tidak merekam audio mikrofon. Inisialisasi hanya pemutar lokal. Pilih speaker atau earpiece sebagai output default. SDK secara otomatis memutar audio server dari track Audio.
Copy
AoqClientEngine.AoqAudioPlaybackConfig playbackConfig = new AoqClientEngine.AoqAudioPlaybackConfig();playbackConfig.channel = 1;playbackConfig.isDefaultSpeaker = true;engine.startAudioPlayer(playbackConfig);
Konfigurasi decoder SDK sesuai format audio output yang dipilih di run-task. Lalu terbitkan track Data dan berlangganan track Audio dan Data. Nilai berikut adalah contoh PCM untuk tutorial ini. Isi field AoqConnectConfig dari respons token server aplikasi.
Copy
AoqClientEngine.AoqAudioCodecConfig audioDecoderConfig = new AoqClientEngine.AoqAudioCodecConfig();audioDecoderConfig.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;audioDecoderConfig.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioPCM;audioDecoderConfig.sampleRate = 24000; // Contoh. Sesuaikan dengan run-task.sample_rate.audioDecoderConfig.channel = 1;engine.setAudioDecoderConfig(audioDecoderConfig);AoqClientEngine.AoqTrackParam publishDataTrack = new AoqClientEngine.AoqTrackParam();publishDataTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeData;connectConfig.publishTracks.add(publishDataTrack);AoqClientEngine.AoqTrackParam subscribeAudioTrack = new AoqClientEngine.AoqTrackParam();subscribeAudioTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;connectConfig.subscribeTracks.add(subscribeAudioTrack);AoqClientEngine.AoqTrackParam subscribeDataTrack = new AoqClientEngine.AoqTrackParam();subscribeDataTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeData;connectConfig.subscribeTracks.add(subscribeDataTrack);engine.connect(connectConfig);
4. Panggil sendDataMsg untuk mengirim event run-task
Setelah koneksi berhasil, buat UUID task_id baru dan konfigurasikan model, voice, jenis teks, format audio, dan laju sampel. Untuk parameter opsional, lihat Event klien.
Copy
taskId = UUID.randomUUID().toString();JSONObject header = new JSONObject() .put("action", "run-task") .put("task_id", taskId) .put("streaming", "duplex");JSONObject parameters = new JSONObject() .put("text_type", "PlainText") .put("voice", voice) .put("format", "pcm") .put("sample_rate", 24000);JSONObject payload = new JSONObject() .put("task_group", "audio") .put("task", "tts") .put("function", "SpeechSynthesizer") .put("model", "qwen-audio-3.0-tts-flash") .put("input", new JSONObject()) .put("parameters", parameters);JSONObject runTask = new JSONObject().put("header", header).put("payload", payload);AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();dataMessage.data = runTask.toString().getBytes(StandardCharsets.UTF_8);engine.sendDataMsg(dataMessage);
5. Panggil sendDataMsg untuk mengirim event continue-task
Kirim continue-task hanya setelah task-started diterima. Satu tugas dapat berisi beberapa segmen. Setiap event mendukung hingga 20.000 karakter, dan satu tugas mendukung hingga total 200.000 karakter. Kirim segmen berikutnya atau selesaikan tugas segera. Jangan bergantung pada nilai timeout koneksi tetap.
Copy
JSONObject continueHeader = new JSONObject() .put("action", "continue-task") .put("task_id", taskId) .put("streaming", "duplex");JSONObject payload = new JSONObject() .put("input", new JSONObject().put("text", text));JSONObject continueTask = new JSONObject() .put("header", continueHeader) .put("payload", payload);AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();dataMessage.data = continueTask.toString().getBytes(StandardCharsets.UTF_8);engine.sendDataMsg(dataMessage);
Di onDataMsg, baca header.event untuk mempertahankan status tugas dan menangani kegagalan. result-generated menunjukkan bahwa satu kalimat telah disintesis, sedangkan audio tetap dikembalikan melalui track Audio. Untuk semua field, lihat Event server.
Copy
JSONObject header = event.optJSONObject("header");if (header == null) return;String name = header.optString("event");if ("task-started".equals(name)) { // Aplikasi kini dapat mengirim satu atau beberapa event continue-task.} else if ("result-generated".equals(name)) { // Satu kalimat telah disintesis. Audio dikirimkan melalui track Audio.} else if ("task-finished".equals(name)) { taskActive = false;} else if ("task-failed".equals(name)) { taskActive = false; String message = header.optString("error_message"); // Tampilkan atau catat error tersebut.}
7. Panggil sendDataMsg untuk mengirim event finish-task
Segera setelah semua teks dikirim, kirim finish-task untuk mensintesis teks yang belum lengkap yang di-buffer oleh layanan, lalu tunggu task-finished. Untuk detailnya, lihat Event klien.
Copy
JSONObject finishHeader = new JSONObject() .put("action", "finish-task") .put("task_id", taskId) .put("streaming", "duplex");JSONObject finishTask = new JSONObject() .put("header", finishHeader) .put("payload", new JSONObject().put("input", new JSONObject()));AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();dataMessage.data = finishTask.toString().getBytes(StandardCharsets.UTF_8);engine.sendDataMsg(dataMessage);
Jangan putuskan koneksi segera setelah finish-task dikirim. Setelah task-finished atau task-failed diterima, putuskan koneksi dan hapus engine jika tidak ada tugas lanjutan yang akan dimulai. SDK secara otomatis menutup pemutar audio.
Kelas berikut menerima AoqConnectConfig yang dipetakan dari respons token server aplikasi. Setelah koneksi berhasil, panggil synthesize(text, voice). Tambahkan izin, status UI, dan logika rekoneksi di lingkungan produksi.
Copy
import android.content.Context;import com.alibaba.aoq.clientsdk.AoqClientEngine;import com.alibaba.aoq.clientsdk.AoqClientListener;import org.json.JSONException;import org.json.JSONObject;import java.nio.charset.StandardCharsets;import java.util.UUID;public final class TtsClient { private AoqClientEngine engine; private String taskId; private String pendingText; private String pendingVoice; private boolean connected; private boolean taskActive; public TtsClient(Context context, AoqClientEngine.AoqConnectConfig connectConfig) { AoqClientListener listener = new AoqClientListener() { @Override public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) { if (status == AoqClientEngine.AoqConnectionStatus.AoqConnectionStatusConnected) { connected = true; } else if (status == AoqClientEngine.AoqConnectionStatus .AoqConnectionStatusDisconnected) { connected = false; } } @Override public void onDataMsg(AoqClientEngine.AoqDataMsg msg) { try { JSONObject event = new JSONObject( new String(msg.data, StandardCharsets.UTF_8)); String eventName = event.optJSONObject("header") == null ? "" : event.optJSONObject("header").optString("event"); if ("task-started".equals(eventName)) { sendContinueTask(); sendFinishTask(); } else if ("task-finished".equals(eventName) || "task-failed".equals(eventName)) { taskActive = false; } } catch (JSONException e) { throw new IllegalArgumentException("Invalid server event", e); } } }; AoqClientEngine.AoqCreateConfig createConfig = new AoqClientEngine.AoqCreateConfig(); createConfig.workDir = context.getFilesDir().getAbsolutePath(); engine = AoqClientEngine.createEngine(context, createConfig, listener); // Nilai contoh. Sesuaikan pengaturan ini dengan format audio output di run-task. AoqClientEngine.AoqAudioCodecConfig audioDecoderConfig = new AoqClientEngine.AoqAudioCodecConfig(); audioDecoderConfig.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio; audioDecoderConfig.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioPCM; audioDecoderConfig.sampleRate = 24000; audioDecoderConfig.channel = 1; engine.setAudioDecoderConfig(audioDecoderConfig); AoqClientEngine.AoqAudioPlaybackConfig playbackConfig = new AoqClientEngine.AoqAudioPlaybackConfig(); playbackConfig.channel = 1; playbackConfig.isDefaultSpeaker = true; engine.startAudioPlayer(playbackConfig); AoqClientEngine.AoqTrackParam publishDataTrack = new AoqClientEngine.AoqTrackParam(); publishDataTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeData; connectConfig.publishTracks.add(publishDataTrack); AoqClientEngine.AoqTrackParam subscribeAudioTrack = new AoqClientEngine.AoqTrackParam(); subscribeAudioTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio; connectConfig.subscribeTracks.add(subscribeAudioTrack); AoqClientEngine.AoqTrackParam subscribeDataTrack = new AoqClientEngine.AoqTrackParam(); subscribeDataTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeData; connectConfig.subscribeTracks.add(subscribeDataTrack); engine.connect(connectConfig); } public void synthesize(String text, String voice) { if (!connected || taskActive) { throw new IllegalStateException("Koneksi belum siap atau tugas sedang aktif."); } taskId = UUID.randomUUID().toString(); pendingText = text; pendingVoice = voice; taskActive = true; sendRunTask(); } private void sendRunTask() { try { JSONObject header = new JSONObject() .put("action", "run-task") .put("task_id", taskId) .put("streaming", "duplex"); JSONObject parameters = new JSONObject() .put("text_type", "PlainText") .put("voice", pendingVoice) .put("format", "pcm") .put("sample_rate", 24000); JSONObject payload = new JSONObject() .put("task_group", "audio") .put("task", "tts") .put("function", "SpeechSynthesizer") .put("model", "qwen-audio-3.0-tts-flash") .put("input", new JSONObject()) .put("parameters", parameters); JSONObject runTask = new JSONObject().put("header", header).put("payload", payload); AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg(); dataMessage.data = runTask.toString().getBytes(StandardCharsets.UTF_8); engine.sendDataMsg(dataMessage); } catch (JSONException e) { throw new IllegalStateException("Gagal membuat run-task", e); } } private void sendContinueTask() { try { JSONObject header = new JSONObject() .put("action", "continue-task") .put("task_id", taskId) .put("streaming", "duplex"); JSONObject payload = new JSONObject() .put("input", new JSONObject().put("text", pendingText)); JSONObject continueTask = new JSONObject() .put("header", header) .put("payload", payload); AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg(); dataMessage.data = continueTask.toString().getBytes(StandardCharsets.UTF_8); engine.sendDataMsg(dataMessage); } catch (JSONException e) { throw new IllegalStateException("Gagal membuat continue-task", e); } } private void sendFinishTask() { try { JSONObject header = new JSONObject() .put("action", "finish-task") .put("task_id", taskId) .put("streaming", "duplex"); JSONObject finishTask = new JSONObject() .put("header", header) .put("payload", new JSONObject().put("input", new JSONObject())); AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg(); dataMessage.data = finishTask.toString().getBytes(StandardCharsets.UTF_8); engine.sendDataMsg(dataMessage); } catch (JSONException e) { throw new IllegalStateException("Gagal membuat finish-task", e); } } public void close() { engine.disconnect(); AoqClientEngine.destroy(); }}
Setelah task-finished diterima, kirim run-task lain dengan task_id baru melalui koneksi AOQ yang sama. Tidak diperlukan token baru selama koneksi tetap aktif. Jika koneksi ditutup, peroleh kredensial koneksi baru.
Setiap run-task dapat memilih voice sistem atau voice_id yang valid di parameters.voice. Oleh karena itu, Anda dapat mengubah voice antar tugas melalui koneksi yang sama.
Pastikan kredensial diperoleh dari URL token Inferensi, dan periksa nama model, task_id, serta publikasi track Data di run-task.
continue-task ditolak
Tunggu task-started, dan gunakan task_id yang sama di run-task, continue-task, dan finish-task.
Tugas berhasil tetapi tidak ada audio yang diputar
Pastikan track Audio telah berlangganan dan pemutar sedang berjalan, serta verifikasi bahwa decoder SDK sesuai dengan format audio output yang dipilih di run-task.
Teks terakhir tidak memiliki audio
Kirim finish-task setelah semua teks dikirim, dan tunggu sisa audio serta task-finished sebelum memutus koneksi.