हर वीडियो फ़्रेम पर बेहतर तरीके से कार्रवाई करना

ब्राउज़र में वीडियो को ज़्यादा असरदार तरीके से इस्तेमाल करने के लिए, requestVideoFrameCallback() का इस्तेमाल करने का तरीका जानें.

पब्लिश होने की तारीख: 8 जनवरी, 2023

Browser Support

  • Chrome: 83.
  • Edge: 83.
  • Firefox: 132.
  • Safari: 15.4.

Source

HTMLVideoElement.requestVideoFrameCallback() मेथड की मदद से, वेब ऑथर एक कॉलबैक रजिस्टर कर सकते हैं. यह कॉलबैक, रेंडरिंग के चरणों में तब चलता है, जब कंपोज़िटर को नया वीडियो फ़्रेम भेजा जाता है. इससे डेवलपर, वीडियो के हर फ़्रेम पर असरदार तरीके से कार्रवाई कर सकते हैं. जैसे, वीडियो प्रोसेसिंग और कैनवस पर पेंटिंग, वीडियो विश्लेषण या बाहरी ऑडियो सोर्स के साथ सिंक्रनाइज़ेशन.

requestAnimationFrame() से अंतर

इस एपीआई की मदद से किए गए ऑपरेशन, जैसे कि drawImage() की मदद से कैनवस पर वीडियो फ़्रेम बनाना, स्क्रीन पर चल रहे वीडियो के फ़्रेम रेट के साथ सिंक किए जाते हैं. यह window.requestAnimationFrame() से अलग है. window.requestAnimationFrame() आम तौर पर, हर सेकंड में 60 बार ट्रिगर होता है.

requestVideoFrameCallback() को वीडियो के ओरिजनल फ़्रेम रेट के हिसाब से ही प्रोसेस किया जाता है. हालांकि, इसमें एक अहम अपवाद है:

कॉलबैक को जिस रेट पर चलाया जाता है वह वीडियो के रेट और ब्राउज़र के रेट में से कम होता है. इसका मतलब है कि अगर कोई ब्राउज़र 60 हर्ट्ज़ पर पेंट करता है, तो 25 एफ़पीएस वाला वीडियो 25 हर्ट्ज़ पर कॉलबैक ट्रिगर करेगा. उसी 60 हर्ट्ज़ वाले ब्राउज़र में 120 एफ़पीएस का वीडियो, 60 हर्ट्ज़ पर कॉलबैक ट्रिगर करेगा.

सुविधा का पता लगाना

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

पॉलीफ़िल

Window.requestAnimationFrame() और HTMLVideoElement.getVideoPlaybackQuality() पर आधारित, requestVideoFrameCallback() तरीके के लिए पॉलीफ़िल उपलब्ध है. इसका इस्तेमाल करने से पहले, 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, of type unsigned long: मीडिया पिक्सल में वीडियो फ़्रेम की चौड़ाई.
  • height, of type unsigned long: The height of the video frame, in media pixels.
  • mediaTime, जिसका टाइप double है: प्रस्तुत किए गए फ़्रेम का मीडिया प्रज़ेंटेशन टाइमस्टैंप (पीटीएस), सेकंड में. जैसे, video.currentTime टाइमलाइन पर उसका टाइमस्टैंप.
  • presentedFrames, जिसका टाइप unsigned long है: कंपोज़िशन के लिए सबमिट किए गए फ़्रेम की संख्या. इस कुकी की मदद से क्लाइंट यह तय कर पाते हैं कि VideoFrameRequestCallback के इंस्टेंस के बीच फ़्रेम छूट गए हैं या नहीं.
  • processingDuration, टाइप double: यह उस समय को दिखाता है जब एन्कोड किए गए पैकेट को सबमिट किया गया था. इस पैकेट का प्रज़ेंटेशन टाइमस्टैंप (पीटीएस) इस फ़्रेम (जैसे, mediaTime) के पीटीएस के बराबर है. यह समय, डिकोडर को तब तक लगता है, जब तक डिकोड किया गया फ़्रेम प्रज़ेंटेशन के लिए तैयार नहीं हो जाता. यह समय सेकंड में होता है.

WebRTC ऐप्लिकेशन के लिए, ये अतिरिक्त प्रॉपर्टी दिख सकती हैं:

  • captureTime, DOMHighResTimeStamp टाइप का है: लोकल या रिमोट सोर्स से आने वाले वीडियो फ़्रेम के लिए, यह वह समय होता है जब कैमरे ने फ़्रेम को कैप्चर किया था. रिमोट सोर्स के लिए, कैप्चर करने के समय का अनुमान लगाने के लिए, घड़ी के सिंक्रनाइज़ेशन और आरटीसीपी सेंडर रिपोर्ट का इस्तेमाल किया जाता है. इससे आरटीपी टाइमस्टैंप को कैप्चर करने के समय में बदला जा सकता है.
  • receiveTime, DOMHighResTimeStamp टाइप का: रिमोट सोर्स से आने वाले वीडियो फ़्रेम के लिए, यह वह समय होता है जब एन्कोड किया गया फ़्रेम प्लैटफ़ॉर्म को मिला था. इसका मतलब है कि यह वह समय होता है जब इस फ़्रेम से जुड़ा आखिरी पैकेट नेटवर्क पर मिला था.
  • rtpTimestamp, जिसका टाइप unsigned long है: इस वीडियो फ़्रेम से जुड़ा आरटीपी टाइमस्टैंप.

इस सूची में सबसे ज़्यादा दिलचस्पी mediaTime में है. Chromium, ऑडियो क्लॉक को टाइम सोर्स के तौर पर इस्तेमाल करता है. यह video.currentTime को बैक करता है. वहीं, mediaTime को सीधे तौर पर फ़्रेम के presentationTimestamp से पॉप्युलेट किया जाता है. अगर आपको फ़्रेम की पहचान ठीक उसी तरह से करनी है जिस तरह से उन्हें बनाया गया था, तो आपको mediaTime का इस्तेमाल करना चाहिए. इसमें यह पहचान करना भी शामिल है कि आपने कौनसे फ़्रेम छोड़ दिए हैं.

अगर आपको लगता है कि वीडियो में एक फ़्रेम का अंतर है

वर्टिकल सिंक्रनाइज़ेशन (या सिर्फ़ vsync), एक ग्राफ़िक्स टेक्नोलॉजी है. यह वीडियो के फ़्रेम रेट और मॉनिटर के रीफ़्रेश रेट को सिंक करती है. requestVideoFrameCallback() मुख्य थ्रेड पर चलता है. हालांकि, वीडियो कंपोज़िटिंग, कंपोज़िटर थ्रेड पर होती है. इसलिए, इस एपीआई से मिलने वाली हर चीज़ सबसे अच्छी होती है. साथ ही, ब्राउज़र कोई सख्त गारंटी नहीं देता है.

ऐसा हो सकता है कि वीडियो फ़्रेम रेंडर होने के मुकाबले, एपीआई एक वीसिंक बाद में हो. एपीआई के ज़रिए वेब पेज में किए गए बदलावों को स्क्रीन पर दिखने में एक वीसिंक लगता है (window.requestAnimationFrame() की तरह). इसलिए, अगर अपने वेब पेज पर mediaTime या फ़्रेम नंबर को अपडेट किया जाता है और उसकी तुलना नंबर वाले वीडियो फ़्रेम से की जाती है, तो वीडियो आखिर में एक फ़्रेम आगे दिखेगा.

असल में, vsync x पर फ़्रेम तैयार हो जाता है. इसके बाद, vsync x+1 पर कॉलबैक ट्रिगर होता है और फ़्रेम रेंडर होता है. साथ ही, vsync x+2 पर कॉलबैक में किए गए बदलाव रेंडर होते हैं. यह देखा जा सकता है कि कॉल बैक, वीसिंक से देर से हुआ है या नहीं. साथ ही, यह भी देखा जा सकता है कि फ़्रेम पहले ही स्क्रीन पर रेंडर हो गया है या नहीं. इसके लिए, यह देखना होगा कि metadata.expectedDisplayTime, now के बराबर है या वीसिंक के एक फ़्रेम के बाद है. अगर यह now के पांच से दस माइक्रोसेकंड के अंदर है, तो फ़्रेम पहले ही रेंडर हो जाता है. अगर now लगभग 16 मिलीसेकंड बाद है (मान लें कि आपका ब्राउज़र 60 हर्ट्ज़ पर रीफ़्रेश हो रहा है), तो इसका मतलब है कि आप फ़्रेम के साथ सिंक हैं.expectedDisplayTime

डेमो

मैंने एक छोटा डेमो बनाया है. इसमें दिखाया गया है कि वीडियो के फ़्रेम रेट के हिसाब से, कैनवस पर फ़्रेम कैसे बनाए जाते हैं. साथ ही, इसमें यह भी दिखाया गया है कि डीबग करने के लिए, फ़्रेम का मेटाडेटा कहां लॉग किया जाता है.

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() तरीके से, इस समस्या को हल करने के तरीके को बेहतर बनाया गया है.

Acknowledgements

requestVideoFrameCallback एपीआई को थॉमस गिल्बर्ट ने बनाया और लागू किया था. इस पोस्ट की समीक्षा जो मेडली और केसी बास्क ने की है.