合唱各方串行加入,主唱推出一条流,包含了伴奏和人声,副唱跟着主唱的伴奏进行合唱,观众再拉由副唱发出的混流。
串行方案的数据流示例如下:
串行KTV方案中按照角色划分有主唱、副唱、观众。
主唱
副唱(互动观众)
普通观众
串行方案主要有几个环节:K歌场景音频配置、推拉流配置、下载伴奏、媒体播放器混音,以及歌词同步等。结合实际的K歌业务、上面环节以及时序进度,本方案将实现流程分为合唱准备和合唱开始阶段。按照合唱准备和合唱开始阶段,下文将具体描述如何基于 ZEGO SDK 打造串行K歌房。
一、合唱准备
合唱准备阶段,开发者需要完成四项配置:K歌场景音频配置、推拉流配置、下载伴奏,以及媒体播放器混音。
音频编码格式需要设置为 Low3,人声流为双声道,码率为128k。
ZegoAudioConfig *config = [[ZegoAudioConfig alloc] initWithPreset:ZegoAudioConfigPresetHighQuality];
config.channel = ZegoAudioChannelStereo;
config.codecID = ZegoAudioCodecIDLow3;
[self.engine setAudioConfig:config channel:ZegoPublishChannelMain];
设备外放时,可以开启 AEC(回声消除)和 ANS(噪声抑制);带耳机时,可以考虑关闭 AEC 和 ANS。AEC 和 ANS 会对音质有一点损伤和延迟。外放时,如果发现有噪音和回声,可以考虑默认打开 AEC 和 ANS。
[self.engine setAECMode:ZegoAECModeMedium];
[self.engine enableAEC:YES];
[self.engine setANSMode:ZegoANSModeMedium];
[self.engine enableANS:YES];
[self.engine enableAGC:NO];
另外为了K歌效果好,可以考虑添加混响。混流为枚举值。详情见 setReverbPreset。
[self.engine setReverbPreset:value];
由于逐字歌词需要高频率回调同步进度,可以将进度回调间隔设置为60ms。歌曲评分也会用到媒体播放器的进度回调。如果觉得回调间隔不满足需求,可以调到30ms。
self.mediaPlayer = [[ZegoExpressEngine sharedEngine] createMediaPlayer];
[self.mediaPlayer setProgressInterval:60];
主唱和副唱连麦,只推一路流。
[self.engine startPublishingStream:[self streamID] channel:ZegoPublishChannelMain];
串行方案要求本地播放伴奏,所以需要将伴奏下载到本地。 合唱示例 demo 会将下载好的歌曲信息通过房间附加消息通知给其他用户,合唱用户需要下载资源参与合唱,观众需要下载歌词用于同步展示。
//把歌曲信息通过房间附加消息发出去
NSDictionary *infoValueDict = @{@"songID" : info.songID,@"songName" : info.songName,@"singerName" : info.singerName,@"albumImg" : info.albumImg,@"krcToken" : info.krcToken};
NSString *json = [infoValueDict toJson];
[self.manager setRoomExtraInfo:@"roomMusicInfo" value:json];
实现此功能也可以使用其他方式,只需通知到位即可。收到歌曲信息后,参与合唱的用户开始下载歌曲。如何搜索歌词、展示歌单、下载歌曲和下载歌词,请参考 ZEGO 内容中心 - 点歌。
- (void)onRoomExtraInfoUpdate:(NSArray<ZegoRoomExtraInfo *> *)roomExtraInfoList roomID:(NSString *)roomID{
//在这里获取歌曲信息并开始下载歌曲
}
主唱的伴奏需要混到人声流里推出去,这里需要配置媒体播放器的混音接口。
[self.mediaPlayer enableAux:YES];
合唱开始
此时已经是播放音乐开始合唱了,合唱的过程中需要支持歌词同步的功能。
主唱通过媒体播放器的回调中的进度同步歌词即可,同时需要将歌词进度通过 SEI 发送出去。
- (void)mediaPlayer:(ZegoMediaPlayer *)mediaPlayer playingProgress:(unsigned long long)millisecond {
NSMutableDictionary *dic = [@{} mutableCopy];
[dic safeSetValue:@(millisecond) forKey:@"kProgressKey"];
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
[self.engine sendSEI:data channel:ZegoPublishChannelMain];
}
副唱端通过解析主唱端的 SEI 中的伴奏进度进行同步歌词,同时需要将进度再次发送出去。
- (void)onPlayerRecvSEI:(NSData *)data streamID:(NSString *)streamID {
NSError *error = nil;
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
if (dictionary == nil) { return; }
NSNumber *progress = [dictionary safeObjectForKey:@"kProgressKey"];
NSNumber *total = [dictionary safeObjectForKey:@"kTotalKey"];
if ([self.delegate respondsToSelector:@selector(onUpdateMeidaplayerProgress:duration:)]) {
[self.delegate onUpdateMeidaplayerProgress:progress.longLongValue duration:total.longLongValue];
}
}
- (void)onUpdateMeidaplayerProgress:(long long)progress duration:(long long)duration {
self.lyricByWordView.progress = progress;
self.currentProgress = progress;
//歌词滚动相关
//打分相关
static int pitchViewTimesCount = 0;
pitchViewTimesCount++;
if (pitchViewTimesCount >= 2) {
pitchViewTimesCount = 0;
[self.pitchView setCurrentSongProgress:(int)progress pitch:self.pitch];
}
NSString *curString = [self formatTime:(int)progress];
NSString *totalString = [self formatTime:(int)duration];
self.musicProcessLabel.text = [NSString stringWithFormat:@"%@ / %@",curString,totalString];
}
通过解析副唱流中的 SEI,观众端获取播放点歌用户的播放进度,以此同步歌词进度。
- (void)onPlayerRecvSEI:(NSData *)data streamID:(NSString *)streamID {
NSError *error = nil;
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
if (dictionary == nil) { return; }
NSNumber *progress = [dictionary safeObjectForKey:@"kProgressKey"];
NSNumber *total = [dictionary safeObjectForKey:@"kTotalKey"];
if ([self.delegate respondsToSelector:@selector(onUpdateMeidaplayerProgress:duration:)]) {
[self.delegate onUpdateMeidaplayerProgress:progress.longLongValue duration:total.longLongValue];
}
}
- (void)onUpdateMeidaplayerProgress:(long long)progress duration:(long long)duration {
self.lyricByWordView.progress = progress;
self.currentProgress = progress;
//歌词滚动相关
//打分相关
[self.pitchView setCurrentSongProgress:(int)progress pitch:self.pitch];
NSString *curString = [self formatTime:(int)progress];
NSString *totalString = [self formatTime:(int)duration];
self.musicProcessLabel.text = [NSString stringWithFormat:@"%@ / %@",curString,totalString];
}
联系我们
文档反馈