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가 필요합니다 (window.requestAnimationFrame()
와 동일). 따라서 웹페이지에서 mediaTime
또는 프레임 번호를 계속 업데이트하고 이를 번호가 매겨진 동영상 프레임과 비교하면 결국 동영상이 한 프레임 앞서 있는 것처럼 보입니다.
실제로 일어나는 일은 vsync x에서 프레임이 준비되고, 콜백이 실행되고, vsync x+1에서 프레임이 렌더링되며, 콜백에서 이루어진 변경사항이 vsync x+2에서 렌더링되는 것입니다.
metadata.expectedDisplayTime
가 대략 now
인지 아니면 1개의 vsync만큼 미래인지 확인하여 콜백이 vsync 지연인지 (프레임이 이미 화면에 렌더링됨) 확인할 수 있습니다.
now
에서 약 5~10마이크로초 이내이면 프레임이 이미 렌더링된 것입니다. expectedDisplayTime
이 약 16밀리초 후 (브라우저/화면이 60Hz로 새로고침된다고 가정)이면 프레임과 동기화된 것입니다.
데모
동영상의 정확한 프레임 속도로 캔버스에 프레임이 그려지는 방식과 디버깅 목적으로 프레임 메타데이터가 로깅되는 위치를 보여주는 작은 데모를 만들었습니다.
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에 의해 지정되고 구현되었습니다.
이 게시물은 조 메들리와 케이시 바스크가 검토했습니다.
Unsplash의 데니스 얀스가 촬영한 히어로 이미지