
Twitterやインスタを見ていると皆さん短いやり取りなのに的確にコミュニケーションがとれていて、何というか人間の通信プロトコルも変化していると感じます。おじさんはConnection TimeoutとかAccess Deniedとかされないようにしたいものです。体のどこかにUSBポートが隠されていてapt upgradeなんて入力できないもんかね。
(訳:今回のブログは長いよ!)
カーオーディオで愛用してきたUSB-DDCを新調したものの、Roon ARCで動作せず対策しました、というネタです。
ついでにスマホから生えるUSBケーブルが野暮ったいので、音質を確保しながらスマートにしてみようと。
iOS版Roon ARCはAirPlayに対応しており、AirPlayレシーバーを用意すればワイヤレスかつ良い音で聴けます。市販製品としては
SOUNDFORM CONNECT (Belkin)
WiiM Mini (WiiM)
などで、これらは光デジタル出力を搭載しています。自分はUSB-DDCを使うため、USBトランスポートになるAirPlayレシーバーを探したらそれもちゃんとあって、
ZEN Stream (iFi audio)
や、ラズパイユーザーにはお馴染みのVolumioなどが対応しているようです。
しかし、これらをAirPlayで使ったときにロスレス伝送なのか、さらにビットパーフェクトなのかとなると判然としない。ネットを漁ってみると、
「AirPlay1は16bit/44.1kHzのALAC、AirPlay2は現時点ではAAC」
なんて情報もあり。
古い方が高音質ってこと?現時点って何?Appleさんが詳細な仕様を公開していないので本当のところがわかりません。
そこで、AirPlay1専用に設定することで音質を担保できそうなラズパイで自作することにしました。AirPlay2のメリットである低遅延やマルチルーム再生は捨て、できる限り音質重視となるよう構成してみます。途中でALACにしただけではビットパーフェクトにならないこともわかったので、その対策も行っています。
ハードウェア選定
AirPlayの受信用にWi-Fi、オーディオ出力用にUSBが1本あればよく、今回はラズパイシリーズの中でも小型の
Raspberry Pi Zero 2 W
を使います。定価$15、国内でも3,000円以下で買え「た」ボードですが、半導体不足のため品薄が続いています。Amazonなどは転売ヤーか!って位の価格になってしまっていて、今買うのはおススメしません。
ラズパイで似たスペックなら3A+あたりはまだ良心的?な価格で入手でき、この後紹介するOSのページにあるハードウェアを試すのも面白いかも。(次のブログでNanoPiを使いました。)
Zero 2でCPUがクアッドコアとなり、ラズパイ3に近い性能があります。ただしRAMが512MBと少ないのでPCのようにウィンドウをパカパカ開いて動画を見たりするには向かず、デスクトップレス(GUI無し)で使うのが良いと思います。
USB(片方は電源)がmicro、HDMIがminiと小型コネクタになっており、変換ケーブルか変換コネクタを用意しておきます。microSDカードは現在購入できる最小クラスの4GBか8GBもあれば十分で、逆に64GBとかだと不具合の可能性も。
起動用microSDカード作成
OSとして、今回は「純正」のRaspberry Pi OSよりさらに軽量(=RAMの使用量が少ない)の
DietPi
をインストールしました。
ライトウェイトにこだわり32bit版のARMv7用を選択。
公式サイトからダウンロードした7zipを展開して取り出したimgファイルで起動用カードを作成します。

microSDカードへの書き込みには
Raspberry Pi Imagerを使用しました。Raspberry Pi OSなどをダウンロードから書き込みまでこれ一つで実行できるアプリですが、

一番下、予めダウンロードしておいたimgファイルを焼くこともできます。
接続と初期設定
こんな環境で設定してゆきます。完成したらラズパイとiPhoneを別のネットワーク(車載ルーター・モバイルルーターなど)に持ち出すイメージです。
(家の中でRoonだけ使うならAirPlayよりRoon Bridgeの方が高音質です。)

カードを基板にセット、ケースに入れてケーブル類を接続します。このksy製ケースも後で入手したflirc製も同じで、ケースに入れるとカードを交換できないのは誤算でした。基板を外す時は無理な力をかけないよう要注意。

最初はHDMIポートにモニターやTV、USBポートにキーボードをつないだ「ローカルコンソール」で設定してゆくのが確実です。
少し慣れた人なら、カード作成時に設定ファイルを編集しておきキーボード無しでWi-Fiに接続させる「ヘッドレスインストール」に挑戦しても良いでしょう。その場合でも設定中HDMIだけはつないでおいた方が起動時の挙動を把握でき安心かと。USB接続のHDMIキャプチャーボード(2,000円くらいで買える)も使えます。

起動しました。DietPiはDebian系LinuxなのでコマンドなどRaspberry Pi OSに近い感覚で使えます。
表示されている初期ユーザーとパスワードでログインすると、

最初にOS更新が走りますがネットワーク接続がないとして中断します。この状態では何か設定を変更するとネットにアクセスしようとして数10秒タイムアウト待ちとなることがあります。「フリーズした!」とあわてて電源を落としたりせず、まずは家のWi-Fiアクセスポイントに接続します。
「Network Settings」を選択して「OK」(カーソルキーとTABキーが使えます)

IPv6はOFFで良いでしょう。ONにする時は外部から侵入されないようルーターの設定を確認しておきます。

Wi-Fiを有効にして設定に入ります。

アクセスポイントを5つまで登録できます。複数登録した場合は起動時に見つかったAPの中で最も電波の強いものに接続するので、家以外のモバイルルーターなどもここで登録して大丈夫です。(後で追加も可能)
SSIDとパスキーを入力、完了すると再起動を促され、再度ログインするとOS更新を行って続行します。

サーベイ(情報提供)に参加するかどうか

ソフトウェアインストール用のパスワードを変更するか
この時点ではまだキーボードを設定していないのでキーと文字が一致していない可能性があり、スキップした方が良いでしょう。(2023/4/23追記)最新のv8.16で対策されたようです。

rootおよびdietpiユーザー用のパスワードを変更するか。ここも飛ばします。

UART(シリアル)は使用しないので「Yes」で無効にします。

ここがメニューの最上位、DietPi-Softwareです。
SSHサーバーは軽量の「dropbear」がデフォルトで有効になっています。今回の使い方ならこのままで良いですし、ファイルのアップロードなどを行う場合はOpenSSHに変更することも可能です。

最初にDietPi-Configを選択し、地域とキーボードを設定します。

キーボードは使っているものに合わせます。日本語キーボードあるあるで、設定が違うとパイプ文字「|」が入力できなかったりします。

USBオーディオを使うため「Audio Options」でALSAをインストールしておきます(DietPi-Softwareからのインストールも可能)。その他、先ほどスキップしたパスワード変更なども行えます。

全て設定し終えたらDietPi-Softwareまで戻って「Install」

ソフトウェアを選択していないので「最小インストールでいいか?」と聞かれています。「OK」でインストールが始まります。
ここまでコマンド入力も設定ファイル編集も一切なく完了です。至れり尽くせりですね。
ローカルコンソールでの設定はここまで。インストールが終了しコマンドプロンプトに戻ったら、logoutしキーボードを外してOKです。
USBオーディオ接続
キーボードをつないでいたUSBポートを空けるため、ここからはネットワーク上のPCからリモートコンソールを開いて設定してゆきます。

コンソール用アプリの
PuTTYでラズパイのIPアドレス(ローカルコンソールの画面左上に表示されていた数字。スマホアプリのFingなどで調べることもできます)を指定し、接続タイプ「SSH」でコンソールを開きます。
PCのキーボードとモニターを使ってローカルコンソールと同じように操作できます。

ビルトインユーザー「dietpi」でログイン。(以降、プロンプトが$となっているものは「dietpi」、#は管理者権限「root」でのログインを示しています。)
空いたUSBポートにUSBオーディオ機器をつなぎます。
ユーザーをaudioグループに追加。
$ sudo adduser dietpi audio
$ sudo reboot
再起動してSSHで再度ログインし、以下のコマンドで機器のIDを取得します。USBの接続状態が変わるとIDも変わったりするので、最終状態で確認します。
$ aplay -l
**** ハードウェアデバイス PLAYBACK のリスト ****
カード 1: U20F1 [USB Audio 2.0(F1)], デバイス 0: USB Audio [USB Audio]
サブデバイス: 1/1
サブデバイス #0: subdevice #0
「カード」と「デバイス」の番号を覚えておきます。
shairport-syncのインストール
AirPlayレシーバーとして
Shairport Syncを使います。
Appleの非公開技術であるAirPlayを独自に解析して作られたのがオリジナルのShairport(現在は開発停止)で、Shairport Syncはそのフォーク(分家)の一つとなります。
公式のガイド:
Build and Install Shairport Syncに従ってインストールしてゆきます。
旧Shairport Syncのクリーンアップ
実はDietPiでは先ほどのDietPi-Softwareから簡単に導入できるのですが、今回はロスレス伝送を確実にするため手動でインストールします。
DietPi-Software用の設定が残っていると不具合の元になるため、ガイドの 1.Prepare の手順に従いクリーンアップを実行しておきます。(DietPi-Softwareからを含め初回のインストールであればおそらく不要です。)
ビルド用ツールとlibalacのインストール
Shairport Syncの作者さんが公開している
Apple純正のALACデコーダをビルドします。
$ sudo apt-get install build-essential git autoconf automake libtool
$ git clone https://github.com/mikebrady/alac.git
$ cd alac
$ autoreconf -fi
$ ./configure
$ make
$ sudo make install
$ sudo ldconfig
$ cd
長いコマンドはここで選択・コピーしてSSHのウィンドウ上で右クリックするとペーストできます。
ライブラリのインストール
ガイドで「classic」と書かれている手順でAirPlay1対応に必要なライブラリをインストールします。
$ sudo apt-get install --no-install-recommends libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
(2023/5/7追記)畳み込み演算を行う場合は以下のライブラリを追加しておきます。
libsndfile1-dev libglib2.0-dev
ん?なんでリサンプル用のライブラリであるsoxrが必要なんだ?
公式のReadmeと掲示板のスレッドに説明がありました。
Bit perfect stream for a DAC through USB?
AirPlayでは1/44100秒ごとの各サンプルにタイムスタンプが付加されていて、受信側ではこれを自分の時刻と比較することでサンプル数の調整(補間・間引き)を行っているそうです。
例えば受信側のクロックが遅ければデータを間引きし、352個のサンプルを351個に作り直すといったことが行われます。それを高品質に実行するためのsoxrとのこと。なるほどHomePodを2つ使ってステレオにできるのはそういう仕掛けか。
確かに、USBオーディオのようにDACのクロックに合わせてデータを送っていたら複数の機器で同期再生なんて無理ですよね※。ガチのオーディオマニアの人達にはAirPlayが格下に見られるのも、ストリーミング系プロトコルの音質が(ファイル再生に比べると)どこか100%に思えないのもこのあたりの要因があるのかも。
でもAirPlayが劣っているということでは決してなく、ビットパーフェクトとタイムパーフェクト、目指すもの・設計思想の違いでしょう。(自分は前者を取りますけど。)
※USB Audio Classのアシンクロナス-アイソクロナス転送ではUSBのバスクロックでデータが「送りつけられる」わけですが、このモードでは受信側から送信側に対してリアルタイムにデータ量を増減させる通信経路(パイプ)が存在し、DACのクロックスピードに同期してデータを供給できるようになっています。
対してネットワークオーディオの場合、UPnP/DLNA/OpenHomeは受信側がデータを「取ってくる」プロトコルなのでDACのスピードで伝送可能、RAATも詳細は不明ながらクロックのオーナーシップがDACにあることを明言しています。
ビルドとインストール
続いてShairport Sync本体をビルドします。ALACおよびAirPlay1対応を指定しています。
$ git clone https://github.com/mikebrady/shairport-sync.git
$ cd shairport-sync
$ autoreconf -fi
$ ./configure --sysconfdir=/etc --with-alsa --with-soxr --with-apple-alac --with-avahi --with-ssl=openssl --with-systemd
$ make
$ sudo make install
$ cd
(2023/5/7追記)畳み込み演算を行う場合は、configure 時に以下のオプションを追加します。
--with-convolution --with-dbus-interface
設定ファイルの編集
/etc にある設定ファイルを好みのテキストエディタで開いて編集します。
$ sudo nano /etc/shairport-sync.conf
ほとんどの行がコメントアウトされています。設定する行のみ行頭の//を消してパラメータを指定します。自分は以下のようにしました。
general =
{
name = "Shairport Sync(DietPi)";
interpolation = "soxr";
output_backend = "alsa";
alac_decoder = "apple";
ignore_volume_control = "yes";
};
alsa =
{
output_device = "hw:1,0";
output_rate = 44100;
output_format = "S16";
disable_synchronization = "yes";
};
"hw:1,0"が先に調べたUSBオーディオ機器のIDです。
ボリュームを100%に固定、ALAC指定、バックエンドの出力フォーマットを16bit/44.1kHzとしてビットパーフェクトを目指しています。当然ですがiPhoneからのボリュームコントロールは効かないのでUSB以降のアンプなどで音量を絞れるシステムでないとスピーカーが壊れます。
ライブラリも入れていないのでAACでの伝送はやんないんじゃないできない仕様です(どや!)
soxrは高音質なリサンプラですがリサンプル自体無い方が望ましいので、スレッドの情報に従い同期を無効に設定しています。
この場合iPhoneとラズパイのクロックの誤差が補正されないためバッファが次第に埋まってor減ってゆき、いつかは必ずオーバーフロー・アンダーフローすることになります。その時は音が途切れるのかエラーで止まってしまうのか、不具合覚悟の音質優先設定というわけです。
(2023/4/6追記)
USBオーディオ機器によって曲の頭がミュートされる場合は、alsaセクションに以下の設定(autoまたはalways)を加えます。
disable_standby_mode = "auto";
(2023/4/16追記)
いっそ明示的にsoxrを使わせないよう
interpolation = "basic";
としても良いかも。soxrの場合はnサンプルから(n±1)サンプルを生成するのに対して、basicでは単純に時刻が合わなくなったサンプルの補間・間引きとなります。このためsoxrの方が高音質とされていますが、そもそも同期を無効化している以上サンプル数の調整は発生しないはずです。
動作確認
最初はコマンドラインで起動して動作確認します。
$ shairport-sync
※畳み込み演算を設定した場合はここでワーニングが出ますが気にしなくて大丈夫です。

Roon ARCの場合は再生画面の下にあるAirPlayアイコンから

簡単に選択できます。

シグナルパス表示ではAirPlayはロスレスとは見なされないようです。Wi-Fiの向こうの処理がどうなっているかはRoonの知る範囲ではないはずで気にすることもないでしょうけど、実際どこまで出来ているかは気になります。
(2023/6/5追記)気になったので
次のブログで設定を追い込みました。音質に特にこだわる方はご覧ください。
再生確認できたら、Ctrl + Cで終了します。
(2023/4/28追記)

Roonでは再生機器に合わせたフォーマット変換をサーバー(コア)が実行しますが、AirPlay用の変換は現在Roon ARC側で行われています。コアと同様64bit浮動小数点の高精度な演算です。
シグナルパス表示を見ていると、レート変換を行う時などAirPlayに対して24bitで渡しているようなので、(AirPlayが24bitで送信しているかは不明ですが)バックエンドの出力フォーマットも24bitにしてみました。
Audiophilleo2の場合は
output_format = "S24_3LE";
Singxer F-1では
output_format = "S32_LE";
が使えました。
(2023/5/3追記)
16bitを超えて指定できるパラメータはUSBオーディオデバイスによって異なり、次のコマンドで調べることができます。数字は環境に応じて変更してください。
$ cat /proc/asound/card1/stream0

24bit用、16bit用、DSD用の3つのセットがあることが読み取れます。
サービス化
電源ONで自動起動するよう設定します。
$ sudo systemctl enable shairport-sync
$ sudo reboot
再起動して、ラズパイに何も触れずに再生できれば成功です。
Wi-Fiのスリープ無効化
(2023/5/10追記)長時間再生しないとWi-Fiが省電力モードになることがあります。Wi-Fiが「寝て」しまうとAirPlayに応答できなくなり今回のようなヘッドレス構成では「起こす」ことも難しいので、パワーマネジメントを無効化しておきます。
rootでログインし、以下のコマンドを入力。
# iwconfig wlan0 power off

「Power Management」がOFFであることを確認します。
ファイルシステムのROM化
「電源ブチ切○」は登録商標らしいです。気に入らんな。
車載運用する場合シャットダウンコマンドなんてやってられないので、突然電源が落ちてもファイルシステムが破損しないよう対策します。
以前
ラズパイサーバーを製作した時にはスーパーキャパシタによるUPSを準備しましたが、今回は楽曲用のSSDもなくラズパイだけなのでもっと簡単にします。
overlayroot、オーバレイされた(重ねた)ルートファイルシステムという意味で、RO(Read Only)としてマウントしたmicroSDと、RW(Read/Write)属性のRAMディスクを「重ねた」ファイルシステムです。読み出しは普通にSDカードから行い、カード上のファイルに書き込みが発生した時はRAMディスクに書き込みます。書き込み中に電源が切れてもRAM上のデータが消えるだけなのでSDカードは元通り、つまりoverlayrootの動作中に更新したファイルは電源OFFで全て無かったことになります。
DietPi用のoverlayrootはまだありませんが、こちらのスレッドの方法でインストールできました。
Samba Fails on RO filesystem - dietpi.com
rootでログインし以下の通り入力
# apt update
# apt install initramfs-tools
# git clone https://github.com/chesty/overlayroot.git chesty
# cd chesty
# ./install.sh
.bashrcファイルを開きスクリプトを追加
# cd /root
# nano .bashrc
if [ ! -z "${IMCHROOTED}" ]; then
PS1="chroot(${IMCHROOTED})\w:# "
fi
リブートして有効化。
# reboot
mountコマンドでoverlay関連の表示があれば動作しています。
$ mount
overlay on / type overlay (rw,relatime,lowerdir=/overlay/lower,upperdir=/overlay/upper,workdir=/overlay/work)
tmpfs on /overlay type tmpfs (rw,relatime)
/dev/mmcblk0p2 on /overlay/lower type ext4 (ro,relatime)
設定変更などファイルを更新する時は、/boot/config.txtファイルを開き「initramfs init.gz」の行を#でコメントアウトして再起動するとアンインストールされます。
# nano /boot/config.txt
再度有効化するにはインストールスクリプトinstall.shを(1つ前のcdコマンドから)再実行します。安易にconfig.txtだけ戻したら起動不能となりOS導入からやり直す羽目になりました。
車載!
ファイルシステムをROM化しても、起動中それが有効になる前に電源が落ちたらやっぱりファイル破損の原因になります。
車載ではエンジン始動時が問題で、ACCONして起動が始まったちょうどいい(まずい)タイミングでイグニッション=電源OFFとなるので対策します。
以前PCの車載用に製作した、
タイマーリレーによる遅延起動回路を使用し、イグニッション終了後に電源ONとなるようにします。

ラズパイの電源電圧に合わせ、この後に接続するDC/DCコンバータを5V出力品に変更しています。

シガーソケットからの12Vをタイマーリレーに接続、DC/DCコンバータ、電源ノイズフィルターのiPurifier DCを通してラズパイに入力しています。USBはプロセッサー近くのDDCへ。
ラズパイは放熱性能に優れデザインも良いflirc製のメタルケースに入れてみました。こちらもカードスロットに開口部が無く、ケースとCPUを放熱パッドで貼り付けたり基板の取り付けにドライバーを使ったりでカード交換はksy製以上に面倒です。
以上で完成です。
AirPlayはOSの標準機能だけあって、UPnPなどネットワークオーディオのように配信と相性が悪かったりすることもなく

YouTubeの音声も飛ばせたり

ネットラジオを流してみたり、手軽に使えるのが良いですね。
AirPlayの仕様上CD品質までとはなりますが、ハイレゾ音源はRoon ARCで再生すればうまいことダウンコンバートしてくれるので意外に「使える」印象です。
AirPlayもMac相手(AirPlay to Mac)ならハイレゾを通すようなので、いずれラズパイでも可能になるかもしれませんし、これは願望ですがRoon ARCからRAATで出せるようになれば理想的ですよね。
(2023/4/2追記)
追記にさらっと重要事項を書くことの多いブログでごめんなさいw
Shairport Syncはコンボリューション(畳み込み演算)をサポートしており、一部界隈で話題の音響補正ができます。これ楽しそう。
再生側に近い非同期ドメインで演算するので音質的にも有利なのではないかと。
(2023/4/8追記)
RAMの使用量を確認してみました。

・・・極小です。ただでさえ少ないラズパイZeroのRAMの半分をoverlayrootが使うので軽量化を心がけたのですが、これなら64bit版のOSでも全く問題なさそうです。
(2023/4/9追記)
Roon ARCをモバイル回線で使う時などiPhoneとラズパイの間にネットワーク接続がない場合は、iPhoneのテザリングを有効にするとAirPlayで再生できます。

iPhoneの「設定」-「インターネット共有」で表示されるSSIDとパスキーをラズパイに登録します。ただしiPhoneのDHCPではうまくアドレス配布されず、ラズパイのIPアドレスを固定しました。よくある192.168...ではなく画像のような設定になります。
DietPiで静的アドレスにする場合SSIDの登録は一つだけにする必要があるとのことで、家や車載のルーター環境と切り替えて使うことはできません。最近ドコモの車載ルーターの速度が低下していてモバイル回線との併用を目論んでいたのでちょっと残念。
(2023/6/18追記)
Roonについては中の人が見ているんじゃないかと思う位(まさか)改良が進んでいて、不満はほぼ無くなってきたので、細かな要望を一つ。
Roon ARCの「Original Format」設定でストリーミング、またはダウンロード再生したハイレゾ音源をAirPlayに出力した場合、こんなシグナルパスで一発でレート変換されるのですが、
「CD Quality」設定だといったん48kHzにダウンサンプルされて配信され、ARC側で44.1kHzに再度変換されます。

64bitフロートで行われるとはいえ変換処理が2回入るのはちょっと嫌ですね。Roonは音声処理をきっちりやる印象があるので、ここも最適化して頂きたいです。