Узнайте, как использовать функцию requestVideoFrameCallback() для более эффективной работы с видео в браузере.
Опубликовано: 8 января 2023 г.
Метод HTMLVideoElement.requestVideoFrameCallback() позволяет веб-разработчикам регистрировать функцию обратного вызова, которая запускается на этапах рендеринга при отправке нового видеокадра в композитор. Это позволяет разработчикам эффективно выполнять покадровые операции с видео, такие как обработка видео и рисование на холсте, анализ видео или синхронизация с внешними источниками звука.
Разница с requestAnimationFrame()
Операции, выполняемые с помощью этого API, такие как отрисовка видеокадра на холсте с помощью drawImage() , синхронизируются по мере возможности с частотой кадров видео, воспроизводимого на экране. Это отличается от window.requestAnimationFrame() , который обычно срабатывает около 60 раз в секунду.
requestVideoFrameCallback() привязана к фактической частоте кадров видео — с важным исключением :
Эффективная частота выполнения обратных вызовов — это меньшая из двух частот: частоты воспроизведения видео и частоты браузера. Это означает, что видео с частотой 25 кадров в секунду, воспроизводимое в браузере с частотой отрисовки 60 Гц, будет вызывать обратные вызовы с частотой 25 Гц. Видео с частотой 120 кадров в секунду в том же браузере с частотой 60 Гц будет вызывать обратные вызовы с частотой 60 Гц.
Обнаружение признаков
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
Полиэфирный наполнитель
Доступен полифил для метода requestVideoFrameCallback() , основанный на Window.requestAnimationFrame() и HTMLVideoElement.getVideoPlaybackQuality() . Перед использованием ознакомьтесь с ограничениями, указанными в README .
Используйте этот метод
Если вы используете метод requestAnimationFrame() , вы узнаете метод requestVideoFrameCallback() . Зарегистрируйте первоначальный коллбэк один раз, а затем перерегистрируйте его всякий раз, когда срабатывает коллбэк.
const doSomethingWithTheFrame = (now, metadata) => {
// Do something with the frame.
console.log(now, metadata);
// Re-register the callback to be notified about the next frame.
video.requestVideoFrameCallback(doSomethingWithTheFrame);
};
// Initially register the callback to be notified about the first frame.
video.requestVideoFrameCallback(doSomethingWithTheFrame);
В функции обратного вызова now представляет собой объект DOMHighResTimeStamp , а metadata — словарь VideoFrameMetadata со следующими свойствами:
-
presentationTime, типDOMHighResTimeStamp: время, когда пользовательский агент отправил кадр на компоновку. -
expectedDisplayTime, типDOMHighResTimeStamp: время, когда пользовательский агент ожидает, что фрейм станет видимым. -
width, типаunsigned long: ширина видеокадра в пикселях. -
height, типunsigned long: Высота видеокадра в пикселях. -
mediaTime, типdouble: Временная метка представления медиафайла (PTS) в секундах для отображаемого кадра (например, его временная метка на временной шкалеvideo.currentTime). -
presentedFrames, типunsigned long: счетчик количества кадров, отправленных для композиции. Позволяет клиентам определить, были ли пропущены кадры между вызовамиVideoFrameRequestCallback. -
processingDuration, типdouble: прошедшее время в секундах с момента отправки закодированного пакета с той же меткой времени представления (PTS), что и этот кадр (например, совпадающей сmediaTime), декодеру до момента готовности декодированного кадра к показу.
Для приложений WebRTC могут отображаться дополнительные свойства:
-
captureTime, типDOMHighResTimeStamp: Для видеокадров, поступающих как из локального, так и из удаленного источника, это время, когда кадр был захвачен камерой. Для удаленного источника время захвата оценивается с использованием синхронизации часов и отчетов отправителя RTCP для преобразования меток времени RTP во время захвата. -
receiveTime, типаDOMHighResTimeStamp: Для видеокадров, поступающих из удаленного источника, это время получения закодированного кадра платформой, то есть время получения последнего пакета, относящегося к этому кадру, по сети. -
rtpTimestamp, типunsigned long: метка времени RTP, связанная с этим видеокадром.
Особый интерес в этом списке представляет параметр mediaTime . В реализации Chromium в качестве источника времени для video.currentTime используется аудиочасы, тогда как mediaTime заполняется непосредственно меткой presentationTimestamp кадра. mediaTime следует использовать, если вы хотите точно идентифицировать кадры воспроизводимым образом, в том числе, чтобы точно определить, какие кадры вы пропустили.
Если что-то кажется не так на один кадр
Вертикальная синхронизация (или просто vsync) — это графическая технология, которая синхронизирует частоту кадров видео и частоту обновления монитора. Поскольку requestVideoFrameCallback() выполняется в основном потоке, но, по сути, композитинг видео происходит в потоке композитора, все действия, выполняемые через этот API, являются лишь приблизительными, и браузер не предоставляет никаких строгих гарантий.
Возможно, API отстает на один vsync относительно момента рендеринга видеокадра. Для того чтобы изменения, внесенные на веб-страницу через API, отобразились на экране, требуется один vsync (аналогично window.requestAnimationFrame() ). Поэтому, если вы постоянно обновляете mediaTime или номер кадра на вашей веб-странице и сравниваете это с номерами видеокадров, в конечном итоге видео будет выглядеть так, будто оно опережает на один кадр.
На самом деле происходит следующее: кадр готов в момент синхронизации по оси Vsync x, срабатывает функция обратного вызова, кадр отрисовывается в момент синхронизации по оси Vsync x+1, а изменения, внесенные в функцию обратного вызова, отрисовываются в момент синхронизации по оси Vsync x+2. Вы можете проверить, отстает ли функция обратного вызова на один шаг синхронизации по оси Vsync (и кадр уже отрисован на экране), проверив, находится ли значение metadata.expectedDisplayTime примерно now или на один шаг синхронизации по оси Vsync в будущем. Если оно находится в пределах пяти-десяти микросекунд от now , кадр уже отрисован; если expectedDisplayTime находится примерно в шестнадцати миллисекундах в будущем (при условии, что ваш браузер обновляется с частотой 60 Гц), то вы синхронизированы с кадром.
Демо
Я создал небольшую демонстрацию , которая показывает, как кадры отрисовываются на холсте точно с той же частотой кадров, что и видео, и где записываются метаданные кадров для целей отладки.
let paintCount = 0;
let startTime = 0.0;
const updateCanvas = (now, metadata) => {
if (startTime === 0.0) {
startTime = now;
}
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const elapsed = (now - startTime) / 1000.0;
const fps = (++paintCount / elapsed).toFixed(3);
fpsInfo.innerText = `video fps: ${fps}`;
metadataInfo.innerText = JSON.stringify(metadata, null, 2);
video.requestVideoFrameCallback(updateCanvas);
};
video.requestVideoFrameCallback(updateCanvas);
Выводы
Люди давно используют покадровую обработку — без доступа к самим кадрам, основываясь только на video.currentTime . Метод requestVideoFrameCallback() значительно улучшает этот обходной путь.
Благодарности
API requestVideoFrameCallback был разработан и реализован Томасом Гильбертом . Данная статья была проверена Джо Медли и Кейси Баскес .