signed

QiShunwang

“诚信为本、客户至上”

【语音合成】基于语音信号变速matlab源码

2021/3/21 12:07:27   来源:

实现变声的效果主要有两种方式,一种是对语音的本身进行修改,通过修改语音的一些特性达到变声的效果;另一种则是语音克隆,直接将输入语音特性转化成模板的语音特性。这里我们介绍第一种方式,语音克隆后面有机会再介绍。其实如果没有特定的变声需求,直接用不同的采样率读取原来的文件就会产生变声效果。

 

基音同步叠加(Pitch Synchronous Overlap Add, PSOLA, 发音是:劈死欧拉)是一种将语音在频域(FD-PSOLA)或者时域(TD-PSOLA)进行调整的算法,经常用于语音波形合成,这里将其稍微修改用于语音变声。其主要原理是通过改变基频间隔的时间长度来调整音高,通过重复或者省略一些基音片段来调整音长,达到变速和变调的效果改变语音。PSOLA算法分为基音同步分析和标注、基音同步修改和基音同步叠加几个部分。下面分别进行介绍。

 

基音同步分析和标注

基音同步分析和标注是为了确定语音单元基音周期的位置,语音分为清音段和浊音段,对于不同的语音段标注方式也不同。对于浊音段进行正常的标注,对于清音段直接将其基音周期设定一个确定的常数。进行基音标注首先要求基音周期,这部分内容在花式估计基音周期里面已经介绍过了,这里就不再赘述。一般基音周期估计后一般会经过中值滤波进行平滑处理。至此,基音同步分析的过程就结束了。

 

下面介绍基于动态规划的标注过程。再获得每一帧的基音周期后,可以根据基音周期的值直接将语音分为浊音段和清音段。对每个浊音段进行如下的操作:

  • 找出浊音段的最大峰值,该峰值对应的位置t就是一个基音脉冲的标注点

  • 取出该浊音段最大峰值对应的基音周期T

  • 在[t-1.5T, t-0.5T]和[t+0.5T, t+1.5T]范围内以t为中心向两边搜索,在搜索区间内选择三个峰值作为待定的基音标注位置,然后依此搜索当前浊音段

  • 对所有候选位置基于动态规划求解最佳路径,使得相邻两帧之间的标注距离最短

  • 对其他浊音段重复上述步骤

这里基音标注标注其实标注的是声道的脉冲响应,可以参考说话声音是如何产生的,如下图所示。

图片

图片

 

基音同步修改

在求出基音标注位置后,我们就可以对基音进行修改。音长可以通过插入、删除语音单元的同步标注点来实现;音高可以通过增加、减少语音单元同步标注点的间距来实现。我们标注好的基音周期如下图所示

图片

如果我们不修改音高和音长,而只是单纯的叠加可以得到如下的波形,这个波形和原始语音波形相近。

图片

如果我们减少一个周期,可以得到如下的波形,在时间维度上,波形变长意味着语音时间减少。

图片

图片

 

如果我们减少基音标注的间距,会使得基音变高,反之则会变低。如下所示。

图片

(a)原始基音标注

图片

(b)基音标注间距减少

图片

(c)基音标记间距增加

 

基音同步叠加

经过上述两个步骤之后我们就可以开始最后的基音同步叠加了。这个过程类似于分帧加窗FFT后还原的过程,大家应该都比较熟悉了。其具体过程如下图所示。

图片

clear all; clc; close all;

[xx,fs]=wavread('C7_4_y.wav');                     % 读取文件
xx=xx-mean(xx);                           % 去除直流分量
x=xx/max(abs(xx));                        % 归一化
N=length(x);                              % 数据长度
time=(0:N-1)/fs;                          % 信号的时间刻度
wlen=240;                                 % 帧长
inc=80;                                   % 帧移
overlap=wlen-inc;                         % 重叠长度
tempr1=(0:overlap-1)'/overlap;            % 斜三角窗函数w1
tempr2=(overlap-1:-1:0)'/overlap;         % 斜三角窗函数w2
n2=1:wlen/2+1;                            % 正频率的下标值
X=enframe(x,wlen,inc)';                   % 分帧
fn=size(X,2);                             % 帧数
T1=0.1; r2=0.5;                           % 端点检测参数
miniL=10;                                 % 有话段最短帧数
mnlong=5;                                 % 元音主体最短帧数
ThrC=[10 15];                             % 阈值
p=12;                                     % LPC阶次
frameTime=FrameTimeC(fn,wlen,inc,fs);     % 计算每帧的时间刻度
in=input('请输入伸缩语音的时间长度是原语音时间长度的倍数:','s');%输入伸缩长度比例
rate=str2num(in);

for i=1 : fn                              % 求取每帧的预测系数和增益
    u=X(:,i);
    [ar,g]=lpc(u,p);
    AR_coeff(:,i)=ar;
    Gain(i)=g;
end

% 基音检测
[voiceseg,vosl,SF,Ef,period]=pitch_Ceps(x,wlen,inc,T1,fs); %基于倒谱法的基音周期检测
Dpitch=pitfilterm1(period,voiceseg,vosl);       % 对T0进行平滑处理求出基音周期T0

tal=0;                                    % 初始化
zint=zeros(p,1); 
%% LSP参数的提取
for i=1 : fn
    a2=AR_coeff(:,i);                     % 取来本帧的预测系数
    lsf=lpctolsf(a2);                       % 调用ar2lsf函数求出lsf
    Glsf(:,i)=lsf;                        % 把lsf存储在Glsf数组中
end

% 通过内插把相应数组缩短或伸长
fn1=floor(rate*fn);                        % 设置新的总帧数fn1
Glsfm=interp1((1:fn),Glsf',linspace(1,fn,fn1))';% 把LSF系数内插
Dpitchm=interp1(1:fn,Dpitch,linspace(1,fn,fn1));% 把基音周期内插
Gm=interp1((1:fn),Gain,linspace(1,fn,fn1));%把增益系数内插
SFm=interp1((1:fn),SF,linspace(1,fn,fn1)); %把SF系数内插

%% 语音合成
for i=1:fn1; 
    lsf=Glsfm(:,i);                       % 获取本帧的lsf参数
    ai=lsftolpc(lsf);                       % 调用lsf2ar函数把lsf转换成预测系数ar 
    sigma=sqrt(Gm(i));

    if SFm(i)==0                          % 无话帧
        excitation=randn(wlen,1);         % 产生白噪声
        [synt_frame,zint]=filter(sigma,ai,excitation,zint);
    else                                  % 有话帧
        PT=round(Dpitchm(i));             % 取周期值
        exc_syn1 =zeros(wlen+tal,1);      % 初始化脉冲发生区
        exc_syn1(mod(1:tal+wlen,PT)==0)=1;% 在基音周期的位置产生脉冲,幅值为1
        exc_syn2=exc_syn1(tal+1:tal+inc); % 计算帧移inc区间内的脉冲个数
        index=find(exc_syn2==1);
        excitation=exc_syn1(tal+1:tal+wlen);% 这一帧的激励脉冲源
        
        if isempty(index)                 % 帧移inc区间内没有脉冲
            tal=tal+inc;                  % 计算下一帧的前导零点
        else                              % 帧移inc区间内有脉冲
            eal=length(index);            % 计算有几个脉冲
            tal=inc-index(eal);           % 计算下一帧的前导零点
        end

三、运行结果

在这里插入图片描述

四、备注

完整代码或者代写添加QQ1575304183

往期回顾>>>>>>

【信号处理】基于遗传算法的VST混响matlab源码

【语音合成】基于重叠存储法的信号分帧与还原matlab源码

【信号处理】脉搏信号之脉率存档matlab源码含GUI

【信号处理】单通道盲源分离(SSA-ICA)算法

【信号处理】数字电子琴设计与实现matlab源码

【信号处理】LDPC码的校验矩阵、编译码matlab源码

【信号处理】OFDM-MIMO通信建模与仿真matlab源码

【信号处理】数字调制信号仿真matlab源码含GUI