このチュートリアルでは、Androidを用いてAOQ Client SDKとqwen3.8-livetranslate-flash-realtimeを統合し、リアルタイム音声翻訳を実現する方法を示します。他のプラットフォームでも、対応するSDKメソッドを用いた類似した統合およびモデルイベント処理フローが適用されます。以下に示すすべてのクライアントサイドの例はJavaで記述されています。
注記モデルの機能は、リアルタイム音声・音声動画翻訳をご覧ください。メディアとモデルイベントの役割分担は、AOQ アクセスをご覧ください。
SDKのダウンロード
AOQ Client SDKおよびOpus音声プラグインについては、SDKダウンロードをご参照ください。このチュートリアルの音声送信の例ではOpusを使用するため、プラットフォームに対応するプラグインも必ずインポートしてください。対応地域、対応言語、料金、レート制限については、リアルタイム音声および音声/動画翻訳をご参照ください。
SDKのインポート
Android
AoqClientSdk-release.aarをapp/libs/に配置します。libPluginOpus.soはABIに応じてapp/libs/armeabi-v7a/およびapp/libs/arm64-v8a/に配置します。
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'])
}
ホストプロジェクトにすでに同名のネイティブライブラリが含まれている場合、パッケージ化前にそのバージョンおよび依存関係を確認し、同一ライブラリの異なるバージョンが重複して含まれるのを防いでください。
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" />
ランタイムでRECORD_AUDIO権限を要求し、ユーザーの承認を得てください。音声のみの翻訳にはカメラ権限は不要です。視覚的強化を有効化する場合にのみ、CAMERAを宣言および要求してください。
Javaの例では、以下のインポートを使用します:
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
AoqClientSdk.frameworkおよびPluginOpus.frameworkをXcodeプロジェクトにドラッグ&ドロップします。Target > General > Frameworks, Libraries, and Embedded Contentで、Embed & Signを選択します。
Info.plistでマイク使用説明を宣言します:
キー | 例値 |
|---|---|
| リアルタイム翻訳のために音声をキャプチャするには |
キャプチャ開始前にマイク権限を取得してください。視覚的強化を有効化する場合にのみ、NSCameraUsageDescriptionを追加し、カメラ権限を要求してください。
AoqClientSdkモジュールを通じてSDKにアクセスします。Swiftではimport AoqClientSdkを、Objective-Cでは#import <AoqClientSdk/AoqClientSdk.h>を使用します。Androidの例と同じセッション設定およびイベントハンドリングフローに従い、対応するiOS SDKメソッドを使用してください。
HarmonyOS
AoqClientSdk.harをプロジェクトルートディレクトリのlibs/フォルダに配置します。entry/oh-package.json5のdependenciesに以下のエントリを追加します:
{
"dependencies": {
"@aoq/client-sdk": "file:../libs/AoqClientSdk.har"
}
}
libPluginOpus.soをABIに応じてentry/libs/armeabi-v7a/およびentry/libs/arm64-v8a/に配置します。
moduleオブジェクト(entry/src/main/module.json5内)に以下の権限を追加します。perm_mic_reasonはアプリケーションの文字列リソースで定義してください(例:「リアルタイム翻訳のために音声をキャプチャする」):
{
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" },
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:perm_mic_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
]
}
これらのフィールドを既存の依存関係およびモジュールオブジェクトにマージしてください。これらのスニペットで設定ファイル全体を置き換えないでください。アプリケーションで異なるAbility名を使用している場合は、EntryAbilityをその名前に置き換えてください。
キャプチャ開始前に、abilityAccessCtrl.createAtManager().requestPermissionsFromUserを使用してランタイムでマイク権限を要求してください。視覚的強化を有効化する場合にのみ、カメラ権限を追加してください。
AppServer経由でのトークン取得
Model StudioのAPIキーをクライアント側に保存しないでください。AppServerは、対応するリージョンおよびワークスペースのAPIキーを用いて、各翻訳接続ごとに一時的な資格情報を要求します。AppServerは、アプリケーションの認証を通過したクライアントに対してHTTPS経由で資格情報を返します。
Qwen-LiveTranslate-Realtime の AOQ 認証手順に従って AppServer を構築します。リアルタイム翻訳は Realtime プロトコルを使用します。認証情報の要求時には、次を指定してください。
model=qwen3.8-livetranslate-flash-realtime
以下のコマンドはAppServer上でのみ実行してください。BAILIAN_ENDPOINTをワークスペースのエンドポイント({WorkspaceId}.cn-beijing.maas.aliyuncs.comまたは{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com)に設定してください。DASHSCOPE_API_KEYはサーバーサイドの環境変数またはシークレット管理サービスから読み込んでください。
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 '{}'
AppServerが信頼されたプロキシチェーンからクライアントの実際のパブリックIPアドレスを取得できる場合、アクセスポイントのスケジューリングのためにリクエストボディにclientIpを含めてください。
レスポンスフィールドをクライアント側のAoqConnectConfigにマッピングします:
ゲートウェイレスポンスフィールド | SDKフィールド | 説明 |
|---|---|---|
|
| この接続のための一時トークン |
|
| このセッションの識別子 |
|
| 返されたままの証明書フィンガープリントを正確に使用してください |
|
| 完全なアクセスポイントリストをSDK構造に変換してください |
|
| 返された値を使用してください。値が存在しない場合は、SDKの要件に従ってください |
リアルタイム音声翻訳の実装
インタラクションフロー
エンジンの作成およびコールバックの設定
createEngineを呼び出してエンジンを作成し、接続ステータス、Dataメッセージ、およびエラーのコールバックを登録します。アプリケーションは、セッション中を通してエンジンおよびリスナー・オブジェクトを保持する必要があります。
// 配置引擎工作目录。
AoqCreateConfig createConfig = new AoqCreateConfig();
createConfig.workDir = appCtx.getFilesDir().getAbsolutePath();
// 创建引擎并注册事件监听对象。
engine = AoqClientEngine.createEngine(appCtx, createConfig, listener);
接続、モデルメッセージ、およびメディアデバイスイベントを受信するためのAoqClientListenerを実装し、エンジン作成時にこれを渡します。
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);
}
};
音声キャプチャおよび再生の開始
マイク権限を取得した後、音声キャプチャおよび再生を準備します。キャプチャ開始前に音声送信を無効化してください。これにより、接続後にエンジンが自動的にメディアを送信するのを防ぎます。
キャプチャを開始しても、モデルが音声を受信できる状態になるわけではありません。session.updatedを受信し、送信を正常に有効化した後にのみ、ユーザーに話すよう促してください。このチュートリアルでは、接続前の音声バッファリングは実装していません。
// 关闭音频上行,等待模型配置确认。
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);
コーデックの設定および接続の確立
主なシナリオでは、以下のトラックが必要です:
方向 | トラック | 目的 |
|---|---|---|
Publish | Audio | モデルへ音声を送信 |
Publish | Data | セッション設定および完了イベントを送信 |
Subscribe | Audio | 翻訳された音声を受信 |
Subscribe | Data | 原文テキスト、翻訳テキスト、およびステータスイベントを受信 |
アップストリームおよびダウンストリームの音声を別々に設定します。以下の例では、送信にOpus、16 kHz、モノラル音声を使用しています。
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);
connectからの成功した戻り値は、呼び出しが受理されたことを意味します。モデル設定を送信する前に、onConnectionStatusChangeが成功した接続を報告するのを待ってください。
翻訳セッションの設定
接続後に、Dataトラックを介して以下のsession.updateイベントを送信します。この設定ではデフォルトの音声を使用し、ボイスクローンは有効化しません:
{
"type": "session.update",
"session": {
"output_modalities": ["text", "audio"],
"audio": {
"input": {
"turn_detection": {
"type": "speaker_detection",
"threshold": 0.5
}
}
},
"translation": {
"language": "en"
}
}
}
インタラクションフローについては、リアルタイム翻訳をご参照ください。すべてのsession.updateパラメーターについては、リアルタイム翻訳クライアントイベントをご参照ください。
JSONをUTF-8バイトとしてエンコードし、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);
}
}
session.updatedを受信後のメディア送信の有効化
onDataMsgでイベントタイプを解析します。
以下のスニペットは制御イベントをルーティングします。サーバーイベントをご参照ください。session.finishedを受信した後、アプリケーションの完了状態を設定し、「切断およびエンジンの破棄」で説明されている解放メソッドを使用してクリーンアップを完了します。
// 接收 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);
}
}
注記AOQ と WebSocket の違い:このサンプルでは Audio トラックで音声を直接送受信します。同じ音声を重複してアップロードする input_audio_buffer.append の送信や、WebSocket のサンプルをそのまま使用し、response.audio.delta の受信時に別のプレイヤーで再生することは避けてください。翻訳音声は SDK の音声サブスクリプションとプレイヤーが処理します。Data トラックは字幕とモデルイベントに使用します。メディアと Data トラックの使い方は、AOQ アクセスをご覧ください。
入力の終了および最終翻訳の待機
ユーザーが翻訳を停止した場合、以下の順序で処理してください:
- 新しい入力が送信されないように、キャプチャを停止します。
- 入力を確定した後、アップストリーム音声を無効化し、
session.finishを1回送信します。AOQ接続、Dataサブスクリプション、および音声再生は有効なままにしてください。 - 最終的な原文テキスト、翻訳テキスト、および翻訳音声の受信を継続します。
session.finishedを待ってください。 - ローカル再生の完了ポリシーに従って、切断およびリソースの解放を行います。
session.finishの送信要件については、リアルタイム翻訳クライアントイベントをご参照ください。session.finishedのフィールドおよび意味については、リアルタイム翻訳サーバーイベントをご参照ください。
AoqDataMsg message = new AoqDataMsg();
message.data = "{\"type\":\"session.finish\"}".getBytes(StandardCharsets.UTF_8);
// 通知模型输入结束,等待 session.finished。
engine.sendDataMsg(message);
切断およびエンジンの破棄
session.finishedを受信した後、サーバーはこのセッションの処理を終了しました。これは、ローカルスピーカーが最後の音声セグメントの再生を終了したことを意味するものではありません。 アプリケーションがすべての翻訳音声を再生する必要がある場合、以下のメソッドを実行する前にローカル再生を完了させてください。
// 停止译音播放。
engine.stopAudioPlayer();
// 断开 AOQ 连接。
engine.disconnect();
// 销毁本会话独占的引擎。
AoqClientEngine.destroy();