Add documentation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 13:23:58 +03:00
parent 84fb48f39d
commit b077388b74
24 changed files with 7157 additions and 8 deletions

61
docs/research/README.md Normal file
View File

@@ -0,0 +1,61 @@
# Исследование движков воспроизведения (начало проекта, 2026-10-11…12)
Это исследование сделано **до** написания кода: по исходникам, issue-трекерам, Maven и дампу тестового ТВ. Часть выводов потом подтвердилась на реальном телевизоре, часть оказалась неверной. Что изменилось после тестов, сказано в конце этого файла, а итог описан в [`../playback.md`](../playback.md).
## Файлы
| Файл | О чём |
|---|---|
| [`decisions.md`](decisions.md) | Принятые решения: гибридный движок, выбор движка, fallback, тестовый ТВ, что проверить прототипом (часть 7 общего [`jellyfin-client-research.md`](jellyfin-client-research.md)) |
| [`media3.md`](media3.md) | media3/ExoPlayer: Dolby Vision, HDR, tunneling, частота дисплея, passthrough, FFmpeg, ASS (libass), контейнеры, DeviceProfile из jellyfin-androidtv |
| [`engines.md`](engines.md) | Как устроены несколько движков в других клиентах (Findroid, Wholphin, JellyPlay, Swiftfin, Plezy, Kodi…), дизайн абстракции, Gradle-модули, libVLC против libmpv, алгоритм выбора, runtime-fallback |
| [`device-xiaomi.md`](device-xiaomi.md) | Тестовый ТВ Xiaomi S Pro Mini LED 75 и семейство MediaTek: известные баги декодеров, DV, passthrough, чек-лист проверок |
| [`library-profile.md`](library-profile.md) | Профиль медиатеки тестового сервера: какие контейнеры, кодеки, HDR, аудио и субтитры встречаются и сколько |
| [`tv-dump/`](tv-dump/) | Сырые дампы ТВ: `media_codecs-mt5896.xml`, `dumpsys display`, `audio_policy` |
Полный исходный документ со всеми частями (Jellyfin API, обзор клиентов, libVLC, Android TV, инструменты): [`jellyfin-client-research.md`](jellyfin-client-research.md).
## Главное из исследования
**Цель** — воспроизводить всё, что можно, без серверного транскода и с правильным HDR и Dolby Vision. Официальный клиент Jellyfin, Moonfin и Dune на этом ТВ спотыкались каждый на своих файлах: транскодировали, давали тусклый HDR или зависали.
1. **Одного движка недостаточно.**
- media3/ExoPlayer — единственный путь к аппаратному декодированию с HDR10, HDR10+, HLG и DV, а также к passthrough. Но он не открывает M2TS, ASF/WMV, ISO/BDMV, MKV со сжатием zlib, не умеет программно H.264 Hi10P, VC-1, MPEG-4 ASP и не рисует ASS сам.
- libVLC и libmpv играют почти всё через ffmpeg, но HDR у них ненадёжен.
- Отсюда **гибрид: media3 основной, libVLC запасной.**
2. **Запасным выбран libVLC 3.7.x, а не libmpv.**
- Это официальный артефакт VideoLAN со стабильным Java API, внутри libass, PGS и VobSub, есть passthrough.
- Стоковый mpv-android переводит HDR в SDR через GLES, а AAR у него неофициальный.
- Цена libVLC — +24–55 МБ на ABI.
3. **Автоматический выбор движка до запроса PlaybackInfo.** Данные файла от Jellyfin (контейнер, кодек, битность, `VideoRangeType`, профиль DV, дорожки) сравниваются с возможностями устройства (`MediaCodecList`, HDR-типы дисплея, `AudioTrack.isDirectPlaybackSupported`). У каждого движка свой DeviceProfile.
4. **Runtime-fallback по образцу Plezy.** Если движок упал, завис или не дал кадра, сессия переходит на другой движок с той же позиции; если не справился и он — серверный транскод. Среди Jellyfin-клиентов такого нет ни у кого.
5. **Архитектура:**
- свой узкий интерфейс `PlaybackEngine` плюс интерфейсы-возможности (задержка звука, стиль субтитров и т. п.);
- каждый движок — отдельный Gradle-модуль, подключается через Hilt `@IntoSet` (позже DI перевели на Koin);
- реализация движка «как media3 `Player`» (Findroid, Wholphin) приводит к десяткам `TODO()`, поэтому так не делаем.
6. **Риски, найденные заранее:**
- DV P7 в media3 без DV-декодера не откатывается на HEVC;
- DV + HDR10+ зависает на MediaTek (#1895);
- HEVC-декодер MTK падает посреди потока (#3421), и `setEnableDecoderFallback` это не ловит;
- вечная буферизация при AC3 passthrough после быстрых перемоток (#3407);
- TrueHD и DTS-HD из приложения на встроенных динамиках, скорее всего, не выйдут в passthrough;
- смену частоты и HDR-режима media3 сам не делает.
7. **Тестовый ТВ** (дамп через adb):
- MediaTek MT9689, Android 14, **только 32-bit userspace (armeabi-v7a)**, 2,4 ГБ RAM;
- один режим дисплея 4K@60;
- дисплей заявляет DV, HDR10, HDR10+ и HLG;
- аппаратные AVC/HEVC/VP9/AV1 до 8K, MPEG-2/VC-1/MPEG-4 до 1080p.
## Что тесты потом опровергли или уточнили
| Ожидание из исследования | Как оказалось на ТВ |
|---|---|
| DV P7 есть в железе (по XML-дампу) | В рантайме `MediaCodecList` **не показывает** `dvhe.dtb` (P7). Наличие в XML не гарантирует наличие в API |
| H.264 Hi10P — только программно (libVLC) | `c2.mtk.avc.decoder` заявляет High10 и **играет Hi10P аппаратно** |
| DV + HDR10+ виснет на MediaTek (#1895) | На этом ТВ играет без зависаний, и с DV-декодером, и как базовый слой |
| DV-декодер даёт «тусклую» картинку | ТВ действительно включает режим Dolby Vision (значок, DispService `Hdr Dolby`). «Тускло» — это режим изображения DV в настройках ТВ, а не поломка. DV-декодер включён по умолчанию |
| Смена частоты нужна вручную (`preferredDisplayModeId`) | Android видит один режим 4K@60, но **на аппаратном видеослое ТВ сам переключает панель**: 23,976 → 119,88 Гц, 25 → 100 Гц. Через GPU-путь (TextureView) этого нет |
| HTTPS для libVLC достаточно проверить на самоподписанном сертификате | libVLC (GnuTLS) через reverse proxy ненадёжен даже с нормальным сертификатом: зацикливается prefetch, перемотка к концу файла ждёт по ~10 с. Решено локальным HTTP-прокси внутри приложения |
| `VLCVideoLayout` и libVLC — вопрос API | Главные проблемы VLC оказались в звуке (многоканальный PCM на этом ТВ беззвучен) и в сетевом кэше (prefetch выбрасывает прочитанное) |
| VC-1 через media3, если есть декодер | Декодер зарегистрирован как MIME `video/VC1` (регистр!), а media3 ищет `video/wvc1` |
| Аппаратное декодирование в libVLC — по умолчанию | Опрос MediaCodec модулем VLC на MediaTek длится ~45 с. Аппаратно в VLC включаем только для видео больше 1080p |

View File

@@ -0,0 +1,55 @@
# Часть 7. Гибридный движок воспроизведения (дополнение от 2026-10-12)
Цель проекта уточнена: не «клиент на libVLC», а **клиент, который воспроизводит всё возможное без серверного транскода и с корректным HDR/Dolby Vision**. Полные отчёты: [media3.md](media3.md), [engines.md](engines.md), [device-xiaomi.md](device-xiaomi.md); сырые дампы тестового ТВ — [tv-dump/](tv-dump/).
## 7.1 Принятые решения
| Вопрос | Решение |
|---|---|
| Движок | Гибрид: **media3/ExoPlayer — основной** (аппаратный MediaCodec, HDR10/HDR10+/HLG/DV, passthrough), **libVLC 3.7.7 — запасной** (программный ffmpeg для того, что не тянет железо/media3). mpv и libVLC 4 — возможные будущие модули. |
| Расширяемость | Свой узкий интерфейс `PlaybackEngine` + capability-интерфейсы (AudioDelay, SubtitleDelay, Passthrough, SubtitleStyle…); каждый движок — отдельный Gradle-модуль, регистрируется через Hilt `@IntoSet`; один адаптер `SimpleBasePlayer` в ядре для MediaSession/UI. Треки адресуются индексами Jellyfin `MediaStream`. |
| Выбор движка | Автоматический, **до** `PlaybackInfo`: данные файла (контейнер, кодек, профиль, битность, `VideoRangeType`, DV-профиль, субтитры, аудио) × возможности устройства (`MediaCodecList`, HDR-типы дисплея, `AudioCapabilities`, `RendererCapabilities` media3). Порядок: ручной выбор → media3 Direct → вариант с наименьшими потерями (HDR важнее всего) → серверный транскод. DeviceProfile — свой для каждого движка. |
| Runtime-fallback | Как в Plezy: при ошибке декодера / зависании / 0 кадров — один переход на другой движок с сохранением позиции и треков; если упал и он — транскод. Ни один Jellyfin-клиент этого не делает — это ключевое отличие проекта. |
| Сервер / SDK | Только Jellyfin 12.x, `jellyfin-sdk-kotlin` 1.9.x. |
| Платформа | Сначала Android TV, общие модули для будущего phone-UI. |
| DI / лицензия | Hilt (позже заменён на Koin); GPL-3.0-or-later; собственное название без Jellyfin-бренда. |
| Сборки | `armeabi-v7a` обязателен (тестовый ТВ — 32-bit userspace), сплиты по ABI; flavors `full` (Exo+VLC) / `lite` (только Exo). Dynamic Feature Delivery не подходит (sideload/F-Droid). |
## 7.2 media3: что играет и что нет (кратко)
- **Да:** HDR10/HDR10+/HLG, DV в MKV и MP4 (`dvcC`/`dvvC`), passthrough AC3/E-AC3/JOC/TrueHD/DTS/DTS-HD, ASS через `io.github.peerless2012:ass-media` (libass), PGS/VobSub/DVB внутри MKV, AVI. Вывод только через `SurfaceView` (Compose `PlayerSurface` с `SURFACE_TYPE_SURFACE_VIEW`).
- **Нет (→ libVLC):** M2TS, ASF/WMV, ISO/VIDEO_TS/BDMV, MKV с zlib, ordered chapters; H.264 Hi10P; VC-1/MPEG-2/MPEG-4 ASP без HW-декодера; TrueHD/PGS/VC-1 в TS; внешние `.sup`/`.idx`. FFmpeg-расширение media3 — только аудио (`org.jellyfin.media3:media3-ffmpeg-decoder`).
- **Риски:** DV P7 без DV-декодера не имеет фолбэка на HEVC (нужен свой `MediaCodecSelector` или `DOVI_WITH_EL` как неподдерживаемый); DV P8.1+HDR10+ виснет на MediaTek (#1895); падения HEVC-декодера MTK посреди потока не ловятся `setEnableDecoderFallback`; смену частоты/HDR-режима дисплея делать самим (`preferredDisplayModeId` + ожидание `onDisplayChanged`); все PGS-дорожки декодируются даже невыбранные (#2667, память). Tunneling — опция, по умолчанию выкл.
- jellyfin-androidtv объявляет в direct play ASF/WMV/VOB, которые media3 не открывает, — вероятный источник ошибок официального клиента.
## 7.3 libVLC vs libmpv как запасной
libVLC 3.7.7 выбран: официальный артефакт VideoLAN с регулярными релизами, стабильный Java API, libass/PGS/VobSub внутри, passthrough, выравнивание 16 KB есть. libmpv (`dev.jdtech.mpv:libmpv`) меньше (≈25 МБ против 55 МБ несжатых `.so` на arm64), но артефакт неофициальный, а HDR в стоковом mpv-android уходит в SDR через GLES. Для VLC при `useLegacyPackaging=true` размер ≈ +24 МБ.
## 7.4 Тестовый ТВ: Xiaomi TV S Pro Mini LED 75 (снято через adb 2026-10-12)
| Параметр | Значение |
|---|---|
| Модель / ОС | `MiTV-MOEU1` (`lalaland`), Android 14 / API 34, `leanback_only` |
| SoC | MediaTek **MT9689** (`ro.hardware=mt5896`, платформа `merak`) |
| ABI | **только `armeabi-v7a`** (32-bit userspace) |
| RAM | 2,4 ГБ видно системе; `MemAvailable` ≈ 870 МБ (MemFree ≈ 60–240 МБ вводит в заблуждение — остальное кэш), swap 600 МБ почти занят — бюджет памяти умеренно жёсткий; хранилище 22 ГБ свободно |
| UI / дисплей | приложения рисуются в 1920×1080 (320 dpi), видеоплоскость 4K; Android видит **один режим 3840×2160@60** — переключение частоты через Android API недоступно; есть Xiaomi-настройка `mitv_vrr_frame_rate_enable_23` (не исследовано) |
| HDR дисплея | DV, HDR10, HLG, HDR10+ |
| HW-видео (`media_codecs-mt5896.xml`) | AVC/HEVC/VP9/AV1 до 8K; MPEG-2, VC-1, WMV3, MPEG-4, H.263, RealVideo, AVS/AVS2 до 1080p; DV-декодеры `dvav.per/pen`, `dvhe.der/den/dtr/stn/dth/dtb/st`, `dvav.se`, `dvav1` — т.е. профили 1–10, **включая P7 (`dvhe.dtb`)** |
| Аудио | Dolby MS12; выходы заявляют AC3, E-AC3, E-AC3-JOC, TrueHD, DTS, DTS-HD; сейчас активен только `speaker` |
Расхождения с веб-исследованием: исследование по открытым источникам предполагало отсутствие P7 и VC-1 в железе — дамп устройства это опровергает (наличие в XML ≠ гарантированная работа через MediaCodecList; проверить в прототипе).
Известные проблемы этого семейства (проверить на устройстве): jellyfin-androidtv #5825 — на Xiaomi S Pro (кит. HyperOS) media3-клиенты не включают DV, а Just Player включает; stutter `c2.mtk.hevc.decoder` (media3 #3347); вечная буферизация при AC-3 passthrough после быстрых seek на MTK audio HAL (#3407); искажённый E-AC3 passthrough на Xiaomi (jellyfin #5366); MTK DV-декодеру нужен 4-байтный start code для NAL 62/63 (квирк Kodi).
## 7.5 Что проверить первым прототипом
1. DV P5, P8.1, P7 (MEL/FEL), P8.1+HDR10+ через media3 — включается ли DV-режим ТВ (кейс #5825).
2. HDR10 / HDR10+ / HLG; возврат дисплея из HDR после выхода.
3. 24p-контент: есть ли judder при фиксированных 60 Гц; что делает `Surface.setFrameRate`.
4. VC-1 и MPEG-2 через media3+MediaCodec vs libVLC; H.264 Hi10P через libVLC (хватит ли CPU).
5. ASS со шрифтами (ass-media vs libVLC), PGS, VobSub.
6. Звук: динамики ТВ и (если есть) eARC — passthrough E-AC3/JOC, TrueHD, DTS-HD; быстрые seek при passthrough.
7. M2TS / WMV / ISO через libVLC; runtime-переход Exo → VLC при сбое.
8. Потребление памяти обоих движков на 2,4 ГБ RAM.

View File

@@ -0,0 +1,134 @@
# Xiaomi TV S Pro Mini LED 75: что важно знать для Jellyfin-клиента (media3 + libVLC/libmpv)
Дата исследования: 2026-10-12. Пометка **(не проверено)** означает, что в первоисточнике факт не подтверждён: это вывод по аналогии, данные агрегатора или сообщение одного пользователя.
---
## 0. Главное: под одним названием продаются три разных устройства
| Вариант | Где продаётся | ОС | SoC | RAM/ROM | Комментарий |
|---|---|---|---|---|---|
| **Xiaomi TV S Pro Mini LED 75 2026** (глобальная, RU: артикул вида L75MB-S**RU**/-SME) | Глобальный и российский рынок, с конца 2025 / начала 2026 | **Google TV на Android TV 14** | Quad Cortex-A73 + **Mali-G52 (2EE) MC1**, по классу это MediaTek Pentonic 700 (вероятно MT9653) **(модель чипа не проверена)** | 3 / 32 ГБ | Наиболее вероятный тестовый девайс, если ТВ куплен в РФ/ЕС с Google TV |
| **Xiaomi TV S Mini LED 75 2025** (без «Pro», глобальная) | ЕС/РФ с конца 2024 | Google TV | Тот же Quad A73 + Mali-G52 MC1 | 3 / 32 ГБ | Платформа та же. На 4PDA модель 2026 называют «урезанной китайской S 2025» |
| **Xiaomi TV S Pro 75 2025/2026, китайская версия** | Китай (бывает в серых поставках) | **HyperOS 2/3** (Xiaomi TV OS, без GMS) | **MediaTek MT9655 (Pentonic 800)** + сопроцессор Xiaomi XM9000 | 4 / 64 ГБ | 240–330 Гц, до 5200 нит. Отдельный issue Jellyfin про DV (см. §3) |
**Что сделать сразу:** определить вариант по `adb shell getprop ro.product.model`, `ro.board.platform`, `ro.hardware`, `ro.build.version.release` и `ro.product.device`. Список кодеков взять из `adb shell dumpsys media.player` или `/vendor/etc/media_codecs*.xml`, плюс посмотреть `dumpsys audio` и `dumpsys display`. Дальше в отчёте основной вариант — глобальная модель на Google TV, особенности китайской указаны отдельно.
---
## 1. Характеристики (глобальная S Pro Mini LED 75 2026)
Источник: официальная страница mi.com/global, проверено загрузкой страницы.
- **CPU/GPU:** Quad Cortex-A73, Mali-G52 (2EE) MC1. **RAM 3 ГБ, ROM 32 ГБ.**
- По таблице Wikipedia сочетание A73 @1.4 ГГц + Mali-G52 MC1 соответствует **Pentonic 700 (MT9618 / MT9653 / MT9689)**. Pentonic 800 (MT9655) использует уже Mali-G57.
- Clubic называет для S Mini LED 2025 чип **MT9653** **(не проверено официально)**.
- Похожее сочетание есть у MT9613 (A73 1.3 ГГц, G52 MC1), но у него максимум 120 Гц, а у Pentonic 700 VRR до 144 Гц. Поэтому MT9653 вероятнее.
- **ОС:** Google TV, Android TV 14 (обзоры Onliner и FormulaTV на 55" 2026, модель L55MB-SRU). Это Android 14, API 34.
- **Панель:** QD-Mini LED VA, 4K, 704 зоны (75"), пиковая яркость 1700 нит. Xiaomi заявляет 144 Гц, Onliner пишет о **нативных 120 Гц** (144 Гц доступны в игровом режиме / HDMI enhanced). Game Boost даёт 288 Гц ценой падения разрешения до 1440p и глубины цвета до 8 бит. MEMC 4K120. VRR (FreeSync Premium), ALLM.
- **HDR:** Dolby Vision, HDR10+, HDR10, HLG, Filmmaker Mode.
- Dolby Vision IQ и IMAX Enhanced официально указаны только у S Mini LED 2025. У 2026 DV IQ, по Onliner, нет.
- Отзывы: в DV тёмные сцены теряют детали и выглядят темнее, чем в HDR10+ (Onliner, FormulaTV). Choose.tv о HDR тоже отзывается негативно. Can Buy or Not (индийская S Mini LED 2026) пишет, что на их образце DV не было вовсе. Индийская линейка, вероятно, на другой платформе **(не проверено)**.
- **Звук:** 2×15 Вт, Dolby Atmos, Harman AudioEFX (приходит через OTA).
- На глобальной странице mi.com **DTS не указан**. В российских обзорах (Onliner, FormulaTV) и в обзоре AbsoluteGeeks упомянут **DTS:X**. Сам чип MediaTek поддерживает «DTS Virtual:X».
- Вывод: декодер DTS в системе, скорее всего, есть, но это нужно проверить через `AudioManager`/`dumpsys audio` **(не проверено)**.
- **HDMI:** 3× HDMI 2.1 (CEC, ALLM, VRR). HDMI 1 и 2 поддерживают 4K144. **eARC на HDMI 2.** Третий порт без FRL (4K60, 8 бит). Есть оптический выход, 3.5 мм, USB 2.0 + USB 3.0, LAN, Wi-Fi 6, BT 5.2.
- **Netflix:** по Onliner, приложение не сертифицировано («эта версия несовместима с устройством»), FormulaTV пишет обратное. Это косвенный признак «сырой» сертификации прошивки.
- **Китайская версия (HyperOS):** MT9655, 4/64 ГБ, HyperOS 3.0, заявлен DTS:X. Источник — блог-обзоры, **(частично не проверено)**.
---
## 2. Аппаратные видеодекодеры
По данным MediaTek для MT9613 и таблицы Wikipedia для семейства MT96xx и Pentonic.
| Кодек | Поддержка | Комментарий |
|---|---|---|
| HEVC Main10 4K60 | Да | Основной путь. Имена декодеров на MTK TV: `OMX.MTK.VIDEO.DECODER.HEVC` или `c2.mtk.hevc.decoder` |
| AV1 4K60 10-bit | Да | Заявлен у всего семейства MT96xx и Pentonic |
| VP9 Profile 2 (10-bit) | Да | 4K60@10bit по спецификации. Поддержку HDR-профиля проверить в MediaCodecList |
| H.264 | Да | High 4K. Hi10P (10-bit H.264) аппаратно почти наверняка **нет** — нужен софт-фоллбэк **(не проверено)** |
| AVS2/AVS3 | Да | Для нас неважно |
| MPEG-2 | Почти наверняка да | ТВ с DVB-T2/C/S2 обязан декодировать MPEG-2. Выставлен ли он в MediaCodec, **(не проверено)** |
| VC-1 / WMV3 | Неизвестно | На MTK TV чипах исторически есть, но наружу в MediaCodec часто не выставляется **(не проверено)**. Предусмотреть софт (ffmpeg в libVLC/libmpv) |
| Dolby Vision | Да (декодер `video/dolby-vision`) | Имена у MTK: `OMX.MTK.VIDEO.DECODER.DVHE.STN` (видели на Fire TV на MTK) или `…DVHE.STH`. Профили 5 и 8.x — да. **Профиль 7 (dual-layer, FEL/MEL) аппаратно — скорее всего нет** (общая ситуация для Android TV, PR Jellyfin #5470) **(для этого SoC не проверено)** |
**Квирк MTK DV-декодера (Kodi).** Декодер `OMX.MTK…DVHE` ожидает 4-байтовые start code (`00 00 00 01`) для NAL-юнитов типов 62/63 (RPU и EL). Kodi включает «DoviWorkaround» в BitstreamConverter для `OMX.MTK*` + `video/dolby-vision` и для `OMX.MTK` задаёт invalid-PTS = −1 («use DTS»). Источник: патч quietvoid, вошёл в Kodi 20 через PR #19099. Для собственного пайплайна (не media3) это обязательно.
---
## 3. Известные проблемы на этом ТВ и SoC-семействе
### 3.1. Прямо про Xiaomi S Pro
- **jellyfin-androidtv #5825 (2026-09, открыт).** Xiaomi S Pro 2025 65", китайская, **HyperOS 3.0**. После обновления до HyperOS 3.0 Dolby Vision во встроенном плеере Jellyfin (ExoPlayer/media3) **не включается**: «неправильные цвета», т.е. сигнал идёт как HEVC/HDR10 или вообще без HDR. То же в Wholphin. А **Just Player** (тоже media3) и системный плеер Xiaomi включают DV на том же файле (WEB-DL DV P8 без HDR10+). Подтверждено вторым пользователем на beta2.
- Вывод: на этой платформе ExoPlayer-путь Jellyfin неверно выбирает декодер или неверно определяет DV, а у Just Player это работает. Стоит изучить, как Just Player выбирает декодер `video/dolby-vision` (MediaCodecSelector / DV→HEVC fallback) и как сигнализирует HDR.
- **jellyfin-androidtv #5366.** «Xiaomi TV / Mi Box», Android 9: E-AC3 в passthrough звучит «как из коробки», Kodi же декодирует в stereo нормально. Закрыт без решения. Модель не указана, поэтому к нашему ТВ относится только косвенно.
- **4PDA (тема S Mini LED 2025):** со временем перестаёт работать MEMC и появляются подёргивания. **Проблемы со звуком по SPDIF при включённом MEMC** (вероятно, рассинхрон или задержка) **(не проверено, пересказ)**. Тема S Pro 2026: https://4pda.to/forum/index.php?showtopic=1115060 (сам 4PDA вернул 403, содержание взято из поисковых сниппетов).
- Onliner (55" 2025, глобальная): жалобы на зависания и проблемы с приложениями.
### 3.2. Семейство MediaTek Pentonic 700 / MT96xx (TCL, Hisense, Philips, Sony)
- **Kodi на Hisense U7K (MT9618, Pentonic 700):** в dev-сборке Kodi 21 DV-файлы давали чёрный экран при рабочем звуке, после ноябрьского nightly MP4-DV заиграл. На U8H предыдущего поколения DV через Kodi работал (forum.kodi.tv tid=374191). Там же сказано, что Jellyfin и Plex DV на U7K играют.
- **Hisense:** Kodi видит DV-дисплей, но при DV-воспроизведении ТВ показывает HDR10+. Значит, сигнализация HDR через MediaCodec не всегда соответствует ожиданиям.
- **DV P7:** по общему консенсусу Kodi-форума, Android-устройства P7 «как есть» не поддерживают. Варианты: режим совместимости Kodi (P7 → P8.1, EL отбрасывается, по качеству это примерно MEL) или фоллбэк на HDR10 base layer. FEL на встроенных ТВ-приложениях фактически недостижим.
- **Philips Android TV на MediaTek (jellyfin #5827, media3 #3421):** `OMX.MTK.VIDEO.DECODER.HEVC` **падает посреди потока** (vendor-internal error после успешной инициализации, при resume/seek в конкретный файл). `setEnableDecoderFallback` это не ловит, потому что работает только при ошибке init. Обходной путь: свой `MediaCodecSelector`, который исключает упавший декодер, затем re-prepare + seek, с переходом на `c2.android.hevc.decoder`. Jellyfin PR отклонил, issue в media3 открыт.
- **media3 #2711 / #2765:** MediaTek HEVC перестаёт выдавать кадры или выдаёт чёрный/замёрзший кадр после OS-апдейтов (это телефоны Dimensity, но драйверы того же вендора).
- **media3 #3347:** `c2.mtk.hevc.decoder` на Google TV Streamer (MT8696) отдаёт около 30% кадров с опозданием 30–130 мс на некоторых x265-файлах, отсюда постоянный stutter. **VLC на том же железе работает нормально.** Аргумент в пользу libVLC/libmpv как фоллбэка.
- **media3 #3407:** Sony Bravia (Android 12, **MediaTek audio HAL**), AC-3 passthrough без tunneling. После двух быстрых seek плеер навсегда остаётся в BUFFERING: позиция AudioTrack идёт назад и трактуется как 32-битный wrap. В tunneled-режиме (HW_AV_SYNC) не воспроизводится.
- **jellyfin #5028:** Philips (MediaTek) — E-AC3 7.1 при транскоде даёт slow-motion без звука, downmix в stereo помогает.
- **jellyfin #5093:** TCL C855 (MediaTek, Android 12) — регрессии в capability-репорте 0.19.x (DOVIInvalid, P8 с CCID 6, «range type not supported») вызывают лишний транскод. Урок: не отбрасывать «невалидные» DV-потоки, а давать фоллбэк на HDR10.
- **jellyfin #4021:** файлы DV + HDR10+ на многих Android TV дают чёрный экран. Нужна настройка «игнорировать HDR10+ в DV-файлах», как «Allowed HDR dynamic metadata formats» в Kodi. Для нашего ТВ (поддерживает и DV, и HDR10+) это менее критично, но правило полезно для универсальности.
### 3.3. Аудио passthrough на ТВ (встроенные приложения → eARC)
- По опыту TCL 2022/2023 (MediaTek) из XDA: из **внутренних приложений** ТВ отдаёт по eARC DD, DD+, DD+ Atmos (JOC) и AC-4, а **TrueHD — «no support»** (по сообщениям, конвертируется в DD+ без Atmos). DTS-HD MA и DTS:X на TCL 2022 тоже отмечены как «no support». Форумная формулировка: «нет ТВ на рынке, который выдаёт TrueHD из внутренних приложений».
- Для Xiaomi это **не проверено**, но ожидаемо то же самое: **TrueHD и DTS-HD MA bitstream из приложения, скорее всего, не пройдут**. Правильное поведение клиента — опрашивать `AudioManager` (`getDirectProfilesForAttributes` / `isDirectPlaybackSupported` на API 33+, `AudioCapabilities` в media3) и при отсутствии поддержки декодировать в PCM на клиенте (ffmpeg/libVLC: TrueHD → PCM 7.1 или EAC3-реэнкод), а не транскодировать на сервере.
- **DTS:** у глобальной модели в спецификации нет, в российских обзорах указан DTS:X **(не проверено)**. Опираться только на рантайм-проверку `ENCODING_DTS`/`ENCODING_DTS_HD` и иметь софтверный DTS-декодер (ffmpeg dca) как фоллбэк. Тренд на отказ от DTS подтверждается: LG убрал DTS из ТВ 2025 года, Google TV Streamer не умеет DTS-HD/TrueHD passthrough.
- Сценарий «Jellyfin/Kodi на внешнем боксе → HDMI → ТВ → eARC → AVR» пропускает TrueHD и DTS-HD. Для приложения на самом ТВ это не применимо.
---
## 4. Общие особенности MediaTek Android TV для media3 и libVLC/libmpv
1. **Dolby Vision через MediaCodec.**
- Опрашивать `MediaCodecList` по `video/dolby-vision` с профилями: DvheStn = P5, DvheSt = P8. P7 на MTK обычно не объявлен.
- При отсутствии P7: преобразовывать P7 → P8.1 на лету (выкинуть EL, переписать RPU; так делают Kodi, `DvCompatVideoRenderer` из PR #5470 и dovi_tool) или играть BL как HDR10.
- Учесть, что media3 `MatroskaExtractor` может не распознать DV при «раздутом» CodecPrivate от MakeMKV (~800 байт). Фоллбэк — `videoRangeType` от сервера Jellyfin.
- На этом ТВ (особенно HyperOS 3.0) проверить, какой декодер выбирает media3 и запускается ли DV-режим: см. #5825, Just Player справляется.
- Для libVLC/libmpv: DV через mediacodec в libmpv (`hwdec=mediacodec`) зависит от передачи `video/dolby-vision`. libmpv обычно отдаёт HEVC, и получается HDR10 base layer. libVLC 4.x умеет DV через MediaCodec ограниченно **(не проверено)**. Реалистично DV только через media3, а libVLC/libmpv использовать для HDR10/HLG/SDR и экзотических кодеков.
2. **HDR-сигнализация.** На ТВ (в отличие от бокса) режим HDR включает видеопайплайн по метаданным декодера, переключения HDMI-режима нет. Нужен корректный `KEY_COLOR_TRANSFER`/`KEY_HDR_STATIC_INFO`, иначе картинка «блёклая». Для libmpv лучше `vo=mediacodec_embed` (прямой вывод в Surface с HDR-метаданными), а не gpu/gpu-next: GPU-рендер на Mali-G52 MC1 4K HDR не потянет, и HDR-режим ТВ не включится.
3. **Tunneled playback.** MTK TV декодеры обычно объявляют `FEATURE_TunneledPlayback` **(для этого SoC не проверено)**. Плюсы: точный A/V sync с HW_AV_SYNC, меньше рывков, в #3405 туннелирование помогло пропускной способности, баг #3407 в туннельном режиме не воспроизводится. Минусы: не все аудиоформаты (passthrough + tunneling на некоторых HAL ломается), не работают субтитры поверх с некоторыми рендерерами, ограничения seek. Нужен пользовательский переключатель и тест на устройстве.
4. **Смена частоты (AFR).**
- Встроенная панель 120/144 Гц: «переключение режима HDMI» как на боксе не нужно, но ТВ должен перейти на кратную частоту (24p → 120 Гц, 5:5) без judder.
- На Android 12+ есть системная опция «Match content frame rate» (Display & Sound → Advanced display) и API `Surface.setFrameRate` (media3: `setVideoChangeFrameRateStrategy`). Seamless-переход поддерживается, non-seamless требует `preferredDisplayModeId`.
- Как Xiaomi реализует это на 144-герцовой панели и не сбивает ли MEMC, **(не проверено)**. Протестировать 23.976/25/50/59.94 и проверить `dumpsys display` (supportedModes) и `dumpsys SurfaceFlinger`.
5. **Слабое железо.** 3 ГБ RAM и Mali-G52 MC1: софт-декод 4K HEVC/AV1 невозможен. libVLC/libmpv использовать только с hwdec (mediacodec). ASS-субтитры (libass) на 4K-Surface — нагрузка на CPU, а overlay-рендер поверх HDR-видео может ломать HDR (#5576: PGS + DV/HDR вызывает ошибку). Высокие битрейты (UHD-ремуксы 80–100 Мбит/с) требуют большого буфера и быстрой сети.
- **Проверить скорость Ethernet-порта:** у многих ТВ он 100 Мбит/с **(для этой модели не проверено)**. Для ремуксов UHD BD это критично, Wi-Fi 6 может оказаться быстрее.
6. **Ошибки декодера посреди потока** (MTK HEVC, #5827/#3421): реализовать runtime-fallback (исключить декодер → re-prepare → seek) и/или переход на libVLC/libmpv.
7. **AudioTrack passthrough на MTK HAL** (#3407): после быстрых seek возможна «вечная буферизация». Нужен watchdog: если плеер буферизуется дольше N секунд при полном буфере, делать re-seek или flush.
8. **E-AC3 passthrough** на некоторых Xiaomi/MTK звучит искажённо (#5366). Нужна настройка «декодировать EAC3 в PCM» на клиенте.
---
## 5. Чек-лист проверок на реальном ТВ
1. `adb shell getprop | grep -iE "model|platform|hardware|board|version.release|build.fingerprint"`: какая из трёх моделей и какой SoC.
2. `adb shell dumpsys media.player` и `cat /vendor/etc/media_codecs*.xml`: список декодеров, профили DV (dvhe.05/.07/.08?), VC-1, MPEG-2, H.264 Hi10, tunneling.
3. `adb shell dumpsys audio | grep -iA5 "encodings\|formats"` + `dumpsys media.audio_flinger`: какие форматы HAL поддерживает в direct/offload при подключённом eARC-ресивере. Проверить AC3/EAC3/EAC3-JOC/TrueHD/DTS/DTS-HD.
4. `adb shell dumpsys display | grep -iE "mode|hdr"`: supportedModes (60/120/144?), HdrCapabilities (DV, HDR10, HDR10+, HLG).
5. Тест-файлы: DV P5 (MP4), P8.1 (MKV WEB-DL), P7 MEL/FEL (MakeMKV), DV + HDR10+; HDR10+ AV1; VP9.2; VC-1 и MPEG-2 (BD/DVD ремуксы); H.264 Hi10P; TrueHD Atmos, DTS-HD MA, DTS:X, EAC3-JOC.
6. Сравнить media3 и Just Player на одном DV-файле: воспроизвести #5825.
---
## Источники
- Официальные спецификации: https://www.mi.com/global/product/xiaomi-tv-s-pro-mini-led-75-2026/specs/ , https://www.mi.com/global/product/xiaomi-tv-s-mini-led-75-2025/specs/ , FAQ https://www.mi.com/ph/support/faq/details/KA-483202/
- MediaTek MT9613: https://www.mediatek.com/products/pentonic/mt9613 ; таблица SoC: https://en.wikipedia.org/wiki/List_of_MediaTek_systems_on_chips
- Обзоры: https://tech.onliner.by/2026/02/12/xiaomi-s-pro-mini-led-2026 , https://mo.formulatv.ru/obzory/televizory/obzor-televizora-xiaomi-tv-s-2026.html , https://www.absolutegeeks.com/reviews/xiaomi-tv-s-pro-mini-led-75-2026-review-mini-led-mastery-unleashed/ , https://xiaomiforall.com/xiaomi-tv-s-pro-mini-led-2026-review/ (китайская: MT9655, 4/64, HyperOS 3), https://www.clubic.com/544037-xiaomi-s-mini-led-2025.html (MT9653), https://www.notebookcheck.net/2026-Xiaomi-TV-S-Pro-Mini-LED-series-goes-global-but-specs-fall-short-of-Chinese-models.1122730.0.html
- 4PDA: https://4pda.to/forum/index.php?showtopic=1115060 , https://4pda.to/forum/index.php?showtopic=1102774
- Jellyfin: https://github.com/jellyfin/jellyfin-androidtv/issues/5825 , /issues/5366 , /pull/5827 , /issues/5093 , /pull/5470 , /issues/5028 , /issues/4021
- media3: https://github.com/androidx/media/issues/3421 , /issues/3347 , /issues/3407 , /issues/3405 , /issues/2711
- Kodi: https://forum.kodi.tv/showthread.php?tid=374191 (U7K MT9618), https://gist.github.com/quietvoid/db7ac49bd8621a82f914888bca730c74 (MTK DV NAL quirk), https://github.com/xbmc/xbmc/pull/22950 (OMX.MTK.VIDEO.DECODER.DVHE.STN)
- Аудио: https://xdaforums.com/t/tcl-2023-google-tvs-p745-p755-c645-c745-c755-c805-c845-c855-c955-x955.4576005/page-36 , https://xdaforums.com/t/tcl-2022-google-tvs-p635-p735-c635-c735-c835-c935.4432635/page-41 , https://www.techradar.com/televisions/lg-removes-dts-audio-from-its-2025-tvs-heres-what-that-means-in-practice , https://www.aftvnews.com/google-tv-streamer-does-not-support-dolby-truehd-dts-hd-or-dtsx-audio-passthrough/

511
docs/research/engines.md Normal file
View File

@@ -0,0 +1,511 @@
# Несколько движков воспроизведения в Android-клиенте Jellyfin: архитектура, выбор движка, fallback
Дата: 2026-10-12. Метод: shallow-клоны репозиториев (HEAD на 10–12.10.2026) и чтение исходников, Maven Central (maven-metadata, POM, скачанные AAR: размеры `.so`, выравнивание ELF LOAD через `readelf`), GitHub API, документация Android/Kodi.
Всё, что помечено «(не проверено)», не подтверждено кодом или первоисточником.
---
## 0. TL;DR
1. **Почти все клиенты выбирают движок вручную, глобальной настройкой** (Findroid, AFinity, JellyPlay, Swiftfin, Streamyfin, Nova, Just Player). Автоматика есть только у трёх:
- **Wholphin**: режим `PREFER_MPV` — если контент HDR (или 4K при software-декоде), берётся ExoPlayer, иначе mpv.
- **Void**: режим `AUTO` — HDR или Dolby Vision идут в ExoPlayer, всё остальное в mpv.
- **Kodi**: правила в `playercorefactory.xml` по протоколу, расширению, кодеку, разрешению и `hdrtype`; плюс меню «Play using…».
2. **Автоматическое переключение на другой движок при runtime-ошибке декодера реализовано только в Plezy** (Flutter-клиент Plex, нативная часть на Kotlin). Схема ExoPlayer → mpv:
- сначала попытка восстановиться внутри ExoPlayer;
- срабатывает по `onPlayerError`, когда трек видео не выбран, по watchdog «декодер завис» (входные буферы есть, выходных нет) и по watchdog «чёрный экран»;
- воспроизведение продолжается с той же позиции.
Wholphin и AFinity при ошибке переходят на **серверный транскод тем же движком**. JellyPlay сознательно не делает «тихого» fallback.
3. **DeviceProfile везде свой для каждого движка, объединения возможностей нет ни у кого.**
- Wholphin: `if (backend == EXO_PLAYER) deviceProfileService… else mpvDeviceProfile`.
- Swiftfin: `VideoPlayerType.directPlayProfiles/transcodingProfiles/subtitleProfiles`.
- JellyPlay: `DeviceProfileProvider.forPlayer(PlayerType)`.
- Findroid: шлёт пустой профиль «Direct play all» и всегда берёт статический поток.
4. **Абстракция бывает двух видов.**
- Движок как media3 `Player`. Findroid и AFinity: `MPVPlayer : BasePlayer`. Wholphin: `MpvPlayer : BasePlayer`, стаб `: SimpleBasePlayer`.
- Собственный интерфейс: jellyfin-androidtv `PlayerBackend`, JellyPlay `MediaEngine` (~640 строк контракта + `EngineCapabilities`), Swiftfin `MediaPlayerProxy` + capability-протоколы, Nova `IMediaPlayer`.
Реализации «mpv как Player» полны `TODO()` и `UnsupportedOperationException`. В Findroid около 15 мест `TODO("Not yet implemented")`, включая `setVideoSurface` и `setVideoTextureView`.
5. **Рекомендация по архитектуре.** Свой узкий интерфейс `PlaybackEngine`, разбитый на capability-интерфейсы (как у Swiftfin), и **один** адаптер `EngineSessionPlayer : SimpleBasePlayer` в ядре. Адаптер нужен для MediaSession, состояний media3-ui-compose и `PlayerSurface`; подход как у jellyfin-androidtv `MediaSessionPlayer : SimpleBasePlayer`. Каждый движок живёт в отдельном Gradle-модуле и регистрируется через Hilt `@IntoSet`. Наборы движков задаются product flavors `full`/`lite`. Стаб-модули не нужны.
6. **Fallback-движок сейчас: libVLC 3.7.x (`org.videolan.android:libvlc-all:3.7.7`).**
- официальный артефакт VideoLAN с регулярными релизами (3.7.6 — 11.09.2026, 3.7.7 — 01.10.2026);
- стабильный Java API;
- HDR через MediaCodec на Surface (не проверено в этом раунде);
- внутри libass, PGS, DVB и VobSub;
- выравнивание 16 КБ на месте.
Минус — размер: arm64 `.so` занимают 55 МБ без сжатия и 24 МБ со сжатием. libmpv стоит держать вторым движком на будущее, но не делать основным fallback: у него нет официального Android-артефакта, и в стоковом виде он не выводит HDR (тонмапит в SDR через GLES).
---
## 1. Как устроено в существующих клиентах
### 1.1 Findroid (jarnedemeulemeester/findroid, Kotlin)
- **Модули:** `:player:core`, `:player:local`, `:app:phone`, `:app:tv`. Движки лежат в `player/local/src/main/java/dev/jdtech/jellyfin/player/local/`:
- `mpv/MPVPlayer.kt` (1659 строк) — `class MPVPlayer(...) : BasePlayer(), MPVLib.EventObserver, AudioManager.OnAudioFocusChangeListener`.
- ExoPlayer используется напрямую.
- **Выбор:** `PlayerViewModel.kt` читает `appPreferences.playerBackend` и делает `when ("exoplayer" | "mpv")` (стр. 135–173). Это глобальная пользовательская настройка, по умолчанию `"exoplayer"` (`AppPreferences.kt:29`). Автоматики нет.
- **Ошибки:** fallback отсутствует, `onPlayerError` в ViewModel не используется для смены движка.
- **DeviceProfile:** один для всех движков — `DeviceProfile(name="Direct play all", directPlayProfiles=empty, transcodingProfiles=empty, …, subtitleProfiles=[srt,ass EXTERNAL])` (`data/.../JellyfinRepositoryImpl.kt:~315`). Дальше всегда `getVideoStreamUrl(static=true)`, то есть клиент сознательно не пользуется серверным транскодом.
- **Зависимость:** `dev.jdtech.mpv:libmpv:1.0.0` подключена как `implementation` в `:player:local` и как `compileOnly` в `:app:phone`.
- **Сборка:** один flavor `libre`, ABI splits для `armeabi-v7a`, `arm64-v8a`, `x86`, `x86_64`. Для сборки bundle splits отключаются из-за issue AGP 402800800.
- **Состояние MPVPlayer:** около 15 `TODO("Not yet implemented")`, включая `setVideoSurface`, `setVideoSurfaceHolder` и `setVideoTextureView`. Реализован только `setVideoSurfaceView`.
### 1.2 Wholphin (damontecres/Wholphin, Android TV, Compose)
- **Модули:** `:app` и `:wholphin-mpv-stub`. Настоящие нативные модули живут в отдельном репозитории `damontecres/wholphin-extensions`: `wholphin-mpv` и `wholphin-media3` (ffmpeg- и av1-декодеры). Они публикуются в **GitHub Packages, а там анонимное чтение невозможно** — README требует classic PAT.
- **Подключение в `app/build.gradle.kts` (стр. 30–36, 370–397), каскадом:**
1. локальный `libs/wholphin-mpv-release.aar`;
2. иначе prebuilt из репозитория `WholphinExtensions…`;
3. иначе `project(":wholphin-mpv-stub")`.
То же сделано для ffmpeg и av1.
- **Стаб:** `wholphin-mpv-stub/.../MpvPlayer.kt` — `class MpvPlayer(...) : SimpleBasePlayer(Looper.getMainLooper()) { override fun getState() = throw MpvStubException() }`. Он нужен потому, что код `:app` напрямую ссылается на класс `MpvPlayer`.
- **Абстракция:** media3 `Player`. `services/PlayerFactory.kt` возвращает `PlayerCreation(player: Player, assHandler)`. Настоящий `MpvPlayer` в extensions — `BasePlayer()`, около 1600 строк; часть методов бросает `UnsupportedOperationException`.
- **MediaSession:** `MediaSessionPlayer : ForwardingSimpleBasePlayer(player)`.
- **Видео:** `PlayerSurface(player, surfaceType = SURFACE_TYPE_SURFACE_VIEW)` из media3-ui-compose, один и тот же для Exo и mpv.
- **Выбор** (`ui/playback/PlaybackViewModel.kt:234–270`):
```kotlin
PlayerBackend.PREFER_MPV -> if (isHdr || (is4k && softwareDecoding)) EXO_PLAYER else MPV
```
Варианты `PlayerBackend`: `EXO_PLAYER`, `MPV`, `PREFER_MPV`, `EXTERNAL_PLAYER`. Движок можно передать и через `Destination.Playback.backend`, то есть выбрать «Play with…» для конкретного элемента. Если движок изменился, старый `Player` уничтожается и создаётся новый.
- **Ошибки** (`onPlayerError`, стр. ~1377): при `DIRECT_PLAY`/`DIRECT_STREAM` вызывается `changeStreams(..., positionMs = player.currentPosition, enableDirectPlay=false, enableDirectStream=false)`, то есть **переход на транскод тем же движком** с той же позиции. Смены движка нет.
- **DeviceProfile на каждый движок** (стр. ~660):
```kotlin
deviceProfile = if (backend == EXO_PLAYER) deviceProfileService.getOrCreateDeviceProfile(...) else mpvDeviceProfile
```
`util/mpv/MpvDeviceProfile.kt` — статический профиль с широким набором контейнеров и кодеков, у PGS/DVDSUB/DVBSUB стоят флаги `embedded=true, encode=true`.
- **Упаковка:** `packaging.jniLibs.pickFirsts += "lib/*/libc++_shared.so"`, потому что libass-android и wholphin-mpv оба кладут `libc++_shared`. Flavors `default`, `appstore`, `firetv` отличаются фичами, не движками.
### 1.3 JellyPlay (raulshma/jellyplay, KMP, бета, создан в мае 2026)
- **Документ:** `docs/player-engines.md`. Три движка: ExoPlayer (по умолчанию), libmpv (`io.github.abdallahmehiz:mpv-android-lib:0.1.12`), LibVLC (`libvlc-all:3.7.5`). Есть ещё External.
- **Абстракция:** собственный `MediaEngine` (`shared/core/player-contract/src/commonMain/.../engine/MediaEngine.kt`, 640 строк):
- `load(PlaybackRequest)`, `play/pause/seekTo`, `StateFlow` для состояния, позиции, треков и cues;
- `capabilities: EngineCapabilities` — около 25 флагов: `supportsAudioDelay`, `supportsSubtitleDelay`, `supportsAudioPassthrough`, `supportsAssOverride`, `supportsVideoFilters` и т. д.;
- `updateConfig(EngineConfig)`, `setAspectRatio`, `zoomSafeSubtitleStrategy`.
UI скрывает элементы управления по флагам capability. Поверхность вынесена в платформенный seam `AndroidSurfaceProvider`.
- **MediaSession:** только у Exo, через `Media3PlayerHost`/`asMedia3Player()`. Для mpv и VLC «JellyPlay provides its own».
- **Выбор:** в документе сказано прямо — «The choice is **per-device and manual** — JellyPlay does not auto-pick an engine based on the file's codec». Раздел «**No silent fallback.** If the chosen engine fails to decode a file, JellyPlay reports the error…»
- **DeviceProfile на каждый движок:** `shared/core/network/.../DeviceProfileProvider.kt` → `forPlayer(PlayerType)`:
- MPV получает свой профиль (два варианта, с direct play для PGS и без);
- EXO_PLAYER и LIBVLC получают общий `hardwareProfile`.
Passthrough-кодеки убираются из direct-play только для движков, которые умеют bitstream.
- **Сборка:** flavors `phone`/`tv` (по платформе, не по движкам). ABI split по умолчанию только arm64 (`extraAbis` через property).
- **Неточности в документе:**
- «ExoPlayer — no audio passthrough» неверно: media3 делает passthrough AC3, E-AC3, DTS и TrueHD через `AudioCapabilities`, а Plezy поверх этого реализует IEC-упаковку.
- «LibVLC does not render ASS» неверно для libVLC как такового: в `libvlc.so` из 3.7.7 есть `ass_render_frame` и исходники `libass/*.c`, это проверено через `strings`.
Скорее всего, оба утверждения описывают их собственную интеграцию, а не возможности библиотек.
### 1.4 AFinity (MakD/AFinity)
- Использует `dev.jdtech.mpv:libmpv:1.0.0`. `app/.../player/mpv/MPVPlayer.kt` — производная от Findroid, media3 `Player`.
- **Выбор:** булева настройка `preferencesRepository.useExoPlayer` (`PlayerViewModel.initializePlayer`). Автоматики нет.
- **Ошибки:** `retryWithTranscodeFallback(error)` — переход на транскод, движок не меняется.
- **MediaSession:** строится поверх любого `Player` (`MediaSession.Builder(context, player)`). Это главный плюс подхода «движок = Player».
### 1.5 Void for Jellyfin (hritwikjohri/Void-for-jellyfin; VoidTV отдельно)
- **mpv:** `dev.marcelsoftware.mpvcompose:mpv-compose:1.0.0` — Compose-обёртка, исходники в nitanmarcel/mpv-compose, единственная версия от 29.06.2025. В `MPVPlayerView.kt` работа идёт прямо через `MPVLib`.
- **Выбор:** `PlayerType { AUTO, MPV, EXOPLAYER }`. В `VideoPlayerScreen.kt:~120–170` для `AUTO` действует правило `selectedVideoStream.isHdrOrDovi() → EXOPLAYER, иначе MPV`; если выбранный движок не может получить URL, берётся второй. Это решение в момент запуска, а не реакция на ошибку.
- **Активность:** последний push Void-for-jellyfin — 31.03.2026, VoidTV — 30.01.2026. Проект стоит.
### 1.6 Streamyfin (React Native/Expo)
- **Модули:** собственные Expo-модули `modules/mpv-player` (Android: `dev.jdtech.mpv:libmpv:1.0.0`, iOS: свой) и `modules/exoplayer-player`.
- **Выбор:** `utils/atoms/settings.ts`. **MPV — «universal default», ExoPlayer — opt-in только на Android TV**: `getActiveVideoPlayerEngine()`, `isExoPlayerSupported`. VLC из проекта убрали (раньше был основным, не проверено).
### 1.7 Swiftfin (jellyfin/Swiftfin, iOS/tvOS)
- **Перечисление движков:** `Shared/Objects/VideoPlayerType/VideoPlayerType.swift` — `enum VideoPlayerType { avPlayer = "native", vlc, mpv }`. mpv включается экспериментальным флагом `Defaults[.Experimental.mpvPlayer]`.
- **Профиль:** каждый тип **сам отдаёт** `directPlayProfiles`, `transcodingProfiles` и `subtitleProfiles` (vlc и mpv пока делят VLC-профили). DeviceProfile собирается из профилей выбранного движка: `MediaPlayerItem+Build.swift` → `for: videoPlayerType`.
- **Абстракция** (`MediaPlayerProxy.swift`) — набор протоколов, разделённых по возможностям. Это удачный шаблон:
- `MediaPlayerProxy` (play/pause/stop/jump/setRate/setSeconds);
- `VideoMediaPlayerProxy` (videoSize, droppedFrames);
- `MediaPlayerAudioTrackConfigurable`, `MediaPlayerSubtitleTrackConfigurable` (работают с Jellyfin `MediaStream`, а не с внутренними треками движка);
- `MediaPlayerOffsetConfigurable` (`setAudioOffset`/`setSubtitleOffset`);
- `MediaPlayerPictureInPictureCapable`, `MediaPlayerSubtitleConfigurable`, `VideoMediaPlayerLayoutConfigurable`.
Реализации: `MediaPlayerProxy+VLC.swift`, `+AVPlayer.swift`, `+MPV.swift`.
- **Выбор:** глобальная настройка `Defaults[.VideoPlayer.videoPlayerType]`, по умолчанию `.vlc`. Автоматики и fallback нет.
### 1.8 Plezy (edde746/plezy, Flutter, клиент Plex) — единственный с авто-fallback
- **Схема:** на Android основной движок ExoPlayer (`android/app/.../exoplayer/ExoPlayerCore.kt`), запасной — mpv (`.../mpv/MpvPlayerCore.kt`). Оба управляются плагином `ExoPlayerPlugin.kt`, который хранит `playerCore` и `mpvCore` и флаг `usingMpvFallback`. Настройка `android_use_exoplayer` позволяет сразу выбрать mpv.
- **Триггеры fallback**, все сводятся к `requestFormatFallback(...)` → `delegate.onFormatUnsupported(uri, headers, positionMs, playWhenReady, errorMessage)`:
1. `onPlayerError`, если не помогла попытка восстановиться внутри Exo (`retryVideoDecoderInPlace`);
2. `onTracksChanged`: группа видео есть, но ни одна не выбрана, то есть нет декодера (`requestFallbackForUnsupportedTracks`);
3. **watchdog «decoder hang»**: `queuedInputBufferCount > 0`, а выходных буферов 0 после таймаута;
4. **watchdog «чёрный экран»**: 0 отрисованных кадров за `WATCHDOG_TIMEOUT_MS` при `isPlaying`;
5. отказ аудио-sink после исчерпания восстановления внутри Exo.
- **Политика повторов** (`VideoDecoderRecoveryPolicy`): если кадр уже был отрисован, формат заведомо поддерживается, поэтому сначала повтор в Exo (`MAX_CONSECUTIVE_ATTEMPTS=1`, `MAX_ATTEMPTS_PER_MEDIA=3`), и только потом mpv. Для Dolby Vision перед fallback делается попытка `retryWithDvConversion` (DV → HEVC/HDR10).
- **Передача между движками:**
- счётчик `mediaGeneration` отсекает устаревшие события;
- повторные запросы fallback схлопываются;
- позиция ограничивается у конца файла (`EndOfStreamPolicy.fallbackStartPositionMs`);
- внешние субтитры и mpv-свойства, выставленные до fallback, воспроизводятся заново;
- таймаут инициализации mpv — 10 с.
- **mpv:** собственная сборка `edde746/mpv-build` (`mpv-build.lock.json` с SHA-256, ffmpeg 8.0.1). При HW-декоде используется **форк `vo=mediacodec`**: буферы MediaCodec сразу идут в compositor, 10-бит и HDR10/HLG dataspace сохраняются, субтитры рисуются на соседней OSD-поверхности. Комментарий в `MpvPlayerCore.kt:165–189` фиксирует, что `gpu-next` + HW-декод ломается на Tegra (синий экран на Shield). Это хорошо показывает, что HDR в стоковом mpv-android не решён.
### 1.9 jellyfin-androidtv (официальный)
- **Модули:** `playback/core` (`backend/PlayerBackend.kt`, `BackendService.kt`, `support/PlaySupportReport.kt`), `playback/media3/exoplayer` (`ExoPlayerBackend`), `playback/media3/session` (`MediaSessionPlayer : SimpleBasePlayer(looper)` поверх `PlaybackManager`), `playback/jellyfin`.
- **`PlayerBackend`:**
- `supportsStream(MediaStream): PlaySupportReport` — Exo реализует через `exoPlayer.getPlaySupportReport(stream.toFormats())` по `RendererCapabilities`;
- `setSurfaceView`, `setSubtitleView`, `prepareItem/playItem`, `seekTo`, `setSpeed`, `setTimedEvents`.
`BackendService.switchBackend()` останавливает старый backend и переносит на новый surface, subtitle view и listener. Задел под несколько движков есть, но в коде только ExoPlayer; libVLC из клиента давно убрали, VLC остался внешним плеером (`VlcExternalPlayerApi.kt`).
### 1.10 Just Player (moneytoo/Player)
Движок один — ExoPlayer с локальными AAR `lib-decoder-ffmpeg`, `av1`, `iamf`, `mpegh`. Настройка `decoderPriority` → `setExtensionRendererMode` (OFF/ON/PREFER). Это «выбор декодера внутри одного движка», а не выбор движка. Приём полезен и нам: `EXTENSION_RENDERER_MODE_ON` плюс `setEnableDecoderFallback(true)`.
### 1.11 Nova Video Player (nova-video-player/aos-*)
- **Интерфейс:** `aos-MediaLib/src/com/archos/medialib/IMediaPlayer.java` с `TYPE_AVOS=0` и `TYPE_ANDROID=1`. Методы `setSubtitleDelay`, `setAvDelay`, `setSubtitleRatio`, `setAudioFilter` присутствуют прямо в интерфейсе.
- **Фабрика** `MediaFactory.createPlayer(ctx, forceSoftwareDecoding)`:
- настройка `KEY_DEC_CHOICE == -1` → `AndroidMediaPlayer`;
- иначе `AvosMediaPlayer` — свой нативный плеер на ffmpeg с `MP_DECODER_ANY` (HW, затем SW) или `MP_DECODER_SW`;
- если `LibAvos` недоступен, используется Android-плеер.
- В манифесте `aos-AVP/exoplayer.xml` есть экспериментальная ветка `dev/exoplayer`.
### 1.12 Kodi
- **Выбор плеера:** `system/playercorefactory.xml` и пользовательский `userdata/playercorefactory.xml` со списком `<players>` и `<rules>`. Атрибуты правил (`xbmc/cores/playercorefactory/PlayerSelectionRule.cpp`):
- `internetstream`, `remote`, `audio`, `video`, `dvd`, `bd`, `discimage`;
- `protocols`, `filetypes`, `mimetypes`, `filename` (regex);
- `audiocodec`, `audiochannels`, `videocodec`, `videoresolution`, `videoaspect`, **`hdrtype`**.
Правила по stream details работают, только если включено извлечение флагов. Правила вкладываются друг в друга. Плеер по умолчанию задаётся алиасами `videodefaultplayer`/`audiodefaultplayer`. В контекстном меню «Play using…» показываются все подходящие плееры.
- **Fallback внутри VideoPlayer:** при открытии декодера Kodi перебирает HW-кодеки (на Android — MediaCodec (Surface) и MediaCodec) и затем ffmpeg SW. Это делается на уровне декодера, а не движка (по знанию архитектуры, в этом раунде код не открывался).
### 1.13 Сводная таблица
| Клиент | Движки | Абстракция | Выбор | Fallback при ошибке | DeviceProfile |
|---|---|---|---|---|---|
| Findroid | Exo, mpv | media3 `Player` (`MPVPlayer : BasePlayer`) | глобальная настройка | нет | один «Direct play all» |
| Wholphin | Exo, mpv (+ext) | media3 `Player` + стаб `SimpleBasePlayer` | настройка + `PREFER_MPV` (HDR→Exo) + per-item | транскод тем же движком | на каждый движок |
| JellyPlay | Exo, mpv, VLC | свой `MediaEngine` + `EngineCapabilities` | вручную, на устройство | нет, сознательно | на каждый движок |
| AFinity | Exo, mpv | media3 `Player` | булева настройка | транскод | (не проверено) |
| Void | Exo, mpv | Compose-view, без общего интерфейса | `AUTO`: HDR/DV→Exo, иначе mpv | нет | (не проверено) |
| Streamyfin | mpv, Exo (TV) | Expo-модули | настройка, mpv по умолчанию | нет | (не проверено) |
| Swiftfin | AVPlayer, VLC, mpv | `MediaPlayerProxy` + capability-протоколы | глобальная настройка | нет | на каждый движок |
| Plezy | Exo → mpv | плагин с двумя core | Exo по умолчанию, mpv по настройке | **да, автоматически** | n/a (Plex) |
| jellyfin-androidtv | Exo | `PlayerBackend` + `supportsStream` | — | — | — |
| Nova | AVOS (ffmpeg), Android MP | `IMediaPlayer` + `MediaFactory` | настройка декодера | при отсутствии lib | n/a |
| Kodi | VideoPlayer, PAPlayer, внешние | `IPlayer` + rules | правила (codec/res/hdrtype) + «Play using» | HW→SW внутри VideoPlayer | n/a |
---
## 2. Дизайн абстракции
### 2.1 Вариант A: каждый движок реализует media3 `Player`
**Плюсы**
- MediaSession, MediaController, уведомления и Google Assistant/«Now playing» на TV работают даром.
- Состояния из media3-ui-compose (`PlayPauseButtonState`, `ProgressStateWithTickInterval` и т. д.) и `PlayerSurface` работают с любым движком.
- Единая модель слушателей.
**Минусы, подтверждённые кодом**
- `Player` огромен: Timeline, плейлисты, `Tracks`/`Format`, `TrackSelectionParameters`, громкость, устройство. Реализации для mpv в Findroid, Wholphin и AFinity неполные (`TODO()`/`UnsupportedOperationException`) и падают, если сторонний код вызовет «не тот» метод. Пример — `setVideoSurface(Surface)` вместо `setVideoSurfaceView`.
- В `Player` нет audio delay, subtitle delay, aspect/zoom, deinterlace, passthrough и стилей ASS. Всё это всё равно приходится выносить в свой интерфейс и проверять `as?`, как в AFinity: `is MPVPlayer -> …` встречается десятки раз в `PlayerViewModel.kt`.
- Треки mpv и VLC приходится переупаковывать в `TrackGroup(Format)`. Связь с индексами Jellyfin `MediaStream.Index` теряется или требует хаков.
- `BasePlayer` вручную реализовывать сложно: потоки и согласованность событий. Если идти этим путём, брать только **`SimpleBasePlayer`**: он сам сравнивает состояния и рассылает события, а реализация отдаёт `State` и переопределяет `handle*`.
### 2.2 Вариант B: собственный `PlayerBackend`/`MediaEngine`
**Плюсы**
- Контракт описан в терминах домена. Треки адресуются индексами Jellyfin `MediaStream`, как в Swiftfin, где `setAudioStream(MediaStream)` удобно связывает с PlaybackInfo, отчётами о прогрессе и сменой потока на сервере.
- Возможности объявлены явно, UI показывает только поддерживаемое.
- Движок легко заменить: интерфейс маленький.
**Минусы:** MediaSession и Compose-состояния нужно соединить самостоятельно.
### 2.3 Рекомендация: вариант B и один `SimpleBasePlayer`-адаптер в ядре
Так устроен jellyfin-androidtv (`PlayerBackend` + `MediaSessionPlayer : SimpleBasePlayer`). Набор capability-интерфейсов взят у Swiftfin.
```kotlin
// :playback:api
interface PlaybackEngine {
val id: EngineId // EXO, VLC, MPV…
val state: StateFlow<EngineState> // idle/buffering/ready/ended + playWhenReady
val position: PositionSource // currentPositionMs(), bufferedMs, durationMs
val events: Flow<EngineEvent> // FirstFrame, VideoSize, Error(EngineError), TracksChanged
val tracks: StateFlow<EngineTracks> // в терминах Jellyfin MediaStream.Index + engine id
fun attach(output: VideoOutput) // SurfaceView-based, см. 2.4
fun detach()
fun load(request: EngineLoadRequest) // url, headers, startMs, внешние сабы, выбранные индексы
fun play(); fun pause(); fun seekTo(ms: Long); fun setSpeed(x: Float)
fun selectAudio(streamIndex: Int?); fun selectSubtitle(streamIndex: Int?)
fun release()
}
// Опциональные возможности: проверка через `engine as? X`, UI скрывает ненужное
interface AudioDelayCapable { fun setAudioDelay(ms: Long) }
interface SubtitleDelayCapable { fun setSubtitleDelay(ms: Long) }
interface AspectCapable { fun setAspect(mode: AspectMode /* FIT, FILL, ZOOM, 16:9, 4:3, CUSTOM */) }
interface PassthroughCapable { fun setPassthrough(codecs: Set<AudioCodec>) }
interface SubtitleStyleCapable { fun applyStyle(style: SubtitleStyle) }
interface DeinterlaceCapable { fun setDeinterlace(mode: DeinterlaceMode) }
interface StatsCapable { val stats: StateFlow<PlaybackStats> } // decoder name, dropped frames, hw/sw
// Плагин движка: SPI, по одному на Gradle-модуль
interface EngineProvider {
val id: EngineId
val priority: Int
fun capabilities(device: DeviceCaps): EngineCapabilities // для профиля и выбора
fun evaluate(source: MediaSourceInfo, sel: StreamSelection, device: DeviceCaps): Verdict
fun deviceProfile(device: DeviceCaps, prefs: PlaybackPrefs): DeviceProfile
fun create(context: Context): PlaybackEngine
}
```
Ядро (`:playback:core`):
- `PlaybackSessionController` хранит item, mediaSourceId, PlaySessionId, выбранные индексы, задержки, позицию и `generation`. Через него идут вся отчётность в Jellyfin (`/Sessions/Playing*`), выбор движка и переключение.
- `EngineSessionPlayer : SimpleBasePlayer` — **единственная** реализация `Player`. Она транслирует состояние контроллера в `State` (один `MediaItem`, позиция, длительность, playWhenReady, команды) и передаёт `handleSetPlayWhenReady`/`handleSeek`/`handleSetVideoOutput` текущему движку. На этом адаптере строятся `MediaSession`/`MediaSessionService` и Compose-состояния.
- ExoPlayer-движок держит `ExoPlayer` внутри. Ему MediaSession «из коробки» не нужна: сессия всегда смотрит на адаптер, поэтому при смене движка её не надо пересоздавать. Wholphin и AFinity, напротив, пересоздают сессию при новом `Player`.
### 2.4 Видеоповерхность
- **Только `SurfaceView`.** HDR-вывод и tunneling работают лишь с SurfaceView. TextureView проходит через композицию GPU: HDR теряется, tunneling не поддерживается, расход энергии выше. Wholphin явно использует `SURFACE_TYPE_SURFACE_VIEW`.
- **Compose:** `PlayerSurface(player = engineSessionPlayer, surfaceType = SURFACE_TYPE_SURFACE_VIEW)` вызывает `Player::setVideoSurfaceView`/`clearVideoSurfaceView` (проверено в исходнике `media3/ui/compose/PlayerSurface.kt`, ветка release; media3 1.11.1 — последняя на 12.10.2026). У `SimpleBasePlayer` это приходит в `handleSetVideoOutput(Object)`, а адаптер передаёт `SurfaceView` текущему движку:
- Exo: `setVideoSurfaceView`;
- libVLC: `IVLCVout.setVideoView` (или `VLCVideoLayout` со своей subtitle-поверхностью);
- mpv: `MPVLib.attachSurface(holder.surface)`.
- **Своя обёртка для VLC:** `VLCVideoLayout` создаёт свои SurfaceView, в том числе отдельную для субтитров при direct rendering. Поэтому лучше иметь собственный `@Composable EngineVideoSurface(engine)` через `AndroidView`, который сам решает, какой View отдать движку, а PlayerSurface оставить только для Exo. Объявить в `VideoOutput` sealed-варианты: `SurfaceViewOutput(SurfaceView)` и `ViewGroupOutput(FrameLayout)`.
- **Aspect/zoom:**
- Exo — через `AspectRatioFrameLayout`/`Modifier.resizeWithContentScale` (media3-ui-compose) или `VIDEO_SCALING_MODE`;
- VLC — `MediaPlayer.setAspectRatio`/`setScale`/`setVideoScale`;
- mpv — `video-aspect-override`/`panscan`.
Всё это закрывается одним `AspectCapable`.
- **Субтитры:**
- Exo рисует `SubtitleView` с CueGroup, ASS — через `io.github.peerless2012:ass-media` (0.5.1 от 11.08.2026, используется в Wholphin и jellyfin-androidtv);
- VLC и mpv рисуют субтитры сами (libass внутри движка).
В контракте нужен флаг «движок рисует субтитры сам», чтобы ядро не накладывало оверлей поверх.
### 2.5 Смена движка посреди сессии (ручная и автоматическая)
Порядок, обобщающий Plezy, Wholphin и jellyfin-androidtv `switchBackend`:
1. `generation++`; события старого движка с прежним `generation` игнорируются. Повторные запросы fallback схлопываются флагом `fallbackInProgress`.
2. Запомнить `positionMs` (у конца файла ограничить: `min(pos, duration - 10 s)`), `playWhenReady`, Jellyfin-индексы аудио и субтитров, задержки, скорость.
3. Отправить `/Sessions/Playing/Stopped` для старой PlaySessionId (или Progress с понятной причиной).
4. Старый движок: `detach()` → `release()`. VLC и mpv отпускать до создания нового, чтобы избежать конфликтов поверхности и MediaCodec. На слабых TV-боксах два одновременных HW-декодера 4K часто недоступны.
5. Вызвать `POST /Items/{id}/PlaybackInfo` с **DeviceProfile нового движка**. Получится новая PlaySessionId; DirectPlay или транскод определится заново.
6. Новый движок: `attach(output)` → `load(startMs=pos, …)`. Воспроизвести заново внешние субтитры и настройки (задержки, стиль, passthrough).
7. Отправить `/Sessions/Playing` с новым PlayMethod. Показать ненавязчивый toast «Переключено на VLC: аппаратный декодер не поддерживает VC-1» и записать причину в статистику.
8. Защита от петель: один fallback на item за сессию; если упал и fallback-движок, перейти на серверный транскод (как Wholphin и AFinity), а не обратно.
---
## 3. Gradle-модуляризация
### 3.1 Предлагаемая структура
```
:playback:api // Android-library без нативных зависимостей: PlaybackEngine, capabilities, EngineProvider, модели
:playback:core // SessionController, EngineSelector, EngineSessionPlayer (SimpleBasePlayer), MediaSessionService, DeviceCaps (MediaCodecList/Display)
:playback:engine-exo // media3-exoplayer, -hls, decoder-ffmpeg (аудио), ass-media; ExoEngineProvider
:playback:engine-vlc // org.videolan.android:libvlc-all:3.7.7; VlcEngineProvider
:playback:engine-mpv // (позже) libmpv AAR; MpvEngineProvider
:app // flavors: full / lite
```
- **Регистрация через Hilt multibinding.** В каждом engine-модуле:
```kotlin
@Module @InstallIn(SingletonComponent::class)
abstract class VlcEngineModule { @Binds @IntoSet abstract fun bind(p: VlcEngineProvider): EngineProvider }
```
Ядро получает `Set<@JvmSuppressWildcards EngineProvider>`. Если модуль не подключён, движка просто нет. **Стаб не нужен**: Wholphin он понадобился только потому, что `:app` ссылается на класс `MpvPlayer` напрямую. Альтернатива без DI — `ServiceLoader` с `META-INF/services` (R8 его поддерживает).
- **Flavors по движкам:**
```kotlin
flavorDimensions += "engines"
productFlavors {
create("full") { dimension = "engines"; isDefault = true } // Exo + VLC
create("lite") { dimension = "engines"; applicationIdSuffix = ".lite" } // только Exo
}
dependencies {
implementation(project(":playback:engine-exo"))
"fullImplementation"(project(":playback:engine-vlc"))
}
```
Можно добавить второе измерение `store` (`github`, `play`, `amazon`), как Wholphin делает `default/appstore/firetv`.
- **Изоляция API движков.** Нативный AAR подключается как `implementation` только в engine-модуле; `api` для него не делать, чтобы классы libVLC не протекали в `:app`. Разработчикам, не трогающим VLC, можно быстро собирать `liteDebug`.
- **Конфликт `libc++_shared.so`:** у libVLC, libmpv и ass-media своя копия, нужен `packaging { jniLibs { pickFirsts += "lib/*/libc++_shared.so" } }` (как в Wholphin). Версии libc++ из разных NDK формально несовместимы. Лучше проверить, что `full` с VLC и ass-media не падает; ass-media в VLC-сборке не обязателен, так что риск можно убрать, оставив ass-media только в пути Exo.
### 3.2 Размеры (измерено по AAR с Maven Central, только arm64-v8a)
| Артефакт | Дата | `.so` без сжатия | `.so` в deflate | 16 КБ (`LOAD` align) |
|---|---|---|---|---|
| `org.videolan.android:libvlc-all:3.7.7` | 01.10.2026 | **55,3 МБ** (libvlc.so 45,9 + libc++ 9,3) | 24,1 МБ | 0x4000 ✔ |
| `dev.jdtech.mpv:libmpv:1.0.0` (mpv 0.41.0, ffmpeg 8.1) | 06.04.2026 | **25,0 МБ** | 11,6 МБ | 0x4000 ✔ |
| `io.github.abdallahmehiz:mpv-android-lib:0.1.12` | 13.02.2026 | 38,3 МБ (с avfilter и libxml2) | 15,8 МБ | 0x4000 ✔ |
- **Влияние `useLegacyPackaging`.** При `minSdk ≥ 23` AGP по умолчанию кладёт `.so` в APK без сжатия (`extractNativeLibs=false`), поэтому APK на GitHub или F-Droid вырастет на «несжатую» цифру: около +55 МБ для VLC. Варианты:
- `packaging.jniLibs.useLegacyPackaging = true`: APK примерно +24 МБ, но после установки библиотеки распаковываются;
- для Play (AAB) — download size примерно равен сжатому размеру.
- **ABI.** Нужны per-ABI APK (`splits.abi`) или AAB. **armeabi-v7a обязателен для Android TV**: многие TV-устройства работают в 32-битном userspace на 64-битном CPU (по известным данным, ряд Fire TV Stick и Chromecast with Google TV; не проверено в этом раунде). x86 и x86_64 нужны только для эмулятора. Findroid собирает 4 ABI, Wholphin 3 (+universal), JellyPlay по умолчанию только arm64.
- **16 КБ:** все три AAR уже выровнены по 16 КБ (`-Wl,-z,max-page-size=16384` есть в `libmpv-android/buildscripts/build.sh:51`). AGP ≥ 8.5.1 сам выравнивает несжатые `.so` в zip.
### 3.3 Dynamic Feature Delivery для нативного движка
- **Технически возможно.** `.so` можно положить в on-demand feature-модуль, загрузка через `SplitCompat`/`SplitInstallHelper.loadLibrary` или ReLinker, зависимые `.so` грузятся вручную по порядку (документация Play on-demand).
- **Практически не годится:**
- работает только при установке из Google Play; при sideload возвращаются `REQUIRES_USER_CONFIRMATION`/`APP_NOT_OWNED`/`PLAY_STORE_NOT_FOUND`;
- для F-Droid, GitHub APK и Amazon Appstore не работает;
- поддержка on-demand на Android TV в документации не описана (не проверено);
- Hilt и feature-модули сочетаются плохо: граф DI идёт `app → feature`, а не наоборот, поэтому нужен reflection или ServiceLoader.
**Вывод:** использовать flavors `full`/`lite`; DFD откладывается до появления Play-дистрибуции, и тогда только как опция.
---
## 4. libVLC или libmpv в роли fallback-движка
### 4.1 Сравнение
| Критерий | libVLC 3.7.x (`libvlc-all`) | libmpv (сторонние AAR) |
|---|---|---|
| Артефакт | **Официальный VideoLAN на Maven Central.** 3.7.6 — 11.09.2026, 3.7.7 — 01.10.2026; 4.0.0-eap29 — 08.07.2026 (libVLC 4 всё ещё EAP). vlc-android активен (push 07.10.2026). | Официального AAR нет. Варианты: `dev.jdtech.mpv:libmpv` (Findroid; релизы 0.5.1 — 07.2025, **1.0.0 — 04.2026**; в репозитории renovate регулярно обновляет зависимости, ffmpeg 9.0.2 от 20.09.2026, но релизов не было с апреля); `io.github.abdallahmehiz:mpv-android-lib` (0.1.12, 02.2026); `wholphin-extensions` (GitHub Packages, нужен токен); Plezy (`edde746/mpv-build`, свои tarball); `mpv-compose` (одна версия 2025). |
| Размер arm64 | 55 МБ без сжатия / 24 МБ сжато | 25 / 11,6 МБ (jdtech) |
| 16 КБ | ✔ | ✔ |
| Android API | Java/Kotlin API: `LibVLC`, `MediaPlayer`, `Media`, `IVLCVout`, `VLCVideoLayout`; треки, `setSpuDelay`/`setAudioDelay`, `setAspectRatio`/`setScale`, события. Стабилен много лет. | `MPVLib`: строковые свойства и команды через JNI плюс observer. Обёртку-плеер пишешь сам (~1600 строк в Findroid/Wholphin). Исторически глобальный синглтон, один экземпляр на процесс. |
| HW-декод | MediaCodec, с direct rendering в Surface | `hwdec=mediacodec` (в GLES-текстуру) или `mediacodec-copy` |
| **HDR на Android TV** | При HW-декоде с direct rendering кадр уходит на Surface с метаданными, телевизор получает HDR10/HLG, как в VLC for Android (по прошлому исследованию; не проверено в этом раунде). DV — только через HEVC base layer или профиль 8.1 на совместимых SoC (не проверено). | В стоковом mpv-android `vo=gpu`/`gpu-next` (GLES), **HDR тонмапится в SDR**: в `MPVView.kt` нет HDR-пути, `mediacodec_embed` по умолчанию отключён. Wholphin (`PREFER_MPV`) и Void (`AUTO`) поэтому отправляют HDR в Exo. Plezy понадобился форк `vo=mediacodec`. |
| Субтитры | libass внутри (подтверждено `strings`), DVB/PGS/VobSub через ffmpeg и spudec, есть отдельная субтитровая поверхность | libass с полным override стилей (`sub-ass-override`), лучший ASS-рендер; PGS/VobSub через libav |
| Exotic SW-декод | ffmpeg внутри (VC-1, WMV, MPEG-2, Hi10P, DivX/Xvid, RV) | то же: ffmpeg (VC-1, PGS, dvdsub подтверждены `strings`) |
| Passthrough | есть (SPDIF/HDMI encoded AudioTrack) | `audio-spdif=…` с `ao=audiotrack` (JellyPlay и Plezy пользуются) |
| Стабильность жизненного цикла | Известные подводные камни: строгий порядок `detachViews` → `stop` → `release`, один `LibVLC` на процесс, нативные падения при быстром release/re-create (из опыта сообщества; не проверено) | Глобальное состояние MPVLib, `vo`/surface attach/detach, баги GLES на конкретных SoC (Tegra + gpu-next → синий экран, Plezy #2010) |
| Перспектива | libVLC 4 сменит API (EAP, сроки неизвестны). При изолированном модуле переход займёт один модуль. | Зависимость от одного стороннего сопровождающего AAR. Сборка своими силами (buildscripts mpv-android) — заметная нагрузка на CI. |
### 4.2 Рекомендация
**Fallback-движок сейчас — libVLC 3.7.7.** Решающие факторы для клиента, у которого главная цель — **корректный HDR без транскода**:
1. При HW-декоде HDR не деградирует в SDR, и это стоковое поведение. У mpv без форка HDR теряется.
2. Официальный, регулярно обновляемый артефакт и стабильный Java API.
3. Полный набор субтитров, включая ASS, PGS и VobSub, плюс passthrough.
Цена — около +24–55 МБ на ABI; её компенсирует flavor `lite`.
**mpv** заложить как будущий `:playback:engine-mpv` для ценителей ASS и аниме и для тонких настроек. Выбирать его автоматически только для SDR-контента с «тяжёлым» ASS. Брать `dev.jdtech.mpv:libmpv` (Maven Central, 16 КБ, используется в Findroid, AFinity и Streamyfin) или собирать самим по `libmpv-android/buildscripts`.
**libVLC 4:** когда выйдет стабильная версия, добавить `:playback:engine-vlc4` рядом с текущим модулем. Провайдер с большим `priority` вытеснит старый без изменений в ядре.
---
## 5. Алгоритм автоматического выбора движка
### 5.1 Возможности устройства (`DeviceCaps`, кэшировать на запуск и смену дисплея)
- **`MediaCodecList(REGULAR_CODECS)`:** для каждого mime (`video/avc`, `video/hevc`, `video/x-vnd.on2.vp9`, `video/av01`, `video/dolby-vision`, `video/mpeg2`, `video/wvc1`/`video/vc1`, `video/mp4v-es`) собрать:
- `isHardwareAccelerated` (API 29+) или эвристику по имени (`c2.android.*`/`OMX.google.*` — software);
- `CodecCapabilities.profileLevels` (AVC High10 = `AVCProfileHigh10`, HEVC Main10 / Main10HDR10 / HDR10Plus, VP9 Profile2HDR, AV1 Main10HDR10, DV-профили `DolbyVisionProfileDvheSt` (P8) / `DvheStn` (P5) / `DvavSe` и т. д.);
- `VideoCapabilities.areSizeAndRateSupported(w,h,fps)`.
- **Дисплей:** `Display.getHdrCapabilities().supportedHdrTypes` (до API 34) или `Display.Mode.getSupportedHdrTypes()` (API 34+), а также `isHdrSdrRatioAvailable`.
- **Аудио:** `AudioCapabilities` media3 — `isPassthroughSupported` для AC3, E-AC3, DTS, DTS-HD, TrueHD.
- **Exo-движок:** надёжнее всего спрашивать сам Exo, как jellyfin-androidtv: собрать `Format` из `MediaStream` и проверить `RendererCapabilities.supportsFormat` у видео- и аудиорендереров (`getPlaySupportReport`). Тогда учитываются и extension-декодеры (ffmpeg-аудио, dav1d).
### 5.2 Входные данные Jellyfin
`MediaSourceInfo.Container`, `MediaStreams[]`:
- **Видео:** `Codec`, `Profile`, `Level`, `BitDepth`, `Width/Height`, `RealFrameRate`, `IsInterlaced`, `VideoRange`, **`VideoRangeType`** (`SDR, HDR10, HDR10Plus, HLG, DOVI, DOVIWithHDR10, DOVIWithHLG, DOVIWithSDR, …`; у новых серверов есть варианты с EL и `DOVIInvalid` — проверить по версии SDK), `DvProfile`, `DvLevel`, `DvBlSignalCompatibilityId`, `RpuPresentFlag`, `ElPresentFlag`, `BlPresentFlag`, `CodecTag` (xvid/divx).
- **Аудио:** `Codec`, `Profile` (DTS-HD MA, TrueHD/Atmos), `Channels`.
- **Субтитры:** `Codec` (`subrip, ass, ssa, webvtt, pgssub, dvdsub, dvbsub, mov_text`), `IsExternal`, `DeliveryMethod`.
### 5.3 Вердикт по каждому движку
```kotlin
sealed interface Verdict {
data class Direct(val quality: Int, val notes: Set<Note>) : Verdict // quality: 100 = идеально
data class Degraded(val quality: Int, val losses: Set<Loss>) : Verdict // HDR→SDR, ASS упрощён, нет passthrough, SW 4K
data class No(val reasons: Set<Reason>) : Verdict
}
enum class Loss { HDR_TONEMAPPED, DV_TO_BASE_LAYER, ASS_SIMPLIFIED, NO_PASSTHROUGH, SOFTWARE_DECODE_HEAVY, NO_DEINTERLACE }
```
**ExoProvider.evaluate**
1. **Контейнер:** `mkv, webm, mp4, m4v, mov, ts, m2ts, mpegts, avi (AviExtractor), flv, ogg, mp3, flac, wav` → ок. `asf/wmv, rm/rmvb, vob/iso/BDMV, mpeg-ps (частично)` → `No(CONTAINER)`.
2. **Видео:**
- если `supportsFormat` == `FORMAT_HANDLED` по HW (или dav1d для AV1) → ок;
- `h264` с `BitDepth=10` (Hi10P) почти нигде нет в HW → `No(VIDEO_CODEC)`;
- `vc1/wmv3`, `msmpeg4*`, `rv*`, `mpeg4` с packed bitstream (DivX) → обычно `No`;
- `mpeg2video` → по MediaCodecList, на многих TV-SoC есть;
- HEVC выше Level HW-декодера (например, 4K120) → `No`.
3. **HDR:** у Exo потерь нет, если HW-декодер поддерживает профиль, а дисплей — тип HDR (или OS тонмапит сама).
- `DOVI*`: есть DV-декодер нужного профиля → Direct. Нет, но BL совместим (`DvBlSignalCompatibilityId` 1/2/4 → HDR10/SDR/HLG) → `Degraded(DV_TO_BASE_LAYER)` через подмену mime (так делает Plezy `DvBitstreamSanitizer`). P5 без DV-декодера → `No` (цвета будут неправильные).
- `DOVIWithEL` (P7 FEL): Direct только на редких SoC, иначе BL HDR10 → Degraded.
4. **Субтитры** (выбранные по умолчанию или пользователем):
- `subrip/webvtt/mov_text/ttml` → ок;
- `pgssub`, `dvdsub` (VobSub в MKV), `dvbsub` → у media3 есть парсеры, ок (проверить на своих файлах);
- `ass/ssa` → с ass-media ок, без него `Degraded(ASS_SIMPLIFIED)`.
5. **Аудио:** при passthrough-возможностях sink ок. TrueHD/DTS без passthrough → ffmpeg-extension (если в сборке) → ок, иначе `No(AUDIO)`.
6. **Interlaced** (`IsInterlaced`, типично MPEG-2/H.264 из ТВ-записей): Exo деинтерлейс не делает (на части SoC его делает сам декодер) → `Degraded(NO_DEINTERLACE)`.
**VlcProvider.evaluate**
- Контейнер и кодек → почти всегда ок.
- HDR через HW → Direct, если кодек есть в HW; при SW-декоде HDR → `Degraded(HDR_TONEMAPPED)` (не проверено).
- 4K HEVC 10-bit или Hi10P 1080p на слабом SoC → `Degraded(SOFTWARE_DECODE_HEAVY)` по эвристике «пиксели × fps × битность против класса CPU» (`Build.SUPPORTED_ABIS`, `ActivityManager.isLowRamDevice`, число ядер).
- ASS ок, PGS/VobSub ок, passthrough ок.
### 5.4 Правило выбора
```kotlin
fun choose(src: MediaSourceInfo, sel: StreamSelection, caps: DeviceCaps,
providers: List<EngineProvider>, prefs: EnginePrefs): EngineChoice {
prefs.forcedEngineFor(itemId = src.itemId, libraryId = src.libraryId)?.let { return it } // per-item и per-library override
if (prefs.mode is Manual) return EngineChoice(prefs.mode.engine)
val verdicts = providers.associateWith { it.evaluate(src, sel, caps) }
val direct = verdicts.filterValues { it is Verdict.Direct }
val degraded = verdicts.filterValues { it is Verdict.Degraded }
// 1. Основной движок (Exo), если он даёт Direct
direct.keys.minByOrNull { it.priority }?.let { return EngineChoice(it.id, reason = "direct") }
// 2. Конфликт «HDR против совместимости»: HDR важнее мелких потерь
// Exo Degraded(ASS_SIMPLIFIED | NO_DEINTERLACE) лучше, чем VLC с HDR_TONEMAPPED
val ranked = degraded.entries.sortedWith(
compareBy<Map.Entry<EngineProvider, Verdict>> { lossWeight((it.value as Verdict.Degraded).losses) }
.thenBy { it.key.priority })
ranked.firstOrNull()?.let { (p, v) ->
// Если лучший вариант теряет HDR, а сервер может транскодировать с HDR (или в DV→HDR10) — спросить политику
if (Loss.HDR_TONEMAPPED in (v as Verdict.Degraded).losses && prefs.preferServerOverHdrLoss)
return EngineChoice(EXO, forceTranscode = true, reason = "keep HDR via server")
return EngineChoice(p.id, reason = v.losses.toString())
}
// 3. Никто не может → транскод основным движком (профиль Exo)
return EngineChoice(EXO, forceTranscode = true, reason = "no local engine")
}
// Пример весов: HDR_TONEMAPPED=100, DV_TO_BASE_LAYER=30, SOFTWARE_DECODE_HEAVY=40, ASS_SIMPLIFIED=20, NO_PASSTHROUGH=15, NO_DEINTERLACE=10
```
**Порядок вызовов с сервером:**
1. Решение принимается **до** `PlaybackInfo` по `MediaSources`/`MediaStreams` элемента (`/Items/{id}` с `Fields=MediaSources,MediaStreams` или результат предварительного PlaybackInfo).
2. Затем `PlaybackInfo` вызывается с **DeviceProfile выбранного движка**. Так делают Wholphin, Swiftfin и JellyPlay; объединять профили нельзя, иначе сервер разрешит DirectPlay того, что Exo не умеет.
3. Если сервер вернул транскод для профиля Exo, а VLC мог бы играть напрямую, сменить выбор и повторить PlaybackInfo с профилем VLC: прямое воспроизведение лучше транскода.
4. **Смена аудио- или субтитр-трека пользователем** запускает выбор заново. Пример: включили PGS в HDR-фильме, Exo справляется, остаёмся на нём; включили сложный ASS при SDR — можно предложить VLC или mpv с переключением как в 2.5.
### 5.5 Runtime-fallback (по образцу Plezy)
- **Триггеры:**
- `PlaybackException` с кодами `ERROR_CODE_DECODER_INIT_FAILED`, `ERROR_CODE_DECODING_FAILED`, `ERROR_CODE_DECODING_FORMAT_UNSUPPORTED`/`EXCEEDS_CAPABILITIES`, `ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED`;
- `onTracksChanged`: есть видеогруппа, но ни одна не выбрана;
- watchdog: `DecoderCounters` показывают вход без выхода дольше N с;
- 0 кадров (`onRenderedFirstFrame` не пришёл) дольше M с при `isPlaying`;
- `AudioSink` init failure после восстановления.
- **Порядок:**
1. если кадр уже был отрисован, один повтор в Exo (пересоздать декодер, `seekTo(pos)`);
2. для DV — попытка DV → HDR10;
3. переход на следующий движок по ранжированию из 5.4;
4. серверный транскод.
Не больше одного перехода на движок за item.
- **Наблюдаемость:** записывать в локальный журнал `(item, codec/profile/rangeType, device model/SoC, decoder name, exception code, выбранный fallback)`. По журналу потом уточнять правила 5.3, например заносить в denylist конкретные `codecName` на конкретных SoC.
- **Настройки UI:**
- «Движок: Авто / ExoPlayer / VLC»;
- «Автоматически переключаться при ошибке: вкл»;
- «Предпочитать сохранение HDR (транскод) вместо SW-декода»;
- per-item «Воспроизвести с помощью…» (как `Destination.Playback.backend` в Wholphin и «Play using…» в Kodi);
- per-library override (например, «Аниме → mpv»).
---
## 6. Ссылки (проверены, если не указано иное)
- Findroid: https://github.com/jarnedemeulemeester/findroid — `player/local/.../mpv/MPVPlayer.kt`, `player/local/.../presentation/PlayerViewModel.kt`, `data/.../JellyfinRepositoryImpl.kt`
- Wholphin: https://github.com/damontecres/Wholphin — `app/.../services/PlayerFactory.kt`, `app/.../ui/playback/PlaybackViewModel.kt`, `app/.../util/mpv/MpvDeviceProfile.kt`, `wholphin-mpv-stub/`, `app/build.gradle.kts`
- wholphin-extensions: https://github.com/damontecres/wholphin-extensions (v0.2.4, 11.09.2026)
- JellyPlay: https://github.com/raulshma/jellyplay — `docs/player-engines.md`, `shared/core/player-contract/.../engine/MediaEngine.kt`, `shared/core/network/.../DeviceProfileProvider.kt`, `.../engine/Media3PlayerHost.kt`
- AFinity: https://github.com/MakD/AFinity — `app/.../ui/player/PlayerViewModel.kt`
- Void: https://github.com/hritwikjohri/Void-for-jellyfin — `domain/model/playback/PlayerType.kt`, `presentation/ui/screen/player/VideoPlayerScreen.kt`
- Streamyfin: https://github.com/streamyfin/streamyfin — `utils/atoms/settings.ts`, `modules/mpv-player`, `modules/exoplayer-player`
- Swiftfin: https://github.com/jellyfin/Swiftfin — `Shared/Objects/VideoPlayerType/VideoPlayerType.swift`, `Shared/Objects/MediaPlayerManager/MediaPlayerProxy/*.swift`
- Plezy: https://github.com/edde746/plezy — `android/app/.../exoplayer/ExoPlayerPlugin.kt`, `ExoPlayerCore.kt`, `VideoDecoderRecoveryPolicy.kt`, `.../mpv/MpvPlayerCore.kt`, `mpv-build.lock.json`
- jellyfin-androidtv: https://github.com/jellyfin/jellyfin-androidtv — `playback/core/src/main/kotlin/backend/PlayerBackend.kt`, `BackendService.kt`, `playback/media3/session/.../MediaSessionPlayer.kt`
- Just Player: https://github.com/moneytoo/Player — `Prefs.java` (`decoderPriority`)
- Nova: https://github.com/nova-video-player/aos-MediaLib — `IMediaPlayer.java`, `MediaFactory.java`; https://github.com/nova-video-player/aos-AVP (`exoplayer.xml`)
- Kodi: https://github.com/xbmc/xbmc/blob/master/system/playercorefactory.xml, `xbmc/cores/playercorefactory/PlayerSelectionRule.cpp`; https://kodi.wiki/view/External_players (403 при загрузке, не проверено)
- media3 PlayerSurface: https://github.com/androidx/media/blob/release/libraries/ui_compose/src/main/java/androidx/media3/ui/compose/PlayerSurface.kt
- libmpv-android: https://github.com/jarnedemeulemeester/libmpv-android (`buildscripts/include/depinfo.sh`, `build.sh`)
- mpv-android: https://github.com/mpv-android/mpv-android (`app/src/main/java/is/xyz/mpv/MPVView.kt`)
- Maven Central: https://repo1.maven.org/maven2/org/videolan/android/libvlc-all/ , https://repo1.maven.org/maven2/dev/jdtech/mpv/libmpv/ , https://repo1.maven.org/maven2/io/github/abdallahmehiz/mpv-android-lib/
- ass-media: https://repo1.maven.org/maven2/io/github/peerless2012/ass-media/ (0.5.1)
- Play Feature Delivery on-demand: https://developer.android.com/guide/playcore/feature-delivery/on-demand

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
# Профиль медиатеки тестового сервера (снято через API 2026-10-12)
Сервер: Jellyfin **10.11.8** (нужно обновить до 12.x под SDK 1.9). Публичный адрес сервера резолвится дома в LAN-адрес (split DNS). У тестового пользователя `RemoteClientBitrateLimit = 0`.
12 671 элемент (фильмы + эпизоды). Счётчик = число медиаисточников, в которых встречается признак.
| Категория | Кол-во | Значение для клиента |
|---|---|---|
| Контейнер MKV / AVI / MP4 | 6924 / 4207 / 1524 | AVI — треть библиотеки |
| MPEG-4 ASP (DivX/Xvid) + Simple | 3807 + 375 | HW-декодер ТВ есть (до 1080p); packed bitstream — проверить media3 AVI vs VLC |
| H.264 8-bit | ~5740 | без проблем |
| **H.264 Hi10P (10-bit)** | 151 | HW нет → только программно (libVLC), CPU 4×A73 |
| HEVC SDR 8/10-bit | 739 / 1751 | HW |
| HEVC HDR10 / HDR10+ / HLG | 30 / 9 / 3 | media3 |
| **DV P8.1 (DOVIWithHDR10)** | 25 | media3, DV-декодер |
| **DV + HDR10+** | 4 | известный баг media3 на MediaTek (#1895) — вероятная причина зависаний |
| DV P8.4 (DOVIWithHLG) | 17 | media3 |
| AV1 (SDR/HDR10) | 4 | HW |
| MPEG-2 | 14 | HW до 1080p; часть **чересстрочные** (15 файлов) → нужен деинтерлейс |
| Аудио AAC / AC3 / EAC3 / MP3 | 4500 / 5673 / 1046 / 2057 | — |
| Аудио DTS / TrueHD / FLAC / Opus / Vorbis / MP2 | 77 / 3 / 61 / 471 / 27 / 3 | TrueHD и DTS на встроенных динамиках → декод в PCM (MS12 или ffmpeg) |
| **Субтитры ASS** | 2475 | главный повод транскода у других клиентов (burn-in) → рендерить на клиенте |
| SRT / mov_text / PGS / DVDSUB | 1389 / 14 / 6 / 1 | — |
| Встроенная обложка (mjpeg/png как видеопоток) | 499 | не путать с видеодорожкой при выборе/профиле |
## Примеры проблемных файлов
- **Фильм 4K HEVC HDR10 + TrueHD Atmos** — MKV 73,8 GiB, HEVC Main10 HDR10 4K 23.976, видео ~55 Мбит/с; аудио по умолчанию **TrueHD Atmos 7.1**; субтитры по умолчанию **ASS (forced)**. Вероятные причины проблем: ASS по умолчанию → burn-in → полный транскод 4K HDR с тонмаппингом (тусклая картинка); TrueHD на динамиках; ~60 Мбит/с суммарно по Wi-Fi.
- **Фильм DV P8.1 + HDR10+ и AC3** — HEVC **DV P8.1 + HDR10+** (`DOVIWithHDR10Plus`), 26,6 Мбит/с, AC3. Совпадает с багом media3 на MediaTek (зависание/чёрный/пурпурный экран).
- **Фильм DV P8.1 + E-AC3** — HEVC DV P8.1 (`DOVIWithHDR10`), 20 Мбит/с, EAC3, SRT, 8 аудио и 12 субтитров, встроенная MJPEG-обложка.

327
docs/research/media3.md Normal file
View File

@@ -0,0 +1,327 @@
# media3/ExoPlayer как основной движок Jellyfin-клиента для Android TV: что умеет, что нет
Дата: 2026-10-12. Версии: media3 **1.11.1** (2026-09-10), jellyfin-androidtv master `0032f4a` (2026-10-12), Jellyfin server 12.2, jellyfin-sdk-kotlin 1.9.2.
Метод: исходники читались напрямую (клоны `androidx/media` main `8c6678b` = 1.11.1, `jellyfin/jellyfin-androidtv` master, `jellyfin/jellyfin-androidx-media`, `peerless2012/libass-android`, `anilbeesetti/nextlib`, `moneytoo/Player` (Just Player), `xbmc/xbmc` raw). Issues/PR проверялись через GitHub API или загрузкой страниц. Всё, что не подтверждено кодом или первоисточником, помечено **(не проверено)**.
---
## 0. TL;DR
| Тема | Вывод |
|---|---|
| DV P5 / P8.x / P10 | Работает, если есть DV-декодер (`video/dolby-vision`) **и** дисплей сообщает `HDR_TYPE_DOLBY_VISION`. Без DV-дисплея media3 сам переключается на HEVC/AVC/AV1 для P4/P8/P9/P10 (кроме 10.0). Для P5 фолбэка нет. |
| DV P7 (FEL/MEL, BD-remux) | В media3 **нет фолбэка на HEVC** (`DvheDtb` исключён в `getAlternativeCodecMimeType`). FEL никогда не обрабатывается (только BL+RPU, если декодер SoC умеет). Фикс фолбэка: PR [#3160](https://github.com/androidx/media/pull/3160), ещё открыт. |
| DV в MKV | `MatroskaExtractor` парсит `BlockAdditionMapping` с `BlockAddIDType` = `dvcC`/`dvvC` → MIME `video/dolby-vision`. В MP4 парсятся `dvcC`/`dvvC`/`dvwC`. |
| HDR10 / HLG / HDR10+ | Статические метаданные идут через `ColorInfo.hdrStaticInfo` → `KEY_HDR_STATIC_INFO`. HDR10+ для HEVC/AV1 передаётся внутри потока (SEI/OBU), для VP9 в MKV через BlockAdditional T.35 → `PARAMETER_KEY_HDR10_PLUS_INFO`. Гибриды DV+HDR10+ ломаются на устройствах MediaTek (Fire TV 4K/Max, Google TV Streamer). |
| Tunneling | Включается через `DefaultTrackSelector.Parameters.setTunnelingEnabled(true)`. Полезен для 4K/HDR на слабых SoC, но ломает часть сценариев (точный seek в конец, видео без аудио, эффекты). jellyfin-androidtv его **не включает**. |
| Частота обновления | media3 сам умеет только `C.VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS` (через `Surface.setFrameRate`). На ТВ переключение почти всегда «не бесшовное», поэтому Kodi, Just Player и jellyfin-androidtv ставят `WindowManager.LayoutParams.preferredDisplayModeId`. |
| Режим HDR | Приложение не может явно включить HDR-режим HDMI. Он переключается автоматически, если HDR-кадры идут в `SurfaceView` (зависит от прошивки и настройки «Match dynamic range»). Обязательно `SurfaceView`, не `TextureView`. Обратного переключения в SDR после остановки может не быть: [#3406](https://github.com/androidx/media/issues/3406). |
| Passthrough | AC3, E-AC3, E-AC3-JOC (Atmos), DTS, DTS-HD, DTS:X P2 (`ENCODING_DTS_UHD_P2`, API 34) и TrueHD (`ENCODING_DOLBY_TRUEHD`) через `DefaultAudioSink` + `AudioCapabilities`. DTS-HD MA пока идёт как `ENCODING_DTS_HD`; PR [#3234](https://github.com/androidx/media/pull/3234) под `ENCODING_DTS_HD_MA` открыт. |
| FFmpeg-расширение | Официальное `media3-decoder-ffmpeg` декодирует **только аудио**: `ExperimentalFfmpegVideoRenderer` в 1.11.1 пустая заглушка. Готовый AAR: `org.jellyfin.media3:media3-ffmpeg-decoder:1.9.0+1` (только аудио, FFmpeg 6.0, LGPL-сборка, POM помечен GPL-3.0). |
| Программное видео | Официально VP9 (libvpx) и AV1 (**dav1d** с 1.9.0, раньше libgav1); ни то, ни другое не публикуется в Maven. Сторонний `io.github.anilbeesetti:nextlib-media3ext:1.11.1-0.16.0` добавляет FFmpeg-видео (h264, hevc, mpeg1/2, vp8, vp9, av1-dav1d), но вывод **8-bit RGBA/YUV420P**, то есть без HDR. VC-1, MPEG-4 ASP и WMV ни в одной готовой сборке нет. |
| ASS/SSA | `io.github.peerless2012:ass-media:0.5.1` (libass, MIT) поддерживает вложенные шрифты MKV. В jellyfin-androidtv подключён за опцией `libass_enabled` (по умолчанию false), режим `AssRenderType.OVERLAY_OPEN_GL`. |
| PGS / VobSub / DVB | Внутри MKV (`S_HDMV/PGS`, `S_VOBSUB`, `S_DVBSUB`) и DVB в TS работают. Внешние `.sup` и `.idx/.sub` через `SubtitleConfiguration` фактически не годятся **(не проверено)**. |
| Контейнеры | MKV: header stripping поддерживается, zlib нет ([#2361](https://github.com/androidx/media/issues/2361)), ordered chapters и linked segments нет. M2TS (192-байтные пакеты) не поддерживается ([#608](https://github.com/androidx/media/issues/608)). ASF/WMV, ISO, VIDEO_TS и BDMV не поддерживаются. AVI есть (`AviExtractor`). |
---
## 1. Dolby Vision в media3
### 1.1 Профили и сопоставление
Источник: `libraries/common/.../CodecSpecificDataUtil.java` и `libraries/exoplayer/.../mediacodec/MediaCodecUtil.java`.
| Профиль DV | Константа `CodecProfileLevel` | Codec string | Базовый слой | Фолбэк media3 без DV-дисплея |
|---|---|---|---|---|
| 4 | `DolbyVisionProfileDvheDtr` | dvhe.04 | HEVC | → `video/hevc` |
| 5 | `DolbyVisionProfileDvheStn` | dvhe.05 | IPTPQc2, без обратной совместимости | **нет** |
| 7 | `DolbyVisionProfileDvheDtb` | dvhe.07 | HEVC Main10 HDR10 + EL | **нет**, явно исключён: «deprecated and is not always backward compatible» |
| 8.1 / 8.2 / 8.4 | `DolbyVisionProfileDvheSt` | dvhe.08 (dvh1.08 в MP4) | HEVC HDR10 / SDR / HLG | → `video/hevc` |
| 9 | `DolbyVisionProfileDvavSe` | dvav.09 | AVC | → `video/avc` |
| 10.x | `DolbyVisionProfileDvav110` | dav1.10 | AV1 | → `video/av01`, **кроме 10.0** (PQ + full range означает отсутствие совместимости) |
- Поддержку P10 добавили в 1.10.0 ([PR #2830](https://github.com/androidx/media/pull/2830)). В 1.10.1 исправили артефакты из-за initialization data для DV-AV1 ([#3153](https://github.com/androidx/media/pull/3153)).
- MIME: `MimeTypes.VIDEO_DOLBY_VISION = "video/dolby-vision"`. `MediaCodecUtil.getCodecMimeType` учитывает вендорские MIME: `OMX.MS.HEVCDV.Decoder` → `video/hevcdv`, а Realtek (`OMX.RTK.video.decoder`, `OMX.realtek.video.decoder.tunneled`) → `video/dv_hevc`.
- `MediaCodecVideoRenderer.onConfigure`: для DV в `MediaFormat` явно пишется `KEY_PROFILE`, потому что некоторые устройства этого требуют.
### 1.2 Как выбирается декодер (`MediaCodecVideoRenderer.getDecoderInfos`)
```
if (SDK_INT >= 26 && format is DV && !Api26.doesDisplaySupportDolbyVision(context)) {
alt = MediaCodecUtil.getAlternativeDecoderInfos(...) // HEVC/AVC/AV1 по таблице выше
if (!alt.isEmpty()) return alt;
}
return MediaCodecUtil.getDecoderInfosSoftMatch(...) // DV-декодеры + альтернативные
```
- `doesDisplaySupportDolbyVision` проверяет `display.isHdr()` и наличие `HDR_TYPE_DOLBY_VISION` в `Display.getHdrCapabilities().getSupportedHdrTypes()` для `DEFAULT_DISPLAY`.
- Если DV-дисплея нет, `supportsFormat` возвращает `DECODER_SUPPORT_FALLBACK_MIMETYPE`.
- `getDecoderInfosSoftMatch` даёт полный список: декодеры для основного MIME плюс для альтернативного (см. javadoc). Отсюда следствие: если на устройстве **есть** DV-декодер, а дисплей DV не поддерживает, для P5 и P7 будет использован DV-декодер. Что он выведет на не-DV экран (тон-маппинг или фиолетовая картинка), зависит от SoC **(не проверено)**.
- Если ни DV-декодера, ни фолбэка нет (например, P7 на Chromecast/Google TV Streamer без DV), трек получает статус unsupported и видео не играет. Решения: (а) DeviceProfile, чтобы сервер срезал DV или транскодировал; (б) на клиенте подменить `sampleMimeType` на `video/hevc` через собственный `MediaCodecSelector` или обёртку экстрактора (так делают сторонние плееры **(не проверено)**).
- Из release notes:
- 1.9.x: «Fix issue where some playbacks of Dolby Vision files fail when attempting to use a fallback AVC or HEVC codec».
- Ранее: «Disable codec reuse for Dolby-Vision content with different profiles» и «Support CEA-608 in DV content».
- 1.11.0: трек с распознанным, но неподдерживаемым профилем/уровнем теперь `NO_EXCEEDS_CAPABILITIES`, а не `YES`.
- 1.10.1: [#3135](https://github.com/androidx/media/issues/3135) перенёс HDR-предпочтения в quality preferences.
- Открыт PR [#3461](https://github.com/androidx/media/pull/3461) «Select HDR video tracks by display HDR capabilities».
### 1.3 Профиль 7 (FEL/MEL)
- В MKV (MakeMKV) BL+EL+RPU объединены в **один трек** с NAL type 62/63, codec `dvhe.07.06`.
- media3 не делает ни композиции EL, ни конвертации P7 в P8. Feature request [#1865](https://github.com/androidx/media/issues/1865) («DV Profile 7 + FEL on Android TV») открыт с 2024-11, без ответа мейнтейнеров.
- Фолбэк P7 → HEVC (BL HDR10/HDR10+) появится только после PR [#3160](https://github.com/androidx/media/pull/3160) (open, approve от контрибьютора 2026-08-14).
- Устройства: Nvidia Shield 2019 фактически играет P7 (MEL и FEL как BL+RPU), но **не сообщает** об этом в `MediaCodecList`. jellyfin-androidtv держит это в `KnownDefects.unreportedDoviProfile7Support` (`Build.MODEL == "SHIELD Android TV"`).
- Ugoos/CoreELEC с `dovi.ko` и полноценным FEL недоступны обычному Android-приложению.
- Свежий баг jellyfin-androidtv: [#5794](https://github.com/jellyfin/jellyfin-androidtv/issues/5794), «TrueHD passthrough and DV P7 fallback broken on Shield 2019». В 0.20 beta частично исправлен; на не-DV Samsung P7 играет в SDR вместо HDR10.
### 1.4 DV в MKV и MP4
- **MKV**: `MatroskaExtractor` понимает `ID_BLOCK_ADDITION_MAPPING (0x41E4)`, `ID_BLOCK_ADD_ID_TYPE (0x41E7)`, `ID_BLOCK_ADD_ID_EXTRA_DATA (0x41ED)`. При `BlockAddIDType == 0x64766343 ('dvcC')` или `0x64767643 ('dvvC')` байты конфигурации парсятся через `DolbyVisionConfig.parse()`, после чего `mimeType = video/dolby-vision`, `codecs = dvhe.PP.LL`. `dvwC` для MKV не обрабатывается, для MP4 обрабатывается. `BlockAdditional` с ID 4 (ITU-T T.35) берётся **только для VP9**, остальные block additions пропускаются.
- **MP4/fMP4**: боксы `dvcC`, `dvvC`, `dvwC`, sample entries `dvhe`, `dvh1`, `dav1`, `dvav`/`dva1` (`Mp4Box.java`).
- **TS/HLS**: DV определяется через `SUPPLEMENTAL-CODECS` в HLS. В «сыром» TS с DV-дескриптором (Blu-ray m2ts) нет **(не проверено)**; M2TS всё равно не открывается, см. §7.
### 1.5 Известные открытые проблемы DV
- [#1895](https://github.com/androidx/media/issues/1895): DV P8.1 + HDR10+ (гибридные релизы) на Fire TV 4K Max и других MediaTek. Симптомы: зависание, чёрный или пурпурный экран. Фикс PR [#3085](https://github.com/androidx/media/pull/3085) («Skip HDR10+ metadata processing when DV Profile 8 is detected»), open, результаты смешанные. jellyfin-androidtv обходит это через `KnownDefects.hevcDoviHdr10PlusBug` для моделей `AFTKRT`, `AFTKA`, `AFTKM`, `AFTMM`.
- [#5184 (jellyfin-androidtv)](https://github.com/jellyfin/jellyfin-androidtv/issues/5184): чёрное мигание на DoVi при смене сцен.
- [#1635](https://github.com/androidx/media/issues/1635): DASH, нельзя выбрать DV AdaptationSet. Для нас неактуально.
---
## 2. HDR10 / HDR10+ / HLG и tunneling
### 2.1 Метаданные
- **Статические** (MaxCLL/MaxFALL/mastering display): из MKV `Colour/MasteringMetadata`, MP4 `mdcv`/`clli` или VUI/SEI попадают в `ColorInfo.hdrStaticInfo`, затем `MediaFormatUtil.maybeSetColorInfo` записывает `MediaFormat.KEY_HDR_STATIC_INFO`, `KEY_COLOR_TRANSFER`, `KEY_COLOR_STANDARD`, `KEY_COLOR_RANGE`. В 1.11.0 добавлено «Matroska: Use codec bitstream metadata to populate accurate ColorInfo», так что неверные или пустые Colour-элементы MKV больше не ломают HDR.
- **HDR10+ (динамические)**:
- HEVC и AV1: метаданные лежат в SEI/OBU потока и уходят в декодер вместе с кадром. Для MKV ничего специального не нужно, декодер должен поддерживать `HEVCProfileMain10HDR10Plus`.
- VP9 в MKV/WebM: BlockAdditional ID 4 → `supplementalData` → `MediaCodec.setParameters(PARAMETER_KEY_HDR10_PLUS_INFO)` (API 29+, `setHdr10PlusInfoV29`).
- **HLG**: определяется по `COLOR_TRANSFER_HLG`, отдельной обработки нет.
- **Новое**: в 1.11.0 добавлена экспериментальная поддержка HAGC (SMPTE ST 2094-50) для MP4/MKV, работает на API 37+. С 1.11.1 отключается через `ExtractorsFactory.setParseHagcMetadata(false)`.
- Открыт PR [#3463](https://github.com/androidx/media/pull/3463): «Map 10-bit AV1 to AV1ProfileMain10HDR10 only for PQ».
### 2.2 Tunneled playback
- API: `DefaultTrackSelector.Parameters.Builder.setTunnelingEnabled(true)`. Проверка: `ExoPlayer.isTunnelingEnabled()`. Работает, только если **и видео-, и аудиорендерер** поддерживают tunneling. Для видео нужен декодер с `FEATURE_TunneledPlayback`, поддерживающий формат и seamless adaptation (`MediaCodecVideoRenderer.supportsFormat`). Нужен audio session id: AV-sync идёт по аудио (HW sync).
- **Плюсы**: кадры идут от декодера сразу в HW-композитор, минуя app/SurfaceFlinger. Меньше дропов для 4K60/HDR на слабых SoC (Amlogic/MediaTek-стики), точнее A/V sync. В Just Player опция «Tunneled playback» описана как «can improve playback of 4K/HDR content on Android TV». Подробнее: [AOSP multimedia tunneling](https://source.android.com/devices/tv/multimedia-tunneling).
- **Минусы и что ломается**:
- Без аудиотрека не работает ([#2541](https://github.com/androidx/media/issues/2541)). Пауза делается через AudioTrack.
- Seek в конец может зависнуть, не доходя до `STATE_ENDED` ([#1574](https://github.com/androidx/media/issues/1574)).
- Гонка с audio session id: crash `IllegalStateException` (исправлен в 1.10.1, [#3099](https://github.com/androidx/media/issues/3099)).
- DTS:X P2 в HLS-fMP4 теряет первые 2 секунды в tunnel ([#1245](https://github.com/androidx/media/issues/1245)).
- Нельзя применять video effects или GL-обработку к кадрам. Не работают `VideoFrameMetadataListener` и покадровый контроль, render-first-frame на паузе после seek ненадёжен **(частично не проверено)**.
- Нет гарантии нескольких одновременных tunneled-декодеров.
- Часть HDR-метаданных на некоторых SoC в tunnel теряется **(не проверено)**.
- **Субтитры**: text-рендерер не зависит от tunneling, cues приходят как обычно, `SubtitleView` или ASS-overlay (`AssRenderType.OVERLAY_*`) рисуются поверх SurfaceView. Не будут работать только режимы, встраивающие субтитры в видеокадр (`AssRenderType.EFFECTS_*`, deprecated) **(логический вывод, не проверено на устройстве)**.
- **Рекомендация**: опция «Tunneling: Авто/Вкл/Выкл», по умолчанию выкл. Если для конкретной модели есть подтверждённые проблемы с плавностью, включать через device-list. jellyfin-androidtv tunneling не использует, а только репортит поддержку (`ExoPlayerPlaySupportReport.tunnelingFromFlags`).
---
## 3. Режим дисплея: частота и HDR
### 3.1 Что умеет media3
- `ExoPlayer.setVideoChangeFrameRateStrategy(int)` принимает только `C.VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS` (по умолчанию) или `..._OFF`. На API 30+ `VideoFrameReleaseHelper.Api30.setSurfaceFrameRate` вызывает `Surface.setFrameRate(rate, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)` (или 0/DEFAULT на паузе). Вариант `CHANGE_FRAME_RATE_ALWAYS` в media3 **не реализован**.
- По [гайду Android](https://developer.android.com/media/optimize/performance/frame-rate):
- для фильмов использовать `Surface.setFrameRate(fps, FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS)` (API 31), но только если пользователь выбрал `DisplayManager.getMatchContentFrameRateUserPreference() == MATCH_CONTENT_FRAMERATE_ALWAYS`;
- дождаться `DisplayListener.onDisplayChanged`, затем выдержать паузу около 2 с и запускать воспроизведение;
- на Android TV «тяжёлую» смену режима (HDMI re-negotiation) `setFrameRate` может не вызвать, и гайд прямо советует для этого `preferredDisplayModeId`.
- Связанные баги:
- [#2258](https://github.com/androidx/media/issues/2258): после смены частоты Shield падал на PCM вместо passthrough. Исправлено в 2025-05, рекомендуется `setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true)`.
- [#3120](https://github.com/androidx/media/issues/3120) и [#2941](https://github.com/androidx/media/issues/2941): чёрные кадры и сброс кодека при смене fps на API < 30 (исправлено в 1.10.x).
### 3.2 Как делают другие
- **jellyfin-androidtv** (`ui/playback/PlaybackController.java`, `findBestDisplayMode`/`setRefreshRate`): перебирает `Display.getSupportedModes()` и пропускает режимы ниже 1280×720 и ниже разрешения видео. Подходят частоты `== fps`, `== 2×fps`, `== 2.5×fps` (в сотых герца). Вес: `100000 − rateDiff + 100000 − resolutionDiff`. Режимы `SCALE_ON_DEVICE` / `SCALE_ON_TV` (`RefreshRateSwitchingBehavior`). Применяет `window.attributes.preferredDisplayModeId`, по выходу сбрасывает в 0 (`CustomPlaybackOverlayFragment`).
- **Just Player** (`Utils.handleFrameRate`): fps берётся из `MediaExtractor`. Ищется режим **того же разрешения** с частотой, кратной fps; если такого нет, берётся максимальная. Ставится `preferredDisplayModeId`, воспроизведение стартует в `DisplayManager.DisplayListener.onDisplayChanged`. Комментарий в коде: «ExoPlayer already uses Surface.setFrameRate() on Android 11+».
- **Kodi** (`XBMCApp.cpp::SetDisplayMode` / `SetDisplayModeCallback`): тоже `preferredDisplayModeId`. HDR-возможности берёт из `Display.getHdrCapabilities().getSupportedHdrTypes()` (`AndroidUtils.cpp`).
- **Nova Video Player**: `preferredDisplayModeId` с похожей логикой **(не проверено по коду)**.
- **Рекомендация**: свой `DisplayModeManager`:
1. До `prepare()` взять fps из Jellyfin `MediaStream.RealFrameRate`/`AverageFrameRate`.
2. Если режим «Всегда», выбрать `Display.Mode` и выставить `preferredDisplayModeId` (на API 31+ можно ещё `Surface.setFrameRate(..., CHANGE_FRAME_RATE_ALWAYS)`).
3. Дождаться `onDisplayChanged` с таймаутом.
4. Включить `setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true)`, потому что после переподключения HDMI меняются AudioCapabilities.
5. Учесть 23.976 против 24.000 (сравнивать с точностью около 0.01 Гц) и фильтровать режимы с меньшим разрешением.
### 3.3 Переключение HDR
- У обычного приложения нет публичного API «включить HDR на HDMI». Платформа или прошивка переключают выход, когда в `SurfaceView` поступают кадры с HDR-dataspace. Это зависит от настроек «Match content dynamic range» (Shield, Google TV, Fire TV) **(не проверено для каждой прошивки)**. На API 34+ есть `DisplayManager.setHdrConversionMode` / `HdrConversionMode`, но это system API **(не проверено)**.
- Проверка возможностей: `Display.isHdr()` и `Display.getHdrCapabilities().supportedHdrTypes` (`HDR_TYPE_DOLBY_VISION`, `HDR_TYPE_HDR10`, `HDR_TYPE_HLG` с API 24, `HDR_TYPE_HDR10_PLUS` с API 29). На API 34+ также `Display.Mode.getSupportedHdrTypes()` **(не проверено)**.
- **Обязателен `SurfaceView`**: `TextureView` HDR не передаёт. В Compose это `PlayerSurface(surfaceType = SURFACE_TYPE_SURFACE_VIEW)` из `media3-ui-compose` или `AndroidExternalSurface`.
- Открытый баг [#3406](https://github.com/androidx/media/issues/3406): после остановки HDR-видео дисплей остаётся в HDR (Mi Box S, Android TV 11/14). Kodi этим не страдает. Гипотеза: нужно освобождать или отсоединять Surface, сбрасывать `preferredDisplayModeId` и закрывать активити плеера **(не проверено)**.
---
## 4. Аудио: passthrough и FFmpeg
### 4.1 Passthrough (`DefaultAudioSink`, `AudioCapabilities`)
- Таблица `AudioCapabilities.ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS`: `AC3` 6, `AC4` 6, `DTS` 6, `DTS_UHD_P2` 10, `E_AC3_JOC` 6, `E_AC3` 8, `DTS_HD` 8, `DOLBY_TRUEHD` 8.
- Встроенные фолбэки: E-AC3-JOC → E-AC3 (Atmos-метаданные при этом остаются внутри потока); DTS-HD и DTS-UHD-P2 → DTS core, если HD не поддерживается.
- На API 33+ для TV используется `AudioManager.getDirectProfilesForAttributes`. На API 29+ для TV учитываются `external_surround_sound_enabled` (Fire TV) и `force_external_surround_sound`. Для Amazon и Xiaomi есть отдельная ветка.
- **Atmos**: E-AC3-JOC (DD+ Atmos) битстримится как `ENCODING_E_AC3_JOC` (API 28+). TrueHD Atmos битстримится как `ENCODING_DOLBY_TRUEHD` (MAT); Atmos в TrueHD передаётся прозрачно.
- **DTS-HD MA / DTS:X (Blu-ray)**: идут как `ENCODING_DTS_HD`, DTS:X прозрачно внутри. Корректный `ENCODING_DTS_HD_MA` (API 34) появится с PR [#3234](https://github.com/androidx/media/pull/3234) (open), связанный issue [#2487](https://github.com/androidx/media/issues/2487). Детект DTS-HD в MKV и MP4 доработан в 1.11 (PR #3226, [#3229](https://github.com/androidx/media/pull/3229)), в TS склеиваются core и ext ([#3147](https://github.com/androidx/media/pull/3147)).
- **TrueHD**:
- MKV: `TrueHdSampleRechunker` группирует sync-фреймы для passthrough.
- fMP4: TrueHD не поддерживается ([#1519](https://github.com/androidx/media/issues/1519)), PR [#3411](https://github.com/androidx/media/pull/3411) открыт. Для нас это значит: не просить сервер ремуксить TrueHD в HLS-fMP4.
- Программный декод 2.0 TrueHD сломан в FFmpeg-расширении с media3 1.2.1 (FFmpeg 6.0, [#1843](https://github.com/androidx/media/issues/1843), open). 5.1/7.1 работают.
- [#3329](https://github.com/androidx/media/issues/3329): сложности учёта данных в `DefaultAudioSink` при TrueHD → MAT.
- Реальность устройств: TrueHD/DTS-HD битстримит Shield. Chromecast/Google TV Streamer и большинство стиков — только DD/DD+ (часто «Dolby Atmos via DD+»). Сводки нет **(не проверено)**.
- Наблюдение jellyfin-androidtv [PR #5822](https://github.com/jellyfin/jellyfin-androidtv/pull/5822): если объявить TrueHD как поддерживаемый, а устройство не может его битстримить, сервер отдаст оригинал, клиент декодирует его в PCM 7.1, а TV в цепочке может свести это в стерео. Лучше просить сервер перекодировать в AC3 5.1. Контрпример: Chromecast напрямую в ресивер. Нужна пользовательская настройка.
### 4.2 Расширение FFmpeg
- Официальный модуль `libraries/decoder_ffmpeg`. Внутри `FfmpegAudioRenderer` и `ExperimentalFfmpegVideoRenderer`. Последний в 1.11.1 явно **нерабочий**: `supportsFormat` возвращает `FORMAT_UNSUPPORTED_TYPE`, `// TODO: Merge actual implementation in PR #1591`, `b/470357054`. `DefaultRenderersFactory` подхватывает его рефлексией, но толку нет.
- Маппинг MIME → FFmpeg (`FfmpegLibrary.getCodecName`): aac, mp3, ac3, eac3 (+JOC), truehd, dca (DTS / Express / HD), vorbis, opus, amrnb/wb, flac, alac, pcm_mulaw/alaw, h264, hevc.
- В Maven Google модуль **не публикуется** ([ExoPlayer #2781](https://github.com/google/ExoPlayer/issues/2781)). Сборка: NDK r26b, FFmpeg `release/6.0`, `build_ffmpeg.sh` с `--disable-everything --enable-decoder=...`, без `--enable-gpl`, то есть получается **LGPL-2.1+**.
- **Готовый AAR от Jellyfin**: `org.jellyfin.media3:media3-ffmpeg-decoder` (Maven Central, последняя `1.9.0+1`, 2025-12-29). Репозиторий [jellyfin/jellyfin-androidx-media](https://github.com/jellyfin/jellyfin-androidx-media): submodules `androidx/media` (ветка release) и FFmpeg `release/6.0`. `ENABLED_DECODERS=(flac alac pcm_mulaw pcm_alaw mp3 aac ac3 eac3 dca mlp truehd)`, ABI 23, **только аудио**. Лицензия репозитория и POM: GPL-3.0; сам libffmpeg собран без `--enable-gpl` (LGPL). jellyfin-androidtv использует `1.9.0+1` вместе с media3 `1.11.1`: бинарно совместимо на практике, но это риск при обновлениях. Режим `EXTENSION_RENDERER_MODE_ON` или `_PREFER` (опция «prefer ffmpeg»).
- **Лицензия для нас**: при GPL-клиенте (клиенты Jellyfin под GPL-2.0/3.0) конфликта нет. LGPL требует возможности перелинковки, у открытого проекта это выполнено. Патентные вопросы по AC3/E-AC3/DTS/TrueHD декодерам в FFmpeg лежат на дистрибьюторе **(юридически не проверено)**. Для Google Play многие плееры (Just Player, Next Player, jellyfin-androidtv) так и поставляются.
---
## 5. Программное декодирование видео
| Источник | Кодеки | Статус / артефакт | HDR / 10-bit |
|---|---|---|---|
| `decoder_vp9` (`LibvpxVideoRenderer`) | VP9 (+VP8?) | собирать самим, в Maven нет | 10-bit выводится через GL YUV-рендер **(не проверено)** |
| `decoder_av1` (`Libdav1dVideoRenderer`) | AV1 | с **1.9.0** на dav1d вместо libgav1; собирать самим (NDK r27, meson) | **(не проверено)** |
| `ExperimentalFfmpegVideoRenderer` (официальный) | — | заглушка | — |
| PR [#1591](https://github.com/androidx/media/pull/1591) «Implement an FfmpegVideoRenderer» (rabbitknight) | VP8/9, MPEG-1/2, VC-1, ProRes, AV1, H.264 Hi10 | open с 2024-08, без ревью мейнтейнеров; адаптирован к 1.11.1 (2026-09); есть баги (green screen при zero stride, `pixelWidthHeightRatio=1`) | 10-bit приводится к 8-bit |
| **NextLib** [anilbeesetti/nextlib](https://github.com/anilbeesetti/nextlib), `io.github.anilbeesetti:nextlib-media3ext:1.11.1-0.16.0` (2026-09-13) | видео: h264, hevc, mpeg2video, mpegvideo, vp8, vp9, libdav1d; аудио: как у Jellyfin + opus, vorbis, amr | Maven Central, синхронизирован с media3 1.11.1, используется Next Player; FFmpeg 9.0.1, `--enable-version3` + mbedTLS (LGPLv3), репозиторий GPL-3.0 | вывод `WINDOW_FORMAT_RGBA_8888` / YUV420P через `sws_scale`, то есть **HDR и 10-bit теряются** |
Что остаётся **невозможным без libVLC/libmpv** (или без серверной транскодировки):
- **VC-1 / WMV3 / WMV9**: нет ASF-экстрактора. VC-1 в MKV распознаётся (FourCC `WVC1` → `video/wvc1`) и играет только при HW-декодере. Он есть на Shield и части Amlogic, на Chromecast/Google TV, по-видимому, нет **(не проверено)**. Программного VC-1 нет ни в одной готовой сборке (можно пересобрать NextLib с `vc1`/`wmv3`).
- **MPEG-2** без HW: NextLib (программный mpeg2video). HW MPEG-2 есть почти везде, но на свежих SoC иногда выкинут **(не проверено)**.
- **H.264 Hi10P (High 10)**: HW-поддержки практически нет. jellyfin-androidtv проверяет `AVCProfileHigh10`. Программно через NextLib, но с 8-bit выводом и большой нагрузкой на CPU (1080p ещё реально, 4K нет).
- **MPEG-4 ASP / DivX / Xvid**: `AviExtractor` и MKV (`V_MPEG4/ISO/ASP`, FourCC DIVX/XVID/DX50/FMP4) отдают `video/mp4v-es`. Работает только при HW-декодере; программный `c2.android.mpeg4.decoder` надёжен лишь для Simple Profile (B-frames, QPEL, GMC ASP не тянет) **(не проверено)**. MS MPEG-4 v2/v3 (`MP42`/`MP43`) распознаются, но декодеров нет.
- **HEVC 4:2:2 / 4:4:4, 12-bit, ProRes, RealVideo, Theora, WMV, H.263+ экзотика, MPEG-1 (без HW)**: только FFmpeg-движок.
- **Любой HDR при программном декоде через media3-экстеншены**: только SDR-вывод.
---
## 6. Субтитры
### 6.1 Встроенная поддержка
- **Парсеры** (`DefaultSubtitleParserFactory`): SSA/ASS (упрощённо: стили, позиционирование, без анимаций и шрифтов), WebVTT, MP4VTT, SubRip, TX3G, **PGS**, **VobSub**, **DVB**, TTML.
- **MKV codec IDs**: `S_TEXT/UTF8`, `S_TEXT/ASS`, `S_TEXT/SSA`, `S_TEXT/WEBVTT`, `S_VOBSUB`, `S_HDMV/PGS`, `S_DVBSUB`.
- **TS**: DVB-subs (`0x59`); PGS в TS (`0x90`) не поддерживается.
- **MP4**: VobSub (1.9), tx3g, wvtt.
- Последние исправления: VobSub SPU START/STOP (1.10), PGS RLE color index 0, пропуски субтитров VobSub.
- Проблема [#2667](https://github.com/androidx/media/issues/2667): **все** image-based дорожки (PGS/VobSub) парсятся в Bitmap, даже невыбранные. При десятках PGS-дорожек это бьёт по памяти и CPU. Обход: пересоздавать или отключать треки, или просить сервер отдавать без лишних дорожек **(не проверено)**.
- **Внешние (side-loaded)**: `MediaItem.SubtitleConfiguration(uri).setMimeType(...).setLanguage(...).setSelectionFlags(...)` → `SubtitleExtractor` (один файл = один парсер). Надёжно: SRT, ASS/SSA, VTT, TTML. Внешние `.sup` (PGS) и `.idx/.sub` (VobSub) так подключить нельзя или ненадёжно: `.sup` содержит PTS в заголовках сегментов, которые `PgsParser` не читает, а VobSub требует idx как initialization data и MPEG-PS для .sub **(не проверено)**. jellyfin-androidtv для DVBSUB/DVDSUB/IDX/PGS объявляет только `EMBED` + `ENCODE` (burn-in), внешних нет.
### 6.2 ASS/SSA через libass: `io.github.peerless2012:ass-media`
- Репозиторий [peerless2012/libass-android](https://github.com/peerless2012/libass-android), MIT, последний коммит 2026-10-09. Версии: `0.5.1` (2026-08), до этого 0.5.0 и 0.4.0. Это 0.x, API менялся (`EFFECTS_*` → deprecated).
- Модули: `lib_ass` (prefab, libass + freetype/harfbuzz/fribidi/fontconfig), `lib_ass_kt` (JNI-обёртка), `lib_ass_media` (интеграция с media3). Внутренне собран против media3 1.8.0.
- Классы: `AssHandler(AssRenderType)`, `AssHandlerConfig(glyphSize, cacheSize, maxRenderPixels)` (ограничение разрешения рендера, полезно для 4K), `AssRenderersFactory`, `AssSubtitleParserFactory`, `AssMatroskaExtractor` (наследник `MatroskaExtractor`, **достаёт вложенные шрифты из MKV Attachments** → `AssHandler.addFont`), `AssSubtitleView`.
- `AssRenderType`: `CUES` (через `SubtitleView`, bitmap-cues), `OVERLAY_CANVAS`, `OVERLAY_OPEN_GL`, deprecated `EFFECTS_CANVAS` / `EFFECTS_OPEN_GL`.
- Пользователи: Jellyfin Android TV, Fladder, NuvioTV, AfuseKt.
- Интеграция в jellyfin-androidtv (`playback/media3/exoplayer/src/main/kotlin/ExoPlayerBackend.kt`):
```kotlin
val assHandler = AssHandler(AssRenderType.OVERLAY_OPEN_GL)
val parserFactory = AssSubtitleParserFactory(assHandler)
val extractors = DefaultExtractorsFactory().withAssMkvSupport(parserFactory, assHandler)
DefaultMediaSourceFactory(dataSourceFactory, extractors).setSubtitleParserFactory(parserFactory)
val renderers = AssRenderersFactory(assHandler, DefaultRenderersFactory(context).apply {
setEnableDecoderFallback(true); setExtensionRendererMode(ON / PREFER) })
// после build(): assHandler.init(player)
// в SubtitleView: addView(AssSubtitleView(context, assHandler))
```
Включается пользовательской настройкой `assDirectPlay` (ключ `libass_enabled`, по умолчанию `false`). Без неё ASS/SSA в DeviceProfile получает `ENCODE` (burn-in на сервере).
- **Риски**: `AssMatroskaExtractor` наследует внутренности `MatroskaExtractor` (protected-методы), поэтому может ломаться при обновлениях media3. Производительность тяжёлых typesetting-субтитров на слабых ТВ-SoC не измерена.
---
## 7. Контейнеры и экстракторы
| Формат | Статус в media3 1.11.1 |
|---|---|
| **MKV/WebM** | Основной. Поддерживаются lacing (Xiph/EBML/fixed), **header stripping** (`ContentCompAlgo = 3`). **zlib/bzlib/lzo** (`ContentCompAlgo` 0/1/2) приводят к `ParserException "ContentCompAlgo N not supported"` ([#2361](https://github.com/androidx/media/issues/2361), open). Шифрование только AES (WebM). Главы парсятся в `Chapter` metadata (1.11.0), но **ordered chapters, linked segments (SegmentUID) и editions не реализованы** (воспроизводится сегмент как есть). Треки после кластеров исправлены в 1.11.1 ([#3377](https://github.com/androidx/media/issues/3377)), треки в последнем кластере в [#3250](https://github.com/androidx/media/issues/3250). ALAC, float64 PCM. `V_MS/VFW/FOURCC` распознаёт только DIVX, H263 и WVC1. Theora и RealVideo не поддерживаются. |
| **MP4/MOV/fMP4** | Полный (DV, HDR10+, главы с 1.11, `mfra`-seek). TrueHD в fMP4 нет (#1519). |
| **AVI** | `AviExtractor`: H.264, MPEG-4 ASP (DivX/Xvid), MJPEG, MP42/MP43; аудио PCM, MP3, AAC, AC3, DTS. Исправлена потеря аудио без keyframe-флагов (1.11.0). Индексы и OpenDML у больших AVI могут работать с ограничениями **(не проверено)**. |
| **MPEG-TS** (188 байт) | H.262/H.264/H.265, AAC/AC3/E-AC3/AC4/DTS/DTS-HD/DTS-UHD, DVB-subs. **Нет TrueHD (0x83), LPCM BD (0x80), PGS (0x90), VC-1 (0xEA)**. |
| **M2TS/MTS** (192 байта, BDAV) | **Не поддерживается**: sniff требует sync-байт через каждые 188 байт. [#608](https://github.com/androidx/media/issues/608) закрыт как дубль [ExoPlayer #1488](https://github.com/google/ExoPlayer/issues/1488): «isn't supported», PR приветствуется. |
| **MPEG-PS/VOB** | `PsExtractor` (MPEG-2 + AC3/MPA/LPCM?) работает для простых случаев; навигации DVD нет. |
| **ISO, VIDEO_TS, BDMV** | Не поддерживаются: нет UDF/ISO, IFO/MPLS, меню. Только через сервер: Jellyfin сам разбирает BDMV/VIDEO_TS и отдаёт поток (remux/transcode) **(поведение 12.2 не проверено)**. |
| **ASF/WMV** | Экстрактора **нет**. Мейнтейнер в [#3249](https://github.com/androidx/media/issues/3249) (2026-06): «WMV ... isn't supported as per CDD». Сторонние ASF-экстракторы есть в форках (mytv-android, FongMi), качество не оценено. |
| FLV, OGG, WAV, FLAC, MP3, AMR, AC3/AC4/ADTS, HEIF/AVIF/JPEG/PNG | есть. Ogg с несколькими треками не поддерживается ([#2876](https://github.com/androidx/media/issues/2876)). |
**Вывод**: в DeviceProfile для media3 **не** объявлять как direct play: `asf`, `wmv`, `m2ts`, `iso`, `vob` (кроме простого MPEG-PS), MKV с zlib-сжатием (сервер это не видит, нужен фолбэк по ошибке), а также кодеки без HW-декодера. При этом jellyfin-androidtv объявляет `ASF`, `WMV`, `VOB`, `XVID`, `OGM` в `directPlayProfile`, что выглядит как источник ошибок. Для нашего гибрида такие контейнеры правильнее маршрутизировать в libVLC/libmpv.
---
## 8. DeviceProfile в jellyfin-androidtv: что переиспользовать
Файлы: `app/src/main/java/org/jellyfin/androidtv/util/profile/`: `deviceProfile.kt`, `MediaCodecCapabilitiesTest.kt`, `KnownDefects.kt`, `deviceProfileReport.kt`, `codec/{MediaCodecQuery, AvcCodecCapabilities, HevcCodecCapabilities, Av1CodecCapabilities, audioPassthrough}.kt`. Сборка через `org.jellyfin.sdk.model.deviceprofile.buildDeviceProfile { }` из SDK (DSL: `transcodingProfile`, `directPlayProfile`, `codecProfile { conditions; applyConditions }`, `subtitleProfile`).
### 8.1 Опрос MediaCodec (`MediaCodecQuery`)
- `MediaCodecList(REGULAR_CODECS)`, только декодеры. Опция `softwareCodecsEnabled`: на API 29+ отбрасываются `isSoftwareOnly`.
- `hasCodecForMime`, `hasDecoder(mime, profile, level)` (по `capabilities.profileLevels`, с особым случаем H.263 Level45), `getDecoderLevel` (максимальный level для профиля), `getMaxResolution` (`videoCapabilities.supportedWidths/Heights.upper`), `supportsMultiInstance` (`maxSupportedInstances > 1`).
- **AVC**: `AVCProfileHigh10 @ Level4` даёт поддержку «high 10». Уровни переводятся в ffprobe-шкалу (×10, 1b = 9) через `LEVEL_MAP`.
- **HEVC**:
- `Main10 @ MainTierLevel4`;
- HDR10 = `HEVCProfileMain10HDR10`, HDR10+ = `HEVCProfileMain10HDR10Plus` (API 29+);
- DV = есть хоть какой-то декодер `video/dolby-vision` (API 24+);
- **DV с EL** = `DolbyVisionProfileDvheDtb @ LevelHd24` **и** multi-instance HEVC (для двухдекодерной схемы).
- Уровни ×30 (4.1 → 123).
- **AV1**: Main10 @ Level5, `AV1ProfileMain10HDR10` / `HDR10Plus`, DV P10 = `DolbyVisionProfileDvav110`. Для Fire OS, где AV1 есть ниже API Q, значения констант захардкожены.
- **VC-1**: `hasCodecForMime(video/wvc1)`.
- **Аудио passthrough** (`audioPassthrough.kt`): `AudioCapabilities.getCapabilities(context, AudioAttributes(USAGE_MEDIA, MOVIE), null, [STEREO, 5.1])` → `isPassthroughPlaybackSupported(Format(mime, 2ch))`. Режимы `BitstreamAudioMode.AUTO`, `ENABLE`, `DISABLE` для AC3, EAC3, DTS и TrueHD по отдельности.
### 8.2 Логика профиля
- **Direct play**:
- контейнеры asf, hls, m4v, mkv, mov, mp4, ogm, ogv, ts, vob, webm, wmv, xvid;
- видео av1, h264, hevc, mpeg, mpeg2video, vc1, vp8, vp9;
- аудио: список с фильтром по bitstream-настройкам; при «downmix to stereo» только aac, mp2, mp3.
- **Transcoding**:
- HLS-TS (hevc если есть, иначе h264; аудио aac/ac3/eac3/mp3);
- HLS-fMP4 (+alac/flac/opus/dts);
- `copyTimestamps=false`, `enableSubtitlesInManifest=true`.
- **CodecProfiles**:
- H.264: допустимые профили; level ≤ max (отдельно для High10); ref frames ≤ 12 при ширине ≥ 1200 и ≤ 4 при ширине ≥ 1900. Это эвристика для слабых декодеров.
- HEVC: main и, если есть, main 10, плюс level.
- AV1: `notEquals "main 10"`, если нет Main10.
- VC-1: `profile == "none"`, если нет декодера.
- Max width/height для каждого кодека.
- `AUDIO_CHANNELS ≤ 8` (или 2).
- **HDR через `VideoRangeType`**: трюк с `conditions { VIDEO_RANGE_TYPE notEquals "A|B|C" }` плюс `applyConditions { VIDEO_RANGE_TYPE inCollection [...] }`. Профиль применяется только к медиа с неподдерживаемым range type, а сервер берёт список из notEquals для StreamBuilder: может срезать DV и оставить HDR10 BL вместо полной транскодировки **(механизм на сервере не проверен)**. Множества:
- всегда `DOVI_INVALID`;
- без DV-EL (и не Shield-исключение) → `DOVI_WITH_EL` (+`DOVI_WITH_ELHDR10_PLUS`);
- без DV → `DOVI`; `DOVI_WITH_HDR10` только если и HDR10 нет;
- без HDR10+ → `HDR10_PLUS` (+`HDR10`, если нет и его);
- на Fire TV 4K (`AFTKRT`, `AFTKA`, `AFTKM`, `AFTMM`) всегда исключать `DOVI_WITH_HDR10_PLUS` / `DOVI_WITH_ELHDR10_PLUS`.
- Пользовательские оверрайды по форматам (`HdrFormat`: HDR10, HDR10+, DV P5, P7, P8) с режимами ENABLE/DISABLE/AUTO.
- **Субтитры**:
- VTT/WebVTT: embed, external, hls;
- SRT/SubRip/TTML: embed, external;
- DVBSUB/DVDSUB/IDX: embed + encode;
- PGS: embed (если `pgsDirectPlay`) + encode;
- ASS/SSA: embed/external при libass, иначе encode.
- **KnownDefects**: всего 2 списка (Fire TV DV+HDR10+, Shield P7). Отдельной базы «плохих» устройств нет.
- **Чего там нет, но нам нужно**:
1. Проверки, что **дисплей** поддерживает DV/HDR10/HDR10+ (`HdrCapabilities`). Сейчас смотрят только декодер. media3 при этом сам уйдёт в фолбэк, а профиль может ошибочно разрешить P5 на не-DV ТВ.
2. Учёта P5 (без фолбэка).
3. Маршрутизации на второй движок (VLC/mpv) для VC-1/WMV/M2TS/Hi10P и т.п.
4. HDR10+ на VP9.
5. `HLG` и `DOVIWithHLG` не рассматриваются явно.
### 8.3 Прочее из ExoPlayerBackend jellyfin-androidtv
- `DefaultRenderersFactory.setEnableDecoderFallback(true)`.
- `DefaultExtractorsFactory.setConstantBitrateSeekingEnabled(true)` + `AlwaysEnabled`. `TsExtractor` timestamp search bytes `TS_PACKET_SIZE * 1800` на low-RAM устройствах.
- `AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED`, `setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true)`.
- `PlaySupportReport` из `RendererCapabilities` (формат, adaptive, tunneling, HW-accel, decoder support) для `supportsStream()`. Это готовый паттерн для выбора движка: если media3 не может, идём во второй движок.
---
## 9. Риски и рекомендации для проекта
1. **DV P7 (самые популярные remux)**: на всём, кроме Shield, P7 не даст DV. Без DV-декодера он не даст и картинки, пока не смержен PR #3160. Варианты: (а) свой `MediaCodecSelector`, отдающий HEVC-декодеры для `video/dolby-vision` с профилем DvheDtb, когда нет DV-дисплея или декодера; (б) DeviceProfile с `DOVI_WITH_EL` в unsupported, чтобы сервер срезал DV.
2. **Гибриды DV + HDR10+ на MediaTek** (Fire TV, Google TV Streamer): держать KnownDefects-список и опцию «предпочитать DV / HDR10+» (так сделано в Wholphin, судя по ссылке в #1895).
3. **FFmpeg-видео в media3** официально не готово. NextLib жизнеспособен, но даёт SDR 8-bit и CPU-нагрузку. Для HDR или экзотики при программном декоде нужен второй движок; HDR-вывод libmpv/libVLC на Android отдельно не исследован здесь **(не проверено)**.
4. **Совместимость бинарных расширений**: `org.jellyfin.media3:media3-ffmpeg-decoder:1.9.0+1` и `ass-media` (собран под 1.8.0) работают с 1.11.1, но это не гарантировано. Лучше собирать FFmpeg-аудио самим в CI под точную версию media3 или взять NextLib, у которого версия привязана к media3 (`1.11.1-0.16.0`).
5. **Смена частоты и HDR**: делать самим через `preferredDisplayModeId` + `DisplayListener`, с паузой. Явно сбрасывать режим и освобождать Surface при выходе (см. #3406).
6. **TrueHD**: не просить HLS-fMP4 с TrueHD. Для 2.0 TrueHD программный декод FFmpeg сломан (#1843). Без passthrough лучше транскод в AC3/E-AC3 на сервере.
7. **Tunneling**: опционально и по умолчанию выкл. Тестировать seek, паузу, субтитры и HDR на целевых устройствах.
8. **Контейнеры**: M2TS, ASF/WMV, ISO, VIDEO_TS и MKV-zlib сразу отправлять во второй движок или на серверный remux.
---
## Источники (проверено)
- androidx/media main @ `8c6678b` (1.11.1): `MatroskaExtractor.java`, `MediaCodecUtil.java`, `MediaCodecVideoRenderer.java`, `VideoFrameReleaseHelper.java`, `AudioCapabilities.java`, `DefaultRenderersFactory.java`, `decoder_ffmpeg/*`, `decoder_av1/*`, `TsExtractor.java`, `avi/StreamFormatChunk.java`, `DefaultSubtitleParserFactory.java`, `RELEASENOTES.md`. https://github.com/androidx/media
- jellyfin-androidtv master @ `0032f4a`: https://github.com/jellyfin/jellyfin-androidtv/tree/master/app/src/main/java/org/jellyfin/androidtv/util/profile, `playback/media3/exoplayer/src/main/kotlin/ExoPlayerBackend.kt`, `ui/playback/PlaybackController.java`
- https://github.com/jellyfin/jellyfin-androidx-media (`build.sh`, `.gitmodules`); Maven: https://repo1.maven.org/maven2/org/jellyfin/media3/media3-ffmpeg-decoder/
- https://github.com/peerless2012/libass-android; Maven: https://repo1.maven.org/maven2/io/github/peerless2012/ass-media/
- https://github.com/anilbeesetti/nextlib; Maven: https://repo1.maven.org/maven2/io/github/anilbeesetti/nextlib-media3ext/
- https://github.com/moneytoo/Player (Just Player, `Utils.java`, `PlayerActivity.java`)
- Kodi: https://github.com/xbmc/xbmc/blob/master/xbmc/platform/android/activity/XBMCApp.cpp, `xbmc/windowing/android/AndroidUtils.cpp`
- https://developer.android.com/media/optimize/performance/frame-rate
- https://source.android.com/devices/tv/multimedia-tunneling
- Issues/PR: androidx/media #1865, #3160, #1895, #3085, #3406, #3461, #3463, #2541, #1574, #3099, #1245, #2258, #3120, #2941, #3234, #2487, #3229, #3147, #1519, #3411, #1843, #3329, #1591, #3249, #608, #2361, #2667, #2876, #2830, #3153, #3135; jellyfin-androidtv #5794, #5822, #5184.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,373 @@
DISPLAY MANAGER (dumpsys display)
mSafeMode=false
mPendingTraversal=false
mViewports=[DisplayViewport{type=INTERNAL, valid=true, isActive=true, displayId=0, uniqueId='local:4627014146675823360', physicalPort=0, orientation=0, logicalFrame=Rect(0, 0 - 1920, 1080), physicalFrame=Rect(0, 0 - 3840, 2160), deviceWidth=3840, deviceHeight=2160}]
mDefaultDisplayDefaultColorMode=0
mWifiDisplayScanRequestCount=0
mStableDisplaySize=Point(3840, 2160)
mMinimumBrightnessCurve=[(0.0, 0.0), (2000.0, 50.0), (4000.0, 90.0)]
mHdrConversionMode=HdrConversionMode{ConversionMode=HDR_CONVERSION_SYSTEM, PreferredHdrOutputType=HDR_TYPE_INVALID}
Display States: size=1
Display Id=0
Display State=ON
Display Brightness=1.0
Display SdrBrightness=1.0
Display Adapters: size=3
LocalDisplayAdapter
VirtualDisplayAdapter
OverlayDisplayAdapter
mCurrentOverlaySetting=
mOverlays: size=0
Display Devices: size=1
DisplayDeviceInfo{"Встроенный экран": uniqueId="local:4627014146675823360", 3840 x 2160, modeId 1, renderFrameRate 60.000004, defaultModeId 1, supportedModes [{id=1, width=3840, height=2160, fps=60.000004, alternativeRefreshRates=[], supportedHdrTypes=[1, 2, 3, 4]}], colorMode 0, supportedColorModes [0], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[1, 2, 3, 4], mMaxLuminance=100.0, mMaxAverageLuminance=50.0, mMinLuminance=0.05}, allmSupported false, gameContentTypeSupported false, density 640, 59.0 x 59.0 dpi, appVsyncOff 1000000, presDeadline 16666666, touch INTERNAL, rotation 0, type INTERNAL, address {port=0, model=0x403674d9f751d3}, deviceProductInfo DeviceProductInfo{name=MStar Demo, manufacturerPnpId=MST, productId=48, modelYear=null, manufactureDate=ManufactureDate{week=10, year=2014}, connectionToSinkType=1}, state ON, committedState ON, frameRateOverride , brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 1.0, hdrSdrRatio NaN, FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY, FLAG_ROTATES_WITH_CONTENT, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, installOrientation 0, displayShape DisplayShape{ spec=-545313861 displayWidth=3840 displayHeight=2160 physicalPixelDisplaySizeRatio=1.0 rotation=0 offsetX=0 offsetY=0 scale=1.0}}
mAdapter=LocalDisplayAdapter
mUniqueId=local:4627014146675823360
mDisplayToken=android.os.BinderProxy@b7048f
mCurrentLayerStack=0
mCurrentFlags=1
mCurrentOrientation=0
mCurrentLayerStackRect=Rect(0, 0 - 1920, 1080)
mCurrentDisplayRect=Rect(0, 0 - 3840, 2160)
mCurrentSurface=null
mPhysicalDisplayId=4627014146675823360
mDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primary=physical: (0.0 Infinity) render: (0.0 60.0) appRequest=physical: (0.0 Infinity) render: (0.0 Infinity)}
mDisplayModeSpecsInvalid=false
mActiveModeId=1
mActiveColorMode=0
mDefaultModeId=1
mUserPreferredModeId=-1
mState=ON
mCommittedState=ON
mBrightnessState=1.0
mBacklightAdapter=BacklightAdapter [useSurfaceControl=true (force_anyway? true), backlight=null]
mAllmSupported=false
mAllmRequested=false
mGameContentTypeSupported=false
mGameContentTypeRequested=false
mStaticDisplayInfo=StaticDisplayInfo{isInternal=true, density=2.0, secure=true, deviceProductInfo=DeviceProductInfo{name=MStar Demo, manufacturerPnpId=MST, productId=48, modelYear=null, manufactureDate=ManufactureDate{week=10, year=2014}, connectionToSinkType=1}, installOrientation=0}
mSfDisplayModes=
DisplayMode{id=0, width=3840, height=2160, xDpi=59.0, yDpi=59.0, refreshRate=60.000004, appVsyncOffsetNanos=1000000, presentationDeadlineNanos=16666666, supportedHdrTypes=[1, 2, 3, 4], group=0}
mActiveSfDisplayMode=DisplayMode{id=0, width=3840, height=2160, xDpi=59.0, yDpi=59.0, refreshRate=60.000004, appVsyncOffsetNanos=1000000, presentationDeadlineNanos=16666666, supportedHdrTypes=[1, 2, 3, 4], group=0}
mActiveRenderFrameRate=60.000004
mSupportedModes=
DisplayModeRecord{mMode={id=1, width=3840, height=2160, fps=60.000004, alternativeRefreshRates=[], supportedHdrTypes=[1, 2, 3, 4]}}
mSupportedColorModes=[0]
mDisplayDeviceConfig=DisplayDeviceConfig{mLoadedFrom=/vendor/etc/displayconfig/display_id_4627014146675823360.xml, mBacklight=[0.035433073, 0.5, 1.0], mNits=[7.0866146, 100.0, 200.0], mRawBacklight=[0.0, 0.5, 1.0], mRawNits=[0.0, 100.0, 200.0], mInterpolationType=0, mBrightness=[0.0, 0.48163262, 1.0], mBrightnessToBacklightSpline=MonotoneCubicSpline{[(0.0, 0.035433073: 0.96456695), (0.48163262, 0.5: 0.9645669), (1.0, 1.0: 0.9645668)]}, mBacklightToBrightnessSpline=MonotoneCubicSpline{[(0.035433073, 0.0: 1.0367347), (0.5, 0.48163262: 1.0367348), (1.0, 1.0: 1.0367348)]}, mNitsToBacklightSpline=MonotoneCubicSpline{[(7.0866146, 0.035433073: 0.005), (100.0, 0.5: 0.005), (200.0, 1.0: 0.005)]}, mBacklightMinimum=0.035433073, mBacklightMaximum=1.0, mBrightnessDefault=1.0, mQuirks=[canSetBrightnessViaHwc], isHbmEnabled=false, mHbmData=null, mSdrToHdrRatioSpline=null, mThermalBrightnessThrottlingDataMapByThrottlingId={}
, mBrightnessRampFastDecrease=0.70472443, mBrightnessRampFastIncrease=0.70472443, mBrightnessRampSlowDecrease=0.23228346, mBrightnessRampSlowIncrease=0.23228346, mBrightnessRampDecreaseMaxMillis=0, mBrightnessRampIncreaseMaxMillis=0
, mAmbientHorizonLong=10000, mAmbientHorizonShort=2000
, mScreenDarkeningMinThreshold=0.0, mScreenDarkeningMinThresholdIdle=0.0, mScreenBrighteningMinThreshold=0.0, mScreenBrighteningMinThresholdIdle=0.0, mAmbientLuxDarkeningMinThreshold=0.0, mAmbientLuxDarkeningMinThresholdIdle=0.0, mAmbientLuxBrighteningMinThreshold=0.0, mAmbientLuxBrighteningMinThresholdIdle=0.0
, mScreenBrighteningLevels=[0.0], mScreenBrighteningPercentages=[10.0], mScreenDarkeningLevels=[0.0], mScreenDarkeningPercentages=[20.0], mAmbientBrighteningLevels=[0.0], mAmbientBrighteningPercentages=[10.0], mAmbientDarkeningLevels=[0.0], mAmbientDarkeningPercentages=[20.0]
, mAmbientBrighteningLevelsIdle=[0.0], mAmbientBrighteningPercentagesIdle=[10.0], mAmbientDarkeningLevelsIdle=[0.0], mAmbientDarkeningPercentagesIdle=[20.0], mScreenBrighteningLevelsIdle=[0.0], mScreenBrighteningPercentagesIdle=[10.0], mScreenDarkeningLevelsIdle=[0.0], mScreenDarkeningPercentagesIdle=[20.0]
, mAmbientLightSensor=Sensor{type: , name: , refreshRateRange: [0.0, Infinity]} , mScreenOffBrightnessSensor=Sensor{type: null, name: null, refreshRateRange: [0.0, Infinity]} , mProximitySensor=Sensor{type: null, name: null, refreshRateRange: [0.0, Infinity]} , mRefreshRateLimitations= [], mDensityMapping= DensityMapping{mDensityMappingEntries=[DensityMappingEntry{squaredDiagonal=19411200, density=640}]}, mAutoBrightnessBrighteningLightDebounce= 4000, mAutoBrightnessDarkeningLightDebounce= 8000, mBrightnessLevelsLux= [0.0], mBrightnessLevelsNits= [], mDdcAutoBrightnessAvailable= true, mAutoBrightnessAvailable= false
, mDefaultLowBlockingZoneRefreshRate= 0, mDefaultHighBlockingZoneRefreshRate= 0, mDefaultPeakRefreshRate= 0, mDefaultRefreshRate= 60, mRefreshRateZoneProfiles= {}, mDefaultRefreshRateInHbmHdr= 0, mDefaultRefreshRateInHbmSunlight= 0, mRefreshRateThrottlingMap= {}
, mLowDisplayBrightnessThresholds= [], mLowAmbientBrightnessThresholds= [], mHighDisplayBrightnessThresholds= [], mHighAmbientBrightnessThresholds= []
, mScreenOffBrightnessSensorValueToLux=null
, mUsiVersion= null}
LogicalDisplayMapper:
mSingleDisplayDemoMode=false
mCurrentLayout=[{dispId: 0(ON), displayGroupName: , addr: {port=0, model=0x403674d9f751d3}, mThermalBrightnessThrottlingMapId: null, mRefreshRateZoneId: null, mLeadDisplayId: -1, mThermalRefreshRateThrottlingMapId: null}]
mDeviceStatesOnWhichToWakeUp={}
mDeviceStatesOnWhichToSleep={}
mInteractive=true
mBootCompleted=true
mDeviceState=0
mPendingDeviceState=-1
mDeviceStateToBeAppliedAfterBoot=-1
Logical Displays: size=1
Display 0:
mDisplayId=0
mIsEnabled=true
mIsInTransition=false
mLayerStack=0
mPosition=-1
mHasContent=true
mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primary=physical: (0.0 Infinity) render: (0.0 60.0) appRequest=physical: (0.0 Infinity) render: (0.0 Infinity)}
mRequestedColorMode=0
mDisplayOffset=(0, 0)
mDisplayScalingDisabled=false
mPrimaryDisplayDevice=Встроенный экран
mBaseDisplayInfo=DisplayInfo{"Встроенный экран", displayId 0, displayGroupId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 3840 x 2160, largest app 3840 x 2160, smallest app 3840 x 2160, appVsyncOff 1000000, presDeadline 16666666, mode 160.000004, defaultMode 1, modes [{id=1, width=3840, height=2160, fps=60.000004, alternativeRefreshRates=[], supportedHdrTypes=[1, 2, 3, 4]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[1, 2, 3, 4], mMaxLuminance=100.0, mMaxAverageLuminance=50.0, mMinLuminance=0.05}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state ON, committedState ON, type INTERNAL, uniqueId "local:4627014146675823360", app 3840 x 2160, density 640 (59.0 x 59.0) dpi, layerStack 0, colorMode 0, supportedColorModes [0], address {port=0, model=0x403674d9f751d3}, deviceProductInfo DeviceProductInfo{name=MStar Demo, manufacturerPnpId=MST, productId=48, modelYear=null, manufactureDate=ManufactureDate{week=10, year=2014}, connectionToSinkType=1}, removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 1.0, installOrientation ROTATION_0, layoutLimitedRefreshRate null, hdrSdrRatio not_available, thermalRefreshRateThrottling {}, thermalBrightnessThrottlingDataId default}
mOverrideDisplayInfo=DisplayInfo{"Встроенный экран", displayId 0, displayGroupId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 1920 x 1080, largest app 1920 x 1920, smallest app 1080 x 1080, appVsyncOff 1000000, presDeadline 16666666, mode 160.000004, defaultMode 1, modes [{id=1, width=3840, height=2160, fps=60.000004, alternativeRefreshRates=[], supportedHdrTypes=[1, 2, 3, 4]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[1, 2, 3, 4], mMaxLuminance=100.0, mMaxAverageLuminance=50.0, mMinLuminance=0.05}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state ON, committedState ON, type INTERNAL, uniqueId "local:4627014146675823360", app 1920 x 1080, density 320 (29.5 x 29.5) dpi, layerStack 0, colorMode 0, supportedColorModes [0], address {port=0, model=0x403674d9f751d3}, deviceProductInfo DeviceProductInfo{name=MStar Demo, manufacturerPnpId=MST, productId=48, modelYear=null, manufactureDate=ManufactureDate{week=10, year=2014}, connectionToSinkType=1}, removeMode 0, refreshRateOverride 60.000004, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 1.0, installOrientation ROTATION_0, layoutLimitedRefreshRate null, hdrSdrRatio not_available, thermalRefreshRateThrottling {}, thermalBrightnessThrottlingDataId default}
mRequestedMinimalPostProcessing=false
mFrameRateOverrides=[]
mPendingFrameRateOverrideUids={}
mDisplayGroupName=
mThermalBrightnessThrottlingDataId=default
mLeadDisplayId=-1
mLayoutLimitedRefreshRate=null
mThermalRefreshRateThrottling={}
DeviceStateToLayoutMap:
Registered Layouts:
state(-1): [{dispId: 0(ON), displayGroupName: , addr: {port=0, model=0x403674d9f751d3}, mThermalBrightnessThrottlingMapId: null, mRefreshRateZoneId: null, mLeadDisplayId: -1, mThermalRefreshRateThrottlingMapId: null}]
Callbacks: size=12
0: mPid=750, mWifiDisplayScanRequested=false
1: mPid=1115, mWifiDisplayScanRequested=false
2: mPid=1365, mWifiDisplayScanRequested=false
3: mPid=1500, mWifiDisplayScanRequested=false
4: mPid=1961, mWifiDisplayScanRequested=false
5: mPid=2223, mWifiDisplayScanRequested=false
6: mPid=3734, mWifiDisplayScanRequested=false
7: mPid=9064, mWifiDisplayScanRequested=false
8: mPid=9914, mWifiDisplayScanRequested=false
9: mPid=17555, mWifiDisplayScanRequested=false
10: mPid=18239, mWifiDisplayScanRequested=false
11: mPid=28050, mWifiDisplayScanRequested=false
Display Power Controllers: size=1
Display Power Controller:
mDisplayId=0
mLeadDisplayId=-1
mLightSensor=null
mDisplayBrightnessFollowers={}
Display Power Controller Locked State:
mDisplayReadyLocked=true
mPendingRequestLocked=policy=BRIGHT, useProximitySensor=false, screenBrightnessOverride=NaN, screenAutoBrightnessAdjustmentOverride=NaN, screenLowPowerBrightnessFactor=1.0, blockScreenOn=false, lowPowerMode=false, boostScreenBrightness=false, dozeScreenBrightness=NaN, dozeScreenState=UNKNOWN
mPendingRequestChangedLocked=false
mPendingUpdatePowerStateLocked=false
Display Power Controller Configuration:
mScreenBrightnessDozeConfig=0.0
mScreenBrightnessDimConfig=0.05
mUseSoftwareAutoBrightnessConfig=false
mSkipScreenOnBrightnessRamp=false
mColorFadeFadesConfig=false
mColorFadeEnabled=false
mIsDisplayInternal=true
mCachedBrightnessInfo.brightness=1.0
mCachedBrightnessInfo.adjustedBrightness=1.0
mCachedBrightnessInfo.brightnessMin=0.0
mCachedBrightnessInfo.brightnessMax=1.0
mCachedBrightnessInfo.hbmMode=0
mCachedBrightnessInfo.hbmTransitionPoint=Infinity
mCachedBrightnessInfo.brightnessMaxReason =0
mDisplayBlanksAfterDozeConfig=false
mBrightnessBucketsInDozeConfig=false
Display Power Controller Thread State:
mPowerRequest=policy=BRIGHT, useProximitySensor=false, screenBrightnessOverride=NaN, screenAutoBrightnessAdjustmentOverride=NaN, screenLowPowerBrightnessFactor=1.0, blockScreenOn=false, lowPowerMode=false, boostScreenBrightness=false, dozeScreenBrightness=NaN, dozeScreenState=UNKNOWN
mBrightnessReason=manual
mAppliedDimming=false
mAppliedLowPower=false
mAppliedThrottling=false
mDozing=false
mSkipRampState=RAMP_STATE_SKIP_NONE
mScreenOnBlockStartRealTime=580658077
mScreenOffBlockStartRealTime=521250842
mPendingScreenOnUnblocker=null
mPendingScreenOffUnblocker=null
mPendingScreenOff=false
mReportedToPolicy=REPORTED_TO_POLICY_SCREEN_ON
mIsRbcActive=false
AutomaticBrightnessStrategy:
mDisplayId=0
mAutoBrightnessAdjustment=0.0
mPendingAutoBrightnessAdjustment=NaN
mTemporaryAutoBrightnessAdjustment=NaN
mShouldResetShortTermModel=false
mAppliedAutoBrightness=false
mAutoBrightnessAdjustmentChanged=false
mAppliedTemporaryAutoBrightnessAdjustment=false
mUseAutoBrightness=false
mWasShortTermModelActive=false
mAutoBrightnessAdjustmentReasonsFlags=0
mScreenBrightnessRampAnimator.isAnimating()=false
Display Power State:
mStopped=false
mScreenState=ON
mScreenBrightness=1.0
mSdrScreenBrightness=1.0
mScreenReady=true
mScreenUpdatePending=false
mColorFadePrepared=false
mColorFadeLevel=1.0
mColorFadeReady=true
mColorFadeDrawPending=false
Photonic Modulator State:
mPendingState=ON
mPendingBacklight=1.0
mPendingSdrBacklight=1.0
mActualState=ON
mActualBacklight=1.0
mActualSdrBacklight=1.0
mStateChangeInProgress=false
mBacklightChangeInProgress=false
No Reduce Bright Colors Adjustments
HighBrightnessModeController:
mBrightness=0.0
mUnthrottledBrightness=0.0
mThrottlingReason=none
mCurrentMin=0.0
mCurrentMax=1.0
mHbmMode=off
mHbmStatsState=HBM_OFF
mHbmData=null
mAmbientLux=0.0 (old/invalid)
mIsInAllowedAmbientRange=false
mIsAutoBrightnessEnabled=false
mIsAutoBrightnessOffByState=false
mIsHdrLayerPresent=false
mBrightnessMin=0.0
mBrightnessMax=1.0
remainingTime=0
mIsTimeAvailable= false
mRunningStartTimeMillis=-1 (51650707 ms ago)
mIsBlockedByLowPowerMode=false
width*height=3840*2160
mEvents=
BrightnessThrottler:
mThermalBrightnessThrottlingDataId=default
mThermalThrottlingData=null
mUniqueDisplayId=local:4627014146675823360
mThrottlingStatus=-1
mBrightnessCap=1.0
mBrightnessMaxReason=none
mDdcThermalThrottlingDataMap={}
mThermalBrightnessThrottlingDataOverride={}
mThermalBrightnessThrottlingDataString=null
SkinThermalStatusObserver:
mStarted: false
ThermalService not available
WakelockController State:
mDisplayId=0
mUnfinishedBusiness=false
mOnStateChangePending=false
mOnProximityPositiveMessages=false
mOnProximityNegativeMessages=false
DisplayBrightnessController:
mDisplayId=: 0
mScreenBrightnessDefault=1.0
mPersistBrightnessNitsForDefaultDisplay=false
mPendingScreenBrightness=NaN
mCurrentScreenBrightness=1.0
mLastUserSetScreenBrightness=NaN
Last selected DisplayBrightnessStrategy= InvalidBrightnessStrategy
DisplayBrightnessStrategySelector:
mDisplayId= 0
mOldBrightnessStrategyName= InvalidBrightnessStrategy
mAllowAutoBrightnessWhileDozingConfig= false
TemporaryBrightnessStrategy:
mTemporaryScreenBrightness:NaN
DisplayStateController:
mPerformScreenOffTransition:false
DisplayPowerProximityStateController:
mPendingWaitForNegativeProximityLocked=false
mDisplayId=0
mWaitingForNegativeProximity=false
mIgnoreProximityUntilChanged=false
mProximitySensor=null
mProximitySensorEnabled=false
mProximityThreshold=0.0
mProximity=Unknown
mPendingProximity=Unknown
mPendingProximityDebounceTime=-1 (51650707 ms ago)
mScreenOffBecauseOfProximity=false
mSkipRampBecauseOfProximityChangeToNegative=false
PersistentDataStore
mLoaded=true
mDirty=false
RememberedWifiDisplays:
DisplayStates:
0: local:4627014146675823360
ColorMode=0
BrightnessValue=1.0
DisplayBrightnessConfigurations:
Resolution=0 0
RefreshRate=0.0
StableDeviceValues:
StableDisplayWidth=3840
StableDisplayHeight=2160
GlobalBrightnessConfigurations:
mBrightnessNitsForDefaultDisplay=-1.0
Display Window Policy Controllers: size=0
BrightnessTracker state:
mStarted=false
mLightSensor=null
mLastBatteryLevel=NaN
mLastBrightness=-1.0
mEventsDirty=false
mEvents.size=0
mWriteBrightnessTrackerStateScheduled=false
mSensorRegistered=false
mColorSamplingEnabled=false
mNoFramesToSample=0
mFrameRate=0.0
DisplayModeDirector
mSupportedModesByDisplay:
0 -> [{id=1, width=3840, height=2160, fps=60.000004, alternativeRefreshRates=[], supportedHdrTypes=[1, 2, 3, 4]}]
mDefaultModeByDisplay:
0 -> {id=1, width=3840, height=2160, fps=60.000004, alternativeRefreshRates=[], supportedHdrTypes=[1, 2, 3, 4]}
mModeSwitchingType: SWITCHING_TYPE_WITHIN_GROUPS
mAlwaysRespectAppRequest: false
SettingsObserver
mDefaultRefreshRate: 60.0
mDefaultPeakRefreshRate: 0.0
AppRequestObserver
mAppRequestedModeByDisplay:
mAppPreferredRefreshRateRangeByDisplay:
BrightnessObserver
mAmbientLux: -1.0
mBrightness: 255
mDefaultDisplayState: 2
mLowPowerModeEnabled: false
mRefreshRateChangeable: false
mShouldObserveDisplayLowChange: false
mShouldObserveAmbientLowChange: false
mRefreshRateInLowZone: 0
mShouldObserveDisplayHighChange: false
mShouldObserveAmbientHighChange: false
mRefreshRateInHighZone: 0
mRegisteredLightSensor: null
mLightSensor: null
mLightSensorName:
mLightSensorType:
mLastSensorData: 0.0
mTimestamp: 1970-01-01 03:00:00.000
UdfpsObserver
mUdfpsRefreshRateEnabled:
mAuthenticationPossible:
HbmObserver
mHbmMode: {}
mHbmActive: {}
mRefreshRateInHbmSunlight: 0
mRefreshRateInHbmHdr: 0
SkinThermalStatusObserver:
mStatus: 0
mThermalThrottlingByDisplay: {0={}}
mVotesByDisplay:
-1:
PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE -> Vote: {width: -1, height: -1, refreshRateRanges: physical: (0.0 Infinity) render: (0.0 Infinity), disableRefreshRateSwitching: false, appRequestBaseModeRefreshRate: 0.0}
PRIORITY_DEFAULT_REFRESH_RATE -> Vote: {width: -1, height: -1, refreshRateRanges: physical: (0.0 Infinity) render: (0.0 60.0), disableRefreshRateSwitching: false, appRequestBaseModeRefreshRate: 0.0}
SensorObserver
mIsProxActive=false
mDozeStateByDisplay:
0 -> false
BrightnessSynchronizer
mLatestIntBrightness=255
mLatestFloatBrightness=1.0
mCurrentUpdate=null
mPendingUpdate=null

View File

@@ -0,0 +1,755 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright (C) 2012 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
<!DOCTYPE MediaCodecs [
<!ELEMENT MediaCodecs (Decoders,Encoders)>
<!ELEMENT Decoders (MediaCodec*)>
<!ELEMENT Encoders (MediaCodec*)>
<!ELEMENT MediaCodec (Type*,Quirk*)>
<!ATTLIST MediaCodec name CDATA #REQUIRED>
<!ATTLIST MediaCodec type CDATA>
<!ELEMENT Type EMPTY>
<!ATTLIST Type name CDATA #REQUIRED>
<!ELEMENT Quirk EMPTY>
<!ATTLIST Quirk name CDATA #REQUIRED>
]>
There's a simple and a complex syntax to declare the availability of a
media codec:
A codec that properly follows the OpenMax spec and therefore doesn't have any
quirks and that only supports a single content type can be declared like so:
<MediaCodec name="OMX.foo.bar" type="something/interesting" />
If a codec has quirks OR supports multiple content types, the following syntax
can be used:
<MediaCodec name="OMX.foo.bar" >
<Type name="something/interesting" />
<Type name="something/else" />
...
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
<Quirk name="output-buffers-are-unreadable" />
</MediaCodec>
Only the three quirks included above are recognized at this point:
"requires-allocate-on-input-ports"
must be advertised if the component does not properly support specification
of input buffers using the OMX_UseBuffer(...) API but instead requires
OMX_AllocateBuffer to be used.
"requires-allocate-on-output-ports"
must be advertised if the component does not properly support specification
of output buffers using the OMX_UseBuffer(...) API but instead requires
OMX_AllocateBuffer to be used.
"output-buffers-are-unreadable"
must be advertised if the emitted output buffers of a decoder component
are not readable, i.e. use a custom format even though abusing one of
the official OMX colorspace constants.
Clients of such decoders will not be able to access the decoded data,
naturally making the component much less useful. The only use for
a component with this quirk is to render the output to the screen.
Audio decoders MUST NOT advertise this quirk.
Video decoders that advertise this quirk must be accompanied by a
corresponding color space converter for thumbnail extraction,
matching surfaceflinger support that can render the custom format to
a texture and possibly other code, so just DON'T USE THIS QUIRK.
-->
<MediaCodecs>
<Settings>
<Setting name="max-video-encoder-input-buffers" value="1" />
<Setting name="supports-multiple-secure-codecs" value="true" />
<Setting name="supports-secure-with-non-secure-codec" value="true" />
</Settings>
<Decoders>
<MediaCodec name="c2.mtk.atv.decoder" type="tv/atv" >
<Limit name="concurrent-instances" max="2" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.hdmi.decoder" type="tv/hdmi" >
<Limit name="concurrent-instances" max="2" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.component.decoder" type="tv/component" >
<Limit name="concurrent-instances" max="2" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.composite.decoder" type="tv/composite" >
<Limit name="concurrent-instances" max="2" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.avc.decoder" type="video/avc" >
<Limit name="concurrent-instances" max="16" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="7680x4320" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="low-latency" />
<Limit name="performance-point-3840x2160" value="30" />
<Limit name="performance-point-7680x4320" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.avc.decoder.secure" type="video/avc" >
<Limit name="concurrent-instances" max="16" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="7680x4320" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Feature name="low-latency" />
<Limit name="performance-point-3840x2160" value="30" />
<Limit name="performance-point-7680x4320" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.hevc.decoder" type="video/hevc" >
<Limit name="concurrent-instances" max="16" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="7680x4320" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="low-latency" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-7680x4320" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.hevc.decoder.secure" type="video/hevc" >
<Limit name="concurrent-instances" max="16" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="7680x4320" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Feature name="low-latency" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-7680x4320" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.shvc.decoder" type="video/vnd.shvc" >
<Limit name="concurrent-instances" max="16" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="4096x2176" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.shvc.decoder.secure" type="video/vnd.shvc" >
<Limit name="concurrent-instances" max="16" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="4096x2176" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.mpeg4.decoder" type="video/mp4v-es" >
<Limit name="concurrent-instances" max="16" />
<Limit name="size" min="16x16" max="1920x1088" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.h263.decoder" type="video/3gpp" >
<Limit name="concurrent-instances" max="16" />
<Limit name="size" min="16x16" max="1408x1152" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1280x720" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.mpeg2.decoder" type="video/mpeg2" >
<Limit name="concurrent-instances" max="16" />
<Limit name="size" min="16x16" max="1920x1088" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.mpeg2.decoder.secure" type="video/mpeg2" >
<Limit name="concurrent-instances" max="16" />
<Limit name="size" min="16x16" max="1920x1088" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.vp8.decoder" type="video/x-vnd.on2.vp8" >
<Limit name="concurrent-instances" max="16" />
<Limit name="size" min="128x128" max="1920x1088" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.vp9.decoder" type="video/x-vnd.on2.vp9" >
<Limit name="concurrent-instances" max="16" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="7680x4320" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="low-latency" />
<Limit name="performance-point-3840x2160" value="30" />
<Limit name="performance-point-7680x4320" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.vp9.decoder.secure" type="video/x-vnd.on2.vp9" >
<Limit name="concurrent-instances" max="16" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="7680x4320" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Feature name="low-latency" />
<Limit name="performance-point-3840x2160" value="30" />
<Limit name="performance-point-7680x4320" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.av1.decoder" type="video/av01" >
<Limit name="concurrent-instances" max="16" />
<Limit name="size" min="64x64" max="7680x4320" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="tunneled-playback" />
<Feature name="adaptive-playback" />
<Feature name="low-latency" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-7680x4320" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.av1.decoder.secure" type="video/av01" >
<Limit name="concurrent-instances" max="16" />
<Limit name="size" min="64x64" max="7680x4320" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="tunneled-playback" />
<Feature name="adaptive-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-7680x4320" value="60" />
<Feature name="secure-playback" required="true" />
<Feature name="low-latency" />
</MediaCodec>
<MediaCodec name="c2.mtk.mjpeg.decoder" type="video/x-motion-jpeg" >
<Limit name="concurrent-instances" max="16" />
<Limit name="size" min="8x8" max="1920x1088" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.vc1.decoder" type="video/VC1" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="32x32" max="1920x1088" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.flv.decoder" type="video/flv" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="16x16" max="1920x1088" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.avs.decoder" type="video/avs" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="1920x1088" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.avs2.decoder" type="video/avs2" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.wmv3.decoder" type="video/x-ms-wmv" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="32x32" max="1920x1088" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.rv.decoder" type="video/vnd.rn-realvideo" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="32x32" max="1920x1088" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-1920x1088" value="60" />
</MediaCodec>
<!--
<MediaCodec name="c2.mtk.dvav.per.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvav.per.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvav.pen.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvav.pen.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.der.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.der.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.den.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.den.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.dtr.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.dtr.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.stn.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.stn.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.dth.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.dth.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.dtb.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.dtb.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.st.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvhe.st.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvav.se.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvav.se.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvav1.10.decoder" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
<MediaCodec name="c2.mtk.dvav1.10.decoder.secure" type="video/dolby-vision" >
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="64x64" max="4096x2176" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="performance-point-3840x2160" value="60" />
<Limit name="performance-point-4096x2176" value="60" />
</MediaCodec>
-->
<MediaCodec name="c2.mtk.hdmi.capture" type="audio/vnd.hdmicap">
<Limit name="channel-count" max="8" />
<Limit name="sample-rate" ranges="8000-48000" />
<Limit name="bitrate" range="8000-960000" />
</MediaCodec>
<MediaCodec name="c2.mtk.atv.capture" type="audio/vnd.atvcap">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</MediaCodec>
<MediaCodec name="c2.mtk.analog.capture" type="audio/vnd.analogcap">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</MediaCodec>
<MediaCodec name="c2.mtk.ac3.decoder">
<Type name="audio/ac3">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
<Type name="audio/eac3">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
<Type name="audio/eac3-joc">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
</MediaCodec>
<MediaCodec name="c2.mtk.dts.decoder">
<Type name="audio/vnd.dts">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
<Type name="audio/vnd.dts.hd">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
<Type name="audio/vnd.dts.hd;profile=lbr">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
<Type name="audio/vnd.dts.uhd;profile=p2">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
<Limit name="concurrent-instances" max="2" />
</Type>
</MediaCodec>
<MediaCodec name="c2.mtk.adts.decoder">
<Type name="audio/aac-adts">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
<Type name="audio/mp4a-latm-hw">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
</MediaCodec>
<MediaCodec name="c2.mtk.mpeg.decoder">
<Type name="audio/mpeg-L1">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
<Type name="audio/mpeg-L2">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
</MediaCodec>
<MediaCodec name="c2.mtk.wma.decoder">
<Type name="audio/x-ms-wmapro">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
<Type name="audio/x-ms-wma">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</Type>
</MediaCodec>
<MediaCodec name="c2.mtk.adpcmima.decoder" type="audio/x-adpcm-dvi-ima" >
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</MediaCodec>
<MediaCodec name="c2.mtk.adpcmms.decoder" type="audio/x-adpcm-ms" >
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</MediaCodec>
<MediaCodec name="c2.mtk.cook.decoder" type="audio/vnd.rn-realaudio" >
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000-48000" />
</MediaCodec>
<MediaCodec name="c2.mtk.heif.decoder" type="image/vnd.android.heic" >
<Limit name="concurrent-instances" max="2" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="16383x16383" />
<Limit name="blocks-per-second" min="1" max="2088960" />
<Limit name="performance-point-1920x1088" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.jpeg.decoder" type="image/jpeg" >
<Limit name="concurrent-instances" max="1" />
<Limit name="alignment" value="32x16" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="8x8" max="16383x16383" />
<Limit name="blocks-per-second" min="1" max="2088960" />
<Limit name="performance-point-1920x1088" value="30" />
</MediaCodec>
<MediaCodec name="c2.mtk.frame.filter" type="image/vnd.android.filter" >
<Limit name="concurrent-instances" max="2" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="size" min="64x64" max="16383x16383" />
<Limit name="blocks-per-second" min="1" max="2088960" />
<Limit name="performance-point-1920x1088" value="30" />
</MediaCodec>
<MediaCodec name="OMX.MS.MP1.Decoder" >
<Type name="audio/mpeg-L1">
<Limit name="concurrent-instances" max="2" />
</Type>
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.MP2.Decoder" >
<Type name="audio/mpeg-L2">
<Limit name="concurrent-instances" max="2" />
</Type>
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.MP3.Decoder" >
<Type name="audio/mpeg">
<Limit name="concurrent-instances" max="2" />
</Type>
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.AAC.Decoder" type="audio/mp4a-latm-hw" >
<Limit name="concurrent-instances" max="2" />
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.VORBIS.Decoder" type="audio/vorbis" >
<Limit name="concurrent-instances" max="2" />
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.ADPCM.Decoder">
<Type name="audio/adpcm" />
<Type name="audio/x-adpcm-ms" />
<Type name="audio/x-adpcm-ima" />
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.LPCM.Decoder" type="audio/lpcm" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.RA.Decoder" type="audio/vnd.rn-realaudio" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.WMA.Decoder" >
<Type name="audio/x-ms-wma" />
<Type name="audio/x-ms-wmapro" />
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.AC3.Decoder" >
<Type name="audio/ac3">
<Limit name="channel-count" max="6" />
<Limit name="sample-rate" ranges="8000-48000" />
<Limit name="concurrent-instances" max="2" />
</Type>
<Type name="audio/eac3">
<Limit name="channel-count" max="6" />
<Limit name="sample-rate" ranges="8000-48000" />
<Limit name="concurrent-instances" max="2" />
</Type>
<Type name="audio/ac3p">
<Limit name="concurrent-instances" max="2" />
</Type>
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.DTS.Decoder" >
<Type name="audio/dts" />
<Type name="audio/dts-hd" />
<Type name="audio/dts-lbr" />
<Type name="audio/vnd.dts" />
<Type name="audio/vnd.dts.hd" />
<Type name="audio/vnd.dts.hd;profile=lbr" />
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.DRA.Decoder" type="audio/dra" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
<MediaCodec name="OMX.MS.FLAC.Decoder" type="audio/flac" >
<Limit name="concurrent-instances" max="2" />
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
</MediaCodec>
</Decoders>
<Encoders>
<MediaCodec name="c2.mtk.avc.encoder" type="video/avc">
<Limit name="concurrent-instances" max="2" />
<Limit name="size" min="176x144" max="1920x1088" />
<!--<Limit name="aspect-ratio" in="blocks" range="11:9-16:9" />-->
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="bitrate" range="102400-28000000" />
<!--<Quirk name="kAvoidMemcopyInputRecordingFrames" />-->
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
<Limit name="performance-point-1920x1088" value="60" />
<Limit name="blocks-per-second" range="1-489600" />
<Limit name="frame-rate" range="1-60" />
</MediaCodec>
</Encoders>
</MediaCodecs>