Todos os produtos
Search
Central de documentação

:Crie conversas de voz em tempo real com qwen-audio-3.0-realtime-plus via AOQ

Última atualização: Sep 02, 2026

Utilize o AOQ para se conectar ao qwen-audio-3.0-realtime-plus e use o VAD no lado do servidor para criar conversas de voz em tempo real com baixa latência. O código do cliente utiliza Android Java.

Visão geral da solução

O Qwen-Audio é um modelo de interação de voz em tempo real ponta a ponta, ideal para cenários de baixa latência como assistentes de voz, atendimento ao cliente e companheiros de IA. O AOQ transporta áudio e eventos em tracks separadas. A track de Audio transporta o uplink PCM do microfone e o downlink PCM do modelo, enquanto a track de Data transporta eventos do protocolo Realtime.

Este tutorial utiliza server_vad. O cliente envia áudio continuamente, e o service detecta quando o usuário começa e para de falar, acionando uma resposta.

Pré-requisitos

  1. Ative o Model Studio e siga Obtain and configure an API key. Armazene a API key apenas no seu servidor de aplicação. Não a inclua no código do cliente nem a envie para um repositório de código.
  2. Confirme o endpoint do AOQ para a região onde sua aplicação está implantada. Para orientações sobre a seleção, consulte Select a region, deployment scope, and endpoint.
  3. Baixe a versão mais recente do AOQ Client SDK conforme descrito em SDK download.
  4. Construa um servidor de aplicação e implemente a autenticação por proxy conforme descrito em Token authentication. Antes de cada nova conexão, o cliente deve obter novas credenciais de conexão do servidor de aplicação.

Importe o SDK

Importe o SDK para sua plataforma de desenvolvimento. A implementação do cliente usa Android Java. Outras plataformas fornecem as mesmas interfaces e fluxo de eventos. Este tutorial utiliza streams de áudio PCM. A codificação Opus é fornecida por um plugin. Importe o plugin Opus caso o uplink utilize Opus.

Android

  1. Coloque AoqClientSdk-release.aar em app/libs e configure a dependência e as ABIs suportadas pelo SDK em app/build.gradle:
android {
    defaultConfig {
        minSdk 21
        ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
}
  1. Declare as seguintes permissões no 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. Solicite as permissões RECORD_AUDIO em tempo de execução antes de utilizar os dispositivos correspondentes.

iOS

  1. Adicione AoqClientSdk.framework ao projeto Xcode e selecione Embed & Sign em Target > General > Frameworks, Libraries, and Embedded Content. O SDK suporta dispositivos arm64 com iOS 13.0 ou posterior.
  2. Adicione NSMicrophoneUsageDescription ao Info.plist e solicite autorização antes de utilizar os dispositivos correspondentes.
  3. Use import AoqClientSdk em Swift ou #import <AoqClientSdk/AoqClientSdk.h> em Objective-C.

HarmonyOS

  1. Coloque AoqClientSdk.har em entry/libs e declare a dependência em entry/oh-package.json5. O SDK é compatível com a API 12 e suporta arm64-v8a:
{
  "dependencies": {
    "@aoq/client-sdk": "file:./libs/AoqClientSdk.har"
  }
}
  1. Declare as seguintes permissões em 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. Antes de utilizar os dispositivos correspondentes, chame abilityAccessCtrl.createAtManager().requestPermissionsFromUser para solicitar ohos.permission.MICROPHONE.

Linux (Python)

  1. Extraia o SDK e mantenha aoq_client_sdk.py, libAoqClientSdk.so e libonnxruntime.so.1.16.3 no mesmo diretório.
  2. Adicione o diretório do SDK aos caminhos de busca do Python e de bibliotecas dinâmicas:
export PYTHONPATH="$PWD/AoqClientSdk:$PYTHONPATH"
export LD_LIBRARY_PATH="$PWD/AoqClientSdk:$LD_LIBRARY_PATH"
  1. Use import aoq_client_sdk em Python. Você também pode especificar o caminho absoluto de libAoqClientSdk.so utilizando AOQ_CLIENT_SDK_LIB.

Experimente a demonstração

Utilize a demonstração para Android do Alibaba Cloud Model Studio para verificar rapidamente a conectividade do AOQ. Baixe o APK e configure a API key e o workspaceId para testar modelos selecionados.

Escaneie o QR code a seguir para baixar a demonstração:

QR code for downloading the demo

Fluxo de implementação

  1. O servidor de aplicação obtém credenciais para a conexão AOQ atual com o qwen-audio-3.0-realtime-plus a partir da URL de token Realtime.
  2. O cliente configura o codificador de uplink e o decodificador de downlink do SDK para o modelo selecionado e o formato de áudio da aplicação.
  3. O cliente inicializa os dispositivos de gravação e reprodução e cria o AoqConnectConfig. Ele preenche os campos de credencial para a conexão atual e configura as tracks de Audio e Data para publicação e assinatura. O cliente mantém o envio da track de Audio desativado e chama connect para estabelecer a conexão AOQ.
  4. Após o estabelecimento da conexão, o cliente envia session.update. Ele ativa a track de Audio somente após receber session.updated.
  5. O VAD no lado do servidor determina automaticamente os limites dos turnos. O áudio do modelo é reproduzido pela track de Audio e os eventos da conversa são retornados pela track de Data.
  6. Para finalizar, desconecte e destrua o engine. O SDK fecha automaticamente os dispositivos de áudio.
Sequence diagram for real-time voice conversations over AOQ

Obtenha um token do servidor de aplicação

Defina DASHSCOPE_API_KEY no servidor de aplicação e envie a solicitação para o endpoint da região selecionada. clientIp é o endereço IP público real do cliente. Este campo é opcional, mas especificá-lo ajuda o service a alocar um endpoint de retransmissão apropriado.

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

ObservaçãoSe o servidor de aplicação não conseguir obter o endereço IP público real do cliente, omita clientIp em vez de passar uma string vazia.

O servidor de aplicação retorna os seguintes campos de resposta ao cliente. Um token AOQ pode ser usado para apenas uma conexão. Antes de cada chamada connect, o cliente deve solicitar um novo token em vez de armazenar em cache ou reutilizar um existente. Nunca retorne a API key para um cliente em produção. Para todos os campos de solicitação e resposta, consulte Token authentication.

Campo de resposta

Campo do SDK

aoqTokenForClient

AoqConnectConfig.token

sid

AoqConnectConfig.sid

clientRelayCertFingerprint

AoqConnectConfig.certFingerprint

clientRelayEndpoints

AoqConnectConfig.relayEndpoints

extraInfo.workspaceIdHash

AoqConnectConfig.workspaceIdHash

Implemente o cliente Android

Antes de cada conexão, o cliente obtém novas credenciais de conexão do servidor de aplicação e cria o AoqConnectConfig. Mapeie os campos de resposta do token e adicione configurações de conexão do lado do cliente, como as tracks de publicação e assinatura. Siga estas etapas para implementar conversas de voz em tempo real no Android.

1. Crie o engine e registre callbacks

Crie o engine AOQ singleton e registre callbacks de eventos. Configure a sessão após o sucesso da conexão e despache eventos do servidor para a UI e a máquina de estados da aplicação.

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. Configure codecs de áudio

Configure o codificador de uplink e o decodificador de downlink do SDK para o modelo selecionado e o formato de áudio da aplicação. Os valores a seguir são exemplos PCM para este tutorial e não restringem o formato de áudio da sua aplicação.

AoqClientEngine.AoqAudioCodecConfig audioEncoderConfig =
        new AoqClientEngine.AoqAudioCodecConfig();
audioEncoderConfig.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
audioEncoderConfig.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioPCM;
audioEncoderConfig.sampleRate = 16000; // Example. Match the model and application format.
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; // Example. Match the model output format.
audioDecoderConfig.channel = 1;
engine.setAudioDecoderConfig(audioDecoderConfig);

3. Configure tracks e conecte

Use as interfaces do SDK para iniciar a captura e a reprodução de áudio. Mapeie a resposta atual do token do servidor de aplicação para os campos de credencial em AoqConnectConfig e configure as tracks de Audio e Data em publishTracks e subscribeTracks. Mantenha o envio da track de Audio desativado ao chamar connect. Ative o envio somente após receber 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. Envie session.update

Após o sucesso da conexão, configure modalidades de saída, voz, formatos de áudio, instruções e VAD. Tanto input_audio_format quanto output_audio_format usam pcm. A configuração de codec do SDK determina as taxas de amostragem. Para todos os parâmetros, consulte Client events.

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

session.updated indica que a configuração da sessão está ativa. Ative o envio da track de Audio apenas neste momento para que o áudio capturado anteriormente não seja enviado ao modelo.

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

6. Trate eventos do servidor

Em onDataMsg, use type para exibir transcrições do usuário e do modelo e tratar erros. Para todos os campos de eventos, consulte Server events.

if ("response.audio_transcript.delta".equals(type)) {
    String delta = event.optString("delta");
    // Append delta to the model transcript in the UI.
} else if ("conversation.item.input_audio_transcription.completed".equals(type)) {
    String transcript = event.optString("transcript");
    // Display the final user transcript in the UI.
} else if ("error".equals(type)) {
    // Read the error fields and update the application state.
}

7. Desconecte e destrua o engine

Quando a conversa terminar, desconecte e destrua o engine singleton. disconnect ou destroy fecham automaticamente a captura e a reprodução de áudio, portanto, não é necessário parar os dispositivos separadamente.

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

Principais eventos do servidor

Os eventos da track de Data são identificados por type. O cliente deve tratar os seguintes eventos principais. Para esquemas completos de eventos, consulte Server events.

Evento

Descrição

session.created

A sessão é criada e as configurações padrão são retornadas

session.updated

As configurações do cliente estão ativas e o uplink de áudio pode ser ativado

input_audio_buffer.speech_started

O service detecta que o usuário começou a falar

input_audio_buffer.speech_stopped

O service detecta que o usuário parou de falar

input_audio_buffer.committed

O áudio do turno foi confirmado

response.created

O modelo começa a gerar uma resposta

response.audio_transcript.delta

Transcrição incremental do modelo

conversation.item.input_audio_transcription.completed

A transcrição final do usuário está disponível

response.done

A resposta está completa

error

Ocorreu um erro no servidor

Exemplo completo

A classe a seguir aceita um AoqConnectConfig preenchido com credenciais para a conexão atual e adiciona as configurações de dispositivo de áudio, track de publicação e track de assinatura. Obtenha novas credenciais e crie uma nova configuração de conexão para cada reconexão. Adicione permissões, estado da UI e lógica de reconexão em produção.

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");
                        // Display delta in the UI.
                    } else if ("conversation.item.input_audio_transcription.completed".equals(type)) {
                        String transcript = event.optString("transcript");
                        // Display transcript in the UI.
                    } else if ("error".equals(type)) {
                        // Read error fields and update the application state.
                    }
                } 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);

        // Example values. Match these settings to the model and application audio format.
        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();
    }
}

Execute e verifique

  1. O áudio do microfone começa a ser transmitido apenas após o recebimento de session.updated.
  2. Depois que o usuário para de falar, o service confirma o áudio e começa a responder. Eventos de texto e áudio da track de Audio são retornados continuamente.

Cenários comuns

Altere o modo de interação

Use server_vad para detecção de turnos baseada em silêncio, smart_turn para detecção de turnos acústica e semântica, ou defina turn_detection como null para push-to-talk. turn_detection só pode ser alterado antes da primeira entrada de áudio. Estabeleça uma nova sessão para mudar de modo.

Altere a voz

Defina session.voice no primeiro session.update. As vozes do sistema suportadas variam conforme o modelo. Para vozes suportadas e clonagem de voz, consulte Qwen-Audio real-time voice conversation.

Alto-falante ou auricular

Defina o dispositivo de saída padrão usando AoqAudioPlaybackConfig.isDefaultSpeaker e chame enableSpeakerphone para alternar enquanto a sessão estiver ativa.

Chamadas em segundo plano no Android

No Android 10 ou posterior, use um service em primeiro plano com foregroundServiceType="microphone|mediaPlayback" para continuar a captura e a reprodução em segundo plano. Inicie-o enquanto o aplicativo estiver visível para o usuário.

Solução de problemas

Problema

Solução

A conexão falha

Certifique-se de que o token é válido, o endpoint corresponde à região de implantação e os campos do AoqConnectConfig estão mapeados corretamente.

A sessão é estabelecida, mas nenhuma resposta é retornada

Verifique se a track de Audio está ativada após session.updated e se o codificador de uplink do SDK corresponde ao formato de áudio do modelo e da aplicação.

A resposta não tem áudio

Confirme se a track de Audio está assinada e o player de áudio está em execução e, em seguida, verifique se o decodificador de downlink do SDK corresponde ao formato de áudio de saída do modelo.

Informações relacionadas

Para todos os parâmetros, campos de eventos e interfaces para outras plataformas, consulte: