最近学习了语音合成方面的知识,总结下LPCNet的算法结构和工程流程。
深度神经网络比如WaveNet在语音合成中效果好但是由于计算复杂度高很难实时;DSP速度快,但是合成质量不高。LPCNet结合了信号处理和深度神经网络提升语音合成的效果。
语音合成
上世纪70年代,人们就开始研究如何对语音进行建模。source_filter模型认为语音可以通过信源和信道两个模块;信源部分将浊音近似为一系列规则间隔的脉冲,清音通过白噪声近似;信道部分可以用LPC滤波器建模。如下图所示
2016年DeepMind提出了WaveNet,基于深度神经网络进行语音合成。WaveNet每个音频点不是直接预测,而是有一个预测和采样的过程,官网解释这里为什么不直接选取最大值而是有一个采样过程是因为语音本身有一定的随机性,如果全部选取概率最大可能就会出现全0的情况。WaveNet能生成高质量的音频,但是复杂度太高所以无法实时。
WaveRNN的提出优化了性能问题,使用RNN和稀疏矩阵降低复杂度,但是还是需要大约10GFLOPS。
LPCNet算法
LPCNet基于WaveRNN为基础,加入LPC filter模块降低神经网络复杂度。他将采样点的预测分解为线性和非线性两部分,基于DSP来预测线性部分,基于神经网络预测较小的非线性残余部分。
LPCNet的网络结构
LPCNet分为FRN和SRN两部分,FRN是以帧级为单位进行操作,项目中以10ms(160采样点)为一帧,一帧计算一次;SRN是以样点为单位,每个采样点都要执行一次,一帧计算160次。
LPCNet结构解析
feature:
输入:训练阶段,15帧(2400个采样点)
输出: [15,20]特征数据
每帧提取的特征,工程中保存了36维的数据,网络使用了20维特征(18个BFCC,2个pitch参数pitch,gain),计算LPC需要16个系数。
其中BFCC的计算和MFCC类似,仅在频带划分有差异(MFCC可参考之前的文章https://cloud.tencent.com/developer/article/1831454)
compute LPC:
输入:前16个采样点{s_{t-16},S_{t-1}},和本帧16个LPC系数
输出:线性预测当前采样点P_t
使用LPC系数和前16个采样点预测下一个采样点Pt
FRN:
输入:feature特征[N,19,20]
输出:通过卷积和全连接层后,得到[N,19,C]特征
feature通过两层卷积和全连接,训练过程使用15帧(2400个采样点),由于要经过两次conv,所以要pad到19。输出为[N,15,C]的cfeat,作为SRN的条件输入
SRN:
输入:FRN的特征提取结果f,LPC预测系数P_t,上一个样点S_{t-1},上一个预测激励e_{t-1}
输出:预测当前时刻的非线性激励信号
输入合并后经过两层GRU和FC,最后通过softmax,得到本次激励e_t,结合p_t相加得到预测点值,训练160次(期间f不变)得到一帧的合成音频数据。
LPCNet工程
源码https://github.com/xiph/LPCNet
根据readme进行操作
- 配置
./autogen.sh
./configure
make
- 生成数据
./dump_data -btrain input.pcm features.f32 data.s16
其中input.pcm是将训练wav写入一个pcm文件,注意是int16
features.f32和data.s16是生成数据
其中features.f32是生成的特征值
--训练lpcnet使用-train,一帧20 16个特征
--训练lpcnet使用-btrain,一帧36 20 16个特征,多了36个burg特征
data.s16是网络模型的输入和输出采样值
生成数据源码dump_data.c文件
- 训练脚本
python3 training_tf2/train_lpcnet.py features.f32 data.s16 model_name --retrain plcn5cq_512_20.h5
- 测试脚本
python ./training_tf2/test_lpcnet.py lpcnet55Puq_640_01.h5 test_features.f32 test.s16
LPCNet源码分析
dump_data.c
主要用于dump生成训练和测试数据
重要代码注释
代码语言:javascript复制 // 二阶滤波,数据增广
biquad(x, mem_hp_x, x, b_hp, a_hp, FRAME_SIZE);
biquad(x, mem_resp_x, x, b_sig, a_sig, FRAME_SIZE);
for (i=0;i<FRAME_SIZE;i ) {
float g;
float f = (float)i/FRAME_SIZE;
g = f*speech_gain (1-f)*old_speech_gain;
x[i] *= g;
}
if (burg) {
float ceps[2*NB_BANDS];
// 计算burg特征,36
burg_cepstral_analysis(ceps, x);
// 写特征
fwrite(ceps, sizeof(float), 2*NB_BANDS, ffeat);
}
// 预加重
preemphasis(x, &mem_preemph, x, PREEMPHASIS, FRAME_SIZE);
for (i=0;i<FRAME_SIZE;i ) x[i] = rand()/(float)RAND_MAX - .5;
/* PCM is delayed by 1/2 frame to make the features centered on the frames. */
for (i=0;i<FRAME_SIZE-TRAINING_OFFSET;i ) pcm[i TRAINING_OFFSET] = float2short(x[i]);
// 计算帧特征,18(bfcc) 2(pitch]) 16(plc)
compute_frame_features(st, x);
RNN_COPY(&pcmbuf[st->pcount*FRAME_SIZE], pcm, FRAME_SIZE);
if (fpcm) {
compute_noise(&noisebuf[st->pcount*FRAME_SIZE], noise_std);
}
if (!quantize) {
// 写特征
process_single_frame(st, ffeat);
if (fpcm) write_audio(st, pcm, &noisebuf[st->pcount*FRAME_SIZE], fpcm, 1);
}
st->pcount ;
/* Running on groups of 4 frames. */
if (st->pcount == 4) {
if (quantize) {
unsigned char buf[8];
process_superframe(st, buf, ffeat, encode, quantize);
if (fpcm) write_audio(st, pcmbuf, noisebuf, fpcm, 4);
}
st->pcount = 0;
}
代码语言:javascript复制void write_audio(LPCNetEncState *st, const short *pcm, const int *noise, FILE *file, int nframes) {
int i, k;
for (k=0;k<nframes;k ) {
short data[2*FRAME_SIZE];
for (i=0;i<FRAME_SIZE;i ) {
float p=0;
float e;
int j;
for (j=0;j<LPC_ORDER;j ) p -= st->features[k][NB_BANDS 2 j]*st->sig_mem[j];
e = lin2ulaw(pcm[k*FRAME_SIZE i] - p);
/* Signal in. data.s16,写入输入p_{t-1} e_{t-1}=S_{t-1}*/
data[2*i] = float2short(st->sig_mem[0]);
/* Signal out. data.s16,写入输出S_t的gt*/
data[2*i 1] = pcm[k*FRAME_SIZE i];
/* Simulate error on excitation. */
e = noise[k*FRAME_SIZE i];
e = IMIN(255, IMAX(0, e));
RNN_MOVE(&st->sig_mem[1], &st->sig_mem[0], LPC_ORDER-1);
st->sig_mem[0] = p ulaw2lin(e);
st->exc_mem = e;
}
fwrite(data, 4*FRAME_SIZE, 1, file);
}
}
lpcnet_demo.c
C代码实现feature特征提取和网络前向运算进行语音合成
代码语言:javascript复制else if (mode == MODE_FEATURES) {
// 创建FRN
LPCNetEncState *net;
net = lpcnet_encoder_create();
while (1) {
float features[NB_TOTAL_FEATURES];
short pcm[LPCNET_FRAME_SIZE];
size_t ret;
ret = fread(pcm, sizeof(pcm[0]), LPCNET_FRAME_SIZE, fin);
if (feof(fin) || ret != LPCNET_FRAME_SIZE) break;
// 计算单帧的feature
lpcnet_compute_single_frame_features(net, pcm, features);
fwrite(features, sizeof(float), NB_TOTAL_FEATURES, fout);
}
lpcnet_encoder_destroy(net);
} else if (mode == MODE_SYNTHESIS) {
LPCNetState *net;
// 创建lpcnet网络
net = lpcnet_create();
while (1) {
float in_features[NB_TOTAL_FEATURES];
float features[NB_FEATURES];
short pcm[LPCNET_FRAME_SIZE];
size_t ret;
// 读feature
ret = fread(in_features, sizeof(features[0]), NB_TOTAL_FEATURES, fin);
if (feof(fin) || ret != NB_TOTAL_FEATURES) break;
RNN_COPY(features, in_features, NB_FEATURES);
// 网络infer,对feature转为wav
lpcnet_synthesize(net, features, pcm, LPCNET_FRAME_SIZE);
fwrite(pcm, sizeof(pcm[0]), LPCNET_FRAME_SIZE, fout);
}
参考资料
LPCnet论文:https://jmvalin.ca/papers/lpcnet_icassp2019.pdf
LPCNet源码:https://github.com/xiph/LPCNet
https://jmvalin.ca/demo/lpcnet/
https://zhuanlan.zhihu.com/p/54952637
https://cloud.tencent.com/developer/article/1831454