parseSdpAudio function
Parse the remote audio endpoint from an SDP body. Returns null if the body has no audio media line we can use.
Implementation
SdpAudio? parseSdpAudio(String sdp) {
if (sdp.trim().isEmpty) return null;
String? sessionConn;
String? mediaConn;
String? mLine;
int? rtcpPort;
SdpDirection? direction;
int? telephoneEventPt;
// fmtp parameters keyed by payload type (e.g. {101: '0-16'}).
final fmtpByPt = <int, String>{};
// Track which payload types appear in m= and any rtpmap entries so we
// can pick PCMU (0) or PCMA (8) deterministically.
final ptInMedia = <int>[];
final rtpmapNames = <int, String>{};
var inAudio = false;
for (final raw in sdp.split('\n')) {
final line = raw.trim();
if (line.isEmpty) continue;
if (line.startsWith('c=') && !inAudio) {
sessionConn = line;
} else if (line.startsWith('m=')) {
inAudio = line.startsWith('m=audio');
if (inAudio) {
mLine = line;
// m=audio <port> RTP/AVP <pt> <pt> ...
final parts = line.split(RegExp(r'\s+'));
for (var i = 3; i < parts.length; i++) {
final pt = int.tryParse(parts[i]);
if (pt != null) ptInMedia.add(pt);
}
}
} else if (inAudio && line.startsWith('c=')) {
mediaConn = line;
} else if (inAudio && line.startsWith('a=rtpmap:')) {
// a=rtpmap:<pt> <name>/<rate>[/channels]
final rest = line.substring('a=rtpmap:'.length);
final sp = rest.indexOf(' ');
if (sp > 0) {
final pt = int.tryParse(rest.substring(0, sp));
if (pt != null) {
final name = rest.substring(sp + 1).split('/').first.toUpperCase();
rtpmapNames[pt] = name;
if (name == 'TELEPHONE-EVENT') telephoneEventPt = pt;
}
}
} else if (inAudio && line.startsWith('a=rtcp:')) {
// a=rtcp:<port> [<nettype> <addrtype> <addr>]
final rest = line.substring('a=rtcp:'.length).trim();
final tok = rest.split(RegExp(r'\s+')).first;
rtcpPort = int.tryParse(tok);
} else if (inAudio && line.startsWith('a=fmtp:')) {
// a=fmtp:<pt> <params>
final rest = line.substring('a=fmtp:'.length);
final sp = rest.indexOf(' ');
if (sp > 0) {
final pt = int.tryParse(rest.substring(0, sp));
if (pt != null) fmtpByPt[pt] = rest.substring(sp + 1).trim();
}
} else if (inAudio) {
switch (line) {
case 'a=sendrecv':
direction = SdpDirection.sendrecv;
break;
case 'a=sendonly':
direction = SdpDirection.sendonly;
break;
case 'a=recvonly':
direction = SdpDirection.recvonly;
break;
case 'a=inactive':
direction = SdpDirection.inactive;
break;
}
}
}
if (mLine == null) return null;
final mParts = mLine.split(RegExp(r'\s+'));
if (mParts.length < 4) return null;
final port = int.tryParse(mParts[1]);
if (port == null || port == 0) return null;
final connLine = mediaConn ?? sessionConn;
if (connLine == null) return null;
// c=IN IP4 <host>
final cParts = connLine.split(RegExp(r'\s+'));
if (cParts.length < 3) return null;
final host = cParts[2];
// Pick first PT we know about (prefer PCMU then PCMA).
G711Variant? chosen;
for (final pt in ptInMedia) {
final variant = G711Variant.fromPayloadType(pt);
if (variant != null) {
chosen = variant;
break;
}
}
// Fall back to rtpmap-by-name if the static PTs weren't 0/8.
if (chosen == null) {
for (final entry in rtpmapNames.entries) {
if (entry.value == 'PCMU') {
chosen = G711Variant.pcmu;
break;
}
if (entry.value == 'PCMA') {
chosen = G711Variant.pcma;
break;
}
}
}
if (chosen == null) return null;
return SdpAudio(
host: host,
port: port,
codec: chosen,
rtcpPort: rtcpPort,
direction: direction ?? SdpDirection.sendrecv,
telephoneEventPt: telephoneEventPt,
telephoneEventRange: telephoneEventPt == null
? null
: fmtpByPt[telephoneEventPt],
);
}