Last active
June 9, 2023 08:26
-
-
Save vpalmisano/3129450f2474ff32620dc0fc7b467534 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Utility functions for converting an sdp textual format to/from MediaSoup format | |
# | |
def sdpToMediasoup(text, config): | |
""" | |
Converts an SDP textual description used by GStreamer into the oRTP | |
format used by MediaSoup | |
:param text: The SDP text | |
:param config: The configuration to use: | |
{ | |
bitrate: Number # the maximum video bitrate | |
simulcast: false|true # if simulcast should be enabled | |
} | |
:return: the oRTP object | |
""" | |
mediasoupConfig = { | |
'dtlsParameters': { | |
'role': 'server', | |
'fingerprints': [{ | |
'algorithm': '', | |
'value': '' | |
}] | |
}, | |
} | |
section = {} | |
encoding = {} | |
encodingScale = 1 | |
text = text.split('\n') | |
for line in text: | |
line = line.strip() | |
if line.startswith('a=fingerprint:'): | |
algorithm, value = line.replace('a=fingerprint:', '').split() | |
mediasoupConfig['dtlsParameters']['fingerprints'][0]['algorithm'] = algorithm | |
mediasoupConfig['dtlsParameters']['fingerprints'][0]['value'] = value | |
elif line.startswith('m=video '): | |
# already found 1 video, add encoding | |
if mediasoupConfig.get('video'): | |
encodingScale = max(1, int(encodingScale / 2)) | |
encoding = { | |
'ssrc': '', | |
'active': True, | |
'maxBitrate': int(config['bitrate'] * 1000 / encodingScale), | |
'scaleResolutionDownBy': encodingScale, | |
'scalabilityMode': 'S1T1', | |
} | |
mediasoupConfig['video']['encodings'].append(encoding) | |
else: | |
mediasoupConfig['video'] = { | |
'kind': 'video', | |
# set some default values, overridden by a=rtpmap sections | |
'codecs': [{ | |
'name': 'VP8', | |
'mimeType': 'video/VP8', | |
'clockRate': 90000, | |
'payloadType': int(line.split(' ')[-1]), | |
'channels': 1, | |
'rtcpFeedback': [], | |
'parameters': {}, | |
}], | |
'headerExtensions': [], | |
'mid': None, | |
'encodings': [{ 'ssrc': '' }] | |
} | |
section = mediasoupConfig['video'] | |
encoding = mediasoupConfig['video']['encodings'][0] | |
# if simulcast, set the lower level encoding parameters | |
if config.get('simulcast'): | |
encodingScale = 4 | |
encoding.update({ | |
'active': True, | |
'maxBitrate': int(config['bitrate'] * 1000 / encodingScale), | |
'scaleResolutionDownBy': encodingScale, | |
'scalabilityMode': 'S1T1', | |
}) | |
elif line.startswith('m=audio '): | |
mediasoupConfig['audio'] = { | |
'kind': 'audio', | |
'codecs': [{ | |
'name': 'OPUS', | |
'mimeType': 'audio/OPUS', | |
'clockRate': 48000, | |
'payloadType': int(line.split(' ')[-1]), | |
'channels': 2, | |
'rtcpFeedback': [], | |
'parameters': { | |
'useinbandfec': 1, | |
'sprop-stereo': 1 | |
} | |
}], | |
'headerExtensions': [], | |
'encodings': [{ 'ssrc': '' }], | |
'mid': None, | |
} | |
section = mediasoupConfig['audio'] | |
encoding = mediasoupConfig['audio']['encodings'][0] | |
elif line.startswith('a=rtpmap'): | |
pt, codec = line.replace('a=rtpmap', '').split(' ') | |
if section['kind'] == 'video': | |
codec, clockRate = codec.split('/') | |
section['codecs'][0]['name'] = codec.upper() | |
section['codecs'][0]['mimeType'] = 'video/' + codec.upper() | |
section['codecs'][0]['clockRate'] = int(clockRate) | |
section['codecs'][0]['parameters'] = { | |
'vp8': None, | |
'vp9': None, | |
'h264': { | |
'packetization-mode': 1, | |
'profile-level-id': '42e01f', | |
'level-asymmetry-allowed': 1, | |
}, | |
'h265': None, | |
}[codec.lower()] | |
elif section['kind'] == 'audio': | |
codec, clockRate, channels = codec.split('/') | |
section['codecs'][0]['name'] = codec.upper() | |
section['codecs'][0]['mimeType'] = 'audio/' + codec.upper() | |
section['codecs'][0]['clockRate'] = int(clockRate) | |
section['codecs'][0]['channels'] = int(channels) | |
elif line.startswith('a=framerate:'): | |
section['framerate'] = int(line.replace('a=framerate:', '')) | |
elif line.startswith('a=ssrc:'): | |
ssrc = int(line.replace('a=ssrc:', '').split(' ')[0]) | |
encoding['ssrc'] = ssrc | |
elif line.startswith('a=mid:'): | |
section['mid'] = line.replace('a=mid:', '') | |
elif line.startswith('a=rtcp-fb:'): | |
pt, fb = line.replace('a=rtcp-fb:', '').split(' ', 1) | |
fb = fb.split(' ', 1) | |
type_ = fb[0] | |
parameter = len(fb) > 1 and fb[1] or '' | |
#try: | |
# parameter = int(parameter) | |
#except ValueError: | |
# pass | |
section['codecs'][0]['rtcpFeedback'].append({ | |
'type': type_, | |
'parameter': parameter | |
}) | |
elif line.startswith('a=extmap:'): | |
id, uri = line.replace('a=extmap:', '').split(' ') | |
section['codecs'][0]['headerExtensions'].append({ | |
'uri': uri, | |
'id': id | |
}) | |
#elif line.startswith('a=fmtp:'): | |
# pt, params = line.replace('a=fmtp:', '').split(' ') | |
# params = params.split(';') | |
# for param in params: | |
# name, value = param.split('=', 1) | |
# if section['kind'] == 'video' and name in ('packetization-mode', 'profile-level-id'): | |
# section['codecs'][0]['parameters'][name] = value | |
# elif section['kind'] == 'audio' and name in ('useinbandfec', 'sprop-stereo'): | |
# section['codecs'][0]['parameters'][name] = value | |
# | |
return mediasoupConfig | |
def mediasoupToSdp(config, transport, consumer=None): | |
""" | |
Converts a MediaSoup transport/consumer object descriptors into an SDP | |
format used by GStreamer | |
:param config: The configuration to use: | |
{ | |
simulcast: false|true # if simulcast should be enabled | |
kind: 'video'|'audio' # the media type | |
codec: 'vp8'|'vp9'|'opus'|'h264' # the media codec | |
pt: Number # the payload type | |
} | |
:param transport: The MediaSoup transport | |
:param consumer: The MediaSoup consumer | |
:return: the SDP string | |
""" | |
sdpConfig = dict( | |
iceLite = transport['iceParameters']['iceLite'] and 'a=ice-lite' or '', | |
ufrag = transport['iceParameters']['usernameFragment'], | |
pwd = transport['iceParameters']['password'], | |
fingerprint_algorithm = transport['dtlsParameters']['fingerprints'][-1]['algorithm'], | |
fingerprint_value = transport['dtlsParameters']['fingerprints'][-1]['value'], | |
mid = '0', | |
ssrc_lines = '', | |
candidates = '\n'.join('a=candidate:%(type)scandidate 1 %(protocol)s %(priority)d %(ip)s %(port)d typ %(type)s' %candidate | |
for candidate in transport['iceCandidates']) + '\na=end-of-candidates' | |
) | |
if consumer: | |
ssrc = consumer['rtpParameters']['encodings'][0]['ssrc'] | |
cname = consumer['rtpParameters']['rtcp']['cname'] | |
sdpConfig['mid'] = consumer['rtpParameters']['mid'] | |
sdpConfig['ssrc_lines'] = '''\ | |
a=ssrc:%(ssrc)s cname:%(cname)s | |
''' %dict(ssrc=ssrc, cname=cname) | |
# | |
sdp = '''\ | |
v=0 | |
o=mediasoup-client 10000 1 IN IP4 0.0.0.0 | |
s=- | |
t=0 0 | |
%(iceLite)s | |
a=fingerprint:%(fingerprint_algorithm)s %(fingerprint_value)s | |
a=msid-semantic: WMS * | |
''' %sdpConfig | |
# add audio | |
if config['kind'] == 'audio': | |
sdpConfig['audio_codec'] = config.get('codec', 'opus').lower() | |
sdp += '''\ | |
a=group:BUNDLE %(mid)s | |
m=audio 7 UDP/TLS/RTP/SAVPF 100 | |
c=IN IP4 127.0.0.1 | |
a=rtpmap:100 %(audio_codec)s/48000/2 | |
a=fmtp:100 useinbandfec=1;sprop-stereo=1 | |
a=setup:active | |
a=mid:%(mid)s | |
a=sendrecv | |
a=ice-ufrag:%(ufrag)s | |
a=ice-pwd:%(pwd)s | |
%(candidates)s | |
a=ice-options:renomination | |
a=rtcp-mux | |
a=rtcp-rsize | |
%(ssrc_lines)s | |
''' %sdpConfig | |
elif config['kind'] == 'video': | |
sdpConfig['video_codec'] = config.get('codec', 'vp8').upper() | |
sdpConfig['pt'] = config.get('pt', 101) | |
if config.get('simulcast'): | |
sdp += '''\ | |
a=group:BUNDLE video1 video2 video3 | |
m=video 7 UDP/TLS/RTP/SAVPF %(pt)d 106 | |
c=IN IP4 127.0.0.1 | |
a=rtpmap:%(pt)d %(video_codec)s/90000 | |
a=rtpmap:106 rtx/90000 | |
a=rtcp-fb:%(pt)d nack | |
a=rtcp-fb:%(pt)d nack pli | |
a=setup:active | |
a=mid:video1 | |
a=sendrecv | |
a=ice-ufrag:%(ufrag)s | |
a=ice-pwd:%(pwd)s | |
%(candidates)s | |
a=ice-options:renomination | |
a=rtcp-mux | |
a=rtcp-rsize | |
m=video 7 UDP/TLS/RTP/SAVPF %(pt)d 106 | |
c=IN IP4 127.0.0.1 | |
a=rtpmap:%(pt)d %(video_codec)s/90000 | |
a=rtpmap:106 rtx/90000 | |
a=rtcp-fb:%(pt)d nack | |
a=rtcp-fb:%(pt)d nack pli | |
a=setup:active | |
a=mid:video2 | |
a=sendrecv | |
a=ice-ufrag:%(ufrag)s | |
a=ice-pwd:%(pwd)s | |
%(candidates)s | |
a=ice-options:renomination | |
a=rtcp-mux | |
a=rtcp-rsize | |
m=video 7 UDP/TLS/RTP/SAVPF %(pt)d 106 | |
c=IN IP4 127.0.0.1 | |
a=rtpmap:%(pt)d %(video_codec)s/90000 | |
a=rtpmap:106 rtx/90000 | |
a=rtcp-fb:%(pt)d nack | |
a=rtcp-fb:%(pt)d nack pli | |
a=setup:active | |
a=mid:video3 | |
a=sendrecv | |
a=ice-ufrag:%(ufrag)s | |
a=ice-pwd:%(pwd)s | |
%(candidates)s | |
a=ice-options:renomination | |
a=rtcp-mux | |
a=rtcp-rsize | |
''' %sdpConfig | |
else: | |
sdp += '''\ | |
a=group:BUNDLE %(mid)s | |
m=video 7 UDP/TLS/RTP/SAVPF 101 106 | |
c=IN IP4 127.0.0.1 | |
a=rtpmap:101 %(video_codec)s/90000 | |
a=rtpmap:106 rtx/90000 | |
a=rtcp-fb:101 nack | |
a=rtcp-fb:101 nack pli | |
a=fmtp:106 apt=101 | |
a=setup:active | |
a=mid:%(mid)s | |
a=sendrecv | |
a=ice-ufrag:%(ufrag)s | |
a=ice-pwd:%(pwd)s | |
%(candidates)s | |
a=ice-options:renomination | |
a=rtcp-mux | |
a=rtcp-rsize | |
%(ssrc_lines)s | |
''' %sdpConfig | |
# | |
return sdp |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment