add-live-streaming-to-your-android-app-using-agora-featured1024×512 121 KB
视频互动直播是当前比较热门的玩法,我们经常见到有PK 连麦、直播答题、一起 KTV、电商直播、互动大班课、视频相亲等。
本文将演示如何通过声网Agora 视频 SDK 在 Android 端实现一个视频直播应用。注册声网账号后,开发者每个月可获得 10000 分钟的免费使用额度,可实现各类实时音视频场景。
话不多说,我们开始动手实操。
一些前提条件
- Agora 开发者帐户(声网开发者注册指南)
- Android Studio
- Android 开发基础知识
一、 通过开源Demo,体验视频直播
可能有些人,还不了解我们要实现的功能最后是怎样的。所以我们在 GitHub上提供一个开源的基础视频直播示例项目,在开始开发之前你可以通过该示例项目体验视频直播的体验效果。
Github:GitHub - Meherdeep/agora-android-live-streaming 1
在这里,我添加了两个直播流,同时可以让多个观众订阅它。
二、 视频直播的技术原理
我们在这里要实现的是视频直播,Agora 的视频直播可以实现互动效果,所以也经常叫互动直播。你可以理解为是多个用户通过加入同一个频道,实现的音视频的互通,而这个频道的数据,会通过声网的 Agora SD-RTN 实时网络来进行低延时传输的。
需要特别说明的是,Agora互动直播不同于视频通话。视频通话不区分主播和观众,所有用户都可以发言并看见彼此;而互动直播的用户分为主播和观众,只有主播可以自由发言,且被其他用户看见。
下图展示在 App 中集成 Agora 互动直播的基本工作流程:
实现互动直播的步骤如下:
1.设置角色:互动直播频道中,用户角色可以是主播或者观众。主播在频道内发布音视频流,观众仅可订阅音视频流。
2.获取 Token:当 App 客户端加入频道时,你需要通过 Token 验证用户身份。App 客户端向 App 服务器发送请求,并获取 Token,然后在客户端加入频道时验证用户身份。
3.加入频道:调用 joinChannel 创建并加入频道。使用同一频道名称的 App 客户端默认加入同一频道。
4.在频道内发布和订阅音视频:加入频道后,角色为主播的 App 客户端可以发布音视频。对于角色为观众的客户端,如果想要发布音视频,可以调用 setClientRole 切换用户角色。
App 客户端加入频道需要以下信息:
- 频道名称:用于标识直播频道的字符串。
- App ID:Agora 随机生成的字符串,用于识别你的 App,可从 Agora 控制台获取,(Agora控制台链接:Dashboard
- 用户ID:用户的唯一标识。你需要自行设置用户 ID,并确保它在频道内是唯一的。
- Token:在测试或生产环境中,你的 App 客户端会从你的服务器中获取 Token。为方便快速测试,你也可以获取临时 Token。临时 Token 的有效期为 24 小时。
三、 开发环境
声网Agora SDK 的兼容性良好,对硬件设备和软件系统的要求不高,开发环境和测试环境满足以下条件即可:
• Android SDK API Level >= 16
• Android Studio 2.0 或以上版本
• 支持语音和视频功能的真机
• App 要求 Android 4.1 或以上设备
以下是本文的开发环境和测试环境:
开发环境
• Windows 10 家庭中文版
• Java Version SE 8
• Android Studio 3.2 Canary 4
测试环境
• Samsung Nexus (Android 4.4.2 API 19)
• Mi Note 3 (Android 7.1.1 API 25)
如果你此前还未接触过声网 Agora SDK,那么你还需要做以下准备工作:
• 注册一个声网账号,进入后台创建 AppID、获取 Token,详细方法可参考这篇教程;(这篇教程:404 - 知乎
• 下载声网官方最新的互动直播SDK;(互动直播SDK链接:下载 - 全部产品 - 文档中心 - 声网Agora
四、 项目设置
1. 实现互动直播之前,参考如下步骤设置你的项目:
如需创建新项目,在 Android Studio里,依次选择 Phone and Tablet > Empty Activity,创建 Android 项目。(创建 Android 项目链接:https://developer.android.com...)
创建项目后,Android Studio会自动开始同步 gradle。请确保同步成功再进行下一步操作。
2. 集成SDK, 本文推荐使用gradle方式集成Agora SDK:
a. 在 /Gradle Scripts/build.gradle(Project: ) 文件中添加如下代码,以添加 jcenter依赖:
buildscript {
repositories {
...
jcenter()
}
...
}
allprojects {
repositories {
...
jcenter()
}
}
b. 在 /Gradle Scripts/build.gradle(Module: .App) 文件中添加如下代码,将 Agora 视频 SDK 集成到你的 Android 项目中:
...
dependencies {
...
// x.y.z,请填写具体的 SDK 版本号,如:3.5.0。
// 通过发版说明获取最新版本号。
implementation 'io.agora.rtc:full-sdk:x.y.z'
//本例使用布局相关设置constraintlayout
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
}
3. 权限设置
在 /App/Manifests/AndroidManifest.xml 文件中的 `` 后面添加如下网络和设备权限:
4. 导入Agora相关的类
在/app/src/main/java/com/agora/samtan/agorabroadcast/VideoActivity文件中,加入如下代码:
package com.agora.samtan.agorabroadcast;
import io.agora.rtc.Constants;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;
5. 设置Agora账号信息
在/app/src/main/res/values/strings.xml文件中,将你的AppID填写到private_App_id中:
……
填写位置
……
五、 客户端实现
本节介绍如何使用Agora视频SDK在你的App里实现视频直播的几个小贴士:
1. 检查并获取必要权限
启动应用程序时,检查是否已在App中授予了实现视频直播所需的权限。在onCreate函数中调用如下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int MY_PERMISSIONS_REQUEST_CAMERA = 0;
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, MY_PERMISSIONS_REQUEST_CAMERA);
}
}
2. 实现互动直播逻辑
打开你的App,创建RtcEngine实例,启用视频后加入频道。如果本地用户是主播,则将本地视频发布到用户界面下方的视图中。如果另一主播加入该频道,你的App会捕捉到这一加入事件,并将远端视频添加到用户界面右上角的视图中。
互动直播的API使用时序见下图:
按照以下步骤实现该逻辑:
a) 初始化RtcEngine
RtcEngine类包含应用程序调用的主要方法,调用RtcEngine的接口最好在同一个线程进行,不建议在不同的线程同时调用。
目前Agora Native SDK只支持一个RtcEngine实例,每个应用程序仅创建一个RtcEngine对象。RtcEngine类的所有接口函数,如无特殊说明,都是异步调用,对接口的调用建议在同一个线程进行。所有返回值为int型的API,如无特殊说明,返回值0为调用成功,返回值小于0为调用失败。
在VideoActivity文件中,通过initializeAgoraEngine用于初始化RtcEngine的方法:
private void initalizeAgoraEngine() {
try {
mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.private_App_id), mRtcEventHandler);
} catch (Exception e) {
e.printStackTrace();
}
}
另外,有个重要的IRtcEngineEventHandler接口类用于SDK向应用程序发送回调事件通知,应用程序通过继承该接口类的方法获取SDK的事件通知。
接口类的所有方法都有缺省(空)实现,应用程序可以根据需要只继承关心的事件。在回调方法中,应用程序不应该做耗时或者调用可能会引起阻塞的API(如SendMessage),否则可能影响SDK的运行。内容如下:
private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler()
{
/**Reports a warning during SDK runtime.
* Warning code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_warn_code.html*/
@Override
public void onWarning(int warn)
{
Log.w(TAG, String.format("onWarning code %d message %s", warn, RtcEngine.getErrorDescription(warn)));
}
/**Reports an error during SDK runtime.
* Error code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html*/
@Override
public void onError(int err)
{
Log.e(TAG, String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
showAlert(String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
}
/**Occurs when a user leaves the channel.
* @param stats With this callback, the Application retrieves the channel information,
* such as the call duration and statistics.*/
@Override
public void onLeaveChannel(RtcStats stats)
{
super.onLeaveChannel(stats);
Log.i(TAG, String.format("local user %d leaveChannel!", myUid));
showLongToast(String.format("local user %d leaveChannel!", myUid));
}
/**Occurs when the local user joins a specified channel.
* The channel name assignment is based on channelName specified in the joinChannel method.
* If the uid is not specified when joinChannel is called, the server automatically assigns a uid.
* @param channel Channel name
* @param uid User ID
* @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered*/
@Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed)
{
Log.i(TAG, String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
myUid = uid;
joined = true;
handler.post(new Runnable()
{
@Override
public void run()
{
join.setEnabled(true);
join.setText(getString(R.string.leave));
}
});
}
@Override
public void onRemoteAudioStats(io.agora.rtc.IRtcEngineEventHandler.RemoteAudioStats remoteAudioStats) {
statisticsInfo.setRemoteAudioStats(remoteAudioStats);
updateRemoteStats();
}
@Override
public void onLocalAudioStats(io.agora.rtc.IRtcEngineEventHandler.LocalAudioStats localAudioStats) {
statisticsInfo.setLocalAudioStats(localAudioStats);
updateLocalStats();
}
@Override
public void onRemoteVideoStats(io.agora.rtc.IRtcEngineEventHandler.RemoteVideoStats remoteVideoStats) {
statisticsInfo.setRemoteVideoStats(remoteVideoStats);
updateRemoteStats();
}
@Override
public void onLocalVideoStats(io.agora.rtc.IRtcEngineEventHandler.LocalVideoStats localVideoStats) {
statisticsInfo.setLocalVideoStats(localVideoStats);
updateLocalStats();
}
@Override
public void onRtcStats(io.agora.rtc.IRtcEngineEventHandler.RtcStats rtcStats) {
statisticsInfo.setRtcStats(rtcStats);
}
};
所以,在我们的initialize函数中,我们将mRtcEventHandler作为参数之一传递给了create方法,这设置了一系列回调事件,每当用户加入频道或离开频道时就会触发这些事件。
private IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
@Override
public void onUserJoined(final int uid, int elapsed) {
super.onUserJoined(uid, elapsed);
runOnUiThread(new Runnable() {
@Override
public void run() {
setupRemoteVideo(uid);
}
});
}
@Override
public void onUserOffline(int uid, int reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
onRemoteUserLeft();
}
});
}
};
b) 设置频道场景和角色
setChannelProfile()是一个使用我们AgoraRtcEngine对象引用的方法。Agora提供了各种配置文件,可以通过该方法调用并集成到应用中。
setClientRole()方法,将用户的角色设置为主播或观众(默认)。这个方法应该在加入频道之前调用。加入频道后可以再次调用,切换客户端角色。
为了方便体验互动直播中主播角色和观众角色的效果,我们将在我们的MainActivity类中添加两个方法:
• 当用户从单选按钮中选择一个选项时,将调用第一个方法。我们将相应地设置一个变量。我们将其设置为一个值,该值将确定用户是主播还是观众。
public void onRadioButtonClicked(View view) {
boolean checked = ((RadioButton) view).isChecked();
switch (view.getId()) {
case R.id.host:
if (checked) {
channelProfile = Constants.CLIENT_ROLE_BROADCASTER;
}
break;
case R.id.audience:
if (checked) {
channelProfile = Constants.CLIENT_ROLE_AUDIENCE;
}
break;
}
}
• 然后我们实现一个在用户提交详细信息时调用的函数。在这里,我们将获得我们需要的所有详细信息,并将它们发送到下一个activity。
public void onSubmit(View view) {
EditText channel = (EditText) findViewById(R.id.channel);
String channelName = channel.getText().toString();
Intent intent = new Intent(this, VideoActivity.class);
intent.putExtra(channelMessage, channelName);
intent.putExtra(profileMessage, channelProfile);
startActivity(intent);
}
c) 开始视频
setupVideoProfile()函数用于定义视频需要渲染的方式。你可以对帧速率、比特率、方向、镜像模式和降级偏好等属性使用自己的自定义配置。
private void setupVideoProfile() {
mRtcEngine.enableVideo();
mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(VideoEncoderConfiguration.VD_640x480, VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));
}
d) 设置本地视频
setupLocalVideo()函数用于从我们的AgoraRtcEngine中引用setupLocalVideo方法,我们通过它为我们的本地用户设置一个在直播流中使用的表面视图:
private void setupLocalVideo() {
FrameLayout container = (FrameLayout) findViewById(R.id.local_video_view_container);
SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());
surfaceView.setZOrderMediaOverlay(true);
container.addView(surfaceView);
mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
}
e) 加入频道
频道是人们在同一个视频通话中的公共空间。joinChannel()方法可以这样调用:
private void joinChannel() {
mRtcEngine.joinChannel(token, channelName, "Optional Data", 0);
}
该方法需要四个参数才能成功运行:
• Token:建议对在生产环境中运行的所有RTE APP进行Token身份验证。更多关于声网Agora平台基于令牌的认证信息,请参见https://docs.agora.io/cn/Vide...。
• 频道名称:需要一个字符串,让用户进入视频通话。
• 可选信息:这是一个可选字段,你可以通过它传递有关频道的其他信息。
• uid:每个加入频道的用户的唯一ID。如果传入0或null值,Agora会自动为每个用户分配一个uid。
注意:此项目仅供参考和开发环境使用,不适用于生产环境。建议对在生产环境中运行的所有RTE APP进行Token身份验证。
本例中初始化App,调用核心方法来创建并加入Agora直播频道。在VideoActivity文件中,在onCreate函数后添加如下代码:
package com.agora.samtan.agorabroadcast;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View; ;//;.;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.Appcompat.App.AppCompatActivity;
import io.agora.rtc.Constants;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;
public class VideoActivity extends AppCompatActivity {
private RtcEngine mRtcEngine;
private String channelName;
private int channelProfile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video);
Intent intent = getIntent();
channelName = intent.getStringExtra(MainActivity.channelMessage);
channelProfile = intent.getIntExtra(MainActivity.profileMessage, -1);
if (channelProfile == -1) {
Log.e("TAG: ", "No profile");
}
initAgoraEngineAndJoinChannel();
}
}
我们宣布了一个名为initAgoraEngineAndJoinChannel的方法,它将调用直播过程中所需的所有其他方法。我们还定义了事件处理程序,它将决定当远程用户加入或离开或静音时调用哪些方法。
private void initAgoraEngineAndJoinChannel() {
initalizeAgoraEngine();
mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
mRtcEngine.setClientRole(channelProfile);
setupVideoProfile();
setupLocalVideo();
joinChannel();
}
f) 当远端主播加入频道时添加远端界面
在VideoActivity文件中,initializeAndJoinChannel函数后加入如下代码:
private void setupRemoteVideo(int uid) {
FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container);
SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());
container.addView(surfaceView);
mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
}
g) 释放资源
最后,我们添加onDestroy方法来释放我们使用过的资源。相关代码如下:
@Override
protected void onDestroy() {
super.onDestroy();
leaveChannel();
RtcEngine.destroy();
mRtcEngine = null;
}
至此,完成,运行看看效果。拿两部手机安装编译好的App,加入同一个频道名,分别选择主播角色和观众角色,如果2个手机都能看见同一个自己,说明你成功了。
如果你在开发过程中遇到问题,可以访问论坛提问与声网工程师交流(链接:https://rtcdeveloper.agora.io/) 1
也可以访问后台获取更进一步的技术