参考源码:https://github.com/ming024/FastSpeech2
最近好长一阵子没有写文章了,一方面是公司里做的一些项目不好公开写成文章,另一方面由于教育双减政策的影响,很多项目临时被停止了,所以这阵子,对原项目的维护以及新领域(音频)方面的自研学习,基本都在忙着,个人时间很少。
另外打个小广告,科室这边也逐渐对外写一些技术文章,主要在微信公众号:“ AI炼丹术 ”上发布。目前发布了很多关于端侧优化部署的文章、OCR相关技术文章等。后续我可能也会参与一些文章的写作发布。(可能关于人体相关技术或正在自我学习的音频方向)有兴趣的小伙伴可以关注一下,如果有帮助的话可以说是咸鱼推荐来的。。
本文的内容属于音频领域中TTS(text to speech),个人正在摸索,具体细节上的理解可能会有误,见谅。
另外还有一个关于音频的资料总结(来自一个知乎大佬:李永强):http://yqli.tech/page/tts_paper.html
text = 大家好!
pinyins = ['da4', 'jia1', 'hao3']
from pypinyin import pinyin, Style
pinyins = [
p[0]
for p in pinyin(
text, style=Style.TONE3, strict=False, neutral_tone_with_five=True
)
]
phones = ['d', 'a4', 'j', 'ia1', 'h', 'ao3']
->'phones = {d a4 j ia1 h ao3}'
for p in pinyins: # lexicon 韵律表字典
if p in lexicon:
phones += lexicon[p]
else:
phones.append("sp") # 停顿
sequence = [151, 174, 155, 226, 154, 193]
def text_to_sequence(text, cleaner_names):
"""Converts a string of text to a sequence of IDs corresponding to the symbols in the text.
The text can optionally have ARPAbet sequences enclosed in curly braces embedded
in it. For example, "Turn left on {HH AW1 S S T AH0 N} Street."
Args:
text: string to convert to a sequence
cleaner_names: names of the cleaner functions to run the text through
Returns:
List of integers corresponding to the symbols in the text
"""
sequence = []
# Check for curly braces and treat their contents as ARPAbet:
while len(text):
m = _curly_re.match(text)
if not m:
sequence += _symbols_to_sequence(_clean_text(text, cleaner_names))
break
sequence += _symbols_to_sequence(_clean_text(m.group(1), cleaner_names))
sequence += _arpabet_to_sequence(m.group(2))
text = m.group(3)
return sequence
output = self.encoder(texts, src_masks)
多人数据训练,通过参数speaker的调节音色。Alshell3 数据集由多个人的音色。
{"SSB1781": 0, "SSB1274": 1, "SSB1585": 2, "SSB1055": 3, "SSB1020": 4, "SSB0668": 5, "SSB1625": 6, ...} # speaker.json文件
p_control, e_control, d_control 作为音高、音量、语速的参数输入,输出合成的梅尔频谱(频谱包含时长信息)。
个人猜测这一块的作用决定了音频最终的音色、音高、音量、语速。可以通过这块调节,不同音量、语速或者个性化声音。
if self.speaker_emb is not None: #
output = output + self.speaker_emb(speakers).unsqueeze(1).expand(-1, max_src_len, -1) # 将speaker加入encoder的output作为variance_adaptor部分的输入
(output, p_predictions, e_predictions, log_d_predictions, d_rounded, mel_lens, mel_masks) = self.variance_adaptor(output, src_masks, mel_masks,
max_mel_len, p_targets, e_targets, d_targets, p_control, e_control, d_control)
fastspeech2 最终输出mel-spectrogram 梅尔频谱,梅尔频谱并不能直接生成音频,它需要再重构才能生成声波,进而生成音频,所以生成的梅尔频谱还需要经过声码器 vocoder,才能得到waveform。(mel-gan 、hifi-gan…);
而fastspeech2S 将声码器一起端到端训练,最终直接输出声波/音频。(2s找不到开源源码)
声波维度T * hop_size:这实际是一个上采样的过程,上采样的倍数为hop size*,*即一帧梅尔频谱特征要还原生成hop size个采样点; 如果音频采样率为22050,hop size设为256,则上采样的倍数为256。
output, mel_masks = self.decoder(output, mel_masks)
output = self.mel_linear(output)
postnet_output = self.postnet(output) + output # 这个应该就是输出的梅尔频谱,原论文没有postnet,这层结构是源码作者自己加上的
return (output, postnet_output, p_predictions, e_predictions,
log_d_predictions, d_rounded, src_masks, mel_masks, src_lens, mel_lens,)
声码器的输入:梅尔频谱 - 一般为 T*80 ; 如 T为频谱长度,与音频长短相关。
声码器的输出:声波 - 一般为 T * hopsize (T在频谱上有)。waveform 长度:lengths = T * preprocess_config [“preprocessing”] [“stft”] [“hop_length”]
sampling_rate = preprocess_config["preprocessing"]["audio"]["sampling_rate"] # 加载配置-采样率
wavfile.write(os.path.join(path, "{}.wav".format(basename)), sampling_rate, wav) # 保存音频 (, lengths)