Add documentation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
61
docs/research/README.md
Normal file
61
docs/research/README.md
Normal 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 |
|
||||
55
docs/research/decisions.md
Normal file
55
docs/research/decisions.md
Normal 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.
|
||||
134
docs/research/device-xiaomi.md
Normal file
134
docs/research/device-xiaomi.md
Normal 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
511
docs/research/engines.md
Normal 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
|
||||
1693
docs/research/jellyfin-client-research.md
Normal file
1693
docs/research/jellyfin-client-research.md
Normal file
File diff suppressed because it is too large
Load Diff
30
docs/research/library-profile.md
Normal file
30
docs/research/library-profile.md
Normal 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
327
docs/research/media3.md
Normal 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.
|
||||
1219
docs/research/tv-dump/audio_policy.txt
Normal file
1219
docs/research/tv-dump/audio_policy.txt
Normal file
File diff suppressed because it is too large
Load Diff
373
docs/research/tv-dump/display.txt
Normal file
373
docs/research/tv-dump/display.txt
Normal 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
|
||||
755
docs/research/tv-dump/media_codecs-mt5896.xml
Normal file
755
docs/research/tv-dump/media_codecs-mt5896.xml
Normal 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>
|
||||
Reference in New Issue
Block a user