
1. 概述
本章节主要介绍实时音视频通话的背景以及视界音视频引擎在实时通话方面的功能亮点.
1.1. 背景介绍
目前,随着音视频技术的日渐成熟,音视频通话应用的领域也越来越广泛,包括直播连麦、低延迟直播、在线教育、视频社交、视频教学、多人会议、游戏语音组队等领域。
使用视界多人音视频通话引擎,所有用户都需要加入同一个房间,进入这个房间的用户都可以将自己的音视频流推送到这个房间。同时,房间里的所有人也可以拉取这个房间中其他人推送的流。通过房间推拉流实现多人实时音视频通话或低延迟直播。
同时,视界音视频引擎还支持服务端音视频流混流、视频存档、转推传统RTMP CDN等功能,以下是示意图:
1.2. 产品能力
视界音视频引擎提供稳定流畅、高品质、多平台、高实时的点对点和多人实时音视频通话服务,服务端采用级联技术,房间最大人数无理论上限,实际取决于终端性能(Mi6经测试可支持9人同时进行640x360通话)。同时,用户可以通过设置加入房间角色为主播方式进行视频直播,主播可以与任一观众随时进行连麦,发起过程中观众无需退出直播房间。
功能亮点:
稳定流畅
多地域BGP机房节点部署,传输通道路径智能优化,根据网络状态实时动态调整网络路径,保障跨网、跨地域稳定传输;智能网络状况探测,码率动态调整,保证弱网下的音视频通话流畅稳定。
高品质
视频采用可伸缩分层(SVC)视频编码,保证在高丢包情况下视频依然清晰流畅。
音频支持16k、48k单双声道音频传输,保证连麦音乐传输效果;基于机器学习的语音前处理算法,智能降噪、回音消除、自动增益、人声检测、啸叫处理等,以提升声音的真实性,引擎还特别作出了针对音乐场景的特殊优化。
高可靠性 云端服务集群自动检测机器状态,机器单体故障时,会自动将用户调度切换到无故障机器,切换过程中做到用户卡顿时间小于200ms。
美颜、变声 支持美颜磨皮、瘦脸、大眼等视频美化处理,背景音乐混音、变声等声音处理。
API灵活性 在常见的屏幕共享、电台、图片共享等场景中,需要支持同时接收/发送多个音视频流。本SDK支持摄像头读取/麦克风采集、APP上层提供音视频流的方式,能灵活满足各种应用场景需求。
多平台支持 支持Android、iOS、Linux、Windows、Mac平台
下面主要介绍多人实时音视频通话的业务流程。
2. 多人音视频通话
多人音视频通话,需要用户先加入同一房间,用户将音视频流推到房间,其它加入房间的用户可以得到新流加入的通知,并通过请求、拉取房间内媒体流方式实现多人进行实时音视频通话。
本章节主要讲述多人实时音视频通话相关内容,包括功能实现流程、核心功能代码示例、API调用流程图。
2.1. 功能实现流程
同一房间内多人之间进行音(视)频通话通过推拉流实现,下面以用户user2加入房间room_123为例说明事件发生流程。
- 为说明流程,假设user2进入房间时,user1此时已经完成进入房间;
- user2调用JoinRoom进入房间时,user2除本地成功加入事件(
OnLocalUserJoined
)外,其它流程基本与user1一致; - 如果会议中还有其它用户在房间,user2将会收到除user1以外的其它用户事件;
- 图中蓝色部分需要上层APP调用,黑色部分事件为收到的事件;
流程及API调用次序说明:
- 本地打开设备并加入房间:客户user2加入房间room_123,user2加入成功后将收到本地加入成功回调(
OnLocalUserJoined
); - 收到远端人员信息:客户user1收到user2的
OnRemoteUserJoined
事件,同时user2也会收到user1的OnRemoteUserJoined
事件; - 收到远端视频:user1与user2收到音视频流,同时收到相应的
OnFirstVideoFrameReceived
事件; - 绑定RenderView:此后user1、user2可以通过
RenderManager
绑定视频流的渲染控件RenderView
;也可以通过相应接口控制音频流音量;RenderManager
API请见相关文档;
在整个user2加入过程中,上层APP
只需要调用JoinRoom
函数并响应相应事件跟新UI即可。
以下章节将从Demo代码角度讲解主要实时通话的主要逻辑。Demo源码下载地址
2.2. 核心功能代码示例
Android平台
多人实时音视频通话,房间内每个用户的代码基本一致,下面仅以用户user1加入房间room_123为例进行说明.
1. 初始化系统
这部分逻辑将初始化系统,整个程序生命周期只需要执行一次
xxxxxxxxxx
// 初始化系统,并提供注册时生成的AppId(整数格式)
SystemUtil::Init(self.getApplicationContext(), XXXX/*<your-app-id>*/);
//DeviceManager设备管理类初始化
DeviceManager deviceManager = new DeviceManager(self.getApplicationContext());
//RenderManager渲染管理类初始化
RenderManager renderManager = new RenderManager(self.getApplicationContext());
2. 初始化房间
x//RoomCallback初始化(房间事件监听)
RoomCallback roomCallback = new RoomCallback() {
...
};
//Room 管理类初始化
Room room = new Room(self.getApplicationContext(), deviceManager.getNativeInstance());
room.attachCallback(roomCallback);
3.加入房间(用户user1登陆房间”room_123”)
xxxxxxxxxx
//以用户user1加入房间"room_123"为例,部分代码如下:
final String roomId = “room_123”;
Room.Profile profile = new Room.Profile();
profile.joinWithoutVideo = false;
profile.videoWidth = 360; //竖屏模式,360x640分辨率
profile.videoHeight = 640;
profile.clientRole = ClientRole.CLIENT_ROLE_ATTENDEE;
room.joinRoom(roomId,
"user1",
profile,
"your_app_token");
// 启动音视频设备
deviceManager.startAudioDevice();
deviceManager.setSpeaker(true);
deviceManager.startCamera();
更多用户角色说明如下:
用户角色 | 描述 |
---|---|
CLIENT_ROLE_COHOST | 直播场景;cohost(主播或嘉宾)角色 |
CLIENT_ROLE_VIEWER | 直播场景;观众角色 |
CLIENT_ROLE_ATTENDEE | 实时通话场景;参会者,普通音视频通话场景,参会者只会能看到参会者,不能看到主播和嘉宾 |
Cohost:Cohost之间可以互相实时看到对方画面,Cohost看不到观众画面。一个房间可以同时存在多个Cohost,产品需求中的嘉宾可以通过Cohost角色加入实现。同时,最后一个Cohost退出房间时,房间将被销毁,同一房间的观众将会被动断开;
观众:观众可以看到多个Cohost(主播和嘉宾)画面,观众之间互相不可见;
参会者:参会者角色CLIENT_ROLE_ATTENDEE
主要用于实时通话场景,此角色与其它直播角色(包括:CLIENT_ROLE_COHOST
、CLIENT_ROLE_VIEWER
)在系统内部是互相隔离的。开发者可以认为他们加入的是不同的类型的房间,因此不能互相看见对方;
4.推拉流进行音(视)频通话
登陆成功,用户user1默认自动推音(视)频流到房间room_123,同时自动拉取房间内其他成员的音(视)频流
xxxxxxxxxx
//RoomCallback
public void onFirstVideoFrameReceived(String userId) {
Point size = new Point(300, 400);
//创建render
VideoView render = renderManager.createRender(size);
render.setVisibility(View.VISIBLE);
render.setOrderMediaOverlay(true);
//绑定render和userId, 渲染显示
rendermanager.bindRenderWithStream(render, userId);
}
5.结束音(视)频通话
离开会议,停止推拉流.
xxxxxxxxxx
//结束通话, 用户user1停止推、拉音(视)频流。
room.leaveRoom();
xxxxxxxxxx
//RoomCallback 内部实现
//收到房间内其他用户流移除的回调
public void onRemoteVideoStreamRemoved(String uid, String streamId) {
VideoView render = renderManager.getRender(uid);
if (render != null) {
render.setVisibility(View.INVISIBLE);
//解绑render和userId,停止渲染
renderManager.unbindRenderWithStream(render);
//删除render
renderManager.destroyRender(render);
}
}
6.释放资源
xxxxxxxxxx
room.destroy();
deviceManager.destroy();
renderManager.destroy();
2.3. API调用流程图
以下流程图以user1加入房间为例,假设以下情况:
- user2、user3已经加入房间,并且每个用户都有音频、视频,即
joinRoom
时joinWithoutVideo = false
; - user1相关流程在图左侧,user2相关流程在右侧;user3收到的事件和流程基本与user2完全一致,因此省略;
3. 常用功能
3.1 麦克风静音
- 静音麦克风:
xxxxxxxxxx
// Android platform
room.muteMicrophone();
// iOS platform
[room muteMicrophone];
// Win32/Linux/Mac (C++)
room.muteMicrophone();
- 取消麦克风静音:
// Android platform
room.unMuteMicrophone();
// iOS platform
[room unMuteMicrophone];
// Win32/Linux/Mac (C++)
room.unMuteMicrophone();
3.2 暂停视频发送
- 暂停本地视频发送
// Android platform
room.MuteVideo();
// iOS platform
[room MuteVideo];
// Win32 platform
room.MuteVideo();
- 恢复本地视频发送
// Android platform
room.unMuteVideo();
// iOS platform
[room unMuteVideo];
// C++ (Win32/Mac/Linux) platform
room.unMuteVideo();
3.3 设置/获取用户音量
- 设置用户音量
// Android platform
room.setUserPlayoutVolume(userId, volume); // volume为双精度型,范围在[0.0-1.0]
// iOS platform
[room setUserPlayoutVolume:userId volume:volumeValue]; // volume为双精度型,范围在[0.0-1.0]
// C++ (Win32/Mac/Linux) platform
room.setUserPlayoutVolume(userId, volume); // volume为双精度型,范围在[0.0-1.0]
- 获取用户音量
// Android platform, volume will be [0.0, 1.0]
double volume = room.getUserPlayoutVolume(userId);
// iOS platform, volume will be [0.0, 1.0]
double volume = [room getUserPlayoutVolume];
// Win32 platform, volume will be [0.0, 1.0]
double volume = room.getUserVolume();
4. 常见问题
4.1 常见错误类型
设备类型错误,通过DeviceCallback
回调给用户:
错误类型 | 描述 |
---|---|
onMicStartFailed | 打开麦克风失败,请检查麦克风是否被占用 |
onCameraStartFailed | 摄像头打开失败,请检查摄像头是否被占用 |
房间错误,通过RoomCallback
的onError
回调返回:
错误类型 | 描述 |
---|---|
ENGINE_NO_ERROR | 系统工作正常,无错误 |
ENGINE_CONNECTION_FAILED | 连接服务器失败 |
ENGINE_CONNECTION_LOST | 与服务器断开连接 |
ENGINE_CONFERENCE_PARAM_INVALID | 会议参数非法 |
ENGINE_UNKNOWN_ERROR | 未知错误 |
4.2. AEC(回声消除)问题
如下图所示,在实时音视频通话中,如果没有回声消除模块(AEC)模块,扬声器播放的声音将会与本地人说话的声音混在一起被采集进去并发送到远端。如果两端都不能很好处理回声问题,则会产生啸叫,极度影响用户体验。
回声问题是整个音频系统的重要问题,因为与回声路径有关,因此也与机器型号有关。视界实时音视频通话方案,回声处理模块,采用AI技术,做到Android机型零适配,效果赶超硬件AEC。
同时,视界实时通话系统,采用硬件AEC与软件AEC混合使用的方式,给用户提供最佳体验。
另外,请注意:以下问题并不是回声模块处理范围:
- 两手机距离太近,导致一款手机播放的声音被另一台手机采集进去产生啸叫,请将手机距离拉开;
注:iOS平台: 默认使用硬件AEC; Android平台: 以下机型使用硬件AEC,非以下机型默认使用软件AEC;
"mi 3","mi 4", "mi 5", "mi 5s", "mi 5s plus", "mi 6", "mi max",
"mi 4w","mi 4lte", "mi 8",
"mi note","mi note pro","mi note lte", "mi note 2", "mi note 3",
"mix","mix 2", "mi mix3",
"redmi note 2","redmi note 3", "redmi note 4", "redmi note 5a", "redmi 5",
"redmi pro","redmi 5 plus","redmi note 6", "redmi 5a", "redmi note 5",
"mi-4c", "hm note 1s","hm note 1lte",
"redmi 3","redmi 4", "redmi 4a",
"redmi y2","redmi s2", "mi a2", "mi a2 lite", "tiare", "perseus",
"platina", "ONC", "beryllium", "e6", "tulip", "cereus", "dipper",
"mi pad",
"skr-a0",
"mp1503",
"natrium", "scorpio", "rolex", "mido", "2014813","sagit", "chiron", "riva",
"vivo x6s", "vivo x6s a", "vivo x7", "vivo y55", "vivo y55l", "vivo y66", "vivo y66l", "vivo x9",
"vivo x9s","vivo v3max","vivo x20", "vivo xplay5a","vivo x6",
"asus_z012da",
"huawei caz-al10", "huawei nxt-al10","huawei gra-ul00","huawei p7-l07",
"eva-al00",
"oppo r9","oppo r9s","oppo r9m","oppo a33","oppo a33m","oppo r11","oppo r11s",
"oppo a59","oppo a59m","oppo a59s","oppo a59st","oppo a53",
“sm-g9280","sm-g9008w"