python rtsp

发布时间:2019-09-20 07:36:53编辑:auto阅读(2090)

    # -*- coding: utf-8 -*-
    """
    A demo python code that ..
    1) Connects to an IP cam with RTSP
    2) Draws RTP/NAL/H264 packets from the camera
    3) Writes them to a file that can be read with any stock video player (say, mplayer, vlc & other ffmpeg based video-players)
    Done for educative/demonstrative purposes, not for efficiency..!
    written 2015 by Sampsa Riikonen.
    """
    import socket
    import re
    import bitstring # if you don't have this from your linux distro, install with "pip install bitstring"
    # ************************ FOR QUICK-TESTING EDIT THIS AREA *********************************************************
    ip="192.168.1.74" # IP address of your cam
    port=1616
    adr="rtsp://admin:12345@192.168.1.74" # username, passwd, etc.
    clientports=[60784,60785] # the client ports we are going to use for receiving video
    fname="stream.h264" # filename for dumping the stream
    rn=5000 # receive this many packets
    # After running this program, you can try your file defined in fname with "vlc fname" or "mplayer fname" from the command line
    # you might also want to install h264bitstream to analyze your h264 file
    # *******************************************************************************************************************
    dest="DESCRIBE "+adr+" RTSP/1.0\r\nCSeq: 2\r\nUser-Agent: python\r\nAccept: application/sdp\r\n\r\n"
    setu="SETUP "+adr+"/trackID=1 RTSP/1.0\r\nCSeq: 3\r\nUser-Agent: python\r\nTransport: RTP/AVP;unicast;client_port="+str(clientports[0])+"-"+str(clientports[1])+"\r\n\r\n"
    play="PLAY "+adr+" RTSP/1.0\r\nCSeq: 5\r\nUser-Agent: python\r\nSession: SESID\r\nRange: npt=0.000-\r\n\r\n"
    # File organized as follows:
    # 1) Strings manipulation routines
    # 2) RTP stream handling routine
    # 3) Main program
    # *** (1) First, some string searching/manipulation for handling the rtsp strings ***
    def getPorts(searchst,st):
          """ Searching port numbers from rtsp strings using regular expressions
          """
          pat=re.compile(searchst+"=\d*-\d*")
          pat2=re.compile('\d+')
          mstring=pat.findall(st)[0] # matched string .. "client_port=1000-1001"
          nums=pat2.findall(mstring)
          numas=[]
          for num in nums:
            numas.append(int(num))
          return numas
    def getLength(st):
          """ Searching "content-length" from rtsp strings using regular expressions
          """
          pat=re.compile("Content-Length: \d*")
          pat2=re.compile('\d+')
          mstring=pat.findall(st)[0] # matched string.. "Content-Length: 614"
          num=int(pat2.findall(mstring)[0])
          return num
    def printrec(recst):
          """ Pretty-printing rtsp strings
          """
          recs=recst.split('\r\n')
          for rec in recs:
            print (rec)
    def sessionid(recst):
          """ Search session id from rtsp strings
          """
          recs=recst.split('\r\n')
          for rec in recs:
            ss=rec.split()
            # #print ">",ss
            if (ss[0].strip()=="Session:"):
              return int(ss[1].split(";")[0].strip())
    def setsesid(recst,idn):
          """ Sets session id in an rtsp string
          """
          return recst.replace("SESID",str(idn))
        
    # ********* (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
          ***c=bt[64:96].uint # ***c 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",***c
        
          # 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==False and end==False): # intermediate fragment in a sequence, just dump "VIDEO FRAGMENT DATA"
            head=""
            lc+=1 # We skip the "Second byte"
          elif (end==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"
            return head+st[lc:]
          else:
            raise(Exception,"unknown frame type for this piece of s***")
    # *********** (3) THE MAIN PROGRAM STARTS HERE ****************
    # Create an TCP socket for RTSP communication
    # further reading: 
    # https://docs.python.org/2.7/howto/sockets.html
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip,port)) # RTSP should peek out from port 1616
    s.send(dest)
    recst=s.recv(4096)
    printrec(recst)
    s.send(setu)
    recst=s.recv(4096)
    printrec(recst)
    idn=sessionid(recst)
    serverports=getPorts("server_port",recst)
    clientports=getPorts("client_port",recst)
    print ("ip,serverports",ip,serverports)
    s1=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    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
    # Now our port is open for receiving shitloads of videodata.  Give the camera the PLAY command..
    print ("*** SENDING PLAY ***")
    play=setsesid(play,idn)
    s.send(play)
    recst=s.recv(4096)
    printrec(recst)
    print ("** STRIPPING RTP INFO AND DUMPING INTO FILE **")
    f=open(fname,'w')
    for i in range(rn):
      recst=s1.recv(4096)
      print ("read",len(recst),"bytes")
      st=digestpacket(recst)
      print ("dumping",len(st),"bytes")
      f.write(st)
    f.close()
    # Before closing the sockets, we should give the "TEARDOWN" command via RTSP, but I am feeling lazy today (after googling, wireshark-analyzing, among other-things).
    s.close()
    s1.close()
    
    
    
    
    
    
    
    Python-RTSP
    Implementation of the RTSP protocol on top of the Twisted Python library. — Read more 
    http://odie5533.com/ 
    https://github.com/odie5533/Python-RTSP

关键字