函数签名
/**
* @param st 全局context,存储一些上下文用的变量和结构体
* @param X 根据入参in生成的频域数据,其中在训练时,低频部分被全部清零
* @param P 是含有基频(pitch)部分语音信号生成的频域数据
* @param Ex [band](https://blog.csdn.net/mimiduck/article/details/106390000)能量数据
* @param Ep 对应pitch信号数据的band能量
* @param Exp 信号能量与pitch能量的相关(correlation)值
* @param features rnnoise训练定义的feature
* @param in 输入数字信号,即时域信号
* @return gain 如果是训练数据有返回值
* */
int compute_frame_features(DenoiseState *st,
kiss_fft_cpx *X,
kiss_fft_cpx *P,
float *Ex,
float *Ep,
float *Exp,
float *features,
const float *in)
该函数主要做以下几件事情:
降采然后找pitch
其中几个函数pitch_downsample,pitch_search,remove_doubling都是opus源码里的,作者基本照搬了过来,主要是为了找pitch
计算pitch信号band能量值,输入信号band能量值,以及根据两者计算出相关系数Exp
apply_window(p); // pitch数据应用window
forward_transform(P, p); // 傅里叶变换计算pitch频域数据
compute_band_energy(Ep, P); // 计算pitch部分的band能量
compute_band_corr(Exp, X, P); // 计算信号频域与pitch频域的相关band能量系数
注意,这里的Bark频率划分为22个区域,所以NB_BANDS=22
Exp做标准化:
E x p i = E x p i / s q r t ( 0.001 + E x i ∗ E p i ) Exp_i = Exp_i/sqrt(0.001+Ex_i*Ep_i) Expi=Expi/sqrt(0.001+Exi∗Epi)
然后在做一次dct,实际上就是信号与pitch相关BFCC了
for (i=0;i<NB_BANDS;i++) Exp[i] = Exp[i]/sqrt(.001+Ex[i]*Ep[i]);
dct(tmp, Exp);
然后填充feature的 NB_BANDS+2NB_DELTA_CEPS (22+26=34)到NB_BANDS+3*NB_DELTA_CEPS (40),填充后实际上是做了一些参数调整的。
for (i=0;i<NB_DELTA_CEPS;i++) features[NB_BANDS+2*NB_DELTA_CEPS+i] = tmp[i];
features[NB_BANDS+2*NB_DELTA_CEPS] -= 1.3;
features[NB_BANDS+2*NB_DELTA_CEPS+1] -= 0.9;
features[NB_BANDS+3*NB_DELTA_CEPS] = .01*(pitch_index-300);
而feature的1-NB_BANDS(22)是由log10(Ex)再做一次DCT后填充的,代码如下,
logMax = -2;
follow = -2;
for (i=0;i<NB_BANDS;i++) {
Ly[i] = log10(1e-2+Ex[i]);
Ly[i] = MAX16(logMax-7, MAX16(follow-1.5, Ly[i]));
logMax = MAX16(logMax, Ly[i]);
follow = MAX16(follow-1.5, Ly[i]);
E += Ex[i];
}
dct(features, Ly);
这个实际上就是输入信号的BFCC
22~33部分为delta差值,
features[0] -= 12;
features[1] -= 4;
ceps_0 = st->cepstral_mem[st->memid];
ceps_1 = (st->memid < 1) ? st->cepstral_mem[CEPS_MEM+st->memid-1] : st->cepstral_mem[st->memid-1];
ceps_2 = (st->memid < 2) ? st->cepstral_mem[CEPS_MEM+st->memid-2] : st->cepstral_mem[st->memid-2];
for (i=0;i<NB_BANDS;i++) ceps_0[i] = features[i];
st->memid++;
for (i=0;i<NB_DELTA_CEPS;i++) {
features[i] = ceps_0[i] + ceps_1[i] + ceps_2[i];
features[NB_BANDS+i] = ceps_0[i] - ceps_2[i];
features[NB_BANDS+NB_DELTA_CEPS+i] = ceps_0[i] - 2*ceps_1[i] + ceps_2[i];
}
/* Spectral variability features. */
if (st->memid == CEPS_MEM) st->memid = 0;
cepstral_mem是一个8*22的数组,每一次feature里的值填充到ceps_0,然后这个数组会往下再做一次。
ceps_0是float指针,它指向的是ceptral_mem第一个NB_BANDS数组,然后每次与相邻的band数组相见,做出一个delta差值。
最后一个特性值,
for (i=0;i<CEPS_MEM;i++)
{
int j;
float mindist = 1e15f;
for (j=0;j<CEPS_MEM;j++)
{
int k;
float dist=0;
for (k=0;k<NB_BANDS;k++)
{
float tmp;
tmp = st->cepstral_mem[i][k] - st->cepstral_mem[j][k];
dist += tmp*tmp;
}
if (j!=i)
mindist = MIN32(mindist, dist);
}
spec_variability += mindist;
}
features[NB_BANDS+3*NB_DELTA_CEPS+1] = spec_variability/CEPS_MEM-2.1;
所以,用于训练的特性值数组整体部署排布如下,
- 34-40 输入信号与pitch相关BFCC
- 0-21 输入信号BFCC
- 0-5 / 22-27/ 28-33 delta BFCC
- 41 二阶delta