ดูวิธีใช้ requestVideoFrameCallback()
เพื่อทำงานกับวิดีโอในเบราว์เซอร์ได้อย่างมีประสิทธิภาพมากขึ้น
เมธอด HTMLVideoElement.requestVideoFrameCallback()
ช่วยให้ผู้เขียนเว็บลงทะเบียนการเรียกกลับซึ่งจะทํางานในขั้นตอนการแสดงผลเมื่อส่งเฟรมวิดีโอใหม่ไปยังโปรแกรมคอมโพสิเตอร์
ซึ่งช่วยให้นักพัฒนาแอปดำเนินการกับวิดีโอแต่ละเฟรมได้อย่างมีประสิทธิภาพ เช่น ประมวลผลวิดีโอและวาดภาพลงในผืนผ้าใบ การวิเคราะห์วิดีโอ หรือการซิงค์กับแหล่งที่มาของเสียงภายนอก
ความแตกต่างกับ requestAnimationFrame()
การดำเนินการต่างๆ เช่น การวาดเฟรมวิดีโอลงในผืนผ้าใบโดยใช้ drawImage()
ที่ทำผ่าน API นี้จะซิงค์ตามอัตราเฟรมของวิดีโอที่เล่นบนหน้าจอ
requestVideoFrameCallback()
แตกต่างจากwindow.requestAnimationFrame()
ตรงที่requestVideoFrameCallback()
มักจะทำงานประมาณ 60 ครั้งต่อวินาที แต่requestVideoFrameCallback()
จะเชื่อมโยงกับอัตราเฟรมวิดีโอจริง โดยมีข้อยกเว้นที่สำคัญดังนี้
อัตราที่มีประสิทธิภาพในการเรียกใช้การเรียกกลับคืออัตราที่น้อยกว่าระหว่างอัตราของวิดีโอกับอัตราของเบราว์เซอร์ ซึ่งหมายความว่าวิดีโอ 25 FPS ที่เล่นในเบราว์เซอร์ซึ่งแสดงผลที่ 60 Hz จะเรียกใช้การเรียกกลับที่ 25 Hz วิดีโอ 120 fps ในเบราว์เซอร์ 60 Hz เดียวกันจะเรียกใช้การเรียกกลับที่ 60 Hz
ชื่อสื่อถึงอะไรบ้าง
เนื่องจากมีความคล้ายคลึงกับ window.requestAnimationFrame()
วิธีการนี้จึงได้รับการเสนอเป็น video.requestAnimationFrame()
ในช่วงแรก และเปลี่ยนชื่อเป็น requestVideoFrameCallback()
ซึ่งได้รับความเห็นชอบหลังจากการพูดคุยกันเป็นเวลานาน
การตรวจหาองค์ประกอบ
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
การสนับสนุนเบราว์เซอร์
โพลีฟิลล์
Polyfill สำหรับเมธอด requestVideoFrameCallback()
ที่อิงตาม Window.requestAnimationFrame()
และ HTMLVideoElement.getVideoPlaybackQuality()
พร้อมใช้งาน โปรดคำนึงถึงข้อจำกัดที่กล่าวถึงใน README
ก่อนใช้
การใช้เมธอด requestVideoFrameCallback()
หากเคยใช้เมธอด 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 1 ครั้งเมื่อเทียบกับเวลาที่แสดงผลเฟรมวิดีโอ
การเปลี่ยนแปลงในหน้าเว็บผ่าน API จะใช้เวลา 1 vsync จึงจะปรากฏบนหน้าจอ (เหมือนกับ window.requestAnimationFrame()
) ดังนั้น หากคุณอัปเดต mediaTime
หรือหมายเลขเฟรมในหน้าเว็บอยู่เรื่อยๆ และเปรียบเทียบกับเฟรมวิดีโอที่มีหมายเลข วิดีโอจะดูเหมือนว่าเล่นไปก่อน 1 เฟรม
สิ่งที่เกิดขึ้นจริงคือเฟรมจะพร้อมที่ Vsync x ระบบจะเรียกใช้การเรียกคืน และแสดงผลเฟรมที่ Vsync x+1 และการเปลี่ยนแปลงที่ทำในการเรียกคืนจะแสดงผลที่ Vsync x+2
คุณสามารถตรวจสอบว่า callback นั้นช้ากว่า vsync หรือไม่ (และเฟรมแสดงผลบนหน้าจอแล้ว) โดยดูว่า metadata.expectedDisplayTime
อยู่ห่างจาก now
หรือ 1 vsync ในอนาคตโดยประมาณหรือไม่
หากอยู่ภายในประมาณ 5-10 ไมโครวินาทีของ now
แสดงว่าเฟรมได้รับการแสดงผลแล้ว หาก expectedDisplayTime
อยู่ในอนาคตประมาณ 16 มิลลิวินาที (สมมติว่าเบราว์เซอร์/หน้าจอรีเฟรชที่ 60 Hz) แสดงว่าคุณซิงค์กับเฟรม
สาธิต
เราได้สร้างตัวอย่างใน Glitch สั้นๆ ที่แสดงวิธีวาดเฟรมบนผืนผ้าใบตามอัตราเฟรมของวิดีโออย่างแม่นยำ และตำแหน่งที่บันทึกข้อมูลเมตาของเฟรมเพื่อวัตถุประสงค์ในการแก้ไขข้อบกพร่อง
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
รูปภาพหลักโดย Denise Jans จาก Unsplash