合唱各方串行加入,主唱推出一条流,包含了伴奏和人声,副唱跟着主唱的伴奏进行合唱,观众再拉由副唱发出的混流。
串行方案的数据流示例如下:
串行KTV方案中按照角色划分有主唱、副唱、观众。
主唱
副唱(互动观众)
普通观众
串行方案主要有五个环节:K歌场景音频配置、推拉流配置、下载伴奏、媒体播放器混音,以及歌词同步等。结合实际的K歌业务、上面环节以及时序进度,本方案将实现流程分为合唱准备和合唱开始阶段。按照合唱准备和合唱开始阶段,下文将具体描述如何基于 ZEGO SDK 打造串行K歌房。
一、合唱准备
合唱准备阶段,开发者需要完成四项配置:K歌场景音频配置、推拉流配置、下载伴奏,以及媒体播放器混音。
音频编码格式需要设置为 Low3,人声流为双声道,码率为128k。
ZegoAudioConfig audioConfig = new ZegoAudioConfig(ZegoAudioConfigPreset.HIGH_QUALITY);
audioConfig.codecID = ZegoAudioCodecID.LOW3;
audioConfig.channel = ZegoAudioChannel.STEREO;
mSDKEngine.setAudioConfig(audioConfig, ZegoPublishChannel.MAIN);
设备外放时,可以开启 AEC(回声消除)和 ANS(噪声抑制);带耳机时,可以考虑关闭 AEC 和 ANS。AEC 和 ANS 会对音质有一点损伤和延迟。外放时,如果发现有噪音和回声,可以考虑默认打开 AEC 和 ANS。
mSDKEngine.enableAEC(true); //是否开启回声消除;true 表示开启;false 表示关闭
mSDKEngine.enableAGC(false); //是否开启自动增益控制;true 表示开启;false 表示关闭
mSDKEngine.enableANS(true); //是否开启噪声抑制;true 表示开启噪声抑制;false 表示关闭噪声抑制
mSDKEngine.setAECMode(ZegoAECMode.MEDIUM); //设置回声消除模式
mSDKEngine.setANSMode(ZegoANSMode.MEDIUM); //设置音频噪声抑制模式
mSDKEngine.enableHeadphoneMonitor(false); //开启耳返, false: 关闭耳返
另外为了K歌效果好,可以考虑添加混响。混流为枚举值。详情见 setReverbPreset。
mSDKEngine.setReverbPreset(ZegoReverbPreset.RECORDING_STUDIO);
主唱和副唱连麦,只推一路流。
mSDKEngine.startPublishingStream(getMainStreamId(),ZegoPublishChannel.MAIN);
串行方案要求本地播放伴奏,所以需要将伴奏下载到本地。 合唱示例 demo 会将下载好的歌曲信息通过房间附加消息通知给其他用户,合唱用户需要下载资源参与合唱,观众需要下载歌词用于同步展示。
public void selectSong(View view) {
if (CheckDoubleClick.isFastDoubleClick()) {
return;
}
songListDialog = new SongListDialog(songId -> {
switchMusicPlayerMenu(songId);
// 设置房间附加消息
ZGKTVCopyrightedSong song = ZGKTVPlayerManager.getInstance().getSong(songId);
RoomMusicInfo roomMusicInfo = new RoomMusicInfo();
roomMusicInfo.setSongID(song.getSongID());
roomMusicInfo.setSongName(song.getSongName());
roomMusicInfo.setSingerName(song.getSingerName());
roomMusicInfo.setAlbumImg(song.getAlbumImageUri());
roomMusicInfo.setKrcToken(song.getKrcToken());
ZGRealTimeKtvManager.getInstance().setRoomExtraMusic(roomMusicInfo);
songListDialog.dismiss();
});
......
}
实现此功能也可以使用其他方式,只需通知到位即可。收到歌曲信息后,参与合唱的用户开始下载歌曲。如何搜索歌词、展示歌单、下载歌曲和下载歌词,请参考 ZEGO 内容中心 - 点歌。
public void setRoomExtraMusic(RoomMusicInfo roomMusicInfo) {
mSDKEngine.setRoomExtraInfo(mRoomID, "RoomMusicInfo", APIBase.getGson().toJson(roomMusicInfo), null);
FileDownloadManager.getInstance().downloadFile(roomMusicInfo.getSongID(), roomMusicInfo);
}
主唱的伴奏需要混到人声流里推出去,这里需要配置媒体播放器的混音接口。
mediaPlayer.enableAux(true);
合唱开始
此时已经是播放音乐开始合唱了,合唱的过程中需要支持歌词同步的功能。
主唱通过媒体播放器的回调中的进度同步歌词即可,同时需要将歌词进度通过 SEI 发送出去。
public void onPlaybackProgressUpdate(ZegoMediaPlayer mediaPlayer, long progress) {
mPlayerCurrentTimeMillis = System.currentTimeMillis();
mPlayerCurrentProgress = progress;
if (ZGRealTimeKtvManager.getInstance().isAnchor()) {
long curtime = ZGRealTimeKtvManager.getInstance().getNetworkTime();
HostSEIInfo hostSEIInfo = new HostSEIInfo();
ZGKTVCopyrightedSong song = ZGKTVPlayerManager.getInstance().getSong(currentPlaySongId);
hostSEIInfo.setkTotalKey(song.getDuration());
hostSEIInfo.setkState(0);
// hostSEIInfo.setkRole();
hostSEIInfo.setkProgressKey(progress);
hostSEIInfo.setkPointTimeKey(curtime);
hostSEIInfo.setkResourceID(song.getResourceID());
ZGRealTimeKtvManager.getInstance().sendSEI(APIBase.getGson().toJson(hostSEIInfo).getBytes());
}
}
副唱端通过解析主唱端的 SEI 中的伴奏进度进行同步歌词,同时需要将进度再次发送出去。
public void onMediaPlayerPlayingProgress(ZegoMediaPlayer mediaPlayer, long millisecond) {
L.i("PlayerManager", "onPlaybackProgressUpdate:progress:" + millisecond);
ZGKTVCopyrightedSong song = getSongByResourceID(mResourceID);
if (song != null && song.getCallback() != null) {
song.getCallback().onPlaybackProgressUpdate(millisecond);
}
if (mIZGKTVPlayerUpdateListener != null) {
mIZGKTVPlayerUpdateListener.onPlaybackProgressUpdate(mediaPlayer, millisecond);
}
}
public void onPlaybackProgressUpdate(ZegoMediaPlayer mediaPlayer, long progress) {
......
if (ZGRealTimeKtvManager.getInstance().isAnchor()) {
......
updateMusicPlayerMenu();
}
}
private void updateMusicPlayerMenu() {
// 计算出当前播放器进度 (当前进度 = 当前时间 - 播放器回调时的时间 + 播放器进度)
long customProgress = System.currentTimeMillis() - mPlayerCurrentTimeMillis + mPlayerCurrentProgress;
L.i(TAG, "onPlaybackProgressUpdate:" + customProgress);
currentProgress = customProgress;
List<ZGKTVCopyrightedSong> orderList = KTVRoomOrderSongManager.getInstance().getOrderList();
if (orderList == null || orderList.isEmpty()) {
return;
}
String songId = orderList.get(orderList.size() - 1).getSongID();
if (songId == null || songId.isEmpty()) {
return;
}
mZegoLyricView.setCurrentTimeMillis(customProgress);
progressInterval++;
if (progressInterval >= 2) {
ZGKTVCopyrightedSong song = ZGKTVPlayerManager.getInstance().getSong(songId);
long duration = 0;
if (song != null) {
duration = song.getDuration();
}
if (duration == 0) {
duration = ZGKTVPlayerManager.getInstance().getTotalDuration();
}
mMusicPlayerMenu.updateTime(customProgress, duration);
mZegoNetworkTime = ZGRealTimeKtvManager.getInstance().getNetworkTime();
mPlayProgress = customProgress;
progressInterval = 0;
mMusicPlayerMenu.getPitchView().setCurrentSongProgress(customProgress, mCurrentPitch);
}
}
通过解析副唱流中的 SEI,观众端获取播放点歌用户的播放进度,以此同步歌词进度。
public void onPlayerRecvSEI(String streamId, HostSEIInfo info) {
if (!ZGRealTimeKtvManager.getInstance().isAnchor()) {
// 观众同步歌词
mZegoLyricView.setCurrentTimeMillis(info.getkProgressKey());
mMusicPlayerMenu.updateTime(info.getkProgressKey(), info.getkTotalKey());
}
}
联系我们
文档反馈