Skip to content

Commit 5d36cea

Browse files
authored
Merge pull request w3c#23 from WICG/improve-examples
Shorten and simplify examples
2 parents 289ecbe + f366da7 commit 5d36cea

File tree

2 files changed

+95
-156
lines changed

2 files changed

+95
-156
lines changed

explainer.md

Lines changed: 91 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -79,136 +79,152 @@ A **VideoTrackWriter** converts a WritableStream of DecodedVideoFrame into a Med
7979
### Example of decode for low-latency live streaming or cloud gaming
8080

8181
```javascript
82-
const transport = ...; // Source of muxed/serialized messsages
83-
// App-specific serialization/containerization code not provided by browser
84-
const demuxer = ...; // Transforms to demuxed/deserialized frames
85-
const audioBuffer = ...; // TransformStream that buffers frames
86-
const videoBuffer = ...;
82+
// The app provides ReadableStreams of encoded audio and video
83+
// in the form of byte arrays defined by a codec such as vp8 or opus
84+
// (not in a media container such as mp4 or webm).
85+
const {encodedAudio, encodedVideo} = ...;
86+
// The app also provides an element to render the decoded media
8787
const videoElem = ...;
8888

89-
transport.readable.pipeTo(demuxer.writable);
89+
// To render to an element, the ReadableStream of decoded audio and video
90+
// must be converted to a MediaStream using TrackWriters.
91+
const videoWriter = new VideoTrackWriter();
92+
const audioWriter = new AudioTrackWriter();
93+
videoElem.srcObject = new MediaStream([audioWriter.track, videoWriter.track]);
9094

95+
// Finally the decoders are created and the encoded media is piped through the decoder
96+
// and into the TrackerWriters which converts them into MediaStreamTracks.
9197
const audioDecoder = new AudioDecoder({codec: 'opus'});
92-
const audioTrackWriter = new AudioTrackWriter();
93-
demuxer.audio
94-
.pipeThrough(audioBuffer)
95-
.pipeThrough(audioDecoder)
96-
.pipeTo(audioTrackWriter.writable);
97-
9898
const videoDecoder = new VideoDecoder({codec: 'vp8'});
99-
const videoWriter = new VideoTrackWriter();
100-
demuxer.video
101-
.pipeThrough(videoBuffer)
102-
.pipeThrough(videoDecoder)
103-
.pipeTo(videoTrackWriter.writable);
104-
105-
videoElem.srcObject = new MediaStream([audioWriter.track, videoWriter.track]);
99+
encodedAudio.pipeThrough(audioDecoder).pipeTo(audioWriter.writable);
100+
encodedVideo.pipeThrough(videoDecoder).pipeTo(videoWriter.writable);
106101
```
107102

108103
### Example of encode for live streaming upload
109104

110105
```javascript
111-
// App-specific tracks, muxer, and transport
112-
const audioTrack = ...;
113-
const videoTrack = ...;
114-
// App-specific serialization/containerization code not provided by browser
115-
const muxer = ...; // Serializes frames for transport
116-
const transport = ...; // Sends muxed frames to server
117-
118-
const audioTrackReader = new AudioTrackReader(audioTrack);
106+
// The app provides sources of audio and video, perhaps from getUserMedia.
107+
const {audioTrack, videoTrack} = ...;
108+
// The app also provides a way to serialize/containerize encoded media and upload it.
109+
// The browser provides the app byte arrays defined by a codec such as vp8 or opus
110+
// (not in a media container such as mp4 or webm).
111+
function muxAndSend(encodedAudio, encodedVideo) { ... };
112+
113+
// First, the tracks are converted to ReadableStreams of unencoded audio and video.
114+
const audio = (new AudioTrackReader(audioTrack)).readable;
115+
const video = (new VideoTrackReader(videoTrack)).readable;
116+
117+
// Lastly, build the encoders and pass media through them.
119118
const audioEncoder = new AudioEncoder({
120119
codec: 'opus',
121120
settings: {
122121
targetBitRate: 60_000,
123122
},
124123
});
125-
audioTrackReader.readable
126-
.pipeThrough(audioEncoder)
127-
.pipeTo(muxer.audio);
128-
129-
const videoTrackReader = new VideoTrackReader(videoTrack);
130124
const videoEncoder = new VideoEncoder({
131125
codec: 'vp8',
132126
settings: {
133127
targetBitRate: 1_000_000
134128
},
135129
});
136-
videoTrackReader.readable
137-
.pipeThrough(videoEncoder)
138-
.pipeTo(muxer.video);
139-
140-
muxer.readable.pipeTo(transport.writable);
130+
// TODO: Example of putting dynamic settings in media flow
131+
const encodedAudio = audio.pipeThrough(audioEncoder);
132+
const encodedVideo = video.pipeThrough(videoEncoder);
141133

134+
muxAndSend(encodedAudio, encodedVideo);
142135
```
143136

144137
### Example of transcoding or offline encode/decode
145138

146139
```javascript
147-
// App-specific sources and sinks of media
148-
const input = ...; // Reads container from source (like a file)
149-
const output = ...; // Writes container to source (like a file)
150-
// App-specific containerization code not provided by browser
151-
const demuxer = ...; // Reads container into frames
152-
const muxer = ...; // Writes frames into container
140+
// App provides a way to demux (decontainerize) and mux (containerize) media.
141+
function demux(input) { ... }
142+
function mux(audio, video) { ... }
143+
const input = ...;
153144

154145
const audioDecoder = new AudioDecoder({codec: 'aac'});
146+
const videoDecoder = new VideoDecoder({codec: 'h264'});
147+
155148
const audioEncoder = new AudioEncoder({
156149
codec: 'opus',
157150
settings: {
158151
targetBitRate: 60_000,
159152
},
160153
});
161-
demuxer.audio
162-
.pipeThrough(audioDecoder)
163-
.pipeThrough(audioEncoder)
164-
.pipeTo(muxer.audio);
165-
166-
const videoDecoder = new VideoDecoder({codec: 'h264'});
167154
const videoEncoder = new VideoEncoder({
168155
codec: 'vp8',
169156
settings: {
170157
bitsPerSecond: 1_000_000,
171158
},
172159
});
173-
demuxer.video
174-
.pipeThrough(videoDecoder)
175-
.pipeThrough(videoEncoder)
176-
.pipeTo(muxer.video);
177160

178-
input.readable.pipeInto(demuxer.writable);
179-
muxer.readable.pipeInto(output.writable);
161+
const {audioIn, videoIn} = demux(input);
162+
const audioOut = audioIn.pipeThrough(audioDecoder).pipeThrough(audioEncoder);
163+
const videoOut = videoIn.pipeThrough(videoDecoder).pipeThrough(videoEncoder);
164+
const output = mux(audioOut, videoOut);
165+
180166
```
181167

182-
### Example of advanced real-time communication
168+
### Example of real-time communication
183169

184170
```javascript
185-
// Sender has app-specific encryptor and transport
186-
const audioTrack = ...;
187-
const videoTrack = ...;
188-
const audioEncryptor = ...; // TransformStream that encrypts encoded media
189-
const videoEncryptor = ...;
190-
// App-specific containerization code not provided by browser
191-
const muxer = ...; // Transforms frames into muxed messages
192-
const transport = ...; // Sink of encrypted, muxed messages
193-
194-
const audioTrackReader = new AudioTrackReader(audioTrack);
171+
// The app provides sources of audio and video, perhaps from getUserMedia.
172+
const {audioTrack, videoTrack} = ...;
173+
// The app also provides ways to send encoded audio and video bitstream.
174+
function sendMedia(encodedAudio, encodedVideo) { ... };
175+
176+
// First, the tracks are converted to ReadableStreams of framed bitstream chunks.
177+
const audio = (new AudioTrackReader(audioTrack)).readable;
178+
const video = (new VideoTrackReader(videoTrack)).readable;
179+
180+
// Next, build the encoders and pass media through them
195181
const audioEncoder = new AudioEncoder({
196-
codec: 'opus',
182+
codec: 'opus',
197183
settings: {
198184
targetBitRate: 60_000,
199185
},
200186
});
201-
audioTrackReader.readable
202-
.pipeThrough(audioEncoder)
203-
.pipeThrough(audioEncryptor)
204-
.pipeThrough(muxer)
205-
.pipeTo(transport.writable);
187+
const videoEncoder = new VideoEncoder({
188+
codec: 'vp8',
189+
settings: {
190+
bitsPerSecond: 1_000_000,
191+
},
192+
});
193+
// TODO: Example of putting dynamic settings in media flow
194+
const encodedAudio = audio.pipeThrough(audioEncoder);
195+
const encodedVideo = video.pipeThrough(videoEncoder);
196+
197+
// Then send the encoded audio and video
198+
sendMedia(encodedAudio, encodedVideo);
199+
200+
// On the receive side, encoded media is likely received
201+
// from an out-of-order p2p transport and then put into a buffer.
202+
// The output of that buffer is the source of encoded audio and video here.
203+
const {encodedAudio, encodedVideo} = ...;
204+
205+
// To render to an element, the ReadableStream of decoded audio and video
206+
// must be converted to a MediaStream using TrackWriters.
207+
const videoWriter = new VideoTrackWriter();
208+
const audioWriter = new AudioTrackWriter();
209+
videoElem.srcObject = new MediaStream([audioWriter.track, videoWriter.track]);
210+
211+
// Finally the decoders are created and the encoded media is piped through the decoder
212+
// and into the TrackerWriters which converts them into MediaStreamTracks.
213+
const audioDecoder = new AudioDecoder({codec: 'opus'});
214+
const videoDecoder = new VideoDecoder({codec: 'vp8'});
215+
encodedAudio.pipeThrough(audioDecoder).pipeTo(audioWriter.writable);
216+
encodedVideo.pipeThrough(videoDecoder).pipeTo(videoWriter.writable);
217+
```
218+
219+
### Example of real-time communication using SVC
206220

207-
const videoTrackReader = new VideoTrackReader(videoTrack);
221+
The same as above, but with fancier codec parameters:
222+
223+
```javascript
208224
const videoEncoder = new VideoEncoder({
209-
codec: 'vp9',
225+
codec: 'vp9',
210226
settings: {
211-
bitsPerSecond: 1000000,
227+
bitsPerSecond: 1_000_000,
212228
// Two spatial layers with two temporal layers each
213229
layers: [{
214230
// Quarter size base layer
@@ -232,83 +248,6 @@ const videoEncoder = new VideoEncoder({
232248
}],
233249
},
234250
});
235-
videoTrackReader.readable
236-
.pipeThrough(videoEncoder)
237-
.pipeThrough(videoEncryptor)
238-
.pipeThrough(muxer.video)
239-
240-
muxer.readable.pipeTo(transport.writable);
241-
242-
243-
// Receiver has app-specific decryptor and buffering behavior
244-
const transport = ...; // Source of encrypted, muxed messsages
245-
// App-specific containerization code not provided by browser
246-
const demuxer = ...; // Transforms muxed messages to demuxed frames
247-
const audioDecryptor = ...; // TransformStream that decrypts frames
248-
const videoDecryptor = ...;
249-
const audioBuffer = ...; // TransformStream that buffers frames
250-
const videoBuffer = ...;
251-
const videoElem = ...;
252-
253-
transport.readable.pipeTo(demuxer.writable);
254-
255-
const audioDecoder = new AudioDecoder({codec: 'opus'});
256-
const audioTrackWriter = new AudioTrackWriter();
257-
demuxer.audio
258-
.pipeThrough(audioDecryptor)
259-
.pipeThrough(audioBuffer)
260-
.pipeThrough(audioDecoder)
261-
.pipeTo(audioTrackWriter.writable);
262-
263-
const videoDecoder = new videoDecoder({codec: 'vp8'});
264-
const videoWriter = new VideoTrackWriter();
265-
demuxer.video
266-
.pipeThrough(videoDecryptor)
267-
.pipeThrough(videoBuffer)
268-
.pipeThrough(videoDecoder)
269-
.pipeTo(videoTrackWriter.writable);
270-
271-
videoElem.srcObject = new MediaStream([audioWriter.track, videoWriter.track]);
272-
273-
```
274-
275-
### Example of transcoding or offline encode/decode
276-
277-
```javascript
278-
// App-specific sources and sinks of media
279-
const input = ...; // Reads container from source (like a file)
280-
const output = ...; // Writes container to source (like a file)
281-
// App-specific containerization code (not provided by browser)
282-
const demuxer = ...; // Reads container into frames
283-
const muxer = ...; // Writes frames into container
284-
285-
286-
const audioDecoder = new AudioDecoder({codec: 'aac'});
287-
const audioEncoder = new AudioEncoder({
288-
codec: 'opus',
289-
settings: {
290-
targetBitRate: 60_000
291-
},
292-
});
293-
demuxer.audio
294-
.pipeThrough(audioDecoder)
295-
.pipeThrough(audioEncoder)
296-
.pipeTo(muxer.audio);
297-
298-
const videoDecoder = new VideoDecoder({codec: 'h264'});
299-
const videoEncoder = new VideoEncoder({
300-
codec: 'vp8',
301-
settings: {
302-
bitsPerSecond: 1_000_000,
303-
},
304-
});
305-
demuxer.video
306-
.pipeThrough(videoDecoder)
307-
.pipeThrough(videoEncoder)
308-
.pipeTo(muxer.video);
309-
310-
input.readable.pipeInto(demuxer.writable);
311-
muxer.readable.pipeInto(output.writable);
312251
```
313252

314253
## Detailed design discussion

webidl.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,22 +223,22 @@ dictionary VideoEncoderOutput {
223223

224224
[Constructor(MediaStreamTrack track)]
225225
interface AudioTrackReader {
226-
readonly attribute ReadableStream readable; // of DecodedAudioPacket
226+
readonly attribute ReadableStream readable; // of AudioEncoderInput
227227
}
228228

229229
[Constructor()]
230230
interface AudioTrackWriter {
231-
readonly attribute WritableStream writable; // of DecodedAudioPacket
231+
readonly attribute WritableStream writable; // of AudioDecoderOutput
232232
readonly attribute MediaStreamTrack track;
233233
}
234234

235235
[Constructor(MediaStreamTrack track)]
236236
interface VideoTrackReader {
237-
readonly attribute ReadableStream readable; // of VideoFrame
237+
readonly attribute ReadableStream readable; // of VideoEncoderInput
238238
}
239239

240240
[Constructor()]
241241
interface VideoTrackWriter {
242-
readonly attribute WritableStream writable; // VideoFrame
242+
readonly attribute WritableStream writable; // VideoDecoderOutput
243243
readonly attribute MediaStreamTrack track;
244244
}

0 commit comments

Comments
 (0)