Привет, хабр!
Хочу поведать о своём недавно проведённом исследовании, в котором я изучил проблему несоответствия TV/PC диапазонов при сжатии/воспроизведении видео. Проблема эта довольно мелочная, но в то же время достаточно массовая, из-за неё я частенько раньше винил кодеки сжатия в изменении цветов.
Пример
Представьте, вы вернулись только что с отпуска, были в местечке N и сделали на мероприятии M суперские фотографии на свой, несомненно, отличный фотоаппарат F. Сбрасываете вы фотографии на компьютер, и понимаете — руки чешутся сделать видео-коллаж и показать всю эту красоту друзьям. Сказано — сделано, устанавливаете любимый видео-редактор, подправляете контрастность, гамму, всё делаете так, чтобы было максимально красиво. И вот проект уже готов: перед вами свежеиспечённый видеофайл. Запускаете для проверки и… Что-то не так, как будто уменьшили контраст и насыщенность. А что если другим кодеком?? Попробовали другой кодек — то же самое. Ладно, главное чтобы на DVD было всё нормально. Записываете клип на DVD — и цвета, действительно, стали на свои места… Как же так?
Предыстория
Пошло всё это ещё со времён аналоговых телевизоров, с такой удивительной штукой как кинескоп. Первые кинескопы были лишены цвета, то есть отображались на нём оттенки яркости — чем более интенсивный поток направлял в заданную точку электронная пушка, тем ярче эта точка светилась, всё просто. Таким же способом передавали сигнал этому телевизору, то есть вещали яркостную составляющую картинки.
Но, чёрно-белая картинка не очень удовлетворяла людей, всё-таки зрение у нас цветное, поэтому совсем скоро появилось цветное телевидение. В цветных телевизорах так же использовался кинескоп, но для передачи цвета в нём использовались, так же как и в современных мониторах, красно-зелёно-синие пиксели. Тут стал вопрос, как передавать сигнал, что бы при этом сохранялась обратная совместимость с чёрно-белыми телевизорами? Можно, конечно, было бы вещать на соседней частоте помимо яркости, ещё и 3 цветовых канала, но, во-первых, диапазон частот, выделенный на канал, был довольно узкий, во-вторых, это сложнее, т. к. получаем в итоге 4 цветовых канала. Так математики и инженеры придумали передавать в дополнение к яркостному каналу два цветовых «хроматических» — синего и красного, получаемых как разность яркости из соответствующего цветового канала, получая из RGB цветовое пространство (Y, Cb=B-Y, Cr=R-Y). Весь цветовой диапазон можно вычислить исходя из этих трёх каналов. Более того, зрение человека менее чувствительно к цветам, поэтому это позволило сократить разрешение цветовых каналов в два раза, практически не потеряв в качестве картинки. То есть разрешение яркостного канала в PAL-кадре 720x576, а цвета для него передавались в разрешении 360x576 (так называемая 4:2:2 цветовая субдискретизация). Но как именно преобразовывать Яркость (Y) и цветовые хроматические каналы (Cb, Cr) в RGB и наоборот?
Так, в 1982 году, был заложен стандарт преобразования YCbCr<=>RGB, называется он CCIR 601 (с 1992 года — BT.601). Основываясь на результатах многочисленных экспериментов по восприятию цвета человеком, яркость определяется как сумма красного, зеленого и синего с коэффициентами 77/256, 150/256 и 29/256, соответственно.
В результате преобразования из RGB=>YCbCr получается сокращённый диапазон яркости (16-235) и цветности (16-240). Как указано в стандарте, значения 0 и 255 могут использоваться для синхронизации, а значения 1-15 и 236-254 считаются некорректными, и будут отображаться как чёрный и белый. Позже, эти сужения диапазонов перенеслись и на цифровое видео, в результате чего узкий диапазон стал стандартом в видео. Хотя для видео высокой чёткости разработали уже другой стандарт преобразования цветов — BT.709, отличающийся от BT.601 лишь коэффициентами.
Как же правильно должно сжиматься и воспроизводиться видео? Видеофайл, если он сжат в одной из яркостно-хроматических схем (а это подавляющее большинство кодеков, лишь в несжатом видео используется RGB), должен быть закодирован в узкий TV-диапазон яркости (16-235). Т. к. монитор использует всё-таки RGB-вывод, то декодер должен преобразовать YCbCr в RGB с полным диапазоном 0-255. Это почти идеальная схема, но почему почти? А вот почему:
- Искусственное сжатие диапазонов — из 256 оттенков серого мы получаем 220 (из 8-ми бит, по сути, получаем чуть больше 7-ми бит), причём отсутствуют объективные причины, зачем нужно сжимать диапазон, кроме как ради совместимости. Мы сознательно ухудшаем качество картинки.
- Каждый пиксель видео проходит от файла до точки на мониторе через кучу разных фильтров (декодер, программа плеер, рендеринг, видеодрайвер). По пути в этой длинной цепи может быть сделано много преобразований, в результате чего теряется качество из-за постоянного сужения диапазона.
- Из-за того, что некоторые фильтры игнорируют сужение диапазонов, а некоторые видео закодированы ошибочно в полном диапазоне, вместо узкого, другие фильтры пытаются это исправлять, в итоге получается ещё большая запутанность.
Эксперимент
Я решил провести эксперимент, протестировать, как ведут себя различные плееры/драйверы при воспроизведении стандартного узкого диапазона (16-235) и для видео, закодированного в полном (0-255) диапазоне. Для этого я взял PNG картинку с серым градиентом от 0-255, через AviSynth отдавал её наиболее популярному и современному енкодеру x264. Использовал я три скрипта avs, первый читал картинку и отдавал её «как есть», в RGB формате (как несжатое видео):
rgb.avs
ImageReader("palette.png", end=24)
Во втором и третьем файле выполнял конвертирование в YV12 цветовое пространство в полный диапазон по двум стандартам BT.601 и BT.709:
pc601.avs
ImageReader("palette.png", end=24) ConvertToYV12(matrix="PC.601")
pc709.avs
ImageReader("palette.png", end=24) ConvertToYV12(matrix="PC.709")
Далее я осуществлял сжатие несколькими вариантами. Дело в том, что в x264 есть два параметра, которые могут влиять на результат: --input-range [TV, PC] и --range [TV, PC]. В старых версиях x264 за это отвечал параметр --fullrange.
colortest.cmd
x264.exe --preset veryslow --crf 1 --output rgb.mp4 rgb.avs x264.exe --preset veryslow --crf 1 --input-range TV --range TV --output rgb-tv-tv.mp4 rgb.avs x264.exe --preset veryslow --crf 1 --input-range TV --range PC --output rgb-tv-pc.mp4 rgb.avs x264.exe --preset veryslow --crf 1 --input-range PC --range TV --output rgb-pc-tv.mp4 rgb.avs x264.exe --preset veryslow --crf 1 --input-range PC --range PC --output rgb-pc-pc.mp4 rgb.avs x264.exe --preset veryslow --crf 1 --output pc601.mp4 pc601.avs x264.exe --preset veryslow --crf 1 --input-range TV --range TV --output pc601-tv-tv.mp4 pc601.avs x264.exe --preset veryslow --crf 1 --input-range TV --range PC --output pc601-tv-pc.mp4 pc601.avs x264.exe --preset veryslow --crf 1 --input-range PC --range TV --output pc601-pc-tv.mp4 pc601.avs x264.exe --preset veryslow --crf 1 --input-range PC --range PC --output pc601-pc-pc.mp4 pc601.avs x264.exe --preset veryslow --crf 1 --output pc709.mp4 pc601.avs x264.exe --preset veryslow --crf 1 --input-range TV --range TV --output pc709-tv-tv.mp4 pc709.avs x264.exe --preset veryslow --crf 1 --input-range TV --range PC --output pc709-tv-pc.mp4 pc709.avs x264.exe --preset veryslow --crf 1 --input-range PC --range TV --output pc709-pc-tv.mp4 pc709.avs x264.exe --preset veryslow --crf 1 --input-range PC --range PC --output pc709-pc-pc.mp4 pc709.avs
В результате получил 15 файлов. После проверки гистограммы с помощью AviSynth получил, что без указания параметров --range и --input-range, видео сжимается так, как подаётся, иначе происходит конверсия диапазонов средствами x264. То есть гладкую гистограмму обеспечивают только файлы pc601.mp4 и pc709.mp4, но т. к. эти стандарты отличаются лишь коэффициентами для хроматических каналов, для нашей серой шкалы разницы между ними не будет, тестировать я буду только два файла — rgb.mp4 и pc601.mp4 (узкий и полный диапазон соответственно).
Протестировал я воспроизведение этих файлов на 4-рёх компьютерах, везде стоит Windows 7 и кодеки ffdswow и K-Lite Codec Pack. Результат занёс в табличку:
Пояснение к таблице:
normal — правильное отображение
in — неправильное отображение, диапазон 16-235 не маштабирован до 0-255
out — неправильное отображение, диапазон 0-255 маштабирован по ошибке, в результате образовалась обрезка диапазона.
Вот скриншоты результатов в плеере MPC-HC:
Стоит отметить, что настройки диапазонов есть практически в каждом фильтре. В моём случае эта настройка есть и в ffdshow video decoder, в рендерах Lav, Haali, в настройках драйвера видеокарты, и также можно заставить преобразовывать диапазон в плеере (есть специальный шейдер). Однако почему-то переключение диапазона в ffdshow video decoder не влияла на результат. Настройка в драйвере не везде влияет на результат, там, где он влиял, я занёс в таблицу (строка настройки видеокарты). Кроме того, при DXVA и CUDA аппаратном ускорении, правильным считается только диапазон 16-235, не говоря уже о телевизорах.
Также заодно протестировал два видео-редактора:
- VirtualDub — диапазон не трогает, если нет конверсии в из YV12/YUV2 в RGB и наоборот, иначе делает преобразование диапазонов, фильтры работают в RGB, при отображении в программе делает преобразование TV->PC.
- AviDemux — диапазон не меняет (попросту не работает с RGB источниками), фильтры работают с YUV2, при отображении в программе конверсия TV->PC.
Итоги
Из таблицы видно, что получаем мы в итоге полную путаницу: как будет отображаться видео — зависит от многих параметров, таких как используемый кодек, плеер, тип рендеринга и драйвера. Тут главное понимать, в чём собственно проблема, и если получится, исправить.
Правильный (хотя и не совсем) диапазон для сжатия видео — TV (16-235), иначе в большинстве случаев отображаться видео будет некорректно (с обрезкой диапазона чёрного и белого). И хотя с точки зрения цифрового видео, логичнее было бы хранить и отображать полный диапазон без всяких преобразований, на нынешнем этапе сложился такой стандарт, не соблюдая который, получим в большинстве устройств неправильное отображение.
Как с этим бороться? Сразу приходит на ум — в метатэгах указывать используемый диапазон, этот метод даже уже реализован (существует флаг fullrange), но, к сожалению, очень часто этот флаг игнорируется. Поэтому:
- разработчикам — необходимо заботиться об реализации влияния такого флага, а также сделать правильное отображение видео в своих плеерах/кодеках, если необходимо преобразование диапазона;
- пользователям, если возникла проблема с цветами, попробовать настроить параметры (в кодеке/плеере/драйвере), или попробовать другой плеер;
- обработчикам видео — знать о существовании такой проблемы, и правильно сжимать (кодировать в диапазоне 16-235 или хотя бы указывать флаг fullrange).
Ссылки
Wikipedia YCbCr
Wikipedia Rec. 601
Wikipedia Rec. 709
Wikipedia 4:2:2
Wikipedia Цветовая субдискретизация