1. Introduction
The [WEBRTC-NV-USE-CASES] document describes several functions that can only be achieved by access to media (requirements N20-N22), including, but not limited to:
-
Funny Hats
-
Machine Learning
-
Virtual Reality Gaming
These use cases further require that processing can be done in worker threads (requirement N23-N24).
Furthermore, the "trusted JavaScript cloud conferencing" use case requires such processing to be done on encoded media, not just the raw media.
This specification gives an interface inspired by [WEB-CODECS] to provide access to such functionality while retaining the setup flow of RTCPeerConnection.
This iteration of the specification provides access to encoded media, which is the output of the encoder part of a codec and the input to the decoder part of a codec.
2. Terminology
3. Specification
The Streams definition doesn’t use WebIDL much, but the WebRTC spec does. This specification shows the IDL extensions for WebRTC.
It
uses
an
additional
API
on
RTCRtpSender
and
RTCRtpReceiver
to
insert
the
processing
into
the
pipeline.
// New dictionarydictionary {
RTCInsertableStreams ReadableStream ;
readable WritableStream ; };
writable typedef (SFrameTransform or RTCRtpScriptTransform ); // New methods for RTCRtpSender and RTCRtpReceiver
RTCRtpTransform partial interface RTCRtpSender {attribute RTCRtpTransform ?transform ; };partial interface RTCRtpReceiver {attribute RTCRtpTransform ?transform ; };
3.1. Extension operation
At
the
time
when
a
codec
is
initialized
as
part
of
the
encoder,
and
the
corresponding
flag
is
set
in
the
RTCPeerConnection
's
RTCConfiguration
argument,
ensure
that
the
codec
is
disabled
and
produces
no
output.
3.1.1. Stream creation
At
construction
of
each
RTCRtpSender
or
RTCRtpReceiver
,
run
the
following
steps:
-
Initialize this .
[[transform]]
to null. -
Initialize this .
[[readable]]
to a newReadableStream
. -
Set up this .
[[readable]]
. this .[[readable]]
is provided frames using the readEncodedData algorithm given this as parameter. -
Set this .
[[readable]]
.[[owner]]
to this . -
Initialize this .
[[writable]]
to a newWritableStream
. -
Set up this .
[[writable]]
with its writeAlgorithm set to writeEncodedData given this as parameter and its sizeAlgorithm to an algorithm that returns0
.Chunk size is set to 0 to explictly disable streams backpressure on the write side.
-
Set this .
[[writable]]
.[[owner]]
to this . -
Initialize this .
[[pipeToController]]
to null. -
Initialize this .
[[lastReceivedFrameCounter]]
to0
. -
Initialize this .
[[lastEnqueuedFrameCounter]]
to0
. -
Queue a task to run the following steps:
-
If this .
[[pipeToController]]
is not null, abort these steps. -
Set this .
[[pipeToController]]
to a newAbortController
. -
Call pipeTo with this .
[[readable]]
, this .[[writable]]
, preventClose equal to true, preventAbort equal to true, preventCancel equal to true and this .[[pipeToController]]
.signal.
-
3.1.2. Stream processing
The readEncodedData algorithm is given a rtcObject as parameter. It is defined by running the following steps:
-
Wait for a frame to be produced by rtcObject ’s encoder if it is a
RTCRtpSender
or rtcObject ’s packetizer if it is aRTCRtpReceiver
. -
Increment rtcObject .
[[lastEnqueuedFrameCounter]]
by1
. -
Let frame be the newly produced frame.
-
Set frame .
[[owner]]
to rtcObject . -
Set frame .
[[counter]]
to rtcObject .[[lastEnqueuedFrameCounter]]
. -
Enqueue frame in rtcObject .
[[readable]]
.
The writeEncodedData algorithm is given a rtcObject as parameter and a frame as input. It is defined by running the following steps:
-
If frame .
[[owner]]
is not equal to rtcObject , abort these steps and return a promise resolved with undefined. A processor cannot create frames, or move frames between streams. -
If frame .
[[counter]]
is equal or smaller than rtcObject .[[lastReceivedFrameCounter]]
, abort these steps and return a promise resolved with undefined. A processor cannot reorder frames, although it may delay them or drop them. -
Set rtcObject .
[[lastReceivedFrameCounter]]
to frame[[counter]]
. -
Enqueue the frame for processing as if it came directly from the encoded data source, by running one of the following steps:
-
If rtcObject is a
RTCRtpSender
, enqueue it to rtcObject ’s packetizer, to be processed in parallel . -
If rtcObject is a
RTCRtpReceiver
, enqueue it to rtcObject ’s decoder, to be processed in parallel .
-
-
Return a promise resolved with undefined.
On
sender
side,
as
part
of
readEncodedData
,
frames
produced
by
rtcObject
’s
encoder
MUST
be
enqueued
in
rtcObject
.
[[readable]]
in
the
encoder’s
output
order.
As
writeEncodedData
ensures
that
the
transform
cannot
reorder
frames,
the
encoder’s
output
order
is
also
the
order
followed
by
packetizers
to
generate
RTP
packets
and
assign
RTP
packet
sequence
numbers.
On
receiver
side,
as
part
of
readEncodedData
,
frames
produced
by
rtcObject
’s
packetizer
MUST
be
enqueued
in
rtcObject
.
[[readable]]
in
the
same
encoder’s
output
order.
To
ensure
the
order
is
respected,
the
depacketizer
will
typically
use
RTP
packet
sequence
numbers
to
reorder
RTP
packets
as
needed
before
enqueuing
frames
in
rtcObject
.
[[readable]]
.
As
writeEncodedData
ensures
that
the
transform
cannot
reorder
frames,
this
will
be
the
order
expected
by
rtcObject
’s
decoder.
3.2. Extension attribute
A
RTCRtpTransform
has
two
private
slots
called
[[readable]]
and
[[writable]]
.
Each RTCRtpTransform has an association steps set, which is empty by default.
The
transform
getter
steps
are:
-
Return this .
[[transform]]
.
The
transform
setter
steps
are:
-
Let transform be the argument to the setter.
-
Let checkedTransform set to transform if it is not null or to an identity transform stream otherwise.
-
Let reader be the result of getting a reader for checkedTransform .
[[readable]]
. -
Let writer be the result of getting a writer for checkedTransform .
[[writable]]
. -
Initialize newPipeToController to a new
AbortController
. -
If this .
[[pipeToController]]
is not null, run the following steps:-
Add the chain transform algorithm to this .
[[pipeToController]]
.signal. -
signal abort this .
[[pipeToController]]
.signal.
-
-
Else, run the chain transform algorithm steps.
-
Set this .
[[pipeToController]]
to newPipeToController . -
Set this .
[[transform]]
to transform . -
Run the steps in the set of association steps of transform with this .
The chain transform algorithm steps are defined as:
-
If newPipeToController is aborted , abort these steps.
-
Release reader .
-
Release writer .
-
Assert that newPipeToController is the same object as rtcObject .
[[pipeToController]]
. -
Call pipeTo with rtcObject .
[[readable]]
, checkedTransform .[[writable]]
, preventClose equal to false, preventAbort equal to false, preventCancel equal to true and newPipeToController .signal. -
Call pipeTo with checkedTransform .
[[readable]]
, rtcObject .[[writable]]
, preventClose equal to true, preventAbort equal to true, preventCancel equal to false and newPipeToController .signal.
This algorithm is defined so that transforms can be updated dynamically. There is no guarantee on which frame will happen the switch from the previous transform to the new transform.
If
a
web
application
sets
the
transform
synchronously
at
creation
of
the
RTCRtpSender
(for
instance
when
calling
addTrack),
the
transform
will
receive
the
first
frame
generated
by
the
RTCRtpSender
's
encoder.
Similarly,
if
a
web
application
sets
the
transform
synchronously
at
creation
of
the
RTCRtpReceiver
(for
instance
when
calling
addTrack,
or
at
track
event
handler),
the
transform
will
receive
the
first
full
frame
generated
by
the
RTCRtpReceiver
's
packetizer.
4. SFrameTransform
The API presented in this section represents a preliminary proposal based on protocol proposals that have not yet been adopted by an IETF WG. As a result, both the API and underlying protocol are likely to change significantly going forward.
enum {
SFrameTransformRole ,
"encrypt" };
"decrypt" dictionary {
SFrameTransformOptions SFrameTransformRole = "encrypt"; };
role typedef [EnforceRange ]unsigned long long ;
SmallCryptoKeyID typedef (SmallCryptoKeyID or bigint ); [
CryptoKeyID Exposed =(Window ,DedicatedWorker )]interface {
SFrameTransform constructor (optional SFrameTransformOptions = {});
options Promise <undefined >setEncryptionKey (CryptoKey ,
key optional CryptoKeyID );
keyID attribute EventHandler ; };
onerror SFrameTransform includes GenericTransformStream ;enum {
SFrameTransformErrorEventType ,
"authentication" ,
"keyID" }; [
"syntax" Exposed =(Window ,DedicatedWorker )]interface :
SFrameTransformErrorEvent Event {(
constructor DOMString ,
type SFrameTransformErrorEventInit );
eventInitDict readonly attribute SFrameTransformErrorEventType ;
errorType readonly attribute CryptoKeyID ?;
keyID readonly attribute any ; };
frame dictionary :
SFrameTransformErrorEventInit EventInit {required SFrameTransformErrorEventType ;
errorType required any ;
frame CryptoKeyID ?; };
keyID
The
new
SFrameTransform(
options
)
constructor
steps
are:
-
Let transformAlgorithm be an algorithm which takes a frame as input and runs the SFrame transform algorithm with this and frame .
-
Set this .
[[transform]]
to a newTransformStream
. -
Set up this .
[[transform]]
with transformAlgorithm set to transformAlgorithm . -
Let options be the method’s first argument.
-
Set this .
[[role]]
to options ["role
"]. -
Set this .
[[readable]]
to this .[[transform]]
.[[readable]]
. -
Set this .
[[writable]]
to this .[[transform]]
.[[writable]]
.
4.1. Algorithm
The SFrame transform algorithm, given sframe as a SFrameTransform object and frame , runs these steps:
-
Let role be sframe .
[[role]]
. -
If frame .
[[owner]]
is aRTCRtpSender
, set role to 'encrypt'. -
If frame .
[[owner]]
is aRTCRtpReceiver
, set role to 'decrypt'. -
Let data be undefined.
-
If frame is a
BufferSource
, set data to frame . -
If frame is a
RTCEncodedAudioFrame
, set data to frame .data
-
If frame is a
RTCEncodedVideoFrame
, set data to frame .data
-
If data is undefined, abort these steps.
-
Let buffer be the result of running the SFrame algorithm with data and role as parameters. This algorithm is defined by the SFrame specification and returns an
ArrayBuffer
. -
If the SFrame algorithm exits abruptly with an error, queue a task to run the following sub steps:
-
If the processing fails on decryption side due to data not following the SFrame format, fire an event named
error
at sframe , using theSFrameTransformErrorEvent
interface with itserrorType
attribute set tosyntax
and itsframe
attribute set to frame . -
If the processing fails on decryption side due to the key identifier parsed in data being unknown, fire an event named
error
at sframe , using theSFrameTransformErrorEvent
interface with itserrorType
attribute set tokeyID
, itsframe
attribute set to frame and itskeyID
attribute set to the keyID value parsed in the SFrame header. -
If the processing fails on decryption side due to validation of the authentication tag, fire an event named
error
at sframe , using theSFrameTransformErrorEvent
interface with itserrorType
attribute set toauthentication
and itsframe
attribute set to frame . -
Abort these steps.
-
-
If frame is a
BufferSource
, set frame to buffer . -
If frame is a
RTCEncodedAudioFrame
, set frame .data
to buffer . -
If frame is a
RTCEncodedVideoFrame
, set frame .data
to buffer . -
Enqueue frame in sframe .
[[transform]]
.
4.2. Methods
The
setEncryptionKey(
key
,
keyID
)
method
steps
are:
-
Let promise be a new promise .
-
If keyID is a
bigint
which cannot be represented as a integer between 0 and 2 64 -1 inclusive, reject promise with aRangeError
exception. -
Otherwise, in parallel , run the following steps:
-
Set key with its optional keyID as key material to use for the SFrame transform algorithm, as defined by the SFrame specification .
-
If setting the key material fails, reject promise with an
InvalidModificationError
exception and abort these steps. -
Resolve promise with undefined.
-
-
Return promise .
5. RTCRtpScriptTransform
// New enum for video frame types. Will eventually re-use the equivalent defined // by WebCodecs.enum {
RTCEncodedVideoFrameType ,
"empty" ,
"key" , };
"delta" dictionary {
RTCEncodedVideoFrameMetadata long long ;
frameId sequence <long long >;
dependencies unsigned short ;
width unsigned short ;
height long ;
spatialIndex long ;
temporalIndex long ;
synchronizationSource sequence <long >; }; // New interfaces to define encoded video and audio frames. Will eventually // re-use or extend the equivalent defined in WebCodecs. [
contributingSources Exposed =(Window ,DedicatedWorker )]interface {
RTCEncodedVideoFrame readonly attribute RTCEncodedVideoFrameType ;
type readonly attribute unsigned long long ;
timestamp attribute ArrayBuffer ;
data RTCEncodedVideoFrameMetadata (); };
getMetadata dictionary {
RTCEncodedAudioFrameMetadata long ;
synchronizationSource sequence <long >; }; [
contributingSources Exposed =(Window ,DedicatedWorker )]interface {
RTCEncodedAudioFrame readonly attribute unsigned long long ;
timestamp attribute ArrayBuffer ;
data RTCEncodedAudioFrameMetadata (); }; // New interfaces to expose JavaScript-based transforms. [
getMetadata Exposed =DedicatedWorker ]interface :
RTCTransformEvent Event {readonly attribute RTCRtpScriptTransformer ; };
transformer partial interface DedicatedWorkerGlobalScope {attribute EventHandler ; }; [
onrtctransform Exposed =DedicatedWorker ]interface {
RTCRtpScriptTransformer readonly attribute ReadableStream readable ;readonly attribute WritableStream writable ;readonly attribute any options ;Promise <undefined >generateKeyFrame (optional sequence <DOMString >);
rids Promise <undefined >sendKeyFrameRequest (); }; [Exposed =Window ]interface {
RTCRtpScriptTransform );constructor (Worker ,
worker optional any ,
options optional sequence <object >); };
transfer
5.1. Operations
The
new
RTCRtpScriptTransform(
worker
,
options
,
transfer
)
constructor
steps
are:
-
Set t1 to an identity transform stream .
-
Set t2 to an identity transform stream .
-
Set this .
[[writable]]
to t1 .[[writable]]
. -
Set this .
[[readable]]
to t2 .[[readable]]
. -
Let serializedOptions be the result of StructuredSerializeWithTransfer ( options , transfer ).
-
Let serializedReadable be the result of StructuredSerializeWithTransfer ( t1 .
[[readable]]
, « t1 .[[readable]]
»). -
Let serializedWritable be the result of StructuredSerializeWithTransfer ( t2 .
[[writable]]
, « t2 .[[writable]]
»). -
Queue a task on the DOM manipulation task source worker ’s global scope to run the following steps:
-
Let transformerOptions be the result of StructuredDeserialize ( serializedOptions , the current Realm).
-
Let readable be the result of StructuredDeserialize ( serializedReadable , the current Realm).
-
Let writable be the result of StructuredDeserialize ( serializedWritable , the current Realm).
-
Let transformer be a new
RTCRtpScriptTransformer
. -
Set transformer .
[[options]]
to transformerOptions . -
Set transformer .
[[readable]]
to readable . -
Set transformer .
[[writable]]
to writable . -
Let event be the result of creating an event with
RTCTransformEvent
. -
Set event .type attribute to "rtctransform".
-
Set event .transformer to transformer .
-
Dispatch event on worker ’s global scope.
-
// FIXME: Describe error handling (worker closing flag true at RTCRtpScriptTransform creation time. And worker being terminated while transform is processing data).
Each RTCRtpScriptTransform has the following set of association steps , given rtcObject :
Let transform be the
RTCRtpScriptTransform
object that owns the association steps .Let encoder be rtcObject ’s encoder if rtcObject is a
RTCRtpSender
or undefined otherwise.Let depacketizer be rtcObject ’s depacketizer if rtcObject is a
RTCRtpReceiver
or undefined otherwise.Queue a task on the DOM manipulation task source worker ’s global scope to run the following steps:
Let transformer be the
RTCRtpScriptTransformer
object associated to transform .Set transformer .
[[encoder]]
to encoder .Set transformer .
[[depacketizer]]
to depacketizer .
The
generateKeyFrame(
rids
)
method
steps
are:
Let promise be a new promise.
In parallel , run the generate key frame algorithm with promise , this .
[[encoder]]
and rids .Return promise .
The
sendKeyFrameRequest()
method
steps
are:
Let promise be a new promise.
In parallel , run the send request key frame algorithm with promise and this .
[[depacketizer]]
.Return promise .
5.2. Attributes
A
RTCRtpScriptTransformer
has
three
the
following
private
slots
called
[[depacketizer]]
,
[[encoder]]
,
[[options]]
,
[[readable]]
and
[[writable]]
.
In
addition,
a
RTCRtpScriptTransformer
is
always
associated
with
its
parent
RTCRtpScriptTransform
transform.
This
allows
algorithms
to
go
from
an
RTCRtpScriptTransformer
object
to
its
RTCRtpScriptTransform
parent
and
vice
versa.
The
options
getter
steps
are:
-
Return this .
[[options]]
.
The
readable
getter
steps
are:
-
Return this .
[[readable]]
.
The
writable
getter
steps
are:
-
Return this .
[[writable]]
.
5.3. KeyFrame Algorithms
The generate key frame algorithm , given promise , encoder and rids , is defined by running these steps:
If encoder is undefined, reject promise with
InvalidStateError
, abort these steps.If encoder is not processing video frames, reject promise with
InvalidStateError
, abort these steps.If rids is defined, validate each of the RID values in rids . If any RID value is invalid, reject promise with
NotAllowedError
and abort these steps.Gather a list of video encoders, named videoEncoders from encoder .
If rids is not empty, remove from videoEncoders any video encoder that does not match a value in rids .
If videoEncoders is empty, reject promise with
NotFoundError
and abort these steps. videoEncoders is expected to be empty if the correspondingRTCRtpSender
is not active, or the correspondingRTCRtpSender
track is ended.For each videoEncoder in videoEncoders , instruct videoEncoder to generate a key frame for the next provided video frame.
Wait until a new video frame is provided to each videoEncoder in videoEncoders .
Resolve promise with undefined.
The send request key frame algorithm , given promise and depacketizer , is defined by running these steps:
If depacketizer is undefined, reject promise with
InvalidStateError
, abort these steps.If depacketizer is not processing video packets, reject promise with
InvalidStateError
, abort these steps.If sending a Full Intra Request (FIR) by depacketizer ’s receiver is not deemed appropriate, resolve promise with undefined and abort these steps. Section 4.3.1 of [RFC5104] provides guidelines of how and when it is appropriate to sending a Full Intra Request.
Generate a Full Intra Request (FIR) packet as defined in section 4.3.1 of [RFC5104] and send it through depacketizer ’s receiver.
Resolve promise with undefined.
6. RTCRtpSender extension
An
additional
API
on
RTCRtpSender
is
added
to
complement
the
generation
of
key
frame
added
to
RTCRtpScriptTransformer
.
partial interface RTCRtpSender {Promise <undefined >generateKeyFrame (optional sequence <DOMString >); };
rids
6.1. Extension operation
The
generateKeyFrame(
rids
)
method
steps
are:
Let promise be a new promise.
In parallel , run the generate key frame algorithm with promise , this ’s encoder and rids .
Return promise .
7. Privacy and security considerations
This API gives Javascript access to the content of media streams. This is also available from other sources, such as Canvas and WebAudio.
However, streams that are isolated (as specified in [WEBRTC-IDENTITY] ) or tainted with another origin, cannot be accessed using this API, since that would break the isolation rule.
The API will allow access to some aspects of timing information that are otherwise unavailable, which allows some fingerprinting surface.
The API will give access to encoded media, which means that the JS application will have full control over what’s delivered to internal components like the packetizer or the decoder. This may require additional care with auditing how data is handled inside these components.
For instance, packetizers may expect to see data only from trusted encoders, and may not be audited for reception of data from untrusted sources.
7.
8.
Examples
See the explainer document .