1. 소개
[WEBRTC-NV-USE-CASES] 문서는 미디어에 대한 접근을 통해서만 달성할 수 있는 여러 기능(요구사항 N20-N22)을 설명하며, 여기에는 다음이 포함되지만 이에 한정되지는 않는다:
-
재미있는 모자
-
머신 러닝
-
가상 현실 게임
이러한 사용 사례는 또한 처리가 worker 스레드에서 수행될 수 있어야 함을 요구한다(요구사항 N23-N24).
이 명세는 [WEBCODECS] 및 [STREAMS]를 기반으로 하는 인터페이스를 제공하여 이러한 기능에 접근할 수 있게 한다.
이 명세는 카메라, 마이크, 화면 캡처와 같은 미디어 소스의 출력, 또는 코덱의 디코더 부분의 출력이자 코덱의 디코더 부분에 대한 입력인 원시 미디어에 대한 접근을 제공한다. 처리된 미디어는 HTML <video> 태그, RTCPeerConnection, canvas 또는 MediaRecorder를 포함하여 MediaStreamTrack을 받을 수 있는 모든 목적지에서 소비될 수 있다.
이 명세는 다음 사용 사례를 지원하는 것을 명시적인 목표로 한다:
-
비디오 처리: 이는 입력이 단일 비디오 트랙이고 출력이 변환된 비디오 트랙인 "Funny Hats" 사용 사례이다.
-
사용자 정의 비디오 싱크: 이 사용 사례에서 목적은 처리된
MediaStreamTrack을 생성하는 것이 아니라 미디어를 다른 방식으로 소비하는 것이다. 예를 들어 애플리케이션은 [WEBCODECS]와 [WEBTRANSPORT]를 사용하여 서로 다른 코덱 구성과 네트워킹 프로토콜을 사용하는RTCPeerConnection과 유사한 싱크를 만들 수 있다. -
다중 소스 처리: 이 사용 사례에서는 둘 이상의 트랙이 하나로 결합된다. 예를 들어 실시간 날씨 지도와 발표자의 카메라 트랙을 포함하는 프레젠테이션을 결합하여 날씨 보도 애플리케이션을 만들 수 있다.
참고: 오디오 사용 사례를 지원해야 하는지 여부에 대해서는 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)
-
init.
track이 유효하지 않은MediaStreamTrack이면TypeError를 던진다. -
init.
track이MediaStreamTrackHandle이면 다음 하위 단계를 실행한다:-
init.
track.[[track]]이 유효하지 않은MediaStreamTrack을 참조하고 있으면TypeError를 던진다.
-
maxBufferSize를 1로 둔다.
-
init.
maxBufferSize가 1보다 큰 정수 값을 가지면 다음 하위 단계를 실행한다:-
maxBufferSize를 init.
maxBufferSize로 설정한다. -
사용자 에이전트는 maxBufferSize를 더 낮은 값으로 클램프하기로 결정할 수 있지만, 1보다 낮게는 할 수 없다.
maxBufferSize 클램프는 예를 들어 카메라처럼 특정 시점에 제한된 수의 VideoFrame만 사용할 수 있는 일부 소스에 유용할 수 있다.
-
-
processor를 새
MediaStreamTrackProcessor객체로 둔다. -
processor.
[[track]]을 init.track으로 설정한다. -
processor.
[[maxBufferSize]]를 maxBufferSize로 설정한다. -
processor.
[[queue]]를 빈 큐로 설정한다. -
processor.
[[numPendingReads]]를 0으로 설정한다. -
processor.
[[isClosed]]를 false로 설정한다. -
processor.
[[numDiscardedFrames]]를 0으로 설정한다. -
processor.
[[numTotalFrames]]를 0으로 설정한다. -
processor를 반환한다.
2.1.4. 속성
readable, ReadableStream 타입, readonly-
[[track]]내부 슬롯에 저장된MediaStreamTrack또는MediaStreamTrackHandle가 전달한 프레임을 읽을 수 있게 한다. 이 속성은 처음 호출될 때 다음 단계에 따라 생성된다:-
this.
readable을 새ReadableStream으로 초기화한다. -
Set up this.
readable을 그 pullAlgorithm은 processorPull로, 매개변수는 this로, cancelAlgorithm은 processorCancel로, 매개변수는 this로, 그리고 highWaterMark는 0으로 설정하여 구성한다.
processorPull 알고리즘은 processor를 입력으로 받는다. 이는 다음 단계로 정의된다:
-
processor.
[[numPendingReads]]의 값을 1 증가시킨다. -
태스크를 큐에 넣어 maybeReadFrame 알고리즘을 processor를 매개변수로 하여 실행한다.
-
undefined로 해결된 promise를 반환한다.
maybeReadFrame 알고리즘은 processor를 입력으로 받는다. 이는 다음 단계로 정의된다:
-
processor.
[[queue]]가 비어 있으면, 이 단계를 중단한다. -
processor.
[[numPendingReads]]가 0이면, 이 단계를 중단한다. -
frame을 processor.
[[queue]]에서 프레임 미디어 데이터를 dequeueing한 결과로 둔다. -
processor.
[[numPendingReads]]를 1 감소시킨다. -
1단계로 간다.
processorCancel 알고리즘은 processor를 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다:
-
processorClose 알고리즘을 processor를 매개변수로 하여 실행한다.
-
undefined로 해결된 promise를 반환한다.
processorClose 알고리즘은 processor를 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다:
-
processor.
[[isClosed]]가 true이면, 이 단계를 중단한다. -
processor를 processor.
[[track]]에서 연결 해제한다. 이를 수행하는 메커니즘은 UA별로 다르며, 그 결과 processor는 더 이상 processor.[[track]]의 싱크가 아니거나, 그것이 참조하는MediaStreamTrack의 싱크가 아니다. -
processor.
[[track]]을 null로 설정한다. -
processor.
readable.[[controller]]를 Close한다. -
processor.
[[queue]]를 Empty한다. -
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를 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다:
-
processor.
[[queue]]가 processor.[[maxBufferSize]]개의 요소를 가지면, 다음 단계를 실행한다:-
droppedFrame을 processor.
[[queue]]를 dequeueing한 결과로 둔다. -
droppedFrame으로 Close VideoFrame 알고리즘을 실행한다.
-
processor.
[[numDiscardedFrames]]를 1 증가시킨다.
-
-
processor.
[[numTotalFrames]]를 1 증가시킨다. -
새 프레임 미디어 데이터를 processor.
[[queue]]에 Enqueue한다. -
태스크를 큐에 넣어 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에는
다음 슬롯이 있다:
-
핸들 객체 인스턴스가 한 번 전송되면 그 인스턴스를 다시 전송할 수 없도록 보장하는 데 사용되는
[[transferable]]내부 슬롯. -
프록시된
MediaStreamTrack객체에 대한 약한 참조인[[track]]내부 슬롯.
2.1.7. 생성자
MediaStreamTrackHandle(track)
-
handle을 새
MediaStreamTrackHandle객체로 둔다. -
handle.
[[transferable]]을 true로 설정한다. -
handle.
[[track]]을 track에 대한 내부 참조로 설정한다. -
handle을 반환한다.
MediaStreamTrackHandle
전송 단계는, value와 dataHolder가 주어졌을 때, 다음과 같다:
-
value.
[[transferable]]이 false이면DataCloneErrorDOMException을 던진다. -
value.
[[transferable]]을 false로 설정한다. -
dataHolder.
[[track]]을 handle.[[track]]으로 설정한다. -
value.
[[track]]을 null로 설정한다.
MediaStreamTrackHandle
전송 수신 단계는, dataHolder와
handle이 주어졌을 때, 다음과 같다:
-
handle.
[[track]]을 dataHolder.[[track]]으로 설정한다.
MediaStreamTrackHandle은
프록시된 MediaStreamTrack의
수명을 늘릴 수 있다.
가비지 컬렉션 관점에서 보면 MediaStreamTrackHandle이
자신의 MediaStreamTrack에
대한 강한 참조를
보유하는 것과 같다.
또한 MediaStreamTrack은
전송 중인 데이터 홀더에서 참조되고 있으면 가비지 컬렉션되어서는 안 된다.
2.2. VideoTrackGenerator
VideoTrackGenerator는
MediaStreamTrack을
위한 비디오 소스를
생성할 수 있게 하며,
이는
MediaStream 모델에서 VideoFrame
객체의 Stream으로부터 프레임을 생성한다. 이는 두 readonly
속성을 가진다: writable
WritableStream와
track
MediaStreamTrack이다.
VideoTrackGenerator는
자신의 writable
속성의 underlying sink]이다. track
속성은
출력이다. 동일한 VideoTrackGenerator에
연결되는 추가 트랙은
track
속성에 대해
clone
메서드를 사용하여 생성할 수 있다.
WritableStream은
VideoFrame
객체를 받는다.
VideoFrame이
writable에
쓰이면,
프레임의 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()
-
generator를 새
VideoTrackGenerator객체로 둔다. -
track을 source가 generator로, tieSourceToContext가
false로 설정된, 새로 생성된MediaStreamTrack으로 둔다. -
generator.
track을 track으로 초기화한다. -
generator를 반환한다.
2.2.4. 속성
writable, WritableStream 타입, readonly-
비디오 프레임을
VideoTrackGenerator에 쓸 수 있게 한다. 이 속성에 처음 접근할 때 다음 단계로 반드시 초기화되어야 한다:-
this.
writable을 새WritableStream으로 초기화한다. -
Set up this.
writable을, 그 writeAlgorithm은 writeFrame로 this를 매개변수로 하여 설정하고, closeAlgorithm은 closeWritable로 this를 매개변수로 하여 설정하며, abortAlgorithm은 closeWritable로 this를 매개변수로 하여 설정한다.
writeFrame 알고리즘은 generator와 frame을 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다:
-
frame이
VideoFrame객체가 아니면,TypeError로 거부된 promise를 반환한다. -
frame의
[[Detached]]내부 슬롯 값이 true이면,TypeError로 거부된 promise를 반환한다. -
generator.
[[isMuted]]의 값이 false이면, generator로부터 공급되는 각 live 트랙(이름은 track)에 대해 다음 단계를 실행한다:-
clone을 frame으로 Clone videoFrame 알고리즘을 실행한 결과로 둔다.
-
clone을 track에 보낸다.
-
-
frame으로 Close VideoFrame 알고리즘을 실행한다.
-
undefined로 해결된 promise를 반환한다.
closeWritable 알고리즘은 generator를 입력으로 받는다. 이는 다음 단계를 실행하여 정의된다.
-
generator로부터 공급되는 각 트랙
t에 대해,t를 end한다. -
undefined로 해결된 promise를 반환한다.
-
미디어 데이터가 트랙에 전송될 때, UA는 트랙에 전송되는 미디어 데이터가 트랙의 제약을 만족하도록 처리(예: 자르기 및 축소)를 적용할 수 있다. 각 트랙은 그 제약에 따라 서로 다른 버전의 미디어 데이터를 받을 수 있다.
muted, boolean 타입-
VideoTrackGenerator를 음소거한다. getter 단계는 this.[[isMuted]]를 반환하는 것이다. setter 단계는 값 newValue가 주어졌을 때 다음과 같다:-
newValue가 this.
[[isMuted]]와 같으면, 이 단계를 중단한다. -
this.
[[isMuted]]를 newValue로 설정한다. -
이 이벤트 루프 실행에서 아직 하나도 큐에 넣어지지 않았다면, 다음 단계를 실행할 태스크를 큐에 넣는다:
-
settledValue를 this.
[[isMuted]]로 둔다. -
this에 의해 공급되는 각 live 트랙에 대해, settledValue로 트랙의 muted 상태를 설정하는 태스크를 큐에 넣는다.
-
-
track, MediaStreamTrack 타입, readonlyMediaStreamTrack출력. getter 단계는 this.[[track]]을 반환하는 것이다.
2.2.5. MediaStreamTrack 동작의 특수화
VideoTrackGenerator는
하나 이상의 MediaStreamTrack의
소스로 동작한다.
이 절은
VideoTrackGenerator로부터
공급되는
MediaStreamTrack이
어떻게 동작하는지에 대한 설명을 추가한다.
2.2.5.1. stop
stop
메서드는 트랙을 정지한다. VideoTrackGenerator로부터
공급되는 마지막 트랙이
종료되면, 그 VideoTrackGenerator의
writable은
닫힌다.
2.2.5.2. 제약 가능한 속성
다음 제약 가능한 속성은
VideoTrackGenerator로부터
공급되는 모든 MediaStreamTrack에
대해
정의된다:
| 속성 이름 | 값 | 참고 |
|---|---|---|
| width |
ConstrainULong
|
설정으로서 이는 트랙이 받은 최신 프레임의 픽셀 단위 너비이다.
capability로서 max는 VideoFrame이
가질 수 있는
가장 큰 너비를 반드시 반영해야 하며, min은
VideoFrame이
가질 수 있는
가장 작은 너비를 반드시 반영해야 한다.
|
| height |
ConstrainULong
|
설정으로서 이는 트랙이 받은 최신 프레임의 픽셀 단위 높이이다.
capability로서 max는
VideoFrame이
가질 수 있는 가장 큰 높이를
반드시 반영해야 하며, min은
VideoFrame이
가질 수 있는
가장 작은 높이를 반드시 반영해야 한다.
|
| frameRate |
ConstrainDouble
|
설정으로서 이는 트랙이 최근에 받은 프레임을 기반으로 한
프레임 레이트의 추정치이다.
capability로서 min은 반드시 0이어야 하고
max는 시스템이 지원하는 최대 프레임 레이트여야 한다.
|
| aspectRatio |
ConstrainDouble
|
설정으로서 이는 트랙이 전달한 최신 프레임의 종횡비이다;
이는 픽셀 단위 너비를 픽셀 단위 높이로 나눈 값을
소수점 이하 10자리로 반올림한 double이다. capability로서
min은 VideoFrame이
지원하는
가장 작은 종횡비여야 하고,
max는 VideoFrame이
지원하는
가장 큰 종횡비여야 한다.
|
| resizeMode |
ConstrainDOMString
|
설정으로서 이 문자열은
VideoResizeModeEnum의
멤버 중 하나여야 한다.
값 "none"은
MediaStreamTrack이 출력하는 프레임이 어떤 제약과 관계없이
트랙을 뒷받침하는
writable에
쓰인 프레임의 수정되지 않은
버전임을 의미한다.
값 "crop-and-scale"은
MediaStreamTrack이 출력하는 프레임이 트랙의 width, height 및
aspectRatio 제약 값에 기반하여
소스 프레임의 잘리거나 및/또는 축소된 버전일 수 있음을
의미한다.
capability로서 값 "none"과
"crop-and-scale"은
모두 반드시 존재해야 한다.
|
applyConstraints
메서드는 VideoTrackGenerator로부터
공급되는 비디오 MediaStreamTrack에
적용될 때
위에 정의된 속성을 지원한다.
이는 예를 들어 프레임 크기를 조정하거나 트랙의 프레임 레이트를 조절하는 데 사용될 수 있다.
이러한 제약은
VideoTrackGenerator의
writable에
쓰이는 VideoFrame
객체에는 아무 효과가 없고,
제약이 적용된 트랙의 출력에만 효과가 있다는 점에 유의한다.
또한 VideoTrackGenerator는
원칙적으로
지원되는 제약 가능한 속성에 대한 임의의 설정으로 미디어 데이터를 생성할 수 있으므로,
VideoTrackGenerator가
뒷받침하는 트랙에 대한
applyConstraints
호출은 주어진 제약이
getCapabilities가
보고한
시스템 지원 범위를 벗어나지 않는 한, 일반적으로
OverconstrainedError로
실패하지 않는다는 점도 유의한다.
2.2.5.3. 이벤트와 속성
이벤트와 속성은 모든MediaStreamTrack의
경우와 동일하게 동작한다.
VideoTrackGenerator의
writable
스트림이 닫히면, 이에 연결된 모든 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() 작업은 이 맥락에서 문제가 있다:
-
과도한 큐잉을 방지하는 역압력 메커니즘을 무력화한다
-
동일한 버퍼에 대한 여러 링크를 생성하므로, 어떤 소비자가 버퍼를 destroy()해야 하는지에 대한 문제가 해결하기 어려워진다
따라서 미디어를 포함하는 Streams에 tee()를 사용하는 것은 그 영향을 충분히 이해한 경우에만 수행해야 한다. 대신, 사용 사례에 더 적합한 스트림 분할용 사용자 정의 요소를 사용해야 한다.
-
두 분기가 모두 프레임을 폐기할 수 있어야 한다면, frame을 clone()하고 두 큐에 각각 별도의 사본을 enqueue한다. 이는 함수 ReadableStreamTee(stream, cloneForBranch2=true)에 해당한다. 그런 다음 아래 대안 중 하나를 선택한다.
-
한 분기는 모든 프레임을 필요로 하고 다른 분기는 드롭된 프레임을 허용한다면, 모든 프레임이 필요한 스트림에 버퍼를 enqueue하고, 그 스트림의 역압력 신호를 사용하여 소스에서 읽는 것을 멈춘다. 다른 스트림의 역압력 신호가 공간이 있음을 나타내면, 동일한 프레임을 그 큐에도 enqueue한다.
-
어느 스트림도 드롭된 프레임을 허용하지 않는다면, 결합된 역압력 신호를 사용하여 소스에서 읽는 것을 멈춘다. 이 경우 버퍼 크기가 모두 1이면 프레임은 lockstep으로 처리된다.
-
프로세스에 할당된 기반 버퍼 풀이 고갈될 때에만 들어오는 스트림이 멈추는 것이 괜찮다면, 표준 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)의
보안 및 개인정보 보호는
동일 출처 정책에 의존한다. 즉,
VideoTrackGenerator가
MediaStreamTrack
형태로
사용 가능하게 만들 수 있는 데이터는 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 ; };
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 브랜치를 가리키는 링크가 여기에 배치될 것이다.