Streams를 사용한 MediaStreamTrack 삽입 가능 미디어 처리

W3C 작업 초안,

이 문서에 대한 더 자세한 정보
이 버전:
https://www.w3.org/TR/2026/WD-mediacapture-transform-20260416/
최신 공개 버전:
https://www.w3.org/TR/mediacapture-transform/
편집자 초안:
https://w3c.github.io/mediacapture-transform/
이전 버전:
이력:
https://www.w3.org/standards/history/mediacapture-transform/
피드백:
public-webrtc@w3.org 에 제목 줄 “[mediacapture-transform] … 메시지 주제 …” 사용 (아카이브)
GitHub
편집자:
(Google)
(Google)

개요

이 API는 원시 데이터를 전달하는 MediaStreamTrack의 비트를 조작하기 위한 API 표면을 정의한다.

이 문서의 상태

이 절은 이 문서가 발행된 시점의 상태를 설명한다. 현재 W3C 발행물과 이 기술 보고서의 최신 개정판 목록은 W3C 기술 보고서 색인에서 찾을 수 있다.

이 문서는 Web Real-Time Communications Working Group에서 Recommendation 트랙을 사용하여 Working Draft로 발행했다. 이 문서는 W3C Recommendation이 되는 것을 목표로 한다.

이 문서에 대해 의견을 보내려면 public-webrtc@w3.org로 보내기 바란다 (구독, 아카이브). 전자우편을 보낼 때에는 제목에 “mediacapture-transform”이라는 텍스트를 넣고, 가능하면 다음과 같은 형식으로 넣기 바란다: “[mediacapture-transform] …의견 요약…”. 모든 의견을 환영한다.

Working Draft로 발행되었다고 해서 W3C와 그 회원들의 지지를 의미하지는 않는다. 이것은 초안 문서이며 언제든지 갱신, 대체 또는 다른 문서에 의해 폐기될 수 있다. 이 문서를 진행 중인 작업 이외의 것으로 인용하는 것은 부적절하다.

이 문서는 W3C Patent Policy에 따라 운영되는 그룹에서 작성했다. W3C는 그룹의 결과물과 관련하여 이루어진 모든 특허 공개의 공개 목록을 유지하며, 해당 페이지에는 특허 공개 방법에 대한 지침도 포함되어 있다. 개인이 Essential Claim(s)을 포함한다고 믿는 특허에 대해 실제 지식을 가지고 있는 경우, 그 개인은 W3C Patent Policy 제6절에 따라 해당 정보를 공개해야 한다.

이 문서는 2025년 8월 18일 W3C Process Document의 적용을 받는다.

1. 소개

[WEBRTC-NV-USE-CASES] 문서는 미디어에 대한 접근을 통해서만 달성할 수 있는 여러 기능(요구사항 N20-N22)을 설명하며, 여기에는 다음이 포함되지만 이에 한정되지는 않는다:

이러한 사용 사례는 또한 처리가 worker 스레드에서 수행될 수 있어야 함을 요구한다(요구사항 N23-N24).

이 명세는 [WEBCODECS][STREAMS]를 기반으로 하는 인터페이스를 제공하여 이러한 기능에 접근할 수 있게 한다.

이 명세는 카메라, 마이크, 화면 캡처와 같은 미디어 소스의 출력, 또는 코덱의 디코더 부분의 출력이자 코덱의 디코더 부분에 대한 입력인 원시 미디어에 대한 접근을 제공한다. 처리된 미디어는 HTML <video> 태그, RTCPeerConnection, canvas 또는 MediaRecorder를 포함하여 MediaStreamTrack을 받을 수 있는 모든 목적지에서 소비될 수 있다.

이 명세는 다음 사용 사례를 지원하는 것을 명시적인 목표로 한다:

참고: 오디오 사용 사례를 지원해야 하는지 여부에 대해서는 WG 합의가 없다.

참고: WG는 Streams 명세가 현재 Streams 명세의 일부 문제를 해결하기 위해 관련 설명 문서에 개요로 제시된 해결책을 채택할 것으로 기대한다.

2. 명세

이 명세는 [MEDIACAPTURE-STREAMS]에 대한 IDL 확장을 보여준다. 이는 MediaStreamTrack 인터페이스를 상속하고 MediaStreamTrack으로부터 생성될 수 있는 몇 가지 새 객체를 정의한다.

API는 두 요소로 구성된다. 하나는 트랙에서 인코딩되지 않은 미디어 프레임을 ReadableStream에 노출할 수 있는 트랙 싱크이다. 다른 하나는 그 역으로, 미디어 프레임을 입력으로 받는 트랙 소스를 제공한다.

2.1. MediaStreamTrackProcessor

MediaStreamTrackProcessor는 주어진 MediaStreamTrack을 통해 흐르는 미디어를 노출할 수 있는 ReadableStream의 생성을 허용한다. MediaStreamTrack이 비디오 트랙인 경우, 스트림이 노출하는 청크는 VideoFrame 객체가 된다.

이로 인해 MediaStreamTrackProcessor는 사실상 MediaStream 모델의 싱크가 된다.

MediaStreamTrackProcessor는 내부적으로 연결된 트랙이 전달하는 들어오는 미디어 프레임을 버퍼링할 수 있는 순환 큐를 포함한다. 이 버퍼링은 MediaStreamTrackProcessor가 연결된 ReadableStream에서 읽히기를 기다리는 프레임을 일시적으로 보관할 수 있게 한다. 애플리케이션은 MediaStreamTrackProcessor 생성자에 제공되는 매개변수를 통해 큐의 최대 크기에 영향을 줄 수 있다. 그러나 큐의 최대 크기는 UA가 결정하며 동적으로 변경될 수 있지만, 애플리케이션이 요청한 크기를 초과하지는 않는다. 애플리케이션이 최대 크기 매개변수를 제공하지 않으면 UA는 큐의 최대 크기를 자유롭게 결정할 수 있다.

새 프레임이 MediaStreamTrackProcessor에 도착했을 때, 큐가 최대 크기에 도달한 경우, 가장 오래된 프레임이 큐에서 제거되고 새 프레임이 큐에 추가된다. 이는 최대 크기가 1인 큐의 특정 경우, 큐에 프레임이 있으면 그것은 항상 가장 최근의 프레임이라는 뜻이다.

UA는 또한 언제든지 큐에서 임의의 프레임을 자유롭게 제거할 수 있다. UA는 리소스를 절약하거나 특정 상황에서 성능을 개선하기 위해 프레임을 제거할 수 있다. 이 경우 [[numDiscardedFrames]]는 그에 따라 반드시 증가되어야 한다. 모든 경우에, 드롭되지 않은 프레임은 ReadableStream에서 MediaStreamTrackProcessor에 도착한 순서대로 사용할 수 있어야 한다.

MediaStreamTrackProcessor는 스트림에 읽기 요청이 발행된 경우에만 연결된 ReadableStream에서 프레임을 사용할 수 있게 한다. 그 의도는 UA가 버퍼링 정책을 선택할 충분한 유연성을 갖지 못하게 하는 스트림의 내부 버퍼링을 피하는 것이다.

작성자는 출력 VideoFrames가 더 이상 필요하지 않으면 즉시 close()를 호출하는 것이 권장된다. 기반 미디어 리소스는 MediaStreamTrack의 소스가 소유한다. 이를 해제하지 않거나 가비지 컬렉션을 기다리면 소스가 새 VideoFrames를 방출하지 못하게 될 수 있다.

2.1.1. 인터페이스 정의

[Exposed=(Window,DedicatedWorker), Transferable]
interface MediaStreamTrackHandle {
   constructor(MediaStreamTrack track);
};

[Exposed=DedicatedWorker]
interface MediaStreamTrackProcessor {
    constructor(MediaStreamTrackProcessorInit init);
    readonly attribute ReadableStream readable;
    readonly attribute unsigned long long discardedFrames;
    readonly attribute unsigned long long totalFrames;
};

typedef (MediaStreamTrack or MediaStreamTrackHandle) MediaStreamTrackOrHandle;
dictionary MediaStreamTrackProcessorInit {
  required MediaStreamTrackOrHandle track;
  [EnforceRange] unsigned short maxBufferSize;
};

참고: 인터페이스가 DedicatedWorker에 노출되어야 한다는 WG 합의가 있다. 인터페이스가 Window에 노출되어야 하는지 여부에 대해서는 WG 합의가 없다.

참고: 종류가 "video"인 MediaStreamTrack으로부터 MediaStreamTrackProcessor를 생성할 수 있어야 한다는 WG 합의가 있다. 종류가 "audio"인 MediaStreamTrack으로부터 MediaStreamTrackProcessor를 생성하는 것이 지원되어야 하는지 여부에 대해서는 WG 합의가 없다.

2.1.2. 내부 슬롯

[[track]]
MediaStreamTrackProcessor에 의해 원시 데이터가 노출될 트랙.
[[maxBufferSize]]
애플리케이션이 지정한 대로 MediaStreamTrackProcessor가 버퍼링할 미디어 프레임의 최대 개수. 애플리케이션이 이를 제공하지 않은 경우 값이 없을 수 있다. 그 최소 유효 값은 1이다.
[[queue]]
애플리케이션이 아직 읽지 않은 미디어 프레임을 버퍼링하는 데 사용되는
[[numPendingReads]]
아직 처리되지 않은, 애플리케이션이 발행한 읽기 요청 수를 나타내는 정수.
[[isClosed]]
MediaStreamTrackProcessor가 닫혔는지를 나타내는 boolean.
[[numDiscardedFrames]]
MediaStreamTrackProcessor가 버린 프레임 수.
[[numTotalFrames]]
버려진 프레임을 포함하여 이 MediaStreamTrackProcessor가 받은 프레임 수.

2.1.3. 생성자

MediaStreamTrackProcessor(init)
  1. init.track이 유효하지 않은 MediaStreamTrack이면 TypeError를 던진다.

  2. init.trackMediaStreamTrackHandle이면 다음 하위 단계를 실행한다:

    1. init.track.[[transferable]]이 false이면 TypeError를 던진다.

    2. init.track.[[track]]이 유효하지 않은 MediaStreamTrack을 참조하고 있으면 TypeError를 던진다.

  3. maxBufferSize를 1로 둔다.

  4. init.maxBufferSize가 1보다 큰 정수 값을 가지면 다음 하위 단계를 실행한다:

    1. maxBufferSizeinit.maxBufferSize로 설정한다.

    2. 사용자 에이전트는 maxBufferSize를 더 낮은 값으로 클램프하기로 결정할 수 있지만, 1보다 낮게는 할 수 없다.

      maxBufferSize 클램프는 예를 들어 카메라처럼 특정 시점에 제한된 수의 VideoFrame만 사용할 수 있는 일부 소스에 유용할 수 있다.

  5. processor를 새 MediaStreamTrackProcessor 객체로 둔다.

  6. processor.[[track]]init.track으로 설정한다.

  7. processor.[[maxBufferSize]]maxBufferSize로 설정한다.

  8. processor.[[queue]]를 빈 로 설정한다.

  9. processor.[[numPendingReads]]를 0으로 설정한다.

  10. processor.[[isClosed]]를 false로 설정한다.

  11. processor.[[numDiscardedFrames]]를 0으로 설정한다.

  12. processor.[[numTotalFrames]]를 0으로 설정한다.

  13. processor를 반환한다.

2.1.4. 속성

readable, ReadableStream 타입, readonly
[[track]] 내부 슬롯에 저장된 MediaStreamTrack 또는 MediaStreamTrackHandle가 전달한 프레임을 읽을 수 있게 한다. 이 속성은 처음 호출될 때 다음 단계에 따라 생성된다:
  1. this.readable을 새 ReadableStream으로 초기화한다.

  2. Set up this.readable을 그 pullAlgorithmprocessorPull로, 매개변수는 this로, cancelAlgorithmprocessorCancel로, 매개변수는 this로, 그리고 highWaterMark는 0으로 설정하여 구성한다.

processorPull 알고리즘은 processor를 입력으로 받는다. 이는 다음 단계로 정의된다:

  1. processor.[[numPendingReads]]의 값을 1 증가시킨다.

  2. 태스크를 큐에 넣어 maybeReadFrame 알고리즘을 processor를 매개변수로 하여 실행한다.

  3. undefined로 해결된 promise를 반환한다.

maybeReadFrame 알고리즘은 processor를 입력으로 받는다. 이는 다음 단계로 정의된다:

  1. processor.[[queue]]비어 있으면, 이 단계를 중단한다.

  2. processor.[[numPendingReads]]가 0이면, 이 단계를 중단한다.

  3. frameprocessor.[[queue]]에서 프레임 미디어 데이터를 dequeueing한 결과로 둔다.

  4. frameprocessor.readableEnqueue한다.

  5. processor.[[numPendingReads]]를 1 감소시킨다.

  6. 1단계로 간다.

processorCancel 알고리즘은 processor를 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다:

  1. processorClose 알고리즘을 processor를 매개변수로 하여 실행한다.

  2. undefined로 해결된 promise를 반환한다.

processorClose 알고리즘은 processor를 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다:

  1. processor.[[isClosed]]가 true이면, 이 단계를 중단한다.

  2. processorprocessor.[[track]]에서 연결 해제한다. 이를 수행하는 메커니즘은 UA별로 다르며, 그 결과 processor는 더 이상 processor.[[track]]의 싱크가 아니거나, 그것이 참조하는 MediaStreamTrack의 싱크가 아니다.

  3. processor.[[track]]을 null로 설정한다.

  4. processor.readable.[[controller]]Close한다.

  5. processor.[[queue]]Empty한다.

  6. processor.[[isClosed]]를 true로 설정한다.

discardedFrames, unsigned long long 타입, readonly
processor.[[numDiscardedFrames]]의 값을 반환한다.
totalFrames, unsigned long long 타입, readonly
processor.[[numTotalFrames]]의 값을 반환한다.

2.1.5. 트랙과의 상호작용 처리

MediaStreamTrackProcessor processor[[track]]processor에 프레임을 전달할 때, UA는 processor를 매개변수로 하여 handleNewFrame 알고리즘을 반드시 실행해야 한다.

handleNewFrame 알고리즘은 processor를 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다:

  1. processor.[[queue]]processor.[[maxBufferSize]]개의 요소를 가지면, 다음 단계를 실행한다:

    1. droppedFrameprocessor.[[queue]]dequeueing한 결과로 둔다.

    2. droppedFrame으로 Close VideoFrame 알고리즘을 실행한다.

    3. processor.[[numDiscardedFrames]]를 1 증가시킨다.

  2. processor.[[numTotalFrames]]를 1 증가시킨다.

  3. 새 프레임 미디어 데이터를 processor.[[queue]]Enqueue한다.

  4. 태스크를 큐에 넣어 maybeReadFrame 알고리즘을 processor를 매개변수로 하여 실행한다.

언제든지 UA는 processor.[[queue]]에서 임의의 프레임을 remove할 수 있다. UA는 예를 들어 리소스 고갈을 방지하거나 특정 상황에서 성능을 개선하기 위해 processor.[[queue]]에서 프레임을 제거하기로 결정할 수 있다. 이 경우 processor.[[numDiscardedFrames]]는 그에 따라 반드시 증가되어야 한다.

애플리케이션은 프레임의 타임스탬프에 간격이 있음을 알아차리거나 discardedFrames를 확인하여 프레임이 드롭되었음을 감지할 수 있다.

MediaStreamTrackProcessor processor[[track]]종료되면, processorClose 알고리즘은 processor를 매개변수로 하여 실행되어야 한다.

2.1.6. MediaStreamTrackHandle

MediaStreamTrackHandle 인터페이스는 다른 컨텍스트에 존재하는 MediaStreamTrack에 대해 DedicatedWorkerGlobalScope MediaStreamTrackProcessor를 싱크로 만드는 데 유용한 MediaStreamTrack 객체에 대한 프록시를 나타낸다.

MediaStreamTrackHandle에는 다음 슬롯이 있다:

  1. 핸들 객체 인스턴스가 한 번 전송되면 그 인스턴스를 다시 전송할 수 없도록 보장하는 데 사용되는 [[transferable]] 내부 슬롯.

  2. 프록시된 MediaStreamTrack 객체에 대한 약한 참조인 [[track]] 내부 슬롯.

2.1.7. 생성자

MediaStreamTrackHandle(track)
  1. handle을 새 MediaStreamTrackHandle 객체로 둔다.

  2. handle.[[transferable]]을 true로 설정한다.

  3. handle.[[track]]track에 대한 내부 참조로 설정한다.

  4. handle을 반환한다.

MediaStreamTrackHandle 전송 단계는, valuedataHolder가 주어졌을 때, 다음과 같다:

  1. value.[[transferable]]이 false이면 DataCloneError DOMException을 던진다.

  2. value.[[transferable]]을 false로 설정한다.

  3. dataHolder.[[track]]handle.[[track]]으로 설정한다.

  4. value.[[track]]을 null로 설정한다.

MediaStreamTrackHandle 전송 수신 단계는, dataHolderhandle이 주어졌을 때, 다음과 같다:

  1. handle.[[track]]dataHolder.[[track]]으로 설정한다.

MediaStreamTrackHandle은 프록시된 MediaStreamTrack의 수명을 늘릴 수 있다. 가비지 컬렉션 관점에서 보면 MediaStreamTrackHandle이 자신의 MediaStreamTrack에 대한 강한 참조를 보유하는 것과 같다. 또한 MediaStreamTrack은 전송 중인 데이터 홀더에서 참조되고 있으면 가비지 컬렉션되어서는 안 된다.

2.2. VideoTrackGenerator

VideoTrackGeneratorMediaStreamTrack을 위한 비디오 소스를 생성할 수 있게 하며, 이는 MediaStream 모델에서 VideoFrame 객체의 Stream으로부터 프레임을 생성한다. 이는 두 readonly 속성을 가진다: writable WritableStreamtrack MediaStreamTrack이다.

VideoTrackGenerator는 자신의 writable 속성의 underlying sink]이다. track 속성은 출력이다. 동일한 VideoTrackGenerator에 연결되는 추가 트랙은 track 속성에 대해 clone 메서드를 사용하여 생성할 수 있다.

WritableStreamVideoFrame 객체를 받는다. VideoFramewritable에 쓰이면, 프레임의 close() 메서드가 자동으로 호출되어 그 내부 리소스가 더 이상 JavaScript에서 접근 가능하지 않게 된다.

참고: 종류가 "video"인 MediaStreamTrack을 생성할 수 있는 소스가 존재해야 한다는 WG 합의가 있다. 종류가 "audio"인 MediaStreamTrack을 생성할 수 있는 소스가 존재해야 하는지 여부에 대해서는 WG 합의가 없다.

2.2.1. 인터페이스 정의

[Exposed=DedicatedWorker]
interface VideoTrackGenerator {
  constructor();
  readonly attribute WritableStream writable;
  attribute boolean muted;
  readonly attribute MediaStreamTrack track;
};

참고: 이 인터페이스가 DedicatedWorker에 노출되어야 한다는 WG 합의가 있다. 이것이 Window에 노출되어야 하는지 여부에 대해서는 WG 합의가 없다.

2.2.2. 내부 슬롯

[[track]]
이 소스의 MediaStreamTrack 출력
[[isMuted]]
이 소스와 이 소스가 공급하는 모든 MediaStreamTrack들이 현재 muted인지 아닌지를 나타내는 boolean.

2.2.3. 생성자

VideoTrackGenerator()
  1. generator를 새 VideoTrackGenerator 객체로 둔다.

  2. tracksourcegenerator로, tieSourceToContextfalse로 설정된, 새로 생성된 MediaStreamTrack으로 둔다.

  3. generator.tracktrack으로 초기화한다.

  4. generator를 반환한다.

2.2.4. 속성

writable, WritableStream 타입, readonly
비디오 프레임을 VideoTrackGenerator에 쓸 수 있게 한다. 이 속성에 처음 접근할 때 다음 단계로 반드시 초기화되어야 한다:
  1. this.writable을 새 WritableStream으로 초기화한다.

  2. Set up this.writable을, 그 writeAlgorithmwriteFramethis를 매개변수로 하여 설정하고, closeAlgorithmcloseWritablethis를 매개변수로 하여 설정하며, abortAlgorithmcloseWritablethis를 매개변수로 하여 설정한다.

writeFrame 알고리즘은 generatorframe을 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다:

  1. frameVideoFrame 객체가 아니면, TypeError거부된 promise를 반환한다.

  2. frame[[Detached]] 내부 슬롯 값이 true이면, TypeError거부된 promise를 반환한다.

  3. generator.[[isMuted]]의 값이 false이면, generator로부터 공급되는 각 live 트랙(이름은 track)에 대해 다음 단계를 실행한다:

    1. cloneframe으로 Clone videoFrame 알고리즘을 실행한 결과로 둔다.

    2. clonetrack에 보낸다.

  4. frame으로 Close VideoFrame 알고리즘을 실행한다.

  5. undefined로 해결된 promise를 반환한다.

closeWritable 알고리즘은 generator를 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다.

  1. generator로부터 공급되는 각 트랙 t에 대해, tend한다.

  2. undefined로 해결된 promise를 반환한다.

미디어 데이터가 트랙에 전송될 때, UA는 트랙에 전송되는 미디어 데이터가 트랙의 제약을 만족하도록 처리(예: 자르기 및 축소)를 적용할 수 있다. 각 트랙은 그 제약에 따라 서로 다른 버전의 미디어 데이터를 받을 수 있다.

muted, boolean 타입
VideoTrackGenerator를 음소거한다. getter 단계는 this.[[isMuted]]를 반환하는 것이다. setter 단계는 값 newValue가 주어졌을 때 다음과 같다:
  1. newValuethis.[[isMuted]]와 같으면, 이 단계를 중단한다.

  2. this.[[isMuted]]newValue로 설정한다.

  3. 이 이벤트 루프 실행에서 아직 하나도 큐에 넣어지지 않았다면, 다음 단계를 실행할 태스크를 큐에 넣는다:

    1. settledValuethis.[[isMuted]]로 둔다.

    2. this에 의해 공급되는 각 live 트랙에 대해, settledValue트랙의 muted 상태를 설정하는 태스크를 큐에 넣는다.

track, MediaStreamTrack 타입, readonly
MediaStreamTrack 출력. getter 단계는 this.[[track]]을 반환하는 것이다.

2.2.5. MediaStreamTrack 동작의 특수화

VideoTrackGenerator는 하나 이상의 MediaStreamTrack의 소스로 동작한다. 이 절은 VideoTrackGenerator로부터 공급되는 MediaStreamTrack이 어떻게 동작하는지에 대한 설명을 추가한다.
2.2.5.1. stop
stop 메서드는 트랙을 정지한다. VideoTrackGenerator로부터 공급되는 마지막 트랙이 종료되면, 그 VideoTrackGeneratorwritable닫힌다.
2.2.5.2. 제약 가능한 속성

다음 제약 가능한 속성은 VideoTrackGenerator로부터 공급되는 모든 MediaStreamTrack에 대해 정의된다:

속성 이름 참고
width ConstrainULong 설정으로서 이는 트랙이 받은 최신 프레임의 픽셀 단위 너비이다. capability로서 maxVideoFrame이 가질 수 있는 가장 큰 너비를 반드시 반영해야 하며, minVideoFrame이 가질 수 있는 가장 작은 너비를 반드시 반영해야 한다.
height ConstrainULong 설정으로서 이는 트랙이 받은 최신 프레임의 픽셀 단위 높이이다. capability로서 maxVideoFrame이 가질 수 있는 가장 큰 높이를 반드시 반영해야 하며, minVideoFrame이 가질 수 있는 가장 작은 높이를 반드시 반영해야 한다.
frameRate ConstrainDouble 설정으로서 이는 트랙이 최근에 받은 프레임을 기반으로 한 프레임 레이트의 추정치이다. capability로서 min은 반드시 0이어야 하고 max는 시스템이 지원하는 최대 프레임 레이트여야 한다.
aspectRatio ConstrainDouble 설정으로서 이는 트랙이 전달한 최신 프레임의 종횡비이다; 이는 픽셀 단위 너비를 픽셀 단위 높이로 나눈 값을 소수점 이하 10자리로 반올림한 double이다. capability로서 minVideoFrame이 지원하는 가장 작은 종횡비여야 하고, maxVideoFrame이 지원하는 가장 큰 종횡비여야 한다.
resizeMode ConstrainDOMString 설정으로서 이 문자열은 VideoResizeModeEnum의 멤버 중 하나여야 한다. 값 "none"은 MediaStreamTrack이 출력하는 프레임이 어떤 제약과 관계없이 트랙을 뒷받침하는 writable에 쓰인 프레임의 수정되지 않은 버전임을 의미한다. 값 "crop-and-scale"은 MediaStreamTrack이 출력하는 프레임이 트랙의 width, height 및 aspectRatio 제약 값에 기반하여 소스 프레임의 잘리거나 및/또는 축소된 버전일 수 있음을 의미한다. capability로서 값 "none"과 "crop-and-scale"은 모두 반드시 존재해야 한다.

applyConstraints 메서드는 VideoTrackGenerator로부터 공급되는 비디오 MediaStreamTrack에 적용될 때 위에 정의된 속성을 지원한다. 이는 예를 들어 프레임 크기를 조정하거나 트랙의 프레임 레이트를 조절하는 데 사용될 수 있다. 이러한 제약은 VideoTrackGeneratorwritable에 쓰이는 VideoFrame 객체에는 아무 효과가 없고, 제약이 적용된 트랙의 출력에만 효과가 있다는 점에 유의한다. 또한 VideoTrackGenerator는 원칙적으로 지원되는 제약 가능한 속성에 대한 임의의 설정으로 미디어 데이터를 생성할 수 있으므로, VideoTrackGenerator가 뒷받침하는 트랙에 대한 applyConstraints 호출은 주어진 제약이 getCapabilities가 보고한 시스템 지원 범위를 벗어나지 않는 한, 일반적으로 OverconstrainedError로 실패하지 않는다는 점도 유의한다.

2.2.5.3. 이벤트와 속성
이벤트와 속성은 모든 MediaStreamTrack의 경우와 동일하게 동작한다. VideoTrackGeneratorwritable 스트림이 닫히면, 이에 연결된 모든 live 트랙이 종료되고 그 트랙들에서 ended 이벤트가 발생한다는 점은 주목할 만하다.

3. 예제

3.1. 비디오 처리

얼굴 위치(어떤 형식)를 반환하는 얼굴 인식 함수 detectFace(videoFrame)와, 주어진 videoFrame과 비슷하지만 얼굴이 아닌 부분을 흐리게 처리한 새 VideoFrame을 반환하는 조작 함수 blurBackground(videoFrame, facePosition)를 고려한다. 이 예제는 또한 비디오 요소에서 효과 적용 전후의 비디오를 보여준다.
// main.js

const stream = await navigator.mediaDevices.getUserMedia({video:true});
const videoBefore = document.getElementById('video-before');
const videoAfter = document.getElementById('video-after');
videoBefore.srcObject = stream;

const trackHandle = new MediaStreamTrackHandle(stream.getVideoTracks()[0]);
const worker = new Worker('worker.js');
worker.postMessage({trackHandle}, [trackHandle]);

const {data} = await new Promise(r => worker.onmessage);
videoAfter.srcObject = new MediaStream([data.track]);

// worker.js

self.onmessage = async ({data: {trackHandle}}) => {
  const source = new VideoTrackGenerator();
  parent.postMessage({track: source.track}, [source.track]);

  const {readable} = new MediaStreamTrackProcessor({track: trackHandle});
  const transformer = new TransformStream({
    async transform(frame, controller) {
      const facePosition = await detectFace(frame);
      const newFrame = blurBackground(frame, facePosition);
      frame.close();
      controller.enqueue(newFrame);
    }
  });
  await readable.pipeThrough(transformer).pipeTo(source.writable);
};

3.2. 제약을 사용한 다중 소비자 후처리

일반적인 사용 사례는 라이브 카메라 비디오에서 배경을 제거하여 화상 회의에 제공하고, 그 결과를 보여주는 라이브 셀프뷰를 표시하는 것이다. 실제 전송에 사용되는 프레임 레이트가 대역폭 제약으로 인한 역압력 때문에 더 낮아질 수 있더라도, 셀프뷰는 높은 프레임 레이트를 갖는 것이 바람직하다. 이는 트랙 clone에 제약을 적용함으로써 달성할 수 있으며, 처리를 두 번 할 필요를 피한다.
// main.js

const stream = await navigator.mediaDevices.getUserMedia({video:true});
const [track] = stream.getVideoTracks();
const worker = new Worker('worker.js');
worker.postMessage({track}, [track]);

const {data} = await new Promise(r => worker.onmessage);
const selfView = document.getElementById('video-self');
selfView.srcObject = new MediaStream([data.track.clone()]); // 60 fps

await data.track.applyConstraints({width: 320, height: 200, frameRate: 30});
const pc = new RTCPeerConnection(config);
pc.addTrack(data.track); // 30 fps

// worker.js

self.onmessage = async ({data: {track}}) => {
  const source = new VideoTrackGenerator();
  parent.postMessage({track: source.track}, [source.track]);

  const {readable} = new MediaStreamTrackProcessor({track});
  const transformer = new TransformStream({transform: myRemoveBackgroundFromVideo});
  await readable.pipeThrough(transformer).pipeTo(source.writable);
};

3.3. 워커에서 제약을 사용한 다중 소비자 후처리

더 높은 프레임 레이트의 셀프뷰를 표시할 수 있는 것은 워커에서 WebTransport를 통해 비디오 프레임을 전송할 때에도 관련이 있다. 위와 동일한 기법을 여기에서도 사용할 수 있지만, 제약은 워커 안에서 트랙 clone에 적용된다.
// main.js

const stream = await navigator.mediaDevices.getUserMedia({video:true});
const [track] = stream.getVideoTracks();
const worker = new Worker('worker.js');
worker.postMessage({track}, [track]);

const {data} = await new Promise(r => worker.onmessage);
const selfView = document.getElementById('video-self');
selfView.srcObject = new MediaStream([data.track]); // 60 fps

// worker.js

self.onmessage = async ({data: {track}}) => {
  const source = new VideoTrackGenerator();
  const sendTrack = source.track.clone();
  parent.postMessage({track: source.track}, [source.track]);

  await sendTrack.applyConstraints({width: 320, height: 200, frameRate: 30});

  const wt = new WebTransport("https://webtransport.org:8080/up");

  const {readable} = new MediaStreamTrackProcessor({track});
  const transformer = new TransformStream({transform: myRemoveBackgroundFromVideo});
  await readable.pipeThrough(transformer)
    .pipeThrough({writable: source.writable, readable: sendTrack.readable}),
    .pipeThrough(createMyEncodeVideoStream({
      codec: "vp8",
      width: 640,
      height: 480,
      bitrate: 1000000,
    }))
    .pipeThrough(new TransformStream({transform: mySerializer}));
    .pipeTo(wt.createUnidirectionalStream()); // 30 fps
};

위 예제는 실시간 스트림에서의 문제 때문에, 여러 소비자를 제공하기 위해 tee() 함수를 사용하는 것을 피한다.

간결함을 위해, 이 예제는 단일 WebTransport 스트림을 통해 비디오 프레임을 인코딩하고 전송하는 WebCodecs 래퍼 사용도 지나치게 단순화한다(헤드 오브 라인 블로킹을 초래함).

4. 구현 조언

이 절은 정보 제공용이다.

4.1. 여러 소비자와 함께 사용

프로그래머가 단일 프레임 스트림을 여러 소비자가 소비하기를 바랄 수 있는 사용 사례가 있다.

예에는 배경 흐림 함수의 결과가 셀프뷰에 표시되는 동시에 VideoEncoder를 사용해 인코딩되어야 하는 경우가 포함된다.

두 소비자가 모두 처리되지 않은 프레임을 소비하고 동기화가 필요하지 않은 경우에는, 여러 MediaStreamTrackProcessor 객체를 인스턴스화하는 것이 견고한 해결책이다.

두 소비자가 모두 처리 단계의 결과를 VideoTrackGenerator를 사용하여 MediaStreamTrack으로 변환하려는 경우, 예를 들어 처리된 스트림을 <video> 태그와 RTCPeerConnection 모두에 제공하는 경우, 결과 MediaStreamTrack을 여러 싱크에 연결하는 것이 가장 적절한 메커니즘일 수 있다.

다운스트림 처리가 스트림이 아니라 프레임을 받는 경우에는, 프레임을 필요에 따라 clone하여 다운스트림 처리로 보낼 수 있다; "clone"은 비용이 저렴한 작업이다.

스트림이 어떤 처리의 출력이고 두 분기가 모두 추가 처리를 위해 Stream 객체를 필요로 하는 경우에는, 하나의 스트림에서 두 스트림을 생성하는 함수가 필요하다.

그러나 표준 tee() 작업은 이 맥락에서 문제가 있다:

따라서 미디어를 포함하는 Streams에 tee()를 사용하는 것은 그 영향을 충분히 이해한 경우에만 수행해야 한다. 대신, 사용 사례에 더 적합한 스트림 분할용 사용자 정의 요소를 사용해야 한다.

참고: 이 절에 영향을 미칠 수 있는 해결책이 Streams 명세에 이슈로 등록되어 있다: https://github.com/whatwg/streams/issues/1157, https://github.com/whatwg/streams/issues/1156, https://github.com/whatwg/streams/issues/401, https://github.com/whatwg/streams/issues/1186

5. 보안 및 개인정보 고려사항

이 API는 MediaStreamTrack 소스와 MediaStreamTrack 싱크를 정의한다. 소스(VideoTrackGenerator)의 보안 및 개인정보 보호는 동일 출처 정책에 의존한다. 즉, VideoTrackGeneratorMediaStreamTrack 형태로 사용 가능하게 만들 수 있는 데이터는 VideoFrame 객체가 생성되어 VideoTrackGenerator로 푸시되기 전에 문서에 보여야 한다. 교차 출처 데이터를 사용하여 VideoFrame 객체를 생성하려는 모든 시도는 실패한다. 따라서 VideoTrackGenerator는 새로운 fingerprinting 표면을 도입하지 않는다.

이 API가 도입하는 MediaStreamTrack 싱크(MediaStreamTrackProcessor)는 WebRTC 피어 연결과 미디어 요소 같은 다른 MediaStreamTrack 싱크가 노출하는 것과 동일한 데이터를 MediaStreamTrack으로 노출한다. MediaStreamTrackProcessor의 보안 및 개인정보 보호는 MediaStreamTrackProcessor가 연결된 트랙의 MediaStreamTrack 소스의 보안 및 개인정보 보호에 의존한다. 예를 들어 카메라, 마이크 및 화면 캡처 트랙은 권한 대화상자를 통한 명시적인 사용 승인에 의존하며(참조: [MEDIACAPTURE-STREAMS][SCREEN-CAPTURE]), element capture와 VideoTrackGenerator는 동일 출처 정책에 의존한다.

MediaStreamTrackProcessor의 잠재적 문제는 리소스 고갈이다. 예를 들어 사이트가 열린 VideoFrame 객체를 너무 많이 보유하여 GPU 메모리가 뒷받침하는 프레임의 시스템 전체 풀을 고갈시킬 수 있다. UA는 사이트가 보유할 수 있는 풀 기반 프레임 수를 제한하여 이 위험을 완화할 수 있다. 이는 버퍼링된 프레임의 최대 수를 줄이고 예산 한도에 도달하면 readable에 더 많은 프레임을 전달하는 것을 거부함으로써 달성될 수 있다. 우발적인 고갈도 VideoTrackGenerator에 쓰이는 즉시 VideoFrame 객체가 자동으로 닫힘으로써 완화된다.

6. 이전 제안과의 하위 호환성

이 절은 정보 제공용이다.

이 인터페이스에 대한 이전 제안에는 다음과 같은 API가 있었다:

[Exposed=Window,DedicatedWorker]
interface MediaStreamTrackGenerator : MediaStreamTrack {
    constructor(MediaStreamTrackGeneratorInit init);
    attribute WritableStream writable;  // VideoFrame or AudioData
};

dictionary MediaStreamTrackGeneratorInit {
  required DOMString kind;
};
이 인터페이스는 MediaStreamTrack의 생성기가 MediaStreamTrack을 포함하는 것이 아니라 MediaStreamTrack의 인스턴스가 되도록 했다.

VideoTrackGenerator는 다음과 같이 MediaStreamTrackGenerator 위에 shim될 수 있다:

// Not tested, unlikely to work as written!
class VideoTrackGenerator {
  constructor() {
     this.innerGenerator = new MediaStreamTrackGenerator({kind: 'video'});
     this.writable = this.innerGenerator.writable;
     this.track = this.innerGenerator.clone();
  }
  // Missing: shim for setting of the "muted" attribute.
};

오디오 처리와 관련된 고려사항을 포함하여 이전 제안에 대한 추가 설명은 이 문서의 이전 버전에서 찾을 수 있다.

참고: 저장소 이동을 마치면 chrome-96 브랜치를 가리키는 링크가 여기에 배치될 것이다.

적합성

문서 규약

적합성 요구사항은 설명적 단언과 RFC 2119 용어의 조합으로 표현된다. 이 문서의 규범 부분에서 사용하는 핵심 단어 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, 그리고 “OPTIONAL”은 RFC 2119에 설명된 대로 해석되어야 한다. 다만 가독성을 위해, 이 명세에서는 이러한 단어들이 모두 대문자로 나타나지는 않는다.

명시적으로 비규범이라고 표시된 절, 예제, 참고를 제외하고 이 명세의 모든 텍스트는 규범적이다. [RFC2119]

이 명세의 예제는 “예를 들어”라는 말로 도입되거나, 다음과 같이 class="example"을 사용하여 규범 텍스트와 구분된다:

이것은 정보 제공용 예제의 예이다.

정보 제공용 참고는 “참고”라는 말로 시작하며, 다음과 같이 class="note"를 사용하여 규범 텍스트와 구분된다:

참고, 이것은 정보 제공용 참고이다.

적합한 알고리즘

알고리즘의 일부로 명령형으로 표현된 요구사항 (예: "선행 공백 문자를 모두 제거하라" 또는 "false를 반환하고 이 단계를 중단하라")은 알고리즘을 도입할 때 사용된 핵심 단어 ("must", "should", "may" 등)의 의미로 해석되어야 한다.

알고리즘이나 특정 단계로 표현된 적합성 요구사항은 최종 결과가 동등하기만 하면 어떤 방식으로든 구현될 수 있다. 특히 이 명세에서 정의된 알고리즘은 이해하기 쉽도록 의도된 것이며 성능이 좋도록 의도된 것은 아니다. 구현자는 최적화할 것이 권장된다.

색인

이 명세에서 정의한 용어

참조로 정의된 용어

참조

규범 참조

[CSS-TEXT-4]
Elika Etemad; et al. CSS Text Module Level 4. 2024년 5월 29일. WD. URL: https://www.w3.org/TR/css-text-4/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[MEDIACAPTURE-STREAMS]
Cullen Jennings; et al. Media Capture and Streams. 2025년 10월 9일. CRD. URL: https://www.w3.org/TR/mediacapture-streams/
[RFC2119]
S. Bradner. RFC에서 요구 수준을 나타내기 위해 사용하는 핵심 단어. 1997년 3월. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[STREAMS]
Adam Rice; et al. Streams Standard. Living Standard. URL: https://streams.spec.whatwg.org/
[WEBCODECS]
Paul Adenot; Eugene Zemtsov. WebCodecs. 2026년 4월 14일 . WD. URL: https://www.w3.org/TR/webcodecs/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/
[WEBRTC]
Cullen Jennings; et al. WebRTC: Real-Time Communication in Browsers. 2025년 3월 13일. REC. URL: https://www.w3.org/TR/webrtc/

정보성 참조

[SCREEN-CAPTURE]
Jan-Ivar Bruaroey; Elad Alon. Screen Capture. 2025년 7월 17일. WD. URL: https://www.w3.org/TR/screen-capture/
[WEBRTC-NV-USE-CASES]
Bernard Aboba. WebRTC Extended Use Cases. 2023년 12월 14일. DNOTE. URL: https://www.w3.org/TR/webrtc-nv-use-cases/
[WEBTRANSPORT]
Nidhi Jaju; Victor Vasiliev; Jan-Ivar Bruaroey. WebTransport. 2026년 3월 25일. WD. URL: https://www.w3.org/TR/webtransport/

IDL 색인

[Exposed=(Window,DedicatedWorker), Transferable]
interface MediaStreamTrackHandle {
   constructor(MediaStreamTrack track);
};

[Exposed=DedicatedWorker]
interface MediaStreamTrackProcessor {
    constructor(MediaStreamTrackProcessorInit init);
    readonly attribute ReadableStream readable;
    readonly attribute unsigned long long discardedFrames;
    readonly attribute unsigned long long totalFrames;
};

typedef (MediaStreamTrack or MediaStreamTrackHandle) MediaStreamTrackOrHandle;
dictionary MediaStreamTrackProcessorInit {
  required MediaStreamTrackOrHandle track;
  [EnforceRange] unsigned short maxBufferSize;
};

[Exposed=DedicatedWorker]
interface VideoTrackGenerator {
  constructor();
  readonly attribute WritableStream writable;
  attribute boolean muted;
  readonly attribute MediaStreamTrack track;
};