# Copyright (c) 2007, Kundan Singh. All rights reserved. See LICENSING for details.
# implements only for unicast ''' The SDP offer/answer model for unicast sessions. Suppose the offerer wants to support PCMU and PCMA audio and H261 video, it can use the following code to generate the offer SDP. from std.rfc4566 import SDP, attrs as format from std.rfc3264 import createOffer, createAnswer >>> audio = SDP.media(media='audio', port='9000') >>> audio.fmt = [format(pt=0, name='PCMU', rate=8000), format(pt=8, name='PCMA', rate=8000)] >>> video = SDP.media(media='video', port='9002') >>> video.fmt = [format(pt=31, name='H261', rate=90000)] >>> offer = createOffer([audio, video]) >>> >>> offer.o.sessionid = offer.o.version = 1192000146 # so that testing doesn't depend on time >>> offer.o.host = '192.168.1.66' # or IP address >>> print str(offer).replace('\\r', '\\\\r').replace('\\n', '\\\\n') v=0\\r\\no=- 1192000146 1192000146 IN IP4 192.168.1.66\\r\\ns=-\\r\\nt=0 0\\r\\nm=audio 9000 RTP/AVP 0 8\\r\\na=rtpmap:0 PCMU/8000\\r\\na=rtpmap:8 PCMA/8000\\r\\nm=video 9002 RTP/AVP 31\\r\\na=rtpmap:31 H261/90000\\r\\n When the offer is received by the answerer, it can use the following code to generate the answer. Suppose the answerer wants to support PCMU and GSM audio and no video. from std.rfc4566 import SDP, attrs as format from std.rfc3264 import createAnswer >>> audio = SDP.media(media='audio', port='8020') >>> audio.fmt = [format(pt=0), format(pt=3)] # for known payload types, description is optional >>> answer = createAnswer([audio], offer) >>> >>> answer.o.sessionid = answer.o.version = 1192000146 >>> answer.o.host = '192.168.1.66' >>> print str(answer).replace('\\r', '\\\\r').replace('\\n', '\\\\n') v=0\\r\\no=- 1192000146 1192000146 IN IP4 192.168.1.66\\r\\ns=-\\r\\nt=0 0\\r\\nm=audio 8020 RTP/AVP 0\\r\\na=rtpmap:0 PCMU/8000\\r\\nm=video 0 RTP/AVP 31\\r\\na=rtpmap:31 H261/90000\\r\\n Suppose the offerer wants to change the offer (e.g., using SIP re-INVITE) by removing video from the offer; it should reuse the previous offer as follows: newOffer = createOffer([audio], offer) '''
This document defines a mechanism by which two entities can make use of the Session Description Protocol (SDP) to arrive at a common view of a multimedia session between them. In the model, one participant offers the other a description of the desired session from their perspective, and the other participant answers with the desired session from their perspective. This offer/answer model is most useful in unicast sessions where information from both participants is needed for the complete view of the session. The offer/answer model is used by protocols like the Session Initiation Protocol (SIP).
The means by which the offers and answers are conveyed are outside the scope of this document. The offer/answer model defined here is the mandatory baseline mechanism used by the Session Initiation Protocol (SIP) [7].
from std.rfc4566 import SDP, attrs as format # although RFC 3264 used old RFC 2327 for SDP definition, we use new RFC 4566 import socket _debug = True
Media Stream: From RTSP [8], a media stream is a single media instance, e.g., an audio stream or a video stream as well as a single whiteboard or shared application group. In SDP, a media stream is described by an "m=" line and its associated attributes.
# A media stream is implemented by SDP.media class of std.rfc4566 module
5 Generating the Initial Offer The offer (and answer) MUST be a valid SDP message, as defined by RFC 2327 [1], with one exception. RFC 2327 mandates that either an e or a p line is present in the SDP message. This specification relaxes that constraint; an SDP formulated for an offer/answer application MAY omit both the e and p lines. The numeric value of the session id and version in the o line MUST be representable with a 64 bit signed integer. The initial value of the version MUST be less than (2**62)-1, to avoid rollovers. Although the SDP specification allows for multiple session descriptions to be concatenated together into a large SDP message, an SDP message used in the offer/answer model MUST contain exactly one session description. The SDP "s=" line conveys the subject of the session, which is reasonably defined for multicast, but ill defined for unicast. For unicast sessions, it is RECOMMENDED that it consist of a single space character (0x20) or a dash (-). Unfortunately, SDP does not allow the "s=" line to be empty. The SDP "t=" line conveys the time of the session. Generally, streams for unicast sessions are created and destroyed through external signaling means, such as SIP. In that case, the "t=" line SHOULD have a value of "0 0". The offer will contain zero or more media streams (each media stream is described by an "m=" line and its associated attributes). Zero media streams implies that the offerer wishes to communicate, but that the streams for the session will be added at a later time through a modified offer. The streams MAY be for a mix of unicast and multicast; the latter obviously implies a multicast address in the relevant "c=" line(s). Construction of each offered stream depends on whether the stream is multicast or unicast.
def createOffer(streams, previous=None, **kwargs): '''Create an offer SDP using local (streams) list of media Stream objects. If a previous offer/answer SDP is specified then it creates a modified offer. Additionally, the optional keyword arguments such as e and p can be specified.''' s = SDP() s.v = '0' for a in "iep": # add optioanl e and p headers if present if a in kwargs: s[a] = kwargs[a] s.o = SDP.originator(previous and str(previous.o) or None) if previous: s.o.version = s.o.version + 1 s.s = '-' s.t = ['0 0'] # because t= can appear multiple times, it is a list. s.m = streams return s def createAnswer(streams, offer, **kwargs): '''Create an answer SDP for the remote offer SDP using local (streams) list of media Stream objects.''' s = SDP() s.v = '0' for a in "iep": if a in kwargs: s[a] = kwargs[a] s.o = SDP.originator() s.s = '-' s.t = offer.t s.m = [] streams = list(streams) # so that original stream is not modified for your in offer.m: # for each m= line in offer my = None # answered stream for i in range(0, len(streams)): if streams[i].media == your.media: # match the first stream in streams my = SDP.media(str(streams[i])) # found, hence del streams[i] # remove from streams so that we don't match again for another m= found = [] for fy in your.fmt: # all offered formats, find the matching pairs for fm in my.fmt:# the preference order is from offer, hence do for fy, then for fm. try: fmpt, fypt = int(fm.pt), int(fy.pt) # try using numeric payload type except: fmpt = fypt = -1 if 0<=fmpt<32 and 0<=fypt<32 and fmpt == fypt \ or fmpt<0 and fypt<0 and fm.pt == fy.pt \ or fm.name == fy.name and fm.rate == fy.rate and fm.count == fy.count: # we don't match the params found.append((fy, fm)); break if found: # we found some matching formats, put them in my.fmt = map(lambda x: x[0], found) # use remote's fy including fy.pt else: my.fmt = [format(pt=0)] # no match in formats, but matched media, must put a format with payload type 0 my.port = 0 # and reset the port. if not my: # did not match the stream, must put a stream with port = 0 my = SDP.media(str(your)) my.port = 0 s.m.append(my) # append it to our media valid = False for my in s.m: # check if any valid matching stream is present with valid port if my.port != 0: valid = True break return valid and s or None # if no valid matching stream found, return None if __name__ == '__main__': import doctest doctest.testmod()