AIMA开放平台文档中心Agent SDK (Android)

超视距遥操(Teleop)指南 · Android

超视距遥操(Teleop)指南 · Android

⚠️ 尚未上线:本能力为 v1.5.0-SNAPSHOT 开发中特性,接口与协议可能调整,正式发布前请勿用于生产环境。

v1.5.0 新增。Teleop = 超视距遥操:远端操作员通过 SDK 在线接管机器人的语音交互。Android 侧的常见形态是「操作员 App」——通过 AgentSDK 把操作员语音上行给机器人,通常还会另行集成第三方 WebRTC 拉流方案拿到机器人现场画面。本页只讲 AgentSDK 的公开用法;视频拉流仅在文末给出与灵心开放平台相关的拉流授权接口。接口签名请参阅 Teleop API。

Android SDK 使用与 Java 完全一致的 com.agibot.aiem.sdk.teleop.* 类。API 语义参考 Java v1.5.0 · 超视距遥操指南,本页只补充 Android 独有差异:

  • Android SDK TeleopRequest.appendAudioDelta(...) 会在发送前把 base64 解码一次得到 audioLen 字段一并下发(服务端便于校验,业务侧无感);
  • TeleopRequest.enter/exit 内部会调用 AgentSdkTeleopMgr.trackEvent(eventId, this),即使 App 侧不显式跟踪也能被 SDK 的 dispatcher 找回对应回调;
  • Android 端的麦克风采集、PCM/Opus 编码、base64 分片均由业务侧实现,SDK 只接收 base64 字符串。

调用流程

严格顺序与 Java 一致:enter → 任意次 startAudio / appendAudioDelta / doneAudio → exit。 遥操存续期间可任意频次调用 keepalive 探活(推荐每 10 秒一次,见下文 机器人在线检测(keepalive))。

text
              enter                      exit
   ┌──────────────────────┐       ┌──────────────────────┐
   │  TeleopRequest.enter │       │  TeleopRequest.exit  │
   │      (onEnterAck)    │       │      (onExitAck)     │
   └──────────┬───────────┘       └──────────┬───────────┘
              │  任意次 (fire-and-forget)      │
              ▼                                │
   ┌──────────────────────┐                   │
   │  startAudio(param)    │                   │
   │  appendAudioDelta(*)  │───────────────────┘
   │  doneAudio            │
   └──────────────────────┘

   ┌──────────────────────────┐
   │ TeleopRequest.keepalive  │ ← 建议每 10 秒一次;code != 0 视为机器人离线
   │   (onKeepaliveAck)       │
   └──────────────────────────┘
  • enter / keepalive / exit 都有 ACK 回调;on*Ack 收到 code == 0 视为成功;
  • startAudio / appendAudioDelta / doneAudio 是 fire-and-forget,本地不排队等 ACK;
  • 若发送瞬间 SDK 连接已关闭,enter / keepalive / exit 回调会在原线程同步以 code == 1000 + msg == "Agentsdk connection is closed" 触发一次失败回执。

回调码定义同 Java:0 成功、-1 服务端拒绝、1000 SDK 侧连接已 closed。

最小 Android 集成

1. AndroidManifest.xml 权限

xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

RECORD_AUDIO 是危险权限,Activity 首次调用 startAudio 前必须 ActivityCompat.requestPermissions。

2. 创建 SDK 实例 & 注册遥操会话

java
AgentSdk agentSdk = AgentSdk.create(
        "wss://open.agibot.com/api/V1/open-portal/app/wss/agent-sdk",
        appId, appKey, appSecret);

agentSdk.registerAuth(new AgentAuthCallback() {
    @Override
    public void onAuthState(String appId, int code, String msg) {
        // code == 0 后才可以调用 enter
    }
});
agentSdk.initialize();

String teleopId = IdGenerator.generateTeleopId();
TeleopRequest teleopRequest = new TeleopRequest(agentSdk, agentId, teleopId);
agentSdk.registerTeleop(teleopRequest);

3. 进入遥操

java
AgentParam enterParam = AgentParam.create()
        .setString("role", "male")
        .setInteger("threshold", 0);
teleopRequest.enter(enterParam, new TeleopEnterCallback() {
    @Override
    public void onEnterAck(String teleopId, String eventId, int code, String msg) {
        // 主线程 UI 操作请切回 Handler,避免在 SDK 线程直接操作 View
    }
}, 2000);

4. 推麦克风音频

Android 侧建议用 AudioRecord(16kHz 单声道 s16le,20ms 一片)采集,base64 后 appendAudioDelta:

java
String eventId = teleopRequest.startAudio(startParam);
// 麦克风回调里:
byte[] pcm20ms = ... // 640 bytes @16kHz mono s16le
String base64 = Base64.encodeToString(pcm20ms, Base64.NO_WRAP);
teleopRequest.appendAudioDelta(eventId, base64);
// 停止说话时:
teleopRequest.doneAudio(eventId);

5. 退出

java
teleopRequest.exit(new TeleopExitCallback() {
    @Override
    public void onExitAck(String teleopId, String eventId, int code, String msg) {
        // 主线程 UI 提示 / 关闭 Activity
    }
}, 2000);
agentSdk.unregisterTeleop(teleopId);

机器人在线检测(keepalive)

遥操会话通常持续较长,期间机器人可能因网络中断 / 电量耗尽 / 现场断电等原因掉线。SDK 提供 TeleopRequest.keepalive(...) 一次性探活方法,业务侧应定时调用(建议每 10 秒一次)判断机器人是否仍在线。

接口与回调

java
public String keepalive(TeleopKeepaliveCallback keepaliveCallback, long timeout);

public abstract class TeleopKeepaliveCallback {
    public abstract void onKeepaliveAck(String teleopId, String eventId, int code, String msg);
}
  • timeout:本地占位超时(< 50 clamp 到 50,语义同 enter/exit);
  • 返回本次消息的 eventId;
  • 每次调用发送一条 agentsdk.teleop.keepalive,网关按 teleopId 转发到目标机器人;机器人在线则回 success == true。

code 含义(与 enter / exit 一致)

code含义
0机器人在线且响应正常
-1服务端返回失败,msg 携带 errorMsg——表示机器人已离线,业务侧必须处理该异常
1000SDK 端 WebSocket 已关闭,本次未真正下发

Android 定时探活(Handler + postDelayed)

Android 侧推荐用 Handler(Looper.getMainLooper()) + postDelayed 方式实现定时探活——对 Android 开发者更自然,且在 Activity onDestroy 中一行 removeCallbacks 即可停止。

注意:onKeepaliveAck 不在 UI 线程回调,更新界面需要切回主线程。

任何一次 code != 0,都应视为机器人已离线——立即停止定时任务、exit 退出遥操、unregisterTeleop 释放资源,避免后续 appendAudioDelta 继续下发无效音频。

java
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private final AtomicBoolean robotOnline = new AtomicBoolean(true);
private Runnable keepaliveTick;

@Override
protected void onCreate(Bundle savedInstanceState) {
    // ... enter 成功后启动定时
    keepaliveTick = new Runnable() {
        @Override
        public void run() {
            if (!robotOnline.get()) return;
            teleopRequest.keepalive(new TeleopKeepaliveCallback() {
                @Override
                public void onKeepaliveAck(String teleopId, String eventId, int code, String msg) {
                    if (code == 0) {
                        // 继续下一轮
                        mainHandler.postDelayed(keepaliveTick, 10_000);
                        return;
                    }
                    if (!robotOnline.compareAndSet(true, false)) return;
                    Log.w(TAG, "robot offline => code=" + code + ", msg=" + msg);
                    mainHandler.removeCallbacks(keepaliveTick);
                    // 掉线自动退出
                    teleopRequest.exit(new TeleopExitCallback() {
                        @Override
                        public void onExitAck(String teleopId, String eventId, int code, String msg) {
                            agentSdk.unregisterTeleop(teleopId);
                        }
                    }, 2000);
                }
            }, 2000);
        }
    };
    mainHandler.postDelayed(keepaliveTick, 10_000);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    mainHandler.removeCallbacks(keepaliveTick);
    // 保险起见也 exit 一下
}

调用 exit 后应主动 mainHandler.removeCallbacks(keepaliveTick) 停止发送探活消息。

视频拉流与拉流授权

遥操场景下操作员通常需要同时看到机器人现场画面。视频拉流不属于 AgentSDK 的能力:AgentSDK 只负责把操作员语音上行到机器人;现场视频由 App 自行集成第三方 WebRTC 拉流方案(如腾讯 TRRO 的 Android SDK 或其他标准 WebRTC 客户端)完成。两条链路在业务层通过同一台机器人关联。

本节只描述与灵心开放平台相关的拉流授权接口(下发 WebRTC 拉流所需的 password 与 authCode)。第三方 WebRTC SDK 自身的初始化、连接、渲染与音频播放请参阅其官方文档,不在本页范围。

拉流授权:authCode / password 获取接口

一次 WebRTC 拉流会话需要两个由灵心开放平台签发的凭证:

  • password:初始化 WebRTC 拉流 SDK 时使用;
  • authCode:建立拉流会话时作为会话权限令牌使用(如 TRRO 的 setSessionPermissionToken)。

接口

text
POST {authBaseUrl}/api/V1/interaction/app-credential/gw/webrtc/authCode
Header: Authorization: <登录后拿到的 bearer token>
Content-Type: application/json

authBaseUrl 按环境取值:

环境authBaseUrl
生产(PRD)https://open.agibot.com
准生产(UAT)https://open.uat.agibot.com

入参(请求体)

json
{
  "sn": "<机器人硬件序列号>",
  "pullDid": 123456789
}
字段类型说明
snstring目标机器人的硬件序列号。与 agentId 是两个概念:agentId 是灵心平台颁发的智能体标识(遥操寻址用),sn 是硬件序列号(WebRTC 网关寻址用)。
pullDidnumber(int64)拉流方(操作员设备)ID,需在进程内稳定;由 App 自行生成。

出参(响应体)

json
{
  "code": 0,
  "message": "ok",
  "data": {
    "password": "<拉流密码>",
    "authCode": "<会话授权码>"
  }
}
字段说明
code0 为成功,非 0 为失败(原因见 message)
data.password传给 WebRTC 拉流 SDK 初始化
data.authCode建立拉流会话时作为权限令牌下发

拿到 password / authCode 后,按第三方 WebRTC SDK 的文档完成初始化与连接即可。

与遥操会话的协同建议

  • 建议在 enter 拿到 onEnterAck(code == 0) 之后再发起拉流授权与拉流连接,避免对着未就绪的画面开麦;
  • 退出时建议先断开 / 销毁视频拉流,再 exit 遥操,避免画面残留;
  • 回声消除(AEC)由 App 自行处理:当设备同时播放机器人下行音、又采集操作员上行音时会产生回声。AgentSDK 不介入任何音频处理,仅接收 base64 PCM;是否做 AEC / 降噪、如何做,完全由 App 决定。

常见问题

  1. onEnterAck 收到 code == 1000:底层 WebSocket 未建立或已断开。等 AgentAuthCallback.onAuthState(code == 0) 之后再 enter;断连后 SDK 会自动重连,重连成功再次 enter 即可。
  2. enter 之后能否立即 startAudio:可以。若同时集成了视频拉流,更稳的做法是等画面出图后再开麦,避免操作员对着黑屏说话。
  3. 每片 audio delta 建议多长:20 ms ~ 40 ms 的 PCM/Opus 编码为 base64 后 appendAudioDelta;太大易堵塞 WS 单帧,太小则消息数过多。
  4. onKeepaliveAck 在哪个线程回调:非 UI 线程。更新界面请 runOnUiThread 或 Handler.post。