如何将 Riva TTS API 与开箱即用模型结合使用?
目录

如何将 Riva TTS API 与开箱即用模型结合使用?#
本教程将引导您了解 Riva TTS 服务的基础知识,特别介绍如何将 Riva TTS API 与 OOTB(开箱即用)模型结合使用。
NVIDIA Riva 概述#
NVIDIA Riva 是一个 GPU 加速的 SDK,用于构建为您的用例定制并提供实时性能的语音 AI 应用程序。
Riva 提供了一系列丰富的语音和自然语言理解服务,例如
自动语音识别 (ASR)
文本到语音合成 (TTS)
一系列自然语言处理 (NLP) 服务,例如命名实体识别 (NER)、标点符号和意图分类。
在本教程中,我们将与文本到语音合成 (TTS) API 交互,并使用 SSML 自定义 Riva TTS 音频输出。
有关 Riva 的更多信息,请参阅 Riva 开发者网站。
基础知识:使用 Riva TTS API 生成语音#
Riva TTS 服务基于两阶段管道:Riva 使用第一个模型生成梅尔频谱图,然后使用梅尔频谱图使用第二个模型生成语音。此管道构成了一个文本到语音系统,使您能够从原始文本记录合成听起来自然的语音,而无需任何其他信息,例如语音的模式或节奏。
Riva 为英语提供了两种最先进的声音(一种男性和一种女性),可以使用 Riva 快速入门脚本轻松部署。Riva 还支持以各种方式轻松自定义 TTS,以满足您的特定需求。
后续的 Riva 版本将包含诸如模型注册之类的功能,以支持使用相同 API 的多种语言/声音,并支持重采样到替代采样率。
有关更多信息,请参阅 Riva TTS 文档。
现在,让我们使用 Riva API 和 OOTB 英语 TTS 管道生成音频。
要求和设置#
启动 Riva 服务器。在运行本教程之前,请按照《Riva 快速入门指南》中的说明在 Riva 服务器上部署 OOTB TTS 模型。默认情况下,仅部署英语模型。
安装额外的 Python 库以运行本教程。运行以下命令来安装库
# We need numpy to read the output from the Riva TTS request.
pip install numpy
pip install nvidia-riva-client
导入 Riva 客户端库#
我们首先导入一些必需的库,包括 Riva 客户端库。
import numpy as np
import IPython.display as ipd
import riva.client
创建 Riva 客户端并连接到 Riva 服务器#
以下 URI 假定 Riva 服务器在默认端口上的本地部署。如果服务器部署在不同的主机上,或者通过 Kubernetes 上的 Helm Chart 进行部署,请使用适当的 URI。
auth = riva.client.Auth(uri='localhost:50051')
riva_tts = riva.client.SpeechSynthesisService(auth)
TTS 模式#
Riva TTS 支持流式和批量推理模式。在批量模式下,音频在生成请求文本的完整音频序列之前不会返回,并且可以实现更高的吞吐量。但是,在发出流式请求时,音频块会在生成后立即返回,从而显着减少大型请求的延迟(以首次音频的时间衡量)。
设置 TTS API 参数#
sample_rate_hz = 44100
req = {
"language_code" : "en-US",
"encoding" : riva.client.AudioEncoding.LINEAR_PCM , # LINEAR_PCM and OGGOPUS encodings are supported
"sample_rate_hz" : sample_rate_hz, # Generate 44.1KHz audio
"voice_name" : "English-US.Female-1" # The name of the voice to generate
}
理解 TTS API 参数#
如上所示,Riva TTS 在向 gRPC 端点发出文本到语音请求时支持许多选项。让我们详细了解这些参数
language_code
- 生成音频的语言。en-US
代表英语(美国),目前是唯一支持 OOTB 的语言。encoding
- 要生成的音频编码类型。支持LINEAR_PCM
和OGGOPUS
编码。sample_rate_hz
- 生成音频的采样率。取决于麦克风,通常为22khz
或44khz
。voice_name
- 用于合成音频的声音。目前,Riva 提供两种 OOTB 声音(English-US.Female-1
、English-US.Male-1
)。
向 Riva 服务器发出 gRPC 请求#
对于批量推理模式,请使用 synthesize
。当整个音频合成后,将返回结果。
req["text"] = "Is it recognize speech or wreck a nice beach?"
resp = riva_tts.synthesize(**req)
audio_samples = np.frombuffer(resp.audio, dtype=np.int16)
ipd.Audio(audio_samples, rate=sample_rate_hz)
对于在线推理,请使用 synthesize_online
。结果会在合成时分块返回。
req["text"] = "Is it recognize speech or wreck a nice beach?"
resp = riva_tts.synthesize_online(**req)
empty = np.array([])
for i, rep in enumerate(resp):
audio_samples = np.frombuffer(rep.audio, dtype=np.int16) / (2**15)
print("Chunk: ",i)
ipd.display(ipd.Audio(audio_samples, rate=44100))
empty = np.concatenate((empty, audio_samples))
print("Final synthesis:")
ipd.display(ipd.Audio(empty, rate=44100))
使用 SSML 自定义 Riva TTS 音频输出#
语音合成标记语言 (SSML) 规范是一种用于指导虚拟扬声器性能的标记。Riva 支持 SSML 的一部分,允许您调整生成的音频的音调、语速和发音。
所有 SSML 输入都必须是有效的 XML 文档,并使用
Riva TTS 支持以下 SSML 标记
prosody
标签,它支持属性rate
、pitch
和volume
,通过这些属性我们可以控制生成的音频的语速、音调和音量。phoneme
标签,它允许我们控制生成的音频的发音。sub
标签,它允许我们用不同的单词或短语替换指定单词或短语的发音。
让我们详细了解如何使用这些 SSML 标签自定义 Riva TTS。
使用 prosody
标签自定义语速、音调和音量#
音调属性#
Riva 支持对音调进行附加的相对更改。pitch
属性的范围为 [-3, 3] 或 [-150, 150] Hz。超出此范围的值会导致记录错误且不返回音频。
当使用不以 Hz
结尾的绝对值时,音调会按该值乘以模型配置中定义的说话人音调标准差进行偏移。对于在 LJSpeech 上训练的预训练检查点,标准差为 52.185。例如,1.25 的音调偏移会导致 1.25*52.185=~65.23 Hz 的音调向上偏移。
Riva 还支持以下标签(根据 SSML 规范):x-low
、low
、medium
、high
、x-high
和 default
。
pitch
属性以以下格式表示
pitch="1"
pitch="95hZ"
pitch="+1.8"
pitch="-0.65"
pitch="+75Hz"
pitch="-84.5Hz"
pitch="high"
pitch="default"
对于预训练的 Female-1 检查点,标准差为 53.33 Hz。对于预训练的 Male-1 检查点,标准差为 47.15 Hz。
pitch
属性不支持 st
和 %
更改。
在 FastPitch 中,音调的处理方式与 RadTTS 不同。虽然这两种模型都接受两种音调格式,但在内部,FastPitch 使用归一化音调,而 RadTTS 使用非归一化音调。如果 TTS 请求使用 RadTTS 模型,并且音调属性以 [-3, 3] 格式提供,则 Riva 会使用模型的音调标准差将其转换为非归一化音调偏移。如果 TTS 请求使用 FastPitch 模型,并且音调属性以 [-150, 150] Hz 格式提供,则 Riva 会使用模型的音调标准差将其转换为归一化音调偏移。在 Riva 从 NeMo 模型配置确定音调标准差的情况下,使用 59.02 Hz 的值作为音调标准差。
语速属性#
Riva 支持对语速进行百分比相对更改。rate
属性的范围为 [25%, 250%]。超出此范围的值会导致记录错误且不返回音频。Riva 还支持以下标签(根据 SSML 规范):x-low
、low
、medium
、high
、x-high
和 default
。
rate
属性以以下格式表示
rate="35%"
rate="+200%"
rate="low"
rate="default"
音量属性#
Riva 支持 SSML 规范中描述的音量属性。音量属性支持 [-13, 8]dB 的范围。超出此范围的值会导致记录错误且不返回音频。支持标签 silent
、x-soft
、soft
、medium
、loud
、x-loud
和 default
。
音量属性以以下格式表示
volume="+1dB"
volume="-5.7dB"
volume="x-loud"
volume="default"
情感属性#
Riva 在 Beta 版中支持情感混合,情感属性如 SSML 规范中所述。情感属性会覆盖请求中默认的子语音情感,并支持 [0.0, 1.0] 浮动范围内的混合权重。支持混合权重标签 xlow
、low
、medium
、very
和 extreme
。目前,情感混合仅在 RadTTS++ 模型中受支持。
当选择情感时,它会根据指定的权重与中性情感混合,以对其进行量化。例如,快乐且混合权重为 0.5 时,快乐的极端情感与中性情感以 1:1 的比例混合,以获得快乐:0.5。
情感属性以以下格式表示
emotion="sad:1.0,fearful:0.7"
emotion="happy:extreme,calm:low"
让我们看一个示例,展示 Riva TTS 的音调、语速和音量自定义
"""
Raw text is "Today is a sunny day. But it might rain tomorrow."
We are updating this raw text with SSML:
1. Envelope raw text in '<speak>' tags as is required for SSML
2. Add '<prosody>' tag with 'pitch' attribute set to '2.5'
3. Add '<prosody>' tag with 'rate' attribute set to 'high'
4. Add '<volume>' tag with 'volume' attribute set to '+1dB'
"""
raw_text = "Today is a sunny day. But it might rain tomorrow."
ssml_text = """<speak><prosody pitch='2.5'>Today is a sunny day</prosody>. <prosody rate='high' volume='+1dB'>But it might rain tomorrow.</prosody></speak>"""
print("Raw Text: ", raw_text)
print("SSML Text: ", ssml_text)
req["text"] = ssml_text
# Request to Riva TTS to synthesize audio
resp = riva_tts.synthesize(**req)
# Playing the generated audio from Riva TTS request
audio_samples = np.frombuffer(resp.audio, dtype=np.int16)
ipd.display(ipd.Audio(audio_samples, rate=sample_rate_hz))
运行教程的预期结果:#
<prosody pitch='2.5'>今天 是 晴天</prosody>。 <prosody rate='high' volume='+1dB'>但 明天 可能会 下雨。</prosody>
注意#
如果在整个笔记本中看不到音频控件。请在 github dev 中打开笔记本,或在 riva 文档中查看
以下是更多示例,展示了音调、语速和情感属性值的更改对生成音频的影响
# SSML texts we want to try
ssml_texts = [
"""<speak>This is a normal sentence</speak>""",
"""<speak><prosody pitch="0." rate="100%">This is also a normal sentence</prosody></speak>""",
"""<speak><prosody rate="200%">This is a fast sentence</prosody></speak>""",
"""<speak><prosody rate="60%">This is a slow sentence</prosody></speak>""",
"""<speak><prosody pitch="+1.0">Now, I'm speaking a bit higher</prosody></speak>""",
"""<speak><prosody pitch="-0.5">And now, I'm speaking a bit lower</prosody></speak>""",
"""<speak>S S M L supports <prosody pitch="-1">nested tags. So I can speak <prosody rate="150%">faster</prosody>, <prosody rate="75%">or slower</prosody>, as desired.</prosody></speak>""",
"""<speak><prosody volume='x-soft'>I'm speaking softly.</prosody><prosody volume='x-loud'> And now, This is loud.</prosody></speak>""",
]
# Loop through 'ssml_texts' list and synthesize audio with Riva TTS for each entry 'ssml_texts'
for ssml_text in ssml_texts:
req["text"] = ssml_text
resp = riva_tts.synthesize(**req)
audio_samples = np.frombuffer(resp.audio, dtype=np.int16)
print("SSML Text: ", ssml_text)
ipd.display(ipd.Audio(audio_samples, rate=sample_rate_hz))
print("--------------------------------------------")
运行教程的预期结果:#
这是一个 正常的 句子
<prosody pitch="0." rate="100%">这也是 一个 正常的 句子</prosody>
<prosody rate="200%">这是一个 快速的 句子</prosody>
<prosody rate="60%">这是一个 慢速的 句子</prosody>
<prosody pitch="+1.0">现在, 我 说话的 声音 有点 高</prosody>
<prosody pitch="-0.5">现在, 我 说话的 声音 有点 低</prosody>
SSML 支持 <prosody pitch="-1">嵌套 标签。 因此 我 可以 以 <prosody rate="150%">更快</prosody>, <prosody rate="75%">或 更慢</prosody> 的速度 说话, 如 所 需。</prosody>
<prosody volume='x-soft'>我 说话 很 轻。</prosody><prosody volume='x-loud'> 现在, 这 声音 很大。</prosody>
# Note: This code segment uses the beta radtts model which supports emotion mixing, in case of other models the emotions will be ignored except set via voice_name.
req_emotion = {
"language_code" : "en-US",
"encoding" : riva.client.AudioEncoding.LINEAR_PCM , # LINEAR_PCM and OGGOPUS encodings are supported
"sample_rate_hz" : sample_rate_hz, # Generate 44.1KHz audio
"voice_name" : "English-US-RadTTSpp.Male.happy" # The name of the voice to generate
}
ssml_text="""<speak> I am happy.<prosody emotion="sad:very"> And now, I am sad.</prosody><prosody emotion="angry:extreme"> This makes me angry.</prosody><prosody emotion="calm:extreme"> And now, I am calm.</prosody></speak>"""
print("SSML Text: ", ssml_text)
req_emotion["text"] = ssml_text
# Request to Riva TTS to synthesize audio
resp = riva_tts.synthesize(**req_emotion)
# Playing the generated audio from Riva TTS request
audio_samples = np.frombuffer(resp.audio, dtype=np.int16)
ipd.display(ipd.Audio(audio_samples, rate=sample_rate_hz))
运行教程的预期结果:#
我 很高兴。<prosody emotion="sad:very"> 现在, 我 很 伤心。</prosody><prosody emotion="angry:extreme"> 这 让 我 生气了。</prosody><prosody emotion="calm:extreme"> 现在, 我 很 平静。</prosody>
使用 phoneme
标签自定义发音#
我们可以使用 phoneme
标签来覆盖预测发音中单词的发音。对于给定的单词或单词序列,使用 ph
属性提供显式发音,并使用 alphabet
属性提供音素集。
从 Riva 2.8.0 版本开始,ipa
将是 TTS 模型唯一支持的发音字母表。较旧的 Riva 模型仅支持 x-arpabet
。
IPA#
有关支持的 ipa
音素的完整列表,请参阅 Riva TTS 音素支持页面。
Arpabet#
CMUdict 中的音素完整列表可在 cmudict.phone 中找到。带有重音的支持符号列表可在 cmudict.symbols 中找到。有关这些音素到英语声音的映射,请参阅 ARPABET 维基百科页面。
让我们看一个示例,展示 Riva TTS 的此自定义发音
# Setting up Riva TTS request with SynthesizeSpeechRequest
"""
Raw text is "You say tomato, I say tomato."
We are updating this raw text with SSML:
1. Envelope raw text in '<speak>' tags as is required for SSML
2. For a substring in the raw text, add '<phoneme>' tags with 'alphabet' attribute set to 'x-arpabet'
(currently the only supported value) and 'ph' attribute set to a custom pronunciation based on CMUdict and ARPABET
"""
raw_text = "You say tomato, I say tomato."
ssml_text = '<speak>You say <phoneme alphabet="ipa" ph="təˈmeɪˌtoʊ">tomato</phoneme>, I say <phoneme alphabet="ipa" ph="təˈmɑˌtoʊ">tomato</phoneme>.</speak>'
# Older arpabet version
# ssml_text = '<speak>You say <phoneme alphabet="x-arpabet" ph="{@T}{@AH0}{@M}{@EY1}{@T}{@OW2}">tomato</phoneme>, I say <phoneme alphabet="x-arpabet" ph="{@T}{@AH0}{@M}{@AA1}{@T}{@OW2}">tomato</phoneme>.</speak>'
print("Raw Text: ", raw_text)
print("SSML Text: ", ssml_text)
req["text"] = ssml_text
# Request to Riva TTS to synthesize audio
resp = riva_tts.synthesize(**req)
# Playing the generated audio from Riva TTS request
audio_samples = np.frombuffer(resp.audio, dtype=np.int16)
ipd.display(ipd.Audio(audio_samples, rate=sample_rate_hz))
运行教程的预期结果:#
你说 <phoneme alphabet="ipa" ph="təˈmeɪˌtoʊ">tomato</phoneme>, 我说 <phoneme alphabet="ipa" ph="təˈmɑˌtoʊ">tomato</phoneme>。
以下是更多示例,展示了生成音频中发音的自定义
# SSML texts we want to try
ssml_texts = [
"""<speak>You say <phoneme ph="ˈdeɪtə">data</phoneme>, I say <phoneme ph="ˈdætə">data</phoneme>.</speak>""",
"""<speak>Some people say <phoneme ph="ˈɹut">route</phoneme> and some say <phoneme ph="ˈɹaʊt">route</phoneme>.</speak>""",
]
# Older arpabet version
# ssml_texts = [
# """<speak>You say <phoneme alphabet="x-arpabet" ph="{@D}{@EY1}{@T}{@AH0}">data</phoneme>, I say <phoneme alphabet="x-arpabet" ph="{@D}{@AE1}{@T}{@AH0}">data</phoneme>.</speak>""",
# """<speak>Some people say <phoneme alphabet="x-arpabet" ph="{@R}{@UW1}{@T}">route</phoneme> and some say <phoneme alphabet="x-arpabet" ph="{@R}{@AW1}{@T}">route</phoneme>.</speak>""",
# ]
# Loop through 'ssml_texts' list and synthesize audio with Riva TTS for each entry 'ssml_texts'
for ssml_text in ssml_texts:
req["text"] = ssml_text
resp = riva_tts.synthesize(**req)
audio_samples = np.frombuffer(resp.audio, dtype=np.int16)
print("SSML Text: ", ssml_text)
ipd.display(ipd.Audio(audio_samples, rate=sample_rate_hz))
print("--------------------------------------------")
运行教程的预期结果:#
你说 <phoneme ph="ˈdeɪtə">data</phoneme>, 我说 <phoneme ph="ˈdætə">data</phoneme>。
有些人 说 <phoneme ph="ˈɹut">route</phoneme> ,有些人 说 <phoneme ph="ˈɹaʊt">route</phoneme>。
使用 sub
标签替换发音#
我们可以使用 sub
标签来替换指定单词或短语的发音。您可以使用 alias 属性指定要替换的发音。
# Setting up Riva TTS request with SynthesizeSpeechRequest
"""
Raw text is "WWW is know as the web"
We are updating this raw text with SSML:
1. Envelope raw text in '<speak>' tags as is required for SSML
2. Add '<sub>' tag with 'alias' attribute set to replace www with `World Wide Web`
"""
raw_text = "WWW is know as the web."
ssml_text = '<speak><sub alias="World Wide Web">WWW</sub> is known as the web.</speak>'
print("Raw Text: ", raw_text)
print("SSML Text: ", ssml_text)
req["text"] = ssml_text
# Request to Riva TTS to synthesize audio
resp = riva_tts.synthesize(**req)
# Playing the generated audio from Riva TTS request
audio_samples = np.frombuffer(resp.audio, dtype=np.int16)
ipd.display(ipd.Audio(audio_samples, rate=sample_rate_hz))
运行教程的预期结果:#
<sub alias="万维网">WWW</sub> 被称为 web。
使用 emphasis
标签强调词语#
使用 emphasis 标签来强调词语。将 riva-build
命令与 enable_emphasis_tag
、start_of_emphasis_token
和 end_of_emphasis_token
标签一起使用,以启用强调功能。emphasis 标签应按单词使用。如果单词以标点符号结尾,则只会强调单词,而不会强调标点符号。
局限性#
emphasis 标签依赖于训练数据,并且仅在 English-US
模型中可用。在没有训练数据中 emphasis 标签的情况下训练的模型不会产生强调的语音。包含用 emphasis 标签包裹的多个单词的输入文本是无效输入。在 emphasis 标签内包裹空格也是无效输入。
# Setting up Riva TTS request with SynthesizeSpeechRequest
"""
Raw text is "Hello World"
We are updating this raw text with SSML:
1. Envelope raw text in '<speak>' tags as is required for SSML
2. Add '<emphasis>' tag around `love`
"""
ssml_texts = [
"""<speak>I would <emphasis>love</emphasis> to try that.</speak>""",
"""<speak><emphasis>Wow!</emphasis> Thats really cool.</speak>"""
]
print("Raw Text: ", raw_text)
print("SSML Text: ", ssml_text)
for ssml_text in ssml_texts:
req["text"] = ssml_text
resp = riva_tts.synthesize(**req)
# Playing the generated audio from Riva TTS request
audio_samples = np.frombuffer(resp.audio, dtype=np.int16)
print("SSML Text: ", ssml_text)
ipd.display(ipd.Audio(audio_samples, rate=sample_rate_hz))
print("--------------------------------------------")