Todos los productos
Search
Centro de documentos

Alibaba Cloud Model Studio:Crear conversaciones de voz en tiempo real con qwen-audio-3.1-realtime-plus mediante AOQ

Última actualización: Oct 12, 2026

Utilice AOQ para conectarse a qwen-audio-3.1-realtime-plus y emplee el VAD del lado del servidor para crear conversaciones de voz en tiempo real de baja latencia. El código del cliente utiliza Android Java.

Descripción general de la solución

Qwen-Audio es un modelo de interacción de voz en tiempo real de extremo a extremo diseñado para escenarios de baja latencia, como asistentes de voz, atención al cliente y compañeros de IA. AOQ transporta audio y eventos en pistas separadas. La pista de Audio transporta el audio del micrófono en enlace ascendente y el audio del modelo en enlace descendente, mientras que la pista de Datos transporta los eventos del protocolo Realtime.

Este tutorial utiliza server_vad. El cliente envía audio continuamente, y el servicio detecta cuándo el usuario comienza y deja de hablar, y activa una respuesta.

Requisitos previos

  1. Active Model Studio y siga los pasos en Obtener y configurar una clave API. Almacene la clave API únicamente en su servidor de aplicaciones. No la incluya en el código del cliente ni la confirme en un repositorio de código.
  2. Confirm el endpoint de AOQ correspondiente a la región donde está implementada su aplicación. Para obtener orientación sobre la selección, consulte Seleccionar una región, un alcance de implementación y un endpoint.
  3. Descargue la versión más reciente del SDK de cliente AOQ como se describe en Descarga del SDK.
  4. Construya un servidor de aplicaciones e implemente la autenticación proxy tal como se describe en Autenticación por token. Antes de cada nueva conexión, el cliente debe obtener nuevas credenciales de conexión del servidor de aplicaciones.

Importar el SDK

Importe el SDK correspondiente a su plataforma de desarrollo. La implementación del cliente utiliza Android Java. Otras plataformas proporcionan las mismas interfaces y flujo de eventos. Este tutorial utiliza flujos de audio Opus. Importe el complemento Opus antes de continuar.

Android

  1. Coloque libPluginOpus.so del complemento Opus en app/src/main/jniLibs/armeabi-v7a/ y app/src/main/jniLibs/arm64-v8a/ para las ABI correspondientes. Coloque AoqClientSdk-release.aar en app/libs, y configure la dependencia y las ABI compatibles con el SDK en app/build.gradle:
android {
    defaultConfig {
        minSdk 21
        ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
}
  1. Declare los siguientes permisos en 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 los permisos RECORD_AUDIO en tiempo de ejecución antes de utilizar los dispositivos correspondientes.

iOS

  1. Agregue AoqClientSdk.framework y PluginOpus.framework al proyecto de Xcode y seleccione Embed & Sign en Target > General > Frameworks, Libraries, and Embedded Content. El SDK es compatible con dispositivos arm64 que ejecutan iOS 13.0 o posterior.
  2. Agregue NSMicrophoneUsageDescription a Info.plist y solicite autorización antes de utilizar los dispositivos correspondientes.
  3. Utilice import AoqClientSdk en Swift o #import <AoqClientSdk/AoqClientSdk.h> en Objective-C.

HarmonyOS

  1. Coloque libPluginOpus.so del complemento Opus en entry/libs/arm64-v8a/. Coloque AoqClientSdk.har en entry/libs y declare la dependencia en entry/oh-package.json5. El SDK es compatible con API 12 y admite arm64-v8a:
{
  "dependencies": {
    "@aoq/client-sdk": "file:./libs/AoqClientSdk.har"
  }
}
  1. Declare los siguientes permisos en 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 los dispositivos correspondientes, llame a abilityAccessCtrl.createAtManager().requestPermissionsFromUser para solicitar ohos.permission.MICROPHONE.

Linux (Python)

  1. Extraiga el SDK y conserve aoq_client_sdk.py, libAoqClientSdk.so y libonnxruntime.so.1.16.3 en el mismo directorio.
  2. Agregue el directorio del SDK a las rutas de búsqueda de Python y de bibliotecas dinámicas:
export PYTHONPATH="$PWD/AoqClientSdk:$PYTHONPATH"
export LD_LIBRARY_PATH="$PWD/AoqClientSdk:$LD_LIBRARY_PATH"
  1. Utilice import aoq_client_sdk en Python. También puede especificar la ruta absoluta de libAoqClientSdk.so mediante AOQ_CLIENT_SDK_LIB.

Probar la demostración

Utilice la demostración de Android de Alibaba Cloud Model Studio para verificar rápidamente la conectividad AOQ. Descargue el APK y configure la clave de API y workspaceId para probar modelos seleccionados.

Escanee el siguiente código QR para descargar la demostración:

Código QR para descargar la demostración

Flujo de implementación

  1. El servidor de aplicaciones obtiene las credenciales para la conexión AOQ actual a qwen-audio-3.1-realtime-plus desde la URL del token Realtime.
  2. El cliente configura el codificador de enlace ascendente y el decodificador de enlace descendente del SDK para el modelo seleccionado y el formato de audio de la aplicación.
  3. El cliente inicializa los dispositivos de grabación y reproducción, y crea AoqConnectConfig. Rellena los campos de credenciales para la conexión actual y configura las pistas de Audio y Datos para publicar y suscribirse. El cliente mantiene deshabilitado el envío de la pista de Audio y llama a connect para establecer la conexión AOQ.
  4. Después de establecer la conexión, el cliente envía session.update. Habilita la pista de Audio solo después de recibir session.updated.
  5. El VAD del lado del servidor determina automáticamente los límites de los turnos. El audio del modelo se reproduce a través de la pista de Audio y los eventos de conversación se devuelven a través de la pista de Datos.
  6. Para finalizar, desconéctese y destruya el motor. El SDK cierra automáticamente los dispositivos de audio.
Diagrama de secuencia para conversaciones de voz en tiempo real a través de AOQ

Obtener un token del servidor de aplicaciones

Establezca DASHSCOPE_API_KEY en el servidor de aplicaciones y envíe la solicitud al endpoint de la región seleccionada. clientIp es la dirección IP pública real del cliente. Este campo es opcional, pero especificarlo ayuda al servicio a asignar un endpoint de retransmisión adecuado.

curl -X POST \
  "https://{endpoint}/api/v1/webrtc/realtime?model=qwen-audio-3.1-realtime-plus" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${DASHSCOPE_API_KEY}" \
  -H "x-dashscope-rtc-transport: moq" \
  -d "{\"clientIp\": \"${CLIENT_REAL_IP}\"}"

NotaSi el servidor de aplicaciones no puede obtener la dirección IP pública real del cliente, omita clientIp en lugar de pasar una cadena vacía.

El servidor de aplicaciones devuelve los siguientes campos de respuesta al cliente. Un token de AOQ solo puede utilizarse para una única conexión. Antes de cada llamada a connect, el cliente debe solicitar un nuevo token en lugar de almacenar en caché o reutilizar uno existente. Nunca devuelva la clave API a un cliente en entornos de producción. Para consultar todos los campos de solicitud y respuesta, véase Autenticación por token.

Campo de respuesta

Campo del SDK

aoqTokenForClient

AoqConnectConfig.token

sid

AoqConnectConfig.sid

clientRelayCertFingerprint

AoqConnectConfig.certFingerprint

clientRelayEndpoints

AoqConnectConfig.relayEndpoints

extraInfo.workspaceIdHash

AoqConnectConfig.workspaceIdHash

Implementar el cliente de Android

Antes de cada conexión, el cliente obtiene nuevas credenciales de conexión del servidor de aplicaciones y crea AoqConnectConfig. Asigne los campos de respuesta del token y agregue la configuración de conexión del lado del cliente, como las pistas de publicación y suscripción. Siga estos pasos para implementar conversaciones de voz en tiempo real en Android.

1. Crear el motor y registrar devoluciones de llamada

Cree el motor AOQ singleton y registre las devoluciones de llamada de eventos. Configure la sesión después de que la conexión se establezca correctamente y distribuya los eventos del servidor a la interfaz de usuario y a la máquina de estados de la aplicación.

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. Configurar códecs de audio

Configure el codificador de enlace ascendente y el decodificador de enlace descendente del SDK para el modelo seleccionado y el formato de audio de la aplicación. Los siguientes valores son ejemplos de Opus para este tutorial y no restringen el formato de audio de su aplicación.

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

3. Configurar pistas y conectar

Utilice las interfaces del SDK para iniciar la captura y reproducción de audio. Asigne la respuesta actual del token del servidor de aplicaciones a los campos de credenciales en AoqConnectConfig, y configure las pistas de Audio y Datos en publishTracks y subscribeTracks. Mantenga deshabilitado el envío de la pista de Audio cuando llame a connect. Habilite el envío solo después de recibir 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. Enviar session.update

Después de que la conexión se establezca correctamente, configure las modalidades de salida, la voz, los formatos de audio, las instrucciones y el VAD. Tanto input_audio_format como output_audio_format utilizan pcm. La configuración del códec del SDK determina las tasas de muestreo. Para consultar todos los parámetros, véase Eventos del cliente.

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_v3.1")
        .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. Habilitar enlace ascendente después de session.updated

session.updated indica que la configuración de la sesión está activa. Habilite el envío de la pista de audio solo en este punto para evitar que el audio capturado anteriormente se envíe al modelo.

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

6. Manejar eventos del servidor

En onDataMsg, utilice type para mostrar las transcripciones del usuario y del modelo, y para gestionar errores. Para todos los campos de eventos, consulte Eventos del servidor.

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. Desconectar y destruir el motor

Cuando finalice la conversación, desconecte y destruya el motor singleton. disconnect o destroy cierran automáticamente la captura y reproducción de audio, por lo que no es necesario detener los dispositivos por separado.

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

Principales eventos del servidor

Los eventos de la pista de datos se identifican mediante type. El cliente debe gestionar los siguientes eventos clave. Para consultar los esquemas completos de los eventos, véase Eventos del servidor.

Evento

Descripción

session.created

Se crea la sesión y se devuelve la configuración predeterminada

session.updated

La configuración del cliente está activa y se puede habilitar el enlace ascendente de audio

input_audio_buffer.speech_started

El servicio detecta que el usuario comenzó a hablar

input_audio_buffer.speech_stopped

El servicio detecta que el usuario dejó de hablar

input_audio_buffer.committed

Se confirma el audio del turno

response.created

El modelo comienza a generar una respuesta

response.audio_transcript.delta

Transcripción incremental del modelo

conversation.item.input_audio_transcription.completed

La transcripción final del usuario está disponible

response.done

La respuesta está completa

error

Se produce un error del servidor

Ejemplo completo

La siguiente clase acepta un AoqConnectConfig rellenado con las credenciales de la conexión actual y agrega la configuración del dispositivo de audio, la pista de publicación y la pista de suscripción. Obtenga nuevas credenciales y cree una nueva configuración de conexión para cada reconexión. Agregue permisos, estado de la interfaz de usuario y lógica de reconexión en entornos de producción.

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.AoqEncoderTypeAudioOpus;
        audioEncoderConfig.sampleRate = 16000;
        audioEncoderConfig.channel = 1;
        engine.setAudioEncoderConfig(audioEncoderConfig);

        AoqClientEngine.AoqAudioCodecConfig audioDecoderConfig =
                new AoqClientEngine.AoqAudioCodecConfig();
        audioDecoderConfig.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
        audioDecoderConfig.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioOpus;
        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_v3.1")
                    .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();
    }
}

Ejecutar y verificar

  1. El audio del micrófono comienza a transmitirse solo después de recibir session.updated.
  2. Después de que el usuario deja de hablar, el servicio confirma el audio y comienza a responder. Los eventos de texto y el audio de Audio-track se devuelven continuamente.

Escenarios comunes

Cambiar el modo de interacción

Utilice server_vad para la detección de turnos basada en silencio, smart_turn para la detección acústica y semántica de turnos, o establezca turn_detection en null para pulsar para hablar. turn_detection solo puede modificarse antes de la primera entrada de audio. Establezca una nueva sesión para cambiar de modo.

Cambiar la voz

Establezca session.voice en la primera solicitud session.update. Las voces del sistema compatibles varían según el modelo. Para consultar las voces admitidas y la clonación de voz, véase Qwen-Audio conversación de voz en tiempo real.

Altavoz o auricular

Establezca el dispositivo de salida predeterminado mediante AoqAudioPlaybackConfig.isDefaultSpeaker y llame a enableSpeakerphone para cambiarlo mientras la sesión esté activa.

Llamadas en segundo plano en Android

En Android 10 o posterior, utilice un servicio en primer plano con foregroundServiceType="microphone|mediaPlayback" para continuar la captura y reproducción en segundo plano. Inícielo mientras la aplicación sea visible para el usuario.

Solución de problemas

Problema

Solución

La conexión falla

Asegúrese de que el token sea válido, de que el endpoint coincida con la región de implementación y de que los campos de AoqConnectConfig estén asignados correctamente.

La sesión se establece pero no se devuelve ninguna respuesta

Asegúrese de que la pista de Audio esté habilitada después de session.updated y de que el codificador de enlace ascendente del SDK coincida con el formato de audio del modelo y de la aplicación.

La respuesta no tiene audio

Asegúrese de que la pista de Audio esté suscrita y de que el reproductor de audio se esté ejecutando; luego verifique que el decodificador de enlace descendente del SDK coincida con el formato de audio de salida del modelo.

Información relacionada

Para todos los parámetros, campos de eventos e interfaces para otras plataformas, consulte: