【音视频】基于声网的多人视频通话功能建设
背景
随着越来越多的人们习惯于有事直接发起微信语音通话/视频通话,实时音视频在 IM 场景扮演了越来越重要的角色。IM 的核心服务功能除了有消息传输、会话/消息管理、群组管理、推送服务外,还有包含短语音消息、短视频消息、一对一实时语音通话、一对一实时视频通话、语音呼叫电话能力、多对对实时音视频通话的音视频能力。最近几年一直在建设 IM 的音视频能力,今天以声网视频通话 SDK 为实时音视频通道,分享 IM 场景的多人视频通话功能建设。
需求
功能上包含两大块:
可发起/邀请多人通话
多人音视频通话保持
UI 交互上核心能力:
可九宫格展示用户画面,可左右滑动展示更多;
可点击大屏展示某个用户画面;
可随着用户加入/退出通话,动态增删画面;
用户摄像头未开启,显示用户头像,并不可点击切换到大屏模式;
被大屏观看的用户退出通话,自动切换到九宫格模式。
实现
实现部分主要分三块:
发起呼叫信令通道
实时音视频通道
UI 实现
通话建立信令通道
通道类型
发起视频通话,需要将邀请通话的指令分别发送给被邀请用户。这就要求我们的 APP 本身均在线(系统 push 触达的先不讨论),并且有和服务器保持连接的长连接通道。常用的方式有信令通道有:
基于 TCP 的自定义协议的 Socket 通道;
基于 WebSocket 的通道;
基于 Http2 的通道;
基于 Http 轮询通道
基于 UDP 自定义协议的 Socket 通道
因为是基于 IM 的音视频通话,直接复用了 IM 的信令通道能力,以减少应用端口占用。
通话信令
一个完整的通话过程需要哪些信令支撑呢?
基础功能信令
发起呼叫/邀请其他人信令:将通话信令从主叫通知到被叫;
接听信令:被叫接受呼叫信令;
拒接信令:被叫拒绝接听信令;
终止呼叫信令:主叫取消通话邀请;
挂断信令:主叫/被叫任意一方中断通话。
异常保证信令
基础功能信令只能保证在正常 case 下跑通,如何保证通信过程健壮性呢?比如:
如何确保主叫的呼叫信令已经发送到 server?
发起呼叫指令如果没有到达被叫怎么办?如果使用增加服务端持续呼叫手段怎么避免重复信令?
如何确保双方都能正常加入多媒体房间?
呼叫过程对方已经再跟别人通话怎么处理?
如何保证在通话过程中某一方多媒体通道掉线,对方无效等待?
为了解决上述问题我们需要引入一些辅助信令:
增加呼叫响应信令,只有主叫接收到呼叫响应信令才算是呼叫请求信令成功发送到 server;
增加呼叫确认信令,server 相隔固定时间持续发送主叫请求信令给被叫,直到收到被叫呼叫确认信令。被叫需要根据呼叫 ID 过滤掉重复的呼叫请求信令。主叫收到呼叫确认信令后可以在呼叫页面做 UI 更新,比如刷新提示文字“呼叫中”为“等待被叫接听”。
增加主叫接通信令:为了避免呼叫过程中过早加入多媒体房间产生无效内容,在主叫收到被叫接听信令后再加入多媒体通道,加入多媒体通道成功后,发送主叫接听信令给被叫,被叫收到后再加入多媒体通道。主叫先加入多媒体通道,而不是被叫点击接听后就加入多媒体通道,可以避免接听信令发送失败被叫产生的额外多媒体房间无效内容。
增加忙线信令:如果被叫正在通话中,收到其他呼叫请求信令,则发送忙线信令。
增加心跳信令与心跳异常信令:通话建立后,每个 t 秒持续向 server 发送心跳信令,并持续接收 server 返回的心跳信令,如果持续 n 次发送或者接收心跳失败,则判断通话异常,退出通话。
除此之外,还需要一些额外的定时器处理超时任务。
UI 关联信令
在 UI 上的一些交互也需要我们借助信令通道触达对方:
状态广播信令:退出房间用户通知到其他用户;
通知信令:开关摄像头通知其他用户。
信令状态实现
上面介绍了要实现一个完整通话需要十多个信令指令,维护主被叫各种状态下的信令变成了一个令人头疼的事情。我们先将通话建立过程抽象为空闲态、接通态、主叫呼叫发送中态、主叫等待 1、主叫等待 2、被叫待接听、被叫连接中七种状态,我们用一张图来描述引起状态变化的信令:
捋清楚状态之间的逻辑,我们可以基于状态+命令设计模式进行实现。状态接口定义各状态行为:
发送呼叫信令
接收呼叫请求信令
收到呼叫响应信令
发送呼叫确认信令
接收呼叫确认信令
...
实时音视频通道
多媒体通道我们基于声网视频通话 SDK 实现:
我们主要关心:
设置本地视频
加入频道
监听远端用户进入
setupRemoteVideo 与 setupLocalVideo 都需要传入 VideoCanvas 类,VideoCanvas 封装了 SurfaceView,我们需要背本地视频与每个远端用户画面创建一个 SurfaceView。RtcEngine 为我们提供了 CreateRendererView 方法用于构建 SurfaceView,也可以使用 CreateTextureView 创建 TextureView。
总结一下,我们用到的 API:
RtcEngine.create
RtcEngine.joinChannel
RtcEngine.leaveChannel
RtcEngine.enableLocalAudio
RtcEngine.setupLocalVideo
RtcEngine.setupRemoteVideo
RtcEngine.createRenderView
RtcEngine.createTextureView
RtcEngine.startPreview
IRtcEngineEventHandler.onJoinChannelSuccess
IRtcEngineEventHandler.onUserJoined
详细可查阅声网视频通话API参考
UI 实现
UI 层主要负责管理多人画面的 SurfaceView、全屏和九宫格切换逻辑、以及开关麦克风/摄像头引起的 UI 刷新。这里使用 Android 系统提供的 RecyclerView 实现九宫格及九宫格画面滑动。堆叠模式时隐藏 RecyclerView,并将要显示对方 SurfaceView 与自己画面 SurfaceView 拿出进行绘制。UI 实现因为一些交互实现,逻辑还是比较复杂,这里不进行展开讨论。
问题
走到这里终于可以完成多人视频通话了,跑起来后效果还不错。QA 测试时却出来一个头疼的问题:容易掉线。而且是在视频画面正常情况下突然掉线。定位日志后发现,是心跳信令发送/接收失败引起的。为什么视频画面还正常的情况下信令连接却先除了问题呢?
分析 QA 复现问题的环境:工位摆了十几个手机同时进入同一个房间,时间不长就与用户连接失败;把十几个手机分散的放到各个地方,情况有了明显好转。初步定位是网络引起的,工位所有手机连到同一个热点,导致网络环境变差。
但是为什么视频画面是可以的呢?因为音视频通道基于 UDP,而我们的信令通道基于 TCP,UDP 经过封装优化,增加了弱网对抗能力,在弱网环境下 UDP 的视频通道会抢占整体带宽,导致 TCP 通道更差,甚至连不上。
定位到问题后就要着手解决了,这里提供几个思路:
UI 不展示的用户画面不拉流,降低下行带宽压力;
限制最多通话人数;
降低视频码率;
替换 TCP 通道为 UDP 通道。
声网云信令 SDK 有基于 UDP 的通道,帮助提升 RTM 弱网对抗。具体参考云信令文档.特别是在 1.4.0 版本优化了弱网对抗能力,提高了弱网环境下的登录成功率和消息投递成功率,优化了重连机制。
总结
本文介绍了基于声网视频通话 SDK 实现的 IM 场景多人视频通话功能。主要介绍了信令通道、多人视频通话用到的信令、声网视频 SDK 接口以及弱网情况下信令通道掉线问题解决。
版权声明: 本文为 InfoQ 作者【轻口味】的原创文章。
原文链接:【http://xie.infoq.cn/article/fd97b78187b01905ccedea690】。文章转载请联系作者。
评论