集成 RTC SDK 后,你可以使用其中接口快速构建基础应用,实现基本实时音视频通话;你也能通过阅读代码,了解音视频通话的最佳实践。
如果你想了解完整的项目实现,参看快速开始 Demo。

前提条件
步骤 1:(可选)创建项目

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

  1. 打开 Android Studio。在 Welcome to Android Studio 窗口中,单击 New Project。
  2. 在 New Project 窗口中,选择 Empty Activity,然后单击 Next。
  3. 在 Empty Activity 窗口中,完成以下操作:
    1. 在 Name 字段中输入Hello Volcengine。
    2. 在 Package name 字段中输入com.example.volcenginedemo。
    3. 在 Save location 中输入项目的存储路径。
    4. 从 Language 下拉菜单中选择 Java。
    5. 在 Minimum SDK 字段中选择 API 19 作为应用支持的最低 Android 版本。
    6. 如果应用需要旧版库支持,选中 Use legacy android.support libraries 复选框。

alt

  1. 单击 Finish,经过一段时间的处理后,Android Studio 主窗口会出现,同时你已经构建好了 Android 工程,可以开始编码了。
步骤 2:集成 SDK

你可以选择 Maven 的方式或使用离线 SDK 的方式。

Maven 方式:配置 Maven 仓库地址和依赖

推荐使用 Maven 在项目中接入 RTC SDK,步骤如下:

  1. 在根目录下的 build.gradle 文件中,配置 maven 仓库地址,如下所示:
allprojects {
    repositories {
        ...
        maven { url "https://artifact.bytedance.com/repository/Volcengine/" }
    }
}
groovy
  1. 为模块配置构建配置。在 app/build.gradle 文件中添加配置和依赖。
dependencies {
    ...
    implementation 'com.volcengine:VolcEngineRTC:3.xx.xxx'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.72'// 使用 Maven 集成 3.37.1 ~ 3.42.1 版本 SDK 时,需要引入 kotlin,kotlin 版本号根据实际项目进行指定
}
groovy

最新的版本号参看下载 SDK。如果希望引入历史版本,请咨询技术支持人员。

离线方式:下载 SDK,并拷贝到项目目录下
  1. 将工程视图修改为 Project 模式,将下载好的 VolcEngineRTC.aar 文件放在 app/libs/ 目录下。
  1. 打开项目根路径下的 build.gradle 文件,在 allprojects 的 repositories 属性里面增加如下代码:
flatDir {
    dirs 'libs'
}

groovy

修改后的代码:

allprojects {
    repositories {
        flatDir {
            dirs 'libs'
        }
        google()
        jcenter()
    }
}

groovy
  1. 在 app 模块的 build.gradle 的 dependencies 中增加 aar 的依赖,代码如下:
dependencies {
    ...
		implementation(name: 'VolcEngineRTC', ext: 'aar')
    implementation "com.google.code.gson:gson:2.8.5"
    implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.72' // RTC 3.37.1 及以后版本 SDK 开始添加 kotlin 依赖。kotlin 版本号根据实际项目需要进行指定
    ...
}
groovy
步骤3:申请系统权限

根据实际应用需要,向系统申请所需权限。

进入 app/src/main 目录,在 AndroidManifest.xml 中添加如下权限。

<uses-permission	android:name="android.permission.INTERNET" />
<uses-permission	android:name="android.permission.RECORD_AUDIO" />
<uses-permission	android:name="android.permission.CAMERA" />
<!-- MODIFY_AUDIO_SETTINGS、BLUETOOTH 权限对于音频的路由是必须的。没有 BLUETOOTH 权限会导致蓝牙耳机连接时,声音从外放播放而不是蓝牙耳机-->
<uses-permission	android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission	android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission	android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission	android:name="android.permission.BLUETOOTH"	/>
<uses-permission	android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 获取手机网络状态的权限 -->
<uses-permission  android:name="READ_PHONE_STATE">
<!-- 仅在 App targetSDKVersion >= 31 时声明 BLUETOOTH_CONNECT 权限 -->
<!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
xml

如果 app targetSDKVersion >=31,除上述权限声明外,你必须在 Activity 中动态申请 BLUETOOTH_CONNECT 权限。参考以下代码进行申请:

private static final int PERMISSION_REQ_ID_RECORD_AUDIO = 22;// 可以任意值
private static final int PERMISSION_REQ_ID_CAMERA = PERMISSION_REQ_ID_RECORD_AUDIO + 1;
private static final int PERMISSION_REQ_ID_BLUETOOTH_CONNECT = PERMISSION_REQ_ID_CAMERA + 1;

// Check necessary permissions granted and
// if the permissions are not granted, use the built-in Android feature to request them.
private boolean checkSelfPermissions() {
    return  checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO) &&
            checkSelfPermission(Manifest.permission.CAMERA, PERMISSION_REQ_ID_CAMERA) &&
            // If targetSDKVersion >= 31 and want to use bluetooth headset, need request permission.
            checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT, PERMISSION_REQ_ID_BLUETOOTH_CONNECT);
}

public boolean checkSelfPermission(String permission, int requestCode) {
    if (PermissionChecker.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
        return false;
    }
    return true;
}
java
步骤4:实现音视频通话
基础音视频通话 API 时序图

alt

实现流程
(可选)创建用户界面

根据场景需要,为你的项目创建音视频通话的用户界面。

如果你想实现基本的音视频通话,我们建议你在项目中添加如下元素:

你也可以用如下示例代码在src/main/res/layout/activity_main.xml中进行替换,快速创建用户界面。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:background="@color/grey"
    tools:context="com.ss.video.rtc.demo.quickstart.RTCRoomActivity"
    tools:ignore="ContentDescription,RtlHardcoded,SpUsage">

    <FrameLayout
        android:id="@+id/title_bar"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/white"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/switch_camera"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="16dp"
            android:src="@drawable/switch_camera"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/self_video_container" />

        <TextView
            android:id="@+id/room_id_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:ellipsize="end"
            android:lines="1"
            android:maxLength="15"
            android:maxLines="1"
            android:singleLine="true"
            android:textColor="@color/black"
            android:textSize="18dp"
            tools:text="ID:userid" />

        <ImageView
            android:id="@+id/switch_audio_router"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_gravity="center_vertical|right"
            android:layout_marginRight="16dp"
            android:src="@drawable/speaker_on"
            app:layout_constraintLeft_toRightOf="@id/switch_camera"
            app:layout_constraintTop_toBottomOf="@id/self_video_container" />
    </FrameLayout>

    <FrameLayout
        android:id="@+id/bottom_bar"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@color/white"
        android:paddingBottom="10dp"
        app:layout_constraintBottom_toBottomOf="parent">

        <ImageView
            android:id="@+id/switch_local_audio"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="16dp"
            android:src="@drawable/normal_audio" />

        <ImageView
            android:id="@+id/hang_up"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_gravity="center"
            android:src="@drawable/hang_up" />

        <ImageView
            android:id="@+id/switch_local_video"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_gravity="center_vertical|right"
            android:layout_marginRight="16dp"
            android:src="@drawable/normal_video" />
    </FrameLayout>

    <FrameLayout
        android:id="@+id/self_video_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/remote_video_1_container"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/remote_video_0_container"
        app:layout_constraintTop_toBottomOf="@id/title_bar" />

    <TextView
        android:id="@+id/self_video_user_id_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/purple_700"
        android:textSize="14dp"
        app:layout_constraintBottom_toBottomOf="@id/self_video_container"
        app:layout_constraintRight_toRightOf="@id/self_video_container" />

    <FrameLayout
        android:id="@+id/remote_video_0_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/remote_video_2_container"
        app:layout_constraintLeft_toRightOf="@id/self_video_container"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/title_bar" />

    <TextView
        android:id="@+id/remote_video_0_user_id_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/purple_700"
        android:textSize="14dp"
        app:layout_constraintBottom_toBottomOf="@id/remote_video_0_container"
        app:layout_constraintRight_toRightOf="@id/remote_video_0_container" />

    <FrameLayout
        android:id="@+id/remote_video_1_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/bottom_bar"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/remote_video_2_container"
        app:layout_constraintTop_toBottomOf="@id/self_video_container" />

    <TextView
        android:id="@+id/remote_video_1_user_id_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/purple_700"
        android:textSize="14dp"
        app:layout_constraintBottom_toBottomOf="@id/remote_video_1_container"
        app:layout_constraintRight_toRightOf="@id/remote_video_1_container" />

    <FrameLayout
        android:id="@+id/remote_video_2_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/bottom_bar"
        app:layout_constraintLeft_toRightOf="@id/remote_video_1_container"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/remote_video_0_container" />

    <TextView
        android:id="@+id/remote_video_2_user_id_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/purple_700"
        android:textSize="14dp"
        app:layout_constraintBottom_toBottomOf="@id/remote_video_2_container"
        app:layout_constraintRight_toRightOf="@id/remote_video_2_container" />

</androidx.constraintlayout.widget.ConstraintLayout>
xml
  1. 创建引擎实例 createRTCVideo
public static synchronized RTCVideo RTCVideo.createRTCVideo(
    Context context,
    String appId,
    IRTCVideoEventHandler handler,
    Object eglContext,
    JSONObject parameters)
java
参数名类型说明
contextContextAndroid Application Context
appIdString每个应用的唯一标识符,由 RTC 控制台随机生成的。不同的 AppId 生成的实例在 RTC 中进行音视频通话完全独立,无法互通。
handlerIRTCVideoEventHandlerSDK 回调给应用层的 Handler,详见 IRTCVideoEventHandler
eglContextObject如果需要支持外部纹理硬编码,则需要以 JObject 方式传入 eglContext。
parametersJSONObject用以覆盖默认参数的本引擎实例参数。JSON 字符串格式。
  1. 设置视频编码参数 setVideoEncoderConfig
public int RTCVideo.setVideoEncoderConfig(
    VideoEncoderConfig max_solution)
java
参数名类型说明
max_solutionVideoEncoderConfig期望发布的最大分辨率视频流参数。参看 VideoEncoderConfig。
  1. 设置本地视频渲染视图 setLocalVideoCanvas
public int RTCVideo.setLocalVideoCanvas(
    StreamIndex streamIndex,
    VideoCanvas videoCanvas)
java
参数名类型说明
streamIndexStreamIndex视频流属性, 参看 StreamIndex
videoCanvasVideoCanvas视图信息和渲染模式, 参看 VideoCanvas
  1. 开始视频采集 startVideoCapture
public void RTCVideo.startVideoCapture()
java
  1. 开始音频采集 startAudioCapture
public void RTCVideo.startAudioCapture()
java
  1. 创建房间 createRTCRoom
public RTCRoom RTCVideo.createRTCRoom(
    String roomId)
java
  1. 监听房间回调事件 setRTCRoomEventHandler
public void RTCRoom.setRTCRoomEventHandler(
    IRTCRoomEventHandler rtcRoomEventHandler)
java
  1. 加入房间 joinRoom
public int RTCRoom.joinRoom(
    String token,
    UserInfo userInfo,
    RTCRoomConfig roomConfig)
java
参数名类型说明
tokenString动态密钥。用于对进房用户进行鉴权验证。 进入房间需要携带 Token。测试时可使用控制台生成临时 Token,正式上线需要使用密钥 SDK 在你的服务端生成并下发 Token。 使用不同 AppID 的 App 是不能互通的。 请务必保证生成 Token 使用的 AppID 和创建引擎时使用的 AppID 相同,否则会导致加入房间失败。
userInfoUserInfo用户信息。参看 UserInfo。
roomConfigRTCRoomConfig房间参数配置,设置房间模式以及是否自动发布或订阅流。具体配置模式参看 RTCRoomConfig。
  1. 处理房间状态改变回调 onRoomStateChanged
public void IRTCRoomEventHandler.onRoomStateChanged(
    String roomId,
    String uid,
    int state,
    String extraInfo)
java
参数名类型说明
roomIdString房间 ID。
uidString用户 ID。
stateint房间状态码。 • 0: 成功。 • !0: 失败,具体原因参看 ErrorCode 及 WarningCode。
extraInfoString额外信息。 joinType表示加入房间的类型,0为首次进房,1为重连进房。 elapsed表示加入房间耗时,即本地用户从调用 joinRoom 到加入房间成功所经历的时间间隔,单位为 ms。
  1. 处理用户加入房间回调 onUserJoined
public void IRTCRoomEventHandler.onUserJoined(
    UserInfo userInfo,
    int elapsed)
java
参数名类型说明
roomIdString房间 ID。
uidString用户 ID。
  1. 处理远端视频首帧解码的回调 onFirstRemoteVideoFrameDecoded
public void IRTCVideoEventHandler.onFirstRemoteVideoFrameDecoded(
    RemoteStreamKey remoteStreamKey,
    VideoFrameInfo frameInfo)
java
参数名类型说明
remoteStreamKeyRemoteStreamKey远端流信息,参看 RemoteStreamKey
frameInfoVideoFrameInfo视频帧信息,参看 VideoFrameInfo
  1. 设置远端视图 setRemoteVideoCanvas
public int RTCVideo.setRemoteVideoCanvas(
    String userId,
    StreamIndex streamIndex,
    VideoCanvas videoCanvas)
java
参数名类型说明
userIdString视频来源的远端用户 ID
streamIndexStreamIndex视频流属性,参看 StreamIndex
videoCanvasVideoCanvas视图信息和渲染模式,参看 VideoCanvas
  1. 处理用户离开房间回调 onUserLeave
public void IRTCRoomEventHandler.onUserLeave(
    String uid,
    int reason)
java
参数名类型说明
uidString离开房间,或切至不可见的的远端用户 ID。
reasonint用户离开房间的原因。
  1. 离开房间 leaveRoom
public void RTCRoom.leaveRoom()
java
  1. 销毁引擎实例 destroyRTCVideo
public static synchronized void RTCVideo.destroyRTCVideo()
java

至此,我们实现了基本的音视频通话。

常见问题

1. 关于混淆配置

在 RTC SDK 334+ 版本中已优化混淆规则,无需手动配置。
若使用之前版本,需要在混淆文件里面增加对应内容,详见 FAQ: 在开发 Android 应用时,如何进行混淆配置?

2. 使用硬件耳返功能如何配置依赖项?

如果你需要使用华为硬件耳返功能,建议通过 Maven 集成 RTC SDK,不需要手动添加耳返依赖。如果你通过离线方式集成 RTC SDK,添加耳返功能依赖方法详见使用硬件耳返的依赖配置。