#経緯
音声認識が流行ってますし、折角RaspberryPiあるし。
以下、私の環境で躓いた、気になった点のみ記載しておきます。
(絶賛Try & Error中なので、生暖かい目で見守って下さい。)
一応IBM WATSON(のText to speech)とも繋げました。後半中盤にあります。(だんだん長くなってきて見づらいのでその内分割しますが、暫くはまだ漸次的に書きます。)
#結論(2016/05/19追記)
面倒くさい事が好きな人は良いんですが、殆どの人には、思っていたのと違う感が有ると思いますので、Julius動かしてみるのは辞めたほうが良いんではないかと思ってます。
それでも、WATSONとかなんかやりたいんだYo!という人は以下読んでみてください。
(多分、手作りするより、MacのSiriとか、Echoとか、Googleのやつとか、そういう製品がもうすぐ出回るので待ったほうが良いかと。)
- 先生:[cubic9.com 日本語音声認識][sensei]
- HW:RaspberryPi Type B
- OS:Raspbian Jessie (download)
- DAC:iBUFFALO USBオーディオ変換ケーブル
- Mic:サンワサプライ フラット型PCマイク MM-MC23
- Julius:
- dictation-kit-v4.3.1-linux.tgz
- julius-4.3.1.tar.gz
- grammar-kit-v4.1.tar.gz
#気付き
##最新版のjuliusだとファイル構成が変わってて先生の例が使えない
私の先生は、make後のテストとして以下のようなコマンドでテストされてました。
~/julius-4.2.3/julius/julius -C ~/julius-kits/dictation-kit-v4.2.3/fast.jconf -charconv EUC-JP UTF-8
ただ、最新バージョンだと構造が異なるようで動かず。(もちろんファイルパスは変えてますよ。)
ですので、、
~/julius-4.3.1/julius/julius -C ~/julius-kits/grammar-kit-v4.1/testmic.jconf -charconv EUC-JP UTF-8
でとりあえず動かしてみました。その場合は辞書がフルーツなので、ほとんど「ぶどう です」としか認識しませんでしたが(笑)
##/etc/modprobe.d/alsa-base.confは作れるし、効く
なので、先生の例の通り、普通に無ければ作っちゃってリブートすればOKです。
こんな感じ。
# This sets the index value of the cards but doesn't reorder.
options snd_usb_audio index=0
options snd_bcm2835 index=1
# Does the reordering.
options snd slots=snd-usb-audio,snd-bcm2835
ただ、このせいなのか、これ以降alsamixerでF4をおしてマイクの設定イジろうとしても、alsamixerが強制終了しちゃいます。詳しくは調べてないですが。F5は効くので、結果的にはそれでOK牧場。
##途中にバスパワーのHUB挟むとやっぱりNG
まぁ、これは色んな所に書いてありますが、バスパワーのHUBにDAC挿したらノイズがひどすぎてやってられないので、
- セルフパワーのHubにぶら下げる
- Raspiへの給電を2Aとかの太いものにしておいて、DAC直刺し
のどちらかが良いでしょうね。綺麗な電源って大事ですね。
##マイクのパワーは100%にしても全然よさそう
我が家の環境では、テレビの後ろにマイク配置して2m程離れた場所で普通に話しても認識してくれそうです。
まだあんまり色々試したわけでは無いので、エラー率がどの程度かは今後の話ですが。
#以上
また何か気がついたら追記します。
#追記:2016/03/12
なんかjuliusってあんまし使えないんじゃないか疑惑が浮上中。
ということで、早速浮気でIBM watsonのSpeech to TextやGoogle Speech APIなんかを考慮してみるも、WATSONは月1,000分迄は無料でその枠に収めるとなると1日30分ちょい。。Googleだと50回/日か。
そもそもAPI呼び出すのに例えばなんかボタンとか押すのとか嫌だから音声によるトリガーはローカルで実装しなきゃならんとなると、、とりあえずjulius随時起動でWATSONという単語を拾ったらAPI叩くようにするとかそんな事できるのか?
見た資料:
##四の五の言わずに「WATSON」とだけ解らせてみる。
改めて[先生][sensei]を見る。
###単語元帳作成
とりあえず。言われたとおりやりました。
###単語元帳の変換
これも、パス名が変わっている程度で特に問題なく完了。
###設定ファイル(jconf)作成
先生の言うとおりにやってみるも、時代が変わったのか先生が書いてるファイルが無くなってる!しかもなんかちょっとバージョン変わったって感じに見えないのがある。。
仕方ない調べる。
####MonophoneとTriphoneってなんじゃ?
オフィシャル文献
Julius は音響モデルとして,HMM (Hidden Markov Model) を用いることができる.コンテキスト非依存モデル (monophone),およびコンテキスト依存モデルを triphone まで扱える.
なんのこっちゃ。
What is the different between a monophone and a triphone?
Monophone: The pronunciation of a word can be given as a series symbols that correspond to the individual units of sound that make up a word. These are called 'phonemes' or 'phones'. A monophone refers to a single phone.
Triphone: A triphone is simply a group of 3 phones in the form "L-X+R" - where the "L" phone (i.e. the left-hand phone) precedes "X" phone and the "R" phone (i.e. the right-hand phone) follows it.
Below is an example of the conversion of a monophone declaration of the word "TRANSLATE" to a triphone declaration (the first line shows the "monophone" declaration, and the second line shows the "triphone" declaration):
TRANSLATE [TRANSLATE] t r @ n s l e t
TRANSLATE [TRANSLATE] t+r t-r+@ r-@+n @-n+s n-s+l s-l+e l-e+t e-t
つまり、monophoneは音を単語の塊として認識するためのもので、triphoneはある音を「前の音 - 今の音 + 次の音」というような3つ塊で表現/認識するためのもの、ってこと?(前と次ってのがつまりコンテキスト?)
そういえば先日のニューラルネットワーク勉強家の時もなんか似たような構造で解析かけるっぽい事を話してたような気がするので、文法とか色々見るときには便利なんだろうか。
仮にそうだとすると、今回「ワトソン起きろ」とかその程度の決め打ちワードさえわかってくれれば良いという前提ならば、monophoneで良さそうな気がする。
ということで、先に進んで見る。
ここまでを踏まえると
-w sushi.dic
-v model/lang_m/web.60k.htkdic
-h model/phone_m/hmmdefs_ptm_gid.binhmm
-hlist model/phone_m/logicalTri
-n 5
-output 1
-input mic
-zmeanframe
-rejectshort 800
-charconv EUC-JP UTF-8
-w original_words.dic
-v model/lang_m/bccwj.60k.htkdic
-h model/phone_m/jnas-mono-16mix-gid.binhmm
-hlist model/phone_m/logicalTri
-n 5
-output 1
-input mic
-zmeanframe
-rejectshort 800
-charconv EUC-JP UTF-8
となる。-v
はbingram
形式のファイルは1個しか無いのでそれにしました。-h
がさっきの話で、monophoneでとりあえず行ってみよう。
あとは大丈夫そう。(全部の意味は意味調べてないけどまぁ先生のTTPで。)
作成したファイルは~/julius-kits/dictation-kit-v4.3.1-linux/org.jconf
とする。
早速実行してみたら失敗した。
$ cd ~/julius-kits/dictation-kit-v4.3.1-linux
$ julius -C org.jconf
...
中略
...
Error: rdhmmlist: line 21427: physical HMM "hy+a" not found
Error: rdhmmlist: line 21428: physical HMM "r+e" not found
Error: rdhmmlist: line 21429: physical HMM "g+o" not found
Error: init_phmm: HMMList "model/phone_m/logicalTri" read error
ERROR: m_fusion: failed to initialize AM
ERROR: Error in loading model
意味不明とおもいきや、"g+o" not found
というどこかで見たような記述。これはTriphoneじゃんか。ということは、
-hlist model/phone_m/logicalTri
のlogicalTri
のTri
ってTriphone
のTri
か!
気を取り直して、
-w original_words.dic
-v model/lang_m/bccwj.60k.htkdic
-h model/phone_m/jnas-tri-3k16-gid.binhmm
-hlist model/phone_m/logicalTri
-n 5
-output 1
-input mic
-zmeanframe
-rejectshort 800
-charconv EUC-JP UTF-8
で再実行。
$ julius -C org.jconf
...
中略
...
### read waveform input
Stat: adin_oss: device name = /dev/dsp (application default)
Stat: adin_oss: sampling rate = 16000Hz
Stat: adin_oss: going to set latency to 50 msec
Stat: adin_oss: audio I/O Latency = 32 msec (fragment size = 512 samples)
STAT: AD-in thread created
<<< please speak >>>
おお、キタキタ!
よし、ではいでよ「ワトソン!」
pass1_best: WATSON
pass1_best_wordseq: WATSON
pass1_best_phonemeseq: silB w a t o s o N silE
pass1_best_score: -1964.247070
sentence1: WATSON
wseq1: WATSON
phseq1: silB w a t o s o N silE
cmscore1: 0.442
score1: -1964.247070
おおおおおお。まずは先に進みました。(スコア低そうですが後で考える。)
[先生][sensei]の記載はここまででしたので、お礼を言って先に進みましょう。
##何かしらコマンドを連携させる!
今考えているのは、
- julius起動、随時コマンド待ち
- 「WATSON」の認識が発生したら、juliusを停止してマイクを開放させる
- WATSONへの接続を行う処理(後で作るとし、仮名watson_kicker)を実行
- WATSONへの準備ができたら「私だ、WATSONだ。」とつぶやかせる(それか、LED光らせる。)
- WATSONとお話し。その結果の処理はまたベッケンバウアーとし、後で考える。
- watson_kickerの処理終了時にもう一度julius起動させる(もしくは、何か全然別の監視プロセス動かしておいて、止まっててかつwatson_kickerが動いてなかったら即起動させる?)
- 振り出しに戻る
という感じかしら。果たして動くのかな。パフォーマンスどうなんだろう。
先の先生の方がコマンド連携の赤外線学習リモコンR2-D2という記事を上げてらっしゃいますが、2の箇所の参考になるのか?
というか、どうやってjuliusから結果取ってくるんだ!?と思ったら、
公式ガイドで発見、第10章 モジュールモードなるのが有るわけですね。julius自身がHTTPサーバー的に10500番ポートでお話するみたい。
ということは、HomeAutomationWithVoiceCommand.pyスクリプトはクライアント側だしまんま参考になりそうですね。前言撤回。2はなんとかなる気がする。
まずは、モジュールモードのテスト。
$ julius -C org.jconf -module
...
中略
...
Stat: server-client: socket ready as server
///////////////////////////////
/// Module mode ready
/// waiting client at 10500
///////////////////////////////
よしよし。ちなみにこの時のCPU使用率はほぼ0%みたいなので、常駐させてもよさそうです。
公式ガイドでは、
Julius にはサンプルのクライアント jcontrol が付属している.これはJuliusから送信されたメッセージをそのまま標準出力に出力し, またいくつかの簡単なコマンドを送ることができるツールである.これを使い, 以下の要領でJuliusのモジュール動作を試すことができる.
(1) サーバ:Julius を通常の起動方法に "-module" オプションを追加して起動 % julius -C fast.jconf -module (2) クライアント:jcontrol を以下のように起動 % jcontrol (1)を実行しているホスト名
この状態で音声認識を行うと,結果が jcontrol 側に出力される.また, jcontrol で "pause" と入力して enter と押すと認識中断,"resume"で認識が再開できる.
とあるので試す。
$ ./julius-4.3.1/jcontrol/jcontrol localhost
connecting to localhost:10500...done
> <STARTPROC/>
> <INPUT STATUS="LISTEN" TIME="1457799541"/>
動いた。
改めて。「WATSON!」
> <INPUT STATUS="STARTREC" TIME="1457799963"/>
> <STARTRECOG/>
> <INPUT STATUS="ENDREC" TIME="1457799964"/>
> <ENDRECOG/>
> <INPUTPARAM FRAMES="115" MSEC="1150"/>
> <RECOGOUT>
> <SHYPO RANK="1" SCORE="-2450.027588" GRAM="0">
> <WHYPO WORD="WATSON" CLASSID="WATSON" PHONE="silB w a t o s o N silE" CM="0.765"/>
> </SHYPO>
> </RECOGOUT>
よさそうね。
しかし、中身をよくよく見ていると、
> <INPUT STATUS="STARTREC" TIME="1457800335"/>
> <STARTRECOG/>
> <INPUT STATUS="ENDREC" TIME="1457800336"/>
> <ENDRECOG/>
なるものがあるです。内部的には一瞬録音したりしているのか?
しているならそのファイルを変換してWATSONに送りつけるという手も有る気がするけど、後ほど調べるとする。
気を取り直して、10500番ポートに繋いで結果を取る処理を行うPythonを作ります。
まずは基本の所だけ。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import socket
import requests
import re
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 10500))
sf = s.makefile('')
reWATSON = re.compile(r'WHYPO WORD="WATSON" .* CM="(\d\.\d*)"')
while True:
line = sf.readline().decode('utf-8')
#if line.find('WHYPO') != -1:
tmp = reWATSON.search( line )
if tmp:
print line
if float(tmp.group(1)) > 0.8:
print 'call WATSON'
reWATSONは、正規表現を事前コンパイルしたものです。それを使って、結果のWORDの値がWATSONである事の検出と、そのCM値を取れるようにして、その値が仮に0.8以上だった場合はcall WATSON
と出力する様にしました。
現段階では、一旦、WATSONが検出された時点での入力行を出力してますので0.8以下の場合とそうでない場合の差がわかるはずです。
chmod u+x julius_listener.py
してから、再度juliusをモジュールとして起動。
$ julius -C org.jconf -module
...
中略
...
Stat: server-client: socket ready as server
///////////////////////////////
/// Module mode ready
/// waiting client at 10500
///////////////////////////////
よし。では新たに別のTerminal立ち上げてpython動かしてみましょう。
$ ./julius_listener.py
すると、julius側の窓で
$ julius -C org.jconf -module
...
中略
...
Stat: server-client: socket ready as server
///////////////////////////////
/// Module mode ready
/// waiting client at 10500
///////////////////////////////
/// Stat: server-client: connect from 127.0.0.1
STAT: ###### initialize input device
----------------------- System Information begin ---------------------
...
さらに中略
...
------
### read waveform input
Stat: adin_oss: device name = /dev/dsp (application default)
Stat: adin_oss: sampling rate = 16000Hz
Stat: adin_oss: going to set latency to 50 msec
Stat: adin_oss: audio I/O Latency = 32 msec (fragment size = 512 samples)
STAT: AD-in thread created
よしよし。どうやらつながった様です。
では、改めて、「わとしょん!」とダメなケース、そして「WATSON」とちゃんとしたケースで話しかけてみる。
$ ./julius_listener.py
<WHYPO WORD="WATSON" CLASSID="WATSON" PHONE="silB w a t o s o N silE" CM="0.591"/>
<WHYPO WORD="WATSON" CLASSID="WATSON" PHONE="silB w a t o s o N silE" CM="0.835"/>
call WATSON
おお、2回目のがちゃんと閾値クリア出来てるのでcall WATSON
出てきた。
よしよし。いいぞ!
ということで、
-
julius起動、随時コマンド待ち→ ほとんどOK - ~~「WATSON」の認識が発生したら、~~juliusを停止してマイクを開放させる → 停止を組み込むぞ!
- WATSONへの接続を行う処理(後で作るとし、仮名watson_kicker)を実行
- WATSONへの準備ができたら「私だ、WATSONだ。」とつぶやかせる(それか、LED光らせる。)
- WATSONとお話し。その結果の処理はまたベッケンバウアーとし、後で考える。
- watson_kickerの処理終了時にもう一度julius起動させる(もしくは、何か全然別の監視プロセス動かしておいて、止まっててかつwatson_kickerが動いてなかったら即起動させる?)
- 振り出しに戻る
ガイドによると、DIEというコマンドを送付すればjuliusが強制終了するらしいが、実際にやってみても、落ちる気配が無い。killするしか無いか。
続く。
#追記:2016/03/15
##だんだんPythonスクリプトのお勉強になってきた。
とりあえず、
- juliusを起動して、
- 10500番ポートに接続してjulius結果を拾って、
- 「WATSON」のCM値が80%以上だった時に、
- ダミーでWATSON繋いだ事にするメッセージ出力
- 再度julius起動(ループ)
- (一応、Ctrl-cしたら優しく止める)
という処理をするのが以下です。
まだ肝心のWATSON君とのお話部分は全く未完成ですが、動かしてみたい人はjulius_path
とjconf_path
を適宜置き換えて実行してみてください。
WATSON君を呼ぶcall WATSON
あたりの箇所をカスタマイズすれば普通に使えると思います。juliusを落とさなくていいなら、call WATSON
後のkill_julius
とdelete_socket
部分をコメントアウトしたほうが良いですね。
##サンプルスクリプト
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import sys
import socket
import requests
import re
import subprocess
import shlex
import time
julius_path = '/usr/local/bin/julius'
jconf_path = '/home/pi/julius-kits/dictation-kit-v4.3.1-linux/org.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)
def main():
global julius
global julius_socket
julius, julius_socket, sf = invoke_julius_set()
# ###
# # re definition
# ###
reWATSON = re.compile(r'WHYPO WORD="WATSON" .* CM="(\d\.\d*)"')
while True:
if julius.poll() is not None: # means , julius dead
delete_socket(julius_socket)
julius, julius_socket, sf = invoke_julius_set()
else:
line = sf.readline().decode('utf-8')
print line
tmp = reWATSON.search(line)
if tmp:
# print line
if float(tmp.group(1)) > 0.8:
print 'WARN : DIE julius, call WATSON'
kill_julius(julius)
delete_socket(julius_socket)
print '====================================='
time.sleep(2.0)
print '====================================='
time.sleep(2.0)
print '====================================='
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)
##うーん。。
しかし、juliusの認識率が低い。「WATSON」と近くで言っても中々数字が悪いので、そもそもjuliusでWATSONをKickするのはダメかも。。。まぁでも一通りやってみよう。
続く。(今度こそWATSON君につなげるぞ!)
#追記:2016/03/17
##今日こそWATSONとお話するぞ。
ということで、きっと誰かが良いもの作ってるはず。。。
これか? watson-developer-cloud/speech-to-text-websockets-python
こことか? watson-developer-cloud/raspberry-pi-speech-to-text
ふむふむ。
Raspiの方はドンピシャっぽい気もするけど、でもNode.js入れろとか言ってる。
うーん。あんましNode.js勉強してないのと、既存のアパッチ君との兼ね合いとか考えるの面倒なので、、、上の方つかってみる。
##サンプルスクリプトをインストールするぞ。
mkdir watson
cd watson
git clone https://github.com/watson-developer-cloud/speech-to-text-websockets-python.git
cd speech-to-text-websockets-python
sudo pip install -r requirements.txt
大量のエラー発生(多すぎて載せません)。どうもコンパイルで失敗。Python.hが無いとか言ってる。
先に下のをやらないといけないのか?
sudo apt-get install build-essential python-dev
もちろんこれは難なく完了。
さあ、再挑戦。
$ sudo pip install -r requirements.txt
Traceback (most recent call last):
File "/usr/bin/pip", line 9, in <module>
load_entry_point('pip==1.5.6', 'console_scripts', 'pip')()
File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 356, in load_entry_point
return get_distribution(dist).load_entry_point(group, name)
File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 2476, in load_entry_point
return ep.load()
File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 2190, in load
['__name__'])
File "/usr/lib/python2.7/dist-packages/pip/__init__.py", line 74, in <module>
from pip.vcs import git, mercurial, subversion, bazaar # noqa
File "/usr/lib/python2.7/dist-packages/pip/vcs/mercurial.py", line 9, in <module>
from pip.download import path_to_url
File "/usr/lib/python2.7/dist-packages/pip/download.py", line 25, in <module>
from requests.compat import IncompleteRead
ImportError: cannot import name IncompleteRead
なんか戦況が悪化した感じ。全く内容が違う。python-dev
入れた時になんか変になったのか?pip入れなおしてみる。
$ sudo apt-get purge python-pip
$ curl -kL https://bootstrap.pypa.io/get-pip.py | sudo python
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1487k 100 1487k 0 0 167k 0 0:00:08 0:00:08 --:--:-- 188k
Collecting pip
Downloading pip-8.1.1-py2.py3-none-any.whl (1.2MB)
66% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺枕 | 798kB 1.3MB/s eta 0:00:0 67% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺幕 | 808kB 1.1MB/s eta 0:00:0 68% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺哩 | 819kB 468kB/s eta 0:00:0 69% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 楯 829kB 474kB/s eta 0:00 70% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 鋼 839kB 474kB/s eta 0:00 70% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 掛 849kB 474kB/s eta 0:00 71% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 ・ 860kB 335kB/s eta 0:00 72% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺鮪 | 870kB 332kB/s eta 0: 73% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺膜 | 880kB 335kB/s eta 0: 74% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺哩 | 890kB 335kB/s eta 0: 75% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎 | 901kB 255kB/s eta 0: 76% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 鋼 911kB 274kB/s eta 76% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 弓 921kB 217kB/s eta 77% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 ・ 931kB 216kB/s eta 78% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺柾 | 942kB 186kB/s et 79% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺膜 | 952kB 179kB/s et 80% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺槙 | 962kB 215kB/s et 81% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎 | 972kB 177kB/s et 82% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 旨 983kB 148kB/s 82% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 芸 993kB 148kB/s 83% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 榎 1.0MB 136kB/s 84% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 ・ 1.0MB 136kB/s 85% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺枕 | 1.0MB 170kB/ 86% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺幕 | 1.0MB 143kB/ 87% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎 | 1.0MB 160kB/ 88% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 楯 1.1MB 121k 88% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 芸 1.1MB 107k 89% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 掛 1.1MB 120k 90% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 ・ 1.1MB 119k 91% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺鮪 | 1.1MB 11 92% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺膜 | 1.1MB 11 93% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺哩 | 1.1MB 10 94% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎 | 1.1MB 10 94% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 鋼 1.1MB 95% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 弓 1.1MB 96% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆 ・ 1.2MB 97% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺柾| 1.2M 98% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺膜| 1.2M 99% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺槙| 1.2M 100% |笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎笆遺毎| 1.2MB 34kB/s
Installing collected packages: pip
Successfully installed pip-8.1.1
よしよし入ったぞ。(プログレスバー的なやつがめちゃ文字化けしてると思ったら、Teratermの端末設定をUTF-8にしてなかった。。。)
さて、Retryするぞ。
sudo pip install -r requirements.txt
お、ちゃんと入ってるくさい。結構時間かかります。
入ったー!!バンザイ!
##WATSON使うための情報ゲトするぞ
ちょっと優しくないかもですが、リストで書きます。
- bluemixでユーザー登録する(30日間無料です)
- 中に入ったら、ダッシュボードあたりで、「サービスまたはAPIの追加」に行き、WATSONの「Speech To Text」をポチる。
- 右横の「サービスの追加」でアプリは「アンバインドのまま」(アプリに紐付かせない)にして後はお好きにどうぞ。プランは「標準」で「作成」して下さい。(「標準 最初の 1000 分は無料です」だそうです。)
- 再びダッシュボードに行き、作ったばかりの「Speech To Text」を開いて、「サービス資格情報」を確認。すると、JSON形式でcredentials情報が載ってます。ここのユーザー名とパスワードを使いますよ。
##ではWATSON動くかな。。。。
(パスワードとユーザー名は適宜置き換えて下さいね。)
$ python ./sttClient.py -credentials <username>:<password> -model en-US_BroadbandModel
:0: UserWarning: You do not have a working installation of the service_identity module: 'No module named service_identity'. Please install it from <https://pypi.python.org/pypi/service_identity> and make sure all of its dependencies are satisfied. Without the service_identity module and a recent enough pyOpenSSL to support it, Twisted can perform only rudimentary TLS client hostname verification. Many valid certificate/hostname mappings may be rejected.
the output directory "./output" already exists, overwrite? (y/n)? y
2016-03-17 23:59:48+0900 [-] Log opened.
2016-03-17 23:59:48+0900 [-] ./recordings/0001.wav
2016-03-17 23:59:48+0900 [-] ./recordings/0002.wav
2016-03-17 23:59:48+0900 [-] ./recordings/0003.wav
2016-03-17 23:59:48+0900 [-] ./recordings/0004.wav
2016-03-17 23:59:48+0900 [-] ./recordings/0005.wav
2016-03-17 23:59:48+0900 [-] ./recordings/0006.wav
2016-03-17 23:59:48+0900 [-] ./recordings/0007.wav
2016-03-17 23:59:48+0900 [-] ./recordings/0008.wav
2016-03-17 23:59:48+0900 [-] ./recordings/0009.wav
2016-03-17 23:59:48+0900 [-] ./recordings/0010.wav
2016-03-17 23:59:48+0900 [-] Traceback (most recent call last):
2016-03-17 23:59:48+0900 [-] File "./sttClient.py", line 298, in <module>
2016-03-17 23:59:48+0900 [-] factory = WSInterfaceFactory(q, summary, args.dirOutput, args.contentType, args.model, url, headers, debug=False)
2016-03-17 23:59:48+0900 [-] File "./sttClient.py", line 55, in __init__
2016-03-17 23:59:48+0900 [-] WebSocketClientFactory.__init__(self, url=url, headers=headers, debug=debug)
2016-03-17 23:59:48+0900 [-] File "/usr/local/lib/python2.7/dist-packages/autobahn/twisted/websocket.py", line 278, in __init__
2016-03-17 23:59:48+0900 [-] protocol.WebSocketClientFactory.__init__(self, *args, **kwargs)
2016-03-17 23:59:48+0900 [-] TypeError: __init__() got an unexpected keyword argument 'debug'
ぐう。何かごちゃごちゃ言われてます。。。
前半は無視するとして(大丈夫なのか?)、、、
debug
ってパラメータが不要っぽい?まさかとは思うけど、取ってみるか。まずは保存してから編集。
cp -ip sttClient.py sttClient.py.org
vi sttClient.py
編集箇所は
#55行目Before
WebSocketClientFactory.__init__(self, url=url, headers=headers, debug=debug)
#55行目after
WebSocketClientFactory.__init__(self, url=url, headers=headers)
#299行目Before
factory = WSInterfaceFactory(q, summary, args.dirOutput, args.contentType, args.model, url, headers, debug=False)
#299行目after
factory = WSInterfaceFactory(q, summary, args.dirOutput, args.contentType, args.model, url, headers)
上記の2箇所。
よし、再実行!!
##(再挑戦)いでよワトソン!!!!
(中略)
2016-03-18 00:10:09+0900 [-] sendMessage(init)
2016-03-18 00:10:09+0900 [-] ./recordings/0001.wav
2016-03-18 00:10:09+0900 [-] onOpen ends
2016-03-18 00:10:09+0900 [-] Text message received: {
2016-03-18 00:10:09+0900 [-] "state": "listening"
2016-03-18 00:10:09+0900 [-] }
2016-03-18 00:10:14+0900 [-] Text message received: {
2016-03-18 00:10:14+0900 [-] "results": [
2016-03-18 00:10:14+0900 [-] {
2016-03-18 00:10:14+0900 [-] "alternatives": [
2016-03-18 00:10:14+0900 [-] {
2016-03-18 00:10:14+0900 [-] "timestamps": [
2016-03-18 00:10:14+0900 [-] [
2016-03-18 00:10:14+0900 [-] "several",
2016-03-18 00:10:14+0900 [-] 1.0,
2016-03-18 00:10:14+0900 [-] 1.52
2016-03-18 00:10:14+0900 [-] ],
2016-03-18 00:10:14+0900 [-] [
2016-03-18 00:10:14+0900 [-] "to",
2016-03-18 00:10:14+0900 [-] 1.52,
2016-03-18 00:10:14+0900 [-] 1.67
2016-03-18 00:10:14+0900 [-] ]
2016-03-18 00:10:14+0900 [-] ],
2016-03-18 00:10:14+0900 [-] "transcript": "several to "
2016-03-18 00:10:14+0900 [-] }
2016-03-18 00:10:14+0900 [-] ],
2016-03-18 00:10:14+0900 [-] "final": false
2016-03-18 00:10:14+0900 [-] }
2016-03-18 00:10:14+0900 [-] ],
2016-03-18 00:10:14+0900 [-] "result_index": 0
2016-03-18 00:10:14+0900 [-] }
2016-03-18 00:10:14+0900 [-] interim hyp: "several to "
2016-03-18 00:10:14+0900 [-] Text message received: {
2016-03-18 00:10:14+0900 [-] "results": [
2016-03-18 00:10:14+0900 [-] {
2016-03-18 00:10:14+0900 [-] "alternatives": [
2016-03-18 00:10:14+0900 [-] {
2016-03-18 00:10:14+0900 [-] "timestamps": [
2016-03-18 00:10:14+0900 [-] [
2016-03-18 00:10:14+0900 [-] "several",
2016-03-18 00:10:14+0900 [-] 1.0,
2016-03-18 00:10:14+0900 [-] 1.52
2016-03-18 00:10:14+0900 [-] ],
2016-03-18 00:10:14+0900 [-] [
2016-03-18 00:10:14+0900 [-] "tornadoes",
2016-03-18 00:10:14+0900 [-] 1.52,
2016-03-18 00:10:14+0900 [-] 2.23
2016-03-18 00:10:14+0900 [-] ]
2016-03-18 00:10:14+0900 [-] ],
2016-03-18 00:10:14+0900 [-] "transcript": "several tornadoes "
2016-03-18 00:10:14+0900 [-] }
2016-03-18 00:10:14+0900 [-] ],
2016-03-18 00:10:14+0900 [-] "final": false
2016-03-18 00:10:14+0900 [-] }
2016-03-18 00:10:14+0900 [-] ],
2016-03-18 00:10:14+0900 [-] "result_index": 0
2016-03-18 00:10:14+0900 [-] }
(この後も大量に。。略)
なんか出た!!ワトソンとお話出来たっぽい!!!
よしよし。とりあえずサンプルの音源10個では出来た。ファイルを送りつけては、その返答が帰ってくる。10個やるのにそこそこ時間掛かってる。WAVファイルのUploadにはそこまで時間掛かって無さそうだけど、返答くるのに時間かかってるみたい。Upしてから20秒位考えてる疑惑。まぁその辺は後で考えるしかないか。。(juliusの方がほぼ実時間だからまだマシなのか。。何がネック・・?)
##あとまだやりたいこと/確認したいこと
- 日本語モードの指定方法(きっと
ja-JP_BroadbandModel
ってとこかな?) - JuliusからのKickを経て、WATSON用の音源録音の開始と終了のやりかた(これが全く検討つかないぞ。。)
- テレビ&AirPlayを鳴らすスピーカーの上にマイク置いてるから、スピーカー音を逆位相とかでオフセットする方法
- サンプルPythonスクリプトの学習
- juliusで認識した音のファイルへの保存って出来るのか?(出来るなら、WATSONで始まって、以上で終わらせるとか簡単そうな気がする。)
- あるいは、julius上げっぱで、マイクで録音する方法があればそれはそれでなんとかなる気がするな。
- 地味に、上の方で作ったPythonの標準出力の仕方が良くない気がする。ファイルへのリダイレクトでクラッシュした。それもPythonのお勉強すね。
###改めて、全体の整理
julius起動、随時コマンド待ち「WATSON」の認識が発生したら、juliusを停止してマイクを開放させる- WATSONへの接続を行う処理(後で作るとし、仮名watson_kicker)を実行 →今ココ。まだサンプル音源を送るモードになってるから、引数でWAVファイルを指定する方法に修正すればなんとかなるだろ。
- WATSONへの準備ができたら「私だ、WATSONだ。」とつぶやかせる(それか、LED光らせる。) →これLEDとか適当な音源にするか。。
- WATSONとお話し。その結果の処理はまたベッケンバウアーとし、後で考える。
- watson_kickerの処理終了時にもう一度julius起動させる(もしくは、何か全然別の監視プロセス動かしておいて、止まっててかつwatson_kickerが動いてなかったら即起動させる?)
- 振り出しに戻る
まだ先は長いが、徐々に出来てきた。楽しいねぇ。
続く。
#追記:2016/03/18
さて、1個ずつやっつける。
##日本語モードの指定方法:
アッサリ発見。しかも想定通り。
Mediawen/watson-go-sdk
Here is the list of Models in September, 2015:
en-US 8000 => en-US_NarrowbandModel
ja-JP 16000 => ja-JP_BroadbandModel
es-ES 16000 => es-ES_BroadbandModel
ja-JP 8000 => ja-JP_NarrowbandModel
es-ES 8000 => es-ES_NarrowbandModel
en-US 16000 => en-US_BroadbandModel
おっしゃ。次。
##WATSON用の録音どうするか
- JuliusからのKickを経て、WATSON用の音源録音の開始と終了のやりかた(これが全く検討つかないぞ。。)
- juliusで認識した音のファイルへの保存って出来るのか?(出来るなら、WATSONで始まって、以上で終わらせるとか簡単そうな気がする。)
- あるいは、julius上げっぱで、マイクで録音する方法があればそれはそれでなんとかなる気がするな。
これらもなんとかなりそうだ。
普通に調べてみたら公式ガイドのマイク入力についてになんとも素敵なツールが載ってた。
また,Julius にはマイクから1発話を録音するプログラム adinrec が付属しています.これを用いて,Julius が取り込む音声をチェックすることもできます.
% ./adinrec/adinrec myfile.wav
上記のように実行すると,adinrec はマイクから1回分の発声をファイル myfile.wav に記録します.この adinrec は Julius 本体と同じ取り込みルーチンを使用しているので,この録音ファイルの音質がすなわちJuliusが認識しようとしている音声の音質になります.
ということは、julius止めてからこいつを動かせば良いのか。楽勝じゃん。スクリプトに組み込む。
Juliusで「WATSON」を検知したら、録音モードに変更し最大30秒間録音。
録音完了したら、ファイル名を戻してくれるのでそれをWATSONに送ればよいのだ。
##録音開始と終了タイミングがわからない対策
録音開始と録音終了のタイミングがわからないから、「ピポ」という音を何種類かiPhoneの音系アプリで作って、それを鳴らす様にした。作成したのは
- julius起動時(ふわーっ)
- WATSONモード録音開始時(ぴぴ)
- WATSONモード録音終了時(ぴぴぴ)
- WATSONモード録音エラー時(ぶぶー)
の4種類。とりあえずこれで良さそう。
何故か(コラ)、aplayだと3.5mmジャックから音出すので、AirPlayを流しながらでも上記のWAVファイルは再生可能でした。(aplayの設定でどこかにそう書いてあるんだろう。その内気が向いたら調べる。)
##WATSON呼び出しスクリプト読んだ
あと、WATSON呼び出しスクリプトをちょっと読んでみた。
どうやら、特定の録音されたWAVファイルを使いたい場合は、-in
パラメータの後にSTTしたいWAVファイル名が記載してあるテキストファイルを渡せば良い。(WAVファイル名を渡さぬように。エラーになりますよ。)
なので、録音したファイル名+.txt
でそのテキストファイルを作成して、中身は録音したファイル名にする。
これでいける。
##現時点でのスクリプトは以下。
使ってみたい人はCredentialsとWAVファイルはご自分でご用意下さい。(WAVはその内どこかにUpします)あと、ファイルパスなどは最初に集めてあるので適宜ご自分の環境に置き換えてください。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import sys
import socket
import requests
import re
import subprocess
import shlex
import time
import datetime
julius_path = '/usr/local/bin/julius'
jconf_path = '/home/pi/julius-kits/dictation-kit-v4.3.1-linux/org.jconf'
adinrec_path = '/usr/local/bin/adinrec'
aplay_path = '/usr/bin/aplay'
rec_start_sound = '/home/pi/Music/hh2.wav'
rec_stop_sound = '/home/pi/Music/hh3.wav'
rec_error_sound = '/home/pi/Music/hh.wav'
julius_invoke_sound = '/home/pi/Music/hh4.wav'
wav_dafult_path = '/tmp'
watson_stt = '/home/pi/bin/watson/speech-to-text-websockets-python/sttClient.py'
watson_user = ''
watson_pass = ''
watson_model = 'ja-JP_BroadbandModel'
julius = None
julius_socket = None
def call_watson(sound_file):
print 'INFO : call watson'
ret_flg = True
count = 0
in_file = sound_file + '.txt'
args = '/usr/bin/python ' + watson_stt
args += ' -credentials ' + watson_user + ':' + watson_pass
args += ' -model ' + watson_model
args += ' -in ' + in_file
f = open(in_file, 'w')
f.write(sound_file)
f.close()
p = subprocess.Popen(
shlex.split(args),
stdin=None,
stdout=None,
stderr=None
)
while p.poll() is None:
# while False: # always through
if count >= 60:
print 'WARN : watson: time over. abort.'
ret_flg = False
p.kill()
time.sleep(0.1)
count += 0.1
os.remove(in_file)
print 'INFO : call watson complete.'
return ret_flg
def play_wav(sound_file, second=1):
print 'INFO : play wav'
args = aplay_path + ' ' + sound_file + ' -d ' + str(int(second))
p = subprocess.Popen(
shlex.split(args),
stdin=None,
stdout=None,
stderr=None
)
time.sleep(0.5)
print 'INFO : play wav complete.'
return True
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'
play_wav(julius_invoke_sound)
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 rec_one_sentence():
limit_threshold = 30 # 30 second.
count = 0
ret_flg = True # default return flag is True.
print 'INFO : start recoding a sentence. invoke adinrec.'
play_wav(rec_start_sound)
now = datetime.datetime.now()
file_name = wav_dafult_path + '/' # add last '/' in anyway.
file_name += "watson_sentence_{0:%Y%m%d_%H%M%S}.wav".format(now)
args = adinrec_path + ' ' + file_name
try:
p = subprocess.Popen(
shlex.split(args),
stdin=None,
stdout=None,
stderr=None
)
while p.poll() is None:
print 'INFO : reconding...'
if count >= limit_threshold:
print 'WARN : recoding too long. abort.'
ret_flg = False
p.kill()
time.sleep(1)
count += 1
except:
print "CRIT : Unexpected error in " + sys._getframe().f_code.co_name + " :", sys.exc_info()[0]
p.kill()
raise Exception('error in ' + sys._getframe().f_code.co_name)
if ret_flg:
print 'INFO ; recording complete'
play_wav(rec_stop_sound)
else:
play_wav(rec_error_sound)
return (ret_flg, file_name, count)
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)
def main():
global julius
global julius_socket
julius, julius_socket, sf = invoke_julius_set()
# ###
# # re definition
# ###
reWATSON = re.compile(r'WHYPO WORD="WATSON" .* CM="(\d\.\d*)"')
while True:
if julius.poll() is not None: # means , julius dead
delete_socket(julius_socket)
julius, julius_socket, sf = invoke_julius_set()
else:
line = sf.readline().decode('utf-8')
print line
tmp = reWATSON.search(line)
if tmp:
# print line
if float(tmp.group(1)) > 0.8:
print 'WARN : DIE julius, call WATSON'
kill_julius(julius)
delete_socket(julius_socket)
rec_return, file_name, rec_time = rec_one_sentence()
if rec_return:
# play_wav(file_name, rec_time)
print file_name
watson_return = call_watson(file_name)
os.remove(file_name)
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)
これでやっと手放しWATSONが出来るようになりました。
シーケンスとしては
- スクリプト実行
- 「ふぁーっ」って音がなったらJulius準備OK
- 「WATSON」と話しかける。綺麗に伝わったら
- 「ピピ」っと音がなるので録音開始
- 「電気を消して下さい」とか話してみる
- 「ピピピ」ってなると録音完了
- WATSONにファイル転送&解析中
- 結果ゲット!(以下)
- 再度「ふぁーっ」となり、julius準備OK。(3に戻る)
って感じで動いてます。よしよし。一応、下が戻ってきた結果。かなりいい感じ。
##WATSONに「電気を消して下さい」と(丁寧語で)言ってみた
2016-03-19 03:05:46+0900 [-] Text message received: {
2016-03-19 03:05:46+0900 [-] "results": [
2016-03-19 03:05:46+0900 [-] {
2016-03-19 03:05:46+0900 [-] "alternatives": [
2016-03-19 03:05:46+0900 [-] {
2016-03-19 03:05:46+0900 [-] "word_confidence": [
2016-03-19 03:05:46+0900 [-] [
2016-03-19 03:05:46+0900 [-] "電気",
2016-03-19 03:05:46+0900 [-] 0.772810046140911
2016-03-19 03:05:46+0900 [-] ],
2016-03-19 03:05:46+0900 [-] [
2016-03-19 03:05:46+0900 [-] "を",
2016-03-19 03:05:46+0900 [-] 1.0
2016-03-19 03:05:46+0900 [-] ],
2016-03-19 03:05:46+0900 [-] [
2016-03-19 03:05:46+0900 [-] "消して",
2016-03-19 03:05:46+0900 [-] 1.0
2016-03-19 03:05:46+0900 [-] ],
2016-03-19 03:05:46+0900 [-] [
2016-03-19 03:05:46+0900 [-] "下さい",
2016-03-19 03:05:46+0900 [-] 0.990391454391391
2016-03-19 03:05:46+0900 [-] ]
2016-03-19 03:05:46+0900 [-] ],
2016-03-19 03:05:46+0900 [-] "confidence": 0.932,
2016-03-19 03:05:46+0900 [-] "transcript": "電気 を 消して 下さい ",
マジバッチリじゃん。
1. julius起動、随時コマンド待ちその結果の処理はまたベッケンバウアーとし、後で考える。
2. 「WATSON」の認識が発生したら、juliusを停止してマイクを開放させる
3. WATSONへの接続を行う処理(後で作るとし、仮名watson_kicker)を実行
4. WATSONへの準備ができたら「私だ、WATSONだ。」とつぶやかせる(それか、LED光らせる。) →これLEDとか適当な音源にするか。。
5. WATSONとお話し。
6. watson_kickerの処理終了時にもう一度julius起動させる(もしくは、何か全然別の監視プロセス動かしておいて、止まっててかつwatson_kickerが動いてなかったら即起動させる?)
7. 振り出しに戻る
とまぁ、かなり進みました。あとは何させるかとかだな。
##残課題の整理(と追加):
- 戻ってきた結果の最終候補だけ取り出して、それを元にコマンドを作る
- ネットラジオを適当に鳴らす
- 音楽を止める
- エアコンを止める
- エアコンを任意の設定でつける
- 天気予報を喋らせる
- テレビを消す
- 上記のようなイベントをスケジュールさせる
- テレビ&AirPlayを鳴らすスピーカーの上にマイク置いてるから、スピーカー音を逆位相とかでオフセットする方法
- サンプルPythonスクリプトの学習
- 地味に、上の方で作ったPythonの標準出力の仕方が良くない気がする。ファイルへのリダイレクトでクラッシュした。それもPythonのお勉強すね。
- WAVファイルではなく圧縮した形式で送る場合のパフォーマンス検証
- 常時起動でどのくらい誤作動がおきうるのか。今のCM80%以上という閾値の妥当性を探る。
- ログをどの程度保管するか。ハウスキープも検討。
- 使用量の積算とか考えてみる。
- ワトソン呼び出しの合言葉、、、「WATSON」ではなくてなんか他の考えるか。「OK Gxxgxx」とか「Hey Sxrx」みたいなの。「ジャー◯ス」とか「TA◯S」とか「CA◯E」とか「タチ◯マ」とかなんかそんなの。
- そもそも、Watsonの他の機能を学習する
続く。
#追記:2016/03/20
細かい所をやってます。
##標準出力のリダイレクトで落ちる
- 地味に、上の方で作ったPythonの標準出力の仕方が良くない気がする。ファイルへのリダイレクトでクラッシュした。それもPythonのお勉強すね。
エラーは以下のような感じ。
$ ./julius_listener.py > /tmp/hoge.log
Playing WAVE '/home/pi/Music/hh4.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
Traceback (most recent call last):
File "./julius_listener.py", line 211, in <module>
main()
File "./julius_listener.py", line 191, in main
print line
UnicodeEncodeError: 'ascii' codec can't encode characters in position 17-18: ordinal not in range(128)
これ、ここ見た。(中身深くは理解してないけど)実行前にPYTHONIOENCODING=utf-8
を渡せば良いっぽいのでそれで良しとしておこう。
これでリダイレクトは出来たけど、即時書込じゃなかったのでここ見てpython -u
とすることにする。
あと、SIGTERMをもらった時にちゃんとJuliusを落としてから落ちたほうが良いのでsignal参考にして仕込む。
##パラメータチューニング
よし、あとは必要なときにちゃんと認識してくれるようにパラメータいじってみる。
[先生][sensei]のやつからはちょっといじって、今のところ以下でTryしてみている。
-quiet
-w original_words2.dic
-v model/lang_m/bccwj.60k.htkdic
-h model/phone_m/jnas-tri-3k16-gid.binhmm
-hlist model/phone_m/logicalTri
-lv 2000
-input mic
-rejectshort 800
-charconv EUC-JP UTF-8
マイクはテレビ横に置いてあって距離が有るので-lv 2000
にしてみた。
感度は上がるっぽいが、その分ずっと処理する為CPUが結構高いままになってるな。100%で張り付いてるわけではないので、よく働いてくれているのなら一旦良しとしよう。
後ディクショナリファイルの単語数をゴミ除去用とおもって色々追加した為か、感度は良くなっても認識してくれないことがめちゃ増えた。
まだ、テレビを付けてるだけで「WATSON」の誤認識は幾つか出てくる。例えば全体で2225の単語検出が発生した中で、8件がWATSONだった。呼んでもないのに。
誤認識=WATSONの月使用上限が無駄に削られるという事なので結構気を使う。
###今後のチューニング方針
- 単語数を減らす。(今2500個程度だから、数百位のイメージ。)
- 呼び出し音声を長めにする。(某社の「Hey Siri」とかの呼び出し方は、それはそれで色々研究されて日常会話でまず出てこないだろうという事で設定したんだろう。)
引き続き見ていく。
##PulseAudio化
そろそろコマンドを作りたい所だが、ネットラジオの自動再生するにもShairportがデバイス掴んでるから現状では無理。途中にソフトウェアのミキサー入れないと、、と思って探したらPulseAudioだったらイイらしいという話。
pulseaudio を使って Raspberry Pi [から|へ] 音を飛ばす
iPodとPCの音声出力をRaspberry Piに丸投げするまでの作業ログ
ただ、この手の作業は得てして既存環境がメチャメチャになるので気をつけたい。
少なくとも今感じている事前確認事項は
- マイクがちゃんと動かないんじゃないか疑惑。
- これまでやったAudio関係設定をおさらいしとく
- OSバックアップ取っておこう(まだこの作業がめんどい。商用Unixは楽だな。。。)
というあたり。
上から確認したら早速いいのあった。
How can I get line-in microphone working with Skype?
コレやっといたほうが良さそう。一応やらなくてもいいかもテストしないと。
##単語リストチューニング(3/22)
現在、WATSONの読み方3個と2500個のゴミ拾い用単語の合わせて2503個でほったらかしてみている。テレビの音など結構余計な勘違いが有るための仮措置。
それで、3/22の全体の単語検知が4989個で、うちWATSONは13個。ちなみに、WATSONとは一度もつぶやいていない。全て誤検知。一応、「WATSON」モードでは1回最大30秒まで録音してWATSONに聞きに行かせるので、13回×30=6分30秒。
一応、1000分/月まで無料なので、一日30分迄は許容値だな。回数にして60回。そんなに聞くことも無いから良いけど、無駄撃ちが多いのはイケてない。なんとかしたい。
- 最低限の有用なゴミ拾い用単語だけを登録しておく
- WATSON誤検知を減らす
を改めて目標としたい。
この日のトップ20を出してみる。
$ grep "WHYPO W" julius_listener_20160322.log |awk -F\" '{print $2}'|sort |uniq -c|sort -rn |head -20
179 んう
135 ぬぬ
100 うん
95 うう
94 ほう
92 んん
87 ねぬ
64 んふ
63 むん
61 ねん
59 なん
59 うふ
57 にぬ
55 ぬう
54 ぬん
53 ぬふ
52 ええ
51 うほ
46 おほ
44 んい
ふーん。uとnが多いね。低音のブーンとかをそう捉えちゃうのだろうか。ローパスフィルター噛ませば良いのかな。そんな簡単じゃないか?
また、検知した単語の種類としては682種類だったから1821個の辞書登録は無駄っぽい。それらは省いておいたほうがjuliusの処理としてはきっと軽くなるはず。
諸々、もう少し様子見しよう。
#2016/03/24追記
3日たったので再度検証。
まず、3日通しての多かった単語、トップ 100。
$ grep "WHYPO W" julius_listener_2016032*.log |awk -F\" '{print $2}'|sort |uniq -c|sort -rn |head -100
479 んう
463 ぬぬ
323 うん
278 うう
273 んん
266 ほう
218 ねぬ
212 にぬ
193 んふ
187 むん
177 ぬう
157 ええ
154 ねん
154 ぬん
152 うふ
151 なん
150 あん
139 うほ
139 ああ
129 おほ
128 ぬふ
125 ぬほ
125 ぬな
124 なぬ
121 おう
119 ねほ
116 んほ
114 んへ
109 えう
108 ねふ
104 おん
103 ふほ
100 ねへ
88 むほ
88 ふふ
88 えん
82 いん
81 ほん
79 にん
77 んい
77 ぬえ
76 んむ
73 ねう
71 へん
71 のぬ
70 はん
69 ぬね
69 ぬあ
68 たぬ
67 んあ
65 ほぬ
65 のん
64 んお
64 ほふ
64 たん
63 のほ
60 ぬの
59 もん
58 いい
54 はは
54 うぬ
53 んぬ
53 むふ
51 ねは
49 うあ
47 ひん
47 えい
45 なえ
45 えへ
44 ふん
44 なや
41 んく
41 ほほ
40 ひい
40 ねな
39 ねに
39 たえ
38 ぬに
38 あは
37 もう
37 へぬ
37 へう
37 なふ
36 なな
36 WATSON
35 ふぬ
35 にふ
34 はへ
33 もほ
33 へほ
33 へい
32 へふ
32 ひふ
32 おふ
32 えふ
30 んひ
30 よぬ
30 へへ
30 へえ
28 んは
そして、最初の1文字のみでの出現数
$ grep "WHYPO W" julius_listener_2016032*.log |awk -F\" '{print $2}'|cut -c 1-3 |sort |uniq -c |perl -nle '$A=$_;$A=~/^\s
*(\d*) .*$/;print "$_"."*"x($A/30)'|sort -rn
1770 ん***********************************************************
1742 ぬ**********************************************************
1126 う*************************************
1011 ね*********************************
656 な*********************
634 ほ*********************
592 え*******************
540 に******************
485 む****************
481 あ****************
462 お***************
419 た*************
397 ふ*************
356 へ***********
343 の***********
295 は*********
245 い********
219 ひ*******
198 も******
175 わ*****
135 か****
113 て***
112 や***
76 ま**
75 つ**
72 め**
69 よ**
64 す**
62 と**
58 し*
50 け*
48 せ*
45 く*
37 ゆ*
36 WAT*
32 き*
22 み
20 さ
15 こ
9 ち
9 そ
ということで、「こ」「ち」「そ」から始まる呼び出しコマンドを検討するかなぁ。。
#2016/03/26追記
(単語数を減らした効果の測定ですが、そもそもnmonとか取って無かったからCPUリソース的な改善があったかわからんばい。検知された単語の出現数集計してみて、WATSONが最下位に来ていたのでその点は良しと評価する事にする)
##PulseAudio化を真面目に検討します。
恐ろしいのでまずはBackupしました。Mac経由でDDしたファイルをgzipしてNASに入れておく。DDのブロックサイズは1Mで、8GBのSDカードで370秒程度かかりました。圧縮したサイズは2Gちょい。よし、これで怖くなかろーもん。
##PulseAudio要件定義
想定する使い方を以下で整理してみる。上からアプリケーション、ミドル(PulseAudio)、デバイスとなる。
Shairport Aplay(wav) (mpd?) julius WATSON
|Out |Out |Out |In |In
---------------------------------------
| Pulse Audio |
---------------------------------------
|
ALSA(/OSS?)
入出力としては、Shairport経由でAirPlay(iPhoneもしくはMac)、JuliusおよびWatsonコントロールシェルからのWAV等の出力、ネットラジオの再生(MPDとか?)、そしてjuliusとWATSONのマイク入力。(あとシステムの元々の音とかあるのかな。)
これらについて、
(1)juliusで呼び出しコマンド検知後に、Shairportとmpdなど、他の出力レベルを通常の20%程度に下げる。
(2)Aplayでピポ音を出す
(3)静かな状態でWATSON用の録音
(4)終わったらAplayでピポポ音を出す
(5)出力レベルを全て元通りにする。
というような使い方ができればそれっぽい。
なので、基本的にはコマンド(CLI)でミキサー操作が出来る必要が有る。基本PulseAudioはGUIツールっぽいので初期セットアップ位はGUIでも良い。
##下調べ
どうやらarchlinuxのPulseAudioが詳しい。
あと、Ubuntu Forumsのhow to control PulseAudio in command line?も大事そう。
導入が必要と思われるコンポーネントについて、本体のpulseaudio
は必須でしょう。
Ubuntuのやつとしては、コマンド操作用でpactl
とpacmd
を使っている用に見える。(archの方見るとどうやらデフォルトで入るモノっぽいぞ)
archlinuxの方にのってるponymix と pamixerはどうもapt-getできなそう。
よし、とりあえず方針としては本体だけ入れて、出力と録音が出来るか調査する事としよう。
しかし、、Raspi 3買っちゃおうかな。でもCPUが80度になるとかちょっと頑張りすぎでしょ。2.5Aて。やっぱりZeroほしいな。Zero沢山動かしてみたいな。
続く。
#2016/03/30追記
Raspi 3買っちゃいました。欲しい人は、Raspberry Pi3が通販で買える店(技適についても)とかご参考下さい。3のセットアップは別記事で載せるです。
さて。PulseAudio化を進めます。
##インストール
さしあたり必要そうなのは、上記でもちょっと調べましたがpulseaudioとpacmdとpactlと思われます。その内pactlとpacmdはpulseaudio-utilsに含まれるです。
かつ、そのpulseaudio-utilsもpulseaudio入れると自動で入るので、やっぱりpulseaudioをまず入れれば大丈夫。
$ sudo apt-get install pulseaudio
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
python-chardet python-colorama python-distlib python-html5lib python-ndg-httpsclient python-openssl
python-pkg-resources python-pyasn1 python-requests python-setuptools python-six python-urllib3 python-wheel
Use 'apt-get autoremove' to remove them.
The following extra packages will be installed:
libasound2-plugins libpulsedsp libspeexdsp1 libwebrtc-audio-processing-0 pulseaudio-module-x11
pulseaudio-utils rtkit
Suggested packages:
pavumeter pavucontrol paman paprefs
The following NEW packages will be installed:
libasound2-plugins libpulsedsp libspeexdsp1 libwebrtc-audio-processing-0 pulseaudio pulseaudio-module-x11
pulseaudio-utils rtkit
0 upgraded, 8 newly installed, 0 to remove and 0 not upgraded.
Need to get 1242 kB of archives.
After this operation, 5522 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
なんかゴチャゴチャ入るっぽいですが、yしましょう。あと、
Suggested packages:
pavumeter pavucontrol paman paprefs
とあるけど、こいつらは全部GUIツールなので不要?まぁあってもいいけどCUIで行くと思うのであんまり使わない気がする。
Do you want to continue? [Y/n] y
Get:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main libspeexdsp1 armhf 1.2~rc1.2-1 [42.9 kB]
Get:2 http://mirrordirector.raspbian.org/raspbian/ jessie/main libasound2-plugins armhf 1.0.28-1+b1 [62.2 kB]
Get:3 http://mirrordirector.raspbian.org/raspbian/ jessie/main libpulsedsp armhf 5.0-13 [36.4 kB]
Get:4 http://mirrordirector.raspbian.org/raspbian/ jessie/main libwebrtc-audio-processing-0 armhf 0.1-3 [81.6 kB]
Get:5 http://mirrordirector.raspbian.org/raspbian/ jessie/main pulseaudio-utils armhf 5.0-13 [67.5 kB]
Get:6 http://mirrordirector.raspbian.org/raspbian/ jessie/main pulseaudio armhf 5.0-13 [892 kB]
Get:7 http://mirrordirector.raspbian.org/raspbian/ jessie/main pulseaudio-module-x11 armhf 5.0-13 [31.7 kB]
Get:8 http://mirrordirector.raspbian.org/raspbian/ jessie/main rtkit armhf 0.11-2 [27.9 kB]
Fetched 1242 kB in 51s (23.9 kB/s)
Selecting previously unselected package libspeexdsp1:armhf.
(Reading database ... 120859 files and directories currently installed.)
Preparing to unpack .../libspeexdsp1_1.2~rc1.2-1_armhf.deb ...
Unpacking libspeexdsp1:armhf (1.2~rc1.2-1) ...
Selecting previously unselected package libasound2-plugins:armhf.
Preparing to unpack .../libasound2-plugins_1.0.28-1+b1_armhf.deb ...
Unpacking libasound2-plugins:armhf (1.0.28-1+b1) ...
Selecting previously unselected package libpulsedsp:armhf.
Preparing to unpack .../libpulsedsp_5.0-13_armhf.deb ...
Unpacking libpulsedsp:armhf (5.0-13) ...
Selecting previously unselected package libwebrtc-audio-processing-0:armhf.
Preparing to unpack .../libwebrtc-audio-processing-0_0.1-3_armhf.deb ...
Unpacking libwebrtc-audio-processing-0:armhf (0.1-3) ...
Selecting previously unselected package pulseaudio-utils.
Preparing to unpack .../pulseaudio-utils_5.0-13_armhf.deb ...
Unpacking pulseaudio-utils (5.0-13) ...
Selecting previously unselected package pulseaudio.
Preparing to unpack .../pulseaudio_5.0-13_armhf.deb ...
Unpacking pulseaudio (5.0-13) ...
Selecting previously unselected package pulseaudio-module-x11.
Preparing to unpack .../pulseaudio-module-x11_5.0-13_armhf.deb ...
Unpacking pulseaudio-module-x11 (5.0-13) ...
Selecting previously unselected package rtkit.
Preparing to unpack .../rtkit_0.11-2_armhf.deb ...
Unpacking rtkit (0.11-2) ...
Processing triggers for man-db (2.7.0.2-5) ...
Processing triggers for dbus (1.8.20-0+deb8u1) ...
Setting up libspeexdsp1:armhf (1.2~rc1.2-1) ...
Setting up libasound2-plugins:armhf (1.0.28-1+b1) ...
Setting up libpulsedsp:armhf (5.0-13) ...
Setting up libwebrtc-audio-processing-0:armhf (0.1-3) ...
Setting up pulseaudio-utils (5.0-13) ...
Setting up pulseaudio (5.0-13) ...
Adding user pulse to group audio
Setting up pulseaudio-module-x11 (5.0-13) ...
Setting up rtkit (0.11-2) ...
Processing triggers for libc-bin (2.19-18+deb8u1) ...
Processing triggers for dbus (1.8.20-0+deb8u1) ...
よし、入った。
あとやっぱり
$ sudo apt-get install pulseaudio-module-zeroconf
も入れておく。
##まずはインストール直後のテスト
現状裏でShairPort(AirPlayサーバー)が動いているけど、再生してどうなるか。
なる!普通に鳴る。そうなんだね。ホッとした。
当たり前だ。pulseaudio動いてないんだもん。アホか 。
##設定ファイル系の確認
/etc/pulse/default.paはそのまんまの方が良いとかarchさんに書いてあったけど、オリジナル保管してあるしいじってみる。
###以下を追加。第三オクテットXXXは適宜入れて下さい。
load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1;192.168.XXX.0/24
###以下はコメントアウトを外した。Avahi用。
load-module module-zeroconf-publish
###以下はコメントアウトした。サスペンドさせない。
#load-module module-suspend-on-idle
Shairport対策
pcm.pluse {
type pulse
}
ctl.pulse {
type pulse
}
pcm.!default {
type pulse
# If defaults.namehint.showall is set to off in alsa.conf, then this is
# necessary to make this pcm show up in the list returned by
# snd_device_name_hint or aplay -L
hint.description "Default Audio Device"
}
ctl.!default {
type pulse
}
その他、ラズパイ用。(オススメらしい。)
resample-method = trivial
default-sample-rate = 44100 ##デフォルトだからそのままでも良いか。。。
alternate-sample-rate = 48000 ##デフォルトだからそのままでも良いか。。。
これで、デーモン化コマンドを叩く。
pulseaudio -D
さて、OS再起動してみるか。
$ ps -ef|grep pulse
pi 1277 1 2 00:53 ? 00:00:00 /usr/bin/pulseaudio --start
pi 1328 1 0 00:53 ? 00:00:00 /bin/sh /usr/bin/start-pulseaudio-x11
よし、動いてる。
##再び稼動確認
ShairPort経由でiPhoneから音を出してみる。
音が流れた!わーい。
CPU的にも全然食わない。よし。
この後の確認ポイント:
- マイク入力テスト(Juliusと録音)
- 再起動時(実際は停止時)のスピーカーボン回避がちゃんと動くか
- MPDのインストールとpulseの利用
- Aplayからpulseの利用
- ソースとシンクの音量調節をコマンドで実施
まだまだ続くぞー!
#2016/04/01追記
続きから。上2つはすぐに確認とれたので良しとする。
マイク入力テスト(Juliusと録音)再起動時(実際は停止時)のスピーカーボン回避がちゃんと動くか- MPDのインストールとpulseの利用
- Aplayからpulseの利用
- ソースとシンクの音量調節をコマンドで実施
先にAplayで鳴らしているWAVをPulse経由で利用するぞ。
。。。CUIで頑張ろうと思っていたが、GUI使います。paprefsとpavucontrolを入れます。
。。。なんか色々やったら壊れました。Shairportから音ならない。。くそう。
リストアして再チャレンジするか。。。
続く。。。
#参考リンク
関数 - php プログラマのための Python チュートリアル
How can I get line-in microphone working with Skype?
pulseaudio を使って Raspberry Pi [から|へ] 音を飛ばす
iPodとPCの音声出力をRaspberry Piに丸投げするまでの作業ログ