Gunakan AOQ untuk terhubung ke qwen-audio-3.0-realtime-plus dan manfaatkan VAD sisi server guna membangun percakapan suara real-time berlatensi rendah. Kode klien menggunakan Android Java.
Ikhtisar solusi
Qwen-Audio adalah model interaksi suara real-time end-to-end untuk skenario latensi rendah seperti asisten suara, layanan pelanggan, dan teman AI. AOQ mengirimkan audio dan event pada track yang terpisah. Track Audio membawa PCM uplink dari mikrofon dan PCM downlink dari model, sedangkan Track Data membawa event protokol Realtime.
Tutorial ini menggunakan server_vad. Klien terus-menerus mengirimkan audio, dan layanan mendeteksi kapan pengguna mulai dan berhenti berbicara serta memicu respons.
Prasyarat
- Aktifkan Model Studio dan ikuti Dapatkan dan konfigurasikan Kunci API. Simpan Kunci API hanya di server aplikasi Anda. Jangan sertakan dalam kode klien atau commit ke repositori kode.
- Konfirmasi endpoint AOQ untuk wilayah tempat aplikasi Anda diterapkan. Untuk panduan pemilihan, lihat Pilih wilayah, cakupan penerapan, dan endpoint.
- Unduh AOQ Client SDK versi terbaru sebagaimana dijelaskan dalam Unduhan SDK.
- Bangun server aplikasi dan implementasikan otentikasi proxy sebagaimana 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. Pengkodean Opus disediakan oleh plugin. Impor plugin Opus jika uplink menggunakan Opus.
Android
- Letakkan AoqClientSdk-release.aar di app/libs, lalu konfigurasikan dependensi serta ABIs yang didukung SDK di app/build.gradle:
android {
defaultConfig {
minSdk 21
ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
}
- Deklarasikan izin berikut di AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
- Minta izin RECORD_AUDIO saat runtime sebelum perangkat terkait digunakan.
iOS
- 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.
- Tambahkan NSMicrophoneUsageDescription ke Info.plist dan minta otorisasi sebelum perangkat terkait digunakan.
- Gunakan import AoqClientSdk di Swift atau #import <AoqClientSdk/AoqClientSdk.h> di Objective-C.
HarmonyOS
- 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"
}
}
- Deklarasikan izin berikut di entry/src/main/module.json5:
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" },
{ "name": "ohos.permission.MICROPHONE",
"reason": "$string:perm_mic_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }
]
- Sebelum perangkat terkait digunakan, panggil abilityAccessCtrl.createAtManager().requestPermissionsFromUser untuk meminta ohos.permission.MICROPHONE.
Linux (Python)
- Ekstrak SDK dan simpan aoq_client_sdk.py, libAoqClientSdk.so, dan libonnxruntime.so.1.16.3 dalam direktori yang sama.
- Tambahkan direktori SDK ke jalur pencarian Python dan pustaka dinamis:
export PYTHONPATH="$PWD/AoqClientSdk:$PYTHONPATH"
export LD_LIBRARY_PATH="$PWD/AoqClientSdk:$LD_LIBRARY_PATH"
- 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 secara cepat. Unduh APK dan konfigurasikan Kunci API serta workspaceId untuk mencoba model yang dipilih.
Pindai kode QR berikut untuk mengunduh demo:
Alur implementasi
- Server aplikasi memperoleh kredensial untuk koneksi AOQ saat ini ke qwen-audio-3.0-realtime-plus dari URL token Realtime.
- Klien mengonfigurasi penyandi uplink SDK dan decoder downlink sesuai model yang dipilih dan format audio aplikasi.
- Klien menginisialisasi perangkat perekaman dan pemutaran serta membuat AoqConnectConfig. Klien mengisi bidang kredensial untuk koneksi saat ini dan mengonfigurasi Track Audio dan Data untuk dipublikasikan dan dilanggani. Klien menonaktifkan pengiriman Track Audio dan memanggil connect untuk membuat koneksi AOQ.
- Setelah koneksi terbentuk, klien mengirim session.update. Klien hanya mengaktifkan Track Audio setelah menerima session.updated.
- VAD sisi server secara otomatis menentukan batas giliran. Audio model diputar melalui Track Audio dan event percakapan dikembalikan melalui Track Data.
- Untuk mengakhiri, putuskan koneksi dan hapus engine. SDK secara otomatis menutup perangkat audio.
Peroleh token dari server aplikasi
Tetapkan DASHSCOPE_API_KEY di server aplikasi dan kirim permintaan ke endpoint untuk wilayah yang dipilih. clientIp adalah Alamat IP publik aktual dari klien. Bidang ini opsional, tetapi menentukannya membantu layanan mengalokasikan endpoint relay yang sesuai.
curl -X POST \
"https://{endpoint}/api/v1/webrtc/realtime?model=qwen-audio-3.0-realtime-plus" \
-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 dari klien, hilangkan clientIp daripada mengirimkan string kosong.
Server aplikasi mengembalikan bidang respons berikut ke klien. Token AOQ hanya dapat digunakan untuk satu koneksi. Sebelum setiap pemanggilan connect, klien harus meminta token baru daripada menyimpan cache atau menggunakan ulang token lama. Jangan pernah mengembalikan Kunci API ke klien di lingkungan produksi. Untuk semua bidang permintaan dan respons, lihat Otentikasi token.
Bidang respons | Bidang SDK |
aoqTokenForClient | AoqConnectConfig.token |
sid | AoqConnectConfig.sid |
clientRelayCertFingerprint | AoqConnectConfig.certFingerprint |
clientRelayEndpoints | AoqConnectConfig.relayEndpoints |
extraInfo.workspaceIdHash | AoqConnectConfig.workspaceIdHash |
Implementasikan client Android
Sebelum setiap koneksi, klien memperoleh kredensial koneksi baru dari server aplikasi dan membuat AoqConnectConfig. Petakan bidang respons token dan tambahkan pengaturan koneksi sisi klien seperti track publish dan subscribe. Ikuti langkah-langkah berikut untuk mengimplementasikan percakapan suara real-time di Android.
1. Buat engine dan daftarkan callback
Buat engine AOQ singleton dan daftarkan callback event. Konfigurasikan sesi setelah koneksi berhasil, dan salurkan event server ke UI dan mesin keadaan aplikasi.
AoqClientListener listener = new AoqClientListener() {
@Override
public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) {
if (status == AoqClientEngine.AoqConnectionStatus.AoqConnectionStatusConnected) {
configureSession();
}
}
@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. Konfigurasikan kodek audio
Konfigurasikan penyandi uplink SDK dan decoder downlink sesuai model yang dipilih dan format audio aplikasi. Nilai berikut adalah contoh PCM untuk tutorial ini dan tidak membatasi format audio aplikasi Anda.
AoqClientEngine.AoqAudioCodecConfig audioEncoderConfig =
new AoqClientEngine.AoqAudioCodecConfig();
audioEncoderConfig.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
audioEncoderConfig.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioPCM;
audioEncoderConfig.sampleRate = 16000; // Contoh. Sesuaikan dengan model dan format aplikasi.
audioEncoderConfig.channel = 1;
engine.setAudioEncoderConfig(audioEncoderConfig);
AoqClientEngine.AoqAudioCodecConfig audioDecoderConfig =
new AoqClientEngine.AoqAudioCodecConfig();
audioDecoderConfig.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
audioDecoderConfig.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioPCM;
audioDecoderConfig.sampleRate = 24000; // Contoh. Sesuaikan dengan format output model.
audioDecoderConfig.channel = 1;
engine.setAudioDecoderConfig(audioDecoderConfig);
3. Konfigurasikan track dan hubungkan
Gunakan antarmuka SDK untuk memulai penangkapan dan pemutaran audio. Petakan respons token server aplikasi saat ini ke bidang kredensial di AoqConnectConfig, lalu konfigurasikan Track Audio dan Data di publishTracks dan subscribeTracks. Nonaktifkan pengiriman Track Audio saat Anda memanggil connect. Aktifkan pengiriman hanya setelah menerima session.updated.
AoqClientEngine.AoqAudioCaptureConfig captureConfig =
new AoqClientEngine.AoqAudioCaptureConfig();
captureConfig.channel = 1;
captureConfig.isVoipMode = true;
engine.startAudioCapture(captureConfig);
AoqClientEngine.AoqAudioPlaybackConfig playbackConfig =
new AoqClientEngine.AoqAudioPlaybackConfig();
playbackConfig.channel = 1;
playbackConfig.isVoipMode = true;
playbackConfig.isDefaultSpeaker = true;
engine.startAudioPlayer(playbackConfig);
AoqClientEngine.AoqTrackParam publishAudioTrack = new AoqClientEngine.AoqTrackParam();
publishAudioTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
connectConfig.publishTracks.add(publishAudioTrack);
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.enableSendMediaStream(
AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, false);
engine.connect(connectConfig);
4. Kirim session.update
Setelah koneksi berhasil, konfigurasikan modalities output, voice, format audio, instruksi, dan VAD. Baik input_audio_format maupun output_audio_format menggunakan pcm. Konfigurasi kodek SDK menentukan laju sampel. Untuk semua parameter, lihat Event klien.
JSONObject vad = new JSONObject()
.put("type", "server_vad")
.put("threshold", 0.5)
.put("silence_duration_ms", 800);
JSONObject session = new JSONObject()
.put("modalities", new JSONArray().put("text").put("audio"))
.put("voice", "longanqian")
.put("input_audio_format", "pcm")
.put("output_audio_format", "pcm")
.put("instructions", "You are a helpful voice assistant.")
.put("turn_detection", vad);
JSONObject sessionUpdate = new JSONObject()
.put("type", "session.update")
.put("session", session);
AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();
dataMessage.data = sessionUpdate.toString().getBytes(StandardCharsets.UTF_8);
engine.sendDataMsg(dataMessage);
5. Aktifkan uplink setelah session.updated
session.updated menunjukkan bahwa konfigurasi sesi aktif. Aktifkan pengiriman Track Audio hanya pada titik ini agar audio yang ditangkap sebelumnya tidak dikirim ke model.
if ("session.updated".equals(type)) {
engine.enableSendMediaStream(
AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, true);
}
6. Menangani event server
Di onDataMsg, gunakan type untuk menampilkan transkrip pengguna dan model serta menangani error. Untuk semua field event, lihat Event server.
if ("response.audio_transcript.delta".equals(type)) {
String delta = event.optString("delta");
// Tambahkan delta ke transkrip model di UI.
} else if ("conversation.item.input_audio_transcription.completed".equals(type)) {
String transcript = event.optString("transcript");
// Tampilkan transkrip pengguna akhir di UI.
} else if ("error".equals(type)) {
// Baca field error dan perbarui status aplikasi.
}
7. Putuskan koneksi dan hapus engine
Saat percakapan berakhir, putuskan koneksi dan hapus engine singleton. disconnect atau destroy secara otomatis menutup penangkapan dan pemutaran audio, sehingga Anda tidak perlu menghentikan perangkat secara terpisah.
engine.disconnect();
AoqClientEngine.destroy();
Event server utama
Event Track Data diidentifikasi berdasarkan type. Klien harus menangani event kunci berikut. Untuk skema event lengkap, lihat Event server.
Event | Deskripsi |
session.created | Sesi dibuat dan pengaturan default dikembalikan |
session.updated | Pengaturan client aktif dan uplink audio dapat diaktifkan |
input_audio_buffer.speech_started | Layanan mendeteksi bahwa pengguna mulai berbicara |
input_audio_buffer.speech_stopped | Layanan mendeteksi bahwa pengguna berhenti berbicara |
input_audio_buffer.committed | Audio untuk giliran telah dikomit. |
response.created | Model mulai menghasilkan respons |
response.audio_transcript.delta | Transkrip model inkremental |
conversation.item.input_audio_transcription.completed | Transkrip pengguna akhir tersedia |
response.done | Respons selesai |
error | Terjadi error server |
Contoh lengkap
Kelas berikut menerima AoqConnectConfig yang diisi dengan kredensial untuk koneksi saat ini dan menambahkan pengaturan perangkat audio, track publish, dan track subscribe. Peroleh kredensial baru dan buat konfigurasi koneksi baru untuk setiap rekoneksi. 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.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
public final class RealtimeVoiceChatClient {
private AoqClientEngine engine;
public RealtimeVoiceChatClient(Context context, AoqClientEngine.AoqConnectConfig connectConfig) {
AoqClientListener listener = new AoqClientListener() {
@Override
public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) {
if (status == AoqClientEngine.AoqConnectionStatus.AoqConnectionStatusConnected) {
configureSession();
}
}
@Override
public void onDataMsg(AoqClientEngine.AoqDataMsg msg) {
try {
JSONObject event = new JSONObject(
new String(msg.data, StandardCharsets.UTF_8));
String type = event.optString("type");
if ("session.updated".equals(type)) {
engine.enableSendMediaStream(
AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, true);
} else if ("response.audio_transcript.delta".equals(type)) {
String delta = event.optString("delta");
// Tampilkan delta di UI.
} else if ("conversation.item.input_audio_transcription.completed".equals(type)) {
String transcript = event.optString("transcript");
// Tampilkan transkrip di UI.
} else if ("error".equals(type)) {
// Baca field error dan perbarui status aplikasi.
}
} 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 model dan format audio aplikasi.
AoqClientEngine.AoqAudioCodecConfig audioEncoderConfig =
new AoqClientEngine.AoqAudioCodecConfig();
audioEncoderConfig.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
audioEncoderConfig.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioPCM;
audioEncoderConfig.sampleRate = 16000;
audioEncoderConfig.channel = 1;
engine.setAudioEncoderConfig(audioEncoderConfig);
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.AoqAudioCaptureConfig captureConfig =
new AoqClientEngine.AoqAudioCaptureConfig();
captureConfig.channel = 1;
captureConfig.isVoipMode = true;
engine.startAudioCapture(captureConfig);
AoqClientEngine.AoqAudioPlaybackConfig playbackConfig =
new AoqClientEngine.AoqAudioPlaybackConfig();
playbackConfig.channel = 1;
playbackConfig.isVoipMode = true;
playbackConfig.isDefaultSpeaker = true;
engine.startAudioPlayer(playbackConfig);
AoqClientEngine.AoqTrackParam publishAudioTrack =
new AoqClientEngine.AoqTrackParam();
publishAudioTrack.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
connectConfig.publishTracks.add(publishAudioTrack);
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.enableSendMediaStream(AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, false);
engine.connect(connectConfig);
}
private void configureSession() {
try {
JSONObject vad = new JSONObject()
.put("type", "server_vad")
.put("threshold", 0.5)
.put("silence_duration_ms", 800);
JSONObject session = new JSONObject()
.put("modalities", new JSONArray().put("text").put("audio"))
.put("voice", "longanqian")
.put("input_audio_format", "pcm")
.put("output_audio_format", "pcm")
.put("turn_detection", vad);
JSONObject sessionUpdate = new JSONObject()
.put("type", "session.update")
.put("session", session);
AoqClientEngine.AoqDataMsg dataMessage = new AoqClientEngine.AoqDataMsg();
dataMessage.data = sessionUpdate.toString().getBytes(StandardCharsets.UTF_8);
engine.sendDataMsg(dataMessage);
} catch (JSONException e) {
throw new IllegalStateException("Failed to create session.update", e);
}
}
public void close() {
engine.disconnect();
AoqClientEngine.destroy();
}
}
Jalankan dan verifikasi
- Audio mikrofon mulai dialirkan hanya setelah menerima session.updated.
- Setelah pengguna berhenti berbicara, layanan mengirimkan audio dan mulai merespons. Event teks dan audio dari Audio-Track dikembalikan secara berkelanjutan.
Skenario umum
Ubah mode interaksi
Gunakan server_vad untuk deteksi giliran berbasis kesenyapan, smart_turn untuk deteksi giliran akustik dan semantik, atau atur turn_detection ke null untuk push-to-talk. turn_detection hanya dapat diubah sebelum input audio pertama. Buat sesi baru untuk mengubah mode.
Ubah suara
Tetapkan session.voice di session.update pertama. Voice sistem yang didukung bervariasi tergantung model. Untuk voice yang didukung dan kloning suara, lihat Percakapan suara real-time Qwen-Audio.
Speaker atau earpiece
Tetapkan perangkat output default dengan menggunakan AoqAudioPlaybackConfig.isDefaultSpeaker, dan panggil enableSpeakerphone untuk beralih selama sesi aktif.
Panggilan latar belakang di Android
Di Android 10 atau lebih baru, gunakan foreground service dengan foregroundServiceType="microphone|mediaPlayback" untuk melanjutkan penangkapan dan pemutaran di latar belakang. Mulai saat aplikasi terlihat oleh pengguna.
Pemecahan masalah
Masalah | Solusi |
Koneksi gagal | Pastikan token valid, endpoint sesuai dengan wilayah penerapan, dan bidang AoqConnectConfig dipetakan dengan benar. |
Sesi terbentuk tetapi tidak ada respons yang dikembalikan | Pastikan track Audio diaktifkan setelah session.updated dan penyandi uplink SDK sesuai dengan format audio model dan aplikasi. |
Respons tidak memiliki audio | Pastikan track Audio dilanggani dan pemutar audio berjalan, lalu verifikasi bahwa decoder downlink SDK sesuai dengan format audio output model. |
Informasi terkait
Untuk semua parameter, field event, dan antarmuka untuk platform lain, lihat: