全部產品
Search
文件中心

Alibaba Cloud Model Studio:通過AOQ使用qwen3.5-omni-plus-realtime實現即時通話

更新時間:Aug 26, 2026

本文檔說明如何在 Android、iOS、HarmonyOS 平台接入 AOQ Client SDK,實現 AOQ+qwen3.5-omni-plus-realtime 音視訊通話功能。

SDK 擷取

AOQ Client SDK 及音頻 Opus 外掛程式請參見SDK下載。Opus 編碼以獨立外掛程式形式提供,請根據您的情境按需引入。

SDK 匯入

請根據不同平台將核心 SDK 產物匯入工程依賴目錄,並在工程配置中聲明相關許可權。

Android

AoqClientSdk-release.aar 放入工程 app/libs/ 目錄,將 libPluginOpus.so 按 ABI 放入 app/libs/armeabi-v7a/app/libs/arm64-v8a/,並在 app/build.gradle 中:

android {
    defaultConfig {
        minSdk 21
        ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
    }
    sourceSets { main { jniLibs.srcDirs = ['libs'] } }
    packagingOptions {
        // 避免與宿主工程的同名 so 衝突
        pickFirsts += ['lib/*/*.so']
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
}

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" />
<uses-permission android:name="android.permission.CAMERA" />

其中 RECORD_AUDIOCAMERA 為運行時許可權,應用需在運行時調用 Android ActivityCompat.requestPermissions() 方法,主動向 Android 系統申請使用者授權。

iOS(framework)

  1. AoqClientSdk.frameworkPluginOpus.framework 拖入 Xcode 工程,在 Target > General > Frameworks, Libraries, and Embedded Content 中選擇 Embed & Sign

  2. 許可權聲明:在 Xcode 中選中您的 Target > Info > Custom iOS Target Properties,添加以下兩項許可權用途描述:

    Key

    Value

    NSMicrophoneUsageDescription

    用於即時語音通話

    NSCameraUsageDescription

    用於即時視訊通話

  3. Swift 工程:import AoqClientSdk;Objective-C 工程:#import <AoqClientSdk/AoqClientSdk.h>

HarmonyOS(har)

  1. aoq-client-sdk.har 放入工程 libs/ 目錄,將 libPluginOpus.so 按 ABI 放入 entry/libs/armeabi-v7a/entry/libs/arm64-v8a/;並在 entry/oh-package.json5 中聲明。
  2. entry/src/main/module.json5 添加許可權:
"requestPermissions": [
  { "name": "ohos.permission.INTERNET" },
  { "name": "ohos.permission.MICROPHONE",
    "reason": "$string:perm_mic_reason",
    "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } },
  { "name": "ohos.permission.CAMERA",
    "reason": "$string:perm_camera_reason",
    "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }
]
  1. EntryAbility 中通過 abilityAccessCtrl.createAtManager().requestPermissionsFromUser 觸發運行時授權。

體驗 Demo

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

掃描以下二維碼下載並安裝 Android Demo:

Demo 下載二維碼

AppServer擷取Token

請按照Token鑒權的 AOQ 章節搭建擷取 Token 的 AppServer。每次通話前,用戶端需要向業務側 AppServer 請求一次 Token。

實現 AI 音視訊通話

111

建立引擎並設定回調

調用 createEngine 介面建立 AoqClientEngine 執行個體。

iOS:
let config = AoqCreateConfig()
config.workDir = workDir
engine = AoqClientEngine.createEngine(config, delegate: self)

實現 AoqEngineDelegate 協議監聽 onConnectionStatusChangeonDataMsgonError 等回調。

Android:
AoqCreateConfig config = new AoqCreateConfig();
config.workDir = appCtx.getFilesDir().getAbsolutePath();
engine = AoqClientEngine.createEngine(appCtx, config, this);
HarmonyOS:
const config: AoqCreateConfig = { workDir: context.filesDir, extras: '' };
engine = AoqClientEngine.createEngine(config, this, context);

啟動音視頻採集與播放

調用 startAudioCapturestartAudioPlayer 啟動本地音頻採集與播放;調用 startVideoCapture 啟動網路攝影機,並通過 setLocalView 將 SDK 渲染目標綁定到業務側的預覽處理常式。

iOS:
// 音頻採集
let capCfg = AoqAudioCaptureConfig()
capCfg.channel = 1; capCfg.isExternal = false
engine.startAudioCapture(capCfg)

// 音頻播放
let playCfg = AoqAudioPlaybackConfig()
playCfg.channel = 1; playCfg.isExternal = false
engine.startAudioPlayer(playCfg)

// 視頻採集
let vidCfg = AoqVideoCaptureConfig()
vidCfg.width = 720; vidCfg.height = 1280; vidCfg.fps = 15
engine.startVideoCapture(vidCfg)

// 為本地預覽畫面設定渲染視圖
let canvas = AoqVideoCanvas()
canvas.view = localPreview
canvas.renderMode = .crop
engine.setLocalView(.video, canvas: canvas)
Android:
// 音頻採集
AoqAudioCaptureConfig capCfg = new AoqAudioCaptureConfig();
capCfg.channel = 1; capCfg.isExternal = false;
engine.startAudioCapture(capCfg);

// 音頻播放
AoqAudioPlaybackConfig playCfg = new AoqAudioPlaybackConfig();
playCfg.channel = 1; playCfg.isExternal = false;
engine.startAudioPlayer(playCfg);

// 視頻採集
AoqVideoCaptureConfig vidCfg = new AoqVideoCaptureConfig();
vidCfg.width = 720; vidCfg.height = 1280; vidCfg.fps = 15;
engine.startVideoCapture(vidCfg);

// 為本地預覽畫面設定渲染視圖
AoqVideoCanvas canvas = new AoqVideoCanvas();
canvas.view = localPreview;
canvas.renderMode = AoqRenderMode.AoqRenderModeCrop;
engine.setLocalView(AoqTrackType.AoqTrackTypeVideo, canvas);
HarmonyOS:
// 音頻採集
const capCfg: AoqAudioCaptureConfig = { channel: 1, isExternal: false };
engine.startAudioCapture(capCfg);

// 音頻播放
const playCfg: AoqAudioPlaybackConfig = { channel: 1, isExternal: false };
engine.startAudioPlayer(playCfg);

// 視頻採集
const vidCfg: AoqVideoCaptureConfig = { width: 720, height: 1280, fps: 15, isExternal: false };
engine.startVideoCapture(vidCfg);

// 為本地預覽畫面設定渲染視圖
const canvas: AoqVideoCanvas = { view: localCtrl, renderMode: AoqRenderMode.AoqRenderModeCrop };
engine.setLocalView(AoqTrackType.AoqTrackTypeVideo, canvas);

擷取串連憑證

由業務 AppServer 代理百鍊請求,參見Token鑒權

設定編解碼及建立串連

設定編解碼參數後調用 connect

注意:qwen3.5-omni-plus-realtime 要求用戶端在收到服務端的 session.updated 之後才能開始發送媒體資料。為避免 connect 建聯成功到 session.updated 到達之間的空檔期誤推媒體,在 connect 之前對上行音頻與視頻軌道分別調用 enableSendMediaStream(trackType, false),將上行推流暫時關閉。WebSocket事件說明詳見用戶端事件

iOS:
// 音頻編解碼配置
let encCfg = AoqAudioCodecConfig()
encCfg.codecType = .audioPCM; encCfg.sampleRate = 16000; encCfg.channel = 1
engine.setAudioEncoderConfig(encCfg)
engine.setAudioDecoderConfig(encCfg)

// connect 前關閉媒體發送,待 session.updated 後再開啟
engine.enableSendMediaStream(.audio, enable: false)
engine.enableSendMediaStream(.video, enable: false)

// 建立串連
let conn = AoqConnectConfig()
conn.token = token; conn.sid = sid; conn.certFingerprint = cert
conn.relayEndpoints = endpoints; conn.workspaceIdHash = workspaceIdHash

let aTrack = AoqTrackParam(); aTrack.trackType = .audio
let vTrack = AoqTrackParam(); vTrack.trackType = .video
let dTrack = AoqTrackParam(); dTrack.trackType = .data
conn.publishTracks   = [aTrack, vTrack, dTrack]
conn.subscribeTracks = [aTrack, dTrack]
engine.connect(conn)
Android:
// 音頻編解碼配置
AoqAudioCodecConfig encCfg = new AoqAudioCodecConfig();
encCfg.codecType = AoqEncoderType.AoqEncoderTypeAudioPCM;
encCfg.sampleRate = 16000; encCfg.channel = 1;
engine.setAudioEncoderConfig(encCfg);
engine.setAudioDecoderConfig(encCfg);

// connect 前關閉媒體發送,待 session.updated 後再開啟
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeAudio, false);
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeVideo, false);

// 建立串連
AoqConnectConfig conn = new AoqConnectConfig();
conn.token = token; conn.sid = sid; conn.certFingerprint = cert;
conn.relayEndpoints.addAll(endpoints); conn.workspaceIdHash = workspaceIdHash;

AoqTrackParam aTrack = new AoqTrackParam(); aTrack.trackType = AoqTrackType.AoqTrackTypeAudio;
AoqTrackParam vTrack = new AoqTrackParam(); vTrack.trackType = AoqTrackType.AoqTrackTypeVideo;
AoqTrackParam dTrack = new AoqTrackParam(); dTrack.trackType = AoqTrackType.AoqTrackTypeData;
conn.publishTracks.add(aTrack);
conn.publishTracks.add(vTrack);
conn.publishTracks.add(dTrack);
conn.subscribeTracks.add(aTrack);
conn.subscribeTracks.add(dTrack);
engine.connect(conn);
HarmonyOS:
// 音頻編解碼配置
const encCfg: AoqAudioCodecConfig = {
  codecType: AoqEncoderType.AoqEncoderTypeAudioPCM,
  sampleRate: 16000, channel: 1
};
engine.setAudioEncoderConfig(encCfg);
engine.setAudioDecoderConfig(encCfg);

// connect 前關閉媒體發送,待 session.updated 後再開啟
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeAudio, false);
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeVideo, false);

// 建立串連
const conn: AoqConnectConfig = {
  token, sid, certFingerprint: cert,
  relayEndpoints: endpoints,
  workspaceIdHash,
  publishTracks: [
    { trackType: AoqTrackType.AoqTrackTypeAudio },
    { trackType: AoqTrackType.AoqTrackTypeVideo },
    { trackType: AoqTrackType.AoqTrackTypeData }
  ],
  subscribeTracks: [
    { trackType: AoqTrackType.AoqTrackTypeAudio },
    { trackType: AoqTrackType.AoqTrackTypeData }
  ]
};
engine.connect(conn);

重要重要:AOQ SDK 在建聯後會預設發送媒體資料,此樣本示範了串連模型時關閉媒體發送的能力。

配置 AI 會話

onConnectionStatusChange(Connected) 回調中通過 sendDataMsg 發送 session.update 訊息(業務自訂 JSON,包含 modalities、voice、instructions、turn_detection 等會話參數),完成會話握手,WebSocket事件說明詳見用戶端事件

iOS:
func onConnectionStatusChange(_ status: AoqConnectionStatus) {
    if status == .connected { sendSessionUpdate() }
}

private func sendSessionUpdate() {
    let json = """
    {
      // 該事件的id,由用戶端產生
      "event_id": "event_ToPZqeobitzUJnt3QqtWg",
      // 事件類型,固定為session.update
      "type": "session.update",
      // 會話配置
      "session": {
          // 輸出模態,支援設定為["text"](僅輸出文本)或["text","audio"](輸出文本與音頻)。
          "modalities": [
              "text",
              "audio"
          ],
          // 輸出音訊音色
          "voice": "Ethan",
          // 輸入音頻格式,當前僅支援設定為pcm。輸入音頻為16 kHz採樣率的PCM音頻流。
          "input_audio_format": "pcm",
          // 輸出音頻格式,當前僅支援設定為pcm。輸出音頻為24 kHz採樣率的PCM音頻流。
          "output_audio_format": "pcm",
          // 系統訊息,用於設定模型的目標或角色。
          "instructions": "你是某五星級酒店的AI客服專員,請準確且友好地解答客戶關於房型、設施、價格、預訂政策的諮詢。請始終以專業和樂於助人的態度回應,杜絕提供未經證實或超出酒店服務涵蓋範圍的資訊。",
          // 是否開啟語音活動檢測。若需啟用,需傳入一個設定物件,服務端將據此自動檢測語音起止。
          // 設定為null表示由用戶端決定何時發起模型響應。
          "turn_detection": {
              // VAD類型,取值為server_vad或semantic_vad。使用qwen3.5-omni-realtime模型時推薦設為semantic_vad。
              "type": "semantic_vad",
              // VAD檢測閾值。建議在嘈雜的環境中增加,在安靜的環境中降低。
              "threshold": 0.5,
              // 檢測語音停止的靜音期間,超過此值後會觸發模型響應
              "silence_duration_ms": 800
          }
      }
    }
    """
    let msg = AoqDataMsg()
    msg.data = json.data(using: .utf8)!
    engine.send(msg)
}
Android:
@Override
public void onConnectionStatusChange(AoqConnectionStatus status) {
    if (status == AoqConnectionStatus.AoqConnectionStatusConnected) {
        sendSessionUpdate();
    }
}

private void sendSessionUpdate() {
    String sessionUpdateJson = /* 與 Swift 樣本中相同的 session.update JSON */;
    AoqDataMsg msg = new AoqDataMsg();
    msg.data = sessionUpdateJson.getBytes(StandardCharsets.UTF_8);
    engine.sendDataMsg(msg);
}
HarmonyOS:
onConnectionStatusChange(status: AoqConnectionStatus): void {
  if (status === AoqConnectionStatus.AoqConnectionStatusConnected) {
    this.sendSessionUpdate();
  }
}

private sendSessionUpdate(): void {
  const sessionUpdateJson = /* 與 Swift 樣本中相同的 session.update JSON */;
  const msg: AoqDataMsg = { data: new TextEncoder().encode(sessionUpdateJson).buffer };
  this.engine.sendDataMsg(msg);
}

收到 session.updated 後開啟媒體發送

onDataMsg 回調中解析下行訊息,收到模型回複 session.updated 的時候,對上一步禁推的每個軌道類型調用 enableSendMediaStream(trackType, true) 放開推流。下面為程式碼範例,WebSocket事件說明詳見服務端事件

iOS:
func onDataMsg(_ msg: AoqDataMsg) {
    guard let obj = try? JSONSerialization.jsonObject(with: msg.data) as? [String: Any],
          let type = obj["type"] as? String else { return }
    if type == "session.updated" {
        engine.enableSendMediaStream(.audio, enable: true)
        engine.enableSendMediaStream(.video, enable: true)
    }
}
Android:
@Override
public void onDataMsg(AoqDataMsg msg) {
    if (msg == null || msg.data == null) return;
    try {
        JSONObject obj = new JSONObject(new String(msg.data, StandardCharsets.UTF_8));
        if ("session.updated".equals(obj.optString("type"))) {
            engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeAudio, true);
            engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeVideo, true);
        }
    } catch (JSONException ignored) {}
}
HarmonyOS:
onDataMsg(msg: AoqDataMsg): void {
  if (!msg?.data) return;
  try {
    const text = new TextDecoder('utf-8').decode(new Uint8Array(msg.data));
    const obj = JSON.parse(text) as { type?: string };
    if (obj.type === 'session.updated') {
      this.engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeAudio, true);
      this.engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeVideo, true);
    }
  } catch (_) { /* 非 JSON,忽略 */ }
}

重要

重要
  1. 模型必須在收到 session.updated 後才開啟媒體流發送,否則 AI 側可能還未準備好接收資料。

  2. 建連時添加的音頻軌道和視頻軌道(即 AOQ 媒體通道)會自動將資料轉送到服務端。

    1. 音頻:通過音頻軌道直接傳輸,無需發送 input_audio_buffer.append 事件。
    2. 視頻:通過視頻軌道發送畫面幀,無需發送 input_image_buffer.append 事件。

中斷連線與銷毀引擎

engine.disconnect()
AoqClientEngine.destroy()

典型情境

打斷(Barge-in)

  • SDK 與百鍊深度融合,支援百鍊模型的打斷訊息會在新一輪對話開始時打斷上一輪次。
  • SDK 提供本地播放器打斷介面 interruptAudioPlayer,當使用者主動需要停止時可以調用打斷 API 實現此功能。
// iOS
engine.interruptAudioPlayer(.audio, fadeMs: 100)

靜音 / 取消靜音

靜音後 SDK 仍在採集音頻,但只推送靜音幀,session 不會中斷。

engine.muteAudioCapture(true);   // 靜音麥克風(採集仍在跑,但只送靜音幀)
engine.muteAudioCapture(false);  // 恢複

切換前後網路攝影機

// 傳入期望切換到的方向枚舉即可
engine.switchCamera(AoqCameraDirection.AoqCameraDirectionFront);
engine.switchCamera(AoqCameraDirection.AoqCameraDirectionBack);

通話字幕與ASR結果顯示

服務端通過下行資料訊息推送 ASR 結果與 AI 文本回複。業務側在 onDataMsg 回調中根據 type 欄位分流即可。WebSocket事件說明詳見服務端事件

注意事項

  1. 單例語義createEngine 是單例,重複調用返回同一執行個體;destroy 後才能重新建立。多頁面共用建議在 Application/Ability 級管理引擎生命週期。

  2. 本地預覽 View 類型

    • Android:SurfaceViewTextureView;其它類型不支援。
    • iOS:任意 UIView 子類。
    • HarmonyOS:請參考 SDK 文檔。
  3. 音頻路由變化:耳機插拔、藍芽串連等會觸發 onAudioDeviceRouteChanged,業務側通常無需處理;如果 UI 上顯示"擴音器/耳機"開關,需要根據該回調同步狀態。

  4. 後台續傳:如需通話切到後台後繼續傳音頻,Info.plist 必須開啟 UIBackgroundModes = audio,並在前台時正確啟用 AVAudioSession(SDK 會處理大部分情況,業務側用 setAudioSessionRestriction: 可精細控制是否讓 SDK 接管)。

iOS Demo 源碼

iOS Demo 介面

如需參考 iOS 端的 AOQ 接入實現,請下載樣本源碼:aoqdemo.zip

相關文檔