本文介绍如何集成火山引擎 RTC SDK,并实现实时音视频通话。根据如下步骤操作,即可从 0 开始构建一个简单的音视频通话应用。
你也可以参考示例项目,了解更完整的项目实现。
在开始集成 RTC SDK 前,请确保满足以下要求:
说明
推荐使用真机进行调试,连接指南参看在硬件设备上运行应用。
SDK 已在内部声明所需权限,无需手动添加。对于敏感权限,你需要在 Activity 中动态申请,本文动态申请权限章节将提供示例代码。
说明
AndroidManifest.xml 文件中声明前台服务类型 android:foregroundServiceType:microphone。此外,如果你的应用以 Android 14(API 级别 34)或更高版本为目标平台,则必须针对前台服务将要执行的工作类型请求 FOREGROUND_SERVICE_MICROPHONE 权限。摄像头权限同理。详细说明和示例代码参看前台服务权限适配方法。AndroidManifest.xml 文件中额外声明 BLUETOOTH_CONNECT 权限,并在 Activity 中动态申请。示例代码参看应用的 targetSDKVersion >= 31 时如何配置蓝牙权限?在 Android Studio 左上角将工程视图切换为 Project 模式,在项目根目录的 settings.gradle 文件配置 Maven 仓库地址。
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { ... maven { url 'https://artifact.bytedance.com/repository/Volcengine/' } } }
说明
如果你的 Android Gradle Plugin 版本低于 v7.1.0,则应在项目根目录的 build.gradle 文件配置 Maven 仓库地址。
allprojects { repositories { ... maven { url 'https://artifact.bytedance.com/repository/Volcengine/' } } }
在 App 的 build.gradle 文件中添加 RTC SDK 依赖。
说明
你需要将 '3.x.y.z' 替换为具体的版本号,最新版本号请参看下载 SDK。
dependencies { ... implementation 'com.volcengine:VolcEngineRTC:3.x.y.z' // 填写需要接入的 RTC SDK 版本号 }
在项目根目录的 gradle.properties 文件中添加 android.enableJetifier=true,解决兼容性问题。
设置完成后,单击 Sync now 完成同步。
说明
本章节介绍全量集成 RTC SDK 的方法。如需减小 App 体积,请参看按需集成插件。
下载并解压火山引擎 RTC SDK文件。
在 Android Studio 左上角将工程视图切换为 Project 模式,将解压后的 VolcEngineRTC-lite.aar、effectAAR-release_V4.4.3Lite.aar 放在 app/libs 目录下,解压的四个架构文件夹放置在 app/jniLibs 目录下。
在 App 的 build.gradle 文件中添加 RTC SDK 依赖和 .so 文件依赖。
android { ... sourceSets { main { // 指定 .so 文件所在目录 jniLibs.srcDirs = ['jniLibs'] } } } ... dependencies { ... // 集成主库 implementation files('libs/VolcEngineRTC-lite.aar') // 集成特效库,用于实现美颜和特效相关功能 implementation files('libs/effectAAR-release_V4.4.3Lite.aar') }
在项目根目录的 gradle.properties 文件中添加 android.enableJetifier=true,解决兼容性问题。
设置完成后,单击 Sync now 完成同步。
根据场景需要,为你的项目创建音视频通话的用户界面。为实现基本的音视频通话,建议用户界面中至少包含本地视频窗口、远端视频窗口、加入房间按钮、退出房间按钮。
将以下示例代码在 src/main/res/layout/activity_main.xml 中进行替换,即可快速创建用户界面。
<?xml version="1.0" encoding="utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".examples.QuickStartActivity" android:layout_marginHorizontal="15dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="300dp" android:orientation="horizontal"> <FrameLayout android:id="@+id/local_view_container" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" app:layout_constraintTop_toTopOf="parent" /> <FrameLayout android:id="@+id/remote_view_container" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" app:layout_constraintTop_toTopOf="parent" /> </LinearLayout> <androidx.appcompat.widget.AppCompatButton android:id="@+id/btn_join_room" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="加入房间"/> <androidx.appcompat.widget.AppCompatButton android:id="@+id/btn_leave_room" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="退出房间"/> </androidx.appcompat.widget.LinearLayoutCompat>
本章节将先向你提供 API 调用时序图和完整的实现代码,再对具体的实现步骤展开介绍。
下图为使用火山引擎 RTC SDK 实现基础音视频通话的 API 调用时序图。
复制以下示例代码,替换 MainActivity.java 文件中 package com.example.<projectname>; 后的全部内容,此处的 com.example.<projectname> 即为你新建项目时指定的软件包名称。连接并选择你的 Android 设备,单击 Android Studio 窗口右上角的运行按钮(Run 'app'),即可快速实现音视频通话。
说明
你需要将代码中的 roomId、userId、APP_ID、token 替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。
// package com.example.<projectname>; 你的软件包名称 import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.AppCompatButton; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.view.TextureView; import android.widget.FrameLayout; import com.ss.bytertc.engine.RTCEngine; import com.ss.bytertc.engine.RTCRoom; import com.ss.bytertc.engine.RTCRoomConfig; import com.ss.bytertc.engine.UserInfo; import com.ss.bytertc.engine.VideoCanvas; import com.ss.bytertc.engine.data.EngineConfig; import com.ss.bytertc.engine.data.RemoteStreamKey; import com.ss.bytertc.engine.data.StreamIndex; import com.ss.bytertc.engine.handler.IRTCEngineEventHandler; import com.ss.bytertc.engine.handler.IRTCRoomEventHandler; import com.ss.bytertc.engine.type.ChannelProfile; public class MainActivity extends AppCompatActivity { private static final String APP_ID = ""; // 填写 appId private static final String roomId = ""; // 填写房间号 private static final String userId = ""; // 填写 userId private static final String token = ""; // 填写临时 token private FrameLayout localViewContainer; private FrameLayout remoteViewContainer; private RTCEngine rtcEngine; private RTCRoom rtcRoom; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestPermission(); localViewContainer = findViewById(R.id.local_view_container); remoteViewContainer = findViewById(R.id.remote_view_container); ((AppCompatButton) findViewById(R.id.btn_join_room)).setOnClickListener(v -> { joinRoom(roomId); }); ((AppCompatButton) findViewById(R.id.btn_leave_room)).setOnClickListener(v -> { leaveRoom(); }); // 创建引擎 EngineConfig engineConfig = new EngineConfig(); engineConfig.context = this; engineConfig.appID = APP_ID; rtcEngine = RTCEngine.createRTCEngine(engineConfig, engineEventHandler); // 设置本端渲染视图 setLocalRenderView(); // 开启音视频采集 rtcEngine.startVideoCapture(); rtcEngine.startAudioCapture(); } public void requestPermission() { String[] PERMISSIONS_STORAGE = { Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA}; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, "android.permission.CAMERA") != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, "android.permission.RECORD_AUDIO") != PackageManager.PERMISSION_GRANTED) { requestPermissions(PERMISSIONS_STORAGE, 22); } } } /** * 设置本地渲染视图,支持TextureView和SurfaceView */ private void setLocalRenderView() { TextureView textureView = new TextureView(this); localViewContainer.removeAllViews(); localViewContainer.addView(textureView); VideoCanvas videoCanvas = new VideoCanvas(); videoCanvas.renderView = textureView; videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN; // 设置本地视频渲染视图 rtcEngine.setLocalVideoCanvas(videoCanvas); } private void setRemoteRenderView(String streamId) { TextureView remoteTextureView = new TextureView(this); remoteViewContainer.removeAllViews(); remoteViewContainer.addView(remoteTextureView); VideoCanvas videoCanvas = new VideoCanvas(); videoCanvas.renderView = remoteTextureView; videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN; // 设置远端视频渲染视图 rtcEngine.setRemoteVideoCanvas(streamId, videoCanvas); } private void removeRemoteView(String streamId) { rtcEngine.setRemoteVideoCanvas(streamId, null); } /** * 引擎回调信息 */ IRTCEngineEventHandler engineEventHandler = new IRTCEngineEventHandler() { }; IRTCRoomEventHandler rtcRoomEventHandler = new IRTCRoomEventHandler() { @Override public void onUserPublishStreamVideo(String streamId, StreamInfo streamInfo, boolean isPublish) { if (isPublish) { runOnUiThread(() -> setRemoteRenderView(streamInfo.streamId)); } else { runOnUiThread(() -> removeRemoteView(streamInfo.streamId)); } } }; /** * 加入房间 * @param roomId 房间ID */ private void joinRoom(String roomId) { rtcRoom = rtcEngine.createRTCRoom(roomId); rtcRoom.setRTCRoomEventHandler(rtcRoomEventHandler); // 用户信息 UserInfo userInfo = new UserInfo(userId, ""); boolean isAutoPublishAudio = true; boolean isAutoPublishVideo = true; boolean isAutoSubscribeAudio = true; boolean isAutoSubscribeVideo = true; RTCRoomConfig roomConfig = new RTCRoomConfig( ChannelProfile.CHANNEL_PROFILE_CHAT_ROOM, null, isAutoPublishAudio, isAutoPublishVideo, isAutoSubscribeAudio, isAutoSubscribeVideo ); // 加入房间 rtcRoom.joinRoom(token, userInfo, true, roomConfig); } /** * 离开房间 */ private void leaveRoom() { if (rtcRoom != null) { rtcRoom.leaveRoom(); rtcRoom.destroy(); rtcRoom = null; } } @Override protected void onDestroy() { super.onDestroy(); if (rtcEngine != null) { rtcEngine.stopVideoCapture(); rtcEngine.stopAudioCapture(); } RTCEngine.destroyRTCEngine(); } }
引入 SDK 需要的类。
import com.ss.bytertc.engine.RTCRoom; import com.ss.bytertc.engine.RTCRoomConfig; import com.ss.bytertc.engine.RTCEngine; import com.ss.bytertc.engine.UserInfo; import com.ss.bytertc.engine.VideoCanvas; import com.ss.bytertc.engine.data.RemoteStreamKey; import com.ss.bytertc.engine.data.StreamIndex; import com.ss.bytertc.engine.handler.IRTCRoomEventHandler; import com.ss.bytertc.engine.handler.IRTCEngineEventHandler; import com.ss.bytertc.engine.type.ChannelProfile; import com.ss.bytertc.engine.data.EngineConfig;
启动应用程序时,检查是否已在 App 中授予了所需的权限。
public void requestPermission() { String[] PERMISSIONS_STORAGE = { Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA}; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, "android.permission.CAMERA") != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, "android.permission.RECORD_AUDIO") != PackageManager.PERMISSION_GRANTED) { requestPermissions(PERMISSIONS_STORAGE, 22); } } }
调用 createRTCEngine 初始化 RTCEngine 引擎,所有 RTC 相关的 API 调用都要在创建引擎之后。
// 创建引擎 EngineConfig engineConfig = new EngineConfig(); engineConfig.context = this; engineConfig.appID = APP_ID; rtcEngine = RTCEngine.createRTCEngine(engineConfig, engineEventHandler);
创建引擎后,调用 startVideoCapture 开启视频采集,调用 startAudioCapture 开启音频采集。
// 开启音视频采集 rtcEngine.startVideoCapture(); rtcEngine.startAudioCapture();
调用 setLocalVideoCanvas 设置本端渲染窗口,支持 TextureView 和 SurfaceView。
/** * 设置本地渲染视图,支持TextureView和SurfaceView */ private void setLocalRenderView() { TextureView textureView = new TextureView(this); localViewContainer.removeAllViews(); localViewContainer.addView(textureView); VideoCanvas videoCanvas = new VideoCanvas(); videoCanvas.renderView = textureView; videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN; // 设置本地视频渲染视图 rtcEngine.setLocalVideoCanvas(videoCanvas); }
按以下步骤创建并加入房间:
RTCRoom 类。/** * 加入房间 * @param roomId 房间ID */ private void joinRoom(String roomId) { rtcRoom = rtcEngine.createRTCRoom(roomId); rtcRoom.setRTCRoomEventHandler(rtcRoomEventHandler); // 用户信息 UserInfo userInfo = new UserInfo(userId, ""); // 设置房间配置 boolean isAutoPublishAudio = true; boolean isAutoPublishVideo = true; boolean isAutoSubscribeAudio = true; boolean isAutoSubscribeVideo = true; RTCRoomConfig roomConfig = new RTCRoomConfig( ChannelProfile.CHANNEL_PROFILE_CHAT_ROOM, null, isAutoPublishAudio, isAutoPublishVideo, isAutoSubscribeAudio, isAutoSubscribeVideo ); // 加入房间 rtcRoom.joinRoom(token, userInfo, true, roomConfig);; }
当远端用户加入频道并发布视频流时,在 onUserPublishStreamVideo 或 onFirstRemoteVideoFrameDecoded 回调中调用 setRemoteVideoCanvas 设置并渲染远端视图。建议您在 onFirstRemoteVideoFrameDecoded 回调中设置视频渲染,确保视频渲染在视频解码完成后进行,以避免卡顿。
在 onUserPublishStreamVideo 回调中,当 isPublish 参数为 false 时,触发解除远端视频渲染视图绑定的逻辑。调用用户自定的 removeRemoteView 方法来解除渲染远端视图。
IRTCEngineEventHandler rtcEngineEventHandler = new IRTCEngineEventHandler() { @Override public void onFirstRemoteVideoFrameDecoded(String streamId, int width, int height, int rotation) { // 设置远端视频渲染视图 runOnUiThread(() -> setRemoteRenderView(streamInfo.streamId)); } }; IRTCRoomEventHandler rtcRoomEventHandler = new IRTCRoomEventHandler() { @Override public void onUserPublishStreamVideo(String streamId, StreamInfo streamInfo, boolean isPublish) { if (!isPublish) { // 解除远端视频渲染视图绑定 runOnUiThread(() -> removeRemoteView(streamInfo.streamId)); } } }; private void setRemoteRenderView(String streamId) { TextureView remoteTextureView = new TextureView(this); remoteViewContainer.removeAllViews(); remoteViewContainer.addView(remoteTextureView); VideoCanvas videoCanvas = new VideoCanvas(); videoCanvas.renderView = remoteTextureView; videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN; // 设置远端视频渲染视图 rtcEngine.setRemoteVideoCanvas(streamId, videoCanvas); } private void removeRemoteView(String streamId) { rtcEngine.setRemoteVideoCanvas(streamId, null); }
调用 leaveRoom 离开房间, 并调用 destroy 销毁房间实例。
/** * 离开房间 */ private void leaveRoom() { if (rtcRoom != null) { rtcRoom.leaveRoom(); rtcRoom.destroy(); rtcRoom = null; } }
退出 app 时,调用 destroyRTCEngine 销毁 RTCEngine 引擎。
RTCEngine.destroyRTCEngine();
在实现音视频通话后,如遇无声音、无画面、视频卡顿等问题时,您可以使用诊断工具快速排查和定位异常房间及用户,并获取异常根因分析、处理建议、分析报告等。