Стриминг совсем без задержек невозможен, но все производители и дистрибьюторы видеоконтента стараются свести их к минимуму с помощью разных технологий.
Меня зовут Денис Филиппов, я руководитель отдела разработки стриминговой платформы EdgeЦентр. Сегодня расскажу вам, как нам удалось сократить задержки в трансляциях до 4–6 секунд, и как нам в этом, в частности, помог свой JIT (Just in time) packager.
Если вы ищите способы, как ускорить доставку видео до зрителей — эта статья для вас. Я подробно расскажу о каждом решении, которые можно использовать в работе.
Краткий ликбез: откуда берётся задержка в трансляциях
Задержка — это промежуток времени между моментом, когда камера фиксирует происходящее, и моментом, когда зрители видят кадр. Стриминг совсем без задержек невозможен, как минимум потому что ничего не может передаваться быстрее скорости света.
Трансляция — это довольно сложный процесс, состоящий из множества этапов:
Видео захватывается камерой, кодируется в нужный формат и передаётся на видеосервер.
Проходит обработку, транскодируется в разные форматы и готовится к отдаче конечным пользователям.
Отправляется зрителям.
Попадает в декодер, который отдаёт кадры на отрисовку.
Воспроизводится в плеере.
Каждый из этих этапов занимает время. И чтобы увеличить скорость отдачи стрима, оптимизировать процесс нужно на каждом этапе.
Классическая реализация онлайн-трансляций и её проблемы
Самые распространённые, классические протоколы для доставки трансляций — HLS (HTTP Live Streaming) и MPEG-DASH (Dynamic Adaptive Streaming over HTTP). Оба они основаны на HTTP. А значит, их легко можно использовать при доставке через CDN, и такую трансляцию можно будет просто и дёшево масштабировать на большую аудиторию.
Оба предполагают, что аудио- и видеоданные сегментируются (делятся на небольшие кусочки длительностью в несколько секунд). Это позволяет передавать информацию быстрее, упрощает кеширование и даёт возможность переключаться между разными битрейтами (чтобы качество можно было снизить, если у зрителя плохой интернет). Клиент при этом получает небольшой текстовой документ (плейлист или манифест), в котором указана последовательность и адреса этих кусочков вместе с дополнительными метаданными: разрешения, кодеки, битрейт, длительность кусков, языки и т.п.
В качестве контейнера для контента используется MPEG-2 Transport Stream .ts в HLS и .mp4 в MPEG-DASH. И это вызывало некоторые проблемы:
Так как некоторые устройства могут не поддерживать один из форматов, нужно использовать оба контейнера. Это приводит к дублированию контента. А значит, нужно больше ресурсов, где всё это хранить.
Если следовать рекомендациям по длине сегментов, задержка получается довольно большая — около 20 секунд.
На втором пункте остановимся немного подробнее. Разберём на примере, почему так происходит.
Допустим, мы запустили транскодирование и формируем части длительностью 6 секунд. Когда мы начинаем воспроизведение, плееру сначала нужно заполнить буфер — загрузить 3 кусочка. К моменту запуска мы видим, что 3 полностью сформированные и ближайших к реальному времени куска — 3, 4 и 5 (см. схему ниже), а значит, воспроизведение начнётся с 3-го. А раз каждый у нас — 6 секунд, получается, что задержка будет минимум 18 секунд.
Конечно, можно сократить части до 1-2 секунд, тогда отдача станет быстрее.
Но в этом случае придётся уменьшить размер GOP (расстояние между 2 ключевыми кадрами), что приведёт к снижению эффективности кодирования. А ещё сильно вырастет расход трафика. Ведь в каждом куске, кроме основной информации, содержится так называемый overhead, а ещё добавляется overhead HTTP при каждом запросе. Чем меньше сегменты по длине, тем их больше, а соответственно и больший overhead. Добавим к этому тысячную аудиторию, которая постоянно делает запросы, и получим огромное количество бесполезно расходуемого трафика.
Это будет затратно для нас и невыгодно для наших клиентов. Поэтому мы решили пойти другим путём. Благо, сейчас есть много технологий, которые исправляют «болезни» классического HTTP-вещания.
Какие технологии решают эти проблемы
Есть множество технологий, которые специально разрабатывались для ускорения отправки видео. Вкратце рассмотрим каждую.
1. CMAF
И начнём мы с формата, который сам по себе не ускоряет доставку, но решает другую проблему — с дублированием контента. CMAF (Common Media Application Format) — медиаформат, разработанный по заказу Apple и Microsoft для потоковой передачи. После его внедрения устройства Apple стали поддерживать воспроизведение фрагментированных MP4 файлов.
Это позволило использовать одни и те же сегменты для HLS и MPEG-DASH, а соответственно отпала необходимость дублировать стрим в TS-контейнере, который до этого был обязателен для HLS. Как следствие:
Кодировать и пакетировать видеофайл нужно только 1 раз.
Требуется в 2 раза меньше ресурсов для хранения.
Повышается эффективность доставки через CDN.
Но, как я уже сказал, скорость формат не увеличивает. Чтобы доставлять контент быстрее, его нужно применять с другими технологиями.
2. Chunk encoding и Chunked Transfer Encoding
Существенно увеличить скорость при использовании CMAF позволил Chunk encoding —кодирование по чанкам.
Основной принцип — деление сегментов на ещё более мелкие частички — чанки с moof и mdat mp4 боксами. Если традиционно транскодер ждал, когда создастся полный кусок, чтобы отправить его зрителям, то теперь можно не ждать и отправлять чанки до того, как вся часть будет готова.
А передавать файлы до полной готовности позволяет Chunked Transfer Encoding (CTE). Этот механизм передачи данных в HTTP был создан специально, чтобы информацию можно было передавать, не зная точный размер всего тела HTTP-сообщения.
Благодаря CTE отправляется только 1 HTTP-запрос для полного куска, а внутри созданной сессии передаются маленькие фрагментики по мере готовности. Конец обозначается отправкой чанка с нулевой длиной.
Обе технологии позволяют в разы улучшить ситуацию. Если взять предыдущий пример с длиной сегментов 2 секунды, их можно будет разбить на чанки в 500 мс, значит, задержка будет всего 1 секунду.
3. LL-HLS и Chuncked CMAF
На основе описанного выше подхода появились расширения для протоколов — LL-HLS и Chunked CMAF (DASH). Оба эти расширения делят сегменты на более мелкие частички и передают их сразу по мере готовности.
В Chunked CMAF (DASH) это реализовано довольно просто. В MPEG-DASH изначально были предусмотрены поля, контролирующие временной план, частоту обновлений и дистанцию до края плейлиста. В расширение разработчики добавили поддержку CTE и Fetch API, которые сделали возможной отдачу информации плееру, как только она становится доступна.
На Low-Latency-поток указывают теги Latency target и availabilityTimeOffset. Первый сигнализирует о целевой задержке. Второй — о том, что можно начать загрузку до окончания формирования полного сегмента.
Использование Chunked CMAF (DASH) позволяет стримить с задержками 2-4 секунды в зависимости от конфигурации, серверных и плеерных настроек. Ещё расширение имеет обратную совместимость — устройства, не понимающие формат Low Latency, могут перейти на традиционную схему воспроизведения.
LL-HLS (Low Latency HLS) передаёт новые сегменты сразу за счёт специального тега PREFETCH. С помощью этого тега в плейлисте предварительно заявляется адрес live-сегмента, который ещё не готов целиком, но чанки которого уже можно отдавать.
В результате процесс получается вот таким:
Сервер заранее заявляет в плейлисте адрес нового live-сегмента.
Плеер запрашивает его и начинает получать первый чанк, как только он готов на сервере.
Не дожидаясь следующих данных, плеер передаёт чанк на воспроизведение.
Это сокращает время доставки в разы.
Однако во всей этой чудесной картине есть один нюанс. При использовании Chunked Transfer Encoding трудно определить ширину канала пользователя, предусмотреть своевременную адаптацию и переключение между разрешениями. Т.е. трудно применять адаптивный битрейт.
Сейчас объясню, почему. Если мы отдаём готовые куски целиком, мы заранее знаем время отправки запроса, размер куска видео и время, когда он был полностью загружен. Если разделить размер на время загрузки, можно достаточно точно определить скорость сети.
А если мы отдаём чанки, то узнать размер всего сегмента мы сможем только примерно: время его загрузки по чанкам будет примерно равно его длительности. И при таком раскладе посчитать скорость сети становится гораздо сложнее.
Именно поэтому Apple решили внести доработки в LL-HLS и не применять CTE:
Протокол генерирует частичные фрагменты с минимальной длительностью вплоть до 200 мс, которые обозначает в плейлисте как X-PART. Они становятся доступны ещё до формирования полного куска. При этом по мере поступления новых устаревшие частичные удаляются и заменяются полными.
Обновлённые плейлисты отправляются после их обновления, а не после непосредственного запроса.
Вместо полного плейлиста отправляется разница в них. То есть сохраняется дефолтный плейлист, а дальше шлётся только добавочная разница (дельта). Она обозначается как X-SKIP. И это позволяет сократить объем передаваемых файлов.
Частичные сегменты, которые скоро станут доступны, анонсируются до их готовности с помощью тега PRELOAD-HINT. Это позволяет клиентам отправить запрос заранее, а серверу ответить сразу, как только информация станет доступной.
С помощью тега RENDITION-REPORT в плейлист записывается информация о последних сегментах и фрагментах смежных плейлистов. Это позволяет быстрее переключаться между качествами.
Расширение также обеспечивает задержки в 2-6 секунд и имеет обратную совместимость.
При этом в нативном плеере на яблочных устройствах работает только LL-HLS, поэтому, если мы хотим, чтобы стримы были доступны на iOS и MacOS, от него никуда не деться.
4. HESP
Итак, LL-HLS и Chunked CMAF обеспечивают достаточно высокую скорость. Но есть протокол, с помощью которого ситуацию можно сделать ещё лучше.
HESP (High Efficiency Stream Protocol) — адаптивный протокол потоковой передачи видео на основе HTTP, разработанный для обеспечения стриминга с ультранизкими задержками. Если у LL-HLS и Chunked CMAF задержка 2-6 секунд, то здесь она меньше 2 секунд. При этом требуется на 10–20% меньшая полоса пропускания за счёт того, что можно использовать большую длительность GOP.
На самом деле, HESP пока мало распространён на российском рынке, и на русском языке о нём мало информации. Когда-нибудь мы обязательно напишем о нём отдельную статью. А сейчас расскажем кратко, что это за зверь и в чём его суть.
Разработан он был THEO Technologies специально, чтобы сделать возможным недорогой, масштабируемый, максимально быстрый HTTP Live-стриминг.
Здесь так же задействуется Chunked Transfer Encoding. Сперва плеер получает json manifest с информацией о потоках и времени. При этом стриминг происходит в два потока: поток инициализации и продолжения.
В первом передаются только I кадры (полные ключевые кадры). Плеер может запрашивать их в любой момент времени для начала воспроизведения. Дальше, после запуска, он продолжает воспроизведение, используя поток продолжения, и может это сделать после любого полученного кадра из потока инициализации. В потоке продолжения передаются только разностные кадры (отличия от ключевого кадра), которые весят гораздо меньше. За счёт этого экономится время и увеличивается скорость загрузки. Такая система позволяет быстро и беспрерывно транслировать стрим и переключаться между качествами.
Если сравнить всё упомянутое с точки зрения скорости, картинка будет выглядеть так:
HESP, как видите, выглядит самым привлекательным. Но есть загвоздка — его поддерживают далеко не все браузеры и устройства. Это закрытый протокол, и далеко не всем нашим клиентам он подходит.
Поэтому мы решили, что наша платформа должна поддерживать все эти технологии. Они достаточно эффективны для Low Latency Streaming.
Как нам удалось реализовать все технологии на нашей платформе
Итак, нам нужно было найти решение, чтобы каждая из названных технологий поддерживалась нашей стриминговой платформой. Мы решили не искать сторонний сервис, а сделать свой собственный JIT packager.
Почему было принято такое решение:
Независимость от вендора. Если ты покупаешь сторонний сервис, и в нём вдруг возникают какие-то проблемы, оперативно исправить ситуацию не получится. Придётся ждать, пока поставщик согласится их исправить, если вообще согласится. А у нас есть собственная глобальная инфраструктура, отдел разработки и достаточно экспертизы, чтобы создать своё.
Всё рассмотренное можно реализовать в общей системе. У всех технологий есть общие черты. Мы решили, что сделать для них общее решение будет несложно.
Мониторинг. Со своей системой мы сможем организовать его, как нам удобно, по нужным метрикам.
Быстрая адаптация под клиентские нужды. У нас в EdgeЦентр практикуется гибкий подход. Мы прислушиваемся к пожеланиям клиентов и легко можем сделать какую-то доработку под их нужды (конечно, в разумных пределах), что-то кастомизировать под конкретную задачу. Сторонний сервис нас сильно бы ограничивал.
Обратная совместимость. JIT packager должен совмещаться с другими компонентами стриминговой платформы. А без совместимости нововведения могут негативно сказаться на клиентах, чего мы не могли допустить.
Не все программы поддерживают LL-HLS и HESP. Для HTLS был бы нужен Apple Media Stream Segmenter. Но если мы хотим поддерживает ещё и HESP, потребуется HESP Packager + HTTP Origin. И чтобы совместить решения между собой, понадобится целый зоопарк костылей, которые будет трудно поддерживать и масштабировать.
В качестве языка программирования мы решили взять GO.
В итоге у нас получилось собрать уникальный JIT packager, который способен на лету упаковывать и готовить к доставке видеопотоки одновременно в HLS и MPEG-DASH, а также во всех имеющихся сейчас Low Latency форматах стриминга.
Вся система выглядит следующим образом:
Транскодер отправляет файлы в JIT packager.
Тот налету формирует все необходимые фрагменты, плейлисты.
Клиенты запрашивают стримы на CDN.
CDN идёт на нужный сервер за контентом. Получает его и кеширует в оперативной памяти Chunked-proxy.
Все остальные клиенты получают ответ из кеша.
В результате с такой системой при использовании HESP удалось добиться задержек чуть больше 700 мс, а с HLS и MPEG-DASH — 4–5 секунд.
Считаю, что сейчас это отличный результат. Но это не повод останавливаться. Мы продолжим оптимизировать систему, чтобы добиться ещё более высокой скорости.