使用 Arduino 进行语音识别和合成
组件和用品
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 3 | ||||
| × | 3 | ||||
| × | 1 |
必要的工具和机器
|
应用和在线服务
|
关于这个项目
在我之前的项目中,我展示了如何使用 Arduino 板和 BitVoicer 服务器来控制几个 LED。在这个项目中,我会让事情变得更复杂一些。我还将使用 Arduino DUE 数模转换器 (DAC) 合成语音。如果您没有 Arduino DUE,您可以使用其他 Arduino 板,但您需要一个外部 DAC 和一些额外的代码来操作 DAC(BVSSpeaker 库不会帮助您)。
在下面的视频中,您可以看到我还让 Arduino 播放了一首小曲,并使 LED 闪烁,就好像它们是钢琴键一样。对不起我的钢琴技巧,但这是我能做的最好的:)。 LED 实际上以与真正的 C、D 和 E 键相同的顺序和时间闪烁,因此如果您身边有钢琴,您可以跟随 LED 播放相同的歌曲。这是一个老零售商(Mappin)的叮当声,现在已经不复存在了。
将执行以下程序将语音命令转换为 LED 活动和合成语音:
- 1. Sparkfun Electret Breakout 板将捕获和放大音频波;
- 2.放大后的信号将在 Arduino 中使用其模数转换器 (ADC) 进行数字化和缓冲;
- 3.音频样本将使用 Arduino 串行端口流式传输到 BitVoicer 服务器;
- 4. BitVoicer Server 将处理音频流并识别其中包含的语音;
- 5.识别出的语音将映射到将发送回 Arduino 的预定义命令。如果命令之一是合成语音,BitVoicer Server 将准备音频流并将其发送到 Arduino;
- 6. Arduino 将识别命令并执行适当的操作。如果接收到音频流,它将排队进入 BVSSpeaker 类并使用 DUE DAC 和 DMA 播放。
- 7. SparkFun 单声道音频放大器将放大 DAC 信号,以便驱动 8 欧姆扬声器。
材料清单:
- Arduino DUE:~U$ 50.00
- Sparkfun 驻极体麦克风突破:7.95 美元
- SparkFun Mono Audio Amp Breakout:7.95 美元
- BitVoicer Server 1.0:9.90 美元
- 8 欧姆扬声器:~2.00 美元
- 面包板:~10.00 美元
- 3 个 LED:约 1.00 美元
- 3 x 330 欧姆电阻:~U$ 0.75
- 跳线:~U$ 0.50
第 1 步:接线
第一步是将 Arduino 和面包板与组件连接起来,如下图所示。我不得不在扬声器下面放一块小橡胶,因为它振动很大,如果没有橡胶,音频质量会受到很大影响。
图> 图> 图>在这里,我们与我之前的项目有一个很小但很重要的区别。大多数 Arduino 板以 5V 运行,但 DUE 以 3.3V 运行。因为我在 3.3V 下运行 Sparkfun Electret Breakout 获得了更好的结果,如果您使用的是 5V Arduino 板,我建议您在 3.3V 引脚和 AREF 引脚之间添加一个跳线。 DUE 已经使用 3.3V 模拟参考,因此您不需要跳线到 AREF 引脚。实际上,DUE 上的 AREF 引脚通过电阻桥连接到微控制器。要使用 AREF 引脚,电阻 BR1 必须从 PCB 上拆下。
第 2 步:将代码上传到 Arduino
现在您必须将以下代码上传到您的 Arduino。为方便起见,本文底部的“附件”部分也提供了 Arduino 草图。在上传代码之前,您必须将 BitVoicer Server 库正确安装到 Arduino IDE 中(导入 .zip 库)。
Arduino 草图 :BVS_Demo2.ino
这个草图有七个主要部分:
- 库引用和变量声明 :前四行包括对 BVSP、BVSMic、BVSSpeaker 和 DAC 库的引用。这些库由 BitSophia 提供,可以在 BitVoicer Server 安装文件夹中找到。添加对 BVSSpeaker 库的引用时,会自动包含 DAC 库。其他行声明了整个草图中使用的常量和变量。 BVSP 类用于与 BitVoicer Server 通信,BVSMic 类用于捕获和存储音频样本,BVSSpeaker 类用于使用 DUE DAC 再现音频。
- 设置功能 :该函数执行以下操作:设置引脚模式及其初始状态;初始化串行通信;并初始化 BVSP、BVSMic 和 BVSSpeaker 类。它还为 BVSP 类的 frameReceived、modeChanged 和 streamReceived 事件设置“事件处理程序”(它们实际上是函数指针)。
- 循环函数 :该函数执行五个重要操作:向服务器请求状态信息(keepAlive() 函数);检查服务器是否发送了任何数据并处理接收到的数据(receive() 函数);控制音频流的录制和发送(isSREAvailable()、startRecording()、stopRecording()和sendStream()函数);播放排队进入 BVSSpeaker 类的音频样本(play() 函数);并调用 playNextLEDNote() 函数,该函数控制接收到 playLEDNotes 命令后 LED 的闪烁方式。
- BVSP_frameReceived 函数 :每次receive() 函数识别出已接收到一个完整的帧时,都会调用此函数。在这里,我运行从 BitVoicer Server 发送的命令。控制 LED 的命令包含 2 个字节。第一个字节表示引脚,第二个字节表示引脚值。我使用analogWrite() 函数为引脚设置适当的值。我还检查是否已收到字节类型的 playLEDNotes 命令。如果已收到,我将 playLEDNotes 设置为 true 并标记当前时间。该时间将被 playNextLEDNote 函数用于将 LED 灯与歌曲同步。
- BVSP_modeChanged 函数 :每次接收()函数识别出站方向(服务器-> Arduino)的模式更改时都会调用此函数。哇!!!那是什么?! BitVoicer Server 可以向 Arduino 发送帧数据或音频流。在通信从一种模式进入另一种模式之前,BitVoicer Server 会发送一个信号。 BVSP 类识别此信号并引发 modeChanged 事件。在 BVSP_modeChanged 函数中,如果我检测到通信正在从流模式转为帧模式,我知道音频已经结束,所以我可以告诉 BVSSpeaker 类停止播放音频样本。
- BVSP_streamReceived 函数 :每次receive() 函数识别出已接收到音频样本时,都会调用此函数。我只需检索样本并将它们排入 BVSSpeaker 类,以便 play() 函数可以重现它们。
- playNextLEDNote 功能 :此函数仅在 BVSP_frameReceived 函数识别 playLEDNotes 命令时运行。它控制 LED 并将其与从 BitVoicer 服务器发送的音频同步。为了使 LED 与音频同步并知道正确的时间,我使用了 Sonic Visualizer。这个免费软件让我可以看到音频波,所以我可以很容易地知道什么时候按下了钢琴键。它还显示了一个时间线,这就是我如何获得此函数中使用的毫秒数。听起来像一个愚蠢的把戏,它是。我认为可以分析音频流并打开相应的 LED,但这是我无法实现的。
第 3 步:导入 BitVoicer 服务器解决方案对象
现在您必须设置 BitVoicer 服务器才能与 Arduino 配合使用。 BitVoicer Server 有四个主要的解决方案对象:Locations、Devices、BinaryData 和 Voice Schemas。
位置表示安装设备的物理位置。就我而言,我创建了一个名为 Home 的位置。
设备是 BitVoicer 服务器客户端。我创建了一个混合设备,命名为 ArduinoDUE 并输入通信设置。 重要 :即使 Arduino DUE 也有少量内存来存储 BitVoicer Server 将流式传输的所有音频样本。如果不限制带宽,则需要更大的缓冲区来存储音频。由于这个原因,我遇到了一些缓冲区溢出,因此我不得不将通信设置中的数据速率限制为每秒 8000 个样本。
BinaryData 是一种 BitVoicer Server 可以发送到客户端设备的命令。它们实际上是可以链接到命令的字节数组。当 BitVoicer Server 识别出与该命令相关的语音时,它会将字节数组发送到目标设备。我为每个引脚值创建了一个 BinaryData 对象,并将它们命名为 ArduinoDUEGreenLedOn、ArduinoDUEGreenLedOff 等等。我的解决方案中有 18 个 BinaryData 对象,因此我建议您从 VoiceSchema.sof 下载并导入这些对象 文件在下面。
语音模式是一切都聚集在一起的地方。它们定义了应该识别哪些句子以及运行哪些命令。对于每个句子,您可以根据需要定义任意数量的命令以及它们将执行的顺序。您还可以定义命令之间的延迟。这就是我设法执行您在视频中看到的一系列操作的方法。
我的 Voice Schema 中有一个句子是“播放一首小歌”。这句话包含两个命令。第一个命令发送一个字节,指示以下命令将是音频流。然后 Arduino 在传输音频时开始“播放”LED。音频是我自己录制的一小段钢琴曲,并将其设置为第二个命令的音频源。 BitVoicer Server 仅支持 8 位单声道 PCM 音频(每秒 8000 个样本),因此如果您需要将音频文件转换为这种格式,我建议使用以下在线转换工具:http://audio.online-convert.com/convert -to-wav。
您可以从以下文件导入(导入解决方案对象)我在该项目中使用的所有解决方案对象。一个包含 DUE 设备,另一个包含语音架构及其命令。
解决方案对象文件 :
- Device.sof
- VoiceSchema.sof
第 4 步:结论
你去吧!您可以打开所有设备并执行视频中显示的操作。
正如我在之前的项目中所做的那样,我通过在 BitVoicer 服务器管理器中启用 Arduino 设备来启动语音识别。一旦启用,Arduino 就会识别一个可用的语音识别引擎并开始将音频流式传输到 BitVoicer 服务器。但是,现在您会在 Arduino RX LED 中看到更多活动,同时音频从 BitVoicer 服务器流式传输到 Arduino。
在我的下一个项目中,我将更加雄心勃勃。我打算将 WiFi 通信添加到一个 Arduino 并通过语音一起控制另外两个 Arduino。我在想他们之间的某种游戏。非常欢迎提出建议!
代码
- Arduino 草图
Arduino SketchArduino
#include#include #include #include // 定义用于捕获音频的 Arduino 引脚 #define BVSM_AUDIO_INPUT 7//定义 LED 引脚#define RED_LED_PIN 6#define YELLOW_LED_PIN 9#define GREEN_LED_PIN 10// 定义将作为参数传递给 // BVSP.begin 函数的常量const unsigned long STATUS_REQUEST_TIMEOUT =3000;const unsigned long STATUS_RE4QUEST_INTER00;定义麦克风音频缓冲区大小 const int MIC_BUFFER_SIZE =64;// 定义扬声器音频缓冲区大小const int SPEAKER_BUFFER_SIZE =128;// 定义接收缓冲区大小const int RECEIVE_BUFFER_SIZE =2;// 初始化一个新的全局实例BVSP 类 BVSP bvsp =BVSP();// 初始化 BVSMic 类的新全局实例 BVSMic bvsm =BVSMic();// 初始化 BVSSpeaker 类的新全局实例 BVSSpeaker bvss =BVSSpeaker();// 创建将用于读取记录样本的缓冲区 // 从 t he BVSMic class byte micBuffer[MIC_BUFFER_SIZE];// 创建一个缓冲区,用于将音频样本写入 // 到 BVSSpeaker 类 byte SpeakerBuffer[SPEAKER_BUFFER_SIZE];// 创建一个缓冲区,用于读取发送的命令//来自 BitVoicer Server。// Byte 0 =pin number// Byte 1 =pin valuebyte receiveBuffer[RECEIVE_BUFFER_SIZE];// 这些变量用于控制何时播放//“LED Notes”。这些音符将与 // 从 BitVoicer Server 流式传输的歌曲一起播放。bool playLEDNotes =false;unsigned int playStartTime =0;void setup() { // 设置引脚模式 pinMode(RED_LED_PIN, OUTPUT); pinMode(YELLOW_LED_PIN, OUTPUT); pinMode(GREEN_LED_PIN,输出); // 设置所有 LED 的初始状态 digitalWrite(RED_LED_PIN, LOW);数字写入(YELLOW_LED_PIN,低);数字写入(GREEN_LED_PIN,低); // 以 115200 bps 的速率开始串行通信 Serial.begin(115200); // 设置将用于 // 通信的 Arduino 串行端口,状态请求需要多长时间 // 超时以及状态请求应多久发送到 // BitVoicer 服务器。 bvsp.begin(Serial, STATUS_REQUEST_TIMEOUT, STATUS_REQUEST_INTERVAL); // 定义处理frameReceived的函数 // event bvsp.frameReceived =BVSP_frameReceived; // 设置将处理 modeChanged 的函数 // 事件 bvsp.modeChanged =BVSP_modeChanged; // 设置处理streamReceived的函数 // event bvsp.streamReceived =BVSP_streamReceived; // 准备 BVSMic 类计时器 bvsm.begin(); // 设置 BVSSpeaker 类将使用的 DAC bvss.begin(DAC);}void loop() { // 检查状态请求间隔是否已经过去,如果已经过去,则向 BitVoicer 服务器发送状态请求bvsp.keepAlive(); // 检查串口缓冲区中是否有可用数据 // 并根据 BitVoicer 服务器协议的规范 // 处理其内容 bvsp.receive(); // 检查是否有一个 SRE 可用。如果有, // 开始记录。 if (bvsp.isSREAvailable()) { // 如果 BVSMic 类未录制,则设置音频 // 输入并开始录制 if (!bvsm.isRecording) { bvsm.setAudioInput(BVSM_AUDIO_INPUT, DEFAULT); bvsm.startRecording(); } // 检查 BVSMic 类是否有可用样本 if (bvsm.available) { // 在传输流之前确保入站模式为 STREAM_MODE if (bvsp.inboundMode ==FRAMED_MODE) bvsp.setInboundMode(STREAM_MODE); // 从 BVSMic 类中读取音频样本 int bytesRead =bvsm.read(micBuffer, MIC_BUFFER_SIZE); // 将音频流发送到 BitVoicer Server bvsp.sendStream(micBuffer, bytesRead); } } else { // 没有可用的 SRE。如果 BVSMic 类正在录制, // 停止它。 if (bvsm.isRecording) bvsm.stopRecording(); } // 播放 BVSSpeaker 类中可用的所有音频样本 // 内部缓冲区。这些示例写在 // BVSP_streamReceived 事件处理程序中。 // 如果内部缓冲区中没有可用的样本,则不会播放任何内容。 bvss.play(); // 如果 playLEDNotes 已设置为 true, // 会随着音乐播放“LED 音符”。 if (playLEDNotes) playNextLEDNote();}// 处理 frameReceived 事件 void BVSP_frameReceived(byte dataType, int payloadSize) { // 检查接收到的帧是否包含二进制数据 // 0x07 =二进制数据(字节数组) if (dataType ==DATA_TYPE_BINARY) { // 如果接收到 2 个字节,则处理命令。 if (bvsp.getReceivedBytes(receiveBuffer, RECEIVE_BUFFER_SIZE) ==RECEIVE_BUFFER_SIZE) {analogWrite(receiveBuffer[0], receiveBuffer[1]); } } // 检查接收的帧是否包含字节数据类型 // 0x01 =字节数据类型 else if (dataType ==DATA_TYPE_BYTE) { // 如果接收到的字节值为 255,则设置 playLEDNotes // 并标记当前时间。 if (bvsp.getReceivedByte() ==255) { playLEDNotes =true; playStartTime =毫秒(); } }}// 处理 modeChanged 事件 void BVSP_modeChanged() { // 如果 outboundMode(服务器 --> 设备)已变为 // FRAMED_MODE,则不应接收任何音频流。 // 告诉 BVSSpeaker 类在其 // 内部缓冲区变空时完成播放。 if (bvsp.outboundMode ==FRAMED_MODE) bvss.finishPlaying();} // 处理 streamReceived 事件 void BVSP_streamReceived(int size) { // 从 BVSP 类中获取接收到的流 int bytesRead =bvsp.getReceivedStream(speakerBuffer, SPEAKER_BUFFER_SIZE); // 将接收到的流排入队列播放 bvss.enqueue(speakerBuffer, bytesRead);}// 根据时间点亮相应的 LED // 收到开始播放 LED 音符的命令。// 这里使用的时间与the music.void playNextLEDNote(){ // 获取 playStartTime 和 // 当前时间之间经过的时间。 unsigned long elapsed =millis() - playStartTime; // 关闭所有 LED allLEDsOff(); // 最后一个音符已播放。 // 关闭最后一个 LED 并停止播放 LED 音符。 if (elapsed>=11500) {analogWrite(RED_LED_PIN, 0); playLEDNotes =false; } else if (elapsed>=9900)analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=9370) analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=8900) analogWrite(YELLOW_LED_PIN, 255); // D 注意 else if (elapsed>=8610) analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=8230) analogWrite(YELLOW_LED_PIN, 255); // D 注意 else if (elapsed>=7970) analogWrite(YELLOW_LED_PIN, 255); // D 注意 else if (elapsed>=7470) analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=6760) analogWrite(GREEN_LED_PIN, 255); // E 注意 else if (elapsed>=6350) analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=5880) analogWrite(YELLOW_LED_PIN, 255); // D 注意 else if (elapsed>=5560) analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=5180) analogWrite(YELLOW_LED_PIN, 255); // D 注意 else if (elapsed>=4890) analogWrite(YELLOW_LED_PIN, 255); // D 注意 else if (elapsed>=4420) analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=3810) analogWrite(GREEN_LED_PIN, 255); // E 注意 else if (elapsed>=3420) analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=2930) analogWrite(YELLOW_LED_PIN, 255); // D 注意 else if (elapsed>=2560) analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=2200) analogWrite(YELLOW_LED_PIN, 255); // D 注意 else if (elapsed>=1930) analogWrite(YELLOW_LED_PIN, 255); // D 注意 else if (elapsed>=1470) analogWrite(RED_LED_PIN, 255); // C 注意 else if (elapsed>=1000) analogWrite(GREEN_LED_PIN, 255); // E note}// 关闭所有 LED.void allLEDsOff(){analogWrite(RED_LED_PIN, 0);模拟写入(YELLOW_LED_PIN,0);模拟写入(GREEN_LED_PIN,0);}
示意图
制造工艺
- 使用 Arduino 和 RFID 和 Python 的考勤系统
- 带 LED 和压电扬声器的 DHT11 传感器
- NeoPixel Ring 的陀螺仪乐趣
- Arduino 温度。使用 3.2 显示的监视器和实时时钟
- 使用 Arduino 和 Android 设备控制 Roomba 机器人
- DIY 电压表与 Arduino 和诺基亚 5110 显示器
- 使用 Arduino 和 MPU6050 控制伺服电机
- u-blox LEA-6H 02 GPS 模块,带有 Arduino 和 Python
- 如何使用 DHT11 在 Blynk 上读取温度和湿度
- 4x4x4 LED 立方体,带有 Arduino Uno 和 1sheeld
- 带 GPS 和 TFT 显示屏蔽的 GPS 位置显示
- 使用 Arduino Uno 和蓝牙进行汽车控制