[SCM] liblivemedia/master: Imported Upstream version 2010.09.25
xtophe-guest at users.alioth.debian.org
xtophe-guest at users.alioth.debian.org
Mon Oct 11 22:47:46 UTC 2010
The following commit has been merged in the master branch:
commit 40b2ed384196425b26d1a28773f6abfe7a8f6114
Author: Christophe Mutricy <xtophe at videolan.org>
Date: Fri Oct 1 22:44:39 2010 +0100
Imported Upstream version 2010.09.25
diff --git a/BasicUsageEnvironment/BasicTaskScheduler.cpp b/BasicUsageEnvironment/BasicTaskScheduler.cpp
index ffc2012..06e3c8c 100644
--- a/BasicUsageEnvironment/BasicTaskScheduler.cpp
+++ b/BasicUsageEnvironment/BasicTaskScheduler.cpp
@@ -35,6 +35,8 @@ BasicTaskScheduler* BasicTaskScheduler::createNew() {
BasicTaskScheduler::BasicTaskScheduler()
: fMaxNumSockets(0) {
FD_ZERO(&fReadSet);
+ FD_ZERO(&fWriteSet);
+ FD_ZERO(&fExceptionSet);
}
BasicTaskScheduler::~BasicTaskScheduler() {
@@ -46,6 +48,8 @@ BasicTaskScheduler::~BasicTaskScheduler() {
void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
fd_set readSet = fReadSet; // make a copy for this select() call
+ fd_set writeSet = fWriteSet; // ditto
+ fd_set exceptionSet = fExceptionSet; // ditto
DelayInterval const& timeToDelay = fDelayQueue.timeToNextAlarm();
struct timeval tv_timeToDelay;
@@ -66,8 +70,7 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
tv_timeToDelay.tv_usec = maxDelayTime%MILLION;
}
- int selectResult = select(fMaxNumSockets, &readSet, NULL, NULL,
- &tv_timeToDelay);
+ int selectResult = select(fMaxNumSockets, &readSet, &writeSet, &exceptionSet, &tv_timeToDelay);
if (selectResult < 0) {
#if defined(__WIN32__) || defined(_WIN32)
int err = WSAGetLastError();
@@ -75,9 +78,11 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
// it was called with no entries set in "readSet". If this happens, ignore it:
if (err == WSAEINVAL && readSet.fd_count == 0) {
err = EINTR;
- // To stop this from happening again, create a dummy readable socket:
+ // To stop this from happening again, create a dummy socket:
int dummySocketNum = socket(AF_INET, SOCK_DGRAM, 0);
FD_SET((unsigned)dummySocketNum, &fReadSet);
+ FD_SET((unsigned)dummySocketNum, &fWriteSet);
+ FD_SET((unsigned)dummySocketNum, &fExceptionSet);
}
if (err != EINTR) {
#else
@@ -87,12 +92,12 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
#if !defined(_WIN32_WCE)
perror("BasicTaskScheduler::SingleStep(): select() fails");
#endif
- abort();
+ internalError();
}
}
// Call the handler function for one readable socket:
- HandlerIterator iter(*fReadHandlers);
+ HandlerIterator iter(*fHandlers);
HandlerDescriptor* handler;
// To ensure forward progress through the handlers, begin past the last
// socket number that we handled:
@@ -106,13 +111,16 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
}
}
while ((handler = iter.next()) != NULL) {
- if (FD_ISSET(handler->socketNum, &readSet) &&
- FD_ISSET(handler->socketNum, &fReadSet) /* sanity check */ &&
- handler->handlerProc != NULL) {
- fLastHandledSocketNum = handler->socketNum;
+ int sock = handler->socketNum; // alias
+ int resultConditionSet = 0;
+ if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet)/*sanity check*/) resultConditionSet |= SOCKET_READABLE;
+ if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet)/*sanity check*/) resultConditionSet |= SOCKET_WRITABLE;
+ if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet)/*sanity check*/) resultConditionSet |= SOCKET_EXCEPTION;
+ if ((resultConditionSet&handler->conditionSet) != 0 && handler->handlerProc != NULL) {
+ fLastHandledSocketNum = sock;
// Note: we set "fLastHandledSocketNum" before calling the handler,
// in case the handler calls "doEventLoop()" reentrantly.
- (*handler->handlerProc)(handler->clientData, SOCKET_READABLE);
+ (*handler->handlerProc)(handler->clientData, resultConditionSet);
break;
}
}
@@ -121,13 +129,16 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
// so try again from the beginning:
iter.reset();
while ((handler = iter.next()) != NULL) {
- if (FD_ISSET(handler->socketNum, &readSet) &&
- FD_ISSET(handler->socketNum, &fReadSet) /* sanity check */ &&
- handler->handlerProc != NULL) {
- fLastHandledSocketNum = handler->socketNum;
+ int sock = handler->socketNum; // alias
+ int resultConditionSet = 0;
+ if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet)/*sanity check*/) resultConditionSet |= SOCKET_READABLE;
+ if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet)/*sanity check*/) resultConditionSet |= SOCKET_WRITABLE;
+ if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet)/*sanity check*/) resultConditionSet |= SOCKET_EXCEPTION;
+ if ((resultConditionSet&handler->conditionSet) != 0 && handler->handlerProc != NULL) {
+ fLastHandledSocketNum = sock;
// Note: we set "fLastHandledSocketNum" before calling the handler,
// in case the handler calls "doEventLoop()" reentrantly.
- (*handler->handlerProc)(handler->clientData, SOCKET_READABLE);
+ (*handler->handlerProc)(handler->clientData, resultConditionSet);
break;
}
}
@@ -135,37 +146,38 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
}
// Also handle any delayed event that may have come due. (Note that we do this *after* calling a socket
- // handler, in case the delayed event handler modifies the set of readable socket.)
+ // handler, in case the delayed event handler modifies the set of readable sockets.)
fDelayQueue.handleAlarm();
}
-void BasicTaskScheduler::turnOnBackgroundReadHandling(int socketNum,
- BackgroundHandlerProc* handlerProc,
- void* clientData) {
- if (socketNum < 0) return;
- fReadHandlers->assignHandler(socketNum, handlerProc, clientData);
- FD_SET((unsigned)socketNum, &fReadSet);
-
- if (socketNum+1 > fMaxNumSockets) {
- fMaxNumSockets = socketNum+1;
- }
-}
-
-void BasicTaskScheduler::turnOffBackgroundReadHandling(int socketNum) {
+void BasicTaskScheduler
+ ::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) {
if (socketNum < 0) return;
FD_CLR((unsigned)socketNum, &fReadSet);
- fReadHandlers->removeHandler(socketNum);
-
- if (socketNum+1 == fMaxNumSockets) {
- --fMaxNumSockets;
+ FD_CLR((unsigned)socketNum, &fWriteSet);
+ FD_CLR((unsigned)socketNum, &fExceptionSet);
+ if (conditionSet == 0) {
+ fHandlers->clearHandler(socketNum);
+ if (socketNum+1 == fMaxNumSockets) {
+ --fMaxNumSockets;
+ }
+ } else {
+ fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData);
+ if (socketNum+1 > fMaxNumSockets) {
+ fMaxNumSockets = socketNum+1;
+ }
+ if (conditionSet&SOCKET_READABLE) FD_SET((unsigned)socketNum, &fReadSet);
+ if (conditionSet&SOCKET_WRITABLE) FD_SET((unsigned)socketNum, &fWriteSet);
+ if (conditionSet&SOCKET_EXCEPTION) FD_SET((unsigned)socketNum, &fExceptionSet);
}
}
void BasicTaskScheduler::moveSocketHandling(int oldSocketNum, int newSocketNum) {
if (oldSocketNum < 0 || newSocketNum < 0) return; // sanity check
- FD_CLR((unsigned)oldSocketNum, &fReadSet);
- fReadHandlers->moveHandler(oldSocketNum, newSocketNum);
- FD_SET((unsigned)newSocketNum, &fReadSet);
+ if (FD_ISSET(oldSocketNum, &fReadSet)) {FD_CLR((unsigned)oldSocketNum, &fReadSet); FD_SET((unsigned)newSocketNum, &fReadSet);}
+ if (FD_ISSET(oldSocketNum, &fWriteSet)) {FD_CLR((unsigned)oldSocketNum, &fWriteSet); FD_SET((unsigned)newSocketNum, &fWriteSet);}
+ if (FD_ISSET(oldSocketNum, &fExceptionSet)) {FD_CLR((unsigned)oldSocketNum, &fExceptionSet); FD_SET((unsigned)newSocketNum, &fExceptionSet);}
+ fHandlers->moveHandler(oldSocketNum, newSocketNum);
if (oldSocketNum+1 == fMaxNumSockets) {
--fMaxNumSockets;
diff --git a/BasicUsageEnvironment/BasicTaskScheduler0.cpp b/BasicUsageEnvironment/BasicTaskScheduler0.cpp
index cd22246..08c49a9 100644
--- a/BasicUsageEnvironment/BasicTaskScheduler0.cpp
+++ b/BasicUsageEnvironment/BasicTaskScheduler0.cpp
@@ -45,11 +45,11 @@ private:
BasicTaskScheduler0::BasicTaskScheduler0()
: fLastHandledSocketNum(-1) {
- fReadHandlers = new HandlerSet;
+ fHandlers = new HandlerSet;
}
BasicTaskScheduler0::~BasicTaskScheduler0() {
- delete fReadHandlers;
+ delete fHandlers;
}
TaskToken BasicTaskScheduler0::scheduleDelayedTask(int64_t microseconds,
@@ -80,7 +80,8 @@ void BasicTaskScheduler0::doEventLoop(char* watchVariable) {
////////// HandlerSet (etc.) implementation //////////
-HandlerDescriptor::HandlerDescriptor(HandlerDescriptor* nextHandler) {
+HandlerDescriptor::HandlerDescriptor(HandlerDescriptor* nextHandler)
+ : conditionSet(0), handlerProc(NULL) {
// Link this descriptor into a doubly-linked list:
if (nextHandler == this) { // initialization
fNextHandler = fPrevHandler = this;
@@ -111,9 +112,7 @@ HandlerSet::~HandlerSet() {
}
void HandlerSet
-::assignHandler(int socketNum,
- TaskScheduler::BackgroundHandlerProc* handlerProc,
- void* clientData) {
+::assignHandler(int socketNum, int conditionSet, TaskScheduler::BackgroundHandlerProc* handlerProc, void* clientData) {
// First, see if there's already a handler for this socket:
HandlerDescriptor* handler = lookupHandler(socketNum);
if (handler == NULL) { // No existing handler, so create a new descr:
@@ -121,11 +120,12 @@ void HandlerSet
handler->socketNum = socketNum;
}
+ handler->conditionSet = conditionSet;
handler->handlerProc = handlerProc;
handler->clientData = clientData;
}
-void HandlerSet::removeHandler(int socketNum) {
+void HandlerSet::clearHandler(int socketNum) {
HandlerDescriptor* handler = lookupHandler(socketNum);
delete handler;
}
diff --git a/BasicUsageEnvironment/BasicUsageEnvironment.cpp b/BasicUsageEnvironment/BasicUsageEnvironment.cpp
index a201012..9fc846e 100644
--- a/BasicUsageEnvironment/BasicUsageEnvironment.cpp
+++ b/BasicUsageEnvironment/BasicUsageEnvironment.cpp
@@ -32,7 +32,7 @@ BasicUsageEnvironment::BasicUsageEnvironment(TaskScheduler& taskScheduler)
if (!initializeWinsockIfNecessary()) {
setResultErrMsg("Failed to initialize 'winsock': ");
reportBackgroundError();
- abort();
+ internalError();
}
#endif
}
diff --git a/BasicUsageEnvironment/BasicUsageEnvironment0.cpp b/BasicUsageEnvironment/BasicUsageEnvironment0.cpp
index 62a5992..64b29a4 100644
--- a/BasicUsageEnvironment/BasicUsageEnvironment0.cpp
+++ b/BasicUsageEnvironment/BasicUsageEnvironment0.cpp
@@ -59,11 +59,11 @@ void BasicUsageEnvironment0::setResultMsg(MsgString msg1, MsgString msg2,
appendToResultMsg(msg3);
}
-void BasicUsageEnvironment0::setResultErrMsg(MsgString msg) {
+void BasicUsageEnvironment0::setResultErrMsg(MsgString msg, int err) {
setResultMsg(msg);
#ifndef _WIN32_WCE
- appendToResultMsg(strerror(getErrno()));
+ appendToResultMsg(strerror(err == 0 ? getErrno() : err));
#endif
}
diff --git a/BasicUsageEnvironment/include/BasicUsageEnvironment.hh b/BasicUsageEnvironment/include/BasicUsageEnvironment.hh
index 678def0..8681753 100644
--- a/BasicUsageEnvironment/include/BasicUsageEnvironment.hh
+++ b/BasicUsageEnvironment/include/BasicUsageEnvironment.hh
@@ -57,16 +57,15 @@ protected:
// Redefined virtual functions:
virtual void SingleStep(unsigned maxDelayTime);
- virtual void turnOnBackgroundReadHandling(int socketNum,
- BackgroundHandlerProc* handlerProc,
- void* clientData);
- virtual void turnOffBackgroundReadHandling(int socketNum);
+ virtual void setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData);
virtual void moveSocketHandling(int oldSocketNum, int newSocketNum);
protected:
- // To implement background reads:
+ // To implement background operations:
int fMaxNumSockets;
fd_set fReadSet;
+ fd_set fWriteSet;
+ fd_set fExceptionSet;
};
#endif
diff --git a/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh b/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh
index bbeb79f..37713cb 100644
--- a/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh
+++ b/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh
@@ -47,7 +47,7 @@ public:
virtual void setResultMsg(MsgString msg1,
MsgString msg2,
MsgString msg3);
- virtual void setResultErrMsg(MsgString msg);
+ virtual void setResultErrMsg(MsgString msg, int err = 0);
virtual void appendToResultMsg(MsgString msg);
@@ -94,7 +94,7 @@ protected:
DelayQueue fDelayQueue;
// To implement background reads:
- HandlerSet* fReadHandlers;
+ HandlerSet* fHandlers;
int fLastHandledSocketNum;
};
diff --git a/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh b/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh
index f64c7e2..ee65613 100644
--- a/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh
+++ b/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh
@@ -4,7 +4,7 @@
#ifndef _BASICUSAGEENVIRONMENT_VERSION_HH
#define _BASICUSAGEENVIRONMENT_VERSION_HH
-#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_STRING "2010.04.09"
-#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_INT 1270771200
+#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_STRING "2010.09.25"
+#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_INT 1285372800
#endif
diff --git a/BasicUsageEnvironment/include/HandlerSet.hh b/BasicUsageEnvironment/include/HandlerSet.hh
index c2c3122..1ecf1a3 100644
--- a/BasicUsageEnvironment/include/HandlerSet.hh
+++ b/BasicUsageEnvironment/include/HandlerSet.hh
@@ -20,6 +20,10 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#ifndef _HANDLER_SET_HH
#define _HANDLER_SET_HH
+#ifndef _BOOLEAN_HH
+#include "Boolean.hh"
+#endif
+
////////// HandlerSet (etc.) definition //////////
class HandlerDescriptor {
@@ -28,6 +32,7 @@ class HandlerDescriptor {
public:
int socketNum;
+ int conditionSet;
TaskScheduler::BackgroundHandlerProc* handlerProc;
void* clientData;
@@ -44,10 +49,8 @@ public:
HandlerSet();
virtual ~HandlerSet();
- void assignHandler(int socketNum,
- TaskScheduler::BackgroundHandlerProc* handlerProc,
- void* clientData);
- void removeHandler(int socketNum);
+ void assignHandler(int socketNum, int conditionSet, TaskScheduler::BackgroundHandlerProc* handlerProc, void* clientData);
+ void clearHandler(int socketNum);
void moveHandler(int oldSocketNum, int newSocketNum);
private:
diff --git a/UsageEnvironment/UsageEnvironment.cpp b/UsageEnvironment/UsageEnvironment.cpp
index 0677f0a..bad8100 100644
--- a/UsageEnvironment/UsageEnvironment.cpp
+++ b/UsageEnvironment/UsageEnvironment.cpp
@@ -31,6 +31,12 @@ UsageEnvironment::UsageEnvironment(TaskScheduler& scheduler)
UsageEnvironment::~UsageEnvironment() {
}
+// By default, we handle 'should not occur'-type library errors by calling abort(). Subclasses can redefine this, if desired.
+void UsageEnvironment::internalError() {
+ abort();
+}
+
+
TaskScheduler::TaskScheduler() {
}
@@ -43,3 +49,8 @@ void TaskScheduler::rescheduleDelayedTask(TaskToken& task,
unscheduleDelayedTask(task);
task = scheduleDelayedTask(microseconds, proc, clientData);
}
+
+// By default, we handle 'should not occur'-type library errors by calling abort(). Subclasses can redefine this, if desired.
+void TaskScheduler::internalError() {
+ abort();
+}
diff --git a/UsageEnvironment/include/UsageEnvironment.hh b/UsageEnvironment/include/UsageEnvironment.hh
index ef530eb..d64b026 100644
--- a/UsageEnvironment/include/UsageEnvironment.hh
+++ b/UsageEnvironment/include/UsageEnvironment.hh
@@ -60,8 +60,8 @@ public:
virtual void setResultMsg(MsgString msg) = 0;
virtual void setResultMsg(MsgString msg1, MsgString msg2) = 0;
virtual void setResultMsg(MsgString msg1, MsgString msg2, MsgString msg3) = 0;
- virtual void setResultErrMsg(MsgString msg) = 0;
- // like setResultMsg(), except that an 'errno' message is appended
+ virtual void setResultErrMsg(MsgString msg, int err = 0) = 0;
+ // like setResultMsg(), except that an 'errno' message is appended. (If "err == 0", the "getErrno()" code is used instead.)
virtual void appendToResultMsg(MsgString msg) = 0;
@@ -69,6 +69,8 @@ public:
// used to report a (previously set) error message within
// a background event
+ virtual void internalError(); // used to 'handle' a 'should not occur'-type error condition within the library.
+
// 'errno'
virtual int getErrno() const = 0;
@@ -117,17 +119,15 @@ public:
// Combines "unscheduleDelayedTask()" with "scheduleDelayedTask()"
// (setting "task" to the new task token).
- // For handling socket reads in the background:
+ // For handling socket operations in the background (from the event loop):
typedef void BackgroundHandlerProc(void* clientData, int mask);
// Possible bits to set in "mask". (These are deliberately defined
// the same as those in Tcl, to make a Tcl-based subclass easy.)
#define SOCKET_READABLE (1<<1)
#define SOCKET_WRITABLE (1<<2)
#define SOCKET_EXCEPTION (1<<3)
- virtual void turnOnBackgroundReadHandling(int socketNum,
- BackgroundHandlerProc* handlerProc,
- void* clientData) = 0;
- virtual void turnOffBackgroundReadHandling(int socketNum) = 0;
+ virtual void setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) = 0;
+ void disableBackgroundHandling(int socketNum) { setBackgroundHandling(socketNum, 0, NULL, NULL); }
virtual void moveSocketHandling(int oldSocketNum, int newSocketNum) = 0;
// Changes any socket handling for "oldSocketNum" so that occurs with "newSocketNum" instead.
@@ -138,6 +138,14 @@ public:
// (If "watchVariable" is not NULL, then we return from this
// routine when *watchVariable != 0)
+ // The following two functions are deprecated, and are provided for backwards-compatibility only:
+ void turnOnBackgroundReadHandling(int socketNum, BackgroundHandlerProc* handlerProc, void* clientData) {
+ setBackgroundHandling(socketNum, SOCKET_READABLE, handlerProc, clientData);
+ }
+ void turnOffBackgroundReadHandling(int socketNum) { disableBackgroundHandling(socketNum); }
+
+ virtual void internalError(); // used to 'handle' a 'should not occur'-type error condition within the library.
+
protected:
TaskScheduler(); // abstract base class
};
diff --git a/UsageEnvironment/include/UsageEnvironment_version.hh b/UsageEnvironment/include/UsageEnvironment_version.hh
index e936928..cfd2ef4 100644
--- a/UsageEnvironment/include/UsageEnvironment_version.hh
+++ b/UsageEnvironment/include/UsageEnvironment_version.hh
@@ -4,7 +4,7 @@
#ifndef _USAGEENVIRONMENT_VERSION_HH
#define _USAGEENVIRONMENT_VERSION_HH
-#define USAGEENVIRONMENT_LIBRARY_VERSION_STRING "2010.04.09"
-#define USAGEENVIRONMENT_LIBRARY_VERSION_INT 1270771200
+#define USAGEENVIRONMENT_LIBRARY_VERSION_STRING "2010.09.25"
+#define USAGEENVIRONMENT_LIBRARY_VERSION_INT 1285372800
#endif
diff --git a/config.armlinux.orig b/config.armlinux.orig
deleted file mode 100644
index 3e604f7..0000000
--- a/config.armlinux.orig
+++ /dev/null
@@ -1,18 +0,0 @@
-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)gcc
-CPLUSPLUS_FLAGS = $(COMPILE_OPTS) -Wall -DBSD=1
-OBJ = o
-LINK = $(CROSS_COMPILE)gcc -o
-LINK_OPTS = -L.
-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 =
diff --git a/config.armlinux.rej b/config.armlinux.rej
deleted file mode 100644
index 49e7c9b..0000000
--- a/config.armlinux.rej
+++ /dev/null
@@ -1,35 +0,0 @@
-***************
-*** 1,16 ****
-- 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)gcc
- CPLUSPLUS_FLAGS = $(COMPILE_OPTS) -Wall -DBSD=1
- OBJ = o
-- LINK = $(CROSS_COMPILE)gcc -o
-- LINK_OPTS = -L.
- CONSOLE_LINK_OPTS = $(LINK_OPTS)
-- LIBRARY_LINK = $(CROSS_COMPILE)ar cr
- LIBRARY_LINK_OPTS = $(LINK_OPTS)
- LIB_SUFFIX = a
- LIBS_FOR_CONSOLE_APPLICATION =
---- 1,16 ----
-+ 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 =
diff --git a/config.iphoneos b/config.iphoneos
new file mode 100644
index 0000000..df28b92
--- /dev/null
+++ b/config.iphoneos
@@ -0,0 +1,17 @@
+COMPILE_OPTS = $(INCLUDES) -I. $(EXTRA_LDFLAGS) -DBSD=1 -O2 -DSOCKLEN_T=socklen_t -DHAVE_SOCKADDR_LEN=1 -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -fPIC -arch armv7 --sysroot=/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk
+C = c
+C_COMPILER = /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc
+C_FLAGS = $(COMPILE_OPTS)
+CPP = cpp
+CPLUSPLUS_COMPILER = /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/g++
+CPLUSPLUS_FLAGS = $(COMPILE_OPTS) -Wall
+OBJ = o
+LINK = /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/g++ -o
+LINK_OPTS = -L. -arch armv7 --sysroot=/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk
+CONSOLE_LINK_OPTS = $(LINK_OPTS)
+LIBRARY_LINK = ar cr
+LIBRARY_LINK_OPTS =
+LIB_SUFFIX = a
+LIBS_FOR_CONSOLE_APPLICATION =
+LIBS_FOR_GUI_APPLICATION =
+EXE =
diff --git a/config.linux b/config.linux-64bit
similarity index 74%
copy from config.linux
copy to config.linux-64bit
index 5c7bc8f..09df0a7 100644
--- a/config.linux
+++ b/config.linux-64bit
@@ -1,4 +1,4 @@
-COMPILE_OPTS = $(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
+COMPILE_OPTS = $(INCLUDES) -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
C = c
C_COMPILER = cc
C_FLAGS = $(COMPILE_OPTS)
diff --git a/groupsock/Groupsock.cpp b/groupsock/Groupsock.cpp
index c1d3137..1390b19 100644
--- a/groupsock/Groupsock.cpp
+++ b/groupsock/Groupsock.cpp
@@ -237,10 +237,14 @@ void Groupsock::removeAllDestinations() {
}
void Groupsock::multicastSendOnly() {
+ // We disable this code for now, because - on some systems - leaving the multicast group seems to cause sent packets
+ // to not be received by other applications (at least, on the same host).
+#if 0
socketLeaveGroup(env(), socketNum(), fIncomingGroupEId.groupAddress().s_addr);
for (destRecord* dests = fDests; dests != NULL; dests = dests->fNext) {
socketLeaveGroup(env(), socketNum(), dests->fGroupEId.groupAddress().s_addr);
}
+#endif
}
Boolean Groupsock::output(UsageEnvironment& env, u_int8_t ttlToSend,
diff --git a/groupsock/GroupsockHelper.cpp b/groupsock/GroupsockHelper.cpp
index c7c8098..5fe49e3 100644
--- a/groupsock/GroupsockHelper.cpp
+++ b/groupsock/GroupsockHelper.cpp
@@ -222,110 +222,39 @@ int setupStreamSocket(UsageEnvironment& env,
return newSocket;
}
-#ifndef IMN_PIM
-static int blockUntilReadable(UsageEnvironment& env,
- int socket, struct timeval* timeout) {
- int result = -1;
- do {
- fd_set rd_set;
- FD_ZERO(&rd_set);
- if (socket < 0) break;
- FD_SET((unsigned) socket, &rd_set);
- const unsigned numFds = socket+1;
-
- result = select(numFds, &rd_set, NULL, NULL, timeout);
- if (timeout != NULL && result == 0) {
- break; // this is OK - timeout occurred
- } else if (result <= 0) {
- int err = env.getErrno();
- if (err == EINTR || err == EAGAIN || err == EWOULDBLOCK) continue;
- socketErr(env, "select() error: ");
- break;
- }
-
- if (!FD_ISSET(socket, &rd_set)) {
- socketErr(env, "select() error - !FD_ISSET");
- break;
- }
- } while (0);
-
- return result;
-}
-#else
-extern int blockUntilReadable(UsageEnvironment& env,
- int socket, struct timeval* timeout);
-#endif
-
int readSocket(UsageEnvironment& env,
int socket, unsigned char* buffer, unsigned bufferSize,
- struct sockaddr_in& fromAddress,
- struct timeval* timeout) {
- int bytesRead = -1;
- do {
- int result = blockUntilReadable(env, socket, timeout);
- if (timeout != NULL && result == 0) {
- bytesRead = 0;
- break;
- } else if (result <= 0) {
- break;
- }
-
- SOCKLEN_T addressSize = sizeof fromAddress;
- bytesRead = recvfrom(socket, (char*)buffer, bufferSize, 0,
- (struct sockaddr*)&fromAddress,
- &addressSize);
- if (bytesRead < 0) {
- //##### HACK to work around bugs in Linux and Windows:
- int err = env.getErrno();
- if (err == 111 /*ECONNREFUSED (Linux)*/
+ struct sockaddr_in& fromAddress) {
+ SOCKLEN_T addressSize = sizeof fromAddress;
+ int bytesRead = recvfrom(socket, (char*)buffer, bufferSize, 0,
+ (struct sockaddr*)&fromAddress,
+ &addressSize);
+ if (bytesRead < 0) {
+ //##### HACK to work around bugs in Linux and Windows:
+ int err = env.getErrno();
+ if (err == 111 /*ECONNREFUSED (Linux)*/
#if defined(__WIN32__) || defined(_WIN32)
- // What a piece of crap Windows is. Sometimes
- // recvfrom() returns -1, but with an 'errno' of 0.
- // This appears not to be a real error; just treat
- // it as if it were a read of zero bytes, and hope
- // we don't have to do anything else to 'reset'
- // this alleged error:
- || err == 0
+ // What a piece of crap Windows is. Sometimes
+ // recvfrom() returns -1, but with an 'errno' of 0.
+ // This appears not to be a real error; just treat
+ // it as if it were a read of zero bytes, and hope
+ // we don't have to do anything else to 'reset'
+ // this alleged error:
+ || err == 0 || err == EWOULDBLOCK
#else
- || err == EAGAIN
+ || err == EAGAIN
#endif
- || err == 113 /*EHOSTUNREACH (Linux)*/) {
- //Why does Linux return this for datagram sock?
- fromAddress.sin_addr.s_addr = 0;
- return 0;
- }
- //##### END HACK
- socketErr(env, "recvfrom() error: ");
- break;
+ || err == 113 /*EHOSTUNREACH (Linux)*/) { // Why does Linux return this for datagram sock?
+ fromAddress.sin_addr.s_addr = 0;
+ return 0;
}
- } while (0);
+ //##### END HACK
+ socketErr(env, "recvfrom() error: ");
+ }
return bytesRead;
}
-
-int readSocketExact(UsageEnvironment& env,
- int socket, unsigned char* buffer, unsigned bufferSize,
- struct sockaddr_in& fromAddress,
- struct timeval* timeout) {
- /* read EXACTLY bufferSize bytes from the socket into the buffer.
- fromaddress is address of last read.
- return the number of bytes actually read when an error occurs
- */
- int bsize = bufferSize;
- int bytesRead = 0;
- int totBytesRead =0;
- do {
- bytesRead = readSocket (env, socket, buffer + totBytesRead, bsize,
- fromAddress, timeout);
- if (bytesRead <= 0) break;
- totBytesRead += bytesRead;
- bsize -= bytesRead;
- } while (bsize != 0);
-
- return totBytesRead;
-}
-
Boolean writeSocket(UsageEnvironment& env,
int socket, struct in_addr address, Port port,
u_int8_t ttlArg,
@@ -591,17 +520,23 @@ netAddressBits ourIPAddress(UsageEnvironment& env) {
if (!writeSocket(env, sock, testAddr, testPort, 0,
testString, testStringLength)) break;
- unsigned char readBuffer[20];
+ // Block until the socket is readable (with a 5-second timeout):
+ fd_set rd_set;
+ FD_ZERO(&rd_set);
+ FD_SET((unsigned)sock, &rd_set);
+ const unsigned numFds = sock+1;
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
+ int result = select(numFds, &rd_set, NULL, NULL, &timeout);
+ if (result <= 0) break;
+
+ unsigned char readBuffer[20];
int bytesRead = readSocket(env, sock,
readBuffer, sizeof readBuffer,
- fromAddr, &timeout);
- if (bytesRead == 0 // timeout occurred
- || bytesRead != (int)testStringLength
- || strncmp((char*)readBuffer, (char*)testString,
- testStringLength) != 0) {
+ fromAddr);
+ if (bytesRead != (int)testStringLength
+ || strncmp((char*)readBuffer, (char*)testString, testStringLength) != 0) {
break;
}
@@ -613,9 +548,7 @@ netAddressBits ourIPAddress(UsageEnvironment& env) {
// so try instead to look it up directly.
char hostname[100];
hostname[0] = '\0';
-#ifndef CRIS
gethostname(hostname, sizeof hostname);
-#endif
if (hostname[0] == '\0') {
env.setResultErrMsg("initial gethostname() failed");
break;
diff --git a/groupsock/include/GroupsockHelper.hh b/groupsock/include/GroupsockHelper.hh
index b1501ca..f9f2713 100644
--- a/groupsock/include/GroupsockHelper.hh
+++ b/groupsock/include/GroupsockHelper.hh
@@ -31,15 +31,7 @@ int setupStreamSocket(UsageEnvironment& env,
int readSocket(UsageEnvironment& env,
int socket, unsigned char* buffer, unsigned bufferSize,
- struct sockaddr_in& fromAddress,
- struct timeval* timeout = NULL);
-
-int readSocketExact(UsageEnvironment& env,
- int socket, unsigned char* buffer, unsigned bufferSize,
- struct sockaddr_in& fromAddress,
- struct timeval* timeout = NULL);
- // like "readSocket()", except that it rereads as many times as needed until
- // *exactly* "bufferSize" bytes are read.
+ struct sockaddr_in& fromAddress);
Boolean writeSocket(UsageEnvironment& env,
int socket, struct in_addr address, Port port,
diff --git a/groupsock/include/groupsock_version.hh b/groupsock/include/groupsock_version.hh
index c28bcf3..286bd29 100644
--- a/groupsock/include/groupsock_version.hh
+++ b/groupsock/include/groupsock_version.hh
@@ -4,7 +4,7 @@
#ifndef _GROUPSOCK_VERSION_HH
#define _GROUPSOCK_VERSION_HH
-#define GROUPSOCK_LIBRARY_VERSION_STRING "2010.04.09"
-#define GROUPSOCK_LIBRARY_VERSION_INT 1270771200
+#define GROUPSOCK_LIBRARY_VERSION_STRING "2010.09.25"
+#define GROUPSOCK_LIBRARY_VERSION_INT 1285372800
#endif
diff --git a/liveMedia/AMRAudioRTPSource.cpp b/liveMedia/AMRAudioRTPSource.cpp
index 01b2b2e..f2991cc 100644
--- a/liveMedia/AMRAudioRTPSource.cpp
+++ b/liveMedia/AMRAudioRTPSource.cpp
@@ -537,7 +537,7 @@ void AMRDeinterleavingBuffer
#ifdef DEBUG
fprintf(stderr, "AMRDeinterleavingBuffer::deliverIncomingFrame() param sanity check failed (%d,%d,%d,%d)\n", frameSize, fILL, ILP, frameIndex);
#endif
- abort();
+ source->envir().internalError();
}
--frameIndex; // because it was incremented by the source when this frame was read
diff --git a/liveMedia/AMRAudioSource.cpp b/liveMedia/AMRAudioSource.cpp
index fed1eb7..b8c3c4b 100644
--- a/liveMedia/AMRAudioSource.cpp
+++ b/liveMedia/AMRAudioSource.cpp
@@ -29,6 +29,10 @@ AMRAudioSource::AMRAudioSource(UsageEnvironment& env,
AMRAudioSource::~AMRAudioSource() {
}
+char const* AMRAudioSource::MIMEtype() const {
+ return "audio/AMR";
+}
+
Boolean AMRAudioSource::isAMRAudioSource() const {
return True;
}
diff --git a/liveMedia/BasicUDPSink.cpp b/liveMedia/BasicUDPSink.cpp
index 7ab6c2e..2553e2d 100644
--- a/liveMedia/BasicUDPSink.cpp
+++ b/liveMedia/BasicUDPSink.cpp
@@ -82,13 +82,8 @@ void BasicUDPSink::afterGettingFrame1(unsigned frameSize, unsigned numTruncatedB
struct timeval timeNow;
gettimeofday(&timeNow, NULL);
- int uSecondsToGo;
- if (fNextSendTime.tv_sec < timeNow.tv_sec) {
- uSecondsToGo = 0; // prevents integer underflow if too far behind
- } else {
- uSecondsToGo = (fNextSendTime.tv_sec - timeNow.tv_sec)*1000000
- + (fNextSendTime.tv_usec - timeNow.tv_usec);
- }
+ int64_t uSecondsToGo;
+ uSecondsToGo = (fNextSendTime.tv_sec - timeNow.tv_sec)*1000000 + (fNextSendTime.tv_usec - timeNow.tv_usec);
// Delay this amount of time:
nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo,
diff --git a/liveMedia/DarwinInjector.cpp b/liveMedia/DarwinInjector.cpp
index 0f335ca..12706f2 100644
--- a/liveMedia/DarwinInjector.cpp
+++ b/liveMedia/DarwinInjector.cpp
@@ -77,7 +77,7 @@ DarwinInjector::DarwinInjector(UsageEnvironment& env,
DarwinInjector::~DarwinInjector() {
if (fSession != NULL) { // close down and delete the session
- fRTSPClient->teardownMediaSession(*fSession);
+ fRTSPClient->sendTeardownCommand(*fSession, NULL);
Medium::close(fSession);
}
@@ -100,6 +100,17 @@ void DarwinInjector::addStream(RTPSink* rtpSink, RTCPInstance* rtcpInstance) {
fSubstreamSDPSizes += strlen(newDescriptor->sdpLines());
}
+// Define a special subclass of "RTSPClient" that has a pointer field to a "DarwinInjector". We'll use this to implement RTSP ops:
+class RTSPClientForDarwinInjector: public RTSPClient {
+public:
+ RTSPClientForDarwinInjector(UsageEnvironment& env, char const* rtspURL, int verbosityLevel, char const* applicationName,
+ DarwinInjector* ourDarwinInjector)
+ : RTSPClient(env, rtspURL, verbosityLevel, applicationName, 0),
+ fOurDarwinInjector(ourDarwinInjector) {}
+ virtual ~RTSPClientForDarwinInjector() {}
+ DarwinInjector* fOurDarwinInjector;
+};
+
Boolean DarwinInjector
::setDestination(char const* remoteRTSPServerNameOrAddress,
char const* remoteFileName,
@@ -116,8 +127,15 @@ Boolean DarwinInjector
Boolean success = False; // until we learn otherwise
do {
+ // Construct a RTSP URL for the remote stream:
+ char const* const urlFmt = "rtsp://%s:%u/%s";
+ unsigned urlLen
+ = strlen(urlFmt) + strlen(remoteRTSPServerNameOrAddress) + 5 /* max short len */ + strlen(remoteFileName);
+ url = new char[urlLen];
+ sprintf(url, urlFmt, remoteRTSPServerNameOrAddress, remoteRTSPServerPortNumber, remoteFileName);
+
// Begin by creating our RTSP client object:
- fRTSPClient = RTSPClient::createNew(envir(), fVerbosityLevel, fApplicationName);
+ fRTSPClient = new RTSPClientForDarwinInjector(envir(), url, fVerbosityLevel, fApplicationName, this);
if (fRTSPClient == NULL) break;
// Get the remote RTSP server's IP address:
@@ -176,24 +194,23 @@ Boolean DarwinInjector
p += strlen(p);
}
- // Construct a RTSP URL for the remote stream:
- char const* const urlFmt = "rtsp://%s:%u/%s";
- unsigned urlLen
- = strlen(urlFmt) + strlen(remoteRTSPServerNameOrAddress) + 5 /* max short len */ + strlen(remoteFileName);
- url = new char[urlLen];
- sprintf(url, urlFmt, remoteRTSPServerNameOrAddress, remoteRTSPServerPortNumber, remoteFileName);
-
// Do a RTSP "ANNOUNCE" with this SDP description:
- Boolean announceSuccess;
+ Authenticator auth;
+ Authenticator* authToUse = NULL;
if (remoteUserName[0] != '\0' || remotePassword[0] != '\0') {
- announceSuccess
- = fRTSPClient->announceWithPassword(url, sdp, remoteUserName, remotePassword, timeout);
- } else {
- announceSuccess = fRTSPClient->announceSDPDescription(url, sdp, NULL, timeout);
+ auth.setUsernameAndPassword(remoteUserName, remotePassword);
+ authToUse = &auth;
}
- if (!announceSuccess) break;
+ fWatchVariable = 0;
+ (void)fRTSPClient->sendAnnounceCommand(sdp, genericResponseHandler, authToUse);
- // Tell the remote server to start receiving the stream from us.
+ // Now block (but handling events) until we get a response:
+ envir().taskScheduler().doEventLoop(&fWatchVariable);
+
+ delete[] fResultString;
+ if (fResultCode != 0) break; // an error occurred with the RTSP "ANNOUNCE" command
+
+ // Next, tell the remote server to start receiving the stream from us.
// (To do this, we first create a "MediaSession" object from the SDP description.)
fSession = MediaSession::createNew(envir(), sdp);
if (fSession == NULL) break;
@@ -206,11 +223,15 @@ Boolean DarwinInjector
while ((subsession = iter.next()) != NULL) {
if (!subsession->initiate()) break;
- if (!fRTSPClient->setupMediaSubsession(*subsession,
- True /*streamOutgoing*/,
- True /*streamUsingTCP*/)) {
- break;
- }
+ fWatchVariable = 0;
+ (void)fRTSPClient->sendSetupCommand(*subsession, genericResponseHandler,
+ True /*streamOutgoing*/,
+ True /*streamUsingTCP*/);
+ // Now block (but handling events) until we get a response:
+ envir().taskScheduler().doEventLoop(&fWatchVariable);
+
+ delete[] fResultString;
+ if (fResultCode != 0) break; // an error occurred with the RTSP "SETUP" command
// Tell this subsession's RTPSink and RTCPInstance to use
// the RTSP TCP connection:
@@ -224,7 +245,14 @@ Boolean DarwinInjector
if (subsession != NULL) break; // an error occurred above
// Tell the RTSP server to start:
- if (!fRTSPClient->playMediaSession(*fSession)) break;
+ fWatchVariable = 0;
+ (void)fRTSPClient->sendPlayCommand(*fSession, genericResponseHandler);
+
+ // Now block (but handling events) until we get a response:
+ envir().taskScheduler().doEventLoop(&fWatchVariable);
+
+ delete[] fResultString;
+ if (fResultCode != 0) break; // an error occurred with the RTSP "PLAY" command
// Finally, make sure that the output TCP buffer is a reasonable size:
increaseSendBufferTo(envir(), fRTSPClient->socketNum(), 100*1024);
@@ -241,6 +269,19 @@ Boolean DarwinInjector::isDarwinInjector() const {
return True;
}
+void DarwinInjector::genericResponseHandler(RTSPClient* rtspClient, int responseCode, char* responseString) {
+ DarwinInjector* di = ((RTSPClientForDarwinInjector*)rtspClient)-> fOurDarwinInjector;
+ di->genericResponseHandler1(responseCode, responseString);
+}
+
+void DarwinInjector::genericResponseHandler1(int responseCode, char* responseString) {
+ // Set result values:
+ fResultCode = responseCode;
+ fResultString = responseString;
+
+ // Signal a break from the event loop (thereby returning from the blocking command):
+ fWatchVariable = ~0;
+}
////////// SubstreamDescriptor implementation //////////
diff --git a/liveMedia/FileSink.cpp b/liveMedia/FileSink.cpp
index 0284f58..529e4b5 100644
--- a/liveMedia/FileSink.cpp
+++ b/liveMedia/FileSink.cpp
@@ -88,7 +88,7 @@ void FileSink::afterGettingFrame(void* clientData, unsigned frameSize,
sink->afterGettingFrame1(frameSize, presentationTime);
}
-void FileSink::addData(unsigned char* data, unsigned dataSize,
+void FileSink::addData(unsigned char const* data, unsigned dataSize,
struct timeval presentationTime) {
if (fPerFrameFileNameBuffer != NULL) {
// Special case: Open a new file on-the-fly for this frame
diff --git a/liveMedia/FramedSource.cpp b/liveMedia/FramedSource.cpp
index cd6bc99..3471f9f 100644
--- a/liveMedia/FramedSource.cpp
+++ b/liveMedia/FramedSource.cpp
@@ -62,7 +62,7 @@ void FramedSource::getNextFrame(unsigned char* to, unsigned maxSize,
// Make sure we're not already being read:
if (fIsCurrentlyAwaitingData) {
envir() << "FramedSource[" << this << "]::getNextFrame(): attempting to read more than once at the same time!\n";
- abort();
+ envir().internalError();
}
fTo = to;
diff --git a/liveMedia/H264VideoFileSink.cpp b/liveMedia/H264VideoFileSink.cpp
index fbbc9d3..8da17ab 100644
--- a/liveMedia/H264VideoFileSink.cpp
+++ b/liveMedia/H264VideoFileSink.cpp
@@ -20,13 +20,16 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#include "H264VideoFileSink.hh"
#include "OutputFile.hh"
+#include "H264VideoRTPSource.hh"
////////// H264VideoFileSink //////////
H264VideoFileSink
-::H264VideoFileSink(UsageEnvironment& env, FILE* fid, unsigned bufferSize,
- char const* perFrameFileNamePrefix)
- : FileSink(env, fid, bufferSize, perFrameFileNamePrefix) {
+::H264VideoFileSink(UsageEnvironment& env, FILE* fid,
+ char const* sPropParameterSetsStr,
+ unsigned bufferSize, char const* perFrameFileNamePrefix)
+ : FileSink(env, fid, bufferSize, perFrameFileNamePrefix),
+ fSPropParameterSetsStr(sPropParameterSetsStr), fHaveWrittenFirstFrame(False) {
}
H264VideoFileSink::~H264VideoFileSink() {
@@ -34,7 +37,8 @@ H264VideoFileSink::~H264VideoFileSink() {
H264VideoFileSink*
H264VideoFileSink::createNew(UsageEnvironment& env, char const* fileName,
- unsigned bufferSize, Boolean oneFilePerFrame) {
+ char const* sPropParameterSetsStr,
+ unsigned bufferSize, Boolean oneFilePerFrame) {
do {
FILE* fid;
char const* perFrameFileNamePrefix;
@@ -49,20 +53,28 @@ H264VideoFileSink::createNew(UsageEnvironment& env, char const* fileName,
perFrameFileNamePrefix = NULL;
}
- return new H264VideoFileSink(env, fid, bufferSize, perFrameFileNamePrefix);
+ return new H264VideoFileSink(env, fid, sPropParameterSetsStr, bufferSize, perFrameFileNamePrefix);
} while (0);
return NULL;
}
-Boolean H264VideoFileSink::sourceIsCompatibleWithUs(MediaSource& source) {
- // Just return true, should be checking for H.264 video streams though
- return True;
-}
+void H264VideoFileSink::afterGettingFrame1(unsigned frameSize, struct timeval presentationTime) {
+ unsigned char const start_code[4] = {0x00, 0x00, 0x00, 0x01};
+
+ if (!fHaveWrittenFirstFrame) {
+ // If we have PPS/SPS NAL units encoded in a "sprop parameter string", prepend these to the file:
+ unsigned numSPropRecords;
+ SPropRecord* sPropRecords = parseSPropParameterSets(fSPropParameterSetsStr, numSPropRecords);
+ for (unsigned i = 0; i < numSPropRecords; ++i) {
+ addData(start_code, 4, presentationTime);
+ addData(sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength, presentationTime);
+ }
+ delete[] sPropRecords;
+ fHaveWrittenFirstFrame = True; // for next time
+ }
-void H264VideoFileSink::afterGettingFrame1(unsigned frameSize,
- struct timeval presentationTime) {
- unsigned char start_code[4] = {0x00, 0x00, 0x00, 0x01};
+ // Write the input data to the file, with the start code in front:
addData(start_code, 4, presentationTime);
// Call the parent class to complete the normal file write with the input data:
diff --git a/liveMedia/MP3ADU.cpp b/liveMedia/MP3ADU.cpp
index ab5d5f7..f0711cd 100644
--- a/liveMedia/MP3ADU.cpp
+++ b/liveMedia/MP3ADU.cpp
@@ -49,7 +49,7 @@ public:
unsigned const Segment::headerSize = 4;
-#define SegmentQueueSize 10
+#define SegmentQueueSize 20
class SegmentQueue {
public:
diff --git a/liveMedia/MPEG1or2Demux.cpp b/liveMedia/MPEG1or2Demux.cpp
index c552878..ad1637c 100644
--- a/liveMedia/MPEG1or2Demux.cpp
+++ b/liveMedia/MPEG1or2Demux.cpp
@@ -162,7 +162,7 @@ void MPEG1or2Demux::registerReadInterest(u_int8_t streamIdTag,
if (out.isCurrentlyAwaitingData) {
envir() << "MPEG1or2Demux::registerReadInterest(): attempt to read stream id "
<< (void*)streamIdTag << " more than once!\n";
- abort();
+ envir().internalError();
}
out.to = to; out.maxSize = maxSize;
diff --git a/liveMedia/MPEG1or2VideoRTPSource.cpp b/liveMedia/MPEG1or2VideoRTPSource.cpp
index f3d7443..ecb1abd 100644
--- a/liveMedia/MPEG1or2VideoRTPSource.cpp
+++ b/liveMedia/MPEG1or2VideoRTPSource.cpp
@@ -45,7 +45,7 @@ Boolean MPEG1or2VideoRTPSource
// There's a 4-byte video-specific header
if (packet->dataSize() < 4) return False;
- u_int32_t header = ntohl(*(unsigned*)(packet->data()));
+ u_int32_t header = ntohl(*(u_int32_t*)(packet->data()));
u_int32_t sBit = header&0x00002000; // sequence-header-present
u_int32_t bBit = header&0x00001000; // beginning-of-slice
@@ -66,7 +66,7 @@ Boolean MPEG1or2VideoRTPSource
// Extract the "Picture-Type" field from this, to determine whether
// this packet can be used in jitter calculations:
- unsigned header = ntohl(*(unsigned*)packet);
+ unsigned header = ntohl(*(u_int32_t*)packet);
unsigned short pictureType = (header>>8)&0x7;
if (pictureType == 1) { // an I frame
diff --git a/liveMedia/MPEG2TransportStreamFramer.cpp b/liveMedia/MPEG2TransportStreamFramer.cpp
index 7625963..3ea9a1d 100644
--- a/liveMedia/MPEG2TransportStreamFramer.cpp
+++ b/liveMedia/MPEG2TransportStreamFramer.cpp
@@ -24,15 +24,29 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#include <GroupsockHelper.hh> // for "gettimeofday()"
#define TRANSPORT_PACKET_SIZE 188
+
+////////// Definitions of constants that control the behavior of this code /////////
+
+#if !defined(NEW_DURATION_WEIGHT)
#define NEW_DURATION_WEIGHT 0.5
// How much weight to give to the latest duration measurement (must be <= 1)
+#endif
+
+#if !defined(TIME_ADJUSTMENT_FACTOR)
#define TIME_ADJUSTMENT_FACTOR 0.8
// A factor by which to adjust the duration estimate to ensure that the overall
// packet transmission times remains matched with the PCR times (which will be the
// times that we expect receivers to play the incoming packets).
// (must be <= 1)
+#endif
+
+#if !defined(MAX_PLAYOUT_BUFFER_DURATION)
#define MAX_PLAYOUT_BUFFER_DURATION 0.1 // (seconds)
+#endif
+
+#if !defined(PCR_PERIOD_VARIATION_RATIO)
#define PCR_PERIOD_VARIATION_RATIO 0.5
+#endif
////////// PIDStatus //////////
diff --git a/liveMedia/Makefile.tail b/liveMedia/Makefile.tail
index ac9eaae..ad685f2 100644
--- a/liveMedia/Makefile.tail
+++ b/liveMedia/Makefile.tail
@@ -179,7 +179,7 @@ BasicUDPSink.$(CPP): include/BasicUDPSink.hh
include/BasicUDPSink.hh: include/MediaSink.hh
AMRAudioFileSink.$(CPP): include/AMRAudioFileSink.hh include/AMRAudioSource.hh include/OutputFile.hh
include/AMRAudioFileSink.hh: include/FileSink.hh
-H264VideoFileSink.$(CPP): include/H264VideoFileSink.hh include/OutputFile.hh
+H264VideoFileSink.$(CPP): include/H264VideoFileSink.hh include/OutputFile.hh include/H264VideoRTPSource.hh
include/H264VideoFileSink.hh: include/FileSink.hh
HTTPSink.$(CPP): include/HTTPSink.hh
include/HTTPSink.hh: include/MediaSink.hh
diff --git a/liveMedia/Media.cpp b/liveMedia/Media.cpp
index b705359..5accb58 100644
--- a/liveMedia/Media.cpp
+++ b/liveMedia/Media.cpp
@@ -118,8 +118,8 @@ Boolean Medium::isDarwinInjector() const {
////////// _Tables implementation //////////
-_Tables* _Tables::getOurTables(UsageEnvironment& env) {
- if (env.liveMediaPriv == NULL) {
+_Tables* _Tables::getOurTables(UsageEnvironment& env, Boolean createIfNotPresent) {
+ if (env.liveMediaPriv == NULL && createIfNotPresent) {
env.liveMediaPriv = new _Tables(env);
}
return (_Tables*)(env.liveMediaPriv);
diff --git a/liveMedia/MediaSession.cpp b/liveMedia/MediaSession.cpp
index 6a598d4..9003333 100644
--- a/liveMedia/MediaSession.cpp
+++ b/liveMedia/MediaSession.cpp
@@ -964,10 +964,10 @@ void MediaSubsession::setDestinations(netAddressBits defaultDestAddress) {
}
double MediaSubsession::getNormalPlayTime(struct timeval const& presentationTime) {
- // First, check whether our "RTPSource" object has already been synchronized using RTCP.
- // If it hasn't, then - as a special case - we need to use the RTP timestamp to compute the NPT.
if (rtpSource() == NULL || rtpSource()->timestampFrequency() == 0) return 0.0; // no RTP source, or bad freq!
+ // First, check whether our "RTPSource" object has already been synchronized using RTCP.
+ // If it hasn't, then - as a special case - we need to use the RTP timestamp to compute the NPT.
if (!rtpSource()->hasBeenSynchronizedUsingRTCP()) {
if (!rtpInfo.infoIsNew) return 0.0; // the "rtpInfo" structure has not been filled in
u_int32_t timestampOffset = rtpSource()->curPacketRTPTimestamp() - rtpInfo.timestamp;
@@ -983,6 +983,7 @@ double MediaSubsession::getNormalPlayTime(struct timeval const& presentationTime
if (rtpInfo.infoIsNew) {
// This is the first time we've been called with a synchronized presentation time since the "rtpInfo"
// structure was last filled in. Use this "presentationTime" to compute "fNPT_PTS_Offset":
+ if (seqNumLT(rtpSource()->curPacketRTPSeqNum(), rtpInfo.seqNum)) return -0.1; // sanity check; ignore old packets
u_int32_t timestampOffset = rtpSource()->curPacketRTPTimestamp() - rtpInfo.timestamp;
double nptOffset = (timestampOffset/(double)(rtpSource()->timestampFrequency()))*scale();
double npt = playStartTime() + nptOffset;
diff --git a/liveMedia/MediaSink.cpp b/liveMedia/MediaSink.cpp
index 885360a..2c736cb 100644
--- a/liveMedia/MediaSink.cpp
+++ b/liveMedia/MediaSink.cpp
@@ -134,8 +134,8 @@ void OutPacketBuffer::enqueue(unsigned char const* from, unsigned numBytes) {
increment(numBytes);
}
-void OutPacketBuffer::enqueueWord(unsigned word) {
- unsigned nWord = htonl(word);
+void OutPacketBuffer::enqueueWord(u_int32_t word) {
+ u_int32_t nWord = htonl(word);
enqueue((unsigned char*)&nWord, 4);
}
@@ -153,8 +153,8 @@ void OutPacketBuffer::insert(unsigned char const* from, unsigned numBytes,
}
}
-void OutPacketBuffer::insertWord(unsigned word, unsigned toPosition) {
- unsigned nWord = htonl(word);
+void OutPacketBuffer::insertWord(u_int32_t word, unsigned toPosition) {
+ u_int32_t nWord = htonl(word);
insert((unsigned char*)&nWord, 4, toPosition);
}
@@ -169,8 +169,8 @@ void OutPacketBuffer::extract(unsigned char* to, unsigned numBytes,
memmove(to, &fBuf[realFromPosition], numBytes);
}
-unsigned OutPacketBuffer::extractWord(unsigned fromPosition) {
- unsigned nWord;
+u_int32_t OutPacketBuffer::extractWord(unsigned fromPosition) {
+ u_int32_t nWord;
extract((unsigned char*)&nWord, 4, fromPosition);
return ntohl(nWord);
}
diff --git a/liveMedia/MultiFramedRTPSink.cpp b/liveMedia/MultiFramedRTPSink.cpp
index 42066fd..268d13c 100644
--- a/liveMedia/MultiFramedRTPSink.cpp
+++ b/liveMedia/MultiFramedRTPSink.cpp
@@ -389,17 +389,10 @@ void MultiFramedRTPSink::sendPacketIfNecessary() {
// sending the next packet.
struct timeval timeNow;
gettimeofday(&timeNow, NULL);
- int uSecondsToGo;
- if (fNextSendTime.tv_sec < timeNow.tv_sec
- || (fNextSendTime.tv_sec == timeNow.tv_sec && fNextSendTime.tv_usec < timeNow.tv_usec)) {
- uSecondsToGo = 0; // prevents integer underflow if too far behind
- } else {
- uSecondsToGo = (fNextSendTime.tv_sec - timeNow.tv_sec)*1000000 + (fNextSendTime.tv_usec - timeNow.tv_usec);
- }
+ int64_t uSecondsToGo = (fNextSendTime.tv_sec - timeNow.tv_sec)*1000000 + (fNextSendTime.tv_usec - timeNow.tv_usec);
// Delay this amount of time:
- nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo,
- (TaskFunc*)sendNext, this);
+ nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);
}
}
diff --git a/liveMedia/MultiFramedRTPSource.cpp b/liveMedia/MultiFramedRTPSource.cpp
index 4b11a66..85acfff 100644
--- a/liveMedia/MultiFramedRTPSource.cpp
+++ b/liveMedia/MultiFramedRTPSource.cpp
@@ -77,6 +77,7 @@ void MultiFramedRTPSource::reset() {
fCurrentPacketBeginsFrame = True; // by default
fCurrentPacketCompletesFrame = True; // by default
fAreDoingNetworkReads = False;
+ fPacketReadInProgress = NULL;
fNeedDelivery = False;
fPacketLossInFragmentedFrame = False;
}
@@ -214,28 +215,41 @@ void MultiFramedRTPSource
#define ADVANCE(n) do { bPacket->skip(n); } while (0)
-void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source,
- int /*mask*/) {
- // Get a free BufferedPacket descriptor to hold the new network packet:
- BufferedPacket* bPacket
- = source->fReorderingBuffer->getFreePacket(source);
+void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source, int /*mask*/) {
+ source->networkReadHandler1();
+}
+
+void MultiFramedRTPSource::networkReadHandler1() {
+ BufferedPacket* bPacket = fPacketReadInProgress;
+ if (bPacket == NULL) {
+ // Normal case: Get a free BufferedPacket descriptor to hold the new network packet:
+ bPacket = fReorderingBuffer->getFreePacket(this);
+ }
// Read the network packet, and perform sanity checks on the RTP header:
Boolean readSuccess = False;
do {
- if (!bPacket->fillInData(source->fRTPInterface)) break;
+ Boolean packetReadWasIncomplete = fPacketReadInProgress != NULL;
+ if (!bPacket->fillInData(fRTPInterface, packetReadWasIncomplete)) break;
+ if (packetReadWasIncomplete) {
+ // We need additional read(s) before we can process the incoming packet:
+ fPacketReadInProgress = bPacket;
+ return;
+ } else {
+ fPacketReadInProgress = NULL;
+ }
#ifdef TEST_LOSS
- source->setPacketReorderingThresholdTime(0);
+ setPacketReorderingThresholdTime(0);
// don't wait for 'lost' packets to arrive out-of-order later
if ((our_random()%10) == 0) break; // simulate 10% packet loss
#endif
// Check for the 12-byte RTP header:
if (bPacket->dataSize() < 12) break;
- unsigned rtpHdr = ntohl(*(unsigned*)(bPacket->data())); ADVANCE(4);
+ unsigned rtpHdr = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(4);
Boolean rtpMarkerBit = (rtpHdr&0x00800000) >> 23;
- unsigned rtpTimestamp = ntohl(*(unsigned*)(bPacket->data()));ADVANCE(4);
- unsigned rtpSSRC = ntohl(*(unsigned*)(bPacket->data())); ADVANCE(4);
+ unsigned rtpTimestamp = ntohl(*(u_int32_t*)(bPacket->data()));ADVANCE(4);
+ unsigned rtpSSRC = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(4);
// Check the RTP version number (it should be 2):
if ((rtpHdr&0xC0000000) != 0x80000000) break;
@@ -248,7 +262,7 @@ void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source,
// Check for (& ignore) any RTP header extension
if (rtpHdr&0x10000000) {
if (bPacket->dataSize() < 4) break;
- unsigned extHdr = ntohl(*(unsigned*)(bPacket->data())); ADVANCE(4);
+ unsigned extHdr = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(4);
unsigned remExtSize = 4*(extHdr&0xFFFF);
if (bPacket->dataSize() < remExtSize) break;
ADVANCE(remExtSize);
@@ -264,21 +278,21 @@ void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source,
}
// Check the Payload Type.
if ((unsigned char)((rtpHdr&0x007F0000)>>16)
- != source->rtpPayloadFormat()) {
+ != rtpPayloadFormat()) {
break;
}
// The rest of the packet is the usable data. Record and save it:
- source->fLastReceivedSSRC = rtpSSRC;
+ fLastReceivedSSRC = rtpSSRC;
unsigned short rtpSeqNo = (unsigned short)(rtpHdr&0xFFFF);
Boolean usableInJitterCalculation
- = source->packetIsUsableInJitterCalculation((bPacket->data()),
+ = packetIsUsableInJitterCalculation((bPacket->data()),
bPacket->dataSize());
struct timeval presentationTime; // computed by:
Boolean hasBeenSyncedUsingRTCP; // computed by:
- source->receptionStatsDB()
+ receptionStatsDB()
.noteIncomingPacket(rtpSSRC, rtpSeqNo, rtpTimestamp,
- source->timestampFrequency(),
+ timestampFrequency(),
usableInJitterCalculation, presentationTime,
hasBeenSyncedUsingRTCP, bPacket->dataSize());
@@ -288,13 +302,13 @@ void MultiFramedRTPSource::networkReadHandler(MultiFramedRTPSource* source,
bPacket->assignMiscParams(rtpSeqNo, rtpTimestamp, presentationTime,
hasBeenSyncedUsingRTCP, rtpMarkerBit,
timeNow);
- if (!source->fReorderingBuffer->storePacket(bPacket)) break;
+ if (!fReorderingBuffer->storePacket(bPacket)) break;
readSuccess = True;
} while (0);
- if (!readSuccess) source->fReorderingBuffer->freePacket(bPacket);
+ if (!readSuccess) fReorderingBuffer->freePacket(bPacket);
- source->doGetNextFrame1();
+ doGetNextFrame1();
// If we didn't get proper data this time, we'll get another chance
}
@@ -344,13 +358,12 @@ void BufferedPacket
frameDurationInMicroseconds = 0; // by default. Subclasses should correct this.
}
-Boolean BufferedPacket::fillInData(RTPInterface& rtpInterface) {
- reset();
+Boolean BufferedPacket::fillInData(RTPInterface& rtpInterface, Boolean& packetReadWasIncomplete) {
+ if (!packetReadWasIncomplete) reset();
unsigned numBytesRead;
struct sockaddr_in fromAddress;
- if (!rtpInterface.handleRead(&fBuf[fTail], fPacketSize-fTail, numBytesRead,
- fromAddress)) {
+ if (!rtpInterface.handleRead(&fBuf[fTail], fPacketSize-fTail, numBytesRead, fromAddress, packetReadWasIncomplete)) {
return False;
}
fTail += numBytesRead;
@@ -459,8 +472,7 @@ void ReorderingPacketBuffer::reset() {
fSavedPacket = NULL;
}
-BufferedPacket* ReorderingPacketBuffer
-::getFreePacket(MultiFramedRTPSource* ourSource) {
+BufferedPacket* ReorderingPacketBuffer::getFreePacket(MultiFramedRTPSource* ourSource) {
if (fSavedPacket == NULL) { // we're being called for the first time
fSavedPacket = fPacketFactory->createNewPacket(ourSource);
fSavedPacketFree = True;
diff --git a/liveMedia/OnDemandServerMediaSubsession.cpp b/liveMedia/OnDemandServerMediaSubsession.cpp
index 2524250..069132e 100644
--- a/liveMedia/OnDemandServerMediaSubsession.cpp
+++ b/liveMedia/OnDemandServerMediaSubsession.cpp
@@ -466,7 +466,7 @@ void StreamState
// Change RTP and RTCP to use the TCP socket instead of UDP:
if (fRTPSink != NULL) {
fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);
- fRTPSink->setServerRequestAlternativeByteHandler(serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
+ fRTPSink->setServerRequestAlternativeByteHandler(dests->tcpSocketNum, serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
}
if (fRTCPInstance != NULL) {
fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId);
diff --git a/liveMedia/RTCP.cpp b/liveMedia/RTCP.cpp
index ea2d74a..e004feb 100644
--- a/liveMedia/RTCP.cpp
+++ b/liveMedia/RTCP.cpp
@@ -149,6 +149,7 @@ RTCPInstance::RTCPInstance(UsageEnvironment& env, Groupsock* RTCPgs,
fKnownMembers = new RTCPMemberDatabase(*this);
fInBuf = new unsigned char[maxPacketSize];
if (fKnownMembers == NULL || fInBuf == NULL) return;
+ fNumBytesAlreadyRead = 0;
// A hack to save buffer space, because RTCP packets are always small:
unsigned savedMaxSize = OutPacketBuffer::maxSize;
@@ -297,7 +298,7 @@ void RTCPInstance::setStreamSocket(int sockNum,
void RTCPInstance::addStreamSocket(int sockNum,
unsigned char streamChannelId) {
// First, turn off background read handling for the default (UDP) socket:
- fRTCPInterface.stopNetworkReading();
+ envir().taskScheduler().turnOffBackgroundReadHandling(fRTCPInterface.gs()->socketNum());
// Add the RTCP-over-TCP interface:
fRTCPInterface.addStreamSocket(sockNum, streamChannelId);
@@ -319,18 +320,24 @@ void RTCPInstance::incomingReportHandler(RTCPInstance* instance,
}
void RTCPInstance::incomingReportHandler1() {
- unsigned char* pkt = fInBuf;
- unsigned packetSize;
- struct sockaddr_in fromAddress;
- int typeOfPacket = PACKET_UNKNOWN_TYPE;
-
do {
int tcpReadStreamSocketNum = fRTCPInterface.nextTCPReadStreamSocketNum();
unsigned char tcpReadStreamChannelId = fRTCPInterface.nextTCPReadStreamChannelId();
- if (!fRTCPInterface.handleRead(pkt, maxPacketSize,
- packetSize, fromAddress)) {
- break;
+ unsigned packetSize = 0;
+ unsigned numBytesRead;
+ struct sockaddr_in fromAddress;
+ Boolean packetReadWasIncomplete;
+ Boolean readResult
+ = fRTCPInterface.handleRead(&fInBuf[fNumBytesAlreadyRead], maxPacketSize - fNumBytesAlreadyRead,
+ numBytesRead, fromAddress, packetReadWasIncomplete);
+ if (packetReadWasIncomplete) {
+ fNumBytesAlreadyRead += numBytesRead;
+ return; // more reads are needed to get the entire packet
+ } else { // normal case: We've read the entire packet
+ packetSize = fNumBytesAlreadyRead + numBytesRead;
+ fNumBytesAlreadyRead = 0; // for next time
}
+ if (!readResult) break;
// Ignore the packet if it was looped-back from ourself:
if (RTCPgs()->wasLoopedBackFromUs(envir(), fromAddress)) {
@@ -346,6 +353,7 @@ void RTCPInstance::incomingReportHandler1() {
}
}
+ unsigned char* pkt = fInBuf;
if (fIsSSMSource) {
// This packet was received via unicast. 'Reflect' it by resending
// it to the multicast group.
@@ -360,10 +368,9 @@ void RTCPInstance::incomingReportHandler1() {
#ifdef DEBUG
fprintf(stderr, "[%p]saw incoming RTCP packet (from address %s, port %d)\n", this, our_inet_ntoa(fromAddress.sin_addr), ntohs(fromAddress.sin_port));
- unsigned char* p = pkt;
for (unsigned i = 0; i < packetSize; ++i) {
if (i%4 == 0) fprintf(stderr, " ");
- fprintf(stderr, "%02x", p[i]);
+ fprintf(stderr, "%02x", pkt[i]);
}
fprintf(stderr, "\n");
#endif
@@ -374,7 +381,7 @@ void RTCPInstance::incomingReportHandler1() {
// must be version=2, with no padding bit, and a payload type of
// SR (200) or RR (201):
if (packetSize < 4) break;
- unsigned rtcpHdr = ntohl(*(unsigned*)pkt);
+ unsigned rtcpHdr = ntohl(*(u_int32_t*)pkt);
if ((rtcpHdr & 0xE0FE0000) != (0x80000000 | (RTCP_PT_SR<<16))) {
#ifdef DEBUG
fprintf(stderr, "rejected bad RTCP packet: header 0x%08x\n", rtcpHdr);
@@ -384,6 +391,7 @@ void RTCPInstance::incomingReportHandler1() {
// Process each of the individual RTCP 'subpackets' in (what may be)
// a compound RTCP packet.
+ int typeOfPacket = PACKET_UNKNOWN_TYPE;
unsigned reportSenderSSRC = 0;
Boolean packetOK = False;
while (1) {
@@ -395,7 +403,7 @@ void RTCPInstance::incomingReportHandler1() {
// Assume that each RTCP subpacket begins with a 4-byte SSRC:
if (length < 4) break; length -= 4;
- reportSenderSSRC = ntohl(*(unsigned*)pkt); ADVANCE(4);
+ reportSenderSSRC = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
Boolean subPacketOK = False;
switch (pt) {
@@ -406,9 +414,9 @@ void RTCPInstance::incomingReportHandler1() {
if (length < 20) break; length -= 20;
// Extract the NTP timestamp, and note this:
- unsigned NTPmsw = ntohl(*(unsigned*)pkt); ADVANCE(4);
- unsigned NTPlsw = ntohl(*(unsigned*)pkt); ADVANCE(4);
- unsigned rtpTimestamp = ntohl(*(unsigned*)pkt); ADVANCE(4);
+ unsigned NTPmsw = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+ unsigned NTPlsw = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+ unsigned rtpTimestamp = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
if (fSource != NULL) {
RTPReceptionStatsDB& receptionStats
= fSource->receptionStatsDB();
@@ -434,14 +442,14 @@ void RTCPInstance::incomingReportHandler1() {
// Use this information to update stats about our transmissions:
RTPTransmissionStatsDB& transmissionStats = fSink->transmissionStatsDB();
for (unsigned i = 0; i < rc; ++i) {
- unsigned senderSSRC = ntohl(*(unsigned*)pkt); ADVANCE(4);
+ unsigned senderSSRC = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
// We care only about reports about our own transmission, not others'
if (senderSSRC == fSink->SSRC()) {
- unsigned lossStats = ntohl(*(unsigned*)pkt); ADVANCE(4);
- unsigned highestReceived = ntohl(*(unsigned*)pkt); ADVANCE(4);
- unsigned jitter = ntohl(*(unsigned*)pkt); ADVANCE(4);
- unsigned timeLastSR = ntohl(*(unsigned*)pkt); ADVANCE(4);
- unsigned timeSinceLastSR = ntohl(*(unsigned*)pkt); ADVANCE(4);
+ unsigned lossStats = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+ unsigned highestReceived = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+ unsigned jitter = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+ unsigned timeLastSR = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+ unsigned timeSinceLastSR = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
transmissionStats.noteIncomingRR(reportSenderSSRC, fromAddress,
lossStats,
highestReceived, jitter,
@@ -541,7 +549,7 @@ void RTCPInstance::incomingReportHandler1() {
#endif
break;
}
- rtcpHdr = ntohl(*(unsigned*)pkt);
+ rtcpHdr = ntohl(*(u_int32_t*)pkt);
if ((rtcpHdr & 0xC0000000) != 0x80000000) {
#ifdef DEBUG
fprintf(stderr, "bad RTCP subpacket: header 0x%08x\n", rtcpHdr);
@@ -860,10 +868,11 @@ void RTCPInstance::schedule(double nextTime) {
fNextReportTime = nextTime;
double secondsToDelay = nextTime - dTimeNow();
+ if (secondsToDelay < 0) secondsToDelay = 0;
#ifdef DEBUG
fprintf(stderr, "schedule(%f->%f)\n", secondsToDelay, nextTime);
#endif
- int usToGo = (int)(secondsToDelay * 1000000);
+ int64_t usToGo = (int64_t)(secondsToDelay * 1000000);
nextTask() = envir().taskScheduler().scheduleDelayedTask(usToGo,
(TaskFunc*)RTCPInstance::onExpire, this);
}
diff --git a/liveMedia/RTPInterface.cpp b/liveMedia/RTPInterface.cpp
index d49d544..ccba41e 100644
--- a/liveMedia/RTPInterface.cpp
+++ b/liveMedia/RTPInterface.cpp
@@ -37,8 +37,10 @@ static void sendRTPOverTCP(unsigned char* packet, unsigned packetSize,
// "SocketDescriptor" that contains a hash table for each of the
// sub-channels that are reading from this socket.
-static HashTable* socketHashTable(UsageEnvironment& env) {
- _Tables* ourTables = _Tables::getOurTables(env);
+static HashTable* socketHashTable(UsageEnvironment& env, Boolean createIfNotPresent = True) {
+ _Tables* ourTables = _Tables::getOurTables(env, createIfNotPresent);
+ if (ourTables == NULL) return NULL;
+
if (ourTables->socketTable == NULL) {
// Create a new socket number -> SocketDescriptor mapping table:
ourTables->socketTable = HashTable::create(ONE_WORD_HASH_KEYS);
@@ -63,6 +65,7 @@ public:
private:
static void tcpReadHandler(SocketDescriptor*, int mask);
+ void tcpReadHandler1(int mask);
private:
UsageEnvironment& fEnv;
@@ -70,14 +73,19 @@ private:
HashTable* fSubChannelHashTable;
ServerRequestAlternativeByteHandler* fServerRequestAlternativeByteHandler;
void* fServerRequestAlternativeByteHandlerClientData;
+ u_int8_t fStreamChannelId, fSizeByte1;
+ enum { AWAITING_DOLLAR, AWAITING_STREAM_CHANNEL_ID, AWAITING_SIZE1, AWAITING_SIZE2, AWAITING_PACKET_DATA } fTCPReadingState;
};
static SocketDescriptor* lookupSocketDescriptor(UsageEnvironment& env, int sockNum, Boolean createIfNotFound = True) {
+ HashTable* table = socketHashTable(env, createIfNotFound);
+ if (table == NULL) return NULL;
+
char const* key = (char const*)(long)sockNum;
- SocketDescriptor* socketDescriptor = (SocketDescriptor*)(socketHashTable(env)->Lookup(key));
+ SocketDescriptor* socketDescriptor = (SocketDescriptor*)(table->Lookup(key));
if (socketDescriptor == NULL && createIfNotFound) {
socketDescriptor = new SocketDescriptor(env, sockNum);
- socketHashTable(env)->Add((char const*)(long)(sockNum), socketDescriptor);
+ table->Add((char const*)(long)(sockNum), socketDescriptor);
}
return socketDescriptor;
@@ -166,14 +174,11 @@ void RTPInterface::removeStreamSocket(int sockNum,
}
}
-void RTPInterface::setServerRequestAlternativeByteHandler(ServerRequestAlternativeByteHandler* handler, void* clientData) {
- for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;
- streams = streams->fNext) {
- // Get (or create, if necessary) a socket descriptor for "streams->fStreamSocketNum":
- SocketDescriptor* socketDescriptor = lookupSocketDescriptor(envir(), streams->fStreamSocketNum);
+void RTPInterface
+::setServerRequestAlternativeByteHandler(int socketNum, ServerRequestAlternativeByteHandler* handler, void* clientData) {
+ SocketDescriptor* socketDescriptor = lookupSocketDescriptor(envir(), socketNum);
- socketDescriptor->setServerRequestAlternativeByteHandler(handler, clientData);
- }
+ if (socketDescriptor != NULL) socketDescriptor->setServerRequestAlternativeByteHandler(handler, clientData);
}
@@ -207,10 +212,9 @@ void RTPInterface
}
}
-Boolean RTPInterface::handleRead(unsigned char* buffer,
- unsigned bufferMaxSize,
- unsigned& bytesRead,
- struct sockaddr_in& fromAddress) {
+Boolean RTPInterface::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
+ unsigned& bytesRead, struct sockaddr_in& fromAddress, Boolean& packetReadWasIncomplete) {
+ packetReadWasIncomplete = False; // by default
Boolean readSuccess;
if (fNextTCPReadStreamSocketNum < 0) {
// Normal case: read from the (datagram) 'groupsock':
@@ -229,7 +233,11 @@ Boolean RTPInterface::handleRead(unsigned char* buffer,
if (bytesRead >= totBytesToRead) break;
curBytesToRead -= curBytesRead;
}
- if (curBytesRead <= 0) {
+ fNextTCPReadSize -= bytesRead;
+ if (curBytesRead == 0 && curBytesToRead > 0) {
+ packetReadWasIncomplete = True;
+ return True;
+ } else if (curBytesRead < 0) {
bytesRead = 0;
readSuccess = False;
} else {
@@ -294,7 +302,8 @@ void sendRTPOverTCP(unsigned char* packet, unsigned packetSize,
SocketDescriptor::SocketDescriptor(UsageEnvironment& env, int socketNum)
:fEnv(env), fOurSocketNum(socketNum),
fSubChannelHashTable(HashTable::create(ONE_WORD_HASH_KEYS)),
- fServerRequestAlternativeByteHandler(NULL), fServerRequestAlternativeByteHandlerClientData(NULL) {
+ fServerRequestAlternativeByteHandler(NULL), fServerRequestAlternativeByteHandlerClientData(NULL),
+ fTCPReadingState(AWAITING_DOLLAR) {
}
SocketDescriptor::~SocketDescriptor() {
@@ -335,54 +344,87 @@ void SocketDescriptor
}
void SocketDescriptor::tcpReadHandler(SocketDescriptor* socketDescriptor, int mask) {
- do {
- UsageEnvironment& env = socketDescriptor->fEnv; // abbrev
- int socketNum = socketDescriptor->fOurSocketNum;
-
- // Begin by reading any characters that aren't '$'. Any such characters are regular RTSP commands or responses,
- // which need to be handled separately.
- unsigned char c;
- struct sockaddr_in fromAddress;
- struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0;
- while (1) {
- int result = readSocket(env, socketNum, &c, 1, fromAddress, &timeout);
- if (result != 1) { // error reading TCP socket
- if (result < 0) {
- env.taskScheduler().turnOffBackgroundReadHandling(socketNum); // stops further calls to us
+ socketDescriptor->tcpReadHandler1(mask);
+}
+
+void SocketDescriptor::tcpReadHandler1(int mask) {
+ // We expect the following data over the TCP channel:
+ // optional RTSP command or response bytes (before the first '$' character)
+ // a '$' character
+ // a 1-byte channel id
+ // a 2-byte packet size (in network byte order)
+ // the packet data.
+ // However, because the socket is being read asynchronously, this data might arrive in pieces.
+
+ u_int8_t c;
+ struct sockaddr_in fromAddress;
+ if (fTCPReadingState != AWAITING_PACKET_DATA) {
+ int result = readSocket(fEnv, fOurSocketNum, &c, 1, fromAddress);
+ if (result != 1) { // error reading TCP socket, or no more data available
+ if (result < 0) { // error
+ fEnv.taskScheduler().turnOffBackgroundReadHandling(fOurSocketNum); // stops further calls to us
+ }
+ return;
+ }
+ }
+
+ switch (fTCPReadingState) {
+ case AWAITING_DOLLAR: {
+ if (c == '$') {
+ fTCPReadingState = AWAITING_STREAM_CHANNEL_ID;
+ } else {
+ // This character is part of a RTSP request or command, which is handled separately:
+ if (fServerRequestAlternativeByteHandler != NULL) {
+ (*fServerRequestAlternativeByteHandler)(fServerRequestAlternativeByteHandlerClientData, c);
}
- return;
}
-
- if (c == '$') break;
- if (socketDescriptor->fServerRequestAlternativeByteHandler != NULL) {
- (*socketDescriptor->fServerRequestAlternativeByteHandler)(socketDescriptor->fServerRequestAlternativeByteHandlerClientData, c);
+ break;
+ }
+ case AWAITING_STREAM_CHANNEL_ID: {
+ // The byte that we read is the stream channel id.
+ fStreamChannelId = c;
+ fTCPReadingState = AWAITING_SIZE1;
+ break;
+ }
+ case AWAITING_SIZE1: {
+ // The byte that we read is the first (high) byte of the 16-bit RTP or RTCP packet 'size'.
+ fSizeByte1 = c;
+ fTCPReadingState = AWAITING_SIZE2;
+ break;
+ }
+ case AWAITING_SIZE2: {
+ // The byte that we read is the second (low) byte of the 16-bit RTP or RTCP packet 'size'.
+ unsigned short size = (fSizeByte1<<8)|c;
+
+ // Record the information about the packet data that will be read next:
+ RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);
+ if (rtpInterface != NULL) {
+ rtpInterface->fNextTCPReadSize = size;
+ rtpInterface->fNextTCPReadStreamSocketNum = fOurSocketNum;
+ rtpInterface->fNextTCPReadStreamChannelId = fStreamChannelId;
}
+ fTCPReadingState = AWAITING_PACKET_DATA;
+ break;
}
-
- // The next byte is the stream channel id:
- unsigned char streamChannelId;
- if (readSocket(env, socketNum, &streamChannelId, 1, fromAddress) != 1) break;
- RTPInterface* rtpInterface = socketDescriptor->lookupRTPInterface(streamChannelId);
- if (rtpInterface == NULL) break; // we're not interested in this channel
-
- // The next two bytes are the RTP or RTCP packet size (in network order)
- unsigned short size;
- if (readSocketExact(env, socketNum, (unsigned char*)&size, 2,
- fromAddress) != 2) break;
- rtpInterface->fNextTCPReadSize = ntohs(size);
- rtpInterface->fNextTCPReadStreamSocketNum = socketNum;
- rtpInterface->fNextTCPReadStreamChannelId = streamChannelId;
+ case AWAITING_PACKET_DATA: {
+ // Call the appropriate read handler to get the packet data from the TCP stream:
+ RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);
+ if (rtpInterface != NULL) {
+ if (rtpInterface->fNextTCPReadSize == 0) {
+ // We've already read all the data for this packet.
+ fTCPReadingState = AWAITING_DOLLAR;
+ break;
+ }
+ if (rtpInterface->fReadHandlerProc != NULL) {
#ifdef DEBUG
- fprintf(stderr, "SocketDescriptor::tcpReadHandler() reading %d bytes on channel %d\n", rtpInterface->fNextTCPReadSize, streamChannelId);
+ fprintf(stderr, "SocketDescriptor::tcpReadHandler() reading %d bytes on channel %d\n", rtpInterface->fNextTCPReadSize, rtpInterface->fNextTCPReadStreamChannelId);
#endif
-
- // Now that we have the data set up, call this subchannel's
- // read handler:
- if (rtpInterface->fReadHandlerProc != NULL) {
- rtpInterface->fReadHandlerProc(rtpInterface->fOwner, mask);
+ rtpInterface->fReadHandlerProc(rtpInterface->fOwner, mask);
+ }
+ }
+ return;
}
-
- } while (0);
+ }
}
diff --git a/liveMedia/RTPSource.cpp b/liveMedia/RTPSource.cpp
index ac289b0..810449a 100644
--- a/liveMedia/RTPSource.cpp
+++ b/liveMedia/RTPSource.cpp
@@ -217,8 +217,8 @@ void RTPReceptionStats::init(u_int32_t SSRC) {
}
void RTPReceptionStats::initSeqNum(u_int16_t initialSeqNum) {
- fBaseExtSeqNumReceived = initialSeqNum-1;
- fHighestExtSeqNumReceived = initialSeqNum;
+ fBaseExtSeqNumReceived = 0x10000 | initialSeqNum;
+ fHighestExtSeqNumReceived = 0x10000 | initialSeqNum;
fHaveSeenInitialSequenceNumber = True;
}
@@ -245,19 +245,33 @@ void RTPReceptionStats
// Check whether the new sequence number is the highest yet seen:
unsigned oldSeqNum = (fHighestExtSeqNumReceived&0xFFFF);
+ unsigned seqNumCycle = (fHighestExtSeqNumReceived&0xFFFF0000);
+ unsigned seqNumDifference = (unsigned)((int)seqNum-(int)oldSeqNum);
+ unsigned newSeqNum = 0;
if (seqNumLT((u_int16_t)oldSeqNum, seqNum)) {
// This packet was not an old packet received out of order, so check it:
- unsigned seqNumCycle = (fHighestExtSeqNumReceived&0xFFFF0000);
- unsigned seqNumDifference = (unsigned)((int)seqNum-(int)oldSeqNum);
+
if (seqNumDifference >= 0x8000) {
// The sequence number wrapped around, so start a new cycle:
seqNumCycle += 0x10000;
}
-
- unsigned newSeqNum = seqNumCycle|seqNum;
+
+ newSeqNum = seqNumCycle|seqNum;
if (newSeqNum > fHighestExtSeqNumReceived) {
fHighestExtSeqNumReceived = newSeqNum;
}
+ } else if (fTotNumPacketsReceived > 1) {
+ // This packet was an old packet received out of order
+
+ if ((int)seqNumDifference >= 0x8000) {
+ // The sequence number wrapped around, so switch to an old cycle:
+ seqNumCycle -= 0x10000;
+ }
+
+ newSeqNum = seqNumCycle|seqNum;
+ if (newSeqNum < fBaseExtSeqNumReceived) {
+ fBaseExtSeqNumReceived = newSeqNum;
+ }
}
// Record the inter-packet delay
diff --git a/liveMedia/RTSPClient.cpp b/liveMedia/RTSPClient.cpp
index e7382fa..f1f03b1 100644
--- a/liveMedia/RTSPClient.cpp
+++ b/liveMedia/RTSPClient.cpp
@@ -25,752 +25,579 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#include <GroupsockHelper.hh>
#include "our_md5.h"
-////////// RTSPClient //////////
+////////// RTSPClient implementation //////////
-RTSPClient* RTSPClient::createNew(UsageEnvironment& env,
+RTSPClient* RTSPClient::createNew(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel,
char const* applicationName,
portNumBits tunnelOverHTTPPortNum) {
- return new RTSPClient(env, verbosityLevel,
- applicationName, tunnelOverHTTPPortNum);
+ return new RTSPClient(env, rtspURL,
+ verbosityLevel, applicationName, tunnelOverHTTPPortNum);
}
-Boolean RTSPClient::lookupByName(UsageEnvironment& env,
- char const* instanceName,
- RTSPClient*& resultClient) {
- resultClient = NULL; // unless we succeed
-
- Medium* medium;
- if (!Medium::lookupByName(env, instanceName, medium)) return False;
-
- if (!medium->isRTSPClient()) {
- env.setResultMsg(instanceName, " is not a RTSP client");
- return False;
- }
-
- resultClient = (RTSPClient*)medium;
- return True;
+unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
}
-RTSPClient::RTSPClient(UsageEnvironment& env,
- int verbosityLevel, char const* applicationName,
- portNumBits tunnelOverHTTPPortNum)
- : Medium(env),
- fVerbosityLevel(verbosityLevel),
- fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum),
- fInputSocketNum(-1), fOutputSocketNum(-1), fServerAddress(0), fCSeq(0),
- fBaseURL(NULL), fTCPStreamIdCount(0), fLastSessionId(NULL),
- fSessionTimeoutParameter(0),
- fServerIsKasenna(False), fKasennaContentType(NULL),
- fServerIsMicrosoft(False)
-{
- fResponseBufferSize = 20000;
- fResponseBuffer = new char[fResponseBufferSize+1];
+unsigned RTSPClient::sendOptionsCommand(responseHandler* responseHandler, Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "OPTIONS", responseHandler));
+}
- // 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;
- char const* libPrefix; char const* libSuffix;
- if (applicationName == NULL || applicationName[0] == '\0') {
- applicationName = libPrefix = libSuffix = "";
- } else {
- libPrefix = " (";
- libSuffix = ")";
- }
- char const* const formatStr = "User-Agent: %s%s%s%s%s\r\n";
- unsigned headerSize
- = strlen(formatStr) + strlen(applicationName) + strlen(libPrefix)
- + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix);
- fUserAgentHeaderStr = new char[headerSize];
- sprintf(fUserAgentHeaderStr, formatStr,
- applicationName, libPrefix, libName, libVersionStr, libSuffix);
- fUserAgentHeaderStrSize = strlen(fUserAgentHeaderStr);
+unsigned RTSPClient::sendAnnounceCommand(char const* sdpDescription, responseHandler* responseHandler, Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "ANNOUNCE", responseHandler, NULL, NULL, False, 0.0, 0.0, 0.0, sdpDescription));
}
-void RTSPClient::setUserAgentString(char const* userAgentStr) {
- if (userAgentStr == NULL) return;
+unsigned RTSPClient::sendSetupCommand(MediaSubsession& subsession, responseHandler* responseHandler,
+ Boolean streamOutgoing, Boolean streamUsingTCP, Boolean forceMulticastOnUnspecified,
+ Authenticator* authenticator) {
+ if (fTunnelOverHTTPPortNum != 0) streamUsingTCP = True; // RTSP-over-HTTP tunneling uses TCP (by definition)
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+
+ u_int32_t booleanFlags = 0;
+ if (streamUsingTCP) booleanFlags |= 0x1;
+ if (streamOutgoing) booleanFlags |= 0x2;
+ if (forceMulticastOnUnspecified) booleanFlags |= 0x4;
+ return sendRequest(new RequestRecord(++fCSeq, "SETUP", responseHandler, NULL, &subsession, booleanFlags));
+}
- // Change the existing user agent header string:
- char const* const formatStr = "User-Agent: %s\r\n";
- unsigned const headerSize = strlen(formatStr) + strlen(userAgentStr) + 1;
- delete[] fUserAgentHeaderStr;
- fUserAgentHeaderStr = new char[headerSize];
- sprintf(fUserAgentHeaderStr, formatStr, userAgentStr);
- fUserAgentHeaderStrSize = strlen(fUserAgentHeaderStr);
+unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
+ double start, double end, float scale,
+ Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, &session, NULL, 0, start, end, scale));
}
-RTSPClient::~RTSPClient() {
- envir().taskScheduler().turnOffBackgroundReadHandling(fInputSocketNum); // must be called before:
- reset();
+unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
+ double start, double end, float scale,
+ Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, NULL, &subsession, 0, start, end, scale));
+}
- delete[] fResponseBuffer;
- delete[] fUserAgentHeaderStr;
+unsigned RTSPClient::sendPauseCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, &session));
}
-Boolean RTSPClient::isRTSPClient() const {
- return True;
+unsigned RTSPClient::sendPauseCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, NULL, &subsession));
}
-void RTSPClient::resetTCPSockets() {
- if (fInputSocketNum >= 0) {
- ::closeSocket(fInputSocketNum);
- if (fOutputSocketNum != fInputSocketNum) ::closeSocket(fOutputSocketNum);
- }
- fInputSocketNum = fOutputSocketNum = -1;
+unsigned RTSPClient::sendRecordCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, &session));
}
-void RTSPClient::reset() {
- resetTCPSockets();
- fServerAddress = 0;
+unsigned RTSPClient::sendRecordCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, NULL, &subsession));
+}
- delete[] fBaseURL; fBaseURL = NULL;
+unsigned RTSPClient::sendTeardownCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, &session));
+}
- fCurrentAuthenticator.reset();
+unsigned RTSPClient::sendTeardownCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, NULL, &subsession));
+}
- delete[] fKasennaContentType; fKasennaContentType = NULL;
- delete[] fLastSessionId; fLastSessionId = NULL;
+unsigned RTSPClient::sendSetParameterCommand(MediaSession& session, responseHandler* responseHandler,
+ char const* parameterName, char const* parameterValue,
+ Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+ char* paramString = new char[strlen(parameterName) + strlen(parameterValue) + 10];
+ sprintf(paramString, "%s: %s\r\n", parameterName, parameterValue);
+ unsigned result = sendRequest(new RequestRecord(++fCSeq, "SET_PARAMETER", responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString));
+ delete[] paramString;
+ return result;
}
-static char* getLine(char* startOfLine) {
- // returns the start of the next line, or NULL if none
- for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) {
- // Check for the end of line: \r\n (but also accept \r or \n by itself):
- if (*ptr == '\r' || *ptr == '\n') {
- // We found the end of the line
- if (*ptr == '\r') {
- *ptr++ = '\0';
- if (*ptr == '\n') ++ptr;
- } else {
- *ptr++ = '\0';
- }
- return ptr;
- }
+unsigned RTSPClient::sendGetParameterCommand(MediaSession& session, responseHandler* responseHandler, char const* parameterName,
+ Authenticator* authenticator) {
+ if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+
+ // We assume that:
+ // parameterName is NULL means: Send no body in the request.
+ // parameterName is "" means: Send only \r\n in the request body.
+ // parameterName is non-empty means: Send "<parameterName>\r\n" as the request body.
+ unsigned parameterNameLen = parameterName == NULL ? 0 : strlen(parameterName);
+ char* paramString = new char[parameterNameLen + 3]; // the 3 is for \r\n + the '\0' byte
+ if (parameterName == NULL) {
+ paramString[0] = '\0';
+ } else {
+ sprintf(paramString, "%s\r\n", parameterName);
}
-
- return NULL;
+ unsigned result = sendRequest(new RequestRecord(++fCSeq, "GET_PARAMETER", responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString));
+ delete[] paramString;
+ return result;
}
-char* RTSPClient::describeURL(char const* url, Authenticator* authenticator,
- Boolean allowKasennaProtocol, int timeout) {
- char* cmd = NULL;
- fDescribeStatusCode = 0;
- do {
- // First, check whether "url" contains a username:password to be used:
- 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;
- }
+Boolean RTSPClient::changeResponseHandler(unsigned cseq, responseHandler* newResponseHandler) {
+ // Look for the matching request record in each of our 'pending requests' queues:
+ RequestRecord* request;
+ if ((request = fRequestsAwaitingConnection.findByCSeq(cseq)) != NULL
+ || (request = fRequestsAwaitingHTTPTunneling.findByCSeq(cseq)) != NULL
+ || (request = fRequestsAwaitingResponse.findByCSeq(cseq)) != NULL) {
+ request->handler() = newResponseHandler;
+ return True;
+ }
- if (!openConnectionFromURL(url, authenticator, timeout)) break;
+ return False;
+}
- // Send the DESCRIBE command:
+Boolean RTSPClient::lookupByName(UsageEnvironment& env,
+ char const* instanceName,
+ RTSPClient*& resultClient) {
+ resultClient = NULL; // unless we succeed
- // First, construct an authenticator string:
- fCurrentAuthenticator.reset();
- char* authenticatorStr
- = createAuthenticatorString(authenticator, "DESCRIBE", url);
+ Medium* medium;
+ if (!Medium::lookupByName(env, instanceName, medium)) return False;
- char const* acceptStr = allowKasennaProtocol
- ? "Accept: application/x-rtsp-mh, application/sdp\r\n"
- : "Accept: application/sdp\r\n";
+ if (!medium->isRTSPClient()) {
+ env.setResultMsg(instanceName, " is not a RTSP client");
+ return False;
+ }
- // (Later implement more, as specified in the RTSP spec, sec D.1 #####)
- char const* const cmdFmt =
- "DESCRIBE %s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "%s"
- "%s"
- "%s"
- "\r\n";
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(url)
- + 20 /* max int len */
- + strlen(acceptStr)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- url,
- ++fCSeq,
- acceptStr,
- authenticatorStr,
- fUserAgentHeaderStr);
- delete[] authenticatorStr;
+ resultClient = (RTSPClient*)medium;
+ return True;
+}
- if (!sendRequest(cmd, "DESCRIBE")) break;
-
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("DESCRIBE", bytesRead, responseCode, firstLine, nextLineStart,
- False /*don't check for response code 200*/)) break;
-
- // Inspect the first line to check whether it's a result code that
- // we can handle.
- Boolean wantRedirection = False;
- char* redirectionURL = NULL;
- if (responseCode == 301 || responseCode == 302) {
- wantRedirection = True;
- redirectionURL = new char[fResponseBufferSize]; // ensures enough space
- } else if (responseCode != 200) {
- checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
- envir().setResultMsg("cannot handle DESCRIBE response: ", firstLine);
+Boolean RTSPClient::parseRTSPURL(UsageEnvironment& env, char const* url,
+ NetAddress& address,
+ portNumBits& portNum,
+ char const** urlSuffix) {
+ do {
+ // Parse the URL as "rtsp://<address>:<port>/<etc>"
+ // (with ":<port>" and "/<etc>" optional)
+ // Also, skip over any "<username>[:<password>]@" preceding <address>
+ char const* prefix = "rtsp://";
+ unsigned const prefixLength = 7;
+ if (_strncasecmp(url, prefix, prefixLength) != 0) {
+ env.setResultMsg("URL is not of the form \"", prefix, "\"");
break;
}
- // Skip over subsequent header lines, until we see a blank line.
- // The remaining data is assumed to be the SDP descriptor that we want.
- // While skipping over the header lines, we also check for certain headers
- // that we recognize.
- // (We should also check for "Content-type: application/sdp",
- // "Content-location", "CSeq", etc.) #####
- char* serverType = new char[fResponseBufferSize]; // ensures enough space
- int contentLength = -1;
- char* lineStart;
- while (1) {
- lineStart = nextLineStart;
- if (lineStart == NULL) break;
-
- nextLineStart = getLine(lineStart);
- if (lineStart[0] == '\0') break; // this is a blank line
-
- if (sscanf(lineStart, "Content-Length: %d", &contentLength) == 1
- || sscanf(lineStart, "Content-length: %d", &contentLength) == 1) {
- if (contentLength < 0) {
- envir().setResultMsg("Bad \"Content-length:\" header: \"",
- lineStart, "\"");
- break;
- }
- } else if (strncmp(lineStart, "Content-Base:", 13) == 0) {
- int cbIndex = 13;
+ unsigned const parseBufferSize = 100;
+ char parseBuffer[parseBufferSize];
+ char const* from = &url[prefixLength];
- while (lineStart[cbIndex] == ' ' || lineStart[cbIndex] == '\t') ++cbIndex;
- if (lineStart[cbIndex] != '\0'/*sanity check*/) {
- delete[] fBaseURL; fBaseURL = strDup(&lineStart[cbIndex]);
- }
- } else if (sscanf(lineStart, "Server: %s", serverType) == 1) {
- if (strncmp(serverType, "Kasenna", 7) == 0) fServerIsKasenna = True;
- if (strncmp(serverType, "WMServer", 8) == 0) fServerIsMicrosoft = True;
- } else if (wantRedirection) {
- if (sscanf(lineStart, "Location: %s", redirectionURL) == 1) {
- // Try again with this URL
- if (fVerbosityLevel >= 1) {
- envir() << "Redirecting to the new URL \""
- << redirectionURL << "\"\n";
- }
- reset();
- char* result = describeURL(redirectionURL, authenticator, allowKasennaProtocol, timeout);
- delete[] redirectionURL;
- delete[] serverType;
- delete[] cmd;
- return result;
- }
+ // Skip over any "<username>[:<password>]@"
+ // (Note that this code fails if <password> contains '@' or '/', but
+ // given that these characters can also appear in <etc>, there seems to
+ // be no way of unambiguously parsing that situation.)
+ char const* from1 = from;
+ while (*from1 != '\0' && *from1 != '/') {
+ if (*from1 == '@') {
+ from = ++from1;
+ break;
}
+ ++from1;
}
- delete[] serverType;
- // We're now at the end of the response header lines
- if (wantRedirection) {
- envir().setResultMsg("Saw redirection response code, but not a \"Location:\" header");
- delete[] redirectionURL;
- break;
+ char* to = &parseBuffer[0];
+ unsigned i;
+ for (i = 0; i < parseBufferSize; ++i) {
+ if (*from == '\0' || *from == ':' || *from == '/') {
+ // We've completed parsing the address
+ *to = '\0';
+ break;
+ }
+ *to++ = *from++;
}
- if (lineStart == NULL) {
- envir().setResultMsg("no content following header lines: ", fResponseBuffer);
+ if (i == parseBufferSize) {
+ env.setResultMsg("URL is too long");
break;
}
- // Use the remaining data as the SDP descr, but first, check
- // the "Content-length:" header (if any) that we saw. We may need to
- // read more data, or we may have extraneous data in the buffer.
- char* bodyStart = nextLineStart;
- if (contentLength >= 0) {
- // We saw a "Content-length:" header
- unsigned numBodyBytes = &firstLine[bytesRead] - bodyStart;
- if (contentLength > (int)numBodyBytes) {
- // We need to read more data. First, make sure we have enough
- // space for it:
- unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
- unsigned remainingBufferSize
- = fResponseBufferSize - (bytesRead + (firstLine - fResponseBuffer));
- if (numExtraBytesNeeded > remainingBufferSize) {
- char tmpBuf[200];
- sprintf(tmpBuf, "Read buffer size (%d) is too small for \"Content-length:\" %d (need a buffer size of >= %d bytes\n",
- fResponseBufferSize, contentLength,
- fResponseBufferSize + numExtraBytesNeeded - remainingBufferSize);
- envir().setResultMsg(tmpBuf);
- break;
- }
-
- // Keep reading more data until we have enough:
- if (fVerbosityLevel >= 1) {
- envir() << "Need to read " << numExtraBytesNeeded
- << " extra bytes\n";
- }
- while (numExtraBytesNeeded > 0) {
- struct sockaddr_in fromAddress;
- char* ptr = &firstLine[bytesRead];
- int bytesRead2 = readSocket(envir(), fInputSocketNum, (unsigned char*)ptr,
- numExtraBytesNeeded, fromAddress);
- if (bytesRead2 <= 0) break;
- ptr[bytesRead2] = '\0';
- if (fVerbosityLevel >= 1) {
- envir() << "Read " << bytesRead2 << " extra bytes: "
- << ptr << "\n";
- }
-
- bytesRead += bytesRead2;
- numExtraBytesNeeded -= bytesRead2;
- }
- if (numExtraBytesNeeded > 0) break; // one of the reads failed
- }
+ NetAddressList addresses(parseBuffer);
+ if (addresses.numAddresses() == 0) {
+ env.setResultMsg("Failed to find network address for \"",
+ parseBuffer, "\"");
+ break;
+ }
+ address = *(addresses.firstAddress());
- // Remove any '\0' characters from inside the SDP description.
- // Any such characters would violate the SDP specification, but
- // some RTSP servers have been known to include them:
- int from, to = 0;
- for (from = 0; from < contentLength; ++from) {
- if (bodyStart[from] != '\0') {
- if (to != from) bodyStart[to] = bodyStart[from];
- ++to;
- }
+ portNum = 554; // default value
+ char nextChar = *from;
+ if (nextChar == ':') {
+ int portNumInt;
+ if (sscanf(++from, "%d", &portNumInt) != 1) {
+ env.setResultMsg("No port number follows ':'");
+ break;
}
- if (from != to && fVerbosityLevel >= 1) {
- envir() << "Warning: " << from-to << " invalid 'NULL' bytes were found in (and removed from) the SDP description.\n";
+ if (portNumInt < 1 || portNumInt > 65535) {
+ env.setResultMsg("Bad port number");
+ break;
}
- bodyStart[to] = '\0'; // trims any extra data
+ portNum = (portNumBits)portNumInt;
+ while (*from >= '0' && *from <= '9') ++from; // skip over port number
}
- ////////// BEGIN Kasenna BS //////////
- // If necessary, handle Kasenna's non-standard BS response:
- if (fServerIsKasenna && strncmp(bodyStart, "<MediaDescription>", 18) == 0) {
- // Translate from x-rtsp-mh to sdp
- int videoPid, audioPid;
- u_int64_t mh_duration;
- char* currentWord = new char[fResponseBufferSize]; // ensures enough space
- delete[] fKasennaContentType;
- fKasennaContentType = new char[fResponseBufferSize]; // ensures enough space
- char* currentPos = bodyStart;
-
- while (strcmp(currentWord, "</MediaDescription>") != 0) {
- sscanf(currentPos, "%s", currentWord);
-
- if (strcmp(currentWord, "VideoPid") == 0) {
- currentPos += strlen(currentWord) + 1;
- sscanf(currentPos, "%s", currentWord);
- currentPos += strlen(currentWord) + 1;
- sscanf(currentPos, "%d", &videoPid);
- currentPos += 3;
- }
-
- if (strcmp(currentWord, "AudioPid") == 0) {
- currentPos += strlen(currentWord) + 1;
- sscanf(currentPos, "%s", currentWord);
- currentPos += strlen(currentWord) + 1;
- sscanf(currentPos, "%d", &audioPid);
- currentPos += 3;
- }
-
- if (strcmp(currentWord, "Duration") == 0) {
- currentPos += strlen(currentWord) + 1;
- sscanf(currentPos, "%s", currentWord);
- currentPos += strlen(currentWord) + 1;
- sscanf(currentPos, "%llu", &mh_duration);
- currentPos += 3;
- }
+ // The remainder of the URL is the suffix:
+ if (urlSuffix != NULL) *urlSuffix = from;
- if (strcmp(currentWord, "TypeSpecificData") == 0) {
- currentPos += strlen(currentWord) + 1;
- sscanf(currentPos, "%s", currentWord);
- currentPos += strlen(currentWord) + 1;
- sscanf(currentPos, "%s", fKasennaContentType);
- currentPos += 3;
- printf("Kasenna Content Type: %s\n", fKasennaContentType);
- }
+ return True;
+ } while (0);
- currentPos += strlen(currentWord) + 1;
- }
+ return False;
+}
- if (fKasennaContentType != NULL
- && strcmp(fKasennaContentType, "PARTNER_41_MPEG-4") == 0) {
- char* describeSDP = describeURL(url, authenticator, True, timeout);
+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;
- delete[] currentWord;
- delete[] cmd;
- return describeSDP;
+ // 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
- unsigned char byte1 = fServerAddress & 0x000000ff;
- unsigned char byte2 = (fServerAddress & 0x0000ff00) >> 8;
- unsigned char byte3 = (fServerAddress & 0x00ff0000) >> 16;
- unsigned char byte4 = (fServerAddress & 0xff000000) >> 24;
-
- char const* sdpFmt =
- "v=0\r\n"
- "o=NoSpacesAllowed 1 1 IN IP4 %u.%u.%u.%u\r\n"
- "s=%s\r\n"
- "c=IN IP4 %u.%u.%u.%u\r\n"
- "t=0 0\r\n"
- "a=control:*\r\n"
- "a=range:npt=0-%llu\r\n"
- "m=video 1554 RAW/RAW/UDP 33\r\n"
- "a=control:trackID=%d\r\n";
- unsigned sdpBufSize = strlen(sdpFmt)
- + 4*3 // IP address
- + strlen(url)
- + 20 // max int length
- + 20; // max int length
- char* sdpBuf = new char[sdpBufSize];
- sprintf(sdpBuf, sdpFmt,
- byte1, byte2, byte3, byte4,
- url,
- byte1, byte2, byte3, byte4,
- mh_duration/1000000,
- videoPid);
-
- char* result = strDup(sdpBuf);
- delete[] sdpBuf; delete[] currentWord;
- delete[] cmd;
- return result;
+ char* urlCopy = strDup(url);
+ urlCopy[atIndex] = '\0';
+ if (colonIndex > 0) {
+ urlCopy[colonIndex] = '\0';
+ password = strDup(&urlCopy[colonIndex+1]);
+ } else {
+ password = strDup("");
}
- ////////// END Kasenna BS //////////
+ username = strDup(&urlCopy[usernameIndex]);
+ delete[] urlCopy;
- delete[] cmd;
- return strDup(bodyStart);
+ return True;
} while (0);
- delete[] cmd;
- if (fDescribeStatusCode == 0) fDescribeStatusCode = 2;
- return NULL;
+ return False;
}
-char* RTSPClient
-::describeWithPassword(char const* url,
- char const* username, char const* password,
- Boolean allowKasennaProtocol, int timeout) {
- Authenticator authenticator;
- authenticator.setUsernameAndPassword(username, password);
- char* describeResult = describeURL(url, &authenticator, allowKasennaProtocol, timeout);
- if (describeResult != NULL) {
- // We are already authorized
- return describeResult;
- }
-
- // The "realm" field should have been filled in:
- if (authenticator.realm() == NULL) {
- // We haven't been given enough information to try again, so fail:
- return NULL;
- }
-
- // Try again:
- describeResult = describeURL(url, &authenticator, allowKasennaProtocol, timeout);
- if (describeResult != NULL) {
- // The authenticator worked, so use it in future requests:
- fCurrentAuthenticator = authenticator;
- }
+void RTSPClient::setUserAgentString(char const* userAgentName) {
+ if (userAgentName == NULL) return;
- return describeResult;
+ // Change the existing user agent header string:
+ char const* const formatStr = "User-Agent: %s\r\n";
+ unsigned const headerSize = strlen(formatStr) + strlen(userAgentName);
+ delete[] fUserAgentHeaderStr;
+ fUserAgentHeaderStr = new char[headerSize];
+ sprintf(fUserAgentHeaderStr, formatStr, userAgentName);
+ fUserAgentHeaderStrLen = strlen(fUserAgentHeaderStr);
}
-char* RTSPClient::sendOptionsCmd(char const* url,
- char* username, char* password,
- Authenticator* authenticator,
- int timeout) {
- char* result = NULL;
- char* cmd = NULL;
- Boolean haveAllocatedAuthenticator = False;
- do {
- 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;
- newAuthenticator.setUsernameAndPassword(username, password);
- result = sendOptionsCmd(url, username, password, &newAuthenticator, timeout);
- delete[] username; delete[] password; // they were dynamically allocated
- break;
- } else if (username != NULL && password != NULL) {
- // Use the separately supplied username and password:
- authenticator = new Authenticator;
- haveAllocatedAuthenticator = True;
- authenticator->setUsernameAndPassword(username, password);
-
- result = sendOptionsCmd(url, username, password, authenticator, timeout);
- if (result != NULL) break; // We are already authorized
-
- // The "realm" field should have been filled in:
- if (authenticator->realm() == NULL) {
- // We haven't been given enough information to try again, so fail:
- break;
- }
- // Try again:
- }
- }
+unsigned RTSPClient::responseBufferSize = 20000; // default value; you can reassign this in your application if you need to
- if (!openConnectionFromURL(url, authenticator, timeout)) break;
+RTSPClient::RTSPClient(UsageEnvironment& env, char const* rtspURL,
+ int verbosityLevel, char const* applicationName,
+ portNumBits tunnelOverHTTPPortNum)
+ : Medium(env),
+ fVerbosityLevel(verbosityLevel), fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum),
+ fUserAgentHeaderStr(NULL), fUserAgentHeaderStrLen(0), fInputSocketNum(-1), fOutputSocketNum(-1), fServerAddress(0), fCSeq(1),
+ fBaseURL(NULL), fTCPStreamIdCount(0), fLastSessionId(NULL), fSessionTimeoutParameter(0),
+ fSessionCookieCounter(0), fHTTPTunnelingConnectionIsPending(False) {
+ setBaseURL(rtspURL);
- // Send the OPTIONS command:
+ fResponseBuffer = new char[responseBufferSize+1];
+ resetResponseBuffer();
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(authenticator, "OPTIONS", url);
+ // 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;
+ char const* libPrefix; char const* libSuffix;
+ if (applicationName == NULL || applicationName[0] == '\0') {
+ applicationName = libPrefix = libSuffix = "";
+ } else {
+ libPrefix = " (";
+ libSuffix = ")";
+ }
+ unsigned userAgentNameSize
+ = strlen(applicationName) + strlen(libPrefix) + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix) + 1;
+ char* userAgentName = new char[userAgentNameSize];
+ sprintf(userAgentName, "%s%s%s%s%s", applicationName, libPrefix, libName, libVersionStr, libSuffix);
+ setUserAgentString(userAgentName);
+ delete[] userAgentName;
+}
- char const* const cmdFmt =
- "OPTIONS %s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "%s"
- "%s"
- "\r\n";
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(url)
- + 20 /* max int len */
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- url,
- ++fCSeq,
- authenticatorStr,
- fUserAgentHeaderStr);
- delete[] authenticatorStr;
+RTSPClient::~RTSPClient() {
+ reset();
- if (!sendRequest(cmd, "OPTIONS")) break;
+ delete[] fResponseBuffer;
+ delete[] fUserAgentHeaderStr;
+}
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("OPTIONS", bytesRead, responseCode, firstLine, nextLineStart,
- False /*don't check for response code 200*/)) break;
- if (responseCode != 200) {
- checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
- envir().setResultMsg("cannot handle OPTIONS response: ", firstLine);
- break;
- }
+Boolean RTSPClient::isRTSPClient() const {
+ return True;
+}
- // Look for a "Public:" header (which will contain our result str):
- char* lineStart;
- while (1) {
- lineStart = nextLineStart;
- if (lineStart == NULL) break;
+void RTSPClient::reset() {
+ resetTCPSockets();
+ resetResponseBuffer();
+ fServerAddress = 0;
- nextLineStart = getLine(lineStart);
+ setBaseURL(NULL);
- if (_strncasecmp(lineStart, "Public: ", 8) == 0) {
- delete[] result; result = strDup(&lineStart[8]);
- }
- }
- } while (0);
+ fCurrentAuthenticator.reset();
- delete[] cmd;
- if (haveAllocatedAuthenticator) delete authenticator;
- return result;
+ delete[] fLastSessionId; fLastSessionId = NULL;
}
-static Boolean isAbsoluteURL(char const* url) {
- // Assumption: "url" is absolute if it contains a ':', before any
- // occurrence of '/'
- while (*url != '\0' && *url != '/') {
- if (*url == ':') return True;
- ++url;
+void RTSPClient::resetTCPSockets() {
+ if (fInputSocketNum >= 0) {
+ envir().taskScheduler().disableBackgroundHandling(fInputSocketNum);
+ ::closeSocket(fInputSocketNum);
+ if (fOutputSocketNum != fInputSocketNum) {
+ envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum);
+ ::closeSocket(fOutputSocketNum);
+ }
}
-
- return False;
+ fInputSocketNum = fOutputSocketNum = -1;
}
-char const* RTSPClient::sessionURL(MediaSession const& session) const {
- char const* url = session.controlPath();
- if (url == NULL || strcmp(url, "*") == 0) url = fBaseURL;
-
- return url;
+void RTSPClient::resetResponseBuffer() {
+ fResponseBytesAlreadySeen = 0;
+ fResponseBufferBytesLeft = responseBufferSize;
}
-void RTSPClient::constructSubsessionURL(MediaSubsession const& subsession,
- char const*& prefix,
- char const*& separator,
- char const*& suffix) {
- // Figure out what the URL describing "subsession" will look like.
- // The URL is returned in three parts: prefix; separator; suffix
- //##### NOTE: This code doesn't really do the right thing if "sessionURL()"
- // doesn't end with a "/", and "subsession.controlPath()" is relative.
- // The right thing would have been to truncate "sessionURL()" back to the
- // rightmost "/", and then add "subsession.controlPath()".
- // In practice, though, each "DESCRIBE" response typically contains
- // a "Content-Base:" header that consists of "sessionURL()" followed by
- // a "/", in which case this code ends up giving the correct result.
- // However, we should really fix this code to do the right thing, and
- // also check for and use the "Content-Base:" header appropriately. #####
- prefix = sessionURL(subsession.parentSession());
- if (prefix == NULL) prefix = "";
-
- suffix = subsession.controlPath();
- if (suffix == NULL) suffix = "";
-
- if (isAbsoluteURL(suffix)) {
- prefix = separator = "";
- } else {
- unsigned prefixLen = strlen(prefix);
- separator = (prefix[prefixLen-1] == '/' || suffix[0] == '/') ? "" : "/";
- }
+void RTSPClient::setBaseURL(char const* url) {
+ delete[] fBaseURL; fBaseURL = strDup(url);
}
-Boolean RTSPClient::announceSDPDescription(char const* url,
- char const* sdpDescription,
- Authenticator* authenticator,
- int timeout) {
- char* cmd = NULL;
+int RTSPClient::openConnection() {
do {
- if (!openConnectionFromURL(url, authenticator, timeout)) break;
+ // Set up a connection to the server. Begin by parsing the URL:
- // Send the ANNOUNCE command:
+ NetAddress destAddress;
+ portNumBits urlPortNum;
+ char const* urlSuffix;
+ if (!parseRTSPURL(envir(), fBaseURL, destAddress, urlPortNum, &urlSuffix)) break;
+ portNumBits destPortNum
+ = fTunnelOverHTTPPortNum == 0 ? urlPortNum : fTunnelOverHTTPPortNum;
- // First, construct an authenticator string:
- fCurrentAuthenticator.reset();
- char* authenticatorStr
- = createAuthenticatorString(authenticator, "ANNOUNCE", url);
+ // 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;
+
+ // Connect to the remote endpoint:
+ fServerAddress = *(unsigned*)(destAddress.data());
+ int connectResult = connectToServer(fInputSocketNum, destPortNum);
+ if (connectResult < 0) break;
+ else if (connectResult > 0) {
+ // The connection succeeded. Arrange to handle responses to requests sent on it:
+ envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE,
+ (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
+ }
+ return connectResult;
+ } while (0);
+
+ resetTCPSockets();
+ return -1;
+}
- char const* const cmdFmt =
- "ANNOUNCE %s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Content-Type: application/sdp\r\n"
- "%s"
- "Content-length: %d\r\n\r\n"
- "%s";
- // Note: QTSS hangs if an "ANNOUNCE" contains a "User-Agent:" field (go figure), so don't include one here
- unsigned sdpSize = strlen(sdpDescription);
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(url)
- + 20 /* max int len */
- + strlen(authenticatorStr)
- + 20 /* max int len */
- + sdpSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- url,
- ++fCSeq,
- authenticatorStr,
- sdpSize,
- sdpDescription);
- delete[] authenticatorStr;
+int RTSPClient::connectToServer(int socketNum, portNumBits remotePortNum) {
+ MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(remotePortNum));
+ if (fVerbosityLevel >= 1) {
+ envir() << "Opening connection to " << our_inet_ntoa(remoteName.sin_addr) << ", port " << remotePortNum << "...\n";
+ }
+ if (connect(socketNum, (struct sockaddr*) &remoteName, sizeof remoteName) != 0) {
+ if (envir().getErrno() == EINPROGRESS) {
+ // The connection is pending; we'll need to handle it later. Wait for our socket to be 'writable', or have an exception.
+ envir().taskScheduler().setBackgroundHandling(socketNum, SOCKET_WRITABLE|SOCKET_EXCEPTION,
+ (TaskScheduler::BackgroundHandlerProc*)&connectionHandler, this);
+ return 0;
+ }
+ envir().setResultErrMsg("connect() failed: ");
+ if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n";
+ return -1;
+ }
+ if (fVerbosityLevel >= 1) envir() << "...local connection opened\n";
- if (!sendRequest(cmd, "ANNOUNCE")) break;
+ return 1;
+}
+
+char*
+RTSPClient::createAuthenticatorString(char const* cmd, char const* url) {
+ Authenticator& auth = fCurrentAuthenticator; // alias, for brevity
+ if (auth.realm() != NULL && auth.username() != NULL && auth.password() != NULL) {
+ // We have a filled-in authenticator, so use it:
+ char* authenticatorStr;
+ if (auth.nonce() != NULL) { // Digest authentication
+ char const* const authFmt =
+ "Authorization: Digest username=\"%s\", realm=\"%s\", "
+ "nonce=\"%s\", uri=\"%s\", response=\"%s\"\r\n";
+ char const* response = auth.computeDigestResponse(cmd, url);
+ unsigned authBufSize = strlen(authFmt)
+ + strlen(auth.username()) + strlen(auth.realm())
+ + strlen(auth.nonce()) + strlen(url) + strlen(response);
+ authenticatorStr = new char[authBufSize];
+ sprintf(authenticatorStr, authFmt,
+ auth.username(), auth.realm(),
+ auth.nonce(), url, response);
+ auth.reclaimDigestResponse(response);
+ } else { // Basic authentication
+ char const* const authFmt = "Authorization: Basic %s\r\n";
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("ANNOUNCE", bytesRead, responseCode, firstLine, nextLineStart,
- False /*don't check for response code 200*/)) break;
+ unsigned usernamePasswordLength = strlen(auth.username()) + 1 + strlen(auth.password());
+ char* usernamePassword = new char[usernamePasswordLength+1];
+ sprintf(usernamePassword, "%s:%s", auth.username(), auth.password());
- // Inspect the first line to check whether it's a result code 200
- if (responseCode != 200) {
- checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
- envir().setResultMsg("cannot handle ANNOUNCE response: ", firstLine);
- break;
+ char* response = base64Encode(usernamePassword, usernamePasswordLength);
+ unsigned const authBufSize = strlen(authFmt) + strlen(response) + 1;
+ authenticatorStr = new char[authBufSize];
+ sprintf(authenticatorStr, authFmt, response);
+ delete[] response; delete[] usernamePassword;
}
- delete[] cmd;
- return True;
- } while (0);
+ return authenticatorStr;
+ }
- delete[] cmd;
- return False;
+ // We don't have a (filled-in) authenticator.
+ return strDup("");
}
-Boolean RTSPClient
-::announceWithPassword(char const* url, char const* sdpDescription,
- char const* username, char const* password, int timeout) {
- Authenticator authenticator;
- authenticator.setUsernameAndPassword(username, password);
- if (announceSDPDescription(url, sdpDescription, &authenticator, timeout)) {
- // We are already authorized
- return True;
+static char* createSessionString(char const* sessionId) {
+ char* sessionStr;
+ if (sessionId != NULL) {
+ sessionStr = new char[20+strlen(sessionId)];
+ sprintf(sessionStr, "Session: %s\r\n", sessionId);
+ } else {
+ sessionStr = strDup("");
}
+ return sessionStr;
+}
- // The "realm" field should have been filled in:
- if (authenticator.realm() == NULL) {
- // We haven't been given enough information to try again, so fail:
- return False;
+static char* createScaleString(float scale, float currentScale) {
+ char buf[100];
+ if (scale == 1.0f && currentScale == 1.0f) {
+ // This is the default value; we don't need a "Scale:" header:
+ buf[0] = '\0';
+ } else {
+ Locale l("C", LC_NUMERIC);
+ sprintf(buf, "Scale: %f\r\n", scale);
}
- // Try again:
- Boolean secondTrySuccess
- = announceSDPDescription(url, sdpDescription, &authenticator, timeout);
+ return strDup(buf);
+}
- if (secondTrySuccess) {
- // The authenticator worked, so use it in future requests:
- fCurrentAuthenticator = authenticator;
+static char* createRangeString(double start, double end) {
+ char buf[100];
+ if (start < 0) {
+ // We're resuming from a PAUSE; there's no "Range:" header at all
+ buf[0] = '\0';
+ } else if (end < 0) {
+ // There's no end time:
+ Locale l("C", LC_NUMERIC);
+ sprintf(buf, "Range: npt=%.3f-\r\n", start);
+ } else {
+ // There's both a start and an end time; include them both in the "Range:" hdr
+ Locale l("C", LC_NUMERIC);
+ sprintf(buf, "Range: npt=%.3f-%.3f\r\n", start, end);
}
- return secondTrySuccess;
+ return strDup(buf);
}
-Boolean RTSPClient::setupMediaSubsession(MediaSubsession& subsession,
- Boolean streamOutgoing,
- Boolean streamUsingTCP,
- Boolean forceMulticastOnUnspecified) {
+unsigned RTSPClient::sendRequest(RequestRecord* request) {
char* cmd = NULL;
- char* setupStr = NULL;
-
- if (fServerIsMicrosoft) {
- // Microsoft doesn't send the right endTime on live streams. Correct this:
- char *tmpStr = subsession.parentSession().mediaSessionType();
- if (tmpStr != NULL && strncmp(tmpStr, "broadcast", 9) == 0) {
- subsession.parentSession().playEndTime() = 0.0;
- }
- }
-
do {
- // Construct the SETUP command:
-
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator,
- "SETUP", fBaseURL);
-
- // When sending more than one "SETUP" request, include a "Session:"
- // header in the 2nd and later "SETUP"s.
- char* sessionStr;
- if (fLastSessionId != NULL) {
- sessionStr = new char[20+strlen(fLastSessionId)];
- sprintf(sessionStr, "Session: %s\r\n", fLastSessionId);
- } else {
- sessionStr = strDup("");
- }
-
- char* transportStr = NULL;
-
- char const *prefix, *separator, *suffix;
- constructSubsessionURL(subsession, prefix, separator, suffix);
- char const* transportFmt;
-
- if (strcmp(subsession.protocolName(), "UDP") == 0) {
- char const* setupFmt = "SETUP %s%s RTSP/1.0\r\n";
- unsigned setupSize = strlen(setupFmt)
- + strlen(prefix) + strlen (separator);
- setupStr = new char[setupSize];
- sprintf(setupStr, setupFmt, prefix, separator);
-
- transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n";
- } else {
- char const* setupFmt = "SETUP %s%s%s RTSP/1.0\r\n";
- unsigned setupSize = strlen(setupFmt)
- + strlen(prefix) + strlen (separator) + strlen(suffix);
- setupStr = new char[setupSize];
- sprintf(setupStr, setupFmt, prefix, separator, suffix);
+ Boolean connectionIsPending = False;
+ if (!fRequestsAwaitingConnection.isEmpty()) {
+ // A connection is currently pending (with at least one enqueued request). Enqueue this request also:
+ connectionIsPending = True;
+ } else if (fInputSocketNum < 0) { // we need to open a connection
+ int connectResult = openConnection();
+ if (connectResult < 0) break; // an error occurred
+ else if (connectResult == 0) {
+ // A connection is pending
+ connectionIsPending = True;
+ } // else the connection succeeded. Continue sending the command.u
+ }
+ if (connectionIsPending) {
+ fRequestsAwaitingConnection.enqueue(request);
+ return request->cseq();
+ }
+
+ // If requested (and we're not already doing it, or have done it), set up the special protocol for tunneling RTSP-over-HTTP:
+ if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && fOutputSocketNum == fInputSocketNum) {
+ if (!setupHTTPTunneling1()) break;
+ fRequestsAwaitingHTTPTunneling.enqueue(request);
+ return request->cseq();
+ }
+
+ // Construct and send the command:
+
+ // First, construct command-specific headers that we need:
+
+ char* cmdURL = fBaseURL; // by default
+ Boolean cmdURLWasAllocated = False;
+
+ char const* protocolStr = "RTSP/1.0"; // by default
+
+ char* extraHeaders = (char*)""; // by default
+ Boolean extraHeadersWereAllocated = False;
+
+ char* contentLengthHeader = (char*)""; // by default
+ Boolean contentLengthHeaderWasAllocated = False;
+
+ char const* contentStr = request->contentStr(); // by default
+ if (contentStr == NULL) contentStr = "";
+ unsigned contentStrLen = strlen(contentStr);
+ if (contentStrLen > 0) {
+ char const* contentLengthHeaderFmt =
+ "Content-length: %d\r\n";
+ unsigned contentLengthHeaderSize = strlen(contentLengthHeaderFmt)
+ + 20 /* max int len */;
+ contentLengthHeader = new char[contentLengthHeaderSize];
+ sprintf(contentLengthHeader, contentLengthHeaderFmt, contentStrLen);
+ contentLengthHeaderWasAllocated = True;
+ }
+
+ if (strcmp(request->commandName(), "DESCRIBE") == 0) {
+ extraHeaders = (char*)"Accept: application/sdp\r\n";
+ } else if (strcmp(request->commandName(), "OPTIONS") == 0) {
+ } else if (strcmp(request->commandName(), "ANNOUNCE") == 0) {
+ extraHeaders = (char*)"Content-Type: application/sdp\r\n";
+ } else if (strcmp(request->commandName(), "SETUP") == 0) {
+ MediaSubsession& subsession = *request->subsession();
+ Boolean streamUsingTCP = (request->booleanFlags()&0x1) != 0;
+ Boolean streamOutgoing = (request->booleanFlags()&0x2) != 0;
+ Boolean forceMulticastOnUnspecified = (request->booleanFlags()&0x4) != 0;
+
+ char const *prefix, *separator, *suffix;
+ constructSubsessionURL(subsession, prefix, separator, suffix);
+
+ char const* transportFmt;
+ if (strcmp(subsession.protocolName(), "UDP") == 0) {
+ suffix = "";
+ transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n";
+ } else {
+ transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n";
+ }
- transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n";
- }
+ cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1];
+ cmdURLWasAllocated = True;
+ sprintf(cmdURL, "%s%s%s", prefix, separator, suffix);
- if (transportStr == NULL) {
// Construct a "Transport:" header.
char const* transportTypeStr;
char const* modeStr = streamOutgoing ? ";mode=receive" : "";
// Note: I think the above is nonstandard, but DSS wants it this way
char const* portTypeStr;
- unsigned short rtpNumber, rtcpNumber;
+ portNumBits rtpNumber, rtcpNumber;
if (streamUsingTCP) { // streaming over the RTSP connection
transportTypeStr = "/TCP;unicast";
portTypeStr = ";interleaved";
@@ -778,1648 +605,1329 @@ Boolean RTSPClient::setupMediaSubsession(MediaSubsession& subsession,
rtcpNumber = fTCPStreamIdCount++;
} else { // normal RTP streaming
unsigned connectionAddress = subsession.connectionEndpointAddress();
- Boolean requestMulticastStreaming = IsMulticastAddress(connectionAddress)
- || (connectionAddress == 0 && forceMulticastOnUnspecified);
+ Boolean requestMulticastStreaming
+ = IsMulticastAddress(connectionAddress) || (connectionAddress == 0 && forceMulticastOnUnspecified);
transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast";
portTypeStr = ";client_port";
rtpNumber = subsession.clientPortNum();
if (rtpNumber == 0) {
envir().setResultMsg("Client port number unknown\n");
- delete[] authenticatorStr; delete[] sessionStr; delete[] setupStr;
+ delete[] cmdURL;
break;
}
rtcpNumber = rtpNumber + 1;
}
-
unsigned transportSize = strlen(transportFmt)
+ strlen(transportTypeStr) + strlen(modeStr) + strlen(portTypeStr) + 2*5 /* max port len */;
- transportStr = new char[transportSize];
+ char* transportStr = new char[transportSize];
sprintf(transportStr, transportFmt,
transportTypeStr, modeStr, portTypeStr, rtpNumber, rtcpNumber);
+
+ // When sending more than one "SETUP" request, include a "Session:" header in the 2nd and later commands:
+ char* sessionStr = createSessionString(fLastSessionId);
+
+ // The "Transport:" and "Session:" (if present) headers make up the 'extra headers':
+ extraHeaders = new char[transportSize + strlen(sessionStr)];
+ extraHeadersWereAllocated = True;
+ sprintf(extraHeaders, "%s%s", transportStr, sessionStr);
+ delete[] transportStr; delete[] sessionStr;
+ } else if (strcmp(request->commandName(), "GET") == 0 || strcmp(request->commandName(), "POST") == 0) {
+ NetAddress destAddress;
+ portNumBits urlPortNum;
+ if (!parseRTSPURL(envir(), fBaseURL, destAddress, urlPortNum, (char const**)&cmdURL)) break;
+ if (cmdURL[0] == '\0') cmdURL = (char*)"/";
+
+ protocolStr = "HTTP/1.0";
+
+ if (strcmp(request->commandName(), "GET") == 0) {
+ // Create a 'session cookie' string, using MD5:
+ struct {
+ struct timeval timestamp;
+ unsigned counter;
+ } seedData;
+ gettimeofday(&seedData.timestamp, NULL);
+ seedData.counter = ++fSessionCookieCounter;
+ our_MD5Data((unsigned char*)(&seedData), sizeof seedData, fSessionCookie);
+ // DSS seems to require that the 'session cookie' string be 22 bytes long:
+ fSessionCookie[23] = '\0';
+
+ char const* const extraHeadersFmt =
+ "x-sessioncookie: %s\r\n"
+ "Accept: application/x-rtsp-tunnelled\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache\r\n";
+ unsigned extraHeadersSize = strlen(extraHeadersFmt)
+ + strlen(fSessionCookie);
+ extraHeaders = new char[extraHeadersSize];
+ extraHeadersWereAllocated = True;
+ sprintf(extraHeaders, extraHeadersFmt,
+ fSessionCookie);
+ } else { // "POST"
+ protocolStr = "HTTP/1.0";
+
+ char const* const extraHeadersFmt =
+ "x-sessioncookie: %s\r\n"
+ "Content-Type: application/x-rtsp-tunnelled\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Content-Length: 32767\r\n"
+ "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n";
+ unsigned extraHeadersSize = strlen(extraHeadersFmt)
+ + strlen(fSessionCookie);
+ extraHeaders = new char[extraHeadersSize];
+ extraHeadersWereAllocated = True;
+ sprintf(extraHeaders, extraHeadersFmt,
+ fSessionCookie);
+ }
+ } else { // "PLAY", "PAUSE", "TEARDOWN", "RECORD", "SET_PARAMETER", "GET_PARAMETER"
+ // First, make sure that we have a RTSP session in progress
+ if (fLastSessionId == NULL) {
+ envir().setResultMsg("No RTSP session is currently in progress\n");
+ break;
+ }
+
+ char const* sessionId;
+ float originalScale;
+ if (request->session() != NULL) {
+ // Session-level operation
+ cmdURL = (char*)sessionURL(*request->session());
+
+ sessionId = fLastSessionId;
+ originalScale = request->session()->scale();
+ } else {
+ // Media-level operation
+ char const *prefix, *separator, *suffix;
+ constructSubsessionURL(*request->subsession(), prefix, separator, suffix);
+ cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1];
+ cmdURLWasAllocated = True;
+ sprintf(cmdURL, "%s%s%s", prefix, separator, suffix);
+
+ sessionId = request->subsession()->sessionId;
+ originalScale = request->subsession()->scale();
+ }
+
+ if (strcmp(request->commandName(), "PLAY") == 0) {
+ // Create "Session:", "Scale:", and "Range:" headers; these make up the 'extra headers':
+ char* sessionStr = createSessionString(sessionId);
+ char* scaleStr = createScaleString(request->scale(), originalScale);
+ char* rangeStr = createRangeString(request->start(), request->end());
+ extraHeaders = new char[strlen(sessionStr) + strlen(scaleStr) + strlen(rangeStr) + 1];
+ extraHeadersWereAllocated = True;
+ sprintf(extraHeaders, "%s%s%s", sessionStr, scaleStr, rangeStr);
+ delete[] sessionStr; delete[] scaleStr; delete[] rangeStr;
+ } else {
+ // Create a "Session:" header; this makes up our 'extra headers':
+ extraHeaders = createSessionString(sessionId);
+ extraHeadersWereAllocated = True;
+ }
}
- // (Later implement more, as specified in the RTSP spec, sec D.1 #####)
+ char* authenticatorStr = createAuthenticatorString(request->commandName(), fBaseURL);
+
char const* const cmdFmt =
- "%s"
+ "%s %s %s\r\n"
"CSeq: %d\r\n"
"%s"
"%s"
"%s"
"%s"
- "\r\n";
-
+ "\r\n"
+ "%s";
unsigned cmdSize = strlen(cmdFmt)
- + strlen(setupStr)
+ + strlen(request->commandName()) + strlen(cmdURL) + strlen(protocolStr)
+ 20 /* max int len */
- + strlen(transportStr)
- + strlen(sessionStr)
+ strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
+ + fUserAgentHeaderStrLen
+ + strlen(extraHeaders)
+ + strlen(contentLengthHeader)
+ + contentStrLen;
cmd = new char[cmdSize];
sprintf(cmd, cmdFmt,
- setupStr,
- ++fCSeq,
- transportStr,
- sessionStr,
+ request->commandName(), cmdURL, protocolStr,
+ request->cseq(),
authenticatorStr,
- fUserAgentHeaderStr);
- delete[] authenticatorStr; delete[] sessionStr; delete[] setupStr; delete[] transportStr;
-
- // And then send it:
- if (!sendRequest(cmd, "SETUP")) break;
-
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("SETUP", bytesRead, responseCode, firstLine, nextLineStart)) break;
-
- // Look for a "Session:" header (to set our session id), and
- // a "Transport: " header (to set the server address/port)
- // For now, ignore other headers.
- char* lineStart;
- char* sessionId = new char[fResponseBufferSize]; // ensures we have enough space
- unsigned cLength = 0;
- while (1) {
- lineStart = nextLineStart;
- if (lineStart == NULL) break;
-
- nextLineStart = getLine(lineStart);
-
- if (sscanf(lineStart, "Session: %[^;]", sessionId) == 1) {
- subsession.sessionId = strDup(sessionId);
- delete[] fLastSessionId; fLastSessionId = strDup(sessionId);
-
- // Also look for an optional "; timeout = " parameter following this:
- char* afterSessionId
- = lineStart + strlen(sessionId) + strlen ("Session: ");;
- int timeoutVal;
- if (sscanf(afterSessionId, "; timeout = %d", &timeoutVal) == 1) {
- fSessionTimeoutParameter = timeoutVal;
- }
- continue;
- }
-
- char* serverAddressStr;
- portNumBits serverPortNum;
- unsigned char rtpChannelId, rtcpChannelId;
- if (parseTransportResponse(lineStart,
- serverAddressStr, serverPortNum,
- rtpChannelId, rtcpChannelId)) {
- delete[] subsession.connectionEndpointName();
- subsession.connectionEndpointName() = serverAddressStr;
- subsession.serverPortNum = serverPortNum;
- subsession.rtpChannelId = rtpChannelId;
- subsession.rtcpChannelId = rtcpChannelId;
- continue;
- }
-
- // Also check for a "Content-Length:" header. Some weird servers include this
- // in the RTSP "SETUP" response.
- if (sscanf(lineStart, "Content-Length: %d", &cLength) == 1) continue;
- }
- delete[] sessionId;
-
- if (subsession.sessionId == NULL) {
- envir().setResultMsg("\"Session:\" header is missing in the response");
+ fUserAgentHeaderStr,
+ extraHeaders,
+ contentLengthHeader,
+ contentStr);
+ delete[] authenticatorStr;
+ if (cmdURLWasAllocated) delete[] cmdURL;
+ if (extraHeadersWereAllocated) delete[] extraHeaders;
+ if (contentLengthHeaderWasAllocated) delete[] contentLengthHeader;
+
+ if (fVerbosityLevel >= 1) envir() << "Sending request: " << cmd << "\n";
+
+ if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && strcmp(request->commandName(), "POST") != 0) {
+ // When we're tunneling RTSP-over-HTTP, we Base-64-encode the request before we send it.
+ // (However, we don't do this for the HTTP "GET" and "POST" commands that we use to set up the tunnel.)
+ char* origCmd = cmd;
+ cmd = base64Encode(origCmd, strlen(cmd));
+ if (fVerbosityLevel >= 1) envir() << "\tThe request was base-64 encoded to: " << cmd << "\n\n";
+ delete[] origCmd;
+ }
+
+ if (send(fOutputSocketNum, cmd, strlen(cmd), 0) < 0) {
+ char const* errFmt = "%s send() failed: ";
+ unsigned const errLength = strlen(errFmt) + strlen(request->commandName());
+ char* err = new char[errLength];
+ sprintf(err, errFmt, request->commandName());
+ envir().setResultErrMsg(err);
+ delete[] err;
break;
}
- // If we saw a "Content-Length:" header in the response, then discard whatever
- // included data it refers to:
- if (cLength > 0) {
- char* dummyBuf = new char[cLength+1]; // allow for a trailing '\0'
- getResponse1(dummyBuf, cLength);
- delete[] dummyBuf;
- }
-
- if (streamUsingTCP) {
- // Tell the subsession to receive RTP (and send/receive RTCP)
- // over the RTSP stream:
- if (subsession.rtpSource() != NULL)
- subsession.rtpSource()->setStreamSocket(fInputSocketNum,
- subsession.rtpChannelId);
- if (subsession.rtcpInstance() != NULL)
- subsession.rtcpInstance()->setStreamSocket(fInputSocketNum,
- subsession.rtcpChannelId);
- } else {
- // Normal case.
- // Set the RTP and RTCP sockets' destination address and port
- // from the information in the SETUP response (if present):
- netAddressBits destAddress = subsession.connectionEndpointAddress();
- if (destAddress == 0) destAddress = fServerAddress;
- subsession.setDestinations(destAddress);
- }
+ // The command send succeeded, so enqueue the request record, so that its response (when it comes) can be handled:
+ fRequestsAwaitingResponse.enqueue(request);
delete[] cmd;
- return True;
+ return request->cseq();
} while (0);
+ // An error occurred, so call the response handler immediately (indicating the error):
delete[] cmd;
- return False;
+ handleRequestError(request);
+ delete request;
+ return 0;
}
-static char* createScaleString(float scale, float currentScale) {
- char buf[100];
- if (scale == 1.0f && currentScale == 1.0f) {
- // This is the default value; we don't need a "Scale:" header:
- buf[0] = '\0';
- } else {
- Locale l("C", LC_NUMERIC);
- sprintf(buf, "Scale: %f\r\n", scale);
+void RTSPClient::handleRequestError(RequestRecord* request) {
+ int 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
}
+ if (request->handler() != NULL) (*request->handler())(this, resultCode, strDup(envir().getResultMsg()));
+}
- return strDup(buf);
+Boolean RTSPClient
+::parseResponseCode(char const* line, unsigned& responseCode, char const*& responseString, Boolean& responseIsHTTP) {
+ responseIsHTTP = False; // by default
+ if (sscanf(line, "RTSP/%*s%u", &responseCode) != 1) {
+ if (sscanf(line, "HTTP/%*s%u", &responseCode) != 1) return False;
+ responseIsHTTP = True;
+ // Note: We check for HTTP responses as well as RTSP responses, both in order to setup RTSP-over-HTTP tunneling,
+ // and so that we get back a meaningful error if the client tried to mistakenly send a RTSP command to a HTTP-only server.
+ }
+
+ // Use everything after the RTSP/* as the response string:
+ responseString = line;
+ while (responseString[0] != '\0' && responseString[0] != ' ' && responseString[0] != '\t') ++responseString;
+ while (responseString[0] != '\0' && (responseString[0] == ' ' || responseString[0] == '\t')) ++responseString; // skip whitespace
+
+ return True;
}
-static char* createRangeString(double start, double end) {
- char buf[100];
- if (start < 0) {
- // We're resuming from a PAUSE; there's no "Range:" header at all
- buf[0] = '\0';
- } else if (end < 0) {
- // There's no end time:
- Locale l("C", LC_NUMERIC);
- sprintf(buf, "Range: npt=%.3f-\r\n", start);
+void RTSPClient::handleIncomingRequest() {
+ // Parse the request string into command name and 'CSeq', then 'handle' the command (by responding that we don't support it):
+ char cmdName[RTSP_PARAM_STRING_MAX];
+ char urlPreSuffix[RTSP_PARAM_STRING_MAX];
+ char urlSuffix[RTSP_PARAM_STRING_MAX];
+ char cseq[RTSP_PARAM_STRING_MAX];
+ if (!parseRTSPRequestString(fResponseBuffer, fResponseBytesAlreadySeen,
+ cmdName, sizeof cmdName,
+ urlPreSuffix, sizeof urlPreSuffix,
+ urlSuffix, sizeof urlSuffix,
+ cseq, sizeof cseq)) {
+ return;
} else {
- // There's both a start and an end time; include them both in the "Range:" hdr
- Locale l("C", LC_NUMERIC);
- sprintf(buf, "Range: npt=%.3f-%.3f\r\n", start, end);
+ if (fVerbosityLevel >= 1) {
+ envir() << "Received incoming RTSP request: " << fResponseBuffer << "\n";
+ }
+ char tmpBuf[2*RTSP_PARAM_STRING_MAX];
+ snprintf((char*)tmpBuf, sizeof tmpBuf,
+ "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n\r\n", cseq);
+ send(fOutputSocketNum, tmpBuf, strlen(tmpBuf), 0);
}
-
- return strDup(buf);
}
-static char const* NoSessionErr = "No RTSP session is currently in progress\n";
+Boolean RTSPClient::checkForHeader(char const* line, char const* headerName, unsigned headerNameLength, char const*& headerParams) {
+ if (_strncasecmp(line, headerName, headerNameLength) != 0) return False;
-Boolean RTSPClient::playMediaSession(MediaSession& session,
- double start, double end, float scale) {
- char* cmd = NULL;
- do {
- // First, make sure that we have a RTSP session in progress
- if (fLastSessionId == NULL) {
- envir().setResultMsg(NoSessionErr);
- break;
- }
+ // The line begins with the desired header name. Trim off any whitespace, and return the header parameters:
+ unsigned paramIndex = headerNameLength;
+ while (line[paramIndex] != '\0' && (line[paramIndex] == ' ' || line[paramIndex] == '\t')) ++paramIndex;
+ if (&line[paramIndex] == '\0') return False; // the header is assumed to be bad if it has no parameters
- // Send the PLAY command:
+ headerParams = &line[paramIndex];
+ return True;
+}
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator, "PLAY", fBaseURL);
- // And then a "Scale:" string:
- char* scaleStr = createScaleString(scale, session.scale());
- // And then a "Range:" string:
- char* rangeStr = createRangeString(start, end);
+Boolean RTSPClient::parseTransportParams(char const* paramsStr,
+ char*& serverAddressStr, portNumBits& serverPortNum,
+ unsigned char& rtpChannelId, unsigned char& rtcpChannelId) {
+ // Initialize the return parameters to 'not found' values:
+ serverAddressStr = NULL;
+ serverPortNum = 0;
+ rtpChannelId = rtcpChannelId = 0xFF;
- char const* const cmdFmt =
- "PLAY %s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "%s"
- "%s"
- "%s"
- "%s"
- "\r\n";
+ char* foundServerAddressStr = NULL;
+ Boolean foundServerPortNum = False;
+ portNumBits clientPortNum = 0;
+ Boolean foundClientPortNum = False;
+ Boolean foundChannelIds = False;
+ unsigned rtpCid, rtcpCid;
+ Boolean isMulticast = True; // by default
+ char* foundDestinationStr = NULL;
+ portNumBits multicastPortNumRTP, multicastPortNumRTCP;
+ Boolean foundMulticastPortNum = False;
- char const* sessURL = sessionURL(session);
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(sessURL)
- + 20 /* max int len */
- + strlen(fLastSessionId)
- + strlen(scaleStr)
- + strlen(rangeStr)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- sessURL,
- ++fCSeq,
- fLastSessionId,
- scaleStr,
- rangeStr,
- authenticatorStr,
- fUserAgentHeaderStr);
- delete[] scaleStr;
- delete[] rangeStr;
- delete[] authenticatorStr;
+ // Run through each of the parameters, looking for ones that we handle:
+ char const* fields = paramsStr;
+ char* field = strDupSize(fields);
+ while (sscanf(fields, "%[^;]", field) == 1) {
+ if (sscanf(field, "server_port=%hu", &serverPortNum) == 1) {
+ foundServerPortNum = True;
+ } else if (sscanf(field, "client_port=%hu", &clientPortNum) == 1) {
+ foundClientPortNum = True;
+ } else if (_strncasecmp(field, "source=", 7) == 0) {
+ delete[] foundServerAddressStr;
+ foundServerAddressStr = strDup(field+7);
+ } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {
+ rtpChannelId = (unsigned char)rtpCid;
+ rtcpChannelId = (unsigned char)rtcpCid;
+ foundChannelIds = True;
+ } else if (strcmp(field, "unicast") == 0) {
+ isMulticast = False;
+ } else if (_strncasecmp(field, "destination=", 12) == 0) {
+ delete[] foundDestinationStr;
+ foundDestinationStr = strDup(field+12);
+ } else if (sscanf(field, "port=%hu-%hu", &multicastPortNumRTP, &multicastPortNumRTCP) == 2 ||
+ sscanf(field, "port=%hu", &multicastPortNumRTP) == 1) {
+ foundMulticastPortNum = True;
+ }
- if (!sendRequest(cmd, "PLAY")) break;
+ fields += strlen(field);
+ while (fields[0] == ';') ++fields; // skip over all leading ';' chars
+ if (fields[0] == '\0') break;
+ }
+ delete[] field;
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("PLAY", bytesRead, responseCode, firstLine, nextLineStart)) break;
+ // If we're multicast, and have a "destination=" (multicast) address, then use this
+ // as the 'server' address (because some weird servers don't specify the multicast
+ // address earlier, in the "DESCRIBE" response's SDP:
+ if (isMulticast && foundDestinationStr != NULL && foundMulticastPortNum) {
+ delete[] foundServerAddressStr;
+ serverAddressStr = foundDestinationStr;
+ serverPortNum = multicastPortNumRTP;
+ return True;
+ }
+ delete[] foundDestinationStr;
- // Look for various headers that we understand:
- char* lineStart;
- while (1) {
- lineStart = nextLineStart;
- if (lineStart == NULL) break;
+ // We have a valid "Transport:" header if any of the following are true:
+ // - We saw a "interleaved=" field, indicating RTP/RTCP-over-TCP streaming, or
+ // - We saw a "server_port=" field, or
+ // - We saw a "client_port=" field.
+ // If we didn't also see a "server_port=" field, then the server port is assumed to be the same as the client port.
+ if (foundChannelIds || foundServerPortNum || foundClientPortNum) {
+ if (foundClientPortNum && !foundServerPortNum) {
+ serverPortNum = clientPortNum;
+ }
+ serverAddressStr = foundServerAddressStr;
+ return True;
+ }
- nextLineStart = getLine(lineStart);
+ delete[] foundServerAddressStr;
+ return False;
+}
- if (parseScaleHeader(lineStart, session.scale())) continue;
- if (parseRangeHeader(lineStart, session.playStartTime(), session.playEndTime())) continue;
+Boolean RTSPClient::parseScaleParam(char const* paramStr, float& scale) {
+ Locale l("C", LC_NUMERIC);
+ return sscanf(paramStr, "%f", &scale) == 1;
+}
- u_int16_t seqNum; u_int32_t timestamp;
- if (parseRTPInfoHeader(lineStart, seqNum, timestamp)) {
- // This is data for our first subsession. Fill it in, and do the same for our other subsessions:
- MediaSubsessionIterator iter(session);
- MediaSubsession* subsession;
- while ((subsession = iter.next()) != NULL) {
- subsession->rtpInfo.seqNum = seqNum;
- subsession->rtpInfo.timestamp = timestamp;
- subsession->rtpInfo.infoIsNew = True;
+Boolean RTSPClient::parseRTPInfoParams(char const*& paramsStr, u_int16_t& seqNum, u_int32_t& timestamp) {
+ while (paramsStr[0] == ',') ++paramsStr;
- if (!parseRTPInfoHeader(lineStart, seqNum, timestamp)) break;
- }
- continue;
- }
- }
+ // "paramsStr" now consists of a ';'-separated list of parameters, ending with ',' or '\0'.
+ char* field = strDupSize(paramsStr);
- if (fTCPStreamIdCount == 0) { // we're not receiving RTP-over-TCP
- // Arrange to handle incoming requests sent by the server
- envir().taskScheduler().turnOnBackgroundReadHandling(fInputSocketNum,
- (TaskScheduler::BackgroundHandlerProc*)&incomingRequestHandler, this);
+ while (sscanf(paramsStr, "%[^;,]", field) == 1) {
+ if (sscanf(field, "seq=%hu", &seqNum) == 1 ||
+ sscanf(field, "rtptime=%u", ×tamp) == 1) {
}
- delete[] cmd;
- return True;
- } while (0);
+ paramsStr += strlen(field);
+ if (paramsStr[0] == '\0' || paramsStr[0] == ',') break;
+ // ASSERT: paramsStr[0] == ';'
+ ++paramsStr; // skip over the ';'
+ }
- delete[] cmd;
- return False;
+ delete[] field;
+ return True;
}
-Boolean RTSPClient::playMediaSubsession(MediaSubsession& subsession,
- double start, double end, float scale,
- Boolean hackForDSS) {
- char* cmd = NULL;
+Boolean RTSPClient::handleSETUPResponse(MediaSubsession& subsession, char const* sessionParamsStr, char const* transportParamsStr,
+ Boolean streamUsingTCP) {
+ char* sessionId = new char[responseBufferSize]; // ensures we have enough space
+ Boolean success = False;
do {
- // First, make sure that we have a RTSP session in progress
- if (subsession.sessionId == NULL) {
- envir().setResultMsg(NoSessionErr);
+ // Check for a session id:
+ if (sessionParamsStr == NULL || sscanf(sessionParamsStr, "%[^;]", sessionId) != 1) {
+ envir().setResultMsg("Missing or bad \"Session:\" header");
break;
}
+ subsession.sessionId = strDup(sessionId);
+ delete[] fLastSessionId; fLastSessionId = strDup(sessionId);
- // Send the PLAY command:
-
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator, "PLAY", fBaseURL);
- // And then a "Scale:" string:
- char* scaleStr = createScaleString(scale, subsession.scale());
- // And then a "Range:" string:
- char* rangeStr = createRangeString(start, end);
+ // Also look for an optional "; timeout = " parameter following this:
+ char const* afterSessionId = sessionParamsStr + strlen(sessionId);
+ int timeoutVal;
+ if (sscanf(afterSessionId, "; timeout = %d", &timeoutVal) == 1) {
+ fSessionTimeoutParameter = timeoutVal;
+ }
- char const* const cmdFmt =
- "PLAY %s%s%s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "%s"
- "%s"
- "%s"
- "%s"
- "\r\n";
-
- char const *prefix, *separator, *suffix;
- constructSubsessionURL(subsession, prefix, separator, suffix);
- if (hackForDSS || fServerIsKasenna) {
- // When "PLAY" is used to inject RTP packets into a DSS
- // (violating the RTSP spec, btw; "RECORD" should have been used)
- // the DSS can crash (or hang) if the '/trackid=...' portion of
- // the URL is present.
- separator = suffix = "";
+ // Parse the "Transport:" header parameters:
+ char* serverAddressStr;
+ portNumBits serverPortNum;
+ unsigned char rtpChannelId, rtcpChannelId;
+ if (!parseTransportParams(transportParamsStr, serverAddressStr, serverPortNum, rtpChannelId, rtcpChannelId)) {
+ envir().setResultMsg("Missing or bad \"Transport:\" header");
+ break;
}
+ delete[] subsession.connectionEndpointName();
+ subsession.connectionEndpointName() = serverAddressStr;
+ subsession.serverPortNum = serverPortNum;
+ subsession.rtpChannelId = rtpChannelId;
+ subsession.rtcpChannelId = rtcpChannelId;
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(prefix) + strlen(separator) + strlen(suffix)
- + 20 /* max int len */
- + strlen(subsession.sessionId)
- + strlen(scaleStr)
- + strlen(rangeStr)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- prefix, separator, suffix,
- ++fCSeq,
- subsession.sessionId,
- scaleStr,
- rangeStr,
- authenticatorStr,
- fUserAgentHeaderStr);
- delete[] scaleStr;
- delete[] rangeStr;
- delete[] authenticatorStr;
+ if (streamUsingTCP) {
+ // Tell the subsession to receive RTP (and send/receive RTCP) over the RTSP stream:
+ if (subsession.rtpSource() != NULL) {
+ subsession.rtpSource()->setStreamSocket(fInputSocketNum, subsession.rtpChannelId);
+ subsession.rtpSource()->setServerRequestAlternativeByteHandler(fInputSocketNum, handleAlternativeRequestByte, this);
+ }
+ if (subsession.rtcpInstance() != NULL) subsession.rtcpInstance()->setStreamSocket(fInputSocketNum, subsession.rtcpChannelId);
+ } else {
+ // Normal case.
+ // Set the RTP and RTCP sockets' destination address and port from the information in the SETUP response (if present):
+ netAddressBits destAddress = subsession.connectionEndpointAddress();
+ if (destAddress == 0) destAddress = fServerAddress;
+ subsession.setDestinations(destAddress);
+ }
- if (!sendRequest(cmd, "PLAY")) break;
+ success = True;
+ } while (0);
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("PLAY", bytesRead, responseCode, firstLine, nextLineStart)) break;
+ delete[] sessionId;
+ return success;
+}
- // Look for various headers that we understand:
- char* lineStart;
- while (1) {
- lineStart = nextLineStart;
- if (lineStart == NULL) break;
+Boolean RTSPClient::handlePLAYResponse(MediaSession& session, MediaSubsession& subsession,
+ char const* scaleParamsStr, char const* rangeParamsStr, char const* rtpInfoParamsStr) {
+ Boolean scaleOK = False, rangeOK = False;
+ do {
+ if (&session != NULL) {
+ // The command was on the whole session
+ if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, session.scale())) break;
+ scaleOK = True;
+ if (rangeParamsStr != NULL && !parseRangeParam(rangeParamsStr, session.playStartTime(), session.playEndTime())) break;
+ rangeOK = True;
- nextLineStart = getLine(lineStart);
+ u_int16_t seqNum; u_int32_t timestamp;
+ if (rtpInfoParamsStr != NULL) {
+ if (!parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) break;
+ // This is data for our first subsession. Fill it in, and do the same for our other subsessions:
+ MediaSubsessionIterator iter(session);
+ MediaSubsession* subsession;
+ while ((subsession = iter.next()) != NULL) {
+ subsession->rtpInfo.seqNum = seqNum;
+ subsession->rtpInfo.timestamp = timestamp;
+ subsession->rtpInfo.infoIsNew = True;
- if (parseScaleHeader(lineStart, subsession.scale())) continue;
- if (parseRangeHeader(lineStart, subsession._playStartTime(), subsession._playEndTime())) continue;
+ if (!parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) break;
+ }
+ }
+ } else {
+ // The command was on a subsession
+ if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, subsession.scale())) break;
+ scaleOK = True;
+ if (rangeParamsStr != NULL && !parseRangeParam(rangeParamsStr, subsession._playStartTime(), subsession._playEndTime())) break;
+ rangeOK = True;
u_int16_t seqNum; u_int32_t timestamp;
- if (parseRTPInfoHeader(lineStart, seqNum, timestamp)) {
+ if (rtpInfoParamsStr != NULL) {
+ if (!parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) break;
subsession.rtpInfo.seqNum = seqNum;
subsession.rtpInfo.timestamp = timestamp;
subsession.rtpInfo.infoIsNew = True;
- continue;
}
}
- delete[] cmd;
return True;
} while (0);
- delete[] cmd;
+ // An error occurred:
+ if (!scaleOK) {
+ envir().setResultMsg("Bad \"Scale:\" header");
+ } else if (!rangeOK) {
+ envir().setResultMsg("Bad \"Range:\" header");
+ } else {
+ envir().setResultMsg("Bad \"RTP-Info:\" header");
+ }
return False;
}
-Boolean RTSPClient::pauseMediaSession(MediaSession& session) {
- char* cmd = NULL;
- do {
- // First, make sure that we have a RTSP session in progress
- if (fLastSessionId == NULL) {
- envir().setResultMsg(NoSessionErr);
- break;
+Boolean RTSPClient::handleTEARDOWNResponse(MediaSession& session, MediaSubsession& subsession) {
+ if (&session != NULL) {
+ // The command was on the whole session
+ // Run through each subsession, deleting its "sessionId":
+ MediaSubsessionIterator iter(session);
+ MediaSubsession* subsession;
+ while ((subsession = iter.next()) != NULL) {
+ delete[] (char*)subsession->sessionId;
+ subsession->sessionId = NULL;
}
-
- // Send the PAUSE command:
-
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator, "PAUSE", fBaseURL);
-
- char const* sessURL = sessionURL(session);
- char const* const cmdFmt =
- "PAUSE %s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "%s"
- "%s"
- "\r\n";
-
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(sessURL)
- + 20 /* max int len */
- + strlen(fLastSessionId)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- sessURL,
- ++fCSeq,
- fLastSessionId,
- authenticatorStr,
- fUserAgentHeaderStr);
- delete[] authenticatorStr;
-
- if (!sendRequest(cmd, "PAUSE")) break;
-
- if (fTCPStreamIdCount == 0) { // When TCP streaming, don't look for a response
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("PAUSE", bytesRead, responseCode, firstLine, nextLineStart)) break;
- }
-
- delete[] cmd;
- return True;
- } while (0);
-
- delete[] cmd;
- return False;
+ } else {
+ // The command was on a subsession
+ delete[] (char*)subsession.sessionId;
+ subsession.sessionId = NULL;
+ }
+ return True;
}
-Boolean RTSPClient::pauseMediaSubsession(MediaSubsession& subsession) {
- char* cmd = NULL;
+Boolean RTSPClient::handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString) {
do {
- // First, make sure that we have a RTSP session in progress
- if (subsession.sessionId == NULL) {
- envir().setResultMsg(NoSessionErr);
- break;
- }
-
- // Send the PAUSE command:
-
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator, "PAUSE", fBaseURL);
-
- char const* const cmdFmt =
- "PAUSE %s%s%s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "%s"
- "%s"
- "\r\n";
+ // If "parameterName" is non-empty, it should be (possibly followed by ':' and whitespace) at the start of the result string:
+ if (parameterName != NULL && parameterName[0] != '\0') {
+ if (parameterName[1] == '\0') break; // sanity check; there should have been \r\n at the end of "parameterName"
- char const *prefix, *separator, *suffix;
- constructSubsessionURL(subsession, prefix, separator, suffix);
- if (fServerIsKasenna) separator = suffix = "";
-
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(prefix) + strlen(separator) + strlen(suffix)
- + 20 /* max int len */
- + strlen(subsession.sessionId)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- prefix, separator, suffix,
- ++fCSeq,
- subsession.sessionId,
- authenticatorStr,
- fUserAgentHeaderStr);
- delete[] authenticatorStr;
-
- if (!sendRequest(cmd, "PAUSE")) break;
-
- if (fTCPStreamIdCount == 0) { // When TCP streaming, don't look for a response
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("PAUSE", bytesRead, responseCode, firstLine, nextLineStart)) break;
+ unsigned parameterNameLen = strlen(parameterName);
+ // ASSERT: parameterNameLen >= 2;
+ parameterNameLen -= 2; // because of the trailing \r\n
+ if (_strncasecmp(resultValueString, parameterName, parameterNameLen) != 0) break; // parameter name wasn't in the output
+ resultValueString += parameterNameLen;
+ if (resultValueString[0] == ':') ++resultValueString;
+ while (resultValueString[0] == ' ' || resultValueString[0] == '\t') ++resultValueString;
}
- delete[] cmd;
+ // The rest of "resultValueStr" should be our desired result, but first trim off any \r and/or \n characters at the end:
+ unsigned resultLen = strlen(resultValueString);
+ while (resultLen > 0 && (resultValueString[resultLen-1] == '\r' || resultValueString[resultLen-1] == '\n')) --resultLen;
+ resultValueString[resultLen] = '\0';
+
return True;
} while (0);
- delete[] cmd;
+ // An error occurred:
+ envir().setResultMsg("Bad \"GET_PARAMETER\" response");
return False;
}
-Boolean RTSPClient::recordMediaSubsession(MediaSubsession& subsession) {
- char* cmd = NULL;
- do {
- // First, make sure that we have a RTSP session in progress
- if (subsession.sessionId == NULL) {
- envir().setResultMsg(NoSessionErr);
- break;
- }
+Boolean RTSPClient::handleAuthenticationFailure(char const* paramsStr) {
+ // Fill in "fCurrentAuthenticator" with the information from the "WWW-Authenticate:" header:
+ Boolean alreadyHadRealm = fCurrentAuthenticator.realm() != NULL;
+ char* realm = strDupSize(paramsStr);
+ char* nonce = strDupSize(paramsStr);
+ Boolean success = True;
+ if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"", realm, nonce) == 2) {
+ fCurrentAuthenticator.setRealmAndNonce(realm, nonce);
+ } else if (sscanf(paramsStr, "Basic realm=\"%[^\"]\"", realm) == 1) {
+ fCurrentAuthenticator.setRealmAndNonce(realm, NULL); // Basic authentication
+ } else {
+ success = False; // bad "WWW-Authenticate:" header
+ }
+ delete[] realm; delete[] nonce;
- // Send the RECORD command:
+ if (alreadyHadRealm || fCurrentAuthenticator.username() == NULL || fCurrentAuthenticator.password() == NULL) {
+ // We already had a 'realm', or don't have a username and/or password,
+ // so the new "WWW-Authenticate:" header information won't help us. We remain unauthenticated.
+ success = False;
+ }
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator,
- "RECORD", fBaseURL);
+ return success;
+}
- char const* const cmdFmt =
- "RECORD %s%s%s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "Range: npt=0-\r\n"
- "%s"
- "%s"
- "\r\n";
+Boolean RTSPClient::resendCommand(RequestRecord* request) {
+ if (fVerbosityLevel >= 1) envir() << "Resending...\n";
+ if (request != NULL) request->cseq() = ++fCSeq;
+ return sendRequest(request) != 0;
+}
- char const *prefix, *separator, *suffix;
- constructSubsessionURL(subsession, prefix, separator, suffix);
+char const* RTSPClient::sessionURL(MediaSession const& session) const {
+ char const* url = session.controlPath();
+ if (url == NULL || strcmp(url, "*") == 0) url = fBaseURL;
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(prefix) + strlen(separator) + strlen(suffix)
- + 20 /* max int len */
- + strlen(subsession.sessionId)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- prefix, separator, suffix,
- ++fCSeq,
- subsession.sessionId,
- authenticatorStr,
- fUserAgentHeaderStr);
- delete[] authenticatorStr;
+ return url;
+}
- if (!sendRequest(cmd, "RECORD")) break;
+void RTSPClient::handleAlternativeRequestByte(void* rtspClient, u_int8_t requestByte) {
+ ((RTSPClient*)rtspClient)->handleAlternativeRequestByte1(requestByte);
+}
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("RECORD", bytesRead, responseCode, firstLine, nextLineStart)) break;
+void RTSPClient::handleAlternativeRequestByte1(u_int8_t requestByte) {
+ fResponseBuffer[fResponseBytesAlreadySeen] = requestByte;
+ handleResponseBytes(1);
+}
- delete[] cmd;
- return True;
- } while (0);
+static Boolean isAbsoluteURL(char const* url) {
+ // Assumption: "url" is absolute if it contains a ':', before any
+ // occurrence of '/'
+ while (*url != '\0' && *url != '/') {
+ if (*url == ':') return True;
+ ++url;
+ }
- delete[] cmd;
return False;
}
-Boolean RTSPClient::setMediaSessionParameter(MediaSession& /*session*/,
- char const* parameterName,
- char const* parameterValue) {
- char* cmd = NULL;
- do {
- // First, make sure that we have a RTSP session in progress
- if (fLastSessionId == NULL) {
- envir().setResultMsg(NoSessionErr);
- break;
- }
-
- // Send the SET_PARAMETER command:
-
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator,
- "SET_PARAMETER", fBaseURL);
-
- char const* const cmdFmt =
- "SET_PARAMETER %s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "%s"
- "%s"
- "Content-length: %d\r\n\r\n"
- "%s: %s\r\n";
+void RTSPClient::constructSubsessionURL(MediaSubsession const& subsession,
+ char const*& prefix,
+ char const*& separator,
+ char const*& suffix) {
+ // Figure out what the URL describing "subsession" will look like.
+ // The URL is returned in three parts: prefix; separator; suffix
+ //##### NOTE: This code doesn't really do the right thing if "sessionURL()"
+ // doesn't end with a "/", and "subsession.controlPath()" is relative.
+ // The right thing would have been to truncate "sessionURL()" back to the
+ // rightmost "/", and then add "subsession.controlPath()".
+ // In practice, though, each "DESCRIBE" response typically contains
+ // a "Content-Base:" header that consists of "sessionURL()" followed by
+ // a "/", in which case this code ends up giving the correct result.
+ // However, we should really fix this code to do the right thing, and
+ // also check for and use the "Content-Base:" header appropriately. #####
+ prefix = sessionURL(subsession.parentSession());
+ if (prefix == NULL) prefix = "";
- unsigned parameterNameLen = strlen(parameterName);
- unsigned parameterValueLen = strlen(parameterValue);
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(fBaseURL)
- + 20 /* max int len */
- + strlen(fLastSessionId)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize
- + parameterNameLen + parameterValueLen;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- fBaseURL,
- ++fCSeq,
- fLastSessionId,
- authenticatorStr,
- fUserAgentHeaderStr,
- parameterNameLen + parameterValueLen + 2, // the "+ 2" is for the \r\n after the parameter "name: value"
- parameterName, parameterValue);
- delete[] authenticatorStr;
+ suffix = subsession.controlPath();
+ if (suffix == NULL) suffix = "";
- if (!sendRequest(cmd, "SET_PARAMETER")) break;
+ if (isAbsoluteURL(suffix)) {
+ prefix = separator = "";
+ } else {
+ unsigned prefixLen = strlen(prefix);
+ separator = (prefixLen == 0 || prefix[prefixLen-1] == '/' || suffix[0] == '/') ? "" : "/";
+ }
+}
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("SET_PARAMETER", bytesRead, responseCode, firstLine, nextLineStart)) break;
+Boolean RTSPClient::setupHTTPTunneling1() {
+ // Set up RTSP-over-HTTP tunneling, as described in
+ // http://developer.apple.com/documentation/QuickTime/QTSS/Concepts/chapter_2_section_14.html
+ if (fVerbosityLevel >= 1) {
+ envir() << "Requesting RTSP-over-HTTP tunneling (on port " << fTunnelOverHTTPPortNum << ")\n\n";
+ }
- delete[] cmd;
- return True;
- } while (0);
+ // Begin by sending a HTTP "GET", to set up the server->client link. Continue when we handle the response:
+ return sendRequest(new RequestRecord(1, "GET", responseHandlerForHTTP_GET)) != 0;
+}
- delete[] cmd;
- return False;
+void RTSPClient::responseHandlerForHTTP_GET(RTSPClient* rtspClient, int responseCode, char* responseString) {
+ if (rtspClient != NULL) rtspClient->responseHandlerForHTTP_GET1(responseCode, responseString);
}
-Boolean RTSPClient::getMediaSessionParameter(MediaSession& /*session*/,
- char const* parameterName,
- char*& parameterValue) {
- parameterValue = NULL; // default result
- Boolean const haveParameterName = parameterName != NULL && parameterName[0] != '\0';
- char* cmd = NULL;
+void RTSPClient::responseHandlerForHTTP_GET1(int responseCode, char* responseString) {
+ RequestRecord* request;
do {
- // First, make sure that we have a RTSP session in progress
- if (fLastSessionId == NULL) {
- envir().setResultMsg(NoSessionErr);
- break;
+ // Having successfully set up (using the HTTP "GET" command) the server->client link, set up a second TCP connection
+ // (to the same server & port as before) for the client->server link. All future output will be to this new socket.
+ fOutputSocketNum = setupStreamSocket(envir(), 0);
+ if (fOutputSocketNum < 0) break;
+
+ fHTTPTunnelingConnectionIsPending = True;
+ int connectResult = connectToServer(fOutputSocketNum, fTunnelOverHTTPPortNum);
+ if (connectResult < 0) break; // an error occurred
+ else if (connectResult == 0) {
+ // A connection is pending. Continue setting up RTSP-over-HTTP when the connection completes.
+ // First, move the pending requests to the 'awaiting connection' queue:
+ while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
+ fRequestsAwaitingConnection.enqueue(request);
+ }
+ return;
}
- // Send the GET_PARAMETER command:
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator,
- "GET_PARAMETER", fBaseURL);
-
- if (haveParameterName) {
- char const* const cmdFmt =
- "GET_PARAMETER %s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "%s"
- "%s"
- "Content-type: text/parameters\r\n"
- "Content-length: %d\r\n\r\n"
- "%s\r\n";
+ // The connection succeeded. Continue setting up RTSP-over-HTTP:
+ if (!setupHTTPTunneling2()) break;
- unsigned parameterNameLen = strlen(parameterName);
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(fBaseURL)
- + 20 /* max int len */
- + strlen(fLastSessionId)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize
- + parameterNameLen;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- fBaseURL,
- ++fCSeq,
- fLastSessionId,
- authenticatorStr,
- fUserAgentHeaderStr,
- parameterNameLen + 2, // the "+ 2" is for the \r\n after the parameter name
- parameterName);
- } else {
- char const* const cmdFmt =
- "GET_PARAMETER %s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "%s"
- "%s"
- "\r\n";
-
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(fBaseURL)
- + 20 /* max int len */
- + strlen(fLastSessionId)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- fBaseURL,
- ++fCSeq,
- fLastSessionId,
- authenticatorStr,
- fUserAgentHeaderStr);
+ // RTSP-over-HTTP tunneling succeeded. Resume the pending request(s):
+ while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
+ sendRequest(request);
}
- delete[] authenticatorStr;
-
- if (!sendRequest(cmd, "GET_PARAMETER")) break;
+ return;
+ } while (0);
- // Get the response from the server:
- // This section was copied/modified from the RTSPClient::describeURL func
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("GET_PARAMETER", bytesRead, responseCode, firstLine,
- nextLineStart, False /*don't check for response code 200*/)) break;
+ // An error occurred. Dequeue the pending request(s), and tell them about the error:
+ fHTTPTunnelingConnectionIsPending = False;
+ while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
+ handleRequestError(request);
+ delete request;
+ }
+ resetTCPSockets();
+}
- // Inspect the first line to check whether it's a result code that
- // we can handle.
- if (responseCode != 200) {
- envir().setResultMsg("cannot handle GET_PARAMETER response: ", firstLine);
- break;
- }
+Boolean RTSPClient::setupHTTPTunneling2() {
+ fHTTPTunnelingConnectionIsPending = False;
- // Skip every subsequent header line, until we see a blank line
- // The remaining data is assumed to be the parameter data that we want.
- char* serverType = new char[fResponseBufferSize]; // ensures enough space
- int contentLength = -1;
- char* lineStart;
- while (1) {
- lineStart = nextLineStart;
- if (lineStart == NULL) break;
+ // Send a HTTP "POST", to set up the client->server link. (Note that we won't see a reply to the "POST".)
+ return sendRequest(new RequestRecord(1, "POST", NULL)) != 0;
+}
- nextLineStart = getLine(lineStart);
- if (lineStart[0] == '\0') break; // this is a blank line
+void RTSPClient::connectionHandler(void* instance, int /*mask*/) {
+ RTSPClient* client = (RTSPClient*)instance;
+ client->connectionHandler1();
+}
- if (sscanf(lineStart, "Content-Length: %d", &contentLength) == 1
- || sscanf(lineStart, "Content-length: %d", &contentLength) == 1) {
- if (contentLength < 0) {
- envir().setResultMsg("Bad \"Content-length:\" header: \"",
- lineStart, "\"");
- break;
- }
- }
- }
- delete[] serverType;
+void RTSPClient::connectionHandler1() {
+ // Restore normal handling on our sockets:
+ envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum);
+ envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE,
+ (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
+
+ // Move all requests awaiting connection into a new, temporary queue, to clear "fRequestsAwaitingConnection"
+ // (so that "sendRequest()" doesn't get confused by "fRequestsAwaitingConnection" being nonempty, and enqueue them all over again).
+ RequestQueue tmpRequestQueue;
+ RequestRecord* request;
+ while ((request = fRequestsAwaitingConnection.dequeue()) != NULL) {
+ tmpRequestQueue.enqueue(request);
+ }
- // We're now at the end of the response header lines
- if (lineStart == NULL) {
- envir().setResultMsg("no content following header lines: ",
- fResponseBuffer);
+ // Find out whether the connection succeeded or failed:
+ do {
+ int err = 0;
+ SOCKLEN_T len = sizeof err;
+ if (getsockopt(fInputSocketNum, SOL_SOCKET, SO_ERROR, (char*)&err, &len) < 0 || err != 0) {
+ envir().setResultErrMsg("Connection to server failed: ", err);
+ if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n";
break;
}
- // Use the remaining data as the parameter data, but first, check
- // the "Content-length:" header (if any) that we saw. We may need to
- // read more data, or we may have extraneous data in the buffer.
- char* bodyStart = nextLineStart;
- if (contentLength >= 0) {
- // We saw a "Content-length:" header
- unsigned numBodyBytes = &firstLine[bytesRead] - bodyStart;
- if (contentLength > (int)numBodyBytes) {
- // We need to read more data. First, make sure we have enough
- // space for it:
- unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
- unsigned remainingBufferSize
- = fResponseBufferSize - (bytesRead + (firstLine - fResponseBuffer));
- if (numExtraBytesNeeded > remainingBufferSize) {
- char tmpBuf[200];
- sprintf(tmpBuf, "Read buffer size (%d) is too small for \"Content-length:\" %d (need a buffer size of >= %d bytes\n",
- fResponseBufferSize, contentLength,
- fResponseBufferSize + numExtraBytesNeeded - remainingBufferSize);
- envir().setResultMsg(tmpBuf);
- break;
- }
+ // The connection succeeded. If the connection came about from an attempt to set up RTSP-over-HTTP, finish this now:
+ if (fVerbosityLevel >= 1) envir() << "...remote connection opened\n";
+ if (fHTTPTunnelingConnectionIsPending && !setupHTTPTunneling2()) break;
- // Keep reading more data until we have enough:
- if (fVerbosityLevel >= 1) {
- envir() << "Need to read " << numExtraBytesNeeded
- << " extra bytes\n";
- }
- while (numExtraBytesNeeded > 0) {
- struct sockaddr_in fromAddress;
- char* ptr = &firstLine[bytesRead];
- int bytesRead2 = readSocket(envir(), fInputSocketNum, (unsigned char*)ptr,
- numExtraBytesNeeded, fromAddress);
- if (bytesRead2 <= 0) break;
- ptr[bytesRead2] = '\0';
- if (fVerbosityLevel >= 1) {
- envir() << "Read " << bytesRead2 << " extra bytes: "
- << ptr << "\n";
- }
-
- bytesRead += bytesRead2;
- numExtraBytesNeeded -= bytesRead2;
- }
- if (numExtraBytesNeeded > 0) break; // one of the reads failed
- }
+ // Resume sending all pending requests:
+ while ((request = tmpRequestQueue.dequeue()) != NULL) {
+ sendRequest(request);
}
-
- if (haveParameterName
- && !parseGetParameterHeader(bodyStart, parameterName, parameterValue)) break;
-
- delete[] cmd;
- return True;
+ return;
} while (0);
- delete[] cmd;
- return False;
+ // An error occurred. Tell all pending requests about the error:
+ while ((request = tmpRequestQueue.dequeue()) != NULL) {
+ handleRequestError(request);
+ delete request;
+ }
+ resetTCPSockets();
}
-Boolean RTSPClient::teardownMediaSession(MediaSession& session) {
- char* cmd = NULL;
- do {
- // First, make sure that we have a RTSP session in progreee
- if (fLastSessionId == NULL) {
- envir().setResultMsg(NoSessionErr);
- break;
- }
-
- // Send the TEARDOWN command:
-
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator,
- "TEARDOWN", fBaseURL);
-
- char const* sessURL = sessionURL(session);
- char const* const cmdFmt =
- "TEARDOWN %s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "%s"
- "%s"
- "\r\n";
-
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(sessURL)
- + 20 /* max int len */
- + strlen(fLastSessionId)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- sessURL,
- ++fCSeq,
- fLastSessionId,
- authenticatorStr,
- fUserAgentHeaderStr);
- delete[] authenticatorStr;
+void RTSPClient::incomingDataHandler(void* instance, int /*mask*/) {
+ RTSPClient* client = (RTSPClient*)instance;
+ client->incomingDataHandler1();
+}
- if (!sendRequest(cmd, "TEARDOWN")) break;
+void RTSPClient::incomingDataHandler1() {
+ struct sockaddr_in dummy; // 'from' address - not used
- if (fTCPStreamIdCount == 0) { // When TCP streaming, don't look for a response
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- getResponse("TEARDOWN", bytesRead, responseCode, firstLine, nextLineStart); // ignore the response; from our POV, we're done
+ int bytesRead = readSocket(envir(), fInputSocketNum, (unsigned char*)&fResponseBuffer[fResponseBytesAlreadySeen], fResponseBufferBytesLeft, dummy);
+ handleResponseBytes(bytesRead);
+}
- // Run through each subsession, deleting its "sessionId":
- MediaSubsessionIterator iter(session);
- MediaSubsession* subsession;
- while ((subsession = iter.next()) != NULL) {
- delete[] (char*)subsession->sessionId;
- subsession->sessionId = NULL;
+static char* getLine(char* startOfLine) {
+ // returns the start of the next line, or NULL if none. Note that this modifies the input string to add '\0' characters.
+ for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) {
+ // Check for the end of line: \r\n (but also accept \r or \n by itself):
+ if (*ptr == '\r' || *ptr == '\n') {
+ // We found the end of the line
+ if (*ptr == '\r') {
+ *ptr++ = '\0';
+ if (*ptr == '\n') ++ptr;
+ } else {
+ *ptr++ = '\0';
}
-
- delete[] fLastSessionId; fLastSessionId = NULL;
- // we're done with this session
+ return ptr;
}
+ }
- delete[] cmd;
- return True;
- } while (0);
-
- delete[] cmd;
- return False;
+ return NULL;
}
-Boolean RTSPClient::teardownMediaSubsession(MediaSubsession& subsession) {
- char* cmd = NULL;
+void RTSPClient::handleResponseBytes(int newBytesRead) {
do {
- // First, make sure that we have a RTSP session in progreee
- if (subsession.sessionId == NULL) {
- envir().setResultMsg(NoSessionErr);
- break;
- }
-
- // Send the TEARDOWN command:
+ if (newBytesRead > 0 && (unsigned)newBytesRead < fResponseBufferBytesLeft) break; // data was read OK; process it below
- // First, construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(&fCurrentAuthenticator,
- "TEARDOWN", fBaseURL);
-
- char const* const cmdFmt =
- "TEARDOWN %s%s%s RTSP/1.0\r\n"
- "CSeq: %d\r\n"
- "Session: %s\r\n"
- "%s"
- "%s"
- "\r\n";
-
- char const *prefix, *separator, *suffix;
- constructSubsessionURL(subsession, prefix, separator, suffix);
-
- unsigned cmdSize = strlen(cmdFmt)
- + strlen(prefix) + strlen(separator) + strlen(suffix)
- + 20 /* max int len */
- + strlen(subsession.sessionId)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize;
- cmd = new char[cmdSize];
- sprintf(cmd, cmdFmt,
- prefix, separator, suffix,
- ++fCSeq,
- subsession.sessionId,
- authenticatorStr,
- fUserAgentHeaderStr);
- delete[] authenticatorStr;
+ if ((unsigned)newBytesRead >= fResponseBufferBytesLeft) {
+ // We filled up our response buffer. Treat this as an error (for the first response handler):
+ envir().setResultMsg("RTSP response was truncated. Increase \"RTSPClient::responseBufferSize\"");
+ }
- if (!sendRequest(cmd, "TEARDOWN")) break;
+ // An error occurred while reading our TCP socket. Call all pending response handlers, indicating this error:
+ RequestRecord* request;
+ while ((request = fRequestsAwaitingResponse.dequeue()) != NULL) {
+ handleRequestError(request);
+ delete request;
- if (fTCPStreamIdCount == 0) { // When TCP streaming, don't look for a response
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- getResponse("TEARDOWN", bytesRead, responseCode, firstLine, nextLineStart); // ignore the response; from our POV, we're done
+ if (newBytesRead > 0) break; // The "RTSP response was truncated" error is applied to the first response handler only
}
- delete[] (char*)subsession.sessionId;
- subsession.sessionId = NULL;
- // we're done with this session
-
- delete[] cmd;
- return True;
+ if (newBytesRead <= 0) resetTCPSockets();
+ resetResponseBuffer();
+ return;
} while (0);
- delete[] cmd;
- return False;
-}
+ fResponseBufferBytesLeft -= newBytesRead;
+ fResponseBytesAlreadySeen += newBytesRead;
+ fResponseBuffer[fResponseBytesAlreadySeen] = '\0';
+ if (fVerbosityLevel >= 1 && newBytesRead > 1) envir() << "Received " << newBytesRead << " new bytes of response data.\n";
+
+ // Data was read OK. Look through the data that we've read so far, to see if it contains <CR><LF><CR><LF>.
+ // (If not, wait for more data to arrive.)
+ Boolean endOfHeaders = False;
+ if (fResponseBytesAlreadySeen > 3) {
+ char const* const ptrEnd = &fResponseBuffer[fResponseBytesAlreadySeen-3];
+ char const* ptr = fResponseBuffer;
+ while (ptr < ptrEnd) {
+ if (*ptr++ == '\r' && *ptr++ == '\n' && *ptr++ == '\r' && *ptr++ == '\n') {
+ // This is it
+ endOfHeaders = True;
+ break;
+ }
+ }
+ }
-Boolean RTSPClient
-::openConnectionFromURL(char const* url, Authenticator* authenticator, int timeout) {
+ if (!endOfHeaders) return; // subsequent reads will be needed to get the complete response
+
+ // Now that we have the complete response headers (ending with <CR><LF><CR><LF>), parse them to get the response code, CSeq,
+ // and various other header parameters. To do this, we first make a copy of the received header data, because we'll be modifying
+ // it by adding '\0' bytes.
+ char* headerDataCopy;
+ unsigned responseCode = 200;
+ char const* responseStr = NULL;
+ Boolean responseIsHTTP = False;
+ RequestRecord* foundRequest = NULL;
+ char const* sessionParamsStr = NULL;
+ char const* transportParamsStr = NULL;
+ char const* scaleParamsStr = NULL;
+ char const* rangeParamsStr = NULL;
+ char const* rtpInfoParamsStr = NULL;
+ char const* wwwAuthenticateParamsStr = NULL;
+ char const* publicParamsStr = NULL;
+ char* bodyStart = NULL;
+ unsigned numBodyBytes = 0;
+ Boolean responseSuccess = False; // by default
do {
- // Set this as our base URL:
- delete[] fBaseURL; fBaseURL = strDup(url); if (fBaseURL == NULL) break;
+ headerDataCopy = new char[responseBufferSize];
+ strncpy(headerDataCopy, fResponseBuffer, fResponseBytesAlreadySeen);
+ headerDataCopy[fResponseBytesAlreadySeen] = '\0';
- // Begin by parsing the URL:
+ char* lineStart = headerDataCopy;
+ char* nextLineStart = getLine(lineStart);
+ if (!parseResponseCode(lineStart, responseCode, responseStr, responseIsHTTP)) {
+ // This does not appear to be a RTSP response; perhaps it's a RTSP request instead?
+ handleIncomingRequest();
+ break; // we're done with this data
+ }
- NetAddress destAddress;
- portNumBits urlPortNum;
- char const* urlSuffix;
- if (!parseRTSPURL(envir(), url, destAddress, urlPortNum, &urlSuffix)) break;
- portNumBits destPortNum
- = fTunnelOverHTTPPortNum == 0 ? urlPortNum : fTunnelOverHTTPPortNum;
+ // Scan through the headers, handling the ones that we're interested in:
+ Boolean reachedEndOfHeaders;
+ unsigned cseq = 0;
+ unsigned contentLength = 0;
- if (fInputSocketNum < 0) {
- // We don't yet have a TCP socket. Set one up (blocking) now:
- fInputSocketNum = fOutputSocketNum
- = setupStreamSocket(envir(), 0, False /* =>blocking */);
- if (fInputSocketNum < 0) break;
-
- // Connect to the remote endpoint:
- fServerAddress = *(unsigned*)(destAddress.data());
- MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(destPortNum));
- fd_set set;
- FD_ZERO(&set);
- timeval tvout = {0,0};
-
- // If we were supplied with a timeout, make our socket temporarily non-blocking
- if (timeout > 0) {
- FD_SET((unsigned)fInputSocketNum, &set);
- tvout.tv_sec = timeout;
- tvout.tv_usec = 0;
- makeSocketNonBlocking(fInputSocketNum);
- }
- if (connect(fInputSocketNum, (struct sockaddr*) &remoteName, sizeof remoteName) != 0) {
- if (envir().getErrno() != EINPROGRESS && envir().getErrno() != EWOULDBLOCK) {
- envir().setResultErrMsg("connect() failed: ");
+ while (1) {
+ reachedEndOfHeaders = True; // by default; may get changed below
+ lineStart = nextLineStart;
+ if (lineStart == NULL) break;
+
+ nextLineStart = getLine(lineStart);
+ if (lineStart[0] == '\0') break; // this is a blank line
+ reachedEndOfHeaders = False;
+
+ char const* headerParamsStr;
+ if (checkForHeader(lineStart, "CSeq:", 5, headerParamsStr)) {
+ if (sscanf(headerParamsStr, "%u", &cseq) != 1 || cseq <= 0) {
+ envir().setResultMsg("Bad \"CSeq:\" header: \"", lineStart, "\"");
break;
}
- if (timeout > 0 && (select(fInputSocketNum + 1, NULL, &set, NULL, &tvout) <= 0)) {
- envir().setResultErrMsg("select/connect() failed: ");
+ // Find the handler function for "cseq":
+ RequestRecord* request;
+ while ((request = fRequestsAwaitingResponse.dequeue()) != NULL) {
+ if (request->cseq() < cseq) { // assumes that the CSeq counter will never wrap around
+ // We never received (and will never receive) a response for this handler, so delete it:
+ delete request;
+ } else if (request->cseq() == cseq) {
+ // This is the handler that we want. Remove its record, but remember it, so that we can later call its handler:
+ foundRequest = request;
+ break;
+ } else { // request->cseq() > cseq
+ // No handler was registered for this response, so ignore it.
+ break;
+ }
+ }
+ } else if (checkForHeader(lineStart, "Content-Length:", 15, headerParamsStr)) {
+ if (sscanf(headerParamsStr, "%u", &contentLength) != 1) {
+ envir().setResultMsg("Bad \"Content-Length:\" header: \"", lineStart, "\"");
break;
}
+ } else if (checkForHeader(lineStart, "Content-Base:", 13, headerParamsStr)) {
+ setBaseURL(headerParamsStr);
+ } else if (checkForHeader(lineStart, "Session:", 8, sessionParamsStr)) {
+ } else if (checkForHeader(lineStart, "Transport:", 10, transportParamsStr)) {
+ } else if (checkForHeader(lineStart, "Scale:", 6, scaleParamsStr)) {
+ } else if (checkForHeader(lineStart, "Range:", 6, rangeParamsStr)) {
+ } else if (checkForHeader(lineStart, "RTP-Info:", 9, rtpInfoParamsStr)) {
+ } else if (checkForHeader(lineStart, "WWW-Authenticate:", 17, headerParamsStr)) {
+ // If we've already seen a "WWW-Authenticate:" header, then we replace it with this new one only if
+ // the new one specifies "Digest" authentication:
+ if (wwwAuthenticateParamsStr == NULL || _strncasecmp(headerParamsStr, "Digest", 6) == 0) {
+ wwwAuthenticateParamsStr = headerParamsStr;
+ }
+ } else if (checkForHeader(lineStart, "Public:", 7, publicParamsStr)) {
+ } else if (checkForHeader(lineStart, "Allow:", 6, publicParamsStr)) {
+ // Note: we accept "Allow:" instead of "Public:", so that "OPTIONS" requests made to HTTP servers will work.
+ } else if (checkForHeader(lineStart, "Location:", 9, headerParamsStr)) {
+ setBaseURL(headerParamsStr);
}
- // If we set our socket to non-blocking, put it back in blocking mode now.
- if (timeout > 0) {
- makeSocketBlocking(fInputSocketNum);
+ // For now, omit parsing the "Server:" header (unless someone convinces us that we still need to treat Windows Media Server especially
+ }
+ if (!reachedEndOfHeaders) break; // an error occurred
+
+ if (foundRequest == NULL && responseIsHTTP) {
+ // Hack: HTTP responses don't have a "CSeq:" header, so if we got a HTTP response, assume it's for our most recent request:
+ foundRequest = fRequestsAwaitingResponse.dequeue();
+ }
+
+ // If we saw a "Content-Length:" header, then make sure that we have the amount of data that it specified:
+ unsigned bodyOffset = nextLineStart - headerDataCopy;
+ bodyStart = &fResponseBuffer[bodyOffset];
+ numBodyBytes = fResponseBytesAlreadySeen - bodyOffset;
+ if (contentLength > numBodyBytes) {
+ // We need to read more data. First, make sure we have enough space for it:
+ unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
+ unsigned remainingBufferSize = responseBufferSize - fResponseBytesAlreadySeen;
+ if (numExtraBytesNeeded > remainingBufferSize) {
+ char tmpBuf[200];
+ sprintf(tmpBuf, "Response buffer size (%d) is too small for \"Content-length:\" %d (need a buffer size of >= %d bytes\n",
+ responseBufferSize, contentLength, fResponseBytesAlreadySeen + numExtraBytesNeeded);
+ envir().setResultMsg(tmpBuf);
+ break;
}
-
- if (fTunnelOverHTTPPortNum != 0 && !setupHTTPTunneling(urlSuffix, authenticator)) break;
- }
-
- return True;
- } while (0);
-
- fDescribeStatusCode = 1;
- resetTCPSockets();
- return False;
-}
-
-Boolean RTSPClient::parseRTSPURL(UsageEnvironment& env, char const* url,
- NetAddress& address,
- portNumBits& portNum,
- char const** urlSuffix) {
- do {
- // Parse the URL as "rtsp://<address>:<port>/<etc>"
- // (with ":<port>" and "/<etc>" optional)
- // Also, skip over any "<username>[:<password>]@" preceding <address>
- char const* prefix = "rtsp://";
- unsigned const prefixLength = 7;
- if (_strncasecmp(url, prefix, prefixLength) != 0) {
- env.setResultMsg("URL is not of the form \"", prefix, "\"");
- break;
- }
- unsigned const parseBufferSize = 100;
- char parseBuffer[parseBufferSize];
- char const* from = &url[prefixLength];
-
- // Skip over any "<username>[:<password>]@"
- // (Note that this code fails if <password> contains '@' or '/', but
- // given that these characters can also appear in <etc>, there seems to
- // be no way of unambiguously parsing that situation.)
- char const* from1 = from;
- while (*from1 != '\0' && *from1 != '/') {
- if (*from1 == '@') {
- from = ++from1;
- break;
+ if (fVerbosityLevel >= 1) {
+ envir() << "Have received " << fResponseBytesAlreadySeen << " total bytes of a "
+ << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)")
+ << " RTSP response; awaiting " << numExtraBytesNeeded << " bytes more.\n";
}
- ++from1;
+ delete[] headerDataCopy;
+ if (foundRequest != NULL) fRequestsAwaitingResponse.putAtHead(foundRequest); // put back our request record; we need it again
+ return; // We need to read more data
}
- char* to = &parseBuffer[0];
- unsigned i;
- for (i = 0; i < parseBufferSize; ++i) {
- if (*from == '\0' || *from == ':' || *from == '/') {
- // We've completed parsing the address
- *to = '\0';
- break;
+ // We now have a complete response (including all bytes specified by the "Content-Length:" header, if any).
+ if (fVerbosityLevel >= 1) {
+ envir() << "Received a complete "
+ << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)")
+ << " response:\n" << fResponseBuffer << "\n";
+ }
+
+ if (foundRequest != NULL) {
+ Boolean needToResendCommand = False; // by default...
+ if (responseCode == 200) {
+ // Do special-case response handling for some commands:
+ if (strcmp(foundRequest->commandName(), "SETUP") == 0) {
+ if (!handleSETUPResponse(*foundRequest->subsession(), sessionParamsStr, transportParamsStr, foundRequest->booleanFlags()&0x1)) break;
+ } else if (strcmp(foundRequest->commandName(), "PLAY") == 0) {
+ if (!handlePLAYResponse(*foundRequest->session(), *foundRequest->subsession(), scaleParamsStr, rangeParamsStr, rtpInfoParamsStr)) break;
+ } else if (strcmp(foundRequest->commandName(), "TEARDOWN") == 0) {
+ if (!handleTEARDOWNResponse(*foundRequest->session(), *foundRequest->subsession())) break;
+ } else if (strcmp(foundRequest->commandName(), "GET_PARAMETER") == 0) {
+ if (!handleGET_PARAMETERResponse(foundRequest->contentStr(), bodyStart)) break;
+ }
+ } else if (responseCode == 401 && handleAuthenticationFailure(wwwAuthenticateParamsStr)) {
+ needToResendCommand = True;
+ } else if (responseCode == 301 || responseCode == 302) { // redirection
+ resetTCPSockets(); // because we need to connect somewhere else next
+ needToResendCommand = True;
}
- *to++ = *from++;
- }
- if (i == parseBufferSize) {
- env.setResultMsg("URL is too long");
- break;
- }
- NetAddressList addresses(parseBuffer);
- if (addresses.numAddresses() == 0) {
- env.setResultMsg("Failed to find network address for \"",
- parseBuffer, "\"");
- break;
- }
- address = *(addresses.firstAddress());
-
- portNum = 554; // default value
- char nextChar = *from;
- if (nextChar == ':') {
- int portNumInt;
- if (sscanf(++from, "%d", &portNumInt) != 1) {
- env.setResultMsg("No port number follows ':'");
- break;
+ if (needToResendCommand) {
+ resetResponseBuffer();
+ if (!resendCommand(foundRequest)) break;
+ delete[] headerDataCopy;
+ return; // without calling our response handler; the response to the resent command will do that
}
- if (portNumInt < 1 || portNumInt > 65535) {
- env.setResultMsg("Bad port number");
- break;
- }
- portNum = (portNumBits)portNumInt;
- while (*from >= '0' && *from <= '9') ++from; // skip over port number
}
- // The remainder of the URL is the suffix:
- if (urlSuffix != NULL) *urlSuffix = from;
-
- return True;
+ responseSuccess = True;
} while (0);
- return False;
-}
-
-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 we have a handler function for this response, call it:
+ resetResponseBuffer(); // in preparation for our next response. Do this now, in case the handler function goes to the event loop.
+ if (foundRequest != NULL && foundRequest->handler() != NULL) {
+ int resultCode;
+ char* resultString;
+ if (responseSuccess) {
+ if (responseCode == 200) {
+ resultCode = 0;
+ resultString = numBodyBytes != 0 ? strDup(bodyStart) : strDup(publicParamsStr);
+ // Note: The "strDup(bodyStart)" call assumes that the body is encoded without interior '\0' bytes
+ } else {
+ resultCode = responseCode;
+ resultString = strDup(responseStr);
+ envir().setResultMsg(responseStr);
}
- }
- if (atIndex == 0) break; // no '@' found
-
- char* urlCopy = strDup(url);
- urlCopy[atIndex] = '\0';
- if (colonIndex > 0) {
- urlCopy[colonIndex] = '\0';
- password = strDup(&urlCopy[colonIndex+1]);
+ (*foundRequest->handler())(this, resultCode, resultString);
} else {
- password = strDup("");
+ // An error occurred parsing the response, so call the handler, indicating an error:
+ handleRequestError(foundRequest);
}
- username = strDup(&urlCopy[usernameIndex]);
- delete[] urlCopy;
+ }
+ delete foundRequest;
+ delete[] headerDataCopy;
+}
- return True;
- } while (0);
- return False;
+////////// RTSPClient::RequestRecord implementation //////////
+
+RTSPClient::RequestRecord::RequestRecord(unsigned cseq, char const* commandName, responseHandler* handler,
+ MediaSession* session, MediaSubsession* subsession, u_int32_t booleanFlags,
+ double start, double end, float scale, char const* contentStr)
+ : fNext(NULL), fCSeq(cseq), fCommandName(commandName), fSession(session), fSubsession(subsession), fBooleanFlags(booleanFlags),
+ fStart(start), fEnd(end), fScale(scale), fContentStr(strDup(contentStr)), fHandler(handler) {
}
-char*
-RTSPClient::createAuthenticatorString(Authenticator const* authenticator,
- char const* cmd, char const* url) {
- if (authenticator != NULL && authenticator->realm() != NULL
- && authenticator->username() != NULL && authenticator->password() != NULL) {
- // We've been provided a filled-in authenticator, so use it:
- char* authenticatorStr;
- if (authenticator->nonce() != NULL) { // Digest authentication
- char const* const authFmt =
- "Authorization: Digest username=\"%s\", realm=\"%s\", "
- "nonce=\"%s\", uri=\"%s\", response=\"%s\"\r\n";
- char const* response = authenticator->computeDigestResponse(cmd, url);
- unsigned authBufSize = strlen(authFmt)
- + strlen(authenticator->username()) + strlen(authenticator->realm())
- + strlen(authenticator->nonce()) + strlen(url) + strlen(response);
- authenticatorStr = new char[authBufSize];
- sprintf(authenticatorStr, authFmt,
- authenticator->username(), authenticator->realm(),
- authenticator->nonce(), url, response);
- authenticator->reclaimDigestResponse(response);
- } else { // Basic authentication
- char const* const authFmt = "Authorization: Basic %s\r\n";
+RTSPClient::RequestRecord::~RequestRecord() {
+ // Delete the rest of the list first:
+ delete fNext;
- unsigned usernamePasswordLength = strlen(authenticator->username()) + 1 + strlen(authenticator->password());
- char* usernamePassword = new char[usernamePasswordLength+1];
- sprintf(usernamePassword, "%s:%s", authenticator->username(), authenticator->password());
+ delete[] fContentStr;
+}
- char* response = base64Encode(usernamePassword, usernamePasswordLength);
- unsigned const authBufSize = strlen(authFmt) + strlen(response) + 1;
- authenticatorStr = new char[authBufSize];
- sprintf(authenticatorStr, authFmt, response);
- delete[] response; delete[] usernamePassword;
- }
- return authenticatorStr;
- }
+////////// RTSPClient::RequestQueue implementation //////////
- return strDup("");
+RTSPClient::RequestQueue::RequestQueue()
+ : fHead(NULL), fTail(NULL) {
}
-void RTSPClient::checkForAuthenticationFailure(unsigned responseCode,
- char*& nextLineStart,
- Authenticator* authenticator) {
- if (responseCode == 401 && authenticator != NULL) {
- // We have an authentication failure, so fill in "authenticator"
- // using the contents of a following "WWW-Authenticate:" line.
- // (Once we compute a 'response' for "authenticator", it can be
- // used in a subsequent request - that will hopefully succeed.)
- char* lineStart;
- while (1) {
- lineStart = nextLineStart;
- if (lineStart == NULL) break;
-
- nextLineStart = getLine(lineStart);
- if (lineStart[0] == '\0') break; // this is a blank line
+RTSPClient::RequestQueue::~RequestQueue() {
+ delete fHead;
+}
- char* realm = strDupSize(lineStart);
- char* nonce = strDupSize(lineStart);
- Boolean foundAuthenticateHeader = False;
- if (sscanf(lineStart, "WWW-Authenticate: Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"",
- realm, nonce) == 2) {
- authenticator->setRealmAndNonce(realm, nonce);
- foundAuthenticateHeader = True;
- } else if (sscanf(lineStart, "WWW-Authenticate: Basic realm=\"%[^\"]\"",
- realm) == 1) {
- authenticator->setRealmAndNonce(realm, NULL); // Basic authentication
- foundAuthenticateHeader = True;
- }
- delete[] realm; delete[] nonce;
- if (foundAuthenticateHeader) break;
- }
+void RTSPClient::RequestQueue::enqueue(RequestRecord* request) {
+ if (fTail == NULL) {
+ fHead = request;
+ } else {
+ fTail->next() = request;
}
+ fTail = request;
}
-Boolean RTSPClient::sendRequest(char const* requestString, char const* tag,
- Boolean base64EncodeIfOverHTTP) {
- if (fVerbosityLevel >= 1) {
- envir() << "Sending request: " << requestString << "\n";
+RTSPClient::RequestRecord* RTSPClient::RequestQueue::dequeue() {
+ RequestRecord* request = fHead;
+ if (fHead == fTail) {
+ fHead = NULL;
+ fTail = NULL;
+ } else {
+ fHead = fHead->next();
}
+ if (request != NULL) request->next() = NULL;
+ return request;
+}
- char* newRequestString = NULL;
- if (fTunnelOverHTTPPortNum != 0 && base64EncodeIfOverHTTP) {
- requestString = newRequestString = base64Encode(requestString, strlen(requestString));
- if (fVerbosityLevel >= 1) {
- envir() << "\tThe request was base-64 encoded to: " << requestString << "\n\n";
- }
+void RTSPClient::RequestQueue::putAtHead(RequestRecord* request) {
+ request->next() = fHead;
+ fHead = request;
+ if (fTail == NULL) {
+ fTail = request;
}
+}
- Boolean result
- = send(fOutputSocketNum, requestString, strlen(requestString), 0) >= 0;
- delete[] newRequestString;
-
- if (!result) {
- if (tag == NULL) tag = "";
- char const* errFmt = "%s send() failed: ";
- unsigned const errLength = strlen(errFmt) + strlen(tag);
- char* err = new char[errLength];
- sprintf(err, errFmt, tag);
- envir().setResultErrMsg(err);
- delete[] err;
+RTSPClient::RequestRecord* RTSPClient::RequestQueue::findByCSeq(unsigned cseq) {
+ RequestRecord* request;
+ for (request = fHead; request != NULL; request = request->next()) {
+ if (request->cseq() == cseq) return request;
}
- return result;
+ return NULL;
}
-Boolean RTSPClient::getResponse(char const* tag,
- unsigned& bytesRead, unsigned& responseCode,
- char*& firstLine, char*& nextLineStart,
- Boolean checkFor200Response) {
- do {
- char* readBuf = fResponseBuffer;
- bytesRead = getResponse1(readBuf, fResponseBufferSize);
- if (bytesRead == 0) {
- envir().setResultErrMsg("Failed to read response: ");
- break;
- }
- if (fVerbosityLevel >= 1) {
- envir() << "Received " << tag << " response: " << readBuf << "\n";
- }
-
- firstLine = readBuf;
- nextLineStart = getLine(firstLine);
- if (!parseResponseCode(firstLine, responseCode)) break;
-
- if (responseCode != 200 && checkFor200Response) {
- envir().setResultMsg(tag, ": cannot handle response: ", firstLine);
- break;
- }
-
- return True;
- } while (0);
-
- // An error occurred:
- return False;
+#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);
}
-unsigned RTSPClient::getResponse1(char*& responseBuffer,
- unsigned responseBufferSize) {
- struct sockaddr_in fromAddress;
-
- if (responseBufferSize == 0) return 0; // just in case...
- responseBuffer[0] = '\0'; // ditto
-
- // Begin by reading and checking the first byte of the response.
- // If it's '$', then there's an interleaved RTP (or RTCP)-over-TCP
- // packet here. We need to read and discard it first.
- Boolean success = False;
- while (1) {
- unsigned char firstByte;
- struct timeval timeout;
- timeout.tv_sec = 30; timeout.tv_usec = 0;
- if (readSocket(envir(), fInputSocketNum, &firstByte, 1, fromAddress, &timeout)
- != 1) break;
- if (firstByte != '$') {
- // Normal case: This is the start of a regular response; use it:
- responseBuffer[0] = firstByte;
- success = True;
- break;
- } else {
- // This is an interleaved packet; read and discard it:
- unsigned char streamChannelId;
- if (readSocket(envir(), fInputSocketNum, &streamChannelId, 1, fromAddress)
- != 1) break;
-
- unsigned short size;
- if (readSocketExact(envir(), fInputSocketNum, (unsigned char*)&size, 2,
- fromAddress) != 2) break;
- size = ntohs(size);
- if (fVerbosityLevel >= 1) {
- envir() << "Discarding interleaved RTP or RTCP packet ("
- << size << " bytes, channel id "
- << streamChannelId << ")\n";
- }
-
- unsigned char* tmpBuffer = new unsigned char[size];
- if (tmpBuffer == NULL) break;
- unsigned bytesRead = 0;
- unsigned bytesToRead = size;
- int curBytesRead;
- while ((curBytesRead = readSocket(envir(), fInputSocketNum,
- &tmpBuffer[bytesRead], bytesToRead,
- fromAddress)) > 0) {
- bytesRead += curBytesRead;
- if (bytesRead >= size) break;
- bytesToRead -= curBytesRead;
- }
- delete[] tmpBuffer;
- if (bytesRead != size) break;
-
- success = True;
- }
- }
- if (!success) return 0;
-
- // Keep reading data from the socket until we see "\r\n\r\n" (except
- // at the start), or until we fill up our buffer.
- // Don't read any more than this.
- char* p = responseBuffer;
- Boolean haveSeenNonCRLF = False;
- int bytesRead = 1; // because we've already read the first byte
- while (bytesRead < (int)responseBufferSize) {
- int bytesReadNow
- = readSocket(envir(), fInputSocketNum,
- (unsigned char*)(responseBuffer+bytesRead),
- 1, fromAddress);
- if (bytesReadNow <= 0) {
- envir().setResultMsg("RTSP response was truncated");
- break;
- }
- bytesRead += bytesReadNow;
-
- // Check whether we have "\r\n\r\n" (or "\r\r" or "\n\n"):
- char* lastToCheck = responseBuffer+bytesRead-4;
- if (lastToCheck < responseBuffer) continue;
- for (; p <= lastToCheck; ++p) {
- if (haveSeenNonCRLF) {
- if ((*p == '\r' && *(p+1) == '\n' && *(p+2) == '\r' && *(p+3) == '\n')
- || (*(p+2) == '\r' && *(p+3) == '\r')
- || (*(p+2) == '\n' && *(p+3) == '\n')) {
- responseBuffer[bytesRead] = '\0';
-
- // Before returning, trim any \r or \n from the start:
- while (*responseBuffer == '\r' || *responseBuffer == '\n') {
- ++responseBuffer;
- --bytesRead;
- }
- return bytesRead;
- }
- } else {
- if (*p != '\r' && *p != '\n') {
- haveSeenNonCRLF = True;
- }
- }
- }
+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;
}
- envir().setResultMsg("We received a response not ending with <CR><LF><CR><LF>");
- return 0;
-}
-
-Boolean RTSPClient::parseResponseCode(char const* line,
- unsigned& responseCode) {
- if (sscanf(line, "%*s%u", &responseCode) != 1) {
- envir().setResultMsg("no response code in line: \"", line, "\"");
- return False;
+ 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);
- return True;
+ // 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;
}
-Boolean RTSPClient::parseTransportResponse(char const* line,
- char*& serverAddressStr,
- portNumBits& serverPortNum,
- unsigned char& rtpChannelId,
- unsigned char& rtcpChannelId) {
- // Initialize the return parameters to 'not found' values:
- serverAddressStr = NULL;
- serverPortNum = 0;
- rtpChannelId = rtcpChannelId = 0xFF;
-
- char* foundServerAddressStr = NULL;
- Boolean foundServerPortNum = False;
- Boolean foundChannelIds = False;
- unsigned rtpCid, rtcpCid;
- Boolean isMulticast = True; // by default
- char* foundDestinationStr = NULL;
- portNumBits multicastPortNumRTP, multicastPortNumRTCP;
- Boolean foundMulticastPortNum = False;
+char* RTSPClient::describeWithPassword(char const* url,
+ char const* username, char const* password,
+ Boolean allowKasennaProtocol, int timeout) {
+ Authenticator authenticator;
+ authenticator.setUsernameAndPassword(username, password);
+ return describeURL(url, &authenticator, allowKasennaProtocol, timeout);
+}
- // First, check for "Transport:"
- if (_strncasecmp(line, "Transport: ", 11) != 0) return False;
- line += 11;
+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;
+ newAuthenticator.setUsernameAndPassword(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;
+ haveAllocatedAuthenticator = True;
+ authenticator->setUsernameAndPassword(username, password);
+
+ result = sendOptionsCmd(url, username, password, authenticator, timeout);
+ if (result != NULL) return result; // We are already authorized
- // Then, run through each of the fields, looking for ones we handle:
- char const* fields = line;
- char* field = strDupSize(fields);
- while (sscanf(fields, "%[^;]", field) == 1) {
- if (sscanf(field, "server_port=%hu", &serverPortNum) == 1) {
- foundServerPortNum = True;
- } else if (_strncasecmp(field, "source=", 7) == 0) {
- delete[] foundServerAddressStr;
- foundServerAddressStr = strDup(field+7);
- } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {
- rtpChannelId = (unsigned char)rtpCid;
- rtcpChannelId = (unsigned char)rtcpCid;
- foundChannelIds = True;
- } else if (strcmp(field, "unicast") == 0) {
- isMulticast = False;
- } else if (_strncasecmp(field, "destination=", 12) == 0) {
- delete[] foundDestinationStr;
- foundDestinationStr = strDup(field+12);
- } else if (sscanf(field, "port=%hu-%hu",
- &multicastPortNumRTP, &multicastPortNumRTCP) == 2) {
- foundMulticastPortNum = True;
+ // The "realm" field should have been filled in:
+ if (authenticator->realm() == NULL) {
+ // We haven't been given enough information to try again, so fail:
+ return NULL;
+ }
}
-
- fields += strlen(field);
- while (fields[0] == ';') ++fields; // skip over all leading ';' chars
- if (fields[0] == '\0') break;
}
- delete[] field;
- // If we're multicast, and have a "destination=" (multicast) address, then use this
- // as the 'server' address (because some weird servers don't specify the multicast
- // address earlier, in the "DESCRIBE" response's SDP:
- if (isMulticast && foundDestinationStr != NULL && foundMulticastPortNum) {
- delete[] foundServerAddressStr;
- serverAddressStr = foundDestinationStr;
- serverPortNum = multicastPortNumRTP;
- return True;
- }
- delete[] foundDestinationStr;
-
- if (foundServerPortNum || foundChannelIds) {
- serverAddressStr = foundServerAddressStr;
- return True;
+ 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);
- delete[] foundServerAddressStr;
- return False;
+ // 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;
}
-Boolean RTSPClient::parseRTPInfoHeader(char*& line, u_int16_t& seqNum, u_int32_t& timestamp) {
- // At this point in the parsing, "line" should begin with either "RTP-Info: " (for the start of the header),
- // or ",", indicating the RTP-Info parameter list for the 2nd-through-nth subsessions:
- if (_strncasecmp(line, "RTP-Info: ", 10) == 0) {
- line += 10;
- } else if (line[0] == ',') {
- ++line;
- } else {
- return False;
- }
-
- // "line" now consists of a ';'-separated list of parameters, ending with ',' or '\0'.
- char* field = strDupSize(line);
-
- while (sscanf(line, "%[^;,]", field) == 1) {
- if (sscanf(field, "seq=%hu", &seqNum) == 1 ||
- sscanf(field, "rtptime=%u", ×tamp) == 1) {
- }
-
- line += strlen(field);
- if (line[0] == '\0' || line[0] == ',') break;
- // ASSERT: line[0] == ';'
- ++line; // skip over the ';'
+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);
- delete[] field;
- return True;
+ // Now block (but handling events) until we get a response (or a timeout):
+ envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+ delete[] fResultString;
+ return fResultCode == 0;
}
-Boolean RTSPClient::parseScaleHeader(char const* line, float& scale) {
- if (_strncasecmp(line, "Scale: ", 7) != 0) return False;
- line += 7;
+Boolean RTSPClient
+::announceWithPassword(char const* url, char const* sdpDescription,
+ char const* username, char const* password, int timeout) {
+ Authenticator authenticator;
+ authenticator.setUsernameAndPassword(username, password);
+ return announceSDPDescription(url, sdpDescription, &authenticator, timeout);
+}
- Locale l("C", LC_NUMERIC);
- return sscanf(line, "%f", &scale) == 1;
+Boolean RTSPClient::setupMediaSubsession(MediaSubsession& subsession,
+ Boolean streamOutgoing,
+ Boolean streamUsingTCP,
+ Boolean forceMulticastOnUnspecified) {
+ fWatchVariableForSyncInterface = 0;
+ fTimeoutTask = NULL;
+ (void)sendSetupCommand(subsession, responseHandlerForSyncInterface, streamOutgoing, streamUsingTCP, forceMulticastOnUnspecified);
+
+ // Now block (but handling events) until we get a response (or a timeout):
+ envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+ delete[] fResultString;
+ return fResultCode == 0;
}
-Boolean RTSPClient::parseGetParameterHeader(char const* line,
- const char* param,
- char*& value) {
- if ((param != NULL && param[0] != '\0') &&
- (line != NULL && line[0] != '\0')) {
- int param_len = strlen(param);
- int line_len = strlen(line);
+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;
+}
- if (_strncasecmp(line, param, param_len) != 0) {
- if (fVerbosityLevel >= 1) {
- envir() << "Parsing for \""<< param << "\" and didn't find it, return False\n";
- }
- return False;
- }
+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;
+}
- // Strip \r\n from the end if it's there.
- if (line[line_len-2] == '\r' && line[line_len-1] == '\n') {
- line_len -= 2;
- }
+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;
+}
- // Look for ": " appended to our requested parameter
- if (line[param_len] == ':' && line[param_len+1] == ' ') {
- // But make sure ": " wasn't in our actual serach param before adjusting
- if (param[param_len-2] != ':' && param[param_len-1] != ' ') {
- if (fVerbosityLevel >= 1) {
- envir() << "Found \": \" appended to parameter\n";
- }
- param_len += 2;
- }
- }
+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;
+}
- // Get the string we want out of the line:
- value = strDup(line+param_len);
- return True;
- }
- return False;
+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::setupHTTPTunneling(char const* urlSuffix,
- Authenticator* authenticator) {
- // Set up RTSP-over-HTTP tunneling, as described in
- // http://developer.apple.com/documentation/QuickTime/QTSS/Concepts/chapter_2_section_14.html
- if (fVerbosityLevel >= 1) {
- envir() << "Requesting RTSP-over-HTTP tunneling (on port "
- << fTunnelOverHTTPPortNum << ")\n\n";
- }
- if (urlSuffix == NULL || urlSuffix[0] == '\0') urlSuffix = "/";
- char* cmd = NULL;
+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;
+}
- do {
- // Create a 'session cookie' string, using MD5:
- struct {
- struct timeval timestamp;
- unsigned counter;
- } seedData;
- gettimeofday(&seedData.timestamp, NULL);
- static unsigned counter = 0;
- seedData.counter = ++counter;
- char sessionCookie[33];
- our_MD5Data((unsigned char*)(&seedData), sizeof seedData, sessionCookie);
- // DSS seems to require that the 'session cookie' string be 22 bytes long:
- sessionCookie[23] = '\0';
-
- // Construct an authenticator string:
- char* authenticatorStr
- = createAuthenticatorString(authenticator, "GET", urlSuffix);
-
- // Begin by sending a HTTP "GET", to set up the server->client link:
- char const* const getCmdFmt =
- "GET %s HTTP/1.0\r\n"
- "%s"
- "%s"
- "x-sessioncookie: %s\r\n"
- "Accept: application/x-rtsp-tunnelled\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache\r\n"
- "\r\n";
- unsigned cmdSize = strlen(getCmdFmt)
- + strlen(urlSuffix)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize
- + strlen(sessionCookie);
- cmd = new char[cmdSize];
- sprintf(cmd, getCmdFmt,
- urlSuffix,
- authenticatorStr,
- fUserAgentHeaderStr,
- sessionCookie);
- delete[] authenticatorStr;
- if (!sendRequest(cmd, "HTTP GET", False/*don't base64-encode*/)) break;
-
- // Get the response from the server:
- unsigned bytesRead; unsigned responseCode;
- char* firstLine; char* nextLineStart;
- if (!getResponse("HTTP GET", bytesRead, responseCode, firstLine, nextLineStart,
- False /*don't check for response code 200*/)) break;
- if (responseCode != 200) {
- checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
- envir().setResultMsg("cannot handle HTTP GET response: ", firstLine);
- break;
- }
+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;
+}
- // Next, set up a second TCP connection (to the same server & port as before)
- // for the HTTP-tunneled client->server link. All future output will be to
- // this socket.
- fOutputSocketNum = setupStreamSocket(envir(), 0, False /* =>blocking */);
- if (fOutputSocketNum < 0) break;
+Boolean RTSPClient::teardownMediaSession(MediaSession& session) {
+ fWatchVariableForSyncInterface = 0;
+ fTimeoutTask = NULL;
+ (void)sendTeardownCommand(session, responseHandlerForSyncInterface);
+
+ // Now block (but handling events) until we get a response (or a timeout):
+ envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+ delete[] fResultString;
+ return fResultCode == 0;
+}
- // Connect to the remote endpoint:
- MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(fTunnelOverHTTPPortNum));
- if (connect(fOutputSocketNum,
- (struct sockaddr*)&remoteName, sizeof remoteName) != 0) {
- envir().setResultErrMsg("connect() failed: ");
- break;
- }
+Boolean RTSPClient::teardownMediaSubsession(MediaSubsession& subsession) {
+ fWatchVariableForSyncInterface = 0;
+ fTimeoutTask = NULL;
+ (void)sendTeardownCommand(subsession, responseHandlerForSyncInterface);
+
+ // Now block (but handling events) until we get a response (or a timeout):
+ envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
+ delete[] fResultString;
+ return fResultCode == 0;
+}
- // Then, send a HTTP "POST", to set up the client->server link:
- authenticatorStr = createAuthenticatorString(authenticator, "POST", urlSuffix);
- char const* const postCmdFmt =
- "POST %s HTTP/1.0\r\n"
- "%s"
- "%s"
- "x-sessioncookie: %s\r\n"
- "Content-Type: application/x-rtsp-tunnelled\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache\r\n"
- "Content-Length: 32767\r\n"
- "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n"
- "\r\n";
- cmdSize = strlen(postCmdFmt)
- + strlen(urlSuffix)
- + strlen(authenticatorStr)
- + fUserAgentHeaderStrSize
- + strlen(sessionCookie);
- delete[] cmd; cmd = new char[cmdSize];
- sprintf(cmd, postCmdFmt,
- urlSuffix,
- authenticatorStr,
- fUserAgentHeaderStr,
- sessionCookie);
- delete[] authenticatorStr;
- if (!sendRequest(cmd, "HTTP POST", False/*don't base64-encode*/)) break;
+void RTSPClient::responseHandlerForSyncInterface(RTSPClient* rtspClient, int responseCode, char* responseString) {
+ if (rtspClient != NULL) rtspClient->responseHandlerForSyncInterface1(responseCode, responseString);
+}
- // Note that there's no response to the "POST".
+void RTSPClient::responseHandlerForSyncInterface1(int responseCode, char* responseString) {
+ // If we have a 'timeout task' pending, then unschedule it:
+ if (fTimeoutTask != NULL) envir().taskScheduler().unscheduleDelayedTask(fTimeoutTask);
- delete[] cmd;
- return True;
- } while (0);
+ // Set result values:
+ fResultCode = responseCode;
+ fResultString = responseString;
- // An error occurred:
- delete[] cmd;
- return False;
+ // Signal a break from the event loop (thereby returning from the blocking command):
+ fWatchVariableForSyncInterface = ~0;
}
-void RTSPClient::incomingRequestHandler(void* instance, int /*mask*/) {
- RTSPClient* session = (RTSPClient*)instance;
- session->incomingRequestHandler1();
+void RTSPClient::timeoutHandlerForSyncInterface(void* rtspClient) {
+ if (rtspClient != NULL) ((RTSPClient*)rtspClient)->timeoutHandlerForSyncInterface1();
}
-void RTSPClient::incomingRequestHandler1() {
- unsigned bytesRead;
- char* readBuf = fResponseBuffer;
- bytesRead = getResponse1(readBuf, fResponseBufferSize);
- if (bytesRead == 0) {
- envir().setResultMsg("Failed to read response: Connection was closed by the remote host.");
- envir().taskScheduler().turnOffBackgroundReadHandling(fInputSocketNum); // because the connection died
- return;
- }
- // Parse the request string into command name and 'CSeq',
- // then handle the command:
- char cmdName[RTSP_PARAM_STRING_MAX];
- char urlPreSuffix[RTSP_PARAM_STRING_MAX];
- char urlSuffix[RTSP_PARAM_STRING_MAX];
- char cseq[RTSP_PARAM_STRING_MAX];
- if (!parseRTSPRequestString((char*)readBuf, bytesRead,
- cmdName, sizeof cmdName,
- urlPreSuffix, sizeof urlPreSuffix,
- urlSuffix, sizeof urlSuffix,
- cseq, sizeof cseq)) {
- return;
- } else {
- if (fVerbosityLevel >= 1) {
- envir() << "Received request: " << readBuf << "\n";
- }
- handleCmd_notSupported(cseq);
- }
-}
+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;
-void RTSPClient::handleCmd_notSupported(char const* cseq) {
- char tmpBuf[512];
- snprintf((char*)tmpBuf, sizeof tmpBuf,
- "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n\r\n", cseq);
- send(fOutputSocketNum, tmpBuf, strlen(tmpBuf), 0);
+ // 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
diff --git a/liveMedia/RTSPCommon.cpp b/liveMedia/RTSPCommon.cpp
index 3f5ab36..6e22c3d 100644
--- a/liveMedia/RTSPCommon.cpp
+++ b/liveMedia/RTSPCommon.cpp
@@ -134,28 +134,35 @@ Boolean parseRTSPRequestString(char const* reqStr,
return True;
}
-Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd) {
- // First, find "Range:"
- while (1) {
- if (*buf == '\0') return False; // not found
- if (_strncasecmp(buf, "Range: ", 7) == 0) break;
- ++buf;
- }
-
- // Then, run through each of the fields, looking for ones we handle:
- char const* fields = buf + 7;
- while (*fields == ' ') ++fields;
+Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeEnd) {
double start, end;
Locale l("C", LC_NUMERIC);
- if (sscanf(fields, "npt = %lf - %lf", &start, &end) == 2) {
+ if (sscanf(paramStr, "npt = %lf - %lf", &start, &end) == 2) {
rangeStart = start;
rangeEnd = end;
- } else if (sscanf(fields, "npt = %lf -", &start) == 1) {
+ } else if (sscanf(paramStr, "npt = %lf -", &start) == 1) {
rangeStart = start;
rangeEnd = 0.0;
+ } else if (strcmp(paramStr, "npt=now-") == 0) {
+ rangeStart = 0.0;
+ rangeEnd = 0.0;
} else {
return False; // The header is malformed
}
return True;
}
+
+Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd) {
+ // First, find "Range:"
+ while (1) {
+ if (*buf == '\0') return False; // not found
+ if (_strncasecmp(buf, "Range: ", 7) == 0) break;
+ ++buf;
+ }
+
+ // Then, run through each of the fields, looking for ones we handle:
+ char const* fields = buf + 7;
+ while (*fields == ' ') ++fields;
+ return parseRangeParam(fields, rangeStart, rangeEnd);
+}
diff --git a/liveMedia/RTSPServer.cpp b/liveMedia/RTSPServer.cpp
index 5cb2cc2..4dde484 100644
--- a/liveMedia/RTSPServer.cpp
+++ b/liveMedia/RTSPServer.cpp
@@ -318,7 +318,7 @@ void RTSPServer::RTSPClientSession::handleAlternativeRequestByte(void* instance,
void RTSPServer::RTSPClientSession::handleAlternativeRequestByte1(u_int8_t requestByte) {
// Add this character to our buffer; then try to handle the data that we have buffered so far:
- if (fRequestBufferBytesLeft == 0) return;
+ if (fRequestBufferBytesLeft == 0|| fRequestBytesAlreadySeen >= RTSP_BUFFER_SIZE) return;
fRequestBuffer[fRequestBytesAlreadySeen] = requestByte;
handleRequestBytes(1);
}
@@ -740,16 +740,28 @@ void RTSPServer::RTSPClientSession
delete[] clientsDestinationAddressStr;
Port serverRTPPort(0);
Port serverRTCPPort(0);
+
+ // Make sure that we transmit on the same interface that's used by the client (in case we're a multi-homed server):
+ struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;
+ getsockname(fClientSocket, (struct sockaddr*)&sourceAddr, &namelen);
+ netAddressBits origSendingInterfaceAddr = SendingInterfaceAddr;
+ netAddressBits origReceivingInterfaceAddr = ReceivingInterfaceAddr;
+ // NOTE: The following might not work properly, so we ifdef it out for now:
+#ifdef HACK_FOR_MULTIHOMED_SERVERS
+ ReceivingInterfaceAddr = SendingInterfaceAddr = sourceAddr.sin_addr.s_addr;
+#endif
+
subsession->getStreamParameters(fOurSessionId, fClientAddr.sin_addr.s_addr,
clientRTPPort, clientRTCPPort,
tcpSocketNum, rtpChannelId, rtcpChannelId,
destinationAddress, destinationTTL, fIsMulticast,
serverRTPPort, serverRTCPPort,
fStreamStates[streamNum].streamToken);
+ SendingInterfaceAddr = origSendingInterfaceAddr;
+ ReceivingInterfaceAddr = origReceivingInterfaceAddr;
+
struct in_addr destinationAddr; destinationAddr.s_addr = destinationAddress;
char* destAddrStr = strDup(our_inet_ntoa(destinationAddr));
- struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;
- getsockname(fClientSocket, (struct sockaddr*)&sourceAddr, &namelen);
char* sourceAddrStr = strDup(our_inet_ntoa(sourceAddr.sin_addr));
if (fIsMulticast) {
switch (streamingMode) {
diff --git a/liveMedia/StreamParser.cpp b/liveMedia/StreamParser.cpp
index b4f3ca2..711158e 100644
--- a/liveMedia/StreamParser.cpp
+++ b/liveMedia/StreamParser.cpp
@@ -79,7 +79,7 @@ void StreamParser::ensureValidBytes1(unsigned numBytesNeeded) {
<< fCurParserIndex << "+ "
<< numBytesNeeded << " > "
<< BANK_SIZE << ")\n";
- abort();
+ fInputSource->envir().internalError();
}
// Try to read as many new bytes as will fit in the current bank:
diff --git a/liveMedia/include/AMRAudioSource.hh b/liveMedia/include/AMRAudioSource.hh
index 738ffb0..a40168c 100644
--- a/liveMedia/include/AMRAudioSource.hh
+++ b/liveMedia/include/AMRAudioSource.hh
@@ -40,6 +40,7 @@ protected:
private:
// redefined virtual functions:
+ virtual char const* MIMEtype() const;
virtual Boolean isAMRAudioSource() const;
protected:
diff --git a/liveMedia/include/DarwinInjector.hh b/liveMedia/include/DarwinInjector.hh
index 556032f..d8e397b 100644
--- a/liveMedia/include/DarwinInjector.hh
+++ b/liveMedia/include/DarwinInjector.hh
@@ -86,6 +86,9 @@ private:
virtual ~DarwinInjector();
+ static void genericResponseHandler(RTSPClient* rtspClient, int responseCode, char* responseString);
+ void genericResponseHandler1(int responseCode, char* responseString);
+
private:
char const* fApplicationName;
int fVerbosityLevel;
@@ -95,6 +98,9 @@ private:
SubstreamDescriptor* fTailSubstream;
MediaSession* fSession;
unsigned fLastTrackId;
+ char fWatchVariable;
+ int fResultCode;
+ char* fResultString;
};
#endif
diff --git a/liveMedia/include/FileSink.hh b/liveMedia/include/FileSink.hh
index 99cc406..a0cdfcb 100644
--- a/liveMedia/include/FileSink.hh
+++ b/liveMedia/include/FileSink.hh
@@ -37,7 +37,7 @@ public:
// file name suffix). The default behavior ("oneFilePerFrame" == False)
// is to output all incoming data into a single file.
- void addData(unsigned char* data, unsigned dataSize,
+ void addData(unsigned char const* data, unsigned dataSize,
struct timeval presentationTime);
// (Available in case a client wants to add extra data to the output file)
diff --git a/liveMedia/include/H264VideoFileSink.hh b/liveMedia/include/H264VideoFileSink.hh
index e63a604..f172657 100644
--- a/liveMedia/include/H264VideoFileSink.hh
+++ b/liveMedia/include/H264VideoFileSink.hh
@@ -28,20 +28,25 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
class H264VideoFileSink: public FileSink {
public:
static H264VideoFileSink* createNew(UsageEnvironment& env, char const* fileName,
- unsigned bufferSize = 10000,
- Boolean oneFilePerFrame = False);
- // (See "FileSink.hh" for a description of these parameters.)
+ char const* sPropParameterSetsStr = NULL,
+ // An optional 'SDP format' string (comma-separated Base64-encoded) representing SPS and/or PPS NAL-units to prepend to the output
+ unsigned bufferSize = 10000,
+ Boolean oneFilePerFrame = False);
+ // See "FileSink.hh" for a description of these parameters.
protected:
- H264VideoFileSink(UsageEnvironment& env, FILE* fid, unsigned bufferSize,
- char const* perFrameFileNamePrefix);
+ H264VideoFileSink(UsageEnvironment& env, FILE* fid,
+ char const* sPropParameterSetsStr,
+ unsigned bufferSize, char const* perFrameFileNamePrefix);
// called only by createNew()
virtual ~H264VideoFileSink();
protected: // redefined virtual functions:
- virtual Boolean sourceIsCompatibleWithUs(MediaSource& source);
- virtual void afterGettingFrame1(unsigned frameSize,
- struct timeval presentationTime);
+ virtual void afterGettingFrame1(unsigned frameSize, struct timeval presentationTime);
+
+private:
+ char const* fSPropParameterSetsStr;
+ Boolean fHaveWrittenFirstFrame;
};
#endif
diff --git a/liveMedia/include/Media.hh b/liveMedia/include/Media.hh
index a80b720..a5cdc3a 100644
--- a/liveMedia/include/Media.hh
+++ b/liveMedia/include/Media.hh
@@ -88,7 +88,7 @@ private:
// The structure pointed to by the "liveMediaPriv" UsageEnvironment field:
class _Tables {
public:
- static _Tables* getOurTables(UsageEnvironment& env);
+ static _Tables* getOurTables(UsageEnvironment& env, Boolean createIfNotPresent = True);
// returns a pointer to an "ourTables" structure (creating it if necessary)
void reclaimIfPossible();
// used to delete ourselves when we're no longer used
diff --git a/liveMedia/include/MediaSession.hh b/liveMedia/include/MediaSession.hh
index 5325713..ef2caa2 100644
--- a/liveMedia/include/MediaSession.hh
+++ b/liveMedia/include/MediaSession.hh
@@ -218,6 +218,7 @@ public:
// (e.g., by a "RTP-Info:" header in a RTSP response).
// Also, for this function to work properly, the RTP stream's presentation times must (eventually) be
// synchronized via RTCP.
+ // (Note: If this function returns a negative number, then the result should be ignored by the caller.)
protected:
friend class MediaSession;
diff --git a/liveMedia/include/MediaSink.hh b/liveMedia/include/MediaSink.hh
index 763a428..01686e6 100644
--- a/liveMedia/include/MediaSink.hh
+++ b/liveMedia/include/MediaSink.hh
@@ -85,11 +85,11 @@ public:
void increment(unsigned numBytes) {fCurOffset += numBytes;}
void enqueue(unsigned char const* from, unsigned numBytes);
- void enqueueWord(unsigned word);
+ void enqueueWord(u_int32_t word);
void insert(unsigned char const* from, unsigned numBytes, unsigned toPosition);
- void insertWord(unsigned word, unsigned toPosition);
+ void insertWord(u_int32_t word, unsigned toPosition);
void extract(unsigned char* to, unsigned numBytes, unsigned fromPosition);
- unsigned extractWord(unsigned fromPosition);
+ u_int32_t extractWord(unsigned fromPosition);
void skipBytes(unsigned numBytes);
diff --git a/liveMedia/include/MultiFramedRTPSource.hh b/liveMedia/include/MultiFramedRTPSource.hh
index 13cfcad..494deae 100644
--- a/liveMedia/include/MultiFramedRTPSource.hh
+++ b/liveMedia/include/MultiFramedRTPSource.hh
@@ -65,9 +65,10 @@ private:
void doGetNextFrame1();
static void networkReadHandler(MultiFramedRTPSource* source, int /*mask*/);
- friend void networkReadHandler(MultiFramedRTPSource*, int);
+ void networkReadHandler1();
Boolean fAreDoingNetworkReads;
+ BufferedPacket* fPacketReadInProgress;
Boolean fNeedDelivery;
Boolean fPacketLossInFragmentedFrame;
unsigned char* fSavedTo;
@@ -90,7 +91,7 @@ public:
Boolean hasUsableData() const { return fTail > fHead; }
unsigned useCount() const { return fUseCount; }
- Boolean fillInData(RTPInterface& rtpInterface);
+ Boolean fillInData(RTPInterface& rtpInterface, Boolean& packetReadWasIncomplete);
void assignMiscParams(unsigned short rtpSeqNo, unsigned rtpTimestamp,
struct timeval presentationTime,
Boolean hasBeenSyncedUsingRTCP,
diff --git a/liveMedia/include/RTCP.hh b/liveMedia/include/RTCP.hh
index ee7d549..074180a 100644
--- a/liveMedia/include/RTCP.hh
+++ b/liveMedia/include/RTCP.hh
@@ -131,6 +131,7 @@ private:
private:
unsigned char* fInBuf;
+ unsigned fNumBytesAlreadyRead;
OutPacketBuffer* fOutBuf;
RTPInterface fRTCPInterface;
unsigned fTotSessionBW;
diff --git a/liveMedia/include/RTPInterface.hh b/liveMedia/include/RTPInterface.hh
index ddbd030..4ab22a3 100644
--- a/liveMedia/include/RTPInterface.hh
+++ b/liveMedia/include/RTPInterface.hh
@@ -62,14 +62,13 @@ public:
void setStreamSocket(int sockNum, unsigned char streamChannelId);
void addStreamSocket(int sockNum, unsigned char streamChannelId);
void removeStreamSocket(int sockNum, unsigned char streamChannelId);
- void setServerRequestAlternativeByteHandler(ServerRequestAlternativeByteHandler* handler, void* clientData);
+ void setServerRequestAlternativeByteHandler(int socketNum, ServerRequestAlternativeByteHandler* handler, void* clientData);
void sendPacket(unsigned char* packet, unsigned packetSize);
void startNetworkReading(TaskScheduler::BackgroundHandlerProc*
handlerProc);
Boolean handleRead(unsigned char* buffer, unsigned bufferMaxSize,
- unsigned& bytesRead,
- struct sockaddr_in& fromAddress);
+ unsigned& bytesRead, struct sockaddr_in& fromAddress, Boolean& packetReadWasIncomplete);
void stopNetworkReading();
UsageEnvironment& envir() const { return fOwner->envir(); }
diff --git a/liveMedia/include/RTPSink.hh b/liveMedia/include/RTPSink.hh
index b8eeff3..3f75be5 100644
--- a/liveMedia/include/RTPSink.hh
+++ b/liveMedia/include/RTPSink.hh
@@ -80,8 +80,8 @@ public:
void removeStreamSocket(int sockNum, unsigned char streamChannelId) {
fRTPInterface.removeStreamSocket(sockNum, streamChannelId);
}
- void setServerRequestAlternativeByteHandler(ServerRequestAlternativeByteHandler* handler, void* clientData) {
- fRTPInterface.setServerRequestAlternativeByteHandler(handler, clientData);
+ void setServerRequestAlternativeByteHandler(int socketNum, ServerRequestAlternativeByteHandler* handler, void* clientData) {
+ fRTPInterface.setServerRequestAlternativeByteHandler(socketNum, handler, clientData);
}
// hacks to allow sending RTP over TCP (RFC 2236, section 10.12)
diff --git a/liveMedia/include/RTPSource.hh b/liveMedia/include/RTPSource.hh
index d0fc9b1..10be0f5 100644
--- a/liveMedia/include/RTPSource.hh
+++ b/liveMedia/include/RTPSource.hh
@@ -63,6 +63,9 @@ public:
// hack to allow sending RTP over TCP (RFC 2236, section 10.12)
fRTPInterface.setStreamSocket(sockNum, streamChannelId);
}
+ void setServerRequestAlternativeByteHandler(int socketNum, ServerRequestAlternativeByteHandler* handler, void* clientData) {
+ fRTPInterface.setServerRequestAlternativeByteHandler(socketNum, handler, clientData);
+ }
void setAuxilliaryReadHandler(AuxHandlerFunc* handlerFunc,
void* handlerClientData) {
@@ -175,7 +178,7 @@ public:
double totNumKBytesReceived() const;
unsigned totNumPacketsExpected() const {
- return fHighestExtSeqNumReceived - fBaseExtSeqNumReceived;
+ return (fHighestExtSeqNumReceived - fBaseExtSeqNumReceived) + 1;
}
unsigned baseExtSeqNumReceived() const { return fBaseExtSeqNumReceived; }
diff --git a/liveMedia/include/RTSPClient.hh b/liveMedia/include/RTSPClient.hh
index 8dacaf5..2b685fe 100644
--- a/liveMedia/include/RTSPClient.hh
+++ b/liveMedia/include/RTSPClient.hh
@@ -15,7 +15,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
**********/
// "liveMedia"
// Copyright (c) 1996-2010 Live Networks, Inc. All rights reserved.
-// A generic RTSP client
+// A generic RTSP client - for a single "rtsp://" URL
// C++ header
#ifndef _RTSP_CLIENT_HH
@@ -31,9 +31,11 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#include "DigestAuthentication.hh"
#endif
+#define RTSPCLIENT_SYNCHRONOUS_INTERFACE 1 // For now, continue to support the old synchronous interface as well
+
class RTSPClient: public Medium {
public:
- static RTSPClient* createNew(UsageEnvironment& env,
+ static RTSPClient* createNew(UsageEnvironment& env, char const* rtspURL,
int verbosityLevel = 0,
char const* applicationName = NULL,
portNumBits tunnelOverHTTPPortNum = 0);
@@ -41,170 +43,229 @@ public:
// 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>
- int socketNum() const { return fInputSocketNum; }
+ typedef void (responseHandler)(RTSPClient* rtspClient,
+ int resultCode, char* resultString);
+ // A function that is called in response to a RTSP command. The parameters are as follows:
+ // "rtspClient": The "RTSPClient" object on which the original command was issued.
+ // "resultCode": If zero, then the command completed successfully. If non-zero, then the command did not complete
+ // successfully, and "resultCode" indicates the error, as follows:
+ // A positive "resultCode" is a RTSP error code (for example, 404 means "not found")
+ // A negative "resultCode" indicates a socket/network error; 0-"resultCode" is the standard "errno" code.
+ // "resultString": A ('\0'-terminated) string returned along with the response, or else NULL.
+ // In particular:
+ // "resultString" for a successful "DESCRIBE" command will be the media session's SDP description.
+ // "resultString" for a successful "OPTIONS" command will be a list of allowed commands.
+ // Note that this string can be present (i.e., not NULL) even if "resultCode" is non-zero - i.e., an error message.
+ // Note also that this string is dynamically allocated, and must be freed by the handler (or the caller)
+ // - using "delete[]".
- static Boolean lookupByName(UsageEnvironment& env,
- char const* sourceName,
- RTSPClient*& resultClient);
+ unsigned sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator = NULL);
+ // Issues a RTSP "DESCRIBE" command, then returns the "CSeq" sequence number that was used in the command.
+ // The (programmer-supplied) "responseHandler" function is called later to handle the response
+ // (or is called immediately - with an error code - if the command cannot be sent).
+ // "authenticator" (optional) is used for access control. If you have username and password strings, you can use this by
+ // passing an actual parameter that you created by creating an "Authenticator(username, password) object".
+ // (Note that if you supply a non-NULL "authenticator" parameter, you need do this only for the first command you send.)
- char* describeURL(char const* url, Authenticator* authenticator = NULL,
- Boolean allowKasennaProtocol = False, int timeout = -1);
- // Issues a RTSP "DESCRIBE" command
- // Returns the SDP description of a session, or NULL if none
- // (This is dynamically allocated, and must later be freed
- // by the caller - using "delete[]")
- char* describeWithPassword(char const* url,
- char const* username, char const* password,
- Boolean allowKasennaProtocol = False,
- int timeout = -1);
- // Uses "describeURL()" to do a "DESCRIBE" - first
- // without using "password", then (if we get an Unauthorized
- // response) with an authentication response computed from "password"
-
- Boolean announceSDPDescription(char const* url,
- char const* sdpDescription,
- Authenticator* authenticator = NULL,
- int timeout = -1);
- // Issues a RTSP "ANNOUNCE" command
- // Returns True iff this command succeeds
- Boolean announceWithPassword(char const* url, char const* sdpDescription,
- char const* username, char const* password, int timeout = -1);
- // Uses "announceSDPDescription()" to do an "ANNOUNCE" - first
- // without using "password", then (if we get an Unauthorized
- // response) with an authentication response computed from "password"
+ unsigned sendOptionsCommand(responseHandler* responseHandler, Authenticator* authenticator = NULL);
+ // Issues a RTSP "OPTIONS" command, then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
- char* sendOptionsCmd(char const* url,
- char* username = NULL, char* password = NULL,
- Authenticator* authenticator = NULL,
- int timeout = -1);
- // Issues a RTSP "OPTIONS" command
- // Returns a string containing the list of options, or NULL
+ unsigned sendAnnounceCommand(char const* sdpDescription, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+ // Issues a RTSP "ANNOUNCE" command (with "sdpDescription" as parameter),
+ // then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
- Boolean setupMediaSubsession(MediaSubsession& subsession,
- Boolean streamOutgoing = False,
- Boolean streamUsingTCP = False,
- Boolean forceMulticastOnUnspecified = False);
- // Issues a RTSP "SETUP" command on "subsession".
- // Returns True iff this command succeeds
- // If "forceMulticastOnUnspecified" is True (and "streamUsingTCP" is False),
- // then the client will request a multicast stream if the media address
- // in the original SDP response was unspecified (i.e., 0.0.0.0).
- // Note, however, that not all servers will support this.
+ unsigned sendSetupCommand(MediaSubsession& subsession, responseHandler* responseHandler,
+ Boolean streamOutgoing = False,
+ Boolean streamUsingTCP = False,
+ Boolean forceMulticastOnUnspecified = False,
+ Authenticator* authenticator = NULL);
+ // Issues a RTSP "SETUP" command, then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
- Boolean playMediaSession(MediaSession& session,
- double start = 0.0f, double end = -1.0f,
- float scale = 1.0f);
- // Issues an aggregate RTSP "PLAY" command on "session".
- // Returns True iff this command succeeds
+ unsigned sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
+ double start = 0.0f, double end = -1.0f, float scale = 1.0f,
+ Authenticator* authenticator = NULL);
+ // Issues an aggregate RTSP "PLAY" command on "session", then returns the "CSeq" sequence number that was used in the command.
// (Note: start=-1 means 'resume'; end=-1 means 'play to end')
- Boolean playMediaSubsession(MediaSubsession& subsession,
- double start = 0.0f, double end = -1.0f,
- float scale = 1.0f,
- Boolean hackForDSS = False);
- // Issues a RTSP "PLAY" command on "subsession".
- // Returns True iff this command succeeds
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+ unsigned sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
+ double start = 0.0f, double end = -1.0f, float scale = 1.0f,
+ Authenticator* authenticator = NULL);
+ // Issues a RTSP "PLAY" command on "subsession", then returns the "CSeq" sequence number that was used in the command.
// (Note: start=-1 means 'resume'; end=-1 means 'play to end')
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
- Boolean pauseMediaSession(MediaSession& session);
- // Issues an aggregate RTSP "PAUSE" command on "session".
- // Returns True iff this command succeeds
- Boolean pauseMediaSubsession(MediaSubsession& subsession);
- // Issues a RTSP "PAUSE" command on "subsession".
- // Returns True iff this command succeeds
+ unsigned sendPauseCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+ // Issues an aggregate RTSP "PAUSE" command on "session", then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+ unsigned sendPauseCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+ // Issues a RTSP "PAUSE" command on "subsession", then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
- Boolean recordMediaSubsession(MediaSubsession& subsession);
- // Issues a RTSP "RECORD" command on "subsession".
- // Returns True iff this command succeeds
+ unsigned sendRecordCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+ // Issues an aggregate RTSP "RECORD" command on "session", then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+ unsigned sendRecordCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+ // Issues a RTSP "RECORD" command on "subsession", then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
- Boolean setMediaSessionParameter(MediaSession& session,
- char const* parameterName,
- char const* parameterValue);
- // Issues a RTSP "SET_PARAMETER" command on "subsession".
- // Returns True iff this command succeeds
+ unsigned sendTeardownCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+ // Issues an aggregate RTSP "TEARDOWN" command on "session", then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+ unsigned sendTeardownCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator = NULL);
+ // Issues a RTSP "TEARDOWN" command on "subsession", then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
- Boolean getMediaSessionParameter(MediaSession& session,
- char const* parameterName,
- char*& parameterValue);
- // Issues a RTSP "GET_PARAMETER" command on "subsession".
- // Returns True iff this command succeeds
+ unsigned sendSetParameterCommand(MediaSession& session, responseHandler* responseHandler,
+ char const* parameterName, char const* parameterValue,
+ Authenticator* authenticator = NULL);
+ // Issues an aggregate RTSP "SET_PARAMETER" command on "session", then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
- Boolean teardownMediaSession(MediaSession& session);
- // Issues an aggregate RTSP "TEARDOWN" command on "session".
- // Returns True iff this command succeeds
- Boolean teardownMediaSubsession(MediaSubsession& subsession);
- // Issues a RTSP "TEARDOWN" command on "subsession".
- // Returns True iff this command succeeds
+ unsigned sendGetParameterCommand(MediaSession& session, responseHandler* responseHandler, char const* parameterName,
+ Authenticator* authenticator = NULL);
+ // Issues an aggregate RTSP "GET_PARAMETER" command on "session", then returns the "CSeq" sequence number that was used in the command.
+ // (The "responseHandler" and "authenticator" parameters are as described for "sendDescribeCommand".)
+
+ Boolean changeResponseHandler(unsigned cseq, responseHandler* newResponseHandler);
+ // Changes the response handler for the previously-performed command (whose operation returned "cseq").
+ // (To turn off any response handling for the command, use a "newResponseHandler" value of NULL. This might be done as part
+ // of an implementation of a 'timeout handler' on the command, for example.)
+ // This function returns True iff "cseq" was for a valid previously-performed command (whose response is still unhandled).
+
+ int socketNum() const { return fInputSocketNum; }
+
+ static Boolean lookupByName(UsageEnvironment& env,
+ char const* sourceName,
+ RTSPClient*& resultClient);
static Boolean parseRTSPURL(UsageEnvironment& env, char const* url,
- NetAddress& address, portNumBits& portNum,
- char const** urlSuffix = NULL);
- // (ignores any "<username>[:<password>]@" in "url")
+ NetAddress& address, portNumBits& portNum, char const** urlSuffix = NULL);
+ // (ignores any "<username>[:<password>]@" in "url"); to get those, use:
static Boolean parseRTSPURLUsernamePassword(char const* url,
- char*& username,
- char*& password);
+ char*& username, char*& password);
- unsigned describeStatus() const { return fDescribeStatusCode; }
-
- void setUserAgentString(char const* userAgentStr);
+ void setUserAgentString(char const* userAgentName);
// sets an alternative string to be used in RTSP "User-Agent:" headers
unsigned sessionTimeoutParameter() const { return fSessionTimeoutParameter; }
+ static unsigned responseBufferSize;
+
protected:
- RTSPClient(UsageEnvironment& env, int verbosityLevel,
- char const* applicationName, portNumBits tunnelOverHTTPPortNum);
+ RTSPClient(UsageEnvironment& env, char const* rtspURL,
+ int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum);
// called only by createNew();
virtual ~RTSPClient();
private: // redefined virtual functions
virtual Boolean isRTSPClient() const;
+public: // Some compilers complain if this is "private:"
+ // The state of a request-in-progress:
+ class RequestRecord {
+ public:
+ RequestRecord(unsigned cseq, char const* commandName, responseHandler* handler,
+ MediaSession* session = NULL, MediaSubsession* subsession = NULL, u_int32_t booleanFlags = 0,
+ double start = 0.0f, double end = -1.0f, float scale = 1.0f, char const* contentStr = NULL);
+ virtual ~RequestRecord();
+
+ RequestRecord*& next() { return fNext; }
+ unsigned& cseq() { return fCSeq; }
+ char const* commandName() const { return fCommandName; }
+ MediaSession* session() const { return fSession; }
+ MediaSubsession* subsession() const { return fSubsession; }
+ u_int32_t booleanFlags() const { return fBooleanFlags; }
+ double start() const { return fStart; }
+ double end() const { return fEnd; }
+ float scale() const { return fScale; }
+ char* contentStr() const { return fContentStr; }
+ responseHandler*& handler() { return fHandler; }
+
+ private:
+ RequestRecord* fNext;
+ unsigned fCSeq;
+ char const* fCommandName;
+ MediaSession* fSession;
+ MediaSubsession* fSubsession;
+ u_int32_t fBooleanFlags;
+ double fStart, fEnd;
+ float fScale;
+ char* fContentStr;
+ responseHandler* fHandler;
+ };
private:
+ class RequestQueue {
+ public:
+ RequestQueue();
+ virtual ~RequestQueue();
+
+ void enqueue(RequestRecord* request); // "request" must not be NULL
+ RequestRecord* dequeue();
+ void putAtHead(RequestRecord* request); // "request" must not be NULL
+ RequestRecord* findByCSeq(unsigned cseq);
+ Boolean isEmpty() const { return fHead == NULL; }
+
+ private:
+ RequestRecord* fHead;
+ RequestRecord* fTail;
+ };
+
void reset();
void resetTCPSockets();
-
- Boolean openConnectionFromURL(char const* url, Authenticator* authenticator,
- int timeout = -1);
- char* createAuthenticatorString(Authenticator const* authenticator,
- char const* cmd, char const* url);
- static void checkForAuthenticationFailure(unsigned responseCode,
- char*& nextLineStart,
- Authenticator* authenticator);
- Boolean sendRequest(char const* requestString, char const* tag,
- Boolean base64EncodeIfOverHTTP = True);
- Boolean getResponse(char const* tag,
- unsigned& bytesRead, unsigned& responseCode,
- char*& firstLine, char*& nextLineStart,
- Boolean checkFor200Response = True);
- unsigned getResponse1(char*& responseBuffer, unsigned responseBufferSize);
- Boolean parseResponseCode(char const* line, unsigned& responseCode);
- Boolean parseTransportResponse(char const* line,
- char*& serverAddressStr,
- portNumBits& serverPortNum,
- unsigned char& rtpChannelId,
- unsigned char& rtcpChannelId);
- Boolean parseRTPInfoHeader(char*& line, u_int16_t& seqNum, u_int32_t& timestamp);
- Boolean parseScaleHeader(char const* line, float& scale);
- Boolean parseGetParameterHeader(char const* line,
- const char* param,
- char*& value);
+ void resetResponseBuffer();
+ void setBaseURL(char const* url);
+ int openConnection(); // -1: failure; 0: pending; 1: success
+ int connectToServer(int socketNum, portNumBits remotePortNum); // used to implement "openConnection()"; result values are the same
+ char* createAuthenticatorString(char const* cmd, char const* url);
+ unsigned sendRequest(RequestRecord* request);
+ void handleRequestError(RequestRecord* request);
+ Boolean parseResponseCode(char const* line, unsigned& responseCode, char const*& responseString, Boolean& responseIsHTTP);
+ void handleIncomingRequest();
+ static Boolean checkForHeader(char const* line, char const* headerName, unsigned headerNameLength, char const*& headerParams);
+ Boolean parseTransportParams(char const* paramsStr,
+ char*& serverAddressStr, portNumBits& serverPortNum,
+ unsigned char& rtpChannelId, unsigned char& rtcpChannelId);
+ Boolean parseScaleParam(char const* paramStr, float& scale);
+ Boolean parseRTPInfoParams(char const*& paramStr, u_int16_t& seqNum, u_int32_t& timestamp);
+ Boolean handleSETUPResponse(MediaSubsession& subsession, char const* sessionParamsStr, char const* transportParamsStr,
+ Boolean streamUsingTCP);
+ Boolean handlePLAYResponse(MediaSession& session, MediaSubsession& subsession,
+ char const* scaleParamsStr, char const* rangeParamsStr, char const* rtpInfoParamsStr);
+ Boolean handleTEARDOWNResponse(MediaSession& session, MediaSubsession& subsession);
+ Boolean handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString);
+ Boolean handleAuthenticationFailure(char const* wwwAuthenticateParamsStr);
+ Boolean resendCommand(RequestRecord* request);
char const* sessionURL(MediaSession const& session) const;
+ static void handleAlternativeRequestByte(void*, u_int8_t requestByte);
+ void handleAlternativeRequestByte1(u_int8_t requestByte);
void constructSubsessionURL(MediaSubsession const& subsession,
char const*& prefix,
char const*& separator,
char const*& suffix);
- Boolean setupHTTPTunneling(char const* urlSuffix, Authenticator* authenticator);
- // Support for handling requests sent back by a server:
- static void incomingRequestHandler(void*, int /*mask*/);
- void incomingRequestHandler1();
- void handleCmd_notSupported(char const* cseq);
+ // Support for tunneling RTSP-over-HTTP:
+ Boolean setupHTTPTunneling1(); // send the HTTP "GET"
+ static void responseHandlerForHTTP_GET(RTSPClient* rtspClient, int responseCode, char* responseString);
+ void responseHandlerForHTTP_GET1(int responseCode, char* responseString);
+ Boolean setupHTTPTunneling2(); // send the HTTP "POST"
+
+ // Support for asynchronous connections to the server:
+ static void connectionHandler(void*, int /*mask*/);
+ void connectionHandler1();
+
+ // Support for handling data sent back by a server:
+ static void incomingDataHandler(void*, int /*mask*/);
+ void incomingDataHandler1();
+ void handleResponseBytes(int newBytesRead);
private:
int fVerbosityLevel;
portNumBits fTunnelOverHTTPPortNum;
char* fUserAgentHeaderStr;
- unsigned fUserAgentHeaderStrSize;
+ unsigned fUserAgentHeaderStrLen;
int fInputSocketNum, fOutputSocketNum;
unsigned fServerAddress;
unsigned fCSeq; // sequence number, used in consecutive requests
@@ -213,17 +274,72 @@ private:
unsigned char fTCPStreamIdCount; // used for (optional) RTP/TCP
char* fLastSessionId;
unsigned fSessionTimeoutParameter; // optionally set in response "Session:" headers
- unsigned fDescribeStatusCode;
- // 0: OK; 1: connection failed; 2: stream unavailable
char* fResponseBuffer;
- unsigned fResponseBufferSize;
+ unsigned fResponseBytesAlreadySeen, fResponseBufferBytesLeft;
+ RequestQueue fRequestsAwaitingConnection, fRequestsAwaitingHTTPTunneling, fRequestsAwaitingResponse;
- // The following fields are used to implement the non-standard Kasenna protocol:
- Boolean fServerIsKasenna;
- char* fKasennaContentType;
+ // Support for tunneling RTSP-over-HTTP:
+ char fSessionCookie[33];
+ unsigned fSessionCookieCounter;
+ Boolean fHTTPTunnelingConnectionIsPending;
- // The following is used to deal with Microsoft servers' non-standard use of RTSP:
- Boolean fServerIsMicrosoft;
+#ifdef RTSPCLIENT_SYNCHRONOUS_INTERFACE
+ // Old "RTSPClient" interface, which performs synchronous (blocking) operations.
+ // This will eventually go away, so new applications should not use it.
+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);
+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
};
#endif
diff --git a/liveMedia/include/RTSPCommon.hh b/liveMedia/include/RTSPCommon.hh
index 9e526d5..9c555b2 100644
--- a/liveMedia/include/RTSPCommon.hh
+++ b/liveMedia/include/RTSPCommon.hh
@@ -44,6 +44,7 @@ Boolean parseRTSPRequestString(char const *reqStr, unsigned reqStrSize,
char* resultCSeq,
unsigned resultCSeqMaxSize);
+Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeEnd);
Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd);
#endif
diff --git a/liveMedia/include/liveMedia_version.hh b/liveMedia/include/liveMedia_version.hh
index 4dc2f03..4fa44aa 100644
--- a/liveMedia/include/liveMedia_version.hh
+++ b/liveMedia/include/liveMedia_version.hh
@@ -4,7 +4,7 @@
#ifndef _LIVEMEDIA_VERSION_HH
#define _LIVEMEDIA_VERSION_HH
-#define LIVEMEDIA_LIBRARY_VERSION_STRING "2010.04.09"
-#define LIVEMEDIA_LIBRARY_VERSION_INT 1270771200
+#define LIVEMEDIA_LIBRARY_VERSION_STRING "2010.09.25"
+#define LIVEMEDIA_LIBRARY_VERSION_INT 1285372800
#endif
diff --git a/mediaServer/version.hh b/mediaServer/version.hh
index 6a65ff8..b141f45 100644
--- a/mediaServer/version.hh
+++ b/mediaServer/version.hh
@@ -5,6 +5,6 @@
#ifndef _MEDIA_SERVER_VERSION_HH
#define _MEDIA_SERVER_VERSION_HH
-#define MEDIA_SERVER_VERSION_STRING "0.42"
+#define MEDIA_SERVER_VERSION_STRING "0.5"
#endif
diff --git a/testProgs/_rtp_over_tcp.txt b/testProgs/_rtp_over_tcp.txt
new file mode 100644
index 0000000..b504e13
--- /dev/null
+++ b/testProgs/_rtp_over_tcp.txt
@@ -0,0 +1,8 @@
+Opening connection to 192.168.3.75, port 554...
+...Connection to server failed: No route to host
+Sending request: DESCRIBE rtsp://192.168.3.75/VideoInput/1/jpeg/1 RTSP/1.0
+CSeq: 3
+User-Agent: ./openRTSP (LIVE555 Streaming Media v2010.07.10)
+Accept: application/sdp
+
+
diff --git a/testProgs/openRTSP.cpp b/testProgs/openRTSP.cpp
index a313b12..104384f 100644
--- a/testProgs/openRTSP.cpp
+++ b/testProgs/openRTSP.cpp
@@ -19,65 +19,31 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#include "playCommon.hh"
-Medium* createClient(UsageEnvironment& env,
- int verbosityLevel, char const* applicationName) {
+RTSPClient* ourRTSPClient = NULL;
+Medium* createClient(UsageEnvironment& env, char const* url, int verbosityLevel, char const* applicationName) {
extern portNumBits tunnelOverHTTPPortNum;
- return RTSPClient::createNew(env, verbosityLevel, applicationName,
- tunnelOverHTTPPortNum);
+ return ourRTSPClient = RTSPClient::createNew(env, url, verbosityLevel, applicationName, tunnelOverHTTPPortNum);
}
-char* getOptionsResponse(Medium* client, char const* url,
- char* username, char* password) {
- RTSPClient* rtspClient = (RTSPClient*)client;
- return rtspClient->sendOptionsCmd(url, username, password);
+void getOptions(RTSPClient::responseHandler* afterFunc) {
+ ourRTSPClient->sendOptionsCommand(afterFunc, ourAuthenticator);
}
-char* getSDPDescriptionFromURL(Medium* client, char const* url,
- char const* username, char const* password,
- char const* /*proxyServerName*/,
- unsigned short /*proxyServerPortNum*/,
- unsigned short /*clientStartPort*/) {
- RTSPClient* rtspClient = (RTSPClient*)client;
- char* result;
- if (username != NULL && password != NULL) {
- result = rtspClient->describeWithPassword(url, username, password);
- } else {
- result = rtspClient->describeURL(url);
- }
-
- return result;
+void getSDPDescription(RTSPClient::responseHandler* afterFunc) {
+ ourRTSPClient->sendDescribeCommand(afterFunc, ourAuthenticator);
}
-Boolean clientSetupSubsession(Medium* client, MediaSubsession* subsession,
- Boolean streamUsingTCP) {
- if (client == NULL || subsession == NULL) return False;
- RTSPClient* rtspClient = (RTSPClient*)client;
- return rtspClient->setupMediaSubsession(*subsession,
- False, streamUsingTCP);
+void setupSubsession(MediaSubsession* subsession, Boolean streamUsingTCP, RTSPClient::responseHandler* afterFunc) {
+ Boolean forceMulticastOnUnspecified = False;
+ ourRTSPClient->sendSetupCommand(*subsession, afterFunc, False, streamUsingTCP, forceMulticastOnUnspecified, ourAuthenticator);
}
-Boolean clientStartPlayingSession(Medium* client,
- MediaSession* session) {
- extern double initialSeekTime, duration, scale;
- double endTime = initialSeekTime;
- if (scale > 0) {
- if (duration <= 0) endTime = -1.0f;
- else endTime = initialSeekTime + duration;
- } else {
- endTime = initialSeekTime - duration;
- if (endTime < 0) endTime = 0.0f;
- }
-
- if (client == NULL || session == NULL) return False;
- RTSPClient* rtspClient = (RTSPClient*)client;
- return rtspClient->playMediaSession(*session, initialSeekTime, endTime, (float)scale);
+void startPlayingSession(MediaSession* session, double start, double end, float scale, RTSPClient::responseHandler* afterFunc) {
+ ourRTSPClient->sendPlayCommand(*session, afterFunc, start, end, scale, ourAuthenticator);
}
-Boolean clientTearDownSession(Medium* client,
- MediaSession* session) {
- if (client == NULL || session == NULL) return False;
- RTSPClient* rtspClient = (RTSPClient*)client;
- return rtspClient->teardownMediaSession(*session);
+void tearDownSession(MediaSession* session, RTSPClient::responseHandler* afterFunc) {
+ ourRTSPClient->sendTeardownCommand(*session, afterFunc, ourAuthenticator);
}
Boolean allowProxyServers = False;
diff --git a/testProgs/playCommon.cpp b/testProgs/playCommon.cpp
index fbbfcb6..ca33219 100644
--- a/testProgs/playCommon.cpp
+++ b/testProgs/playCommon.cpp
@@ -29,9 +29,13 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#endif
// Forward function definitions:
+void continueAfterOPTIONS(RTSPClient* client, int resultCode, char* resultString);
+void continueAfterDESCRIBE(RTSPClient* client, int resultCode, char* resultString);
+void continueAfterSETUP(RTSPClient* client, int resultCode, char* resultString);
+void continueAfterPLAY(RTSPClient* client, int resultCode, char* resultString);
+void continueAfterTEARDOWN(RTSPClient* client, int resultCode, char* resultString);
+
void setupStreams();
-void startPlayingStreams();
-void tearDownStreams();
void closeMediaSinks();
void subsessionAfterPlaying(void* clientData);
void subsessionByeHandler(void* clientData);
@@ -46,6 +50,8 @@ void beginQOSMeasurement();
char const* progName;
UsageEnvironment* env;
Medium* ourClient = NULL;
+Authenticator* ourAuthenticator = NULL;
+char const* streamURL = NULL;
MediaSession* session = NULL;
TaskToken sessionTimerTask = NULL;
TaskToken arrivalCheckTimerTask = NULL;
@@ -64,7 +70,8 @@ int verbosityLevel = 1; // by default, print verbose output
double duration = 0;
double durationSlop = -1.0; // extra seconds to play at the end
double initialSeekTime = 0.0f;
-double scale = 1.0f;
+float scale = 1.0f;
+double endTime;
unsigned interPacketGapMaxTime = 0;
unsigned totNumPacketsReceived = ~0; // used if checking inter-packet gaps
Boolean playContinuously = False;
@@ -74,6 +81,7 @@ Boolean sendOptionsRequestOnly = False;
Boolean oneFilePerFrame = False;
Boolean notifyOnPacketArrival = False;
Boolean streamUsingTCP = False;
+unsigned short desiredPortNum = 0;
portNumBits tunnelOverHTTPPortNum = 0;
char* username = NULL;
char* password = NULL;
@@ -106,7 +114,6 @@ void usage() {
<< "]" << (supportCodecSelection ? " [-A <audio-codec-rtp-payload-format-code>|-M <mime-subtype-name>]" : "")
<< " [-s <initial-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";
- //##### Add "-R <dest-rtsp-url>" #####
shutdown();
}
@@ -125,8 +132,6 @@ int main(int argc, char** argv) {
signal(SIGUSR1, signalHandlerShutdown);
#endif
- unsigned short desiredPortNum = 0;
-
// unfortunately we can't use getopt() here, as Windoze doesn't have it
while (argc > 2) {
char* const opt = argv[1];
@@ -304,6 +309,9 @@ int main(int argc, char** argv) {
++argv; --argc;
}
}
+
+ ourAuthenticator = new Authenticator;
+ ourAuthenticator->setUsernameAndPassword(username, password);
break;
}
@@ -481,10 +489,10 @@ int main(int argc, char** argv) {
durationSlop = qosMeasurementIntervalMS > 0 ? 0.0 : 5.0;
}
- char* url = argv[1];
+ streamURL = argv[1];
// Create our client object:
- ourClient = createClient(*env, verbosityLevel, progName);
+ ourClient = createClient(*env, streamURL, verbosityLevel, progName);
if (ourClient == NULL) {
*env << "Failed to create " << clientProtocolName
<< " client: " << env->getResultMsg() << "\n";
@@ -493,34 +501,40 @@ int main(int argc, char** argv) {
if (sendOptionsRequest) {
// Begin by sending an "OPTIONS" command:
- char* optionsResponse
- = getOptionsResponse(ourClient, url, username, password);
- if (sendOptionsRequestOnly) {
- if (optionsResponse == NULL) {
- *env << clientProtocolName << " \"OPTIONS\" request failed: "
- << env->getResultMsg() << "\n";
- } else {
- *env << clientProtocolName << " \"OPTIONS\" request returned: "
- << optionsResponse << "\n";
- }
- shutdown();
+ getOptions(continueAfterOPTIONS);
+ } else {
+ continueAfterOPTIONS(NULL, 0, NULL);
+ }
+
+ // All subsequent activity takes place within the event loop:
+ env->taskScheduler().doEventLoop(); // does not return
+
+ return 0; // only to prevent compiler warning
+}
+
+void continueAfterOPTIONS(RTSPClient*, int resultCode, char* resultString) {
+ if (sendOptionsRequestOnly) {
+ if (resultCode != 0) {
+ *env << clientProtocolName << " \"OPTIONS\" request failed: " << resultString << "\n";
+ } else {
+ *env << clientProtocolName << " \"OPTIONS\" request returned: " << resultString << "\n";
}
- delete[] optionsResponse;
+ shutdown();
}
+ delete[] resultString;
- // Open the URL, to get a SDP description:
- char* sdpDescription
- = getSDPDescriptionFromURL(ourClient, url, username, password,
- proxyServerName, proxyServerPortNum,
- desiredPortNum);
- if (sdpDescription == NULL) {
- *env << "Failed to get a SDP description from URL \"" << url
- << "\": " << env->getResultMsg() << "\n";
+ // Next, get a SDP description for the stream:
+ getSDPDescription(continueAfterDESCRIBE);
+}
+
+void continueAfterDESCRIBE(RTSPClient*, int resultCode, char* resultString) {
+ if (resultCode != 0) {
+ *env << "Failed to get a SDP description from URL \"" << streamURL << "\": " << resultString << "\n";
shutdown();
}
- *env << "Opened URL \"" << url
- << "\", returning a SDP description:\n" << sdpDescription << "\n";
+ char* sdpDescription = resultString;
+ *env << "Opened URL \"" << streamURL << "\", returning a SDP description:\n" << sdpDescription << "\n";
// Create a media session object from this SDP description:
session = MediaSession::createNew(*env, sdpDescription);
@@ -613,6 +627,41 @@ int main(int argc, char** argv) {
// Perform additional 'setup' on each subsession, before playing them:
setupStreams();
+}
+
+MediaSubsession *subsession;
+Boolean madeProgress = False;
+void continueAfterSETUP(RTSPClient*, int resultCode, char* resultString) {
+ if (resultCode == 0) {
+ *env << "Setup \"" << subsession->mediumName()
+ << "/" << subsession->codecName()
+ << "\" subsession (client ports " << subsession->clientPortNum()
+ << "-" << subsession->clientPortNum()+1 << ")\n";
+ madeProgress = True;
+ } else {
+ *env << "Failed to setup \"" << subsession->mediumName()
+ << "/" << subsession->codecName()
+ << "\" subsession: " << env->getResultMsg() << "\n";
+ }
+
+ // Set up the next subsession, if any:
+ setupStreams();
+}
+
+void setupStreams() {
+ static MediaSubsessionIterator* setupIter = NULL;
+ if (setupIter == NULL) setupIter = new MediaSubsessionIterator(*session);
+ while ((subsession = setupIter->next()) != NULL) {
+ // We have another subsession left to set up:
+ if (subsession->clientPortNum() == 0) continue; // port # was not set
+
+ setupSubsession(subsession, streamUsingTCP, continueAfterSETUP);
+ return;
+ }
+
+ // We're done setting up subsessions.
+ delete setupIter;
+ if (!madeProgress) shutdown();
// Create output files:
if (createReceivers) {
@@ -648,7 +697,7 @@ int main(int argc, char** argv) {
} else {
// Create and start "FileSink"s for each subsession:
madeProgress = False;
- iter.reset();
+ MediaSubsessionIterator iter(*session);
while ((subsession = iter.next()) != NULL) {
if (subsession->readSource() == NULL) continue; // was not initiated
@@ -675,7 +724,8 @@ int main(int argc, char** argv) {
(strcmp(subsession->codecName(), "H264") == 0)) {
// For H.264 video stream, we use a special sink that insert start_codes:
fileSink = H264VideoFileSink::createNew(*env, outFileName,
- fileSinkBufferSize, oneFilePerFrame);
+ subsession->fmtp_spropparametersets(),
+ fileSinkBufferSize, oneFilePerFrame);
} else {
// Normal case:
fileSink = FileSink::createNew(*env, outFileName,
@@ -728,47 +778,27 @@ int main(int argc, char** argv) {
}
// Finally, start playing each subsession, to start the data flow:
-
- startPlayingStreams();
-
- env->taskScheduler().doEventLoop(); // does not return
-
- return 0; // only to prevent compiler warning
-}
-
-
-void setupStreams() {
- MediaSubsessionIterator iter(*session);
- MediaSubsession *subsession;
- Boolean madeProgress = False;
-
- while ((subsession = iter.next()) != NULL) {
- if (subsession->clientPortNum() == 0) continue; // port # was not set
-
- if (!clientSetupSubsession(ourClient, subsession, streamUsingTCP)) {
- *env << "Failed to setup \"" << subsession->mediumName()
- << "/" << subsession->codecName()
- << "\" subsession: " << env->getResultMsg() << "\n";
- } else {
- *env << "Setup \"" << subsession->mediumName()
- << "/" << subsession->codecName()
- << "\" subsession (client ports " << subsession->clientPortNum()
- << "-" << subsession->clientPortNum()+1 << ")\n";
- madeProgress = True;
- }
- }
- if (!madeProgress) shutdown();
-}
-
-void startPlayingStreams() {
if (duration == 0) {
if (scale > 0) duration = session->playEndTime() - initialSeekTime; // use SDP end time
else if (scale < 0) duration = initialSeekTime;
}
if (duration < 0) duration = 0.0;
- if (!clientStartPlayingSession(ourClient, session)) {
- *env << "Failed to start playing session: " << env->getResultMsg() << "\n";
+ endTime = initialSeekTime;
+ if (scale > 0) {
+ if (duration <= 0) endTime = -1.0f;
+ else endTime = initialSeekTime + duration;
+ } else {
+ endTime = initialSeekTime - duration;
+ if (endTime < 0) endTime = 0.0f;
+ }
+
+ startPlayingSession(session, initialSeekTime, endTime, scale, continueAfterPLAY);
+}
+
+void continueAfterPLAY(RTSPClient*, int resultCode, char* resultString) {
+ if (resultCode != 0) {
+ *env << "Failed to start playing session: " << resultString << "\n";
shutdown();
} else {
*env << "Started playing session\n";
@@ -815,12 +845,6 @@ void startPlayingStreams() {
checkInterPacketGaps(NULL);
}
-void tearDownStreams() {
- if (session == NULL) return;
-
- clientTearDownSession(ourClient, session);
-}
-
void closeMediaSinks() {
Medium::close(qtOut);
Medium::close(aviOut);
@@ -871,7 +895,7 @@ void sessionAfterPlaying(void* /*clientData*/) {
shutdown(0);
} else {
// We've been asked to play the stream(s) over again:
- startPlayingStreams();
+ startPlayingSession(session, initialSeekTime, endTime, scale, continueAfterPLAY);
}
}
@@ -1031,7 +1055,7 @@ void printQOSData(int exitCode) {
numPacketsExpected = curQOSRecord->totNumPacketsExpected;
}
*env << "num_packets_received\t" << numPacketsReceived << "\n";
- *env << "num_packets_lost\t" << numPacketsExpected - numPacketsReceived << "\n";
+ *env << "num_packets_lost\t" << int(numPacketsExpected - numPacketsReceived) << "\n";
if (curQOSRecord != NULL) {
unsigned secsDiff = curQOSRecord->measurementEndTime.tv_sec
@@ -1088,7 +1112,9 @@ void printQOSData(int exitCode) {
delete qosRecordHead;
}
+int shutdownExitCode;
void shutdown(int exitCode) {
+ shutdownExitCode = exitCode;
if (env != NULL) {
env->taskScheduler().unscheduleDelayedTask(sessionTimerTask);
env->taskScheduler().unscheduleDelayedTask(arrivalCheckTimerTask);
@@ -1100,18 +1126,25 @@ void shutdown(int exitCode) {
printQOSData(exitCode);
}
- // Close our output files:
- closeMediaSinks();
-
// Teardown, then shutdown, any outstanding RTP/RTCP subsessions
- tearDownStreams();
+ if (session != NULL) {
+ tearDownSession(session, continueAfterTEARDOWN);
+ } else {
+ continueAfterTEARDOWN(NULL, 0, NULL);
+ }
+}
+
+void continueAfterTEARDOWN(RTSPClient*, int /*resultCode*/, char* /*resultString*/) {
+ // Now that we've stopped any more incoming data from arriving, close our output files:
+ closeMediaSinks();
Medium::close(session);
// Finally, shut down our client:
+ delete ourAuthenticator;
Medium::close(ourClient);
// Adios...
- exit(exitCode);
+ exit(shutdownExitCode);
}
void signalHandlerShutdown(int /*sig*/) {
diff --git a/testProgs/playCommon.hh b/testProgs/playCommon.hh
index 57679f3..c2f35da 100644
--- a/testProgs/playCommon.hh
+++ b/testProgs/playCommon.hh
@@ -19,29 +19,21 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#include "liveMedia.hh"
-extern Medium* createClient(UsageEnvironment& env, int verbosityLevel,
- char const* applicationName);
+extern Medium* createClient(UsageEnvironment& env, char const* URL, int verbosityLevel, char const* applicationName);
+extern RTSPClient* ourRTSPClient;
+extern SIPClient* ourSIPClient;
-extern char* getOptionsResponse(Medium* client, char const* url,
- char* username, char* password);
+extern void getOptions(RTSPClient::responseHandler* afterFunc);
-extern char* getSDPDescriptionFromURL(Medium* client, char const* url,
- char const* username,
- char const* password,
- char const* proxyServerName,
- unsigned short proxyServerPortNum,
- unsigned short clientStartPortNum);
+extern void getSDPDescription(RTSPClient::responseHandler* afterFunc);
-extern Boolean clientSetupSubsession(Medium* client,
- MediaSubsession* subsession,
- Boolean streamUsingTCP);
+extern void setupSubsession(MediaSubsession* subsession, Boolean streamUsingTCP, RTSPClient::responseHandler* afterFunc);
-extern Boolean clientStartPlayingSession(Medium* client,
- MediaSession* session);
+extern void startPlayingSession(MediaSession* session, double start, double end, float scale, RTSPClient::responseHandler* afterFunc);
-extern Boolean clientTearDownSession(Medium* client,
- MediaSession* session);
+extern void tearDownSession(MediaSession* session, RTSPClient::responseHandler* afterFunc);
+extern Authenticator* ourAuthenticator;
extern Boolean allowProxyServers;
extern Boolean controlConnectionUsesTCP;
extern Boolean supportCodecSelection;
diff --git a/testProgs/playSIP.cpp b/testProgs/playSIP.cpp
index 9f6ca5c..0dca406 100644
--- a/testProgs/playSIP.cpp
+++ b/testProgs/playSIP.cpp
@@ -20,8 +20,8 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
#include "playCommon.hh"
#include "SIPClient.hh"
-Medium* createClient(UsageEnvironment& env,
- int verbosityLevel, char const* applicationName) {
+SIPClient* ourSIPClient = NULL;
+Medium* createClient(UsageEnvironment& env, char const* /*url*/, int verbosityLevel, char const* applicationName) {
// First, trim any directory prefixes from "applicationName":
char const* suffix = &applicationName[strlen(applicationName)];
while (suffix != applicationName) {
@@ -34,74 +34,74 @@ Medium* createClient(UsageEnvironment& env,
extern unsigned char desiredAudioRTPPayloadFormat;
extern char* mimeSubtype;
- return SIPClient::createNew(env,
- desiredAudioRTPPayloadFormat, mimeSubtype,
- verbosityLevel, applicationName);
+ return ourSIPClient = SIPClient::createNew(env, desiredAudioRTPPayloadFormat, mimeSubtype, verbosityLevel, applicationName);
}
-char* getOptionsResponse(Medium* client, char const* url,
- char* username, char* password) {
- SIPClient* sipClient = (SIPClient*)client;
- sipClient->envir().setResultMsg("NOT SUPPORTED IN CLIENT");//#####
- return NULL;//#####
+void getOptions(RTSPClient::responseHandler* afterFunc) {
+ ourSIPClient->envir().setResultMsg("NOT SUPPORTED IN CLIENT");
+ afterFunc(NULL, -1, strDup(ourSIPClient->envir().getResultMsg()));
}
-char* getSDPDescriptionFromURL(Medium* client, char const* url,
- char const* username, char const* password,
- char const* proxyServerName,
- unsigned short proxyServerPortNum,
- unsigned short clientStartPortNum) {
- SIPClient* sipClient = (SIPClient*)client;
-
+void getSDPDescription(RTSPClient::responseHandler* afterFunc) {
+ extern char* proxyServerName;
if (proxyServerName != NULL) {
// Tell the SIP client about the proxy:
NetAddressList addresses(proxyServerName);
if (addresses.numAddresses() == 0) {
- client->envir() << "Failed to find network address for \""
- << proxyServerName << "\"\n";
+ ourSIPClient->envir() << "Failed to find network address for \"" << proxyServerName << "\"\n";
} else {
NetAddress address = *(addresses.firstAddress());
unsigned proxyServerAddress // later, allow for IPv6 #####
= *(unsigned*)(address.data());
+ extern unsigned short proxyServerPortNum;
if (proxyServerPortNum == 0) proxyServerPortNum = 5060; // default
- sipClient->setProxyServer(proxyServerAddress, proxyServerPortNum);
+ ourSIPClient->setProxyServer(proxyServerAddress, proxyServerPortNum);
}
}
+ extern unsigned short desiredPortNum;
+ unsigned short clientStartPortNum = desiredPortNum;
if (clientStartPortNum == 0) clientStartPortNum = 8000; // default
- sipClient->setClientStartPortNum(clientStartPortNum);
+ ourSIPClient->setClientStartPortNum(clientStartPortNum);
+ extern char const* streamURL;
+ char const* username = ourAuthenticator->username();
+ char const* password = ourAuthenticator->password();
char* result;
if (username != NULL && password != NULL) {
- result = sipClient->inviteWithPassword(url, username, password);
+ result = ourSIPClient->inviteWithPassword(streamURL, username, password);
} else {
- result = sipClient->invite(url);
+ result = ourSIPClient->invite(streamURL);
}
- return result;
+ int resultCode = result == NULL ? -1 : 0;
+ afterFunc(NULL, resultCode, strDup(result));
}
-Boolean clientSetupSubsession(Medium* client, MediaSubsession* subsession,
- Boolean streamUsingTCP) {
+void setupSubsession(MediaSubsession* subsession, Boolean /*streamUsingTCP*/, RTSPClient::responseHandler* afterFunc) {
subsession->sessionId = "mumble"; // anything that's non-NULL will work
- return True;
+
+ afterFunc(NULL, 0, NULL);
}
-Boolean clientStartPlayingSession(Medium* client,
- MediaSession* /*session*/) {
- SIPClient* sipClient = (SIPClient*)client;
- return sipClient->sendACK();
- //##### This isn't quite right, because we should really be allowing
- //##### for the possibility of this ACK getting lost, by retransmitting
- //##### it *each time* we get a 2xx response from the server.
+void startPlayingSession(MediaSession* /*session*/, double /*start*/, double /*end*/, float /*scale*/, RTSPClient::responseHandler* afterFunc) {
+ if (ourSIPClient->sendACK()) {
+ //##### This isn't quite right, because we should really be allowing
+ //##### for the possibility of this ACK getting lost, by retransmitting
+ //##### it *each time* we get a 2xx response from the server.
+ afterFunc(NULL, 0, NULL);
+ } else {
+ afterFunc(NULL, -1, strDup(ourSIPClient->envir().getResultMsg()));
+ }
}
-Boolean clientTearDownSession(Medium* client,
- MediaSession* /*session*/) {
- if (client == NULL) return False;
- SIPClient* sipClient = (SIPClient*)client;
- return sipClient->sendBYE();
+void tearDownSession(MediaSession* /*session*/, RTSPClient::responseHandler* afterFunc) {
+ if (ourSIPClient == NULL || ourSIPClient->sendBYE()) {
+ afterFunc(NULL, 0, NULL);
+ } else {
+ afterFunc(NULL, -1, strDup(ourSIPClient->envir().getResultMsg()));
+ }
}
Boolean allowProxyServers = True;
--
liblivemedia packaging
More information about the pkg-multimedia-commits
mailing list