介绍我开发的一个开源的智能音箱项目 dingdang-robot 。
这个项目其实来源于我生活中的一个需求:我每天晚上都会去厨房做一个面包当明天的早餐,当我把用料按顺序准备好放进面包机时,我需要准确预约到明天早上我吃早餐的时间。然而,几乎每次在这个时候我都没有带手机在身边,而是都放在客厅里充电,这时只能跑去客厅看时间。虽然厨房到客厅只有几步之遥,但自己又是懒癌患者,每天都要这么来回奔波就觉得很不方便。要解决这个问题当然有很多种方法,比如直接买个小时钟放在厨房。不过我更希望“连看都不用看”,直接有人告诉我时间。所以,我需要一个像 Amazon Echo 那样的智能音箱。
然而,不论是 Amazon Echo 、Google Home 还是微软 Cortana 音箱,在国内的使用都是个问题。虽然国内也有类似的智能音箱产品,但我没有用过这些产品,不知道可定制性如何。比如,如果我需要开发个功能让它告诉我某种面包的配方是什么,这些产品就不一定能做到了。考虑再三,我决定自己动手写一个。整个项目用了差不多三个星期的业余零碎时间。
先放上项目主页:http://dingdang.hahack.com
下面分享一下我在开发这个项目过程中的心得。
首先要解决的是硬件问题。我选择在 Raspberry Pi 上开发。于是我买了块 Raspberry Pi 三代主板。麦克风和音响方面,出于美观的目的,买了个自带音响的 USB 全向会议麦克风。整套设备看起来就像这样:
后面觉得这个麦克风自带的音响音质太一般了,所以我又外接了一个小音箱。然后再插了一个摄像头,用来实现拍照功能。最终的完全体进化成了这样:
硬件有了,接下来就得开始写软件了。主要的框架借鉴了 Jasper 项目,并加入了我自己的定制和想法。这里说说一些有意思的部分。
智能音箱要解决的一个最重要的问题就是如何接收指令。这里头主要涉及两个问题:
被动唤醒阶段的基本策略是:每次以 16000 的采样率录制 1024 个采样作为一个采样集,然后对采样集进行信号强度估计,当某个采样集信号强度大于一个阈值时,就认为可能接受到了指令。然后持续录制多 1 秒时间,再转交给语音识别模块。当语音识别模块认为是唤醒词时,进入主动聆听阶段。
主动聆听的策略与被动唤醒基本相似,每次以 16000 的采样率录制 1024 个采样作为一个采样集,然后对采样集进行信号强度估计,当某个采样集信号强度低于一个阈值约 1 秒的时间时,就认为用户已说完了指令。当然还要考虑环境吵杂,一直处于聆听的可能。因此可以再加一个超时保护,超过 12 秒就结束聆听。
说说STT(语音识别)引擎和TTS(文本转文本)引擎的选择。由于被动唤醒会试图识别所有听到的内容,出于隐私保护的目的,应该使用离线的语音识别引擎,因此我选择的是 PocketSphinx 。而对于主动聆听,由于是在唤醒阶段才会进行转换,进入主动聆听前会有蜂鸣提示,用户也会清楚此时叮当正在听他们说话,相对来说隐私泄露的可能性就比较低,因此我选择的是在线的百度 STT 语音识别服务,也省下了扩展语音识别模型的工夫,有利于更好地实现插件可扩展。TTS 引擎方面同样也先支持了百度的语音合成。
在实际测试中,PocketSphinx 的识别出乎意料的好。由于我的离线指令集只有几个候选唤醒词,PocketSphinx 对这些唤醒词的识别非常灵敏,甚至有时候其他声音也可能被误当成唤醒词而唤醒叮当。但即使被意外唤醒了,不去理会叮当就可以了。
相比之下,百度的语音识别就比较迟钝了。有时候明明我发音很清晰了,还是会识别成另外的含义。通过在百度的语音识别平台上传自定义的语音识别词库 可以提高识别的准确率。另外,由于我用的是 Restful API,网速比较差的时候响应也比较慢。我在家用的是 10M 带宽的网络,反应速度还算可以接受。我准备后面尝试接入更多的语音识别平台,看看识别速度和准确度方面能否有所提升。
下面这个视频是我与叮当对话的演示。我把唤醒词设置成了“小梅”:
一个问题是当回答内容比较长(比如问叮当当天的新闻)时,合成语音的耗时会变得很长,给人的感受是叮当的响应很慢。所以我加了个 read_long_content
的选项。当内容过长时,改成发送到用户的邮箱或者微信。下面这段音频是一个例子:
叮当最好玩的部分当然就是玩插件了,通过写插件可以让叮当接入各种各样的服务,完成各种各样的事情。我在叮当里也内置了几个插件[1]。为了方便用户扩展,我把 ~/.dingdang/contrib
设定为第三方插件目录,允许让用户在里头编写插件并提交到 dingdang-contrib 项目共享。
下面这个视频是 Camera 插件的演示[2]:
另外,如果接入了微信,还可以让叮当安静地拍一张家里的照片,而不发出任何声音。下面这个视频演示了如何使用微信与家里的机器人交互,包括远程控制拍照。
这对于需要远程监控家里的情况的用户而言就非常方便了,比如家里有小孩的情况。
既然是智能音箱,当然少不了播放音乐的功能。所以我额外写了个播放网易云音乐的插件 NetEaseMusic 。出于版权考虑,并不集成进官方插件中,而是放进 dingdang-contrib 里头。
这个插件的实现比较复杂。普通的插件接受到指令,响应完就退出了。而为了能支持各种指令控制音乐播放,这个插件在接收到播放控制指令后并不退出插件,而是进入一个播放器模式,这个模式主动聆听得到的指令只会在播放控制指令集中匹配,其他的插件指令都不起作用。只有当用户要求退出播放时才回到普通模式。NetEaseMusic 的播放控制指令如下:
指令 | 相同指令 | 用途 |
---|---|---|
播放音乐 | - | 进入音乐播放模式。在音乐播放模式下,其他的插件功能将不可用。 |
下一首 | 切歌, 下一首歌, 下首歌 | 切换到下一首歌。如果没有下一首歌,就回到列表中第一首歌 |
上一首 | 上一首歌,上首歌 | 切换到上一首歌。如果没有上一首歌,就跳到列表中最后一首歌 |
大声点 | 大点声,大声 | 调高播放音量 |
小声点 | 小点声,小声 | 降低播放音量 |
随机播放 | - | 随机播放列表中的音乐 |
顺序播放 | - | 顺序播放列表中的音乐 |
暂停播放 | - | 暂停音乐的播放 |
播放 | 继续 | 继续音乐的播放 |
榜单 | - | 播放推荐榜单 |
歌单 | - | 播放用户的歌单(如果有多张,将只播放第一张) |
结束播放 | 退出播放,停止播放 | 退出音乐播放模式。 |
搜索 | 查找 | 搜索歌曲/歌手。将自动播放搜索结果。 |
什么歌 | - | 正在播放的是什么歌 |
实现这个插件的过程中还参考了 Vellow 的 MusicBox 项目[3]以及 yaphone 的 RasWxNeteaseMusic 。为了方便重用,我把 MusicBox 的核心 API 抽离了出来封成了一个 MusicBoxApi 库 。比较坑爹的是就在我准备发布叮当的前几天,老的获取音乐地址的方式彻底不能用了,而新的接口批量获取的地址不知道为什么是乱序的,于是我只能在播放每首歌前都调用一下新版的获取地址的 POST 接口,又增加了一点响应时间[4]。
下面这段音频是使用叮当控制音乐播放的演示:
完成了音乐播放功能后,叮当的好玩程度提高了很多。以前要听歌,至少得把电脑或者手机打开。现在只需要喊一声叫叮当播放歌曲就可以了。想换歌、搜索歌曲、调节音量都是说句话就搞定的事情,生活幸福指数大幅提升 ^_^
。
对于有 Coding 能力的 Hacker 而言,自己动手做一个智能音箱,不仅可以当做业余练手项目,还可以自由地定制硬件模块,并实现自己需要的各种功能,这远比直接购买一个 Amazon Echo 有趣得多。
后面我计划做的事情有:
更重要的,我更希望能有其他有兴趣的朋友参与进来,一同开发完善这个智能音箱项目。我相信,这种个性化服务的产品本身就应该是完全可定制的。而您的加入可以使叮当变得更智能!