when far end answers with only pcma, passthrough instead of transcoding to pcmu (#208)

This commit is contained in:
Dave Horton
2025-11-24 11:23:16 -06:00
committed by GitHub
parent 178105acd7
commit 5fab8a7515
3 changed files with 61 additions and 4 deletions

1
.gitignore vendored
View File

@@ -36,3 +36,4 @@ node_modules
.DS_Store
examples/*
CLAUDE.md

View File

@@ -9,7 +9,8 @@ const {
makeFullMediaReleaseKey,
makePartnerFullMediaReleaseKey,
isValidDomainOrIP,
removeVideoSdp
removeVideoSdp,
determineAnswerCodec
} = require('./utils');
const { MediaPath } = require('./constants.json');
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
@@ -99,8 +100,9 @@ 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-${codec}`);
opts.flags.push('codec-accept-telephone-event');
}
} catch {}
@@ -554,12 +556,16 @@ class CallSession extends Emitter {
localSdpB: response.sdp,
localSdpA: async(sdp, res) => {
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 = {
...this.rtpEngineOpts.common,
...this.rtpEngineOpts.uas.mediaOpts,
'from-tag': this.rtpEngineOpts.uas.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
};
const response = await this.answer(opts);

View File

@@ -349,6 +349,55 @@ const removeVideoSdp = (sdp) => {
parsedSdp.media = parsedSdp.media.filter((media) => media.type !== 'video');
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 = {
makeRtpEngineOpts,
selectHostPort,
@@ -364,5 +413,6 @@ module.exports = {
makeFullMediaReleaseKey,
makePartnerFullMediaReleaseKey,
isValidDomainOrIP,
removeVideoSdp
removeVideoSdp,
determineAnswerCodec
};