大话音频变声原理 附简单示例代码

发布日期:2019-02-24

关于音频变声算法,这个是一个很多人特别感兴趣的话题。

当然也有不少开源算法可以参阅学习,有基于时域,也有基于频域的算法。

最终算法想要达到的目的是一致。

最近也有不少网友问过关于变声算法的一些细节问题,邮件询问我。

要给出一个比较合理或者说通俗易懂的解释,看似简单,其实还蛮难的。

按照大概的一个逻辑思路,稍微理一理,所以这个主题必须加上“大话”这个前缀。

也不打算讲特别高深的,当然也是因为讲不来。

之于图像算法领域,非常重要的算法是高斯模糊,

当然也可以认为是卷积,高斯模糊是卷积的一种特例,这里就不展开了。

而之于音频,也许你也猜到了,基于时间的,毫无疑问,就是重采样算法。

音频采样率是指录音设备在一秒钟内对声音信号的采样次数,

采样频率越高声音的还原就越真实越自然。

在当今的主流采集卡上,采样频率一般共分为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) }}

让我们稍微变通一下,设一个采样速率,用来调节声音的速度,同时保证采样率不变。

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) float speed = 0.88//增加一个速度权重 uint32_t out_sampleRate = in_sampleRate * speed 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) //out_sampleRate改为输出一样的采样率in_sampleRate wavWrite_int16(out_file data_out in_sampleRate (uint32_t) out_size) free(data_in) free(data_out) } else { if (data_in) free(data_in) if (data_out) free(data_out) }}

修改后是这个样子的。

有心的朋友发现了。out_size数值有可能增大或缩小了。

以上示例代码,就是一个简单的变速算法。

变速就是这么一个原理,音量增大降低就不做示例了。

而变声是一个什么算法呢?

说白了,就是变速的同时保证out_size还是原来的totalSampleCount。

那要怎么保证呢?

答案就是插值,如果简单粗暴一点,补0或者删0即可。

当然这样做的话,可能会导致音量不一致,最终发声不对的情况。

这肯定是不科学的,最终的插值时候的权重和对应的内容,产生的效果就看各家本领了。

以上原理,也说得差不多了,具体怎么实现的话,

大家自行参阅相关的开源代码,再去理解一下。

另外说一下前面《声音变调算法PitchShift(模拟汤姆猫) 附完整C++算法实现代码》

这篇文章中的sin和cos 没有在有效区间内,所以fastsin fastcos计算的结果是有问题的。

详情大家还是参阅作者原算法吧。

当然,后面有时间我会放出,

简单清晰的变声算法的完整c代码和对应的示例代码。

而关于基于傅里叶变换的重采样算法,《基于傅里叶变换的音频重采样算法 (附完整c代码)》

在对应的github 项目fftResample上,我也做了算法逻辑上的修正。

发表过的文章一般很少进行二次编辑了,

关于后期的一些修正和变更,大家还是关注一下github项目的更新比较直接一点。

具体变声的实现原理,

如上所述,希望通过这篇文章,

大家对音频变声算法能有比较直观的理解和认识。

以上,权当抛砖引玉。

独乐乐不如一起玩乐。

若有其他相关问题或者需求也可以邮件联系俺探讨。

邮箱地址是: gaozhihan@vip.qq.com

 

1 0 9)