Skip to main content
Melhores Práticas

Uso do AOQ com qwen3.5-omni-plus-realtime para chamadas em tempo real

Este tópico descreve como integrar o SDK do cliente AOQ no Android, iOS e HarmonyOS para implementar chamadas de áudio e vídeo com o AOQ e o modelo qwen3.5-omni-plus-realtime.

Obter o SDK

Para obter o SDK do cliente AOQ e o plug-in de áudio Opus, consulte Baixe o SDK. O codec Opus é fornecido como um plug-in separado. Inclua-o conforme necessário para o seu caso de uso.

Importar o SDK

Copie os arquivos principais do SDK para o diretório de dependências do projeto e declare as permissões necessárias na configuração do projeto.

Android

Coloque o arquivo AoqClientSdk-release.aar no diretório app/libs/ do projeto. Distribua o arquivo libPluginOpus.so por ABI nos diretórios app/libs/armeabi-v7a/ e app/libs/arm64-v8a/. No arquivo app/build.gradle:
android {
    defaultConfig {
        minSdk 21
        ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
    }
    sourceSets { main { jniLibs.srcDirs = ['libs'] } }
    packagingOptions {
        // Avoid conflicts with same-named .so files in the host project
        pickFirsts += ['lib/*/*.so']
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
}
Declare as permissões no arquivo 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" />
As permissões RECORD_AUDIO e CAMERA são permissões de tempo de execução. O aplicativo deve chamar o método ActivityCompat.requestPermissions() do Android durante a execução para solicitar autorização ao usuário.

iOS (framework)

  1. Arraste os arquivos AoqClientSdk.framework e PluginOpus.framework para o projeto no Xcode. Em Target > General > Frameworks, Libraries, and Embedded Content, selecione Embed & Sign.
  2. Declare as permissões: no Xcode, selecione Target > Info > Custom iOS Target Properties e adicione as duas descrições de uso de permissão a seguir:

    Chave

    Valor

    NSMicrophoneUsageDescription

    Para chamadas de voz em tempo real

    NSCameraUsageDescription

    Para chamadas de vídeo em tempo real

  3. Projetos Swift: import AoqClientSdk. Projetos Objective-C: #import <AoqClientSdk/AoqClientSdk.h>.

HarmonyOS (har)

  1. Coloque o arquivo aoq-client-sdk.har no diretório libs/ do projeto. Distribua o arquivo libPluginOpus.so por ABI nos diretórios entry/libs/armeabi-v7a/ e entry/libs/arm64-v8a/. Declare a dependência no arquivo entry/oh-package.json5.
  2. Adicione as permissões no arquivo 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. No EntryAbility, acione a autorização em tempo de execução por meio de abilityAccessCtrl.createAtManager().requestPermissionsFromUser.

Testar a demonstração

Use a demonstração para Android do Alibaba Cloud Model Studio para verificar rapidamente a conectividade do AOQ. Baixe o APK e configure a chave de API e o workspaceId para testar modelos selecionados. Escaneie o código QR a seguir para baixar e instalar a demonstração para Android:
QR code for downloading the demo

Obter um token do AppServer

Siga a seção sobre AOQ em Token authentication para configurar um AppServer que obtenha tokens. Antes de cada chamada, o cliente deve solicitar um token ao AppServer.

Implementar chamadas de áudio e vídeo com IA

111

Criar o engine e definir callbacks

Chame a API createEngine para criar uma instância de AoqClientEngine. iOS:
let config = AoqCreateConfig()
config.workDir = workDir
engine = AoqClientEngine.createEngine(config, delegate: self)
Implemente o protocolo AoqEngineDelegate para escutar os callbacks onConnectionStatusChange, onDataMsg, onError, entre outros. 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);

Iniciar captura e reprodução de áudio e vídeo

Chame startAudioCapture e startAudioPlayer para iniciar a captura e a reprodução de áudio local. Chame startVideoCapture para iniciar a câmera e use setLocalView para vincular o alvo de renderização do SDK ao controle de visualização. iOS:
// Audio capture
let capCfg = AoqAudioCaptureConfig()
capCfg.channel = 1; capCfg.isExternal = false
engine.startAudioCapture(capCfg)

// Audio playback
let playCfg = AoqAudioPlaybackConfig()
playCfg.channel = 1; playCfg.isExternal = false
engine.startAudioPlayer(playCfg)

// Video capture
let vidCfg = AoqVideoCaptureConfig()
vidCfg.width = 720; vidCfg.height = 1280; vidCfg.fps = 15
engine.startVideoCapture(vidCfg)

// Set render view for local video preview
let canvas = AoqVideoCanvas()
canvas.view = localPreview
canvas.renderMode = .crop
engine.setLocalView(.video, canvas: canvas)
Android:
// Audio capture
AoqAudioCaptureConfig capCfg = new AoqAudioCaptureConfig();
capCfg.channel = 1; capCfg.isExternal = false;
engine.startAudioCapture(capCfg);

// Audio playback
AoqAudioPlaybackConfig playCfg = new AoqAudioPlaybackConfig();
playCfg.channel = 1; playCfg.isExternal = false;
engine.startAudioPlayer(playCfg);

// Video capture
AoqVideoCaptureConfig vidCfg = new AoqVideoCaptureConfig();
vidCfg.width = 720; vidCfg.height = 1280; vidCfg.fps = 15;
engine.startVideoCapture(vidCfg);

// Set render view for local video preview
AoqVideoCanvas canvas = new AoqVideoCanvas();
canvas.view = localPreview;
canvas.renderMode = AoqRenderMode.AoqRenderModeCrop;
engine.setLocalView(AoqTrackType.AoqTrackTypeVideo, canvas);
HarmonyOS:
// Audio capture
const capCfg: AoqAudioCaptureConfig = { channel: 1, isExternal: false };
engine.startAudioCapture(capCfg);

// Audio playback
const playCfg: AoqAudioPlaybackConfig = { channel: 1, isExternal: false };
engine.startAudioPlayer(playCfg);

// Video capture
const vidCfg: AoqVideoCaptureConfig = { width: 720, height: 1280, fps: 15, isExternal: false };
engine.startVideoCapture(vidCfg);

// Set render view for local video preview
const canvas: AoqVideoCanvas = { view: localCtrl, renderMode: AoqRenderMode.AoqRenderModeCrop };
engine.setLocalView(AoqTrackType.AoqTrackTypeVideo, canvas);

Obter credenciais de conexão

Configure o AppServer para atuar como proxy da solicitação para o Alibaba Cloud Model Studio. Consulte Token authentication.

Configurar codecs e estabelecer conexão

Defina os parâmetros do codec e chame connect. Nota: o modelo qwen3.5-omni-plus-realtime exige que o cliente comece a enviar dados de mídia somente após receber session.updated do servidor. Para evitar o envio acidental de mídia no intervalo entre uma chamada connect bem-sucedida e a chegada de session.updated, chame enableSendMediaStream(trackType, false) para cada faixa de upstream antes de chamar connect. Para detalhes sobre eventos WebSocket, consulte Client events. iOS:
// Audio codec configuration
let encCfg = AoqAudioCodecConfig()
encCfg.codecType = .audioPCM; encCfg.sampleRate = 16000; encCfg.channel = 1
engine.setAudioEncoderConfig(encCfg)
engine.setAudioDecoderConfig(encCfg)

// Disable media sending before connect; enable after receiving session.updated
engine.enableSendMediaStream(.audio, enable: false)
engine.enableSendMediaStream(.video, enable: false)

// Establish connection
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:
// Audio codec configuration
AoqAudioCodecConfig encCfg = new AoqAudioCodecConfig();
encCfg.codecType = AoqEncoderType.AoqEncoderTypeAudioPCM;
encCfg.sampleRate = 16000; encCfg.channel = 1;
engine.setAudioEncoderConfig(encCfg);
engine.setAudioDecoderConfig(encCfg);

// Disable media sending before connect; enable after receiving session.updated
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeAudio, false);
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeVideo, false);

// Establish connection
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:
// Audio codec configuration
const encCfg: AoqAudioCodecConfig = {
  codecType: AoqEncoderType.AoqEncoderTypeAudioPCM,
  sampleRate: 16000, channel: 1
};
engine.setAudioEncoderConfig(encCfg);
engine.setAudioDecoderConfig(encCfg);

// Disable media sending before connect; enable after receiving session.updated
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeAudio, false);
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeVideo, false);

// Establish connection
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);
Importante: Por padrão, o SDK do AOQ começa a enviar dados de mídia logo após a conexão. Este exemplo demonstra como desativar o envio de mídia ao se conectar a um modelo.

Configurar sessão de IA

No callback onConnectionStatusChange(Connected), envie uma mensagem session.update por meio de sendDataMsg. Essa mensagem contém parâmetros da sessão, como modalidades, voz, instruções e turn_detection, concluindo o handshake da sessão. Para detalhes sobre eventos WebSocket, consulte Client events. iOS:
func onConnectionStatusChange(_ status: AoqConnectionStatus) {
    if status == .connected { sendSessionUpdate() }
}

private func sendSessionUpdate() {
    let json = """
    {
      // Event ID, generated by the client
      "event_id": "event_ToPZqeobitzUJnt3QqtWg",
      // Event type, always session.update
      "type": "session.update",
      // Session configuration
      "session": {
          // Output modalities: ["text"] (text only) or ["text","audio"] (text and audio)
          "modalities": [
              "text",
              "audio"
          ],
          // Output audio voice
          "voice": "Ethan",
          // Input audio format; currently only "pcm" is supported. Input audio is 16 kHz PCM.
          "input_audio_format": "pcm",
          // Output audio format; currently only "pcm" is supported. Output audio is 24 kHz PCM.
          "output_audio_format": "pcm",
          // System message for setting the model's objective or role.
          "instructions": "You are an AI customer service agent for a five-star hotel. Accurately and helpfully answer customer inquiries about room types, facilities, pricing, and reservation policies. Always respond professionally and helpfully. Do not provide unverified information or information outside the scope of hotel services.",
          // Whether to enable voice activity detection. If enabled, pass a config object and the server will automatically detect voice start and end.
          // Set to null to let the client control when to trigger a model response.
          "turn_detection": {
              // VAD type: server_vad or semantic_vad. Recommended: semantic_vad for qwen3.5-omni-realtime.
              "type": "semantic_vad",
              // VAD detection threshold. Increase in noisy environments; decrease in quiet ones.
              "threshold": 0.5,
              // Duration of silence after which a model response is triggered
              "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 = /* Same session.update JSON as in the Swift example above */;
    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 = /* Same session.update JSON as in the Swift example above */;
  const msg: AoqDataMsg = { data: new TextEncoder().encode(sessionUpdateJson).buffer };
  this.engine.sendDataMsg(msg);
}

Ativar envio de mídia após receber session.updated

No callback onDataMsg, analise as mensagens recebidas. Ao receber session.updated do modelo, chame enableSendMediaStream(trackType, true) para cada tipo de faixa desativado anteriormente. Para detalhes sobre eventos WebSocket, consulte Server events. 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 (_) { /* Not JSON, ignore */ }
}
Importante
  1. Inicie o envio de fluxos de mídia apenas após receber session.updated. A IA pode não estar pronta para receber dados antes desse evento.
  2. As faixas de áudio e vídeo adicionadas durante a conexão (canais de mídia do AOQ) entregam dados automaticamente ao servidor.
    1. Áudio: transmitido diretamente pela faixa de áudio — não é necessário enviar eventos input_audio_buffer.append.
    2. Vídeo: os quadros são enviados pela faixa de vídeo — não é necessário enviar eventos input_image_buffer.append.

Desconectar e destruir o engine

engine.disconnect()
AoqClientEngine.destroy()

Cenários comuns

Interrupção (Barge-in)

  • O SDK tem integração profunda com o Alibaba Cloud Model Studio. As mensagens de interrupção vindas do modelo cancelam o turno anterior quando um novo turno se inicia.
  • O SDK fornece a interface interruptAudioPlayer para interromper a reprodução local. Chame essa API quando o usuário desejar parar a reprodução.
// iOS
engine.interruptAudioPlayer(.audio, fadeMs: 100)

Silenciar / reativar áudio

Após silenciar, o SDK continua capturando áudio, mas envia apenas quadros silenciosos. A session não é interrompida.
engine.muteAudioCapture(true);   // Mute microphone (capture continues, but only silent frames are sent)
engine.muteAudioCapture(false);  // Unmute

Alternar entre câmeras frontal e traseira

// Pass the desired camera direction enum
engine.switchCamera(AoqCameraDirection.AoqCameraDirectionFront);
engine.switchCamera(AoqCameraDirection.AoqCameraDirectionBack);

Legendas da chamada e resultados de ASR

O servidor envia resultados de ASR e respostas de texto da IA por meio de mensagens de dados downstream. No aplicativo, direcione-as com base no campo type dentro do callback onDataMsg. Para detalhes sobre eventos WebSocket, consulte Server events.

Observações importantes

  1. Semântica de singleton: createEngine é um singleton — chamadas repetidas retornam a mesma instância. O engine só pode ser recriado após a chamada de destroy. Para uso em múltiplas páginas, gerencie o ciclo de vida do engine no nível da Application ou do Ability.
  2. Tipos de visualização para preview local:
    • Android: SurfaceView ou TextureView. Outros tipos não são suportados.
    • iOS: Qualquer subclasse de UIView.
    • HarmonyOS: Consulte a documentação do SDK.
  3. Alterações na rota de áudio: Eventos como inserção/remoção de fones de ouvido ou conexão Bluetooth acionam onAudioDeviceRouteChanged. Geralmente, não é necessário nenhum tratamento no aplicativo. Se a UI incluir um alternador entre alto-falante e auricular, sincronize o estado com base nesse callback.
  4. Continuidade de áudio em segundo plano: Para que o áudio continue quando a chamada for para segundo plano, o arquivo Info.plist deve ter UIBackgroundModes = audio ativado e a AVAudioSession deve estar corretamente ativada em primeiro plano. O SDK lida com a maioria dos casos; use setAudioSessionRestriction: para um controle mais refinado.

Código-fonte da demonstração para iOS

iOS demo interface
Para aprender a implementar o AOQ no iOS, baixe o código-fonte de exemplo: aoqdemo.zip.

Documentos relacionados

  • API detalhada do SDK do cliente AOQ: SDK overview
  • Eventos do cliente do modelo qwen3.5-omni-plus-realtime: Client events
  • Eventos do servidor do modelo qwen3.5-omni-plus-realtime: Server events
Plano de Tokens
Playground de Modelos
  • Music generation
Inferência do Modelo
Avaliação
Compressão de Modelos
Estatísticas e Monitoramento
Suporte