
2025/12/16 6:37
Fix HDMI-CEC weirdness with a Raspberry Pi and a $7 cable
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
概要:
Samsung S95B TV(論理アドレス 0x00)、Denon AVR‑X1700H(0x05)、Apple TV、PS5、Xbox Series X、Nintendo Switch 2、およびをリッスンする Raspberry Pi 4 が含まれるホームシアター構成で、テレビの入力にのみ切り替えるコンソールが原因となるオーディオルーティング問題を著者は解決します。/dev/cec0
Pi(論理アドレス 0x01)から AVR に「System Audio Mode Request」パケット()を送信することで、受信機は ARC を有効化し、すべてのコンソールオーディオをテレビではなく自身経由でルーティングします。15:70:00:00
著者は Python スクリプトでこれを実装しており、長時間稼働するcec_auto_audioを起動し、TRAFFIC 行から Active Source イベント(オペコード 0x82)を解析し、以前に Set System Audio Mode(オペコード 0x72)が検出されていない場合に毎回ウェイク時にパケットを送信します。cec-client -d 8
スクリプトは systemd サービスとしてパッケージ化され、起動時に開始されます。これにより、多層の HomeKit/Eve オートメーションと比べて低レイテンシで軽量な代替手段を提供します。cec_auto_audio.service
トラブルシューティングガイドには、スキャン()、トラフィック監視(echo "scan" | cec-client -s)、および欠落オペコード(0x82, 0x84, 0x70, 0x72)の良いケースと悪いケースの比較が含まれます。cec-client -m
残るエッジケースとして、コンソールのスタンバイがテレビチューナーを起動させる場合や HomeKit オートメーションがアクティブなソースなしでテレビをオンにする場合などには、追加の状態機械ロジックが必要になる可能性があります。著者はコミュニティメンバーに対し、より広範なトラブルシューティングのために CEC パケットトレースを共有してもらうよう呼びかけています。
本文
これまでの背景
数年間、HDMI‑CEC を「家の精霊」のように扱ってきました。
時には役立つこともあれば、ほぼ変わり者で、完全に理解できていないままです。
私のリビングの構成はシンプルです:
- Samsung TV(ARC あり、eARC ではありません)
- Denon AVR‑X1700H をクローゼットに隠し収納
- Apple TV と数機種のゲーム機をレシーバーへ接続
- Raspberry Pi 4 が Homebridge の役割も担っている
CEC に関しては、Apple TV は完璧に動作しますが、他のコンソールは CEC を最後の学期で抜けてしまったかのようです。
テレビを起動させ、入力を切り替えた後、Denon はスリープ状態のままで、私は音声出力を手動で切り替える必要があります。
メディアクローゼットの構築は別途記載していますので、配線図と写真が欲しい場合はそちらからご覧ください。
問題点
- メディアクローゼットで TV への再配線は不可能
- CEC を無効にすることも現実的ではない(Apple TV は機能し、最頻使用デバイス)
そこで、まずは従来の自動化スタックを検討しました。
HomeKit シーンで「テレビ ON → レシーバー ON」をチェインしたり、Eve Energy プラグでワット数トリガーを設定したりです。
これはある程度機能しますが、追加レイヤーごとに 30 秒以上の遅延が発生しました。
最終的に Homebridge‑CEC‑TV‑Control プラグインへ行きましたが、README を読んでいるうちに「Node → Homebridge → HomeKit」を経由して CEC メッセージを送ることになると悟りました。
Pi はすでにラックに接続されているので、これらのレイヤーを飛ばし
/dev/cec0 へ直接書き込む方が高速です。
数時間の試行錯誤の末、Pi は HDMI バス上で静かに待機し、コンソールが自分をアナウンスすると、Samsung と Denon が自動的に交換すべき単一コマンドを発信します。
本記事の構成
- CEC の小さなメンタルモデルを作る
- バスを監視する
- Apple TV が正しくやっていることをコピーする
- Python でラップする
- systemd ユニットとして配備する
HDMI‑CEC 入門
HDMI‑Consumer Electronics Control(CEC)は、HDMI ビデオ/オーディオと並行して走る低帯域幅のサイドチャネルです。
バス上の全機器は論理アドレス(TV は
0x0、オーディオシステムは 0x5、再生装置は 0x4/0x8/0xB1 など)と小さなオペコードで通信します。
物理アドレスはトポロジー内の「緯度/経度」参照です。例えば
3.0.0.0 は「AVR の HDMI 3 入力」を意味します。CEC は消費者がエレクトロニクスを制御できるように設計されており、健全なシステムでは次のフローになります。
コンソール起動 → 自身をアクティブソースとして宣言 TV が ARC パートナーを検知 誰かが「オーディオシステムになってください」と要求 レシーバー起動 → 大きなスピーカーから音声出力
私のホームシアターでは、Apple TV が関与している場合にのみこのパスが通ります。
コンソールを起動するとテレビは入力を切り替えますが、オーディオは小さな TV スピーカーに留まります。
デバイスの役割
| デバイス | 論理アドレス |
|---|---|
| TV | |
| オーディオシステム(Denon AVR‑X1700H) | |
| 再生装置(Apple TV、PS5、Switch 2、Xbox) | , , |
| ブロードキャスト | |
主なオペコード
– Active Source0x82
– Report Physical Address0x84
– System Audio Mode Request0x70
– Set System Audio Mode0x72
cec-client
で CEC バスを監視する
cec-clientRaspberry Pi 4 は
/dev/cec0 を公開しています。$7 の micro‑HDMI→HDMI ケーブルを AVR の未使用 HDMI ポートに接続すると、単なる参加者として振る舞います。信号再生や偽装 EDID はありません。
sudo apt update sudo apt install cec-utils echo "scan" | cec-client -s
スキャン結果の例(アドレスは省略):
device #0: TV address: 0.0.0.0 vendor: Samsung device #1: Recorder 1 address: 3.3.0.0 vendor: Pulse Eight device #4: Playback 1 address: 3.1.0.0 vendor: Unknown (Switch 2) device #5: Audio address: 3.0.0.0 vendor: Denon device #8: Playback 2 address: 3.2.0.0 vendor: Apple device #B: Playback 3 address: 3.6.0.0 vendor: Sony (PS5)
トラフィックを監視するには:
cec-client -d 8 # ログレベル 8(詳細)
TRAFFIC: [...] >> bf:82:36:00 のような行は、論理 0xB(PS5)が物理アドレス 3.6.0.0 を持つ Active Source をブロードキャストしたことを示します。
マジックハンドシェイクの発見
システムをスタンバイにしてログ収集を開始し、Apple TV を起動しました:
8f:82:32:00 # Apple TV (logical 8) → Broadcast: Active Source ... 5f:72:01 # Denon (logical 5) → Broadcast: Set System Audio Mode (on)
Apple TV は自分をアクティブソースとして宣言し、Denon は「システムオーディオモードはオン」と全員に告げます。TV はレシーバーへの出力設定を維持します。
PS5、Xbox、Switch 2 では Active Source は見られましたが
5f:72:01 はありませんでした。つまり「コンソール起動後の Set System Audio Mode ブロードキャスト」が欠けているのです。
cec‑o‑matic.com を使って Pi が送信する CEC フレームを作成しました:
15:70:00:00 # TV (1) → Audio (5): System Audio Mode Request
15 = ソース 0x1(Recorder 1=Pi)70 = オペコード System Audio Mode Request00:00 = オペランド(TV の物理アドレス 0.0.0.0、オーディオ状態 OFF)
このフレームをインタラクティブ
cec-client で送ると (tx 15:70:00:00) Denon がオンになり、ARC が受信機に固定されます。コンソールと TV のみが電源オンの場合でも同様です。
バスへの過剰な書き込みはやめましょう
数秒ごとに
cec-client を呼び出す簡易 Bash ループは遅延が大きく、非効率的です。代わりに長時間稼働する単一の
cec-client プロセスを起動します:
cec-client -d 8
このプロセスはすべての TRAFFIC 行を出力し、標準入力でコマンドを受け付けます(ただし監視モードではない)。
Python スクリプトが橋渡し役となり、stdout がイベントストリーム、stdin が制御チャネルとして機能します。
Python スクリプト
スクリプトのロジックは以下の通りです:
をサブプロセスとして起動cec-client -d 8- TRAFFIC 行を解析
- 再生装置(論理アドレス
,0x4
,0x8
)からの Active Source (0xB
) を検知0x82 - 最後に受信した Set System Audio Mode (
) を追跡し、Apple TV や TV のロジックと競合しないようにする5f:72:01 - もし誰も送っていなければ、
を一度だけ送信tx 15:70:00:00
ポイント:
- デバイス名・ベンダー・物理アドレスをハードコードしていません。
- 任意の再生装置が Active Source に切り替わった瞬間を「コンソール起動」とみなします。
- Apple TV / Samsung / Denon が正しく動作した場合は待機し、
を検知すると処理をスキップ。5f:72:01 - 単一の長時間稼働するプロセスとして実行されます。
GitHub で公開中です:jlian/cec_auto_audio。
systemd サービスにラップして、起動時に自動開始し常駐させました。
アプローチを一般化する手順
-
Pi を HDMI ポート(micro‑HDMI→HDMI ケーブル)へ接続
-
バスのベースラインを確認:
echo "scan" | cec-client -s -d 1 -
「良い」シナリオと「悪い」シナリオを
で記録cec-client -d 8 -
トレースの差分を取り、欠落しているオペコードを特定
-
cec‑o‑matic.com で手動送信 (
) により問題が解決するか確認tx … -
上記マジックパケットを小さなプログラム(Python 等)に組み込み、Pi がバス上で静かに参加
残るエッジケース
| ケース | 可能な対策 |
|---|---|
| コンソールがスタンバイ → TV がアクティブソースになる | フレームペアを監視し、短時間後に入力を Apple TV に戻す |
| 日没自動化で「テレビオンだがアクティブソースなし」 | 「TV ON, Denon スリープ, アクティブソース無し」を検知し、Apple TV とレシーバーの両方を起動 |
「最近最も頻繁にアクティブだったデバイス」を追跡する状態機械を作れば、バスが静寂になったときや TV が自ら切り替えた場合に Apple TV へフォールバックできます。
結論
Apple TV はそのまま動作し続けます。
PS5、Xbox、Switch 2 を起動すると Pi が Denon を半秒以内に呼び込み、音声はレシーバーに固定されます。
Pi はクローゼットで「やや高度なリモコン」として黙って待機しています。
他の HDMI‑CEC の悩みがある場合は、このテクニックを応用してください:欠落したパケットをキャプチャし、Pi から送信し、小さな Python スクリプト + systemd で自動化すれば解決できます。