- PVSM.RU - https://www.pvsm.ru -

Долгие годы я воспринимал систему HDMI-CEC [1] как домового: иногда полезная, часто непредсказуемая и всегда загадочная. У меня в гостиной собран несложный мультимедиа-центр — ТВ Samsung с поддержкой ARC [2] (не eARC, которая заслуживает отдельного поста), Denon AVR-X1700H, спрятанный в кладовке, Apple TV, несколько подключённых к Denon игровых приставок и Raspberry Pi 4 [3], управляющий системой Homebridge [4]. Что касается CEC, то в Apple TV эта фича работает прекрасно, но вот приставки ведут себя так, будто едва с ней знакомы. Они будят ТВ, переключают источник, но оставляют Denon в режим ожидания, вынуждая меня переключать вывод аудио вручную.

Я уже описывал свою медиа-сборку [5], которую организовал у себя в кладовке. Так что, кому интересно узнать всю схему подключения (и посмотреть фото до/после), рекомендую начать с той статьи.
С учётом уже организованной медиа-кладовки переподключать всё к телевизору явно было не вариант, а отключать CEC нецелесообразно (Apple TV исправно работает и используется чаще всего). Первыми мне на ум пришли традиционные системы автоматизации — прописать сценарии HomeKit, чтобы включение ТВ вело к включению ресивера, либо использовать активацию устройств на основе потребляемой энергии через умную розетку Eve Energy [6]. В целом, такая схема работала, но каждый дополнительный этап вносил 30 секунд задержки, а то и более. Последней попыткой в этой эпопее стал плагин homebridge-cec-tv-control [7], но из прочтения документации я понял, что сигналы CEC в итоге будут идти к ресиверу через Node, Homebridge и HomeKit. А поскольку у меня в систему уже подключён Pi, то пропуск этих слоёв и прямая передача через /dev/cec0 была явно более быстрым путём.
Попотев над этой задачей несколько вечеров, я разместил Pi на шине HDMI, откуда он следит за поступлением сигналов с приставок и отправляет единственную команду, которой Samsung и Denon должны были обмениваться сами.
В этой статье я изложил процесс в соответствии с этапами его реализации — моделирование в уме лаконичной схемы CEC, организация мониторинга шины, копирование действий Apple TV, их обёртывание в код Python и отправка в виде службы systemd [8].
Система High-Definition Multimedia Interface [9] Consumer Electronics Control [1], чаще известная как HDMI-CEC, представляет собой сторонний канал передачи данных, пролегающий параллельно HDMI-каналу аудио/видео. Все подключённые к шине устройства общаются на языке логических адресов (0x0 для телевизора, 0x5 для аудиосистем, 0x4/0x8/0xB1 [10] для устройств воспроизведения и так далее) и крохотных опкодов2 [11] вроде 0x82 (Active Source) или 0x72 (Set System Audio Mode). Физические адреса в этой топологии представляют своеобразную «широту/долготу», то есть 3.0.0.0 может означать «Подключение к AVR через HDMI 3».
По идее, CEC должна облегчать для пользователя управление электроникой, поэтому в здравой системе процесс разворачивается так: приставка выходит из сна и сообщает об этом, ТВ замечает появление устройства с поддержкой ARC, некий девайс отправляет сигнал «используй аудиосистему», активируется ресивер, и звук начинает идти через большие колонки. В моём случае такой алгоритм работал, только когда в схеме присутствовал Apple TV. Если же активировалась приставка, ТВ переключал источник, но звук всё так же продолжал идти через крохотные динамики телевизора.
Озадачившись исправлением этой неразберихи, я сначала записал идентификаторы, которыми каждое устройство представляло себя на шине. Вот конкретные роли CEC в моей системе домашнего кинотеатра:
TV — логический адрес 0x0.
Audio system (Denon AVR-X1700H) — логический адрес 0x5.
Playback devices — логические адреса 0x4, 0x8, 0xB (Apple TV, PS5, Switch 2 и Xbox — все соперничают за три слота воспроизведения3 [12]).
Broadcast — логический адрес 0xF (передача сообщения всем устройствам на шине).
А вот основные опкоды, которые меня интересуют:
0x82 — Active Source («Теперь я активный источник ввода»).
0x84 — Report Physical Address («Моё место в HDMI-дереве»).
0x70 — System Audio Mode Request.
0x72 — Set System Audio Mode (Сигнал Denon «Теперь системное аудио на мне»).
Порт micro-HDMI на моей малинке поддерживает интерфейс /dev/cec0, и если соединить её с ресивером простым кабелем HDMI — micro-HDMI, то можно мониторить CEC-трафик от всех подключённых к этому ресиверу устройств.

Имея печальный опыт с Hue Play Sync Box [13]4 [14], поначалу я сомневался: включение в линию перед ТВ любого HDMI-сплиттера или гаджета обычно вело к странным сбоям EDID [15], проблемам с согласованием HDR или полной потере сигнала. Но когда я понял, что Pi никак не вмешивается в HDMI-хэндшейк, то мои сомнения отпали. После подключения к HDMI-входу ресивера он работает так же, как и любой другой участник общей шины CEC. Никакой регенерации сигнала, никакого искажения EDID, ничего, что могли бы заметить другие устройства цепочки.
В итоге получилась такая схема подключения:

Теперь можно установить cec-client из libcec [16]:
sudo apt update
sudo apt install cec-utils
Затем выполнить сканирование, чтобы проверить, какие устройства отзовутся:
echo "scan" | cec-client -s
Ниже показан пример результатов сканирования моей конфигурации. Как видите, и Xbox, и Switch3 [12] заняли один логический адрес 0x81 [10]:
CEC bus information
===================
device #0: TV
address: 0.0.0.0
active source: no
vendor: Samsung
osd string: TV
CEC version: 1.4
power status: on
language: eng
device #1: Recorder 1
address: 3.3.0.0
active source: no
vendor: Pulse Eight
osd string: CECTester
CEC version: 1.4
power status: on
language: eng
device #4: Playback 1
address: 3.1.0.0
active source: yes
vendor: Unknown
osd string: Switch 2
CEC version: 1.3a
power status: on
language: ???
device #5: Audio
address: 3.0.0.0
active source: no
vendor: Denon
osd string: AVR-X1700H
CEC version: 1.4
power status: on
language: ???
device #8: Playback 2
address: 3.2.0.0
active source: no
vendor: Apple
osd string: Apple TV
CEC version: 2.0
power status: standby
language: ???
device #B: Playback 3
address: 3.6.0.0
active source: no
vendor: Sony
osd string: PlayStation 5
CEC version: 1.3a
power status: standby
language: ???
Если все ожидаемые устройства обозначились, используйте режим мониторинга с подходящим уровнем5 [17] логирования:
cec-client -m -d 8
Эта команда ведёт к отслеживанию всех транзакций по шине без вмешательства со стороны Pi.
Строка TRAFFIC: [...] >> bf:82:36:00 означает, что логический адрес 0xB (PS5) передаёт по шине сигнал Active Source (0x82) с физическим адресом 3.6.0.0. Именно такой пакет должна отправлять любая приставка при пробуждении.
Итак, я включаю систему в режим ожидания, начинаю логирование, потом бужу Apple TV. Pi зарегистрировал ожидаемый сигнал Active Source, следом за которым Denon сообщил о том, что взял на себя воспроизведение аудио:
>> 8f:82:32:00 # Apple TV (logical 8) -> Broadcast: Active Source
...
>> 8f:a6:06:10:56:10 # Apple TV (logical 8) -> Broadcast: ???
>> 5f:72:01 # Denon (logical 5) -> Broadcast: Set System Audio Mode (on)
По-русски:
Apple TV объявляет себя активным источником.
Apple TV передаёт некие магические биты?
Вскоре после этого Denon сообщает всем, что «System Audio Mode включён», и TV без вопросов сохраняет в качестве устройства воспроизведения ресивер, не переключаясь обратно на динамики телевизора.
Ровно такой же эксперимент я проделал с PS5, Xbox и Switch 2, но получил уже другой результат:
>> bf:82:36:00 # PS5: Active Source
# …много всяких сообщений, но нет 5f:72:01
Так что же это был за пакет 8f:a6:06:10:56:10, когда подключался Apple TV? В журнале отладки cec-client показывает UNKNOWN (A6). Подозреваю, что libCEC маркирует его как UNKNOWN, потому что этот пакет относится к диапазону конкретного вендора. Байты 06:10:56:10 могут нести проприетарное содержимое Apple, например, некую функцию или расширенную команду управления. Возможно, между Samsung и Apple здесь есть соглашение, которое и ведёт к правильной реакции Denon. Интересно, но я не могу полагаться на этот довод, так как ему нет документального подтверждения, и ручная отправка этого сигнала с логического адреса малинки не сработала. Имитировать Apple TV по CEC нецелесообразно и наверняка опасно.
Однако с помощью cec-o-matic.com [18] оказалось несложно создать пакет CEC с типичным запросом на включение системного аудио, чтобы передавать этот пакет с помощью Pi:
15:70:00:00 # TV (1) -> Audio (5): System Audio Mode Request
Пояснение:
15 = источник 0x1 (Recorder 1 = Pi) отправляет сигнал получателю 0x5 (Audio System = Denon).
70 = опкод System Audio Mode Request.
00:00 = операнды (физический адрес TV, 0.0.0.0, плюс «статус системного аудио» = off/0, которые Denon понимает как «согласуй режим системного аудио и включи ARC»).
Как только я ввёл этот запрос в интерактивную оболочку cec-client с помощью tx 15:70:00:00, Denon включился, и ARC закрепился за ним даже при том, что включены были только приставка и ТВ. Проверив вывод звука с телевизора, я убедился, что так и есть:

Начало вырисовываться решение проблемы — когда приставка просыпается и отправляет сигнал Active Source, в процесс должен включаться Pi и отправлять ресиверу запрос 15:70:00:00 для инициации согласования передачи аудио.
Теперь, когда основа процесса автоматизации стала ясна, самым очевидным действием казалось написание скрипта Bash, который будет запускать cec-client каждые несколько секунд и отправлять on 5. Такой подход, конечно, сработает, но не идеально. Поясню:
Использование цикла означает, что автоматизация происходит с задержкой, а не в виде реагирования на события шины CEC.
В каждой итерации мы запускаем новый cec-client, привязываемся к /dev/cec0, отправляем одну команду и завершаем процесс.
CEC же является общей шиной, а не каналом GPIO только для записи.
Более удачным решением будет:
Запустить один длительный процесс cec-client.6 [19]
Сделать так, чтобы он выводил каждую строку TRAFFIC, которую мы будем парсить.
Подавать ему на stdin команды tx..., только когда нужно вмешаться.
Единственная проблема в том, что в режиме мониторинга (-m) нельзя передавать команды. Поэтому для автоматизации переключаемся в:
cec-client -d 8
Директивы -m здесь уже нет. сec-client по-прежнему выводит весь трафик, но теперь также принимает команды. Наш скрипт Python использует его как мост между HDMI-средой и нашей логикой, в которой stdout отражает поток событий, а stdin выступает каналом управления.
Без экспериментов не обошлось, но в целом было нетрудно написать миниатюрный скрипт для отслеживания активации приставок и отправки в нужный момент магической команды 15:70:00:00. Исходники я положил на GitHub: jlian/cec_auto_audio [20]
Вот его логика:
Запуск cec-client -d 8 в виде подпроцесса.
Парсинг строк TRAFFIC.
Отслеживание поступления сигналов Active Source (0x82) с любого логического адреса Playback (0x4/0x8/0xB).
Отслеживание, когда Denon в последний раз отправлял сигнал Set System Audio Mode (5f:72:01), чтобы не вмешиваться в работу собственной логики Apple TV или телевизора.
Отправка tx 15:70:00:00, если этого ещё не сделало никакое другое устройство, и не более одного раза при каждом пробуждении приставки.
Несколько комментариев:
В скрипте не прописаны никакие имена устройств, производители или физические адреса.
Любой логический адрес Playback (0x4/0x8/0xB) он преобразует в Active Source в виде события «пробуждения приставки».
Когда Apple TV / Samsung / Denon справляются сами, он бездействует (так как поступает реальный сигнал 5f:72:01).
Выполняется скрипт как одиночный длительный процесс, привязанный к одному экземпляру cec-client.
Чтобы скрипт запускался при загрузке и продолжал работать, я завернул его под вид простой службы systemd. Использованный для этого юнит-файл можно найти в разделе README на GitHub [21]. Пользуюсь этим решением уже несколько дней — работает безупречно.
Надеюсь, что этой статьи будет достаточно, чтобы вы могли адаптировать изложенный подход под решение своих проблем с CEC. Моё решение ориентировано конкретно на сценарий связки «Denon + Samsung + приставки», но тот же принцип должен сработать и в отношении других причуд CEC.
Возможно, вы испытываете проблемы не с подключением приставок, а с активацией AVR. Может, у вас не согласуется DTS, или телевизор постоянно переключается обратно на свои динамики. Процесс в этих случаях будет тот же:
Взять Pi с HDMI-портом. Подключите Pi к HDMI-входу ресивера или телевизора с помощью кабеля micro-HDMI — HDMI [22] или переходника. Расположите его в надёжном месте.
Снять данные с шины. Выполните echo "scan" | cec-client -s -d 1, чтобы убедиться, что ваша малинка видит все предполагаемые устройства, а также узнать их логические/физические адреса и роли.
Зафиксировать «успешный» сценарий работы и «неудачный». Используйте cec-client -m -d 8, чтобы логировать трафик в процессе:
Активации «успешного» пути (например, Apple TV исправно подключает звук 5.1).
Активации «неудачного» пути (например, DTS возвращается к стерео-режиму, или ARC возвращается к динамикам ТВ).
Сравнить трейсы трафика. Ищите опкоды, которые фигурируют в успешном трейсе, но отсутствуют в неудачном. В моём случае интересным отличием было присутствие сигнала 5f:72:01 после активации Apple TV и отсутствие чего-либо подобного при активации только приставки.
Вставить опкод вручную. Зайдите на cec-o-matic.com [18] и создайте там недостающий пакет7 [23], после чего выполните cec-client -d 8.
Эта команда для использования cec-client в интерактивном режиме. Затем введите tx ... для отправки нужного магического пакета и смотрите, приведёт ли это к каким-либо изменениям. Если нет, попробуйте ещё раз с другим пакетом.
Скорее всего, вам нужно будет начать с пакета вроде 1f:... (от Recording 1 0x1 (логический адрес Pi) к Broadcast 0xF) или 15... (от Pi к Audio System 0x5) — это уже будет зависеть от ваших целей.
Обернуть всё в код. Когда вы разберётесь, какой магический пакет вам нужен, заверните его в небольшую программу вроде той, что показана выше, и пусть Pi тихо делает своё дело на шине.
Наглядно представить успешный и неудачный пути можно так:

Здесь ваша задача найти недостающий шаг и научить Pi его выполнять.
Apple TV продолжает исправно работать. PS5, Xbox и Switch теперь будят телевизор, Pi в течение полусекунды отправляет сигнал Denon, и вывод аудио остаётся закреплённым за ресивером. Задержка получается незначительная и воспринимается как естественная. Малинку я расположил там же в кладовке, где она играет роль заумного пульта управления.

Но есть ещё пара моментов, которые я не проработал:
Когда приставка уходит в сон, ТВ иногда «услужливо» переключается на антенный вход. У меня и антенны-то нет, поэтому в итоге вместо отката к Apple TV или известному рабочему входу возникает экран с надписью «No Signal». Такой ход технически корректен с позиции ТВ (его собственный тюнер всегда является рабочим источником), но ошибочен с точки зрения реального использования всего сетапа.
Мой механизм автоматического включения ТВ при закате иногда натыкается на нерабочий вход. Я настроил сценарий HomeKit, в котором ТВ включается примерно с заходом Солнца. Чаще всего это означает, что Apple TV пробуждается с приятной атмосферной заставкой. Но если последним источником до этого была приставка, ТВ включается на этом порте HDMI и просто показывает «No Signal», сбивая с толку гостей или домочадцев.
Это похожие проблемы, но они требуют чуть других решений:
При переходе приставки в режим ожидания ТВ становится активным источником. Когда приставка уходит в сон, она освобождает шину, и ТВ услужливо переключается на свой тюнер. Вспомогательный скрипт мог бы отслеживать эту конкретную пару пакетов (console Standby, TV Active Source) и после короткого ожидания переключать источник на Apple TV.
Отсутствие активного источника при автоматическом включении во время заката. В этом случае ТВ включается, но ни одно устройство (даже сам ТВ) не объявляет себя Active Source. В итоге он остаётся на последнем активном HDMI-порту, показывая «No Signal». Скрипт должен обнаруживать состояние, когда в течение N мс ТВ включён, Denon спит, а Active Source отсутствует, после чего будить Apple TV и ресивер, сменяя источник сигнала.
Или можно объединить оба этих решения, реализовав конечный автомат, который будет отслеживать «какой источник был активен последним» и автоматически откатываться к Apple TV, если на шине тишина, или ТВ переключается на свою антенну. В любом случае задача обеспечения адекватного вывода будет лежать на Pi.
Это уже превратит Pi в более общего «смотрителя HDMI», который будет не просто обеспечивать привязку ARC к ресиверу при воспроизведении аудио с внешних источников, но и откатывать систему обратно, когда с этих источников сигнала нет.
Похоже, дело попахивает развитием небольшого кустарного производства двухстраничных скриптов. Если вы адаптируете мой приём под решение каких-то своих траблов с HDMI-CEC, пришлите трейсы пакетов [24] — люблю собирать фольклор.
Логические адреса CEC обычно записываются в шестнадцатеричном виде, поэтому десятичный адрес 11 = 0xB = Playback 3. Дополнительно всё запутывается тем, что cec-client использует для логических адресов шестнадцатеричную запись, но обрезает префикс 0x. В этой статье для большей ясности я префиксы 0x добавил. ↩︎ [25] ↩︎ [26]
Полная неразбериха. Здесь мне реально помогло https://www.cec-o-matic.com [27] . ↩︎ [28]
Как ни удивительно, HDMI-CEC определяет лишь три логических адреса для устройств воспроизведения [29]: 0x4 (Playback 1), 0x8 (Playback 2) и 0xB (Playback 3). Это нормально, если у вас одна ТВ-приставка и пара игровых. У меня же к Denon было подключено четыре устройства воспроизведения (Apple TV, PS5, Switch 2, Xbox). Согласно HDMI-CEC, только три из них могут являться «реальными» устройствами воспроизведения одновременно. Поэтому, когда активируется четвёртое, ТВ и AVR приходится на ходу перераспределять логические адреса. На практике это выглядело так: если Switch была включена, изменить источник на Xbox было невозможно — моргал чёрный экран, и источником обратно выбирался Switch. Чертовщина какая-то. Но так как я использую лишь одну приставку одновременно, эта проблема меня не напрягает, и заморачиваться исправлением, пожалуй, нет смысла. ↩︎ [30] ↩︎ [31]
Не новая версия 8K, а старая, которую я пытался заставить работать с моей конфигурацией 4K120 [5]. ↩︎ [32]
Флаг -d — это битовая маска для уровней логирования (ERROR=1, WARNING=2, NOTICE=4, TRAFFIC=8, DEBUG=16, ALL=31), поэтому установка, например, -d 4 может не показать ничего интересного; истина кроется в исходном коде libcec. Так что -d 8 означает «только трафик». Изначально я попробовал -d 4 (notice) и при включении Apple TV ничего не увидел. В результате пришлось лезть в исходники libcec. Документация этой библиотеки оказалась на удивление скудной, и во многих источниках о ней содержалась обманчивая информация. ↩︎ [33]
Для libCEC есть привязки Python вроде python-cec [34], но документация для них никакая, и мне оказалось проще обернуть cec-client как подпроцесс. ↩︎ [35]
Использование ИИ-инструментов здесь тоже уместно — вставьте успешный и неудачный трейсы в тот же ChatGPT и попросите найти отличия, указав, что среди них могут быть недостающие пакеты CEC. Для более качественного анализа советую использовать модель с хорошими рассуждающими способностями, например, GPT-5.1-Thinking. ↩︎ [36]
Автор: Bright_Translate
Источник [37]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/domashniy-kinoteatr/439740
Ссылки в тексте:
[1] HDMI-CEC: https://en.wikipedia.org/wiki/Consumer_Electronics_Control
[2] ARC: https://en.wikipedia.org/wiki/HDMI#ARC_and_eARC
[3] Raspberry Pi 4: https://www.raspberrypi.com/products/raspberry-pi-4-model-b/
[4] Homebridge: https://homebridge.io/
[5] описывал свою медиа-сборку: https://johnlian.net/posts/media-closet/
[6] Eve Energy: https://www.evehome.com/en/eve-energy
[7] плагин homebridge-cec-tv-control: https://github.com/electroflame/homebridge-cec-tv-control
[8] systemd: https://systemd.io/
[9] High-Definition Multimedia Interface: https://en.wikipedia.org/wiki/HDMI
[10] 1: #id1
[11] 2: #id2
[12] 3: #id3
[13] Hue Play Sync Box: https://www.philips-hue.com/en-us/support/product/sync-box/100010#How_do_I_know_if_I_have_a_sync_box_4K_or_8K
[14] 4: #id4
[15] EDID: https://en.wikipedia.org/wiki/Extended_Display_Identification_Data
[16] libcec: https://github.com/Pulse-Eight/libcec
[17] 5: #id5
[18] cec-o-matic.com: http://cec-o-matic.com
[19] 6: #id6
[20] jlian/cec_auto_audio: https://github.com/jlian/cec_auto_audio
[21] в разделе README на GitHub: https://github.com/jlian/cec_auto_audio?tab=readme-ov-file#running-as-a-systemd-service
[22] кабеля micro-HDMI — HDMI: https://www.amazon.com/dp/B06WWQ7KLV
[23] 7: #id7
[24] пришлите трейсы пакетов: https://johnlian.net/about/#contact
[25] ↩︎: #id11
[26] ↩︎: #id111
[27] https://www.cec-o-matic.com: https://www.cec-o-matic.com
[28] ↩︎: #id12
[29] определяет лишь три логических адреса для устройств воспроизведения: https://feintech.eu/en/blogs/know-how/wozu-dient-hdmi-cec
[30] ↩︎: #id13
[31] ↩︎: #id133
[32] ↩︎: #id14
[33] ↩︎: #id15
[34] python-cec: https://pypi.org/project/cec/
[35] ↩︎: #id16
[36] ↩︎: #id17
[37] Источник: https://habr.com/ru/companies/ruvds/articles/978150/?utm_source=habrahabr&utm_medium=rss&utm_campaign=978150
Нажмите здесь для печати.