智能语音设备的特点是语音交互成为人机交互的重要方式,智能语音设备的声音输出摆脱了预置的逻辑,成为了响应式反馈,尤其是对声音输入的响应。
那么, 技能是在智能语音设备上如何播放声音的呢?
在DuerOS 中,主要提供了三种声音的播放方式:音视频媒体播放,TTS 合成的语音播放 和利用TTS合成的媒体及文本混合播放。
音视频媒体播放
点播和资源类的技能都采用音视频媒体播放的方式。当技能收到用户请求的意图时,技能在响应中返回音视频媒体播放的指令和媒体资源的url,以及其他的相关内容,在智能语音设备收到技能响应的内容后,即可播放技能制定的资源。
基于AudioPlayer的音频播放
音频资源既可以在有屏语音设备(如小度智能屏系列),也可以在无屏语音设备(如小度音箱系列)上播放,主要是通过AudioPlayer 相关指令完成的。
AudioPlayer指令从技能发送给DuerOS,对端上进行音频资源的播放控制。在DuerOS收到该指令后,会经过一系列处理解析为端上对应的播放指令。在云端下发AudioPlayer指令后,端上开始音频资源播放操作。音频资源播放会触发一系列事件上报到云端。这些指令和事件分别是:
指令或事件 | 功能解释 |
---|---|
AudioPlayer.Play指令 | 播放 |
AudioPlayer.Stop指令 | 停止 |
AudioPlayer.PlaybackStarted事件 | 播放开始 |
AudioPlayer.PlaybackStopped事件 | 播放暂停。 |
AudioPlayer.PlaybackFinished事件 | 播放结束,即一首歌播放完后上报的事件 |
AudioPlayer.PlaybackNearlyFinished事件 | 在播放即将结束时上报的事件 |
AudioPlayer.PlaybackFailed事件 | 当设备端播放audio item发生错误时上报此事件 |
AudioPlayer.Play指令是驱动播放音频的核心指令,一般地,使用Play指令时,response需要返回shouldEndSession值为true,使端上关闭麦克风。消息示例如下:
- {
- "type": "AudioPlayer.Play",
- "playBehavior": "{{STRING}}"
- "audioItem": {
- "audioItemId": "{{STRING}}",
- "stream": {
- "url": "{{STRING}}",
- "outputSpeech": {
- "type": "{{STRING}}",
- "text": "{{STRING}}",
- "ssml": "{{STRING}}",
- "ttsTemplates": {{TTSTemplateStructure}}
- },
- "streamFormat": "{{STRING}}",
- "offsetInMilliSeconds": {{LONG}},
- "token": "{{STRING}}",
- "progressReportIntervalMs": {{LONG}}
- },
- "playerInfo": {
- "content": {{AudioPlayerInfoContentStructure}},
- "controls": [
- {{ControlStructure}},
- {{ControlStructure}},
- ......
- ]
- }
- }
- }
当持续播放音频资源的时候,技能需要监听AudioPlayer.PlaybackNearlyFinished事件,通过playBehavior参数对播放的音频队列进行调整,DuerOS 是通过资源迭代的方式完成多个音频资源间连续播放的。
当然,使用全双工特性时,DuerOS的处理稍有不同,详情可以参见《聊聊“全双工”》。
基于VideoPlayer 的视频播放
视频资源只可以在有屏语音设备(如小度智能屏系列)上播放,主要是通过VideoPlayer 相关指令完成的。
VideoPlayer提供了VideoPlayer指令接口和VideoPlayer事件接口。VideoPlayer指令是技能向DuerOS发送的,对视频进行控制的指令,如播放指令、停止播放指令等。DuerOS收到指令后会转化成端上能识别的播放指令,对视频进行相应的控制。VideoPlayer事件是指在视频播放过程中触发一系列事件,DuerOS会将这些事件上报给技能,请求技能进行处理。这些指令和事件分别是:
指令或事件 | 功能解释 |
---|---|
VideoPlayer.Play指令 | 播放 |
VideoPlayer.Stop指令 | 停止 |
VideoPlayer.ClearQueue指令 | 用于清除video_player播放队列 |
VideoPlayer.PlaybackStarted事件 | 播放开始 |
VideoPlayer.PlaybackStopped事件 | 播放暂停 |
VideoPlayer.PlaybackFinished事件 | 播放结束,即一个视频播放完后上报的事件 |
VideoPlayer.PlaybackNearlyFinished事件 | 在播放即将结束时上报的事件。 |
VideoPlayer.ProgressReportIntervalElapsed事件 | 本事件用来统计播放情况。如果Play指令有progressReportIntervalInMilliseconds,则在播放对应video item时,每隔此时间上报本事件。 |
VideoPlayer.ProgressReportDelayElapsed事件 | 本事件用来统计播放情况。如果Play指令中有progressReportDelayInMilliseconds,则对应video item播放此时间长后会上报本事件 |
VideoPlayer.PlaybackStutterStarted事件 | 在PlaybackStarted事件之后,如果设备端缓冲视频数据慢于播放速度时,会上报此事件。 |
VideoPlayer.PlaybackStutterFinished事件 | PlaybackStutterStarted事件之后,缓冲恢复到正常状态,可重新开始播放时端上会上报本事件。 |
VideoPlayer.PlaybackPaused事件 | 视频播放时,如果发生对话/闹钟等行为,则视频播放会暂停,此时,端上会上报PlaybackPaused事件和PlaybackResumed事件。 |
VideoPlayer.PlaybackResumed事件 | 视频播放时,如果发生对话/闹钟等行为,则视频播放会暂停,此时,端上会上报PlaybackPaused事件和PlaybackResumed事件。 |
VideoPlayer.PlaybackQueueCleared事件 | 设备端处理完ClearQueue指令后会上报此事件。 |
技能在返回VideoPlayer.Play指令时,对于show设备中控需要补充RenderVideoPlayerInfo指令用于播放器界面的渲染,消息样例如下:
- {
- "type": "VideoPlayer.Play",
- "playBehavior": "{{STRING}}"
- "videoItem": {
- "videoItemId": "{{STRING}}",
- "stream": {
- "url": "{{STRING}}",
- "offsetInMilliseconds": {{LONG}},
- "stopPointsInMilliseconds":[
- {{LONG}},
- {{LONG}},
- ...
- ],
- "expiryTime": {{STRING}}
- "progressReport": {
- "progressReportDelayInMilliseconds": {{LONG}},
- "progressReportIntervalInMilliseconds": {{LONG}}
- },
- "token": "{{STRING}}",
- "expectedPreviousToken": {{STRING}}
- }
- }
- }
使用VideoPlayer持续播放视频资源的方法与音频资源持续播放的方式类似。
音视频媒体播放是技能播放声音的重要方法,在非资源类技能中也有着广泛的应用。
基于TTS 的语音播放
音视频资源有着很好的表现力,但面向动态播报的内容预先录制的成本比较高,对于各种各样的文本播报,一般采用TTS 合成的方式完成语音播放。
TTS 的语音播报指令——outputSpeech
outputSpeech是基于TTS的语音播报指令,表示本次返回结果中需要播报的语音信息,消息样例如下:
- ......
- "response" : {
- "outputSpeech" : {
- "type" : "{{STRING}}",
- "text" : "{{STRING}}",
- "ssml" : "{{STRING}}",
- },
- "card" : {},
- "directives" : [],
- "expectSpeech": {{BOOLEAN}},
- "shouldEndSession" : {{BOOLEAN}}
- }
在outputSpeech的指令机构中,TTS合成有如下的类型:
- PlainText:普通文本。
- SSML:一种结构化语言,用于辅助描述语音发音声调。
当type取值为PlainText时,该字段为必选字段。长度不能超过256个字符。当type为SSML时,该字段为必选字段,长度不能超过256个字符,SSML 会在下一节“基于TTS的媒体和文本合成播放”中在进行描述。
outputSpeech 的变种——reprompt
在需要用户输入时,如果用户离开了,麦克风没有进行语音的输入,或用户输入的语音请求系统无法解析成技能的任何意图,则可以使用reprompt来播放预置的内容。
reprompt.outputSpeech参数定义与上述outputSpeech的定义一致,消息样例如下:
- "response" : {
- "outputSpeech" : {
- "type" : "{{STRING}}",
- "text" : "{{STRING}}",
- "ssml" : "{{STRING}}",
- },
- "reprompt" : {
- "outputSpeech" : {
- "type" : "{{STRING}}",
- "text" : "{{STRING}}",
- "ssml" : "{{STRING}}",
- }
- },
- "card" : {},
- "directives" : [],
- "expectSpeech": {{BOOLEAN}},
- "shouldEndSession" : {{BOOLEAN}}
- }
repromt指令实际上是outputSpeech指令的变种,是一种应用在特定场景的TTS 语音播报。
长文本的播放
TTS 播报中对文本长度的限制往往被人们所诟病。话术太长往往会被截断,或者在播放的过程中,由于长时间没有用户交互导致session 被关闭,现象就是在TTS 播放一段时间后,技能退出了,用户需要重新打开技能才能继续在技能中交互。
针对长文本的播报,目前DuerOS 提供了两种解决方案:分段持续播放和媒体转换播放。
分段持续播放
既然长文本在播放过程中无法维持session, 那么我们可以把长文本分割成多个短文本,在前一个短文本播放完毕之后,在继续播放下一个短文本,如此类推,实现长文本的持续完整播放。
具体的方式是应用DuerOS 的StreamResponse 特性,StreamResponse 定义了技能在执行过程中,下发部分结果到端上执行,在端执行完这部分结果后,上报事件以获取技能的其它结果,适应于以下场景:技能返回shouldEndSession=false和StreamResponse.SendPart指令。
StreamResponse.SendPart指令告知DuerOS平台本次只返回部分满足结果,期望在StreamResponse.NextRequired事件时继续返回结果。技能在返回本指令时,如果shouldEndSession=true,DuerOS平台将忽略本指令。技能在返回本指令时,如果shouldEndSession=false,DuerOS平台在本次结果执行后,下发StreamResponse.NextRequired事件给技能,消息样例如下:
- {
- "type": "StreamResponse.SendPart",
- "token": "{{STRING}}"
- }
技能在收到StreamResponse.NextRequired事件后,返回计划内的下一个响应结果, request中事件样例如下:
- {
- "type": "StreamResponse.NextRequired",
- "requestId": "{{STRING}}",
- "timestamp": "{{STRING}}",
- "token": "{{STRING}}"
- }
StreamResponse.SendPart指令 和StreamResponse.NextRequired事件组成了一种类似迭代器的机制,使技能可以完成长文本的分段发送。
当然,StreamResponse的应用场景还有很多,长文本的TTS播报只是其中的一个典型应用而已。
媒体转换播放
既然长文本的TTS播报受限于长度,那能否将长文本实时在线转换为音频资源,然后采用AudioPlayer.play 指令播放来突破文本长度的限制呢?答案是肯定的。
DuerOS 提供一种在线将文本转化成音频资源的方法,在技能返回Play指令的时候,content为指定schema的tts,DuerOS会进行及时的批量替换。
schema示例如下:
以下是文本“度秘提醒你今天是妈妈的生日” tts://dueros.baidu.com?text=%E5%BA%A6%E7%A7%98%E6%8F%90%E9%86%92%E4%BD%A0%E4%BB%8A%E5%A4%A9%E6%98%AF%E5%A6%88%E5%A6%88%E7%9A%84%E7%94%9F%E6%97%A5&from=标明技能来源&expire=86400
其中参数需要urlencode, php使用rawurlencode函数即可,其他编程语言类似。
- text:文本长度5k以内
- from: botid,需要传相关来源
- expire: 音频地址的保存时间,最多不超过7天,单位是秒
使用每天转换播放长文本的示例如下:
- {
- "directive": {
- "header": {
- "messageId": "YXVkaW9fbmV3cysxNjAwMTU2ODI4Xzk2NmExampiMQ==",
- "name": "Play",
- "namespace": "ai.dueros.device_interface.audio_player"
- },
- "payload": {
- "audioItem": {
- "audioItemId": "9696687530282687670",
- "stream": {
- "expiryTime": "2020-10-01T00:00:00+08:00",
- "offsetInMilliseconds": 0,
- "progressReport": {
- "progressReportIntervalInMilliseconds": 15000
- },
- "streamFormat": "AUDIO_MPEG",
- "token": "eyJib3RfaWQiOiJhdWRpb19uZXdzIiwicmVzdWx0X3Rva2VuIjoiNjk0ODQ2MDY5Mjc0NmYwYjY3YTRhNzI5ZWMwZDE1ZGUiLCJib3RfdG9rZW4iOiI5Njk2Njg3NTMwMjgyNjg3NjcwIiwibGF1bmNoX2lkcyI6WyJhdWRpb19uZXdzIl19",
- "url": "tts:\/\/dueros.baidu.com?text=%25E9%2598%25AE%25E6%2588%2590%25E5%258F%2591%25E8%25B5%25B4%25E7%2591%259E%25E4%25B8%25BD%25E7%259D%25A3%25E5%25AF%25BC%25E6%25A3%2580%25E6%259F%25A5%25E7%2596%25AB%25E6%2583%2585%25E9%2598%25B2%25E6%258E%25A7%25EF%25BC%259A%25E5%258A%25A1%25E5%25BF%25853%25E6%2597%25A5%25E5%2586%2585%25E5%25AE%258C%25E6%2588%2590%25E5%2585%25A8%25E5%2591%2598%25E6%25A0%25B8%25E9%2585%25B8%25E6%25A3%2580%25E6%25B5%258B%25E3%2580%2582%25E6%258E%258C%25E4%25B8%258A%25E6%2598%25A5%25E5%259F%258E%25E8%25AE%25AF9%25E6%259C%258814%25E6%2597%25A5%25EF%25BC%258C%25E4%25BA%2591%25E5%258D%2597%25E7%259C%2581%25E7%259C%2581%25E5%25A7%2594%25E5%2589%25AF%25E4%25B9%25A6%25E8%25AE%25B0%25E3%2580%2581%25E7%259C%2581%25E9%2595%25BF%25E3%2580%2581%25E7%259C%2581%25E5%25A7%2594%25E7%259C%2581%25E6%2594%25BF%25E5%25BA%259C%25E5%25BA%2594%25E5%25AF%25B9%25E6%2596%25B0%25E5%2586%25A0%25E8%2582%25BA%25E7%2582%258E%25E7%2596%25AB%25E6%2583%2585%25E5%25B7%25A5%25E4%25BD%259C%25E9%25A2%2586%25E5%25AF%25BC%25E5%25B0%258F%25E7%25BB%2584%25E7%25BB%2584%25E9%2595%25BF%25E9%2598%25AE%25E6%2588%2590%25E5%258F%2591%25E8%25B5%25B6%25E8%25B5%25B4%25E5%25BE%25B7%25E5%25AE%258F%25E5%2582%25A3%25E6%2597%258F%25E6%2599%25AF%25E9%25A2%2587%25E6%2597%258F%25E8%2587%25AA%25E6%25B2%25BB%25E5%25B7%259E%25E7%2591%259E%25E4%25B8%25BD%25E5%25B8%2582%25E7%259D%25A3%25E5%25AF%25BC%25E6%25A3%2580%25E6%259F%25A5%25E7%2596%25AB%25E6%2583%2585%25E9%2598%25B2%25E6%258E%25A7%25E5%25B7%25A5%25E4%25BD%259C%25EF%25BC%258C%25E5%25AF%25B9%25E5%25BD%2593%25E5%2589%258D%25E7%2596%25AB%25E6%2583%2585%25E9%2598%25B2%25E6%258E%25A7%25E5%25BD%25A2%25E5%258A%25BF%25E8%25BF%259B%25E8%25A1%258C%25E5%2586%258D%25E5%2588%2586%25E6%259E%2590%25E3%2580%2581%25E5%2586%258D%25E7%25A0%2594%25E5%2588%25A4%25EF%25BC%258C%25E5%25AF%25B9%25E8%25BE%25B9%25E5%25A2%2583%25E7%25AE%25A1%25E6%258E%25A7%25E5%2592%258C%25E9%2598%25B2%25E6%258E%25A7%25E6%258E%25AA%25E6%2596%25BD%25E8%25BF%259B%25E8%25A1%258C%25E5%2586%258D%25E7%25A0%2594%25E7%25A9%25B6%25E3%2580%2581%25E5%2586%258D%25E5%25AE%2589%25E6%258E%2592%25E3%2580%2582%25E5%25BD%2593%25E6%2599%259A%25EF%25BC%258C%25E9%2598%25AE%25E6%2588%2590%25E5%258F%2591%25E4%25B8%258E%25E5%259B%25BD%25E5%25AE%25B6%25E5%258D%25AB%25E7%2594%259F%25E5%2581%25A5%25E5%25BA%25B7%25E5%25A7%2594%25E8%25B5%25B4%25E4%25BA%2591%25E5%258D%2597%25E4%25B8%2593%25E5%25AE%25B6%25E7%25BB%2584%25E8%25BF%259B%25E8%25A1%258C%25E5%25B7%25A5%25E4%25BD%259C%25E5%25BA%25A7%25E8%25B0%2588%25EF%25BC%258C%25E4%25BB%258B%25E7%25BB%258D%25E8%25BE%25B9%25E5%25A2%2583%25E7%2596%25AB%25E6%2583%2585%25E9%2598%25B2%25E6%258E%25A7%25E6%2583%2585%25E5%2586%25B5%25E5%258F%258A%25E5%25A2%2583%25E5%25A4%2596%25E7%2596%25AB%25E6%2583%2585%25E8%25BE%2593%25E5%2585%25A5%25E5%25A4%2584%25E7%25BD%25AE%25E5%25BA%2594%25E5%25AF%25B9%25E5%25B7%25A5%25E4%25BD%259C%25EF%25BC%258C%25E5%2590%25AC%25E5%258F%2596%25E4%25B8%2593%25E5%25AE%25B6%25E7%25BB%2584%25E5%25AF%25B9%25E5%25BA%2594%25E6%2580%25A5%25E5%2593%258D%25E5%25BA%2594%25E3%2580%2581%25E5%2585%25A8%25E5%2591%2598%25E6%25A0%25B8%25E9%2585%25B8%25E6%25A3%2580%25E6%25B5%258B%25E3%2580%2581%25E4%25BA%25BA%25E5%2591%2598%25E7%25AE%25A1%25E6%258E%25A7%25E3%2580%2581%25E6%25B5%2581%25E8%25A1%258C%25E7%2597%2585%25E5%25AD%25A6%25E8%25B0%2583%25E6%259F%25A5%25E3%2580%2581%25E5%258C%25BB%25E7%2596%2597%25E6%2595%2591%25E6%25B2%25BB%25E3%2580%2581%25E4%25BF%25A1%25E6%2581%25AF%25E6%258A%25A5%25E9%2580%2581%25E7%25AD%2589%25E6%2596%25B9%25E9%259D%25A2%25E5%25B7%25A5%25E4%25BD%259C%25E7%259A%2584%25E6%2584%258F%25E8%25A7%2581%25E5%25BB%25BA%25E8%25AE%25AE%25E3%2580%2582&speaker=1100&speed=5&audio=http%253A%252F%252Fdueros-short-video.bj.bcebos.com%252Fshort-video%252Fstop_1_24k.wav&from=news"
- }
- },
- "playBehavior": "ENQUEUE"
- }
- }
- }
通过分段持续播放和媒体转换播放这两种解决方案,在DuerOS上可以相对有效地解决TTS 文本播放的问题。
基于TTS 的媒体与文本合成播放
音视频资源可以有效地提升声音的品质和效果,TTS 文本播报能解决动态内容的播放问题,二者结合可以进一步提升技能的表现效果,提升服务的用户体验。
目前,DuerOS 提供的可行方式是在技能中使用SSML。DuerOS支持基础标签和扩展标签两种:基础标签里的所有标签都是SSML标准标签,相当于SSML标签的子集;扩展标签指DuerOS使用标准SSML语言定制的标签。
基础标签目前包括6种:
- speak:根标签
- audio:根据url合成已有音频
- say-as:设置数字、符号等的读法
- sub:替换目标单词
- silence:设置静音,在文本播报的开头或者结尾增加静音片段,最大10s
- phoneme:多音字注音
对于audio标签而言,音频以服务器可以访问的的地址给出,目前支持16K采样和24K采样,16bit,单声道,44字节头的wave格式文件。出于性能的约束,要求必须将相应的音频文件上传到百度云bos平台,使用bos提供的地址。单个请求最大限制3个并列的audio资源,单个audio资源大小限制为3M。
扩展标签目前包括4种:
- background:设置背景声
- say-as:在属性interpret-as加入两个新值,仅对英文有效
- poem:设置诗词,属性值 “wuyan”代表五言诗;“qiyan”代表七言诗;“songci”代表宋词
- space:在所包含文本的空格处生成停顿
其中background标签与audio 标签具有类似的性质,要求必须将相应的音频文件上传到百度云bos平台,使用bos提供的资源url地址。
小结
声音的播放是智能语音设备的基础能力,直接播放音视频资源是一种直接的声音播放方式,面向动态文本内容的声音播放一般采用TTS合成的方式实现。outputSpeech 是DuerOS中的TTS合成指令,repromt指令是outputSpeech 指令在特定场景下的变种。对于长文本的播放,DuerOS 提供了分段连续播放和媒体在线转换两种解决方案,DuerOS中的SSML 播放则可以相对有效地将媒体资源播放和TTS语音播放结合起来,从而使DuerOS 技能给用户带来更好的用户体验。
除了SSML之外,如何进一步提高语音合成的表现力呢?家中的孩童能否在“小度系列产品”上听到爸爸声音讲的故事呢?一系列与声音播放相关的新功能特性已经在路上了,DuerOS,值得期待!
【本文来自51CTO专栏作者“老曹”的原创文章,作者微信公众号:喔家ArchiSelf,id:wrieless-com】