スマートフォン・ジン | Smartphone-Zine

引っ越し先→ https://smartphone-zine.com/

ラズパイ・オーディオにチャレンジ!Pi:ゼロから始めるハイレゾ対応ポータブルアンプ生活

  低価格シングルコンピュータRaspberry piを使って、ハイレゾ・オーディオシステムを作成する「ラズパイ・オーディオ」が、オーディオファンの間で流行っているようですね。低価格でネットワーク対応の超高音質オーディオマシンが試せるのが魅力のようです。 5ドルのラズパイRaspberryPi Zeroが出た時に同好会メンバーそれぞれで購入したものの、何に使うか思いつかないまま放置していました。そのうちカメラ接続用のI/Fが搭載されたVersion1.3がリリースされ、これもまた同好会メンバーで即購入。最初に購入したゼロが完全に余ってしまいましたので、ちょっと改良して、ラズパイ・オーディオを楽しむことにしましょう。 しかもこのサイズ!これはもう、コンパクトなオーディオを作るしかないですよね!ということで次の目標を立てました。

  1. 超小型ラズパイ・オーディオ
  2. 当然ハイレゾ対応
  3. バッテリー内蔵

それではこれらをさくっと作ってしまいましょう。 まずはDAC選び。DAC(ダック)というのは、Digital/Analog converterの略。ラズパイに保存されたデジタルデータはそのままでは再生できないので、スピーカーで再生できるようにするために、デジタル信号をアナログ信号に変換するのがDACの仕事。このときの変換が音の決め手になります。 当然サイズの小さいながらも、高音質のものをチョイスしたいところ。ちょうど手元に、Zeroと一緒に購入したhatの中に(hatというのはHardware Attached on Topの事で、ラズパイにぽんと乗っければ使えるようになっている基盤の事です)pHAT DACがあったのでこれにしましょう。 pHAT DAC そうそう、ゼロにはオーディオアウトが(HDMIだけしか)ないので不便だと思って買っておいたのでした。 このDACがなかなかの高性能で、筆者が愛用しているTEAC HA-P50-Bというポタアン(ポタアンってのはポータブル・アンプの事。持ち歩けるアンプですね)にも使われている、BurrBrown PCM5102の後継PCM5102Aがつかわれているのです。ってことは音の味付けはTEACのポタアンとほぼ同じって事です。どんな風に音の違いがでるか楽しみです。 また、このDACはI2S接続でラズパイとつながります。I2S(IC間サウンド)とは、IC間でデジタル音声データをシリアル転送するための規格で、通常、DACやADCといった、音響機器内部のIC間で音声デジタル信号を直接やり取りするための規格。 で、これが何がすごいのかというと、通常PCでハイレゾ・オーディオを楽しむためには、パソコンと外付けのDAC、そしてアンプとスピーカーを用意して、パソコンとDACを「USB」接続します。しかし、ラズパイゼロとpHAT DACを接続するのにUSBは使いません。ここがすごいところ。元々PCで外付けDACでは、やれUSBケーブルの相性だの、OSドライバがアシンクロナスモード対応してないだの、ドライバの出来に左右されるだの、といった色々面倒な気遣いが必要だったんですが、ラズパイ・オーディオではUSBを使わずIC間で直接やり取りするもっと「生」なデータで転送ができるわけで、より高品質なオーディオ再生が期待できるわけです。 次に、ポタアン化にあたり、ヘッドホンアンプが必要になります。値段も手頃でそこそこ音のいいものをチョイスしたいところ。外で気軽に高音質というのがコンセプトのポタアンにオーバースペックなアンプは必要ないでしょう。ちょうど秋月にポータブルヘッドホン用のアンプキットがあるのでこれを使用することにしました。 ポータブルヘッドフォンアンプキット 電源を用意するのは手間なので、適当なモバイルバッテリーと合わせる事にします。Zeroへの給電はUSBケーブル経由だと全体のサイズが大きくなってしまうため、モバイルバッテリーのUSBソケットの根本からラズパイに直接電源を接続します。電源のOFFをどうすのか、という点が心配になりますが、これはモバイルバッテリーに電源のON/OFFがあるため、問題ありません。 [caption id="attachment_2566" align="alignnone" width="300"] 赤いコードが+[/caption] ちょこっとここで欲が出てきました。できれば、ディスプレイ表示も欲しいですよね。という訳で、LCDを購入。 Raspberry Pi用I2C接続のLCDキット この製品はI2Cインタフェースになっており比較的に簡単に接続が可能です。 という訳で、準備が整いました。さて、組み上げて行きましょう。 まずはRaspberry pi zero と pHAT DACの接続です。 全部のピンを接続しちゃってもいいのですが、基盤を見ながら、必要そうなやつだけ半田付けしていきます。まずはI2Sは必須ですね、あとは電源。具体的には以下のピンをハンダ付けします。 Pin 1 (3.3V) Pin 2 (5V) Pin 12 (GPIO 18) Pin 35 (GPIO 19) Pin 39 (Ground) Pin 40 (GPIO 21) LCDもサイトの説明通りハンダ付けすればOK。I2Cなので接続する本数は信号線2本、電源2本の合計4本だけで済みます。

後はDACのLINE OUTをヘッドフォンアンプのLINE INへ接続します。

では、肝心のOS部分とプログラミング部分です。今回オーディオ用ディストリビューションとしてVolumioを採用しました。ただここで問題が発生。現状VolumioのイメージはRaspberry Pi Zeroに対応しておらず、起動しないことがわかりました。 対応としては、まずRaspberry Pi 2で起動し、アップデートを済ませてからZeroで起動する必要があります。結構面倒ですね。また、バージョンアップによりSambaのバージョンが上がり、動作しなくなります。これも面倒な修正が必要です。 まずは、Volumio 1.55 のイメージをダウンロードしましょう。 https://sourceforge.net/projects/volumio/files/Raspberry%20PI/1.5/Volumio1.55PI.img.zip/download ダウンロードしたイメージを16GB SDHCカードに書き込みます。次はMacの場合の例です。rdisk2はSDカードの場所です。環境にあわせて変更する必要があります。間違った場所を指定すると、PCのデータが消えてしまうので十分に注意しましょう。SDカードを指す前と、差した後にそれぞれdf -hを実行して、差分がSDカードだとわかります。

df -h
sudo diskutil unmount /dev/disk2s1
sudo dd bs=1m if=Volumio1.55PI.img of=/dev/rdisk2

このSDカードとRaspberry Pi 2で起動します。

su -

コマンドで rootになります。パスワードは volumio です。 次のコマンドを実行します。

apt-get -y update && apt-get -y upgrade && apt-get dist-upgrade && apt-get -y autoremove && apt-get -y autoclean

binutilsをインストールします。

apt-get install binutils

rpi-updateを実行します。

rpi-update

再起動します。しかし、エラーが発生してSMBが起動しません。次のコマンドで修正します。

apt-get install samba samba-doc samba-common smbclient
apt-get remove samba samba-doc samba-common smbclient --purge
apt-get autoclean
apt-get autoremove
apt-get install samba samba-doc samba-common smbclient

これで下準備はOKです。poweroff後Raspberry Pi 2からSDカードを抜き、Pi Zeroに挿し直します。 無事起動したら、pHAT DACの設定を行います。

su -
curl -sS get.pimoroni.com/phatdac | bash

パイゼロと同じネットワーク上のPCから、ラズパイのアドレスを打ち込んで volumioの画面を開きます。 設定します。 MENU > Systemを開きます。 I2S driverの項目で I2S DACにHifiberryを選択します。

MENU > Turn offを開きます。 REBOOTします。   16GBのSDHCを使いましたが、ほとんど使われていません。これを使えるようにしましょう。

volumio@volumio:~$ df -h
 Filesystem Size Used Avail Use% Mounted on
 /dev/root 1.5G 1.1G 379M 74% /
 devtmpfs 237M 0 237M 0% /dev
 tmpfs 49M 260K 48M 1% /run
 tmpfs 5.0M 4.0K 5.0M 1% /run/lock
 Ramdisk 256M 0 256M 0% /run/shm
 /dev/mmcblk0p1 75M 30M 46M 40% /boot

パーティションを用意

root@volumio:~# fdisk /dev/mmcblk0

 

root@volumio:~# fdisk /dev/mmcblk0
Command (m for help): p(現在のパーティションを確認)
Disk /dev/mmcblk0: 15.8 GB, 15819866112 bytes
4 heads, 16 sectors/track, 482784 cylinders, total 30898176 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00043284
        Device Boot      Start         End      Blocks   Id  System
/dev/mmcblk0p1   *        2048      155647       76800    b  W95 FAT32
/dev/mmcblk0p3          155648     3411967     1628160   83  Linux
Command (m for help): n(新しいパーティションを作成)
Partition type:
   p   primary (2 primary, 0 extended, 2 free)
   e   extended
Select (default p): p(プライマリ)
Partition number (1-4, default 2): 4(パーティション4)
First sector (3411968-30898175, default 3411968): エンター押す
Using default value 3411968
Last sector, +sectors or +size{K,M,G} (3411968-30898175, default 30898175): エンター押す
Using default value 30898175
Command (m for help): p(結果を確認)
Disk /dev/mmcblk0: 15.8 GB, 15819866112 bytes
4 heads, 16 sectors/track, 482784 cylinders, total 30898176 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00043284
        Device Boot      Start         End      Blocks   Id  System
/dev/mmcblk0p1   *        2048      155647       76800    b  W95 FAT32
/dev/mmcblk0p3          155648     3411967     1628160   83  Linux
/dev/mmcblk0p4         3411968    30898175    13743104   83  Linux
Command (m for help): w(作業を書き込んで終了)
The partition table has been altered!
Calling ioctl() to re-read partition table.
WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

再起動します。

reboot

再起動したら、次のコマンドで新規パーティションをフォーマットします。

mkfs.ext4 /dev/mmcblk0p4
volumio@volumio:~$ su -
Password:
root@volumio:~# mkfs.ext4 /dev/mmcblk0p4
mke2fs 1.42.12 (29-Aug-2014)
Discarding device blocks: done
Creating filesystem with 3435776 4k blocks and 860160 inodes
Filesystem UUID: ce7a4f52-e1e6-4858-9cb4-27b43a74956a
Superblock backups stored on blocks:
    32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

パーティションのマウント設定を行います。

root@volumio:~# vi /etc/fstab
# /etc/fstab: static file system information.
#
#/dev/mmcblk0p3 /               ext4    noatime,discard,data=writeback,journal_async_commit,nouser_xattr,barrier=0,errors=remount-ro 0       1
/dev/mmcblk0p3 /               ext4    noatime,nouser_xattr,errors=remount-ro 0       1
/dev/mmcblk0p1  /boot        vfat    utf8,user,rw,umask=111,dmask=000            0       0
/dev/mmcblk0p4  /mnt/USB  ext4     defaults   0  0
Ramdisk   /run/shm        tmpfs   defaults,size=256M,noexec,nodev,nosuid        0       0

設定を有効にするため再起動します。

reboot

13GBの領域が確保されました。

volumio@volumio:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       1.5G  1.1G  379M  74% /
devtmpfs        237M     0  237M   0% /dev
tmpfs            49M  264K   48M   1% /run
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
Ramdisk         256M     0  256M   0% /run/shm
/dev/mmcblk0p1   75M   30M   46M  40% /boot
/dev/mmcblk0p4   13G   33M   13G   1% /mnt/USB

  samba設定ファイルの変更 sambaの再インストールにより設定ファイルが書き換わっています。先ほど拡張したmicroUSBの領域が他のコンピュータからアクセスできるように設定しましょう。

nano /etc/samba/smb.conf

[share]
 comment = MPD directory (/mnt/USB/)
 path = /mnt/USB/
 guest ok = yes
 read only = no
 writable = yes
 available = yes
 browsable = yes
 public = yes
 follow symlinks = yes
 wide links = yes
 create mode = 0777
 directory mode = 0777
 share modes = yes

LCDのインストール

sudo apt-get install python-smbus
sudo apt-get install i2c-tools
volumio@volumio:~$ sudo i2cdetect -y 1
 0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

20が表示されていれば接続はOK

サンプルコードの実行
sudo apt-get update
sudo apt-get install build-essential python-dev python-smbus python-pip git
sudo pip install RPi.GPIO
su -
git clone https://github.com/adafruit/Adafruit_Python_CharLCD.git
cd Adafruit_Python_CharLCD
sudo python setup.py install

実行

cd examples
sudo python char_lcd_plate.py

サンプルが実行されます。このサンプルを見ながら、LCDディスプレイに現在表示中の曲名を表示するプログラムを作成していきます。

ポイントは、Music Player Daemon(mpc)コマンドです。このコマンドで曲名の取得、次曲、前曲、再生、一時停止などを行います。

コードは次のようになりました。突貫で作成したので所々無駄なコードが残っています。

#!/usr/bin/python
# Example using a character LCD plate.
import time
import os
import sys
import threading
import commands
import Adafruit_CharLCD as LCD
import logging
import time
import subprocess
import signal
isLcdOn = True
runFlg = True
lcdTimer = None
#logging.basicConfig(level=logging.DEBUG,
#                    format='(%(threadName)-10s) %(message)s',
#                    )
# Initialize the LCD using the pins
lcd = LCD.Adafruit_CharLCDPlate()
def lcd_off():
    global lcdTimer
    lcd.set_backlight(0)
    lcdTimer=None
def show_song():
    while runFlg:
        # show song name. wait change event by 'mpc idle' command
        global idle
        global lcdTimer
        idle = subprocess.Popen('mpc idle player', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        sts = idle.communicate()
        if sts !='':
            st = commands.getoutput('mpc | kakasi -Jk -Hk -Kk -Ea -s -i utf-8 -o sjis')
            st = st.replace('(kigou)','-')
            spl=st.splitlines(True)
            if len(spl)== 3:
                song=spl[0].split(" - ",1)
                lcd.clear()
                lcd.message(song[1][0:16].strip() + '\n' + song[0][0:16].strip())
                if not(lcdTimer is None):
                    lcdTimer.cancel()
                lcdTimer=threading.Timer(5,lcd_off)
                lcdTimer.start()
    print('stop show_song thread')
# create some custom characters
lcd.create_char(1, [2, 3, 2, 2, 14, 30, 12, 0])
lcd.create_char(2, [0, 1, 3, 22, 28, 8, 0, 0])
lcd.create_char(3, [0, 14, 21, 23, 17, 14, 0, 0])
lcd.create_char(4, [31, 17, 10, 4, 10, 17, 31, 0])
lcd.create_char(5, [8, 12, 10, 9, 10, 12, 8, 0])
lcd.create_char(6, [2, 6, 10, 18, 10, 6, 2, 0])
lcd.create_char(7, [31, 17, 21, 21, 21, 21, 17, 31])
# Show some basic colors.
lcd.set_color(1.0, 0.0, 0.0)
lcd.clear()
# Make list of button value, text, and backlight color.
buttons = ( (LCD.SELECT, 'Play/Pause', 'mpc toggle'),
            (LCD.LEFT,   'Prev'  , 'mpc prev'),
            (LCD.UP,     'LCD ON/OFF', ' '),
            (LCD.DOWN,   'POWER OFF ?\nYES           NO'  , 'mpc seek +00:00:10'),
            (LCD.RIGHT,  'Next' , 'mpc next') )
print('Press Ctrl-C to quit.')
t=threading.Thread(target=show_song)
t.daemon = True
t.start()
# do finish process when pressed Ctrl+C
def func_stop(signal, handler):
    lcd.clear()
    lcd.set_color(0.0, 0.0, 0.0)
    runFlg = False
    idle.terminate()
    print('ext')
    time.sleep(1)
    sys.exit(0)
signal.signal(signal.SIGINT, func_stop)
signal.signal(signal.SIGTERM, func_stop)
while True:
    # Loop through each button and check if it is pressed.
    for button in buttons:
        if lcd.is_pressed(button[0]):
            lcd.set_backlight(1)
            # Button is pressed, change the message and backlight.
            logging.debug(button[1])
            if button[0]!=LCD.UP:
                lcd.clear()
                lcd.message(button[1])
            #lcd.set_color(button[2][0], button[2][1], button[2][2])
            if button[0]==LCD.UP:
                if isLcdOn==True:
                    lcd.set_backlight(0)
                    isLcdOn=False
                    print('LCD OFF')
                else:
                    lcd.set_backlight(1)
                    isLcdOn=True
                    print('LCD ON')
            elif button[0]==LCD.DOWN:
                while True:
                    if lcd.is_pressed(LCD.LEFT):
                        lcd.message('power off')
                        time.sleep(0.5)
                        lcd.set_backlight(0)
                        os.system('poweroff')
                    elif lcd.is_pressed(LCD.RIGHT):
                        break
            else:
                os.system(button[2])
            time.sleep(1.0)

Raspberry Pi Zeroの起動時にこのスクリプトを起動するようにしておけばバッチリです。 最初はアクリル板で作成していましたが、同好会メンバーが木箱を作ってくれました。木箱にあわせてアクリルをミラー調スプレーで塗装すると、かなりいい雰囲気がでました! [caption id="attachment_2561" align="alignnone" width="300"] 木製のケースに収めている所。ミラー調のアクリルと合わせていい雰囲気です。[/caption] 音楽再生アプリケーションとしては定番のVolumioを使っています。そのため、Volumioでできることはひと通り出来ます。持ち歩かない場合にはネットワークオーディオとして楽しむことが出来ます。 ・SDカード内の音楽ファイル再生 ・USB接続されたストレージ内の音楽再生 ・NAS上の音楽ファイル再生 ・ネットラジオDLNA対応 ・Airplay対応 ・ブラウザ等からコントロール可能 [caption id="attachment_2569" align="alignnone" width="300"] PCのブラウザやiPhoneから操作することも可能[/caption] さて、これで一通り完成です!今回はLCDディスプレイが結構大きくて、採用すべきかどうか最後まで悩みました。最終的には利便性を優先して採用しましたが、全体の半分近いサイズを占有しており、まだ満足してません。今後はLCDディスプレイを「電子ペーパー」に置き換え、劇的にコンパクトなプレイヤーに仕上げたいと画策中です!