QwenCloudBRASIL
Tradução de apoio para consulta. Código e identificadores técnicos preservados. Consultar referência original ↗

Chamada em tempo real com qwen3.8-omni-flash-realtime via AOQ

Use qwen3.8-omni-flash-realtime Modelo através do protocolo AOQ para chamadas em tempo real

Descreve como integrar o Cliente de AOQ SDK no Android, iOS e HarmonyOS para implementar chamadas de áudio/vídeo usando AOQ e qwen3.8-omni-flash-realtime.

Pegue o SDK.

Para o Cliente de AOQ SDK e o plug-in de áudio Opus, veja [SDK download] (/api-reference/realtime-api/sdk-download). Este exemplo usa Opus e requer o plug-in Opus.

Importar o SDK

Copie os arquivos SDK do núcleo para o diretório de dependência do seu projeto e declare as permissões necessárias em sua configuração do projeto.

Androide.

Lugar AoqClientSdk-release.aar em seu projeto. app/libs/ Diretório. Lugar libPluginOpus.so pela ABI em app/libs/armeabi-v7a/ e app/libs/arm64-v8a/. Em 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 permissão. 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_AUDIO e CAMERA são permissões de execução. Seu aplicativo deve chamar o Android's. ActivityCompat.requestPermissions() No horário de execução para solicitar autorização do usuário.

iOS (quadro)

  1. Arrastar AoqClientSdk.framework e PluginOpus.framework em seu projeto Xcode. Sob o alvo > Geral > Frameworks, Bibliotecas, e Conteúdo Incorporado, selecione Embed & Sign.

  2. Declare permissões: no Xcode, selecione o seu Alvo > Informações > Propriedades de Destino Personalizadas do iOS e adicione as seguintes duas descrições de uso de permissão:

Chave Valor
NSMicrophoneUsageDescription Para chamadas de voz em tempo real
NSCameraUsageDescription Para chamadas de vídeo em tempo real.
  1. Projeto rápido: import AoqClientSdk. Projeto objetivo C: #import <AoqClientSdk/AoqClientSdk.h>.

HarmonyOS (har)

  1. Lugar aoq-client-sdk.har em seu projeto. libs/ Diretório. Lugar libPluginOpus.so pela ABI em entry/libs/armeabi-v7a/ e entry/libs/arm64-v8a/. Declare a dependência em entry/oh-package.json5.

  2. Adicione permissões. 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. Em EntryAbility, gatilho de autorização de execução via abilityAccessCtrl.createAtManager().requestPermissionsFromUser.

Pegue um símbolo do AppServer.

Siga a seção do AOQ em [Autenticação do Token] (/api-reference/realtime-api/token-auth) para criar um AppServer que obtenha fichas. Antes de cada chamada, o cliente deve pedir um sinal do seu AppServer.

Implementar chamadas de áudio e vídeo de IA

111

Crie motores e ajuste chamadas.

Chame o createEngine API para criar um AoqClientEngine - Por exemplo.

iOS

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

Implementar o `AoqEngineDelegate`  Protocolo para ouvir `onConnectionStatusChange`, `onDataMsg`, `onError`E outras chamadas.

Android

```java
AoqCreateConfig config = new AoqCreateConfig();
config.workDir = appCtx.getFilesDir().getAbsolutePath();
engine = AoqClientEngine.createEngine(appCtx, config, this);
```

HarmonyOS

```typescript
const config: AoqCreateConfig = { workDir: context.filesDir, extras: '' };
engine = AoqClientEngine.createEngine(config, this, context);
```

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

Chamada. startAudioCapture e startAudioPlayer para iniciar a captura de áudio local e reprodução. Chamada. startVideoCapture para iniciar a câmera, e usar setLocalView Ligar o alvo de renderização do SDK ao seu controle de visualização.

iOS

```swift
// 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

```java
// 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

```typescript
// 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

Peça ao seu servidor que faça o pedido. QwenCloud. Veja [Autenticação do Token] (/api-reference/realtime-api/token-auth).

Configure os codecs e estabeleça conexão.

Defina parâmetros de codec e chame connect.

Nota: qwen3.8-omni-flash-realtime requer que o cliente comece a enviar dados de mídia apenas após receber session.updated do servidor. Para evitar acidentalmente enviar mídia durante a janela entre um sucesso connect e a chegada de session.updated, chamar enableSendMediaStream(trackType, false) para cada faixa de upstream antes de chamar connect. Para detalhes do evento WebSocket, veja [Eventos do cliente] (/api-reference/real-time-multimodal/client-events).

iOS

```swift
// Audio codec configuration
let encCfg = AoqAudioCodecConfig()
encCfg.codecType = .audioOpus; 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

```java
// Audio codec configuration
AoqAudioCodecConfig encCfg = new AoqAudioCodecConfig();
encCfg.codecType = AoqEncoderType.AoqEncoderTypeAudioOpus;
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

```typescript
// Audio codec configuration
const encCfg: AoqAudioCodecConfig = {
  codecType: AoqEncoderType.AoqEncoderTypeAudioOpus,
  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);
```

O SDK do AOQ começa a enviar dados de mídia por padrão após a conexão. Este exemplo demonstra como desativar o envio de mídia ao conectar-se a um modelo.

Configure a sessão de IA

Na onConnectionStatusChange(Connected) -Retorne, envie um. session.update mensagem via sendDataMsg. Esta mensagem contém parâmetros de sessão, como modalidades, voz, instruções, e detecção, completando o aperto de mão da sessão. Para detalhes do evento WebSocket, veja [Eventos do cliente] (/api-reference/real-time-multimodal/client-events).

```swift
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": "Tina",
        // 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.
            "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

```java
@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

```typescript
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);
}
```

Habilitar envio de mídia após receber sessão.

Na onDataMsg Chamada de volta, analisar mensagens recebidas. Quando session.updated é recebido do modelo, ligue enableSendMediaStream(trackType, true) para cada tipo de pista que foi desativado antes. Para detalhes do evento WebSocket, veja [Eventos de servidor] (/api-reference/real-time-multimodal/server-events).

iOS

```swift
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

```java
@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

```typescript
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 */ }
}
```
  1. Comece a enviar transmissões de mídia só após receber session.updated. A IA pode não estar pronta para receber dados antes deste evento.
  2. As faixas de áudio e vídeo adicionadas durante a conexão (canais de mídia AOQ) automaticamente entregam dados ao servidor.
    • Áudio: transmitido diretamente através da faixa de áudio - não há necessidade de enviar input_audio_buffer.append eventos.
    • Vídeo: quadros são enviados através da faixa de vídeo - não há necessidade de enviar input_image_buffer.append eventos.

Desligue e destrua o motor.

engine.disconnect()
AoqClientEngine.destroy()

Cenários comuns

Barge-in

// iOS
engine.interruptAudioPlayer(.audio, fadeMs: 100)

Mudo / unmute

Após silenciar, o SDK continua a capturar áudio, mas envia apenas quadros silenciosos. O session Não é interrompido.

engine.muteAudioCapture(true);   // Mute microphone (capture continues, but only silent frames are sent)
engine.muteAudioCapture(false);  // Unmute

Troque entre as câmeras dianteira e traseira.

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

Legendas e resultados da ASR

O servidor empurra os resultados da ASR e as respostas de texto da IA através de mensagens de dados a jusante. Em seu aplicativo, roteie-os pelo type - Não, não, não. onDataMsg -Retorne. Para detalhes do evento WebSocket, veja [Eventos de servidor] (/api-reference/real-time-multimodal/server-events).

Notas importantes.

  1. #Semântica de singleton# createEngine É um singleton. Chamadas repetidas retornam o mesmo caso. O motor só pode ser recriado depois. destroy é chamado. Para uso em várias páginas, gerencie o ciclo de vida do motor no nível de Aplicação ou Capacidade.

  2. ** Tipos de visualização local:

    • Androide: SurfaceView Ou TextureView. Outros tipos não são suportados.
    • Qualquer um. UIView - Subclasse.
    • Veja a documentação da SDK.
  3. Acontecimentos como inserção/remoção de fones de ouvido ou gatilho de conexão Bluetooth onAudioDeviceRouteChanged. Não é necessário nenhum manuseio no seu aplicativo. Se a sua interface incluir um botão de voz/outorga, sincronize o estado com base nessa chamada.

  4. Para o áudio continuar quando a chamada se mover para o fundo, Info.plist Deve ter. UIBackgroundModes = audio habilitado e AVAudioSession deve ser corretamente ativado em primeiro plano. O SDK lida com a maioria dos casos; use setAudioSessionRestriction: para controle de grãos finos.

Código fonte da demonstração iOS

[Interface demo dos iOS] (https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/images/p1087917.png)

Para aprender a implementar o AOQ no iOS, baixe o código fonte da amostra:https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20260715/pnvhed/aoqdemo.zip).

Documentos relacionados