Bump to version 2013.07.16

This commit is contained in:
Roman Gaufman
2013-07-29 20:20:19 +01:00
parent e79ecf9519
commit eecaa2eff0
35 changed files with 948 additions and 1073 deletions

View File

@@ -103,7 +103,7 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
// that had already been closed) being used in "select()" - we print out the sockets that were being used in "select()",
// to assist in debugging:
fprintf(stderr, "socket numbers used in the select() call:");
for (int i = 0; i < 100; ++i) {
for (int i = 0; i < 10000; ++i) {
if (FD_ISSET(i, &fReadSet) || FD_ISSET(i, &fWriteSet) || FD_ISSET(i, &fExceptionSet)) {
fprintf(stderr, " %d(", i);
if (FD_ISSET(i, &fReadSet)) fprintf(stderr, "r");

View File

@@ -4,7 +4,7 @@
#ifndef _BASICUSAGEENVIRONMENT_VERSION_HH
#define _BASICUSAGEENVIRONMENT_VERSION_HH
#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_STRING "2013.04.30"
#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_INT 1367280000
#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_STRING "2013.07.16"
#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_INT 1373932800
#endif

View File

@@ -18,7 +18,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#if defined(__BORLANDC__) || (!defined(USE_LIVE555_BOOLEAN) && defined(_MSC_VER) && _MSC_VER >= 1400)
// Use the "bool" type defined by the Borland compiler, and MSVC++ 8.0, Visual Studio 2005 and higher
#define Boolean bool
typedef bool Boolean;
#define False false
#define True true
#else

View File

@@ -4,7 +4,7 @@
#ifndef _USAGEENVIRONMENT_VERSION_HH
#define _USAGEENVIRONMENT_VERSION_HH
#define USAGEENVIRONMENT_LIBRARY_VERSION_STRING "2013.04.30"
#define USAGEENVIRONMENT_LIBRARY_VERSION_INT 1367280000
#define USAGEENVIRONMENT_LIBRARY_VERSION_STRING "2013.07.16"
#define USAGEENVIRONMENT_LIBRARY_VERSION_INT 1373932800
#endif

View File

@@ -1,18 +1,18 @@
CROSS_COMPILE?= arm-elf-
COMPILE_OPTS = $(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -DNO_SSTREAM=1 -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
C = c
C_COMPILER = $(CROSS_COMPILE)gcc
C_FLAGS = $(COMPILE_OPTS)
CPP = cpp
CPLUSPLUS_COMPILER = $(CROSS_COMPILE)g++
CPLUSPLUS_FLAGS = $(COMPILE_OPTS) -Wall -DBSD=1
OBJ = o
LINK = $(CROSS_COMPILE)g++ -o
LINK_OPTS =
CONSOLE_LINK_OPTS = $(LINK_OPTS)
LIBRARY_LINK = $(CROSS_COMPILE)ar cr
LIBRARY_LINK_OPTS = $(LINK_OPTS)
LIB_SUFFIX = a
LIBS_FOR_CONSOLE_APPLICATION =
LIBS_FOR_GUI_APPLICATION =
EXE =
CROSS_COMPILE?= arm-elf-
COMPILE_OPTS = $(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -DNO_SSTREAM=1 -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
C = c
C_COMPILER = $(CROSS_COMPILE)gcc
C_FLAGS = $(COMPILE_OPTS)
CPP = cpp
CPLUSPLUS_COMPILER = $(CROSS_COMPILE)g++
CPLUSPLUS_FLAGS = $(COMPILE_OPTS) -Wall -DBSD=1
OBJ = o
LINK = $(CROSS_COMPILE)g++ -o
LINK_OPTS =
CONSOLE_LINK_OPTS = $(LINK_OPTS)
LIBRARY_LINK = $(CROSS_COMPILE)ar cr
LIBRARY_LINK_OPTS = $(LINK_OPTS)
LIB_SUFFIX = a
LIBS_FOR_CONSOLE_APPLICATION =
LIBS_FOR_GUI_APPLICATION =
EXE =

View File

@@ -3,9 +3,9 @@
# At least one interface changes, or is removed => CURRENT += 1; REVISION = 0; AGE = 0
# One or more interfaces were added, but no existing interfaces were changed or removed => CURRENT += 1; REVISION = 0; AGE += 1
libliveMedia_VERSION_CURRENT=7
libliveMedia_VERSION_REVISION=5
libliveMedia_VERSION_AGE=0
libliveMedia_VERSION_CURRENT=11
libliveMedia_VERSION_REVISION=2
libliveMedia_VERSION_AGE=1
libliveMedia_LIB_SUFFIX=so.$(shell expr $(libliveMedia_VERSION_CURRENT) - $(libliveMedia_VERSION_AGE)).$(libliveMedia_VERSION_AGE).$(libliveMedia_VERSION_REVISION)
libBasicUsageEnvironment_VERSION_CURRENT=0
@@ -13,7 +13,7 @@ libBasicUsageEnvironment_VERSION_REVISION=1
libBasicUsageEnvironment_VERSION_AGE=0
libBasicUsageEnvironment_LIB_SUFFIX=so.$(shell expr $(libBasicUsageEnvironment_VERSION_CURRENT) - $(libBasicUsageEnvironment_VERSION_AGE)).$(libBasicUsageEnvironment_VERSION_AGE).$(libBasicUsageEnvironment_VERSION_REVISION)
libUsageEnvironment_VERSION_CURRENT=0
libUsageEnvironment_VERSION_CURRENT=1
libUsageEnvironment_VERSION_REVISION=0
libUsageEnvironment_VERSION_AGE=0
libUsageEnvironment_LIB_SUFFIX=so.$(shell expr $(libUsageEnvironment_VERSION_CURRENT) - $(libUsageEnvironment_VERSION_AGE)).$(libUsageEnvironment_VERSION_AGE).$(libUsageEnvironment_VERSION_REVISION)

View File

@@ -4,7 +4,7 @@
#ifndef _GROUPSOCK_VERSION_HH
#define _GROUPSOCK_VERSION_HH
#define GROUPSOCK_LIBRARY_VERSION_STRING "2013.04.30"
#define GROUPSOCK_LIBRARY_VERSION_INT 1367280000
#define GROUPSOCK_LIBRARY_VERSION_STRING "2013.07.16"
#define GROUPSOCK_LIBRARY_VERSION_INT 1373932800
#endif

View File

@@ -39,16 +39,23 @@ static void initBase64DecodeTable() {
unsigned char* base64Decode(char const* in, unsigned& resultSize,
Boolean trimTrailingZeros) {
static Boolean haveInitedBase64DecodeTable = False;
if (!haveInitedBase64DecodeTable) {
if (in == NULL) return NULL; // sanity check
return base64Decode(in, strlen(in), resultSize, trimTrailingZeros);
}
unsigned char* base64Decode(char const* in, unsigned inSize,
unsigned& resultSize,
Boolean trimTrailingZeros) {
static Boolean haveInitializedBase64DecodeTable = False;
if (!haveInitializedBase64DecodeTable) {
initBase64DecodeTable();
haveInitedBase64DecodeTable = True;
haveInitializedBase64DecodeTable = True;
}
unsigned char* out = (unsigned char*)strDupSize(in); // ensures we have enough space
int k = 0;
int const jMax = strlen(in) - 3;
// in case "in" is not a multiple of 4 bytes (although it should be)
int const jMax = inSize - 3;
// in case "inSize" is not a multiple of 4 (although it should be)
for (int j = 0; j < jMax; j += 4) {
char inTmp[4], outTmp[4];
for (int i = 0; i < 4; ++i) {

View File

@@ -105,7 +105,7 @@ class RTSPClientForDarwinInjector: public RTSPClient {
public:
RTSPClientForDarwinInjector(UsageEnvironment& env, char const* rtspURL, int verbosityLevel, char const* applicationName,
DarwinInjector* ourDarwinInjector)
: RTSPClient(env, rtspURL, verbosityLevel, applicationName, 0),
: RTSPClient(env, rtspURL, verbosityLevel, applicationName, 0, -1),
fOurDarwinInjector(ourDarwinInjector) {}
virtual ~RTSPClientForDarwinInjector() {}
DarwinInjector* fOurDarwinInjector;

View File

@@ -139,8 +139,8 @@ H264VideoStreamParser::~H264VideoStreamParser() {
void H264VideoStreamParser::removeEmulationBytes(u_int8_t* nalUnitCopy, unsigned maxSize, unsigned& nalUnitCopySize) {
u_int8_t* nalUnitOrig = fStartOfFrame + fOutputStartCodeSize;
unsigned const NumBytesInNALunit = fTo - nalUnitOrig;
if (NumBytesInNALunit > maxSize) return;
nalUnitCopySize = 0;
if (NumBytesInNALunit > maxSize) return;
for (unsigned i = 0; i < NumBytesInNALunit; ++i) {
if (i+2 < NumBytesInNALunit && nalUnitOrig[i] == 0 && nalUnitOrig[i+1] == 0 && nalUnitOrig[i+2] == 3) {
nalUnitCopy[nalUnitCopySize++] = nalUnitOrig[i++];

View File

@@ -23,8 +23,8 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#define TRANSPORT_PACKET_SIZE 188
#define PAT_FREQUENCY 100 // # of packets between Program Association Tables
#define PMT_FREQUENCY 500 // # of packets between Program Map Tables
#define PAT_PERIOD 100 // # of packets between Program Association Tables
#define PMT_PERIOD 500 // # of packets between Program Map Tables
#define PID_TABLE_SIZE 256
@@ -56,7 +56,7 @@ void MPEG2TransportStreamMultiplexor::doGetNextFrame() {
do {
// Periodically return a Program Association Table packet instead:
if (fOutgoingPacketCounter++ % PAT_FREQUENCY == 0) {
if (fOutgoingPacketCounter++ % PAT_PERIOD == 0) {
deliverPATPacket();
break;
}
@@ -64,7 +64,7 @@ void MPEG2TransportStreamMultiplexor::doGetNextFrame() {
// Periodically (or when we see a new PID) return a Program Map Table instead:
Boolean programMapHasChanged = fPIDState[fCurrentPID].counter == 0
|| fCurrentInputProgramMapVersion != fPreviousInputProgramMapVersion;
if (fOutgoingPacketCounter % PMT_FREQUENCY == 0 || programMapHasChanged) {
if (fOutgoingPacketCounter % PMT_PERIOD == 0 || programMapHasChanged) {
if (programMapHasChanged) { // reset values for next time:
fPIDState[fCurrentPID].counter = 1;
fPreviousInputProgramMapVersion = fCurrentInputProgramMapVersion;

View File

@@ -119,7 +119,7 @@ MPEG4VideoStreamFramer.$(CPP): include/MPEG4VideoStreamFramer.hh MPEGVideoStream
include/MPEG4VideoStreamFramer.hh: include/MPEGVideoStreamFramer.hh
MPEG4VideoStreamDiscreteFramer.$(CPP): include/MPEG4VideoStreamDiscreteFramer.hh
include/MPEG4VideoStreamDiscreteFramer.hh: include/MPEG4VideoStreamFramer.hh
H264VideoStreamFramer.$(CPP): include/H264VideoStreamFramer.hh MPEGVideoStreamParser.hh BitVector.hh include/H264VideoRTPSource.hh
H264VideoStreamFramer.$(CPP): include/H264VideoStreamFramer.hh MPEGVideoStreamParser.hh include/BitVector.hh include/H264VideoRTPSource.hh
include/H264VideoStreamFramer.hh: include/MPEGVideoStreamFramer.hh
H264VideoStreamDiscreteFramer.$(CPP): include/H264VideoStreamDiscreteFramer.hh
include/H264VideoStreamDiscreteFramer.hh: include/H264VideoStreamFramer.hh
@@ -132,12 +132,12 @@ MPEG4LATMAudioRTPSource.$(CPP): include/MPEG4LATMAudioRTPSource.hh
include/MPEG4LATMAudioRTPSource.hh: include/MultiFramedRTPSource.hh
MPEG4ESVideoRTPSource.$(CPP): include/MPEG4ESVideoRTPSource.hh
include/MPEG4ESVideoRTPSource.hh: include/MultiFramedRTPSource.hh
MPEG4GenericRTPSource.$(CPP): include/MPEG4GenericRTPSource.hh BitVector.hh include/MPEG4LATMAudioRTPSource.hh
MPEG4GenericRTPSource.$(CPP): include/MPEG4GenericRTPSource.hh include/BitVector.hh include/MPEG4LATMAudioRTPSource.hh
include/MPEG4GenericRTPSource.hh: include/MultiFramedRTPSource.hh
MP3FileSource.$(CPP): include/MP3FileSource.hh MP3StreamState.hh include/InputFile.hh
include/MP3FileSource.hh: include/FramedFileSource.hh
MP3StreamState.hh: MP3Internals.hh
MP3Internals.hh: BitVector.hh
MP3Internals.hh: include/BitVector.hh
MP3Transcoder.$(CPP): include/MP3ADU.hh include/MP3Transcoder.hh
include/MP3ADU.hh: include/FramedFilter.hh
include/MP3Transcoder.hh: include/MP3ADU.hh include/MP3ADUTranscoder.hh
@@ -305,6 +305,7 @@ include/AC3AudioFileServerMediaSubsession.hh: include/FileServerMediaSubsession.
MPEG2TransportUDPServerMediaSubsession.$(CPP): include/MPEG2TransportUDPServerMediaSubsession.hh include/BasicUDPSource.hh include/SimpleRTPSource.hh include/MPEG2TransportStreamFramer.hh include/SimpleRTPSink.hh
include/MPEG2TransportUDPServerMediaSubsession.hh: include/OnDemandServerMediaSubsession.hh
ProxyServerMediaSession.$(CPP): include/liveMedia.hh include/RTSPCommon.hh
include/ProxyServerMediaSession.hh: include/ServerMediaSession.hh include/MediaSession.hh include/RTSPClient.hh
QuickTimeFileSink.$(CPP): include/QuickTimeFileSink.hh include/InputFile.hh include/OutputFile.hh include/QuickTimeGenericRTPSource.hh include/H263plusVideoRTPSource.hh include/MPEG4GenericRTPSource.hh include/MPEG4LATMAudioRTPSource.hh
include/QuickTimeFileSink.hh: include/MediaSession.hh
QuickTimeGenericRTPSource.$(CPP): include/QuickTimeGenericRTPSource.hh
@@ -336,7 +337,7 @@ MatroskaFileServerDemux.$(CPP): include/MatroskaFileServerDemux.hh H264VideoMatr
include/MatroskaFileServerDemux.hh: include/ServerMediaSession.hh include/MatroskaFile.hh
DarwinInjector.$(CPP): include/DarwinInjector.hh
include/DarwinInjector.hh: include/RTSPClient.hh include/RTCP.hh
BitVector.$(CPP): BitVector.hh
BitVector.$(CPP): include/BitVector.hh
StreamParser.$(CPP): StreamParser.hh
DigestAuthentication.$(CPP): include/DigestAuthentication.hh our_md5.h
our_md5.$(C): our_md5.h

View File

@@ -106,9 +106,7 @@ Boolean MediaSession::initializeWithSDP(char const* sdpDescription) {
char const* nextSDPLine;
while (1) {
if (!parseSDPLine(sdpLine, nextSDPLine)) return False;
//##### We should really check for:
// - "a=control:" attributes (to set the URL for aggregate control)
// - the correct SDP version (v=0)
//##### We should really check for the correct SDP version (v=0)
if (sdpLine[0] == 'm') break;
sdpLine = nextSDPLine;
if (sdpLine == NULL) break; // there are no m= lines at all
@@ -1081,6 +1079,12 @@ Boolean MediaSubsession::parseSDPAttribute_fmtp(char const* sdpLine) {
} else if (sscanf(sdpLine, " channel-order = %[^; \t\r\n]", valueStr) == 1) {
// Note: We used "sdpLine" here, because the value is case-sensitive.
delete[] fChannelOrder; fChannelOrder = strDup(valueStr);
} else if (sscanf(line, " width = %u", &u) == 1) {
// A non-standard parameter, but one that's often used:
fVideoWidth = u;
} else if (sscanf(line, " height = %u", &u) == 1) {
// A non-standard parameter, but one that's often used:
fVideoHeight = u;
} else {
// Some of the above parameters are Boolean. Check whether the parameter
// names appear alone, without a "= 1" at the end:

View File

@@ -72,7 +72,7 @@ MultiFramedRTPSource
fReorderingBuffer = new ReorderingPacketBuffer(packetFactory);
// Try to use a big receive buffer for RTP:
increaseReceiveBufferTo(env, RTPgs->socketNum(), 50*1024);
increaseReceiveBufferTo(env, RTPgs->socketNum(), 2000000);
}
void MultiFramedRTPSource::reset() {

View File

@@ -443,6 +443,7 @@ void StreamState
if (fRTPSink != NULL) {
fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);
fRTPSink->setServerRequestAlternativeByteHandler(dests->tcpSocketNum, serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
// So that we continue to handle RTSP commands from the client
}
if (fRTCPInstance != NULL) {
fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId);
@@ -484,14 +485,19 @@ void StreamState::pause() {
}
void StreamState::endPlaying(Destinations* dests) {
#if 0
// The following code is temporarily disabled, because it erroneously sends RTCP "BYE"s to all clients if multiple
// clients are streaming from the samedata source (i.e., if "reuseFirstSource" is True). It will be fixed for real later.
if (fRTCPInstance != NULL) {
// Hack: Explicitly send a RTCP "BYE" packet now, because the code below will prevent that from happening later,
// when "fRTCPInstance" gets deleted:
fRTCPInstance->sendBYE();
}
#endif
if (dests->isTCP) {
if (fRTPSink != NULL) {
fRTPSink->setServerRequestAlternativeByteHandler(dests->tcpSocketNum, NULL, NULL);
fRTPSink->removeStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);
}
if (fRTCPInstance != NULL) {

View File

@@ -67,22 +67,24 @@ UsageEnvironment& operator<<(UsageEnvironment& env, const ProxyServerMediaSessio
ProxyServerMediaSession* ProxyServerMediaSession
::createNew(UsageEnvironment& env, RTSPServer* ourRTSPServer,
char const* inputStreamURL, char const* streamName,
char const* username, char const* password, portNumBits tunnelOverHTTPPortNum, int verbosityLevel) {
return new ProxyServerMediaSession(env, ourRTSPServer, inputStreamURL, streamName, username, password, tunnelOverHTTPPortNum, verbosityLevel);
char const* username, char const* password,
portNumBits tunnelOverHTTPPortNum, int verbosityLevel, int socketNumToServer) {
return new ProxyServerMediaSession(env, ourRTSPServer, inputStreamURL, streamName, username, password,
tunnelOverHTTPPortNum, verbosityLevel, socketNumToServer);
}
ProxyServerMediaSession::ProxyServerMediaSession(UsageEnvironment& env, RTSPServer* ourRTSPServer,
char const* inputStreamURL, char const* streamName,
char const* username, char const* password,
portNumBits tunnelOverHTTPPortNum, int verbosityLevel)
portNumBits tunnelOverHTTPPortNum, int verbosityLevel, int socketNumToServer)
: ServerMediaSession(env, streamName, NULL, NULL, False, NULL),
describeCompletedFlag(0), fOurRTSPServer(ourRTSPServer), fClientMediaSession(NULL),
fVerbosityLevel(verbosityLevel), fPresentationTimeSessionNormalizer(new PresentationTimeSessionNormalizer(envir())) {
// Open a RTSP connection to the input stream, and send a "DESCRIBE" command.
// We'll use the SDP description in the response to set ourselves up.
fProxyRTSPClient = createNewProxyRTSPClient(inputStreamURL, username, password,
tunnelOverHTTPPortNum, verbosityLevel > 0 ? verbosityLevel-1 : verbosityLevel);
fProxyRTSPClient = createNewProxyRTSPClient(inputStreamURL, username, password, tunnelOverHTTPPortNum,
verbosityLevel > 0 ? verbosityLevel-1 : verbosityLevel, socketNumToServer);
ProxyRTSPClient::sendDESCRIBE(fProxyRTSPClient);
}
@@ -106,9 +108,9 @@ char const* ProxyServerMediaSession::url() const {
ProxyRTSPClient* ProxyServerMediaSession
::createNewProxyRTSPClient(char const* rtspURL, char const* username, char const* password,
portNumBits tunnelOverHTTPPortNum, int verbosityLevel){
portNumBits tunnelOverHTTPPortNum, int verbosityLevel, int socketNumToServer){
// default implementation:
return new ProxyRTSPClient(*this, rtspURL, username, password, tunnelOverHTTPPortNum, verbosityLevel);
return new ProxyRTSPClient(*this, rtspURL, username, password, tunnelOverHTTPPortNum, verbosityLevel, socketNumToServer);
}
void ProxyServerMediaSession::continueAfterDESCRIBE(char const* sdpDescription) {
@@ -192,9 +194,10 @@ UsageEnvironment& operator<<(UsageEnvironment& env, const ProxyRTSPClient& proxy
}
ProxyRTSPClient::ProxyRTSPClient(ProxyServerMediaSession& ourServerMediaSession, char const* rtspURL,
char const* username, char const* password, portNumBits tunnelOverHTTPPortNum, int verbosityLevel)
char const* username, char const* password,
portNumBits tunnelOverHTTPPortNum, int verbosityLevel, int socketNumToServer)
: RTSPClient(ourServerMediaSession.envir(), rtspURL, verbosityLevel, "ProxyRTSPClient",
tunnelOverHTTPPortNum == (portNumBits)(~0) ? 0 : tunnelOverHTTPPortNum),
tunnelOverHTTPPortNum == (portNumBits)(~0) ? 0 : tunnelOverHTTPPortNum, socketNumToServer),
fOurServerMediaSession(ourServerMediaSession), fOurURL(strDup(rtspURL)), fStreamRTPOverTCP(tunnelOverHTTPPortNum != 0),
fSetupQueueHead(NULL), fSetupQueueTail(NULL), fNumSetupsDone(0), fNextDESCRIBEDelay(1),
fServerSupportsGetParameter(False), fLastCommandWasPLAY(False),

View File

@@ -71,7 +71,7 @@ private:
ServerRequestAlternativeByteHandler* fServerRequestAlternativeByteHandler;
void* fServerRequestAlternativeByteHandlerClientData;
u_int8_t fStreamChannelId, fSizeByte1;
Boolean fReadErrorOccurred, fDeleteMyselfNext;
Boolean fReadErrorOccurred, fDeleteMyselfNext, fAreInReadHandlerLoop;
enum { AWAITING_DOLLAR, AWAITING_STREAM_CHANNEL_ID, AWAITING_SIZE1, AWAITING_SIZE2, AWAITING_PACKET_DATA } fTCPReadingState;
};
@@ -343,7 +343,7 @@ SocketDescriptor::SocketDescriptor(UsageEnvironment& env, int socketNum)
:fEnv(env), fOurSocketNum(socketNum),
fSubChannelHashTable(HashTable::create(ONE_WORD_HASH_KEYS)),
fServerRequestAlternativeByteHandler(NULL), fServerRequestAlternativeByteHandlerClientData(NULL),
fReadErrorOccurred(False), fDeleteMyselfNext(False), fTCPReadingState(AWAITING_DOLLAR) {
fReadErrorOccurred(False), fDeleteMyselfNext(False), fAreInReadHandlerLoop(False), fTCPReadingState(AWAITING_DOLLAR) {
}
SocketDescriptor::~SocketDescriptor() {
@@ -358,7 +358,21 @@ SocketDescriptor::~SocketDescriptor() {
removeSocketDescription(fEnv, fOurSocketNum);
if (fSubChannelHashTable != NULL) {
while (fSubChannelHashTable->RemoveNext() != NULL) {} // remove the "RTPInterface"s from the table, but don't delete them
// Remove knowledge of this socket from any "RTPInterface"s that are using it:
HashTable::Iterator* iter = HashTable::Iterator::create(*fSubChannelHashTable);
RTPInterface* rtpInterface;
char const* key;
while ((rtpInterface = (RTPInterface*)(iter->next(key))) != NULL) {
long streamChannelIdLong = (long)key;
unsigned char streamChannelId = (unsigned char)streamChannelIdLong;
rtpInterface->removeStreamSocket(fOurSocketNum, streamChannelId);
}
delete iter;
// Then remove the hash table entries themselves, and then remove the hash table:
while (fSubChannelHashTable->RemoveNext() != NULL) {}
delete fSubChannelHashTable;
}
}
@@ -396,14 +410,20 @@ void SocketDescriptor
if (fSubChannelHashTable->IsEmpty()) {
// No more interfaces are using us, so it's curtains for us now:
fDeleteMyselfNext = True; // hack to cause ourself to be deleted from "tcpReadHandler()" below
if (fAreInReadHandlerLoop) {
fDeleteMyselfNext = True; // we can't delete ourself yet, by we'll do so from "tcpReadHandler()" below
} else {
delete this;
}
}
}
void SocketDescriptor::tcpReadHandler(SocketDescriptor* socketDescriptor, int mask) {
// Call the read handler until it returns false, with a limit to avoid starving other sockets
unsigned count = 2000;
socketDescriptor->fAreInReadHandlerLoop = True;
while (!socketDescriptor->fDeleteMyselfNext && socketDescriptor->tcpReadHandler1(mask) && --count > 0) {}
socketDescriptor->fAreInReadHandlerLoop = False;
if (socketDescriptor->fDeleteMyselfNext) delete socketDescriptor;
}

View File

@@ -30,9 +30,10 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
RTSPClient* RTSPClient::createNew(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel,
char const* applicationName,
portNumBits tunnelOverHTTPPortNum) {
portNumBits tunnelOverHTTPPortNum,
int socketNumToServer) {
return new RTSPClient(env, rtspURL,
verbosityLevel, applicationName, tunnelOverHTTPPortNum);
verbosityLevel, applicationName, tunnelOverHTTPPortNum, socketNumToServer);
}
unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
@@ -295,7 +296,7 @@ unsigned RTSPClient::responseBufferSize = 20000; // default value; you can reass
RTSPClient::RTSPClient(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel, char const* applicationName,
portNumBits tunnelOverHTTPPortNum)
portNumBits tunnelOverHTTPPortNum, int socketNumToServer)
: Medium(env),
fVerbosityLevel(verbosityLevel), fCSeq(1),
fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum), fUserAgentHeaderStr(NULL), fUserAgentHeaderStrLen(0),
@@ -306,6 +307,14 @@ RTSPClient::RTSPClient(UsageEnvironment& env, char const* rtspURL,
fResponseBuffer = new char[responseBufferSize+1];
resetResponseBuffer();
if (socketNumToServer >= 0) {
// This socket number is (assumed to be) already connected to the server.
// Use it, and arrange to handle responses to requests sent on it:
fInputSocketNum = fOutputSocketNum = socketNumToServer;
envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
(TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
}
// Set the "User-Agent:" header to use in each request:
char const* const libName = "LIVE555 Streaming Media v";
char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;
@@ -388,7 +397,7 @@ int RTSPClient::openConnection() {
// We don't yet have a TCP socket (or we used to have one, but it got closed). Set it up now.
fInputSocketNum = fOutputSocketNum = setupStreamSocket(envir(), 0);
if (fInputSocketNum < 0) break;
ignoreSigPipeOnSocket(fInputSocketNum); // so that servers on the same host that killed don't also kill us
ignoreSigPipeOnSocket(fInputSocketNum); // so that servers on the same host that get killed don't also kill us
// Connect to the remote endpoint:
fServerAddress = *(netAddressBits*)(destAddress.data());
@@ -538,7 +547,7 @@ unsigned RTSPClient::sendRequest(RequestRecord* request) {
else if (connectResult == 0) {
// A connection is pending
connectionIsPending = True;
} // else the connection succeeded. Continue sending the command.u
} // else the connection succeeded. Continue sending the command.
}
if (connectionIsPending) {
fRequestsAwaitingConnection.enqueue(request);
@@ -1031,6 +1040,7 @@ Boolean RTSPClient::handleSETUPResponse(MediaSubsession& subsession, char const*
if (subsession.rtpSource() != NULL) {
subsession.rtpSource()->setStreamSocket(fInputSocketNum, subsession.rtpChannelId);
subsession.rtpSource()->setServerRequestAlternativeByteHandler(fInputSocketNum, handleAlternativeRequestByte, this);
// So that we continue to receive & handle RTSP commands and responses from the server
subsession.rtpSource()->enableRTCPReports() = False;
// To avoid confusing the server (which won't start handling RTP/RTCP-over-TCP until "PLAY"), don't send RTCP "RR"s yet
}
@@ -1270,6 +1280,7 @@ void RTSPClient::responseHandlerForHTTP_GET(RTSPClient* rtspClient, int response
void RTSPClient::responseHandlerForHTTP_GET1(int responseCode, char* responseString) {
RequestRecord* request;
do {
delete[] responseString; // we don't need it (but are responsible for deleting it)
if (responseCode != 0) break; // The HTTP "GET" failed.
// Having successfully set up (using the HTTP "GET" command) the server->client link, set up a second TCP connection
@@ -1758,325 +1769,45 @@ RTSPClient::RequestRecord* RTSPClient::RequestQueue::findByCSeq(unsigned cseq) {
}
#ifdef RTSPCLIENT_SYNCHRONOUS_INTERFACE
// Implementation of the old (synchronous) "RTSPClient" interface, using the new (asynchronous) interface:
RTSPClient* RTSPClient::createNew(UsageEnvironment& env,
int verbosityLevel,
char const* applicationName,
portNumBits tunnelOverHTTPPortNum) {
return new RTSPClient(env, NULL,
verbosityLevel, applicationName, tunnelOverHTTPPortNum);
////////// HandlerServerForREGISTERCommand implementation /////////
HandlerServerForREGISTERCommand* HandlerServerForREGISTERCommand
::createNew(UsageEnvironment& env, onRTSPClientCreationFunc* creationFunc, Port ourPort,
int verbosityLevel, char const* applicationName) {
int ourSocket = setUpOurSocket(env, ourPort);
if (ourSocket == -1) return NULL;
return new HandlerServerForREGISTERCommand(env, creationFunc, ourSocket, ourPort, verbosityLevel, applicationName);
}
char* RTSPClient::describeURL(char const* url, Authenticator* authenticator,
Boolean allowKasennaProtocol, int timeout) {
// Sorry 'Kasenna', but the party's over. You've had 6 years to make your servers compliant with the standard RTSP protocol.
// We're not going to support your non-standard hacked version of the protocol any more. Starting now, the "allowKasennaProtocol"
// parameter is a noop (and eventually, when the synchronous interface goes away completely, then so will this parameter).
// First, check whether "url" contains a username:password to be used. If so, handle this using "describeWithPassword()" instead:
char* username; char* password;
if (authenticator == NULL
&& parseRTSPURLUsernamePassword(url, username, password)) {
char* result = describeWithPassword(url, username, password, allowKasennaProtocol, timeout);
delete[] username; delete[] password; // they were dynamically allocated
return result;
}
setBaseURL(url);
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL; // by default, unless:
if (timeout > 0) {
// Schedule a task to be called when the specified timeout interval expires.
// Note that we do this *before* attempting to send the RTSP command, in case this attempt fails immediately, calling the
// command response handler - because we want the response handler to unschedule any pending timeout handler.
fTimeoutTask = envir().taskScheduler().scheduleDelayedTask(timeout*1000000, timeoutHandlerForSyncInterface, this);
}
(void)sendDescribeCommand(responseHandlerForSyncInterface, authenticator);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
if (fResultCode == 0) return fResultString; // success
delete[] fResultString;
return NULL;
HandlerServerForREGISTERCommand
::HandlerServerForREGISTERCommand(UsageEnvironment& env, onRTSPClientCreationFunc* creationFunc, int ourSocket, Port ourPort,
int verbosityLevel, char const* applicationName)
: RTSPServer(env, ourSocket, ourPort, NULL, 30/*small reclamationTestSeconds*/),
fCreationFunc(creationFunc), fVerbosityLevel(verbosityLevel), fApplicationName(strDup(applicationName)) {
}
char* RTSPClient::describeWithPassword(char const* url,
char const* username, char const* password,
Boolean allowKasennaProtocol, int timeout) {
Authenticator authenticator(username, password);
return describeURL(url, &authenticator, allowKasennaProtocol, timeout);
HandlerServerForREGISTERCommand::~HandlerServerForREGISTERCommand() {
delete[] fApplicationName;
}
char* RTSPClient::sendOptionsCmd(char const* url,
char* username, char* password,
Authenticator* authenticator,
int timeout) {
char* result = NULL;
Boolean haveAllocatedAuthenticator = False;
if (authenticator == NULL) {
// First, check whether "url" contains a username:password to be used
// (and no username,password pair was supplied separately):
if (username == NULL && password == NULL
&& parseRTSPURLUsernamePassword(url, username, password)) {
Authenticator newAuthenticator(username,password);
result = sendOptionsCmd(url, username, password, &newAuthenticator, timeout);
delete[] username; delete[] password; // they were dynamically allocated
return result;
} else if (username != NULL && password != NULL) {
// Use the separately supplied username and password:
authenticator = new Authenticator(username,password);
haveAllocatedAuthenticator = True;
result = sendOptionsCmd(url, username, password, authenticator, timeout);
if (result != NULL) { // We are already authorized
delete authenticator;
return result;
}
// The "realm" field should have been filled in:
if (authenticator->realm() == NULL) {
// We haven't been given enough information to try again, so fail:
delete authenticator;
return NULL;
}
}
}
setBaseURL(url);
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL; // by default, unless:
if (timeout > 0) {
// Schedule a task to be called when the specified timeout interval expires.
// Note that we do this *before* attempting to send the RTSP command, in case this attempt fails immediately, calling the
// command response handler - because we want the response handler to unschedule any pending timeout handler.
fTimeoutTask = envir().taskScheduler().scheduleDelayedTask(timeout*1000000, timeoutHandlerForSyncInterface, this);
}
(void)sendOptionsCommand(responseHandlerForSyncInterface, authenticator);
if (haveAllocatedAuthenticator) delete authenticator;
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
if (fResultCode == 0) return fResultString; // success
delete[] fResultString;
return NULL;
RTSPClient* HandlerServerForREGISTERCommand
::createNewRTSPClient(char const* rtspURL, int verbosityLevel, char const* applicationName, int socketNumToServer) {
// Default implementation: create a basic "RTSPClient":
return RTSPClient::createNew(envir(), rtspURL, verbosityLevel, applicationName, 0, socketNumToServer);
}
Boolean RTSPClient::announceSDPDescription(char const* url,
char const* sdpDescription,
Authenticator* authenticator,
int timeout) {
setBaseURL(url);
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL; // by default, unless:
if (timeout > 0) {
// Schedule a task to be called when the specified timeout interval expires.
// Note that we do this *before* attempting to send the RTSP command, in case this attempt fails immediately, calling the
// command response handler - because we want the response handler to unschedule any pending timeout handler.
fTimeoutTask = envir().taskScheduler().scheduleDelayedTask(timeout*1000000, timeoutHandlerForSyncInterface, this);
}
(void)sendAnnounceCommand(sdpDescription, responseHandlerForSyncInterface, authenticator);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
delete[] fResultString;
return fResultCode == 0;
char const* HandlerServerForREGISTERCommand::allowedCommandNames() {
return "OPTIONS, REGISTER";
}
Boolean RTSPClient
::announceWithPassword(char const* url, char const* sdpDescription,
char const* username, char const* password, int timeout) {
Authenticator authenticator(username,password);
return announceSDPDescription(url, sdpDescription, &authenticator, timeout);
Boolean HandlerServerForREGISTERCommand::weImplementREGISTER() {
return True;
}
Boolean RTSPClient::setupMediaSubsession(MediaSubsession& subsession,
Boolean streamOutgoing,
Boolean streamUsingTCP,
Boolean forceMulticastOnUnspecified) {
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendSetupCommand(subsession, responseHandlerForSyncInterface, streamOutgoing, streamUsingTCP, forceMulticastOnUnspecified);
void HandlerServerForREGISTERCommand::implementCmd_REGISTER(char const* url, char const* urlSuffix, int socketToRemoteServer) {
// Create a new "RTSPClient" object, and call our 'creation function' with it:
RTSPClient* newRTSPClient = createNewRTSPClient(url, fVerbosityLevel, fApplicationName, socketToRemoteServer);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
delete[] fResultString;
return fResultCode == 0;
if (fCreationFunc != NULL) (*fCreationFunc)(newRTSPClient);
}
Boolean RTSPClient::playMediaSession(MediaSession& session,
double start, double end, float scale) {
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendPlayCommand(session, responseHandlerForSyncInterface, start, end, scale);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
delete[] fResultString;
return fResultCode == 0;
}
Boolean RTSPClient::playMediaSubsession(MediaSubsession& subsession,
double start, double end, float scale,
Boolean /*hackForDSS*/) {
// NOTE: The "hackForDSS" flag is no longer supported. (However, we will consider resupporting it
// if we get reports that it is still needed.)
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendPlayCommand(subsession, responseHandlerForSyncInterface, start, end, scale);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
delete[] fResultString;
return fResultCode == 0;
}
Boolean RTSPClient::pauseMediaSession(MediaSession& session) {
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendPauseCommand(session, responseHandlerForSyncInterface);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
delete[] fResultString;
return fResultCode == 0;
}
Boolean RTSPClient::pauseMediaSubsession(MediaSubsession& subsession) {
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendPauseCommand(subsession, responseHandlerForSyncInterface);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
delete[] fResultString;
return fResultCode == 0;
}
Boolean RTSPClient::recordMediaSubsession(MediaSubsession& subsession) {
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendRecordCommand(subsession, responseHandlerForSyncInterface);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
delete[] fResultString;
return fResultCode == 0;
}
Boolean RTSPClient::setMediaSessionParameter(MediaSession& session,
char const* parameterName,
char const* parameterValue) {
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendSetParameterCommand(session, responseHandlerForSyncInterface, parameterName, parameterValue);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
delete[] fResultString;
return fResultCode == 0;
}
Boolean RTSPClient::getMediaSessionParameter(MediaSession& session,
char const* parameterName,
char*& parameterValue) {
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendGetParameterCommand(session, responseHandlerForSyncInterface, parameterName);
// Now block (but handling events) until we get a response (or a timeout):
envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
parameterValue = fResultString;
return fResultCode == 0;
}
Boolean RTSPClient::teardownMediaSession(MediaSession& session) {
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendTeardownCommand(session, NULL);
return True; // we don't wait for a response to the "TEARDOWN"
}
Boolean RTSPClient::teardownMediaSubsession(MediaSubsession& subsession) {
fWatchVariableForSyncInterface = 0;
fTimeoutTask = NULL;
(void)sendTeardownCommand(subsession, NULL);
return True; // we don't wait for a response to the "TEARDOWN"
}
Boolean RTSPClient::parseRTSPURLUsernamePassword(char const* url,
char*& username,
char*& password) {
username = password = NULL; // by default
do {
// Parse the URL as "rtsp://<username>[:<password>]@<whatever>"
char const* prefix = "rtsp://";
unsigned const prefixLength = 7;
if (_strncasecmp(url, prefix, prefixLength) != 0) break;
// Look for the ':' and '@':
unsigned usernameIndex = prefixLength;
unsigned colonIndex = 0, atIndex = 0;
for (unsigned i = usernameIndex; url[i] != '\0' && url[i] != '/'; ++i) {
if (url[i] == ':' && colonIndex == 0) {
colonIndex = i;
} else if (url[i] == '@') {
atIndex = i;
break; // we're done
}
}
if (atIndex == 0) break; // no '@' found
char* urlCopy = strDup(url);
urlCopy[atIndex] = '\0';
if (colonIndex > 0) {
urlCopy[colonIndex] = '\0';
password = strDup(&urlCopy[colonIndex+1]);
} else {
password = strDup("");
}
username = strDup(&urlCopy[usernameIndex]);
delete[] urlCopy;
return True;
} while (0);
return False;
}
void RTSPClient::responseHandlerForSyncInterface(RTSPClient* rtspClient, int responseCode, char* responseString) {
if (rtspClient != NULL) rtspClient->responseHandlerForSyncInterface1(responseCode, responseString);
}
void RTSPClient::responseHandlerForSyncInterface1(int responseCode, char* responseString) {
// If we have a 'timeout task' pending, then unschedule it:
if (fTimeoutTask != NULL) envir().taskScheduler().unscheduleDelayedTask(fTimeoutTask);
// Set result values:
fResultCode = responseCode;
fResultString = responseString;
// Signal a break from the event loop (thereby returning from the blocking command):
fWatchVariableForSyncInterface = ~0;
}
void RTSPClient::timeoutHandlerForSyncInterface(void* rtspClient) {
if (rtspClient != NULL) ((RTSPClient*)rtspClient)->timeoutHandlerForSyncInterface1();
}
void RTSPClient::timeoutHandlerForSyncInterface1() {
// A RTSP command has timed out, so we should have a queued request record. Disable it by setting its response handler to NULL.
// (Because this is a synchronous interface, there should be exactly one pending response handler - for "fCSeq".)
// all of them.)
changeResponseHandler(fCSeq, NULL);
fTimeoutTask = NULL;
// Fill in 'negative' return values:
fResultCode = ~0;
fResultString = NULL;
// Signal a break from the event loop (thereby returning from the blocking command):
fWatchVariableForSyncInterface = ~0;
}
#endif

View File

@@ -268,6 +268,30 @@ Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd,
return parseRangeParam(fields, rangeStart, rangeEnd, absStartTime, absEndTime);
}
Boolean parseScaleHeader(char const* buf, float& scale) {
// Initialize the result parameter to a default value:
scale = 1.0;
// First, find "Scale:"
while (1) {
if (*buf == '\0') return False; // not found
if (_strncasecmp(buf, "Scale:", 6) == 0) break;
++buf;
}
// Then, run through each of the fields, looking for ones we handle:
char const* fields = buf + 6;
while (*fields == ' ') ++fields;
float sc;
if (sscanf(fields, "%f", &sc) == 1) {
scale = sc;
} else {
return False; // The header is malformed
}
return True;
}
// Used to implement "RTSPOptionIsSupported()":
static Boolean isSeparator(char c) { return c == ' ' || c == ',' || c == ';' || c == ':'; }

View File

@@ -20,6 +20,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#include "RTSPServer.hh"
#include "RTSPCommon.hh"
#include "ProxyServerMediaSession.hh"
#include "Base64.hh"
#include <GroupsockHelper.hh>
@@ -110,6 +111,61 @@ void RTSPServer::deleteServerMediaSession(char const* streamName) {
deleteServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName)));
}
int RTSPServer::registerStream(ServerMediaSession* serverMediaSession,
char const* remoteClientNameOrAddress, Port remotePort,
responseHandlerForREGISTER* responseHandler) {
RegisterRequestRecord* registerRequest = new RegisterRequestRecord(*this, serverMediaSession, responseHandler);
do {
// Begin by resolving "remoteClientNameOrAddress" to an IP address (if it's not already one):
NetAddressList addresses(remoteClientNameOrAddress);
if (addresses.numAddresses() == 0) {
envir() << "Failed to find network address for \"" << remoteClientNameOrAddress << "\"";
break;
}
netAddressBits remoteAddress = *(unsigned*)(addresses.firstAddress()->data());
// Try to connect to this address, with the specified port:
int& sock = registerRequest->socketNum(); // alias
sock = setupStreamSocket(envir(), 0);
if (sock < 0) break;
ignoreSigPipeOnSocket(sock); // so that servers on the same host that get killed don't also kill us
MAKE_SOCKADDR_IN(remoteSockaddr, remoteAddress, remotePort.num());
registerRequest->remoteAddress() = remoteSockaddr;
#ifdef DEBUG
fprintf(stderr, "REGISTER: connecting to %s (%s), port %u...\n",
remoteClientNameOrAddress, AddressString(remoteAddress).val(), ntohs(remotePort.num()));
#endif
if (connect(sock, (struct sockaddr*) &remoteSockaddr, sizeof remoteSockaddr) != 0) {
int const err = envir().getErrno();
if (err == EINPROGRESS || err == EWOULDBLOCK) {
// The connection is pending; we'll need to handle it later. Wait for our socket to be 'writable', or have an exception.
#ifdef DEBUG
fprintf(stderr, "...pending...\n");
#endif
envir().taskScheduler().
setBackgroundHandling(sock, SOCKET_WRITABLE|SOCKET_EXCEPTION,
(TaskScheduler::BackgroundHandlerProc*)&RegisterRequestRecord::connectionHandler, registerRequest);
return sock;
}
envir().setResultErrMsg("connect() failed: ");
#ifdef DEBUG
fprintf(stderr, "...Failed:%s\n", envir().getResultMsg());
#endif
break;
}
// Having connected to the remote site, use the socket to continue REGISTERing our stream:
return continueRegisterStream(registerRequest);
} while (0);
// An error occurred:
registerRequest->callResponseHandler(0, NULL);
return -1;
}
char* RTSPServer
::rtspURL(ServerMediaSession const* serverMediaSession, int clientSocket) const {
char* urlPrefix = rtspURLPrefix(clientSocket);
@@ -206,14 +262,25 @@ int RTSPServer::setUpOurSocket(UsageEnvironment& env, Port& ourPort) {
return -1;
}
Boolean RTSPServer
::specialClientAccessCheck(int /*clientSocket*/, struct sockaddr_in& /*clientAddr*/, char const* /*urlSuffix*/) {
char const* RTSPServer::allowedCommandNames() {
return "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER";
}
Boolean RTSPServer::weImplementREGISTER() {
// By default, servers do not implement our custom "REGISTER" command:
return False;
}
void RTSPServer::implementCmd_REGISTER(char const* /*url*/, char const* /*urlSuffix*/, int /*socketToRemoteServer*/) {
// By default, this function is a 'noop'
}
Boolean RTSPServer::specialClientAccessCheck(int /*clientSocket*/, struct sockaddr_in& /*clientAddr*/, char const* /*urlSuffix*/) {
// default implementation
return True;
}
Boolean RTSPServer
::specialClientUserAccessCheck(int /*clientSocket*/, struct sockaddr_in& /*clientAddr*/,
Boolean RTSPServer::specialClientUserAccessCheck(int /*clientSocket*/, struct sockaddr_in& /*clientAddr*/,
char const* /*urlSuffix*/, char const * /*username*/) {
// default implementation; no further access restrictions:
return True;
@@ -225,11 +292,12 @@ RTSPServer::RTSPServer(UsageEnvironment& env,
UserAuthenticationDatabase* authDatabase,
unsigned reclamationTestSeconds)
: Medium(env),
fRTSPServerSocket(ourSocket), fRTSPServerPort(ourPort), fHTTPServerSocket(-1), fHTTPServerPort(0),
fRTSPServerPort(ourPort), fRTSPServerSocket(ourSocket), fHTTPServerSocket(-1), fHTTPServerPort(0),
fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
fClientConnectionsForHTTPTunneling(NULL), // will get created if needed
fClientSessions(HashTable::create(STRING_HASH_KEYS)),
fPendingRegisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)),
fAuthDB(authDatabase), fReclamationTestSeconds(reclamationTestSeconds) {
ignoreSigPipeOnSocket(ourSocket); // so that clients on the same host that are killed don't also kill us
@@ -267,6 +335,13 @@ RTSPServer::~RTSPServer() {
removeServerMediaSession(serverMediaSession); // will delete it, because it no longer has any 'client session' objects using it
}
delete fServerMediaSessions;
// Delete any pending REGISTER requests:
RTSPServer::RegisterRequestRecord* registerRequest;
while ((registerRequest = (RTSPServer::RegisterRequestRecord*)fPendingRegisterRequests->getFirst()) != NULL) {
delete registerRequest;
}
delete fPendingRegisterRequests;
}
Boolean RTSPServer::isRTSPServer() const {
@@ -311,6 +386,31 @@ void RTSPServer::incomingConnectionHandler(int serverSocket) {
(void)createNewClientConnection(clientSocket, clientAddr);
}
int RTSPServer::continueRegisterStream(RegisterRequestRecord* registerRequest) {
// Now that a connection has been set up to the remote endpoint, construct and send the "REGISTER" command:
int& sock = registerRequest->socketNum(); // alias
char const* const formatStr =
"REGISTER %s RTSP/1.0\r\n"
"CSeq: 1\r\n\r\n";
char* ourURL = rtspURL(registerRequest->serverMediaSession(), sock);
char* cmdBuffer = new char[strlen(formatStr) + strlen(ourURL)]; // big enough to hold the command
sprintf(cmdBuffer, formatStr, ourURL);
delete[] ourURL;
#ifdef DEBUG
fprintf(stderr, "Sending command: %s", cmdBuffer);
#endif
send(sock, (char const*)cmdBuffer, strlen(cmdBuffer), 0);
delete[] cmdBuffer;
// Having sent the "REGISTER" command, be prepared to handle a response:
envir().taskScheduler().
setBackgroundHandling(sock, SOCKET_READABLE|SOCKET_EXCEPTION,
(TaskScheduler::BackgroundHandlerProc*)&RegisterRequestRecord::incomingResponseHandler, registerRequest);
return sock;
}
////////// RTSPServer::RTSPClientConnection implementation //////////
@@ -341,15 +441,23 @@ RTSPServer::RTSPClientConnection::~RTSPClientConnection() {
closeSockets();
}
// Handler routines for specific RTSP commands:
// Special mechanism for handling our custom "REGISTER" command:
static char const* allowedCommandNames
= "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER";
RTSPServer::RTSPClientConnection::ParamsForREGISTER
::ParamsForREGISTER(RTSPServer::RTSPClientConnection* ourConnection, char const* url, char const* urlSuffix, Boolean registerRemote)
: fOurConnection(ourConnection), fURL(strDup(url)), fURLSuffix(strDup(urlSuffix)), fRegisterRemote(registerRemote) {
}
RTSPServer::RTSPClientConnection::ParamsForREGISTER::~ParamsForREGISTER() {
delete[] fURL; delete[] fURLSuffix;
}
// Handler routines for specific RTSP commands:
void RTSPServer::RTSPClientConnection::handleCmd_OPTIONS() {
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sPublic: %s\r\n\r\n",
fCurrentCSeq, dateHeader(), allowedCommandNames);
fCurrentCSeq, dateHeader(), fOurServer.allowedCommandNames());
}
void RTSPServer::RTSPClientConnection
@@ -451,17 +559,30 @@ static void lookForHeader(char const* headerName, char const* source, unsigned s
}
}
void RTSPServer::RTSPClientConnection::handleCmd_REGISTER(char const* url, char const* urlSuffix, Boolean registerRemote) {
if (fOurServer.weImplementREGISTER()) {
// We implement the "REGISTER" command by first replying to it, then actually handling it
// (in a separate event-loop task, that will get called after the reply has been done):
setRTSPResponse("200 OK");
ParamsForREGISTER* registerParams = new ParamsForREGISTER(this, url, urlSuffix, registerRemote);
envir().taskScheduler().scheduleDelayedTask(0, (TaskFunc*)continueHandlingREGISTER, registerParams);
} else {
handleCmd_notSupported();
}
}
void RTSPServer::RTSPClientConnection::handleCmd_bad() {
// Don't do anything with "fCurrentCSeq", because it might be nonsense
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 400 Bad Request\r\n%sAllow: %s\r\n\r\n",
dateHeader(), allowedCommandNames);
dateHeader(), fOurServer.allowedCommandNames());
}
void RTSPServer::RTSPClientConnection::handleCmd_notSupported() {
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n%sAllow: %s\r\n\r\n",
fCurrentCSeq, dateHeader(), allowedCommandNames);
fCurrentCSeq, dateHeader(), fOurServer.allowedCommandNames());
}
void RTSPServer::RTSPClientConnection::handleCmd_notFound() {
@@ -673,14 +794,25 @@ void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
if (fClientOutputSocket != fClientInputSocket) {
// We're doing RTSP-over-HTTP tunneling, and input commands are assumed to have been Base64-encoded.
// We therefore Base64-decode as much of this new data as we can (i.e., up to a multiple of 4 bytes):
// We therefore Base64-decode as much of this new data as we can (i.e., up to a multiple of 4 bytes).
// But first, we remove any whitespace that may be in the input data:
unsigned toIndex = 0;
for (int fromIndex = 0; fromIndex < newBytesRead; ++fromIndex) {
char c = ptr[fromIndex];
if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) { // not 'whitespace': space,tab,CR,NL
ptr[toIndex++] = c;
}
}
newBytesRead = toIndex;
unsigned numBytesToDecode = fBase64RemainderCount + newBytesRead;
unsigned newBase64RemainderCount = numBytesToDecode%4;
numBytesToDecode -= newBase64RemainderCount;
if (numBytesToDecode > 0) {
ptr[newBytesRead] = '\0';
unsigned decodedSize;
unsigned char* decodedBytes = base64Decode((char const*)(ptr-fBase64RemainderCount), decodedSize);
unsigned char* decodedBytes = base64Decode((char const*)(ptr-fBase64RemainderCount), numBytesToDecode, decodedSize);
#ifdef DEBUG
fprintf(stderr, "Base64-decoded %d input bytes into %d new bytes:", numBytesToDecode, decodedSize);
for (unsigned k = 0; k < decodedSize; ++k) fprintf(stderr, "%c", decodedBytes[k]);
@@ -748,6 +880,12 @@ void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
// Handle the specified command (beginning by checking those that don't require session ids):
fCurrentCSeq = cseq;
if (strcmp(cmdName, "OPTIONS") == 0) {
// If the request included a "Session:" id, and it refers to a client session that's current ongoing, then use this
// command to indicate 'liveness' on that client session:
if (sessionIdStr[0] != '\0') {
clientSession = (RTSPServer::RTSPClientSession*)(fOurServer.fClientSessions->Lookup(sessionIdStr));
if (clientSession != NULL) clientSession->noteLiveness();
}
handleCmd_OPTIONS();
} else if (urlPreSuffix[0] == '\0' && urlSuffix[0] == '*' && urlSuffix[1] == '\0') {
// The special "*" URL means: an operation on the entire server. This works only for GET_PARAMETER and SET_PARAMETER:
@@ -793,6 +931,16 @@ void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
} else {
clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
}
} else if (strcmp(cmdName, "REGISTER") == 0 || strcmp(cmdName, "REGISTER_REMOTE") == 0) {
// Because - unlike other commands - an implementation of these commands needs the entire URL, we re-parse the
// command to get it:
char* url = strDupSize((char*)fRequestBuffer);
if (sscanf((char*)fRequestBuffer, "%*s %s", url) == 1) {
handleCmd_REGISTER(url, urlSuffix, strcmp(cmdName, "REGISTER_REMOTE") == 0);
} else {
handleCmd_bad();
}
delete[] url;
} else {
// The command is one that we don't handle:
handleCmd_notSupported();
@@ -1089,6 +1237,26 @@ void RTSPServer::RTSPClientConnection
}
}
void RTSPServer::RTSPClientConnection::continueHandlingREGISTER(ParamsForREGISTER* params) {
params->fOurConnection->continueHandlingREGISTER1(params);
}
void RTSPServer::RTSPClientConnection::continueHandlingREGISTER1(ParamsForREGISTER* params) {
int socketNumToBackEndServer = params->fRegisterRemote ? -1 : fClientOutputSocket;
RTSPServer* ourServer = &fOurServer; // copy the pointer now, in case we "delete this" below
if (socketNumToBackEndServer >= 0) {
// Because our socket will no longer be used by the server to handle incoming requests, we can now delete this
// "RTSPClientConnection" object. We do this now, in case the "implementCmd_REGISTER()" call below would also end up
// deleting this.
fClientInputSocket = fClientOutputSocket = -1; // so the socket doesn't get closed when we get deleted
delete this;
}
ourServer->implementCmd_REGISTER(params->fURL, params->fURLSuffix, socketNumToBackEndServer);
delete params;
}
////////// RTSPServer::RTSPClientSession implementation //////////
@@ -1551,30 +1719,6 @@ void RTSPServer::RTSPClientSession
if (noSubsessionsRemain) delete this;
}
static Boolean parseScaleHeader(char const* buf, float& scale) {
// Initialize the result parameter to a default value:
scale = 1.0;
// First, find "Scale:"
while (1) {
if (*buf == '\0') return False; // not found
if (_strncasecmp(buf, "Scale:", 6) == 0) break;
++buf;
}
// Then, run through each of the fields, looking for ones we handle:
char const* fields = buf + 6;
while (*fields == ' ') ++fields;
float sc;
if (sscanf(fields, "%f", &sc) == 1) {
scale = sc;
} else {
return False; // The header is malformed
}
return True;
}
void RTSPServer::RTSPClientSession
::handleCmd_PLAY(RTSPServer::RTSPClientConnection* ourClientConnection,
ServerMediaSubsession* subsession, char const* fullRequestStr) {
@@ -1913,3 +2057,164 @@ void UserAuthenticationDatabase::removeUserRecord(char const* username) {
char const* UserAuthenticationDatabase::lookupPassword(char const* username) {
return (char const*)(fTable->Lookup(username));
}
///////// RegisterRequestRecord implementation //////////
RTSPServer::RegisterRequestRecord
::RegisterRequestRecord(RTSPServer& ourServer, ServerMediaSession* serverMediaSession, responseHandlerForREGISTER* responseHandler)
: fSocketNum(-1), fOurServer(ourServer), fServerMediaSession(serverMediaSession), fHandler(responseHandler) {
// Add ourself to our 'pending REGISTER requests' table:
fOurServer.fPendingRegisterRequests->Add((char const*)this, this);
}
RTSPServer::RegisterRequestRecord::~RegisterRequestRecord() {
// Remove ourself from the server's 'pending REGISTER requests' hash table before we go:
fOurServer.fPendingRegisterRequests->Remove((char const*)this);
if (fSocketNum >= 0) {
envir().taskScheduler().disableBackgroundHandling(fSocketNum);
::closeSocket(fSocketNum);
}
}
void RTSPServer::RegisterRequestRecord::connectionHandler(void* instance, int /*mask*/) {
RegisterRequestRecord* registerRequest = (RegisterRequestRecord*)instance;
registerRequest->connectionHandler1();
}
void RTSPServer::RegisterRequestRecord::connectionHandler1() {
// A connection to the remote endpoint has either been set up, or failed.
// Disable background handling on the socket for now, and check whether the connection succeeded.
// (If it did, continue to send the "REGISTER" command.)
envir().taskScheduler().disableBackgroundHandling(fSocketNum);
int err = 0;
SOCKLEN_T len = sizeof err;
if (getsockopt(fSocketNum, SOL_SOCKET, SO_ERROR, (char*)&err, &len) < 0 || err != 0) {
envir().setResultErrMsg("Connection to server failed: ", err);
#ifdef DEBUG
fprintf(stderr, "...%s\n", envir().getResultMsg());
#endif
callResponseHandler(0, NULL);
return;
}
// The connection succeeded, so continue to send the "REGISTER" command:
(void)fOurServer.continueRegisterStream(this);
}
void RTSPServer::RegisterRequestRecord::incomingResponseHandler(void* instance, int /*mask*/) {
RegisterRequestRecord* registerRequest = (RegisterRequestRecord*)instance;
registerRequest->incomingResponseHandler1();
}
#define REGISTER_RESPONSE_BUFFER_SIZE 1000
void RTSPServer::RegisterRequestRecord::incomingResponseHandler1() {
struct sockaddr_in dummy; // not used
unsigned char responseBuffer[REGISTER_RESPONSE_BUFFER_SIZE];
int resultCode = 0;
char* resultString = NULL;
int bytesRead = readSocket(envir(), fSocketNum, responseBuffer, REGISTER_RESPONSE_BUFFER_SIZE, dummy);
if (bytesRead > 0 && bytesRead < REGISTER_RESPONSE_BUFFER_SIZE) {
// We got the response OK. Because we expect it to be short, assume that we got the whole thing.
// Parse it, looking for "RTSP/<vers> <resultCode> <resultMsg>\r\n"
responseBuffer[bytesRead] = '\0';
#ifdef DEBUG
fprintf(stderr, "Received (%d-byte) REGISTER response (from %s):%s\n", bytesRead, AddressString(fRemoteAddress).val(), responseBuffer);
#endif
char resultMsg[REGISTER_RESPONSE_BUFFER_SIZE];
if (sscanf((char*)responseBuffer, "%*s %d %[^\r\n]", &resultCode, resultMsg) == 2) {
resultString = resultMsg;
if (resultCode == 200) {
// The REGISTER command succeeded, so use the still-open socket to await incoming commands from the remote endpoint:
int sock = fSocketNum; fSocketNum = -1; // so that the socket doesn't get closed when we delete ourself below
(void)fOurServer.createNewClientConnection(sock, fRemoteAddress);
}
}
}
callResponseHandler(resultCode, resultString);
}
void RTSPServer::RegisterRequestRecord::callResponseHandler(int resultCode, char const* resultString) {
if (fHandler != NULL) {
// Call the specified response handler. But first, make sure that we have a reasonable "resultCode" and "resultString":
if (resultCode == 0) {
resultCode = -envir().getErrno();
if (resultCode == 0) {
// Choose some generic error code instead:
#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
resultCode = -WSAENOTCONN;
#else
resultCode = -ENOTCONN;
#endif
}
resultString = envir().getResultMsg();
}
char* newResultString = strDup(resultString); // because the handler expects a result string that's been heap allocated
(*fHandler)(&fOurServer, fSocketNum, resultCode, newResultString);
}
// We're completely done with the REGISTER command now, so delete ourself:
delete this;
}
///////// RTSPServerWithREGISTERProxying implementation /////////
RTSPServerWithREGISTERProxying* RTSPServerWithREGISTERProxying
::createNew(UsageEnvironment& env, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) {
int ourSocket = setUpOurSocket(env, ourPort);
if (ourSocket == -1) return NULL;
return new RTSPServerWithREGISTERProxying(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
}
RTSPServerWithREGISTERProxying
::RTSPServerWithREGISTERProxying(UsageEnvironment& env, int ourSocket, Port ourPort,
UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
: RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds),
fAllowedCommandNames(NULL) {
}
RTSPServerWithREGISTERProxying::~RTSPServerWithREGISTERProxying() {
delete[] fAllowedCommandNames;
}
char const* RTSPServerWithREGISTERProxying::allowedCommandNames() {
if (fAllowedCommandNames == NULL) {
char const* baseAllowedCommandNames = RTSPServer::allowedCommandNames();
char const* newAllowedCommandName = ", REGISTER";
fAllowedCommandNames = new char[strlen(baseAllowedCommandNames) + strlen(newAllowedCommandName) + 1/* for '\0' */];
sprintf(fAllowedCommandNames, "%s%s", baseAllowedCommandNames, newAllowedCommandName);
}
return fAllowedCommandNames;
}
Boolean RTSPServerWithREGISTERProxying::weImplementREGISTER() {
return True;
}
void RTSPServerWithREGISTERProxying::implementCmd_REGISTER(char const* url, char const* urlSuffix, int socketToRemoteServer) {
// Continue setting up proxying for the specified URL.
// By default:
// - We use the "urlSuffix" as the (front-end) stream name. (This means that if two or more REGISTERs are done for different
// URLs that have the same "urlSuffix", then each new one will replace the previous one.)
// - There is no 'username' and 'password' for the back-end stream. (Thus, access-controlled back-end streams will fail.)
// - If "registerRemote" is False, we pass our current TCP connection (socket) to the proxy, allowing it to use this
// existing connection to access the back-end stream.
// To change this default behavior, you will need to subclass "RTSPServerWithREGISTERProxying", and reimplement this function.
#ifdef DEBUG
int const verbosityLevel = 2;
#else
int const verbosityLevel = 0;
#endif
addServerMediaSession(ProxyServerMediaSession::createNew(envir(), this, url, urlSuffix,
NULL, NULL, 0, verbosityLevel, socketToRemoteServer));
}

View File

@@ -30,6 +30,12 @@ unsigned char* base64Decode(char const* in, unsigned& resultSize,
// returns a newly allocated array - of size "resultSize" - that
// the caller is responsible for delete[]ing.
unsigned char* base64Decode(char const* in, unsigned inSize,
unsigned& resultSize,
Boolean trimTrailingZeros = True);
// As above, but includes the size of the input string (i.e., the number of bytes to decode) as a parameter.
// This saves an extra call to "strlen()" if we already know the length of the input string.
char* base64Encode(char const* orig, unsigned origLength);
// returns a 0-terminated string that
// the caller is responsible for delete[]ing.

View File

@@ -28,6 +28,9 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#ifndef _MEDIA_SESSION_HH
#include "MediaSession.hh"
#endif
#ifndef _RTSP_CLIENT_HH
#include "RTSPClient.hh"
#endif
// A subclass of "RTSPClient", used to refer to the particular "ProxyServerMediaSession" object being used.
// It is used only within the implementation of "ProxyServerMediaSession", but is defined here, in case developers wish to
@@ -37,7 +40,7 @@ class ProxyRTSPClient: public RTSPClient {
public:
ProxyRTSPClient(class ProxyServerMediaSession& ourServerMediaSession, char const* rtspURL,
char const* username, char const* password,
portNumBits tunnelOverHTTPPortNum, int verbosityLevel);
portNumBits tunnelOverHTTPPortNum, int verbosityLevel, int socketNumToServer);
virtual ~ProxyRTSPClient();
void continueAfterDESCRIBE(char const* sdpDescription);
@@ -82,9 +85,13 @@ public:
char const* username = NULL, char const* password = NULL,
portNumBits tunnelOverHTTPPortNum = 0,
// for streaming the *proxied* (i.e., back-end) stream
int verbosityLevel = 0);
int verbosityLevel = 0,
int socketNumToServer = -1);
// Hack: "tunnelOverHTTPPortNum" == 0xFFFF (i.e., all-ones) means: Stream RTP/RTCP-over-TCP, but *not* using HTTP
// "verbosityLevel" == 1 means display basic proxy setup info; "verbosityLevel" == 2 means display RTSP client protocol also.
// If "socketNumToServer" is >= 0, then it is the socket number of an already-existing TCP connection to the server.
// (In this case, "inputStreamURL" must point to the socket's endpoint, so that it can be accessed via the socket.)
virtual ~ProxyServerMediaSession();
char const* url() const;
@@ -98,12 +105,13 @@ public:
protected:
ProxyServerMediaSession(UsageEnvironment& env, RTSPServer* ourRTSPServer,
char const* inputStreamURL, char const* streamName,
char const* username, char const* password, portNumBits tunnelOverHTTPPortNum, int verbosityLevel);
char const* username, char const* password,
portNumBits tunnelOverHTTPPortNum, int verbosityLevel, int socketNumToServer);
// If you subclass "ProxyRTSPClient", then you should also subclass "ProxyServerMediaSession" and redefine this virtual function
// in order to create new objects of your "ProxyRTSPClient" subclass:
virtual ProxyRTSPClient* createNewProxyRTSPClient(char const* rtspURL, char const* username, char const* password,
portNumBits tunnelOverHTTPPortNum, int verbosityLevel);
portNumBits tunnelOverHTTPPortNum, int verbosityLevel, int socketNumToServer);
protected:
RTSPServer* fOurRTSPServer;

View File

@@ -30,16 +30,22 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#ifndef _DIGEST_AUTHENTICATION_HH
#include "DigestAuthentication.hh"
#endif
#ifndef _RTSP_SERVER_HH
#include "RTSPServer.hh" // For the optional "HandlerForREGISTERCommand" mini-server
#endif
class RTSPClient: public Medium {
public:
static RTSPClient* createNew(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel = 0,
char const* applicationName = NULL,
portNumBits tunnelOverHTTPPortNum = 0);
portNumBits tunnelOverHTTPPortNum = 0,
int socketNumToServer = -1);
// If "tunnelOverHTTPPortNum" is non-zero, we tunnel RTSP (and RTP)
// over a HTTP connection with the given port number, using the technique
// described in Apple's document <http://developer.apple.com/documentation/QuickTime/QTSS/Concepts/chapter_2_section_14.html>
// over a HTTP connection with the given port number, using the technique
// described in Apple's document <http://developer.apple.com/documentation/QuickTime/QTSS/Concepts/chapter_2_section_14.html>
// If "socketNumToServer" is >= 0, then it is the socket number of an already-existing TCP connection to the server.
// (In this case, "rtspURL" must point to the socket's endpoint, so that it can be accessed via the socket.)
typedef void (responseHandler)(RTSPClient* rtspClient,
int resultCode, char* resultString);
@@ -208,7 +214,7 @@ public: // Some compilers complain if this is "private:"
protected:
RTSPClient(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum);
int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum, int socketNumToServer);
// called only by createNew();
virtual ~RTSPClient();
@@ -305,67 +311,43 @@ private:
char fSessionCookie[33];
unsigned fSessionCookieCounter;
Boolean fHTTPTunnelingConnectionIsPending;
};
#ifdef RTSPCLIENT_SYNCHRONOUS_INTERFACE
// Old "RTSPClient" interface, which performs synchronous (blocking) operations.
// This will eventually go away, so new applications should not use it.
////////// HandlerServerForREGISTERCommand /////////
// A simple server that creates a new "RTSPClient" object whenever a "REGISTER" request arrives (specifying the "rtsp://" URL
// of a stream). The new "RTSPClient" object will be created with the specified URL, and passed to the provided handler function.
typedef void onRTSPClientCreationFunc(RTSPClient* newRTSPClient);
class HandlerServerForREGISTERCommand: public RTSPServer {
public:
static RTSPClient* createNew(UsageEnvironment& env,
int verbosityLevel = 0,
char const* applicationName = NULL,
portNumBits tunnelOverHTTPPortNum = 0);
char* describeURL(char const* url, Authenticator* authenticator = NULL,
Boolean allowKasennaProtocol = False, int timeout = -1);
char* describeWithPassword(char const* url,
char const* username, char const* password,
Boolean allowKasennaProtocol = False,
int timeout = -1);
char* sendOptionsCmd(char const* url,
char* username = NULL, char* password = NULL,
Authenticator* authenticator = NULL,
int timeout = -1);
Boolean announceSDPDescription(char const* url,
char const* sdpDescription,
Authenticator* authenticator = NULL,
int timeout = -1);
Boolean announceWithPassword(char const* url, char const* sdpDescription,
char const* username, char const* password, int timeout = -1);
Boolean setupMediaSubsession(MediaSubsession& subsession,
Boolean streamOutgoing = False,
Boolean streamUsingTCP = False,
Boolean forceMulticastOnUnspecified = False);
Boolean playMediaSession(MediaSession& session,
double start = 0.0f, double end = -1.0f,
float scale = 1.0f);
Boolean playMediaSubsession(MediaSubsession& subsession,
double start = 0.0f, double end = -1.0f,
float scale = 1.0f,
Boolean hackForDSS = False);
Boolean pauseMediaSession(MediaSession& session);
Boolean pauseMediaSubsession(MediaSubsession& subsession);
Boolean recordMediaSubsession(MediaSubsession& subsession);
Boolean setMediaSessionParameter(MediaSession& session,
char const* parameterName,
char const* parameterValue);
Boolean getMediaSessionParameter(MediaSession& session,
char const* parameterName,
char*& parameterValue);
Boolean teardownMediaSession(MediaSession& session);
Boolean teardownMediaSubsession(MediaSubsession& subsession);
static HandlerServerForREGISTERCommand* createNew(UsageEnvironment& env, onRTSPClientCreationFunc* creationFunc,
Port ourPort = 0, int verbosityLevel = 0, char const* applicationName = NULL);
// If ourPort.num() == 0, we'll choose the port number ourself. (Use the following function to get it.)
portNumBits serverPortNum() const { return ntohs(fRTSPServerPort.num()); }
static Boolean parseRTSPURLUsernamePassword(char const* url,
char*& username, char*& password);
private: // used to implement the old interface:
static void responseHandlerForSyncInterface(RTSPClient* rtspClient,
int responseCode, char* responseString);
void responseHandlerForSyncInterface1(int responseCode, char* responseString);
static void timeoutHandlerForSyncInterface(void* rtspClient);
void timeoutHandlerForSyncInterface1();
TaskToken fTimeoutTask;
char fWatchVariableForSyncInterface;
char* fResultString;
int fResultCode;
#endif
protected:
HandlerServerForREGISTERCommand(UsageEnvironment& env, onRTSPClientCreationFunc* creationFunc, int ourSocket, Port ourPort,
int verbosityLevel, char const* applicationName);
// called only by createNew();
virtual ~HandlerServerForREGISTERCommand();
virtual RTSPClient* createNewRTSPClient(char const* rtspURL, int verbosityLevel, char const* applicationName,
int socketNumToServer);
// This function - by default - creates a (base) "RTSPClient" object. If you want to create a subclass
// of "RTSPClient" instead, then subclass this class, and redefine this virtual function.
protected: // redefined virtual functions
virtual char const* allowedCommandNames(); // we support "OPTIONS" and "REGISTER" only
virtual Boolean weImplementREGISTER(); // redefined to return True
virtual void implementCmd_REGISTER(char const* url, char const* urlSuffix, int socketToRemoteServer);
private:
onRTSPClientCreationFunc* fCreationFunc;
int fVerbosityLevel;
char* fApplicationName;
};
#endif

View File

@@ -54,6 +54,8 @@ Boolean parseRTSPRequestString(char const *reqStr, unsigned reqStrSize,
Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime);
Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime);
Boolean parseScaleHeader(char const* buf, float& scale);
Boolean RTSPOptionIsSupported(char const* commandName, char const* optionsResponseString);
// Returns True iff the RTSP command "commandName" is mentioned as one of the commands supported in "optionsResponseString"
// (which should be the 'resultString' from a previous RTSP "OPTIONS" request).

View File

@@ -102,6 +102,18 @@ public:
// Equivalent to:
// "closeAllClientSessionsForServerMediaSession(streamName); removeServerMediaSession(streamName);
typedef void (responseHandlerForREGISTER)(RTSPServer* rtspServer, int socketNum,
int resultCode, char* resultString);
int registerStream(ServerMediaSession* serverMediaSession,
char const* remoteClientNameOrAddress, Port remotePortNumber,
responseHandlerForREGISTER* responseHandler);
// 'Register' the stream represented by "serverMediaSession" with the given remote client (specifed by name and port number).
// This is done using our custom "REGISTER" RTSP command.
// The function returns the socket number from the (still-open) connection, or -1 if no connection could be opened.
// When a response is received from the remote client (or the "REGISTER" request fails), the specified response handler
// (if non-NULL) is called (with the socket number as parameter). (Note that the "resultString" passed to the handler was
// dynamically allocated, and should be delete[]d by the handler after use.)
char* rtspURL(ServerMediaSession const* serverMediaSession, int clientSocket = -1) const;
// returns a "rtsp://" URL that could be used to access the
// specified session (which must already have been added to
@@ -134,6 +146,10 @@ protected:
static int setUpOurSocket(UsageEnvironment& env, Port& ourPort);
virtual char const* allowedCommandNames(); // used to implement "RTSPClientConnection::handleCmd_OPTIONS()"
virtual Boolean weImplementREGISTER(); // used to implement "RTSPClientConnection::handleCmd_REGISTER()"
virtual void implementCmd_REGISTER(char const* url, char const* urlSuffix, int socketToRemoteServer); // ditto
virtual Boolean specialClientAccessCheck(int clientSocket, struct sockaddr_in& clientAddr,
char const* urlSuffix);
// a hook that allows subclassed servers to do server-specific access checking
@@ -154,14 +170,30 @@ public: // should be protected, but some old compilers complain otherwise
public:
RTSPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr);
virtual ~RTSPClientConnection();
// A data structure that's used to implement the "REGISTER" command:
class ParamsForREGISTER {
public:
ParamsForREGISTER(RTSPClientConnection* ourConnection, char const* url, char const* urlSuffix, Boolean registerRemote);
virtual ~ParamsForREGISTER();
private:
friend class RTSPClientConnection;
RTSPClientConnection* fOurConnection;
char* fURL;
char* fURLSuffix;
Boolean fRegisterRemote;
};
protected:
friend class RTSPClientSession;
// Make the handler functions for each command virtual, to allow subclasses to redefine them:
// Make the handler functions for each command virtual, to allow subclasses to reimplement them, if necessary:
virtual void handleCmd_OPTIONS();
// You probably won't need to subclass/reimplement this function; reimplement "RTSPServer::allowedCommandNames()" instead.
virtual void handleCmd_GET_PARAMETER(char const* fullRequestStr); // when operating on the entire server
virtual void handleCmd_SET_PARAMETER(char const* fullRequestStr); // when operating on the entire server
virtual void handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix,
char const* fullRequestStr);
virtual void handleCmd_REGISTER(char const* url, char const* urlSuffix, Boolean registerRemote);
// You probably won't need to subclass/reimplement this function;
// reimplement "RTSPServer::weImplementREGISTER()" and "RTSPServer::implementCmd_REGISTER()" instead.
virtual void handleCmd_bad();
virtual void handleCmd_notSupported();
virtual void handleCmd_notFound();
@@ -189,12 +221,15 @@ public: // should be protected, but some old compilers complain otherwise
Boolean authenticationOK(char const* cmdName, char const* urlSuffix, char const* fullRequestStr);
void changeClientInputSocket(int newSocketNum, unsigned char const* extraData, unsigned extraDataSize);
// used to implement RTSP-over-HTTP tunneling
static void continueHandlingREGISTER(ParamsForREGISTER* params);
virtual void continueHandlingREGISTER1(ParamsForREGISTER* params);
// Shortcuts for setting up a RTSP response (prior to sending it):
void setRTSPResponse(char const* responseStr);
void setRTSPResponse(char const* responseStr, u_int32_t sessionId);
void setRTSPResponse(char const* responseStr, char const* contentStr);
void setRTSPResponse(char const* responseStr, u_int32_t sessionId, char const* contentStr);
protected:
RTSPServer& fOurServer;
Boolean fIsActive;
int fClientInputSocket, fClientOutputSocket;
@@ -242,6 +277,13 @@ public: // should be protected, but some old compilers complain otherwise
void noteLiveness();
static void noteClientLiveness(RTSPClientSession* clientSession);
static void livenessTimeoutTask(RTSPClientSession* clientSession);
// Shortcuts for setting up a RTSP response (prior to sending it):
void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr) { ourClientConnection->setRTSPResponse(responseStr); }
void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr, u_int32_t sessionId) { ourClientConnection->setRTSPResponse(responseStr, sessionId); }
void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr, char const* contentStr) { ourClientConnection->setRTSPResponse(responseStr, contentStr); }
void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr, u_int32_t sessionId, char const* contentStr) { ourClientConnection->setRTSPResponse(responseStr, sessionId, contentStr); }
protected:
RTSPServer& fOurServer;
u_int32_t fOurSessionId;
@@ -287,12 +329,46 @@ private:
void incomingConnectionHandler(int serverSocket);
public: // Some compilers complain if this is "private:"
// A class that represents the state of a "REGISTER" request in progress:
class RegisterRequestRecord {
public:
RegisterRequestRecord(RTSPServer& ourServer, ServerMediaSession* serverMediaSession, responseHandlerForREGISTER* responseHandler);
virtual ~RegisterRequestRecord();
UsageEnvironment& envir() { return fOurServer.envir(); }
int& socketNum() { return fSocketNum; }
struct sockaddr_in& remoteAddress() { return fRemoteAddress; }
ServerMediaSession* serverMediaSession() { return fServerMediaSession; }
static void connectionHandler(void*, int /*mask*/);
static void incomingResponseHandler(void*, int /*mask*/);
void callResponseHandler(int resultCode, char const* resultString);
private:
void connectionHandler1();
void incomingResponseHandler1();
private:
int fSocketNum;
struct sockaddr_in fRemoteAddress;
RTSPServer& fOurServer;
ServerMediaSession* fServerMediaSession;
responseHandlerForREGISTER* fHandler;
};
private:
int continueRegisterStream(RegisterRequestRecord* registerRequest);
protected:
Port fRTSPServerPort;
private:
friend class RTSPClientConnection;
friend class RTSPClientSession;
friend class ServerMediaSessionIterator;
friend class RegisterRequestRecord;
int fRTSPServerSocket;
Port fRTSPServerPort;
int fHTTPServerSocket; // for optional RTSP-over-HTTP tunneling
Port fHTTPServerPort; // ditto
HashTable* fServerMediaSessions; // maps 'stream name' strings to "ServerMediaSession" objects
@@ -300,8 +376,33 @@ private:
HashTable* fClientConnectionsForHTTPTunneling; // maps client-supplied 'session cookie' strings to "RTSPClientConnection"s
// (used only for optional RTSP-over-HTTP tunneling)
HashTable* fClientSessions; // maps 'session id' strings to "RTSPClientSession" objects
HashTable* fPendingRegisterRequests;
UserAuthenticationDatabase* fAuthDB;
unsigned fReclamationTestSeconds;
};
////////// A subclass of "RTSPServer" that implements the "REGISTER" command to set up proxying on the specified URL //////////
class RTSPServerWithREGISTERProxying: public RTSPServer {
public:
static RTSPServerWithREGISTERProxying* createNew(UsageEnvironment& env, Port ourPort = 554,
UserAuthenticationDatabase* authDatabase = NULL,
unsigned reclamationTestSeconds = 65);
protected:
RTSPServerWithREGISTERProxying(UsageEnvironment& env, int ourSocket, Port ourPort,
UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds);
// called only by createNew();
virtual ~RTSPServerWithREGISTERProxying();
protected: // redefined virtual functions
virtual char const* allowedCommandNames();
virtual Boolean weImplementREGISTER(); // used to implement "RTSPClientConnection::handleCmd_REGISTER()"
virtual void implementCmd_REGISTER(char const* url, char const* urlSuffix, int socketToRemoteServer); // ditto
private:
char* fAllowedCommandNames;
};
#endif

View File

@@ -4,7 +4,7 @@
#ifndef _LIVEMEDIA_VERSION_HH
#define _LIVEMEDIA_VERSION_HH
#define LIVEMEDIA_LIBRARY_VERSION_STRING "2013.04.30"
#define LIVEMEDIA_LIBRARY_VERSION_INT 1367280000
#define LIVEMEDIA_LIBRARY_VERSION_STRING "2013.07.16"
#define LIVEMEDIA_LIBRARY_VERSION_INT 1373932800
#endif

113
modifications.patch Normal file
View File

@@ -0,0 +1,113 @@
diff -rupN --exclude=.git live555-orig/liveMedia/MultiFramedRTPSource.cpp live555/liveMedia/MultiFramedRTPSource.cpp
--- live555-orig/liveMedia/MultiFramedRTPSource.cpp 2013-07-29 20:12:22.000000000 +0100
+++ live555/liveMedia/MultiFramedRTPSource.cpp 2013-07-29 20:09:32.000000000 +0100
@@ -72,7 +72,7 @@ MultiFramedRTPSource
fReorderingBuffer = new ReorderingPacketBuffer(packetFactory);
// Try to use a big receive buffer for RTP:
- increaseReceiveBufferTo(env, RTPgs->socketNum(), 50*1024);
+ increaseReceiveBufferTo(env, RTPgs->socketNum(), 2000000);
}
void MultiFramedRTPSource::reset() {
diff -rupN --exclude=.git live555-orig/modifications.patch live555/modifications.patch
--- live555-orig/modifications.patch 1970-01-01 01:00:00.000000000 +0100
+++ live555/modifications.patch 2013-07-29 20:19:15.000000000 +0100
@@ -0,0 +1,12 @@
+diff -rupN --exclude=.git live555-orig/liveMedia/MultiFramedRTPSource.cpp live555/liveMedia/MultiFramedRTPSource.cpp
+--- live555-orig/liveMedia/MultiFramedRTPSource.cpp 2013-07-29 20:12:22.000000000 +0100
++++ live555/liveMedia/MultiFramedRTPSource.cpp 2013-07-29 20:09:32.000000000 +0100
+@@ -72,7 +72,7 @@ MultiFramedRTPSource
+ fReorderingBuffer = new ReorderingPacketBuffer(packetFactory);
+
+ // Try to use a big receive buffer for RTP:
+- increaseReceiveBufferTo(env, RTPgs->socketNum(), 50*1024);
++ increaseReceiveBufferTo(env, RTPgs->socketNum(), 2000000);
+ }
+
+ void MultiFramedRTPSource::reset() {
diff -rupN --exclude=.git live555-orig/proxyServer/live555ProxyServer.cpp live555/proxyServer/live555ProxyServer.cpp
--- live555-orig/proxyServer/live555ProxyServer.cpp 2013-07-29 20:12:22.000000000 +0100
+++ live555/proxyServer/live555ProxyServer.cpp 2013-07-29 20:15:44.000000000 +0100
@@ -28,6 +28,7 @@ UserAuthenticationDatabase* authDB;
int verbosityLevel = 0;
Boolean streamRTPOverTCP = False;
portNumBits tunnelOverHTTPPortNum = 0;
+portNumBits rtspServerPortNum = 554;
char* username = NULL;
char* password = NULL;
Boolean proxyREGISTERRequests = False;
@@ -44,6 +45,7 @@ void usage() {
*env << "Usage: " << progName
<< " [-v|-V]"
<< " [-t|-T <http-port>]"
+ << " [-p <rtsp-port>]"
<< " [-u <username> <password>]"
<< " [-R]"
<< " <rtsp-url-1> ... <rtsp-url-n>\n";
@@ -53,7 +55,7 @@ void usage() {
int main(int argc, char** argv) {
// Increase the maximum size of video frames that we can 'proxy' without truncation.
// (Such frames are unreasonably large; the back-end servers should really not be sending frames this large!)
- OutPacketBuffer::maxSize = 100000; // bytes
+ OutPacketBuffer::maxSize = 500000; // bytes
// Begin by setting up our usage environment:
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
@@ -92,12 +94,28 @@ int main(int argc, char** argv) {
case 'T': {
// stream RTP and RTCP over a HTTP connection
if (argc > 3 && argv[2][0] != '-') {
- // The next argument is the HTTP server port number:
- if (sscanf(argv[2], "%hu", &tunnelOverHTTPPortNum) == 1
- && tunnelOverHTTPPortNum > 0) {
- ++argv; --argc;
- break;
- }
+ // The next argument is the HTTP server port number:
+ if (sscanf(argv[2], "%hu", &tunnelOverHTTPPortNum) == 1
+ && tunnelOverHTTPPortNum > 0) {
+ ++argv; --argc;
+ break;
+ }
+ }
+
+ // If we get here, the option was specified incorrectly:
+ usage();
+ break;
+ }
+
+ case 'p': {
+ // set port
+ if (argc > 3 && argv[2][0] != '-') {
+ // The next argument is the RTSP server port number:
+ if (sscanf(argv[2], "%hu", &rtspServerPortNum) == 1
+ && rtspServerPortNum > 0) {
+ ++argv; --argc;
+ break;
+ }
}
// If we get here, the option was specified incorrectly:
@@ -157,10 +175,6 @@ int main(int argc, char** argv) {
portNumBits rtspServerPortNum = 554;
rtspServer = createRTSPServer(rtspServerPortNum);
if (rtspServer == NULL) {
- rtspServerPortNum = 8554;
- rtspServer = createRTSPServer(rtspServerPortNum);
- }
- if (rtspServer == NULL) {
*env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
exit(1);
}
@@ -176,8 +190,8 @@ int main(int argc, char** argv) {
}
ServerMediaSession* sms
= ProxyServerMediaSession::createNew(*env, rtspServer,
- proxiedStreamURL, streamName,
- username, password, tunnelOverHTTPPortNum, verbosityLevel);
+ proxiedStreamURL, streamName,
+ username, password, tunnelOverHTTPPortNum, verbosityLevel);
rtspServer->addServerMediaSession(sms);
char* proxyStreamURL = rtspServer->rtspURL(sms);

View File

@@ -22,6 +22,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
char const* progName;
UsageEnvironment* env;
UserAuthenticationDatabase* authDB;
// Default values of command-line parameters:
int verbosityLevel = 0;
@@ -30,6 +31,15 @@ portNumBits tunnelOverHTTPPortNum = 0;
portNumBits rtspServerPortNum = 554;
char* username = NULL;
char* password = NULL;
Boolean proxyREGISTERRequests = False;
static RTSPServer* createRTSPServer(Port port) {
if (proxyREGISTERRequests) {
return RTSPServerWithREGISTERProxying::createNew(*env, port, authDB);
} else {
return RTSPServer::createNew(*env, port, authDB);
}
}
void usage() {
*env << "Usage: " << progName
@@ -37,6 +47,7 @@ void usage() {
<< " [-t|-T <http-port>]"
<< " [-p <rtsp-port>]"
<< " [-u <username> <password>]"
<< " [-R]"
<< " <rtsp-url-1> ... <rtsp-url-n>\n";
exit(1);
}
@@ -63,72 +74,77 @@ int main(int argc, char** argv) {
if (opt[0] != '-') break; // the remaining parameters are assumed to be "rtsp://" URLs
switch (opt[1]) {
case 'v': { // verbose output
verbosityLevel = 1;
break;
case 'v': { // verbose output
verbosityLevel = 1;
break;
}
case 'V': { // more verbose output
verbosityLevel = 2;
break;
}
case 't': {
// Stream RTP and RTCP over the TCP 'control' connection.
// (This is for the 'back end' (i.e., proxied) stream only.)
streamRTPOverTCP = True;
break;
}
case 'T': {
// stream RTP and RTCP over a HTTP connection
if (argc > 3 && argv[2][0] != '-') {
// The next argument is the HTTP server port number:
if (sscanf(argv[2], "%hu", &tunnelOverHTTPPortNum) == 1
&& tunnelOverHTTPPortNum > 0) {
++argv; --argc;
break;
}
}
case 'V': { // more verbose output
verbosityLevel = 2;
break;
}
// If we get here, the option was specified incorrectly:
usage();
break;
}
case 't': {
// Stream RTP and RTCP over the TCP 'control' connection.
// (This is for the 'back end' (i.e., proxied) stream only.)
streamRTPOverTCP = True;
break;
}
case 'T': {
// stream RTP and RTCP over a HTTP connection
if (argc > 3 && argv[2][0] != '-') {
// The next argument is the HTTP server port number:
if (sscanf(argv[2], "%hu", &tunnelOverHTTPPortNum) == 1
&& tunnelOverHTTPPortNum > 0) {
case 'p': {
// set port
if (argc > 3 && argv[2][0] != '-') {
// The next argument is the RTSP server port number:
if (sscanf(argv[2], "%hu", &rtspServerPortNum) == 1
&& rtspServerPortNum > 0) {
++argv; --argc;
break;
}
}
// If we get here, the option was specified incorrectly:
usage();
break;
}
case 'p': {
// set port
if (argc > 3 && argv[2][0] != '-') {
// The next argument is the RTSP server port number:
if (sscanf(argv[2], "%hu", &rtspServerPortNum) == 1
&& rtspServerPortNum > 0) {
++argv; --argc;
break;
}
}
// If we get here, the option was specified incorrectly:
usage();
break;
}
// If we get here, the option was specified incorrectly:
usage();
break;
}
case 'u': { // specify a username and password (to be used if the 'back end' (i.e., proxied) stream requires authentication)
if (argc < 4) usage(); // there's no argv[3] (for the "password")
username = argv[2];
password = argv[3];
argv += 2; argc -= 2;
break;
}
case 'u': { // specify a username and password (to be used if the 'back end' (i.e., proxied) stream requires authentication)
if (argc < 4) usage(); // there's no argv[3] (for the "password")
username = argv[2];
password = argv[3];
argv += 2; argc -= 2;
break;
}
case 'R': { // Handle incoming "REGISTER" requests by proxying the specified stream:
proxyREGISTERRequests = True;
break;
}
default: {
usage();
break;
}
default: {
usage();
break;
}
}
++argv; --argc;
}
if (argc < 2) usage(); // there must be at least one "rtsp://" URL at the end
if (argc < 2 && !proxyREGISTERRequests) usage(); // there must be at least one "rtsp://" URL at the end
// Make sure that the remaining arguments appear to be "rtsp://" URLs:
int i;
for (i = 1; i < argc; ++i) {
@@ -144,7 +160,7 @@ int main(int argc, char** argv) {
}
}
UserAuthenticationDatabase* authDB = NULL;
authDB = NULL;
#ifdef ACCESS_CONTROL
// To implement client access control to the RTSP server, do the following:
authDB = new UserAuthenticationDatabase;
@@ -156,7 +172,8 @@ int main(int argc, char** argv) {
// Create the RTSP server. Try first with the default port number (554),
// and then with the alternative port number (8554):
RTSPServer* rtspServer;
rtspServer = RTSPServer::createNew(*env, rtspServerPortNum, authDB);
portNumBits rtspServerPortNum = 554;
rtspServer = createRTSPServer(rtspServerPortNum);
if (rtspServer == NULL) {
*env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
exit(1);
@@ -173,8 +190,8 @@ int main(int argc, char** argv) {
}
ServerMediaSession* sms
= ProxyServerMediaSession::createNew(*env, rtspServer,
proxiedStreamURL, streamName,
username, password, tunnelOverHTTPPortNum, verbosityLevel);
proxiedStreamURL, streamName,
username, password, tunnelOverHTTPPortNum, verbosityLevel);
rtspServer->addServerMediaSession(sms);
char* proxyStreamURL = rtspServer->rtspURL(sms);
@@ -183,6 +200,10 @@ int main(int argc, char** argv) {
delete[] proxyStreamURL;
}
if (proxyREGISTERRequests) {
*env << "(We handle incoming \"REGISTER\" requests on port " << rtspServerPortNum << ")\n";
}
// Also, attempt to create a HTTP server for RTSP-over-HTTP tunneling.
// Try first with the default HTTP port (80), and then with the alternative HTTP
// port numbers (8000 and 8080).

View File

@@ -29,6 +29,10 @@ Medium* createClient(UsageEnvironment& env, char const* url, int verbosityLevel,
return ourRTSPClient = RTSPClient::createNew(env, url, verbosityLevel, applicationName, tunnelOverHTTPPortNum);
}
void assignClient(Medium* client) {
ourRTSPClient = (RTSPClient*)client;
}
void getOptions(RTSPClient::responseHandler* afterFunc) {
ourRTSPClient->sendOptionsCommand(afterFunc, ourAuthenticator);
}

View File

@@ -33,6 +33,8 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#endif
// Forward function definitions:
void continueAfterClientCreation0(RTSPClient* client);
void continueAfterClientCreation1();
void continueAfterOPTIONS(RTSPClient* client, int resultCode, char* resultString);
void continueAfterDESCRIBE(RTSPClient* client, int resultCode, char* resultString);
void continueAfterSETUP(RTSPClient* client, int resultCode, char* resultString);
@@ -107,6 +109,9 @@ Boolean packetLossCompensate = False;
Boolean syncStreams = False;
Boolean generateHintTracks = False;
unsigned qosMeasurementIntervalMS = 0; // 0 means: Don't output QOS data
Boolean createHandlerServerForREGISTERCommand = False;
portNumBits handlerServerForREGISTERCommandPortNum = 0;
HandlerServerForREGISTERCommand* handlerServerForREGISTERCommand;
struct timeval startTime;
@@ -118,7 +123,7 @@ void usage() {
<< (allowProxyServers ? " [<proxy-server> [<proxy-server-port>]]" : "")
<< "]" << (supportCodecSelection ? " [-A <audio-codec-rtp-payload-format-code>|-M <mime-subtype-name>]" : "")
<< " [-s <initial-seek-time>]|[-U <absolute-seek-time>] [-z <scale>]"
<< " [-w <width> -h <height>] [-f <frames-per-second>] [-y] [-H] [-Q [<measurement-interval>]] [-F <filename-prefix>] [-b <file-sink-buffer-size>] [-B <input-socket-buffer-size>] [-I <input-interface-ip-address>] [-m] <url> (or " << progName << " -o [-V] <url>)\n";
<< " [-w <width> -h <height>] [-f <frames-per-second>] [-y] [-H] [-Q [<measurement-interval>]] [-F <filename-prefix>] [-b <file-sink-buffer-size>] [-B <input-socket-buffer-size>] [-I <input-interface-ip-address>] [-m] [<url>|-R [<port-num>]] (or " << progName << " -o [-V] <url>)\n";
shutdown();
}
@@ -138,11 +143,14 @@ int main(int argc, char** argv) {
#endif
// unfortunately we can't use getopt() here, as Windoze doesn't have it
while (argc > 2) {
while (argc > 1) {
char* const opt = argv[1];
if (opt[0] != '-') usage();
switch (opt[1]) {
if (opt[0] != '-') {
if (argc == 2) break; // only the URL is left
usage();
}
switch (opt[1]) {
case 'p': { // specify start port number
int portArg;
if (sscanf(argv[2], "%d", &portArg) != 1) {
@@ -445,7 +453,21 @@ int main(int argc, char** argv) {
break;
}
case 'R': {
// set up a handler server for incoming "REGISTER" commands
createHandlerServerForREGISTERCommand = True;
if (argc > 2 && argv[2][0] != '-') {
// The next argument is the REGISTER handler server port number:
if (sscanf(argv[2], "%hu", &handlerServerForREGISTERCommandPortNum) == 1 && handlerServerForREGISTERCommandPortNum > 0) {
++argv; --argc;
break;
}
}
break;
}
default: {
*env << "Invalid option: " << opt << "\n";
usage();
break;
}
@@ -453,7 +475,9 @@ int main(int argc, char** argv) {
++argv; --argc;
}
if (argc != 2) usage(); // there must be exactly one "rtsp://" URL at the end
// There must be exactly one "rtsp://" URL at the end (unless '-R' was used, in which case there's no URL)
if (!( (argc == 2 && !createHandlerServerForREGISTERCommand) || (argc == 1 && createHandlerServerForREGISTERCommand) )) usage();
if (outputQuickTimeFile && outputAVIFile) {
*env << "The -i and -q (or -4) options cannot both be used!\n";
usage();
@@ -507,19 +531,23 @@ int main(int argc, char** argv) {
streamURL = argv[1];
// Create our client object:
ourClient = createClient(*env, streamURL, verbosityLevel, progName);
if (ourClient == NULL) {
*env << "Failed to create " << clientProtocolName
<< " client: " << env->getResultMsg() << "\n";
shutdown();
}
if (sendOptionsRequest) {
// Begin by sending an "OPTIONS" command:
getOptions(continueAfterOPTIONS);
// Create (or arrange to create) our client object:
if (createHandlerServerForREGISTERCommand) {
handlerServerForREGISTERCommand
= HandlerServerForREGISTERCommand::createNew(*env, continueAfterClientCreation0,
handlerServerForREGISTERCommandPortNum, verbosityLevel, progName);
if (handlerServerForREGISTERCommand == NULL) {
*env << "Failed to create a server for handling incoming \"REGISTER\" commands: " << env->getResultMsg() << "\n";
} else {
*env << "Awaiting an incoming \"REGISTER\" command on port " << handlerServerForREGISTERCommand->serverPortNum() << "\n";
}
} else {
continueAfterOPTIONS(NULL, 0, NULL);
ourClient = createClient(*env, streamURL, verbosityLevel, progName);
if (ourClient == NULL) {
*env << "Failed to create " << clientProtocolName << " client: " << env->getResultMsg() << "\n";
shutdown();
}
continueAfterClientCreation1();
}
// All subsequent activity takes place within the event loop:
@@ -528,6 +556,27 @@ int main(int argc, char** argv) {
return 0; // only to prevent compiler warning
}
void continueAfterClientCreation0(RTSPClient* newRTSPClient) {
if (newRTSPClient == 0) return;
assignClient(ourClient = newRTSPClient);
streamURL = newRTSPClient->url();
// Having handled one "REGISTER" command (giving us a "rtsp://" URL to stream from), we don't handle any more:
Medium::close(handlerServerForREGISTERCommand); handlerServerForREGISTERCommand = NULL;
continueAfterClientCreation1();
}
void continueAfterClientCreation1() {
if (sendOptionsRequest) {
// Begin by sending an "OPTIONS" command:
getOptions(continueAfterOPTIONS);
} else {
continueAfterOPTIONS(NULL, 0, NULL);
}
}
void continueAfterOPTIONS(RTSPClient*, int resultCode, char* resultString) {
if (sendOptionsRequestOnly) {
if (resultCode != 0) {

View File

@@ -20,6 +20,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#include "liveMedia.hh"
extern Medium* createClient(UsageEnvironment& env, char const* URL, int verbosityLevel, char const* applicationName);
extern void assignClient(Medium* client);
extern RTSPClient* ourRTSPClient;
extern SIPClient* ourSIPClient;

View File

@@ -51,6 +51,10 @@ Medium* createClient(UsageEnvironment& env, char const* /*url*/, int verbosityLe
return ourSIPClient = SIPClient::createNew(env, desiredAudioRTPPayloadFormat, mimeSubtype, verbosityLevel, applicationName);
}
// The followign function is implemented, but is not used for "playSIP":
void assignClient(Medium* /*client*/) {
}
void getOptions(RTSPClient::responseHandler* afterFunc) {
ourSIPClient->envir().setResultMsg("NOT SUPPORTED IN CLIENT");
afterFunc(NULL, -1, strDup(ourSIPClient->envir().getResultMsg()));

View File

@@ -431,7 +431,7 @@ ourRTSPClient* ourRTSPClient::createNew(UsageEnvironment& env, char const* rtspU
ourRTSPClient::ourRTSPClient(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum)
: RTSPClient(env,rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum) {
: RTSPClient(env,rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum, -1) {
}
ourRTSPClient::~ourRTSPClient() {

View File

@@ -1,517 +0,0 @@
/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2.1 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
**********/
// Copyright (c) 1996-2013, Live Networks, Inc. All rights reserved
// A demo application, showing how to create and run a RTSP client (that can potentially receive multiple streams concurrently).
//
// NOTE: This code - although it builds a running application - is intended only to illustrate how to develop your own RTSP
// client application. For a full-featured RTSP client application - with much more functionality, and many options - see
// "openRTSP": http://www.live555.com/openRTSP/
#include "liveMedia.hh"
#include "BasicUsageEnvironment.hh"
// Forward function definitions:
// RTSP 'response handlers':
void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char* resultString);
void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString);
void continueAfterPLAY(RTSPClient* rtspClient, int resultCode, char* resultString);
// Other event handler functions:
void subsessionAfterPlaying(void* clientData); // called when a stream's subsession (e.g., audio or video substream) ends
void subsessionByeHandler(void* clientData); // called when a RTCP "BYE" is received for a subsession
void streamTimerHandler(void* clientData);
// called at the end of a stream's expected duration (if the stream has not already signaled its end using a RTCP "BYE")
// The main streaming routine (for each "rtsp://" URL):
void openURL(UsageEnvironment& env, char const* progName, char const* rtspURL);
// Used to iterate through each stream's 'subsessions', setting up each one:
void setupNextSubsession(RTSPClient* rtspClient);
// Used to shut down and close a stream (including its "RTSPClient" object):
void shutdownStream(RTSPClient* rtspClient, int exitCode = 1);
// A function that outputs a string that identifies each stream (for debugging output). Modify this if you wish:
UsageEnvironment& operator<<(UsageEnvironment& env, const RTSPClient& rtspClient) {
return env << "[URL:\"" << rtspClient.url() << "\"]: ";
}
// A function that outputs a string that identifies each subsession (for debugging output). Modify this if you wish:
UsageEnvironment& operator<<(UsageEnvironment& env, const MediaSubsession& subsession) {
return env << subsession.mediumName() << "/" << subsession.codecName();
}
void usage(UsageEnvironment& env, char const* progName) {
env << "Usage: " << progName << " <rtsp-url-1> ... <rtsp-url-N>\n";
env << "\t(where each <rtsp-url-i> is a \"rtsp://\" URL)\n";
}
char eventLoopWatchVariable = 0;
int main(int argc, char** argv) {
// Begin by setting up our usage environment:
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
// We need at least one "rtsp://" URL argument:
if (argc < 2) {
usage(*env, argv[0]);
return 1;
}
// There are argc-1 URLs: argv[1] through argv[argc-1]. Open and start streaming each one:
for (int i = 1; i <= argc-1; ++i) {
openURL(*env, argv[0], argv[i]);
}
// All subsequent activity takes place within the event loop:
env->taskScheduler().doEventLoop(&eventLoopWatchVariable);
// This function call does not return, unless, at some point in time, "eventLoopWatchVariable" gets set to something non-zero.
return 0;
// If you choose to continue the application past this point (i.e., if you comment out the "return 0;" statement above),
// and if you don't intend to do anything more with the "TaskScheduler" and "UsageEnvironment" objects,
// then you can also reclaim the (small) memory used by these objects by uncommenting the following code:
/*
env->reclaim(); env = NULL;
delete scheduler; scheduler = NULL;
*/
}
// Define a class to hold per-stream state that we maintain throughout each stream's lifetime:
class StreamClientState {
public:
StreamClientState();
virtual ~StreamClientState();
public:
MediaSubsessionIterator* iter;
MediaSession* session;
MediaSubsession* subsession;
TaskToken streamTimerTask;
double duration;
};
// If you're streaming just a single stream (i.e., just from a single URL, once), then you can define and use just a single
// "StreamClientState" structure, as a global variable in your application. However, because - in this demo application - we're
// showing how to play multiple streams, concurrently, we can't do that. Instead, we have to have a separate "StreamClientState"
// structure for each "RTSPClient". To do this, we subclass "RTSPClient", and add a "StreamClientState" field to the subclass:
class ourRTSPClient: public RTSPClient {
public:
static ourRTSPClient* createNew(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel = 0,
char const* applicationName = NULL,
portNumBits tunnelOverHTTPPortNum = 0);
protected:
ourRTSPClient(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum);
// called only by createNew();
virtual ~ourRTSPClient();
public:
StreamClientState scs;
};
// Define a data sink (a subclass of "MediaSink") to receive the data for each subsession (i.e., each audio or video 'substream').
// In practice, this might be a class (or a chain of classes) that decodes and then renders the incoming audio or video.
// Or it might be a "FileSink", for outputting the received data into a file (as is done by the "openRTSP" application).
// In this example code, however, we define a simple 'dummy' sink that receives incoming data, but does nothing with it.
class DummySink: public MediaSink {
public:
static DummySink* createNew(UsageEnvironment& env,
MediaSubsession& subsession, // identifies the kind of data that's being received
char const* streamId = NULL); // identifies the stream itself (optional)
private:
DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId);
// called only by "createNew()"
virtual ~DummySink();
static void afterGettingFrame(void* clientData, unsigned frameSize,
unsigned numTruncatedBytes,
struct timeval presentationTime,
unsigned durationInMicroseconds);
void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigned durationInMicroseconds);
private:
// redefined virtual functions:
virtual Boolean continuePlaying();
private:
u_int8_t* fReceiveBuffer;
MediaSubsession& fSubsession;
char* fStreamId;
};
#define RTSP_CLIENT_VERBOSITY_LEVEL 1 // by default, print verbose output from each "RTSPClient"
static unsigned rtspClientCount = 0; // Counts how many streams (i.e., "RTSPClient"s) are currently in use.
void openURL(UsageEnvironment& env, char const* progName, char const* rtspURL) {
// Begin by creating a "RTSPClient" object. Note that there is a separate "RTSPClient" object for each stream that we wish
// to receive (even if more than stream uses the same "rtsp://" URL).
RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, progName);
if (rtspClient == NULL) {
env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg() << "\n";
return;
}
++rtspClientCount;
// Next, send a RTSP "DESCRIBE" command, to get a SDP description for the stream.
// Note that this command - like all RTSP commands - is sent asynchronously; we do not block, waiting for a response.
// Instead, the following function call returns immediately, and we handle the RTSP response later, from within the event loop:
rtspClient->sendDescribeCommand(continueAfterDESCRIBE);
}
// Implementation of the RTSP 'response handlers':
void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char* resultString) {
do {
UsageEnvironment& env = rtspClient->envir(); // alias
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
if (resultCode != 0) {
env << *rtspClient << "Failed to get a SDP description: " << resultString << "\n";
delete[] resultString;
break;
}
char* const sdpDescription = resultString;
env << *rtspClient << "Got a SDP description:\n" << sdpDescription << "\n";
// Create a media session object from this SDP description:
scs.session = MediaSession::createNew(env, sdpDescription);
delete[] sdpDescription; // because we don't need it anymore
if (scs.session == NULL) {
env << *rtspClient << "Failed to create a MediaSession object from the SDP description: " << env.getResultMsg() << "\n";
break;
} else if (!scs.session->hasSubsessions()) {
env << *rtspClient << "This session has no media subsessions (i.e., no \"m=\" lines)\n";
break;
}
// Then, create and set up our data source objects for the session. We do this by iterating over the session's 'subsessions',
// calling "MediaSubsession::initiate()", and then sending a RTSP "SETUP" command, on each one.
// (Each 'subsession' will have its own data source.)
scs.iter = new MediaSubsessionIterator(*scs.session);
setupNextSubsession(rtspClient);
return;
} while (0);
// An unrecoverable error occurred with this stream.
shutdownStream(rtspClient);
}
void setupNextSubsession(RTSPClient* rtspClient) {
UsageEnvironment& env = rtspClient->envir(); // alias
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
scs.subsession = scs.iter->next();
if (scs.subsession != NULL) {
if (!scs.subsession->initiate()) {
env << *rtspClient << "Failed to initiate the \"" << *scs.subsession << "\" subsession: " << env.getResultMsg() << "\n";
setupNextSubsession(rtspClient); // give up on this subsession; go to the next one
} else {
env << *rtspClient << "Initiated the \"" << *scs.subsession
<< "\" subsession (client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1 << ")\n";
// Continue setting up this subsession, by sending a RTSP "SETUP" command:
rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP);
}
return;
}
// We've finished setting up all of the subsessions. Now, send a RTSP "PLAY" command to start the streaming:
if (scs.session->absStartTime() != NULL) {
// Special case: The stream is indexed by 'absolute' time, so send an appropriate "PLAY" command:
rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY, scs.session->absStartTime(), scs.session->absEndTime());
} else {
scs.duration = scs.session->playEndTime() - scs.session->playStartTime();
rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY);
}
}
void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString) {
do {
UsageEnvironment& env = rtspClient->envir(); // alias
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
if (resultCode != 0) {
env << *rtspClient << "Failed to set up the \"" << *scs.subsession << "\" subsession: " << resultString << "\n";
break;
}
env << *rtspClient << "Set up the \"" << *scs.subsession
<< "\" subsession (client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1 << ")\n";
// Having successfully setup the subsession, create a data sink for it, and call "startPlaying()" on it.
// (This will prepare the data sink to receive data; the actual flow of data from the client won't start happening until later,
// after we've sent a RTSP "PLAY" command.)
scs.subsession->sink = DummySink::createNew(env, *scs.subsession, rtspClient->url());
// perhaps use your own custom "MediaSink" subclass instead
if (scs.subsession->sink == NULL) {
env << *rtspClient << "Failed to create a data sink for the \"" << *scs.subsession
<< "\" subsession: " << env.getResultMsg() << "\n";
break;
}
env << *rtspClient << "Created a data sink for the \"" << *scs.subsession << "\" subsession\n";
scs.subsession->miscPtr = rtspClient; // a hack to let subsession handle functions get the "RTSPClient" from the subsession
scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
subsessionAfterPlaying, scs.subsession);
// Also set a handler to be called if a RTCP "BYE" arrives for this subsession:
if (scs.subsession->rtcpInstance() != NULL) {
scs.subsession->rtcpInstance()->setByeHandler(subsessionByeHandler, scs.subsession);
}
} while (0);
delete[] resultString;
// Set up the next subsession, if any:
setupNextSubsession(rtspClient);
}
void continueAfterPLAY(RTSPClient* rtspClient, int resultCode, char* resultString) {
Boolean success = False;
do {
UsageEnvironment& env = rtspClient->envir(); // alias
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
if (resultCode != 0) {
env << *rtspClient << "Failed to start playing session: " << resultString << "\n";
break;
}
// Set a timer to be handled at the end of the stream's expected duration (if the stream does not already signal its end
// using a RTCP "BYE"). This is optional. If, instead, you want to keep the stream active - e.g., so you can later
// 'seek' back within it and do another RTSP "PLAY" - then you can omit this code.
// (Alternatively, if you don't want to receive the entire stream, you could set this timer for some shorter value.)
if (scs.duration > 0) {
unsigned const delaySlop = 2; // number of seconds extra to delay, after the stream's expected duration. (This is optional.)
scs.duration += delaySlop;
unsigned uSecsToDelay = (unsigned)(scs.duration*1000000);
scs.streamTimerTask = env.taskScheduler().scheduleDelayedTask(uSecsToDelay, (TaskFunc*)streamTimerHandler, rtspClient);
}
env << *rtspClient << "Started playing session";
if (scs.duration > 0) {
env << " (for up to " << scs.duration << " seconds)";
}
env << "...\n";
success = True;
} while (0);
delete[] resultString;
if (!success) {
// An unrecoverable error occurred with this stream.
shutdownStream(rtspClient);
}
}
// Implementation of the other event handlers:
void subsessionAfterPlaying(void* clientData) {
MediaSubsession* subsession = (MediaSubsession*)clientData;
RTSPClient* rtspClient = (RTSPClient*)(subsession->miscPtr);
// Begin by closing this subsession's stream:
Medium::close(subsession->sink);
subsession->sink = NULL;
// Next, check whether *all* subsessions' streams have now been closed:
MediaSession& session = subsession->parentSession();
MediaSubsessionIterator iter(session);
while ((subsession = iter.next()) != NULL) {
if (subsession->sink != NULL) return; // this subsession is still active
}
// All subsessions' streams have now been closed, so shutdown the client:
shutdownStream(rtspClient);
}
void subsessionByeHandler(void* clientData) {
MediaSubsession* subsession = (MediaSubsession*)clientData;
RTSPClient* rtspClient = (RTSPClient*)subsession->miscPtr;
UsageEnvironment& env = rtspClient->envir(); // alias
env << *rtspClient << "Received RTCP \"BYE\" on \"" << *subsession << "\" subsession\n";
// Now act as if the subsession had closed:
subsessionAfterPlaying(subsession);
}
void streamTimerHandler(void* clientData) {
ourRTSPClient* rtspClient = (ourRTSPClient*)clientData;
StreamClientState& scs = rtspClient->scs; // alias
scs.streamTimerTask = NULL;
// Shut down the stream:
shutdownStream(rtspClient);
}
void shutdownStream(RTSPClient* rtspClient, int exitCode) {
UsageEnvironment& env = rtspClient->envir(); // alias
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
// First, check whether any subsessions have still to be closed:
if (scs.session != NULL) {
Boolean someSubsessionsWereActive = False;
MediaSubsessionIterator iter(*scs.session);
MediaSubsession* subsession;
while ((subsession = iter.next()) != NULL) {
if (subsession->sink != NULL) {
Medium::close(subsession->sink);
subsession->sink = NULL;
if (subsession->rtcpInstance() != NULL) {
subsession->rtcpInstance()->setByeHandler(NULL, NULL); // in case the server sends a RTCP "BYE" while handling "TEARDOWN"
}
someSubsessionsWereActive = True;
}
}
if (someSubsessionsWereActive) {
// Send a RTSP "TEARDOWN" command, to tell the server to shutdown the stream.
// Don't bother handling the response to the "TEARDOWN".
rtspClient->sendTeardownCommand(*scs.session, NULL);
}
}
env << *rtspClient << "Closing the stream.\n";
Medium::close(rtspClient);
// Note that this will also cause this stream's "StreamClientState" structure to get reclaimed.
if (--rtspClientCount == 0) {
// The final stream has ended, so exit the application now.
// (Of course, if you're embedding this code into your own application, you might want to comment this out,
// and replace it with "eventLoopWatchVariable = 1;", so that we leave the LIVE555 event loop, and continue running "main()".)
exit(exitCode);
}
}
// Implementation of "ourRTSPClient":
ourRTSPClient* ourRTSPClient::createNew(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum) {
return new ourRTSPClient(env, rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum);
}
ourRTSPClient::ourRTSPClient(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum)
: RTSPClient(env,rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum) {
}
ourRTSPClient::~ourRTSPClient() {
}
// Implementation of "StreamClientState":
StreamClientState::StreamClientState()
: iter(NULL), session(NULL), subsession(NULL), streamTimerTask(NULL), duration(0.0) {
}
StreamClientState::~StreamClientState() {
delete iter;
if (session != NULL) {
// We also need to delete "session", and unschedule "streamTimerTask" (if set)
UsageEnvironment& env = session->envir(); // alias
env.taskScheduler().unscheduleDelayedTask(streamTimerTask);
Medium::close(session);
}
}
// Implementation of "DummySink":
// Even though we're not going to be doing anything with the incoming data, we still need to receive it.
// Define the size of the buffer that we'll use:
#define DUMMY_SINK_RECEIVE_BUFFER_SIZE 100000
DummySink* DummySink::createNew(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId) {
return new DummySink(env, subsession, streamId);
}
DummySink::DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId)
: MediaSink(env),
fSubsession(subsession) {
fStreamId = strDup(streamId);
fReceiveBuffer = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
}
DummySink::~DummySink() {
delete[] fReceiveBuffer;
delete[] fStreamId;
}
void DummySink::afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigned durationInMicroseconds) {
DummySink* sink = (DummySink*)clientData;
sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds);
}
// If you don't want to see debugging output for each received frame, then comment out the following line:
#define DEBUG_PRINT_EACH_RECEIVED_FRAME 1
void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigned /*durationInMicroseconds*/) {
// We've just received a frame of data. (Optionally) print out information about it:
#ifdef DEBUG_PRINT_EACH_RECEIVED_FRAME
if (fStreamId != NULL) envir() << "Stream \"" << fStreamId << "\"; ";
envir() << fSubsession.mediumName() << "/" << fSubsession.codecName() << ":\tReceived " << frameSize << " bytes";
if (numTruncatedBytes > 0) envir() << " (with " << numTruncatedBytes << " bytes truncated)";
char uSecsStr[6+1]; // used to output the 'microseconds' part of the presentation time
sprintf(uSecsStr, "%06u", (unsigned)presentationTime.tv_usec);
envir() << ".\tPresentation time: " << (int)presentationTime.tv_sec << "." << uSecsStr;
if (fSubsession.rtpSource() != NULL && !fSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP()) {
envir() << "!"; // mark the debugging output to indicate that this presentation time is not RTCP-synchronized
}
#ifdef DEBUG_PRINT_NPT
envir() << "\tNPT: " << fSubsession.getNormalPlayTime(presentationTime);
#endif
envir() << "\n";
#endif
// Then continue, to request the next frame of data:
continuePlaying();
}
Boolean DummySink::continuePlaying() {
if (fSource == NULL) return False; // sanity check (should not happen)
// Request the next frame of data from our input source. "afterGettingFrame()" will get called later, when it arrives:
fSource->getNextFrame(fReceiveBuffer, DUMMY_SINK_RECEIVE_BUFFER_SIZE,
afterGettingFrame, this,
onSourceClosure, this);
return True;
}