From 68ab85897ce2d2be6deafc68dc845080452bc9ad Mon Sep 17 00:00:00 2001 From: Janssen Wee Date: Wed, 26 Jun 2019 21:44:37 -0700 Subject: [PATCH 1/2] Added sdputils.js from AppRTC project Added sdputils.js from AppRTC project and used in offerfunction --- rtcpeerconnection.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rtcpeerconnection.js b/rtcpeerconnection.js index ccc7d94..909b68d 100644 --- a/rtcpeerconnection.js +++ b/rtcpeerconnection.js @@ -2,6 +2,7 @@ var util = require('util'); var SJJ = require('sdp-jingle-json'); var WildEmitter = require('wildemitter'); var cloneDeep = require('lodash.clonedeep'); +var sdputils = require('./sdputils'); function PeerConnection(config, constraints) { var self = this; @@ -403,6 +404,17 @@ PeerConnection.prototype.offer = function (constraints, cb) { mediaConstraints ).then( function (offer) { + + //successful callback + + + //from AppRTC PeerConnectionClient.prototype.setLocalSdpAndNotify_() using sdputils.js + offer.sdp = sdputils.maybePreferAudioReceiveCodec(offer.sdp, self.params_); + offer.sdp = sdputils.maybePreferVideoReceiveCodec(offer.sdp, self.params_); + offer.sdp = sdputils.maybeSetAudioReceiveBitRate(offer.sdp, self.params_); + offer.sdp = sdputils.maybeSetVideoReceiveBitRate(offer.sdp, self.params_); + offer.sdp = sdputils.maybeRemoveVideoFec(offer.sdp, self.params_); + // does not work for jingle, but jingle.js doesn't need // this hack... var expandedOffer = { From cd2d23ce8ee67cbbdefc6c78cd5ee9781ec9ad6d Mon Sep 17 00:00:00 2001 From: Janssen Wee Date: Wed, 26 Jun 2019 21:46:22 -0700 Subject: [PATCH 2/2] sdputils.js from AppRTC project sdputils.js from AppRTC project https://github.com/webrtc/apprtc/tree/master/src/web_app/js --- sdputils.js | 544 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 sdputils.js diff --git a/sdputils.js b/sdputils.js new file mode 100644 index 0000000..24a4579 --- /dev/null +++ b/sdputils.js @@ -0,0 +1,544 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* More information about these options at jshint.com/docs/options */ + +/* globals adapter, trace */ +/* exported setCodecParam, iceCandidateType, + maybeSetOpusOptions, maybePreferAudioReceiveCodec, + maybePreferAudioSendCodec, maybeSetAudioReceiveBitRate, + maybeSetAudioSendBitRate, maybePreferVideoReceiveCodec, + maybePreferVideoSendCodec, maybeSetVideoReceiveBitRate, + maybeSetVideoSendBitRate, maybeSetVideoSendInitialBitRate, + maybeRemoveVideoFec, mergeConstraints, removeCodecParam*/ + +'use strict'; + var sdputils; +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* More information about these options at jshint.com/docs/options */ + +/* globals adapter, trace */ +/* exported setCodecParam, iceCandidateType, + maybeSetOpusOptions, maybePreferAudioReceiveCodec, + maybePreferAudioSendCodec, maybeSetAudioReceiveBitRate, + maybeSetAudioSendBitRate, maybePreferVideoReceiveCodec, + maybePreferVideoSendCodec, maybeSetVideoReceiveBitRate, + maybeSetVideoSendBitRate, maybeSetVideoSendInitialBitRate, + maybeRemoveVideoFec, mergeConstraints, removeCodecParam*/ + +'use strict'; + +function mergeConstraints(cons1, cons2) { + if (!cons1 || !cons2) { + return cons1 || cons2; + } + var merged = cons1; + for (var key in cons2) { + merged[key] = cons2[key]; + } + return merged; +} + +function iceCandidateType(candidateStr) { + return candidateStr.split(' ')[7]; +} + +function maybeSetOpusOptions(sdp, params) { + // Set Opus in Stereo, if stereo is true, unset it, if stereo is false, and + // do nothing if otherwise. + if (params.opusStereo === 'true') { + sdp = setCodecParam(sdp, 'opus/48000', 'stereo', '1'); + } else if (params.opusStereo === 'false') { + sdp = removeCodecParam(sdp, 'opus/48000', 'stereo'); + } + + // Set Opus FEC, if opusfec is true, unset it, if opusfec is false, and + // do nothing if otherwise. + if (params.opusFec === 'true') { + sdp = setCodecParam(sdp, 'opus/48000', 'useinbandfec', '1'); + } else if (params.opusFec === 'false') { + sdp = removeCodecParam(sdp, 'opus/48000', 'useinbandfec'); + } + + // Set Opus DTX, if opusdtx is true, unset it, if opusdtx is false, and + // do nothing if otherwise. + if (params.opusDtx === 'true') { + sdp = setCodecParam(sdp, 'opus/48000', 'usedtx', '1'); + } else if (params.opusDtx === 'false') { + sdp = removeCodecParam(sdp, 'opus/48000', 'usedtx'); + } + + // Set Opus maxplaybackrate, if requested. + if (params.opusMaxPbr) { + sdp = setCodecParam( + sdp, 'opus/48000', 'maxplaybackrate', params.opusMaxPbr); + } + return sdp; +} + +function maybeSetAudioSendBitRate(sdp, params) { + if (!params.audioSendBitrate) { + return sdp; + } + console.log('Prefer audio send bitrate: ' + params.audioSendBitrate); + return preferBitRate(sdp, params.audioSendBitrate, 'audio'); +} + +function maybeSetAudioReceiveBitRate(sdp, params) { + if (!params.audioRecvBitrate) { + return sdp; + } + console.log('Prefer audio receive bitrate: ' + params.audioRecvBitrate); + return preferBitRate(sdp, params.audioRecvBitrate, 'audio'); +} + +function maybeSetVideoSendBitRate(sdp, params) { + if (!params.videoSendBitrate) { + return sdp; + } + console.log('Prefer video send bitrate: ' + params.videoSendBitrate); + return preferBitRate(sdp, params.videoSendBitrate, 'video'); +} + +function maybeSetVideoReceiveBitRate(sdp, params) { + if (!params.videoRecvBitrate) { + return sdp; + } + console.log('Prefer video receive bitrate: ' + params.videoRecvBitrate); + return preferBitRate(sdp, params.videoRecvBitrate, 'video'); +} + +// Add a b=AS:bitrate line to the m=mediaType section. +function preferBitRate(sdp, bitrate, mediaType) { + var sdpLines = sdp.split('\r\n'); + + // Find m line for the given mediaType. + var mLineIndex = findLine(sdpLines, 'm=', mediaType); + if (mLineIndex === null) { + console.log('Failed to add bandwidth line to sdp, as no m-line found'); + return sdp; + } + + // Find next m-line if any. + var nextMLineIndex = findLineInRange(sdpLines, mLineIndex + 1, -1, 'm='); + if (nextMLineIndex === null) { + nextMLineIndex = sdpLines.length; + } + + // Find c-line corresponding to the m-line. + var cLineIndex = findLineInRange(sdpLines, mLineIndex + 1, + nextMLineIndex, 'c='); + if (cLineIndex === null) { + console.log('Failed to add bandwidth line to sdp, as no c-line found'); + return sdp; + } + + // Check if bandwidth line already exists between c-line and next m-line. + var bLineIndex = findLineInRange(sdpLines, cLineIndex + 1, + nextMLineIndex, 'b=AS'); + if (bLineIndex) { + sdpLines.splice(bLineIndex, 1); + } + + // Create the b (bandwidth) sdp line. + var bwLine = 'b=AS:' + bitrate; + // As per RFC 4566, the b line should follow after c-line. + sdpLines.splice(cLineIndex + 1, 0, bwLine); + sdp = sdpLines.join('\r\n'); + return sdp; +} + +// Add an a=fmtp: x-google-min-bitrate=kbps line, if videoSendInitialBitrate +// is specified. We'll also add a x-google-min-bitrate value, since the max +// must be >= the min. +function maybeSetVideoSendInitialBitRate(sdp, params) { + var initialBitrate = parseInt(params.videoSendInitialBitrate); + if (!initialBitrate) { + return sdp; + } + + // Validate the initial bitrate value. + var maxBitrate = parseInt(initialBitrate); + var bitrate = parseInt(params.videoSendBitrate); + if (bitrate) { + if (initialBitrate > bitrate) { + console.log('Clamping initial bitrate to max bitrate of ' + bitrate + ' kbps.'); + initialBitrate = bitrate; + params.videoSendInitialBitrate = initialBitrate; + } + maxBitrate = bitrate; + } + + var sdpLines = sdp.split('\r\n'); + + // Search for m line. + var mLineIndex = findLine(sdpLines, 'm=', 'video'); + if (mLineIndex === null) { + console.log('Failed to find video m-line'); + return sdp; + } + // Figure out the first codec payload type on the m=video SDP line. + var videoMLine = sdpLines[mLineIndex]; + var pattern = new RegExp('m=video\\s\\d+\\s[A-Z/]+\\s'); + var sendPayloadType = videoMLine.split(pattern)[1].split(' ')[0]; + var fmtpLine = sdpLines[findLine(sdpLines, 'a=rtpmap', sendPayloadType)]; + var codecName = fmtpLine.split('a=rtpmap:' + + sendPayloadType)[1].split('/')[0]; + + // Use codec from params if specified via URL param, otherwise use from SDP. + var codec = params.videoSendCodec || codecName; + sdp = setCodecParam(sdp, codec, 'x-google-min-bitrate', + params.videoSendInitialBitrate.toString()); + sdp = setCodecParam(sdp, codec, 'x-google-max-bitrate', + maxBitrate.toString()); + + return sdp; +} + +function removePayloadTypeFromMline(mLine, payloadType) { + mLine = mLine.split(' '); + for (var i = 0; i < mLine.length; ++i) { + if (mLine[i] === payloadType.toString()) { + mLine.splice(i, 1); + } + } + return mLine.join(' '); +} + +function removeCodecByName(sdpLines, codec) { + var index = findLine(sdpLines, 'a=rtpmap', codec); + if (index === null) { + return sdpLines; + } + var payloadType = getCodecPayloadTypeFromLine(sdpLines[index]); + sdpLines.splice(index, 1); + + // Search for the video m= line and remove the codec. + var mLineIndex = findLine(sdpLines, 'm=', 'video'); + if (mLineIndex === null) { + return sdpLines; + } + sdpLines[mLineIndex] = removePayloadTypeFromMline(sdpLines[mLineIndex], + payloadType); + return sdpLines; +} + +function removeCodecByPayloadType(sdpLines, payloadType) { + var index = findLine(sdpLines, 'a=rtpmap', payloadType.toString()); + if (index === null) { + return sdpLines; + } + sdpLines.splice(index, 1); + + // Search for the video m= line and remove the codec. + var mLineIndex = findLine(sdpLines, 'm=', 'video'); + if (mLineIndex === null) { + return sdpLines; + } + sdpLines[mLineIndex] = removePayloadTypeFromMline(sdpLines[mLineIndex], + payloadType); + return sdpLines; +} + +function maybeRemoveVideoFec(sdp, params) { + if (params.videoFec !== 'false') { + return sdp; + } + + var sdpLines = sdp.split('\r\n'); + + var index = findLine(sdpLines, 'a=rtpmap', 'red'); + if (index === null) { + return sdp; + } + var redPayloadType = getCodecPayloadTypeFromLine(sdpLines[index]); + sdpLines = removeCodecByPayloadType(sdpLines, redPayloadType); + + sdpLines = removeCodecByName(sdpLines, 'ulpfec'); + + // Remove fmtp lines associated with red codec. + index = findLine(sdpLines, 'a=fmtp', redPayloadType.toString()); + if (index === null) { + return sdp; + } + var fmtpLine = parseFmtpLine(sdpLines[index]); + var rtxPayloadType = fmtpLine.pt; + if (rtxPayloadType === null) { + return sdp; + } + sdpLines.splice(index, 1); + + sdpLines = removeCodecByPayloadType(sdpLines, rtxPayloadType); + return sdpLines.join('\r\n'); +} + +// Promotes |audioSendCodec| to be the first in the m=audio line, if set. +function maybePreferAudioSendCodec(sdp, params) { + return maybePreferCodec(sdp, 'audio', 'send', params.audioSendCodec); +} + +// Promotes |audioRecvCodec| to be the first in the m=audio line, if set. +function maybePreferAudioReceiveCodec(sdp, params) { + return maybePreferCodec(sdp, 'audio', 'receive', params.audioRecvCodec); +} + +// Promotes |videoSendCodec| to be the first in the m=audio line, if set. +function maybePreferVideoSendCodec(sdp, params) { + return maybePreferCodec(sdp, 'video', 'send', params.videoSendCodec); +} + +// Promotes |videoRecvCodec| to be the first in the m=audio line, if set. +function maybePreferVideoReceiveCodec(sdp, params) { + return maybePreferCodec(sdp, 'video', 'receive', params.videoRecvCodec); +} + +// Sets |codec| as the default |type| codec if it's present. +// The format of |codec| is 'NAME/RATE', e.g. 'opus/48000'. +function maybePreferCodec(sdp, type, dir, codec) { + var str = type + ' ' + dir + ' codec'; + if (!codec) { + console.log('No preference on ' + str + '.'); + return sdp; + } + + console.log('Prefer ' + str + ': ' + codec); + + var sdpLines = sdp.split('\r\n'); + + // Search for m line. + var mLineIndex = findLine(sdpLines, 'm=', type); + if (mLineIndex === null) { + return sdp; + } + + // If the codec is available, set it as the default in m line. + var payload = null; + // Iterate through rtpmap enumerations to find all matching codec entries + for (var i = sdpLines.length-1; i >= 0 ; --i) { + // Finds first match in rtpmap + var index = findLineInRange(sdpLines, i, 0, 'a=rtpmap', codec, 'desc'); + if (index !== null) { + // Skip all of the entries between i and index match + i = index; + payload = getCodecPayloadTypeFromLine(sdpLines[index]); + if (payload) { + // Move codec to top + sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], payload); + } + } else { + // No match means we can break the loop + break; + } + } + + sdp = sdpLines.join('\r\n'); + return sdp; +} + +// Set fmtp param to specific codec in SDP. If param does not exists, add it. +function setCodecParam(sdp, codec, param, value) { + var sdpLines = sdp.split('\r\n'); + + var fmtpLineIndex = findFmtpLine(sdpLines, codec); + + var fmtpObj = {}; + if (fmtpLineIndex === null) { + var index = findLine(sdpLines, 'a=rtpmap', codec); + if (index === null) { + return sdp; + } + var payload = getCodecPayloadTypeFromLine(sdpLines[index]); + fmtpObj.pt = payload.toString(); + fmtpObj.params = {}; + fmtpObj.params[param] = value; + sdpLines.splice(index + 1, 0, writeFmtpLine(fmtpObj)); + } else { + fmtpObj = parseFmtpLine(sdpLines[fmtpLineIndex]); + fmtpObj.params[param] = value; + sdpLines[fmtpLineIndex] = writeFmtpLine(fmtpObj); + } + + sdp = sdpLines.join('\r\n'); + return sdp; +} + +// Remove fmtp param if it exists. +function removeCodecParam(sdp, codec, param) { + var sdpLines = sdp.split('\r\n'); + + var fmtpLineIndex = findFmtpLine(sdpLines, codec); + if (fmtpLineIndex === null) { + return sdp; + } + + var map = parseFmtpLine(sdpLines[fmtpLineIndex]); + delete map.params[param]; + + var newLine = writeFmtpLine(map); + if (newLine === null) { + sdpLines.splice(fmtpLineIndex, 1); + } else { + sdpLines[fmtpLineIndex] = newLine; + } + + sdp = sdpLines.join('\r\n'); + return sdp; +} + +// Split an fmtp line into an object including 'pt' and 'params'. +function parseFmtpLine(fmtpLine) { + var fmtpObj = {}; + var spacePos = fmtpLine.indexOf(' '); + var keyValues = fmtpLine.substring(spacePos + 1).split(';'); + + var pattern = new RegExp('a=fmtp:(\\d+)'); + var result = fmtpLine.match(pattern); + if (result && result.length === 2) { + fmtpObj.pt = result[1]; + } else { + return null; + } + + var params = {}; + for (var i = 0; i < keyValues.length; ++i) { + var pair = keyValues[i].split('='); + if (pair.length === 2) { + params[pair[0]] = pair[1]; + } + } + fmtpObj.params = params; + + return fmtpObj; +} + +// Generate an fmtp line from an object including 'pt' and 'params'. +function writeFmtpLine(fmtpObj) { + if (!fmtpObj.hasOwnProperty('pt') || !fmtpObj.hasOwnProperty('params')) { + return null; + } + var pt = fmtpObj.pt; + var params = fmtpObj.params; + var keyValues = []; + var i = 0; + for (var key in params) { + keyValues[i] = key + '=' + params[key]; + ++i; + } + if (i === 0) { + return null; + } + return 'a=fmtp:' + pt.toString() + ' ' + keyValues.join(';'); +} + +// Find fmtp attribute for |codec| in |sdpLines|. +function findFmtpLine(sdpLines, codec) { + // Find payload of codec. + var payload = getCodecPayloadType(sdpLines, codec); + // Find the payload in fmtp line. + return payload ? findLine(sdpLines, 'a=fmtp:' + payload.toString()) : null; +} + +// Find the line in sdpLines that starts with |prefix|, and, if specified, +// contains |substr| (case-insensitive search). +function findLine(sdpLines, prefix, substr) { + return findLineInRange(sdpLines, 0, -1, prefix, substr); +} + +// Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| +// and, if specified, contains |substr| (case-insensitive search). +function findLineInRange( + sdpLines, + startLine, + endLine, + prefix, + substr, + direction +) { + if (direction === undefined) { + direction = 'asc'; + } + + direction = direction || 'asc'; + + if (direction === 'asc') { + // Search beginning to end + var realEndLine = endLine !== -1 ? endLine : sdpLines.length; + for (var i = startLine; i < realEndLine; ++i) { + if (sdpLines[i].indexOf(prefix) === 0) { + if (!substr || + sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { + return i; + } + } + } + } else { + // Search end to beginning + var realStartLine = startLine !== -1 ? startLine : sdpLines.length-1; + for (var j = realStartLine; j >= 0; --j) { + if (sdpLines[j].indexOf(prefix) === 0) { + if (!substr || + sdpLines[j].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { + return j; + } + } + } + } + return null; +} + +// Gets the codec payload type from sdp lines. +function getCodecPayloadType(sdpLines, codec) { + var index = findLine(sdpLines, 'a=rtpmap', codec); + return index ? getCodecPayloadTypeFromLine(sdpLines[index]) : null; +} + +// Gets the codec payload type from an a=rtpmap:X line. +function getCodecPayloadTypeFromLine(sdpLine) { + var pattern = new RegExp('a=rtpmap:(\\d+) [a-zA-Z0-9-]+\\/\\d+'); + var result = sdpLine.match(pattern); + return (result && result.length === 2) ? result[1] : null; +} + +// Returns a new m= line with the specified codec as the first one. +function setDefaultCodec(mLine, payload) { + var elements = mLine.split(' '); + + // Just copy the first three parameters; codec order starts on fourth. + var newLine = elements.slice(0, 3); + + // Put target payload first and copy in the rest. + newLine.push(payload); + for (var i = 3; i < elements.length; i++) { + if (elements[i] !== payload) { + newLine.push(elements[i]); + } + } + return newLine.join(' '); +} + module.exports = sdputils = { + maybeSetOpusOptions: maybeSetOpusOptions, + maybePreferAudioReceiveCodec: maybePreferAudioReceiveCodec, + maybePreferAudioSendCodec: maybePreferAudioSendCodec, + maybeSetAudioReceiveBitRate: maybeSetAudioReceiveBitRate, + maybeSetAudioSendBitRate: maybeSetAudioSendBitRate, + maybePreferVideoReceiveCodec: maybePreferVideoReceiveCodec, + maybePreferVideoSendCodec: maybePreferVideoSendCodec, + maybeSetVideoReceiveBitRate: maybeSetVideoReceiveBitRate, + maybeSetVideoSendBitRate: maybeSetVideoSendBitRate, + maybeSetVideoSendInitialBitRate: maybeSetVideoSendInitialBitRate, + maybeRemoveVideoFec: maybeRemoveVideoFec, + mergeConstraints: mergeConstraints, + removeCodecParam: removeCodecParam +};