Raspberry Piで音楽プレーヤーを作るならVolumio一択だと思います。
しかしRaspberry Pi Zero WHには結構きつい重さ。そこで、Bit Trade Oneで売っていたADRSZDACというハイレゾDAC拡張基盤を買い、スタンドアロンの音楽プレーヤーを作りました。
処理の重さを考え、USBストレージに曲を保存して、mpvで再生するという軽い気持ちで始めましたが、なかなかに難しく感じました。
また高音質化ということでADRPM1801Cも買い組み合わせています。
操作にはWeb UIを使わず、4個のタクトスイッチで各種操作します。機能としては、mpv起動、再生/停止、曲送り、曲戻し、シャッフルのON/OFFを物理ボタンで操作します。

結論
完成した構成は、Raspberry Pi Zero WHに「ADRSZDAC」「ADRPM1801C」、USB音楽ストレージ、4個のタクトスイッチを組み合わせたものです。
音楽ファイルはUSBメモリを/mnt/musicへマウントし、Pythonで書いたものが/music以下のサブディレクトリを含めて音楽ファイルを検索します。そしてプレイリストの/tmp/music.m3uを作成し、mpvで再生します。
ボタンは次のように割り当てました。
| ボタン | GPIO | 物理ピン | 操作 |
|---|---|---|---|
| SW1 | GPIO 17 | 11 | mpv 起動 / 終了 |
| SW1 | GPIO 17 | 11 | 再生 / 停止 |
| SW2 | GPIO 27 | 13 | 曲送り |
| SW3 | GPIO 22 | 15 | 曲戻し |
| SW4 | GPIO 23 | 16 | シャッフル ON / OFF |
| GND | ― | 39 | 4個のスイッチ共通 |
mpvはそのままだと電源投入時から起動してしまいます。毎回電源を切るとなると、Raspberry Pi Zeroの省電力性が損なわれてしまいます。
そのため電源は常時いれたまま、systemdでmusic-controller.pyを起動して、SW1を長押ししたときにPythonからmpvを起動します。
つまり、【systemd → music-controller.py → SW1長押し → mpv起動】という構成です。
制作環境
今回の完成版では、OSをRaspberry Pi OS Lite 32bit / Trixieに変更しました。製作途中でBookwormからTrixieに変わってしまったため、再現性が失われると思い、はじめからつくりなおしました。
<ハードウェア構成>
- Windows 11(PowerShell 5.1、主にsshで接続用)
- Raspberry Pi Zero WH
- Raspberry Pi OS Lite 32bit Trixie
- ADRSZDAC(デジタルアナログコンバーター)
- ADRPM1801C(外部システムクロックボード)
- USBメモリ(400曲程度であれば16GBもあれば十分)
- メスヘッダー40PIN
- 6*6mmのタクトスイッチ4つ(4つ足でも足を折ったり切ったりすれば簡単に扱えます)
- プロトタイプ基盤
- 導線(ホームセンターで売っているものを使いました)

<ソフトウェア構成>
- Raspberry Pi OS Lite 32bit(Debian GNU/Linux 13ベース)
- Python3.13.5(Raspberry Pi OS標準搭載)
- python3-gpiozero(GPIOボタン制御用)
- python3-lgpio(gpiozeroからGPIOを操作するためのバックエンド。systemdではGPIOZERO_PIN_FACTORY=lgpioを指定しています)
- mpv 0.40.0(音楽生成エンジン)
ADRSZDACはHiFiBerry DAC PLUS Proとして認識されます。
コントローラー用スクリプト
コントローラーに使用したスクリプトです。
#!/usr/bin/env python3
"""
Raspberry Pi Zero WHとADRSZDAC用GPIOコントローラー
====================================================================
"""
from __future__ import annotations
import json
import logging
import os
import re
import socket
import subprocess
import threading
import time
from dataclasses import dataclass
from pathlib import Path
from signal import pause
from gpiozero import Button
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%H:%M:%S",
)
log = logging.getLogger("music_controller")
@dataclass(frozen=True)
class Config:
music_dir: Path = Path("/mnt/music")
playlist_path: Path = Path("/tmp/music.m3u")
mpv_socket: str = "/tmp/mpv.sock"
audio_device: str = "alsa/plughw:CARD=sndrpihifiberry,DEV=0"
hold_time: float = 1.5
bounce_time: float = 0.1
socket_timeout: float = 1.0
socket_wait_timeout: float = 3.0
CFG = Config()
AUDIO_EXTENSION_RE = re.compile(r"\.(mp3|m4a|flac|wav)$", re.IGNORECASE)
class MusicController:
def __init__(self, cfg: Config) -> None:
self.cfg = cfg
self._process: subprocess.Popen | None = None
self._lock = threading.Lock()
self._sw1_long_handled = False
self._shuffle_enabled = False
def is_running(self) -> bool:
if self._process is None:
return False
if self._process.poll() is None:
return True
self._process = None
return False
def _scan_playlist(self) -> list[Path]:
tracks = [
path
for path in self.cfg.music_dir.rglob("*")
if path.is_file() and AUDIO_EXTENSION_RE.search(path.name)
]
tracks.sort()
return tracks
def _write_playlist(self) -> bool:
if not self.cfg.music_dir.is_mount():
log.warning("USBメモリがマウントされていません: %s", self.cfg.music_dir)
return False
tracks = self._scan_playlist()
if not tracks:
log.warning("音楽ファイルが見つかりません: %s", self.cfg.music_dir)
return False
self.cfg.playlist_path.write_text(
"\n".join(str(t) for t in tracks) + "\n", encoding="utf-8"
)
log.info("プレイリストを作成しました: %d曲", len(tracks))
return True
def _wait_for_socket(self) -> bool:
deadline = time.monotonic() + self.cfg.socket_wait_timeout
while time.monotonic() < deadline:
if os.path.exists(self.cfg.mpv_socket):
return True
if not self.is_running():
return False
time.sleep(0.05)
return False
def start(self) -> None:
with self._lock:
if self.is_running():
log.info("mpv: 起動済み")
return
if not self._write_playlist():
return
Path(self.cfg.mpv_socket).unlink(missing_ok=True)
self._shuffle_enabled = False
log.info("mpv: 起動します")
try:
self._process = subprocess.Popen(
[
"mpv",
"--no-video",
f"--audio-device={self.cfg.audio_device}",
f"--input-ipc-server={self.cfg.mpv_socket}",
"--loop-playlist=inf",
f"--playlist={self.cfg.playlist_path}",
]
)
except OSError as exc:
log.error("mpvの起動に失敗しました: %s", exc)
self._process = None
return
if self._wait_for_socket():
log.info("mpv: READY")
else:
log.warning("mpv: IPCソケットの生成待ちがタイムアウトしました")
def stop(self) -> None:
with self._lock:
if not self.is_running():
log.info("mpv: 既に停止しています")
return
log.info("mpv: 停止します")
self._process.terminate()
try:
self._process.wait(timeout=3)
except subprocess.TimeoutExpired:
log.warning("mpv: terminateがタイムアウトしたためkillします")
self._process.kill()
self._process.wait()
self._process = None
self._shuffle_enabled = False
Path(self.cfg.mpv_socket).unlink(missing_ok=True)
def _send(self, *command: str) -> bool:
if not self.is_running():
return False
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.settimeout(self.cfg.socket_timeout)
sock.connect(self.cfg.mpv_socket)
sock.sendall(
(json.dumps({"command": list(command)}) + "\n").encode("utf-8")
)
try:
sock.recv(4096)
except socket.timeout:
pass
return True
except OSError as exc:
log.warning("mpv IPCエラー: %s", exc)
return False
def sw1_held(self) -> None:
self._sw1_long_handled = True
if self.is_running():
self.stop()
else:
self.start()
def sw1_released(self) -> None:
if not self._sw1_long_handled:
log.info("SW1: PLAY / PAUSE")
self._send("cycle", "pause")
self._sw1_long_handled = False
def sw2_pressed(self) -> None:
if self._send("playlist-next"):
log.info("SW2: NEXT")
def sw3_pressed(self) -> None:
if self._send("playlist-prev"):
log.info("SW3: PREVIOUS")
def sw4_pressed(self) -> None:
if not self.is_running():
log.info("SW4: mpv停止中のため無視します")
return
if not self._shuffle_enabled:
if self._send("playlist-shuffle"):
self._shuffle_enabled = True
log.info("SW4: SHUFFLE ON")
else:
if self._send("playlist-unshuffle"):
self._shuffle_enabled = False
log.info("SW4: SHUFFLE OFF")
def main() -> None:
controller = MusicController(CFG)
sw1 = Button(17, hold_time=CFG.hold_time, hold_repeat=False, bounce_time=CFG.bounce_time)
sw2 = Button(27, bounce_time=CFG.bounce_time)
sw3 = Button(22, bounce_time=CFG.bounce_time)
sw4 = Button(23, bounce_time=CFG.bounce_time)
sw1.when_held = controller.sw1_held
sw1.when_released = controller.sw1_released
sw2.when_pressed = controller.sw2_pressed
sw3.when_pressed = controller.sw3_pressed
sw4.when_pressed = controller.sw4_pressed
log.info("Music Controller 起動完了")
log.info("SW1 GPIO17 : 長押し=mpv起動/停止, 短押し=再生/一時停止")
log.info("SW2 GPIO27 : 次の曲")
log.info("SW3 GPIO22 : 前の曲")
log.info("SW4 GPIO23 : シャッフル ON/OFF")
log.info("GND : 39番ピン")
try:
pause()
except KeyboardInterrupt:
log.info("終了します")
controller.stop()
if __name__ == "__main__":
main()
サービスの内容は次の通りです。
[Unit]
Description=Raspberry Pi Music Controller
After=local-fs.target mnt-music.automount
Wants=mnt-music.automount
[Service]
Type=simple
User=ユーザー名
Environment=LG_WD=/tmp
Environment=GPIOZERO_PIN_FACTORY=lgpio
ExecStart=/usr/bin/python3 /home/ユーザー名/music_controller.py
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
くじけそうだった点
今回、特に時間がかかったのがADRSZDACに搭載されているスイッチの扱いです。結論から言えば元からついているボタンをPythonで扱うことはできませんでした。
あまり状態のよくないRaspberry Pi Zeroだったので、GPIOそのものの故障を疑いました。ジャンパピンでのGPIO単体のテストでは正常でした。問題の切り分けの結果ADRSZDAC側に問題がありそうな感じでした。
公式ページに書かれていた/boot/firmware/config.txtの設定通りに変更しましたが、やはり動きません。結局Pythonで制御するためには、config.txtの内容は以下の通りで安定することになりました。
dtparam=i2s=on
dtoverlay=vc4-kms-v3d
dtoverlay=hifiberry-dacplus-pro,24db_digital_gain
dtoverlay=dwc2,dr_mode=host
dtoverlay=nospi10
※dtoverlay=hifiberry-dacplus-pro,24db_digital_gainはADRPM1801C(別売り外部システムクロックボード)の設定です。
※ADRSDACだけの場合は、dtoverlay=hifiberry-dacplus-std,24db_digital_gainです。
なぜVolumioやMoOdeを使わないのか
今回、VolumioやMoOdeを採用しませんでした。理由はRaspberry Pi Zero WHの性能(シングルコアCPU、RAM512MB)の力不足です。
Raspberry Pi Imagerで書き込める高機能オーディオ専用OSは、システムが重く、Raspberry Pi Zero WHで快適に動作させることは難しいです。
様々考えましたが、Raspberry Pi OS LiteとmpvとPython制御によるタクトスイッチが一番ストレスなく動かせました。
実際に使ったコマンド
ここからは実際に使ったコマンドについて触れていきたいとおもいます。
STEP 1:OSの更新
いつも通りの3つのコマンドです。
sudo apt update
sudo apt full-upgrade -y
sudo reboot
再起動後に作業を始めます。ただRaspberry Pi Zero WHはとても再起動が遅いので根気よく待ちましょう。
STEP 2:必要パッケージのインストール
必要なパッケージをインストールします。Python3はTrixieになってから標準搭載になったので、素のままで行けました。
sudo apt install -y mpv
sudo apt install -y python3-gpiozero
sudo apt install -y python3-lgpio
sudo apt install -y netcat-openbsd
STEP 3:DACの設定をする
ADRSDAC(DAC)を有効化します。
sudo nano /boot/firmware/config.txt
dtparam=i2s=on
dtoverlay=vc4-kms-v3d
dtoverlay=hifiberry-dacplus-pro,24db_digital_gain
dtoverlay=dwc2,dr_mode=host
dtoverlay=nospi10
今回はADRPM1801Cを乗せているので、dtoverlay=hifiberry-dacplus-pro,24db_digital_gainです。
そして再起動して反映させます。
sudo reboot
再起動を待っている間にUSBメモリに曲を入れておきましょう。
STEP 4:USBメモリを自動マウントできるようにします
再起動したらUSBメモリをfstabに登録します。必須ではありませんが、USBメモリは挿したり抜いたりすると認識されない場合があります。それは名前がsda1やsda2など変わるためです。名前を固定するためにUUIDをfstabに記入します。
まずはUUIDの確認をします。
blkid
すると、このようにUUIDが表示されるのでメモを取るかコピペしておいてください。
/dev/sda1: UUID="USBメモリのUUID" TYPE="exfat"
UUIDが分かったらfstabへ登録します。
sudo nano /etc/fstab
おそらく既に2行登録されていると思いますが、3行目に次の一行を追加してください。こちらは” “は不要です。
UUID=USBメモリのUUID /mnt/music exfat defaults,nofail,x-systemd.automount,x-systemd.device-timeout=10 0 0
fstabをいじったら必ずdaemon-reloadを実行してください。
sudo systemctl daemon-reload
自動マウントを行うように次のように打ち込みます。
sudo systemctl start mnt-music.automount
そして確認を行います。
- 曲入りUSBメモリを抜きます
- 2~3秒ほど待つ
- 曲入りUSBメモリを再度挿します
ls /mnt/music
これで曲名やサブフォルダ名を見ることができたらUSBメモリの自動マウントは完成です。
STEP 5:mpvを確認
mpvを確認
ここからはPowerShell 5.1を2窓展開したほうが楽になります。
まずはmpv単体で動かしてみます。
mpv --no-video \
--audio-device=alsa/plughw:CARD=sndrpihifiberry,DEV=0 \
"/mnt/music/"
–audio-device=alsa/plughw:CARD=sndrpihifiberry,DEV=0この部分はADRSZDACから音を出すために指定しています。
STEP 6:曲をプレイリスト化してmpvで鳴らす
最終的に組むPythonでは、1曲ずつファイル名を指定するのではなくプレイリストをつくります。
/tmp/music.m3u
このようなプレイリストを作り、それをmpvへ渡します。
mpv --no-video \
--audio-device=alsa/plughw:CARD=sndrpihifiberry,DEV=0 \
--playlist=/tmp/music.m3u
これで曲が鳴ればひとまず完成です。
STEP 7:コントローラーの作成
記事の前半に書きましたが各GPIOにタクトスイッチから伸ばした導線をハンダ付けしていきます。私はハンダ付けがとても苦手なので、一つメスヘッダーをダメにしました。最終的には次のようになります。
- GPIO17 → SW1 1.5秒長押し→ mpv起動 / mpv停止
- GPIO17 → SW1 → 再生 / 停止
- GPIO27 → SW2 → 曲送り
- GPIO22 → SW3 → 曲戻し
- GPIO23 → SW4 → シャッフルON / OFF
ダメにしてしまったメスヘッダー。素直にフラックスを使った方がよかったです。戒めのため、このメスヘッダーを公開しておきます。

さて、結論の部分にも同じものを載せていますが、もう一度載せます。
#!/usr/bin/env python3
"""
====================================================================
Raspberry Pi Zero WHとADRSZDAC/ADRPM1801C用音楽コントローラー
====================================================================
"""
from __future__ import annotations
import json
import logging
import os
import re
import socket
import subprocess
import threading
import time
from dataclasses import dataclass
from pathlib import Path
from signal import pause
from gpiozero import Button
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%H:%M:%S",
)
log = logging.getLogger("music_controller")
@dataclass(frozen=True)
class Config:
music_dir: Path = Path("/mnt/music")
playlist_path: Path = Path("/tmp/music.m3u")
mpv_socket: str = "/tmp/mpv.sock"
audio_device: str = "alsa/plughw:CARD=sndrpihifiberry,DEV=0"
hold_time: float = 1.5
bounce_time: float = 0.1
socket_timeout: float = 1.0
socket_wait_timeout: float = 3.0
CFG = Config()
AUDIO_EXTENSION_RE = re.compile(r"\.(mp3|m4a|flac|wav)$", re.IGNORECASE)
class MusicController:
def __init__(self, cfg: Config) -> None:
self.cfg = cfg
self._process: subprocess.Popen | None = None
self._lock = threading.Lock()
self._sw1_long_handled = False
self._shuffle_enabled = False
def is_running(self) -> bool:
if self._process is None:
return False
if self._process.poll() is None:
return True
self._process = None
return False
def _scan_playlist(self) -> list[Path]:
tracks = [
path
for path in self.cfg.music_dir.rglob("*")
if path.is_file() and AUDIO_EXTENSION_RE.search(path.name)
]
tracks.sort()
return tracks
def _write_playlist(self) -> bool:
if not self.cfg.music_dir.is_mount():
log.warning("USBメモリがマウントされていません: %s", self.cfg.music_dir)
return False
tracks = self._scan_playlist()
if not tracks:
log.warning("音楽ファイルが見つかりません: %s", self.cfg.music_dir)
return False
self.cfg.playlist_path.write_text(
"\n".join(str(t) for t in tracks) + "\n", encoding="utf-8"
)
log.info("プレイリストを作成しました: %d曲", len(tracks))
return True
def _wait_for_socket(self) -> bool:
deadline = time.monotonic() + self.cfg.socket_wait_timeout
while time.monotonic() < deadline:
if os.path.exists(self.cfg.mpv_socket):
return True
if not self.is_running():
return False
time.sleep(0.05)
return False
def start(self) -> None:
with self._lock:
if self.is_running():
log.info("mpv: 起動済み")
return
if not self._write_playlist():
return
Path(self.cfg.mpv_socket).unlink(missing_ok=True)
self._shuffle_enabled = False
log.info("mpv: 起動します")
try:
self._process = subprocess.Popen(
[
"mpv",
"--no-video",
f"--audio-device={self.cfg.audio_device}",
f"--input-ipc-server={self.cfg.mpv_socket}",
"--loop-playlist=inf",
f"--playlist={self.cfg.playlist_path}",
]
)
except OSError as exc:
log.error("mpvの起動に失敗しました: %s", exc)
self._process = None
return
if self._wait_for_socket():
log.info("mpv: READY")
else:
log.warning("mpv: IPCソケットの生成待ちがタイムアウトしました")
def stop(self) -> None:
with self._lock:
if not self.is_running():
log.info("mpv: 既に停止しています")
return
log.info("mpv: 停止します")
self._process.terminate()
try:
self._process.wait(timeout=3)
except subprocess.TimeoutExpired:
log.warning("mpv: terminateがタイムアウトしたためkillします")
self._process.kill()
self._process.wait()
self._process = None
self._shuffle_enabled = False
Path(self.cfg.mpv_socket).unlink(missing_ok=True)
def _send(self, *command: str) -> bool:
if not self.is_running():
return False
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.settimeout(self.cfg.socket_timeout)
sock.connect(self.cfg.mpv_socket)
sock.sendall(
(json.dumps({"command": list(command)}) + "\n").encode("utf-8")
)
try:
sock.recv(4096)
except socket.timeout:
pass
return True
except OSError as exc:
log.warning("mpv IPCエラー: %s", exc)
return False
def sw1_held(self) -> None:
self._sw1_long_handled = True
if self.is_running():
self.stop()
else:
self.start()
def sw1_released(self) -> None:
if not self._sw1_long_handled:
log.info("SW1: PLAY / PAUSE")
self._send("cycle", "pause")
self._sw1_long_handled = False
def sw2_pressed(self) -> None:
if self._send("playlist-next"):
log.info("SW2: NEXT")
def sw3_pressed(self) -> None:
if self._send("playlist-prev"):
log.info("SW3: PREVIOUS")
def sw4_pressed(self) -> None:
if not self.is_running():
log.info("SW4: mpv停止中のため無視します")
return
if not self._shuffle_enabled:
if self._send("playlist-shuffle"):
self._shuffle_enabled = True
log.info("SW4: SHUFFLE ON")
else:
if self._send("playlist-unshuffle"):
self._shuffle_enabled = False
log.info("SW4: SHUFFLE OFF")
def main() -> None:
controller = MusicController(CFG)
sw1 = Button(17, hold_time=CFG.hold_time, hold_repeat=False, bounce_time=CFG.bounce_time)
sw2 = Button(27, bounce_time=CFG.bounce_time)
sw3 = Button(22, bounce_time=CFG.bounce_time)
sw4 = Button(23, bounce_time=CFG.bounce_time)
sw1.when_held = controller.sw1_held
sw1.when_released = controller.sw1_released
sw2.when_pressed = controller.sw2_pressed
sw3.when_pressed = controller.sw3_pressed
sw4.when_pressed = controller.sw4_pressed
log.info("Music Controller 起動完了")
log.info("SW1 GPIO17 : 長押し=mpv起動/停止, 短押し=再生/一時停止")
log.info("SW2 GPIO27 : 次の曲")
log.info("SW3 GPIO22 : 前の曲")
log.info("SW4 GPIO23 : シャッフル ON/OFF")
log.info("GND : 39番ピン")
try:
pause()
except KeyboardInterrupt:
log.info("終了します")
controller.stop()
if __name__ == "__main__":
main()
これがコントローラー本体になります。長い構文になってしまったので、一応チェックしたいと思います。
python3 -m py_compile ~/music_controller.py
STEP 8:サービスファイルを作成します。
紆余曲折しましたが、とにかく、最終的に動作したサービスファイルを作ります。
/etc/systemd/system/music-controller.service
[Unit]
Description=Raspberry Pi Music Controller
After=local-fs.target mnt-music.automount
Wants=mnt-music.automount
[Service]
Type=simple
User=p
Environment=LG_WD=/tmp
Environment=GPIOZERO_PIN_FACTORY=lgpio
ExecStart=/usr/bin/python3 /home/p/music_controller.py
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
ここが結構重要で、Environment=GPIOZERO_PIN_FACTORY=lgpioとgpiozeroのGPIOバックエンドとしてlgpioを明示しないと動きませんでした。
Environment=LG_WD=/tmpこれも重要です。systemdから起動した場合のlgpioの通知ファイルで.lgd-nfy-Nを作成するために必要でした。これが無い状態だと、FileNotFoundError: .lgd-nfy-3が発生して、結構沼にはまりました。
STEP 9:systemdに登録します
sudo systemctl daemon-reload
sudo systemctl enable music-controller.service
sudo systemctl start music-controller.service
エラーがでてないか確認をします。Active: active (running)が表示されることを確認します。Active: activationとなっていた場合しばらくしてからもう一度ためしてみてください。
systemctl status music-controller.service --no-pager
STEP 10:再起動後の確認
まずは再起動をかけます。
sudo reboot
再起動後もチェックを行い、正常稼働しているか確認します。
systemctl status music-controller.service --no-pager
さらに実際のコントローラーできちんと動作するかを確認します。ボタンをおして挙動がおかしい場合はPythonのGPIOの番号が間違っている可能性が高いです。
すべてが確認出来たら完成です。

よくある質問(FAQ)
- QGUIは必要ですか?
- A
今回の構成では必要ないです。Raspberry Pi OS Lite 32bit上でGPIOとmpvを使用しているため、Web UIもGUIも使っていません
- Q音楽ファイルはどこに置きますか?
- A
今回は楽をするためにUSBメモリに音楽ファイルを全て入れています。USBメモリを/mnt/musicへマウントします。.mp3、.m4a、.flac、.wavを検索し、サブディレクトリも対象にしています。
- Qmpvは常に起動していますか?
- A
いいえ。systemdが起動するのはmusic_controller.pyだけです。PythonのプログラムがGPIO入力を待機し、SW1を長押ししたときにmpvを起動/停止させます。
いいえ。systemdが起動するのはmusic_controller.pyです。
PythonプログラムがGPIO入力を待機し、SW1を長押ししたときにmpvを起動します。初期段階では勝手に鳴り始めるのが不便だったため、このような設計になりました。
- Qボタンは何個必要ですか?
- A
目的に合わせて4つにしました。ボタンの短押し、長押しなどで制御できるため、数*2以上の制御ができます。アンプに繋ぐ予定だったため、音量ボタンは付けませんでしたが、増やすことで音量の増減もできると思います。
- QRaspberry Piを再起動しても使えますか?
- A
systemdへ登録しているため、再起動後もmusic-controller.pyが自動起動します。今回の検証では再起動後の動作も確認しています。
まとめ
今回作成したのは、Raspberry Pi Zero WH、ADRSZDAC、ADRPM1801C、USBストレージ、4個の物理ボタンを組み合わせたシンプルかつ軽量な音楽プレーヤーです。
Raspberry Pi ZeroではWeb UIの動作は重いです。そこでRaspberry Pi OS Lite上でmpvとPythonを動かし、物理ボタンだけで音楽を操作できるようにしました。
音楽ファイルはUSBメモリに取り込み/mnt/musicへマウントします。自動マウントさせることにより、音楽ファイルからプレイリストを自動生成し、mpvが再生します。
最初はDAC側のボタン入力でつまずきましたが、外部タクトスイッチを使い、DAC設定とsystemdのGPIO環境を整えることで、最終的に再起動後も動作するスタンドアロンプレーヤーとして完成しました。
公式リファレンス
- Bit Trade One ADRPM1801C:
専用ページが無かったためhttps://bit-trade-one.co.jp/product/module/adrpm1801p/ - Bit Trade One ADRSZDAC:
https://bit-trade-one.co.jp/adrszdac/ - ADRSZDAC公式FAQ:
https://github.com/bit-trade-one/RasPi-Zero-One-Series/blob/master/3rd/ADRSZDAC_Hi-Rez_DAC/FAQ.md - gpiozero Button API:
https://gpiozero.readthedocs.io/en/stable/api_input.html - mpv JSON IPC:
https://github.com/mrxdst/mpv-json-ipc - mpv Command Interface:
https://mpv.io/manual/stable/
関連記事
Raspberry Pi、Linux、DAC、音楽再生に関する記事を関連リンクとして追加します。
更新履歴
- 2026年10月4日 更新
- 2026年9月30日 初投稿