RaspberryPiとArduinoを連携する【Firmata編】

RaspberryPiとArduinoを連携する【Firmata編】

前回のエントリではRaspberryPiとArduinoをUSBシリアルで接続をしてみたというものでした。

uepon.hatenadiary.com

すると・・・

ぐえ、スペル適当にしてあとで治そうと思っていたのですが直してませんでした。Johnny-Fiveもスペル違ってたしw。 ってことで、引き続きFirmataでRaspberryPiとArduinoを連携させてみたいと思います。

んでFirmataって?

Firmataとは?

http://firmata.org/wiki/Main_Page

いろいろググってみたところFirmataはシリアル通信を介してPC等のホストマシンから Arduinoやその他マイコンボード等のデバイスを制御するためのプロトコルのようです。

RaspberryPiというかpythonからこのプロトコルを使う場合には

github.com

github.com

上記2つがあるようです。更新履歴をみるとpyFirmataのほうが新しいようですのでそれを使用してみます。python-firmataのメンテナンスは長期間行われていないっぽいですね。

Firmata使用のためのArduino側の準備

以下のドキュメントを見ると

https://raspberrypi-aa.github.io/session3/firmata.html

  1. Click File->Examples->Firmata->StandardFirmata
  2. From the Tools->Board menu, select the type of Arduino you are using.
  3. From the Tools->Serial Port menu, choose the USB port to which your Arduino is connected.
  4. Click the upload button (it looks like a right arrow, just next to the checkmark) and wait for your sketch to upload. A message in the bottom black windowwill indicate success or failure
  5. Once the Firmata sketch is loaded on your Arduino, you can test it out with the Firmata Test Program.

という手順で準備ができるそうです。

まずはArduinoIDEを起動します。

f:id:ueponx:20170101212659p:plain

メニューバーから【ファイル】→【スケッチ例】→【Firmata】→【StandardFirmata】を選択します。

f:id:ueponx:20170101212924p:plain

するとエディタ部分にStandardFirmataのソースコードが表示されます。これを接続したArduinoに書き込みを行います。 このときArduinoを接続したポートとボードタイプを適切にしておく必要があります。

f:id:ueponx:20170101213421p:plain

【Ctrl+U】で書き込みを行います。

f:id:ueponx:20170101213725p:plain

このようにエラーが無ければ作業は完了です。

pyFirmataのインストール

続いてはRaspberryPi側でpython環境の Githubのドキュメントを見ながらのインストールになります。

$ sudo pip install pyfirmata
Downloading/unpacking pyfirmata
  Downloading pyFirmata-1.0.3-py2.py3-none-any.whl
Requirement already satisfied (use --upgrade to upgrade): pyserial in /usr/lib/python2.7/dist-packages (from pyfirmata)
Installing collected packages: pyfirmata
Successfully installed pyfirmata
Cleaning up...

インストール作業としてはこれで完了です。以下のようにpipのlistで以下のように確認します。

$ pip list
()
pyFirmata (1.0.3)
()

リスト上に表示されればインストールはできています。 あとはpythonインタプリタで動作を確認します。 以下では13pinに配置されているLEDのON/OFFを行います。

$ python
Python 2.7.9 (default, Sep 17 2016, 20:26:04)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyfirmata import Arduino, util
>>> board = Arduino('/dev/ttyACM0')
>>> board.digital[13].write(1)
>>> board.digital[13].write(0)

ポートのアクセスですが、

pin13 = board.get_pin('d:13:o')
pin13.write(1)

という風にも記載することができます。d:13:oはデジタル13pinをoutputにするという意味になります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pyfirmata import Arduino, util
import time

board = Arduino('/dev/ttyACM0')
pin13 = board.get_pin('d:13:o')

print('start')
pin13.write(1)
time.sleep(3)
pin13.write(0)
time.sleep(3)
print('end')

まとめ

使っていて感じたのはFirmataはどちらかというと主従関係がはっきリしている場合に使うのかなと。 Arduino側のコードをほとんど記述しなくてもGPIOなどのポートが自由に使用できるのでそういった意味では実装が容易にできるもメリットだと思います。 接続したArduinoが並列で動作できる点ではシリアルを使っての制御がいいかなと思いますが、データの収受で同期を取るのが面倒かなと思いますが、そのあたりは臨機応変にやるということでしょうか。

RaspberryPiとArduinoを連携する【USBシリアル編】

RaspberryPiとArduinoを連携する

RaspberryPiを使っているとセンサ系での物足りなさを感じてくることが良くあります。 特にアナログ値を使うようなセンサでは回路の知識がないと悲しくなります。 そこでそういった部分をカバーすることを考えるとArduinoとの連携を行いたくなってきます。 ArduinoであればGroveインターフェースで回路側の負担が少なくできることもメリットです。 あとオリジナルのUnoではなく激安品まで考えると1枚当たりのコストはセンサより安価にもできます。 (300円程度の商品もあるようです。絶対という保証はないですが。)

Arduino以外にmbedも使うことができると思うのですが、まずはArduinoってことで。

Arduinoとの接続は

  • I2C接続
  • シリアル通信
  • Firmata

とありますが、今回はシリアル通信を行うことにします。 個人的にはシリアルが一番汎用的に使用できるロジックだと思いますが、I2Cもいい選択肢かなと思います。 コードのベースができてしまえば使いまわしができます。Firmataはどうなんでしょうか?(RaspberryPiでFirmata接続するArduinoの エントリがあんまりないなと思いますが一般的じゃない?node.jsでJohnny-Fiveのほうが一般的?)

USBポートにArduinoを接続してみる

【接続前】

$ lsusb
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

【接続後】

$ lsusb
Bus 001 Device 005: ID 2341:0001 Arduino SA Uno (CDC ACM)
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Bus 001 Device 005: ID 2341:0001 Arduino SA Uno (CDC ACM)が追加されています。Arduinoって書いてあるので発見は楽です。認識はされているので今度はシリアルデバイスとしてどのように認識されているか確認してみます。

【接続前】

$ ls /dev/ttyA*
/dev/ttyAMA0

【接続後】

$ ls /dev/ttyA*
/dev/ttyACM0  /dev/ttyAMA0

接続後にはシリアルデバイスとして/dev/ttyACM0が追加されています。これを使うことになります。

Arduinoのシリアル接続制御のひな形

Arduinoとの接続がうまく行ったのでArduinoでシリアル送受信をする処理を書いてみました。stringのライブラリがうまく使えなかったので微妙なところはあるのですが…

f:id:ueponx:20170101132859j:plain

#include <String.h>
#define maxLength 64
String inString = String(maxLength);

void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  inString = "";
}

void loop() {
  if (Serial.available() > 0) {
    char inChar = Serial.read();
    if (inString.length() < maxLength) {
      inString.concat(String(inChar));
      if (inChar == '\n') {
        if (inString.startsWith("ON")) {
          Serial.println("<--HI-->");
          digitalWrite(13, HIGH);
        } else if (inString.startsWith("OFF")) {
          Serial.println("<--LOW-->");
          digitalWrite(13, LOW);
        }
        inString = "";
      }
      Serial.println(inString);
    }
    else {
      Serial.println("Maximum number of characters.");
    }
  }
}

このコードではON【CR】【LF】(またはON【LF】='\n')を受信すると13pin(オンボード上)のLEDをONとし、 OFF【CR】【LF】(またはOFF【LF】)を受信するとLEDをOFFするという動きになります。 (厳密にいうとONから始まり【LF】で終わるような場合にONし、OFFから始まり【LF】で終わるような場合はOFFにします。)

この受信文字("ON"など)の処理部分を拡張することで多様な処理ができるかなと思います。 ただ、センサの値によりトリガとして動作させる時には別途処理は考えないといけないかと。

pythonからシリアル制御を行う

pySerialを使用します。

http://pythonhosted.org//pyserial/

このブログでも過去に使用していますので以下を参考にします。

uepon.hatenadiary.com

インタラクティブシェルで以下のようにするとLEDのON/OFFができました。

$ python
Python 2.7.9 (default, Sep 17 2016, 20:26:04)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import serial
>>> import time
>>> s = serial.Serial('/dev/ttyACM0',9600)
>>> time.sleep(2)
>>> s.write("ON\n")
3
>>> s.write("OFF\n")
4
>>> s.write("ON\n")
3
>>>

これでシリアル通信を介したRaspberryPiとArduinoの通信処理はうまく行ったようです。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import serial
import time

s = serial.Serial('/dev/ttyACM0', 9600)
time.sleep(2)
s.write("OFF\n")
time.sleep(10)
s.write("ON\n")
s.close()

まとめ

とりあえず、RaspberryPiとArduinoをUSB接続してシリアルで制御できました。 これでそれなりにセンサデバイスとしてArduinoが使えそうです。 なんとなくですが、能動的にセンサーのデータを取得するのはArduinoで行い、RaspberryPiは受信に徹する方がいいのかなと思います。その場合は、RaspberryPi側での受信にはtimeoutを設定することでブロッキング時間を短縮したほうがいいかなと思います。例えば以下のような感じ。

s = serial.Serial('/dev/ttyACM0', baudrate=9600, timeout=3)

折角なので他の方法も試してみたいと思います。

RaspberryPiのSDカードアダプタを使用する

RaspberryPiのSDカードアダプタを使用する

実はこれまでRaspberryPiはRaspberry Pi 1 Model B使用していたのですが、やっぱりSDカードの出っ張りが気になってきました。 ということで、出っ張らないSDカードアダプタを購入し、SDカードをMicroSDカードにに変更することにしました。 これまで使っていたSDカードはこれになります。

16GbyteのSDカードだったので以下のカードへの移行を行います。

出っ張らないSDカードのアダプタは

を使用することにしました。

早速SDカードのバックアップをとります。いつも通りWin32DiskImagerを使用します。

ja.osdn.net

バックアップはこのあたりのエントリで。(昔は使い方の勘違いがあったので)

uepon.hatenadiary.com

バックアップが作成できたらMicroSDカードに書き込むわけですが、

f:id:ueponx:20161228130224j:plain

あれ?サイズが違うエラー?

カードの刻印には16Gbyteと書いてあるのですが、どうも微妙にサイズを違う?

再インストールに向けての準備(Sambaのインストール)

これでは折角のアダプタが無駄になるのでファイルのバックアップ(イメージのバックアップではなく)を行って再インストールを行おうと思います。 FTPでバックアップをするのは面倒なので、RaspberryPiにsambaをインストールします。

Sambaのインストールと設定と接続

こちらのブログを参考にしました。以下でインストールします。かなり時間がかかるのでびっくりしますが待機です。

$ sudo apt-get update
$ sudo apt-get install samba

ファイルのバックアップをとるだけなのでブログの設定と同様に

共有設定は以下で追加します。

  • 共有名 : pi
  • 共有するフォルダ : /home/pi (ユーザー名「pi」でログインした際のホームフォルダ
  • 読み込みだけでなく、書き込みも可能
  • クライアントからの接続は、ゲスト接続
  • サーバー側では、ファイル操作を、ユーザー「pi」 によって実行

設定ファイルをvimで開き

$ sudo vim /etc/samba/smb.conf
[pi]
path = /home/pi
read only = No
guest ok = Yes
force user = pi

以上を追加します。

設定の追加後に以下のようにサービスを再起動します。

$ sudo service smbd restart

これでWindows側からアクセスが可能になります。

まとめ

単純に16GByteと書いてあるメモリカードのサイズが一致しないこともあったので結構焦ってしまいました。 折角なのでRASPBIAN JESSIE WITH PIXELも2016-11-25に出たので、これまでの環境をクリーンインストールして移行したいと思います。

www.raspberrypi.org

RaspberryPiのマイクで会話してみる【ヒミツのクマちゃん 完結編】

RaspberryPiのマイクで会話してみる【ヒミツのクマちゃん 完結編】

これまでのエントリの内容に最後に「今日会った出来事を教えてね」感じで会話するようにします。

uepon.hatenadiary.com

uepon.hatenadiary.com

uepon.hatenadiary.com

uepon.hatenadiary.com

uepon.hatenadiary.com

動作の概要は以下のような感じになります。

f:id:ueponx:20161226015507j:plain

これまでのソースファイルに音声認識APIへのアクセスをいれ、そのあとにNobyAPIに送信することで 会話を行うことになります。少しアレンジした点は音声認識の開始と終了に効果音をいれたところになります。

                # pygame
                pygame.mixer.music.load("./Q.mp3")
                pygame.mixer.music.play()
                time.sleep(1)
                pygame.mixer.music.stop()

このように音声認識開始と終了の部分に短い効果音をいれると話す人の戸惑いが少ないと思います。(ペッパーなどでも入れてますね。) 他のロボットと違い、目などの色で話している人にロボットがどのような状態かを知らせる手段がないので こういう工夫が必要だと思います。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import json
import types
import time
import picamera
import os
import sys
import pyaudio
import pygame.mixer
import wave

# MSEmotionAPIのwait
SHORTSLEEP = 10
LONGSLEEP = 60

# 音声録音のパラメータ
chunk = 1024*2
FORMAT = pyaudio.paInt16
CHANNELS = 1
#サンプリングレート、マイク性能に依存
RATE = 16000
#録音時間
RECORD_SECONDS = 5

# pygame
pygame.mixer.init()

try:
  while True:
        # MS Emotion APIで顔の認識を行う
        with picamera.PiCamera() as camera:
                camera.start_preview()
                camera.hflip = True
                camera.vflip = True
                time.sleep(2) #カメラ初期化
                camera.capture('foo.jpg')

        url = 'https://api.projectoxford.ai/emotion/v1.0/recognize'
        MS_APIKEY = '【Microsoft Emotion APIのキー】'
        headers = {'Content-type': 'application/octet-stream', 'Ocp-Apim-Subscription-Key': MS_APIKEY}
        payload = open('./foo.jpg', 'rb').read()

        r = requests.post(url, data=payload, headers=headers)
        data = r.json()

        ninshiki = len(data)

        # 顔認識が出来たらNoby APIにアクセスしておみくじを引く

        if ninshiki > 0:
                dict = data[0]['scores']
                max_key = max(dict, key=(lambda x: dict[x]))

                print "key: %s,\t value:%f" %(max_key, dict[max_key])

                ENDPOINT = 'https://www.cotogoto.ai/webapi/noby.json'
                NOBY_APIKEY = '【NobyのAPIキー】'

                payload = {'text': 'おみくじ引きたいな。', 'app_key': NOBY_APIKEY}
                r = requests.get(ENDPOINT, params=payload)
                data = r.json()

                response = data['text']
                # print "response: %s" %(response)

                uranai = response.split("\r\n")[1]
                print uranai

                # おみくじのフレーズを話す
                voice1 = 'こんにちは、今日は来てくれてありがとう。今日の運勢を占うね。'
                # shを呼び出す。
                cmdtext = './jtalk2.sh ' + uranai
                # print cmdtext

                os.system('./jtalk2.sh ' + voice1) #成功すると0 # import os
                os.system(cmdtext.encode('utf-8')) #成功すると0 # import os

                os.system('./jtalk2.sh ' + '最近あったできごとを教えて?')
                print 'マイクに5秒間話しかけてください >>>'

                # pygame
                pygame.mixer.music.load("./Q.mp3")
                pygame.mixer.music.play()
                time.sleep(1)
                pygame.mixer.music.stop()

                # pyaudio
                p = pyaudio.PyAudio()
                #マイク0番からの入力を5秒間録音し、ファイル名:voice.wavで保存する。
                #マイク0番を設定
                input_device_index = 0
                #マイクからデータ取得
                stream = p.open(format = FORMAT,
                                channels = CHANNELS,
                                rate = RATE,
                                input = True,
                                frames_per_buffer = chunk)
                all = []
                for i in range(0, RATE / chunk * RECORD_SECONDS):
                        data = stream.read(chunk)
                        all.append(data)

                stream.close()
                data = ''.join(all)
                out = wave.open('voice.wav','w')
                out.setnchannels(1) #mono
                out.setsampwidth(2) #16bits
                out.setframerate(RATE)
                out.writeframes(data)
                out.close()

                p.terminate()
                print '<<< 録音完了'

                pygame.mixer.music.load("./OK.mp3")
                pygame.mixer.music.play()
                time.sleep(1)
                pygame.mixer.music.stop()

                path = 'voice.wav'
                DOCOMO_APIKEY = '【docomo音声認識APIのキー】'
                url = "https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize?APIKEY={}".format(DOCOMO_APIKEY)
                files = {"a": open(path, 'rb'), "v":"on"}
                r = requests.post(url, files=files)
                print r.json()['text']

                payload = {'text': r.json()['text'], 'app_key': NOBY_APIKEY}
                r = requests.get(ENDPOINT, params=payload)
                print r.json()['text']

                cmdtext = './jtalk2.sh ' + r.json()['text']
                os.system(cmdtext.encode('utf-8')) #成功すると0 # import os

                os.system('./jtalk2.sh ' + '教えてくれてありがとう。また会いにきてね!')

                print 'LONG_SLEEP_MODE'
                time.sleep(LONGSLEEP)
        else:
                print 'SHORT_SLEEP_MODE'
                time.sleep(SHORTSLEEP)

except KeyboardInterrupt:
  sys.exit()

もう少し関数とかクラスとかを使用するときれいにまとまりますが、それは今後の自分のpythonスキルの向上のために今後修正していこうと思います。 また、音声認識に関しては非常にネットワークAPIとの相性が良くない(特にリアルタイムのレスポンスを求めるものは難しい)のでスレッドなどを使用したりなどのイライラさせない工夫がカギになってくると思います。

まとめ

やっと音声関係というか「ヒミツのクマちゃん」を使ったもので完結編になりました。 軽い気持ちで購入したヒミツのクマちゃんですが、ちゃんとした会話のできるロボットになってくれました。 ぬいぐるみの出来がよいので、最終的にはRaspberryPiをどういう風に隠すか、マイクをどのようにするかなど考えればきりがないのですが。 それも含めて工作や工夫の醍醐味のあるテーマだったかなと思います。

Raspberry Pixelを古いPCにインストールしてみる

RASPBIAN JESSIE WITH PIXELが発表されましたが、更にこれにPCとMACに対応したバージョンが登場しました。 Raspberry Pixelは軽量デスクトップOSとして使用できそうです。昔から古いPCにUbuntuにをインストールしようと思っていたので これを機にPixelをインストールしようと思います。

掲載ページ

www.raspberrypi.org

The MagPi magazineという雑誌のおまけについたDVDの公開されたもののようですが、そのイメージをダウンロードできるようになっています。

ダウンロードリンク

http://downloads.raspberrypi.org/pixel_x86/images/pixel_x86-2016-12-13/2016-12-13-pixel-x86-jessie.iso

今回はUSBメモリに関してはSANDISKのコンパクトタイプのものAmazonでは1,000円ほどですが、秋葉原あたりではもう少し安く購入できました。 出っ張りも少ないし、アクセススピードも結構早いかなと思います。

そしてターゲットとなる古いPCは以下になります。もう購入してから5~6年ほど立ったものかなと思いますが、【Acer Aspire One 753】となります。 スペックとしては以下のようになります。以前からメモリは増設してあったものなので4Gほど搭載されています。Windows7のプリインストールモデルだったのですが、 Windows10ではSSDに換装してもCPUが100%になることもしばしばという状態だったので、ほどんど使っていませんでした。

www.notebookcheck.net

かなり旧式のCeleronですし、しかたないです。

公式サイドからISOファイルをダウンロードしたらサイト推奨の【Etcher】というツールを使ってイメージ書き込みをUSBにするわけですが、 【Win32DiskImager】でも試してみましたが問題なくできました。

etcher.io

sourceforge.net

あとは、USBを挿して起動すれば問題なくOSが起動できます。

https://www.raspberrypi.org/wp-content/uploads/2016/12/VirtualBox_PIXEL-CD_18_12_2016_10_25_32.png

https://www.raspberrypi.org/wp-content/uploads/2016/12/splash-768x576.jpeg

f:id:ueponx:20161225214324p:plain

f:id:ueponx:20161225214328p:plain

ChromiumVLCもインストールされているのでそれなりに使用できるようです。使用したPCでも負荷もほとんどないようなので常用できそうです。ただし、キーマップとか日本語化はしないといけないようですし、コマンド関係もかなり絞られているようなので別途インストールは必要の様です。

使用してみた感想としてはわりといい感じです。むしろPCで使用するよりもVMとして使用したほうがいいのかも?

【関連】

uepon.hatenadiary.com

RaspberryPiのマイクで録音した音声をテキスト化する【ヒミツのクマちゃん その3】

RaspberryPiのマイクで録音した音声をテキスト化する

ヒミツのくまちゃんと話すための企画その2になります。

前回のエントリーではマイクで録音するところまで来ました。

uepon.hatenadiary.com

後は録音した音声をテキスト化することができれば、拡張性(雑談APIやNobyに渡すことができるので会話っぽい表現を実装可)が増えそうです。

音声のテキスト化(音声認識)のAPI

あたりがいいかなと思いました。(docomoさんのは内部的にはAmiVoiceっぽい) ということでdocomoさんのAPIを使用します。(後日、GoogleさんのAPIも触る予定ですが)

基本的な情報は以下のURLにあります。

dev.smt.docomo.ne.jp

基本動作は音声データを送信すると音声認識したデータをテキスト化してJSON化して返信するものです。つまり、データの送り方(REST)とデータの形式(ファイルのバイナリフォーマット)が分かれば概ね処理ができます。

f:id:ueponx:20161217091728j:plain

今回は特別なSDKなどを使用せず、RESTで通信を行いたかったのでdocomoさんのAPIの中の音声認識API【Powered by アドバンスト・メディア】を使用します。

以下は上記サイトの引用です。

音声認識API【Powered by アドバンスト・メディア】とは
音声データをREST形式で送信するだけで音声認識をすることができます。

主な特徴
* HTTPで音声データをPOSTするだけなので、AndroidiOSに限らず様々なプラットフォームでのご利用が可能です。
* クライアントアプリケーションに特殊なライブラリを組み込む必要がないため自由度の高い実装が可能です。
* クライアントアプリケーションにライブラリを組み込んでリアルタイム認識を行うタイプの音声認識と比較して、応答速度が遅い為、レスポンスが求められる用途には向いていません。

ドキュメントを眺めてみる

以下のURLがドキュメントページになります。

https://dev.smt.docomo.ne.jp/?p=docs.api.page&api_name=speech_recognition&p_name=api_amivoice_1#tag01

サービスのエンドポイント

アクセスを行うエンドポイントは以下となります。

https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize

amiVoiceって書いてあるw

リクエストクエリパラメータ

キー 必須 説明
APIKEY APIにアクセスするアプリの認証に利用する

リクエストヘッダのMIME-TYPE

HTTPで送信するデータ形式MIMEタイプはこのようになっている?

キー 必須 説明
Content-Type MIMEタイプmultipart/form-data; boundary=<バウンダリ文字列>

あれRESTアクセスだし、MIMEタイプは単純なOctet-Streamなんじゃねーの?んが。

やられました

とはいってもマルチパートでってことはファイル+制御用のJSONを送ればいいのかなと予想がつきます。

リクエストパラメータ(マルチパート化されたデータの中身)

キー 必須 説明
a 音声のバイナリデータ。10秒を超える音声データは途中で打ち切られます。音声データのフォーマット:PCM(MSB)16khz/16bit
v - 発話区間検出処理。"on"を指定すると、音声データの無音部分を無視して音声認識処理を行います。

このあたりをマルチパート化して送信するようです。

音声ファイルのフォーマットはサンプリングレート16kHz、16bitの10秒以内の音声でいいようなので、PyAudioへは

FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000

のようにパラメータを指定すれば問題なさそうです。

また、vのキーに関しては制度を気にしなければ問題はなさそうなのですが、無音部分は無視してもらわないとかなり精度がわるそうな気がするので、指定をすることにします。

ドキュメントを見てもわかりますが以下のように通信データが送信されます。

POST https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize?APIKEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: multipart/form-data; boundary=<バウンダリ文字列>
--<バウンダリ文字列>
Content-Disposition: form-data; name="v"

on
--<バウンダリ文字列>
Content-Disposition: form-data; name="a"; filename="sample.adc"
Content-Type: application/octet-stream

音声データ(binary)
--<バウンダリ文字列>--

データの格納さえうまく調整できれば、Arduinoとかmbedからでも通信できそうな印象です。

ここまでわかれば、あとはpythonのrequestsモジュールでマルチパート化してPOSTする処理が わかれば、使用できそうです。

pythonのrequestsモジュールでマルチパート化処理を行う

こちらを参考にしました。

クイックスタート — requests-docs-ja 1.0.4 documentation

stackoverflow.com

ふむふむ、複数のデータはマルチパート化してPOSTするときには、以下のどちらかの形式でタプル化してpostの引数に与えれば良いようです。

  • (キー, データ)
  • (キー, データ, Content-Type)
  • (キー, データ, Content-Type, ヘッダー)

例えば、StackOverflowのサンプルをみてみれば、

  • キー=>foo、データ=>bar
  • キー=>spam、データ=>eggs

というデータセットをマルチパート化する場合には以下のような処理になります。

requests.post('http://…', files=(('foo', 'bar'), ('spam', 'eggs')))
# または
files = {'foo':'bar', 'spam':'eggs'}
requests.post('http://…', files=files)

今回のAPIではavというキーにデータを入れることになるので

files = {"a": 【音声ファイルのデータ】, "v":"on"}
r = requests.post(url, files=files)

こんな感じになるでしょう。あとはファイルの読み込みですがopen()で読みすればよさそうです。以下は処理の抜粋になります。

path = 'voice.wav'

files = {"a": open(path, 'rb'), "v":"on"}
r = requests.post(url, files=files)

これで一応大体の構想ができました。

API利用登録

では、登録を行っていきます。 下記のdocomoDeveloperSupportのページからログインを行ってください。SNS系のログインでも問題ありません。

dev.smt.docomo.ne.jp

次に音声認識のページの中ほどにある【申請する】のボタンをクリックします。

f:id:ueponx:20161219232846j:plain

すると、【API使用申請】のページに遷移します。

f:id:ueponx:20161219232944j:plain

ここで使用するアプリケーションの情報を登録することになります。 登録時点で決まっていなくても後で修正ができるので問題ありません。

入力が終わったら【API機能選択へ】のボタンをクリックします。

f:id:ueponx:20161219233148j:plain

API機能選択】の一覧画面に遷移するので、その中から

を選択します。他のAPIを含めても問題ありませんが、今回は1つだけにします。選択が終了したら下の方にある【利用するAPI利用規約に同意して次へ】をクリックします。

f:id:ueponx:20161219233928j:plain

【確認画面】に遷移するので、内容確認後【利用申請する】ボタンをクリックします。

f:id:ueponx:20161219234115j:plain

これで申請が完了しました。

f:id:ueponx:20161219234202j:plain

詳細は【マイページ】にある【API利用申請・管理】画面の【登録アプリケーション一覧】に情報が表示されます。API使用のキーに関してもここで確認ができます。

f:id:ueponx:20161219234228j:plain

これで音声認識APIが使用できるようになりました。

音声認識APIにデータを送信する

音声認識APIにデータを送信して、認識結果のテキスト情報を確認してみます。まずは録音した音声データがあればそれを使用します。 音声のデータフォーマットが10秒以内、サンプリングレート16kHz、サンプル16bitになっていない場合にはエラーとなるので注意が必要です。 今回はvoice.wavというファイルを送信することにします。

前述の通りpythonでデータを送信するコードを作成すると以下のようになります。認識された結果はJSON形式で返されるのですが、その中でtextのデータが認識された文書全体となります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
path = 'voice.wav'
APIKEY = '【発行されたAPIのキー】'
url = "https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize?APIKEY={}".format(APIKEY)
files = {"a": open(path, 'rb'), "v":"on"}
r = requests.post(url, files=files)
print r.json()['text']

このプログラムを実行すると以下のようになります。

$ python sample.py
、おまえ滑舌悪いんじゃないの。

無事に認識できたようです。(実験中に嫁に言われました…涙)

録音機能と連携させる

先程は事前に収録したものでテストしましたが、今度はPyAudioと連携し、録音後API側に対して要求を出すものにします。とは言っても、データは一度ファイルとして蓄積するので先程の処理とは大きく変わらないと思います。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

#マイクからの入力を5秒間録音し、ファイル名:voice.wavで保存する。

import requests
import pyaudio
import sys
import time
import wave

chunk = 1024*2
FORMAT = pyaudio.paInt16
CHANNELS = 1
#サンプリングレート、マイク性能に依存
RATE = 16000
#録音時間
RECORD_SECONDS = 5 #input('収録時間をしていしてください(秒数) >>>')

print 'マイクに5秒間話しかけてください >>>'

#pyaudio
p = pyaudio.PyAudio()

#マイク0番を設定
input_device_index = 0
#マイクからデータ取得
stream = p.open(format = FORMAT,
all = []
for i in range(0, RATE / chunk * RECORD_SECONDS):
        data = stream.read(chunk)
        all.append(data)

stream.close()
data = ''.join(all)
out = wave.open('voice.wav','w')
out.setnchannels(1) #mono
out.setsampwidth(2) #16bit
out.setframerate(RATE)
out.writeframes(data)
out.close()

p.terminate()

print '<<< 録音完了'

path = 'voice.wav'
APIKEY = '【発行されたAPIのキー】'
url = "https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize?APIKEY={}".format(APIKEY)
files = {"a": open(path, 'rb'), "v":"on"}
r = requests.post(url, files=files)
print r.json()['text']

実行結果

$ python pyaudio_amivoice.py
マイクに5秒間話しかけてください >>>
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
<<< 録音完了
おはようございます。。

いろいろWarningは出ていますが、うまく動きました。

まとめ

一応録音した音声をテキスト化することができました。更にこれを雑談APIやNobyAPIなどに送信すれば会話を行うことができるようになります。 もう少しでクマちゃんとの会話ができるようになるのではないかと思います。

次回は会話っぽいやり取りまで進めたいと思います。

RaspberryPiでマイク録音してみる【ヒミツのクマちゃん その2】

RaspberryPiでマイク録音してみる

前回のエントリでヒミツのクマちゃんが占いをしてくれるようになったので、今度は会話したくなってきました。そこでその第一歩としてRaspberryPiにマイクを接続しようと思います。

qiita.com

こちらの記事を参考にしています。

以下のマイクが実績があるとのことだったので購入することにしました。

値段的にはピン形状のマイク(アナログ)でもいいかなと思うのですが、ノイズが結構入るかなと思ったのでUSB接続にしました。

マイクを接続してみる。

マイクをUSBに接続してみてlsusbで認識状況を確認します。

【接続前】

$ lsusb
Bus 001 Device 006: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 005: ID 2019:1201 PLANEX
Bus 001 Device 004: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp. LAN9500 Ethernet 10/100 Adapter / SMSC9512/9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

【接続後】

$ lsusb
Bus 001 Device 007: ID 0d8c:0134 C-Media Electronics, Inc.
Bus 001 Device 006: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 005: ID 2019:1201 PLANEX
Bus 001 Device 004: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp. LAN9500 Ethernet 10/100 Adapter / SMSC9512/9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

比較すると一番上の行に以下のエントリが追加されていることがわかります。

Bus 001 Device 007: ID 0d8c:0134 C-Media Electronics, Inc.

以前使用していたUSBスピーカーもC-Media製だったような気がするのでC-Media製の音声デバイスって多いみたい。

何もしなくてもデバイスの認識はされたような感じですね。

通常であれば

/etc/modprobe.d/alsa-base.confを編集して、使用するオーディオモジュールの優先順位の変更して、RaspberryPiのリブート

の処理を行うことになるのですが、実は行わなくても録音は可能です。

alsaを使用して録音してみる。

基本的なテストはCLIalsaのコマンド群を使用することで確認ができます。

録音に使用できるデバイス一覧の確認

$ arecord -l
**** ハードウェアデバイス CAPTURE のリスト ****
カード 1: Device [USB PnP Audio Device], デバイス 0: USB Audio [USB Audio]
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0

複数ある場合にはいくつかのデバイスが列挙できますが、今回は1つしか存在していないのでこのような表示になります。

ここで覚えて置かなければならなのは カード番号が1、デバイス番号が0であることです。

先程オーディオモジュールの優先順位を変更を飛ばしたといいましたが、デフォルト値ではなく、デバイス指定を行うので不要でしたということになります。

録音を行う

$ arecord -D plughw:1,0 test.wav
録音中 WAVE 'test.wav' : Unsigned 8 bit, レート 8000 Hz, モノラル
^Cシグナル 割り込み で中断...

wavファイルはデフォルトの設定で録音します(データは8bit、サンプリングレートは8kHzのモノラル)。 コマンドスイッチで-D plughw:1,0としている部分がデバイスを明示的にしている部分となります。明示的に指定しないとデフォルト値(デバイスの優先順位が高いものを)探しに行くので注意が必要です。

先程のarecord -lコマンドの結果で、「カード番号が1、デバイス番号が0」と検出されていたので、この値を-Dで渡しています。

再生する

再生に関しては以下となります。

$ aplay test.wav
再生中 WAVE 'test.wav' : Unsigned 8 bit, レート 8000 Hz, モノラル

録音のパラメータも正しく認識されているので問題ありません。

録音ボリューム調整

録音すると音量が小さいことがあります。その場合には以下で調整することになります。

$ amixer sset Mic 46  -c 1
Simple mixer control 'Mic',0
  Capabilities: cvolume cvolume-joined cswitch cswitch-joined
  Capture channels: Mono
  Limits: Capture 0 - 62
  Mono: Capture 46 [74%] [14.62dB] [on]

ここで-cで渡している番号カード番号となるので、arecord -lコマンドの結果で、「カード番号が1、デバイス番号が0」と検出されていたので、カード番号の1を渡し、-c 1となります。

最小は0で最大は62まで指定できるようです。

録音ボリュームの値を確認するだけであれば以下で確認できます。

$ amixer sget Mic -c 1
Simple mixer control 'Mic',0
  Capabilities: cvolume cvolume-joined cswitch cswitch-joined
  Capture channels: Mono
  Limits: Capture 0 - 62
  Mono: Capture 46 [74%] [14.62dB] [on]

これでデバイスの基本的な機能の確認ができました。

pythonから録音してみる

これからは毎度のことですが、いつも通りpythonから使用できるかを確認することになります。ネットを調べるとpyaudioというモジュールを使うと良さそうです。

people.csail.mit.edu

pipでインストールしてみる

ではpipコマンドを使用してインストールしてみます。

$ pip install pyaudio
Downloading/unpacking pyaudio
  Downloading PyAudio-0.2.9.tar.gz (289kB): 289kB downloaded
  Running setup.py (path:/tmp/pip-build-uNj4Vs/pyaudio/setup.py) egg_info for package pyaudio

    warning: no files found matching '*.c' under directory 'test'
Installing collected packages: pyaudio
  Running setup.py install for pyaudio
    building '_portaudio' extension
    arm-linux-gnueabihf-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -fPIC -I/usr/include/python2.7 -c src/_portaudiomodule.c -o build/temp.linux-armv6l-2.7/src/_portaudiomodule.o
    src/_portaudiomodule.c:28:20: fatal error: Python.h: そのようなファイルやデ ィレクトリはありません
     #include "Python.h"
                        ^
    compilation terminated.
    error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1
    Complete output from command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-uNj4Vs/pyaudio/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-RuGTpw-record/install-record.txt --single-version-externally-managed --compile:
    running install

running build

running build_py

creating build

creating build/lib.linux-armv6l-2.7

copying src/pyaudio.py -> build/lib.linux-armv6l-2.7

running build_ext

building '_portaudio' extension

creating build/temp.linux-armv6l-2.7

creating build/temp.linux-armv6l-2.7/src

arm-linux-gnueabihf-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -fPIC -I/usr/include/python2.7 -c src/_portaudiomodule.c -o build/temp.linux-armv6l-2.7/src/_portaudiomodule.o

src/_portaudiomodule.c:28:20: fatal error: Python.h: そのようなファイルやディレ クトリはありません

 #include "Python.h"

                    ^

compilation terminated.

error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1

----------------------------------------
Cleaning up...
Command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-uNj4Vs/pyaudio/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-RuGTpw-record/install-record.txt --single-version-externally-managed --compile failed with error code 1 in /tmp/pip-build-uNj4Vs/pyaudio
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/pip/__init__.py", line 248, in main
    return command.main(cmd_args)
  File "/usr/lib/python2.7/dist-packages/pip/basecommand.py", line 161, in main
    text = '\n'.join(complete_log)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 57: ordinal not in range(128)

エラー?

先程のサイトをみると

pip will download the PyAudio source and build it for your system. Be sure to install the portaudio library development package (portaudio19-dev) and the python development package (python-all-dev) beforehand. For better isolation from system packages, consider installing PyAudio in a virtualenv.

という記述が。インストール時にビルドするので事前にportaudio19-devpython-all-devのパッケージは事前に入れてねということみたいです。PyAudioはportaudioの機能を使用するモジュールなので確かに必要ですね。

今回はお手軽さを目指したいのでapt-getでインストールします。

apt-getでインストールしてみる

$ sudo apt-get install python-pyaudio python3-pyaudio

パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  gyp libasn1-8-heimdal libc-ares-dev libc-ares2 libgssapi3-heimdal
  libhcrypto4-heimdal libheimbase1-heimdal libheimntlm0-heimdal
  libhx509-5-heimdal libjs-node-uuid libjs-underscore libkrb5-26-heimdal
  libroken18-heimdal libssl-dev libssl-doc libv8-3.14-dev libv8-3.14.5
  libwind0-heimdal rlwrap
これを削除するには 'apt-get autoremove' を利用してください。
提案パッケージ:
  python-pyaudio-doc
以下のパッケージが新たにインストールされます:
  python-pyaudio python3-pyaudio
アップグレード: 0 個、新規インストール: 2 個、削除: 0 個、保留: 181 個。
46.8 kB のアーカイブを取得する必要があります。
この操作後に追加で 194 kB のディスク容量が消費されます。
取得:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-pyaudio armhf 0.2.8-1 [23.2 kB]
取得:2 http://mirrordirector.raspbian.org/raspbian/ jessie/main python3-pyaudio armhf 0.2.8-1 [23.6 kB]
46.8 kB を 1秒 で取得しました (30.8 kB/s)
以前に未選択のパッケージ python-pyaudio を選択しています。
(データベースを読み込んでいます ... 現在 129542 個のファイルとディレクトリがインストールされています。)
.../python-pyaudio_0.2.8-1_armhf.deb を展開する準備をしています ...
python-pyaudio (0.2.8-1) を展開しています...
以前に未選択のパッケージ python3-pyaudio を選択しています。
.../python3-pyaudio_0.2.8-1_armhf.deb を展開する準備をしています ...
python3-pyaudio (0.2.8-1) を展開しています...
python-pyaudio (0.2.8-1) を設定しています ...
python3-pyaudio (0.2.8-1) を設定しています ...

python3はまだ使用する気はないですが、一応入れます。

PyAudioで録音を行う

公式サイトのサンプルを実験してみます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""PyAudio example: Record a few seconds of audio and save to a WAVE file."""

import pyaudio
import wave

CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)

print("* recording")

frames = []

for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
    data = stream.read(CHUNK)
    frames.append(data)

print("* done recording")

stream.stop_stream()
stream.close()
p.terminate()

wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

これを実行させると…

$ python pyaudio_sample.py
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
Expression 'parameters->channelCount <= maxChans' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1514
Expression 'ValidateParameters( inputParameters, hostApi, StreamDirection_In )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2818
Traceback (most recent call last):
  File "pyaudio_sample.py", line 19, in <module>
    frames_per_buffer=CHUNK)
  File "/usr/lib/python2.7/dist-packages/pyaudio.py", line 747, in open
    stream = Stream(self, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/pyaudio.py", line 442, in __init__
    self._stream = pa.open(**arguments)
IOError: [Errno Invalid number of channels] -9998

おや?エラーになってしまいました。エラーメッセージをみるとbuffer関連でエラーが出ています。

  File "pyaudio_sample.py", line 19, in <module>
    frames_per_buffer=CHUNK)

いろいろ実験を行った結果録音のパラメータがハードウエアに対応していなかったりするとこのようなエラーがでるようです。そこでパラメータを以下のように変更してみます。

【冒頭の設定部分】

CHUNK = 1024 * 2
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

バッファのサイズを倍、チャンネル数をモノラル、レートを16kHzへ変更してみました。

再度実行を行うと

$ python pyaudio_sample.py
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
* recording
* done recording

いろいろalsaやportaudioからアラートは出ていますが、無事に録音できました。

PyAudioで再生を行う

PyAudioは再生ももちろんできるのですが… 公式サイトの再生のサンプルを使ってみます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""PyAudio Example: Play a WAVE file."""

import pyaudio
import wave
import sys

CHUNK = 1024

if len(sys.argv) < 2:
    print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
    sys.exit(-1)

wf = wave.open(sys.argv[1], 'rb')

p = pyaudio.PyAudio()

stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
                output=True)

data = wf.readframes(CHUNK)

while data != '':
    stream.write(data)
    data = wf.readframes(CHUNK)

stream.stop_stream()
stream.close()

p.terminate()

これを実行すると…

$ python pyaudio_sample_play.py output.wav
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started

こちらもいろいろアラートが表示されますが、再生はできます…しかし、なんかノイズのっています。aplayで再生するとノイズが乗っていないので再生時にノイズがのっているようですが、原因はわかりませんでした。(処理能力などの問題でしょうか。)

単に再生するだけならpygameでも再生はできるのでそっちを使ってもいいかもしれません。(ループなど再生機能が多機能なので)

今回はpythonから録音ができたので、次は会話ができるように拡張をしていきたいと思います。