import socket # import re import bitstring # if you don't have this from your linux distro, install with "pip install bitstring" # ********* (2) The routine for handling the RTP stream *********** def digestpacket(st): """ This routine takes a UDP packet, i.e. a string of bytes and .. (a) strips off the RTP header (b) adds NAL "stamps" to the packets, so that they are recognized as NAL's (c) Concantenates frames (d) Returns a packet that can be written to disk as such and that is recognized by stock media players as h264 stream """ startbytes = "\x00\x00\x00\x01" # this is the sequence of four bytes that identifies a NAL packet.. must be in front of every NAL packet. 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. lc = 12 # bytecounter bc = 12 * 8 # bitcounter version = bt[0:2].uint # version p = bt[3] # P x = bt[4] # X cc = bt[4:8].uint # CC m = bt[9] # M pt = bt[9:16].uint # PT sn = bt[16:32].uint # sequence number timestamp = bt[32:64].uint # timestamp ssrc = bt[64:96].uint # ssrc identifier # The header format can be found from: # https://en.wikipedia.org/wiki/Real-time_Transport_Protocol lc = 12 # so, we have red twelve bytes bc = 12 * 8 # .. and that many bits print("version, p, x, cc, m, pt", version, p, x, cc, m, pt) print("sequence number, timestamp", sn, timestamp) print("sync. source identifier", ssrc) # st=f.read(4*cc) # csrc identifiers, 32 bits (4 bytes) each cids = [] for i in range(cc): cids.append(bt[bc:bc+32].uint) bc += 32 lc += 4 print("csrc identifiers:", cids) if (x): # this section haven't been tested.. might fail hid = bt[bc:bc+16].uint bc += 16 lc += 2 hlen = bt[bc:bc+16].uint bc += 16 lc += 2 print("ext. header id, header len", hid, hlen) # hst = bt[bc:bc+32*hlen] bc += 32 * hlen lc += 4 * hlen # OK, now we enter the NAL packet, as described here: # # https://tools.ietf.org/html/rfc6184#section-1.3 # # Some quotes from that document: # """ 5.3. NAL Unit Header Usage The structure and semantics of the NAL unit header were introduced in Section 1.3. For convenience, the format of the NAL unit header is reprinted below: +---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+-+ |F|NRI| Type | +---------------+ This section specifies the semantics of F and NRI according to this specification. """ """ Table 3. Summary of allowed NAL unit types for each packetization mode (yes = allowed, no = disallowed, ig = ignore) Payload Packet Single NAL Non-Interleaved Interleaved Type Type Unit Mode Mode Mode ------------------------------------------------------------- 0 reserved ig ig ig 1-23 NAL unit yes yes no 24 STAP-A no yes no 25 STAP-B no no yes 26 MTAP16 no no yes 27 MTAP24 no no yes 28 FU-A no yes yes 29 FU-B no no yes 30-31 reserved ig ig ig """ # This was also very usefull: # http://stackoverflow.com/questions/7665217/how-to-process-raw-udp-packets-so-that-they-can-be-decoded-by-a-decoder-filter-i # A quote from that: """ First byte: [ 3 NAL UNIT BITS | 5 FRAGMENT TYPE BITS] Second byte: [ START BIT | RESERVED BIT | END BIT | 5 NAL UNIT BITS] Other bytes: [... VIDEO FRAGMENT DATA...] """ fb = bt[bc] # i.e. "F" nri = bt[bc+1:bc+3].uint # "NRI" # nlu0 = bt[bc:bc+3] # "3 NAL UNIT BITS" (i.e. [F | NRI]) typ = bt[bc+3:bc+8].uint # "Type" print("F, NRI, Type :", fb, nri, typ) print("first three bits together :", bt[bc:bc+3]) if (typ == 7 or typ == 8): # this means we have either an SPS or a PPS packet # they have the meta-info about resolution, etc. # more reading for example here: # http://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set/ if (typ == 7): print(">>>>> SPS packet") else: print(">>>>> PPS packet") return startbytes+st[lc:] # .. notice here that we include the NAL starting sequence "startbytes" and the "First byte" bc += 8 lc += 1 # let's go to "Second byte" # ********* WE ARE AT THE "Second byte" ************ # The "Type" here is most likely 28, i.e. "FU-A" start = bt[bc] # start bit end = bt[bc+2] # end bit # nlu1 = bt[bc+3:bc+8] # 5 nal unit bits if (start): # OK, this is a first fragment in a movie frame print(">>> first fragment found") # nlu = nlu0 + nlu1 # Create "[3 NAL UNIT BITS | 5 NAL UNIT BITS]" # head = startbytes+nlu.bytes # .. add the NAL starting sequence lc += 1 # We skip the "Second byte" if (start is False and end is False): # intermediate fragment in a sequence, just dump "VIDEO FRAGMENT DATA" # head = "" lc += 1 # We skip the "Second byte" elif (end is True): # last fragment in a sequence, just dump "VIDEO FRAGMENT DATA" # head = "" print("<<<< last fragment found") lc += 1 # We skip the "Second byte" if (typ == 28): # This code only handles "Type" = 28, i.e. "FU-A" print("missing code here?") else: raise(Exception, "unknown frame type for this piece of s***") # *********** (3) THE MAIN PROGRAM STARTS HERE **************** s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # unknown name `clientports` # s1.bind(("", clientports[0])) # we open a port that is visible to the whole internet (the empty string "" takes care of that) s1.settimeout(5) # if the socket is dead for 5 s., its thrown into trash # further reading: # https://wiki.python.org/moin/UdpCommunication print("** STRIPPING RTP INFO AND DUMPING INTO FILE **") # unknown name `fname` # f = open(fname, 'w') # unoknown name rn # for i in range(rn): # print # print # recst = s1.recv(4096) # print("read", len(recst), "bytes") # st = digestpacket(recst) # print("dumping", len(st), "bytes") # f.write(st) # f.close() s1.close()