K歌、短视频技术最佳实践——“唱吧”音视频技术探索

内容原创,转载请注明出处

一、引言

  移动App发展到今时今日,几乎所有应用程序里都有音视频相关功能,总结起来大概有音视频录制,音视频播放,音视频特效处理,音视频传输这几方面内容。
  比较热门的短视频应用抖音与快手,K歌应用唱吧与全民K歌,以及网易云新出的音街,还有听歌的网易云音乐和qq音乐等。虽然每种应用的运营模式不一样,但是从技术实现上大同小异。短视频类软件的视频处理更丰富,有非常炫酷的滤镜,贴纸,K歌类软件对音乐的细节处理的较多,诸如音量,音效,音调。
  笔者是唱吧较早的用户,发展至今,唱吧涵盖的音视频技术相对比较全面,原唱吧音视频技术负责人展晓凯老师在《音视频进阶指南》一书中对移动端音视频最佳实践给出了具体实现,所以就以唱吧为参考,探索移动端的音视频相关技术。(代码实现上基于安卓,不过对于音视频这种偏底层的操作,各平台实现思路大致一样。另外,笔者未曾在唱吧工作过,若有侵权或其他行为,请留言沟通。若想与笔者共同探讨移动音视频技术,欢迎留言或微信smzh_james探讨)。


涉及技术目录一览
一、音频录制(录音,音频解码,音频重采样,音频混合)
二、音频播放与效果处理 (音量,音调,音效)
三、视频录制 (Camera相关,视频编码)
四、视频特效处理(美颜,滤镜,贴纸)
五、视频播放(视频解码,视频渲染, 音视频同步)
六、音视频混合
(内容链接会陆续更新)

二、效果对比与技术实现浅析

参考唱吧App的界面,做了一款简易的K歌软件,黑白风格,但是涵盖了大多数功能,以下称为SuperKtv。效果对比如下图。

(一)K歌列表页

  图1是SuperKtv,图2是唱吧。

  SuperKtv实现比较简单,直接获取手机上现有音频文件以列表展示,为后续功能提供一个入口。实现上没啥难度,做开发的都懂,就不再赘述。

(二)音频录制页

  前两幅图是SuperKtv的音频录制页,第三幅图是唱吧音频录制页。

  • 歌词
      对于K歌软件来说歌词是很重要的一部,像唱吧这种专业K歌软件,曲库很庞大,为了与演唱同步,歌词一般都带有时间戳,以便演唱过程中歌词动态滚动,还有打分功能。从技术方面分析,歌词和打分功能每首歌应该都不相同,这方面本质不涉及音视频技术,笔者也没有太多精力为歌曲自定义歌词和打分功能。
      为了让SuperKtv与唱吧有较高的锲合度,笔者还是想办法做了这方面的工作。要快速解决SuperKtv的歌词问题,只能从网络入手,利用 歌词网https://www.90lrc.cn 强大的搜索功能,使用其搜索接口先搜索歌名,返回带搜索结果列表的Html,然后解析Html获取合适的列表项,一般取第一项最为合适,并取出其中的歌词链接,再通过歌词链接获取带歌词的Html,过滤后即可获取获取歌词文本,显示效果如上图1。打分功能的一中实现方法是在演唱过程中匹配动态识别的音高与预定义文件中的内容。

  • 录音
       K歌软件的核心功能之一,对于录音的低延时性能要求较高,这一点上IOS比Android做的要好的多,而且功能很完善,用AudioUnit可以解决大部分问题;Android8.0以下推荐使用OpenSL ES,是OpengSL在嵌入式设备上的精简实现;Android8.0及以上推荐使用AAudio,一个轻量级录音接口,为低延时音频处理而生,据官网文档描述低延时性能较好,而且给出了Pixel几款机型的测试数据。但是因为安卓的机型是在太多,硬件实现上付出的努力不尽相同,所以这两者在兼容性上均不如AudioRecorder好。Google Android团队对OpenSL ES和AAudio进行了封装,命名为Oboe,看起来大有追赶AudioUnit的理想,提供了统一的调用接口,使用极为方便,在GitHub上直接能搜到Oboe https://GitHub/Oboe,提供的Demo参考价值很大,但根据GitHub上的反馈来看,使用起来问题不少,主要在杂音和延迟上。期待后续的更新能彻底解决这个安卓从问世以来就一直被诟病的问题。
       低延迟录音的另一个解决方案是计算录音延迟,设想如果能获取到录音延迟,那么在编辑页混合的时候可以人为的将录到的声音提前一段时间,得到的结果几乎接近于没有延迟。但要是能这么容易解决的话就不是Android了,录音的延迟计算并不容易,粗略的计算 录音延迟 = 硬件延迟 + 缓冲延迟,如果在录音回调中有较多计算的话还需要加上计算延迟。截止目前只有AAudio中提供了计算延迟的方法,尴尬的是经实测延迟为0。即便是8.0以上的设备已经占据主流市场,但对于商用软件来说支持到8.0肯定是远远不够的,硬件的延迟或许只有厂商能够拿到比较准确的数据,所以和厂商合作也是其中一种解决办法。对于华为手机来说,华为的Sdk里面提供了相应的Api,使用AudioKit能直接获取录音延迟,而且具备耳返功能和七个音效,这应该是迄今听到的最好的消息了。

  • 音频解码和重采样
       在K歌软件录音的时候一般会播放伴奏或者原唱为演唱者提供参考,底层的Api一般都不提供解码功能,所以在将数据“填充”到设备缓冲区之前要将伴奏或原唱解码成原始数据,就是大名鼎鼎的Pcm。歌曲文件一般是Mp3或者AAC格式,Mp3的编解码推荐使用Lame,AAC的编解码推荐使用Fdk-aac,这两者在速度上迄今为止做到了最好,对于有实时性要求的最合适不过了,可以直接使用或者将这两个库编译到FFmpeg里面,调用FFmpeg的Api操作,再退一步,直接使用FFmpeg自带的解码算法也能完成相应功能,当然性能上是有所区别。在解码上普遍的做法是编译带有Lame或Fdk-aac的FFmpeg,调用FFmpeg的Api去做编解码,因为其Api的统一性上很有优势。
       在Android平台下还可以使用MediaCodec进行解码,系统自带Api,使用方便,文档较多,而且使用硬件解码,速度上也比软件解码快很多,遗憾的是直到Android6.0以后才提供了MediaCodec的C++接口,考虑到和IOS的统一性,推荐使用前者。
       重采样关键点是音乐文件的采样率、声道数和采样深度(也称量化精度)。某一个音乐文件的采样率、声道数、量化精度可以认为是不确定的,但播放平台硬件支持的采样率、声道、采样深度是有限的、确定的,所以在“填充”数据给音响设备的缓冲区之前的另一项工作是重采样,直接使用FFmpeg就好了,FFmpeg提供的重采样功能比较强大,包括采样率、声道数和采样深度都可以改变。参考文档或在网上搜索都可以 。需要注意的是网上的FFmpeg重采样教程大多忽略了一个问题,从单声道重采样成双声道的时候,为了保证听觉上音量不变,实际的分贝(响度)会有轻微的变化,这点还需要特别注意。

  • 音频内容保存
       考虑到K歌软件录到的音频内容在后面的要进行编辑,在磁盘空间不受限制的情况下建议直接保存原始数据,方便后续操作,一首歌的空间一般在100Mb以内。如果考虑磁盘空间的话,可以将录到的数据编码保存,编辑的时候再进行解码。关于音频编码部分在保存的时候再详细分析,要注意的是在保存数据的时候要频繁读写文件,考虑到性能问题,可以选用异步操作来完成,异步操作的线程同步问题可以使用Boost库中提供的无锁队列来完成。

(三)音频编辑页


  前两幅图是SuperKtv,后两幅图是唱吧。在写这边文章的时候唱吧的编辑页进行的改版,页面布局发生了巨大变化,风格上跟音街比较像。但在笔者模仿唱吧界面的时候,老用户应该比较清楚,长相跟SuperKtv非常接近,此处比较许遗憾,没能以最高相似度复原唱吧App。

  • 音频播放
       K歌软件的在编辑页需要音频播放功能,对低延时性能要求也比较高,目的是将前面录好的内容和伴奏同时播放,听起来像是在听歌星的专辑一样。在Android平台上使用OpenSL ES和AAudio是较好的选择。为达到较好的同步效果,选用低延时Api是必要的,此外,还需要精确控制两个轨道播放的时间一样。比较简陋的做法是打开两个播放器,一个播放人声,另一个播放伴奏,以达到同步播放的效果,更具使用价值的是选择一个播放器,将人声和伴奏的Pcm数据混合之后播放,后者的同步性要好于前者,效率是也更高。在SuperKtv中使用的是第二种方案,效果还不错。在播放之前要关注Pcm数据的声道、采样率、采样深度,必要时进行重采样。

  • 音量控制
       在音频处理中,对编码好的音频处理很难,一般是对原始数据进行处理。在后续的音量、音效、音调处理都是对Pcm处理。
       众所周知声音是波,音量可以理解为波的振幅,也叫响度,改变音量即改变波的振幅,从数学的角度要增大一个正弦波的振幅只要乘以增益就可以,同样,音量也是对Pcm数据乘以增益就可以,当然可以直接使用FFmpeg来解决。要注意的是增大振幅之后不能超过每一帧数据的最大值和最小值,以16位整形精度为例,如果最终的数值大于32767或小于-32768,那么音质就会受损,听起来像杂音一样。

  • 音调控制
       音调主要由声音的频率决定,同时也与声音强度有关。对一定强度的纯音,音调随频率的升降而升降;对一定频率的纯音、低频纯音的音调随声强增加而下降,高频纯音的音调却随强度增加而上升。音调调节推荐使用SoundTouch https://sourceforge.net/projects/soundtouch/),用法比较简单,而且还具有变速等功能。
       音调主要是由声波的频率决定的,联想到数学或物理学中波相关的知识点,可以自己手动实现,简单理解为将音频数据重采样成另一个频率,但数据内容没有减少。由傅里叶变换的定义可以知道,任何周期性的波都可以分解成若干个正弦波的叠加。在这里普遍的做法是将Pcm数据经傅里叶变换得到频域的结果,改变基波的频率,因为频率的变化可能导致数据减少或增多,需要再进行插值计算,最后做反向傅里叶变换将数据变换到时域,就可以得到改变频率的Pcm数据,也就是改变了音调。

  • 音效控制
       因为声音是波,所以音效也是根据波的一些特性进行处理的,比如回声效果就是根据波的反射性实现的。展晓凯老师在《音视频进阶指南》一书中给出了用Sox实现音频效果的具体实现,FFmpeg也可以对音频进行效果处理,SuperKtv中用两者均实现了音频效果处理。Sox在音频效果处理上功能稍微更全面一些,推荐使用Sox http://sox.sourceforge.net/,被誉为音频处理方面的瑞士军刀,遗憾的是这个库在早些时候已经停止更新了,其功能逐渐被桌面版客户端AudioCity取代。
       音频效果处理分为混响,均衡,压缩。混响可以理解为在一个房子里面唱歌有回声,影响的因素一般有房间的大小、墙壁的反射率(装修材料等),演唱者所处的位置等,所以通过改变房间大小等参数,就可以模仿专业演播大厅的效果,像维也纳演播大厅的混响参数都是已知的,参考这些参数就能模拟出在维也纳演播大厅开演唱会的效果。均衡器可以分别调节各种频率成分信号放大量,一般调音台上的均衡器仅能对高频、中频、低频三段频率电信号分别进行调节,均衡器还具备高通滤波和低通滤波的功能,比如唱吧中的留声机效果就是使用了均衡器,一方面过滤掉某些频率的谐波,另一方变改变某些频率信号分量的增益就可以得到留声机的效果。压缩器在百度百科上的定义如下:“压缩器是一种随着输入信号电平增大而本身增益减少的放大器,实质上改变的就是输入与输出信号的比例。压缩器是两种最常见的用于处理音频信号动态范围的设备之一”,这个定义应该很明了了,在Sox的实现上主要以调节参数为主。通过这三种效果的叠加,就可以调节出Ktv、录音机、流行,说唱等不同风格的效果。上图中的自定义音效是对混响、均衡、压缩三种效果中一些重要参数提供了可调节入口,其他音效可以理解为一些固定参数根据经验的组合。以上三种算法原理可以参考Sox源码中reverb.c,bequed.c,compand.c这三个文件。

(四)视频录制页

  前三幅图的SuperKtv的视频录制界面,后三幅图是唱吧的视频录制界面。
  从功能上对比,SuperKtv美颜面板少了锐化,瘦脸,大眼功能,另外道具功能也没有实现,稍后具体分析。
   音频部分跟前面一样,重点分析视频的录制。视频的录制需要保存带美颜效果的视频,当然也可以保存原始视频画面在编辑页进行处理,唱吧中采用了前者,所以SuperKtv中也采用了第一种方案。

  • 相机操作及美颜、滤镜
       这一部分内容需要有一定的OpenGL ES基础,可以理解为对每一帧画面中的每一个像素进行处理,然后渲染到屏幕上。详细原理可以参考博主OpenGL ES系列文章
    Android OpenGL ES从入门到进阶(一)—— 五分钟开发一款美颜相机
    Android OpenGL ES从入门到进阶(六)—— OpenGL ES人像美白与磨皮初探
    Android OpenGL ES从入门到进阶(八)—— 万能的Lookup滤镜
    上述内容都属于OpenGL ES基础部分,本文不再描述,有疑问的可以留言交流。

  • 贴纸(道具)
       在美颜相机类产品中,贴纸功能应该算一个重头戏,唱吧中称为道具,目前抖音中的贴纸类功能算比较强大的。这类功能的实现依赖于人脸面部特征点识别,而且对实时性要求比较高,国内商用人脸识别Sdk里面做得比较好的商汤科技和旷视科技,中小型公司的美颜类产品都从这两家购买Sdk,抖音和FaceU激萌据笔者所知用的是字节跳动自研的人脸识别Sdk。由于对实时性要求较高,一般都是采用深度学习算法,训练好模型之后根据模型识别。如果对实时性要求不高的话可以使用Dlib http://dlib.net/,基于OpenCV的使用比较简单,这个也是基于深度学习的,官方文档中给的模型大概有100Mb,毕竟是开源免费的,性能上较商用Sdk有不小差距。经实测,Dlib识别一幅1080 x 720的图片大概需要400 - 500ms,在某些性能低的手机上甚至需要1000ms,而商用Sdk可以在20ms内完成,这也是为什么有开源免费的,商用的还能卖那么贵的原因,同时启示我们要尊重知识产权。由于这些限制,SuperKtv中暂时没有动态贴纸功能。

   关于贴纸的使用,可以参考另一篇博客 Android OpenGL ES从入门到进阶(七)—— OpenGL ES 2D贴纸与Blend混合 这是一篇关于静态贴纸的文章,动态贴纸稍微麻烦一些,需要根据面部特征点时时改变贴纸的位置和角度,有的还需要做平移和缩放,贴纸的变化规则需要提前定义好,以商汤Sdk为例,贴纸的运动规则定义在一个json文件中,使用贴纸先解析json内容,然后随时间做周期运动,这就是动态贴纸的大致原理。

  • 视频编码
       不同于音频,原始视频占用磁盘空间非常大,如果录到的视频保存成原始数据的话那不可想象,所以在录制视频之后要编码之后进行保存,视频编码格式很多,移动端流行H264和H265,SuperKtv中采用的事H264编码,所以文中也以H264为例分析。
       如果保存了带美颜效果的视频后不需要对画面进行二次编辑(比如唱吧),在Android平台首选MediaCodec配合Surface,一方面使用硬件编码速度要优于软件编码,另一方面系统自带Api使用上会更方便,Mediacodec可以将Surface上的数据直接编码,只需要将编码后的数据写文件即可,这非常契合SuperKtv的需求。但硬件编码的兼容性不是很好,尤其是安卓的机型成千上万种,总有几款手机使用起来有这样那样的问题,考虑到这样的情况有时候也会采用软件编码,软件编码H264推荐使用X264,算法性能上要优于FFmpeg自带的H264编码算法,但是普遍的用法是将X264编译到FFmpeg里面,用统一的接口调用,这种方法稍微麻烦一些,先要将相机的回调数据做美颜、滤镜等处理,然后再根据需求看是否需要进行数据格式转换,因为Android的Camera API有两套,Camera1推荐使用NV21,Camera2推荐使用Yuv420p,用FFmpeg编码的时候,由于颜色空间的特殊性,为方便使用,建议将数据统一转换为Yuv420p格式再进行编码,转换算法网上有,这里推荐使用LibYuv,功能很强大,这一部分内容要求要对图像的几种颜色空间有所了解,特别是RGBA系列和YUV系列。
       如果在编辑页面要对原始视频进行二次编辑,那么只能保存原始画面,这时候可以使用MediaCodec,也可以使用FFmpeg,在这种需求下二者的却别不是很大了,硬件编码速度快,但兼容性稍差,软件编码兼容性好,但速度稍慢,可适当取舍而定。当然FFmpeg也是支持硬件编码的,只是编译的时候需要做一些额外的处理,但如果是硬件编码,为何不直接使用系统API呢。

(五)音视频编辑页

  前两幅图是SuperKtv的实现,后两幅图是唱吧的实现。
  同样的,唱吧在音视频编辑页面也进行了较大改版。唱吧在演唱功能下没有实现对视频的编辑,所以也跟随了唱吧的风格,主要是对音频的编辑,功能实现上跟前面提到的音频编辑一样,比较重要的一点是视频播放,涉及到视频解码和音视频同步。如果说要实现视频的编辑功能,有两种思路,一中是用OpenGL ES处理后后台保存,另一种是使用FFmpeg或OpenCV对数据处理后保存,推荐使用OpenGL ES进行视频编辑。

  • 视频解码
       Android平台推荐使用MediaCodec解码,可以直接解码到Surface上进行显示,这省去了很多额外的操作(数据格式转换和渲染)。
    当然也可以使用X264或FFmpeg进行解码,将解码之后的数据拷贝到ANative_Window提供的缓冲区用以显示,这比起FFmpeg编码已经方便很多了,若需要使用OpenGL ES对视频进行编辑,需要将数据转换成纹理,并上传到OpenGL ES提供的缓冲区用来显示。唱吧导入外部视频可以编辑,可以用上述思路均可实现。

  • 音视频同步
       音视频同步是视频播放很重要的一个环节,直接影响到前面所有工作的结果。音视频同步一般使用时间戳同步,这要求在编码的时候加上正确的时间戳。Camera的回调里面有当前帧的时间戳,以微秒为单位,跟MediaCodec所需单位一致,编码时使用较方便。FFmpeg的时间戳跟MediaCodec不同,使用的是根据时间基计算出来的,编码时只需要加上当前帧的索引就可以。同步的原因是因为音频在单位时间内的播放数据量是固定的,只需给播放Api回调填充数据就可以,但视频没有相应的机制,在画面上显示的内容以及什么时候显示都是由外界控制,假如用解码速度控制的话,如果解码速度太快,在很短的时间内就能播放完一个较长视频,如果解码速度太慢,就会导致画面卡顿。同步的过程大致可以描述如下:如果当前帧播放快了,那下一阵继续播放当前帧,相当于等待,如果当前帧播放太慢,那就丢弃,播放下一帧或下下一帧,这样就能控制视频播放的速度了。同步的方法一般有音频向视频同步,视频向音频同步,二者向基准时间同步,普片选用视频向音频同步,因为音频在单位时间内播放的数据固定的。

(六)保存和作品列表页

  第一幅图是保存的界面,第二幅图是保存后的作品列表页面。界面简单,没有与唱吧进行对比。

  保存的目的是为了让保存的结果和编辑页面调节后的效果一样,相当于重复一遍编辑页面的操作,只不过要变成后台操作,前台唯一需要显示的是保存进度,后台处理过的数据直接进行编码即可。

  • 音频编码
       前面提到,为方便编辑,录到声音后保存成了原始数据,但音频的结果必然是成品(编码后的)的,所以在保存之前先要编码。移动端音频编码一般使用Mp3合适或者AAC格式。Mp3编码推荐使用Lame,AAC编码推荐使用Fdk-aac,也可以使用MediaCodec 或者FFmpeg自带编码算法。对于SuperKtv的保存需求来说,编码速度影响不是很大,所以选哪一种都可以,在了解MP3格式和AAC格式的区别之后,任选一种使用哪种方法都可以达到很好的效果。有一个不同点是MediaCodec编码的AAC是不带ADTS的裸流,如果此处是最终音频文件,需要手动添加ADTS,而使用Fdk-aac编码之后会自动加上ADTS。

  • 音视频合并
       截止这一步骤,前面生成的音视频文件都是单独的,对于音频录制来说到这一步就已经完成了,对于视频来说还需要将音频和视频合并成一个文件,音视频合并可以理解为将两个文件合并成一个文件,通过某种规则将两个类型的数据区分,使用的时候能够单独拿到音频和视频数据,有点类似于编辑页面音视频的同步的感觉。使用Android提供API MediaMuxer或者使用FFmpeg均可以。使用MediaMuxer合并音视频的时候,若音频是AAC格式,需要提供AAC裸流,使用FFmpeg的话就不用,甚至可以直接用命令来完成,合并完之后就是普遍意义上的视频,是可以直接发布的作品。

(七)本地作品播放页

  第一幅推是音频播放界面,第二幅图是视频播放界面。界面简单,没有与唱吧进行对比。

   前面做了好多工作,最后的结果只是一个文件,一种是音频文件,类似于.mp3,常用的还有.m4a等;另一种是视频文件,类似于.mp4,常用格式还有.flv等。对于这样的文件每个人应该都很熟悉,手机自带功能就能够播放,所以在SuperKtv中欣赏或者分享个人作品的时候没必要自己再写一套播放器,使用现成的就可以,甚至直接调用系统API MediaPlayer就可以。关于播放器的使用,安卓平台下推荐使用EXOPlayer,这是谷歌基于MediaCodec的一款开源播放器,支持音、视频及其常用格式,使用硬件解码,还带有一些基础控件,定制性较高。SuperKtv就是基于ExoPlayer做的本地作品播放功能,效果展示如上图,使用体验也很不错。

三、总结

   本文是以“唱吧App”为参考,基于Android平台,总结了移动端音视频技术的实现策略,适合有一定音视频基础的开发人员,可以作为技术方案参考,由于时间精力有限,实现细节随后以文章形式发布,若有疑问欢迎大家留言交流。

转载请注明出处。

友情链接:
1、FFmpeg http://ffmpeg.org/ (音视频必备神器)
2、Sox http://sox.sourceforge.net/ (音频界瑞士军刀)
3、SoundTouch https://sourceforge.net/projects/soundtouch/ (音调、变速)
4、Dlib http://dlib.net/ (人脸特征点识别开源库)
5、Oboe https://GitHub/Oboe (Android平台低延时音频接口)

猜你喜欢

转载自blog.csdn.net/liuderong0/article/details/109172929