Raspberry Pi Zero で USB to Airplay Audio トランスミッターを作る備忘録です.
Linux の USB Gadget 機能を使って,PCからは USB Audio として認識され,送り込まれた音声を適当な Airplay 再生機器に送信する中継機(トランスミッター)を作ってみた備忘録です.
接続したPCから中継機を操作できるよう,単に USB Audio として認識されるだけでなく,シリアル接続で操作できるようにもしています.
使用機材
使用機材は Raspberry Pi Zero WH と64GBの microSD カードですが,microSD の容量は8GBでも大丈夫なはずです.また,Raspberry Pi Zero 2, Raspberry pi 4, 5 でもできると思います.
Raspberry pi OS の導入
使用した OS は純正の Raspberry Pi OS Lite です.導入自体は,Raspberry Pi Imager
を使って最新安定版(legacy はダメ)のものを microSD に書き込むだけですし,この辺りの説明は例えば
などを見れば詳しい解説があるので,本ページでは行いません.また,最近はデフォルトで作られる
- USER: pi
- PASSWORD: raspberry
を無効化することが推奨ですが,今回はこのデフォルトのユーザーを使って設定していくことにします.
Raspberry pi OS の最新開発版へのアップデート
2024/12/8 追記:安定版の pipewire の ver. は 1.2.4 と大幅にアップデートされましたので,この手順は不要です.
Raspberry pi OS の本稿執筆時点の最新安定版は debian 12 bookworm ベースですが,残念ながらこの最新安定版に含まれている pipewire は ver. 0.3.65 で,少なくとも私が試した限りでは,まともに Airplay 機器を認識してくれません.
debian 12 bookworm の backport に含まれている pipewire 1.0.5 も試しましたが,残念ながら今度は pipewire 自体が起動してくれません.したがって今回は已む無く,開発版 debian 13 trixie ベースに OS をアップデートしました.
trixie ベースへのアップデートは簡単で,まず,/etc/apt/sources.list を以下のように書き換えます.
$ sudo vi /etc/apt/sources.list deb [ arch=armhf ] http://raspbian.raspberrypi.com/raspbian/ trixie main contrib non-free rpi # Uncomment line below then 'apt-get update' to enable 'apt-get source' #deb-src http://raspbian.raspberrypi.com/raspbian/ trixie main contrib non-free rpi
その後,以下のとおりアップグレードし,再起動するだけです.
$ sudo apt update $ sudo apt dist-upgrade;sudo apt autoremove $ sudo reboot
再起動後,アップグレードできていることを以下で確かめることができます.
$ cat /etc/debian_version trixie/sid
Pipewire の導入
次は,オーディオを Airplay 再生機器に中継を担う Pipewire と Pulseaudio のコントロールコマンドである pactl(パッケージとしては pulseaudio-utils)を導入します.Pipewire は Pulseaudio の置き換(後継?)なので,pactl と合わせて Pulseaudio 関係のパッケージを入れないように注意してください.
具体的には,以下のように導入してから,再起動します.
$ sudo apt install pipewire-audio pipewire-pulse pipewire-alsa libspa-0.2-bluetooth wireplumber pipewire-media-session- pulseaudio-utils
再起動後,Pipewire が起動していることを以下のとおり確かめることができます.Pipewire ver. 1.0.3 が起動していることが分かります.
$ pactl info Server String: /run/user/1000/pulse/native Library Protocol Version: 35 Server Protocol Version: 35 Is Local: yes Client Index: 30830 Tile Size: 65496 User Name: pi Host Name: rbpz2 Server Name: PulseAudio (on PipeWire 1.0.3) Server Version: 15.0.0 Default Sample Specification: float32le 2ch 44100Hz Default Channel Map: front-left,front-right Default Sink: alsa_output.platform-20980000.usb.stereo-fallback Default Source: alsa_input.platform-20980000.usb.stereo-fallback Cookie: b402:f37b
ローカルネットワーク内の Airplay 機器のチェック
ここで,ローカルネットワークに接続された Airplay 機器が pipewire からどのように認識されるのかをチェックします.
まずやるべきことは pipewire ROAP Discover モジュールの読み込みですが,これについては以下の記事をご覧ください.
なお,次回以降自動的に ROAP Discover モジュールが読み込まれるよう,~/.config/pipewire/pipewire.conf.d/raop-discover.conf を以下の通り設定してください.
$ vi ~/.config/pipewire/pipewire.conf.d/raop-discover.conf context.modules = [ { name = libpipewire-module-raop-discover args = {} } ]
ROAP Discover モジュールを読み込むことで,ローカルネットワーク内の Airplay 再生機器を pipewire に認識させることができます.認識されている Airplay 再生機器としてどのようなものがあるかは,pactl list sinks を用いて調べることができます.
$ pactl list sinks ... Sink #173 State: SUSPENDED Name: raop_sink.iPhone4sw.local.192.168.1.100.5000 Description: AirSpeaker Driver: PipeWire Sample Specification: s16le 2ch 44100Hz ...
上の例だと,再生機器として,raop_sink.iPhone4sw.local.192.168.1.104.5000 が認識されていることが分かります(Airplay 機器は必ず roap_sink から始まる名前になるようです).
認識されない場合はそもそも Raspberry pi がローカルネットワークに接続されているか否かやファイヤーウォールの設定を確かめてください.
Linux USB Gadget 機能の有効化
次は,PC等から Raspberry pi が UAB Audio として認識されるように,Linux USB Gadget 機能を有効化します.
まず,/boot/firmware/config.txt ファイルを以下の内容を追記します(以前は /boot 直下のファイルを編集していましたが,現在は /boot/firmware 以下になっているようです).
$ sudo vi /boot/firmware/config.txt ... [all] # Enable DWC2 USB Driver dtoverlay=dwc2
また,/etc/modules に以下の内容を追記します.
$ sudo vi /etc/modules ... # Enable DWC2 USB Driver dwc2 libcomposite
ここで再起動し,モジュールが読み込まれていることをチェックしておきます.
$ sudo reboot $ lsmod | grep dwc2 dwc2 172032 0 roles 16384 1 dwc2
読み込めていることを確認できたら, /usr/local/bin/on.MFCGadget ファイルを以下の内容で作成し,実行権限をつけます(44,100 Hz/16bit の USB Audio として認識されるよう設定しています.理由は次々節をご覧ください).
$ sudo vi /usr/local/bin/on.MFCGadget #!/bin/sh cd /sys/kernel/config/usb_gadget /usr/bin/mkdir g1 cd g1 /bin/echo 0x1d6b > idVendor /bin/echo 0x0104 > idProduct /bin/echo 0x0100 > bcdDevice /bin/echo 0x0200 > bcdUSB /usr/bin/mkdir strings/0x409 /bin/echo "24051401"> strings/0x409/serialnumber /bin/echo "labohyt" > strings/0x409/manufacturer /bin/echo "Multifunction Composite Gadget" > strings/0x409/product /bin/echo "0xEF" > bDeviceClass /bin/echo "0x02" > bDeviceSubClass /bin/echo "0x01" > bDeviceProtocol /usr/bin/mkdir -p configs/c.1 /usr/bin/mkdir -p configs/c.1/strings/0x409 /usr/bin/mkdir -p functions/acm.GS0 /usr/bin/mkdir -p functions/uac2.usb0 /bin/echo 44100 > functions/uac2.usb0/c_srate /bin/echo 2 > functions/uac2.usb0/c_ssize /bin/echo 3 > functions/uac2.usb0/c_chmask /bin/echo 44100 > functions/uac2.usb0/p_srate /bin/echo 2 > functions/uac2.usb0/p_ssize /bin/echo 3 > functions/uac2.usb0/p_chmask /usr/bin/ln -s functions/acm.GS0 configs/c.1 /usr/bin/ln -s functions/uac2.usb0 configs/c.1 /usr/bin/ls /sys/class/udc > UDC $ sudo chmod +x /usr/local/bin/on.MFCGadget
また,このコマンドが起動時に自動的に実行されるよう /etc/rc.local に以下の内容を追記します.
2024/12/9 追記:Raspberry Pi OS の最新安定版は rc.local のサポートが削除されたようなので,systemd に移行が必要です.具体的には,
$ sudo vi /etc/systemd/system/on.MFCGadget.service [Unit] Description=On Multifunction Composite Gadget After=local-fs.target [Service] ExecStart=/usr/local/bin/on.MFCGadget Type=oneshot RemainAfterExit=true [Install] WantedBy=multi-user.target $ sudo systemctl enable on.MFCGadget.service
のようにすれば良いと思います.
$ sudo vi /etc/rc.local ..... # Print the IP address _IP=$(hostname -I) || true if [ "$_IP" ]; then printf "My IP address is %s\n" "$_IP" fi /usr/local/bin/on.MFCGadget exit 0
この状態で,いったん Raspberry pi の電源を切り,以下の写真のようにしてPC等につなぐと,Raspberry pi が USB Audio かつ Serial 接続可能機器として認識されるはずです.
PCの OS が Linux (Ubuntu 22.04)の場合は以下の通りです.
$ lsusb ... Bus 001 Device 008: ID 1d6b:0104 Linux Foundation Multifunction Composite Gadget ... $ pactl list sinks ... シンク #61 状態: SUSPENDED 名前: alsa_output.usb-labohyt_Multifunction_Composite_Gadget_24051401-02.analog-stereo 説明: Multifunction Composite Gadget Analog Stereo ドライバー: PipeWire サンプル仕様: s16le 2ch 44100Hz チャンネルマップ: front-left,front-right ... $ ls /dev/ttyACM* /dev/ttyACM0
Windows の場合は USB Audio 昨日は Source/Sink,USB Serial ポートは COM3 として認識されましたが,特に Serial の認識については機器によりポート番号が違ってくるのではないかと思います.
PC から Serial 接続可能にする
前節で Linux USB Gadget の Serial を有効化しましたが,このままだと Raspberry pi OS を Serial ポート経由で操作できないので,これを可能にするための設定を行います.なお,本件について詳しくは以前の以下の記事をご覧ください.ここでは最低限の手順のみ示します.
まず,systemctl edit コマンドを用いて,systemd の設定ファイルを以下のように編集します.注意として,必ず ## Edits below this comment will be discarded の上に [Service] 以下の内容を記してください.
$ sudo systemctl edit [email protected] ### Editing /etc/systemd/system/[email protected]/override.conf ### Anything between here and the comment below will become the contents of the drop-in file [Service] ExecStart= ExecStart=-/sbin/agetty --autologin pi --keep-baud 115200,57600,38400,9600 - $TERM ### Edits below this comment will be discarded ....
その後,[email protected] を以下の手順で自動起動するよう設定します.
sudo systemctl enable [email protected] sudo systemctl start [email protected]
正常に起動していることを以下のように確認します.
$ sudo systemctl status [email protected] ● [email protected] - Serial Getty on ttyGS0 Loaded: loaded (/usr/lib/systemd/system/[email protected]; enabled; preset: enabled) Drop-In: /etc/systemd/system/[email protected] └─override.conf Active: active (running) since Thu 2024-06-06 11:25:28 JST; 25min ago Docs: man:agetty(8) man:systemd-getty-generator(8) https://0pointer.de/blog/projects/serial-console.html Main PID: 729 (login) Tasks: 0 (limit: 387) CPU: 212ms CGroup: /system.slice/system-serial\x2dgetty.slice/[email protected] ‣ 729 /bin/login -f --
これで Linux や Windows から Raspberry pi のシェルにユーザー名,パスワードの入力なしで接続ができるようになっているはずです.
loopback を設定する
次は Pipewire のサンプリングレートと loopback の設定を行います.
まず,Pipewire のデフォルトサンプリングレートを 44,100 Hz に設定します.これは以下のように行います(~/.config/pipewire/pipewire.conf の方が良いかもしれません).
$ sudo vi /usr/share/pipewire/pipewire.conf ... ## Properties for the DSP configuration. default.clock.rate = 44100 default.clock.allowed-rates = [ 44100 ] #default.clock.quantum = 1024 #default.clock.min-quantum = 32 #default.clock.max-quantum = 2048 ....
理由は,私が所有している Airplay 再生機器が 44,100 Hz/16bit のみにしか対応していないことです.なお,Airplay 2 対応機器の場合は 48,000Hz/24bit にも対応しているようなので,この場合は 44,100 ではなく Pipewire のデフォルトの値のままでも良いかもしれません(音源のサンプリングレートやビット深度の問題もあるので一概には言えないと思います).
次に Pipewire の loopback module の設定を行うための実行ファイル on.AirPlayTransmitter を以下の内容で作成し,実行権限を付けます(前々々節で取りあげた ROAP Discover モジュールで見つかった Airplay 再生機器 raop_sink.iPhone4sw.local.192.168.1.100.5000 の場合の設定です.IPアドレスが変わる可能性を考慮した形にしています).
$ sudo vi /usr/local/bin/on.AirPlayTransmitter #!/bin/bash while [ "$SOURCE_NAME" = "" -o "$SINK_NAME" = "" ]; do SOURCE_NAME=`pactl list sources|grep -n "Name: alsa_input.platform"|cut -d":" -f3|sed 's/ //g'` SINK_NAME=`pactl list sinks|grep -n "Name: raop_sink.iPhone4sw"|cut -d":" -f3|sed 's/ //g'` done pactl load-module module-loopback source=$SOURCE_NAME sink=$SINK_NAME $ sudo chmod +x /usr/local/bin/on.AirPlayTransmitter
ついでに,OFF にするためのコマンド
$ sudo vi /usr/local/bin/off.AirplayTransmitter #!/bin/bash pactl unload-module module-loopback $ sudo chmod +x /usr/local/bin/off.AirplayTransmitter
も作成しておきます.
また,この実行ファイルがユーザー権限で1度だけ実行されるよう systemctl edit を用いて設定します.これは以下のように行います.
$ systemctl --user edit pipewire-pulse.service ### Editing /home/pi/.config/systemd/user/pipewire-pulse.service.d/override.conf ### Anything between here and the comment below will become the contents of the drop-in file [Service] ExecStartPost=/usr/local/bin/on.AirPlayTransmitter ### Edits below this comment will be discarded ......
自動ログインの設定
最後にユーザー pi で自動ログインする設定を行います.Pipewire サービスがユーザー権限で動作されることが理由です.
この自動ログインの設定は,前々節と同様に行うことももちろんできますが,raspi-config を使ってやる方が簡単です.実際の手順については,以下のページが分かりやすいと思います.
利用方法と使用雑感
以下の写真のように USB で PC につなぐだけで使えます.
PCから見ると,UAC2 ドライバに対応した USB Audio Interface として見えますので,接続後しばらく待ってから音声の出力先を適切に選べば,設定した Airplay 再生機器から音声が出力されるはずです.
実際に試したところ,Windows や Linux だけでなく macOS, iPad OS, iOS でも使えました(もちろんこれらの機器ははじめから Airplay に対応しているので実用上の意味はありません).また,Android や ChromeOS でも使えるようです.
また,本トランスミッターの設定の変更が必要なら,Linux の場合は /dev/ttyACM0,Windows の場合は COM* 経由でシリアル接続すれば Raspberry pi OS のシェルにログインできます.
使い勝手ですが,挿すだけで使えるので,結構実用的です.私の場合は,Windows を自宅で使うときに利用する USB Dock に本アダプターを挿しっぱなしにしていて,使いたいときはパッと Audio の出力先を切り替えて使うことが多いです.もちろん,アダプターが起動してくるまでしばらく待たないといけない(Raspberry pi zero 2W を使えばかなり改善されますし,Raspberry pi Zero って低消費電力なので常時起動でも大した電気代にはなりません)ことや,Youtube とかで使うと映像にかなり遅れて音声が出力されるなどの問題はありますが(Airplay のバッファを調整することができる機器ならかなりましにはなる),手軽に使えるのでそこまで気にはなっていません.
なお,本アダプター,この blog を定期的にみておられる方ならお気付きの方もおられると思いますが,以前の記事で作成した USB to Bluetooth Audioトランスミッターの進化形です.
この USB to Bluetooth Audio トランスミッターの場合はほとんど実用にはならなかったのですが,今回は結構実用的ですし,設定方法も以前よりは改善されていると思います.また,USB to Bluetooth Audio トランスミッターなら,より使い勝手のよい製品がいろいろ市販されていますが,今回は類似の製品がほとんど見つかりません(唯一 WiiM Pro が Airplay 送信機能に対応しているらしい).
WiiM Pro って決してお安くはありませんので,このためだけに購入はためらわれると思います.本 USB to Airplay トランスミッターなら全部合わせても4000円程度,失敗しても Raspberry pi zero WH を別用途に使えるので,まぁ悪くはないのではないかと思います.もちろん,類似製品がほとんどないのは,需要が全くないからかもしれないことは心に棚を作って考えないことにしているんですけどね!
以上!