语音相比文字图片更丰富,比视频又更简便,是天然的社交工具。以95后为代表的Z世代用户,在微信、QQ、微博等主流社交工具以外,更愿意尝试基于不同兴趣相对小众的社交工具。ZEGO 即构科技推出语聊房解决方案,帮助客户快速搭建语聊房。本次分享,我们邀请到了 即构科技交付解决方案专家 JIN 。他向我们分享了线上社交以及语聊房的发展、玩法,并详细解析如何快速搭建语聊房,提供稳定、低延时,高品质的线上互动体验。
文 | JIN 整理 | LiveVideoStack
大家晚上好,我是即构科技解决方案专家 JIN。很荣幸担任语聊房专题的讲师,今晚的主题就是语聊房的搭建,我将从三个方面为大家讲述,包括语聊房的衍生玩法及发展,ZEGO语聊房解决方案,以及语聊房搭建实战。
01
语聊房的衍生与玩法
首先进入第一部分。
科技改变生活,在零几年诺基亚盛行的时候,大家可以通过QQ发送文字消息进行聊天,随着智能手机的兴起,即时通讯、短视频、视频直播、语聊等逐渐火热。而今晚的主题就是语聊。
近几年语聊发展的相当火爆,尤其是年前的Clubhouse火了很长一段时间,具体火到什么程度,可以看这个图。这是2014-2019年中国语聊房行业用户付费率及付费 ARPPU 值的统计图,付费率指的是100人中,有多少人愿意付费,ARPPU 值指的是付费的人中平均付费多少钱。在2014年,100人中只有3-4人愿意付费,而这3-4人平均愿意付费64.8元,到了2019年已经涨到了161.5元,付费意愿人数比例也从之前的3-4人,提升到15人。
上图数据是2021年8-10月Z世代用户在语聊行业APP活跃度,左边刻度单位为“万人”。Soul是在语聊行业中比较火的APP,紫色代表的是十月份的活跃度,大概有1993万人在线,而8月有2375万人在线,人数相当多,这只是一家APP的数据。
上图展示的是今年8-10月Z世代用户在语聊行业APP在线时长,数据来源于即构的后台统计,Soul8月总计在线时长达到了1017336分钟。从以上三组数据可以看出语聊房是非常火爆的,它为泛娱乐行业APP带来了很大的收益,同时也增加了用户粘性及在线时长。
语聊房比较常见的形式是1V1聊天房,在一些相亲类的APP里,比较常见的是提供1V1的语聊服务,不过这些服务都是要付费的。
第二种形式是多人语聊房,这种玩法就比较多了,首先是多人纯语聊,在线会议是比较常见场景,第二种是游戏开黑,第三种是赛事直播,一起观看赛事直播发表评论,第四种也是比较常见的一起看电影,第五种pia戏,就是多人根据剧本台词,进行实时的配音扮演,如右图所示。
第三种形式是语音电台,可以理解为主播直播,用户可以对他进行打赏。
第四种类型是KTV玩法,在语聊房技术上加上K歌,可以一边唱歌一边聊天进行打赏。
02
ZEGO语聊房解决方案
第二部分是即构提出的语聊房解决方案。
主要分为三大模块。
第一模块多人语音聊天,我们在里面做了四种适配,第一种是对接一线网络运营商,节点资源丰富,无上限扩大容量。第二种是针对回声消除进行的算法优化。第三种是降低端到端延迟,让语聊更快。第四种是适应多种复杂网络,同时兼容5000 安卓机型。
第二个模块是音乐播放,单纯语音聊天比较单调,通过播放背景音乐或者气氛音效提升活跃度。我们支持播放MP3、MP4格式的背景音乐文件,支持播放器将播放的音频混入推流中,同时支持音效播放器的音频文件。
第三个模块是支持APP后台保持,切换到游戏实现语音开黑。
以上就是即构解决方案的三大模块,接下来介绍一下我们的优势,首先是多人上麦语音聊天以及上下麦平滑切换,其次是支持实时变声、立体声、气氛音效、混响等多种音频效果,第三是支持弹幕、点赞、送礼等多种消息类型,第四是先进的不连续发射(DTX)和语音活动检测(VAD)技术,这两个技术会检测当前用户是否在说话,如果正在说话,我们将他的声音录制下来推出去,如果不说话的话,我们会发空白帧,减少用户的流量。
除此之外,还有各种配套的功能支持,这里有音效播放器、媒体播放器、混音、混响、声浪与音频频谱、媒体次要信息以及房间信令。声浪与音频频谱用来展示当前谁在说话与频域分量信息,媒体次要信息属于H.264中的一个类型,用来发用户的自定义消息。
03
语聊房搭建实战
第三部分进入我们的语聊房搭建实战环节。
首先,介绍一下我们语聊房的技术架构。
语聊房把用户分为两种,一种是麦上用户,一种是麦下观众。对于麦上用户来说,A连麦者需要推出自己了流A同时再去拉B的流,B连麦者也是一样的,在上麦时将自己的流推出去,推到服务器,同时将A的流拉过来,这时A和B就可以进行语音互动。麦下的用户只需要拉A和B的流收听即可,麦下的观众可以有很多个。最右边的混流服务可以根据业务需求自行选择,有需要的可以用,没有需要就可以不用,混流可以将A和B的流混成一条流,转推到CDN,观众再从CDN拉流过来,这也是节省成本的一种方式。
接下来我们讲一下语聊房的集成(代码部分)。
由于本人是iOS开发,接下来会给各位展示下用OC实现语聊房集成。可以参考Zego音视频集成文档。 (https://doc-zh.zego.im/article/196)
代码语言:javascript复制- (void)createEngine{
//创建引擎
ZegoEngineProfile *profile = [[ZegoEngineProfile alloc] init];
profile.appID = kKTVAppID;
profile.appSign = kKTVSign;
profile.scenario = ZegoScenarioGeneral;
self.engine = [ZegoExpressEngine createEngineWithProfile:profile eventHandler:self];
//设置音频编码配置
ZegoAudioConfig *config = [[ZegoAudioConfig alloc] initWithPreset:ZegoAudioConfigPresetStandardQuality];
config.channel = ZegoAudioChannelMono;
config.codecID = ZegoAudioCodecIDLow3;
[self.engine setAudioConfig:config channel:ZegoPublishChannelMain];
//设置监听声浪回调
[self.engine startSoundLevelMonitor];
}
首先需要创建引擎,创建引擎的时候需要设置一个配置,配置是ZegoEngineProfile对象,ZegoEngineProfile对象需要设置三个参数:appid、appsign、scenario。appid和appsign可以到zego官网申请获得。
这里需要重点讲解下scenario这个参数。
/// Application scenario.
typedef NS_ENUM(NSUInteger, ZegoScenario) {
/// General scenario
ZegoScenarioGeneral = 0,
/// Communication scenario
ZegoScenarioCommunication = 1,
/// Live scenario
ZegoScenarioLive = 2
};
scenario这个参数是个枚举值,通过SDK接口可以看到有三个值。第一个General适用在对音质要求高的场景,例如在线KTV。设置这个参数后SDK内部会关闭系统的回声消除,我们也称这个配置为媒体音量模式。第二个和第三个枚举值设置后SDK内部会开启系统回声消除,我们称为通话音量模式,通话音量的效果就跟使用手机的电话通话时候的效果一样。因为是使用回声消除算法会对音质有损伤。一般使用在语聊场景,在线教育场景。语聊房场景下使用Communication即可。
接下来咱们设置下音频编码配置,先初始化一个ZegoAudioConfig对象,然后咱们设置下音频码率为48k,使用默认参数即可。然后设置下声道数为Mono,在语聊场景下人声使用双声道有点浪费带宽,也不需双声道的效果。音频编码格式为Low3,可以降低端到端延迟,提高用户体验。最后设置下音频配置。
在语聊房中咱们通常会需要在麦位上展示声浪,这里需要打开声浪的开关。声浪分为本端声浪和远端声浪。这里有两个回调可以处理声浪。这两个回调接口中都带有soundLevel参数,这个表示音量的大小,可以根据音量大小去决定是否展示声浪。
代码语言:javascript复制//本端声浪
- (void)onCapturedSoundLevelUpdate:(NSNumber *)soundLevel {
if ([self.delegate respondsToSelector:@selector(onCapturedSoundLevelUpdate:)]) {
[self.delegate onCapturedSoundLevelUpdate:soundLevel];
}
}
//拉流声浪
- (void)onRemoteSoundLevelUpdate:(NSDictionary<NSString *,NSNumber *> *)soundLevels{
if ([self.delegate respondsToSelector:@selector(onRemoteSoundLevelUpdate:)]) {
[self.delegate onRemoteSoundLevelUpdate:soundLevels];
}
}
接下来咱们需要登录房间。登录的时候需要设置用户信息,用户信息包括用户ID和用户昵称,以及房间号。
- (void)joinRoomWithRoomID:(NSString *)roomID userID:(NSString *)userID userName:(NSString *)userName{
ZegoUser *user = [ZegoUser userWithUserID:userID userName:userName];
ZegoRoomConfig *config = [ZegoRoomConfig defaultConfig];
self.roomID = roomID;
[[ZegoExpressEngine sharedEngine] loginRoom:roomID user:user config:config];
}
登录房间后怎么判断登录成功了?咱们需要在房间的回调中进行处理,这个回调是onRoomStateUpdate。这个回调中有个房间状态枚举值,对应有三种状态。
代码语言:javascript复制/// Room state.
typedef NS_ENUM(NSUInteger, ZegoRoomState) {
/// Unconnected state, enter this state before logging in and after exiting the room. If there is a steady state abnormality in the process of logging in to the room, such as AppID and AppSign are incorrect, or if the same user name is logged in elsewhere and the local end is KickOut, it will enter this state.
ZegoRoomStateDisconnected = 0,
/// The state that the connection is being requested. It will enter this state after successful execution login room function. The display of the UI is usually performed using this state. If the connection is interrupted due to poor network quality, the SDK will perform an internal retry and will return to the requesting connection status.
ZegoRoomStateConnecting = 1,
/// The status that is successfully connected. Entering this status indicates that the login to the room has been successful. The user can receive the callback notification of the user and the stream information in the room.
ZegoRoomStateConnected = 2
};
咱们首先需要判断状态是否为ZegoRoomStateConnected,然后在判断errorCode是否为0。这个时候就是登录房间成功。如果状态为ZegoRoomStateConnected但是errorCode不为0,表示当前情况为掉线重连成功,errorCode会显示对应的错误码。退出房间也是一样的道理。
代码语言:javascript复制 - (void)onRoomStateUpdate:(ZegoRoomState)state errorCode:(int)errorCode extendedData:(nullable NSDictionary *)extendedData roomID:(NSString *)roomID {
if (state == ZegoRoomStateConnected && errorCode == 0) {
if ([self.delegate respondsToSelector:@selector(onLoginRoom)]) {
[self.delegate onLoginRoom];
}
}
}
登录房间成功后需要拉流。一般情况下,在语聊房中除了房主其他用户登录房间后都不会马上推流,需要上麦后才推流。房主登录房间后可以在onRoomStateUpdate回调中推流,推流接口为:
代码语言:javascript复制- (void)startPublishingStream:(NSString *)streamID channel:(ZegoPublishChannel)channel;
拉流接口为:
代码语言:javascript复制- (void)startPlayingStream:(NSString *)streamID canvas:(nullable ZegoCanvas *)canvas;
这里需要说明下,ZegoSDK是按照流去设计接口。其他市面上的厂商的SDK设计理念不一样,他们是按照角色和场景SDK内部就实现了推拉流。使用ZegoSDK需要手动去推拉流。推流时机一般是在登录成功后或者上麦成功后。拉流时机为登录房间后且在流变更回调中去拉流。
流变更回调为onRoomStreamUpdate。
这个回调用有ZegoUpdateType枚举值,对应的是流新增和流删除。当某个用户推流成功后房间内其他人会触发流新增回调,当某个用户停止推流后房间内其他人会收到流删除回调。所以在这里实现拉流的操作即可。
代码语言:javascript复制 - (void)onRoomStreamUpdate:(ZegoUpdateType)updateType streamList:(NSArray<ZegoStream *> *)streamList extendedData:(nullable NSDictionary *)extendedData roomID:(NSString *)roomID {
if (updateType == ZegoUpdateTypeAdd) {
for (ZegoStream *stream in streamList) {
[self.playStreams addObject:stream.streamID];
[self.engine startPlayingStream:stream.streamID canvas:nil];
if ([self.delegate respondsToSelector:@selector(onUserJoinedWithStreamdID:)]) {
[self.delegate onUserJoinedWithStreamdID:stream.streamID];
}
}
}else if(updateType == ZegoUpdateTypeDelete){
for (ZegoStream *stream in streamList) {
[self.playStreams removeObject:stream.streamID];
[self.engine stopPlayingStream:stream.streamID];
if ([self.delegate respondsToSelector:@selector(onUserLeavedWithStreamdID:)]) {
[self.delegate onUserLeavedWithStreamdID:stream.streamID];
}
}
}
}
以上的功能实现集合在一起就可以搭建一个简单的语聊房。
如果需要播放音乐可以使用媒体播放,接下来咱们创建个媒体播放器。
代码语言:javascript复制self.mediaPlayer = [[ZegoExpressEngine sharedEngine] createMediaPlayer];
播放之前需要先加载资源,使用loadResource接口,然后调用start播放即可。
以上就是集成语聊房需要用到的基本接口,其他接口可以参考文档集成。
4、常见问题 —— 幽灵麦
接下来咱们聊一聊语聊房场景的问题。
比较常见的坑就是幽灵麦的问题,用户已经不在麦上了,但还能听到他的声音。
针对幽灵麦问题,我们提出了三种解决方案:
- 使用Token鉴权:即用户在登入房间时,对其身份进行校验,如果校验不成功,则不允许其进入房间。
- 流ID不和用户ID绑定:我们常遇到的场景是会将流ID和用户ID进行绑定,使用用户的ID当做流ID进行推拉流。比如一个用户登录房间A进行聊天,此时直接关闭APP,立即重新登录房间B,并上麦推拉流。由于流ID和用户ID是一样的,我们很难发现用户是什么时候掉线的,并且在用户掉线时,会自动尝试重连,重连有90s的时间,如果在这个时间内产生了上述操作,那么,在之前房间A拉的流没有停止,所以还是能听到他的声音。如果使用流ID不和用户ID绑定的方案,每次登录房间后推流的ID不一样,即使上一次的流链接还存在,但是没有数据,也就不会出现幽灵麦的问题。
- 麦位管理配合流变更通知:常用的麦位管理会建议用户使用第三方麦位管理,同时为避免不稳定,可以配合流新增的回调做处理。如果我们这里流触发新增了,再更新UI显示在麦上,如果流没有新增,即使第三方麦位管理显示已经成功上麦,说话也不成功,删除下麦也是同理。
以上是本次分享的全部内容,谢谢大家。
Q&A
1. 在多人通话连接或聊天当中,环境不同造成的大量回声和噪声嘈杂,有哪些处理保证通话质量?
我们会在创建引擎时,会选择Communication场景模式,在此模式下,使用系统回声消除会把回声消除掉以及压制噪音。这就像我们平时用手机系统打电话时的效果,已经把它消得特别干净了。如果你一定要使用General场景的话,我们也提供额外的软件AEC进行处理,同时对噪声也有个ANS,可以将它们打开。
2. 是用WebRTC吗?
目前没有使用WebRTC,用的是原生语言开发的,底层是C 。
3. 语聊房目前的应用场景多吗?和实际应用的状况怎么样?
语聊房目前应用场景是挺多的,之前也提到过1V1聊天房、多人语聊房、语音电台、KTV语聊房,目前用的比较多的是多人语聊房,它在不同的APP都有非常多衍生玩法。
4. 本次演讲没有提到录音功能,有很多人希望聊天能够被记录下来,后期剪辑变成更适合输出和保存的内容,想问下有没有类似的功能?
语聊房是有录制功能的,可以在本地保留数据,再进行本地录制,也可以在远端进行录制,主要看实际的场景需求。具体可以在即构官网开发者中心下的文档中心中搜索,会有详细的介绍。开发者文档中心链接:https://doc-zh.zego.im
视频回放地址:
https://sh2022.livevideostack.cn/live/4963