아두이노와 BitVoicer Server를 이용한 음성인식(2)

by 강종구 | 2017-07-18 16:36
아두이노 음성 음성인식

이번 시간에는 저번 시간에 다루엇던 BItVoicer Server를 이용한 프로젝트를 조금 더 심화해서 진행하려 합니다. 아두이노 DUE를 사용해서 몇가지 노래를 플레이하고 멜로디에 따라 LED가 켜지는 프로젝트 입니다. 만약 아두이노 DUE가 없다면, 다른 아두이노 보드를 사용하셔도 됩니다.


1. 준비물

아두이노 DUE, Sparkfun Mono Audio Amp Breakout, Sparkfun Electret Microphone Breakout, 스피커, BitVoicer Server 1.0, 빵판, LED 3개, 330옴 레지스터 3개, 점퍼선


2. 음성인식의 순서

1) Sparkfun Electret Breakout board에 의해 오디오 파동이 측정되고 증폭되어진다.

2) 증폭된 신호가 analog-to-digital converter(ADC)를 사용해서 아두이노 내에 디지털화되고 버퍼화된다.

3) 오디오 샘플이 아두이노 시리얼 포트를 이용해서 BitVoice Server에 스트림된다

4) BitVoicer Server 오디오 스트림 과정을 수행하고 음성을 인식한다.

5) 인식된 음성이 미리 지정된 명령에 대해서 매칭되고 그것은 다시 아두이노로 전송된다. 만약 명령어가 여러가지 복합된 음성으로 구성되었다면, BitVoicer Server는 오디오 stream을 준비해서 이를 아두이노로 전송한다.

6) 아두이노가 명령어들을 인식해서 적절한 행동을 수행한다. 만약 오디오 Stream을 받는다면, BVSSpeaker 클래스에 대기하거나 DUE DAC와 DMA를 실행한다.

7) Sparkfun Mono Audio Amp는 DAC 신호를 받아서 스피커를 통해 출력해준다.


3. 연결방법


- 전체 연결 방법


- 실제 연결한 모습


- LED 부분


- Sparkfun Mono Audio Amp 부분


여기서 중요한 것은, 대부분의 아두이노 보드는 5V에서 작동하지만, 아두이노 DUE는 3.3V에서 작동한다는 것입니다. 이 프로젝트에서는 Sparkfun Electret breakout이 3.3V에서 작동하기에 아두이노 DUE를 사용했습니다. 그래서 만약 다른 5V 보드를 사용하신다면, 점퍼선을 3.3V 핀과 AREF 핀 사이에 추가해주세요.

아두이노 DUE의 경우 AREF핀에 점퍼선을 사용할 필요는 없습니다.


4. 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#include <BVSP.h>
#include <BVSMic.h>
#include <BVSSpeaker.h>
#include <DAC.h>
 
// Defines the Arduino pin that will be used to capture audio 
#define BVSM_AUDIO_INPUT 7
 
// Defines the LED pins
#define RED_LED_PIN 6
#define YELLOW_LED_PIN 9
#define GREEN_LED_PIN 10
 
// Defines the constants that will be passed as parameters to 
// the BVSP.begin function
const unsigned long STATUS_REQUEST_TIMEOUT = 3000;
const unsigned long STATUS_REQUEST_INTERVAL = 4000;
 
// Defines the size of the mic audio buffer 
const int MIC_BUFFER_SIZE = 64;
 
// Defines the size of the speaker audio buffer
const int SPEAKER_BUFFER_SIZE = 128;
 
// Defines the size of the receive buffer
const int RECEIVE_BUFFER_SIZE = 2;
 
// Initializes a new global instance of the BVSP class 
BVSP bvsp = BVSP();
 
// Initializes a new global instance of the BVSMic class 
BVSMic bvsm = BVSMic();
 
// Initializes a new global instance of the BVSSpeaker class 
BVSSpeaker bvss = BVSSpeaker();
 
// Creates a buffer that will be used to read recorded samples 
// from the BVSMic class 
byte micBuffer[MIC_BUFFER_SIZE];
 
// Creates a buffer that will be used to write audio samples 
// into the BVSSpeaker class 
byte speakerBuffer[SPEAKER_BUFFER_SIZE];
 
// Creates a buffer that will be used to read the commands sent
// from BitVoicer Server.
// Byte 0 = pin number
// Byte 1 = pin value
byte receiveBuffer[RECEIVE_BUFFER_SIZE];
 
// These variables are used to control when to play
// "LED Notes". These notes will be played along with 
// the song streamed from BitVoicer Server.
bool playLEDNotes = false;
unsigned int playStartTime = 0;
 
void setup() 
{
  // Sets up the pin modes
  pinMode(RED_LED_PIN, OUTPUT);
  pinMode(YELLOW_LED_PIN, OUTPUT);
  pinMode(GREEN_LED_PIN, OUTPUT);
 
  // Sets the initial state of all LEDs
  digitalWrite(RED_LED_PIN, LOW);
  digitalWrite(YELLOW_LED_PIN, LOW);
  digitalWrite(GREEN_LED_PIN, LOW);
  
  // Starts serial communication at 115200 bps 
  Serial.begin(115200); 
  
  // Sets the Arduino serial port that will be used for 
  // communication, how long it will take before a status request 
  // times out and how often status requests should be sent to 
  // BitVoicer Server. 
  bvsp.begin(Serial, STATUS_REQUEST_TIMEOUT, STATUS_REQUEST_INTERVAL);
    
  // Defines the function that will handle the frameReceived 
  // event 
  bvsp.frameReceived = BVSP_frameReceived;
 
  // Sets the function that will handle the modeChanged 
  // event 
  bvsp.modeChanged = BVSP_modeChanged; 
  
  // Sets the function that will handle the streamReceived 
  // event 
  bvsp.streamReceived = BVSP_streamReceived;
  
  // Prepares the BVSMic class timer 
  bvsm.begin();
 
  // Sets the DAC that will be used by the BVSSpeaker class 
  bvss.begin(DAC);
}
 
void loop() 
{
  // Checks if the status request interval has elapsed and if it 
  // has, sends a status request to BitVoicer Server 
  bvsp.keepAlive();
  
  // Checks if there is data available at the serial port buffer 
  // and processes its content according to the specifications 
  // of the BitVoicer Server Protocol 
  bvsp.receive();
 
  // Checks if there is one SRE available. If there is one, 
  // starts recording.
  if (bvsp.isSREAvailable()) 
  {
    // If the BVSMic class is not recording, sets up the audio 
    // input and starts recording 
    if (!bvsm.isRecording)
    {
      bvsm.setAudioInput(BVSM_AUDIO_INPUT, DEFAULT); 
      bvsm.startRecording();
    }
 
    // Checks if the BVSMic class has available samples 
    if (bvsm.available)
    {
      // Makes sure the inbound mode is STREAM_MODE before 
      // transmitting the stream
      if (bvsp.inboundMode == FRAMED_MODE)
        bvsp.setInboundMode(STREAM_MODE); 
        
      // Reads the audio samples from the BVSMic class
      int bytesRead = bvsm.read(micBuffer, MIC_BUFFER_SIZE);
      
      // Sends the audio stream to BitVoicer Server
      bvsp.sendStream(micBuffer, bytesRead);
    }
  }
  else
  {
    // No SRE is available. If the BVSMic class is recording, 
    // stops it.
    if (bvsm.isRecording)
      bvsm.stopRecording();
  }
 
  // Plays all audio samples available in the BVSSpeaker class
  // internal buffer. These samples are written in the 
  // BVSP_streamReceived event handler. If no samples are 
  // available in the internal buffer, nothing is played.
  bvss.play();
 
  // If playLEDNotes has been set to true, 
  // plays the "LED notes" along with the music.
  if (playLEDNotes)
    playNextLEDNote();
}
 
// Handles the frameReceived event 
void BVSP_frameReceived(byte dataType, int payloadSize) 
{
  // Checks if the received frame contains binary data
  // 0x07 = Binary data (byte array)
  if (dataType == DATA_TYPE_BINARY)
  {
    // If 2 bytes were received, process the command.
    if (bvsp.getReceivedBytes(receiveBuffer, RECEIVE_BUFFER_SIZE) == 
      RECEIVE_BUFFER_SIZE)
    {
      analogWrite(receiveBuffer[0], receiveBuffer[1]);
    }
  }
  // Checks if the received frame contains byte data type
  // 0x01 = Byte data type
  else if (dataType == DATA_TYPE_BYTE)
  {   
    // If the received byte value is 255, sets playLEDNotes
    // and marks the current time.
    if (bvsp.getReceivedByte() == 255)
    {
      playLEDNotes = true;
      playStartTime = millis();
    }
  }
}
 
// Handles the modeChanged event 
void BVSP_modeChanged() 
  // If the outboundMode (Server --> Device) has turned to 
  // FRAMED_MODE, no audio stream is supposed to be received. 
  // Tells the BVSSpeaker class to finish playing when its 
  // internal buffer become empty. 
  if (bvsp.outboundMode == FRAMED_MODE)
    bvss.finishPlaying();
 
// Handles the streamReceived event 
void BVSP_streamReceived(int size) 
  // Gets the received stream from the BVSP class 
  int bytesRead = bvsp.getReceivedStream(speakerBuffer, 
    SPEAKER_BUFFER_SIZE); 
    
  // Enqueues the received stream to play
  bvss.enqueue(speakerBuffer, bytesRead);
}
 
// Lights up the appropriate LED based on the time 
// the command to start playing LED notes was received.
// The timings used here are syncronized with the music.
void playNextLEDNote()
{
  // Gets the elapsed time between playStartTime and the 
  // current time.
  unsigned long elapsed = millis() - playStartTime;
 
  // Turns off all LEDs
  allLEDsOff();
 
  // The last note has been played.
  // Turns off the last LED and stops playing LED notes.
  if (elapsed >= 11500)
  {
    analogWrite(RED_LED_PIN, 0);
    playLEDNotes = false;
  }
  else if (elapsed >= 9900)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 9370)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 8900)
    analogWrite(YELLOW_LED_PIN, 255); // D note
  else if (elapsed >= 8610)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 8230)
    analogWrite(YELLOW_LED_PIN, 255); // D note
  else if (elapsed >= 7970)
    analogWrite(YELLOW_LED_PIN, 255); // D note
  else if (elapsed >= 7470)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 6760)
    analogWrite(GREEN_LED_PIN, 255); // E note
  else if (elapsed >= 6350)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 5880)
    analogWrite(YELLOW_LED_PIN, 255); // D note
  else if (elapsed >= 5560)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 5180)
    analogWrite(YELLOW_LED_PIN, 255); // D note
  else if (elapsed >= 4890)
    analogWrite(YELLOW_LED_PIN, 255); // D note
  else if (elapsed >= 4420)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 3810)
    analogWrite(GREEN_LED_PIN, 255); // E note
  else if (elapsed >= 3420)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 2930)
    analogWrite(YELLOW_LED_PIN, 255); // D note
  else if (elapsed >= 2560)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 2200)
    analogWrite(YELLOW_LED_PIN, 255); // D note
  else if (elapsed >= 1930)
    analogWrite(YELLOW_LED_PIN, 255); // D note
  else if (elapsed >= 1470)
    analogWrite(RED_LED_PIN, 255); // C note
  else if (elapsed >= 1000)
    analogWrite(GREEN_LED_PIN, 255); // E note
}
 
// Turns off all LEDs.
void allLEDsOff()
{
  analogWrite(RED_LED_PIN, 0);
  analogWrite(YELLOW_LED_PIN, 0);
  analogWrite(GREEN_LED_PIN, 0);
}
cs


1) 라이브러리와 변수

처음 4개의 라이브러리는 BitSophia 사이트에서 받을 수 있습니다. 이를 다운 받아서 아두이노 라이브러리에 추가해 주세요.


2) Setup 함수

핀모드 셋업하고 초기화를 담당합니다. serial communication을 초기화 하고 BVSP, BVSMic, BVSSpeaker 클래스들을 초기화 합니다. 또한 BVSP클래스의 frameReceived, modeChanged, streamReceived 이벤트를 위해 'event handler'를 설정합니다.


3) loop 함수

이 함수에서는 5가지 기능들을 수행합니다. 

- 서버에 상태 정보를 요청함 (keepAlive() 함수)

- 서버가 데이터를 받았는지를 체크하고 받은 데이터를 처리함 (receive() 함수)

- audio stream의 녹음과 전송을 제어함 (isSREAvailable(), startRecording(), stopRecording() and sendStream() 함수)

- BVSSpeaker 클래스에 순서대로 저장된 샘플들을 실햄함 (play() 함수)

- playLEDNotes명령이 도착한 뒤 LED들을 깜빡이고 playNextLEDNote() 함수를 호출한다 (playNextLEDNote() 함수)


4) BVSP_frameReceived 함수

이 함수는 매 순간 receive()함수를 호출해서 하나의 완전한 프레임을 받았는지를 확인한다.

하나의 명령을 BitVoicer Server로 보냈을 때, LED를 제어 하는 명령어들은 2바이트의 사이즈를 가진다. 첫 바이트는 핀을 가리키고 두번째 바이트는 핀에 적절한 값을 전달한다.

analogWrite() 함수를 통해 핀에 적절한 값들을 보내준다.

또한 playLEDNotes 명령어들을 받았는지(어떤 바이트 타입인지)를 체크한다. 만약 명령어를 받았다면 playLEDNotes를 true로 설정하고 현재 시각을 표시한다. 이 시각은 LED와 노래의 싱크를 맞추기 위해 playNextLEDNote 함수에서 사용된다.


5) BVSP_ModeChanged 함수

이 함수는 매순간 reveice() 함수를 불러서 모드의 변화를 확인한다 (Server -> Arduino)

BitVoicer Server는 프레임화된 데이터와 오디오 스트림을 아두이노로 전송할 수 있다. 이런 커뮤니케이션이 한 모드에서 다른 모드로 실행되기 전에 BitVoicer Server는 신호를 전송한다. BVSP 클래스는 이 신호를 확인하고 modeChanged 이벤트를 실행해준다. BVSP_modeChanged 함수에서, 만약 스트림 모드에서 프레임화 모드로 실행되는 것이 감시되었다면, 오디오가 끝나가는 것을 알 수 있다. 그래서 BVSSpeaker에 정보를 보내 오디오 샘플이 실행되는 것을 멈출 수 있다.


6) BVSP_streamReceived 함수

이 함수는 매 순간 receive() 함수를 불러 오디오 샘플을 받는지 확인한다. 그래서 샘플을 가져와 BVSSpeaker 클래스의 큐에 등록하고 play()함수를 통해 그것들을 실행한다.


7) PlaynextLEDNote 함수

이 함수는 BVSP_frameReceive 함수가 playLEDNotes 명령을 인지하면 실행된다. 이 함수는 BitVoicer Server로부터 전송된 오디오와 LED들의 싱크를 맞추고 제어한다. 오디오에 따라 LED의 싱크를 맞추고 정확한 타이밍을 알기 위해서는 Sonic Visualizer 라는 것을 사용했다. 이 프리소프트웨어는 오디오의 파동을 보게 해줘 피아노 키가 눌렸을 때, 말을 할 수 있게 해준다.


5. BitVoicer Server 솔루션 오브젝트들 입력하기

Arduino와 BItVoicer Server를 함께 구동하기 위해서는 BitVoicer Server를 설치해야 한다. BitVoicer server는 4개의 주요 솔루션 오브젝트들을 가지고 있다(Locations, Devices, Binary Data 그리고 Voice Schemas)


1) Location

Location은 디바이스가 설치되는 실제 장소를 나타낸다. 이 프로젝트에서는 Home이라는 장소를 지정하였다.


2) Devices

디바이스는 BitVoicer Server의 Client들을 말한다. 이 프로젝트는 아두이노 DUE라 불리면서 의사소통 세팅들을 가진 혼합된 디바이스를 만들었다. 아두이노 DUE는 BitVoicer Server에서 스트림되는 모든 오디오 샘플을 저장할만큼의 저장공간을 가지고 있다. 만약 Bandwidth에 제한이 없이 사용하고 싶다면, 오디오를 저장하기 위해 더 큰 버퍼가 필요합니다. 만약에 버퍼가 여러가지 이유로 오버플로우가 발생한다면, 이 communication setting을 참조해서 설정을 바꿔준다.


3) Binary Data

이진 데이터는 Bitvoicer Server가 client 장치에게 전송하는 명령어의 한 타입이다. 그것들은 명령어들을 연결할 수 있는 Byte array들이다. BitVoicer Server가 명령어와 관계된 음성을 인식할 때, 그것은 Target 장치에 byte array형태로 전송된다. 이 프로젝트에서는 1핀당 1개의 Binary Data를 만들었고, 그것들은 ArduinoDUEGreenLedOn, ArduinoDUEGreenLedoff 등으로 설정해주었다. 총 18개의 Binary Data를 사용했고, 아래의 VoiceSchema.sof 파일을 다운 받아서 오브젝트에 추가하시길 추천합니다.


4) Voice Schema

음성 스키마는 어떤 문장이 인식되어야 하고 어떤 명령어들이 실행되어야 하는지를 결정합니다. 각각의 문장에 있어서 작성자가 필요한 만큼, 또 실행되어지길 원하는 순서대로 많은 명령들을 결정합니다. 또한 명령들 간의 delay를 설정할 수도 있습니다.


링크를 통해서 모든 솔루션들을 프로젝트에 포함시킬 수 있습니다.


- Device.sof

- VoiceScheman.sof


6. 완성





이와 같이 완성할 수 있습니다.


만약 이해가 안되신다면 아두이노와 BitVoicer Server를 활용한 음성인식(1) 를 확인하시고 이 프로젝트를 진행하시기 바랍니다.


더 많은 정보를 원하신다면 원문을 확인하셔도 좋습니다.

댓글 0

아두이노를 활용한 음성인식

아두이노와 BitVoicer Server를 이용한 음성인식(2)

by 강종구