零基础快速搭建K歌应用【含源码】
玩法开天辟地,体验不留缝隙。K歌不遗余力,应用解决效益。总是羡慕别人家的“歌房”苦叹自家“茅草房”消除不了回音和混音?这次就将带你实战K歌功能,细分应用场景,提升产品表现,为你在“造房“路上“添砖加瓦“,给你最实用的”武器“,让你的”K歌房“摆脱尴尬的余音绕梁,从此高品质翱翔。看淡K歌之王,用技术推你做”K歌王中王“!
本期腾讯云大学大咖分享课程邀请到腾讯云高级工程师,主要从三个方面说明如何搭建K歌应用
- K歌功能应用场景和产品表现
- K歌功能技术实现方案
- K歌功能的开发实战
K歌的种类分为本地录制和在线歌房。
本地录制主要分两大模块:跟随伴奏唱歌录制和调音台。调音台用来通过调整人声位置来调整抢慢拍缺陷,调节人声及伴奏音量,混响及变声等音效。均衡器是对声音的不同频段进行增强或降低。例如,唱吧和全民K歌应用的都是本地录制模式。
在线歌房是房主的声音和伴奏通过网络编码发送给房间听众。例如,直播。
本地录制流程
伴奏文件经解码转入播放设备,人声跟随伴奏录制后用采集设备进行人声伴奏的同步采集。通过回声消除将伴奏声音消除,留下人声,并存为人声临时文件。另一分支的耳返的作用是将人声送达至用户的耳朵。整个录制流程是人声经变声等设置后与伴奏形成耳返混音一并转入播放设备。录制后进行后期处理与伴奏最终生成音频文件。
技术应用
常见伴奏文件形式有mp3,ogg,acc,wav。大部分手机尽管支持MP3解码,但安卓机型容错能力较差,对异常MP3文件(MP3文件不完整或格式错误)通常无法播放。部分安卓系统支持ogg播放,但IOS系统全部不支持,这些情况都需要打包解码库。解码库的多样化可能造成的问题:对于app而言,音乐文件格式仅一种,并不需要占安装包的大小进行无用打包
解决方式:动态加载。即每个解码库单独操作为动态库(SO,DLL等)播放音乐时先加载动态库,可加载到就正常播放,加载不到就返回即库不存在。用户可灵活选择打包数量。可支持动态下载的动态库,例如安卓和Windows,在APP运行期间,根据文件格式选择库。
边下边播指的是边下载伴奏文件边播放,此时会出现卡顿问题。即便是本地文件,播放过程中也可能存在卡顿。
卡顿出现的原因:
- 解码线程和其他任务线程共用时,因其他任务过重导致编码不及时产生卡顿。因此编码需要使用单独线程。
- 设置缓存 单独线程的卡顿,在如今的并发系统中线程的调度是通过时间片轮巡的,不使用Buffer时,当设备需要数据,线程未被调度就会引起卡顿。
录制
录制部分需要回声消除即播放的部分无需采集。通常为系统自带功能,例如在通话时开通扬声器,对方的声音不会被采集后转发回去。系统回声消除存在一定局限性,例如只支持在通话情况下开启回声消除,媒体音量开启并无效果,有些设备显示返回失败。通话条件下开启回声消除时采样率降低。因在高采样率下,采集到的声音频段丰富,回声消除操作对算法和设备性能要求高。K歌场景下,如果用16k,很难满足用户需求,所以采用采样率为44.1k的自研回声消除。
在t0时刻,伴奏经解码后播放,从播放到用户接受到伴奏存在的时间间隔称为播放延迟。IOS系统,播放延时小。安卓系统相对较大,一般播放延时为几百毫秒。从用户开始唱歌到t1时刻人声伴奏采集到内存称为采集延迟。采集延迟和播放延迟时间相似,安卓设备的两个延迟一般是100~200ms。在t1时刻下采集到t0时刻的伴奏,为实现人声伴奏对齐需要计算出t0和t1的时间间隔。通过计算播放延迟和采集延迟或一并计算总延迟。例如,计算总延迟,先播放一段测试声音,采集后对比两个信号的偏移情况得出结果。计算总延迟的方法相对精确,实验室常用此方法,但此方法并不适合线上环境,很难实现在唱歌前播放一段测试音乐,这种情况下采用分段计算即可。播放延迟分为两部分,获取数据时的buffer最小值为mini buffer,可根据码率声噪等计算出时长。获取mini buffer后,对100款主流机型进行测试得出播放延迟大约为mini buffer的二倍。同理可得采集延迟。人声伴奏对齐主要关注开始播放时,按播放采集延时计算,暂停后重新对齐。
混响
混响是声源发出声音后被反射物反射,反射后的声音与声源声音相结合出的声音。
混响的影响因素:反射物的远近,多少和材质。反射物的远近决定反射声音的到达时间,例如房间里回声快速到达,山谷回音经久不息。反射物的多少决定反射信号的多少,例如山谷里的回声清晰可辨,房间里回声难以分辨。反射物的材质决定混响时间,反射物多,信号被吸收的多,声音持续时间短。
以下为原声和空灵两模式的对比图。空灵模式下,直达声和反射声距离长,所谓山谷回音。
原声是在房间里的说话状态,左边的长线段为直达声,反射声随之到达,很难进行区分。房间内物体多,吸收声音效果强,混响时间短。第一个反射声到达时间,反射声多少,混响持续时间决定混响程度。通过8种混响模式对以上三种因素进行调节,下文demo操作时会具体呈现。
均衡器
均衡器是对不同频率的声音进行缩放。下图是一段录音经傅里叶变换后发现一个声音由很多频率的声音构成。此录音在1000Hz之前相对较大,随之缩小,16000Hz之后能量几乎为零。根据频率图,我们可以对不同频段的声音进行放大或缩小。低频声音若饱满,则说明低频信号较合适,若低频信号小,声音较为单薄。低频信号高,信号较厚重。高频信号较为明快。一般来说均衡器支持以下10级。下方的数字说明的是一个区间值,例如31,是均衡31~61这段区间的区间值。
变声
变声支持12类型:萝莉,大叔,熊孩子,感冒,困兽,空灵肥仔,重金属,外国人,重机械,强电流,土话。根据以上声音特点使用的技术方法并非单一,例如萝莉和大叔两种,通过升降频率实现,空灵通过混响来实现。
以下是原声和萝莉的语谱图,横坐标为时间,纵坐标为频率。颜色代表特定时间点下,某频率的声音强度。原声的声音频率较窄,为16000Hz以下。萝莉音频率被拉长,一些低频音被放大为高频,频率为14000Hz左右。可以用QQ语音消息进行体验,和GME的K歌功能一样,都是音视频实验室研发的。
在线K歌
在线K歌发送端和稳定录制相似,增加了编码这一项。根据流程图所示,编码后发包,收包等,最后解码播放。
K歌房考虑因素:伴奏,人声,歌词同步。和上述伴奏人声同步不同的是,虽然本地伴奏与人声同步,但发送期间由于网络延迟不固定,接收端收到后,伴奏和人声还是会有偏移。因此伴奏和人声需要混音发送。歌词同步就是时间戳的同步,根据当前收到的声音所在时间
来展示歌词。展示歌词分逐字展示和逐句展示,逐字对时延要求较高。
同步时间戳的两种方式:
1开始时发送信令,然后暂停或结束再次发送信令,对方根据接收时间,通过计时器的累加决定伴奏的时间。这种方式的好处不需要改变音频帧的格式,如果音频帧的扩展性不好,可采用此方式。
2 如果音频帧的扩展性较好,对时间戳精度要求高,则需要把当前伴奏的时间戳放在音频帧的头或尾,与音频帧一同传送。此种方式实施效果好,目前我们采用此方式。
延迟控制
延迟控制主要控制直播场景中,说话者的声音经网络发送到听者的时间。这个时间可细分为播放和采集延时,设备相关问题可控范围小。另一个原因是网络传输,可通过增加后台服务器,使每个用户就近连接,通过最近通路传输。
延迟的另一个类型是由网络收发包buffer引起的延迟。音频数据通过UDT形式传输,而UDT不能保证持续性。例如有三个音频包,可能接受顺序是混乱的,部分音频包会被丢弃,导致声音断断续续并不完整。因此,延迟也并非越小越好。我们需要对不同场景进行优化,比如直播模式无交互,听众不会和直播者互动,只需保证直播人的声音和画面是同步的,即使晚几秒传输,听众是无法明显察觉的。在这种情况下先保证避免卡顿,延时可略微增加。
连麦模式是在直播的过程中实现听众与直播者的互动,这种情况下对实时性要求较高。因此连麦模式需要低延时,卡顿可略微存在。不同模式的侧重点不同,需根据具体场景调整参数。
录制过程中,只有一位录制者时,录制并上传即可。如果多人参与录制,则采取后台录制,即后台统一解码多人声音,生成文件,录制结束时,返回服务器地址,即可进行观看。
以下是实现一个简单的本地录制功能的实战演练环节。
加文章结尾“学习君”微信可获取课程资料。
1、初始化
ITMGContext.GetInstance(this).Init(String.valueOf(mAppId), mUserId);//初始化sdk,用来登录
ITMGContext.GetInstance(this).SetTMGDelegate(new MyDelegate());//设置代理类,用来接受各种回调和事件
EnginePollHelper.createEnginePollHelper();//定时去调用Poll函数,可以用这个辅助类,也可以在某个周期函数中调用
2、进房
byte[] authbuff = AuthBuffer.getInstance().genAuthBuffer(mAppId, mRoomId, mUserId,mAppKey);//获得鉴权信息,最好放在server
ITMGContext.GetInstance(this).EnterRoom(mRoomId, 2, authbuff);//进入房间
3、K歌相关接口
/*功能:开始录制
**参数:
**type:K歌场景传ITMG_AUDIO_RECORDING_KTV
**dstFile:目标文件路径,用于保存录制完成的音乐
**accMixFile:一般是没有原声的伴奏,用来和人声合成音乐文件
**accPlayFile:用于播放的音乐文件,正常情况下和accMixFile是同一个文件。但在用户不熟悉歌曲的时候,可以是带原唱的音乐文件
*/
int StartRecord(int type, String dstFile, String accMixFile, String accPlayFile);
/*功能:停止录制*/
int StopRecord();
/*功能:暂停录制*/
int PauseRecord();
/*功能:继续录制*/
int ResumeRecord();
/*功能:设置播放用的音乐文件,一般用于在原唱和纯音伴奏之间切换
**参数:accPlayFile,用于播放的音乐文件
*/
int SetAccompanyFile(String accPlayFile);
/*功能:获取伴奏文件的长度*/
int GetAccompanyTotalTimeByMs();
/*功能:获取当前录制了多长时间*/
int GetRecordTimeByMs();
/*功能:将录制时间跳转到指定时刻。如果参数比当前时间靠前,则重复的地方重新录制;比当前时间靠后,则用静音数据填充没有录制的部分
**参数:timeMs,跳转的时刻,单位ms
*/
int SetRecordTimeByMs(int timeMs);
/*功能:设置音效
**参数:type音效类型,参见ITMG_KaraokeType
*/
int SetRecordKaraokeType(int type);
/*功能:获得录制文件的长度*/
int GetRecordFileDurationByMs();
/*功能:开始预览录制文件*/
int StartPreview();
/*功能:停止预览录制文件*/
int StopPreview();
/*功能:暂停预览录制文件*/
int PausePreview();
/*功能:继续预览录制文件*/
int ResumePreview();
/*功能:设置当前预览的时间点
**参数:time,预览文件的时间点单位ms
*/
int SetPreviewTimeByMs(int time);
/*功能:获取当前预览的时间点*/
int GetPreviewTimeByMs();
/*功能:设置人声和伴奏的缩放比例
**参数:
**mic:人声的缩放比例,1.0为原来音量,小于1.0为缩小,大于1.0为放大
**acc:伴奏的缩放比例,1.0为原来音量,小于1.0为缩小,大于1.0为放大
*/
int SetMixWieghts(float mic, float acc);
/*功能:设置人声相对于伴奏的偏移,一般用于调整声音跟不上节拍的问题
**参数:time,人声相对于伴奏的偏移时间,单位ms。大于0为向后移动,小于0为向前移动
*/
int AdjustAudioTimeByMs(int time);
/*功能:将录制好的人声和伴奏合并成一个文件*/
int MixRecordFile();
/*功能:取消合并操作*/
int CancelMixRecordFile();
需要监听的事件:
/*功能:录制完成的回调。伴奏播放结束或者调用StopRecord触发
**参数:
**result:录制结果错误码,0为成功
**filepath:目标文件的路径,StartRecord传入的
**duration:录制文件的长度,单位ms
*/
ITMG_MAIN_EVENT_TYPE_RECORD_COMPLETED
/*功能:预览完成的回调。预览文件播放结束或者调用StopPreview触发
**参数:result,播放结果错误码
*/
ITMG_MAIN_EVENT_TYPE_RECORD_PREVIEW_COMPLETED
/*功能:合成文件完成的回调。合成文件完成触发,完成之前调用CancelMixRecordFile则没有回调
**参数:
**result:合成结果错误码,0为成功
**filepath:目标文件的路径,StartRecord传入的
**duration:录制文件的长度,单位ms
*/
ITMG_MAIN_EVENT_TYPE_RECORD_MIX_COMPLETED(32)
接口详细说明参见:https://cloud.tencent.com/document/product/607
问卷
为了给广大开发者提供最实用、最热门前沿、最干货的视频教程,请让我们听到你的需要,感谢您的时间!点击填写 问卷
腾讯云大学是腾讯云旗下面向云生态用户的一站式学习成长平台。腾讯云大学大咖分享邀请行业技术大咖,为你提供免费、专业、行业最新技术动态分享。