All Products
Search
Document Center

Alibaba Cloud Model Studio:Sintesis suara dengan qwen-audio-3.0-tts-flash melalui AOQ

Last Updated:Sep 02, 2026

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.

Ikhtisar solusi

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.

Prasyarat

  1. 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.
  2. Konfirmasi titik akhir AOQ untuk wilayah tempat aplikasi Anda diterapkan. Untuk panduan pemilihan, lihat Memilih wilayah, cakupan penerapan, dan titik akhir.
  3. Unduh AOQ Client SDK versi terbaru seperti dijelaskan dalam Unduhan SDK.
  4. 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

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

  1. Letakkan AoqClientSdk-release.aar di app/libs, lalu konfigurasikan dependensi dan ABI yang didukung SDK di app/build.gradle:
android {
    defaultConfig {
        minSdk 21
        ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
}
  1. Deklarasikan izin berikut di AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  1. Skenario ini tidak memerlukan izin mikrofon atau kamera.

iOS

  1. 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.
  2. Skenario ini tidak menggunakan mikrofon atau kamera dan tidak memerlukan izin terkait.
  3. Gunakan import AoqClientSdk di Swift atau #import <AoqClientSdk/AoqClientSdk.h> di Objective-C.

HarmonyOS

  1. Letakkan AoqClientSdk.har di entry/libs dan deklarasikan dependensi di entry/oh-package.json5. SDK kompatibel dengan API 12 dan mendukung arm64-v8a:
{
  "dependencies": {
    "@aoq/client-sdk": "file:./libs/AoqClientSdk.har"
  }
}
  1. Deklarasikan izin berikut di entry/src/main/module.json5:
"requestPermissions": [
  { "name": "ohos.permission.INTERNET" }
]
  1. Skenario ini tidak memerlukan izin mikrofon atau kamera.

Linux (Python)

  1. Ekstrak SDK dan simpan aoq_client_sdk.py, libAoqClientSdk.so, dan libonnxruntime.so.1.16.3 dalam direktori yang sama.
  2. Tambahkan direktori SDK ke jalur pencarian Python dan pustaka dinamis:
export PYTHONPATH="$PWD/AoqClientSdk:$PYTHONPATH"
export LD_LIBRARY_PATH="$PWD/AoqClientSdk:$LD_LIBRARY_PATH"
  1. Gunakan import aoq_client_sdk di Python. Anda juga dapat menentukan jalur mutlak libAoqClientSdk.so dengan menggunakan AOQ_CLIENT_SDK_LIB.

Coba demo

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:

QR code for downloading the demo

Alur implementasi

  1. Server aplikasi memperoleh parameter koneksi AOQ untuk qwen-audio-3.0-tts-flash dari URL token Inferensi.
  2. Klien menerbitkan track Data, berlangganan track Audio dan Data, serta mengonfigurasi decoder SDK sesuai format audio output yang dipilih di run-task.
  3. Klien memulai pemutar lokal dan terhubung ke AOQ. Setelah koneksi berhasil, klien mengirim run-task dengan task_id baru.
  4. Setelah task-started diterima, klien mengirim satu atau beberapa segmen teks continue-task sesuai kecepatan yang dibutuhkan aplikasi.
  5. Setelah semua teks dikirim, klien mengirim finish-task. Layanan mengembalikan sisa audio dan akhirnya mengirim task-finished.
  6. Setelah task-finished diterima, mulai tugas lain melalui koneksi AOQ yang sama dengan task_id baru, atau putuskan koneksi dan hapus engine.
Sequence diagram for streaming speech synthesis over AOQ

Memperoleh token dari server aplikasi

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.

curl -X POST \
  "https://{endpoint}/api/v1/webrtc/inference?model=qwen-audio-3.0-tts-flash" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${DASHSCOPE_API_KEY}" \
  -H "x-dashscope-rtc-transport: moq" \
  -d "{\"clientIp\": \"${CLIENT_REAL_IP}\"}"

CatatanJika 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.

Field respons

Bidang SDK

aoqTokenForClient

AoqConnectConfig.token

sid

AoqConnectConfig.sid

clientRelayCertFingerprint

AoqConnectConfig.certFingerprint

clientRelayEndpoints

AoqConnectConfig.relayEndpoints

extraInfo.workspaceIdHash

AoqConnectConfig.workspaceIdHash

Implementasi client Android

Setelah klien memperoleh AoqConnectConfig dari server aplikasi, ikuti langkah-langkah berikut untuk mengimplementasikan sintesis suara streaming di Android.

1. Buat engine dan daftarkan callback

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.

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);

2. Mulai pemutaran audio

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.

AoqClientEngine.AoqAudioPlaybackConfig playbackConfig =
        new AoqClientEngine.AoqAudioPlaybackConfig();
playbackConfig.channel = 1;
playbackConfig.isDefaultSpeaker = true;
engine.startAudioPlayer(playbackConfig);

3. Konfigurasi decoder dan track lalu hubungkan

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.

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.

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.

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);

6. Tangani event server

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.

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.

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);

8. Putuskan koneksi dan hapus engine

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.

engine.disconnect();
AoqClientEngine.destroy();

Event server utama

Event

Deskripsi

task-started

Tugas telah dimulai dan continue-task dapat dikirim

result-generated

Satu kalimat lengkap telah disintesis dan audionya dikembalikan melalui track Audio

task-finished

Semua teks yang di-buffer telah diproses dan tugas selesai

task-failed

Tugas gagal. Baca kode kesalahan dan pesannya

Contoh lengkap

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.

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();
    }
}

Jalankan dan verifikasi

  1. Teks hanya dikirim setelah task-started diterima.
  2. Audio untuk kalimat lengkap diputar terus-menerus melalui track Audio. Kalimat yang belum lengkap disintesis setelah finish-task.
  3. task-finished diterima setelah semua audio selesai. Tugas lain kemudian dapat dimulai dengan task_id baru.

Skenario umum

Beberapa tugas melalui satu koneksi

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.

Ubah suara

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.

Speaker atau earpiece

Atur output default menggunakan AoqAudioPlaybackConfig.isDefaultSpeaker, dan panggil enableSpeakerphone untuk beralih saat koneksi aktif.

Pemecahan masalah

Masalah

Solusi

Koneksi berhasil tetapi tugas tidak dimulai

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.

Informasi terkait

Untuk semua parameter, field event, dan antarmuka untuk platform lain, lihat: