Raspberry piでAmazon Echo(AVS)を動かしてみる未遂 - Qiita
Raspberry piでAmazon Echo(AVS)を動かしてみる未遂 - Qiita... |
オレオレAmazonEchoにやらせたいこと
とにかくラジオが好きだ。よく聞いている。朝はTBSのスタンバイを聞くことが最近の日課だ。
6:42から始まる歌のない歌謡曲は最高だ。
しかし寝起きにそのあたりに転がっているiPhoneを取り出し、radikoアプリを起動し、TBSを選択し、Airplayを設定するのはとてもめんどくさい。
となればラズパイをradikoサーバにでもしてcronで自動起動させればよいじゃないかと思うのだが、自動は嫌だ。
と言う事で話しかければradikoが鳴ることを最終目標にオレオレAmazonEchoを作ることにした。
環境
- Raspberry pi (Raspbian)
- git, python, node.js
- USBマイク
raspberry piでサウンド関係はちょっとめんどくさいので事前に調べて設定していただきたい。
ここがわかりやすいと思う。
Raspbian(Jessie) のサウンド設定 - Qiita
Raspbian(Jessie) のサウンド設定 - Qiita... |
音声認識
LINEBot SDK+Microsoft Speech API+Translate API でバリニーズと会話できるようにしてみる - Qiita
LINEBot SDK+Microsoft Speech API+Translate API でバリニーズと会話できるようにしてみる - Qiita... |
Juliusのインストール
Juliusをgithubからインストールし、ラズパイで音声認識できるまでもっていく。
# 依存ライブラリインストール
$ sudo apt-get install libasound2-dev
# Juliusインストール
$ git clone https://github.com/julius-speech/julius.git
$ cd julius
$ ./configure
$ make
$ sudo make install
ディクテーションキットとディクショナリーキットのインストール
$ wget -O dictation-kit-v4.3.1-linux.tgz
'http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Fjulius%2F60416%2Fdictation-kit-v4.3.1-linux.tgz'
$ wget -O grammar-kit-v4.1.tar.gz
'http://sourceforge.jp/frs/redir.php?m=osdn&f=%2Fjulius%2F51159%2Fgrammar-kit-v4.1.tar.gz'
そのまま使ってもよいが自分で辞書を作成するとそこに記載の言葉しか認識しないという仕様なので、今回の要件にはちょうど良いと思ったので辞書を作ることにした。
辞書の作成
$ vi /home/pi/julius_sample/sample.yomi
$ cat /home/pi/julius_sample/sample.yomi
シシマル ししまる
NHK えぬえちけー
JFM じぇーうぇーぶ
TBS てぃーびーえす
INT いんたーえふえむ
消して けして
辞書の変換
$ cd ~/julius-master
$ iconv -f utf8 -t eucjp ~/sample.yomi | ./yomi2voca.pl
> ~/julius-kits/dictation-kit-v4.3.1-linux/sample.dic
julius conf作成
$ vi /home/pi/julius_sample/sample.jconf
$ cat /home/pi/julius_sample/sample.jconf
-w sample.dic
-v model/lang_m/bccwj.60k.bingram
-h model/phone_m/jnas-tri-3k16-gid.binhmm
-hlist model/phone_m/logicalTri
-n 5
-output 1
-input mic
-rejectshort 500
-charconv euc-jp utf8
-lv 1000
juliusをモジュールモードで起動
$ julius -C /home/pi/julius-kits/dictation-kit-v4.4/sample.jconf -module
radiko
無事辞書にある言葉が認識したらraspberry piでradiko再生を行える環境を整える。
今回はこちらを利用させて頂いた。
Raspberry Piでradikoの再生、録音 - Muchuu
Raspberry Piでradikoの再生、録音 - Muchuu... |
プログラム
juliusが音声認識を行ってくれるが、それを受けるプログラムを作ることにする。
PHPで書きたかったが情報があまりなかったのでpythonを利用することにした。
ただし僕はpythonを書けないので何人かの方のサンプルを組合させていただいた。
(ちなみにシシマルとは昔飼ってた犬の名前。日常生活もしくはラジオ・テレビから発せられることはあまりないと思い、誤認識はしないと思ったから)
流れ
- pythonからjuliusを起動させる。
- シシマルという言葉を認識すると待機状態に入る(Hey SiriやAlexaのようなこと)
- 待機状態で辞書に登録された言葉を認識すると各々の動作を行う(今回はラジオを再生する)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import socket
import requests
import re
import subprocess
import shlex
import time
import threading
julius_path = '/home/pi/src/julius-master/julius/julius'
jconf_path = '/home/pi/julius-kits/dictation-kit-v4.4/sample.jconf'
julius = None
julius_socket = None
def invoke_julius():
print 'INFO : invoke julius'
args = julius_path + ' -C ' + jconf_path + ' -module '
p = subprocess.Popen(
shlex.split(args),
stdin=None,
stdout=None,
stderr=None
)
print 'INFO : invoke julius complete.'
print 'INFO : wait 2 seconds.'
time.sleep(3.0)
print 'INFO : invoke julius complete'
return p
def kill_julius(julius):
print 'INFO : terminate julius'
julius.kill()
while julius.poll() is None:
print 'INFO : wait for 0.1 sec julius\' termination'
time.sleep(0.1)
print 'INFO : terminate julius complete'
def get_OS_PID(process):
psef = 'ps -ef | grep ' + process + ' | grep -ve grep -vie python |head -1|awk \'{print($2)}\''
if sys.version_info.major == 3:
PID = str(subprocess.check_output(psef, shell=True), encoding='utf-8').rstrip ()
else:
PID = subprocess.check_output(psef, shell=True).rstrip ()
return PID
def create_socket():
print 'INFO : create a socket to connect julius'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 10500))
print 'INFO : create a socket to connect julius complete'
return s
def delete_socket(s):
print 'INFO : delete a socket'
s.close()
print 'INFO : delete a socket complete'
return True
def invoke_julius_set():
julius = invoke_julius()
julius_socket = create_socket()
sf = julius_socket.makefile('rb')
return (julius, julius_socket, sf)
STATUS_WAITING = 0
STATUS_READY = 1
STATUS_EXECUTING = 2
STATUS_FINISH = 3
status = STATUS_WAITING
def status_change(new_status):
global status
if status == STATUS_WAITING:
if new_status == STATUS_READY:
status = new_status
print "Status is READY."
threading.Timer(15, status_change, args=[STATUS_WAITING]).start()
elif status == STATUS_READY:
if new_status == STATUS_WAITING:
status = new_status
print "Status is WAITING."
elif new_status == STATUS_EXECUTING:
status = new_status
print "Status is EXECUTING."
threading.Timer(30, status_change, args=[STATUS_FINISH]).start()
elif status == STATUS_EXECUTING:
if new_status == STATUS_WAITING:
print "Command is executed now, so I do NOT change status."
if new_status == STATUS_FINISH:
status = STATUS_WAITING
print "Status is WAITING."
def play_mp3(sound_file):
args = "mplayer --quiet " + sound_file
print args
p = subprocess.Popen(
shlex.split(args),
stdin=None,
stdout=None,
stderr=None
)
time.sleep(0.5)
return True
def call_jsay(text):
cmd = "/usr/local/bin/jsay %s" % text
subprocess.call(cmd, shell=True)
def radio_on(channel):
cmd ="/home/pi/radiko/radiko.sh -p %s >/dev/null 2>&1 &" %channel
subprocess.call(cmd, shell=True)
def nhk_on(channel):
cmd ="/home/pi/radiko/nhk.sh %s >/dev/null 2>&1 &" %channel
subprocess.call(cmd, shell=True)
def radio_off():
# off
cmd ="killall mplayer"
subprocess.call(cmd, shell=True)
def main():
global julius
global julius_socket
julius, julius_socket, sf = invoke_julius_set()
while True:
if julius.poll() is not None: # means , julius dead
print 'ERROR : julius dead'
delete_socket(julius_socket)
julius, julius_socket, sf = invoke_julius_set()
else:
line = sf.readline().decode('utf-8')
if line.find('WHYPO') != -1:
if line.find(u'シシマル') != -1:
if status == STATUS_WAITING:
print "Julius is ready to your command."
status_change(STATUS_READY)
play_mp3("/home/pi/julius_sample/sound/ok.mp3")
print "Shishimaru is wating."
elif line.find(u'TBS') != -1:
if status == STATUS_READY:
call_jsay('ティービーエスを流すよ')
time.sleep(2.0)
status_change(STATUS_EXECUTING)
radio_on('TBS')
print "Call TBS"
elif line.find(u'INT') != -1:
if status == STATUS_READY:
call_jsay('インターエフエムを流すよ')
time.sleep(2.0)
status_change(STATUS_EXECUTING)
radio_on('INT')
print "Call INTERFM"
elif line.find(u'JFM') != -1:
if status == STATUS_READY:
call_jsay('ジェーウェーブを流すよ')
time.sleep(2.0)
status_change(STATUS_EXECUTING)
radio_on('FMJ')
print "Call JWAVE"
elif line.find(u'NHK') != -1:
if status == STATUS_READY:
call_jsay('エヌエチケーを流すよ')
time.sleep(2.0)
status_change(STATUS_EXECUTING)
nhk_on('fm')
print "Call NHK"
elif line.find(u'消して') != -1:
if status == STATUS_READY:
call_jsay('ラジオを消すよ')
time.sleep(2.0)
status_change(STATUS_EXECUTING)
radio_off()
print "Call Radio Off"
print 'WARN : while loop breaked'
print 'INFO : exit'
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print 'Interrupted. Exit sequence start..'
kill_julius(julius)
delete_socket(julius_socket)
print 'INFO : Exit sequence done.'
sys.exit(0)()
動作確認
$ python sample_listner.py
再生
— momochan_qiita (@momochan_qiita) 2017年1月13日
思った通りに動作しているようだ。
シシマルを認識したらポーンという音を鳴らすことにした。わかりやすいから。
停止
— momochan_qiita (@momochan_qiita) 2017年1月13日
停止には言葉ではなく以前ハックしたAmazon Dash Buttonを利用している。
理由はスピーカーの横にマイクを置いているため言葉を認識してくれなかったからだ。
念のため停止するスクリプト。
Amazon Dash Buttonが押されたら、killall mplayerしてるだけ。
var dash_button = require('node-dash-button');
var dash = dash_button("xx:xx:xx:xx:xx:xx", null, null, 'all'); //address from step above
dash.on("detected", function (){
var exec = require('child_process').exec
, cmd;
cmd = 'killall mplayer';
killmplayer = function() {
return exec(cmd, {timeout: 1000},
function(error, stdout, stderr) {
console.log('stdout: '+(stdout||'none'));
console.log('stderr: '+(stderr||'none'));
if(error !== null) {
console.log('exec error: '+error);
}
}
)
};
認識していないパターン
— momochan_qiita (@momochan_qiita) 2017年1月13日
以上で無事オレオレAmazonEchoが作れた。
あとはこれを昨日覚えたsupervisorでデーモン化すれば完了。
ラズパイの使い方としては満足なものができた。
追記
ラジオを聴いているときの誤認識が激しい。
誤認識するぐらいなら良いのだが二重にラジオが再生されてしまう。
ということで後日改良したいと思う。