raspberry piの音周り

ラズベリーパイにしゃべらせたりairplayサーバーにしたりして遊んでいたメモを放出。
モデルはRaspberrry pi type B 512MB。

イヤホンジャック/HDMIから出力

参考: http://elinux.org/R-Pi_Troubleshooting#Sound

$ amixer cset numid=3 

n はヘッドフォン端子が1、HDMIでは2、0で自動判別

テスト

$ aplay /usr/share/sounds/alsa/Noise.wav

ボリューム調節

$ amixer set PCM 20%

USBスピーカー

USB経由での方が音がよいという噂なので

を買ってみた。

電力を食うのか、繋いだ瞬間にraspberry piが落ちる。電源付きのUSBハブ越しでも落ちる。wifiのドングルはハブ越しなら平気だったのに…。
ハブにつないだまま起動すれば問題はない。それだけで認識はする(alsamixerコマンドで選択出来るようになっていた気がする)が、

$ sudo vi /etc/modprobe.d/alsa-base.conf

options snd-usb-audio index=-2

の行をコメントアウトして再起動するとデフォルトのサウンドバイスになる。

音質はヘッドフォン端子から出すよりはよい、がまあそれなり、という感じ。ゲインを0にするとよくなるという噂を見てやってみたら(要は上でvolumeを100% にする。本体にも物理的なボリュームがあるのでそっちで調節)少しよくなった気がする?

あと1ヶ月くらい使ってるうちに音質悪くなくなってきた気がする(耳が慣れただけだ)。さすがにiphoneよりは良い音がする。

ゆっくり

ゆっくりの名で有名な AquasTalk のラズベリーパイ版。商用だが、個人・非営利利用ならライセンス料はかからない。http://www.a-quest.com/products/aquestalkpi.html からいただいたパッケージをどこかに解凍するだけ。解凍したディレクトリに行って

$ ./AquesTalkPi ゆっくりしていってね | aplay

とするとしゃべる。英単語もある程度読める。

オープンソースの OpenJTalk というのも試したのだが、商用だけあってこっちの方が読み上げが自然だし動作も軽快。

このままだとなんかエラーが発生したとき aplay に変なデータが流れ込んでザーッと言う音になって耳障りなので、シェルスクリプトを作った。
atalk:

#!/bin/bash
aquestalkpi=/home/pi/build/aquestalkpi/AquesTalkPi
var=`$aquestalkpi "$@" | base64; echo ":${PIPESTATUS[0]}"`
ret=(${var##*:})
data=${var%:*}
if [ $ret -eq 0 ]; then
  echo $data | base64 --decode --ignore-garbage | aplay -q
else
  echo $data | base64 --decode --ignore-garbage
  exit $ret
fi

というのを作って実行権限を付けてパスの通ったところに置いて、

$ atalk -g 15 ほげ

とする。オプションはもともとのAquesTalkと同じで、例えば上の -g は音量(100が最大)。なんも指定しないで走らせるともとの AquesTalkPi のヘルプを表示するのでそれを参照。

$ atalk
NAME
  AquesTalkPi - Raspberry Pi用テキスト音声合成コマンド Ver.1.00

SYNOPSIS
  AquesTalkPi [-h] [-s speed] [-g volume] [-b] [-v f1|f2]
              [-k] [-t] [-o out.wav] [-f file | string]

DESCRIPTION
  日本語のテキスト音声合成を行います。
  標準出力にWAV形式の音声データを出力します。

OPTIONS
  string
    発声する文字列を指定します(UTF-8)。漢字も読めます。
    音声記号列での指定も可能です(-k オプション)
    音声記号列の詳細は、AquesTalk音声記号列仕様を参照。
    スペース等を含む場合は ""で囲んで指定してください。
  -f file
    発声する文字列をファイルで指定するときに指定します。
    先頭行だけしか処理しません。
    stringを指定せず、且つ fileに - を指定したときは、
    標準入力からの入力になります(pipe使用可能)。
  -o file
    WAVファイルとして出力するときにファイル名を指定します。
    指定しないときは標準出力に出力されます。
  -t
    WAV形式音声データの代わりに音声記号列を出力します。
    言語処理の結果を返します。
  -k
    発声する文字列が音声記号列の場合に指定します。
  -v f1 | f2
    声種を指定します。 f1:女声1(default) f2:女声2
  -b
    棒読み(アクセントが平板)になります。
  -g volume
    音量を指定します。 (0-100) default:100
  -s speed
    発話速度を指定します。 (50-300) default:100
  -h
    このメッセージを表示します。

EXAMPLE
    $ ./AquesTalkPi 漢字も読めます。 | aplay
    $ echo ゆっくりしていってね? | ./AquesTalkPi -b -f -  | aplay
    $ ./AquesTalkPi -s 150 -v f2 -k -o out.wav "ファイルニ、シュツ'リョクシマ_ス。"

  *実行時にはAquesTalkPiと同じディレクトリに付属の/aq_dic以下が必要です。

LICENCE, etc.
    商用利用、再配布には別途ライセンス契約(有償)が必要です。
    ライセンス、その他につきましては、下記サイトを参照ください。
    http://www.a-quest.com/products/aquestalkpi.html

    ---- COPYRIGHT 2013 AQUEST Corp. ----

AirPlay

参考: http://www.lifehacker.jp/2013/03/130306raspberry_piairplay.html

sudo apt-get install git libao-dev libssl-dev libcrypt-openssl-rsa-perl libio-socket-inet6-perl libwww-perl avahi-utils libmodule-build-perl
git clone https://github.com/njh/perl-net-sdp.git
cd perl-net-sdp
perl Build.PL
sudo ./Build
sudo ./Build test
sudo ./Build install
cd ..
git clone https://github.com/hendrikw82/shairport.git
cd shairport
make
# ./shairport.pl -a AirPi
sudo make install
sudo cp shairport.init.sample /etc/init.d/shairport
sudo chmod 755 /etc/init.d/shairport
sudo update-rc.d shairport defaults
sudo vi /etc/init.d/shairport
#DAEMON_ARGS="-w $PIDFILE"
#DAEMON_ARGS="-w $PIDFILE -a AirPi"
sudo service start shairport

で、同じネットワークのiphoneとかから AirPi という名前で見えるようになるはず。

youtube

$ sudo apt-get install mplayer youtube-dl
$ sudo apt-get install rtmpdump swftools libxml2-utils
$ mplayer $(youtube-dl -g www.youtube.com/watch?v=Y6ljFaKRTrI)

時報・天気予報をしゃべらせる

応用としてcrontabに入れて定時でしゃべるようにしてみる。

時報

上で作った atalk が ~/bin 以下に置いてあれば

0 */1 * * * /home/pi/bin/atalk -g 15 `date +\%-H`'時だよ'

としておくと毎時時報をしゃべる。crontabだと % をバックスラッシュでエスケープしなければいけないらしいのでそこは注意。

天気予報

コード参考(というかほぼそのまま):

pythonでtenki.jpから天気予報の情報を読んで、atalkでしゃべらせられるようなテキストにする。
tenki:

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

import urllib2
import re
import socket
import htmlentitydefs

def unescape_html_entity(text):
    reference_regex = re.compile(u'&(#x?[0-9a-f]+|[a-z]+);', re.IGNORECASE)
    num16_regex = re.compile(u'#x\d+', re.IGNORECASE)
    num10_regex = re.compile(u'#\d+', re.IGNORECASE)

    result = ''
    i = 0
    while True:
        match = reference_regex.search(text, i)
        if match is None:
            result += text[i:]
            break

        result += text[i:match.start()]
        i = match.end()
        name = match.group(1)

        if name in htmlentitydefs.name2codepoint.keys():
            result += unichr(htmlentitydefs.name2codepoint[name])
        elif num16_regex.match(name):
            result += unichr(int(u'0'+name[1:], 16))
        elif num10_regex.match(name):
            result += unichr(int(name[1:]))

    return result

def tenki(url):
    socket.setdefaulttimeout(10.0)
    # 正規表現のパターンを定義 - タグ消し
    remove_tag = re.compile(r'<.*?>')

    try:
        htmldata = urllib2.urlopen(url)
    except urllib2.HTTPError as err:
        print('HTTPError')
        print(err)
    except urllib2.URLError as err:
        print('URLError')
        print(err)
        if isinstance(err.reason, socket.timeout):
            print('timeout')
    else:
        # print('Get HTML')
        pass

    content = htmldata.read()
    htmldata.close()
    content_list = content.split('\n')

    flag = False

    result = ''
    for line in content_list:
        if flag==True and not "</div>" in line:
            #正規表現でタグ消しした後にstripメソッドでスペースを除去。後ろに,をつけると改行しない
            result += remove_tag.sub("", line).strip()
            #print line
            #filewrite(line)
        if flag==True and "</div>" in line:
            result += remove_tag.sub("", line).strip()
            #print line
            #filewrite(line)
            flag = False
        elif "weatherCountryDescriptionBody" in line:
            result += remove_tag.sub("", line).strip()
            #print line
            #filewrite(line)
            flag = True

    print unescape_html_entity(result.decode('utf-8')).encode('utf-8')
    # print result

if __name__ == "__main__":
    url = "http://tenki.jp/forecast/pref-14.html"
    tenki(url)

上は埼玉県の天気を出力するが、一番下のurlを変えたら他の県もいけるはず。
実行権限をつけて ~/bin において、

$ tenki

で埼玉県の天気がテキストで出力されるのを確認する。
そしたらcrontabに

30 8 * * * /home/pi/bin/atalk -g 15 '天気予報です、'`/home/pi/bin/tenki`

と書いておくと毎朝8時半に天気予報をしゃべってくれる。