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'un nouveau frame vidéo est envoyé au compositeur. Cela permet aux développeurs d'effectuer des opérations efficaces par frame sur la vidéo, telles que le traitement et la peinture vidéo 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'un frame vidéo sur un canevas à l'aide de drawImage() effectuées via cette API seront synchronisées dans la mesure du possible avec la fréquence d'images de la vidéo lue à l'écran. Contrairement à window.requestAnimationFrame(), qui se déclenche généralement environ 60 fois par seconde, requestVideoFrameCallback() est lié au débit d'images vidéo réel, à une exception importante:

La fréquence effective à laquelle les rappels sont exécutés correspond à la fréquence la plus faible entre celle de la vidéo et celle du navigateur. Cela signifie qu'une vidéo 25 FPS lue dans un navigateur qui peint à 60 Hz déclenche 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 similitude avec window.requestAnimationFrame(), la méthode a d'abord été proposée sous le nom video.requestAnimationFrame() et a été rebaptisée requestVideoFrameCallback(), ce qui a été convenu après une discussion approfondie.

Détection de fonctionnalités

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

Prise en charge des navigateurs

Navigateurs pris en charge

  • Chrome: 83.
  • Edge: 83.
  • Firefox: 132.
  • Safari: 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 la section README.

À l'aide de la méthode requestVideoFrameCallback()

Si vous avez déjà utilisé la méthode requestAnimationFrame(), vous vous sentirez immédiatement à l'aise avec la méthode requestVideoFrameCallback(). Vous enregistrez un rappel initial une fois, puis le réenregistrez 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 : heure à laquelle l'agent utilisateur a envoyé le frame pour la composition.
  • expectedDisplayTime, de type DOMHighResTimeStamp : heure à laquelle l'agent utilisateur s'attend à ce que le frame soit visible.
  • width, de type unsigned long : largeur du frame vidéo, en pixels multimédias.
  • height, de type unsigned long : hauteur du frame vidéo, en pixels multimédias.
  • mediaTime, de type double : code temporel de présentation multimédia (PTS) en secondes du frame présenté (par exemple, son code temporel sur la chronologie video.currentTime).
  • presentedFrames, de type unsigned long : nombre de frames envoyés pour la composition. Permet aux clients de déterminer si des images ont été manquées 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 ce frame (par exemple, le même que le mediaTime) au décodeur jusqu'à ce que le frame décodé soit prêt à être présenté.

Pour les applications WebRTC, d'autres propriétés 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, l'heure de capture est estimée à l'aide de la synchronisation d'horloge et des rapports d'expéditeur RTCP pour convertir les codes temporels 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 l'image encodée a été reçue par la plate-forme, c'est-à-dire l'heure à laquelle le dernier paquet appartenant à cette image a été reçu sur le réseau.
  • rtpTimestamp, de type unsigned long : code temporel RTP associé à ce frame vidéo.

mediaTime est particulièrement intéressant dans cette liste. L'implémentation de Chromium utilise l'horloge audio comme source de temps pour video.currentTime, tandis que mediaTime est renseigné directement par presentationTimestamp du frame. Vous devez utiliser mediaTime si vous souhaitez identifier exactement les images de manière reproductible, y compris pour identifier exactement les images que vous avez manquées.

Si les éléments semblent décalés d'un frame :

La synchronisation verticale (ou vsync) est une technologie graphique qui synchronise la fréquence d'images d'une vidéo et la fréquence d'actualisation d'un écran. Étant donné que requestVideoFrameCallback() s'exécute sur le thread principal, mais que, sous le capot, le compositing vidéo se produit sur le thread du compositeur, tout ce qui provient de cette API est fait de son mieux, et le navigateur n'offre aucune garantie stricte. Il se peut que l'API soit en retard d'une synchronisation verticale par rapport au moment où un frame vidéo est affiché. Un vsync est nécessaire pour que les modifications apportées à la page Web via l'API s'affichent à l'écran (comme pour window.requestAnimationFrame()). Par conséquent, si vous continuez à mettre à jour le mediaTime ou le numéro de frame sur votre page Web et à le comparer aux frames vidéo numérotés, la vidéo semblera être en avance d'un frame.

En réalité, le frame est prêt à l'étape vsync x, le rappel est déclenché et le frame est affiché à l'étape vsync x+1, et les modifications apportées dans le rappel sont affichées à l'étape vsync x+2. Vous pouvez vérifier si le rappel est un vsync tardif (et si le frame est déjà affiché à l'écran) en vérifiant si metadata.expectedDisplayTime est approximativement now ou un vsync dans le futur. Si elle se situe entre cinq et dix microsecondes après now, le frame est déjà affiché. Si expectedDisplayTime est à environ seize millisecondes dans le futur (en supposant que votre navigateur/écran s'actualise à 60 Hz), vous êtes synchronisé avec le frame.

Démo

J'ai créé une petite démo sur Glitch qui montre comment les images sont dessinées sur un canevas à exactement la fréquence d'images de la vidéo et où les métadonnées des images sont enregistré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 un traitement au niveau des frames, sans avoir accès aux frames réels, uniquement sur la base 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. Cet article a été examiné par Joe Medley et Kayce Basques. Image héros par Denise Jans sur Unsplash.