mirror of
https://github.com/rgaufman/live555.git
synced 2026-01-12 00:04:30 +08:00
Bump to version 2013.07.16
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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++];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 == ':'; }
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
113
modifications.patch
Normal 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);
|
||||
@@ -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).
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user