Привет, Хабр! С вами Матвей Мочалов. В этом посте я хочу кратко затронуть тему того, без чего не обходится любой стриминговый сервис от соцсетей до онлайн‑кинотеатров — о транскодировании.
Операторы в кино и блогеры в погоне за красивой картинкой в исходном виде готовы пойти на любые жертвы, используя наиболее быстрые и объёмные накопители. А также немалые вычислительные мощности, чтобы это всё банально воспроизвести. Но подобный подход, увы, слабо совместим с доставкой видеоконтента через интернет, особенно, если вы хотите, чтобы это было коммерчески выгодным. А если в один момент его потребляют миллионы пользователей, тогда ещё и под их потребности необходимы разные версии в различных разрешениях, с разной частотой кадров, озвучкой и т. д.
Чтобы улучшить положение, исходный контент подвергается облегчению посредством транскодирования. Как по своему объёму, так и по нагрузке, требуемой для его воспроизведения на клиентском устройстве.
Как это работает
Особого секрета здесь нет. Как и почти во всём, что связано с видеоконтентом, мы имеем дело с творением сумрачного французского гения математики и программирования — Фабриса Беллара — FFmpeg. Швейцарский нож по работе с медиаконтентом, несмотря на создателя француза, пожалуй, один из наиболее важных проектов из мира Open Source.
Однако, несмотря на то, что FFmpeg любим, обожаем и замечателен, в нём нет кнопки «сделай красиво». Да и под капотом у него не магия происходит. А для понимания итогового результата стоит всё‑таки немного разбираться в принципе его работы. А лучше всего принцип работы какой‑то технологии можно понять попробовав её на практике. Предлагаю сделать это на примере данного видео, где рассказывается, как IT‑компании испытывают дефицит джуниор‑разработчиков и избыток мидлов с сеньорами.
Итак, приступаем к рубрике «Эээксперименты!»
Для начала нам потребуется скачать видео. Советую делать это с помощью утилиты yt‑dlp — это форк знакомого многим yt‑dl, но обладающий в отличие от него рядом преимуществ из коробки, сохраняя совместимость по синтаксису большей части команд.
Для этого особо заморачиваться не будем и используем самый простой метод, который скачает нам наше видео вместе со звуком в максимальном разрешении. Поэтому мы просто введём в командной строке консоли команду yt‑dlp и в кавычках «адрес» нашего видео. Всё, вы великолепны! Теперь у нас есть видео в формате.webm, с которым можем дальше работать.
Далее узнаем информацию о скачанном нами файле с помощью команды —
ffmpeg ‑i Not_a_Rickroll.webm
В результате мы получаем вывод следующего вида:
Input #0, matroska,webm, from 'Not_a_Rickroll.webm':
Metadata:
COMPATIBLE_BRANDS: iso6mp41
MAJOR_BRAND : dash
MINOR_VERSION : 0
ENCODER : Lavf60.16.100
Duration: 00:03:32.07, start: 0.000000, bitrate: 3881 kb/s
Stream #0:0: Video: vp9 (Profile 0), yuv420p(tv, bt709), 1920x1080, SAR 1:1 DAR 16:9, 25 fps, 25 tbr, 1k tbn (default)
Metadata:
HANDLER_NAME : ISO Media file produced by Google Inc. Created on: 04/04/2024.
VENDOR_ID : [0][0][0][0]
DURATION : 00:03:32.040000000
Stream #0:1(eng): Audio: opus, 48000 Hz, stereo, fltp (default)
Metadata:
DURATION : 00:03:32.068000000
Из чего нам становится понятно (помимо очевидного, что контейнер видео формата WebM), что битрейт — 3881 Кб/c, используется кодек vp9, разрешение 1920×1080, 25 кадров в секунду, цветовое пространство yuv420p и т. д.
Всего с одним стерео аудио‑потоком и при длине в 3 минуты 32 секунды, наше видео весит 98.1 Мегабайт. Для сравнения с тем, как бы видео весило в оригинале, поступим схожим образом с составляющими задачки по физике. Нет, мы не округлим π до 3, G до 10 и представим, что силы трения нет. Мы посчитаем вес только с учётом видео‑потока, без аудио.
Это будет количество пикселей из разрешения на количество кадров в секунду, на глубину цветового пространства, которое пусть будет трёхцветным RGB на 8 бит, которая пусть будет равна нулю — никакой экономии!
1920 * 1080 * 25/c * 8 * 3 = 1244160000 бит в секунду, это 155,2 мегабайт в секунду, и это только объём потока данных. А всего у нас 212 секунд. В итоге конечный размер файла будет составлять аж 32,97 ГИГАБАЙТ. И это всего навсего для трёх с половиной минут контента — разница в 344 раза.
Для пользователя на том же YouTube проблема хранения неактуальна, это забота Google, но вот потребление трафика и ресурсов — уже другая история.
Напомню, 1 гигабит в секунду — это 125 мегабайт в секунду, так как скорость в отличии от объёма измеряется в количестве бит за секунду, а не байт. В 1 байте — 8 бит, 1000/8 = 125, а у нас вышло 155,2 Мб/c. То есть такое видео на лету мы даже не сможем воспроизводить без подгрузок.
Если же речь идёт о прямой трансляции, то слово «прямая» из неё можно смело убрать, так как и одного гигабита не хватит для её просмотра без задержки.
И, думаю, после этого становится весьма очевидно, зачем требуется транскодирование как минимум из исходников. Используемый тем же WebM кодек vp9 банально делает хранение, передачу и воспроизведение контента в сотни раз более простой задачей. Достигается это за счёт ряда различных мер по оптимизации. Одна из них — это, к примеру, отказ от цифрового пространства RGB в пользу YUV420, где у нас есть только ЧБ канал для яркости и красный с синим, которые зачастую составляют половину от размера ЧБ канала.
И оптимизацией исходников потребность в транскодировании не ограничивается. Хорошо, допустим 25% нашей пользовательской базы имеют устройства, которые нормально поддерживают и работают с WebM форматом и трафика/мощности им хватает для FullHD, но что делать с остальными 75%? Или если бы мы использовали MP4 с h264, а 80% пользователей не имели возможности воспроизводить контент с аппаратным ускорением?
Из этого вытекает потребность во множестве разных версий предоставляемого нами контента, который для видео можно создать заранее, а для трансляций потребуется динамически производить на лету.
Допустим наш сервис/приложение распознало, что платформа, используемая пользователем, имеет поддержку аппаратного ускорения, и у него недостаточная пропускная способность для нашего .webm с потоком 3881 Кб/c.
Попробуем для начала в лоб транскодировать .webm в .mp4 —
ffmpeg -i Not_a_Rickroll.webm Not_a_Rickroll.mp4
И смотрим, что у нас получилось:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'Not_a_Rickroll.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf60.16.100
Duration: 00:03:32.05, start: 0.000000, bitrate: 4330 kb/s
Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x1080 [S
AR 1:1 DAR 16:9], 4194 kb/s, 25 fps, 25 tbr, 12800 tbn (default)
Metadata:
handler_name : ISO Media file produced by Google Inc. Created on: 04/04/2024.
vendor_id : [0][0][0][0]
encoder : Lavc60.31.102 libx264
Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 130 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Аппаратное кодирование h264 было выбрано по умолчанию, а вот битрейт вырос до 4194 Кб/с. А у нашего пользователя траффика и на меньший в оригинале не хватало. Да и, к примеру, скорость транскодирования speed=2.66x, нас не устраивает: хочется быстрее и с меньшей нагрузкой на сервер. Для этого нам потребуются дополнительные настройки.
Попробуем для начала изменить скорость компрессии, но стоит помнить, что чем она выше, тем ниже качество. Попробуем на максимум с veryfast:
ffmpeg -i Not_a_Rickroll.webm -preset veryfast Not_a_Rickroll.mp4
Скорость кратно возросла — speed= 5.3x, битрейт уже чуть ниже, чем у .webm — 3822 Кб/c, но незначительно.
Попробуем напрямую поменять битрейт через изменение параметра crf (Constant Rate Factor). По умолчанию при транскодировании он равен 23, 0 — без сжатия и 53 — максимальное значение. Ограничимся 27.
ffmpeg -i Not_a_Rickroll.webm -preset veryfast -crf 27 Not_a_Rickroll.mp4
За счёт дополнительного снижения битрейта мы дополнительно выиграли в скорости транскодирования — speed=5.73x. И, наконец-то, получили меньший битрейт на выходе после транскодирования, относительно .webm — 2441 Кб/c. Теперь пользователю требуется скорость соединения на 37% ниже. И он сможет посмотреть свой футбольный матч без задержек из-за интернета или низкой производительности своего устройства, ура!
Как несложно догадаться, одна из услуг предоставляемых сервисом cdnnow! — это транскодирование видеоконтента, будь-то уже записанные видео или live-трансляции. Под капотом у нашего сервиса за основу используется всё также FFmpeg, разве что архитектура сервиса в целом более комплексная. Так, помимо самого транскодирования, добавляется ещё и CDN для распределения нагрузки на сервера в зависимости от количества пользователей и их географического расположения.
Подробнее мы разберём работу транскодирования в следующей части статьи. Нам предстоит разобраться в устройстве формата mp4, а также исходного кода FFmpeg.
В заключение хочу поблагодарить всех за прочтение, буду рад, если в комментариях поделитесь опытом по теме, либо предложите новые темы для следующих постов. До встречи!