Выбрать главу

В качестве альтернативы для передачи данных можно использовать транспортный протокол на основе UDP, например RTP. Такие протоколы не производят повторную передачу данных. То есть потеря пакетов из-за перегрузки или ошибок передачи приведет к тому, что часть медиаданных не будет доставлена. Решать эту проблему приходится медиаплееру. Можно просто игнорировать ее, допуская наличие ошибок в видео и аудио. Если ошибки случаются нечасто, такой подход будет работать и ошибки будут практически не заметны. Другой подход состоит в том, чтобы использовать упреждающую коррекцию ошибок (forward error correction), в частности, путем кодирования видеофайла с некоторой долей избыточности (например, с использованием кода Хэмминга или кода Рида — Соломона). В таком случае у медиаплеера будет достаточно информации для того, чтобы исправлять ошибки самостоятельно, и ему не потребуется запрашивать повторную передачу данных или пропускать поврежденные участки фильмов.

Недостатком этого метода является то, что внесение избыточности в файл ведет к увеличению его размера. Еще один подход сводится к выборочному повтору передачи наиболее важных для воспроизведения контента частей видеопотока. Например, в случае сжатого видеоряда потеря пакета в I-кадре имеет самые серьезные последствия, поскольку ошибки декодирования, возникающие в результате такой потери, могут распространяться на целую группу изображений. С другой стороны, потери в производных кадрах (P- и B-кадрах) наносят гораздо меньший урон. Схожим образом, ценность повторной передачи также зависит от того, успеет ли заново отправленный контент поступить к моменту его воспроизведения. В результате некоторые повторные передачи намного важнее других, и одна из возможных стратегий сводится к тому, чтобы выборочно повторять передачу определенных пакетов (например, пакетов внутри I-кадра, которые придут к моменту воспроизведения). Над RTP и QUIC был надстроен ряд протоколов, обеспечивающих неравномерную защиту от потерь при потоковой передаче видео по UDP; см. работу Фимстера и др. (Feamster et al., 2000), а также Палмера и др. (Palmer et al., 2018).

Третья задача медиаплеера — декомпрессия сжатых данных; это достаточно просто, хотя и затратно с точки зрения вычислений. Сложным моментом является лишь декодирование медиаданных в том случае, когда сетевой протокол не исправляет ошибки передачи. Во многих схемах сжатия данные, полученные позже, невозможно декодировать, пока не будут декодированы предыдущие данные (поскольку более поздние данные кодируются относительно более ранних). Как вы помните, P-кадр строится на основе последнего I-кадра (и всех последующих I-кадров). Если I-кадр поврежден и его не удается декодировать, то все последующие P-кадры бесполезны. При этом медиаплеер вынужден ждать следующего I-кадра, просто пропуская несколько секунд видео.

Из-за этого кодировщик вынужден делать выбор. Если I-кадры идут вплотную, например, с интервалом в 1 с, то пауза в случае ошибки будет незначительной, но видеофайл будет крупнее, поскольку I-кадры намного больше, чем P- или B-кадры. Если I-кадры идут, скажем, с интервалом в 5 с, то видеофайл гораздо меньше, но мы получаем 5-секундную паузу при повреждении I-кадра и паузу меньшего размера при повреждении P-кадра. В силу этого, когда в качестве базового протокола применяется TCP, интервал между I-кадрами может быть намного больше, чем при RTP. Поэтому многие сайты потокового видео используют TCP, чтобы получать файлы меньшего размера с крупными интервалами между I-кадрами и меньшей полосой пропускания, необходимой для плавного воспроизведения.

Четвертой задачей является устранение джиттера, главной проблемы всех систем реального времени. Использование TCP серьезно ее усложняет, поскольку оно вносит случайные задержки каждый раз, когда требуется выполнить повторную передачу. Распространенное решение, к которому прибегают все системы потоковой передачи, сводится к использованию буфера воспроизведения. Перед проигрыванием система буферизует медиаданные для 5–30 с воспроизведения (илл. 7.34). Медиаданные постоянно извлекаются из буфера для отчетливого и плавного воспроизведения звука и видео. Задержка при запуске позволяет заполнить буфер до нижнего предела (low-water mark). При этом ожидается, что в дальнейшем новые данные будут поступать достаточно регулярно для того, чтобы буфер не оказался пустым. Если это все же произойдет, воспроизведение будет остановлено.

Илл. 7.34. Медиаплеер буферизует входящую информацию с медиасервера и воспроизводит медиаданные из буфера, а не напрямую из сети

Буферизация еще больше усложняет процесс. Медиаплеер поддерживает буфер в частично заполненном состоянии, в идеале где-то между нижним и верхним пределами. Это значит, что если объем данных в буфере достигает верхнего предела, плеер сообщает источнику о необходимости прекратить отправку данных, чтобы они не потерялись из-за отсутствия места для их размещения. Верхний предел при этом должен немного не доходить от конца буфера, поскольку потоковая передача данных продолжается, пока медиасервер не получит запрос остановки (STOP). После того как сервер прекращает отправку и канал становится пустым, объем данных в буфере начинает уменьшаться. Когда он достигает нижнего предела, плеер отправляет серверу команду запуска (START), чтобы возобновить потоковую передачу.

Благодаря протоколу, который позволяет сообщать серверу о необходимости остановки и запуска передачи, плеер может держать в буфере достаточно, но не слишком много медиаданных, чтобы обеспечить плавное воспроизведение. Поскольку ОЗУ на сегодняшний день стоит довольно дешево, медиаплеер даже на смартфоне может выделить достаточно места в буфере для нескольких минут воспроизведения, если нужно.

Механизм запуска и остановки обладает еще одной приятной особенностью: с ним скорость передачи сервера не привязана к скорости воспроизведения. Допустим, плееру нужно воспроизвести видео со скоростью 8 Мбит/с. Когда объем данных в буфере опустится до нижнего предела, плеер запросит у сервера доставку дополнительных данных. Если сервер способен производить доставку со скоростью 100 Мбит/с, это не вызовет проблем. Поступающие данные будут просто сохраняться в буфере. При достижении верхнего предела плеер сообщит серверу о необходимости остановки. Таким образом, скорость передачи сервера и скорость воспроизведения совершенно не зависят друг от друга. То, что изначально было системой реального времени, стало обычной системой передачи файлов. Избавление от всех требований к передаче в реальном времени — одна из причин, почему Youtube, Netflix, Hulu и другие сервисы потоковой передачи используют TCP. Это существенно упрощает дизайн всей системы.

Определить подходящий размер буфера не так просто. Если доступно много оперативной памяти, может показаться, что лучше использовать большой буфер, позволив серверу держать его почти заполненным, на случай перегрузки сети. Но следует учесть, что люди порой привередливы. Посчитав какую-нибудь сцену скучной, пользователь может нажать кнопку перемотки вперед, и большая часть или все содержимое буфера станет бесполезным. Как бы там ни было, переход к определенному моменту времени вперед или назад сработает, только если этот кадр является I-кадром. В противном случае плееру нужно найти ближайший I-кадр. Если новая точка воспроизведения находится за пределами буфера, его нужно полностью очистить и загрузить заново. То есть если пользователь часто перематывает видео вперед или назад (а так поступают многие), он фактически впустую тратит пропускную способность сети, делая бесполезными данные буфера, загрузка которых требовала определенных затрат. В рамках всей системы наличие пользователей, склонных часто перематывать видео, — серьезный аргумент в пользу ограничения размера буфера, даже несмотря на низкую стоимость больших объемов ОЗУ. В идеале медиаплеер должен понаблюдать за поведением пользователя и выбрать размер буфера, исходя из свойственной этому человеку манеры просмотра.