【飞桨PaddleSpeech语音技术课程】— 一句话语音合成全流程实践

news/2024/4/23 15:51:03/文章来源:https://blog.csdn.net/qq_21275321/article/details/127620206

(以下内容搬运自飞桨PaddleSpeech语音技术课程,点击链接可直接运行源码)

一句话语音合成全流程实践

点击播放视频

1 声音克隆介绍 & 语音合成基本概念回顾

语音合成(Speech Sysnthesis),又称文本转语音(Text-to-Speech, TTS),指的是将一段文本按照一定需求转化成对应的音频的技术。

1.1 声音克隆的应用场景

随着以语音为交互渠道的产业不断升级,企业对语音合成有着越来越多的需求,比如智能语音助手、手机地图导航、有声书播报等场景都需要用到语音合成技术。通过语音合成技术想要得到一个新的音色,需要定制音库,但是定制音库所耗费的人力成本和时间成本巨大,成为产业升级的屏障。

声音克隆一般是指 one-shot TTS ,即只需要一条参考音频就可以合成该音色的语音,而无需收集大量该音色的音频数据进行训练。

1.2 声音克隆和语音合成的关系

声音克隆属于语音合成的一个小分类,想要合成一个人的声音,可以收集大量该说话人的声音数据进行标注(一般至少一小时,1400+ 条数据),训练一个语音合成模型,也可以用一句话声音克隆方案来实现。

声音克隆模型本质是语音合成的声学模型

一句话声音克隆的效果没有收集大量数据集训练或收集少量数据集 fintune 一个语音合成模型的效果好,其效果是否可以在商业中落地还有待商榷。

一般商业应用中想要合成某个特定的音色,需要收集大量数据进行训练,即使类似于百度地图的定制化音色播报,也是采用某种小样本 finetune的方案,至少需要用户录制 9 ~ 20 句指定文本内容的音频。

1.3 语音合成的基本流程回顾

本教程主要讲解基于深度学习的语音合成技术,流水线包含 文本前端(Text Frontend)声学模型(Acoustic Model)声码器(Vocoder) 三个主要模块:

  • 文本前端模块将原始文本转换为字符/音素
  • 声学模型将字符/音素转换为声学特征,如线性频谱图、mel 频谱图、LPC 特征等
  • 声码器将声学特征转换为波形

语音合成基本流程图

1.3.1 文本前端

文本前端模块主要包含: 分段(Text Segmentation)、文本正则化(Text Normalization, TN)、分词(Word Segmentation, 主要是在中文中)、词性标注(Part-of-Speech, PoS)、韵律预测(Prosody)和字音转换(Grapheme-to-Phoneme,G2P)等。

其中最重要的模块是 文本正则化 模块和 字音转换(TTS 中更常用 G2P 代指) 模块。

各模块输出示例:

• Text: 全国一共有112所211高校
• Text Normalization: 全国一共有一百一十二所二一一高校
• Word Segmentation: 全国/一共/有/一百一十二/所/二一一/高校/
• G2P(注意此句中“一”的读音):quan2 guo2 yi2 gong4 you3 yi4 bai3 yi1 shi2 er4 suo3 er4 yao1 yao1 gao1 xiao4(可以进一步把声母和韵母分开)q uan2 g uo2 y i2 g ong4 y ou3 y i4 b ai3 y i1 sh i2 er4 s uo3 er4 y ao1 y ao1 g ao1 x iao4(把音调和声韵母分开)q uan g uo y i g ong y ou y i b ai y i sh i er s uo er y ao y ao g ao x iao0 2 0 2 0 2 0 4 0 3 ...
• Prosody (prosodic words #1, prosodic phrases #2, intonation phrases #3, sentence #4):全国#2一共有#2一百#1一十二所#2二一一#1高校#4(分词的结果一般是固定的,但是不同人习惯不同,可能有不同的韵律)

1.3.2 声学模型

声学模型将字符/音素转换为声学特征,如线性频谱图、mel 频谱图、LPC 特征等。声学特征以 “帧” 为单位,一般一帧是 10ms 左右,一个音素一般对应 5~20 帧左右。声学模型需要解决的是 “不等长序列间的映射问题”,“不等长”是指,同一个人发不同音素的持续时间不同,同一个人在不同时刻说同一句话的语速可能不同,对应各个音素的持续时间不同,不同人说话的特色不同,对应各个音素的持续时间不同。这是一个困难的 “一对多” 问题。

# 卡尔普陪外孙玩滑梯
000001|baker_corpus|sil 20 k 12 a2 4 er2 10 p 12 u3 12 p 9 ei2 9 uai4 15 s 11 uen1 12 uan2 14 h 10 ua2 11 t 15 i1 16 sil 20

声学模型主要分为自回归模型和非自回归模型。自回归模型在 t 时刻的预测需要依赖 t-1 时刻的输出作为输入,预测时间长,但是音质相对较好;非自回归模型不存在预测上的依赖关系,预测时间快,音质相对较差。

主流声学模型:

  • 自回归模型: Tacotron、Tacotron2 和 Transformer TTS 等
  • 非自回归模型: FastSpeech、SpeedySpeech、FastPitch 和 FastSpeech2 等

1.3.3 声码器

声码器将声学特征转换为波形,它需要解决的是 “信息缺失的补全问题”。信息缺失是指,在音频波形转换为频谱图时,存在相位信息的缺失;在频谱图转换为 mel 频谱图时,存在频域压缩导致的信息缺失。假设音频的采样率是 16kHz, 即 1s 的音频有 16000 个采样点,一帧的音频有 10ms,则 1s 中包含 100 帧,每一帧有 160 个采样点。声码器的作用就是将一个频谱帧变成音频波形的 160 个采样点,所以声码器中一般会包含上采样模块。

与声学模型类似,声码器也分为自回归模型和非自回归模型:

  • 自回归模型:WaveNet、WaveRNN 和 LPCNet 等
  • 非自回归模型:Parallel WaveGAN、Multi Band MelGAN、Style MelGAN 和 HiFiGAN 等

更多关于语音合成基础的精彩细节,请参考之前的课程。

2 基于说话人嵌入的声音克隆:SV2TTS

2.1 基本原理

SV2TTS 论文全称是 Transfer Learning from Speaker Verification to Multispeaker Text-To-Speech Synthesis, 是 Google 发表在 NeurIPS 2018 上的文章。


SV2TTS 模型结构图

SV2TTS 的声学模型使用了 Tacotron2,声码器使用了 WaveNet, 用于提取 speaker embedding 的声纹模型选择了 GE2E。

SV2TTS 原作的开源代码是 Real-Time-Voice-Cloning, 仅支持英文合成,代码实现中使用的声码器是 WaveRNN,MockingBird fork 自原作仓库并支持了中文合成。

在 Speaker Encoder、Synthesizer 和 Vocoder 阶段,PaddleSpeech 提供了不同的说话人编码器、合成器以及声码器组合。

Speaker EncoderSynthesizerVocoder
GE2E / ECAPA-TDNNTacotron2 / FastSpeech2GAN Vocoder
(Parallel WaveGAN / HiFiGAN / …)

2.2 模型训练

2.2.1 声纹模型

PaddleSpeech 已提供了预训练好的声纹模型,相关训练代码请参考 GE2E 和 ECAPA-TDNN。
PaddleSpeech 声纹相关课程请参考声纹检索系统与实践。

2.2.2 声学模型

SV2TTS 与多说话人声学模型的区别是:多说话人声学模型在训练模型的时候,输入 spk_id 标识不同的说话人,模型内通过 Speaker Embedding Table (nn.Embedding 类)获取 speaker embeding,预测时只能选择在训练时包含的 spk_id只能合成训练集内的音色;而 SV2TTS 通过预训练好的声纹模型提取 speaker embeding,预测时可以输入任意说话人的音频作为参考音频,即使该说话人不在训练集中,也可以合成具有其音色的音频。

以 FastSpeech2 为例,两者的区别仅仅是红框所标部分。


基于 FastSpeech2 的多说话人语音合成模型

2.2.3 声码器

声音克隆模型本质是语音合成声学模型,声码器可以直接使用 PaddleSpeech 提供的各种声码器:Parallel WaveGAN、Multi Band MelGAN、Style MelGAN 和 HiFiGAN 等, 由于声音克隆会见到各种不同的音色,建议使用 PaddleSpeech 提供的在多说话人数据集 AISHELL-3 和 VCTK 上训练的声码器

声码器和数据集相关,如果待合成音色是成年女性音色,用 CSMSC(成年女性单说话人数据集)的声码器效果可能会更好,若待合成音色是男性音色,由于和女性声音特征相差较大,用 CSMSC 数据集训练的声码器的表现就不会很好,AISHELL-3、VCTK、CSMSC 都是 24k 的,声码器可以任意换,只是效果好不好的问题。

使用自己的数据重新训练或者 finetune 这些声码器,合成的音频会有更好的音质。

2.3 实践

以 ECAPA-TDNN 声纹模型 + FastSpeech2 声学模型为例,下列代码修改自 voice_cloning.py。

安装 paddlespeech

# pip install paddlespeech or
!git clone https://github.com/PaddlePaddle/PaddleSpeech.git
from IPython.display import clear_output
%cd PaddleSpeech
!pip install .
%cd -
# 清理很长的内容
clear_output()
# 本项目的依赖需要用到 nltk 包,但是有时会因为网络原因导致不好下载,此处手动下载一下放到百度服务器的包
!wget https://paddlespeech.bj.bcebos.com/Parakeet/tools/nltk_data.tar.gz
!tar zxvf nltk_data.tar.gz
clear_output()

获取预训练模型和数据

!wget -P download https://paddlespeech.bj.bcebos.com/Parakeet/released_models/fastspeech2/fastspeech2_aishell3_ckpt_vc2_1.2.0.zip
!unzip -o -d download download/fastspeech2_aishell3_ckpt_vc2_1.2.0.zip
!wget -P download https://paddlespeech.bj.bcebos.com/Parakeet/released_models/pwgan/pwg_aishell3_ckpt_0.5.zip
!unzip -o -d download download/pwg_aishell3_ckpt_0.5.zip
clear_output()

获取 ref_audio

!wget -P download https://paddlespeech.bj.bcebos.com/Parakeet/docs/demos/ref_audio.zip
!unzip -o -d download download/ref_audio.zip
clear_output()

导入 Python 包

# 设置 gpu 环境
%env CUDA_VISIBLE_DEVICES=0import logging
import sys
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)import os
from pathlib import Pathimport numpy as np
import paddle
import soundfile as sf
import yaml
from yacs.config import CfgNodefrom paddlespeech.cli.vector import VectorExecutor
from paddlespeech.t2s.exps.syn_utils import get_am_inference
from paddlespeech.t2s.exps.syn_utils import get_voc_inference
from paddlespeech.t2s.frontend.zh_frontend import Frontend
clear_output()

主程序

am ='fastspeech2_aishell3'
am_config = 'download/fastspeech2_aishell3_ckpt_vc2_1.2.0/default.yaml'
am_ckpt = 'download/fastspeech2_aishell3_ckpt_vc2_1.2.0/snapshot_iter_96400.pdz'
am_stat = 'download/fastspeech2_aishell3_ckpt_vc2_1.2.0/speech_stats.npy'
phones_dict = "download/fastspeech2_aishell3_ckpt_vc2_1.2.0/phone_id_map.txt"voc = 'pwgan_aishell3'
voc_config = 'download/pwg_aishell3_ckpt_0.5/default.yaml'
voc_ckpt = 'download/pwg_aishell3_ckpt_0.5/snapshot_iter_1000000.pdz'
voc_stat = 'download/pwg_aishell3_ckpt_0.5/feats_stats.npy'# 读取 conf 配置文件并结构化
with open(am_config) as f:am_config = CfgNode(yaml.safe_load(f))
with open(voc_config) as f:voc_config = CfgNode(yaml.safe_load(f))
# 加载模型,第一次使用 vec_executor 时候会下载模型
# speaker encoder
vec_executor = VectorExecutor()frontend = Frontend(phone_vocab_path=phones_dict)
print("frontend done!")# acoustic model
am_inference = get_am_inference(am=am,am_config=am_config,am_ckpt=am_ckpt,am_stat=am_stat,phones_dict=phones_dict)# vocoder
voc_inference = get_voc_inference(voc=voc,voc_config=voc_config,voc_ckpt=voc_ckpt,voc_stat=voc_stat)
ref_audio_path = 'download/ref_audio/yuantian01_02.wav'
sentence = '每当你觉得,想要批评什么人的时候,你切要记着,这个世界上的人,并非都具备你禀有的条件。'input_ids = frontend.get_input_ids(sentence, merge_sentences=True)
phone_ids = input_ids["phone_ids"][0]spk_emb = vec_executor(audio_file=ref_audio_path, force_yes=True)
spk_emb = paddle.to_tensor(spk_emb)with paddle.no_grad():wav = voc_inference(am_inference(phone_ids, spk_emb=spk_emb))sf.write("output.wav",wav.numpy(),samplerate=am_config.fs)
print("Voice Cloning done!")
# 播放参考音频
import IPython.display as dp
dp.Audio("download/ref_audio/yuantian01_02.wav")
# 播放生成的音频
dp.Audio('output.wav', rate=am_config.fs)

SV2TTS 的效果受多说话人数据集和 Speaker Encoder 模型的影响较大,新发音人的声音会跟提取的 speaker embedding 在整个发音人向量空间中的最接近的那几个人的音色会比较像,因此一句话合成整体音色的效果因人而异,音色学习效果并不稳定。

具体实现代码请参考:

  • SV2TTS (GE2E + Tacotron2)
  • SV2TTS (GE2E + FastSpeech2)
  • SV2TTS (ECAPA-TDNN + FastSpeech2)

3 端到端声音克隆:ERNIE-SAT

ERNIE-SAT 是百度自研的文心大模型,是可以同时处理中英文的跨语言的语音-语言跨模态大模型,其在语音编辑、个性化语音合成以及跨语言的语音合成等多个任务取得了领先效果。可以应用于语音编辑、个性化合成、语音克隆、同传翻译等一系列场景。

点击播放视频

语音编辑的应用场景

3.1 基本原理

3.1.1 A3T 简介

ERNIE-SAT 是基于百度自研模型 A3T: Alignment-Aware Acoustic and Text Pretraining for Speech Synthesis and Editing 的改进,A3T 提出了一种对齐感知的声学、文本预训练模型,可以重建高质量的语音信号,多用于语音编辑任务。

训练时,根据 MFA 获取 mel 频谱和音素的对齐信息,mask 住单词文本对应的 mel 频谱,并对其进行重建。


A3T 模型结构图

预测时,使用额外的 Duration Predictor 模块(如,预训练好的 FastSpeech2 模型的 duration_predictor)获取待重建音频(输入文本对应的音频)的时长,构造相应的长度的空 mel 频谱并 mask 住,模型预测对应的 mel 频谱。

A good\color{red}{good}good actor -> A successful\color{blue}{successful}successful actor


语音编辑流程图

3 种不同的语音编辑

3.1.2 模型框架

ERNIE-SAT 中提出了 3 项创新:

  • 提出了一个通用的端到端语音和文本联合预处理框架用于跨语言语音合成,它可以联合学习语音和文本输入的统一表示。
  • 由于多语语音语料库有限,有效地利用多个单语语音数据在单一模型中构建双语音素列表,并利用不同语言的数据重建语音和文本的训练目标。模型在跨语言推理过程中可生成高质量的语音。
  • 能够对看不见的说话人执行跨语言语音合成,并且优于基于说话人嵌入的多说话人 TTS 方法(SV2TTS)。

ERNIE-SAT 模型结构图

ERNIE-SAT 是基于 A3T 的改进,A3T 没有对文本(phoneme)做 mask,只对语音(mel 频谱)做了 mask,只能对被 mask 住的语音做高质量重建,ERNIE-SAT 同时对语音和文本做 mask,可以同时重建语音信息和文本信息;此外,A3T 只能处理单语言的情况,并不支持跨语言合成,ERNIE-SAT 可以实现跨语言合成:

  1. 纯中文和纯英文的 ERNIE-SAT,模型结构和 A3T 一样,直接使用 VCTK 数据集(英文)或 AISHELL-3 数据集(中文)进行训练
  2. 中英文混合的 ERNIE-SAT 是语音和文本一起 mask,可以实现跨语言合成任务,混合 VCTK 数据集(英文)和 AISHELL-3 数据集(中文)进行训练
    • 随机 mask 住 80% 的 mel 频谱特征
    • 再 mask 住剩余的 20% 的 mel 频谱特征对应的 phoneme 里的一半

3.2 ERNIE-SAT 和 SV2TTS 的区别与联系

  1. SV2TTS 和 ERNIE-SAT 本质都是语音合成声学模型,需要输入合成文本信息,输出 mel 频谱特征,其后需要接声码器才能合成音频,都属于 one-shot TTS 模型。
  2. SV2TTS 和 ERNIE-SAT 都是声音克隆模型,都需要输入参考音频获取合成音频的音色信息。
  3. SV2TTS 需要额外的声纹模型作为 speaker encoder 提取参考音频的 speaker embedding 作为输入,在训练和预测时,并不是一个 end-to-end 的系统;ERNIE-SAT 直接输入参考音频的 mel 频谱获取音色特征,在训练和预测时都是完全 end-to-end 的。
  4. 对于 SV2TTS 所需的声纹模型来说,在低资源的语言场景中,收集大量多说话人、多语言数据来训练模型提取 speaker embedding 非常困难。
  5. SV2TTS 仅有声音克隆功能,ERNIE-SAT 在声音克隆的基础上,可同时完成语音编辑以及跨语言的语音合成任务。

3.3 实践

纯中文或纯英文的 ERNIE-SAT 支持语音编辑和个性化合成,中英文混合的 ERNIE-SAT 支持跨语言合成,本质都是声音克隆。

英文语音编辑为例,下列代码修改自 ernie_sat/synthesize_e2e.py,环境准备参考 vctk/ernie_sat。

For that reason cover should\color{red}{should}should not\color{red}{not}not be given -> For that reason cover is\color{blue}{is}is not\color{blue}{not}not impossible\color{blue}{impossible}impossible to\color{blue}{to}to be given

获取预训练模型和数据

# prepare aligner
!mkdir -p tools/aligner
%cd tools
# download MFA
# !wget https://github.com/MontrealCorpusTools/Montreal-Forced-Aligner/releases/download/v1.0.1/montreal-forced-aligner_linux.tar.gz
!wget https://paddlespeech.bj.bcebos.com/MFA/montreal-forced-aligner_linux.tar.gz
# extract MFA
!tar xvf montreal-forced-aligner_linux.tar.gz
# fix .so of MFA
%cd montreal-forced-aligner/lib
!ln -snf libpython3.6m.so.1.0 libpython3.6m.so
%cd -
# download align models and dicts
%cd aligner
!wget https://paddlespeech.bj.bcebos.com/MFA/ernie_sat/aishell3_model.zip
!wget https://paddlespeech.bj.bcebos.com/MFA/AISHELL-3/with_tone/simple.lexicon
!wget https://paddlespeech.bj.bcebos.com/MFA/ernie_sat/vctk_model.zip
!wget https://paddlespeech.bj.bcebos.com/MFA/LJSpeech-1.1/cmudict-0.7b
%cd ../../
clear_output()
# prepare pretrained FastSpeech2 models
!mkdir download
%cd download
!wget https://paddlespeech.bj.bcebos.com/Parakeet/released_models/fastspeech2/fastspeech2_conformer_baker_ckpt_0.5.zip
!wget https://paddlespeech.bj.bcebos.com/Parakeet/released_models/fastspeech2/fastspeech2_nosil_ljspeech_ckpt_0.5.zip
!unzip fastspeech2_conformer_baker_ckpt_0.5.zip
!unzip fastspeech2_nosil_ljspeech_ckpt_0.5.zip
%cd ../
clear_output()
# prepare source data
!mkdir source
%cd source
!wget https://paddlespeech.bj.bcebos.com/Parakeet/released_models/ernie_sat/source/SSB03540307.wav
!wget https://paddlespeech.bj.bcebos.com/Parakeet/released_models/ernie_sat/source/SSB03540428.wav
!wget https://paddlespeech.bj.bcebos.com/Parakeet/released_models/ernie_sat/source/LJ050-0278.wav
!wget https://paddlespeech.bj.bcebos.com/Parakeet/released_models/ernie_sat/source/p243_313.wav
!wget https://paddlespeech.bj.bcebos.com/Parakeet/released_models/ernie_sat/source/p299_096.wav
!wget https://paddlespeech.bj.bcebos.com/Parakeet/released_models/ernie_sat/source/this_was_not_the_show_for_me.wav
!wget https://paddlespeech.bj.bcebos.com/Parakeet/released_models/ernie_sat/source/README.md
%cd ../
clear_output()
# 下载预训练模型
!wget -P download https://paddlespeech.bj.bcebos.com/Parakeet/released_models/ernie_sat/erniesat_vctk_ckpt_1.2.0.zip
!unzip -o -d download download/erniesat_vctk_ckpt_1.2.0.zip
!wget -P download https://paddlespeech.bj.bcebos.com/Parakeet/released_models/hifigan/hifigan_vctk_ckpt_0.2.0.zip
!unzip -o -d download download/hifigan_vctk_ckpt_0.2.0.zip
clear_output()

导入 Python 包

import os
from pathlib import Path
from typing import Listimport librosa
import numpy as np
import paddle
import pypinyin
import soundfile as sf
import yaml
from pypinyin_dict.phrase_pinyin_data import large_pinyin
from yacs.config import CfgNodefrom paddlespeech.t2s.datasets.am_batch_fn import build_erniesat_collate_fn
from paddlespeech.t2s.datasets.get_feats import LogMelFBank
from paddlespeech.t2s.exps.ernie_sat.align import get_phns_spans
from paddlespeech.t2s.exps.ernie_sat.utils import eval_durs
from paddlespeech.t2s.exps.ernie_sat.utils import get_dur_adj_factor
from paddlespeech.t2s.exps.ernie_sat.utils import get_span_bdy
from paddlespeech.t2s.exps.ernie_sat.utils import get_tmp_name
from paddlespeech.t2s.exps.syn_utils import get_am_inference
from paddlespeech.t2s.exps.syn_utils import get_voc_inference
from paddlespeech.t2s.exps.syn_utils import norm

定义 ERNIE-SAT 所需功能函数


def _p2id(phonemes: List[str]) -> np.ndarray:# replace unk phone with spphonemes = [phn if phn in vocab_phones else "sp" for phn in phonemes]phone_ids = [vocab_phones[item] for item in phonemes]return np.array(phone_ids, np.int64)def prep_feats_with_dur(wav_path: str,old_str: str='',new_str: str='',source_lang: str='en',target_lang: str='en',duration_adjust: bool=True,fs: int=24000,n_shift: int=300):'''Returns:np.ndarray: new wav, replace the part to be edited in original wav with 0List[str]: new phonesList[float]: mfa start of new wavList[float]: mfa end of new wavList[int]: masked mel boundary of original wavList[int]: masked mel boundary of new wav'''wav_org, _ = librosa.load(wav_path, sr=fs)phns_spans_outs = get_phns_spans(wav_path=wav_path,old_str=old_str,new_str=new_str,source_lang=source_lang,target_lang=target_lang,fs=fs,n_shift=n_shift)mfa_start = phns_spans_outs['mfa_start']mfa_end = phns_spans_outs['mfa_end']old_phns = phns_spans_outs['old_phns']new_phns = phns_spans_outs['new_phns']span_to_repl = phns_spans_outs['span_to_repl']span_to_add = phns_spans_outs['span_to_add']# 中文的 phns 不一定都在 fastspeech2 的字典里, 用 sp 代替if target_lang in {'en', 'zh'}:old_durs = eval_durs(old_phns, target_lang=source_lang)else:assert target_lang in {'en', 'zh'}, \"calculate duration_predict is not support for this language..."orig_old_durs = [e - s for e, s in zip(mfa_end, mfa_start)]if duration_adjust:d_factor = get_dur_adj_factor(orig_dur=orig_old_durs, pred_dur=old_durs, phns=old_phns)d_factor = d_factor * 1.25else:d_factor = 1if target_lang in {'en', 'zh'}:new_durs = eval_durs(new_phns, target_lang=target_lang)else:assert target_lang == "zh" or target_lang == "en", \"calculate duration_predict is not support for this language..."# duration 要是整数new_durs_adjusted = [int(np.ceil(d_factor * i)) for i in new_durs]new_span_dur_sum = sum(new_durs_adjusted[span_to_add[0]:span_to_add[1]])old_span_dur_sum = sum(orig_old_durs[span_to_repl[0]:span_to_repl[1]])dur_offset = new_span_dur_sum - old_span_dur_sumnew_mfa_start = mfa_start[:span_to_repl[0]]new_mfa_end = mfa_end[:span_to_repl[0]]for dur in new_durs_adjusted[span_to_add[0]:span_to_add[1]]:if len(new_mfa_end) == 0:new_mfa_start.append(0)new_mfa_end.append(dur)else:new_mfa_start.append(new_mfa_end[-1])new_mfa_end.append(new_mfa_end[-1] + dur)new_mfa_start += [i + dur_offset for i in mfa_start[span_to_repl[1]:]]new_mfa_end += [i + dur_offset for i in mfa_end[span_to_repl[1]:]]# 3. get new wav# 在原始句子后拼接if span_to_repl[0] >= len(mfa_start):wav_left_idx = len(wav_org)wav_right_idx = wav_left_idx# 在原始句子中间替换else:wav_left_idx = int(np.floor(mfa_start[span_to_repl[0]] * n_shift))wav_right_idx = int(np.ceil(mfa_end[span_to_repl[1] - 1] * n_shift))blank_wav = np.zeros((int(np.ceil(new_span_dur_sum * n_shift)), ), dtype=wav_org.dtype)# 原始音频,需要编辑的部分替换成空音频,空音频的时间由 fs2 的 duration_predictor 决定new_wav = np.concatenate([wav_org[:wav_left_idx], blank_wav, wav_org[wav_right_idx:]])# 4. get old and new mel span to be maskold_span_bdy = get_span_bdy(mfa_start=mfa_start, mfa_end=mfa_end, span_to_repl=span_to_repl)new_span_bdy = get_span_bdy(mfa_start=new_mfa_start, mfa_end=new_mfa_end, span_to_repl=span_to_add)# old_span_bdy, new_span_bdy 是帧级别的范围outs = {}outs['new_wav'] = new_wavouts['new_phns'] = new_phnsouts['new_mfa_start'] = new_mfa_startouts['new_mfa_end'] = new_mfa_endouts['old_span_bdy'] = old_span_bdyouts['new_span_bdy'] = new_span_bdyreturn outsdef prep_feats(wav_path: str,old_str: str='',new_str: str='',source_lang: str='en',target_lang: str='en',duration_adjust: bool=True,fs: int=24000,n_shift: int=300):with_dur_outs = prep_feats_with_dur(wav_path=wav_path,old_str=old_str,new_str=new_str,source_lang=source_lang,target_lang=target_lang,duration_adjust=duration_adjust,fs=fs,n_shift=n_shift)wav_name = os.path.basename(wav_path)utt_id = wav_name.split('.')[0]wav = with_dur_outs['new_wav']phns = with_dur_outs['new_phns']mfa_start = with_dur_outs['new_mfa_start']mfa_end = with_dur_outs['new_mfa_end']old_span_bdy = with_dur_outs['old_span_bdy']new_span_bdy = with_dur_outs['new_span_bdy']span_bdy = np.array(new_span_bdy)mel = mel_extractor.get_log_mel_fbank(wav)erniesat_mean, erniesat_std = np.load(erniesat_stat)normed_mel = norm(mel, erniesat_mean, erniesat_std)tmp_name = get_tmp_name(text=old_str)tmpbase = './tmp_dir/' + tmp_nametmpbase = Path(tmpbase)tmpbase.mkdir(parents=True, exist_ok=True)mel_path = tmpbase / 'mel.npy'np.save(mel_path, normed_mel)durations = [e - s for e, s in zip(mfa_end, mfa_start)]text = _p2id(phns)datum = {"utt_id": utt_id,"spk_id": 0,"text": text,"text_lengths": len(text),"speech_lengths": len(normed_mel),"durations": durations,"speech": np.load(mel_path),"align_start": mfa_start,"align_end": mfa_end,"span_bdy": span_bdy}batch = collate_fn([datum])outs = dict()outs['batch'] = batchouts['old_span_bdy'] = old_span_bdyouts['new_span_bdy'] = new_span_bdyreturn outsdef get_mlm_output(wav_path: str,old_str: str='',new_str: str='',source_lang: str='en',target_lang: str='en',duration_adjust: bool=True,fs: int=24000,n_shift: int=300):prep_feats_outs = prep_feats(wav_path=wav_path,old_str=old_str,new_str=new_str,source_lang=source_lang,target_lang=target_lang,duration_adjust=duration_adjust,fs=fs,n_shift=n_shift)batch = prep_feats_outs['batch']new_span_bdy = prep_feats_outs['new_span_bdy']old_span_bdy = prep_feats_outs['old_span_bdy']out_mels = erniesat_inference(speech=batch['speech'],text=batch['text'],masked_pos=batch['masked_pos'],speech_mask=batch['speech_mask'],text_mask=batch['text_mask'],speech_seg_pos=batch['speech_seg_pos'],text_seg_pos=batch['text_seg_pos'],span_bdy=new_span_bdy)# 拼接音频output_feat = paddle.concat(x=out_mels, axis=0)wav_org, _ = librosa.load(wav_path, sr=fs)outs = dict()outs['wav_org'] = wav_orgouts['output_feat'] = output_featouts['old_span_bdy'] = old_span_bdyouts['new_span_bdy'] = new_span_bdyreturn outsdef get_wav(wav_path: str,source_lang: str='en',target_lang: str='en',old_str: str='',new_str: str='',duration_adjust: bool=True,fs: int=24000,n_shift: int=300,task_name: str='synthesize'):outs = get_mlm_output(wav_path=wav_path,old_str=old_str,new_str=new_str,source_lang=source_lang,target_lang=target_lang,duration_adjust=duration_adjust,fs=fs,n_shift=n_shift)wav_org = outs['wav_org']output_feat = outs['output_feat']old_span_bdy = outs['old_span_bdy']new_span_bdy = outs['new_span_bdy']masked_feat = output_feat[new_span_bdy[0]:new_span_bdy[1]]with paddle.no_grad():alt_wav = voc_inference(masked_feat)alt_wav = np.squeeze(alt_wav)old_time_bdy = [n_shift * x for x in old_span_bdy]if task_name == 'edit':wav_replaced = np.concatenate([wav_org[:old_time_bdy[0]], alt_wav, wav_org[old_time_bdy[1]:]])else:wav_replaced = alt_wavwav_dict = {"origin": wav_org, "output": wav_replaced}return wav_dict

主程序

erniesat_config = 'download/erniesat_vctk_ckpt_1.2.0/default.yaml'
erniesat_ckpt = 'download/erniesat_vctk_ckpt_1.2.0/snapshot_iter_199500.pdz'
erniesat_stat = 'download/erniesat_vctk_ckpt_1.2.0/speech_stats.npy'
phones_dict = 'download/erniesat_vctk_ckpt_1.2.0/phone_id_map.txt'voc = 'hifigan_vctk'
voc_config = 'download/hifigan_vctk_ckpt_0.2.0/default.yaml'
voc_ckpt = 'download/hifigan_vctk_ckpt_0.2.0/snapshot_iter_2500000.pdz'
voc_stat = 'download/hifigan_vctk_ckpt_0.2.0/feats_stats.npy'task_name = 'edit'
wav_path = 'source/p243_313.wav'
# should not -> is not impossible to
old_str = 'For that reason cover should not be given'
new_str = 'For that reason cover is not impossible to be given'
source_lang = 'en'
target_lang = 'en'
duration_adjust = True
output_name = 'sat_output.wav'# evaluate(args)
with open(erniesat_config) as f:erniesat_config = CfgNode(yaml.safe_load(f))
with open(voc_config) as f:voc_config = CfgNode(yaml.safe_load(f))
# convert Chinese characters to pinyin
if source_lang == 'zh':old_str = pypinyin.lazy_pinyin(old_str,neutral_tone_with_five=True,style=pypinyin.Style.TONE3,tone_sandhi=True)old_str = ' '.join(old_str)
if target_lang == 'zh':new_str = pypinyin.lazy_pinyin(new_str,neutral_tone_with_five=True,style=pypinyin.Style.TONE3,tone_sandhi=True)new_str = ' '.join(new_str)if task_name == 'edit':new_str = new_str
elif task_name == 'synthesize':new_str = old_str + ' ' + new_str
else:new_str = old_str + ' ' + new_str# Extractor
mel_extractor = LogMelFBank(sr=erniesat_config.fs,n_fft=erniesat_config.n_fft,hop_length=erniesat_config.n_shift,win_length=erniesat_config.win_length,window=erniesat_config.window,n_mels=erniesat_config.n_mels,fmin=erniesat_config.fmin,fmax=erniesat_config.fmax)collate_fn = build_erniesat_collate_fn(mlm_prob=erniesat_config.mlm_prob,mean_phn_span=erniesat_config.mean_phn_span,seg_emb=erniesat_config.model['enc_input_layer'] == 'sega_mlm',text_masking=False)vocab_phones = {}with open(phones_dict, 'rt') as f:phn_id = [line.strip().split() for line in f.readlines()]
for phn, id in phn_id:vocab_phones[phn] = int(id)# ernie sat model
erniesat_inference = get_am_inference(am='erniesat_dataset',am_config=erniesat_config,am_ckpt=erniesat_ckpt,am_stat=erniesat_stat,phones_dict=phones_dict)# vocoder
voc_inference = get_voc_inference(voc=voc,voc_config=voc_config,voc_ckpt=voc_ckpt,voc_stat=voc_stat)wav_dict = get_wav(wav_path=wav_path,source_lang=source_lang,target_lang=target_lang,old_str=old_str,new_str=new_str,duration_adjust=duration_adjust,fs=erniesat_config.fs,n_shift=erniesat_config.n_shift,task_name=task_name)sf.write(output_name, wav_dict['output'], samplerate=erniesat_config.fs)
print(f"\033[1;32;m Generated audio saved into {output_name} ! \033[0m")
# 播放输入音频
import IPython.display as dp
dp.Audio("source/p243_313.wav")
# 播放编辑好的音频
dp.Audio('sat_output.wav', rate=erniesat_config.fs)

具体实现代码请参考:

  • 纯中文 ERNIE-SAT
  • 纯英文 ERNIE-SAT
  • 中英文混合 ERNIE-SAT

SV2TTS 和 ERNIE-SAT 均属于一句话克隆,其合成效果有限,能获取一定量的数据情况下,可以考虑小数据集微调方案,具有更高的音色相似度和更好的音频质量。请参考多语言合成与小样本合成技术应用实践。

Web 页面体验 SV2TTS、ERNIE-SAT 一句话合成和小数据集微调:

  • github 项目体验地址
  • AiStudio在线体验地址:【PaddleSpeech进阶】PaddleSpeech小样本合成方案体验

PS:

本教程所指的声音克隆的英文名称是 Voice Cloning,简称 VC,在语音合成领域,有另一种细分领域也简称 VC,全称是 Voice Conversion 声音转换,两者的区别是,Voice Cloning 同时输入参考音频和输入文本,合成的音频有参考音频的音色,说话内容是输入文本;Voice Conversion 输入 source audio 和 target audio 两个音频,无需输入文本,合成的音频具有 target audio 的音色和 source audio 的文本内容以及韵律特征。柯南的变声领结实际上是用 Voice Conversion 声音转换技术实现的(用毛利小五郎的音色说出柯南的说话内容)。Voice Cloning 合成音频的韵律性能是一般是 TTS 模型本身决定的,而 Voice Conversion 合成音频的韵律一般是由 source audio 决定的。

大家以后在语音合成领域看到 “VC” 一定要分清楚指的是声音克隆 Voice Cloning 还是声音转换 Voice Conversion 哟。

关注 PaddleSpeech

请关注我们的 Github Repo,非常欢迎加入以下微信群参与讨论:

  • 扫描二维码
  • 添加运营小姐姐微信
  • 通过后回复【语音】
  • 系统自动邀请加入技术群

P.S. 欢迎关注我们的 github repo PaddleSpeech, 是基于飞桨 PaddlePaddle 的语音方向的开源模型库,用于语音和音频中的各种关键任务的开发,包含大量基于深度学习前沿和有影响力的模型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_410176.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web前端:angular对比React——选择2022年Web开发的理想框架

Javascript世界中的框架列表不断增长和变化,但有两个框架从其他框架中脱颖而出。Angular和React是市场上最受欢迎的框架之一,代表了创建web应用程序和网站的两种不同方法。 试图利用web开发框架的开发人员和企业家现在正在分析Angular和React——这两种方…

软考下午题第2题——E-R图 UML图 逻辑结构设计-示题与解析

下午的第二题主要是找【属性】【主键】【外键】【候选键】之间的关系。 候选键:属性或者是属性组合,其值能够唯一地标识一个元组 主键:在一个关系中可能有多个候选键,从中选择一个作为主键 外键:如果一个关系中的属性或…

微机期末复习指导

目录 位扩展定义字扩展定义1、线选法定义优点缺点2、部分译码法定义3、全译码法定义优点缺点⭐字位扩展定义问题

高压放大器基于声纹影法的声可视化实验的应用

实验名称:高压功率放大器基于声纹影法的声可视化实验应用 研究方向:声学超表面声学隐身斗篷 实验内容:利用声纹影平台,对所设计的声隐身斗篷进行出射平面波的测量,采用安泰放大器来驱动平面超声波阵列,可以…

CSS3专题-[上篇]:过渡、2D转换、动画

目录 CSS3:前置特性 CSS3:盒子模型 CSS3:图片滤镜与模糊处理 blur():高斯模糊 CSS3:计算盒子宽度calc()函数 CSS3:过渡效果 transition属性 2D转换:transform属性 translate()方法 * t…

Mybatis MappedStatement

MappedStatement MappedStatement 类是 Mybatis 框架的核心类之一,它存储了一个 sql 对应的所有信息 Mybatis 通过解析 XML 和 mapper 接口上的注解,生成 sql 对应的 MappedStatement 实例,并放入 SqlSessionTemplate 中 configuration 类属…

凭此五点 这款信创传输系统解决了传输的迫切需求

早在20世纪80年代,我国政府IT底层基础软硬件的自主创新提出了相关要求,但受制于国外巨头垄断关键技术,诸多系统性风险与安全隐患无力解决。自2018年以来,在中兴和华为等公司供应链危机的催化下,信创产业进入快速发展期…

Verilog设计参数化的译码器与编码器,以及设计4位格雷码计数器

Verilog设计参数化的译码器与编码器,以及设计4位格雷码计数器 使用Quartusmodelsim完成设计 文章目录Verilog设计参数化的译码器与编码器,以及设计4位格雷码计数器1. 参数化的译码器分析代码实现Testbench结果2. 参数化的编码器分析代码Testbench结果3.…

Redis 主从架构数据同步

Redis 主从架构图 主从架构能够很大提升并发能力,master 节点负责写数据,slave 节点负责读数据,这样就涉及到 master 和 slave 数据同步的一个过程 一起来看一下数据是如何同步的吧 redis 的主从同步机制可以确保 master 和 slave 之间的数据…

Kubernetes 架构介绍

目录 一、Kubernetes 架构 1、Kubernetes 是什么? 2、Kubernetes 架构 3、Master 节点 4、Node 节点 5、推荐Add-ons 6、Kubeadm 7、查看组件运行状态 8、Kubeadm 容器化组件 二、namespace 1、命名空间 — namespace 2、常用命名空间命令 1. 查看存在哪…

【操作系统】混合索引分配和链接分配相关练习题

混合索引分配练习题: 比较简单,容易理解 练习1: 在UNIX操作系统中,给文件分配外存空间采用的是混合索引分配方式,如下图所示。UNIX系统中的某个文件的索引结点指示出了为该文件分配的外存的物理块的寻找方法。在该索…

C++ 并行编程

C 并行编程1. 进程和线程1.1 常规解释1.2 总结1.3 具体理解1.4 为什么使用多线程1.5 进程和线程的区别2. 并发与并行2.1 多进程并发2.2 多线程并发3. C中的多线程4. 时间管理4.1 C语言:time.h4.2 C11时间标准库:std::chrono4.2.1 获取时间段 int64_t/dou…

SQL学习十九、使用游标

游标(cursor)是一个存储在 DBMS 服务器上的数据库查询, 它不是一条 SELECT 语句,而是被该语句检索出来的结果集。在存储了 游标之后,应用程序可以根据需要滚动或浏览其中的数据。 我们通常的检索操作会返回一组称为结…

vue3+antd中使用Dayjs实现输出的日期格式化,和限制自定义日期选择器的可选范围

场景复现 在vue3antd项目中用到了日期选择器,但是默认的日期选择的结果是标准的日期格式,我们往往需要对最后的结果进行一定的格式化输出 一般输出的是这种标准的数据格式 如果我们想对时间进行指定的格式化输出,通常大家会想到moment&…

如何在页面中制作悬浮发布按钮弹窗

效果展示: 前置准备: 1.已搭建好,待添加悬浮层的页面 2.icon素材 具体步骤:(3) 1.添加悬浮层页面 2.配置悬浮层关闭触发器 3.配置首页发布icon触发器和动画 步骤分解: 1.添加悬浮层页面 1.1…

2022 年跨境电商要尝试的 25 个黑五营销技巧

关键词:黑五营销、黑色星期五活动、跨境电商黑五 我们汇总了以下最佳跨境电商黑五创意清单: 黑五营销技巧分享 如何宣传您的黑色星期五优惠 小型企业的黑五营销创意 黑五营销提示 随意跳到您最感兴趣的部分,或通读它们,看看…

JAVA序列化和反序列化学习笔记

0x01 开始学习JAVA反序列化,参考 《安全漫谈》和feng师傅的文章一步一步来,希望 能赶在这个学期学完java最基础的东西, 笔记做到这里方便自己查阅,也是事先实操了一下 ,才写的笔记 概念: JAVA 序列化 就是…

program arguments,vm arguments,environment variable

作者:david_zhang@sh 【转载时请以超链接形式标明文章】 https://www.cnblogs.com/david-zhang-index/p/16846493.html 参数太多,傻傻分不清楚,简单说 1,program arguments是main函数args[]参数 2,vm arguments是java环境变量 3,environment variable是jvm环境变量 看代码…

华为设备配置NAT原理与示例

网络地址转换NAT 文章目录网络地址转换NAT1 NAT概述1.1 NAT产生的技术背景1.2 私有IP地址1.3 NAT技术原理2 静态NAT2.1 静态NAT原理2.2 静态NAT转换示例2.3 静态NAT配置介绍2.4 静态NAT配置示例3 动态NAT3.1 动态NAT原理3.2 动态NAT转换示例3.3 动态NAT配置介绍3.4 动态NAT配置…

resultMap结果映射

文章目录一、resulrMap结果映射二、驼峰命名自动映射查询结果的列名和Java对象的属性名对应不上怎么办? *第一种方式:as给列名起别名 *第二种方式:使用resultMap进行结果映射 *第三种方式:是否开启驼峰命名自动映射(配…