Este tutorial utiliza Android para mostrar cómo integrar el SDK del cliente AOQ con qwen3.8-livetranslate-flash-realtime para la traducción de voz en tiempo real. Otras plataformas utilizan flujos de integración y manejo de eventos del modelo similares con sus métodos correspondientes del SDK. Todos los ejemplos del lado cliente que se muestran a continuación usan Java.
NotaPara conocer las capacidades del modelo, consulte Traducción de voz y de audio y video en tiempo real. Para conocer las funciones de los medios y los eventos del modelo, consulte Conexión mediante AOQ.
Descargar el SDK
Para obtener el SDK del cliente AOQ y el complemento de audio Opus, consulte Descarga del SDK. Los ejemplos de transmisión de audio en este tutorial utilizan Opus, por lo que también debe importar el complemento para su plataforma. Para conocer las regiones compatibles, idiomas, precios y límites de tasa, consulte Traducción de voz y audio/video en tiempo real.
Importar el SDK
Android
Coloque AoqClientSdk-release.aar en app/libs/. Coloque libPluginOpus.so en app/libs/armeabi-v7a/ y app/libs/arm64-v8a/, según el ABI.
Declare la dependencia en app/build.gradle:
android {
defaultConfig {
minSdk 21
ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
}
sourceSets { main { jniLibs.srcDirs = ['libs'] } }
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
}
Si el proyecto principal ya contiene bibliotecas nativas con los mismos nombres, verifique sus versiones y dependencias antes de empaquetarlas para evitar incluir diferentes versiones de la misma biblioteca.
Declare los 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" />
Solicite el permiso RECORD_AUDIO en tiempo de ejecución y obtenga la autorización del usuario. La traducción únicamente de voz no requiere permiso de cámara. Declare y solicite CAMERA solo cuando habilite la mejora visual.
Los ejemplos en Java utilizan las siguientes importaciones:
import com.alibaba.aoq.clientsdk.AoqClientEngine;
import com.alibaba.aoq.clientsdk.AoqClientEngine.*;
import com.alibaba.aoq.clientsdk.AoqClientListener;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
iOS
Arrastre AoqClientSdk.framework y PluginOpus.framework a su proyecto Xcode. En Target > General > Frameworks, Libraries, and Embedded Content, seleccione Embed & Sign.
Declare la descripción del uso del micrófono en Info.plist:
Clave | Valor de ejemplo |
|---|---|
| Para capturar voz para traducción en tiempo real |
Obtenga el permiso del micrófono antes de iniciar la captura. Agregue NSCameraUsageDescription y solicite el permiso de cámara solo cuando habilite la mejora visual.
Acceda al SDK a través del módulo AoqClientSdk. Utilice import AoqClientSdk en Swift o #import <AoqClientSdk/AoqClientSdk.h> en Objective-C. Siga el mismo flujo de configuración de sesión y manejo de eventos que los ejemplos de Android, utilizando los métodos correspondientes del SDK de iOS.
HarmonyOS
Coloque AoqClientSdk.har en la carpeta libs/ del directorio raíz del proyecto. Agregue la siguiente entrada a dependencies en entry/oh-package.json5:
{
"dependencies": {
"@aoq/client-sdk": "file:../libs/AoqClientSdk.har"
}
}
Coloque libPluginOpus.so en entry/libs/armeabi-v7a/ y entry/libs/arm64-v8a/, según el ABI.
Agregue los siguientes permisos al objeto module en entry/src/main/module.json5. Defina perm_mic_reason en los recursos de cadena de la aplicación, por ejemplo, "Para capturar voz para traducción en tiempo real":
{
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" },
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:perm_mic_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
]
}
Fusione estos campos en los objetos de dependencia y módulo existentes. No reemplace archivos de configuración completos con estos fragmentos. Si su aplicación utiliza un nombre de Ability diferente, reemplace EntryAbility con ese nombre.
Antes de iniciar la captura, utilice abilityAccessCtrl.createAtManager().requestPermissionsFromUser para solicitar el permiso del micrófono en tiempo de ejecución. Agregue el permiso de cámara solo cuando habilite la mejora visual.
Obtener un token a través de su AppServer
No almacene la clave API de Model Studio en el cliente. Su AppServer utiliza la clave API para la región y área de trabajo correspondiente para solicitar credenciales temporales para cada conexión de traducción. Devuelve las credenciales mediante HTTPS a un cliente que haya pasado la autenticación de su aplicación.
Configure un AppServer según los Pasos de autenticación de AOQ para Qwen-LiveTranslate-Realtime. La traducción en tiempo real usa el protocolo Realtime. Especifique lo siguiente al solicitar las credenciales:
model=qwen3.8-livetranslate-flash-realtime
Ejecute el siguiente comando únicamente en su AppServer. Establezca BAILIAN_ENDPOINT como el punto final para su área de trabajo: {WorkspaceId}.cn-beijing.maas.aliyuncs.com o {WorkspaceId}.ap-southeast-1.maas.aliyuncs.com. Lea DASHSCOPE_API_KEY desde una variable de entorno del lado servidor o desde un servicio de gestión de secretos.
curl --fail-with-body --request POST \
"https://${BAILIAN_ENDPOINT}/api/v1/webrtc/realtime?model=qwen3.8-livetranslate-flash-realtime" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${DASHSCOPE_API_KEY}" \
--header "x-dashscope-rtc-transport: moq" \
--data '{}'
Si su AppServer puede obtener la dirección IP pública real del cliente desde una cadena de proxy de confianza, incluya clientIp en el cuerpo de la solicitud para la programación del punto de acceso.
Asigne los campos de respuesta a AoqConnectConfig en el cliente:
Campo de respuesta de la puerta de enlace | Campo del SDK | Descripción |
|---|---|---|
|
| Token temporal para esta conexión |
|
| Identificador de esta sesión |
|
| Utilice la huella digital del certificado exactamente como se devuelve |
|
| Convierta la lista completa de puntos de acceso a la estructura del SDK |
|
| Utilice el valor devuelto. Si está ausente, siga los requisitos del SDK |
Implementar la traducción de voz en tiempo real
Flujo de interacción
Crear el motor y establecer devoluciones de llamada
Llame a createEngine para crear el motor y registrar devoluciones de llamada para el estado de conexión, mensajes de datos y errores. Su aplicación debe mantener los objetos del motor y del oyente durante toda la sesión.
// 配置引擎工作目录。
AoqCreateConfig createConfig = new AoqCreateConfig();
createConfig.workDir = appCtx.getFilesDir().getAbsolutePath();
// 创建引擎并注册事件监听对象。
engine = AoqClientEngine.createEngine(appCtx, createConfig, listener);
Implemente AoqClientListener para recibir eventos de conexión, mensajes del modelo y dispositivos multimedia, y páselo al crear el motor.
AoqClientListener listener = new AoqClientListener() {
// 接收音频采集与播放设备状态。
@Override
public void onAudioDeviceStateChanged(AoqAudioDeviceState state) {
}
// 接收耳机、蓝牙及扬声器等音频路由变化。
@Override
public void onAudioDeviceRouteChanged(int routeType) {
android.util.Log.i("LiveTranslate", "音频路由: " + AoqAudioDeviceRouteType.fromValue(routeType));
}
// 接收音频设备中断及中断结束通知。
@Override
public void onAudioDeviceInterrupted(boolean interrupt) {
android.util.Log.i("LiveTranslate", "音频设备中断: " + interrupt);
}
// 接收 Android 音频焦点变化。
@Override
public void onAudioDeviceFocusChanged(int audioFocus) {
android.util.Log.i("LiveTranslate", "音频焦点: " + audioFocus);
}
// 接收 SDK 异步错误。
@Override
public void onError(int code, String message) {
android.util.Log.e("LiveTranslate", "SDK 错误: " + code + ", " + message);
}
};
Iniciar la captura y reproducción de audio
Después de obtener el permiso del micrófono, prepare la captura y reproducción de audio. Desactive la transmisión de audio antes de iniciar la captura para evitar que el motor envíe medios automáticamente después de conectarse.
Iniciar la captura no significa que el modelo esté listo para recibir audio. Solicite al usuario que hable únicamente después de recibir session.updated y habilitar correctamente la transmisión. Este tutorial no implementa el almacenamiento en búfer de audio antes de la conexión.
// 关闭音频上行,等待模型配置确认。
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeAudio, false);
AoqAudioCaptureConfig audioCaptureConfig = new AoqAudioCaptureConfig();
audioCaptureConfig.channel = 1;
audioCaptureConfig.isExternal = false;
// 启动 SDK 内置麦克风采集。
engine.startAudioCapture(audioCaptureConfig);
AoqAudioPlaybackConfig audioPlaybackConfig = new AoqAudioPlaybackConfig();
audioPlaybackConfig.channel = 1;
audioPlaybackConfig.isExternal = false;
// 启动 SDK 内置播放器,播放下行译音。
engine.startAudioPlayer(audioPlaybackConfig);
Configurar códecs y establecer la conexión
El escenario principal requiere las siguientes pistas:
Dirección | Pista | Propósito |
|---|---|---|
Publicar | Audio | Enviar voz al modelo |
Publicar | Datos | Enviar configuración de sesión y eventos de finalización |
Suscribirse | Audio | Recibir voz traducida |
Suscribirse | Datos | Recibir texto original, texto traducido y eventos de estado |
Configure el audio de subida y bajada por separado. El siguiente ejemplo utiliza Opus, 16 kHz y audio mono para la transmisión.
AoqAudioCodecConfig audioEncoderConfig = new AoqAudioCodecConfig();
audioEncoderConfig.codecType = AoqEncoderType.AoqEncoderTypeAudioOpus;
audioEncoderConfig.sampleRate = 16000;
audioEncoderConfig.channel = 1;
// 设置上行音频编码参数。
engine.setAudioEncoderConfig(audioEncoderConfig);
AoqAudioCodecConfig audioDecoderConfig = new AoqAudioCodecConfig();
audioDecoderConfig.codecType = AoqEncoderType.AoqEncoderTypeAudioOpus;
audioDecoderConfig.sampleRate = 16000;
audioDecoderConfig.channel = 1;
// 设置下行音频解码参数。
engine.setAudioDecoderConfig(audioDecoderConfig);
AoqTrackParam audioTrackParam = new AoqTrackParam();
audioTrackParam.trackType = AoqTrackType.AoqTrackTypeAudio;
AoqTrackParam dataTrackParam = new AoqTrackParam();
dataTrackParam.trackType = AoqTrackType.AoqTrackTypeData;
// 发布音频和 Data 轨,发送语音及模型事件。
connectConfig.publishTracks.add(audioTrackParam);
connectConfig.publishTracks.add(dataTrackParam);
// 订阅音频和 Data 轨,接收译音及字幕事件。
connectConfig.subscribeTracks.add(audioTrackParam);
connectConfig.subscribeTracks.add(dataTrackParam);
// 发起 AOQ 连接,连接结果通过 onConnectionStatusChange 返回。
engine.connect(connectConfig);
Un retorno exitoso de connect significa que la llamada fue aceptada. Espere a que onConnectionStatusChange informe una conexión exitosa antes de enviar la configuración del modelo.
Configurar la sesión de traducción
Después de conectarse, envíe el siguiente evento session.update a través de la pista de Datos. Esta configuración utiliza la voz predeterminada y no habilita la clonación de voz:
{
"type": "session.update",
"session": {
"output_modalities": ["text", "audio"],
"audio": {
"input": {
"turn_detection": {
"type": "speaker_detection",
"threshold": 0.5
}
}
},
"translation": {
"language": "en"
}
}
}
Para el flujo de interacción, consulte Traducción en tiempo real. Para todos los parámetros de session.update, consulte Eventos del cliente de traducción en tiempo real.
Codifique el JSON como bytes UTF-8 y envíelo con sendDataMsg.
private static final String SESSION_UPDATE_JSON =
"{\"type\":\"session.update\",\"session\":{"
+ "\"output_modalities\":[\"text\",\"audio\"],"
+ "\"audio\":{\"input\":{\"turn_detection\":{"
+ "\"type\":\"speaker_detection\",\"threshold\":0.5}}},"
+ "\"translation\":{\"language\":\"en\"}}}";
// 接收 AOQ 连接状态变化。
@Override
public void onConnectionStatusChange(AoqConnectionStatus status) {
if (status == AoqConnectionStatus.AoqConnectionStatusConnected) {
AoqDataMsg message = new AoqDataMsg();
message.data = SESSION_UPDATE_JSON.getBytes(StandardCharsets.UTF_8);
// 通过 Data 轨发送翻译会话配置。
engine.sendDataMsg(message);
}
}
Habilitar la transmisión de medios después de recibir session.updated
Analice el tipo de evento en onDataMsg.
El siguiente fragmento enruta eventos de control. Para eventos de texto original y texto traducido, consulte Eventos del servidor. Después de recibir session.finished, establezca el estado de finalización de su aplicación y use el método de liberación descrito en "Desconectar y destruir el motor" para finalizar la limpieza.
// 接收 Data 轨中的模型事件。
@Override
public void onDataMsg(AoqDataMsg message) {
if (message == null || message.data == null) { return; }
try {
JSONObject event = new JSONObject(new String(message.data, StandardCharsets.UTF_8));
String type = event.optString("type");
if ("session.updated".equals(type)) {
// 模型配置生效后,开启音频上行。
engine.enableSendMediaStream(AoqTrackType.AoqTrackTypeAudio, true);
} else if ("session.finished".equals(type)) {
android.util.Log.i("LiveTranslate", "服务端已完成本次翻译处理");
} else if ("error".equals(type)) {
android.util.Log.e("LiveTranslate", "翻译模型错误: " + event.optJSONObject("error"));
}
} catch (JSONException error) {
android.util.Log.e("LiveTranslate", "无法解析模型事件", error);
}
}
NotaDiferencias entre AOQ y WebSocket: este ejemplo envía y recibe audio directamente mediante las pistas Audio. No envíe input_audio_buffer.append para volver a cargar el mismo audio, ni copie el ejemplo de WebSocket e inicie otro reproductor al recibir response.audio.delta. El audio traducido se maneja mediante la suscripción de audio y el reproductor del SDK. La pista Data transporta subtítulos y eventos del modelo. Para conocer el uso de las pistas de medios y Data, consulte Conexión mediante AOQ.
Finalizar la entrada y esperar la traducción final
Cuando el usuario detenga la traducción, siga este orden:
- Detenga la captura para que no se envíe nueva entrada.
- Después de finalizar la entrada, deshabilite el audio de subida y envíe
session.finishuna vez. Mantenga activa la conexión AOQ, la suscripción de Datos y la reproducción de audio. - Continúe recibiendo el texto original final, el texto traducido y la voz traducida. Espere a
session.finished. - Desconecte y libere recursos según su política local de finalización de reproducción.
Para los requisitos de envío de session.finish, consulte Eventos del cliente de traducción en tiempo real. Para los campos y significado de session.finished, consulte Eventos del servidor de traducción en tiempo real.
AoqDataMsg message = new AoqDataMsg();
message.data = "{\"type\":\"session.finish\"}".getBytes(StandardCharsets.UTF_8);
// 通知模型输入结束,等待 session.finished。
engine.sendDataMsg(message);
Desconectar y destruir el motor
Después de recibir session.finished, el servidor ha finalizado el procesamiento de esta sesión. Esto no significa que el altavoz local haya terminado de reproducir el último segmento de audio. Si su aplicación debe reproducir todo el discurso traducido, complete la reproducción local antes de ejecutar los siguientes métodos.
// 停止译音播放。
engine.stopAudioPlayer();
// 断开 AOQ 连接。
engine.disconnect();
// 销毁本会话独占的引擎。
AoqClientEngine.destroy();