本文系作者原创。如转载,请注明出处。 谢谢!
在做语音类APP时,语音留言(以码流形式)会被保存下来。当听时想快点听完,于是就有了语音加速播放功能。同时还有个需求,能实时切换播放速率,即当1.5倍
速播放时切两倍速,就要立刻两倍速播放。
首先做了一番调研,看几倍速后就基本上听不清说什么了。找来了一款能在PC上运行的有加速播放功能的软件,试验下来两倍速以上就基本上听不清说什么了。于是除
了正常速率外,只有两个速率可调:1.5倍速和两倍速。同时搜了一下变速相关的开源算法(算法原理请看其他相关文章),发现主打变声功能的sound touch使用率较高。它可以改变音调(pitch)和语速(rate)等,好多软件拿它做一些趣味音频。基于sound touch做了一个应用程序看加速效果,同时要兼顾男声和女声。试验下来发现语速加快了后音调就变了,也就是还要同时调音调。改了音调做了一番调试后只能接近原声,能兼顾男女声。再试试在PC上运行的软件,加速后也是只能接近原声。做了这些后就决定用sound touch来做了,在不同的速率下音调(pitch)和语速(rate)参数值也确定了下来(男女声在一个速率下用一组参数值)。
再看我们的相关代码,系统支持ILBC和OPUS两种codec,ILBC每帧30ms, OPUS每帧20ms, 播放线程每20ms运行一次,即每次取20ms PCM数据播放。要想播放语音留言,首先要解码码流成PCM 数据放在buffer1中,再看是否要加速播放,要加速的话就把buffer1中的语音数据调加速函数处理后放在buffer2中,不加速就把buffer1中的语音数据直接拷进buffer2中,等待buffer2中的数据被取走播放。根据这些经过一番尝试后用了分段循环处理的实现方法。一个循环内取固定时间长度的原始音频帧,取出的这段音频帧不管是否加速在这个循环内都要正好播放完,同时兼顾ILBC和OPUS两种不同的帧长和1.5倍速两倍速两种不同的速率。ILBC时,每帧30ms,每次取20ms播放,有1.5倍速两倍速两种速率,最小的原始PCM数据长度(即最小固定长度)是120ms(原始ILBC帧数是4帧,1.5倍速后是80ms,4次可取完播放,2倍速后是60ms,3次可取完播放)。OPUS时,每帧20ms,每次取20ms播放,有1.5倍速两倍速两种速率,最小的原始PCM数据长度也是120ms(原始OPUS帧数是6帧,1.5倍速后是80ms,4次可取完播放,2倍速后是60ms,3次可取完播放)。所以一个循环内取的最小原始音频的固定长度是120ms. 一个循环结束后正好播放完,清空buffer2,然后取下一段原始音频帧解码加速后播放。为了在循环内播放时声音不断断续续,buffer2里要有足够的数据供取走播放。这样在每个循环刚开始的时候,要尽可能多的解码帧数加速后放在buffer2中等取走播放。至于几次解码完所需的码流帧数,这要根据cpu来定。一般一个循环的前一两次就把码流解码加速做完了,后面的次数里只需要从加速后的buffer2里取数据播放就可以了。这样循环内每次播放线程运行时都能取到数据播放,下一循环开始时也能取到数据播放,不存在断断续续的情况。
当要求实时切换语速时,要把这120ms原始语音数据播放完在下一个循环里才能改变语速。由于120ms很短,用户基本无感知,可以认为是实时的,不影响用户体验。
当到语音结尾时,有多少帧就解码加速多少帧供播放,当从加速后的buffer里不能取到20ms数据就可认为播放结束了。这时用零补足20ms数据,把这些数据播放出去,从而结束播放。
跟上层的UI联调后效果不错。这样APP内的语音加速方案就搞定了。