[Amazon Lex] RaspberryPIでLexクライアントを作ってみました
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クライアントを作ってみました(音声対応)