全部產品
Search
文件中心

Alibaba Cloud Model Studio:使用 AOQ 接入 qwen-audio-3.0-realtime-plus 實現即時語音對話

更新時間:Aug 26, 2026

通過 AOQ 接入 qwen-audio-3.0-realtime-plus,使用服務端 VAD 自動劃分輪次,實現低延遲的即時語音對話。用戶端代碼以 Android Java 為例。

方案概述

Qwen-Audio 是端到端即時語音互動模型,適用於語音助手、智能客服和 AI 伴侶等需要低延遲語音互動的情境。AOQ SDK 將音頻與事件分軌傳輸:Audio 軌負責上行麥克風 PCM 和下行模型 PCM,Data 軌負責 Realtime 協議事件。

本教程使用 server_vad:用戶端持續上行音頻,服務端自動識別使用者開始和停止說話,並觸發模型回複。

準備工作

  1. 開通阿里雲百鍊,並按擷取與配置 API Key。API Key 只儲存在業務 AppServer,不要寫入用戶端代碼或提交到代碼倉庫。
  2. 根據業務部署地區確認 AOQ Endpoint。地區和接入地址的選擇方法請參見選擇地區、服務部署範圍和接入網域名稱
  3. SDK 下載擷取最新版 AOQ Client SDK。
  4. 搭建業務 AppServer,並按Token 鑒權實現服務端代理鑒權。每次建立新串連前,用戶端都應從 AppServer 擷取新的串連憑證。

匯入 SDK

根據開發平台匯入對應 SDK。後續用戶端代碼以 Android Java 為例,其他平台使用相同的介面設計和事件流程。本文以 PCM 音頻流為例。Opus 編碼由外掛程式提供;如果需要使用 Opus 編碼上行,請匯入 Opus 外掛程式。

Android

  1. 將 AoqClientSdk-release.aar 放入 app/libs,並在 app/build.gradle 中配置依賴和 SDK 支援的 ABI:
android {
    defaultConfig {
        minSdk 21
        ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
}
  1. 在 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" />
  1. 在使用相應裝置前動態申請 RECORD_AUDIO 許可權。

iOS

  1. 將 AoqClientSdk.framework 拖入 Xcode 工程,在 Target > General > Frameworks, Libraries, and Embedded Content 中選擇 Embed & Sign。SDK 支援 iOS 13.0 及以上 arm64 裝置。
  2. 在 Info.plist 中添加 NSMicrophoneUsageDescription,並在使用相應裝置前請求使用者授權。
  3. Swift 工程使用 import AoqClientSdk;Objective-C 工程使用 #import <AoqClientSdk/AoqClientSdk.h>。

HarmonyOS

  1. 將 AoqClientSdk.har 放入 entry/libs,並在 entry/oh-package.json5 中聲明依賴。該 SDK 相容 API 12,支援 arm64-v8a:
{
  "dependencies": {
    "@aoq/client-sdk": "file:./libs/AoqClientSdk.har"
  }
}
  1. 在 entry/src/main/module.json5 中聲明以下許可權:
"requestPermissions": [
  { "name": "ohos.permission.INTERNET" },
  { "name": "ohos.permission.MICROPHONE",
    "reason": "$string:perm_mic_reason",
    "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }
]
  1. 在使用相應裝置前調用 abilityAccessCtrl.createAtManager().requestPermissionsFromUser 申請 ohos.permission.MICROPHONE。

Linux (Python)

  1. 解壓 SDK,並保持 aoq_client_sdk.py、libAoqClientSdk.so 和 libonnxruntime.so.1.16.3 位於同一目錄。
  2. 將 SDK 目錄加入 Python 和動態庫搜尋路徑:
export PYTHONPATH="$PWD/AoqClientSdk:$PYTHONPATH"
export LD_LIBRARY_PATH="$PWD/AoqClientSdk:$LD_LIBRARY_PATH"
  1. 在 Python 代碼中使用 import aoq_client_sdk。也可通過 AOQ_CLIENT_SDK_LIB 指定 libAoqClientSdk.so 的絕對路徑。

體驗 Demo

阿里雲百鍊提供適用於 Android 平台的 Demo,可用於快速驗證 AOQ 接入效果。下載 APK 並配置 API Key 和 workspaceId 後,即可體驗部分模型。

掃描以下二維碼下載 Demo:

Demo 下載二維碼

實現流程

  1. AppServer 通過 Realtime Token 地址擷取 qwen-audio-3.0-realtime-plus 的本次 AOQ 串連憑證。
  2. 用戶端根據接入模型和業務音頻格式配置 SDK 的上行編碼與下行解碼參數。
  3. 用戶端初始化錄音和播放裝置,建立 AoqConnectConfig,將本次串連憑證寫入對應欄位,並配置需要發布和訂閱的 Audio、Data 軌。保持 Audio 軌發送關閉,調用 connect 建立 AOQ 串連。
  4. 串連成功後發送 session.update;收到 session.updated 後才開啟 Audio 軌發送。
  5. 服務端 VAD 自動劃分輪次,模型音頻通過 Audio 軌自動播放,Data 軌持續返回對話事件。
  6. 結束使用時中斷連線並銷毀引擎,SDK 會自動關閉音訊裝置。
AOQ 即時語音對話時序圖

AppServer 擷取 Token

在 AppServer 設定 DASHSCOPE_API_KEY,並使用所選地區的 Endpoint 發送請求。clientIp 為用戶端的真實公網 IP;該欄位可選,但建議傳入,以便服務分配合適的 Relay 存取點。

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}\"}"

說明如果 AppServer 無法擷取用戶端真實公網 IP,請刪除 clientIp 欄位,不要傳Null 字元串。

AppServer 將響應中的以下欄位返回用戶端。AOQ Token 僅供一次串連使用,用戶端每次調用 connect 前都必須重新請求,不能緩衝或複用。生產環境中不要把 API Key 返回用戶端。完整請求和響應欄位請參見Token 鑒權

響應欄位

SDK 欄位

aoqTokenForClient

AoqConnectConfig.token

sid

AoqConnectConfig.sid

clientRelayCertFingerprint

AoqConnectConfig.certFingerprint

clientRelayEndpoints

AoqConnectConfig.relayEndpoints

extraInfo.workspaceIdHash

AoqConnectConfig.workspaceIdHash

實現 Android 用戶端

每次建立串連前,用戶端先從 AppServer 擷取新的串連憑證,再建立 AoqConnectConfig:映射 Token 響應欄位,並補充發布和訂閱軌道等用戶端串連參數。按以下步驟實現 Android 端即時語音對話。

1. 建立引擎並設定回調

建立 AOQ 單例引擎並註冊事件回調。客戶需要在串連成功時配置會話,並把服務端事件分發給 UI 和業務狀態機器。

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. 配置音頻編解碼

根據接入模型和業務音頻格式配置 SDK 的上行編碼與下行解碼參數。以下數值僅為本教程的 PCM 樣本配置,不限制客戶的業務音頻格式。

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

3. 配置軌道並建立串連

使用 SDK 介面啟動音頻採集與播放。將本次 AppServer Token 響應映射到 AoqConnectConfig 的憑證欄位,並在 publishTracks 和 subscribeTracks 中分別配置 Audio 和 Data 軌。保持 Audio 軌發送關閉,調用 connect 建立串連;後續收到 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. 發送 session.update

串連成功後配置輸出模態、音色、音頻格式、系統指令和 VAD。input_audio_format 與 output_audio_format 的值均為 pcm;實際採樣率由 SDK 編解碼配置確定。完整參數請參見用戶端事件

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. 收到 session.updated 後開啟上行

收到 session.updated 表示會話配置已生效,此時才開啟 Audio 軌發送。客戶需要保證此前採集的音頻不會提前進入模型輸入。

if ("session.updated".equals(type)) {
    engine.enableSendMediaStream(
            AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, true);
}

6. 處理服務端事件

在 onDataMsg 中按 type 展示使用者和模型文本,並處理錯誤。完整事件欄位請參見服務端事件

if ("response.audio_transcript.delta".equals(type)) {
    String delta = event.optString("delta");
    // 將模型回複的文本增量展示在 UI 中。
} else if ("conversation.item.input_audio_transcription.completed".equals(type)) {
    String transcript = event.optString("transcript");
    // 將使用者語音的最終轉寫結果展示在 UI 中。
} else if ("error".equals(type)) {
    // 讀取錯誤欄位並更新應用狀態。
}

7. 中斷連線並銷毀引擎

結束通話時中斷連線並銷毀單例引擎。disconnect 或 destroy 會自動關閉音頻採集與播放,無需額外調用停止裝置的介面。

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

主要服務端事件

Data 軌事件以 type 標識,用戶端需要處理以下關鍵事件。完整事件結構請參見服務端事件

事件

說明

session.created

會話已建立並返回預設配置

session.updated

用戶端配置已生效,可以開啟音頻上行

input_audio_buffer.speech_started

檢測到使用者開始說話

input_audio_buffer.speech_stopped

檢測到使用者停止說話

input_audio_buffer.committed

本輪音頻已提交

response.created

模型開始產生回複

response.audio_transcript.delta

模型回複文本增量

conversation.item.input_audio_transcription.completed

使用者語音轉寫完成

response.done

本輪迴複結束

error

服務端錯誤

完整樣本

以下類接收已填入本次 AppServer 串連憑證的 AoqConnectConfig,並在類內補充音訊裝置和發布、訂閱軌道配置。每次重新串連都必須擷取新的憑證並建立串連配置。生產代碼還需補充許可權、UI 狀態和串連重試。

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");
                        // 將 delta 展示在 UI 中。
                    } else if ("conversation.item.input_audio_transcription.completed".equals(type)) {
                        String transcript = event.optString("transcript");
                        // 將 transcript 展示在 UI 中。
                    } else if ("error".equals(type)) {
                        // 讀取錯誤欄位並更新應用狀態。
                    }
                } 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);

        // 樣本參數,請按接入模型和業務音頻格式調整。
        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();
    }
}

運行並驗證

  1. 收到 session.updated 後才開始上行麥克風音頻。
  2. 使用者停止說話後,服務端自動認可音頻並開始回複;文本事件與 Audio 軌音頻連續返回。

典型情境

切換互動模式

server_vad 適合按靜音時間長度判停;smart_turn 結合聲學與語義判斷輪次;按鍵模式將 turn_detection 設為 null。turn_detection 只能在首次發送音頻前修改,切換模式時應重建立立會話。

切換音色

在首次 session.update 中設定 session.voice。不同模型支援的系統音色可能不同;支援的音色與聲音複刻用法請參見即時語音對話(Qwen-Audio-Realtime)

擴音器或耳機

通過 AoqAudioPlaybackConfig.isDefaultSpeaker 設定預設輸出裝置,運行中可調用 enableSpeakerphone 切換。

Android 後台通話

Android 10 及以上版本如需在後台繼續採集和播放,應使用 foregroundServiceType="microphone|mediaPlayback" 的前台服務,並在應用仍對使用者可見時啟動。

常見問題

問題

處理方法

串連失敗

確認 Token 未到期、Endpoint 與部署地區一致,並檢查 AoqConnectConfig 欄位對應。

會話已建立但無回複

確認收到 session.updated 後已開啟 Audio 軌,並檢查 SDK 上行編碼配置是否與模型和業務音頻格式一致。

回複沒有聲音

確認已訂閱 Audio 軌並啟動音頻播放器,然後檢查 SDK 下行解碼配置是否與模型輸出音頻格式一致。

相關文檔

如需查看完整參數、事件欄位或其他平台介面,請參見: