EasyRTC Documentation

Documentation

Source: easyrtc_rates.js

1
/* global define, module, require, console */
2
/*!
3
Script: easyrtc_rates.js
4
5
This code builds sdp filter functions
6
7
About: License
8
9
Copyright (c) 2016, Priologic Software Inc.
10
All rights reserved.
11
12
Redistribution and use in source and binary forms, with or without
13
modification, are permitted provided that the following conditions are met:
14
15
* Redistributions of source code must retain the above copyright notice,
16
this list of conditions and the following disclaimer.
17
* Redistributions in binary form must reproduce the above copyright
18
notice, this list of conditions and the following disclaimer in the
19
documentation and/or other materials provided with the distribution.
20
21
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
POSSIBILITY OF SUCH DAMAGE.
32
*/
33
34
35
36
(function (root, factory) {
37
if (typeof define === 'function' && define.amd) {
38
//RequireJS (AMD) build system
39
define(['easyrtc'], factory);
40
} else if (typeof module === 'object' && module.exports) {
41
//CommonJS build system
42
module.exports = factory(require('easyrtc'));
43
} else {
44
//Vanilla JS, ensure dependencies are loaded correctly
45
if (typeof window.easyrtc !== 'object' || !window.easyrtc) {
46
throw new Error("easyrtc_rates requires easyrtc");
47
}
48
root.easyrtc = factory(window.easyrtc);
49
}
50
}(this, function (easyrtc, undefined) {
51
52
"use strict";
53
/**
54
* Provides methods for building SDP filters. SDP filters can be used
55
* to control bit rates.
56
* @class Easyrtc_Rates
57
*/
58
59
function buildSdpFilter(options, isLocal) {
60
61
var audioSendBitrate = options.audioSendBitrate;
62
var audioRecvBitrate = options.audioRecvBitrate;
63
var videoRecvBitrate = options.videoRecvBitrate;
64
var videoSendBitrate = options.videoSendBitrate;
65
var videoSendInitialBitrate = options.videoSendInitialBitrate;
66
var audioSendCodec = options.audioSendCodec || '';
67
var audioRecvCodec = options.audioRecvCodec || '';
68
var videoSendCodec = options.videoSendCodec || '';
69
var videoRecvCodec = options.videoRecvCodec || '';
70
var stereo = options.stereo;
71
function trace(arg) {
72
console.log("trace:" + arg);
73
}
74
// these functions were cribbed from the google apprtc.appspot.com demo.
75
76
function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
77
var realEndLine = endLine !== -1 ? endLine : sdpLines.length;
78
for (var i = startLine; i < realEndLine; ++i) {
79
if (sdpLines[i].indexOf(prefix) === 0) {
80
if (!substr || sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
81
return i;
82
}
83
}
84
}
85
return null;
86
}
87
88
function findLine(sdpLines, prefix, substr) {
89
return findLineInRange(sdpLines, 0, -1, prefix, substr);
90
}
91
92
function preferBitRate(sdp, bitrate, mediaType) {
93
var sdpLines = sdp.split('\r\n');
94
var mLineIndex = findLine(sdpLines, 'm=', mediaType);
95
if (mLineIndex === null) {
96
trace('Failed to add bandwidth line to sdp, as no m-line found');
97
return sdp;
98
}
99
var nextMLineIndex = findLineInRange(sdpLines, mLineIndex + 1, -1, 'm=');
100
if (nextMLineIndex === null) {
101
nextMLineIndex = sdpLines.length;
102
}
103
var cLineIndex = findLineInRange(sdpLines, mLineIndex + 1, nextMLineIndex, 'c=');
104
if (cLineIndex === null) {
105
trace('Failed to add bandwidth line to sdp, as no c-line found');
106
return sdp;
107
}
108
var bLineIndex = findLineInRange(sdpLines, cLineIndex + 1, nextMLineIndex, 'b=AS');
109
if (bLineIndex) {
110
sdpLines.splice(bLineIndex, 1);
111
}
112
var bwLine = 'b=AS:' + bitrate;
113
sdpLines.splice(cLineIndex + 1, 0, bwLine);
114
sdp = sdpLines.join('\r\n');
115
return sdp;
116
}
117
118
function setDefaultCodec(mLine, payload) {
119
var elements = mLine.split(' ');
120
var newLine = [];
121
var index = 0;
122
for (var i = 0; i < elements.length; i++) {
123
if (index === 3) {
124
newLine[index++] = payload;
125
}
126
if (elements[i] !== payload) {
127
newLine[index++] = elements[i];
128
}
129
}
130
return newLine.join(' ');
131
}
132
133
function maybeSetAudioSendBitRate(sdp) {
134
if (!audioSendBitrate) {
135
return sdp;
136
}
137
trace('Prefer audio send bitrate: ' + audioSendBitrate);
138
return preferBitRate(sdp, audioSendBitrate, 'audio');
139
}
140
141
function maybeSetAudioReceiveBitRate(sdp) {
142
if (!audioRecvBitrate) {
143
return sdp;
144
}
145
trace('Prefer audio receive bitrate: ' + audioRecvBitrate);
146
return preferBitRate(sdp, audioRecvBitrate, 'audio');
147
}
148
149
function maybeSetVideoSendBitRate(sdp) {
150
if (!videoSendBitrate) {
151
return sdp;
152
}
153
trace('Prefer video send bitrate: ' + videoSendBitrate);
154
return preferBitRate(sdp, videoSendBitrate, 'video');
155
}
156
157
function maybeSetVideoReceiveBitRate(sdp) {
158
if (!videoRecvBitrate) {
159
return sdp;
160
}
161
trace('Prefer video receive bitrate: ' + videoRecvBitrate);
162
return preferBitRate(sdp, videoRecvBitrate, 'video');
163
}
164
165
function getCodecPayloadType(sdpLine) {
166
var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
167
var result = sdpLine.match(pattern);
168
return (result && result.length === 2) ? result[1] : null;
169
}
170
171
function maybeSetVideoSendInitialBitRate(sdp) {
172
if (!videoSendInitialBitrate) {
173
return sdp;
174
}
175
var maxBitrate = videoSendInitialBitrate;
176
if (videoSendBitrate) {
177
if (videoSendInitialBitrate > videoSendBitrate) {
178
trace('Clamping initial bitrate to max bitrate of ' + videoSendBitrate + ' kbps.');
179
videoSendInitialBitrate = videoSendBitrate;
180
}
181
maxBitrate = videoSendBitrate;
182
}
183
var sdpLines = sdp.split('\r\n');
184
var mLineIndex = findLine(sdpLines, 'm=', 'video');
185
if (mLineIndex === null) {
186
trace('Failed to find video m-line');
187
return sdp;
188
}
189
var vp8RtpmapIndex = findLine(sdpLines, 'a=rtpmap', 'VP8/90000');
190
var vp8Payload = getCodecPayloadType(sdpLines[vp8RtpmapIndex]);
191
var vp8Fmtp = 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + videoSendInitialBitrate.toString() + '; x-google-max-bitrate=' + maxBitrate.toString();
192
sdpLines.splice(vp8RtpmapIndex + 1, 0, vp8Fmtp);
193
return sdpLines.join('\r\n');
194
}
195
196
function preferCodec(sdp, codec, codecType){
197
var sdpLines = sdp.split('\r\n');
198
var mLineIndex = findLine(sdpLines, 'm=', codecType);
199
if (mLineIndex === null) {
200
return sdp;
201
}
202
//
203
// there are two m= lines in the sdp, one for audio, one for video.
204
// the audio one comes first. when we search for codecs for audio, we
205
// want stop before we enter the section for video, hence we'll hunt 
206
// for that subsequent m= line before we look for codecs. Otherwise,
207
// you could ask for a audio codec of VP9.
208
//
209
var mBottom = findLineInRange(sdpLines, mLineIndex+1, -1, "m=") || -1;
210
211
var codecIndex = findLineInRange(sdpLines, mLineIndex, mBottom, 'a=rtpmap', codec);
212
if (codecIndex) {
213
var payload = getCodecPayloadType(sdpLines[codecIndex]);
214
if (payload) {
215
sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], payload);
216
}
217
}
218
sdp = sdpLines.join('\r\n');
219
return sdp;
220
221
222
function maybePreferVideoSendCodec(sdp) {
223
if (videoSendCodec === '') {
224
trace('No preference on video send codec.');
225
return sdp;
226
}
227
trace('Prefer video send codec: ' + videoSendCodec);
228
return preferCodec(sdp, videoSendCodec, 'video');
229
}
230
231
function maybePreferVideoReceiveCodec(sdp) {
232
if (videoRecvCodec === '') {
233
trace('No preference on video receive codec.');
234
return sdp;
235
}
236
trace('Prefer video receive codec: ' + videoRecvCodec);
237
return preferCodec(sdp, videoRecvCodec,'video');
238
239
240
function maybePreferAudioSendCodec(sdp) {
241
if (audioSendCodec === '') {
242
trace('No preference on audio send codec.');
243
return sdp;
244
}
245
trace('Prefer audio send codec: ' + audioSendCodec);
246
return preferCodec(sdp, audioSendCodec, 'audio');
247
}
248
249
function maybePreferAudioReceiveCodec(sdp) {
250
if (audioRecvCodec === '') {
251
trace('No preference on audio receive codec.');
252
return sdp;
253
}
254
trace('Prefer audio receive codec: ' + audioRecvCodec);
255
return preferCodec(sdp, audioRecvCodec, 'audio');
256
257
258
function addStereo(sdp) {
259
var sdpLines = sdp.split('\r\n');
260
var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000');
261
var opusPayload;
262
if (opusIndex) {
263
opusPayload = getCodecPayloadType(sdpLines[opusIndex]);
264
}
265
var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString());
266
if (fmtpLineIndex === null) {
267
return sdp;
268
}
269
sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1');
270
sdp = sdpLines.join('\r\n');
271
return sdp;
272
}
273
274
if( isLocal ) {
275
return function(insdp) {
276
console.log("modifying local sdp");
277
var sdp;
278
sdp = maybePreferAudioReceiveCodec(insdp);
279
sdp = maybePreferVideoReceiveCodec(insdp);
280
sdp = maybeSetAudioReceiveBitRate(sdp);
281
sdp = maybeSetVideoReceiveBitRate(sdp);
282
//if( sdp != insdp ) {
283
//    console.log("changed the sdp from \n" + insdp + "\nto\n" + sdp);
284
//}
285
return sdp;
286
};
287
}
288
else {
289
return function(insdp) {
290
console.log("modifying remote sdp");
291
var sdp = maybePreferAudioSendCodec(insdp);
292
var sdp = maybePreferVideoSendCodec(insdp);
293
sdp = maybeSetAudioSendBitRate(sdp);
294
sdp = maybeSetVideoSendBitRate(sdp);
295
sdp = maybeSetVideoSendInitialBitRate(sdp);
296
if (stereo) {
297
sdp = addStereo(sdp);
298
}
299
//if( sdp != insdp ) {
300
//    console.log("changed the sdp from \n" + insdp + "\nto\n" + sdp);
301
//}
302
return sdp;
303
};
304
}
305
}
306
307
/**
308
*  This function returns an sdp filter function.
309
* @function
310
* @memberOf Easyrtc_Rates
311
* @param options A map that optionally includes values for the following keys: audioRecvCodec, audioRecvBitrate, videoRecvBitrate, videoRecvCodec
312
* @returns {Function} which takes an SDP string and returns a modified SDP string.
313
*/
314
easyrtc.buildLocalSdpFilter = function (options) {
315
return buildSdpFilter(options, true);
316
};
317
318
/**
319
* This function returns an sdp filter function.
320
* @function
321
* @memberOf Easyrtc_Rates
322
* @param options A map that optionally includes values for the following keys: stereo, audioSendCodec, audioSendBitrate, videoSendBitrate, videoSendInitialBitRate, videoRecvCodec
323
* @returns {Function} which takes an SDP string and returns a modified SDP string.
324
*/
325
easyrtc.buildRemoteSdpFilter = function(options) {
326
return buildSdpFilter(options, false);
327
};
328
329
return easyrtc;
330
331
}));