Почти все пользуются, и, наверняка, многие слышали про динамическую адаптацию видеопотока под пропускную способность сети. В последнее время это уже практически обязательное требование к online-видео в интернете. Преимущества адаптивного видеопотока очевидны: если сеть временами «проседает», видео продолжает отображаться в плеере без видимых подкачек и буферизации; качество картинки автоматически выбирается адекватным пропускной способности сети.
Несмотря на то, что динамическая адаптация видеопотока уже сравнительно «старая» технология, существует множество мелких подробностей о том, как добиться лучшего результата. Чтобы и на серверной стороне попроще и подешевле, и чтобы такое видео было совместимо с как можно большим количеством клиентов (Web, iOS, Android, ну и не забываем про Smart TV).
На хабре уже было несколько статей про адаптивное видео, так что я не буду повторяться и попробую сфокусироваться на том, как оно устроено, и как его сделать.
Лирическое отступление (максимально кратко и просто про адаптивное видео):
Очевидно, самый простой вариант раздачи видео в интернете — это взять mp4 файл и выложить его на HTTP сервер. Этот вариант плох тем, что у нас всего 1 файл, а клиентов у нас великое множество и такое же множество качественных и не очень интернет-соединений. Если выложим видео 1080p с битрейтом 20 мегабит/с (Blueray качество), его не смогут смотреть смартфоны, а если выложим видео для смартфонов (скажем, 1 мегабит/c и 320x240), оно будет ужасно выглядеть на 55-дюймовом телевизоре.
Ну, раз 1 файл — плохо, давайте выложим десяток файлов, «нарежем» разного видео из одного исходника, будут все битрейты и все размеры кадра, от мобильного до 1080p, с битрейтом от 1 мегабита/c до 20. Прекрасно. Но есть проблемка. Один и тот же смартфон может быть как в домашнем Wi-Fi (то есть быстром), так и в ресторанном (то есть медленном). Одинаковые телевизоры бывают как у людей в Москве, так и у людей на Сахалине.
Тогда пусть плеер проверяет как-нибудь, какая пропускная способность у сети, и, померив ее, выбирает нужный файл для просмотра. Наконец, еще одна нерешенная задача — это как бы еще учесть тот факт, что фильм идет часа 2-3, а интернета то «много», то «мало». Запускать замер пропускной способности сети периодически? Этот способ сработает, но что делать когда нужно переключиться на более или менее «качественный» файл, во время «проседания» или «ускорения» сети? Чтобы это сделать быстро (да еще и не останавливая просмотр того, что уже успело накачаться), нужно заранее знать, с какого места в новом файле нужно запустить скачивание. К несчастью, соотношение смещения от начала файла к времени фильма очень часто нелинейное, из-за переменного битрейта. На быстрых сценах, когда, например, Джеймс Бонд преследует очередного врага, картинка меняется часто и битрейт высокий, а на плавной панораме безоблачного неба картинка почти не меняется и битрейт низкий.
Чтобы справиться с этой задачей, необходимо заранее проиндексировать все файлы (составить пары «время сцены в фильме / позиция от начала файла». Эти пары называют сегментами. После этого можно будет, зная текущее время просмотра, определить, из какого места в другом файле можно скачать следующий сегмент. Для «бесшовного» переключения сегменты из разных битрейтов выравнивают по времени.
Конечно, все эти функции уже давно реализованы практически во всех современных устройствах. Несколько разных битрейтов и размеров кадра для одного и того же фильма упаковываются в определенный формат, информация о том, что в каких файлах и где лежит описывается в специальном файле-дескрипторе (его часто называют манифестом). Клиент перед просмотром скачивает файл манифест и «понимает», откуда что ему качать, где какой размер видео и какой битрейт находится на сервере.
Плохая новость состоит в том, что в современном мире этот простой подход реализован разными компаниями в разное время и по разному. Вот список наиболее известных и распространенных способов адаптивной раздачи видео по HTTP:
- HTTP Live Streaming (или HLS, придуман Apple, используется во многих устройствах)
- HTTP Dynamic Streaming (сокращенно Adobe HDS)
- MPEG-DASH (стандарт опубликован в конце января)
- Smooth Streaming (изобретен в Microsoft)
Стоит еще отметить, что иногда (в форматах HDS и Smooth Streaming) вместо прямых ссылок из манифестов используется специальная схема адресации сегментов, когда сервер по этой специальной схеме «вычисляет», какой же файл запрашивает клиент и чтобы такую схему поддержать, делается еще и серверный манифест.
Посмотрим подробнее на подготовку адаптивного видео на примере HLS, как наиболее просто устроенного и наиболее широко поддерживаемого устройствами формата.
Манифестом в HLS служит группа плейлистов из одного «мастер-плейлиста» и нескольких «плейлистов потока». Проще всего будет показать это на примере. Допустим, у нас есть очень короткий фильм (всего 3 сегмента по 10 секунд, для простоты), для которого мы сделали 3 битрейта видео- 500 kbps, 1000 kbps и 2000 kbps. В файловой системе сервера он может быть расположен например так:
/master-playlist.m3u8
/500K/
/500K/playlist-500K.m3u8
/500K/segment0.ts
/500K/segment1.ts
/500K/segment2.ts
/1000K/
/1000K/playlist-1000K.m3u8
/1000K/segment0.ts
/1000K/segment1.ts
/1000K/segment2.ts
/2000K/
/2000K/playlist-2000K.m3u8
/2000K/segment0.ts
/2000K/segment1.ts
/2000K/segment2.ts
Файл master-playlist.m3u8 внутри выглядит так (некоторую информацию я убрал для простоты изложения):
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=500,CODECS="mp4a.40.2, avc1.640028",RESOLUTION=640x480
500K/playlist-500K.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1000,CODECS="mp4a.40.2, avc1.640028",RESOLUTION=640x480
1000K/playlist-1000K.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2000,CODECS="mp4a.40.2, avc1.640028",RESOLUTION=640x480
2000K/playlist-2000K.m3u8
Те, кто знаком с форматом m3u без труда поймут что тут к чему. В файле содержится три строки-ссылки на другие плейлисты m3u8, а в комментированной значком '#' строке над каждой ссылкой указаны данные соответствующего битрейта. BANDWIDTH, CODECS, RESOLUTION — в общем термины говорят сами за себя. Легко заметить, что отличается только BANDWIDTH, хотя в реальности там все параметры могут быть разными. Задача клиента — понять по этим параметрам, какой плейлист ему в данный момент годится.
Допустим, клиент знает, что у него сейчас «хороший» интернет и предпочитает высокий битрейт (2000К). Клиент выкачивает плейлист 2000K/playlist-2000K.m3u8, который внутри выглядит следующим образом:
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9.8849,
segment0.ts
#EXTINF:9.9266,
segment1.ts
#EXTINF:9.9266,
segment2.ts
#EXT-X-ENDLIST
Видны ссылки на отдельные сегменты, их длительность в секундах указана строкой выше, для нулевого сегмента, например: "#EXTINF:9.8849". Скачав этот плейлист, клиент начинает проигрывание с первого сегмента по третий. Во время просмотра сегмента обычно качается следующий и так далее. Если клиент почувствует, что очередной сегмент выкачивается слишком медленно, клиент может остановить закачку и начать качать такой же сегмент (для того же места в фильме) из другого плейлиста, например, 500K/playlist-500K.m3u8. Когда скорость интернета восстановится, клиент может опять переключиться на закачку сегментов из плейлиста 1000K или 2000K.
Простота HLS позволяет раздавать его практически с любой серверной платформы (серверной «логики», собственно, при раздаче никакой и не требуется). Отдельные сегменты при необходимости легко кэшируются любым доступным образом.
Теперь посмотрим, какие инструменты доступны для создания и упаковки видео в HLS. Этот процесс состоит из трех основных этапов:
Этап 1. Подготовка исходных файлов (.mov или .mp4 с H.264) с нужным битрейтом и размером кадра.
Тут есть множество вариантов, но самый очевидный — это использовать бесплатный ffmpeg. Есть и множество видеоредакторов с GUI, для MacOSX и Windows. Для примера, описанного выше на этом этапе вам нужно получить три файла .mp4 или .mov, со средними битрейтами 500К, 1000К и 2000К. Важно брать для всех них один и тот же исходник, чтобы в конце всего процесса получились сегменты, одинаковые по времени.
Например, если у вас есть исходный файл movie.mp4 (предполагаем, что его битрейт не ниже 2000К), тогда достаточно будет запустить ffmpeg примерно так (ключи обозначают что звуковую дорожку можно взять как есть, а видео битрейт изменить):
$ ffmpeg -i movie.mp4 -acodec copy -vb 500K movie-500K.mp4
$ ffmpeg -i movie.mp4 -acodec copy -vb 1000K movie-1000K.mp4
$ ffmpeg -i movie.mp4 -acodec copy -vb 2000K movie-2000K.mp4
Этап 2. Создание однобитрейтных плейлистов.
Дальше нужно из каждого movie-*K.mp4 сделать набор из плейлиста m3u8 и сегментов *.ts. Важно, чтобы сегменты получились синхронными между разными битрейтами. Скажу сразу, ffmpeg умеет «нарезать» mp4 в m3u8 + сегменты, но только в рамках одного битрейта. К сожалению, мастер-плейлист потом придется создавать руками. Это не очень сложно (в минимальном варианте достаточно любого текстового редактора), но если вы случайно являетесь Apple iOS или OSX разработчиком, то могу посоветовать пакет HTTP Live Streaming Tools (для MacOSX). В него входит несколько программ, из которых нам пригодятся две: mediafilesegmenter и variantplaylistcreator. Первая превращает mp4 файл в плейлист m3u8 и «нарезает» сегменты, вторая собирает несколько однобитрейтных плейлистов в мастер-плейлист.
Итак, создаем три плейлиста из трех файлов полученных на предыдущем шаге (предполагается, что файлы movie-*.mp4 лежат в текущей папке).
$ mkdir 500K
$ mediafilesegmenter -I -f 500K -B segment movie-500K.mp4
$ mkdir 1000K
$ mediafilesegmenter -I -f 1000K -B segment movie-1000K.mp4
$ mkdir 2000K
$ mediafilesegmenter -I -f 2000K -B segment movie-2000K.mp4
Небольшие пояснения:
Ключ -I требует создавать специальный файл (variant plist), который потребуется позже в программе variantplaylistcreator.
Ключ -f 500K обозначает каталог (500K), в который должны складываться “нарезанные” сегменты.
Ключ -B segment сообщает как должны называться файлы сегментов (префикс, который будет дополнен цифрой и расширением .ts).
На этом этапе у вас должны образоваться 3 папки, заполненные файлами сегментов, 3 файла-плейлиста по одному в каждой папке и 3 файла с расширением .plist. Однобитрейтные плейлисты по умолчанию называются prog_index.m3u8. Можно заодно проверить, как они воспроизводятся на клиентах, для этого нужно выложить их на HTTP сервер и запустить на клиенте просмотр на любой из этих prog_index.m3u8.
Этап 3. Собираем три отдельных плейлиста в единый мастер.
Для этого используется variantplaylistcreator. Запускается он так (для нашего примера):
variantplaylistcreator -r -o movie.m3u8
500Kprog_index.m3u8 movie-500K.plist
1000Kprog_index.m3u8 movie-1000K.plist
2000Kprog_index.m3u8 movie-2000K.plist
Ключ -r требует указывать так называемые resolution tags (размер кадра видео). Вообще он не обязателен, но если вы упаковываете в один мастер-плейлист несколько видео с разным разрешением (допустим, мобильное качество, 480p и 1080p), то стоит его указать. В этом случае клиент прямо из мастер-плейлиста узнает, какие разрешения доступны.
Ключ -o movie.m3u8 обозначает имя выходного файла (мастер-плейлиста).
Остальные части командной строки — это пары плейлист-plist для каждого битрейта, которые были получены на предыдущем этапе.
Теперь у нас появился мастер-плейлист movie.m3u8. Можно выкладывать текущий каталог и подкаталоги на HTTP сервер и запускать на клиенте просмотр файла movie.m3u8. Кстати, файлы *.mov больше не нужны, для сокращения занятого под контент места их можно убрать из папки.
На этом у меня пока все, но если данная тема интересна уважаемому сообществу, могу ее продолжить и в будущих постах рассказать, как добавить в HLS альтернативные звуковые дорожки и субтитры, а также как из HLS сделать MPEG-DASH совместимый со Smart TV. Спасибо за внимание.
Автор: ass026