Anthony:

This patch (as discussed on IRC) refactors the RTP Packet object and the parser in a way that I like.

It separates the PT (Payload Type) byte from the Content Type object. It makes parsing a stateless function instead of a parser object. It makes all values of an RTP packet passed as ctor args instead of poked into place after construction. It handles up to one RTP extension header. It puts the RTP header stuff into a separate object, which is convenient if you have a function that needs to get the netbytes for the header separately from the netbytes for the payload (which I have).

This patch also refactors the "send an RTP packet" functionality of RTP Protocol. There is a nice unified "_send_packet()" and a nice "_send_cn_packet()".

I've been using this patch on my proprietary branch since last October. Shtoom passes unit tests after application of this patch. I made a test call with Shtoom after application of this patch and it worked.

Further patches will be forthcoming which depend on and complement this patch.

Please apply.

Regards,

Zooko

The patch is inlined *and* attached:

P.S. If you don't like nested classes, move Header outside of Packet, or mention it and I'll do so.

Index: shtoom/test/test_playout.py
===================================================================
--- shtoom/test/test_playout.py (revision 1157)
+++ shtoom/test/test_playout.py (working copy)
@@ -68,8 +68,7 @@
                 ts = 0
                 seq = 10017
                 while True:
-                    p = RTPPacket(data='', pt=None, ts=ts)
-                    p.seq = seq
+                    p = RTPPacket(0, seq, ts, '')
                     yield p
                     ts += 160
                     seq += 1
Index: shtoom/test/test_codecs.py
===================================================================
--- shtoom/test/test_codecs.py  (revision 1157)
+++ shtoom/test/test_codecs.py  (working copy)
@@ -56,10 +56,10 @@
         ae = self.assertEquals
         ae(p.encode('frobozulate'), 'frobozulate')
         ae(p.decode('frobozulate'), 'frobozulate')
-        p = RTPPacket(PT_RAW, 'farnarkling', ts=None)
+        p = RTPPacket(0, 0, 0, 'farnarkling', ct=PT_RAW)
         ae(c.decode(p), 'farnarkling')
         ae(c.encode('farnarkling').data, 'farnarkling')
-        ae(c.encode('farnarkling').pt, PT_RAW)
+        ae(c.encode('farnarkling').header.pt, PT_RAW.pt)

     # XXX testing other codecs - endianness issues? crap.

@@ -72,7 +72,7 @@
         ae(c.getDefaultFormat(), PT_PCMU)
         ae(len(c.encode(instr).data), 160)
         ae(c.encode(instr).data, ulawout)
-        ae(c.encode(instr).pt, PT_PCMU)
+        ae(c.encode(instr).header.ct, PT_PCMU)

     def testGSMCodec(self):
         if codecs.gsm is None:
Index: shtoom/test/test_rtp.py
===================================================================
--- shtoom/test/test_rtp.py     (revision 1157)
+++ shtoom/test/test_rtp.py     (working copy)
@@ -45,33 +45,29 @@
             ae(pt, RTPDict[(pt.name.lower(),pt.clock,pt.params or 1)])

     def testRTPPacketRoundTrip(self):
-        from shtoom.rtp.packets import RTPParser, RTPPacket
+        from shtoom.rtp.packets import RTPPacket
+        from shtoom.rtp.protocol import parse_rtppacket
         from shtoom.rtp.formats import PT_PCMU, PT_SPEEX, PT_GSM, PT_CN
         ae = self.assertEquals

- ptdict = {}
- for pt, byte in ((PT_PCMU, 0),(PT_GSM,3),(PT_SPEEX,101),(PT_CN,13)):
- ptdict[pt] = byte
- ptdict[byte] = pt
- parser = RTPParser(ptdict)
ts = 12345678
seq = 12345
ssrc = 100001
packets = [
- RTPPacket(PT_PCMU, ''.join([chr(x) for x in range(160)]), 0),
- RTPPacket(PT_GSM, '\0'*33, 0, 1),
- RTPPacket(PT_GSM, '\0'*33, 0, 0),
- RTPPacket(PT_CN, chr(127), 0, 0),
- RTPPacket(PT_CN, chr(127), 0, 1),
+ RTPPacket(ssrc, seq, ts, ''.join([chr(x) for x in range(160)]), 0, ct=PT_PCMU),
+ RTPPacket(ssrc, seq, ts, '\0'*33, 1, ct=PT_GSM),
+ RTPPacket(ssrc, seq, ts, '\0'*33, 0, ct=PT_GSM),
+ RTPPacket(ssrc, seq, ts, chr(127), 0, ct=PT_CN),
+ RTPPacket(ssrc, seq, ts, chr(127), 1, ct=PT_CN),
]
for pack in packets:
- rpack = parser.fromnet(parser.tonet(pack, seq, ts, ssrc), None)
- ae(pack.pt, rpack.pt)
- ae(pack.marker, rpack.marker)
+ rpack = parse_rtppacket(pack.netbytes())
+ ae(pack.header.pt, rpack.header.pt)
+ ae(pack.header.marker, rpack.header.marker)
ae(pack.data, rpack.data)
- ae(rpack.seq, seq)
- ae(rpack.ts, ts)
- ae(rpack.ssrc, ssrc)
+ ae(rpack.header.seq, seq)
+ ae(rpack.header.ts, ts)
+ ae(rpack.header.ssrc, ssrc)


     def testSDPGen(self):
         from shtoom.rtp.formats import SDPGenerator, PTMarker
Index: shtoom/stun.py
===================================================================
--- shtoom/stun.py      (revision 1157)
+++ shtoom/stun.py      (working copy)
@@ -16,7 +16,7 @@
 from shtoom.defcache import DeferredCache
 from shtoom.nat import BaseMapper

-STUNVERBOSE = True
+STUNVERBOSE = False
 # If we're going to follow RFC recommendation, make this 7
 MAX_RETRANSMIT = 5

@@ -397,7 +397,10 @@
else:
if STUNVERBOSE:
print "giving up on %r"%(address,)
- del self._potentialStuns[tid]
+ if hasattr(self, '_potentialStuns') and self._potentialStuns.has_key(tid):
+ del self._potentialStuns[tid]
+ else:
+ print "xxxxx what's going on here? getattr(self, '_potentialStuns'): %s, val: %s" % (getattr(self, '_potentialStuns'), getattr(self, '_potentialStuns', {}).get(tid),)
if not self._potentialStuns:
if STUNVERBOSE:
print "stun state 1 timeout - no internet UDP possible"
Index: shtoom/app/phone.py
===================================================================
--- shtoom/app/phone.py (revision 1157)
+++ shtoom/app/phone.py (working copy)
@@ -35,7 +35,7 @@
self._currentCall = None
self._muted = False
self._rtpProtocolClass = None
- self._debugrev = 10
+ self._debugrev = 17


     def needsThreadedUI(self):
         return self.ui.threadedUI
Index: shtoom/audio/converters.py
===================================================================
--- shtoom/audio/converters.py  (revision 1157)
+++ shtoom/audio/converters.py  (working copy)
@@ -202,15 +202,15 @@
         encaudio = codec.encode(bytes)
         if not encaudio:
             return None
-        return RTPPacket(self.format, encaudio, ts=None)
+        return RTPPacket(0, 0, 0, encaudio, ct=self.format)

     def decode(self, packet):
         "Accepts an RTPPacket, emits audio as bytes"
         if not packet.data:
             return None
-        codec = self.codecs.get(packet.pt)
+        codec = self.codecs.get(packet.header.ct)
         if not codec:
-            raise ValueError("can't decode format %r"%packet.pt)
+            raise ValueError("can't decode format %r"%packet.header.ct)
         encaudio = codec.decode(packet.data)
         return encaudio

Index: shtoom/audio/playout.py
===================================================================
--- shtoom/audio/playout.py (revision 1157)
+++ shtoom/audio/playout.py (working copy)
@@ -61,22 +61,22 @@
raise ValueError("playout got %s instead of bytes"%(type(bytes)))


if self.expect_seq == None:
- self.expect_seq = packet.seq # First packet. Initialize seq
+ self.expect_seq = packet.header.seq # First packet. Initialize seq


         backlog = len(self.queue)
-        if packet.seq == self.expect_seq:
-            self.expect_seq = packet.seq+1
+        if packet.header.seq == self.expect_seq:
+            self.expect_seq = packet.header.seq+1
             if backlog < self.backlog+1:
                 self.queue.append(bytes)
             elif DEBUG:
                 log.msg("BacklogPlayout discarding")
         else:
-            offset = packet.seq-self.expect_seq
+            offset = packet.header.seq-self.expect_seq
             if offset > 0 and offset < 3:
                 # Fill with empty packets
                 self.queue += [None]*offset
                 self.queue.append(bytes)
-                self.expect_seq = packet.seq+1
+                self.expect_seq = packet.header.seq+1
                 if DEBUG:
                     log.msg("BacklogPlayout got hole at %d"%offset)
             elif offset < 0 and offset > -backlog:
Index: shtoom/rtp/packets.py
===================================================================
--- shtoom/rtp/packets.py       (revision 1157)
+++ shtoom/rtp/packets.py       (working copy)
@@ -6,75 +6,80 @@
 import struct
 from time import sleep, time

+# This class supports extension headers, but only one per packet.
class RTPPacket:
- "An RTPPacket contains RTP data to/from the RTP object"
- def __init__(self, pt, data, ts, marker=0):
- self.pt = pt
- self.data = data
- self.ts = ts
- self.marker = marker
- self.ssrc = None
- self.seq = None
+ """ Contains RTP data. """
+ class Header:
+ def __init__(self, ssrc, pt, ct, seq, ts, marker=0, xhdrtype=None, xhdrdata=''):
+ """
+ If xhdrtype is not None then it is required to be an int >= 0 and < 2**16 and xhdrdata is required to be a string.
+ """
+ assert isinstance(ts, (int, long,)), "ts: %s :: %s" % (ts, type(ts),)
+ assert isinstance(ssrc, (int, long,))
+ assert xhdrtype is None or isinstance(xhdrtype, int) and xhdrtype >= 0 and xhdrtype < 2**16
+ assert xhdrtype is None or isinstance(xhdrdata, str)


- def __repr__(self):
- return "<RTPPacket containing %r at %x>"%(self.pt, id(self))
+ self.ssrc, self.pt, self.ct, self.seq, self.ts, self.marker, self.xhdrtype, self.xhdrdata = ssrc, pt, ct, seq, ts, marker, xhdrtype, xhdrdata


+ def netbytes(self):
+ "Return network-formatted header."
+ assert isinstance(self.pt, int) and self.pt >= 0 and self.pt < 2**8, "pt is required to be a simple byte, suitable for stuffing into an RTP packet and sending. pt: %s" % self.pt
+ if self.xhdrtype is not None:
+ firstbyte = 0x90
+ xhdrnetbytes = struct.pack('!HH', self.xhdrtype, len(self.xhdrdata)) + self.xhdrdata
+ else:
+ firstbyte = 0x80
+ xhdrnetbytes = ''
+ return struct.pack('!BBHII', firstbyte, self.pt | self.marker << 7, self.seq % 2**16, self.ts, self.ssrc) + xhdrnetbytes


-class RTPParser:
- """ An RTPParser creates RTPPacket objects from a bytestring. It is
- created with a mapping of RTP PT bytes to PT markers"""
- def __init__(self, ptdict):
- self.ptdict = ptdict
+ def __init__(self, ssrc, seq, ts, data, pt=None, ct=None, marker=0, authtag='', xhdrtype=None, xhdrdata=''):
+ assert pt is None or isinstance(pt, int) and pt >= 0 and pt < 2**8, "pt is required to be a simple byte, suitable for stuffing into an RTP packet and sending. pt: %s" % pt
+ self.header = RTPPacket.Header(ssrc, pt, ct, seq, ts, marker, xhdrtype, xhdrdata)
+ self.data = data
+ self.authtag = authtag # please leave this alone even if it appears unused -- it is required for SRTP


-    def tonet(self, packet, seq, ts, ssrc):
-        "Return network formatted packet"
-        pt = packet.pt
-        # XXX handle cc and x!
-        byte0 = 0x80
-        fmt = self.ptdict[pt] & 127
-        if packet.marker: fmt = fmt | 128
-        hdr = struct.pack('!BBHII', byte0, fmt, seq, ts, ssrc)
-        # Padding?
-        return hdr + packet.data
+    def __repr__(self):
+        if self.header.ct is not None:
+            ptrepr = "%r" % (self.header.ct,)
+        else:
+            ptrepr = "pt %d" % (self.header.pt,)

- # XXX Note that the MediaLayer will create it's own RTPPackets - this is
- # purely for packets coming off the network.
- def fromnet(self, bytes, fromaddr):
- hdr = struct.unpack('!BBHII', bytes[:12])
- padding = hdr[0]&32 and 1 or 0
- cc = hdr[0]&15 and 1 or 0
- x = hdr[0]&16 and 1 or 0
- pt = hdr[1]&127
- if not self.ptdict.get(pt):
- # Could be any old garbage, such as a late STUN packet
- return None
- pt = self.ptdict[pt]
- marker = hdr[1]&128 and 1 or 0
- seq = hdr[2]
- ts = hdr[3]
- ssrc = hdr[4]
- headerlen = 12
- if cc > 1:
- headerlen = headerlen + 4 * cc
- data = bytes[headerlen:]
- if x:
- # Mmm. Tasty tasty header extensions. We eats them.
- xhdrtype,xhdrlen = struct.unpack('!HH', data[:4])
- data = data[4+4*xhdrlen:]
- if padding:
- padcount = ord(data[-1])
- if padcount:
- data = data[:-padcount]
- packet = RTPPacket(pt, data, ts, marker)
- packet.ssrc = ssrc
- packet.seq = seq
- return packet
+ if self.header.xhdrtype is not None:
+ return "<%s #%d (%s) %s [%s] at %x>"%(self.__class__.__name__, self.header.seq, self.header.xhdrtype, ptrepr, repr(self.header.xhdrdata), id(self))
+ else:
+ return "<%s #%d %s at %x>"%(self.__class__.__name__, self.header.seq, ptrepr, id(self))


-    def haspt(self, key):
-        return self.ptdict.has_key(key)
+    def netbytes(self):
+        "Return network-formatted packet."
+        return self.header.netbytes() + self.data + self.authtag

+def parse_rtppacket(bytes):
+ hdr = struct.unpack('!BBHII', bytes[:12])
+ padding = hdr[0]&32 and 1 or 0
+ cc = hdr[0]&15 and 1 or 0
+ x = hdr[0]&16 and 1 or 0
+ pt = hdr[1]&127
+ marker = hdr[1]&128 and 1 or 0
+ seq = hdr[2]
+ ts = hdr[3]
+ ssrc = hdr[4]
+ headerlen = 12
+ if cc > 1:
+ headerlen = headerlen + 4 * cc
+ data = bytes[headerlen:]
+ if x:
+ # Mmm. Tasty tasty header extensions. We eats them. Except for the first one.
+ xhdrtype,xhdrlen = struct.unpack('!HH', data[:4])
+ xhdrdata = data[4:4+4*xhdrlen]
+ data = data[4+4*xhdrlen:]
+ else:
+ xhdrtype, xhdrdata = None, None
+ if padding:
+ padcount = ord(data[-1])
+ if padcount:
+ data = data[:-padcount]
+ return RTPPacket(ssrc, seq, ts, data, marker=marker, pt=pt, xhdrtype=xhdrtype, xhdrdata=xhdrdata)


-
class NTE:
"An object representing an RTP NTE (rfc2833)"
# XXX at some point, this should be hooked into the RTPPacketFactory.
Index: shtoom/rtp/protocol.py
===================================================================
--- shtoom/rtp/protocol.py (revision 1157)
+++ shtoom/rtp/protocol.py (working copy)
@@ -12,7 +12,7 @@
from twisted.python import log


 from shtoom.rtp.formats import SDPGenerator, PT_CN, PT_xCN
-from shtoom.rtp.packets import RTPPacket, RTPParser
+from shtoom.rtp.packets import RTPPacket, parse_rtppacket

 TWO_TO_THE_16TH = 2<<16
 TWO_TO_THE_32ND = 2<<32
@@ -46,14 +46,12 @@

     def setSDP(self, sdp):
         "This is the canonical SDP for the call"
-        from shtoom.rtp.packets import RTPParser
         self.app.selectDefaultFormat(self.cookie, sdp)
         rtpmap = sdp.getMediaDescription('audio').rtpmap
-        ptdict = {}
+        self.ptdict = {}
         for pt, (text, marker) in rtpmap.items():
-            ptdict[pt] = marker
-            ptdict[marker] = pt
-        self.rtpParser = RTPParser(ptdict)
+            self.ptdict[pt] = marker
+            self.ptdict[marker] = pt

     def createRTPSocket(self, locIP, needSTUN=False):
         """ Start listening on UDP ports for RTP and RTCP.
@@ -226,6 +224,26 @@
         d.addCallback(lambda x: self.rtpListener.stopListening())
         d.addCallback(lambda x: self.rtcpListener.stopListening())

+ def _send_packet(self, pt, data, xhdrtype=None, xhdrdata=''):
+ packet = RTPPacket(self.ssrc, self.seq, self.ts, data, pt=pt, xhdrtype=xhdrtype, xhdrdata=xhdrdata)
+
+ self.seq += 1
+ self.transport.write(packet.netbytes(), self.dest)
+
+ def _send_cn_packet(self):
+ # PT 13 is CN.
+ if self.ptdict.has_key(PT_CN):
+ cnpt = PT_CN.pt
+ elif self.ptdict.has_key(PT_xCN):
+ cnpt = PT_xCN.pt
+ else:
+ # We need to send SOMETHING!?!
+ cnpt = 0
+
+ log.msg("sending CN(%s) to seed firewall to %s:%d"%(cnpt, self.dest[0], self.dest[1]), system='rtp')
+
+ self._send_packet(cnpt, chr(127))
+
def startSendingAndReceiving(self, dest, fp=None):
self.dest = dest
self.prevInTime = self.prevOutTime = time()
@@ -248,32 +266,14 @@
self.LC.start(0.020)
# Now send a single CN packet to seed any firewalls that might
# need an outbound packet to let the inbound back.
- # PT 13 is CN.
- if self.rtpParser.haspt(PT_CN):
- cnpt = PT_CN.pt
- elif self.rtpParser.haspt(PT_xCN):
- cnpt = PT_xCN.pt
- else:
- cnpt = None
- if cnpt:
- log.msg("sending CN(%s) to seed firewall to %s:%d"%(cnpt,
- self.dest[0], self.dest[1]), system='rtp')
- hdr = struct.pack('!BBHII', 0x80, cnpt, self.seq, self.ts,self.ssrc)
- self.transport.write(hdr+chr(0), self.dest)
- else:
- log.msg("hack - seeding firewall with PT_PCMU")
- hdr = struct.pack('!BBHII', 0x80, 0, self.seq, self.ts,self.ssrc)
- self.transport.write(hdr+(160*chr(0)), self.dest)
- # We need to send SOMETHING!?!
+ self._send_cn_packet()
if hasattr(self.transport, 'connect'):
self.transport.connect(*self.dest)


def datagramReceived(self, datagram, addr):
# XXX check for late STUN packets here. see, e.g datagramReceived in sip.py
- if self.rtpParser is None:
- log.msg("early(?) rtp packet, no rtpParser available")
- return
- packet = self.rtpParser.fromnet(datagram, addr)
+ packet = parse_rtppacket(datagram)
+ packet.header.ct = self.ptdict[packet.header.pt]
if packet:
self.app.receiveRTP(self.cookie, packet)


@@ -340,26 +340,19 @@
# when we go from silent->talking, set the marker bit. Other end
# can use this as an excuse to adjust playout buffer.
if self.sample is not None:
- packet = self.sample
+ pt = self.ptdict[self.sample.header.ct]
+ self._send_packet(pt, self.sample.data)
self.sent += 1
- data = self.rtpParser.tonet(packet, self.seq, self.ts, self.ssrc)
- self.transport.write(data, self.dest)
self.sample = None
- else:
- if (self.packets - self.sent) % 100 == 0:
- if self.rtpParser.haspt(PT_CN):
- cn = RTPPacket(PT_CN, chr(127), 0)
- cn = self.rtpParser.tonet(cn, self.seq, self.ts, self.ssrc)
- self.transport.write(cn, self.dest)
- self.seq += 1
+ elif (self.packets - self.sent) % 100 == 0:
+ self._send_cn_packet()
+
# Now send any pending DTMF keystrokes
if self._pendingDTMF:
payload = self._pendingDTMF[0].getPayload(self.ts)
if payload:
# XXX Hack. telephone-event isn't always 101!
- hdr = struct.pack('!BBHII', 0x80, 101, self.seq, self.ts, self.ssrc)
- self.transport.write(hdr+payload, self.dest)
- self.seq += 1
+ self._send_packet(pt=101, data=payload)
if self._pendingDTMF[0].isDone():
self._pendingDTMF = self._pendingDTMF[1:]
try:


Index: shtoom/test/test_playout.py
===================================================================
--- shtoom/test/test_playout.py (revision 1157)
+++ shtoom/test/test_playout.py (working copy)
@@ -68,8 +68,7 @@
                 ts = 0
                 seq = 10017
                 while True:
-                    p = RTPPacket(data='', pt=None, ts=ts)
-                    p.seq = seq
+                    p = RTPPacket(0, seq, ts, '')
                     yield p
                     ts += 160
                     seq += 1
Index: shtoom/test/test_codecs.py
===================================================================
--- shtoom/test/test_codecs.py  (revision 1157)
+++ shtoom/test/test_codecs.py  (working copy)
@@ -56,10 +56,10 @@
         ae = self.assertEquals
         ae(p.encode('frobozulate'), 'frobozulate')
         ae(p.decode('frobozulate'), 'frobozulate')
-        p = RTPPacket(PT_RAW, 'farnarkling', ts=None)
+        p = RTPPacket(0, 0, 0, 'farnarkling', ct=PT_RAW)
         ae(c.decode(p), 'farnarkling')
         ae(c.encode('farnarkling').data, 'farnarkling')
-        ae(c.encode('farnarkling').pt, PT_RAW)
+        ae(c.encode('farnarkling').header.pt, PT_RAW.pt)
 
     # XXX testing other codecs - endianness issues? crap.
 
@@ -72,7 +72,7 @@
         ae(c.getDefaultFormat(), PT_PCMU)
         ae(len(c.encode(instr).data), 160)
         ae(c.encode(instr).data, ulawout)
-        ae(c.encode(instr).pt, PT_PCMU)
+        ae(c.encode(instr).header.ct, PT_PCMU)
 
     def testGSMCodec(self):
         if codecs.gsm is None:
Index: shtoom/test/test_rtp.py
===================================================================
--- shtoom/test/test_rtp.py     (revision 1157)
+++ shtoom/test/test_rtp.py     (working copy)
@@ -45,33 +45,29 @@
             ae(pt, RTPDict[(pt.name.lower(),pt.clock,pt.params or 1)])
 
     def testRTPPacketRoundTrip(self):
-        from shtoom.rtp.packets import RTPParser, RTPPacket
+        from shtoom.rtp.packets import RTPPacket
+        from shtoom.rtp.protocol import parse_rtppacket
         from shtoom.rtp.formats import PT_PCMU, PT_SPEEX, PT_GSM, PT_CN
         ae = self.assertEquals
 
-        ptdict = {}
-        for pt, byte in ((PT_PCMU, 0),(PT_GSM,3),(PT_SPEEX,101),(PT_CN,13)):
-            ptdict[pt] = byte
-            ptdict[byte] = pt
-        parser = RTPParser(ptdict)
         ts = 12345678
         seq = 12345
         ssrc = 100001
         packets = [
-            RTPPacket(PT_PCMU, ''.join([chr(x) for x in range(160)]), 0),
-            RTPPacket(PT_GSM, '\0'*33, 0, 1),
-            RTPPacket(PT_GSM, '\0'*33, 0, 0),
-            RTPPacket(PT_CN, chr(127), 0, 0),
-            RTPPacket(PT_CN, chr(127), 0, 1),
+            RTPPacket(ssrc, seq, ts, ''.join([chr(x) for x in range(160)]), 0, 
ct=PT_PCMU),
+            RTPPacket(ssrc, seq, ts, '\0'*33, 1, ct=PT_GSM),
+            RTPPacket(ssrc, seq, ts, '\0'*33, 0, ct=PT_GSM),
+            RTPPacket(ssrc, seq, ts, chr(127), 0, ct=PT_CN),
+            RTPPacket(ssrc, seq, ts, chr(127), 1, ct=PT_CN),
                   ]
         for pack in packets:
-            rpack = parser.fromnet(parser.tonet(pack, seq, ts, ssrc), None)
-            ae(pack.pt, rpack.pt)
-            ae(pack.marker, rpack.marker)
+            rpack = parse_rtppacket(pack.netbytes())
+            ae(pack.header.pt, rpack.header.pt)
+            ae(pack.header.marker, rpack.header.marker)
             ae(pack.data, rpack.data)
-            ae(rpack.seq, seq)
-            ae(rpack.ts, ts)
-            ae(rpack.ssrc, ssrc)
+            ae(rpack.header.seq, seq)
+            ae(rpack.header.ts, ts)
+            ae(rpack.header.ssrc, ssrc)
 
     def testSDPGen(self):
         from shtoom.rtp.formats import SDPGenerator, PTMarker
Index: shtoom/stun.py
===================================================================
--- shtoom/stun.py      (revision 1157)
+++ shtoom/stun.py      (working copy)
@@ -16,7 +16,7 @@
 from shtoom.defcache import DeferredCache
 from shtoom.nat import BaseMapper
 
-STUNVERBOSE = True
+STUNVERBOSE = False
 # If we're going to follow RFC recommendation, make this 7
 MAX_RETRANSMIT = 5
 
@@ -397,7 +397,10 @@
         else:
             if STUNVERBOSE:
                 print "giving up on %r"%(address,)
-            del self._potentialStuns[tid]
+            if hasattr(self, '_potentialStuns') and 
self._potentialStuns.has_key(tid):
+                del self._potentialStuns[tid]
+            else:
+                print "xxxxx what's going on here?  getattr(self, 
'_potentialStuns'): %s, val: %s" % (getattr(self, '_potentialStuns'), 
getattr(self, '_potentialStuns', {}).get(tid),)
             if not self._potentialStuns:
                 if STUNVERBOSE:
                     print "stun state 1 timeout - no internet UDP possible"
Index: shtoom/app/phone.py
===================================================================
--- shtoom/app/phone.py (revision 1157)
+++ shtoom/app/phone.py (working copy)
@@ -35,7 +35,7 @@
         self._currentCall = None
         self._muted = False
         self._rtpProtocolClass = None
-        self._debugrev = 10
+        self._debugrev = 17
 
     def needsThreadedUI(self):
         return self.ui.threadedUI
Index: shtoom/audio/converters.py
===================================================================
--- shtoom/audio/converters.py  (revision 1157)
+++ shtoom/audio/converters.py  (working copy)
@@ -202,15 +202,15 @@
         encaudio = codec.encode(bytes)
         if not encaudio:
             return None
-        return RTPPacket(self.format, encaudio, ts=None)
+        return RTPPacket(0, 0, 0, encaudio, ct=self.format)
 
     def decode(self, packet):
         "Accepts an RTPPacket, emits audio as bytes"
         if not packet.data:
             return None
-        codec = self.codecs.get(packet.pt)
+        codec = self.codecs.get(packet.header.ct)
         if not codec:
-            raise ValueError("can't decode format %r"%packet.pt)
+            raise ValueError("can't decode format %r"%packet.header.ct)
         encaudio = codec.decode(packet.data)
         return encaudio
 
Index: shtoom/audio/playout.py
===================================================================
--- shtoom/audio/playout.py     (revision 1157)
+++ shtoom/audio/playout.py     (working copy)
@@ -61,22 +61,22 @@
             raise ValueError("playout got %s instead of bytes"%(type(bytes)))
 
         if self.expect_seq == None:
-            self.expect_seq = packet.seq # First packet. Initialize seq
+            self.expect_seq = packet.header.seq # First packet. Initialize seq
 
         backlog = len(self.queue)
-        if packet.seq == self.expect_seq:
-            self.expect_seq = packet.seq+1
+        if packet.header.seq == self.expect_seq:
+            self.expect_seq = packet.header.seq+1
             if backlog < self.backlog+1:
                 self.queue.append(bytes)
             elif DEBUG:
                 log.msg("BacklogPlayout discarding")
         else:
-            offset = packet.seq-self.expect_seq
+            offset = packet.header.seq-self.expect_seq
             if offset > 0 and offset < 3:
                 # Fill with empty packets
                 self.queue += [None]*offset
                 self.queue.append(bytes)
-                self.expect_seq = packet.seq+1
+                self.expect_seq = packet.header.seq+1
                 if DEBUG:
                     log.msg("BacklogPlayout got hole at %d"%offset)
             elif offset < 0 and offset > -backlog:
Index: shtoom/rtp/packets.py
===================================================================
--- shtoom/rtp/packets.py       (revision 1157)
+++ shtoom/rtp/packets.py       (working copy)
@@ -6,75 +6,80 @@
 import struct
 from time import sleep, time
 
+# This class supports extension headers, but only one per packet.
 class RTPPacket:
-    "An RTPPacket contains RTP data to/from the RTP object"
-    def __init__(self, pt, data, ts, marker=0):
-        self.pt = pt
-        self.data = data
-        self.ts = ts
-        self.marker = marker
-        self.ssrc = None
-        self.seq = None
+    """ Contains RTP data. """
+    class Header:
+        def __init__(self, ssrc, pt, ct, seq, ts, marker=0, xhdrtype=None, 
xhdrdata=''):
+            """
+            If xhdrtype is not None then it is required to be an int >= 0 and 
< 2**16 and xhdrdata is required to be a string.
+            """
+            assert isinstance(ts, (int, long,)), "ts: %s :: %s" % (ts, 
type(ts),)
+            assert isinstance(ssrc, (int, long,))
+            assert xhdrtype is None or isinstance(xhdrtype, int) and xhdrtype 
>= 0 and xhdrtype < 2**16
+            assert xhdrtype is None or isinstance(xhdrdata, str)
 
-    def __repr__(self):
-        return "<RTPPacket containing %r at %x>"%(self.pt, id(self))
+            self.ssrc, self.pt, self.ct, self.seq, self.ts, self.marker, 
self.xhdrtype, self.xhdrdata = ssrc, pt, ct, seq, ts, marker, xhdrtype, xhdrdata
 
+        def netbytes(self):
+            "Return network-formatted header."
+            assert isinstance(self.pt, int) and self.pt >= 0 and self.pt < 
2**8, "pt is required to be a simple byte, suitable for stuffing into an RTP 
packet and sending. pt: %s" % self.pt
+            if self.xhdrtype is not None:
+                firstbyte = 0x90
+                xhdrnetbytes = struct.pack('!HH', self.xhdrtype, 
len(self.xhdrdata)) + self.xhdrdata
+            else:
+                firstbyte = 0x80
+                xhdrnetbytes = ''
+            return struct.pack('!BBHII', firstbyte, self.pt | self.marker << 
7, self.seq % 2**16, self.ts, self.ssrc) + xhdrnetbytes
 
-class RTPParser:
-    """ An RTPParser creates RTPPacket objects from a bytestring. It is
-        created with a mapping of RTP PT bytes to PT markers"""
-    def __init__(self, ptdict):
-        self.ptdict = ptdict
+    def __init__(self, ssrc, seq, ts, data, pt=None, ct=None, marker=0, 
authtag='', xhdrtype=None, xhdrdata=''):
+        assert pt is None or isinstance(pt, int) and pt >= 0 and pt < 2**8, 
"pt is required to be a simple byte, suitable for stuffing into an RTP packet 
and sending. pt: %s" % pt
+        self.header = RTPPacket.Header(ssrc, pt, ct, seq, ts, marker, 
xhdrtype, xhdrdata)
+        self.data = data
+        self.authtag = authtag # please leave this alone even if it appears 
unused -- it is required for SRTP
 
-    def tonet(self, packet, seq, ts, ssrc):
-        "Return network formatted packet"
-        pt = packet.pt
-        # XXX handle cc and x!
-        byte0 = 0x80
-        fmt = self.ptdict[pt] & 127
-        if packet.marker: fmt = fmt | 128
-        hdr = struct.pack('!BBHII', byte0, fmt, seq, ts, ssrc)
-        # Padding?
-        return hdr + packet.data
+    def __repr__(self):
+        if self.header.ct is not None:
+            ptrepr = "%r" % (self.header.ct,)
+        else:
+            ptrepr = "pt %d" % (self.header.pt,)
 
-    # XXX Note that the MediaLayer will create it's own RTPPackets - this is
-    # purely for packets coming off the network.
-    def fromnet(self, bytes, fromaddr):
-        hdr = struct.unpack('!BBHII', bytes[:12])
-        padding = hdr[0]&32 and 1 or 0
-        cc = hdr[0]&15 and 1 or 0
-        x = hdr[0]&16 and 1 or 0
-        pt = hdr[1]&127
-        if not self.ptdict.get(pt):
-            # Could be any old garbage, such as a late STUN packet
-            return None
-        pt = self.ptdict[pt]
-        marker = hdr[1]&128 and 1 or 0
-        seq = hdr[2]
-        ts = hdr[3]
-        ssrc = hdr[4]
-        headerlen = 12
-        if cc > 1:
-            headerlen = headerlen + 4 * cc
-        data = bytes[headerlen:]
-        if x:
-            # Mmm. Tasty tasty header extensions. We eats them.
-            xhdrtype,xhdrlen = struct.unpack('!HH', data[:4])
-            data = data[4+4*xhdrlen:]
-        if padding:
-            padcount = ord(data[-1])
-            if padcount:
-                data = data[:-padcount]
-        packet = RTPPacket(pt, data, ts, marker)
-        packet.ssrc = ssrc
-        packet.seq = seq
-        return packet
+        if self.header.xhdrtype is not None:
+            return "<%s #%d (%s) %s [%s] at %x>"%(self.__class__.__name__, 
self.header.seq, self.header.xhdrtype, ptrepr, repr(self.header.xhdrdata), 
id(self))
+        else:
+            return "<%s #%d %s at %x>"%(self.__class__.__name__, 
self.header.seq, ptrepr, id(self))
 
-    def haspt(self, key):
-        return self.ptdict.has_key(key)
+    def netbytes(self):
+        "Return network-formatted packet."
+        return self.header.netbytes() + self.data + self.authtag
 
+def parse_rtppacket(bytes):
+    hdr = struct.unpack('!BBHII', bytes[:12])
+    padding = hdr[0]&32 and 1 or 0
+    cc = hdr[0]&15 and 1 or 0
+    x = hdr[0]&16 and 1 or 0
+    pt = hdr[1]&127
+    marker = hdr[1]&128 and 1 or 0
+    seq = hdr[2]
+    ts = hdr[3]
+    ssrc = hdr[4]
+    headerlen = 12
+    if cc > 1:
+        headerlen = headerlen + 4 * cc
+    data = bytes[headerlen:]
+    if x:
+        # Mmm. Tasty tasty header extensions. We eats them.  Except for the 
first one.
+        xhdrtype,xhdrlen = struct.unpack('!HH', data[:4])
+        xhdrdata = data[4:4+4*xhdrlen]
+        data = data[4+4*xhdrlen:]
+    else:
+        xhdrtype, xhdrdata = None, None
+    if padding:
+        padcount = ord(data[-1])
+        if padcount:
+            data = data[:-padcount]
+    return RTPPacket(ssrc, seq, ts, data, marker=marker, pt=pt, 
xhdrtype=xhdrtype, xhdrdata=xhdrdata)
 
-
 class NTE:
     "An object representing an RTP NTE (rfc2833)"
     # XXX at some point, this should be hooked into the RTPPacketFactory.
Index: shtoom/rtp/protocol.py
===================================================================
--- shtoom/rtp/protocol.py      (revision 1157)
+++ shtoom/rtp/protocol.py      (working copy)
@@ -12,7 +12,7 @@
 from twisted.python import log
 
 from shtoom.rtp.formats import SDPGenerator, PT_CN, PT_xCN
-from shtoom.rtp.packets import RTPPacket, RTPParser
+from shtoom.rtp.packets import RTPPacket, parse_rtppacket
 
 TWO_TO_THE_16TH = 2<<16
 TWO_TO_THE_32ND = 2<<32
@@ -46,14 +46,12 @@
 
     def setSDP(self, sdp):
         "This is the canonical SDP for the call"
-        from shtoom.rtp.packets import RTPParser
         self.app.selectDefaultFormat(self.cookie, sdp)
         rtpmap = sdp.getMediaDescription('audio').rtpmap
-        ptdict = {}
+        self.ptdict = {}
         for pt, (text, marker) in rtpmap.items():
-            ptdict[pt] = marker
-            ptdict[marker] = pt
-        self.rtpParser = RTPParser(ptdict)
+            self.ptdict[pt] = marker
+            self.ptdict[marker] = pt
 
     def createRTPSocket(self, locIP, needSTUN=False):
         """ Start listening on UDP ports for RTP and RTCP.
@@ -226,6 +224,26 @@
         d.addCallback(lambda x: self.rtpListener.stopListening())
         d.addCallback(lambda x: self.rtcpListener.stopListening())
 
+    def _send_packet(self, pt, data, xhdrtype=None, xhdrdata=''):
+        packet = RTPPacket(self.ssrc, self.seq, self.ts, data, pt=pt, 
xhdrtype=xhdrtype, xhdrdata=xhdrdata)
+
+        self.seq += 1
+        self.transport.write(packet.netbytes(), self.dest)
+
+    def _send_cn_packet(self):
+        # PT 13 is CN.
+        if self.ptdict.has_key(PT_CN):
+            cnpt = PT_CN.pt
+        elif self.ptdict.has_key(PT_xCN):
+            cnpt = PT_xCN.pt
+        else:
+            # We need to send SOMETHING!?!
+            cnpt = 0
+
+        log.msg("sending CN(%s) to seed firewall to %s:%d"%(cnpt, 
self.dest[0], self.dest[1]), system='rtp')
+
+        self._send_packet(cnpt, chr(127))
+
     def startSendingAndReceiving(self, dest, fp=None):
         self.dest = dest
         self.prevInTime = self.prevOutTime = time()
@@ -248,32 +266,14 @@
         self.LC.start(0.020)
         # Now send a single CN packet to seed any firewalls that might
         # need an outbound packet to let the inbound back.
-        # PT 13 is CN.
-        if self.rtpParser.haspt(PT_CN):
-            cnpt = PT_CN.pt
-        elif self.rtpParser.haspt(PT_xCN):
-            cnpt = PT_xCN.pt
-        else:
-            cnpt = None
-        if cnpt:
-            log.msg("sending CN(%s) to seed firewall to %s:%d"%(cnpt,
-                                    self.dest[0], self.dest[1]), system='rtp')
-            hdr = struct.pack('!BBHII', 0x80, cnpt, self.seq, 
self.ts,self.ssrc)
-            self.transport.write(hdr+chr(0), self.dest)
-        else:
-            log.msg("hack - seeding firewall with PT_PCMU")
-            hdr = struct.pack('!BBHII', 0x80, 0, self.seq, self.ts,self.ssrc)
-            self.transport.write(hdr+(160*chr(0)), self.dest)
-            # We need to send SOMETHING!?!
+        self._send_cn_packet()
         if hasattr(self.transport, 'connect'):
             self.transport.connect(*self.dest)
 
     def datagramReceived(self, datagram, addr):
         # XXX check for late STUN packets here. see, e.g datagramReceived in 
sip.py
-        if self.rtpParser is None:
-            log.msg("early(?) rtp packet, no rtpParser available")
-            return
-        packet = self.rtpParser.fromnet(datagram, addr)
+        packet = parse_rtppacket(datagram)
+        packet.header.ct = self.ptdict[packet.header.pt]
         if packet:
             self.app.receiveRTP(self.cookie, packet)
 
@@ -340,26 +340,19 @@
         # when we go from silent->talking, set the marker bit. Other end
         # can use this as an excuse to adjust playout buffer.
         if self.sample is not None:
-            packet = self.sample
+            pt = self.ptdict[self.sample.header.ct]
+            self._send_packet(pt, self.sample.data)
             self.sent += 1
-            data = self.rtpParser.tonet(packet, self.seq, self.ts, self.ssrc)
-            self.transport.write(data, self.dest)
             self.sample = None
-        else:
-            if (self.packets - self.sent) % 100 == 0:
-                if self.rtpParser.haspt(PT_CN):
-                    cn = RTPPacket(PT_CN, chr(127), 0)
-                    cn = self.rtpParser.tonet(cn, self.seq, self.ts, self.ssrc)
-                    self.transport.write(cn, self.dest)
-        self.seq += 1
+        elif (self.packets - self.sent) % 100 == 0:
+            self._send_cn_packet()
+
         # Now send any pending DTMF keystrokes
         if self._pendingDTMF:
             payload = self._pendingDTMF[0].getPayload(self.ts)
             if payload:
                 # XXX Hack. telephone-event isn't always 101!
-                hdr = struct.pack('!BBHII', 0x80, 101, self.seq, self.ts, 
self.ssrc)
-                self.transport.write(hdr+payload, self.dest)
-                self.seq += 1
+                self._send_packet(pt=101, data=payload)
                 if self._pendingDTMF[0].isDone():
                     self._pendingDTMF = self._pendingDTMF[1:]
         try:
_______________________________________________
Shtoom mailing list
[email protected]
http://mail.python.org/mailman/listinfo/shtoom

Reply via email to