この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
CX事業本部の平内(SIN)です。
今回は、RaspberryPIでAmazon Lex(以下、Lex)のクライアントを作ってみました。
最初に動作している様子です。スイッチを押している間だけマイクの音声を録音し、スイッチを離したタイミングで、Lexに送信しています。
テストに使用したボットは、以下で作成したものです。
2 スイッチ
マイクのON/OFFは、スイッチで制御しています。
ブレッドボード上のスイッチは、GPIO14に接続され、1Kでプルアップされています。スイッチを押すとGNDに落ちます。
スイッチを制御しているコードです。
GPIO14を入力モードにして、HEIGH/LOWを検出しています。チャタリングを考慮して、変化が少しの間、継続することを確認して、スイッチの変化としています。
GPIO15は、GPIO14のHEIGH/LOWをLEDで表示しているものです。
# -*- coding: utf-8 -*-
"""
スイッチを制御するクラス
"""
import RPi.GPIO as GPIO
import time
# スイッチ制御クラス
class Switch:
def __init__(self, on, off):
GPIO.setmode(GPIO.BCM)
GPIO.setup(15,GPIO.OUT)
GPIO.setup(14,GPIO.IN)
self.__on = on
self.__off = off
self.__loop()
def __loop(self):
status = 'off'
count = 9999
while True:
if(GPIO.input(14) == GPIO.HIGH):
# OFF状態をLEDで表示する
GPIO.output(15,GPIO.LOW)
if(status == 'on'):
status = 'off'
count = 0
else:
count += 1
else:
# ON状態をLEDで表示する
GPIO.output(15,GPIO.HIGH)
if(status == 'off'):
status = 'on'
count = 0
else:
count += 1
if(count == 5): # 状態変が0.25sec継続した場合、スイッチ変化のコールバックを起動する
if(status == 'on'):
self.__on()
else:
self.__off()
time.sleep(0.05)
3 マイク
マイクは、USBに接続するタイプのサンワサプライ USBマイクロホン 単一指向性 直挿し型 MM-MCU02BKを使用しています。
OSからは、以下のように認識されています。
$ lsusb
Bus 001 Device 004: ID 0d8c:0016 C-Media Electronics, Inc.
$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: Microphone [USB Microphone], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0
以下のコードでマイクの諸元を取得すると、チャンネル数は、1で、サンプリングレートが、44.1KHzであることが分かります。
import pyaudio
p = pyaudio.PyAudio()
info = p.get_device_info_by_index(0)
print(info)
{'index': 0, 'structVersion': 2, 'name': 'USB Microphone: Audio (hw:1,0)', 'hostApi': 0, 'maxInputChannels': 1, 'maxOutputChannels': 0, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': -1.0, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': -1.0, 'defaultSampleRate': 44100.0}
マイクにアクセスしているコードです。start()で録音を開始し、stop()で、wavファイルに落とします。 なお、Lexへは、16KHzで送信する必要があるので、stopのタイミングで、ffmpegを使用してサンプリングレートを変換しています。
# -*- coding: utf-8 -*-
"""
マイクから録音するクラス
"""
import pyaudio
import wave
import os
import threading
import time
class Record():
def __init__(self, device_index,channels, sample_rate, outfile):
self.__CHANNELS = channels
self.__SAMPLE_RATE = sample_rate # サンプルレート
self.__CHUNK = int(self.__SAMPLE_RATE/4) # 0.25秒ごとに取得する
self.__FORMAT = pyaudio.paInt16
self.__DEVICE_INDEX = device_index
self.__OUTFILE = outfile
self.__p = pyaudio.PyAudio()
def start(self):
self.__frames = []
self.__stream = self.__p.open(format = self.__FORMAT,
channels = self.__CHANNELS,
rate = self.__SAMPLE_RATE,
input = True,
input_device_index = self.__DEVICE_INDEX,
frames_per_buffer = self.__CHUNK)
self.__recording = True
self.__thread = threading.Thread(target=self.__loop)
self.__thread.start()
def __loop(self):
while(self.__recording == True):
data = self.__stream.read(self.__CHUNK)
self.__frames.append(data)
print ("data size:{}".format(len(data)))
def stop(self):
self.__recording = False
data = b''.join(self.__frames)
time.sleep(0.1)
self.__thread.join()
self.__stream.stop_stream()
self.__stream.close()
# save
file_name = "./tmp.wav"
wf = wave.open(file_name, 'wb')
wf.setnchannels(self.__CHANNELS)
wf.setsampwidth(self.__p.get_sample_size(self.__FORMAT))
wf.setframerate(self.__SAMPLE_RATE)
wf.writeframes(data)
wf.close()
# 44.1KHz -> 16KHzへの変換
os.system("ffmpeg -i ./tmp.wav -ar 16000 {}".format(self.__OUTFILE))
os.system("rm ./tmp.wav")
def __del__(self):
self.__p.terminate()
4 再生
再生は、simpleaudioを使用させて頂きました。
$ sudo apt-get install -y python3-pip libasound2-dev
$ pip3 install simpleaudio
# -*- coding: utf-8 -*-
"""
WAVファルを再生するクラス
"""
import simpleaudio as sa
class Audio:
def play(self, wav_file):
print("audio start")
wave_obj = sa.WaveObject.from_wave_file(wav_file)
play_obj = wave_obj.play()
play_obj.wait_done()
print("audio finish.")
スピーカーの音量は、以下のコマンドで調整しています。
$ alsamixer
5 Lex
Lexへのアクセスは、boto3で行っています。
LexRuntimeService
lex-runtime の post_content() を使用すると、音声ファイルを送信することができ、accept で 'audio/pcm' を指定することで、レスポンスもオディオデータで受け取ることができます。
post_content(**kwargs)
# -*- coding: utf-8 -*-
"""
Amazon Lexにアクセスするクラス
"""
import boto3
from cognito import createSession # CognitoのPoolIdでSessionを取得する
import os
import wave
class LexBot:
def __init__(self, pool_id, region):
session = createSession(pool_id, region)
self.__client = session.client('lex-runtime', region_name=region)
self.__alias = "$LATEST"
self.__username = "raspi_client"
self.__bot_name = "OrderFlowers_jaJP"
def post_content(self, audio_file):
f = open(audio_file, 'rb')
response= self.__client.post_content(
botName = self.__bot_name,
botAlias = self.__alias,
userId = self.__username,
inputStream=f,
accept='audio/pcm',
contentType="audio/l16; rate=16000; channels=1")
print(response)
audio_stream = response['audioStream'].read()
response['audioStream'].close()
f = wave.open(audio_file, 'wb')
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(16000)
f.setnframes(0)
f.writeframesraw(audio_stream)
f.close()
6 Cognito
Lexへアクセスする権限は、CognitoのプールIDに最小限のパーミッションを設定して使用しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"Lex:PostContent"
],
"Resource": "arn:aws:lex:ap-northeast-1:*:bot:OrderFlowers_jaJP:$LATEST"
}
]
}
# -*- coding: utf-8 -*-
"""
CognitoのPoolIdでSessionを取得する
"""
import boto3
from boto3.session import Session
def createSession(poolId, region):
client = boto3.client('cognito-identity', region)
resp = client.get_id(IdentityPoolId = poolId)
identityId = client.get_credentials_for_identity(IdentityId=resp['IdentityId'])
secretKey = identityId['Credentials']['SecretKey']
accessKey = identityId['Credentials']['AccessKeyId']
token = identityId['Credentials']['SessionToken']
return boto3.Session(aws_access_key_id = accessKey, aws_secret_access_key = secretKey, aws_session_token = token)
7 最後に
マイクの入力レベルで、発話の終わりを検出するようにすれば、いちいちスイッチを押さなくていいのですが・・・ちょっと安定させるのが難しかったので、とりあえずスイッチでやってみました。この後、スイッチ無しもやってみたいと考えています。
RasPi上の全てのコードは、下記に置きました。
https://github.com/furuya02/LexClient_made_with_RasPI
8 参考にさせて頂いたリンク
Github amazon-pollexy/lex/
[Amazon Lex] HTML+JavaScriptでLexクライアントを作ってみました(音声対応)