【音视频】基于声网的多人视频通话功能建设

【音视频】基于声网的多人视频通话功能建设

背景

随着越来越多的人们习惯于有事直接发起微信语音通话/视频通话,实时音视频在IM场景扮演了越来越重要的角色。IM的核心服务功能除了有消息传输、会话/消息管理、群组管理、推送服务外,还有包含短语音消息、短视频消息、一对一实时语音通话、一对一实时视频通话、语音呼叫电话能力、多对对实时音视频通话的音视频能力。最近几年一直在建设IM的音视频能力,今天以声网视频通话SDK为实时音视频通道,分享IM场景的多人视频通话功能建设。

需求

功能上包含两大块:

  1. 可发起/邀请多人通话
  2. 多人音视频通话保持

UI交互上核心能力:

  1. 可九宫格展示用户画面,可左右滑动展示更多;
  2. 可点击大屏展示某个用户画面;
  3. 可随着用户加入/退出通话,动态增删画面;
  4. 用户摄像头未开启,显示用户头像,并不可点击切换到大屏模式;
  5. 被大屏观看的用户退出通话,自动切换到九宫格模式。



实现

实现部分主要分三块:

  1. 发起呼叫信令通道
  2. 实时音视频通道
  3. UI实现

通话建立信令通道

通道类型

发起视频通话,需要将邀请通话的指令分别发送给被邀请用户。这就要求我们的APP本身均在线(系统push触达的先不讨论),并且有和服务器保持连接的长连接通道。常用的方式有信令通道有:

  1. 基于TCP的自定义协议的Socket通道;
  2. 基于WebSocket的通道;
  3. 基于Http2的通道;
  4. 基于Http轮询通道
  5. 基于UDP自定义协议的Socket通道

因为是基于IM的音视频通话,直接复用了IM的信令通道能力,以减少应用端口占用。

通话信令

一个完整的通话过程需要哪些信令支撑呢?

基础功能信令
  1. 发起呼叫/邀请其他人信令:将通话信令从主叫通知到被叫;
  2. 接听信令:被叫接受呼叫信令;
  3. 拒接信令:被叫拒绝接听信令;
  4. 终止呼叫信令:主叫取消通话邀请;
  5. 挂断信令:主叫/被叫任意一方中断通话。
异常保证信令

基础功能信令只能保证在正常case下跑通,如何保证通信过程健壮性呢?比如:

  1. 如何确保主叫的呼叫信令已经发送到server?
  2. 发起呼叫指令如果没有到达被叫怎么办?如果使用增加服务端持续呼叫手段怎么避免重复信令?
  3. 如何确保双方都能正常加入多媒体房间?
  4. 呼叫过程对方已经再跟别人通话怎么处理?
  5. 如何保证在通话过程中某一方多媒体通道掉线,对方无效等待?

为了解决上述问题我们需要引入一些辅助信令:

  1. 增加呼叫响应信令,只有主叫接收到呼叫响应信令才算是呼叫请求信令成功发送到server;
  2. 增加呼叫确认信令,server相隔固定时间持续发送主叫请求信令给被叫,直到收到被叫呼叫确认信令。被叫需要根据呼叫ID过滤掉重复的呼叫请求信令。主叫收到呼叫确认信令后可以在呼叫页面做UI更新,比如刷新提示文字“呼叫中”为“等待被叫接听”。
  3. 增加主叫接通信令:为了避免呼叫过程中过早加入多媒体房间产生无效内容,在主叫收到被叫接听信令后再加入多媒体通道,加入多媒体通道成功后,发送主叫接听信令给被叫,被叫收到后再加入多媒体通道。主叫先加入多媒体通道,而不是被叫点击接听后就加入多媒体通道,可以避免接听信令发送失败被叫产生的额外多媒体房间无效内容。
  4. 增加忙线信令:如果被叫正在通话中,收到其他呼叫请求信令,则发送忙线信令。
  5. 增加心跳信令与心跳异常信令:通话建立后,每个t秒持续向server发送心跳信令,并持续接收server返回的心跳信令,如果持续n次发送或者接收心跳失败,则判断通话异常,退出通话。

除此之外,还需要一些额外的定时器处理超时任务。

UI关联信令

在UI上的一些交互也需要我们借助信令通道触达对方:

  1. 状态广播信令:退出房间用户通知到其他用户;
  2. 通知信令:开关摄像头通知其他用户。

信令状态实现

上面介绍了要实现一个完整通话需要十多个信令指令,维护主被叫各种状态下的信令变成了一个令人头疼的事情。我们先将通话建立过程抽象为空闲态、接通态、主叫呼叫发送中态、主叫等待1、主叫等待2、被叫待接听、被叫连接中七种状态,我们用一张图来描述引起状态变化的信令:

捋清楚状态之间的逻辑,我们可以基于状态+命令设计模式进行实现。状态接口定义各状态行为:

  1. 发送呼叫信令
  2. 接收呼叫请求信令
  3. 收到呼叫响应信令
  4. 发送呼叫确认信令
  5. 接收呼叫确认信令

%E8%AF%AD%E9%9F%B3%E9%80%9A%E8%AF%9D%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F

实时音视频通道

多媒体通道我们基于声网视频通话SDK实现:

我们主要关心:

  1. 设置本地视频
  2. 加入频道
  3. 监听远端用户进入

setupRemoteVideo 与setupLocalVideo都需要传入VideoCanvas类,VideoCanvas封装了SurfaceView,我们需要背本地视频与每个远端用户画面创建一个SurfaceView。RtcEngine为我们提供了CreateRendererView方法用于构建SurfaceView,也可以使用CreateTextureView创建TextureView。

总结一下,我们用到的API:

  1. RtcEngine.create
  2. RtcEngine.joinChannel
  3. RtcEngine.leaveChannel
  4. RtcEngine.enableLocalAudio
  5. RtcEngine.setupLocalVideo
  6. RtcEngine.setupRemoteVideo
  7. RtcEngine.createRenderView
  8. RtcEngine.createTextureView
  9. RtcEngine.startPreview
  10. IRtcEngineEventHandler.onJoinChannelSuccess
  11. IRtcEngineEventHandler.onUserJoined

详细可查阅声网视频通话API参考

UI实现

UI层主要负责管理多人画面的SurfaceView、全屏和九宫格切换逻辑、以及开关麦克风/摄像头引起的UI刷新。这里使用Android系统提供的RecyclerView实现九宫格及九宫格画面滑动。堆叠模式时隐藏RecyclerView,并将要显示对方SurfaceView与自己画面SurfaceView拿出进行绘制。UI实现因为一些交互实现,逻辑还是比较复杂,这里不进行展开讨论。

问题

走到这里终于可以完成多人视频通话了,跑起来后效果还不错。QA测试时却出来一个头疼的问题:容易掉线。而且是在视频画面正常情况下突然掉线。定位日志后发现,是心跳信令发送/接收失败引起的。为什么视频画面还正常的情况下信令连接却先除了问题呢?

分析QA复现问题的环境:工位摆了十几个手机同时进入同一个房间,时间不长就与用户连接失败;把十几个手机分散的放到各个地方,情况有了明显好转。初步定位是网络引起的,工位所有手机连到同一个热点,导致网络环境变差。

但是为什么视频画面是可以的呢?因为音视频通道基于UDP,而我们的信令通道基于TCP,UDP经过封装优化,增加了弱网对抗能力,在弱网环境下UDP的视频通道会抢占整体带宽,导致TCP通道更差,甚至连不上。

定位到问题后就要着手解决了,这里提供几个思路:

  1. UI不展示的用户画面不拉流,降低下行带宽压力;
  2. 限制最多通话人数;
  3. 降低视频码率;
  4. 替换TCP通道为UDP通道。

声网云信令SDK有基于UDP的通道,帮助提升RTM弱网对抗。具体参考云信令文档.特别是在1.4.0版本优化了弱网对抗能力,提高了弱网环境下的登录成功率和消息投递成功率,优化了重连机制。

总结

本文介绍了基于声网视频通话SDK实现的IM场景多人视频通话功能。主要介绍了信令通道、多人视频通话用到的信令、声网视频SDK接口以及弱网情况下信令通道掉线问题解决。

推荐阅读
相关专栏
SDK 教程
164 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。