news 2026/9/15 19:20:14

IoT-For-Beginners 智能定时器实战:在虚拟 IoT 设备上用 Azure 语音服务实现文本转语音(TTS)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IoT-For-Beginners 智能定时器实战:在虚拟 IoT 设备上用 Azure 语音服务实现文本转语音(TTS)

IoT-For-Beginners 智能定时器实战:在虚拟 IoT 设备上用 Azure 语音服务实现文本转语音(TTS)

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

本篇指南聚焦 IoT-For-Beginners 第 6 部分「消费类物联网」第 3 课中的虚拟设备(Virtual IoT Device)方案,讲解如何复用上一课用于语音识别的 Azure Speech 服务,在smart-timer项目中通过SpeechSynthesizer将文本合成为语音,让设备在定时器启动与结束两个时刻对用户进行语音播报。学完本篇,你将掌握 Speech SDK 的语音合成配置、语音(Voice)动态选取、SSML 拼接,以及「朗读期间暂停连续识别」这一关键防回声技巧,并能读懂仓库中完整的可运行示例源码。

为什么定时器需要文本转语音

在 6-consumer/lessons/3-spoken-feedback/README.md 中,本课将智能助手定义为双向通信设备:用户对它说话("set a 3 minute timer"),它也必须用语音回应("Ok, your timer is set for 3 minutes")。前两课已经完成了「语音→文本→LUIS 意图解析→获取定时秒数」的链路,本课补上最后一块拼图——把确认信息与倒计时结束提醒用语音说出来

文本转语音(Text to Speech,TTS)的典型流程分为三个阶段:文本分析(把 "1234" 按语境转换为 "one thousand two hundred thirty four" 或 "one two three four")、语言分析(拆分为音素并附加语调数据)、波形生成(早期系统拼接单音录音,现代系统使用深度学习模型生成接近真人自然度的语音)。你不需要自己实现这些阶段——Azure 语音服务的SpeechSynthesizer会替你完成全部工作,设备只需把文本和语音参数发给云端服务,即可拿回可直接播放的音频数据。

虚拟设备方案的整体链路

在虚拟 IoT 设备(运行 Python 的 PC 上模拟)中,smart-timer的完整工作流如下,可从 code-spoken-response/virtual-iot-device/smart-timer/app.py 一窥全貌:

  1. SpeechRecognizer持续识别麦克风输入,把语音转成文本;
  2. process_text将文本 POST 到上一课构建的 serverless 函数(text-to-timer函数内部调用 LUIS 解析set timer意图),拿回定时秒数;
  3. create_timerthreading.Timer创建后台定时线程,秒数到达后触发announce_timer
  4. say函数调用SpeechSynthesizer.speak_ssml()朗读公告文本——这正是本文要实现的代码。

下面按原文档的实操步骤,从零编写这段 TTS 代码。

前置条件

  • 已完成上一课(第 2 课「语言理解」)的smart-timer项目,包含speech_api_keylocationlanguage三个变量(Speech 服务密钥、区域、语言代码),并已实现基于SpeechRecognizer的连续语音识别;
  • 虚拟环境已在 VS Code 终端中加载(python -m venv .venv创建后需激活);
  • serverless 函数应用处于运行状态,以便定时器秒数解析正常工作。

步骤一:导入 SpeechSynthesizer

打开smart-timer项目中的app.py,在现有from azure.cognitiveservices.speech import ...导入语句中加入SpeechSynthesizer

from azure.cognitiveservices.speech import SpeechConfig, SpeechRecognizer, SpeechSynthesizer

SpeechSynthesizer是 Speech SDK 中负责「文本/SSML → 音频」的核心类,与SpeechRecognizer同属azure.cognitiveservices.speech包。

步骤二:为合成器创建语音配置

say函数上方创建一套独立于识别器SpeechConfig

speech_config = SpeechConfig(subscription=speech_api_key, region=location) speech_config.speech_synthesis_language = language speech_synthesizer = SpeechSynthesizer(speech_config=speech_config)

这里复用了与识别器相同的 API 密钥、区域和语言,但请注意两点差异:

  • 识别器配置的是speech_recognition_language,而合成器配置的是speech_synthesis_language,二者语义不同、需分别设置;
  • 由于调用get_voices_async()时会用到speech_synthesizer,因此配置与实例化必须放在模块级(脚本主流程中),而不能放在每次调用的say函数内部,否则会反复创建连接、拖慢响应。

步骤三:动态选取匹配语言的语音

每种语言都支持多种语音(Voice),可通过 SDK 直接获取列表并挑选。在配置代码下方追加:

voices = speech_synthesizer.get_voices_async().get().voices first_voice = next(x for x in voices if x.locale.lower() == language.lower()) speech_config.speech_synthesis_voice_name = first_voice.short_name

这段代码的工作机制是:

  • get_voices_async()异步拉取该 Speech 服务支持的全部语音列表,.get()同步等待结果;
  • 列表中的每个语音对象带有locale(如hi-IN)与short_name(如hi-IN-SwaraNeural)属性;
  • next(...)找到第一个locale与当前language(忽略大小写)匹配的语音,把其short_name写入speech_synthesis_voice_name,后续合成即使用该语音。

备选方案:硬编码指定语音

如果你想固定使用某个特定发音人(如带特定口音的语音),可以删掉上述动态查找逻辑,直接从语音支持文档中挑一个short_name硬编码:

speech_config.speech_synthesis_voice_name = 'hi-IN-SwaraNeural'

步骤四:为响应生成 SSML

say函数需要向服务端发送的不是裸文本,而是Speech Synthesis Markup Language(SSML)——一种基于 XML 的语音合成标记语言,可声明文本语言、所用语音,甚至可控制语速、音量、音调。更新say函数内容,拼接 SSML:

def say(text): ssml = f'<speak version=\'1.0\' xml:lang=\'{language}\'>' ssml += f'<voice xml:lang=\'{language}\' name=\'{first_voice.short_name}\'>' ssml += text ssml += '</voice>' ssml += '</speak>'

生成的 SSML 结构与 6-consumer/lessons/3-spoken-feedback/README.md 中给出的示例一致,例如使用英国英语语音en-GB-MiaNeural播报 "Your 3 minute 5 second time has been set" 时,SSML 形如:

<speak version='1.0' xml:lang='en-GB'> <voice xml:lang='en-GB' name='en-GB-MiaNeural'> Your 3 minute 5 second time has been set </voice> </speak>

步骤五:朗读前暂停识别、朗读后恢复

在 SSML 拼接代码下方,执行「停识别 → 朗读 → 恢复识别」三段操作:

recognizer.stop_continuous_recognition() speech_synthesizer.speak_ssml(ssml) recognizer.start_continuous_recognition()

这一步是整个方案中最容易被忽略却最关键的细节:如果朗读期间麦克风识别仍在运行,设备自己播报的语音会被SpeechRecognizer捕获、转成文本、发给 LUIS,并被误判为一条「设置新定时器」的请求——于是新定时器触发新播报、新播报又被识别成新请求,形成永不终止的无限循环。先stop_continuous_recognition()再朗读,可以彻底切断这条回声路径。

💁 想直观验证这一点:把停止/恢复识别的两行注释掉,然后设置一个定时器,观察设备是否会因自己的播报而不断创建新定时器。

步骤六:运行与验证

启动函数应用后运行虚拟设备程序,对着麦克风说类似 "set a two minute timer" 的指令,应当听到两次语音反馈:

  1. 定时器刚创建时——"Your 2 minute timer started."(确认已设置);
  2. 倒计时归零时——"Times up on your 2 minute timer."(提醒时间到)。

完整可运行的参考实现

仓库在 code-spoken-response/virtual-iot-device/smart-timer/app.py 中提供了完整的成品代码(上文各步骤均可在其中找到对应实现)。其核心骨架如下:

import requests import threading import time from azure.cognitiveservices.speech import SpeechConfig, SpeechRecognizer, SpeechSynthesizer speech_api_key = '<key>' location = '<location>' language = '<language>' recognizer_config = SpeechConfig(subscription=speech_api_key, region=location, speech_recognition_language=language) recognizer = SpeechRecognizer(speech_config=recognizer_config) def say(text): ssml = f'<speak version=\'1.0\' xml:lang=\'{language}\'>' ssml += f'<voice xml:lang=\'{language}\' name=\'{first_voice.short_name}\'>' ssml += text ssml += '</voice>' ssml += '</speak>' recognizer.stop_continuous_recognition() speech_synthesizer.speak_ssml(ssml) recognizer.start_continuous_recognition() def announce_timer(minutes, seconds): announcement = 'Times up on your ' if minutes > 0: announcement += f'{minutes} minute ' if seconds > 0: announcement += f'{seconds} second ' announcement += 'timer.' say(announcement) def create_timer(total_seconds): minutes, seconds = divmod(total_seconds, 60) threading.Timer(total_seconds, announce_timer, args=[minutes, seconds]).start() announcement = '' if minutes > 0: announcement += f'{minutes} minute ' if seconds > 0: announcement += f'{seconds} second ' announcement += 'timer started.' say(announcement) def get_timer_time(text): url = '<URL>' body = {'text': text} response = requests.post(url, json=body) if response.status_code != 200: return 0 payload = response.json() return payload['seconds'] def process_text(text): print(text) seconds = get_timer_time(text) if seconds > 0: create_timer(seconds) def recognized(args): process_text(args.result.text) recognizer.recognized.connect(recognized) recognizer.start_continuous_recognition() speech_config = SpeechConfig(subscription=speech_api_key, region=location) speech_config.speech_synthesis_language = language speech_synthesizer = SpeechSynthesizer(speech_config=speech_config) voices = speech_synthesizer.get_voices_async().get().voices first_voice = next(x for x in voices if x.locale.lower() == language.lower()) speech_config.speech_synthesis_voice_name = first_voice.short_name while True: time.sleep(1)

代码要点补充说明:

  • announce_timercreate_timer中的公告文本都会按时间单位是否有值决定是否拼入语句——分钟为 0 时只报秒数,反之亦然,避免出现 "0 minute" 这类冗余播报;
  • threading.Timer(total_seconds, announce_timer, args=[minutes, seconds]).start()创建后台线程,定时结束后自动调用announce_timer(minutes, seconds),不阻塞主循环;
  • while True: time.sleep(1)让主线程保持存活,使识别回调与定时线程持续运行。

函数应用侧的 TTS 对照实现

如果你选用「设备端调用 serverless 函数获取音频」的架构(而非直接在设备上合成),仓库同样给出了函数应用示例,位于 code-spoken-response/functions/smart-timer-trigger:

  • text-to-speech/init.py:接收languagevoicetext三个字段,先用Ocp-Apim-Subscription-Key换取访问令牌,再向https://{location}.tts.speech.microsoft.com/cognitiveservices/v1发送application/ssml+xml请求,返回riff-48khz-16bit-mono-pcm格式音频;
  • get-voices/init.py:调用语音列表端点,按语言过滤后返回该语言下所有ShortName语音名列表——这与虚拟设备端get_voices_async()的职责对等;
  • local.settings.json:定义了SPEECH_KEYSPEECH_LOCATIONLUIS_KEY等环境变量占位符,运行函数前需替换为真实值;
  • text-to-timer/init.py:调用 LUIS 预测接口,命中set timer意图时把numbertime unit实体换算成总秒数返回。

对比可见:虚拟设备方案把语音合成能力放在设备端 Python 进程内(依赖azure-cognitiveservices-speechSDK),而函数方案把合成能力收拢到云端、设备只负责播放;两种思路在本课的 Arduino(Wio Terminal,见 wio-terminal-text-to-speech.md)与树莓派(见 pi-text-to-speech.md)等路径中也各有体现,可按硬件能力与部署策略取舍。

小结与进阶实验

至此,smart-timer已经具备完整的「语音设置定时器 → 语音确认 → 语音提醒」闭环。原文档在 README 的挑战环节还给出了一条进阶路线:SSML 支持对指定词语添加强调、插入停顿、改变音调等控制标记,你可以尝试从设备发送不同 SSML 并对比合成效果,体会标记语言对发音细节的控制能力。

参考路径速览

  • 虚拟设备成品代码:code-spoken-response/virtual-iot-device/smart-timer/app.py
  • 本课总览:6-consumer/lessons/3-spoken-feedback/README.md
  • 定时器设置步骤:single-board-computer-set-timer.md
  • 函数应用 TTS 实现:text-to-speech/init.py

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 19:19:22

如何快速修改AI短剧的镜头和替换包装?

如何快速修改AI短剧的镜头和替换包装&#xff1f;特种猫的做法是正片和包装分层管理&#xff1a;镜头只替换有问题的单个分镜&#xff0c;包装用独立模板轨道一键覆盖&#xff0c;不重跑整集。截至 2026 年&#xff0c;创作者普遍踩3个坑&#xff1a;改1个镜头要整集重新生成、…

作者头像 李华
网站建设 2026/9/15 19:17:47

Unity角色口型同步与眼神模拟:SALSA With RandomEyes实战调优指南

学过几年Unity动画&#xff0c;接手过不少数字人、对话NPC的项目&#xff0c;我敢说在“让角色开口说话”这件事上&#xff0c;最让我省心的方案就是SALSA With RandomEyes。这个插件从名字就能看出来&#xff0c;它干两件事&#xff1a;SALSA负责说话时的口型同步&#xff0c;…

作者头像 李华