WebCodecs के साथ वीडियो प्रोसेस करना

वीडियो स्ट्रीम के कॉम्पोनेंट में बदलाव किया जा रहा है.

Eugene Zemtsov
Eugene Zemtsov
François Beaufort
François Beaufort

आधुनिक वेब टेक्नोलॉजी, वीडियो पर काम करने के कई तरीके उपलब्ध कराती हैं. Media Stream API, मीडिया रिकॉर्डिंग एपीआई, मीडिया सोर्स एपीआई, और WebRTC API ऐड-अप को एक शानदार टूल सेट का इस्तेमाल करना शुरू कर दें. इससे वीडियो स्ट्रीम रिकॉर्ड करने, ट्रांसफ़र करने, और चलाने में मदद मिलती है. कुछ हाई-लेवल टास्क को हल करते समय, ये एपीआई वेब पर प्रोग्रामर, वीडियो स्ट्रीम के अलग-अलग कॉम्पोनेंट, जैसे कि फ़्रेम के साथ काम करते हैं और एन्कोड किए गए वीडियो या ऑडियो के अनम्यूट किए गए हिस्से. इन बेसिक कॉम्पोनेंट का लो-लेवल ऐक्सेस पाने के लिए, डेवलपर ब्राउज़र में वीडियो और ऑडियो कोडेक लाने के लिए WebAssembly का इस्तेमाल करें. लेकिन दिए गए कि मॉडर्न ब्राउज़र पहले से ही कई तरह के कोडेक के साथ आते हैं (जो अक्सर जैसे, हार्डवेयर की मदद से फटाफट किया जाता है), तो उन्हें WebAssembly के तौर पर रीपैकेज करने से, मानव और कंप्यूटर संसाधन शामिल हैं.

WebCodecs API इस कमी को ठीक करता है इससे प्रोग्रामर को उन मीडिया कॉम्पोनेंट को इस्तेमाल करने का तरीका दिया जाता है जो पहले से ही ब्राउज़र खोलें. खास तौर पर:

  • वीडियो और ऑडियो डिकोडर
  • वीडियो और ऑडियो एन्कोडर
  • रॉ वीडियो फ़्रेम
  • इमेज डिकोडर

WebCodecs API उन वेब ऐप्लिकेशन के लिए उपयोगी है जिन्हें मीडिया कॉन्टेंट को प्रोसेस करने के तरीक़े, जैसे कि वीडियो एडिटर, वीडियो कॉन्फ़्रेंसिंग, वीडियो कॉल स्ट्रीमिंग वगैरह.

वीडियो प्रोसेस करने का वर्कफ़्लो

वीडियो प्रोसेस करने के लिए फ़्रेम, सेंटरपीस की तरह काम करते हैं. WebCodecs में ज़्यादातर क्लास इस्तेमाल करने या बनाने में मदद करता है. वीडियो एन्कोडर, फ़्रेम को कोड में बदलते हैं हिस्से. हालांकि, वीडियो डिकोडर इसके उलट होते हैं.

इसके अलावा, CanvasImageSource होने और CanvasImageSource को स्वीकार करने वाला कंस्ट्रक्टर होने की वजह से, VideoFrame अन्य वेब एपीआई के साथ अच्छे से काम करता है. इसलिए, इसका इस्तेमाल drawImage() औरtexImage2D() जैसे फ़ंक्शन में किया जा सकता है. इन्हें कैनवस, बिटमैप, वीडियो एलिमेंट, और अन्य वीडियो फ़्रेम से भी बनाया जा सकता है.

WebCodecs API, Insertable Streams API की क्लास के साथ अच्छे से काम करता है जो WebCodecs को मीडिया स्ट्रीम ट्रैक से कनेक्ट करते हैं.

  • MediaStreamTrackProcessor, मीडिया ट्रैक को अलग-अलग फ़्रेम में बांटता है.
  • MediaStreamTrackGenerator, कई फ़्रेम से एक मीडिया ट्रैक बनाता है.

WebCodecs और वेब वर्कर

WebCodecs API के डिज़ाइन की वजह से, यह मुख्य थ्रेड के अलावा बाकी काम एसिंक्रोनस तरीके से ही करता है. हालांकि, फ़्रेम और हिस्से कॉलबैक को अक्सर एक सेकंड में कई बार कॉल किया जा सकता है, वे मुख्य थ्रेड को अव्यवस्थित कर सकती हैं और इस तरह वेबसाइट के काम करने के तरीके को कम कर सकते हैं. इसलिए, अलग-अलग फ़्रेम और कोड में बदले गए हिस्सों को वेब वर्कर.

इसमें मदद करने के लिए, ReadableStream पर जाएं इससे मीडिया से आने वाले सभी फ़्रेम को अपने-आप ट्रांसफ़र होने का आसान तरीका मिल जाता है उस कर्मचारी को ट्रैक कर सकता है. उदाहरण के लिए, MediaStreamTrackProcessor का इस्तेमाल करके वेब कैमरे से आने वाले मीडिया स्ट्रीम ट्रैक के लिए ReadableStream. उसके बाद स्ट्रीम को किसी वेब वर्कर पर ट्रांसफ़र किया जाता है, जहां फ़्रेम को एक-एक करके पढ़ा जाता है और सूची में जोड़ा जाता है VideoEncoder में बदल दिया जाएगा.

HTMLCanvasElement.transferControlToOffscreen की मदद से, रेंडरिंग को मुख्य थ्रेड से बाहर भी किया जा सकता है. लेकिन अगर सभी हाई लेवल टूल असुविधाजनक होता है, इसलिए VideoFrame ही ट्रांसफ़र किया जा सकता है और हो सकता है कर्मचारियों के बीच स्विच कर गया.

कार्रवाई में WebCodecs

एन्कोडिंग

कैनवस या ImageBitmap से नेटवर्क या स्टोरेज का पाथ
Canvas या ImageBitmap से नेटवर्क या स्टोरेज तक का पाथ

यह VideoFrame से शुरू होता है. वीडियो फ़्रेम बनाने के तीन तरीके हैं.

  • कैनवस, इमेज बिटमैप या वीडियो एलिमेंट जैसे किसी इमेज सोर्स से.

    const canvas = document.createElement("canvas");
    // Draw something on the canvas...
    
    const frameFromCanvas = new VideoFrame(canvas, { timestamp: 0 });
    
  • किसी MediaStreamTrack से फ़्रेम खींचने के लिए MediaStreamTrackProcessor का इस्तेमाल करें

    const stream = await navigator.mediaDevices.getUserMedia({});
    const track = stream.getTracks()[0];
    
    const trackProcessor = new MediaStreamTrackProcessor(track);
    
    const reader = trackProcessor.readable.getReader();
    while (true) {
      const result = await reader.read();
      if (result.done) break;
      const frameFromCamera = result.value;
    }
    
  • BufferSource में, बाइनरी पिक्सल रिप्रज़ेंटेशन से फ़्रेम बनाएं

    const pixelSize = 4;
    const init = {
      timestamp: 0,
      codedWidth: 320,
      codedHeight: 200,
      format: "RGBA",
    };
    const data = new Uint8Array(init.codedWidth * init.codedHeight * pixelSize);
    for (let x = 0; x < init.codedWidth; x++) {
      for (let y = 0; y < init.codedHeight; y++) {
        const offset = (y * init.codedWidth + x) * pixelSize;
        data[offset] = 0x7f;      // Red
        data[offset + 1] = 0xff;  // Green
        data[offset + 2] = 0xd4;  // Blue
        data[offset + 3] = 0x0ff; // Alpha
      }
    }
    const frame = new VideoFrame(data, init);
    

चाहे वे कहीं से भी आ रहे हों, फ़्रेम कोड में बदला जा सकता है VideoEncoder के साथ EncodedVideoChunk ऑब्जेक्ट.

कोड में बदलने के पहले, VideoEncoder को दो JavaScript ऑब्जेक्ट देना होगा:

  • कोड में बदले गए हिस्सों को हैंडल करने के लिए, दो फ़ंक्शन के साथ Init डिक्शनरी और गड़बड़ियां हैं. ये फ़ंक्शन डेवलपर ने तय किए हैं और इन्हें इस तारीख के बाद बदला नहीं जा सकता वे VideoEncoder कंस्ट्रक्टर को पास कर दिए जाते हैं.
  • एन्कोडर कॉन्फ़िगरेशन ऑब्जेक्ट, जिसमें आउटपुट के लिए पैरामीटर शामिल होते हैं वीडियो स्ट्रीम. ये पैरामीटर बाद में configure() को कॉल करके बदले जा सकते हैं.

कॉन्फ़िगरेशन न होने पर configure() पद्धति NotSupportedError फेंक देगी जो ब्राउज़र पर काम करता है. आपको स्टैटिक मेथड को कॉल करने के लिए प्रोत्साहित किया जाता है कॉन्फ़िगरेशन के साथ VideoEncoder.isConfigSupported(), जिससे पहले यह जांच की जा सकती है कि क्या कॉन्फ़िगरेशन काम करता है और प्रॉमिस का इंतज़ार करें.

const init = {
  output: handleChunk,
  error: (e) => {
    console.log(e.message);
  },
};

const config = {
  codec: "vp8",
  width: 640,
  height: 480,
  bitrate: 2_000_000, // 2 Mbps
  framerate: 30,
};

const { supported } = await VideoEncoder.isConfigSupported(config);
if (supported) {
  const encoder = new VideoEncoder(init);
  encoder.configure(config);
} else {
  // Try another config.
}

एन्कोडर सेट अप होने के बाद, encode() तरीके से फ़्रेम स्वीकार किए जा सकते हैं. configure() और encode(), दोनों इसकी इंतज़ार किए बिना तुरंत वापस आ जाएंगे काम नहीं करना है. यह कई फ़्रेम को एनकोडिंग के लिए उसी समय, जबकि encodeQueueSize दिखाता है कि कतार में कितने अनुरोध इंतज़ार कर रहे हैं प्रोसेस करने की प्रोसेस पूरी करें. आर्ग्युमेंट के तौर पर, गड़बड़ियों को तुरंत अपवाद के तौर पर मार्क किया जाता है या तरीके से आने वाले कॉल का क्रम, एपीआई समझौते का उल्लंघन करता है या error() पर कॉल करके कोडेक लागू करने में आई समस्याओं के लिए कॉलबैक. अगर कोड में बदलने का तरीका output() को पूरा करता है कॉलबैक को आर्ग्युमेंट के तौर पर, कोड में बदले गए नए हिस्से के साथ कॉल किया जाता है. यहां एक और ज़रूरी बात यह है कि फ़्रेम को बताना ज़रूरी है कि वे कब कोई फ़्रेम नहीं हैं ज़्यादा समय के लिए close() पर कॉल करें.

let frameCounter = 0;

const track = stream.getVideoTracks()[0];
const trackProcessor = new MediaStreamTrackProcessor(track);

const reader = trackProcessor.readable.getReader();
while (true) {
  const result = await reader.read();
  if (result.done) break;

  const frame = result.value;
  if (encoder.encodeQueueSize > 2) {
    // Too many frames in flight, encoder is overwhelmed
    // let's drop this frame.
    frame.close();
  } else {
    frameCounter++;
    const keyFrame = frameCounter % 150 == 0;
    encoder.encode(frame, { keyFrame });
    frame.close();
  }
}

अंत में यह एक ऐसा फ़ंक्शन लिखकर एन्कोडिंग कोड को पूरा करने का समय है जो कोड में बदले गए वीडियो के हिस्से. आम तौर पर, यह फ़ंक्शन डेटा के अलग-अलग हिस्सों को नेटवर्क पर भेजता है या उन्हें किसी मीडिया में मक्स करना करता है कंटेनर में स्टोर करें.

function handleChunk(chunk, metadata) {
  if (metadata.decoderConfig) {
    // Decoder needs to be configured (or reconfigured) with new parameters
    // when metadata has a new decoderConfig.
    // Usually it happens in the beginning or when the encoder has a new
    // codec specific binary configuration. (VideoDecoderConfig.description).
    fetch("/upload_extra_data", {
      method: "POST",
      headers: { "Content-Type": "application/octet-stream" },
      body: metadata.decoderConfig.description,
    });
  }

  // actual bytes of encoded data
  const chunkData = new Uint8Array(chunk.byteLength);
  chunk.copyTo(chunkData);

  fetch(`/upload_chunk?timestamp=${chunk.timestamp}&type=${chunk.type}`, {
    method: "POST",
    headers: { "Content-Type": "application/octet-stream" },
    body: chunkData,
  });
}

अगर कभी आपको यह पक्का करना पड़े कि कोड में बदलने के सभी लंबित अनुरोधों में पूरा हो गया है, तो आपके पास flush() को कॉल करके, इसके प्रॉमिस का इंतज़ार करने का विकल्प है.

await encoder.flush();

डिकोड किया जा रहा है

नेटवर्क या स्टोरेज से कैनवस या ImageBitmap का पाथ.
नेटवर्क या स्टोरेज से Canvas या ImageBitmap का पाथ.

VideoDecoder को सेट अप करना, VideoEncoder: डिकोडर बनाने के दौरान, दो फ़ंक्शन पास किए जाते हैं और कोडेक पैरामीटर configure() को दिए जाते हैं.

कोडेक के पैरामीटर का सेट, कोडेक से अलग-अलग हो सकता है. उदाहरण के लिए, H.264 कोडेक एक बाइनरी ब्लॉब की ज़रूरत पड़ सकती है का इस्तेमाल करेगा. ऐसा तब तक होगा, जब तक इसे बताए गए ऐनेक्स B फ़ॉर्मैट (encoderConfig.avc = { format: "annexb" }) में एन्कोड नहीं किया जाता.

const init = {
  output: handleFrame,
  error: (e) => {
    console.log(e.message);
  },
};

const config = {
  codec: "vp8",
  codedWidth: 640,
  codedHeight: 480,
};

const { supported } = await VideoDecoder.isConfigSupported(config);
if (supported) {
  const decoder = new VideoDecoder(init);
  decoder.configure(config);
} else {
  // Try another config.
}

डिकोडर शुरू होने के बाद, उसे EncodedVideoChunk ऑब्जेक्ट से फ़ीड किया जा सकता है. डेटा का हिस्सा बनाने के लिए, आपको इनकी ज़रूरत होगी:

  • कोड में बदले गए वीडियो डेटा का BufferSource
  • माइक्रोसेकंड में डेटा ग्रुप के शुरू होने का टाइमस्टैंप (सेगमेंट में, कोड में बदले गए पहले फ़्रेम का मीडिया समय)
  • डेटा समूह का टाइप, इनमें से एक:
    • key अगर इस हिस्से को पिछले हिस्सों से अलग, अलग किया जा सकता हो
    • delta, अगर पहले समूह को सिर्फ़ एक या एक से ज़्यादा हिस्सों को डिकोड करने के बाद ही डिकोड किया जा सकता हो

इसके अलावा, एन्कोडर से उत्सर्जित होने वाले सभी हिस्से भी डिकोडर के लिए तैयार रहते हैं. गड़बड़ी की रिपोर्ट करने और एसिंक्रोनस के बारे में ऊपर बताई गई सभी बातें पूरी तरह से डिकोडर के लिए समान रूप से सही हैं.

const responses = await downloadVideoChunksFromServer(timestamp);
for (let i = 0; i < responses.length; i++) {
  const chunk = new EncodedVideoChunk({
    timestamp: responses[i].timestamp,
    type: responses[i].key ? "key" : "delta",
    data: new Uint8Array(responses[i].body),
  });
  decoder.decode(chunk);
}
await decoder.flush();

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

  1. फ़्रेम दिखाने के लिए सही समय का इंतज़ार किया जा रहा है.
  2. कैनवस पर फ़्रेम बनाना.

फ़्रेम की ज़रूरत न होने पर, दी गई 'यादें' को रिलीज़ करने के लिए close() पर कॉल करें कचरा इकट्ठा करने वाले व्यक्ति के पास तक पहुंचने से पहले, वेब ऐप्लिकेशन में इस्तेमाल की गई मेमोरी.

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let pendingFrames = [];
let underflow = true;
let baseTime = 0;

function handleFrame(frame) {
  pendingFrames.push(frame);
  if (underflow) setTimeout(renderFrame, 0);
}

function calculateTimeUntilNextFrame(timestamp) {
  if (baseTime == 0) baseTime = performance.now();
  let mediaTime = performance.now() - baseTime;
  return Math.max(0, timestamp / 1000 - mediaTime);
}

async function renderFrame() {
  underflow = pendingFrames.length == 0;
  if (underflow) return;

  const frame = pendingFrames.shift();

  // Based on the frame's timestamp calculate how much of real time waiting
  // is needed before showing the next frame.
  const timeUntilNextFrame = calculateTimeUntilNextFrame(frame.timestamp);
  await new Promise((r) => {
    setTimeout(r, timeUntilNextFrame);
  });
  ctx.drawImage(frame, 0, 0);
  frame.close();

  // Immediately schedule rendering of the next frame
  setTimeout(renderFrame, 0);
}

डेवलपर के लिए सलाह

मीडिया पैनल का इस्तेमाल करना मीडिया लॉग देखने और WebCodecs डीबग करने के लिए Chrome DevTools में.

WebCodecs को डीबग करने के लिए मीडिया पैनल का स्क्रीनशॉट
WebCodecs डीबग करने के लिए Chrome DevTools में मीडिया पैनल.

डेमो

नीचे दिया गया डेमो दिखाता है कि कैनवस से ऐनिमेशन फ़्रेम कैसे होते हैं:

  • MediaStreamTrackProcessor ने 25 एफ़पीएस (फ़्रेम प्रति सेकंड) से ReadableStream में कैप्चर किया
  • वेब वर्कर को ट्रांसफ़र किया गया
  • H.264 वीडियो फ़ॉर्मैट में एन्कोड किया गया
  • वीडियो फ़्रेम के क्रम में फिर से डिकोड किया गया
  • और transferControlToOffscreen() का इस्तेमाल करके दूसरे कैनवस पर रेंडर किया गया

अन्य डेमो

हमारे अन्य डेमो भी देखें:

WebCodecs API का इस्तेमाल करना

सुविधा की पहचान

WebCodecs के साथ काम करने की जांच करने के लिए:

if ('VideoEncoder' in window) {
  // WebCodecs API is supported.
}

ध्यान रखें कि WebCodecs API सिर्फ़ सुरक्षित कॉन्टेक्स्ट में उपलब्ध है, इसलिए, अगर self.isSecureContext गलत है, तो पहचान नहीं की जा सकेगी.

सुझाव/राय दें या शिकायत करें

Chrome टीम, WebCodecs API के साथ आपके अनुभव जानना चाहती है.

हमें एपीआई के डिज़ाइन के बारे में बताएं

क्या एपीआई में ऐसा कुछ है जो आपकी उम्मीद के मुताबिक काम नहीं करता? या हैं क्या आपको अपने आइडिया को लागू करने के लिए कुछ तरीके या प्रॉपर्टी नहीं मिल रही हैं? क्या सुरक्षा मॉडल पर सवाल या टिप्पणी? इस पर, स्पेसिफ़िकेशन से जुड़ी समस्या की शिकायत करें संबंधित GitHub रेपो या जोड़ें किसी मौजूदा समस्या के बारे में अपने विचार बताएं.

लागू करने से जुड़ी समस्या की शिकायत करना

क्या आपको Chrome को लागू करने में कोई गड़बड़ी मिली? या, लागू करने पर क्या यह स्पेसिफ़िकेशन से अलग है? new.crbug.com पर जाकर, गड़बड़ी की शिकायत करें. ज़्यादा से ज़्यादा जानकारी शामिल करें, ताकि फिर से बनाना और घटक बॉक्स में Blink>Media>WebCodecs डालें. Glitch, जल्दी और आसान रेप्रस शेयर करने के लिए शानदार काम करता है.

यह एपीआई काम करता है

क्या आपको WebCodecs API का इस्तेमाल करना है? आपके सार्वजनिक समर्थन से उपयोगकर्ताओं की Chrome की टीम, सुविधाओं को प्राथमिकता देती है और अन्य ब्राउज़र वेंडर को बताती है कि उनकी मदद करना.

[email protected] पर ईमेल भेजें या ट्वीट भेजें हैशटैग का इस्तेमाल करके @ChromiumDev पर #WebCodecs और हमें बताएं कि उसका इस्तेमाल कहां और कैसे किया जा रहा है.

हीरो इमेज: डेनिस जैन्स Unस्प्लैश पर.