mirror of
https://github.com/jambonz/sbc-outbound.git
synced 2025-12-19 04:27:45 +00:00
include codec-accept on answer to rtpengine during reinvites (#209)
* include codec-accept on answer to rtpengine during reinvites * attempt to simplify * fixes from testing
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
const Emitter = require('events');
|
const Emitter = require('events');
|
||||||
const sdpTransform = require('sdp-transform');
|
|
||||||
const SrsClient = require('@jambonz/siprec-client-utils');
|
const SrsClient = require('@jambonz/siprec-client-utils');
|
||||||
const {
|
const {
|
||||||
makeRtpEngineOpts,
|
makeRtpEngineOpts,
|
||||||
@@ -10,7 +9,6 @@ const {
|
|||||||
makePartnerFullMediaReleaseKey,
|
makePartnerFullMediaReleaseKey,
|
||||||
isValidDomainOrIP,
|
isValidDomainOrIP,
|
||||||
removeVideoSdp,
|
removeVideoSdp,
|
||||||
determineAnswerCodec
|
|
||||||
} = require('./utils');
|
} = require('./utils');
|
||||||
const { MediaPath } = require('./constants.json');
|
const { MediaPath } = require('./constants.json');
|
||||||
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
||||||
@@ -96,19 +94,6 @@ const initCdr = (req, invite) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRtpEngineFlags = (sdp, opts) => {
|
|
||||||
try {
|
|
||||||
const parsed = sdpTransform.parse(sdp);
|
|
||||||
const codec = parsed.media[0].rtp[0].codec;
|
|
||||||
// Only add telephone-event support, don't restrict to specific G.711 codec yet
|
|
||||||
// This allows far end to choose between PCMU/PCMA freely
|
|
||||||
if (['PCMU', 'PCMA'].includes(codec)) {
|
|
||||||
opts.flags.push('codec-accept-telephone-event');
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
return opts;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CallSession extends Emitter {
|
class CallSession extends Emitter {
|
||||||
constructor(logger, req, res) {
|
constructor(logger, req, res) {
|
||||||
super();
|
super();
|
||||||
@@ -398,13 +383,13 @@ class CallSession extends Emitter {
|
|||||||
const toPublic = uris.some((u) => u.private_network === false);
|
const toPublic = uris.some((u) => u.private_network === false);
|
||||||
let isOfferUpdatedToPrivate = toPrivate && !toPublic;
|
let isOfferUpdatedToPrivate = toPrivate && !toPublic;
|
||||||
|
|
||||||
const opts = updateRtpEngineFlags(this.req.body, {
|
const opts = {
|
||||||
...this.rtpEngineOpts.common,
|
...this.rtpEngineOpts.common,
|
||||||
...this.rtpEngineOpts.uac.mediaOpts,
|
...this.rtpEngineOpts.uac.mediaOpts,
|
||||||
'from-tag': this.rtpEngineOpts.uas.tag,
|
'from-tag': this.rtpEngineOpts.uas.tag,
|
||||||
direction: ['private', toPublic ? 'public' : 'private'],
|
direction: ['private', toPublic ? 'public' : 'private'],
|
||||||
sdp: this.req.body
|
sdp: this.req.body
|
||||||
});
|
};
|
||||||
let response = await this.offer(opts);
|
let response = await this.offer(opts);
|
||||||
this.logger.debug({offer: opts, response}, 'initial offer to rtpengine');
|
this.logger.debug({offer: opts, response}, 'initial offer to rtpengine');
|
||||||
if ('ok' !== response.result) {
|
if ('ok' !== response.result) {
|
||||||
@@ -516,7 +501,7 @@ class CallSession extends Emitter {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
const p = proxy ? ` via ${proxy}` : '';
|
const p = proxy ? ` via ${proxy}` : '';
|
||||||
this.logger.info({uri, p}, `sending INVITE to ${uri}${p}`);
|
this.logger.info({uri, p}, `sending INVITE${p}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* now launch an outbound call attempt */
|
/* now launch an outbound call attempt */
|
||||||
@@ -557,15 +542,16 @@ class CallSession extends Emitter {
|
|||||||
localSdpA: async(sdp, res) => {
|
localSdpA: async(sdp, res) => {
|
||||||
this.rtpEngineOpts.uac.tag = res.getParsedHeader('To').params.tag;
|
this.rtpEngineOpts.uac.tag = res.getParsedHeader('To').params.tag;
|
||||||
|
|
||||||
// Determine which codec to use based on far end negotiation
|
|
||||||
const {codec} = determineAnswerCodec(sdp, this.req.body, this.logger);
|
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
...this.rtpEngineOpts.common,
|
...this.rtpEngineOpts.common,
|
||||||
...this.rtpEngineOpts.uas.mediaOpts,
|
...this.rtpEngineOpts.uas.mediaOpts,
|
||||||
'from-tag': this.rtpEngineOpts.uas.tag,
|
'from-tag': this.rtpEngineOpts.uas.tag,
|
||||||
'to-tag': this.rtpEngineOpts.uac.tag,
|
'to-tag': this.rtpEngineOpts.uac.tag,
|
||||||
flags: ['single codec', 'inject DTMF', `codec-accept-${codec}`, 'codec-accept-telephone-event'],
|
flags: [
|
||||||
|
'single codec',
|
||||||
|
'inject DTMF',
|
||||||
|
'reuse codecs',
|
||||||
|
],
|
||||||
sdp
|
sdp
|
||||||
};
|
};
|
||||||
const response = await this.answer(opts);
|
const response = await this.answer(opts);
|
||||||
|
|||||||
66
lib/utils.js
66
lib/utils.js
@@ -16,8 +16,8 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const srtpOpts = teams ? srtpCopy['teams'] : srtpCopy['default'];
|
const srtpOpts = teams ? srtpCopy['teams'] : srtpCopy['default'];
|
||||||
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCopy;
|
const dstOpts = JSON.parse(JSON.stringify(dstIsUsingSrtp ? srtpOpts : rtpCopy));
|
||||||
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCopy;
|
const srcOpts = JSON.parse(JSON.stringify(srcIsUsingSrtp ? srtpOpts : rtpCopy));
|
||||||
|
|
||||||
/** Allow feature server to send DTMF to the call excepts call from/to teams */
|
/** Allow feature server to send DTMF to the call excepts call from/to teams */
|
||||||
if (!teams) {
|
if (!teams) {
|
||||||
@@ -41,6 +41,16 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams
|
|||||||
'replace': ['origin', 'session-connection'],
|
'replace': ['origin', 'session-connection'],
|
||||||
'record call': process.env.JAMBONES_RECORD_ALL_CALLS ? 'yes' : 'no'
|
'record call': process.env.JAMBONES_RECORD_ALL_CALLS ? 'yes' : 'no'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const codec = {
|
||||||
|
accept: ['PCMU', 'PCMA', 'telephone-event'],
|
||||||
|
...(process.env.JAMBONES_CODEC_OFFER_WITH_ORDER &&
|
||||||
|
{
|
||||||
|
offer: process.env.JAMBONES_CODEC_OFFER_WITH_ORDER.split(','),
|
||||||
|
strip: 'all'
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
common,
|
common,
|
||||||
uas: {
|
uas: {
|
||||||
@@ -51,8 +61,7 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams
|
|||||||
tag: null,
|
tag: null,
|
||||||
mediaOpts: {
|
mediaOpts: {
|
||||||
...dstOpts,
|
...dstOpts,
|
||||||
...(process.env.JAMBONES_CODEC_OFFER_WITH_ORDER &&
|
codec,
|
||||||
{ codec: { offer: process.env.JAMBONES_CODEC_OFFER_WITH_ORDER.split(','), strip: 'all' } }),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -350,54 +359,6 @@ const removeVideoSdp = (sdp) => {
|
|||||||
return sdpTransform.write(parsedSdp);
|
return sdpTransform.write(parsedSdp);
|
||||||
};
|
};
|
||||||
|
|
||||||
const determineAnswerCodec = (farEndSdp, featureServerSdp, logger) => {
|
|
||||||
try {
|
|
||||||
// Parse both SDPs
|
|
||||||
const farEndParsed = sdpTransform.parse(farEndSdp);
|
|
||||||
const fsParsed = sdpTransform.parse(featureServerSdp);
|
|
||||||
|
|
||||||
// Get negotiated codec from far end (first codec in answer)
|
|
||||||
const negotiatedCodec = farEndParsed.media[0].rtp[0].codec;
|
|
||||||
|
|
||||||
// Get all codecs offered by feature server
|
|
||||||
const fsCodecs = fsParsed.media[0].rtp.map((r) => r.codec);
|
|
||||||
|
|
||||||
logger.debug({negotiatedCodec, fsCodecs}, 'determineAnswerCodec: analyzing codec negotiation');
|
|
||||||
|
|
||||||
// If far end negotiated G.711 (PCMU/PCMA) AND it was in the FS offer, pass it through
|
|
||||||
if (['PCMU', 'PCMA'].includes(negotiatedCodec) && fsCodecs.includes(negotiatedCodec)) {
|
|
||||||
logger.info({negotiatedCodec}, 'G.711 codec passthrough - no transcoding needed');
|
|
||||||
return {
|
|
||||||
codec: negotiatedCodec,
|
|
||||||
needsTranscoding: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we need to transcode to first G.711 codec in FS offer
|
|
||||||
const firstG711 = fsCodecs.find((c) => ['PCMU', 'PCMA'].includes(c));
|
|
||||||
if (firstG711) {
|
|
||||||
logger.info({negotiatedCodec, transcodeTarget: firstG711}, 'Transcoding required to G.711');
|
|
||||||
return {
|
|
||||||
codec: firstG711,
|
|
||||||
needsTranscoding: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: use PCMU
|
|
||||||
logger.info({negotiatedCodec}, 'No G.711 in FS offer, defaulting to PCMU');
|
|
||||||
return {
|
|
||||||
codec: 'PCMU',
|
|
||||||
needsTranscoding: true
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
logger.error({err}, 'Error determining answer codec, defaulting to PCMU');
|
|
||||||
return {
|
|
||||||
codec: 'PCMU',
|
|
||||||
needsTranscoding: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
makeRtpEngineOpts,
|
makeRtpEngineOpts,
|
||||||
selectHostPort,
|
selectHostPort,
|
||||||
@@ -414,5 +375,4 @@ module.exports = {
|
|||||||
makePartnerFullMediaReleaseKey,
|
makePartnerFullMediaReleaseKey,
|
||||||
isValidDomainOrIP,
|
isValidDomainOrIP,
|
||||||
removeVideoSdp,
|
removeVideoSdp,
|
||||||
determineAnswerCodec
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user