diff --git a/erizoAPI/binding.gyp b/erizoAPI/binding.gyp index b0880c833f..cda30e50e2 100644 --- a/erizoAPI/binding.gyp +++ b/erizoAPI/binding.gyp @@ -8,16 +8,14 @@ 'target_name': 'addon', 'sources': ['<@(common_sources)'], 'include_dirs' : ['<@(common_include_dirs)'], - 'libraries': ['-L$(ERIZO_HOME)/build/release/erizo -lerizo -Wl,-rpath,./../../erizo/build/release/erizo'], + 'libraries': ['-L$(ERIZO_HOME)/build/release/erizo -lerizo -Wl,-rpath,<(module_root_dir)/../erizo/build/release/erizo'], 'conditions': [ [ 'OS=="mac"', { 'xcode_settings': { 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', # -fno-exceptions - 'GCC_ENABLE_CPP_RTTI': 'YES', # -fno-rtti - 'MACOSX_DEPLOYMENT_TARGET' : '10.11', #from MAC OS 10.7 - 'OTHER_CFLAGS': [ - '-g -O3 -stdlib=libc++ -std=c++11', - ] + 'GCC_ENABLE_CPP_RTTI': 'YES', # -fno-rtti + 'MACOSX_DEPLOYMENT_TARGET' : '10.11', #from MAC OS 10.7 + 'OTHER_CFLAGS': ['-g -O3 -stdlib=libc++ -std=c++11',] }, }, { # OS!="mac" 'cflags!' : ['-fno-exceptions'], @@ -32,16 +30,14 @@ 'target_name': 'addonDebug', 'sources': ['<@(common_sources)'], 'include_dirs' : ['<@(common_include_dirs)'], - 'libraries': ['-L$(ERIZO_HOME)/build/debug/erizo -lerizo -Wl,-rpath,./../../erizo/build/debug/erizo'], + 'libraries': ['-L$(ERIZO_HOME)/build/debug/erizo -lerizo -Wl,-rpath,<(module_root_dir)/../erizo/build/debug/erizo'], 'conditions': [ [ 'OS=="mac"', { 'xcode_settings': { 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', # -fno-exceptions - 'GCC_ENABLE_CPP_RTTI': 'YES', # -fno-rtti - 'MACOSX_DEPLOYMENT_TARGET' : '10.11', #from MAC OS 10.7 - 'OTHER_CFLAGS': [ - '-g -O3 -stdlib=libc++ -std=c++11', - ] + 'GCC_ENABLE_CPP_RTTI': 'YES', # -fno-rtti + 'MACOSX_DEPLOYMENT_TARGET' : '10.11', #from MAC OS 10.7 + 'OTHER_CFLAGS': ['-g -O3 -stdlib=libc++ -std=c++11',] }, }, { # OS!="mac" 'cflags!' : ['-fno-exceptions'], diff --git a/erizo_controller/common/semanticSdp/CandidateInfo.js b/erizo_controller/common/semanticSdp/CandidateInfo.js new file mode 100755 index 0000000000..fbf4a0407f --- /dev/null +++ b/erizo_controller/common/semanticSdp/CandidateInfo.js @@ -0,0 +1,79 @@ +class CandidateInfo { + constructor(foundation, componentId, transport, priority, address, port, + type, generation, relAddr, relPort) { + this.foundation = foundation; + this.componentId = componentId; + this.transport = transport; + this.priority = priority; + this.address = address; + this.port = port; + this.type = type; + this.generation = generation; + this.relAddr = relAddr; + this.relPort = relPort; + } + + clone() { + return new CandidateInfo(this.foundation, this.componentId, this.transport, this.priority, + this.address, this.port, this.type, this.generation, this.relAddr, this.relPort); + } + + plain() { + const plain = { + foundation: this.foundation, + componentId: this.componentId, + transport: this.transport, + priority: this.priority, + address: this.address, + port: this.port, + type: this.type, + generation: this.generation, + }; + if (this.relAddr) plain.relAddr = this.relAddr; + if (this.relPort) plain.relPort = this.relPort; + return plain; + } + + getFoundation() { + return this.foundation; + } + + getComponentId() { + return this.componentId; + } + + getTransport() { + return this.transport; + } + + getPriority() { + return this.priority; + } + + getAddress() { + return this.address; + } + + getPort() { + return this.port; + } + + getType() { + return this.type; + } + + getGeneration() { + return this.generation; + } + + getRelAddr() { + return this.relAddr; + } + + getRelPort() { + return this.relPort; + } + +} + +module.exports = CandidateInfo; diff --git a/erizo_controller/common/semanticSdp/CodecInfo.js b/erizo_controller/common/semanticSdp/CodecInfo.js new file mode 100755 index 0000000000..8ba1fc0d3e --- /dev/null +++ b/erizo_controller/common/semanticSdp/CodecInfo.js @@ -0,0 +1,97 @@ +class CodecInfo { + + constructor(codec, type, rate, encoding, params, feedback) { + this.codec = codec; + this.type = type; + this.rate = rate; + this.encoding = encoding; + this.params = params || {}; + this.feedback = feedback || []; + } + + clone() { + const cloned = new CodecInfo(this.codec, this.type, this.rate, this.encoding, + this.params, this.feedback); + if (this.rtx) { + cloned.setRTX(this.rtx); + } + return cloned; + } + + + plain() { + return { + codec: this.codec, + type: this.type, + rate: this.rate, + encoding: this.encoding, + params: this.params, + feedback: this.feedback, + }; + } + + setRTX(rtx) { + this.rtx = rtx; + } + + getType() { + return this.type; + } + + setType(type) { + this.type = type; + } + + getCodec() { + return this.codec; + } + + getParams() { + return this.params; + } + + hasRTX() { + return this.rtx; + } + + getRTX() { + return this.rtx; + } + + getRate() { + return this.rate; + } + + getEncoding() { + return this.encoding; + } + + getFeedback() { + return this.feedback; + } +} + +CodecInfo.mapFromNames = (names, rtx) => { + const codecs = new Map(); + + let dyn = 96; + names.forEach((nameWithUpperCases) => { + let pt; + const name = nameWithUpperCases.toLowerCase(); + if (name === 'pcmu') pt = 0; + else if (name === 'pcma') pt = 8; + else { + dyn += 1; + pt = dyn; + } + const codec = new CodecInfo(name, pt); + if (rtx && name !== 'ulpfec' && name !== 'flexfec-03' && name !== 'red') { + dyn += 1; + codec.setRTX(dyn); + } + codecs.set(codec.getCodec().toLowerCase(), codec); + }); + return codecs; +}; + +module.exports = CodecInfo; diff --git a/erizo_controller/common/semanticSdp/DTLSInfo.js b/erizo_controller/common/semanticSdp/DTLSInfo.js new file mode 100755 index 0000000000..9fd3cda009 --- /dev/null +++ b/erizo_controller/common/semanticSdp/DTLSInfo.js @@ -0,0 +1,39 @@ +const Setup = require('./Setup'); + +class DTLSInfo { + constructor(setup, hash, fingerprint) { + this.setup = setup; + this.hash = hash; + this.fingerprint = fingerprint; + } + + clone() { + return new DTLSInfo(this.setup, this.hash, this.fingerprint); + } + + plain() { + return { + setup: Setup.toString(this.setup), + hash: this.hash, + fingerprint: this.fingerprint, + }; + } + + getFingerprint() { + return this.fingerprint; + } + + getHash() { + return this.hash; + } + + getSetup() { + return this.setup; + } + + setSetup(setup) { + this.setup = setup; + } +} + +module.exports = DTLSInfo; diff --git a/erizo_controller/common/semanticSdp/Direction.js b/erizo_controller/common/semanticSdp/Direction.js new file mode 100755 index 0000000000..595c2886f3 --- /dev/null +++ b/erizo_controller/common/semanticSdp/Direction.js @@ -0,0 +1,43 @@ +const Enum = require('./Enum'); + +const Direction = Enum('SENDRECV', 'SENDONLY', 'RECVONLY', 'INACTIVE'); + +Direction.byValue = direction => Direction[direction.toUpperCase()]; + +/** +* Get Direction name +* @memberOf Direction +* @param {Direction} direction +* @returns {String} +*/ +Direction.toString = (direction) => { + switch (direction) { + case Direction.SENDRECV: + return 'sendrecv'; + case Direction.SENDONLY: + return 'sendonly'; + case Direction.RECVONLY: + return 'recvonly'; + case Direction.INACTIVE: + return 'inactive'; + default: + return 'unknown'; + } +}; + +Direction.reverse = (direction) => { + switch (direction) { + case Direction.SENDRECV: + return Direction.SENDRECV; + case Direction.SENDONLY: + return Direction.RECVONLY; + case Direction.RECVONLY: + return Direction.SENDONLY; + case Direction.INACTIVE: + return Direction.INACTIVE; + default: + return Direction.SENDRECV; + } +}; + +module.exports = Direction; diff --git a/erizo_controller/common/semanticSdp/DirectionWay.js b/erizo_controller/common/semanticSdp/DirectionWay.js new file mode 100755 index 0000000000..34827ddf61 --- /dev/null +++ b/erizo_controller/common/semanticSdp/DirectionWay.js @@ -0,0 +1,29 @@ +const Enum = require('./Enum'); + +const DirectionWay = Enum('SEND', 'RECV'); + +DirectionWay.byValue = direction => DirectionWay[direction.toUpperCase()]; + +DirectionWay.toString = (direction) => { + switch (direction) { + case DirectionWay.SEND: + return 'send'; + case DirectionWay.RECV: + return 'recv'; + default: + return 'unknown'; + } +}; + +DirectionWay.reverse = (direction) => { + switch (direction) { + case DirectionWay.SEND: + return DirectionWay.RECV; + case DirectionWay.RECV: + return DirectionWay.SEND; + default: + return DirectionWay.SEND; + } +}; + +module.exports = DirectionWay; diff --git a/erizo_controller/common/semanticSdp/Enum.js b/erizo_controller/common/semanticSdp/Enum.js new file mode 100755 index 0000000000..2e6e8409a7 --- /dev/null +++ b/erizo_controller/common/semanticSdp/Enum.js @@ -0,0 +1,12 @@ + +function Enum(...args) { + if (!(this instanceof Enum)) { + return new (Function.prototype.bind.apply(Enum, + [null].concat(Array.prototype.slice.call(args))))(); + } + Array.from(args).forEach((arg) => { + this[arg] = Symbol.for(`LICODE_SEMANTIC_SDP_${arg}`); + }); +} + +module.exports = Enum; diff --git a/erizo_controller/common/semanticSdp/ICEInfo.js b/erizo_controller/common/semanticSdp/ICEInfo.js new file mode 100755 index 0000000000..b52fc89dcc --- /dev/null +++ b/erizo_controller/common/semanticSdp/ICEInfo.js @@ -0,0 +1,88 @@ +function randomIntInc(low, high) { + const range = (high - low) + 1; + const random = Math.random() * range; + return Math.floor(random + low); +} + +function randomBytes(size) { + const numbers = new Uint8Array(size); + + for (let i = 0; i < numbers.length; i += 1) { + numbers[i] = randomIntInc(0, 255); + } + return numbers; +} + +function buf2hex(buffer) { + return Array.prototype.map.call(new Uint8Array(buffer), (x) => { + const hexValue = x.toString(16); + return `00${hexValue}`.slice(-2); + }).join(''); +} + +class ICEInfo { + constructor(ufrag, pwd, opts) { + this.ufrag = ufrag; + this.pwd = pwd; + this.opts = opts; + this.lite = false; + this.endOfCandidates = false; + } + + clone() { + const cloned = new ICEInfo(this.ufrag, this.pwd, this.opts); + cloned.setLite(this.lite); + cloned.setEndOfCandidates(this.endOfCandidates); + return cloned; + } + + plain() { + const plain = { + ufrag: this.ufrag, + pwd: this.pwd, + }; + if (this.lite) plain.lite = this.lite; + if (this.endOfCandidates) plain.endOfCandidates = this.endOfCandidates; + return plain; + } + + getUfrag() { + return this.ufrag; + } + + getPwd() { + return this.pwd; + } + + isLite() { + return this.lite; + } + + getOpts() { + return this.opts; + } + + setLite(lite) { + this.lite = lite; + } + + isEndOfCandidates() { + return this.endOfCandidates; + } + + setEndOfCandidates(endOfCandidates) { + this.endOfCandidates = endOfCandidates; + } + +} + +ICEInfo.generate = () => { + const info = new ICEInfo(); + const frag = randomBytes(8); + const pwd = randomBytes(24); + info.ufrag = buf2hex(frag); + info.pwd = buf2hex(pwd); + return info; +}; + +module.exports = ICEInfo; diff --git a/erizo_controller/common/semanticSdp/MediaInfo.js b/erizo_controller/common/semanticSdp/MediaInfo.js new file mode 100755 index 0000000000..606a7eedbe --- /dev/null +++ b/erizo_controller/common/semanticSdp/MediaInfo.js @@ -0,0 +1,272 @@ +const SimulcastInfo = require('./SimulcastInfo'); +const Direction = require('./Direction'); +const DirectionWay = require('./DirectionWay'); + +class MediaInfo { + constructor(id, type) { + this.id = id; + this.type = type; + this.direction = Direction.SENDRECV; + this.extensions = new Map(); + this.codecs = new Map(); + this.rids = new Map(); + this.simulcast = null; + this.bitrate = 0; + this.ice = null; + this.dtls = null; + this.connection = null; + this.candidates = []; + } + + clone() { + const cloned = new MediaInfo(this.id, this.type); + cloned.setDirection(this.direction); + cloned.setBitrate(this.bitrate); + cloned.setConnection(this.connection); + this.codecs.forEach((codec) => { + cloned.addCodec(codec.clone()); + }); + this.extensions.forEach((extension, id) => { + cloned.addExtension(id, extension); + }); + this.rids.forEach((rid, id) => { + cloned.addRID(id, rid.clone()); + }); + if (this.simulcast) { + cloned.setSimulcast(this.simulcast.clone()); + } + if (this.xGoogleFlag) { + cloned.setXGoogleFlag(this.xGoogleFlag); + } + if (this.ice) { + cloned.setICE(this.ice.clone()); + } + if (this.dtls) { + cloned.setDTLS(this.dtls.clone()); + } + this.candidates.forEach((candidate) => { + cloned.addCandidate(candidate.clone()); + }); + return cloned; + } + + plain() { + const plain = { + id: this.id, + type: this.type, + connection: this.connection, + direction: Direction.toString(this.direction), + xGoogleFlag: this.xGoogleFlag, + extensions: {}, + rids: [], + codecs: [], + candidates: [], + }; + if (this.bitrate) { + plain.bitrate = this.bitrate; + } + this.codecs.forEach((codec) => { + plain.codecs.push(codec.plain()); + }); + this.extensions.forEach((extension) => { + plain.extensions.push(extension.plain()); + }); + this.rids.forEach((rid) => { + plain.rids.push(rid.plain()); + }); + if (this.simulcast) { + plain.simulcast = this.simulcast.plain(); + } + this.candidates.forEach((candidate) => { + plain.candidates.push(candidate.plain()); + }); + plain.ice = this.ice && this.ice.plain(); + plain.dtls = this.dtls && this.dtls.plain(); + return plain; + } + + getType() { + return this.type; + } + + getId() { + return this.id; + } + + addExtension(id, name) { + this.extensions.set(id, name); + } + + addRID(ridInfo) { + this.rids.set(ridInfo.getId(), ridInfo); + } + + addCodec(codecInfo) { + this.codecs.set(codecInfo.getType(), codecInfo); + } + + getCodecForType(type) { + return this.codecs.get(type); + } + + getCodec(codec) { + let result; + this.codecs.forEach((info) => { + if (info.getCodec().toLowerCase() === codec.toLowerCase()) { + result = info; + } + }); + return result; + } + + hasCodec(codec) { + return this.getCodec(codec) !== null; + } + + getCodecs() { + return this.codecs; + } + + getExtensions() { + return this.extensions; + } + + getRIDs() { + return this.rids; + } + + getRID(id) { + return this.rids.get(id); + } + + getBitrate() { + return this.bitrate; + } + + setBitrate(bitrate) { + this.bitrate = bitrate; + } + + getDirection() { + return this.direction; + } + + setDirection(direction) { + this.direction = direction; + } + + getDTLS() { + return this.dtls; + } + + setDTLS(dtlsInfo) { + this.dtls = dtlsInfo; + } + + getICE() { + return this.ice; + } + + setICE(iceInfo) { + this.ice = iceInfo; + } + + addCandidate(candidate) { + this.candidates.push(candidate); + } + + addCandidates(candidates) { + candidates.forEach((candidate) => { + this.addCandidate(candidate); + }); + } + + getCandidates() { + return this.candidates; + } + + setXGoogleFlag(value) { + this.xGoogleFlag = value; + } + + getXGoogleFlag() { + return this.xGoogleFlag; + } + + getConnection() { + return this.connection; + } + + setConnection(connection) { + this.connection = connection; + } + + answer(supported) { + const answer = new MediaInfo(this.id, this.type); + + answer.setDirection(Direction.reverse(this.direction)); + + if (supported.codecs) { + this.codecs.forEach((codec) => { + if (supported.codecs.has(codec.getCodec().toLowerCase())) { + const cloned = supported.codecs.get(codec.getCodec().toLowerCase()).clone(); + cloned.setType(codec.getType()); + if (cloned.hasRTX()) { + cloned.setRTX(codec.getRTX()); + } + answer.addCodec(cloned); + } + }); + } + + this.extensions.forEach((extension, id) => { + if (supported.extensions.has(id)) { + answer.addExtension(id, extension); + } + }); + + if (supported.simulcast && this.simulcast) { + const simulcast = new SimulcastInfo(); + const sendEntries = this.simulcast.getSimulcastStreams(DirectionWay.SEND); + if (sendEntries) { + sendEntries.forEach((sendStreams) => { + const alternatives = []; + sendStreams.forEach((sendStream) => { + alternatives.push(sendStream.clone()); + }); + simulcast.addSimulcastAlternativeStreams(DirectionWay.RECV, alternatives); + }); + } + + const recvEntries = this.simulcast.getSimulcastStreams(DirectionWay.RECV); + if (recvEntries) { + recvEntries.forEach((recvStreams) => { + const alternatives = []; + recvStreams.forEach((recvStream) => { + alternatives.push(recvStream.clone()); + }); + simulcast.addSimulcastAlternativeStreams(DirectionWay.SEND, alternatives); + }); + } + + this.rids.forEach((rid) => { + const reversed = rid.clone(); + reversed.setDirection(DirectionWay.reverse(rid.getDirection())); + answer.addRID(reversed); + }); + + answer.setSimulcast(simulcast); + } + return answer; + } + + getSimulcast() { + return this.simulcast; + } + + setSimulcast(simulcast) { + this.simulcast = simulcast; + } +} + +module.exports = MediaInfo; diff --git a/erizo_controller/common/semanticSdp/RIDInfo.js b/erizo_controller/common/semanticSdp/RIDInfo.js new file mode 100755 index 0000000000..c93ae1830c --- /dev/null +++ b/erizo_controller/common/semanticSdp/RIDInfo.js @@ -0,0 +1,63 @@ +const DirectionWay = require('./DirectionWay'); + +class RIDInfo { + constructor(id, direction) { + this.id = id; + this.direction = direction; + this.formats = []; + this.params = new Map(); + } + + clone() { + const cloned = new RIDInfo(this.id, this.direction); + cloned.setFormats(this.formats); + cloned.setParams(this.params); + return cloned; + } + + plain() { + const plain = { + id: this.id, + direction: DirectionWay.toString(this.direction), + formats: this.formats, + params: {}, + }; + this.params.forEach((param, id) => { + plain.params[id] = param; + }); + return plain; + } + + getId() { + return this.id; + } + + getDirection() { + return this.direction; + } + + setDirection(direction) { + this.direction = direction; + } + + getFormats() { + return this.formats; + } + + setFormats(formats) { + this.formats = []; + formats.forEach((format) => { + this.formats.push(parseInt(format, 10)); + }); + } + + getParams() { + return this.params; + } + + setParams(params) { + this.params = new Map(params); + } +} + +module.exports = RIDInfo; diff --git a/erizo_controller/common/semanticSdp/SDPInfo.js b/erizo_controller/common/semanticSdp/SDPInfo.js new file mode 100755 index 0000000000..80944da322 --- /dev/null +++ b/erizo_controller/common/semanticSdp/SDPInfo.js @@ -0,0 +1,823 @@ +const SDPTransform = require('sdp-transform'); // eslint-disable-line +const CandidateInfo = require('./CandidateInfo'); +const CodecInfo = require('./CodecInfo'); +const DTLSInfo = require('./DTLSInfo'); +const ICEInfo = require('./ICEInfo'); +const MediaInfo = require('./MediaInfo'); +const Setup = require('./Setup'); +const Direction = require('./Direction'); +const DirectionWay = require('./DirectionWay'); +const SourceGroupInfo = require('./SourceGroupInfo'); +const SourceInfo = require('./SourceInfo'); +const StreamInfo = require('./StreamInfo'); +const TrackInfo = require('./TrackInfo'); +const TrackEncodingInfo = require('./TrackEncodingInfo'); +const SimulcastInfo = require('./SimulcastInfo'); +const SimulcastStreamInfo = require('./SimulcastStreamInfo'); +const RIDInfo = require('./RIDInfo'); + +class SDPInfo { + constructor(version) { + this.version = version || 1; + this.name = 'sdp-semantic'; + this.streams = new Map(); + this.medias = []; + this.candidates = []; + this.connection = null; + this.ice = null; + this.dtls = null; + } + + clone() { + const cloned = new SDPInfo(this.version); + cloned.name = this.name; + cloned.setConnection(this.connection); + this.medias.forEach((media) => { + cloned.addMedia(media.clone()); + }); + this.streams.forEach((stream) => { + cloned.addStream(stream.clone()); + }); + this.candidates.forEach((candidate) => { + cloned.addCandidate(candidate.clone()); + }); + cloned.setICE(this.ice.clone()); + cloned.setDTLS(this.dtls.clone()); + return cloned; + } + + plain() { + const plain = { + version: this.version, + name: this.name, + streams: [], + medias: [], + candidates: [], + connection: this.connection, + }; + this.medias.forEach((media) => { + plain.medias.push(media.plain()); + }); + this.streams.forEach((stream) => { + plain.streams.push(stream.plain()); + }); + this.candidates.forEach((candidate) => { + plain.candidates.push(candidate.plain()); + }); + plain.ice = this.ice && this.ice.plain(); + plain.dtls = this.dtls && this.dtls.plain(); + return plain; + } + + setVersion(version) { + this.version = version; + } + + setOrigin(origin) { + this.origin = origin; + } + + setName(name) { + this.name = name; + } + + getConnection() { + return this.connection; + } + + setConnection(connection) { + this.connection = connection; + } + + setTiming(timing) { + this.timing = timing; + } + + addMedia(media) { + this.medias.push(media); + } + + getMedia(type) { + let result; + this.medias.forEach((media) => { + if (media.getType().toLowerCase() === type.toLowerCase()) { + result = media; + } + }); + return result; + } + + getMedias(type) { + if (!type) { + return this.medias; + } + const medias = []; + this.medias.forEach((media) => { + if (media.getType().toLowerCase() === type.toLowerCase()) { + medias.push(media); + } + }); + return medias; + } + + getMediaById(msid) { + let result; + this.medias.forEach((media) => { + if (media.getId().toLowerCase() === msid.toLowerCase()) { + result = media; + } + }); + return result; + } + + getVersion() { + return this.version; + } + + getDTLS() { + return this.dtls; + } + + setDTLS(dtlsInfo) { + this.dtls = dtlsInfo; + } + + getICE() { + return this.ice; + } + + setICE(iceInfo) { + this.ice = iceInfo; + } + + addCandidate(candidate) { + this.candidates.push(candidate); + } + + addCandidates(candidates) { + candidates.forEach((candidate) => { + this.addCandidate(candidate); + }); + } + + getCandidates() { + return this.candidates; + } + + getStream(id) { + return this.streams.get(id); + } + + getStreams() { + return this.streams; + } + + getFirstStream() { + if (this.streams.values().length > 0) { + return this.streams.values()[0]; + } + return null; + } + + addStream(stream) { + this.streams.set(stream.getId(), stream); + } + + removeStream(stream) { + this.streams.delete(stream.getId()); + } + + toJSON() { + const sdp = { + version: 0, + media: [], + }; + + sdp.version = this.version || 0; + sdp.origin = this.origin || { + username: '-', + sessionId: (new Date()).getTime(), + sessionVersion: this.version, + netType: 'IN', + ipVer: 4, + address: '127.0.0.1', + }; + + sdp.name = this.name || 'semantic-sdp'; + + sdp.connection = this.getConnection(); + sdp.timing = this.timing || { start: 0, stop: 0 }; + + let ice = this.getICE(); + if (ice) { + if (ice.isLite()) { + sdp.icelite = 'ice-lite'; + } + sdp.iceOptions = ice.getOpts(); + sdp.iceUfrag = ice.getUfrag(); + sdp.icePwd = ice.getPwd(); + } + + sdp.msidSemantic = this.msidSemantic || { semantic: 'WMS', token: '*' }; + sdp.groups = []; + + const bundle = { type: 'BUNDLE', mids: [] }; + let dtls = this.getDTLS(); + if (dtls) { + sdp.fingerprint = { + type: dtls.getHash(), + hash: dtls.getFingerprint(), + }; + + sdp.setup = Setup.toString(dtls.getSetup()); + } + + this.medias.forEach((media) => { + const md = { + type: media.getType(), + port: 9, + protocol: 'UDP/TLS/RTP/SAVPF', + fmtp: [], + rtp: [], + rtcpFb: [], + ext: [], + bandwidth: [], + candidates: [], + ssrcGroups: [], + ssrcs: [], + rids: [], + }; + + md.direction = Direction.toString(media.getDirection()); + + md.rtcpMux = 'rtcp-mux'; + + md.connection = media.getConnection(); + + md.xGoogleFlag = media.getXGoogleFlag(); + + md.mid = media.getId(); + + bundle.mids.push(media.getId()); + md.rtcp = media.rtcp; + + if (media.getBitrate() > 0) { + md.bandwidth.push({ + type: 'AS', + limit: media.getBitrate(), + }); + } + + const candidates = media.getCandidates(); + candidates.forEach((candidate) => { + md.candidates.push({ + foundation: candidate.getFoundation(), + component: candidate.getComponentId(), + transport: candidate.getTransport(), + priority: candidate.getPriority(), + ip: candidate.getAddress(), + port: candidate.getPort(), + type: candidate.getType(), + generation: candidate.getGeneration(), + }); + }); + + ice = media.getICE(); + if (ice) { + if (ice.isLite()) { + md.icelite = 'ice-lite'; + } + md.iceOptions = ice.getOpts(); + md.iceUfrag = ice.getUfrag(); + md.icePwd = ice.getPwd(); + } + + dtls = media.getDTLS(); + if (dtls) { + md.fingerprint = { + type: dtls.getHash(), + hash: dtls.getFingerprint(), + }; + + md.setup = Setup.toString(dtls.getSetup()); + } + + media.getCodecs().forEach((codec) => { + md.rtp.push({ + payload: codec.getType(), + codec: codec.getCodec(), + rate: codec.getRate(), + encoding: codec.getEncoding(), + }); + + const params = codec.getParams(); + if (Object.keys(params).length > 0) { + md.fmtp.push({ + payload: codec.getType(), + config: Object.keys(params) + .map(item => item + (params[item] ? `=${params[item]}` : '')) + .join(';'), + }); + } + + codec.getFeedback().forEach((rtcpFb) => { + md.rtcpFb.push({ + payload: codec.getType(), + type: rtcpFb.type, + subtype: rtcpFb.subtype, + }); + }); + + if (codec.hasRTX()) { + md.rtp.push({ + payload: codec.getRTX(), + codec: 'rtx', + rate: codec.getRate(), + encoding: codec.getEncoding(), + }); + md.fmtp.push({ + payload: codec.getRTX(), + config: `apt=${codec.getType()}`, + }); + } + }); + const payloads = []; + + md.rtp.forEach((rtp) => { + payloads.push(rtp.payload); + }); + + md.payloads = payloads.join(' '); + + media.getExtensions().forEach((uri, value) => { + md.ext.push({ + value, + uri, + }); + }); + + media.getRIDs().forEach((ridInfo) => { + const rid = { + id: ridInfo.getId(), + direction: DirectionWay.toString(ridInfo.getDirection()), + params: '', + }; + if (ridInfo.getFormats().length) { + rid.params = `pt=${ridInfo.getFormats().join(',')}`; + } + ridInfo.getParams().forEach((param, key) => { + const prefix = rid.params.length ? ';' : ''; + rid.params += `${prefix}${key}=${param}`; + }); + + md.rids.push(rid); + }); + + const simulcast = media.getSimulcast(); + if (simulcast) { + let index = 1; + md.simulcast = {}; + const send = simulcast.getSimulcastStreams(DirectionWay.SEND); + const recv = simulcast.getSimulcastStreams(DirectionWay.RECV); + + if (send && send.length) { + let list = ''; + send.forEach((stream) => { + let alternatives = ''; + stream.forEach((entry) => { + alternatives += + (alternatives.length ? ',' : '') + (entry.isPaused() ? '~' : '') + entry.getId(); + }); + list += (list.length ? ';' : '') + alternatives; + }); + md.simulcast[`dir${index}`] = 'send'; + md.simulcast[`list${index}`] = list; + index += 1; + } + + if (recv && recv.length) { + let list = []; + recv.forEach((stream) => { + let alternatives = ''; + stream.forEach((entry) => { + alternatives += + (alternatives.length ? ',' : '') + (entry.isPaused() ? '~' : '') + entry.getId(); + }); + list += (list.length ? ';' : '') + alternatives; + }); + md.simulcast[`dir${index}`] = 'recv'; + md.simulcast[`list${index}`] = list; + index += 1; + } + } + + sdp.media.push(md); + }); + + for (const stream of this.streams.values()) { // eslint-disable-line no-restricted-syntax + for (const track of stream.getTracks().values()) { // eslint-disable-line no-restricted-syntax + for (const md of sdp.media) { // eslint-disable-line no-restricted-syntax + // Check if it is unified or plan B + if (track.getMediaId()) { + // Unified, check if it is bounded to an specific line + if (track.getMediaId() === md.mid) { + track.getSourceGroups().forEach((group) => { + md.ssrcGroups.push({ + semantics: group.getSemantics(), + ssrcs: group.getSSRCs().join(' '), + }); + }); + + track.getSSRCs().forEach((source) => { + md.ssrcs.push({ + id: source.ssrc, + attribute: 'cname', + value: source.getCName(), + }); + }); + if (stream.getId() && track.getId()) { + md.msid = `${stream.getId()} ${track.getId()}`; + } + break; + } + } else if (md.type.toLowerCase() === track.getMedia().toLowerCase()) { + // Plan B + track.getSourceGroups().forEach((group) => { + md.ssrcGroups.push({ + semantics: group.getSemantics(), + ssrcs: group.getSSRCs().join(' '), + }); + }); + + track.getSSRCs().forEach((source) => { + md.ssrcs.push({ + id: source.ssrc, + attribute: 'cname', + value: source.getCName(), + }); + if (source.getStreamId() && source.getTrackId()) { + md.ssrcs.push({ + id: source.ssrc, + attribute: 'msid', + value: `${source.getStreamId()} ${source.getTrackId()}`, + }); + } + if (source.getMSLabel()) { + md.ssrcs.push({ + id: source.ssrc, + attribute: 'mslabel', + value: source.getMSLabel(), + }); + } + if (source.getLabel()) { + md.ssrcs.push({ + id: source.ssrc, + attribute: 'label', + value: source.getLabel(), + }); + } + }); + break; + } + } + } + } + + bundle.mids = bundle.mids.join(' '); + sdp.groups.push(bundle); + + return sdp; + } + + toString() { + const sdp = this.toJSON(); + return SDPTransform.write(sdp); + } +} + +function getFormats(mediaInfo, md) { + const apts = new Map(); + + md.rtp.forEach((fmt) => { + const type = fmt.payload; + const codec = fmt.codec; + const rate = fmt.rate; + const encoding = fmt.encoding; + + const params = {}; + const feedback = []; + + md.fmtp.forEach((fmtp) => { + if (fmtp.payload === type) { + const list = fmtp.config.split(';'); + list.forEach((entry) => { + const param = entry.split('='); + params[param[0].trim()] = (param[1] || '').trim(); + }); + } + }); + if (md.rtcpFb) { + md.rtcpFb.forEach((rtcpFb) => { + if (rtcpFb.payload === type) { + feedback.push({ type: rtcpFb.type, subtype: rtcpFb.subtype }); + } + }); + } + if (codec.toUpperCase() === 'RTX') { + apts.set(parseInt(params.apt, 10), type); + } else { + mediaInfo.addCodec(new CodecInfo(codec, type, rate, encoding, params, feedback)); + } + }); + + apts.forEach((apt, id) => { + const codecInfo = mediaInfo.getCodecForType(id); + if (codecInfo) { + codecInfo.setRTX(apt); + } + }); +} + +function getRIDs(mediaInfo, md) { + const rids = md.rids; + if (!rids) { + return; + } + rids.forEach((rid) => { + const ridInfo = new RIDInfo(rid.id, DirectionWay.byValue(rid.direction)); + let formats = []; + const params = new Map(); + if (rid.params) { + const list = SDPTransform.parseParams(rid.params); + Object.keys(list).forEach((key) => { + if (key === 'pt') { + formats = list[key].split(','); + } else { + params.set(key, list[key]); + } + }); + ridInfo.setFormats(formats); + ridInfo.setParams(params); + } + mediaInfo.addRID(ridInfo); + }); +} + +function getSimulcastDir(index, md, simulcast) { + const simulcastDir = md.simulcast[`dir${index}`]; + const simulcastList = md.simulcast[`list${index}`]; + if (simulcastDir) { + const direction = DirectionWay.byValue(simulcastDir); + const list = SDPTransform.parseSimulcastStreamList(simulcastList); + list.forEach((stream) => { + const alternatives = []; + stream.forEach((entry) => { + alternatives.push(new SimulcastStreamInfo(entry.scid, entry.paused)); + }); + simulcast.addSimulcastAlternativeStreams(direction, alternatives); + }); + } +} + +function getSimulcast(mediaInfo, md) { + const encodings = []; + if (md.simulcast) { + const simulcast = new SimulcastInfo(); + getSimulcastDir('1', md, simulcast); + getSimulcastDir('2', md, simulcast); + + simulcast.getSimulcastStreams(DirectionWay.SEND).forEach((streams) => { + const alternatives = []; + streams.forEach((stream) => { + const encoding = new TrackEncodingInfo(stream.getId(), stream.isPaused()); + const ridInfo = mediaInfo.getRID(encoding.getId()); + if (ridInfo) { + const formats = ridInfo.getFormats(); + formats.forEach((format) => { + const codecInfo = mediaInfo.getCodecForType(format); + if (codecInfo) { + encoding.addCodec(codecInfo); + } + }); + encoding.setParams(ridInfo.getParams()); + alternatives.push(encoding); + } + }); + if (alternatives.length) { + encodings.push(alternatives); + } + }); + + mediaInfo.setSimulcast(simulcast); + } + return encodings; +} + +function getTracks(encodings, sdpInfo, md) { + const sources = new Map(); + const media = md.type; + if (md.ssrcs) { + let track; + let stream; + let source; + md.ssrcs.forEach((ssrcAttr) => { + const ssrc = ssrcAttr.id; + const key = ssrcAttr.attribute; + const value = ssrcAttr.value; + source = sources.get(ssrc); + if (!source) { + source = new SourceInfo(ssrc); + sources.set(source.getSSRC(), source); + } + if (key.toLowerCase() === 'cname') { + source.setCName(value); + } else if (key.toLowerCase() === 'mslabel') { + source.setMSLabel(value); + } else if (key.toLowerCase() === 'label') { + source.setLabel(value); + } else if (key.toLowerCase() === 'msid') { + const ids = value.split(' '); + const streamId = ids[0]; + const trackId = ids[1]; + source.setStreamId(streamId); + source.setTrackId(trackId); + stream = sdpInfo.getStream(streamId); + if (!stream) { + stream = new StreamInfo(streamId); + sdpInfo.addStream(stream); + } + track = stream.getTrack(trackId); + if (!track) { + track = new TrackInfo(media, trackId); + track.setEncodings(encodings); + stream.addTrack(track); + } + track.addSSRC(source); + } + }); + // Firefox in recvonly + if (source && !stream && md.mid) { + stream = sdpInfo.getStream(md.mid); + if (!stream) { + stream = new StreamInfo(md.mid); + sdpInfo.addStream(stream); + } + track = stream.getFirstTrack(); + if (!track) { + track = new TrackInfo(media, md.mid); + track.setEncodings(encodings); + stream.addTrack(track); + } + track.addSSRC(source); + } + } + + if (md.msid) { + const ids = md.msid.split(' '); + const streamId = ids[0]; + const trackId = ids[1]; + + let stream = sdpInfo.getStream(streamId); + if (!stream) { + stream = new StreamInfo(streamId); + sdpInfo.addStream(stream); + } + let track = stream.getTrack(trackId); + if (!track) { + track = new TrackInfo(media, trackId); + track.setMediaId(md.mid); + track.setEncodings(encodings); + stream.addTrack(track); + } + + sources.forEach((key, ssrc) => { + const source = sources.get(ssrc); + if (!source.getStreamId()) { + source.setStreamId(streamId); + source.setTrackId(trackId); + track.addSSRC(source); + } + }); + } + + if (md.ssrcGroups) { + md.ssrcGroups.forEach((ssrcGroupAttr) => { + const ssrcs = ssrcGroupAttr.ssrcs.split(' '); + const group = new SourceGroupInfo(ssrcGroupAttr.semantics, ssrcs); + const source = sources.get(parseInt(ssrcs[0], 10)); + sdpInfo + .getStream(source.getStreamId()) + .getTrack(source.getTrackId()) + .addSourceGroup(group); + }); + } +} + +SDPInfo.processString = (string) => { + const sdp = SDPTransform.parse(string); + return SDPInfo.process(sdp); +}; + + +SDPInfo.process = (sdp) => { + const sdpInfo = new SDPInfo(); + + sdpInfo.setVersion(sdp.version); + sdpInfo.setTiming(sdp.timing); + sdpInfo.setConnection(sdp.connection); + sdpInfo.setOrigin(sdp.origin); + sdpInfo.msidSemantic = sdp.msidSemantic; + sdpInfo.name = sdp.name; + + let ufrag = sdp.iceUfrag; + let pwd = sdp.icePwd; + let iceOptions = sdp.iceOptions; + if (ufrag || pwd || iceOptions) { + sdpInfo.setICE(new ICEInfo(ufrag, pwd, iceOptions)); + } + + let fingerprintAttr = sdp.fingerprint; + if (fingerprintAttr) { + const remoteHash = fingerprintAttr.type; + const remoteFingerprint = fingerprintAttr.hash; + let setup = Setup.ACTPASS; + if (sdp.setup) { + setup = Setup.byValue(sdp.setup); + } + + sdpInfo.setDTLS(new DTLSInfo(setup, remoteHash, remoteFingerprint)); + } + + sdp.media.forEach((md) => { + const media = md.type; + const mid = md.mid; + const mediaInfo = new MediaInfo(mid, media); + mediaInfo.setXGoogleFlag(md.xGoogleFlag); + mediaInfo.rtcp = md.rtcp; + mediaInfo.setConnection(md.connection); + + if (md.bandwidth && md.bandwidth.length > 0) { + md.bandwidth.forEach((bandwidth) => { + if (bandwidth.type === 'AS') { + mediaInfo.setBitrate(bandwidth.limit); + } + }); + } + + ufrag = md.iceUfrag; + pwd = md.icePwd; + iceOptions = md.iceOptions; + if (ufrag || pwd || iceOptions) { + mediaInfo.setICE(new ICEInfo(ufrag, pwd, iceOptions)); + } + + fingerprintAttr = md.fingerprint; + if (fingerprintAttr) { + const remoteHash = fingerprintAttr.type; + const remoteFingerprint = fingerprintAttr.hash; + let setup = Setup.ACTPASS; + if (md.setup) { + setup = Setup.byValue(md.setup); + } + + mediaInfo.setDTLS(new DTLSInfo(setup, remoteHash, remoteFingerprint)); + } + + let direction = Direction.SENDRECV; + + if (md.direction) { + direction = Direction.byValue(md.direction.toUpperCase()); + } + + mediaInfo.setDirection(direction); + + const candidates = md.candidates; + if (candidates) { + candidates.forEach((candidate) => { + mediaInfo.addCandidate(new CandidateInfo(candidate.foundation, candidate.component, + candidate.transport, candidate.priority, candidate.ip, candidate.port, candidate.type, + candidate.generation, candidate.relAddr, candidate.relPort)); + }); + } + + getFormats(mediaInfo, md); + + const extmaps = md.ext; + if (extmaps) { + extmaps.forEach((extmap) => { + mediaInfo.addExtension(extmap.value, extmap.uri); + }); + } + + getRIDs(mediaInfo, md); + + const encodings = getSimulcast(mediaInfo, md); + + getTracks(encodings, sdpInfo, md); + + sdpInfo.addMedia(mediaInfo); + }); + return sdpInfo; +}; + +module.exports = SDPInfo; diff --git a/erizo_controller/common/semanticSdp/SemanticSdp.js b/erizo_controller/common/semanticSdp/SemanticSdp.js new file mode 100755 index 0000000000..5aa27d3695 --- /dev/null +++ b/erizo_controller/common/semanticSdp/SemanticSdp.js @@ -0,0 +1,27 @@ +const SDPInfo = require('./SDPInfo'); +const CandidateInfo = require('./CandidateInfo'); +const CodecInfo = require('./CodecInfo'); +const DTLSInfo = require('./DTLSInfo'); +const ICEInfo = require('./ICEInfo'); +const MediaInfo = require('./MediaInfo'); +const Setup = require('./Setup'); +const SourceGroupInfo = require('./SourceGroupInfo'); +const SourceInfo = require('./SourceInfo'); +const StreamInfo = require('./StreamInfo'); +const TrackInfo = require('./TrackInfo'); +const Direction = require('./Direction'); + +module.exports = { + SDPInfo, + CandidateInfo, + CodecInfo, + DTLSInfo, + ICEInfo, + MediaInfo, + Setup, + SourceGroupInfo, + SourceInfo, + StreamInfo, + TrackInfo, + Direction, +}; diff --git a/erizo_controller/common/semanticSdp/Setup.js b/erizo_controller/common/semanticSdp/Setup.js new file mode 100755 index 0000000000..ad29d0b8e2 --- /dev/null +++ b/erizo_controller/common/semanticSdp/Setup.js @@ -0,0 +1,37 @@ +const Enum = require('./Enum'); + +const Setup = Enum('ACTIVE', 'PASSIVE', 'ACTPASS', 'INACTIVE'); + +Setup.byValue = setup => Setup[setup.toUpperCase()]; + +Setup.toString = (setup) => { + switch (setup) { + case Setup.ACTIVE: + return 'active'; + case Setup.PASSIVE: + return 'passive'; + case Setup.ACTPASS: + return 'actpass'; + case Setup.INACTIVE: + return 'inactive'; + default: + return 'unknown'; + } +}; + +Setup.reverse = (setup) => { + switch (setup) { + case Setup.ACTIVE: + return Setup.PASSIVE; + case Setup.PASSIVE: + return Setup.ACTIVE; + case Setup.ACTPASS: + return Setup.PASSIVE; + case Setup.INACTIVE: + return Setup.INACTIVE; + default: + return Setup.ACTIVE; + } +}; + +module.exports = Setup; diff --git a/erizo_controller/common/semanticSdp/SimulcastInfo.js b/erizo_controller/common/semanticSdp/SimulcastInfo.js new file mode 100755 index 0000000000..3e3622e881 --- /dev/null +++ b/erizo_controller/common/semanticSdp/SimulcastInfo.js @@ -0,0 +1,72 @@ +const DirectionWay = require('./DirectionWay'); + +class SimulcastInfo { + constructor() { + this.send = []; + this.recv = []; + } + + clone() { + const cloned = new SimulcastInfo(); + this.send.forEach((sendStreams) => { + const alternatives = []; + sendStreams.forEach((sendStream) => { + alternatives.push(sendStream.clone()); + }); + cloned.addSimulcastAlternativeStreams(alternatives); + }); + this.recv.forEach((recvStreams) => { + const alternatives = []; + recvStreams.forEach((recvStream) => { + alternatives.push(recvStream.clone()); + }); + cloned.addSimulcastAlternativeStreams(alternatives); + }); + return cloned; + } + + plain() { + const plain = { + send: [], + recv: [], + }; + this.send.forEach((sendStreams) => { + const alternatives = []; + sendStreams.forEach((sendStream) => { + alternatives.push(sendStream.plain()); + }); + plain.send.push(alternatives); + }); + this.recv.forEach((recvStreams) => { + const alternatives = []; + recvStreams.forEach((recvStream) => { + alternatives.push(recvStream.plain()); + }); + plain.recv.push(alternatives); + }); + return plain; + } + + addSimulcastAlternativeStreams(direction, streams) { + if (direction === DirectionWay.SEND) { + return this.send.push(streams); + } + return this.recv.push(streams); + } + + addSimulcastStream(direction, stream) { + if (direction === DirectionWay.SEND) { + return this.send.push([stream]); + } + return this.recv.push([stream]); + } + + getSimulcastStreams(direction) { + if (direction === DirectionWay.SEND) { + return this.send; + } + return this.recv; + } +} + +module.exports = SimulcastInfo; diff --git a/erizo_controller/common/semanticSdp/SimulcastStreamInfo.js b/erizo_controller/common/semanticSdp/SimulcastStreamInfo.js new file mode 100755 index 0000000000..b29723ed90 --- /dev/null +++ b/erizo_controller/common/semanticSdp/SimulcastStreamInfo.js @@ -0,0 +1,27 @@ +class SimulcastStreamInfo { + constructor(id, paused) { + this.paused = paused; + this.id = id; + } + + clone() { + return new SimulcastStreamInfo(this.id, this.paused); + } + + plain() { + return { + id: this.id, + paused: this.paused, + }; + } + + isPaused() { + return this.paused; + } + + getId() { + return this.id; + } +} + +module.exports = SimulcastStreamInfo; diff --git a/erizo_controller/common/semanticSdp/SourceGroupInfo.js b/erizo_controller/common/semanticSdp/SourceGroupInfo.js new file mode 100755 index 0000000000..06bcf8d9f2 --- /dev/null +++ b/erizo_controller/common/semanticSdp/SourceGroupInfo.js @@ -0,0 +1,32 @@ +class SourceGroupInfo { + constructor(semantics, ssrcs) { + this.semantics = semantics; + this.ssrcs = []; + ssrcs.forEach((ssrc) => { + this.ssrcs.push(parseInt(ssrc, 10)); + }); + } + + clone() { + return new SourceGroupInfo(this.semantics, this.ssrcs); + } + + plain() { + const plain = { + semantics: this.semantics, + ssrcs: [], + }; + plain.ssrcs = this.ssrcs; + return plain; + } + + getSemantics() { + return this.semantics; + } + + getSSRCs() { + return this.ssrcs; + } +} + +module.exports = SourceGroupInfo; diff --git a/erizo_controller/common/semanticSdp/SourceInfo.js b/erizo_controller/common/semanticSdp/SourceInfo.js new file mode 100755 index 0000000000..b46a77f421 --- /dev/null +++ b/erizo_controller/common/semanticSdp/SourceInfo.js @@ -0,0 +1,71 @@ +class SourceInfo { + constructor(ssrc) { + this.ssrc = ssrc; + } + + clone() { + const clone = new SourceInfo(this.ssrc); + clone.setCName(this.cname); + clone.setStreamId(this.streamId); + this.setTrackId(this.trackId); + } + + + plain() { + const plain = { + ssrc: this.ssrc, + }; + if (this.cname) plain.cname = this.cname; + if (this.label) plain.label = this.label; + if (this.mslabel) plain.mslabel = this.mslabel; + if (this.streamId) plain.streamId = this.streamId; + if (this.trackId) plain.trackid = this.trackId; + return plain; + } + + getCName() { + return this.cname; + } + + setCName(cname) { + this.cname = cname; + } + + getStreamId() { + return this.streamId; + } + + setStreamId(streamId) { + this.streamId = streamId; + } + + getTrackId() { + return this.trackId; + } + + setTrackId(trackId) { + this.trackId = trackId; + } + + getMSLabel() { + return this.mslabel; + } + + setMSLabel(mslabel) { + this.mslabel = mslabel; + } + + getLabel() { + return this.label; + } + + setLabel(label) { + this.label = label; + } + + getSSRC() { + return this.ssrc; + } +} + +module.exports = SourceInfo; diff --git a/erizo_controller/common/semanticSdp/StreamInfo.js b/erizo_controller/common/semanticSdp/StreamInfo.js new file mode 100755 index 0000000000..404451af3d --- /dev/null +++ b/erizo_controller/common/semanticSdp/StreamInfo.js @@ -0,0 +1,57 @@ +class StreamInfo { + constructor(id) { + this.id = id; + this.tracks = new Map(); + } + + clone() { + const cloned = new StreamInfo(this.id); + this.tracks.forEach((track) => { + cloned.addTrack(track.clone()); + }); + return cloned; + } + + plain() { + const plain = { + id: this.id, + tracks: [], + }; + this.tracks.forEach((track) => { + plain.tracks.push(track.plain()); + }); + return plain; + } + + getId() { + return this.id; + } + + addTrack(track) { + this.tracks.set(track.getId(), track); + } + + getFirstTrack(media) { + let result; + this.tracks.forEach((track) => { + if (track.getMedia().toLowerCase() === media.toLowerCase()) { + result = track; + } + }); + return result; + } + + getTracks() { + return this.tracks; + } + + removeAllTracks() { + this.tracks.clear(); + } + + getTrack(trackId) { + return this.tracks.get(trackId); + } +} + +module.exports = StreamInfo; diff --git a/erizo_controller/common/semanticSdp/TrackEncodingInfo.js b/erizo_controller/common/semanticSdp/TrackEncodingInfo.js new file mode 100755 index 0000000000..7eceb5354f --- /dev/null +++ b/erizo_controller/common/semanticSdp/TrackEncodingInfo.js @@ -0,0 +1,59 @@ +class TrackEncodingInfo { + constructor(id, paused) { + this.id = id; + this.paused = paused; + this.codecs = new Map(); + this.params = new Map(); + } + + clone() { + const cloned = new TrackEncodingInfo(this.id, this.paused); + this.codecs.forEach((codec) => { + cloned.addCodec(codec.cloned()); + }); + cloned.setParams(this.params); + return cloned; + } + + plain() { + const plain = { + id: this.id, + paused: this.paused, + codecs: {}, + params: {}, + }; + this.codecs.keys().forEach((id) => { + plain.codecs[id] = this.codecs.get(id).plain(); + }); + this.params.keys().forEach((id) => { + plain.params[id] = this.params.get(id).param; + }); + return plain; + } + + getId() { + return this.id; + } + + getCodecs() { + return this.codecs; + } + + addCodec(codec) { + this.codecs.set(codec.getType(), codec); + } + + getParams() { + return this.params; + } + + setParams(params) { + this.params = new Map(params); + } + + isPaused() { + return this.paused; + } +} + +module.exports = TrackEncodingInfo; diff --git a/erizo_controller/common/semanticSdp/TrackInfo.js b/erizo_controller/common/semanticSdp/TrackInfo.js new file mode 100755 index 0000000000..06e3a224be --- /dev/null +++ b/erizo_controller/common/semanticSdp/TrackInfo.js @@ -0,0 +1,123 @@ +class TrackInfo { + constructor(media, id) { + this.media = media; + this.id = id; + this.ssrcs = []; + this.groups = []; + this.encodings = []; + } + + clone() { + const cloned = new TrackInfo(this.media, this.id); + if (this.mediaId) { + cloned.setMediaId(this.mediaId); + } + this.ssrcs.forEach((ssrc) => { + cloned.addSSRC(ssrc); + }); + this.groups.forEach((group) => { + cloned.addSourceGroup(group.clone()); + }); + this.encodings.forEach((encoding) => { + const alternatives = []; + encoding.forEach((entry) => { + alternatives.push(entry.cloned()); + }); + cloned.addAlternativeEncoding(alternatives); + }); + return cloned; + } + + plain() { + const plain = { + media: this.media, + id: this.id, + ssrcs: [], + groups: [], + encodings: [], + }; + if (this.mediaId) { + plain.mediaId = this.mediaId; + } + this.ssrcs.forEach((ssrc) => { + plain.ssrcs.push(ssrc); + }); + this.groups.forEach((group) => { + plain.groups.push(group.plain()); + }); + this.encodings.forEach((encoding) => { + const alternatives = []; + encoding.forEach((entry) => { + alternatives.push(entry.plain()); + }); + plain.encodings.push(alternatives); + }); + return plain; + } + + getMedia() { + return this.media; + } + + setMediaId(mediaId) { + this.mediaId = mediaId; + } + + getMediaId() { + return this.mediaId; + } + + getId() { + return this.id; + } + + addSSRC(ssrc) { + this.ssrcs.push(ssrc); + } + + getSSRCs() { + return this.ssrcs; + } + + addSourceGroup(group) { + this.groups.push(group); + } + + getSourceGroup(schematics) { + let result; + this.groups.forEach((group) => { + if (group.getSemantics().toLowerCase() === schematics.toLowerCase()) { + result = group; + } + }); + return result; + } + + getSourceGroups() { + return this.groups; + } + + hasSourceGroup(schematics) { + let result = false; + this.groups.forEach((group) => { + if (group.getSemantics().toLowerCase() === schematics.toLowerCase()) { + result = true; + } + }); + return result; + } + + getEncodings() { + return this.encodings; + } + + addAlternaticeEncodings(alternatives) { + this.encodings.push(alternatives); + } + + setEncodings(encodings) { + this.encodings = encodings; + } +} + +module.exports = TrackInfo; diff --git a/erizo_controller/erizoClient/gulpfile.js b/erizo_controller/erizoClient/gulpfile.js index cddda906a0..c8f46a131f 100644 --- a/erizo_controller/erizoClient/gulpfile.js +++ b/erizo_controller/erizoClient/gulpfile.js @@ -19,7 +19,7 @@ const config = { production: './dist/production', basicExample: '../../extras/basic_example/public/', spine: '../../spine/', - js: './src/**/*.js', + js: ['./src/**/*.js', '../common/semanticSdp/*.js'], }, }; diff --git a/erizo_controller/erizoClient/src/utils/SdpHelpers.js b/erizo_controller/erizoClient/src/utils/SdpHelpers.js index de3813d1dc..d5a8d74321 100644 --- a/erizo_controller/erizoClient/src/utils/SdpHelpers.js +++ b/erizo_controller/erizoClient/src/utils/SdpHelpers.js @@ -22,33 +22,20 @@ SdpHelpers.addSpatialLayer = (cname, msid, mslabel, `a=ssrc:${spatialLayerIdRtx} mslabel:${mslabel}\r\n` + `a=ssrc:${spatialLayerIdRtx} label:${label}\r\n`; -SdpHelpers.setMaxBW = (sdpInput, spec) => { - let r; - let a; - let sdp = sdpInput; +SdpHelpers.setMaxBW = (sdp, spec) => { if (spec.video && spec.maxVideoBW) { - sdp = sdp.replace(/b=AS:.*\r\n/g, ''); - a = sdp.match(/m=video.*\r\n/); - if (a == null) { - a = sdp.match(/m=video.*\n/); - } - if (a && (a.length > 0)) { - r = `${a[0]}b=AS:${spec.maxVideoBW}\r\n`; - sdp = sdp.replace(a[0], r); + const video = sdp.getMedia('video'); + if (video) { + video.setBitrate(spec.maxVideoBW); } } if (spec.audio && spec.maxAudioBW) { - a = sdp.match(/m=audio.*\r\n/); - if (a == null) { - a = sdp.match(/m=audio.*\n/); - } - if (a && (a.length > 0)) { - r = `${a[0]}b=AS:${spec.maxAudioBW}\r\n`; - sdp = sdp.replace(a[0], r); + const audio = sdp.getMedia('audio'); + if (audio) { + audio.setBitrate(spec.maxVideoBW); } } - return sdp; }; SdpHelpers.enableOpusNacks = (sdpInput) => { diff --git a/erizo_controller/erizoClient/src/webrtc-stacks/BaseStack.js b/erizo_controller/erizoClient/src/webrtc-stacks/BaseStack.js index d0d6762c25..944b2d2fb3 100644 --- a/erizo_controller/erizoClient/src/webrtc-stacks/BaseStack.js +++ b/erizo_controller/erizoClient/src/webrtc-stacks/BaseStack.js @@ -1,4 +1,6 @@ /* global RTCSessionDescription, RTCIceCandidate, RTCPeerConnection */ +// eslint-disable-next-line +import SemanticSdp from '../../../common/semanticSdp/SemanticSdp'; import SdpHelpers from '../utils/SdpHelpers'; import Logger from '../utils/Logger'; @@ -8,6 +10,8 @@ const BaseStack = (specInput) => { const specBase = specInput; let localDesc; let remoteDesc; + let localSdp; + let remoteSdp; Logger.info('Starting Base stack', specBase); @@ -87,16 +91,21 @@ const BaseStack = (specInput) => { if (!isSubscribe) { localDesc.sdp = that.enableSimulcast(localDesc.sdp); } - localDesc.sdp = SdpHelpers.setMaxBW(localDesc.sdp, specBase); + localSdp = SemanticSdp.SDPInfo.processString(localDesc.sdp); + SdpHelpers.setMaxBW(localSdp, specBase); + localDesc.sdp = localSdp.toString(); + specBase.callback({ type: localDesc.type, - sdp: localDesc.sdp, + sdp: localSdp.toJSON(), }); }; const setLocalDescForAnswerp2p = (sessionDescription) => { localDesc = sessionDescription; - localDesc.sdp = SdpHelpers.setMaxBW(localDesc.sdp, specBase); + localSdp = SemanticSdp.SDPInfo.processString(localDesc.sdp); + SdpHelpers.setMaxBW(localSdp, specBase); + localDesc.sdp = localSdp.toString(); specBase.callback({ type: localDesc.type, sdp: localDesc.sdp, @@ -109,7 +118,9 @@ const BaseStack = (specInput) => { const processOffer = (message) => { // Its an offer, we assume its p2p const msg = message; - msg.sdp = SdpHelpers.setMaxBW(msg.sdp, specBase); + remoteSdp = SemanticSdp.SDPInfo.process(msg.sdp); + SdpHelpers.setMaxBW(remoteSdp, specBase); + msg.sdp = remoteSdp.toString(); that.peerConnection.setRemoteDescription(msg).then(() => { that.peerConnection.createAnswer(that.mediaConstraints) .then(setLocalDescForAnswerp2p).catch(errorCallback.bind(null, 'createAnswer p2p', undefined)); @@ -123,7 +134,9 @@ const BaseStack = (specInput) => { Logger.debug('Remote Description', msg.sdp); Logger.debug('Local Description', localDesc.sdp); - msg.sdp = SdpHelpers.setMaxBW(msg.sdp, specBase); + remoteSdp = SemanticSdp.SDPInfo.process(msg.sdp); + SdpHelpers.setMaxBW(remoteSdp, specBase); + msg.sdp = remoteSdp.toString(); remoteDesc = msg; that.peerConnection.setLocalDescription(localDesc).then(() => { @@ -221,22 +234,27 @@ const BaseStack = (specInput) => { specBase.maxAudioBW = config.maxAudioBW; } - localDesc.sdp = SdpHelpers.setMaxBW(localDesc.sdp, specBase); + localSdp = SemanticSdp.SDPInfo.processString(localDesc.sdp); + SdpHelpers.setMaxBW(localSdp, specBase); + localDesc.sdp = localSdp.toString(); + if (config.Sdp || config.maxAudioBW) { Logger.debug('Updating with SDP renegotiation', specBase.maxVideoBW, specBase.maxAudioBW); that.peerConnection.setLocalDescription(localDesc) .then(() => { - remoteDesc.sdp = SdpHelpers.setMaxBW(remoteDesc.sdp, specBase); + remoteSdp = SemanticSdp.SDPInfo.process(remoteDesc.sdp); + SdpHelpers.setMaxBW(remoteSdp, specBase); + remoteDesc.sdp = remoteSdp.toString(); return that.peerConnection.setRemoteDescription(new RTCSessionDescription(remoteDesc)); }).then(() => { specBase.remoteDescriptionSet = true; - specBase.callback({ type: 'updatestream', sdp: localDesc.sdp }); + specBase.callback({ type: 'updatestream', sdp: localSdp.toJSON() }); }).catch(errorCallback.bind(null, 'updateSpec', callback)); } else { Logger.debug('Updating without SDP renegotiation, ' + 'newVideoBW:', specBase.maxVideoBW, 'newAudioBW:', specBase.maxAudioBW); - specBase.callback({ type: 'updatestream', sdp: localDesc.sdp }); + specBase.callback({ type: 'updatestream', sdp: localSdp.toJSON() }); } } if (config.minVideoBW || (config.slideShowMode !== undefined) || diff --git a/erizo_controller/erizoController/models/Client.js b/erizo_controller/erizoController/models/Client.js index ccd3026ca5..af8fee7ced 100644 --- a/erizo_controller/erizoController/models/Client.js +++ b/erizo_controller/erizoController/models/Client.js @@ -238,7 +238,7 @@ class Client extends events.EventEmitter { callback(null, 'ErizoAgent or ErizoJS is not reachable'); return; } - log.debug('Sending message back to the client', signMess); + log.debug('Sending message back to the client', id); this.sendMessage('signaling_message_erizo', {mess: signMess, streamId: id}); }); } else { diff --git a/erizo_controller/erizoJS/erizoJSController.js b/erizo_controller/erizoJS/erizoJSController.js index 4fd7b3445c..84e2488cc3 100644 --- a/erizo_controller/erizoJS/erizoJSController.js +++ b/erizo_controller/erizoJS/erizoJSController.js @@ -4,6 +4,7 @@ var logger = require('./../common/logger').logger; var amqper = require('./../common/amqper'); var Publisher = require('./models/Publisher').Publisher; var ExternalInput = require('./models/Publisher').ExternalInput; +var SemanticSdp = require('./../common/semanticSdp/SemanticSdp'); // Logger var log = logger.getLogger('ErizoJSController'); @@ -103,6 +104,8 @@ exports.ErizoJSController = function (threadPool, ioThreadPool) { case CONN_SDP: case CONN_GATHERED: mess = mess.replace(that.privateRegexp, that.publicIP); + const sdp = SemanticSdp.SDPInfo.processString(mess); + mess = sdp.toJSON(); if (options.createOffer) callback('callback', {type: 'offer', sdp: mess}); else @@ -249,6 +252,8 @@ exports.ErizoJSController = function (threadPool, ioThreadPool) { if (publisher.hasSubscriber(peerId)) { var subscriber = publisher.getSubscriber(peerId); if (msg.type === 'offer') { + const sdp = SemanticSdp.SDPInfo.process(msg.sdp); + msg.sdp = sdp.toString(); subscriber.setRemoteSdp(msg.sdp); disableDefaultHandlers(subscriber); } else if (msg.type === 'candidate') { @@ -256,8 +261,11 @@ exports.ErizoJSController = function (threadPool, ioThreadPool) { msg.candidate.sdpMLineIndex, msg.candidate.candidate); } else if (msg.type === 'updatestream') { - if(msg.sdp) + if(msg.sdp) { + const sdp = SemanticSdp.SDPInfo.process(msg.sdp); + msg.sdp = sdp.toString(); subscriber.setRemoteSdp(msg.sdp); + } if (msg.config) { if (msg.config.slideShowMode !== undefined) { that.setSlideShow(msg.config.slideShowMode, peerId, streamId); @@ -277,6 +285,8 @@ exports.ErizoJSController = function (threadPool, ioThreadPool) { } } else { if (msg.type === 'offer') { + const sdp = SemanticSdp.SDPInfo.process(msg.sdp); + msg.sdp = sdp.toString(); publisher.wrtc.setRemoteSdp(msg.sdp); disableDefaultHandlers(publisher.wrtc); } else if (msg.type === 'candidate') { @@ -285,6 +295,8 @@ exports.ErizoJSController = function (threadPool, ioThreadPool) { msg.candidate.candidate); } else if (msg.type === 'updatestream') { if (msg.sdp) { + const sdp = SemanticSdp.SDPInfo.process(msg.sdp); + msg.sdp = sdp.toJSON(); publisher.wrtc.setRemoteSdp(msg.sdp); } if (msg.config) { diff --git a/erizo_controller/installErizo_controller.sh b/erizo_controller/installErizo_controller.sh index afecfc9c6f..6cf906e317 100755 --- a/erizo_controller/installErizo_controller.sh +++ b/erizo_controller/installErizo_controller.sh @@ -13,7 +13,7 @@ NVM_CHECK="$LICODE_ROOT"/scripts/checkNvm.sh echo [erizo_controller] Installing node_modules for erizo_controller nvm use -npm install --loglevel error amqp socket.io@2.0.3 log4js@1.0.1 node-getopt uuid@3.1.0 +npm install --loglevel error amqp socket.io@2.0.3 log4js@1.0.1 node-getopt uuid@3.1.0 sdp-transform@2.3.0 npm install --loglevel error -g google-closure-compiler-js echo [erizo_controller] Done, node_modules installed diff --git a/erizo_controller/test/erizoJS/erizoJSController.js b/erizo_controller/test/erizoJS/erizoJSController.js index 7494102dab..8ab48707d8 100644 --- a/erizo_controller/test/erizoJS/erizoJSController.js +++ b/erizo_controller/test/erizoJS/erizoJSController.js @@ -206,7 +206,9 @@ describe('Erizo JS Controller', function() { expect(callback.callCount).to.equal(2); expect(callback.args[1]).to.deep.equal(['callback', {type: 'initializing'}]); - expect(callback.args[0]).to.deep.equal(['callback', {sdp: '', type: 'offer'}]); + expect(callback.args[0].length).to.equal(2); + expect(callback.args[0][0]).to.equal('callback'); + expect(callback.args[0][1].type).to.equal('offer'); }); it('should succeed sending answer event from SDP', function() { @@ -215,7 +217,9 @@ describe('Erizo JS Controller', function() { expect(callback.callCount).to.equal(2); expect(callback.args[1]).to.deep.equal(['callback', {type: 'initializing'}]); - expect(callback.args[0]).to.deep.equal(['callback', {sdp: '', type: 'answer'}]); + expect(callback.args[0].length).to.equal(2); + expect(callback.args[0][0]).to.equal('callback'); + expect(callback.args[0][1].type).to.equal('answer'); }); it('should succeed sending answer event', function() { @@ -224,7 +228,9 @@ describe('Erizo JS Controller', function() { expect(callback.callCount).to.equal(2); expect(callback.args[1]).to.deep.equal(['callback', {type: 'initializing'}]); - expect(callback.args[0]).to.deep.equal(['callback', {sdp: '', type: 'answer'}]); + expect(callback.args[0].length).to.equal(2); + expect(callback.args[0][0]).to.equal('callback'); + expect(callback.args[0][1].type).to.equal('answer'); }); it('should succeed sending candidate event', function() { @@ -270,7 +276,7 @@ describe('Erizo JS Controller', function() { }); it('should set remote sdp when received', function() { - controller.processSignaling(kArbitraryId, undefined, {type: 'offer'}); + controller.processSignaling(kArbitraryId, undefined, {type: 'offer', sdp: {media: []}}); expect(mocks.WebRtcConnection.setRemoteSdp.callCount).to.equal(1); }); @@ -286,7 +292,7 @@ describe('Erizo JS Controller', function() { it('should update sdp', function() { controller.processSignaling(kArbitraryId, undefined, { type: 'updatestream', - sdp: 'sdp'}); + sdp: {media: []}}); expect(mocks.WebRtcConnection.setRemoteSdp.callCount).to.equal(1); }); @@ -342,7 +348,8 @@ describe('Erizo JS Controller', function() { }); it('should set remote sdp when received', function() { - controller.processSignaling(kArbitraryId, kArbitraryId2, {type: 'offer'}); + controller.processSignaling(kArbitraryId, kArbitraryId2, {type: 'offer', + sdp: {media: []}}); expect(mocks.WebRtcConnection.setRemoteSdp.callCount).to.equal(1); }); @@ -358,7 +365,7 @@ describe('Erizo JS Controller', function() { it('should update sdp', function() { controller.processSignaling(kArbitraryId, kArbitraryId2, { type: 'updatestream', - sdp: 'sdp'}); + sdp: {media: []}}); expect(mocks.WebRtcConnection.setRemoteSdp.callCount).to.equal(1); }); diff --git a/package.json b/package.json index 9eb1bac037..ab1db17467 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,6 @@ "buildClient": "cd erizo_controller/erizoClient && gulp", "test": " ./node_modules/mocha/bin/mocha nuve/nuveAPI/test/* erizo_controller/test/*", "lintClient": "cd erizo_controller/erizoClient && gulp lint", - "lint": "./node_modules/jshint/bin/jshint erizo_controller/erizoAgent/ erizo_controller/erizoController/ erizo_controller/erizoJS/ erizo_controller/common/ erizo_controller/test/ nuve/ extras/basic_example/ spine/" + "lint": "./node_modules/jshint/bin/jshint erizo_controller/erizoAgent/ erizo_controller/erizoController/ erizo_controller/erizoJS/ erizo_controller/common/amqper.js erizo_controller/common/logger.js erizo_controller/test/ nuve/ extras/basic_example/basicServer.js extras/basic_example/public/*.js spine/" } } diff --git a/spine/nativeClient.js b/spine/nativeClient.js index af94b104e6..fb4a1d559e 100644 --- a/spine/nativeClient.js +++ b/spine/nativeClient.js @@ -2,6 +2,7 @@ const addon = require('./../erizoAPI/build/Release/addon'); // eslint-disable-line import/no-unresolved const licodeConfig = require('./../licode_config'); const mediaConfig = require('./../rtp_media_config'); +const SemanticSdp = require('./../erizo_controller/common/semanticSdp/SemanticSdp'); const logger = require('./logger').logger; const log = logger.getLogger('NativeClient'); @@ -64,7 +65,8 @@ exports.ErizoNativeConnection = (config) => { case CONN_SDP: case CONN_GATHERED: setTimeout(() => { - initConnectionCallback('callback', { type: 'offer', sdp: mess }); + const sdp = SemanticSdp.SDPInfo.processString(mess); + initConnectionCallback('callback', { type: 'offer', sdp: sdp.toJSON() }); }, 100); break; @@ -175,7 +177,8 @@ exports.ErizoNativeConnection = (config) => { } else if (signalingMsg.type === 'answer') { setTimeout(() => { log.info('Passing delayed answer'); - wrtc.setRemoteSdp(signalingMsg.sdp); + const sdp = SemanticSdp.SDPInfo.process(signalingMsg.sdp); + wrtc.setRemoteSdp(sdp.toString()); that.onaddstream({ stream: { active: true } }); }, 10); } diff --git a/test/rtp_media_config_default.js b/test/rtp_media_config_default.js index 975253cae2..2011a2cd4f 100644 --- a/test/rtp_media_config_default.js +++ b/test/rtp_media_config_default.js @@ -1,138 +1,177 @@ -var mediaConfig = {}; - -mediaConfig.extMappings = [ - "urn:ietf:params:rtp-hdrext:ssrc-audio-level", - "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", - "urn:ietf:params:rtp-hdrext:toffset", - "urn:3gpp:video-orientation", - // "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01", - "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", - "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id" +const mediaConfig = {}; + +const extMappings = [ + 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', + 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', + 'urn:ietf:params:rtp-hdrext:toffset', + 'urn:3gpp:video-orientation', + // 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01', + 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay', + 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id', ]; -mediaConfig.rtpMappings = {}; - -mediaConfig.rtpMappings.vp8 = { - payloadType: 100, - encodingName: 'VP8', - clockRate: 90000, - channels: 1, - mediaType: 'video', - feedbackTypes: [ - 'ccm fir', - 'nack', - 'nack pli', - 'goog-remb', -// 'transport-cc', - ], -}; -/* -mediaConfig.rtpMappings.red = { - payloadType: 116, - encodingName: 'red', - clockRate: 90000, - channels: 1, - mediaType: 'video', -}; - -mediaConfig.rtpMappings.rtx = { - payloadType: 96, - encodingName: 'rtx', - clockRate: 90000, - channels: 1, - mediaType: 'video', - formatParameters: { - apt: '100' - }, -}; - -mediaConfig.rtpMappings.ulpfec = { - payloadType: 117, - encodingName: 'ulpfec', - clockRate: 90000, - channels: 1, - mediaType: 'video', -}; - -mediaConfig.rtpMappings.opus = { - payloadType: 111, - encodingName: 'opus', - clockRate: 48000, - channels: 2, - mediaType: 'audio', -}; - -mediaConfig.rtpMappings.isac16 = { - payloadType: 103, - encodingName: 'ISAC', - clockRate: 16000, - channels: 1, - mediaType: 'audio', -}; - -mediaConfig.rtpMappings.isac32 = { - payloadType: 104, - encodingName: 'ISAC', - clockRate: 32000, - channels: 1, - mediaType: 'audio', -}; - -*/ -mediaConfig.rtpMappings.pcmu = { - payloadType: 0, - encodingName: 'PCMU', - clockRate: 8000, - channels: 1, - mediaType: 'audio', -}; -/* -mediaConfig.rtpMappings.pcma = { - payloadType: 8, - encodingName: 'PCMA', - clockRate: 8000, - channels: 1, - mediaType: 'audio', -}; - -mediaConfig.rtpMappings.cn8 = { - payloadType: 13, - encodingName: 'CN', - clockRate: 8000, - channels: 1, - mediaType: 'audio', -}; - -mediaConfig.rtpMappings.cn16 = { - payloadType: 105, - encodingName: 'CN', - clockRate: 16000, - channels: 1, - mediaType: 'audio', -}; - -mediaConfig.rtpMappings.cn32 = { - payloadType: 106, - encodingName: 'CN', - clockRate: 32000, - channels: 1, - mediaType: 'audio', -}; - -mediaConfig.rtpMappings.cn48 = { - payloadType: 107, - encodingName: 'CN', - clockRate: 48000, - channels: 1, - mediaType: 'audio', -}; -*/ -mediaConfig.rtpMappings.telephoneevent = { - payloadType: 126, - encodingName: 'telephone-event', - clockRate: 8000, - channels: 1, - mediaType: 'audio', +const vp8 = { + payloadType: 100, + encodingName: 'VP8', + clockRate: 90000, + channels: 1, + mediaType: 'video', + feedbackTypes: [ + 'ccm fir', + 'nack', + 'nack pli', + 'goog-remb', + // 'transport-cc', + ], +}; + +const vp9 = { + payloadType: 100, + encodingName: 'VP9', + clockRate: 90000, + channels: 1, + mediaType: 'video', + feedbackTypes: [ + 'ccm fir', + 'nack', + 'nack pli', + 'goog-remb', + // 'transport-cc', + ], +}; + +const h264 = { + payloadType: 101, + encodingName: 'H264', + clockRate: 90000, + channels: 1, + mediaType: 'video', + formatParameters: { + 'profile-level-id': '42e01f', + 'level-asymmetry-allowed': '1', + 'packetization-mode': '1', + }, + feedbackTypes: [ + 'ccm fir', + 'nack', + 'nack pli', + 'goog-remb', + // 'transport-cc', + ], +}; + +const red = { + payloadType: 116, + encodingName: 'red', + clockRate: 90000, + channels: 1, + mediaType: 'video', +}; + +const rtx = { + payloadType: 96, + encodingName: 'rtx', + clockRate: 90000, + channels: 1, + mediaType: 'video', + formatParameters: { + apt: '100', + }, +}; + +const ulpfec = { + payloadType: 117, + encodingName: 'ulpfec', + clockRate: 90000, + channels: 1, + mediaType: 'video', +}; + +const opus = { + payloadType: 111, + encodingName: 'opus', + clockRate: 48000, + channels: 2, + mediaType: 'audio', +}; + +const isac16 = { + payloadType: 103, + encodingName: 'ISAC', + clockRate: 16000, + channels: 1, + mediaType: 'audio', +}; + +const isac32 = { + payloadType: 104, + encodingName: 'ISAC', + clockRate: 32000, + channels: 1, + mediaType: 'audio', +}; + +const pcmu = { + payloadType: 0, + encodingName: 'PCMU', + clockRate: 8000, + channels: 1, + mediaType: 'audio', +}; + +const pcma = { + payloadType: 8, + encodingName: 'PCMA', + clockRate: 8000, + channels: 1, + mediaType: 'audio', +}; + +const cn8 = { + payloadType: 13, + encodingName: 'CN', + clockRate: 8000, + channels: 1, + mediaType: 'audio', +}; + +const cn16 = { + payloadType: 105, + encodingName: 'CN', + clockRate: 16000, + channels: 1, + mediaType: 'audio', +}; + +const cn32 = { + payloadType: 106, + encodingName: 'CN', + clockRate: 32000, + channels: 1, + mediaType: 'audio', +}; + +const cn48 = { + payloadType: 107, + encodingName: 'CN', + clockRate: 48000, + channels: 1, + mediaType: 'audio', +}; + +const telephoneevent = { + payloadType: 126, + encodingName: 'telephone-event', + clockRate: 8000, + channels: 1, + mediaType: 'audio', +}; + +mediaConfig.codecConfigurations = { + default: { rtpMappings: { vp8, opus }, extMappings }, + VP8_AND_OPUS: { rtpMappings: { vp8, opus }, extMappings }, + VP9_AND_OPUS: { rtpMappings: { vp9, opus }, extMappings }, + H264_AND_OPUS: { rtpMappings: { h264, opus }, extMappings }, }; var module = module || {}; diff --git a/test/runSpineTest.sh b/test/runSpineTest.sh index 77aa47492e..9dcbd82378 100755 --- a/test/runSpineTest.sh +++ b/test/runSpineTest.sh @@ -9,8 +9,6 @@ NVM_CHECK="$ROOT"/scripts/checkNvm.sh . $NVM_CHECK -export LD_LIBRARY_PATH=/opt/licode/erizo/build/erizo - cd $ROOT/spine node runSpineClients -s ../results/config_${TESTPREFIX}_${TESTID}.json -t $DURATION -i 10 -o $ROOT/results/output_${TESTPREFIX}_${TESTID}.json > /dev/null 2>&1 exit 0