2

I have a video client that can only join a video stream via an rtsp request - it is unable to consume an SDP file directly.

However, the multicast video sources it needs to view do not support rtsp....

Ultimately all an rtsp request is doing is providing a machanism for the SDP to be returned to the client...so I've been trying to find a solution that would allow me to make an rtsp request from the client to a proxy server of some kind, and dependant upon the URI used that server would then return the relevant SDP in response to a DESCRIBE request. This would allow me to play the video sources, despite the fact that the client can only request video via rtsp....

This sounds simple, but I haven't managed to find a way of doing it. Any ideas?

user1447903
  • 303
  • 2
  • 7
  • 14
  • Why don't you just implement the dummy RTSP server with hard-coded responses if that's all you need i.e. on OPTIONS return a hard-coded options string, etc. You can see what responses are required by streaming a multicast session using the live555 RTSP server. – Ralf Sep 11 '15 at 13:52

2 Answers2

0

This is what I ended up doing; the below is written in Python. It won't win any beauty competitions, and I expect there are many ways in which it could break...but it works for a first try. It grabs the SDP file from (in this case) a URL, then responds to RTSP requests made against it, serving up the SDP file to the client. The SDP required can be specified as a parameter to the RTSP URI:

#! /usr/bin/python2.6

#
# To use this, make an rtsp call of the form rtsp://<ip_address>/<camera_id>
#

import socket
import time
import re
import sys
from thread import *
from random import randint
import urllib2

HOST = ''   # Any interface
PORT = 554

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "#### Socket created"

#Bind socket. Loop until success...
while True:
    try:
        print "Attempting to bind to "+str(PORT)
        s.bind((HOST, PORT))
    except socket.error as msg:
        print "Bind failed. Error Code : "+ msg[1]
        time.sleep(10)
    else:
        break
print 'Socket bind complete'

#Start listening on socket
s.listen(5)

print "#### Listening for RTSP calls on port "+str(PORT)

#Function for handling connections. This will be used to create threads
def player_thread(conn):
    data=''
    total_data=[]

    sid=randint(1,100000)

    #######################################################
    # This loop is for the duration of the RTSP connection
    #######################################################
    while True:
        ##########################################################################
        ##########################################################################
        #   
        # Receive RTSP message
        #
        ##########################################################################
        ##########################################################################

        try:
            conn.settimeout(1)
            data = conn.recv(1024)
            if data:
                total_data.append(data)
        except:
            pass

        rtsp_string=''.join(total_data)
        total_data=[]

        ##########################################################################
        ##########################################################################
        #
        # Process incoming messages and respond accordingly 
        #
        ##########################################################################
                ##########################################################################

        if rtsp_string.startswith("DESCRIBE"):
            try:
                                cam
                        except NameError:
                                p="DESCRIBE[ ]*rtsp://([^/]+)/([^ ]+)"
                                m = re.search(p, rtsp_string)
                                cam=m.group(2)

            print "DESCRIBE RECEIVED FOR "+cam
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="Content-type: application/sdp\r\n"

            sdp=get_sdp(cam)

            print sdp+"\n\r"

            sdp+="\r\n"

            resp+="Content-Length: "+str(len(sdp))+"\r\n"
            resp+="\r\n"
            resp+=sdp

        ############################################################################

        elif rtsp_string.startswith("OPTIONS"):
            try:
                                cam
                        except NameError:
                                p="OPTIONS[ ]*rtsp://([^/]+)/([^ ]+)"
                                m = re.search(p, rtsp_string)
                                cam=m.group(2)

            print "OPTIONS RECEIVED FOR "+cam
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="Public: DESCRIBE, OPTIONS, PLAY, SETUP, TEARDOWN\r\n"
            resp+="\r\n"

        ############################################################################

        elif rtsp_string.startswith("SETUP"):
            print "SETUP RECEIVED FOR "+cam
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))
            type=get_type(rtsp_string)

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="Session: "+str(sid)+"\r\n"
            resp+=type+"\r\n"
            resp+="Accept-Ranges: NPT\r\n"
            resp+="\r\n"

        ############################################################################

        elif rtsp_string.startswith("PLAY"):
            print "PLAY RECEIVED"
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="Session: "+str(sid)+"\r\n"
            resp+="Range: npt=0.0-\r\n"
            resp+="\r\n"

        ############################################################################

        elif rtsp_string.startswith("TEARDOWN"):
            print "TEARDOWN RECEIVED FOR "+cam
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="\r\n"
            conn.send(resp)
            print "##### PLAYBACK STOPPED FOR "+cam+" #####"
            break;

        ############################################################################
        #
        # Send our response to the RTSP message (assuming connection still open)
        #
        ############################################################################

        if resp != "":
            try:
                conn.send(resp)
            except:
                print "##### PLAYBACK STOPPED FOR "+cam+" #####"
                break;



###############################################################################################
###############################################################################################
# Various worker functions to parse the incoming messages, grab SDP info, and the like...

def get_type(string):
    p="(Transport.*)"
    m = re.search(p, string)
    if m:
                type=m.group(1)
                return type

        return -1

def get_cseq(string):
    p="CSeq:[ ]+([0-9]+)"
    m = re.search(p, string)
    if m:
        cseq=m.group(1)
        return cseq

    return -1

def get_sdp( cam ):
        url="<wherever your SDP file lives>?cam"
        sdp=urllib2.urlopen(url).read(1000)
        sdp=sdp.strip()
        return sdp


#####################################################################################################
# Main program loop. Sit here waiting for incoming fonnections and creating threads as required 
# to service them
#####################################################################################################

while True:
    conn, addr = s.accept()
    print '##### NEW CONNECTION FOR VIDEO RECEIVED FROM ' + addr[0]
    start_new_thread(player_thread ,(conn,))
s.close()


        s.bind((HOST, PORT))
    except socket.error as msg:
        print "Bind failed. Error Code : "+ msg[1]
        time.sleep(10)
    else:
        break
print 'Socket bind complete'

#Start listening on socket
s.listen(5)

print "#### Listening for RTSP calls on port "+str(PORT)

#Function for handling connections. This will be used to create threads
def player_thread(conn):
    data=''
    total_data=[]

    sid=randint(1,100000)

    #######################################################
    # This loop is for the duration of the RTSP connection
    #######################################################
    while True:
        ##########################################################################
        ##########################################################################
        #   
        # Receive RTSP message
        #
        ##########################################################################
        ##########################################################################

        try:
            conn.settimeout(1)
            data = conn.recv(1024)
            if data:
                total_data.append(data)
        except:
            pass

        rtsp_string=''.join(total_data)
        total_data=[]

        ##########################################################################
        ##########################################################################
        #
        # Process incoming messages and respond accordingly 
        #
        ##########################################################################
                ##########################################################################

        if rtsp_string.startswith("DESCRIBE"):
            try:
                                cam
                        except NameError:
                                p="DESCRIBE[ ]*rtsp://([^/]+)/([^ ]+)"
                                m = re.search(p, rtsp_string)
                                cam=m.group(2)

            print "DESCRIBE RECEIVED FOR "+cam
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="Content-type: application/sdp\r\n"

            sdp=get_sdp(cam)

            print sdp+"\n\r"

            sdp+="\r\n"

            resp+="Content-Length: "+str(len(sdp))+"\r\n"
            resp+="\r\n"
            resp+=sdp

        ############################################################################

        elif rtsp_string.startswith("OPTIONS"):
            try:
                                cam
                        except NameError:
                                p="OPTIONS[ ]*rtsp://([^/]+)/([^ ]+)"
                                m = re.search(p, rtsp_string)
                                cam=m.group(2)

            print "OPTIONS RECEIVED FOR "+cam
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="Public: DESCRIBE, OPTIONS, PLAY, SETUP, TEARDOWN\r\n"
            resp+="\r\n"

        ############################################################################

        elif rtsp_string.startswith("SETUP"):
            print "SETUP RECEIVED FOR "+cam
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))
            type=get_type(rtsp_string)

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="Session: "+str(sid)+"\r\n"
            resp+=type+"\r\n"
            resp+="Accept-Ranges: NPT\r\n"
            resp+="\r\n"

        ############################################################################

        elif rtsp_string.startswith("PLAY"):
            print "PLAY RECEIVED"
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="Session: "+str(sid)+"\r\n"
            resp+="Range: npt=0.0-\r\n"
            resp+="\r\n"

        ############################################################################

        elif rtsp_string.startswith("TEARDOWN"):
            print "TEARDOWN RECEIVED FOR "+cam
            print rtsp_string
            cseq=str(get_cseq(rtsp_string))

            resp ="RTSP/1.0 200 OK\r\n"
            resp+="CSeq: "+cseq+"\r\n"
            resp+="\r\n"
            conn.send(resp)
            print "##### PLAYBACK STOPPED FOR "+cam+" #####"
            break;

        ############################################################################
        #
        # Send our response to the RTSP message (assuming connection still open)
        #
        ############################################################################

        if resp != "":
            try:
                conn.send(resp)
            except:
                print "##### PLAYBACK STOPPED FOR "+cam+" #####"
                break;



###############################################################################################
###############################################################################################
# Various worker functions to parse the incoming messages, grab SDP info, and the like...

def get_type(string):
    p="(Transport.*)"
    m = re.search(p, string)
    if m:
                type=m.group(1)
                return type

        return -1

def get_cseq(string):
    p="CSeq:[ ]+([0-9]+)"
    m = re.search(p, string)
    if m:
        cseq=m.group(1)
        return cseq

    return -1

def get_sdp( cam ):
        url="<wherever your SDP file lives>?cam"
        sdp=urllib2.urlopen(url).read(1000)
        sdp=sdp.strip()
        return sdp


#####################################################################################################
# Main program loop. Sit here waiting for incoming fonnections and creating threads as required 
# to service them
#####################################################################################################

while True:
    conn, addr = s.accept()
    print '##### NEW CONNECTION FOR VIDEO RECEIVED FROM ' + addr[0]
    start_new_thread(player_thread ,(conn,))
s.close()
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
user1447903
  • 303
  • 2
  • 7
  • 14
0

You can create an ServerMediaSession from an SDP file and add it to an RTSPServer.

#include "liveMedia.hh"
#include "BasicUsageEnvironment.hh"

class SDPMediaSubsession: public ServerMediaSubsession 
{
    public:
        static SDPMediaSubsession* createNew(UsageEnvironment& env, MediaSubsession* subsession) { return new SDPMediaSubsession(env, subsession); }

    protected:
        SDPMediaSubsession(UsageEnvironment& env, MediaSubsession* subsession) : ServerMediaSubsession(env), m_subsession(subsession) {};
        virtual ~SDPMediaSubsession() {};

    protected: 
        virtual char const* sdpLines() { return m_subsession->savedSDPLines(); }

        virtual void getStreamParameters(unsigned clientSessionId, netAddressBits clientAddress, Port const& clientRTPPort, Port const& clientRTCPPort, int tcpSocketNum, unsigned char rtpChannelId, unsigned char rtcpChannelId,
            netAddressBits& destinationAddress, u_int8_t& destinationTTL, Boolean& isMulticast, Port& serverRTPPort, Port& serverRTCPPort, void*& streamToken)
        {       
            destinationAddress = m_subsession->connectionEndpointAddress();
            isMulticast = IsMulticastAddress(destinationAddress);     
            serverRTPPort = m_subsession->clientPortNum();
            serverRTCPPort = m_subsession->clientPortNum()+1;           
        }

        virtual void startStream(unsigned clientSessionId, void* streamToken, TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData, unsigned short& rtpSeqNum, unsigned& rtpTimestamp, ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler, void* serverRequestAlternativeByteHandlerClientData) {}  
    protected:
        MediaSubsession* m_subsession;
};


class SDPRTSPServer: public RTSPServer 
{
    public:
        static SDPRTSPServer* createNew(UsageEnvironment& env, Port ourPort, UserAuthenticationDatabase* authDatabase = NULL, unsigned reclamationTestSeconds = 65)
        {
            int ourSocket = setUpOurSocket(env, ourPort);
            if (ourSocket == -1) return NULL;
            return new SDPRTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);            
        }

    protected:
        SDPRTSPServer(UsageEnvironment& env, int ourSocket, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
          : RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds) {}        

    protected: 
        virtual ServerMediaSession* lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession)
        {           
            ServerMediaSession* sms = RTSPServer::lookupServerMediaSession(streamName);
            if (sms == NULL)
            {           
                FILE* file = fopen(streamName, "r");                
                if (file != NULL)
                {
                    sms = ServerMediaSession::createNew(envir(), streamName);
                    fseek(file, 0, SEEK_END);
                    long size = ftell(file);
                    fseek(file, 0, SEEK_SET);
                    char sdp[size];
                    fread(sdp,size,1,file);
                    fclose(file);

                    MediaSession* session = MediaSession::createNew(envir(), sdp);    
                    MediaSubsessionIterator iter(*session);
                    MediaSubsession* subsession = NULL;
                    while ((subsession = iter.next()) != NULL) 
                    {  
                        sms->addSubsession(SDPMediaSubsession::createNew(envir(),subsession));
                    }                                   
                    addServerMediaSession(sms);
                }
            }  
            return sms;
        }
};


int main(int argc, char** argv) 
{   
    TaskScheduler* scheduler = BasicTaskScheduler::createNew();
    BasicUsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

    RTSPServer* rtspServer = SDPRTSPServer::createNew(*env, 8554);
    if (rtspServer == NULL) 
    {
        *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
        exit(1);
    }       

    env->taskScheduler().doEventLoop(); 
    return 0; 
}

This RTSPServer will create a session reading SDP file specified by the url without sending any RTP/RTCP streams.

It will give access to the SDP files available in the running directory of the RTSP server (like live555MediaServer does for video files).
For instance rtsp://<server>:8554/cam1.sdp will give access to the stream described in cam1.sdp

mpromonet
  • 11,326
  • 43
  • 62
  • 91