|
| 1 | +''' |
| 2 | +Inspired by post by Sampsa Riikonen here: |
| 3 | +https://stackoverflow.com/questions/28022432/receiving-rtp-packets-after-rtsp-setup |
| 4 | +
|
| 5 | +Written 2017 Mike Killian |
| 6 | +''' |
| 7 | + |
| 8 | +import re, socket, threading |
| 9 | +import bitstring # if you don't have this from your linux distro, install with "pip install bitstring" |
| 10 | + |
| 11 | +class RTPReceive(threading.Thread): |
| 12 | + ''' |
| 13 | + This will open a socket on the client ports sent in RTSP setup request and |
| 14 | + return data as its received to the callback function. |
| 15 | + ''' |
| 16 | + def __init__(self, client_ports, callback=None): |
| 17 | + threading.Thread.__init__(self) |
| 18 | + self._callback = callback or (lambda x: None) |
| 19 | + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| 20 | + self._sock.bind(("", client_ports[0])) # we open a port that is visible to the whole internet (the empty string "" takes care of that) |
| 21 | + self._sock.settimeout(5) # if the socket is dead for 5 s., its thrown into trash |
| 22 | + self.closed = False |
| 23 | + self.frame = b'' |
| 24 | + self.frame_done = True #Did we get the last packet to fill a whole frame? |
| 25 | + self.running = False |
| 26 | + self.sprop-parameter-sets = 'Z0IAIJWoFAHmQA==,aM48gA==' |
| 27 | + self.start() |
| 28 | + |
| 29 | + def run(self): |
| 30 | + self.running = True |
| 31 | + try: |
| 32 | + while self.running: |
| 33 | + #self.frame = msg = self.recv_msg() |
| 34 | + msg = self._sock.recv(2048) |
| 35 | + framelet = self.digestpacket(msg) |
| 36 | + if framelet: |
| 37 | + self.frame += framelet |
| 38 | + if self.frame and self.frame_done: |
| 39 | + self._callback(self.frame) |
| 40 | + self.frame = b'' |
| 41 | + except Exception as e: |
| 42 | + raise Exception('Run time error: %s' % e) |
| 43 | + self.running = False |
| 44 | + self.close() |
| 45 | + |
| 46 | + def close(self): |
| 47 | + self.closed = True |
| 48 | + self.running = False |
| 49 | + self._sock.close() |
| 50 | + |
| 51 | + def insert_config_info(self, parameters): |
| 52 | + pass |
| 53 | + |
| 54 | + # ********* (2) The routine for handling the RTP stream *********** |
| 55 | + |
| 56 | + def digestpacket(self, st): |
| 57 | + """ This routine takes a UDP packet, i.e. a string of bytes and .. |
| 58 | + (a) strips off the RTP header |
| 59 | + (b) adds NAL "stamps" to the packets, so that they are recognized as NAL's |
| 60 | + (c) Concantenates frames |
| 61 | + (d) Returns a packet that can be written to disk as such and that is recognized by stock media players as h264 stream |
| 62 | + """ |
| 63 | + startbytes = b"\x00\x00\x00\x01" # this is the sequence of four bytes that identifies a NAL packet.. must be in front of every NAL packet. |
| 64 | + |
| 65 | + bt = bitstring.BitArray(bytes=st) # turn the whole string-of-bytes packet into a string of bits. Very unefficient, but hey, this is only for demoing. |
| 66 | + lc = 12 # bytecounter |
| 67 | + bc = 12*8 # bitcounter |
| 68 | + |
| 69 | + version = bt[0:2].uint # Version |
| 70 | + p = bt[3] # Padding |
| 71 | + x = bt[4] # Extension |
| 72 | + cc = bt[4:8].uint # CSRC Count |
| 73 | + m = bt[9] # Marker |
| 74 | + pt = bt[9:16].uint # Payload Type |
| 75 | + sn = bt[16:32].uint # Sequence number |
| 76 | + timestamp = bt[32:64].uint # Timestamp |
| 77 | + ssrc = bt[64:96].uint # ssrc identifier |
| 78 | + # The header format can be found from: |
| 79 | + # https://en.wikipedia.org/wiki/Real-time_Transport_Protocol |
| 80 | + |
| 81 | + lc = 12 # so, we have red twelve bytes |
| 82 | + bc = 12*8 # .. and that many bits |
| 83 | + |
| 84 | + if p: |
| 85 | + #TODO: Deal with padding here |
| 86 | + print("\n****\nPadding alert!!\n****\n") |
| 87 | + |
| 88 | + print("*----* Packet Begin *----* (Len: {})".format(len(st))) |
| 89 | + print("Ver: {}, P: {}, X: {}, CC: {}, M: {}, PT: {}".format(version,p,x,cc,m,pt)) |
| 90 | + print("Sequence number: {}, Timestamp: {}".format(sn,timestamp)) |
| 91 | + print("Sync. Source Identifier: {}".format(ssrc)) |
| 92 | + |
| 93 | + # st=f.read(4*cc) # csrc identifiers, 32 bits (4 bytes) each |
| 94 | + cids = [] |
| 95 | + for i in range(cc): |
| 96 | + cids.append(bt[bc:bc+32].uint) |
| 97 | + bc += 32 |
| 98 | + lc += 4 |
| 99 | + if cids: print("CSRC Identifiers: {}".format(cids)) |
| 100 | + |
| 101 | + if (x): |
| 102 | + # this section haven't been tested.. might fail |
| 103 | + hid = bt[bc:bc+16].uint |
| 104 | + bc += 16 |
| 105 | + lc += 2 |
| 106 | + |
| 107 | + hlen = bt[bc:bc+16].uint |
| 108 | + bc += 16 |
| 109 | + lc += 2 |
| 110 | + |
| 111 | + hst = bt[bc:bc+32*hlen] |
| 112 | + bc += 32*hlen |
| 113 | + lc += 4*hlen |
| 114 | + |
| 115 | + print("*----* Extension Header *----*") |
| 116 | + print("Ext. Header id: {}, Header len: {}".format(hid,hlen)) |
| 117 | + |
| 118 | + # OK, now we enter the NAL packet, as described here: |
| 119 | + # |
| 120 | + # https://tools.ietf.org/html/rfc6184#section-1.3 |
| 121 | + # |
| 122 | + # Some quotes from that document: |
| 123 | + # |
| 124 | + """ |
| 125 | + 5.3. NAL Unit Header Usage |
| 126 | +
|
| 127 | +
|
| 128 | + The structure and semantics of the NAL unit header were introduced in |
| 129 | + Section 1.3. For convenience, the format of the NAL unit header is |
| 130 | + reprinted below: |
| 131 | +
|
| 132 | + +---------------+ |
| 133 | + |0|1|2|3|4|5|6|7| |
| 134 | + +-+-+-+-+-+-+-+-+ |
| 135 | + |F|NRI| Type | |
| 136 | + +---------------+ |
| 137 | +
|
| 138 | + This section specifies the semantics of F and NRI according to this |
| 139 | + specification. |
| 140 | +
|
| 141 | + """ |
| 142 | + """ |
| 143 | + Table 3. Summary of allowed NAL unit types for each packetization |
| 144 | + mode (yes = allowed, no = disallowed, ig = ignore) |
| 145 | +
|
| 146 | + Payload Packet Single NAL Non-Interleaved Interleaved |
| 147 | + Type Type Unit Mode Mode Mode |
| 148 | + ------------------------------------------------------------- |
| 149 | + 0 reserved ig ig ig |
| 150 | + 1-23 NAL unit yes yes no |
| 151 | + 24 STAP-A no yes no |
| 152 | + 25 STAP-B no no yes |
| 153 | + 26 MTAP16 no no yes |
| 154 | + 27 MTAP24 no no yes |
| 155 | + 28 FU-A no yes yes |
| 156 | + 29 FU-B no no yes |
| 157 | + 30-31 reserved ig ig ig |
| 158 | + """ |
| 159 | + # This was also very usefull: |
| 160 | + # http://stackoverflow.com/questions/7665217/how-to-process-raw-udp-packets-so-that-they-can-be-decoded-by-a-decoder-filter-i |
| 161 | + # A quote from that: |
| 162 | + """ |
| 163 | + First byte: [ 3 NAL UNIT BITS | 5 FRAGMENT TYPE BITS] |
| 164 | + Second byte: [ START BIT | RESERVED BIT | END BIT | 5 NAL UNIT BITS] |
| 165 | + Other bytes: [... VIDEO FRAGMENT DATA...] |
| 166 | + """ |
| 167 | + |
| 168 | + fb = bt[bc] # i.e. "F" |
| 169 | + nri = bt[bc+1:bc+3].uint # "NRI" |
| 170 | + nlu0 = bt[bc:bc+3] # "3 NAL UNIT BITS" (i.e. [F | NRI]) |
| 171 | + typ = bt[bc+3:bc+8].uint # "Type" |
| 172 | + print(" *-* NAL Header *-*") |
| 173 | + print("F: {}, NRI: {}, Type: {}".format(fb, nri, typ)) |
| 174 | + print("First three bits together : {}".format(bt[bc:bc+3])) |
| 175 | + |
| 176 | + if (typ==7 or typ==8): |
| 177 | + # this means we have either an SPS or a PPS packet |
| 178 | + # they have the meta-info about resolution, etc. |
| 179 | + # more reading for example here: |
| 180 | + # http://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set/ |
| 181 | + if (typ==7): |
| 182 | + print(">>>>> SPS packet") |
| 183 | + else: |
| 184 | + print(">>>>> PPS packet") |
| 185 | + return startbytes+st[lc:] |
| 186 | + # .. notice here that we include the NAL starting sequence "startbytes" and the "First byte" |
| 187 | + |
| 188 | + bc += 8; |
| 189 | + lc += 1; # let's go to "Second byte" |
| 190 | + # ********* WE ARE AT THE "Second byte" ************ |
| 191 | + # The "Type" here is most likely 28, i.e. "FU-A" |
| 192 | + start = bt[bc] # start bit |
| 193 | + end = bt[bc+2] # end bit |
| 194 | + nlu1 = bt[bc+3:bc+8] # 5 nal unit bits |
| 195 | + head = b"" |
| 196 | + |
| 197 | + if (self.frame_done and start): # OK, this is a first fragment in a movie frame |
| 198 | + print(">>> first fragment found") |
| 199 | + self.frame_done = False |
| 200 | + nlu = nlu0+nlu1 # Create "[3 NAL UNIT BITS | 5 NAL UNIT BITS]" |
| 201 | + print(" >>> NLU0: {}, NLU1: {}, NLU: {}".format(nlu0,nlu1,nlu)) |
| 202 | + head = startbytes+nlu.bytes # .. add the NAL starting sequence |
| 203 | + lc += 1 # We skip the "Second byte" |
| 204 | + elif (self.frame_done==False and start==False and end==False): # intermediate fragment in a sequence, just dump "VIDEO FRAGMENT DATA" |
| 205 | + lc += 1 # We skip the "Second byte" |
| 206 | + elif (self.frame_done==False and end==True): # last fragment in a sequence, just dump "VIDEO FRAGMENT DATA" |
| 207 | + print("<<<< last fragment found") |
| 208 | + self.frame_done = True |
| 209 | + lc += 1 # We skip the "Second byte" |
| 210 | + |
| 211 | + if (typ==28): # This code only handles "Type" = 28, i.e. "FU-A" |
| 212 | + return head+st[lc:] |
| 213 | + else: |
| 214 | + #raise Exception("unknown frame type for this piece of s***") |
| 215 | + return None |
0 commit comments