Bullseye から Trixie へ ― PyAudio が止まった理由と、ALSA + PulseAudio への移行記録

みなさん、こんにちは。

前回の記事では、Raspberry Pi OS Trixie で従来の wpa_supplicant を使い続ける方法をご紹介しました。

OSのメジャーアップデート、特に Bookworm から Trixie への移行は、IoTエンジニアにとって「これまで動いていたものが、なぜか動かなくなる」という試練の連続です。実はネットワーク設定以外にも、もう一つ大きな、そして非常に根の深い「つまづきポイント」がありました。

それが 「オーディオ周り(音声録音)」 です。

これまで Bullseye 環境で快調に動いていた PyAudio による録音処理が、Trixie にした途端にピタリと止まってしまう……。今回は、この「録音ハングアップ問題」の正体と、現場で編み出した解決策を記録しておきます。

 


 

異変 – Trixie 上で PyAudio が「黙る」

 

私の開発環境では、PyAudio を使って PulseAudio 経由で音声録音を行う処理を書いていました。Bullseye では何の問題もなかったコードです。

ところが Trixie に乗り換えた途端、録音処理がブロックされたまま返ってこない という現象が多発しました。

ログを確認しても「録音開始」の記録はある。しかし、その後の処理が一切流れない。タイムアウトすら発生せず、プロセスはただ静かに、永遠に待ち続けている……。この「原因不明の沈黙」こそが、今回のトラブルの始まりでした。

 


 

原因 – Trixie のデフォルト「PipeWire」の挙動

 

なぜ止まったのか? その答えは、Raspberry Pi OS の基盤となっている Debian の変更にありました。

Bookworm 以降、そして Trixie では、デフォルトの音声システムとして PipeWire が採用されています。

PipeWire には PulseAudio 互換レイヤー(pipewire-pulse)があるため、一見すると従来のコードがそのまま動くように見えます。しかし、ここに大きな罠が隠れていました。それが 「ソースの SUSPENDED(サスペンド)状態」 です。

魔の「SUSPENDED」ステータス

PipeWire + pipewire-pulse の組み合わせでは、リソース節約のためにアクティブなクライアントがいない入力ソースを自動的にサスペンド状態にします。

問題は、PyAudio(内部では PortAudio → PulseAudio と連携)がこのサスペンド状態のソースに接続しに行ったときです。本来なら接続と同時に「起きて」データを流すべきなのですが、PipeWire 環境下ではデータが一切流れてこない(ストリームが開始されない) という事象が起きていたのです。

PyAudio の read() は同期的なブロッキング処理です。データが来なければ、プログラムはそこでフリーズしてしまいます。これがハングアップの正体でした。

 


 

解決策 – PipeWire を捨て、ALSA + PulseAudio に回帰する

 

最新の PipeWire は有望な技術ですが、今回の「ヘッドレス環境で安定してサービスを常駐させる」という目的においては、実績のある PulseAudio を直接制御する ほうが確実であると判断しました。

音声システムの再構成(OS設定)

まずは、PipeWire の互換レイヤーを削除し、PulseAudio をシステムに常駐させる設定を行います。

# PulseAudio のインストール
sudo apt-get install pulseaudio pulseaudio-utils alsa-plugins-pulseaudio

# PipeWire の PulseAudio 互換レイヤーを削除
sudo apt remove pipewire-pulse

# PipeWire サービスを無効化し、干渉を防ぐ
systemctl --user stop pipewire-pulse.socket pipewire-pulse.service
systemctl --user disable pipewire-pulse.socket pipewire-pulse.service
systemctl --user mask pipewire-pulse.socket pipewire-pulse.service

# ログインしていなくても PulseAudio が常時稼働するよう「linger」を有効化
# ※ <ユーザー名> は自分の環境に合わせて書き換えてください
sudo loginctl enable-linger <ユーザー名>
systemctl --user unmask pulseaudio.service pulseaudio.socket
systemctl --user enable --now pulseaudio.service pulseaudio.socket

ここで重要なのが loginctl enable-linger です。これにより、SSHセッションを切っても、あるいはシステム起動直後のログイン前であっても、ユーザー権限で PulseAudio がバックグラウンドで動き続けてくれます。

 


 

実装の変更 – PyAudio から arecord(ALSA直接アクセス)へ

 

音声システムを整えても、PyAudio にはデバイスインデックスの不安定さなどの懸念が残ります。そこで、録音処理そのものを ALSA に直接アクセスする arecord コマンド を叩く方式に全面刷新しました。

旧実装(PyAudio)の問題

# PyAudio は PortAudio を通じて PulseAudio と通信するため、
# SUSPENDED 問題の影響をモロに受け、ここで止まっていました。
frames = stream.read(chunk) 

新実装(arecord + subprocess)

arecord を使えば ALSA を直接叩くため、PulseAudio のサスペンド問題に左右されません。また、subprocesstimeout 機能を使い、万が一のハングアップにも備えています。

import subprocess

def get_alsa_card_for_default_source():
    # PulseAudio のデフォルトソースから ALSA のカード番号を特定する
    default_source = subprocess.run(
        ["pactl", "get-default-source"],
        capture_output=True, text=True
    ).stdout.strip()
    
    sources_output = subprocess.run(
        ["pactl", "list", "sources"],
        capture_output=True, text=True
    ).stdout

    in_target = False
    for line in sources_output.split('\n'):
        if default_source and default_source in line:
            in_target = True
        if in_target and 'alsa.card = "' in line:
            card = line.split('"')[1]
            return int(card)
    return 1  # 見つからない場合のフォールバック

# デバイス番号を動的に取得して録音
alsa_card = get_alsa_card_for_default_source()
alsa_device = f"plughw:{alsa_card},0"

result = subprocess.run(
    ["arecord", "-D", alsa_device, "-f", "S16_LE", "-r", "16000", "-c", "1", "-t", "raw", "-d", "5"],
    capture_output=True,
    timeout=15 # 録音時間+αでタイムアウトを設定
)
raw_data = result.stdout

あえて pactl でカード番号を取得しているのは、USBマイクの抜き差しなどでデバイス番号が変わるのを防ぐためです。「管理は PulseAudio、実務(録音)は ALSA」という、いいとこ取りのハイブリッド構成にしました。

 


 

まとめ – Trixie 移行後のオーディオ構成

 

項目Bullseye 時代Trixie 対応後
デフォルト音声系PulseAudioPulseAudio (PipeWire除去)
録音ライブラリPyAudio (PortAudio経由)arecord (ALSA直接アクセス)
デバイス指定device_index (固定)pactl による動的取得
堅牢性なしsubprocess の timeout 指定

Trixie への移行は、単なるOSのアップデートではなく、音声サブシステムのアーキテクチャそのものが入れ替わる大きな変化でした。

「最新の PipeWire で頑張る」という選択肢もありましたが、現場のシステムに求められるのは何よりも 「止まらないこと」 です。もし、Trixie + ヘッドレス環境で PyAudio のハングアップに悩んでいる方がいたら、この「あえて戻す」という選択肢も検討してみてください。

 

本日も最後までお読みいただきありがとうございました。

それでは、よい Raspberry Pi ライフを!

カテゴリ: Raspberry Pi

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール