requestVideoFrameCallback()
를 사용하여 브라우저에서 동영상을 더 효율적으로 작업하는 방법을 알아보세요.
HTMLVideoElement.requestVideoFrameCallback()
메서드를 사용하면 웹 작성자가 새 동영상 프레임이 컴포저이터로 전송될 때 렌더링 단계에서 실행되는 콜백을 등록할 수 있습니다.
이를 통해 개발자는 동영상 처리 및 캔버스에 그리기, 동영상 분석, 외부 오디오 소스와의 동기화와 같은 동영상 프레임별 효율적인 작업을 실행할 수 있습니다.
requestAnimationFrame()과의 차이점
이 API를 통해 drawImage()
를 사용하여 캔버스에 동영상 프레임을 그리는 등의 작업은 화면에서 재생 중인 동영상의 프레임 속도와 최대한 동기화됩니다.
일반적으로 초당 약 60회 실행되는 window.requestAnimationFrame()
과 달리 requestVideoFrameCallback()
는 실제 동영상 프레임 속도에 연결됩니다. 단, 중요한 예외가 있습니다.
콜백이 실행되는 유효 비율은 동영상 비율과 브라우저 비율 중 더 낮은 비율입니다. 즉, 60Hz로 페인트하는 브라우저에서 재생되는 25fps 동영상은 25Hz에서 콜백을 실행합니다. 동일한 60Hz 브라우저에서 120fps 동영상을 재생하면 60Hz에서 콜백이 실행됩니다.
이름에 포함되어야 하는 항목은 무엇인가요?
window.requestAnimationFrame()
와 유사하여 이 메서드는 처음에 video.requestAnimationFrame()
로 제안되었고 requestVideoFrameCallback()
로 이름이 바뀌었습니다. 이 이름은 장시간의 논의 끝에 합의되었습니다.
기능 감지
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
브라우저 지원
폴리필
Window.requestAnimationFrame()
및 HTMLVideoElement.getVideoPlaybackQuality()
를 기반으로 하는 requestVideoFrameCallback()
메서드의 폴리필을 사용할 수 있습니다. 이 기능을 사용하기 전에 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);
콜백에서 now
는 DOMHighResTimeStamp
이고 metadata
는 다음과 같은 속성이 있는 VideoFrameMetadata
사전입니다.
presentationTime
(DOMHighResTimeStamp
유형): 사용자 에이전트가 컴포지션을 위해 프레임을 제출한 시간입니다.expectedDisplayTime
(DOMHighResTimeStamp
유형): 사용자 에이전트가 프레임이 표시될 것으로 예상하는 시간입니다.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
는 재현 가능한 방식으로 프레임을 정확하게 식별하려는 경우(예: 누락된 프레임을 정확하게 식별) 사용해야 합니다.
프레임이 하나씩 어긋나는 것처럼 보이는 경우…
세로 동기화 (줄여서 vsync)는 동영상의 프레임 속도와 모니터의 새로고침 빈도를 동기화하는 그래픽 기술입니다.
requestVideoFrameCallback()
는 기본 스레드에서 실행되지만 내부적으로는 동영상 합성 작업이 컴포저 스레드에서 이루어지므로 이 API의 모든 작업은 최선의 방식으로 실행되며 브라우저는 엄격한 보장을 제공하지 않습니다.
API가 동영상 프레임이 렌더링될 때보다 1번 늦게 vsync될 수 있습니다.
API를 통해 웹페이지에 적용된 변경사항이 화면에 표시되려면 vsync 1회가 필요합니다 (window.requestAnimationFrame()
와 동일). 따라서 웹페이지에서 mediaTime
또는 프레임 번호를 계속 업데이트하고 이를 번호가 지정된 동영상 프레임과 비교하면 결국 동영상이 1프레임 앞서는 것처럼 보입니다.
실제로는 프레임이 vsync x에서 준비되고 콜백이 실행되고 프레임이 vsync x+1에서 렌더링되며 콜백에서 변경된 사항이 vsync x+2에서 렌더링됩니다.
metadata.expectedDisplayTime
가 대략 now
인지 또는 향후 1번의 vsync인지 확인하여 콜백이 vsync 지연인지 (그리고 프레임이 이미 화면에 렌더링되었는지) 확인할 수 있습니다.
expectedDisplayTime
가 now
에서 약 5~10마이크로초 이내이면 프레임이 이미 렌더링된 것입니다. expectedDisplayTime
가 약 16밀리초 뒤에 있으면 (브라우저/화면이 60Hz로 새로고침된다고 가정) 프레임과 동기화된 것입니다.
데모
동영상의 프레임 속도에 정확하게 맞춰 프레임이 캔버스에 그려지는 방식과 디버깅 목적으로 프레임 메타데이터가 로깅되는 위치를 보여주는 작은 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는 토마스 길버트가 지정하고 구현했습니다.
이 게시물은 조 미들리님과 케이스 바스케스님이 검토했습니다.
Unsplash의 Denise Jans님이 제공한 히어로 이미지