ดำเนินการต่อเฟรมวิดีโอได้อย่างมีประสิทธิภาพ

ดูวิธีใช้ requestVideoFrameCallback() เพื่อทำงานกับวิดีโอในเบราว์เซอร์ได้อย่างมีประสิทธิภาพมากขึ้น

เผยแพร่: 8 มกราคม 2023

Browser Support

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

Source

เมธอด HTMLVideoElement.requestVideoFrameCallback() ช่วยให้ผู้เขียนเว็บลงทะเบียนการเรียกกลับ ซึ่งจะทำงานในขั้นตอนการแสดงผลเมื่อมีการส่งเฟรมวิดีโอใหม่ไปยัง Compositor ซึ่งช่วยให้นักพัฒนาซอฟต์แวร์สามารถดำเนินการต่อเฟรมวิดีโอได้อย่างมีประสิทธิภาพ เช่น การประมวลผลวิดีโอและการวาดลงใน Canvas การวิเคราะห์วิดีโอ หรือการซิงค์กับแหล่งเสียงภายนอก

ความแตกต่างจาก requestAnimationFrame()

การดำเนินการที่ทำด้วย API นี้ เช่น การวาดเฟรมวิดีโอลงใน Canvas ด้วย drawImage() จะซิงค์กับอัตราเฟรมของวิดีโอที่เล่นบนหน้าจอ อย่างเต็มที่ ซึ่งแตกต่างจาก window.requestAnimationFrame() ซึ่งโดยปกติจะทริกเกอร์ประมาณ 60 ครั้งต่อวินาที

requestVideoFrameCallback() จะเชื่อมโยงกับอัตราเฟรมของวิดีโอจริง โดยมีข้อยกเว้นที่สำคัญดังนี้

อัตราที่มีประสิทธิภาพซึ่งใช้เรียกใช้ฟังก์ชันเรียกกลับคืออัตราที่ต่ำกว่าระหว่างอัตราของวิดีโอ กับอัตราของเบราว์เซอร์ ซึ่งหมายความว่าวิดีโอ 25 FPS ที่เล่นในเบราว์เซอร์ซึ่งแสดงผลที่ 60 Hz จะเรียกใช้การเรียกกลับที่ 25 Hz วิดีโอ 120 FPS ในเบราว์เซอร์ 60 Hz เดียวกันนั้นจะเรียกใช้การเรียกกลับที่ 60 Hz

การตรวจหาฟีเจอร์

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

Polyfill

Polyfill สำหรับเมธอด requestVideoFrameCallback() อิงตาม Window.requestAnimationFrame() และ HTMLVideoElement.getVideoPlaybackQuality() พร้อมใช้งานแล้ว ก่อนที่จะใช้ฟีเจอร์นี้ โปรดทราบข้อจำกัดที่ระบุไว้ใน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);

ใน Callback now คือ DOMHighResTimeStamp และ metadata คือพจนานุกรม VideoFrameMetadata ที่มีพร็อพเพอร์ตี้ต่อไปนี้

  • presentationTime ประเภท DOMHighResTimeStamp: เวลาที่ User Agent ส่งเฟรมเพื่อการคอมโพสิต
  • expectedDisplayTime ประเภท DOMHighResTimeStamp: เวลาที่ User Agent คาดหวังให้เฟรมปรากฏ
  • width ของประเภท unsigned long: ความกว้างของเฟรมวิดีโอในหน่วยพิกเซลสื่อ
  • height ของประเภท unsigned long: ความสูงของเฟรมวิดีโอในพิกเซลสื่อ
  • mediaTime ประเภท double: การประทับเวลาการนำเสนอสื่อ (PTS) ในหน่วยวินาทีของเฟรมที่นำเสนอ (เช่น การประทับเวลาในไทม์ไลน์ video.currentTime)
  • presentedFrames ประเภท unsigned long: จำนวนเฟรมที่ส่งเพื่อการคอมโพสิต ช่วยให้ไคลเอ็นต์ระบุได้ว่ามีเฟรมขาดหายไประหว่างอินสแตนซ์ของ VideoFrameRequestCallback หรือไม่
  • processingDuration ประเภท double: ระยะเวลาที่ผ่านไปเป็นวินาทีตั้งแต่ส่งแพ็กเก็ตที่เข้ารหัสซึ่งมีไทม์สแตมป์การนำเสนอ (PTS) เดียวกันกับเฟรมนี้ (เช่น เดียวกันกับ mediaTime) ไปยังตัวถอดรหัสจนกว่าเฟรมที่ถอดรหัสจะพร้อมสำหรับการนำเสนอ

สำหรับแอปพลิเคชัน WebRTC อาจมีพร็อพเพอร์ตี้เพิ่มเติมปรากฏขึ้นดังนี้

  • captureTime ที่มีประเภท DOMHighResTimeStamp สำหรับเฟรมวิดีโอที่มาจากแหล่งที่มาในเครื่องหรือแหล่งที่มาจากระยะไกล นี่คือเวลาที่กล้องจับภาพเฟรม สำหรับแหล่งที่มาที่อยู่ระยะไกล ระบบจะประมาณเวลาที่บันทึกโดยใช้การซิงโครไนซ์นาฬิกาและรายงานผู้ส่ง RTCP เพื่อแปลงการประทับเวลา RTP เป็นเวลาที่บันทึก
  • receiveTime ประเภท DOMHighResTimeStamp: สำหรับเฟรมวิดีโอที่มาจากแหล่งที่มาภายนอก นี่คือเวลาที่แพลตฟอร์มได้รับเฟรมที่เข้ารหัส นั่นคือเวลาที่ได้รับแพ็กเก็ตสุดท้ายที่เป็นของเฟรมนี้ผ่านเครือข่าย
  • rtpTimestamp ประเภท unsigned long: การประทับเวลา RTP ที่เชื่อมโยงกับเฟรมวิดีโอนี้

รายการที่น่าสนใจเป็นพิเศษในรายการนี้คือ mediaTime การติดตั้งใช้งานของ Chromium ใช้สัญญาณนาฬิกาเสียงเป็นแหล่งเวลาที่สนับสนุน video.currentTime ในขณะที่ mediaTime จะสร้างขึ้นโดยตรงจาก presentationTimestamp ของเฟรม mediaTime คือสิ่งที่คุณควรใช้หากต้องการระบุเฟรมอย่างแม่นยำในลักษณะที่ทำซ้ำได้ รวมถึงเพื่อระบุเฟรมที่คุณพลาดไปอย่างแม่นยำ

หากดูเหมือนว่าภาพเคลื่อนไหวจะเหลื่อมกัน 1 เฟรม

การซิงโครไนซ์แนวตั้ง (หรือที่เรียกว่า Vsync) เป็นเทคโนโลยีกราฟิกที่ ซิงโครไนซ์อัตราเฟรมของวิดีโอกับอัตราการรีเฟรชของจอภาพ เนื่องจาก requestVideoFrameCallback() ทำงานในเทรดหลัก แต่การคอมโพสิตวิดีโอจะเกิดขึ้นในเทรดคอมโพสิต ทุกอย่างจาก API นี้จึงเป็นความพยายามอย่างดีที่สุด และเบราว์เซอร์ไม่รับประกัน อย่างเคร่งครัด

API อาจมี Vsync ช้ากว่าเมื่อมีการแสดงผลเฟรมวิดีโอ การเปลี่ยนแปลงที่ทำกับหน้าเว็บผ่าน API จะใช้เวลา 1 Vsync จึงจะปรากฏบนหน้าจอ (เช่นเดียวกับ window.requestAnimationFrame()) ดังนั้นหากคุณอัปเดต mediaTime หรือหมายเลขเฟรมในหน้าเว็บอย่างต่อเนื่องและเปรียบเทียบกับเฟรมวิดีโอที่มีหมายเลข ในที่สุดวิดีโอจะดูเหมือนว่าอยู่ล้ำหน้าไป 1 เฟรม

สิ่งที่เกิดขึ้นจริงคือเฟรมพร้อมที่ vsync x จากนั้นระบบจะเรียกใช้ callback และแสดงผลเฟรมที่ vsync x+1 และการเปลี่ยนแปลงที่ทำใน callback จะแสดงผลที่ vsync x+2 คุณตรวจสอบได้ว่าการเรียกกลับเป็น Vsync Late (และมีการแสดงผลเฟรมบนหน้าจอแล้ว) หรือไม่ โดยตรวจสอบว่า metadata.expectedDisplayTime อยู่ที่ประมาณ now หรือ Vsync ในอนาคต หากอยู่ภายในประมาณ 5-10 ไมโครวินาทีของ now แสดงว่าเฟรมได้รับการแสดงผลแล้ว หาก expectedDisplayTime อยู่ในอนาคตประมาณ 16 มิลลิวินาที (สมมติว่าเบราว์เซอร์รีเฟรชที่ 60Hz) แสดงว่าคุณซิงค์กับเฟรม

สาธิต

ฉันได้สร้างเดโมขนาดเล็ก ที่แสดงวิธีวาดเฟรมบน Canvas ที่อัตราเฟรมของวิดีโอ และตำแหน่งที่บันทึกข้อมูลเมตาของเฟรมเพื่อวัตถุประสงค์ในการแก้ไขข้อบกพร่อง

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() วิธีนี้ช่วยปรับปรุงวิธีแก้ปัญหาชั่วคราวนี้ได้เป็นอย่างมาก

คำขอบคุณ

requestVideoFrameCallback API ได้รับการระบุและนำไปใช้โดย Thomas Guilbert โพสต์นี้ได้รับการตรวจสอบโดย Joe Medley และ Kayce Basques