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

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

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

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

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

  • ভিডিও এবং অডিও ডিকোডার
  • ভিডিও এবং অডিও এনকোডার
  • কাঁচা ভিডিও ফ্রেম
  • ইমেজ ডিকোডার

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

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

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

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

WebCodecs API ইনসার্টেবল স্ট্রীমস এপিআই- এর ক্লাসের সাথে ভালোভাবে কাজ করে যা WebCodecs কে মিডিয়া স্ট্রিম ট্র্যাকের সাথে সংযুক্ত করে।

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

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

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

এটিতে সহায়তা করার জন্য, ReadableStream একটি মিডিয়া ট্র্যাক থেকে কর্মীকে স্বয়ংক্রিয়ভাবে সমস্ত ফ্রেম স্থানান্তর করার একটি সুবিধাজনক উপায় প্রদান করে৷ উদাহরণস্বরূপ, MediaStreamTrackProcessor ওয়েব ক্যামেরা থেকে আসা একটি মিডিয়া স্ট্রিম ট্র্যাকের জন্য একটি ReadableStream পেতে ব্যবহার করা যেতে পারে। এর পরে স্ট্রীমটি একটি ওয়েব ওয়ার্কারের কাছে স্থানান্তরিত হয় যেখানে ফ্রেমগুলি একে একে পড়া হয় এবং একটি 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);
}

দেব টিপস

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

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

ডেমো

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

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

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

এছাড়াও আমাদের অন্যান্য ডেমো দেখুন:

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

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

WebCodecs সমর্থন চেক করতে:

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

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

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

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

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

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

বাস্তবায়নের সাথে একটি সমস্যা রিপোর্ট করুন

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

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

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

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

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