前言
语音合成的技术越来越成熟,大家已经不满足于传统千篇一律的 TTS,转而追求更多个性化的声音。复现整理了一下近期的几种主流实现方案,效果各有千秋,有些适合英语口吻有些适合中文语境,有些甚至模仿的惟妙惟肖,几乎能够乱真。给自己的视频配个音,或是在赛博空间里代替一下自己也毫无违和感了。

bark
bark 基于 hubert,采用 类似GPT 的结构,直接由文本生成语音,可以生成很多语气和情绪,也能通过标记加入不同的风格。自定义的语音需要的训练素材非常少,仅不到1分钟的语音就能模仿出相似的口吻。finetune 所需要的文件也很小,很方便通过 tts 库来加入自有的语音。

安装依赖
conda create -n bark python=3.10.0
activate bark
git clone https://github.com/suno-ai/bark
cd bark && pip install .

安装 Transformers 库
pip install git+https://github.com/huggingface/transformers.git

下载基础模型
新建一个目录 suno/bark,将以下链接的文件下载后放入其中(约20G左右,可在文末网盘中加速下载)
https://huggingface.co/suno/bark/tree/main
已有的音色库中,选择不同的语言和不同的人物风格
https://suno-ai.notion.site/8b8e8749ed514b0cbf3f699013548683?v=bc67cff786b04b50b3ceb756fd05f68c

在 voice_preset 中配置后,设置采样频率
from transformers import AutoProcessor, BarkModel
import scipy
processor = AutoProcessor.from_pretrained("./suno/bark")
model = BarkModel.from_pretrained("./suno/bark")
voice_preset = "v2/zh_speaker_4"
inputs = processor("克隆自己的声音,赛博分身必备技能", voice_preset=voice_preset)
audio_array = model.generate(**inputs)
audio_array = audio_array.cpu().numpy().squeeze()
sample_rate = model.generation_config.sample_rate
scipy.io.wavfile.write("bark_out.wav", rate=sample_rate, data=audio_array)
训练自有语音
训练自己的音色,我们需要引入另一个库来实现,TTS。由于bark架构的特殊性,只需要极少量的录音(1分钟左右)就能做到音色迁移。
安装 tts 库
https://github.com/coqui-ai/TTS
pip install TTS

安装依赖
pip install ipython

生成音频
新建目录 bark_voices/speaker,在其中放入录制好的干净的原声,指定 speaker_id 为 speaker,voice_dirs 为 bark_voices
from TTS.tts.configs.bark_config import BarkConfig
from TTS.tts.models.bark import Bark
from scipy.io.wavfile import write as write_wav
# text = "克隆自己的声音,赛博分身必备技能。" # Hello, my name is Manmay , how are you?
text = "Hello, my name is Manmay , how are you?" #
config = BarkConfig()
model = Bark.init_from_config(config)
model.load_checkpoint(config, checkpoint_dir="./suno/bark/", eval=True)
# with random speaker
# output_dict = model.synthesize(text, config, speaker_id="random", voice_dirs=None)
# cloning a speaker.
# It assumes that you have a speaker file in `bark_voices/speaker_n/speaker.wav` or `bark_voices/speaker_n/speaker.npz`
output_dict = model.synthesize(text, config, speaker_id="speaker", voice_dirs="bark_voices/")
sample_rate = 24000
write_wav("bark_out_c.wav", sample_rate, output_dict["wav"])
通过调整 BarkConfig 来配置基础模型的参数,bark_out_c.wav 就是最终新合成的音频。
https://huggingface.co/docs/transformers/main/en/model_doc/bark
由于基准模型多采用英语素材,采用其他语言的适配,会感觉一股老外的味道。要避免这种情况,则需要使用 CustomHuBERT 导入自己的预训练模型,进一步的资料可以参考:
https://github.com/gitmylo/bark-voice-cloning-HuBERT-quantizer/
不过预训练模型需要大量的基础语音素材,如果想省心省力的话,也可以考虑达摩院的 Sambert 模型。
Sambert
要说中文的个性化定制语音,最近达摩院在 KAN-TTS 基础上开源的 Sambert 模型,只需要录制20句话,经过几分钟的训练,就能够获得一个较好的个性化声音。
https://modelscope.cn/models/damo/speech_personal_sambert-hifigan_nsf_tts_zh-cn_pretrain_16k/summary

安装依赖
sudo apt-get install ffmpeg
sudo apt-get install sox
要注意 pandas 和 numpy 的版本匹配,librosa 的 numpy 支持范围
pip install openai-whisper
pip install modelscope
pip install tts-autolabel -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html
pip install typeguard==2.3.1
pip install sox
pip install bitstring
pip install pysptk --no-build-isolation
pip install matplotlib
pip install kantts -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html
pip install pytorch_wavelets
pip install tensorboardX
pip install pandas==1.5.3
pip install librosa==0.10.0
pip install numpy==1.23.1
准备素材
这里我选用凯叔讲故事里西游记的一小段章节作为输入语音(大约时长 20 分钟)。

先使用 whisper 模型来做长语音分割,将素材切割成 486 段短语音,存入 test_wavs 目录备用。
import whisper
from pathlib import Path
import librosa
from scipy.io import wavfile
import numpy as np
import sox
whisper_size = "large"
whisper_model = whisper.load_model(whisper_size)
def split_long_audio(model, filepaths, save_dir="data_dir", out_sr=44100):
if isinstance(filepaths, str):
filepaths = [filepaths]
for file_idx, filepath in enumerate(filepaths):
save_path = Path(save_dir)
save_path.mkdir(exist_ok=True, parents=True)
print(f"Transcribing file {file_idx}: '{filepath}' to segments...")
result = model.transcribe(filepath, word_timestamps=True, task="transcribe", beam_size=5, best_of=5)
segments = result['segments']
wav, sr = librosa.load(filepath, sr=None, offset=0, duration=None, mono=True)
wav, _ = librosa.effects.trim(wav, top_db=20)
peak = np.abs(wav).max()
if peak > 1.0:
wav = 0.98 * wav / peak
wav2 = librosa.resample(wav, orig_sr=sr, target_sr=out_sr)
wav2 /= max(wav2.max(), -wav2.min())
for i, seg in enumerate(segments):
start_time = seg['start']
end_time = seg['end']
wav_seg = wav2[int(start_time * out_sr):int(end_time * out_sr)]
wav_seg_name = f"{file_idx}_{i}.wav"
out_fpath = save_path / wav_seg_name
wavfile.write(out_fpath, rate=out_sr, data=(wav_seg * np.iinfo(np.int16).max).astype(np.int16))
split_long_audio(whisper_model, "001石猴出世.mp3", "test_wavs")
自动标注
采用魔搭的自动标注工具,对原始数据预处理,期间工具会过滤掉背景声音,并将静音的片段删除,标注完成后的数据保存在 output_training_data 下,待训练。
https://modelscope.cn/models/damo/speech_ptts_autolabel_16k/summary
from modelscope.tools import run_auto_label
import os
os.makedirs("output_training_data", exist_ok=True)
input_wav = "./test_wavs/"
output_data = "./output_training_data/"
ret, report = run_auto_label(input_wav=input_wav, work_dir=output_data, resource_revision="v1.0.7")

训练模型
万事俱备,指定 sambert 模型的超参数后,就能开始训练了,11g以上的显卡应该都能顺利完成任务。

from modelscope.metainfo import Trainers
from modelscope.trainers import build_trainer
from modelscope.utils.audio.audio_utils import TtsTrainType
pretrained_model_id = 'damo/speech_personal_sambert-hifigan_nsf_tts_zh-cn_pretrain_16k'
dataset_id = "./output_training_data/"
pretrain_work_dir = "./pretrain_work_dir/"
os.makedirs("pretrain_work_dir", exist_ok=True)
# 训练信息,用于指定需要训练哪个或哪些模型,这里展示AM和Vocoder模型皆进行训练
# 目前支持训练:TtsTrainType.TRAIN_TYPE_SAMBERT, TtsTrainType.TRAIN_TYPE_VOC
# 训练SAMBERT会以模型最新step作为基础进行finetune
train_info = {
TtsTrainType.TRAIN_TYPE_SAMBERT: { # 配置训练AM(sambert)模型
'train_steps': 202, # 训练多少个step
'save_interval_steps': 200, # 每训练多少个step保存一次checkpoint
'log_interval': 10 # 每训练多少个step打印一次训练日志
}
}
# 配置训练参数,指定数据集,临时工作目录和train_info
kwargs = dict(
model=pretrained_model_id, # 指定要finetune的模型
model_revision = "v1.0.6",
work_dir=pretrain_work_dir, # 指定临时工作目录
train_dataset=dataset_id, # 指定数据集id
train_type=train_info # 指定要训练类型及参数
)
trainer = build_trainer(Trainers.speech_kantts_trainer,
default_args=kwargs)
trainer.train()

合成效果
from modelscope.models.audio.tts import SambertHifigan
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
def infer(text):
model_dir = os.path.abspath("./pretrain_work_dir")
custom_infer_abs = {
'voice_name':
'F7',
'am_ckpt':
os.path.join(model_dir, 'tmp_am', 'ckpt'),
'am_config':
os.path.join(model_dir, 'tmp_am', 'config.yaml'),
'voc_ckpt':
os.path.join(model_dir, 'orig_model', 'basemodel_16k', 'hifigan', 'ckpt'),
'voc_config':
os.path.join(model_dir, 'orig_model', 'basemodel_16k', 'hifigan',
'config.yaml'),
'audio_config':
os.path.join(model_dir, 'data', 'audio_config.yaml'),
'se_file':
os.path.join(model_dir, 'data', 'se', 'se.npy')
}
kwargs = {'custom_ckpt': custom_infer_abs}
model_id = SambertHifigan(os.path.join(model_dir, "orig_model"), **kwargs)
inference = pipeline(task=Tasks.text_to_speech, model=model_id)
output = inference(input=text)
filename = text + ".wav"
with open(filename, mode='bx') as f:
f.write(output["output_wav"])
return filename
infer("克隆自己的声音,赛博分身必备技能")
听一下合成的语音,果然有凯叔那味儿了,KAN-TTS 在中文语音迁移上的效果已经很不错。

VITS
原始语音素材的质量是模型能否提取特征的关键,某种程度上来说,预处理的好坏决定了后续语音合成的效果。
接下来,我们另一个语音合成神器 VITS,底模采用原神数据集和VCTK数据集,支持中日英三种语言,所以非常适合亚洲人的声线。
https://github.com/Plachtaa/VITS-fast-fine-tuning
安装依赖
由于环境的版本依赖,建议新建一个虚拟环境,并在 python3.8 下运行。其中 pyopenjtalk 我在 0.3.2 版本下复现成功,如遇到问题,可以参考我的 requirements_vits.txt 文件。
git clone https://github.com/Plachtaa/VITS-fast-fine-tuning.git VITS
conda create -n vits python=3.8
conda activate vits
pip install pyopenjtalk==0.3.2
pip install -r requirements.txt (修改 pyopenjtalk 版本)

下载中文预训练模型
预训练模型非常的大,还需要墙外访问,有需求的朋友也可以在文末我的网盘中加速下载。
wget https://huggingface.co/datasets/Plachta/sampled_audio4ft/resolve/main/VITS-Chinese/D_0.pth -O ./pretrained_models/D_0.pth
wget https://huggingface.co/datasets/Plachta/sampled_audio4ft/resolve/main/VITS-Chinese/G_0.pth -O ./pretrained_models/G_0.pth
wget https://huggingface.co/datasets/Plachta/sampled_audio4ft/resolve/main/VITS-Chinese/config.json -O ./configs/finetune_speaker.json
下载原始素材
# download data for fine-tuning
wget https://huggingface.co/datasets/Plachta/sampled_audio4ft/resolve/main/sampled_audio4ft_v2.zip
unzip sampled_audio4ft_v2.zip
音频预处理
mkdir video_data
mkdir raw_audio
mkdir denoised_audio
mkdir custom_character_voice
mkdir segmented_character_voice
把原始音频放入 raw_audio 目录,需要 wav 格式,mp3的话先用 ffmpeg 转换一下。
ffmpeg -i "001石猴出世 凯叔.mp3" -acodec pcm_s16le -ac 1 -ar 24000 kai_input.wav
降噪处理,采用 demucs 移除背景声
python scripts/denoise_audio.py

使用 whisper 将长音频分割为短音频,这里和前文 Sambert 的处理方式类似
python scripts/long_audio_transcribe.py --languages C --whisper_size large


处理训练数据集
其中 add_auxiliary_data 则加入之前下载的 sampled_audio4ft_v2.zip 的素材进行 finetune 训练,可以避免新数据的影响过大。
python preprocess_v2.py --add_auxiliary_data True --languages C

训练模型
由于我们输入的音频文件比较长,训练 200 个 epoch 之后,模型仍未过拟合,直接选用 G_latest.pth 就行。
python finetune_speaker_v2.py -m ./OUTPUT_MODEL --max_epochs 200 --drop_speaker_embed True

生成音频
试听一下效果,完美!
python VC_inference.py --model_dir ./OUTPUT_MODEL/G_latest.pth --config_dir ./configs/modified_finetune_speaker.json --share True

最终训练的结果都在 OUTPUT_MODEL 目录下,依据最小的g_losss保留最佳的权重文件即可。期间产生的临时文件,可以根据情况自行删除。
rm -rf ./custom_character_voice/* ./video_data/* ./raw_audio/* ./denoised_audio/* ./segmented_character_voice/* ./separated/* long_character_anno.txt short_character_anno.txt
另外除了文本到音频,还能直接采用语音到语音的方式,那么我们顺便试试替换歌手的效果。
RVC

安装依赖
conda create -n chat python=3.10.0
activate chat
git clone https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git RVC
sudo apt install build-essential
pip install -r requirements.txt
预训练模型
下载预训练模型,由于我是N卡,直接下载 RVC0813Nvidia.7z 包含所有文件(也可在文末网盘下载)。
https://huggingface.co/lj1995/VoiceConversionWebUI/tree/main
将 hubert_base.pt 放置到 ./assets/hubert/ 下;
将基础模型放置到 ./assets/pretrained_v2 目录下;
将人声分离模型放置到 ./assets/uvr5_weights 目录下;
将人声音高提取模型放置到 ./assets/rmvpe 目录下。
准备素材
谈到让流行歌曲更换歌手的场景,我们首先要对歌曲做的预处理就是人声分离。我们选用王菲的《匆匆那年》来做测试,将 匆匆那年.mp3 放置到 ./song/ 目录下,先运行
python infer-web.py

人声分离
配置待处理音频文件夹和分割模型,内置的HP2兼容性比较好,其他的模型参考介绍即可。更细致的模型,可以参考后续番外篇 UVR 章节。

顺利的话,在 opt 目录下,就能找到分割好的人声和旋律。

预处理目标语音
配置输入实验名,将凯叔的语音放入 ./assets/speaker/uncle_kai 目录下

提取特征
使用 rmvpe_gpu 提取特征

训练模型
设置超参数,训练 200 个 epoch,batch_size 为 40(RTX3090),显存小的可以相应改小些。

耐心等待一会儿,就能在 tensorboard 中看到整个训练过程
tensorboard --logdir=./logs/uncle_kai

训练完的权重文件 ./assets/weights/uncle_kai.pth
合成歌曲
切换到模型推理页,我们选择刚刚训练好的 uncle_kai.pth 模型;
由于凯叔的声音比较低沉,而王菲的女声比较高,这里设置变调 -20;
待处理的音频,选择刚刚分离的纯人声文件,./opt/vocal_匆匆那年.mp3_10.wav;
index 路径在 logs 目录下,点击转换合成歌曲。

下载合成好的人声(凯叔版本),最后我们可以使用 Audacity 来合成人声和旋律(instrument_匆匆那年.mp3)
https://www.audacityteam.org/download/

好了,男低音版本的凯叔匆匆那年有那味儿了,五音不全的朋友们也能自信地高歌起来啦。
番外篇 UVR
RVC 内置的人声分离模型比较简单,遇到有和声的歌曲则没法处理。要取得更好的效果,可以专门采用 UVR 来进一步优化。
git clone https://github.com/Anjok07/ultimatevocalremovergui.git UVR
下载模型,解压到 models 目录下
https://github.com/Anjok07/ultimatevocalremovergui/releases


匆匆那年_(Instrumental).mp3 为纯伴奏,匆匆那年_(Vocals) 为人声, 至此以后再也不用辛苦的找寻伴奏带了,这简直是卡拉OK爱好者的福音啊。
另外,懒得部署模型,也可以试试免费在线应用
https://vocalremover.org/
总结
本篇介绍了 Bark,Sambert,VITS 三种不同语音克隆的技术实现,并在 RVC(基于 VITS) 上克隆了歌曲。
整理一下整体思路,先将原始音频去噪,然后做人声分离,再将长音频进行标注,顺便分割为多段短音频,准备好原始训练素材;然后接入神经网络提取音频特征训练模型;最后将训练好的模型通过文本或语音(歌曲)渲染成特定的音色。
一直纠结是否要出一篇完整教程来介绍一下声音克隆项目,反复斟酌了好久。如果别有用心之人盗用了自己的声音,那不就间接做了电信诈骗的帮凶么?和朋友们深入地讨论后,仔细想来,恐怖分子都曾是技术专家,还是应该让更多人知道当前技术能实现的与应对策略,才会相对更安全。
一个有效的措施是,提前与家中老人孩子约定“密钥”,在电话中诉说重大事情的时候,对一下暗语,用以确定传递信息的真实性。
克隆声音对于社恐来说是一种福利,有效的解决了视频配音和直播的场景,加上翻译功能的加持,同声传译50种语言也不再是梦,而在动画人物和名人的模仿中,普通人更是可以轻松成为一个优秀的声优,会有很多的乐趣,科技点亮生活。
不多说了,批量导入喜爱的流行歌曲去除人声,进入K歌模式了!
源码下载

本期相关文件资料,可在公众号“深度觉醒”,后台回复:“voice01”,获取下载链接。涉及5个项目,推理的权重文件比较大,一共30个G左右,可以进入相应的目录按需下载。