ดูวิธีใช้ requestVideoFrameCallback() เพื่อทำงานกับวิดีโอในเบราว์เซอร์ได้อย่างมีประสิทธิภาพมากขึ้น
วิธี HTMLVideoElement.requestVideoFrameCallback() ช่วยให้ผู้เขียนเว็บลงทะเบียนการเรียกกลับ
ที่ทำงานในขั้นตอนการแสดงผลเมื่อมีการส่งเฟรมวิดีโอใหม่ไปยัง Compositor
ซึ่งช่วยให้นักพัฒนาซอฟต์แวร์สามารถดำเนินการต่อเฟรมวิดีโอได้อย่างมีประสิทธิภาพ
เช่น การประมวลผลวิดีโอและการวาดลงใน Canvas, การวิเคราะห์วิดีโอ
หรือการซิงค์กับแหล่งเสียงภายนอก
ความแตกต่างจาก requestAnimationFrame()
การดำเนินการต่างๆ เช่น การวาดเฟรมวิดีโอลงใน Canvas โดยใช้
drawImage()
ที่ทำผ่าน API นี้จะได้รับการซิงค์อย่างเต็มที่
กับอัตราเฟรมของวิดีโอที่เล่นบนหน้าจอ
requestVideoFrameCallback() จะเชื่อมโยงกับอัตราเฟรมของวิดีโอจริง ซึ่งมีข้อยกเว้นที่สำคัญ ดังนี้window.requestAnimationFrame()
อัตราที่มีประสิทธิภาพซึ่งใช้เรียกใช้ฟังก์ชันเรียกกลับคืออัตราที่ต่ำกว่าระหว่างอัตราของวิดีโอ กับอัตราของเบราว์เซอร์ ซึ่งหมายความว่าวิดีโอ 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
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() ทำงานในเทรดหลัก แต่การคอมโพสิตวิดีโอจะเกิดขึ้นในเทรดของ Compositor
ทุกอย่างจาก API นี้จึงเป็นความพยายามอย่างดีที่สุด และเบราว์เซอร์ไม่รับประกันอย่างเคร่งครัด
สิ่งที่อาจเกิดขึ้นคือ API อาจช้ากว่า 1 Vsync เมื่อเทียบกับเวลาที่แสดงผลเฟรมวิดีโอ
การเปลี่ยนแปลงที่เกิดขึ้นกับหน้าเว็บผ่าน API จะใช้เวลา 1 Vsync จึงจะปรากฏบนหน้าจอ (เช่นเดียวกับ window.requestAnimationFrame())
ดังนั้นหากคุณอัปเดต mediaTime หรือหมายเลขเฟรมในหน้าเว็บอย่างต่อเนื่องและเปรียบเทียบกับเฟรมวิดีโอที่มีหมายเลข ในที่สุดวิดีโอจะดูเหมือนว่าอยู่ล้ำหน้าไป 1 เฟรม
สิ่งที่เกิดขึ้นจริงคือ เฟรมพร้อมที่ vsync x ระบบจะเรียกใช้แฮนเดิลและแสดงผลเฟรมที่ vsync x+1
และจะแสดงผลการเปลี่ยนแปลงที่ทำในแฮนเดิลที่ 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
รูปภาพหลักโดย
Denise Jans ใน Unsplash