mirror of
https://github.com/jambonz/sbc-outbound.git
synced 2025-12-19 04:27:45 +00:00
when far end answers with only pcma, passthrough instead of transcoding to pcmu (#208)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -36,3 +36,4 @@ node_modules
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
examples/*
|
examples/*
|
||||||
|
CLAUDE.md
|
||||||
@@ -9,7 +9,8 @@ const {
|
|||||||
makeFullMediaReleaseKey,
|
makeFullMediaReleaseKey,
|
||||||
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');
|
||||||
@@ -99,8 +100,9 @@ const updateRtpEngineFlags = (sdp, opts) => {
|
|||||||
try {
|
try {
|
||||||
const parsed = sdpTransform.parse(sdp);
|
const parsed = sdpTransform.parse(sdp);
|
||||||
const codec = parsed.media[0].rtp[0].codec;
|
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)) {
|
if (['PCMU', 'PCMA'].includes(codec)) {
|
||||||
opts.flags.push(`codec-accept-${codec}`);
|
|
||||||
opts.flags.push('codec-accept-telephone-event');
|
opts.flags.push('codec-accept-telephone-event');
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
@@ -554,12 +556,16 @@ class CallSession extends Emitter {
|
|||||||
localSdpB: response.sdp,
|
localSdpB: response.sdp,
|
||||||
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'],
|
flags: ['single codec', 'inject DTMF', `codec-accept-${codec}`, 'codec-accept-telephone-event'],
|
||||||
sdp
|
sdp
|
||||||
};
|
};
|
||||||
const response = await this.answer(opts);
|
const response = await this.answer(opts);
|
||||||
|
|||||||
52
lib/utils.js
52
lib/utils.js
@@ -349,6 +349,55 @@ const removeVideoSdp = (sdp) => {
|
|||||||
parsedSdp.media = parsedSdp.media.filter((media) => media.type !== 'video');
|
parsedSdp.media = parsedSdp.media.filter((media) => media.type !== 'video');
|
||||||
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,
|
||||||
@@ -364,5 +413,6 @@ module.exports = {
|
|||||||
makeFullMediaReleaseKey,
|
makeFullMediaReleaseKey,
|
||||||
makePartnerFullMediaReleaseKey,
|
makePartnerFullMediaReleaseKey,
|
||||||
isValidDomainOrIP,
|
isValidDomainOrIP,
|
||||||
removeVideoSdp
|
removeVideoSdp,
|
||||||
|
determineAnswerCodec
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user