关于音频变声算法,这个是一个很多人特别感兴趣的话题。
当然也有不少开源算法可以参阅学习,有基于时域,也有基于频域的算法。
最终算法想要达到的目的是一致。
最近也有不少网友问过关于变声算法的一些细节问题,邮件询问我。
要给出一个比较合理或者说通俗易懂的解释,看似简单,其实还蛮难的。
按照大概的一个逻辑思路,稍微理一理,所以这个主题必须加上“大话”这个前缀。
也不打算讲特别高深的,当然也是因为讲不来。
之于图像算法领域,非常重要的算法是高斯模糊,
当然也可以认为是卷积,高斯模糊是卷积的一种特例,这里就不展开了。
而之于音频,也许你也猜到了,基于时间的,毫无疑问,就是重采样算法。
音频采样率是指录音设备在一秒钟内对声音信号的采样次数,
采样频率越高声音的还原就越真实越自然。
在当今的主流采集卡上,采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级,
22.05KHz只能达到FM广播的声音品质,
44.1KHz则是理论上的CD音质界限,48KHz则更加精确一些。
看到这里,也许大多数人还是没法理解采样频率大概是什么意思。
换个角度来说的话,就是假设一个人说“你好”,花了20毫秒,而机器在这20毫秒内,
采集的数据多少就可以理解为采样率高低。
也就是说,20毫秒内,采集到的数据量就是可以大概认为目前的采样率,数据量越大,精度越高,采样率越高。
那么,我们再换一个思路,想一个问题。
如果在同样的速率的情况下,
一个人的语速快,一个人的语速慢,那也可能造成采样数据分布不一致。
这里就可以展开一个音频算法,就是变速。
嗯,是的,就是变速。
从原理上来讲的话,其实变速就是在同样的采样率环境下,对采样数据进行拉伸或压缩。
从算法的角度上来说的话,可以认为是插值或抽值。
如果你让一个人讲话的速度变得更快怎么做,
很明显,就是在同样的采样率下,抽掉一些样本。
反之,降速则是插入一些样本。
最终决定变速效果的就是插入样本和抽离样本的权重计算。
例如原来采样到的数据是
1234
加速的时候,抽离样本 1 和 4
23
降速的时候,增加样本
11223344
当然只是举个例子,便于大家理解这个概念逻辑。
看到这里,肯定有人会问,
那声音的大小呢?或者说信号的强弱呢?
其实也就是提升音量和降低音量,我想这个应该不用解释。
变速是时域变,空间不变。
而音量则反之,时域不变,空间变。
可以简单粗暴地理解,就是线性拉伸。
例如原来采样到的数据是
1234
每个样本+4,直接拉伸为
5678
也有采用乘法进行拉伸的,
例如 乘以2
2468
上面是增大音量,降低音量反之就是减和除。
而最终不管变速还是音量调节,
最终算法要做的事情就是确定对应位置的对应权重。
当然也要看最终想要达到什么样的效果,适配权重。
饶了这么一大圈,还是没有说到变声的问题。
其实,变声就是变速+音量调节。
以上变速也好,音量调节也好,相对而言都是线性拉伸,
直接的加减乘除然后插值抽值就能达到的。
而变声的概念其实也是类似的,
就是在在同一时域内同时调节对应时域的音量权重。
换言之就是在同一个采样率内,同时控制语速和音量在一个特定的权重内。
其实就是一个时域和空间的二维拉伸。
理解这个逻辑确实有点绕。
用采样算法来做一个简单的示例。
参阅前面的文章《简洁明了的插值音频重采样算法例子 (附完整C代码)》
这个示例中的采样函数是:
void resampler(char *in_file, char *out_file) { //音频采样率 uint32_t in_sampleRate = 0; //总音频采样数 uint64_t totalSampleCount = 0; int16_t *data_in = wavRead_int16(in_file, &in_sampleRate, &totalSampleCount); uint32_t out_sampleRate = in_sampleRate * 2; uint32_t out_size = (uint32_t) (totalSampleCount * ((float) out_sampleRate / in_sampleRate)); int16_t *data_out = (int16_t *) malloc(out_size * sizeof(int16_t)); //如果加载成功 if (data_in != NULL && data_out != NULL) { resampleData(data_in, in_sampleRate, (uint32_t) totalSampleCount, data_out, out_sampleRate); wavWrite_int16(out_file, data_out, out_sampleRate, (uint32_t) out_size); free(data_in); free(data_out); } else { if (data_in) free(data_in); if (data_out) free(data_out); } }