注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

麦斯·渐行渐远

人无远虑,必有近忧

 
 
 

日志

 
 

Flash中实现语音变声(中)  

2011-02-09 16:15:11|  分类: 工作 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
大家新年快乐~

过完年从家里回来了,真是舒服啊。今天第一天上班,精神充沛,是个好兆头,嗯。。。。

===== 华丽丽地分隔符,下面是正文了 =====

上次我们对语音变声进行了原理上的讨论,提到在实际操作中,输入的信号不可能是一个连续的函数,也不可能做足够多的傅立叶展开。那么具体在实际中我们要如何实现这个算法呢?

为了在科学计算和数字信号处理等领域使用计算机进行傅立叶变换,必须将函数Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远定义在离散点而非连续域内,且须满足有限性和周期性条件。这种情况下,使用离散傅立叶变换,将函数Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远表现为下面的求和形式:

   
  
上面的式子中Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远就是傅立叶幅度。

(以上来源:维基百科)

上面这个公式里面怎么没有sin/cos项呢?其实,由于n是实数,那么根据欧拉公式,上面Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远
 的公式可以改写成:


离散傅立叶变换(DFT)是一种很重要的方法,快速傅立叶变换(FFT)的诞生和发展,使得计算离散傅立叶变换的效率大大提高。FFT现在已经非常成熟,有很多现成的实现可以借鉴而无需自己从头实现一个。

说完了DFT,我们再来看看计算机中是如何获取输入的音频信号的。

声卡或者其他采样设备在启动以后,会每隔一段时间Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远去读取输入信号的强度,并且使用一个数值来表征这个强度,最后把这个数值送入总线(接下来的到底是保存到磁盘还是进行处理就不是采样设备的事情了)。

这个过程,称为采样(采集样本),这个数值就称为采样值,而:


就被称为采样频率。

现在我们将采样得到的连续N个采样值进行快速傅立叶变换以后,可以得到N个傅立叶幅度。根据DFT的定义和上一篇文章的讨论,这个值表征了输入信号的某个特定频率分量的强度。对于第k个点,这个频率是可以计算出来的:

,式子中fs就是前面提到的采样频率。

于是,利用快速傅立叶变换,我们可以得到一段音频信号在各个频率上的分量,即这个音频信号的频谱

前文我们提过,音频变声最简单的形式就是将音频信号的频谱向高频方向伸展或者低频方向收缩。那么在这里如何进行搬移呢?

以前我们说过,要变声的话我们只需要修改sin/cos项的自变量就可以了。不过根据DFT的定义,我们只能根据输入信号得到一系列的傅立叶幅度,这些幅度对应的sin/cos项是根据输入采样的个数和采样频率确定的,是无法修改的。那么这条路走不通了么?

其实我们可以考虑一个变通的方法,既然sin/cos项无法修改,那么我们就修改傅立叶幅度。比如说,还是上面的例子,我们将采样得到的N个采样值进行快速傅立叶变换以后,得到了N个傅立叶幅度,记为Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远,现在我们令:

Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远

从而得到了一个新的N个傅立叶幅度。这意味着什么?这意味这原信号在频率Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远上的强度不再是Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远,而是Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远了,于是这就将原信号的频谱整体向高频方向搬移了:


频谱搬移完成以后,就可以利用快速傅立叶变换的逆变换得到处理过的采样,回放以后就可以得到变声以后的效果。

可能细心的读者已经大家可以看出,上面的变换方法只能将频谱搬移到(1/N)*fs的整数倍,并不是我们要求的频谱伸缩。如果是希望伸缩到任意频率怎么办呢?

考虑最简单的情况,我们希望将频谱进行线性缩放,不妨记系数为a(a>0),当a=1时,表示不修改,a<1时表示向高频方向伸展,a>1时表示向低频方向收缩。任给一个k(1<k<N/2),使用线性插值的办法就可以得到新的傅立叶幅度:

 
需要注意的是,由于Flash中实现语音变声(中) - vivimice -    麦斯·渐行渐远是复数,上述线性插值需要在极坐标形式下进行,如果直接在笛卡尔坐标形式下进行,逆变换以后得到的结果会包含明显的噪声。

另外,为什么上面k的取值是1到N/2,而不是0到N呢?这里有两个原因:

1、第一个点(k=0)是信号的直流分量
2、我们的采样值都是实数,进行FFT以后得到的结果是共轭对称的:

 
所以我们只需要计算1到N/2这些点的傅立叶幅度,0号点直接使用原信号的直流分量,N/2到N-1的值直接求对应的共轭对称结果就可以了。这样就可以形成整个频谱。

以上讨论的已经接近最终代码的实现了,不过似乎还差那么一点东西,我们一起来捋一捋:

首先,采样设备(例如声卡)从信号源(例如话筒)处采集了一堆数据,比如说M个。
然后,我们取这M个数据进行快速傅立叶变换
接着,我们利用线性插值对频谱进行伸展或者收缩
最后,我们把处理过的频谱利用快速傅立叶变换逆变换转换回时域,得到处理过的信号

等等,这里缺了一些什么?对了,问题就在“N”这个数上。

一般情况下,快速傅立叶变换要求输入的样本数为2的整数次幂(2、4、8、16、32、64、256……),但是采样设备不可能每次都恰好得到这么多数据,而且,随着样本数的增加,会产生一些附加的效果(后面讨论),所以不可能每次都把所有采集到的数据都全部进行处理,必须分批次来。

如何分批次呢?最简单的办法,我们假定每次取N个(N为2的整数次幂,如果原始信号不足的话,则以零来补充),处理完这N个以后,然后再处理接下来的N个。信号处理里面把这样的一批数据称为一帧数据。N就称为帧的大小。

听起来似乎是十分完美的,不过我们会发现在处理完成以后,帧与帧的交界处波形会存在不连续,于是就产生了难听的噪声。

这要如何解决呢,比较常用的方法就是让帧重叠。比如说,第一帧从0开始,取了N个数据进行处理,第二帧从0.75N——而不是N——开始取数据,相当于第一帧和第二帧重叠了25%,这个称为帧重叠率。然后处理完毕以后,第一帧的后25%和第二帧的前25%采用线性叠加的方式混在一起,这样就可以有效的削减帧之间的不连续。

帧的大小也是有讲究的,帧太大,会导致出现回声,帧太小则会出现“噼噼啪啪”的声音。仔细的根据输入信号的特点选择帧的大小是一件很重要的工作。

至此,基本的算法就已经解决了。相信有基础的读者已经可以根据上面的叙述很顺利的写出伪代码了。不过效果要接近理想的程度还是有很多工作需要做的:有数字信号处理经验的读者可以考虑再帧取样的时候加各种不同的窗函数,或者利用补零扩充帧的大小,提高后续频谱伸缩的时候插值的精度。另外,还可以考虑将修改插值的算法。

剩下的事情就是实现了。这些功能使用C来实现的话如烹小鲜,现成的FFT库非常之多。不过正如本文标题所说,我们讨论的是在Flash中实现这一功能。Flash如何获取设备的采样呢?让Flash来进行如此密集的运算,是不是合适呢?如果运行性能低下,应该如何优化?下一篇博文将会重点讨论如何使用Flash来实现语音变声。
  评论这张
 
阅读(1844)| 评论(9)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017