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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

posted @ 2018-08-26 20:02  cpuimage  阅读(6106)  评论(4编辑  收藏  举报