Skip to content

Instantly share code, notes, and snippets.

@vpalmisano
Last active June 9, 2023 08:26
Show Gist options
  • Save vpalmisano/3129450f2474ff32620dc0fc7b467534 to your computer and use it in GitHub Desktop.
Save vpalmisano/3129450f2474ff32620dc0fc7b467534 to your computer and use it in GitHub Desktop.
#
# 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