Effectuer des opérations efficaces sur chaque image de la vidéo avec requestVideoFrameCallback()

Découvrez comment utiliser requestVideoFrameCallback() pour travailler plus efficacement avec les vidéos dans le navigateur.

La méthode HTMLVideoElement.requestVideoFrameCallback() permet aux auteurs Web d'enregistrer un rappel qui s'exécute lors des étapes de rendu lorsqu'une nouvelle image vidéo est envoyée au compositeur. Cela permet aux développeurs d'effectuer des opérations efficaces sur chaque image de la vidéo, telles que le traitement vidéo et l'affichage sur un canevas, l'analyse vidéo ou la synchronisation avec des sources audio externes.

Différence avec requestAnimationFrame()

Les opérations telles que le dessin d'une image vidéo sur un canevas à l'aide de drawImage() effectuées via cette API seront synchronisées au mieux avec la fréquence d'images de la vidéo en cours de lecture à l'écran. Contrairement à window.requestAnimationFrame(), qui se déclenche généralement environ 60 fois par seconde, requestVideoFrameCallback() est lié à la fréquence d'images réelle de la vidéo, à une exception importante:

La vitesse effective à laquelle les rappels sont exécutés correspond au taux le plus faible entre la vitesse de la vidéo et celle du navigateur. Cela signifie qu'une vidéo à 25 FPS dans un navigateur dont la fréquence d'affichage est de 60 Hz déclencherait des rappels à 25 Hz. Une vidéo à 120 FPS dans ce même navigateur 60 Hz déclencherait des rappels à 60 Hz.

Qu'est-ce qui se cache derrière un nom ?

En raison de sa similarité avec window.requestAnimationFrame(), la méthode a été initialement proposée comme video.requestAnimationFrame() et renommée requestVideoFrameCallback(), qui a fait l'objet d'un accord après une longue discussion.

Détection de fonctionnalités

if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
  // The API is supported!
}

Prise en charge des navigateurs

Navigateurs pris en charge

  • 83
  • 83
  • x
  • 15,4

Source

Polyfill

Un polyfill pour la méthode requestVideoFrameCallback() basé sur Window.requestAnimationFrame() et HTMLVideoElement.getVideoPlaybackQuality() est disponible. Avant de l'utiliser, tenez compte des limites mentionnées dans le README.

Utiliser la méthode requestVideoFrameCallback()

Si vous avez déjà utilisé la méthode requestAnimationFrame(), vous vous familiariserez immédiatement avec la méthode requestVideoFrameCallback(). Vous devez enregistrer un rappel initial une fois, puis le réenregistrer chaque fois que le rappel se déclenche.

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);

Dans le rappel, now est un DOMHighResTimeStamp et metadata est un dictionnaire VideoFrameMetadata avec les propriétés suivantes:

  • presentationTime, de type DOMHighResTimeStamp : moment auquel le user-agent a envoyé le frame pour la composition.
  • expectedDisplayTime, de type DOMHighResTimeStamp : moment auquel le user-agent s'attend à ce que le frame soit visible.
  • width, de type unsigned long : largeur de l'image vidéo, en pixels du contenu multimédia.
  • height, de type unsigned long : hauteur de l'image vidéo, en pixels de contenu multimédia.
  • mediaTime, de type double : code temporel de la présentation multimédia (PTS) en secondes de l'image présentée (par exemple, son code temporel sur la timeline video.currentTime).
  • presentedFrames, de type unsigned long : nombre d'images envoyées pour la composition. Permet aux clients de déterminer si des frames ont été manqués entre les instances de VideoFrameRequestCallback.
  • processingDuration, de type double : durée écoulée en secondes entre l'envoi du paquet encodé avec le même code temporel de présentation (PTS) que cette image (identique au mediaTime) au décodeur jusqu'à ce que l'image décodée soit prête pour la présentation.

Pour les applications WebRTC, des propriétés supplémentaires peuvent s'afficher:

  • captureTime, de type DOMHighResTimeStamp : Pour les images vidéo provenant d'une source locale ou distante, il s'agit de l'heure à laquelle l'image a été capturée par la caméra. Pour une source distante, la durée de capture est estimée en utilisant la synchronisation de l'horloge et les rapports de l'expéditeur RTCP pour convertir les horodatages RTP en heure de capture.
  • receiveTime, de type DOMHighResTimeStamp : pour les images vidéo provenant d'une source distante, il s'agit de l'heure à laquelle la trame encodée a été reçue par la plate-forme, c'est-à-dire l'heure à laquelle le dernier paquet appartenant à cette trame a été reçu sur le réseau.
  • rtpTimestamp, de type unsigned long : code temporel RTP associé à cette image vidéo.

Cette liste présente un intérêt particulier : mediaTime. L'implémentation de Chromium utilise l'horloge audio comme source temporelle qui sauvegarde video.currentTime, tandis que mediaTime est directement renseigné par la presentationTimestamp de l'image. mediaTime est l'option à utiliser si vous souhaitez identifier précisément les images de manière reproductible, y compris pour identifier exactement celles que vous avez manquées.

Si les choses vous semblent décalées...

La synchronisation verticale (ou simplement vsync) est une technologie graphique qui synchronise la fréquence d'images d'une vidéo et celle d'un écran. Étant donné que requestVideoFrameCallback() s'exécute sur le thread principal, mais que la composition vidéo s'effectue en arrière-plan sur le thread du compositeur, tout le travail de cette API est effectué au mieux et le navigateur n'offre aucune garantie stricte. Il se peut que l'API soit en retard d'une synchronisation vsync par rapport au moment où une image vidéo est affichée. Il faut une vsync pour que les modifications apportées à la page Web via l'API s'affichent à l'écran (identique à window.requestAnimationFrame()). Ainsi, si vous mettez à jour le mediaTime ou le numéro de frame sur votre page Web et que vous le comparez aux images vidéo numérotées, la vidéo finira par avoir l'air d'avoir une image en avance.

En réalité, le frame est prêt au niveau de vsync x, le rappel est déclenché et le frame est rendu à vsync x+1, et les modifications apportées au rappel sont affichées avec vsync x+2. Vous pouvez vérifier si le rappel est une vsync tardive (et si le frame est déjà affiché à l'écran) en vérifiant si metadata.expectedDisplayTime est à peu près now ou vsync à venir. S'il se situe environ cinq à dix microsecondes après now, le frame est déjà affiché. Si expectedDisplayTime a lieu environ 16 millisecondes dans le futur (en supposant que votre navigateur ou votre écran s'actualise à 60 Hz), vous êtes synchronisé avec le frame.

Démonstration

J'ai créé une petite démonstration sur Glitch qui montre comment les images sont dessinées sur un canevas avec exactement la fréquence d'images de la vidéo et où les métadonnées des images sont consignées à des fins de débogage.

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);

Conclusions

Les utilisateurs effectuent depuis longtemps le traitement au niveau des frames, sans avoir accès aux frames eux-mêmes, uniquement en fonction de video.currentTime. La méthode requestVideoFrameCallback() améliore considérablement cette solution de contournement.

Remerciements

L'API requestVideoFrameCallback a été spécifiée et implémentée par Thomas Guilbert. Ce post a été examiné par Joe Medley et Kayce Basques. Image héros de Denise Jans sur Unsplash.