WebCodecs সঙ্গে ভিডিও প্রক্রিয়াকরণ

ভিডিও স্ট্রিম উপাদানগুলির হেরফের।

ইউজিন জেমৎসভ
Eugene Zemtsov
ফ্রাঁসোয়া বিউফোর্ট
François Beaufort

আধুনিক ওয়েব প্রযুক্তি ভিডিওর সাথে কাজ করার জন্য প্রচুর উপায় প্রদান করে। মিডিয়া স্ট্রিম API , মিডিয়া রেকর্ডিং API , মিডিয়া সোর্স API , এবং WebRTC API ভিডিও স্ট্রিম রেকর্ডিং, ট্রান্সফার এবং প্লে করার জন্য একটি সমৃদ্ধ টুল সেট তৈরি করে। কিছু উচ্চ-স্তরের কাজ সমাধান করার সময়, এই APIগুলি ওয়েব প্রোগ্রামারদের ভিডিও স্ট্রিমের পৃথক উপাদান যেমন ফ্রেম এবং এনকোডেড ভিডিও বা অডিওর আনমাক্সড অংশগুলির সাথে কাজ করতে দেয় না। এই মৌলিক উপাদানগুলিতে নিম্ন-স্তরের অ্যাক্সেস পেতে, ডেভেলপাররা ব্রাউজারে ভিডিও এবং অডিও কোডেক আনতে WebAssembly ব্যবহার করে আসছে। কিন্তু যেহেতু আধুনিক ব্রাউজারগুলি ইতিমধ্যেই বিভিন্ন ধরণের কোডেক (যা প্রায়শই হার্ডওয়্যার দ্বারা ত্বরান্বিত হয়) দিয়ে আসে, তাই WebAssembly হিসাবে সেগুলিকে পুনরায় প্যাকেজ করা মানব এবং কম্পিউটার সম্পদের অপচয় বলে মনে হয়।

ওয়েবকোডেক্স এপিআই প্রোগ্রামারদের ব্রাউজারে ইতিমধ্যে উপস্থিত মিডিয়া উপাদানগুলি ব্যবহার করার একটি উপায় দিয়ে এই অদক্ষতা দূর করে। বিশেষ করে:

  • ভিডিও এবং অডিও ডিকোডার
  • ভিডিও এবং অডিও এনকোডার
  • অ-প্রকাশিত ভিডিও ফ্রেম
  • চিত্র ডিকোডার

ওয়েবকোডেক্স এপিআই এমন ওয়েব অ্যাপ্লিকেশনগুলির জন্য কার্যকর যেগুলির জন্য মিডিয়া কন্টেন্ট প্রক্রিয়াকরণের উপর সম্পূর্ণ নিয়ন্ত্রণ প্রয়োজন, যেমন ভিডিও এডিটর, ভিডিও কনফারেন্সিং, ভিডিও স্ট্রিমিং ইত্যাদি।

ভিডিও প্রক্রিয়াকরণ কর্মপ্রবাহ

ভিডিও প্রক্রিয়াকরণের কেন্দ্রবিন্দু হলো ফ্রেম। তাই ওয়েবকোডেক্সে বেশিরভাগ ক্লাস হয় ফ্রেম ব্যবহার করে অথবা তৈরি করে। ভিডিও এনকোডাররা ফ্রেমগুলিকে এনকোডেড অংশে রূপান্তর করে। ভিডিও ডিকোডাররা ঠিক বিপরীত কাজ করে।

এছাড়াও VideoFrame অন্যান্য ওয়েব API গুলির সাথে সুন্দরভাবে কাজ করে কারণ এটি একটি CanvasImageSource এবং একটি কনস্ট্রাক্টর রয়েছে যা CanvasImageSource গ্রহণ করে। তাই এটি drawImage() এবং texImage2D() এর মতো ফাংশনগুলিতে ব্যবহার করা যেতে পারে। এছাড়াও এটি ক্যানভাস, বিটম্যাপ, ভিডিও উপাদান এবং অন্যান্য ভিডিও ফ্রেম থেকে তৈরি করা যেতে পারে।

WebCodecs API, Insertable Streams API এর ক্লাসগুলির সাথে ভালোভাবে কাজ করে যা WebCodecs কে মিডিয়া স্ট্রিম ট্র্যাকের সাথে সংযুক্ত করে।

  • MediaStreamTrackProcessor মিডিয়া ট্র্যাকগুলিকে পৃথক ফ্রেমে ভেঙে দেয়।
  • MediaStreamTrackGenerator ফ্রেমের একটি স্ট্রিম থেকে একটি মিডিয়া ট্র্যাক তৈরি করে।

ওয়েবকোডেক এবং ওয়েব কর্মীরা

ডিজাইন অনুসারে WebCodecs API সমস্ত ভারী কাজ অ্যাসিঙ্ক্রোনাসভাবে এবং মূল থ্রেডের বাইরে করে। কিন্তু যেহেতু ফ্রেম এবং চাঙ্ক কলব্যাকগুলি প্রায়শই প্রতি সেকেন্ডে একাধিকবার কল করা যেতে পারে, তাই তারা মূল থ্রেডকে বিশৃঙ্খল করে তুলতে পারে এবং এর ফলে ওয়েবসাইটটি কম প্রতিক্রিয়াশীল হয়ে উঠতে পারে। অতএব, পৃথক ফ্রেম এবং এনকোডেড চাঙ্কগুলির পরিচালনা একটি ওয়েব ওয়ার্কারে স্থানান্তর করা বাঞ্ছনীয়।

এই ক্ষেত্রে সাহায্য করার জন্য, ReadableStream একটি মিডিয়া ট্র্যাক থেকে আসা সমস্ত ফ্রেম কর্মীর কাছে স্বয়ংক্রিয়ভাবে স্থানান্তর করার একটি সুবিধাজনক উপায় প্রদান করে। উদাহরণস্বরূপ, ওয়েব ক্যামেরা থেকে আসা মিডিয়া স্ট্রিম ট্র্যাকের জন্য একটি ReadableStream পেতে MediaStreamTrackProcessor ব্যবহার করা যেতে পারে। এর পরে স্ট্রিমটি একটি ওয়েব কর্মীর কাছে স্থানান্তরিত হয় যেখানে ফ্রেমগুলি একে একে পড়া হয় এবং একটি VideoEncoder এ সারিবদ্ধ করা হয়।

HTMLCanvasElement.transferControlToOffscreen ব্যবহার করে মূল থ্রেড থেকেও রেন্ডারিং করা সম্ভব। কিন্তু যদি সমস্ত উচ্চ স্তরের সরঞ্জাম অসুবিধাজনক হয়, তাহলে VideoFrame নিজেই স্থানান্তরযোগ্য এবং কর্মীদের মধ্যে স্থানান্তরিত হতে পারে।

ওয়েবকোডেকস কার্যকর হচ্ছে

এনকোডিং

ক্যানভাস বা ইমেজবিটম্যাপ থেকে নেটওয়ার্ক বা স্টোরেজে যাওয়ার পথ
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 দুটি জাভাস্ক্রিপ্ট অবজেক্ট দিতে হবে:

  • এনকোডেড অংশ এবং ত্রুটি পরিচালনার জন্য দুটি ফাংশন সহ 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 দেখায় যে পূর্ববর্তী এনকোডগুলি সম্পন্ন হওয়ার জন্য কতগুলি অনুরোধ সারিতে অপেক্ষা করছে। ত্রুটিগুলি হয় অবিলম্বে একটি ব্যতিক্রম নিক্ষেপ করে রিপোর্ট করা হয়, যদি আর্গুমেন্ট বা পদ্ধতি কলের ক্রম API চুক্তি লঙ্ঘন করে, অথবা কোডেক বাস্তবায়নে সমস্যাগুলির জন্য 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();

ডিকোডিং

নেটওয়ার্ক বা স্টোরেজ থেকে ক্যানভাস বা ইমেজবিটম্যাপে যাওয়ার পথ।
নেটওয়ার্ক বা স্টোরেজ থেকে Canvas বা ImageBitmap যাওয়ার পথ।

VideoDecoder সেট আপ করা VideoEncoder এর মতোই: ডিকোডার তৈরি করার সময় দুটি ফাংশন পাস করা হয় এবং configure() কে কোডেক প্যারামিটার দেওয়া হয়।

কোডেক প্যারামিটারের সেট কোডেক থেকে কোডেকে পরিবর্তিত হয়। উদাহরণস্বরূপ, H.264 কোডেকের জন্য AVCC এর একটি বাইনারি ব্লবের প্রয়োজন হতে পারে, যদি না এটি তথাকথিত Annex 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);
}

ডেভেলপার টিপস

মিডিয়া লগ দেখতে এবং ওয়েবকোডেক ডিবাগ করতে Chrome DevTools-এ মিডিয়া প্যানেল ব্যবহার করুন।

ওয়েবকোডেক্স ডিবাগ করার জন্য মিডিয়া প্যানেলের স্ক্রিনশট
ওয়েবকোডেক ডিবাগ করার জন্য Chrome DevTools-এ মিডিয়া প্যানেল।

ডেমো

ডেমোটি দেখায় যে ক্যানভাসের অ্যানিমেশন ফ্রেমগুলি কেমন:

  • MediaStreamTrackProcessor দ্বারা একটি ReadableStream এ 25fps-এ ক্যাপচার করা হয়েছে
  • একজন ওয়েব কর্মীর কাছে স্থানান্তরিত
  • H.264 ভিডিও ফর্ম্যাটে এনকোড করা হয়েছে
  • আবার ভিডিও ফ্রেমের ক্রমানুসারে ডিকোড করা হয়েছে
  • এবং transferControlToOffscreen() ব্যবহার করে দ্বিতীয় ক্যানভাসে রেন্ডার করা হয়েছে।

অন্যান্য ডেমো

আমাদের অন্যান্য ডেমোগুলিও দেখুন:

WebCodecs API ব্যবহার করে

বৈশিষ্ট্য সনাক্তকরণ

WebCodecs সাপোর্ট চেক করতে:

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

মনে রাখবেন যে WebCodecs API শুধুমাত্র নিরাপদ প্রসঙ্গে উপলব্ধ, তাই self.isSecureContext মিথ্যা হলে সনাক্তকরণ ব্যর্থ হবে।

আরও জানুন

আপনি যদি WebCodecs-এ নতুন হন, তাহলে WebCodecs Fundamentals আপনাকে আরও শিখতে সাহায্য করার জন্য অনেক উদাহরণ সহ গভীর নিবন্ধ সরবরাহ করে।

প্রতিক্রিয়া

Chrome টিম WebCodecs API এর সাথে আপনার অভিজ্ঞতা সম্পর্কে শুনতে চায়।

API ডিজাইন সম্পর্কে আমাদের বলুন

API-তে কি এমন কিছু আছে যা আপনার প্রত্যাশা অনুযায়ী কাজ করছে না? অথবা আপনার ধারণা বাস্তবায়নের জন্য প্রয়োজনীয় পদ্ধতি বা বৈশিষ্ট্যের অভাব আছে? নিরাপত্তা মডেল সম্পর্কে কোন প্রশ্ন বা মন্তব্য আছে? সংশ্লিষ্ট GitHub রেপোতে একটি স্পেক সমস্যা ফাইল করুন, অথবা বিদ্যমান কোনও সমস্যায় আপনার মতামত যোগ করুন।

বাস্তবায়নে কোনও সমস্যার কথা জানান

Chrome এর বাস্তবায়নে কি আপনি কোন বাগ খুঁজে পেয়েছেন? নাকি বাস্তবায়নটি স্পেসিফিকেশন থেকে আলাদা? new.crbug.com এ একটি বাগ ফাইল করুন। যতটা সম্ভব বিস্তারিত তথ্য, পুনরুৎপাদনের জন্য সহজ নির্দেশাবলী অন্তর্ভুক্ত করতে ভুলবেন না এবং Components বাক্সে Blink>Media>WebCodecs লিখুন।

API এর জন্য সমর্থন দেখান

আপনি কি WebCodecs API ব্যবহার করার পরিকল্পনা করছেন? আপনার সর্বজনীন সমর্থন Chrome টিমকে বৈশিষ্ট্যগুলিকে অগ্রাধিকার দিতে সাহায্য করে এবং অন্যান্য ব্রাউজার বিক্রেতাদের দেখায় যে তাদের সমর্থন করা কতটা গুরুত্বপূর্ণ।

media-dev@chromium.org ঠিকানায় ইমেল পাঠান অথবা #WebCodecs হ্যাশট্যাগ ব্যবহার করে @ChromiumDev ঠিকানায় একটি টুইট পাঠান এবং আপনি এটি কোথায় এবং কীভাবে ব্যবহার করছেন তা আমাদের জানান।

আনস্প্ল্যাশে ডেনিস জ্যান্সের হিরো ছবি