> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coze.cn/llms.txt
> Use this file to discover all available pages before exploring further.

本文档介绍如何集成扣子编程 Realtime Android SDK，将你在扣子编程中搭建的 AI 智能体集成到你的 Android 应用中，实现用户与智能体进行音视频通话。

扣子编程 Realtime Android SDK 封装了火山引擎 Android RTC 音视频链路相关 API，接入流程简洁高效。

## 准备工作 {#86537df4}

开始集成 Realtime SDK 前，请确保开发环境满足以下要求：

<!-- @cols-width: 158,658 -->
|**操作** |**说明** |
|---|---|
|发布智能体 |已成功搭建并发布智能体为 API 服务。搭建步骤可参考[搭建可视化智能体](/tutorial/video_bot)或[搭建低延时语音助手](/tutorial/low_latency_voice_assistant)，发布步骤请参见[发布为 API 服务](/guides/publish_agent_api)。 |\
| | |\
| |:::tip 说明 |\
| |若需使用视频理解能力，请为智能体配置一个视觉模型，例如**豆包视觉理解**模型。 |\
| |::: |\
| | |\
| | |
|获取访问密钥 |获取访问密钥，用于身份认证与鉴权。 |\
| | |\
| |* **体验或调试场景**：建议生成短期的个人访问令牌（PAT），以快速完成 Realtime SDK 的整体流程。个人访问令牌的获取方法请参见[添加个人访问令牌](/developer_guides/pat)。 |\
| |* **线上环境**：在线上环境中，应使用服务访问令牌（SAT）或 OAuth 鉴权方案，各鉴权方式的详细说明请参考[鉴权方式概述](/developer_guides/authentication)。 |\
| | |\
| |:::tip 说明 |\
| |扣子编程 SDK 封装了多种鉴权方式，能够有效简化鉴权流程，你可以参考[鉴权示例代码](https://github.com/coze-dev/coze-js/tree/main/examples/coze-js-node/src/auth)实现不同方式的 OAuth 认证，以获取和管理访问扣子编程 API 所需的令牌。 |\
| |::: |\
| | |\
| | |
|准备开发环境 |* macOS 开发电脑，且可以正常访问互联网 |\
| |* Android Studio |\
| |* Kotlin 1.9.22 及以上 |\
| |* Android 7.0 或以上版本真机设备，且可以正常访问互联网 |\
| |* Gradle 8.0及以上 |

## 跑通示例项目源码 {#a160234a}

### 示例项目目录结构 {#daf5d0b2}

[音视频通话 Android 示例项目](https://github.com/coze-dev/coze-android/)的目录结构如下：

```JavaScript [hAGqt4pyWT]
app/

├── build.gradle.kts

├── .gitignore

└── src/

    └── main/

        ├── java/

        │   └── com/

        │       └── coze/

        │           └── java_example/

        │               ├── MainActivity.java

        │               ├── config/

        │               │   └── Config.java

        │               ├── manager/

        │               │   └── CozeAPIManager.java

        │               └── utils/

        │                   └── ToastUtil.java

        ├── res/

        │   ├── layout/

        │   │   └── activity_main.xml

        │   ├── values/

        │   │   ├── colors.xml

        │   │   ├── strings.xml

        │   │   └── themes.xml

        │   ├── ... 其他目录

        └── AndroidManifest.xml
```
```Kotlin [hAGqt4pyWT]
kotlin-example/

├── build.gradle.kts

├── .gitignore

└── src/

    └── main/

        ├── java/

        │   └── com/

        │       └── coze/

        │           └── kotlin_example/

        │               ├── MainActivity.kt

        │               ├── config/

        │               │   └── Config.kt

        │               ├── manager/

        │               │   └── CozeAPIManager.kt

        │               └── utils/

        │                   └── ToastUtil.kt

        ├── res/

        │   ├── layout/

        │   │   └── activity_main.xml

        │   ├── values/

        │   │   ├── colors.xml

        │   │   ├── strings.xml

        │   │   └── themes.xml

        │   ├── ... 其他文件

        └── AndroidManifest.xml
```

### 配置并跑通示例项目 {#baa4d18a}

1. 克隆[音视频通话 Android 示例项目](https://github.com/coze-dev/coze-android/)到本地。
   ```Swift
   git clone https://github.com/coze-dev/coze-android
   ```
2. 在配置文件中设置访问令牌、智能体 ID 和音色。
   编辑 `main/java-example/src/main/res/values/strings.xml` 配置文件，配置 `coze_access_token` 和 `bot_id`和音色 ID。
   你可以调用[查看音色列表](/developer_guides/list_voices) API 查看当前可用的音色列表，也可以使用[系统音色列表](/dev_how_to_guides/sys_voice)。
   ```XML
    <resources>
       <!-- 其他配置 -->
       
       <!-- 替换为从 https://www.coze.cn/open/oauth/pats 获取到的token-->
       <string name="coze_access_token">pat_PLraJCVarqLuoE1********</string>
       <string name="base_url">https://api.coze.cn</string>
       <!-- 替换为自己的bot -->
       <string name="bot_id">74********</string>
       <!-- 音色ID，可选 -->
       <string name="voice_id">742********09</string>
   </resources>
   ```
3. 编译并运行示例项目。
   1. 开启 Android 设备的开发者选项，打开 USB 调试，通过 USB 连接线将 Android 设备接入电脑，并在 Android 设备选项中勾选你的 Android 设备。详情参看[在硬件设备上运行应用](https://developer.android.com/studio/run/device?hl=zh-cn)。
   2. 单击 Android Studio 窗口右上角的 **Sync Project with Gradle Files**（或使用 **Shift ⇧** + **Command ⌘** + **O** 快捷键）同步项目，拉取项目依赖。
   3. 单击 Android Studio 窗口右上角的 **Run ‘app’** （或使用 **Control ⌃** + **R** 快捷键）开始编译。
   4. 编译成功后，你的 Android 设备上会出现 Coze Android RTC Demo，在弹窗中选择开启摄像头和麦克风权限。应用界面如下：

::::cols
@col 50
<div style="text-align: center"><img src="https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/1f2f4bab37e34a388245de23b0386100~tplv-goo7wpa0wc-image.image" width="326px" height="724px" /></div>

@col 50
<Player class="topic-video-player"  class="topic-video-player"  src="https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/b1a9046205d148c5b9f849a324de54a5~tplv-goo7wpa0wc-image.image"></Player>
::::

## 步骤一：（可选）创建并配置 Android 项目 {#f2a81c33}

本步骤为如何创建一个新项目，如集成到已有项目，请直接查看步骤二。

1. 打开 Android Studio。在 **Welcome to Android Studio** 窗口中，单击 **New Project**。
2. 在项目模板页选择 **Phone and Tablet**，模板使用 **No Activity**。
3. 配置项目信息，完成后单击 **Finish**。
   在项目配置页，设置项目名称、软件包名称、存储路径等信息，开发语言选择 **Kotlin** 或 **Java**，**Minimum SDK** 选择 **API 24**。
   ![Image=501x378](https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/f35815ec1d2d42109fac80f717918681~tplv-goo7wpa0wc-image.image)
4. 配置权限。
   SDK 已在内部声明所需权限，无需手动添加。   


## 步骤二：集成 Realtime SDK {#d7f0d9b8}

本文以通过 Gradle 直接集成为例。如需手动集成，请参考[火山引擎 RTC 文档。](https://www.volcengine.com/docs/6348/70134)

1. 添加 Maven 仓库。
   在项目的根目录下找到 `settings.gradle.kts` 文件，并添加以下内容以配置 Maven 仓库：
   ```Kotlin
   dependencyResolutionManagement {
       repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
       repositories {
           google()
           mavenCentral()
           maven { url = uri("https://artifact.bytedance.com/repository/Volcengine/") }
           maven { url = uri("https://repo.maven.apache.org/maven2/") }
       }
   }
   ```
2. 配置依赖版本。
   打开项目根目录下的 `gradle/libs.versions.toml` 文件，配置火山 RTC SDK 和扣子编程 SDK 的版本信息。建议使用 VSCode 或其他文本编辑器打开该文件。
   ::::tabs
   @tab Java
   ```TOML
   [versions]
   # 你的其他配置
   volcenginertc = "3.58.1.19400"  # 替换为实际版本号
   cozeApi = "0.2.1"  # Coze SDK 版本号
   
   [libraries]
   # 你的其他配置
   volcenginertc = { module = "com.volcengine:VolcEngineRTC", version.ref = "volcenginertc" }
   coze-api = { module = "com.coze:coze-api", version.ref = "cozeApi" }
   ```
   
   
   
   @tab Kotlin
   ```TOML
   [versions]
   # 你的其他配置
   volcenginertc = "3.58.1.19400"  # 替换为实际版本号
   cozeApi = "0.2.1"  # Coze SDK 版本号
   
   [libraries]
   # 你的其他配置
   volcenginertc = { module = "com.volcengine:VolcEngineRTC", version.ref = "volcenginertc" }
   coze-api = { module = "com.coze:coze-api", version.ref = "cozeApi" }
   ```
   
   
   ::::
   :::tip 说明
   * 将 `volcenginertc` 中的版本号替换为 RTC 实际使用的版本号。具体版本信息可在[下载 RTC SDK](https://www.volcengine.com/docs/6348/75707#%E4%B8%8B%E8%BD%BD-sdk)页面查看。
   * 将 `cozeApi` 中的版本号替换为扣子编程 SDK 的最新版本，具体版本信息可在[扣子编程 releases](https://github.com/coze-dev/coze-java/releases) 页面查看。
   :::
3. 拉取依赖。
   修改 `toml` 文件后，可以通过以下方式拉取相关依赖：
   * 在终端窗口执行以下命令。
      ```Bash
      ./gradlew clean build --refresh-dependencies
      ```
   * 在 IDE 中单击 **Sync Now**。
      ![Image=2352x460](https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/11e9adf542d94b2eab437694e1521118~tplv-goo7wpa0wc-image.image)      


## 步骤三：实现音视频通话 {#6808c52b}

:::tip 说明
本文以 [MainActivity.kt](https://github.com/coze-dev/coze-android/blob/main/kotlin-example/src/main/java/com/coze/kotlin_example/KotlinActivity.kt) 和 [MainActivity.java](https://github.com/coze-dev/coze-android/blob/main/java-example/src/main/java/com/coze/java_example/JavaActivity.javat) 中的实现步骤为例，讲解如何实现一个基本的音视频通话功能。本示例并未覆盖全部代码内容，如需查看完整代码，请从[音视频通话 Android 示例项目](https://github.com/coze-dev/coze-android/)获取完整代码。
:::

### 引入头文件 {#718345c4}

引入实现音视频通话功能所需的头文件。

::::tabs
@tab Java
```Java
import com.ss.bytertc.engine.RTCRoom;
import com.ss.bytertc.engine.RTCVideo;
import com.coze.openapi.service.service.CozeAPI

public class MainActivity extends AppCompatActivity {
    private RTCVideo rtcVideo;
    private RTCRoom rtcRoom;
    private CozeAPI cozeCli;
}
```



@tab Kotlin
```Kotlin
import com.ss.bytertc.engine.RTCRoom;
import com.ss.bytertc.engine.RTCVideo;
import com.coze.openapi.service.service.CozeAPI

class MainActivity : AppCompatActivity() {
    private var rtcVideo: RTCVideo? = null
    private var rtcRoom: RTCRoom? = null
    private lateinit var cozeCli: CozeAPI
}
```


::::

### 创建用户界面 {#84f9f5a4}

创建基本的用户界面，包括本地视频预览视图、控制按钮等，并设置初始状态。将布局文件中定义的组件绑定到代码中，如控制按钮、本地视频预览视图等，并设置相应的事件监听器。

::::tabs
@tab Java
```Java
private void initUI() {
    localViewContainer = findViewById(R.id.local_view_container);

    btnConnect = findViewById(R.id.btn_connect);
    btnConnect.setBackgroundColor(Color.GRAY);
    btnVideo = findViewById(R.id.btn_video);
    btnVideo.setBackgroundColor(Color.GRAY);
    btnAudio = findViewById(R.id.btn_audio);
    btnAudio.setBackgroundColor(Color.GRAY);
    btnInterrupt = findViewById(R.id.btn_interrupt);
    btnInterrupt.setBackgroundColor(Color.GRAY);
    roomIdInput = findViewById(R.id.room_id_input);
    messageTextView = findViewById(R.id.message_text_view);

    connect();
    initVideoControl();
    initAudioControl();
    setBtnInterrupt();
}
```



@tab Kotlin
```Kotlin
private fun initUI() {
    localViewContainer = findViewById(R.id.local_view_container)

    btnConnect = findViewById<Button>(R.id.btn_connect).apply {
        setBackgroundColor(Color.GRAY)
    }
    btnVideo = findViewById<Button>(R.id.btn_video).apply {
        setBackgroundColor(Color.GRAY)
    }
    btnAudio = findViewById<Button>(R.id.btn_audio).apply {
        setBackgroundColor(Color.GRAY)
    }
    btnInterrupt = findViewById<Button>(R.id.btn_interrupt).apply {
        setBackgroundColor(Color.GRAY)
    }
    roomIdInput = findViewById(R.id.room_id_input)
    messageTextView = findViewById(R.id.message_text_view)

    connect()
    initVideoControl()
    initAudioControl()
    setBtnInterrupt()
}
```


::::

### 申请设备权限 {#4661b64b}

在使用音视频通话功能之前，需要确保应用已获取必要的权限，包括麦克风、摄像头、网络访问权限。

::::tabs
@tab Java
```Java
public void requestPermission() {
    String[] PERMISSIONS_STORAGE = {
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.CAMERA,
            Manifest.permission.INTERNET

    };
    boolean needPermission = false;

    for (String permission : PERMISSIONS_STORAGE) {
        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            needPermission = true;
            break;
        }
    }
    if(needPermission){
        requestPermissions(PERMISSIONS_STORAGE, 22);
    }

}
```



@tab Kotlin
```Kotlin
private fun checkAndRequestPermissions(): Boolean {
    val permissions = arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.INTERNET,
        Manifest.permission.MODIFY_AUDIO_SETTINGS
    )

    val permissionsToRequest = permissions.filter { permission ->
        ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED
    }

    return if (permissionsToRequest.isNotEmpty()) {
        ActivityCompat.requestPermissions(
            this,
            permissionsToRequest.toTypedArray(),
            100
        )
        false  // 返回false表示有权限需要申请
    } else {
        true  // 返回true表示所有权限都已获取
    }
}
```


::::

### 创建房间 {#032b2083}

调用扣子编程的[创建房间](/developer_guides/create_room)接口创建房间，以便后续从扣子编程平台获取房间信息。

::::tabs
@tab Java
```Java
// 第一步，在coze创建房间
CreateRoomReq req = CreateRoomReq.builder()
        .botID(Config.getInstance().getBotID())
        .voiceID(Config.getInstance().getVoiceID())
        .build();
roomInfoTemp = cozeCli.audio().rooms().create(req);
```



@tab Kotlin
```Kotlin
// 第一步，在coze创建房间 
CreateRoomReq req = CreateRoomReq.builder()
        .botID(Config.getInstance().getBotID())
        .voiceID(Config.getInstance().getVoiceID())
        .build();
roomInfoTemp = cozeCli.audio().rooms().create(req);
```


::::

:::tip 说明
创建房间后返回的 Token，其默认有效期为 3 分钟，如果 3 分钟内没有用户加入房间，或者用户静音 3 分钟，智能体将自动退出房间，房间随即被释放，后续若要再次和智能体对话，则需重新创建房间。
:::

### 创建引擎 {#4b69b9c0}

调用 `createRTCVideo()` 方法，使用扣子编程平台返回的房间信息初始化音视频引擎。

::::tabs
@tab Java
```Java
// 创建引擎
rtcVideo = RTCVideo.createRTCVideo(
        getApplicationContext(),  // 使用ApplicationContext而不是Activity
        roomInfo.getAppID(),
        new IRTCVideoEventHandler() {
            @Override
            public void onWarning(int warn) {
                Log.w(TAG, "RTCVideo warning: " + warn);
            }

            @Override
            public void onError(int err) {
                Log.e(TAG, "RTCVideo error: " + err);
            }
        },
        null,
        null
);
```



@tab Kotlin
```Kotlin
// 创建引擎
rtcVideo = RTCVideo.createRTCVideo(
        getApplicationContext(),  // 使用ApplicationContext而不是Activity
        roomInfo.getAppID(),
        new IRTCVideoEventHandler() {
            @Override
            public void onWarning(int warn) {
                Log.w(TAG, "RTCVideo warning: " + warn);
            }

            @Override
            public void onError(int err) {
                Log.e(TAG, "RTCVideo error: " + err);
            }
        },
        null,
        null
);
```


::::

### 设置本端视频渲染视图 {#1bd3fa0c}

调用 `setLocalVideoCanvas()` 方法设置本端视频渲染视图。

::::tabs
@tab Java
```Java
// 设置本地预览窗口
TextureView localTextureView = new TextureView(MainActivity.this);
localViewContainer.removeAllViews();
localViewContainer.addView(localTextureView);

VideoCanvas videoCanvas = new VideoCanvas();
videoCanvas.renderView = localTextureView;
videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN;
// 设置本地视频渲染视图
rtcVideo.setLocalVideoCanvas(StreamIndex.STREAM_INDEX_MAIN, videoCanvas);
```



@tab Kotlin
```Kotlin
// 设置本地预览窗口
val localTextureView = TextureView(this@MainActivity)
localViewContainer.removeAllViews()
localViewContainer.addView(localTextureView)

VideoCanvas().apply {
    renderView = localTextureView
    renderMode = VideoCanvas.RENDER_MODE_HIDDEN
    // 设置本地视频渲染视图
    rtcVideo?.setLocalVideoCanvas(StreamIndex.STREAM_INDEX_MAIN, this)
}
```


::::

### 创建并加入 RTC 房间 {#f204df16}

调用 `createRTCRoom` 方法创建 RTC 房间，调用 `joinRoom()` 方法加入房间。

::::tabs
@tab Java
```Java
// 第创建RTC房间
rtcRoom = rtcVideo.createRTCRoom(roomInfo.getRoomID());
rtcRoom.setRTCRoomEventHandler(rtcRoomEventHandler);
// 用户信息
UserInfo userInfo = new UserInfo(roomInfo.getUid(), "");
// 设置房间配置
RTCRoomConfig roomConfig = new RTCRoomConfig(
        ChannelProfile.CHANNEL_PROFILE_CHAT_ROOM,
        true, true, true);

// 第四步，加入房间
rtcRoom.joinRoom(roomInfo.getToken(), userInfo, roomConfig);
```



@tab Kotlin
```Kotlin
// 创建RTC房间
rtcRoom = rtcVideo?.createRTCRoom(roomInfo?.roomID)?.apply {
    setRTCRoomEventHandler(rtcRoomEventHandler)
    // 用户信息
    val userInfo = UserInfo(roomInfo?.uid, "")
    // 设置房间配置
    val roomConfig = RTCRoomConfig(
        ChannelProfile.CHANNEL_PROFILE_CHAT_ROOM,
        true, true, true
    )

    // 加入房间
    joinRoom(roomInfo?.token, userInfo, roomConfig)
}
```


::::

### 显示实时字幕 {#234e9ac4}

在 `IRTCRoomEventHandler` 回调中重写`onUserMessageReceived`方法，解析接收到的用户消息，根据消息中的 `event_type` 进行不同处理，调用 `updateMessage()` 方法将消息内容更新到界面上。

::::tabs
@tab Java
```Java
@Override
public void onUserMessageReceived(String uid, String message) {
    try {
        Map<String, Object> messageMap = mapper.readValue(message, new TypeReference<Map<String, Object>>() {});
        Log.d(TAG, "接收到原始消息: " + messageMap);

        Map<String, String> jsonMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : messageMap.entrySet()) {
            try {
                if (entry.getValue() instanceof String) {
                    jsonMap.put(entry.getKey(), (String) entry.getValue());
                    continue;
                }
                String jsonValue = mapper.writeValueAsString(entry.getValue());
                jsonMap.put(entry.getKey(), jsonValue);
            } catch (JsonProcessingException e) {
                Log.e(TAG, "序列化value失败: " + entry.getKey(), e);
            }
        }
        if (ChatEventType.CONVERSATION_MESSAGE_DELTA.getValue().equals(jsonMap.get("event_type"))){
            Message msg = mapper.readValue(jsonMap.get("data"), Message.class);
            updateMessage(msg.getContent());
        } else if (ChatEventType.CONVERSATION_MESSAGE_COMPLETED.getValue().equals(jsonMap.get("event_type"))) {
            updateMessage("\n");
        }
    } catch (JsonProcessingException e) {
        Log.e(TAG, "解析消息失败", e);
    }
}
```



@tab Kotlin
```Kotlin
override fun onUserMessageReceived(uid: String, message: String) {
    try {
        val messageMap = mapper.readValue<Map<String, Any>>(
            message,
            object : TypeReference<Map<String, Any>>() {}
        )
        Log.d(TAG, "接收到原始消息: $messageMap")

        val jsonMap = messageMap.mapValues { (_, value) ->
            when (value) {
                is String -> value
                else -> try {
                    mapper.writeValueAsString(value)
                } catch (e: Exception) {
                    Log.e(TAG, "序列化value失败: ${e.message}")
                    null
                }
            }
        }

        when (jsonMap["event_type"]) {
            ChatEventType.CONVERSATION_MESSAGE_DELTA.value -> {
                val msg = mapper.readValue(
                    jsonMap["data"] as String,
                    Message::class.java
                )
                updateMessage(msg.content)
            }
            ChatEventType.CONVERSATION_MESSAGE_COMPLETED.value -> {
                updateMessage("\n")
            }
        }
    } catch (e: Exception) {
        Log.e(TAG, "解析消息失败", e)
    }
}
```


::::

### 开启或关闭本端视频流采集 {#6c34195e}

监听按钮事件，根据 `isVideoEnabled` 的状态，调用 `stopVideoCapture()` 或 `startVideoCapture()` 方法，实现摄像头的启用或禁用功能。

::::tabs
@tab Java
```Java
private void initVideoControl() {
    btnVideo.setOnClickListener(v -> {
        if (rtcVideo != null) {
            if (isVideoEnabled) {
                stopVideo();
            } else {
                startVideo();
            }
        }else{
            ToastUtil.showAlert(this, "请先连接");
        }
    });
}

private void stopVideo(){
    rtcVideo.stopVideoCapture();
    btnVideo.setText("打开视频");
    isVideoEnabled = !isVideoEnabled;
}

private void startVideo(){
    rtcVideo.startVideoCapture();
    btnVideo.setText("关闭视频");
    isVideoEnabled = !isVideoEnabled;
}
```



@tab Kotlin
```Kotlin
private fun initVideoControl() {
    btnVideo.setOnClickListener {
        if (rtcVideo != null) {
            if (isVideoEnabled) {
                stopVideo()
            } else {
                startVideo()
            }
        } else {
            ToastUtil.showAlert(this, "请先连接")
        }
    }
}

private fun stopVideo() {
    rtcVideo?.stopVideoCapture()
    btnVideo.text = "打开视频"
    isVideoEnabled = !isVideoEnabled
}

private fun startVideo() {
    rtcVideo?.startVideoCapture()
    btnVideo.text = "关闭视频"
    isVideoEnabled = !isVideoEnabled
}
```


::::

### 静音或取消静音 {#54a35af9}

监听按钮事件，根据 `isAudioEnabled` 的状态调用 `stopAudioCapture()` 或 `startAudioCapture()` 方法，从而实现麦克风的启用或静音功能。

::::tabs
@tab Java
```Java
private void initAudioControl() {
    btnAudio.setOnClickListener(v -> {
        if (rtcVideo != null) {
            if (isAudioEnabled) {
                stopVoice();
            } else {
                startVoice();
            }
        }else {
            ToastUtil.showAlert(this, "请先连接");
        }
    });
}

private void startVoice(){
    rtcVideo.startAudioCapture();
    btnAudio.setText("静音");
    isAudioEnabled = !isAudioEnabled;
}

private void stopVoice(){
    rtcVideo.stopAudioCapture();
    btnAudio.setText("打开声音");
    isAudioEnabled = !isAudioEnabled;
}
```



@tab Kotlin
```Kotlin
private fun initAudioControl() {
    btnAudio.setOnClickListener {
        if (rtcVideo != null) {
            if (isAudioEnabled) {
                stopVoice()
            } else {
                startVoice()
            }
        } else {
            ToastUtil.showAlert(this, "请先连接")
        }
    }
}

private fun startVoice() {
    rtcVideo?.startAudioCapture()
    btnAudio.text = "静音"
    isAudioEnabled = !isAudioEnabled
}

private fun stopVoice() {
    rtcVideo?.stopAudioCapture()
    btnAudio.text = "打开声音"
    isAudioEnabled = !isAudioEnabled
}
```


::::

### 停止音视频通话 {#cf696b3e}

1. 调用 `leaveRoom()`、`destroy()` 方法离开并销毁房间。
2. 调用 `stopVoice()`和`stopVideo()` 方法停止音视频采集。
3. 调用 `destroyRTCVideo()` 方法销毁引擎。

::::tabs
@tab Java
```Java
private void disconnect(){
    if (rtcRoom != null) {
        rtcRoom.leaveRoom();
        rtcRoom.destroy();
    }
    if (rtcVideo != null){
        stopVoice();
        stopVideo();
        RTCVideo.destroyRTCVideo();
        rtcVideo = null;
    }
    ToastUtil.showAlert(this, "断开连接成功");
}
```



@tab Kotlin
```Kotlin
private fun disconnect() {
    rtcRoom?.apply {
        leaveRoom()
        destroy()
    }
    rtcVideo?.apply {
        stopVoice()
        stopVideo()
        RTCVideo.destroyRTCVideo()
        rtcVideo = null
    }
    ToastUtil.showAlert(this, "断开连接成功")
}
```


::::

:::tip 说明
在实现音视频通话后，如遇无声音、无画面、视频卡顿等问题时，你可以使用[诊断工具](https://www.volcengine.com/docs/6348/125643)快速排查和定位异常房间及用户，并获取异常根因分析、处理建议、分析报告等。
:::

## 信令事件 {#a9da50f2}

#### **事件类型** {#2e3be76a}

智能语音信令事件包括上行事件和下行事件。每个事件有 ID 和 EventType， 通过 EventType 可以区分具体的事件类型，每个事件类型对应的 Payload 在 Data 中，开发者可以按需去提取需要的内容。

* **上行事件**：设备端上报给服务端的事件。应用程序需要根据扣子编程提供的事件结构，在触发事件时填充字段内容并上报事件。设备端需要在监听到 Realtime SDK 的 onUserJoined 回调智能体进房后才能发送上行事件，可通过 Realtime SDK 的 `sendUserMessage` 发送，如果是嵌入式设备集成音视频，可通过 Realtime SDK 的 `byte_rtc_rts_send_message` 发送。详细信息可参考[Realtime 上行事件](/developer_guides/signaling_uplink_event)。
* **下行事件**：服务端下发给设备端的事件，可订阅 Realtime SDK的 `onUserMessageReceived`回调，如果是嵌入式设备集成音视频，可订阅 Realtime SDK 的 ` on_message_received`回调，接收下行事件。应用程序需要解析下行事件，并根据业务需求进行下一步操作。详细信息可参考[Realtime 下行事件](/developer_guides/signaling_downlink_event)。

#### **公共参数** {#bcbe01bb}

智能语音信令事件的公共参数如下：

<!-- @cols-width: 100,162,500 -->
|**参数名称** |**类型** |**描述** |
|---|---|---|
|id |String |事件 ID，也就是事件的唯一标识。由客户端或服务端生成，在故障排查场景下用于定位具体的事件，便于排查问题。 |
|event_type |String |事件的类型。 |
|data |JSON |事件的详细信息，其中包含具体事件的业务字段。 |

#### 信令事件的使用流程 {#34b91905}

信令事件的使用流程如下：

1. 初始化。通过监听 `session.created` 事件，确认房间初始化完成。
2. 发送请求。使用上行事件（如 `conversation.message.create`）向智能体发送消息。
3. 处理响应。监听下行事件（如 `conversation.message.delta`）获取智能体的增量回复。
4. 插件交互。在收到事件 `conversation.chat.requires_action` 后，执行插件操作并通过事件 `conversation.chat.submit_tool_outputs` 提交结果。
5. 错误处理。通过监听 `error` 事件捕获和处理异常情况。

使用示例如下：

::::tabs
@tab Java
```Java
// 通过重载 onUserMessageReceived 方法即可监听所有的下行事件，可以通过 event_type 过滤需要处理的事件
@Override
public void onUserMessageReceived(String uid, String message) {
    try {
        Map<String, Object> messageMap = mapper.readValue(message, new TypeReference<Map<String, Object>>() {});
        Log.i(TAG, "接收到原始消息: " + messageMap);

        Map<String, String> jsonMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : messageMap.entrySet()) {
            try {
                if (entry.getValue() instanceof String) {
                    jsonMap.put(entry.getKey(), (String) entry.getValue());
                    continue;
                }
                String jsonValue = mapper.writeValueAsString(entry.getValue());
                jsonMap.put(entry.getKey(), jsonValue);
            } catch (JsonProcessingException e) {
                Log.e(TAG, "序列化value失败: " + entry.getKey(), e);
            }
        }
        // 根据你需要监听的 event，解析成对应的结构
        if (ChatEventType.CONVERSATION_MESSAGE_DELTA.getValue().equals(jsonMap.get("event_type"))){
            Message msg = mapper.readValue(jsonMap.get("data"), Message.class);
            updateMessage(msg.getContent());
        } else if (ChatEventType.CONVERSATION_MESSAGE_COMPLETED.getValue().equals(jsonMap.get("event_type"))) {
            updateMessage("\n");
        }
    } catch (JsonProcessingException e) {
        Log.e(TAG, "解析消息失败", e);
    }
}
```



@tab Kotlin
```Kotlin
// 通过重载 onUserMessageReceived 方法即可监听所有的下行事件，可以通过 event_type 过滤需要处理的事件
override fun onUserMessageReceived(uid: String, message: String) {
    try {
        val messageMap = mapper.readValue<Map<String, Any>>(
            message,
            object : TypeReference<Map<String, Any>>() {}
        )
        Log.d(TAG, "接收到原始消息: $messageMap")

        val jsonMap = messageMap.mapValues { (_, value) ->
            when (value) {
                is String -> value
                else -> try {
                    mapper.writeValueAsString(value)
                } catch (e: Exception) {
                    Log.e(TAG, "序列化value失败: ${e.message}")
                    null
                }
            }
        }

        when (jsonMap["event_type"]) {
            // 根据你需要监听的 event，解析成对应的结构
            ChatEventType.CONVERSATION_MESSAGE_DELTA.value -> {
                val msg = mapper.readValue(
                    jsonMap["data"] as String,
                    Message::class.java
                )
                updateMessage(msg.content)
            }
            ChatEventType.CONVERSATION_MESSAGE_COMPLETED.value -> {
                updateMessage("\n")
            }
        }
    } catch (e: Exception) {
        Log.e(TAG, "解析消息失败", e)
    }
}
```


::::

#### 信令事件的处理思路 {#31ec8ccc}

在处理信令事件时，建议采用以下思路：

* **监听所有事件**：捕获客户端和服务端的所有交互，便于调试和监控。扣子编程推荐你监听所有的事件，你也可以根据实际的业务需求，监听不同类型的事件，例如客户端事件、服务端事件等。
* **处理特定事件**：根据业务需求，监听和处理特定的事件，例如连接成功、中断、断开、音频状态变化等。
* **错误处理**：捕获和处理错误事件，确保应用的稳定性和用户体验。

示例代码如下：

::::tabs
@tab Java
```Java
// 调用 RTCRoom 对象的 sendUserMessage 方法即可发送上行事件，指定其中的 userId 为聊天的 bot_id 即可将事件发送给指定的 bot，下面以处理端插件事件为例

        @Override
        public void onUserMessageReceived(String uid, String message) {
            try {
                Map<String, Object> messageMap = mapper.readValue(message, new TypeReference<Map<String, Object>>() {});
                Log.i(TAG, "接收到原始消息: " + messageMap);

                Map<String, String> jsonMap = new HashMap<>();
                for (Map.Entry<String, Object> entry : messageMap.entrySet()) {
                    try {
                        if (entry.getValue() instanceof String) {
                            jsonMap.put(entry.getKey(), (String) entry.getValue());
                            continue;
                        }
                        String jsonValue = mapper.writeValueAsString(entry.getValue());
                        jsonMap.put(entry.getKey(), jsonValue);
                    } catch (JsonProcessingException e) {
                        Log.e(TAG, "序列化value失败: " + entry.getKey(), e);
                    }
                }
                }else if(ChatEventType.ERROR.getValue().equals(jsonMap.get("event_type"))){
                    Log.i(TAG, jsonMap.toString());
                }
            } catch (JsonProcessingException e) {
                Log.e(TAG, "解析消息失败", e);
            }
        }
```


::::
