IBM Cloud Docs
生成字计时

生成字计时

您可以使用 IBM Watson® Text to Speech 服务的 WebSocket 接口来获取用户指定位置(例如词边界)或输入文本中所有词的计时信息:

  • 在输入文本中包含 SSML <mark> 元素,以标识标记在音频中出现的时间。
  • 在 JSON 文本消息中指定 timings 参数,可获取输入文本中所有字符串的计时信息。

计时信息对于同步音频和输入文本非常有用。 例如,您可以将头像或机器人的手势与合成语音的内容进行协调。

<mark> 元素和 timings 参数仅适用于 WebSocket 接口,而不适用于 HTTP 接口。 而且,日语输入文本不支持 timings 参数。

服务如何返回词计时

要返回标记或词计时信息,服务会多路复用独立二进制流和文本流以构造其响应:

  • 对于每个 <mark> 元素,服务会返回一条 JSON 文本消息。 每条消息指示从合成音频开头起,遇到 mark 时的确切时间。
  • 对于所有字符串的词计时,服务会返回一条或多条 JSON 文本消息。 每条消息都包含词的数组,以及从合成音频开头起,这些词的开始时间和结束时间。

服务发送的二进制流和文本流是独立的。 因此,服务对其传递的音频块数,以及对用户接收文本和音频消息的时间几乎没有控制权。 例如,如果音频合成速度比压缩速度更快,那么所有文本消息可能都会先于任何音频到达。

实际上,服务可以发送任意数量的音频块,包括在每个文本消息之前和之后发送多个音频块。 还可以使单个二进制块包含位于 mark 或词的计时信息之前和之后的音频数据。

但是,包含计时信息的文本消息始终会先于包含相应音频的二进制块到达。 此外,音频消息始终按顺序到达,以便您可以从二进制结果构造合成文本的完整和准确音频。

指定 SSML mark

可选 SSML <mark> 元素是一个空标记,用于将标记放入要合成的文本中。 当已合成 <mark> 元素之前的所有文本时,将通知客户机。

此元素接受单个 name 属性,用于指定唯一标识 mark 的字符串。 名称必须以字母数字字符开头。 服务会返回名称,以及从合成音频的开头起出现 mark 的时间。 可以在输入文本中包含任意数量的 mark。

以下 JavaScript 代码片段包含名为 here<mark> 元素的实例:

function onOpen(evt) {
  var message = {
    text: 'Hello <mark name="here"/> world',
    accept: '*/*'
  };
  websocket.send(JSON.stringify(message));
}

完成对 mark 前面的文本的合成时,服务会发送一条文本消息,其中指明 mark 的名称以及在音频中遇到该 mark 的时间(以秒为单位):

{
  "marks": [
    ["here", 0.501]
  ]
}

包含计时信息的文本消息始终会先于包含 mark 位置的音频块到达。

请求所有词的词计时

针对请求传递给服务的 JSON 对象的可选 timings 参数会返回输入文本中所有字符串的计时信息。 此便利性使您无需为输入的每个词指定 SSML <mark> 元素。 请传递包含字符串 words 的数组来请求词计时。 传递空数组或省略该参数时,不会收到任何计时信息。

该服务通过 WebSocket 连接返回词计时的方式与它返回各个 <mark> 元素的计时信息的方式相同。 服务会返回一条或多条 JSON 文本消息。 每条消息都包含词的数组,以及从合成音频开头起,这些词的开始时间和结束时间(以秒为单位)。 例如,以下示例将请求词计时信息:

function onOpen(evt) {
  var message = {
    text: 'I have a pet bird.',
    accept: '*/*',
    timings: ['words']
  };
  websocket.send(JSON.stringify(message));
}

在响应中,服务可以返回以下文本消息:

{
  "words": [
    [
      "I", 0.0, 0.157
    ],
    [
      "have", 0.157, 0.321
    ],
    [
      "a", 0.321, 0.406
    ]
  ]
}
{
  "words": [
    [
      "pet", 0.406, 0.731
    ],
    [
      "bird.", 0.731, 1.049
    ]
  ]
}

该响应仅仅是示例。 服务可能会返回一条或多条文本消息,其中包含输入的计时信息。 它还可以针对输入的每个词返回单独的文本消息。 此外,消息还可能与包含二进制音频块的响应交错在一起。 但是,包含词计时信息的文本消息始终会先于包含该词的音频块到达。

纯文本计时

服务的合成过程涉及文本规范化步骤,在此步骤中会拼读数字、日期、时间、金额、首字母缩略词和缩写。 结果对应于如何读此类字符串。 例如,字符串 $200 会读作三个词:twohundreddollars。 由于词计时信息用于将音频与输入文本同步,因此服务会返回与输入的非规范化拼写对应的计时信息。

例如,假设有以下输入文本:

The coldest recorded temperature is -89.2 degrees Celsius in Antarctica on July 21, 1983!

服务将返回以下字符串的音频计时:

"The"、"coldest"、"recorded"、"temperature"、"is"、"-89.2"、"degrees"、"Celsius"、"in"、"Antarctica"、"on"、"July"、"21,"、"1983!"

虽然 "-89.2" 在音频中读作 5 个独立的词(minuseightyninepointtwo),但文本消息将此字符串作为一个单元提供其计时信息,开始时间为读 minus 的时间,结束时间为读 two 的时间。

与上一个示例一样,非规范化字符串也可以包含标点符号。 服务在其返回的文本消息中会包含词前面或后面的标点以及计时。 例如,字符串 "21 ," 和 "1983!" 包含服务在其文本消息中返回的标点符号。 虽然标点会生成静默,但该词的音频计时不会包含该静默时间。

例如,假设输入文本包含以下条件语句:

If it is sunny, I will go to the beach.

服务将返回输入的所有字符串 (包括 "sunny ," 和 "Beach.") 的计时信息,这两个字符串都以生成静默的标点符号结尾。 但是, "sunny ," 的计时信息不包含逗号生成的静默,而 "Beach." 的计时信息不包含时间段的静默。 这些信息仅反映所读字符串的计时。

SSML 文本计时

服务合成纯文本时,会返回除空格以外的所有输入字符,以作为词计时响应中字符串的一部分。 但对于 SSML 却并非如此,因为某些 SSML 元素不会生成音频。 以下列表概述了可能影响词计时信息的 SSML 元素:

  • <say-as> 指示在规范化步骤中如何处理在打开和关闭 <say-as> 标记之间包含的文本。 各属性指定如何读嵌入文本。 以下示例指示如何读日期:

    The baby was born on <say-as interpret-as="date" format="mdy">3/4/2016</say-as>.
    

    服务会返回以下字符串的计时信息:"The"、"baby"、"was"、"born"、"on" 和 "3/4/2016."。 服务会将字符串 "3/4/2016" 规范化为 "march fourth two thousand sixteen"。 该字符串的词计时信息反映的开始时间是读 "march" 的时间,结束时间是读 "sixteen" 的时间。

    以下示例指示要拼读词 Hello

    <say-as interpret-as="letters">Hello</say-as>.
    

    服务会返回字符串 "Hello." 的计时信息。 服务在规范化步骤中,将逐字母拼读该词。 响应中的词计时信息反映的开始时间是读字母 "h" 的时间,结束时间是读字母 "o" 的时间。

  • <phoneme> 提供了包含在开始和结束 <phoneme> 标记中的文本的发音。 但文本和结束标记都是可选的。 以下示例包括嵌入文本和结束标记:

    The <phoneme alphabet="ibm" ph=".0tx.1me.0fo">tomato</phoneme> was ripe.
    

    服务会返回以下字符串的计时信息:"The"、"tomato"、"was" 和 "ripe."

    相反,以下示例提供了一个没有嵌入文本和结束标记的一元 <phoneme> 元素:

    The <phoneme alphabet="ibm" ph=".0tx.1me.0fo"/> was ripe.
    

    在这种情况下,服务将返回以下字符串的计时信息: "The" , "<phoneme>" , "" , "成熟。"

  • <sub> 将元素的 alias 属性中包含的文本替换为在语音音频中的开头和结尾 <sub> 标记之间包含的文本。 例如,以下输入包含单个 <sub> 标记:

    I work at <sub alias="International Business Machines">IBM</sub>.
    

    该服务生成以下字符串的计时信息: "I" , "work" , "at" , "IBM."。 服务会将字符串 "IBM" 规范化为 "International Business Machines"。 该字符串的词计时信息反映的开始时间是读 "International" 的时间,结束时间是读 "Machines" 的时间。

  • <break> 在口语文本中插入暂停。 该服务会将词计时中产生的静默反映为 <break> 元素之前的词的结束时间与该元素之后的词的开始时间之间的间隔。

  • <paragraph> (或 <p>) 可以向音频添加静默。 服务不会返回静默的计时信息。

  • <sentence> (或 <s>) 可以向音频添加静默。 服务不会返回静默的计时信息。

列表中未提及的 SSML 元素不会影响词计时信息。 有关服务对 SSML 的支持的更多信息,请参阅 了解 SSML

使用 mark 元素的示例

以下示例说明了客户机与服务之间的简单 WebSocket 会话。 这些示例侧重于数据交换,而不是打开连接。 客户机发送包含两个名为 SIMPLEEXAMPLE<mark> 元素的文本消息,并请求以 WAV 格式返回音频:

{
  "text": "This is a <mark name=\"SIMPLE\"/>simple <mark name=\"EXAMPLE\"/> example.",
  "accept": "audio/wav"
}

服务首先会发送消息来确认音频格式。 然后,会发送包含结果的多条消息。 服务无法保证发送给客户机的音频块数,也无法保证传递文本和音频消息的顺序。

因此,以下两种响应都是可能的。 无论生成哪种响应,服务都会发送两条文本消息,用于标识 mark 在二进制流中的位置。 但是,服务会发送任意数量的包含音频的二进制消息。 mark 的计时信息始终会先于包含 mark 位置的音频块到达。

  • 在第一个示例响应中, 文本消息与多条音频消息交织在一起:

    {
      "binary_streams": [
        {
          "content_type": "audio/wav"
        }
      ]
    }
    ... One or more chunks of binary audio.
        All audio precedes the SIMPLE mark....
    {
      "marks": [
        [
          "SIMPLE", 0.784
        ]
      ]
    }
    ... One or more chunks of binary audio audio can precede
        and follow the SIMPLE mark.
        All audio precedes the EXAMPLE mark....
    {
      "marks": [
        [
          "EXAMPLE", 1.003
        ]
      ]
    }
    ... One or more chunks of binary audio.
        Audio can precede and follow the EXAMPLE mark....
    
  • 在第二个示例响应中, 文本消息在任何音频消息之前到达:

    {
      "binary_streams": [
        "content_type": "audio/wav"}
      ]
    }
    {
      "marks": [
        [
          "SIMPLE", 0.784
        ]
      ]
    }
    {
      "marks": [
        [
          "EXAMPLE", 1.003
        ]
      ]
    }
    ... One or more chunks of binary audio....