[SCM] liblivemedia/upstream: Imported Upstream version 2015.12.22

sramacher at users.alioth.debian.org sramacher at users.alioth.debian.org
Tue Dec 22 21:46:16 UTC 2015


The following commit has been merged in the upstream branch:
commit dfe4b3a3d98ea4b839664f311acf8f46a2c9fd22
Author: Sebastian Ramacher <sramacher at debian.org>
Date:   Tue Dec 22 21:43:19 2015 +0100

    Imported Upstream version 2015.12.22

diff --git a/BasicUsageEnvironment/BasicHashTable.cpp b/BasicUsageEnvironment/BasicHashTable.cpp
index 6a86612..a85f8b3 100644
--- a/BasicUsageEnvironment/BasicHashTable.cpp
+++ b/BasicUsageEnvironment/BasicHashTable.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Basic Hash Table implementation
 // Implementation
 
diff --git a/BasicUsageEnvironment/BasicTaskScheduler.cpp b/BasicUsageEnvironment/BasicTaskScheduler.cpp
index 15e0732..c0d87f5 100644
--- a/BasicUsageEnvironment/BasicTaskScheduler.cpp
+++ b/BasicUsageEnvironment/BasicTaskScheduler.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Basic Usage Environment: for a simple, non-scripted, console application
 // Implementation
 
@@ -33,7 +33,11 @@ BasicTaskScheduler* BasicTaskScheduler::createNew(unsigned maxSchedulerGranulari
 }
 
 BasicTaskScheduler::BasicTaskScheduler(unsigned maxSchedulerGranularity)
-  : fMaxSchedulerGranularity(maxSchedulerGranularity), fMaxNumSockets(0) {
+  : fMaxSchedulerGranularity(maxSchedulerGranularity), fMaxNumSockets(0)
+#if defined(__WIN32__) || defined(_WIN32)
+  , fDummySocketNum(-1)
+#endif
+{
   FD_ZERO(&fReadSet);
   FD_ZERO(&fWriteSet);
   FD_ZERO(&fExceptionSet);
@@ -42,6 +46,9 @@ BasicTaskScheduler::BasicTaskScheduler(unsigned maxSchedulerGranularity)
 }
 
 BasicTaskScheduler::~BasicTaskScheduler() {
+#if defined(__WIN32__) || defined(_WIN32)
+  if (fDummySocketNum >= 0) closeSocket(fDummySocketNum);
+#endif
 }
 
 void BasicTaskScheduler::schedulerTickTask(void* clientData) {
@@ -89,8 +96,9 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
     if (err == WSAEINVAL && readSet.fd_count == 0) {
       err = EINTR;
       // To stop this from happening again, create a dummy socket:
-      int dummySocketNum = socket(AF_INET, SOCK_DGRAM, 0);
-      FD_SET((unsigned)dummySocketNum, &fReadSet);
+      if (fDummySocketNum >= 0) closeSocket(fDummySocketNum);
+      fDummySocketNum = socket(AF_INET, SOCK_DGRAM, 0);
+      FD_SET((unsigned)fDummySocketNum, &fReadSet);
     }
     if (err != EINTR) {
 #else
@@ -172,7 +180,7 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
   if (fTriggersAwaitingHandling != 0) {
     if (fTriggersAwaitingHandling == fLastUsedTriggerMask) {
       // Common-case optimization for a single event trigger:
-      fTriggersAwaitingHandling = 0;
+      fTriggersAwaitingHandling &=~ fLastUsedTriggerMask;
       if (fTriggeredEventHandlers[fLastUsedTriggerNum] != NULL) {
 	(*fTriggeredEventHandlers[fLastUsedTriggerNum])(fTriggeredEventClientDatas[fLastUsedTriggerNum]);
       }
@@ -207,6 +215,9 @@ void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
 void BasicTaskScheduler
   ::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) {
   if (socketNum < 0) return;
+#if !defined(__WIN32__) && !defined(_WIN32) && defined(FD_SETSIZE)
+  if (socketNum >= (int)(FD_SETSIZE)) return;
+#endif
   FD_CLR((unsigned)socketNum, &fReadSet);
   FD_CLR((unsigned)socketNum, &fWriteSet);
   FD_CLR((unsigned)socketNum, &fExceptionSet);
@@ -228,6 +239,9 @@ void BasicTaskScheduler
 
 void BasicTaskScheduler::moveSocketHandling(int oldSocketNum, int newSocketNum) {
   if (oldSocketNum < 0 || newSocketNum < 0) return; // sanity check
+#if !defined(__WIN32__) && !defined(_WIN32) && defined(FD_SETSIZE)
+  if (oldSocketNum >= (int)(FD_SETSIZE) || newSocketNum >= (int)(FD_SETSIZE)) return; // sanity check
+#endif
   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);}
diff --git a/BasicUsageEnvironment/BasicTaskScheduler0.cpp b/BasicUsageEnvironment/BasicTaskScheduler0.cpp
index 3f6c401..ecf9339 100644
--- a/BasicUsageEnvironment/BasicTaskScheduler0.cpp
+++ b/BasicUsageEnvironment/BasicTaskScheduler0.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Basic Usage Environment: for a simple, non-scripted, console application
 // Implementation
 
@@ -73,7 +73,7 @@ void BasicTaskScheduler0::unscheduleDelayedTask(TaskToken& prevTask) {
   delete alarmHandler;
 }
 
-void BasicTaskScheduler0::doEventLoop(char* watchVariable) {
+void BasicTaskScheduler0::doEventLoop(char volatile* watchVariable) {
   // Repeatedly loop, handling readble sockets and timed events:
   while (1) {
     if (watchVariable != NULL && *watchVariable != 0) break;
diff --git a/BasicUsageEnvironment/BasicUsageEnvironment.cpp b/BasicUsageEnvironment/BasicUsageEnvironment.cpp
index 79dcd94..557d26f 100644
--- a/BasicUsageEnvironment/BasicUsageEnvironment.cpp
+++ b/BasicUsageEnvironment/BasicUsageEnvironment.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Basic Usage Environment: for a simple, non-scripted, console application
 // Implementation
 
diff --git a/BasicUsageEnvironment/BasicUsageEnvironment0.cpp b/BasicUsageEnvironment/BasicUsageEnvironment0.cpp
index 473b409..6474f57 100644
--- a/BasicUsageEnvironment/BasicUsageEnvironment0.cpp
+++ b/BasicUsageEnvironment/BasicUsageEnvironment0.cpp
@@ -13,12 +13,16 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Basic Usage Environment: for a simple, non-scripted, console application
 // Implementation
 
 #include "BasicUsageEnvironment0.hh"
 #include <stdio.h>
+#if defined(__WIN32__) || defined(_WIN32) || defined(_WIN32_WCE)
+#define snprintf _snprintf
+#endif
+
 
 ////////// BasicUsageEnvironment //////////
 
@@ -62,11 +66,25 @@ void BasicUsageEnvironment0::setResultMsg(MsgString msg1, MsgString msg2,
 void BasicUsageEnvironment0::setResultErrMsg(MsgString msg, int err) {
   setResultMsg(msg);
 
-#ifndef _WIN32_WCE
-  appendToResultMsg(strerror(err == 0 ? getErrno() : err));
+  if (err == 0) err = getErrno();
+#if defined(__WIN32__) || defined(_WIN32) || defined(_WIN32_WCE)
+  char errMsg[RESULT_MSG_BUFFER_MAX] = "\0";
+  if (0 != FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, errMsg, sizeof(errMsg)/sizeof(errMsg[0]), NULL)) {
+    // Remove all trailing '\r', '\n' and '.'
+    for (char* p = errMsg + strlen(errMsg); p != errMsg && (*p == '\r' || *p == '\n' || *p == '.' || *p == '\0'); --p) {
+      *p = '\0';
+    }
+  } else
+    snprintf(errMsg, sizeof(errMsg)/sizeof(errMsg[0]), "error %d", err);
+  appendToResultMsg(errMsg);
+#else
+  appendToResultMsg(strerror(err));
 #endif
 }
 
+
+
+
 void BasicUsageEnvironment0::appendToResultMsg(MsgString msg) {
   char* curPtr = &fResultMsgBuffer[fCurBufferSize];
   unsigned spaceAvailable = fBufferMaxSize - fCurBufferSize;
diff --git a/BasicUsageEnvironment/DelayQueue.cpp b/BasicUsageEnvironment/DelayQueue.cpp
index 8e5c366..17ec6f9 100644
--- a/BasicUsageEnvironment/DelayQueue.cpp
+++ b/BasicUsageEnvironment/DelayQueue.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 //	Help by Carlo Bonamico to get working for Windows
 // Delay queue
 // Implementation
@@ -200,7 +200,7 @@ DelayQueueEntry* DelayQueue::findEntryByToken(intptr_t tokenToFind) {
 
 void DelayQueue::synchronize() {
   // First, figure out how much time has elapsed since the last sync:
-  EventTime timeNow = TimeNow();
+  _EventTime timeNow = TimeNow();
   if (timeNow < fLastSyncTime) {
     // The system clock has apparently gone back in time; reset our sync time and return:
     fLastSyncTime  = timeNow;
@@ -220,14 +220,14 @@ void DelayQueue::synchronize() {
 }
 
 
-///// EventTime /////
+///// _EventTime /////
 
-EventTime TimeNow() {
+_EventTime TimeNow() {
   struct timeval tvNow;
 
   gettimeofday(&tvNow, NULL);
 
-  return EventTime(tvNow.tv_sec, tvNow.tv_usec);
+  return _EventTime(tvNow.tv_sec, tvNow.tv_usec);
 }
 
-const EventTime THE_END_OF_TIME(INT_MAX);
+const _EventTime THE_END_OF_TIME(INT_MAX);
diff --git a/BasicUsageEnvironment/Makefile.tail b/BasicUsageEnvironment/Makefile.tail
index 84de5df..9b2f6dc 100644
--- a/BasicUsageEnvironment/Makefile.tail
+++ b/BasicUsageEnvironment/Makefile.tail
@@ -37,7 +37,7 @@ install1: libBasicUsageEnvironment.$(LIB_SUFFIX)
 	  install -m 644 include/*.hh $(DESTDIR)$(PREFIX)/include/BasicUsageEnvironment
 	  install -m 644 libBasicUsageEnvironment.$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)
 install_shared_libraries: libBasicUsageEnvironment.$(LIB_SUFFIX)
-	  ln -s $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).$(SHORT_LIB_SUFFIX)
-	  ln -s $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).so
+	  ln -fs $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).$(SHORT_LIB_SUFFIX)
+	  ln -fs $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).so
 
 ##### Any additional, platform-specific rules come here:
diff --git a/BasicUsageEnvironment/include/BasicHashTable.hh b/BasicUsageEnvironment/include/BasicHashTable.hh
index 8e56a23..7b59984 100644
--- a/BasicUsageEnvironment/include/BasicHashTable.hh
+++ b/BasicUsageEnvironment/include/BasicHashTable.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Basic Hash Table implementation
 // C++ header
 
diff --git a/BasicUsageEnvironment/include/BasicUsageEnvironment.hh b/BasicUsageEnvironment/include/BasicUsageEnvironment.hh
index 1d68da3..da856f7 100644
--- a/BasicUsageEnvironment/include/BasicUsageEnvironment.hh
+++ b/BasicUsageEnvironment/include/BasicUsageEnvironment.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Basic Usage Environment: for a simple, non-scripted, console application
 // C++ header
 
@@ -75,6 +75,12 @@ protected:
   fd_set fReadSet;
   fd_set fWriteSet;
   fd_set fExceptionSet;
+
+private:
+#if defined(__WIN32__) || defined(_WIN32)
+  // Hack to work around a bug in Windows' "select()" implementation:
+  int fDummySocketNum;
+#endif
 };
 
 #endif
diff --git a/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh b/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh
index d64d8c8..c2a1ded 100644
--- a/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh
+++ b/BasicUsageEnvironment/include/BasicUsageEnvironment0.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Basic Usage Environment: for a simple, non-scripted, console application
 // C++ header
 
@@ -86,7 +86,7 @@ public:
 				void* clientData);
   virtual void unscheduleDelayedTask(TaskToken& prevTask);
 
-  virtual void doEventLoop(char* watchVariable);
+  virtual void doEventLoop(char volatile* watchVariable);
 
   virtual EventTriggerId createEventTrigger(TaskFunc* eventHandlerProc);
   virtual void deleteEventTrigger(EventTriggerId eventTriggerId);
@@ -104,7 +104,8 @@ protected:
   int fLastHandledSocketNum;
 
   // To implement event triggers:
-  EventTriggerId fTriggersAwaitingHandling, fLastUsedTriggerMask; // implemented as 32-bit bitmaps
+  EventTriggerId volatile fTriggersAwaitingHandling; // implemented as a 32-bit bitmap
+  EventTriggerId fLastUsedTriggerMask; // implemented as a 32-bit bitmap
   TaskFunc* fTriggeredEventHandlers[MAX_NUM_EVENT_TRIGGERS];
   void* fTriggeredEventClientDatas[MAX_NUM_EVENT_TRIGGERS];
   unsigned fLastUsedTriggerNum; // in the range [0,MAX_NUM_EVENT_TRIGGERS)
diff --git a/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh b/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh
index db74262..f06fee1 100644
--- a/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh
+++ b/BasicUsageEnvironment/include/BasicUsageEnvironment_version.hh
@@ -1,10 +1,10 @@
 // Version information for the "BasicUsageEnvironment" library
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2015 Live Networks, Inc.  All rights reserved.
 
 #ifndef _BASICUSAGEENVIRONMENT_VERSION_HH
 #define _BASICUSAGEENVIRONMENT_VERSION_HH
 
-#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_STRING	"2014.01.13"
-#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_INT		1389571200
+#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_STRING	"2015.12.22"
+#define BASICUSAGEENVIRONMENT_LIBRARY_VERSION_INT		1450742400
 
 #endif
diff --git a/BasicUsageEnvironment/include/DelayQueue.hh b/BasicUsageEnvironment/include/DelayQueue.hh
index f6bb2ed..8ad1a3d 100644
--- a/BasicUsageEnvironment/include/DelayQueue.hh
+++ b/BasicUsageEnvironment/include/DelayQueue.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
- // Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+ // Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // Delay queue
 // C++ header
 
@@ -115,19 +115,19 @@ extern DelayInterval const DELAY_MINUTE;
 extern DelayInterval const DELAY_HOUR;
 extern DelayInterval const DELAY_DAY;
 
-///// EventTime /////
+///// _EventTime /////
 
-class EventTime: public Timeval {
+class _EventTime: public Timeval {
 public:
-  EventTime(unsigned secondsSinceEpoch = 0,
+  _EventTime(unsigned secondsSinceEpoch = 0,
 	    unsigned usecondsSinceEpoch = 0)
     // We use the Unix standard epoch: January 1, 1970
     : Timeval(secondsSinceEpoch, usecondsSinceEpoch) {}
 };
 
-EventTime TimeNow();
+_EventTime TimeNow();
 
-extern EventTime const THE_END_OF_TIME;
+extern _EventTime const THE_END_OF_TIME;
 
 
 ///// DelayQueueEntry /////
@@ -176,7 +176,7 @@ private:
   DelayQueueEntry* findEntryByToken(intptr_t token);
   void synchronize(); // bring the 'time remaining' fields up-to-date
 
-  EventTime fLastSyncTime;
+  _EventTime fLastSyncTime;
 };
 
 #endif
diff --git a/BasicUsageEnvironment/include/HandlerSet.hh b/BasicUsageEnvironment/include/HandlerSet.hh
index dbd00e3..63d5c7c 100644
--- a/BasicUsageEnvironment/include/HandlerSet.hh
+++ b/BasicUsageEnvironment/include/HandlerSet.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Basic Usage Environment: for a simple, non-scripted, console application
 // C++ header
 
diff --git a/UsageEnvironment/HashTable.cpp b/UsageEnvironment/HashTable.cpp
index 63fc592..77302d1 100644
--- a/UsageEnvironment/HashTable.cpp
+++ b/UsageEnvironment/HashTable.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Generic Hash Table
 // Implementation
 
diff --git a/UsageEnvironment/Makefile.tail b/UsageEnvironment/Makefile.tail
index 96c6227..a7c23df 100644
--- a/UsageEnvironment/Makefile.tail
+++ b/UsageEnvironment/Makefile.tail
@@ -31,7 +31,7 @@ install1: $(USAGE_ENVIRONMENT_LIB)
 	  install -m 644 include/*.hh $(DESTDIR)$(PREFIX)/include/UsageEnvironment
 	  install -m 644 $(USAGE_ENVIRONMENT_LIB) $(DESTDIR)$(LIBDIR)
 install_shared_libraries: $(USAGE_ENVIRONMENT_LIB)
-	  ln -s $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).$(SHORT_LIB_SUFFIX)
-	  ln -s $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).so
+	  ln -fs $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).$(SHORT_LIB_SUFFIX)
+	  ln -fs $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).so
 
 ##### Any additional, platform-specific rules come here:
diff --git a/UsageEnvironment/UsageEnvironment.cpp b/UsageEnvironment/UsageEnvironment.cpp
index 2db4a68..1337718 100644
--- a/UsageEnvironment/UsageEnvironment.cpp
+++ b/UsageEnvironment/UsageEnvironment.cpp
@@ -13,15 +13,20 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Usage Environment
 // Implementation
 
 #include "UsageEnvironment.hh"
 
-void UsageEnvironment::reclaim() {
+Boolean UsageEnvironment::reclaim() {
   // We delete ourselves only if we have no remainining state:
-  if (liveMediaPriv == NULL && groupsockPriv == NULL) delete this;
+  if (liveMediaPriv == NULL && groupsockPriv == NULL) {
+    delete this;
+    return True;
+  }
+
+  return False;
 }
 
 UsageEnvironment::UsageEnvironment(TaskScheduler& scheduler)
diff --git a/UsageEnvironment/include/HashTable.hh b/UsageEnvironment/include/HashTable.hh
index 840628d..a0d59c2 100644
--- a/UsageEnvironment/include/HashTable.hh
+++ b/UsageEnvironment/include/HashTable.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Generic Hash Table
 // C++ header
 
diff --git a/UsageEnvironment/include/UsageEnvironment.hh b/UsageEnvironment/include/UsageEnvironment.hh
index ad59669..2ff3469 100644
--- a/UsageEnvironment/include/UsageEnvironment.hh
+++ b/UsageEnvironment/include/UsageEnvironment.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Usage Environment
 // C++ header
 
@@ -52,7 +52,8 @@ class TaskScheduler; // forward
 
 class UsageEnvironment {
 public:
-  void reclaim();
+  Boolean reclaim();
+      // returns True iff we were actually able to delete our object
 
   // task scheduler:
   TaskScheduler& taskScheduler() const {return fScheduler;}
@@ -136,7 +137,7 @@ public:
   virtual void moveSocketHandling(int oldSocketNum, int newSocketNum) = 0;
         // Changes any socket handling for "oldSocketNum" so that occurs with "newSocketNum" instead.
 
-  virtual void doEventLoop(char* watchVariable = NULL) = 0;
+  virtual void doEventLoop(char volatile* watchVariable = NULL) = 0;
       // Causes further execution to take place within the event loop.
       // Delayed tasks, background I/O handling, and other events are handled, sequentially (as a single thread of control).
       // (If "watchVariable" is not NULL, then we return from this routine when *watchVariable != 0)
diff --git a/UsageEnvironment/include/UsageEnvironment_version.hh b/UsageEnvironment/include/UsageEnvironment_version.hh
index 40cfc5a..6179818 100644
--- a/UsageEnvironment/include/UsageEnvironment_version.hh
+++ b/UsageEnvironment/include/UsageEnvironment_version.hh
@@ -1,10 +1,10 @@
 // Version information for the "UsageEnvironment" library
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2015 Live Networks, Inc.  All rights reserved.
 
 #ifndef _USAGEENVIRONMENT_VERSION_HH
 #define _USAGEENVIRONMENT_VERSION_HH
 
-#define USAGEENVIRONMENT_LIBRARY_VERSION_STRING	"2014.01.13"
-#define USAGEENVIRONMENT_LIBRARY_VERSION_INT		1389571200
+#define USAGEENVIRONMENT_LIBRARY_VERSION_STRING	"2015.12.22"
+#define USAGEENVIRONMENT_LIBRARY_VERSION_INT		1450742400
 
 #endif
diff --git a/UsageEnvironment/include/strDup.hh b/UsageEnvironment/include/strDup.hh
index d735a56..4ed8151 100644
--- a/UsageEnvironment/include/strDup.hh
+++ b/UsageEnvironment/include/strDup.hh
@@ -17,11 +17,13 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #ifndef _STRDUP_HH
 #define _STRDUP_HH
 
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A C++ equivalent to the standard C routine "strdup()".
 // This generates a char* that can be deleted using "delete[]"
 // Header
 
+#include <string.h>
+
 char* strDup(char const* str);
 // Note: strDup(NULL) returns NULL
 
@@ -29,4 +31,7 @@ char* strDupSize(char const* str);
 // Like "strDup()", except that it *doesn't* copy the original.
 // (Instead, it just allocates a string of the same size as the original.)
 
+char* strDupSize(char const* str, size_t& resultBufSize);
+// An alternative form of "strDupSize()" that also returns the size of the allocated buffer.
+
 #endif
diff --git a/UsageEnvironment/strDup.cpp b/UsageEnvironment/strDup.cpp
index b0d34a8..f2860d3 100644
--- a/UsageEnvironment/strDup.cpp
+++ b/UsageEnvironment/strDup.cpp
@@ -13,13 +13,12 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A C++ equivalent to the standard C routine "strdup()".
 // This generates a char* that can be deleted using "delete[]"
 // Implementation
 
 #include "strDup.hh"
-#include "string.h"
 
 char* strDup(char const* str) {
   if (str == NULL) return NULL;
@@ -33,10 +32,19 @@ char* strDup(char const* str) {
 }
 
 char* strDupSize(char const* str) {
-  if (str == NULL) return NULL;
-  size_t len = strlen(str) + 1;
-  char* copy = new char[len];
+  size_t dummy;
 
-  return copy;
+  return strDupSize(str, dummy);
 }
 
+char* strDupSize(char const* str, size_t& resultBufSize) {
+  if (str == NULL) {
+    resultBufSize = 0;
+    return NULL;
+  }
+
+  resultBufSize = strlen(str) + 1;
+  char* copy = new char[resultBufSize];
+
+  return copy;
+}
diff --git a/WindowsAudioInputDevice/WindowsAudioInputDevice_common.hh b/WindowsAudioInputDevice/WindowsAudioInputDevice_common.hh
index d3dc510..31d23f3 100644
--- a/WindowsAudioInputDevice/WindowsAudioInputDevice_common.hh
+++ b/WindowsAudioInputDevice/WindowsAudioInputDevice_common.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Windows implementation of a generic audio input device
 // Base class for both library versions:
 //     One that uses Windows' built-in software mixer; another that doesn't.
diff --git a/WindowsAudioInputDevice/WindowsAudioInputDevice_mixer.hh b/WindowsAudioInputDevice/WindowsAudioInputDevice_mixer.hh
index b009ef1..57ef0ac 100644
--- a/WindowsAudioInputDevice/WindowsAudioInputDevice_mixer.hh
+++ b/WindowsAudioInputDevice/WindowsAudioInputDevice_mixer.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Windows implementation of a generic audio input device
 // This version uses Windows' built-in software mixer.
 // C++ header
diff --git a/WindowsAudioInputDevice/WindowsAudioInputDevice_noMixer.hh b/WindowsAudioInputDevice/WindowsAudioInputDevice_noMixer.hh
index 6b67856..784c9f1 100644
--- a/WindowsAudioInputDevice/WindowsAudioInputDevice_noMixer.hh
+++ b/WindowsAudioInputDevice/WindowsAudioInputDevice_noMixer.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Windows implementation of a generic audio input device
 // This version does not use Windows' built-in software mixer.
 // C++ header
diff --git a/WindowsAudioInputDevice/showAudioInputPorts.cpp b/WindowsAudioInputDevice/showAudioInputPorts.cpp
index c879ffe..15a2d19 100644
--- a/WindowsAudioInputDevice/showAudioInputPorts.cpp
+++ b/WindowsAudioInputDevice/showAudioInputPorts.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A program that prints out this computer's audio input ports
 
 #include "AudioInputDevice.hh"
diff --git a/config.iphone-simulator b/config.iphone-simulator
index 3401d59..6fb40fd 100644
--- a/config.iphone-simulator
+++ b/config.iphone-simulator
@@ -1,22 +1,26 @@
-# Change the following version number, if necessary, before running "genMakefiles iphoneos"
-IOS_VERSION =		6.1
+# **Note: You must install the relevant "Command line tools (OSX *.*) for Xcode - Xcode *.*"
+# for this configuration file to work.
 
-DEVELOPER_PATH =	/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer
-TOOL_PATH =		$(DEVELOPER_PATH)/usr/bin
-SDK_PATH =		$(DEVELOPER_PATH)/SDKs
-SDK =	 		$(SDK_PATH)/iPhoneSimulator$(IOS_VERSION).sdk
-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 i386 --sysroot=$(SDK)
+# Change the following version number, if necessary, before running "genMakefiles iphone-simulator"
+IOS_VERSION = 8.3
+MIN_IOS_VERSION =  7.0
+
+DEVELOPER_PATH = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer
+TOOL_PATH = $(DEVELOPER_PATH)/usr/bin
+SDK_PATH = $(DEVELOPER_PATH)/SDKs
+SDK = $(SDK_PATH)/iPhoneSimulator$(IOS_VERSION).sdk
+COMPILE_OPTS =          $(INCLUDES) -I. $(EXTRA_LDFLAGS) -DBSD=1 -O2 -DSOCKLEN_T=socklen_t -DHAVE_SOCKADDR_LEN=1 -miphoneos-version-min=$(MIN_IOS_VERSION) -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -fPIC -arch i386 --sysroot=$(SDK) -isysroot $(SDK)
 C =                     c
-C_COMPILER =            $(TOOL_PATH)/gcc
+C_COMPILER =            /usr/bin/xcrun clang
 C_FLAGS =               $(COMPILE_OPTS)
 CPP =                   cpp
-CPLUSPLUS_COMPILER =    $(TOOL_PATH)/g++
+CPLUSPLUS_COMPILER =    /usr/bin/xcrun clang
 CPLUSPLUS_FLAGS =       $(COMPILE_OPTS) -Wall
 OBJ =                   o
-LINK =                  $(TOOL_PATH)/g++ -o 
-LINK_OPTS =             -L. -arch i386 --sysroot=$(SDK) -L$(SDK)/usr/lib/system
+LINK =                  /usr/bin/xcrun clang -o
+LINK_OPTS =             -L. -arch i386 -miphoneos-version-min=$(MIN_IOS_VERSION) --sysroot=$(SDK) -isysroot -L$(SDK)/usr/lib/system -I$(SDK)/usr/lib /usr/lib/libc++.dylib
 CONSOLE_LINK_OPTS =     $(LINK_OPTS)
-LIBRARY_LINK =          libtool -s -o 
+LIBRARY_LINK =          /usr/bin/xcrun libtool -static -o 
 LIBRARY_LINK_OPTS =
 LIB_SUFFIX =            a
 LIBS_FOR_CONSOLE_APPLICATION =
diff --git a/config.iphoneos b/config.iphoneos
index 33d9af7..c534eed 100644
--- a/config.iphoneos
+++ b/config.iphoneos
@@ -1,24 +1,27 @@
+# **Note: You must install the relevant "Command line tools (OSX *.*) for Xcode - Xcode *.*"
+# for this configuration file to work.
+#
 # Change the following version number, if necessary, before running "genMakefiles iphoneos"
-IOS_VERSION =		6.1
+IOS_VERSION = 8.3
 
-DEVELOPER_PATH =	/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer
-TOOL_PATH =		$(DEVELOPER_PATH)/usr/bin
-SDK_PATH =		$(DEVELOPER_PATH)/SDKs
-SDK =	 		$(SDK_PATH)/iPhoneOS$(IOS_VERSION).sdk
+DEVELOPER_PATH =  /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer
+TOOL_PATH = $(DEVELOPER_PATH)/usr/bin
+SDK_PATH = $(DEVELOPER_PATH)/SDKs
+SDK = $(SDK_PATH)/iPhoneOS$(IOS_VERSION).sdk
 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=$(SDK)
 C =                     c
-C_COMPILER =            $(TOOL_PATH)/gcc
+C_COMPILER =            /usr/bin/xcrun clang
 C_FLAGS =               $(COMPILE_OPTS)
 CPP =                   cpp
-CPLUSPLUS_COMPILER =    $(TOOL_PATH)/g++
+CPLUSPLUS_COMPILER =    /usr/bin/xcrun clang
 CPLUSPLUS_FLAGS =       $(COMPILE_OPTS) -Wall
 OBJ =                   o
-LINK =                  $(TOOL_PATH)/g++ -o 
-LINK_OPTS =             -L. -arch armv7 --sysroot=$(SDK) -L$(SDK)/usr/lib/system
+LINK =                  /usr/bin/xcrun clang -o 
+LINK_OPTS =             -v -L. -arch armv7 --sysroot=$(SDK) -L$(SDK)/usr/lib/system /usr/lib/libc++.dylib
 CONSOLE_LINK_OPTS =     $(LINK_OPTS)
-LIBRARY_LINK =          libtool -s -o 
+LIBRARY_LINK =          /usr/bin/xcrun libtool -static -o 
 LIBRARY_LINK_OPTS =
 LIB_SUFFIX =            a
 LIBS_FOR_CONSOLE_APPLICATION =
 LIBS_FOR_GUI_APPLICATION =
-EXE =
+EXE = 
diff --git a/config.linux-with-shared-libraries b/config.linux-with-shared-libraries
index 687ac77..cc8e64f 100644
--- a/config.linux-with-shared-libraries
+++ b/config.linux-with-shared-libraries
@@ -3,39 +3,39 @@
 # At least one interface changes, or is removed => CURRENT += 1; REVISION = 0; AGE = 0
 # One or more interfaces were added, but no existing interfaces were changed or removed => CURRENT += 1; REVISION = 0; AGE += 1
 
-libliveMedia_VERSION_CURRENT=24
-libliveMedia_VERSION_REVISION=0
+libliveMedia_VERSION_CURRENT=51
+libliveMedia_VERSION_REVISION=4
 libliveMedia_VERSION_AGE=1
 libliveMedia_LIB_SUFFIX=so.$(shell expr $(libliveMedia_VERSION_CURRENT) - $(libliveMedia_VERSION_AGE)).$(libliveMedia_VERSION_AGE).$(libliveMedia_VERSION_REVISION)
 
-libBasicUsageEnvironment_VERSION_CURRENT=0
-libBasicUsageEnvironment_VERSION_REVISION=2
+libBasicUsageEnvironment_VERSION_CURRENT=1
+libBasicUsageEnvironment_VERSION_REVISION=0
 libBasicUsageEnvironment_VERSION_AGE=0
 libBasicUsageEnvironment_LIB_SUFFIX=so.$(shell expr $(libBasicUsageEnvironment_VERSION_CURRENT) - $(libBasicUsageEnvironment_VERSION_AGE)).$(libBasicUsageEnvironment_VERSION_AGE).$(libBasicUsageEnvironment_VERSION_REVISION)
 
-libUsageEnvironment_VERSION_CURRENT=1
+libUsageEnvironment_VERSION_CURRENT=4
 libUsageEnvironment_VERSION_REVISION=0
-libUsageEnvironment_VERSION_AGE=0
+libUsageEnvironment_VERSION_AGE=1
 libUsageEnvironment_LIB_SUFFIX=so.$(shell expr $(libUsageEnvironment_VERSION_CURRENT) - $(libUsageEnvironment_VERSION_AGE)).$(libUsageEnvironment_VERSION_AGE).$(libUsageEnvironment_VERSION_REVISION)
 
-libgroupsock_VERSION_CURRENT=1
-libgroupsock_VERSION_REVISION=4
+libgroupsock_VERSION_CURRENT=8
+libgroupsock_VERSION_REVISION=0
 libgroupsock_VERSION_AGE=0
 libgroupsock_LIB_SUFFIX=so.$(shell expr $(libgroupsock_VERSION_CURRENT) - $(libgroupsock_VERSION_AGE)).$(libgroupsock_VERSION_AGE).$(libgroupsock_VERSION_REVISION)
 #####
 
 COMPILE_OPTS =		$(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -fPIC
 C =			c
-C_COMPILER =		cc
+C_COMPILER =		$(CC)
 C_FLAGS =		$(COMPILE_OPTS) $(CPPFLAGS) $(CFLAGS)
 CPP =			cpp
-CPLUSPLUS_COMPILER =	c++
+CPLUSPLUS_COMPILER =	$(CXX)
 CPLUSPLUS_FLAGS =	$(COMPILE_OPTS) -Wall -DBSD=1 $(CPPFLAGS) $(CXXFLAGS)
 OBJ =			o
-LINK =			c++ -o
+LINK =			$(CXX) -o
 LINK_OPTS =		-L. $(LDFLAGS)
 CONSOLE_LINK_OPTS =	$(LINK_OPTS)
-LIBRARY_LINK =		gcc -o 
+LIBRARY_LINK =		$(CC) -o 
 SHORT_LIB_SUFFIX =	so.$(shell expr $($(NAME)_VERSION_CURRENT) - $($(NAME)_VERSION_AGE))
 LIB_SUFFIX =	 	$(SHORT_LIB_SUFFIX).$($(NAME)_VERSION_AGE).$($(NAME)_VERSION_REVISION)
 LIBRARY_LINK_OPTS =	-shared -Wl,-soname,$(NAME).$(SHORT_LIB_SUFFIX) $(LDFLAGS)
diff --git a/config.solaris-32bit b/config.solaris-32bit
index 3f02c67..0229773 100644
--- a/config.solaris-32bit
+++ b/config.solaris-32bit
@@ -1,4 +1,4 @@
-COMPILE_OPTS =		$(INCLUDES) -I. -O -DSOLARIS -DSOCKLEN_T=socklen_t
+COMPILE_OPTS =		$(INCLUDES) -I. -O -DSOLARIS -DXLOCALE_NOT_USED -DSOCKLEN_T=socklen_t
 C =			c
 C_COMPILER =		cc
 C_FLAGS =		$(COMPILE_OPTS)
diff --git a/config.solaris-64bit b/config.solaris-64bit
index 60ec3b4..9e8258e 100644
--- a/config.solaris-64bit
+++ b/config.solaris-64bit
@@ -1,4 +1,4 @@
-COMPILE_OPTS =          $(INCLUDES) -m64 -I. -O -DSOLARIS -DSOCKLEN_T=socklen_t
+COMPILE_OPTS =          $(INCLUDES) -m64 -I. -O -DSOLARIS -DXLOCALE_NOT_USED -DSOCKLEN_T=socklen_t
 C =                     c
 C_COMPILER =            cc
 C_FLAGS =               $(COMPILE_OPTS)
diff --git a/groupsock/GroupEId.cpp b/groupsock/GroupEId.cpp
index 686e547..6a56baa 100644
--- a/groupsock/GroupEId.cpp
+++ b/groupsock/GroupEId.cpp
@@ -13,92 +13,37 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // "Group Endpoint Id"
 // Implementation
 
 #include "GroupEId.hh"
-#include "strDup.hh"
-#include <string.h>
 
-////////// Scope //////////
-
-void Scope::assign(u_int8_t ttl, const char* publicKey) {
-  fTTL = ttl;
-
-  fPublicKey = strDup(publicKey == NULL ? "nokey" : publicKey);
-}
-
-void Scope::clean() {
-  delete[] fPublicKey;
-  fPublicKey = NULL;
-}
-
-
-Scope::Scope(u_int8_t ttl, const char* publicKey) {
-  assign(ttl, publicKey);
-}
-
-Scope::Scope(const Scope& orig) {
-  assign(orig.ttl(), orig.publicKey());
-}
-
-Scope& Scope::operator=(const Scope& rightSide) {
-  if (&rightSide != this) {
-    if (publicKey() == NULL
-	|| strcmp(publicKey(), rightSide.publicKey()) != 0) {
-      clean();
-      assign(rightSide.ttl(), rightSide.publicKey());
-    } else { // need to assign TTL only
-      fTTL = rightSide.ttl();
-    }
-  }
-
-  return *this;
-}
-
-Scope::~Scope() {
-  clean();
-}
-
-unsigned Scope::publicKeySize() const {
-  return fPublicKey == NULL ? 0 : strlen(fPublicKey);
-}
-
-////////// GroupEId //////////
 
 GroupEId::GroupEId(struct in_addr const& groupAddr,
-		   portNumBits portNum, Scope const& scope,
-		   unsigned numSuccessiveGroupAddrs) {
+		   portNumBits portNum, u_int8_t ttl) {
   struct in_addr sourceFilterAddr;
   sourceFilterAddr.s_addr = ~0; // indicates no source filter
 
-  init(groupAddr, sourceFilterAddr, portNum, scope, numSuccessiveGroupAddrs);
+  init(groupAddr, sourceFilterAddr, portNum, ttl);
 }
 
 GroupEId::GroupEId(struct in_addr const& groupAddr,
 		   struct in_addr const& sourceFilterAddr,
-		   portNumBits portNum,
-		   unsigned numSuccessiveGroupAddrs) {
-  init(groupAddr, sourceFilterAddr, portNum, 255, numSuccessiveGroupAddrs);
-}
-
-GroupEId::GroupEId() {
+		   portNumBits portNum) {
+  init(groupAddr, sourceFilterAddr, portNum, 255);
 }
 
 Boolean GroupEId::isSSM() const {
   return fSourceFilterAddress.s_addr != netAddressBits(~0);
 }
 
-
 void GroupEId::init(struct in_addr const& groupAddr,
 		    struct in_addr const& sourceFilterAddr,
 		    portNumBits portNum,
-		    Scope const& scope,
-		    unsigned numSuccessiveGroupAddrs) {
+		    u_int8_t ttl) {
   fGroupAddress = groupAddr;
   fSourceFilterAddress = sourceFilterAddr;
-  fNumSuccessiveGroupAddrs = numSuccessiveGroupAddrs;
   fPortNum = portNum;
-  fScope = scope;
+  fTTL = ttl;
 }
diff --git a/groupsock/Groupsock.cpp b/groupsock/Groupsock.cpp
index 196b5b5..c59fd3e 100644
--- a/groupsock/Groupsock.cpp
+++ b/groupsock/Groupsock.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // 'Group sockets'
 // Implementation
 
@@ -31,29 +31,27 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 
 OutputSocket::OutputSocket(UsageEnvironment& env)
   : Socket(env, 0 /* let kernel choose port */),
-    fSourcePort(0), fLastSentTTL(0) {
+    fSourcePort(0), fLastSentTTL(256/*hack: a deliberately invalid value*/) {
 }
 
 OutputSocket::OutputSocket(UsageEnvironment& env, Port port)
   : Socket(env, port),
-    fSourcePort(0), fLastSentTTL(0) {
+    fSourcePort(0), fLastSentTTL(256/*hack: a deliberately invalid value*/) {
 }
 
 OutputSocket::~OutputSocket() {
 }
 
-Boolean OutputSocket::write(netAddressBits address, Port port, u_int8_t ttl,
+Boolean OutputSocket::write(netAddressBits address, portNumBits portNum, u_int8_t ttl,
 			    unsigned char* buffer, unsigned bufferSize) {
-  if (ttl == fLastSentTTL) {
-    // Optimization: So we don't do a 'set TTL' system call again
-    ttl = 0;
+  struct in_addr destAddr; destAddr.s_addr = address;
+  if ((unsigned)ttl == fLastSentTTL) {
+    // Optimization: Don't do a 'set TTL' system call again
+    if (!writeSocket(env(), socketNum(), destAddr, portNum, buffer, bufferSize)) return False;
   } else {
-    fLastSentTTL = ttl;
+    if (!writeSocket(env(), socketNum(), destAddr, portNum, ttl, buffer, bufferSize)) return False;
+    fLastSentTTL = (unsigned)ttl;
   }
-  struct in_addr destAddr; destAddr.s_addr = address;
-  if (!writeSocket(env(), socketNum(), destAddr, port, ttl,
-		   buffer, bufferSize))
-    return False;
 
   if (sourcePortNum() == 0) {
     // Now that we've sent a packet, we can find out what the
@@ -73,7 +71,7 @@ Boolean OutputSocket::write(netAddressBits address, Port port, u_int8_t ttl,
 // By default, we don't do reads:
 Boolean OutputSocket
 ::handleRead(unsigned char* /*buffer*/, unsigned /*bufferMaxSize*/,
-	     unsigned& /*bytesRead*/, struct sockaddr_in& /*fromAddress*/) {
+	     unsigned& /*bytesRead*/, struct sockaddr_in& /*fromAddressAndPort*/) {
   return True;
 }
 
@@ -81,9 +79,9 @@ Boolean OutputSocket
 ///////// destRecord //////////
 
 destRecord
-::destRecord(struct in_addr const& addr, Port const& port, u_int8_t ttl,
+::destRecord(struct in_addr const& addr, Port const& port, u_int8_t ttl, unsigned sessionId,
 	     destRecord* next)
-  : fNext(next), fGroupEId(addr, port.num(), ttl), fPort(port) {
+  : fNext(next), fGroupEId(addr, port.num(), ttl), fSessionId(sessionId) {
 }
 
 destRecord::~destRecord() {
@@ -103,8 +101,8 @@ Groupsock::Groupsock(UsageEnvironment& env, struct in_addr const& groupAddr,
 		     Port port, u_int8_t ttl)
   : OutputSocket(env, port),
     deleteIfNoMembers(False), isSlave(False),
-    fIncomingGroupEId(groupAddr, port.num(), ttl), fDests(NULL), fTTL(ttl) {
-  addDestination(groupAddr, port);
+    fDests(new destRecord(groupAddr, port, ttl, 0, NULL)),
+    fIncomingGroupEId(groupAddr, port.num(), ttl) {
 
   if (!socketJoinGroup(env, socketNum(), groupAddr.s_addr)) {
     if (DebugLevel >= 1) {
@@ -130,10 +128,8 @@ Groupsock::Groupsock(UsageEnvironment& env, struct in_addr const& groupAddr,
 		     Port port)
   : OutputSocket(env, port),
     deleteIfNoMembers(False), isSlave(False),
-    fIncomingGroupEId(groupAddr, sourceFilterAddr, port.num()),
-    fDests(NULL), fTTL(255) {
-  addDestination(groupAddr, port);
-
+    fDests(new destRecord(groupAddr, port, 255, 0, NULL)),
+    fIncomingGroupEId(groupAddr, sourceFilterAddr, port.num()) {
   // First try a SSM join.  If that fails, try a regular join:
   if (!socketJoinGroupSSM(env, socketNum(), groupAddr.s_addr,
 			  sourceFilterAddr.s_addr)) {
@@ -168,12 +164,26 @@ Groupsock::~Groupsock() {
   if (DebugLevel >= 2) env() << *this << ": deleting\n";
 }
 
+destRecord* Groupsock
+::createNewDestRecord(struct in_addr const& addr, Port const& port, u_int8_t ttl,
+		      unsigned sessionId, destRecord* next) {
+  // Default implementation:
+  return new destRecord(addr, port, ttl, sessionId, next);
+}
+
 void
 Groupsock::changeDestinationParameters(struct in_addr const& newDestAddr,
-				       Port newDestPort, int newDestTTL) {
-  if (fDests == NULL) return;
+				       Port newDestPort, int newDestTTL, unsigned sessionId) {
+  destRecord* dest;
+  for (dest = fDests; dest != NULL && dest->fSessionId != sessionId; dest = dest->fNext) {}
+
+  if (dest == NULL) { // There's no existing 'destRecord' for this "sessionId"; add a new one:
+    fDests = createNewDestRecord(newDestAddr, newDestPort, newDestTTL, sessionId, fDests);
+    return;
+  }
 
-  struct in_addr destAddr = fDests->fGroupEId.groupAddress();
+  // "dest" is an existing 'destRecord' for this "sessionId"; change its values to the new ones:
+  struct in_addr destAddr = dest->fGroupEId.groupAddress();
   if (newDestAddr.s_addr != 0) {
     if (newDestAddr.s_addr != destAddr.s_addr
 	&& IsMulticastAddress(newDestAddr.s_addr)) {
@@ -186,7 +196,7 @@ Groupsock::changeDestinationParameters(struct in_addr const& newDestAddr,
     destAddr.s_addr = newDestAddr.s_addr;
   }
 
-  portNumBits destPortNum = fDests->fGroupEId.portNum();
+  portNumBits destPortNum = dest->fGroupEId.portNum();
   if (newDestPort.num() != 0) {
     if (newDestPort.num() != destPortNum
 	&& IsMulticastAddress(destAddr.s_addr)) {
@@ -196,40 +206,42 @@ Groupsock::changeDestinationParameters(struct in_addr const& newDestAddr,
       socketJoinGroup(env(), socketNum(), destAddr.s_addr);
     }
     destPortNum = newDestPort.num();
-    fDests->fPort = newDestPort;
   }
 
   u_int8_t destTTL = ttl();
   if (newDestTTL != ~0) destTTL = (u_int8_t)newDestTTL;
 
-  fDests->fGroupEId = GroupEId(destAddr, destPortNum, destTTL);
+  dest->fGroupEId = GroupEId(destAddr, destPortNum, destTTL);
+
+  // Finally, remove any other 'destRecord's that might also have this "sessionId":
+  removeDestinationFrom(dest->fNext, sessionId);
 }
 
-void Groupsock::addDestination(struct in_addr const& addr, Port const& port) {
-  // Check whether this destination is already known:
-  for (destRecord* dests = fDests; dests != NULL; dests = dests->fNext) {
-    if (addr.s_addr == dests->fGroupEId.groupAddress().s_addr
-	&& port.num() == dests->fPort.num()) {
-      return;
-    }
-  }
+unsigned Groupsock
+::lookupSessionIdFromDestination(struct sockaddr_in const& destAddrAndPort) const {
+  destRecord* dest = lookupDestRecordFromDestination(destAddrAndPort);
+  if (dest == NULL) return 0;
 
-  fDests = new destRecord(addr, port, ttl(), fDests);
+  return dest->fSessionId;
 }
 
-void Groupsock::removeDestination(struct in_addr const& addr, Port const& port) {
-  for (destRecord** destsPtr = &fDests; *destsPtr != NULL;
-       destsPtr = &((*destsPtr)->fNext)) {
-    if (addr.s_addr == (*destsPtr)->fGroupEId.groupAddress().s_addr
-	&& port.num() == (*destsPtr)->fPort.num()) {
-      // Remove the record pointed to by *destsPtr :
-      destRecord* next = (*destsPtr)->fNext;
-      (*destsPtr)->fNext = NULL;
-      delete (*destsPtr);
-      *destsPtr = next;
+void Groupsock::addDestination(struct in_addr const& addr, Port const& port, unsigned sessionId) {
+  // Default implementation:
+  // If there's no existing 'destRecord' with the same "addr", "port", and "sessionId", add a new one:
+  for (destRecord* dest = fDests; dest != NULL; dest = dest->fNext) {
+    if (sessionId == dest->fSessionId
+	&& addr.s_addr == dest->fGroupEId.groupAddress().s_addr
+	&& port.num() == dest->fGroupEId.portNum()) {
       return;
     }
   }
+  
+  fDests = createNewDestRecord(addr, port, 255, sessionId, fDests);
+}
+
+void Groupsock::removeDestination(unsigned sessionId) {
+  // Default implementation:
+  removeDestinationFrom(fDests, sessionId);
 }
 
 void Groupsock::removeAllDestinations() {
@@ -247,14 +259,13 @@ void Groupsock::multicastSendOnly() {
 #endif
 }
 
-Boolean Groupsock::output(UsageEnvironment& env, u_int8_t ttlToSend,
-			  unsigned char* buffer, unsigned bufferSize,
+Boolean Groupsock::output(UsageEnvironment& env, unsigned char* buffer, unsigned bufferSize,
 			  DirectedNetInterface* interfaceNotToFwdBackTo) {
   do {
     // First, do the datagram send, to each destination:
     Boolean writeSuccess = True;
     for (destRecord* dests = fDests; dests != NULL; dests = dests->fNext) {
-      if (!write(dests->fGroupEId.groupAddress().s_addr, dests->fPort, ttlToSend,
+      if (!write(dests->fGroupEId.groupAddress().s_addr, dests->fGroupEId.portNum(), dests->fGroupEId.ttl(),
 		 buffer, bufferSize)) {
 	writeSuccess = False;
 	break;
@@ -269,14 +280,13 @@ Boolean Groupsock::output(UsageEnvironment& env, u_int8_t ttlToSend,
     if (!members().IsEmpty()) {
       numMembers =
 	outputToAllMembersExcept(interfaceNotToFwdBackTo,
-				 ttlToSend, buffer, bufferSize,
+				 ttl(), buffer, bufferSize,
 				 ourIPAddress(env));
       if (numMembers < 0) break;
     }
 
     if (DebugLevel >= 3) {
-      env << *this << ": wrote " << bufferSize << " bytes, ttl "
-	  << (unsigned)ttlToSend;
+      env << *this << ": wrote " << bufferSize << " bytes, ttl " << (unsigned)ttl();
       if (numMembers > 0) {
 	env << "; relayed to " << numMembers << " members";
       }
@@ -286,14 +296,16 @@ Boolean Groupsock::output(UsageEnvironment& env, u_int8_t ttlToSend,
   } while (0);
 
   if (DebugLevel >= 0) { // this is a fatal error
-    env.setResultMsg("Groupsock write failed: ", env.getResultMsg());
+    UsageEnvironment::MsgString msg = strDup(env.getResultMsg());
+    env.setResultMsg("Groupsock write failed: ", msg);
+    delete[] (char*)msg;
   }
   return False;
 }
 
 Boolean Groupsock::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
 			      unsigned& bytesRead,
-			      struct sockaddr_in& fromAddress) {
+			      struct sockaddr_in& fromAddressAndPort) {
   // Read data from the socket, and relay it across any attached tunnels
   //##### later make this code more general - independent of tunnels
 
@@ -301,18 +313,19 @@ Boolean Groupsock::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
 
   int maxBytesToRead = bufferMaxSize - TunnelEncapsulationTrailerMaxSize;
   int numBytes = readSocket(env(), socketNum(),
-			    buffer, maxBytesToRead, fromAddress);
+			    buffer, maxBytesToRead, fromAddressAndPort);
   if (numBytes < 0) {
     if (DebugLevel >= 0) { // this is a fatal error
-      env().setResultMsg("Groupsock read failed: ",
-			 env().getResultMsg());
+      UsageEnvironment::MsgString msg = strDup(env().getResultMsg());
+      env().setResultMsg("Groupsock read failed: ", msg);
+      delete[] (char*)msg;
     }
     return False;
   }
 
   // If we're a SSM group, make sure the source address matches:
   if (isSSM()
-      && fromAddress.sin_addr.s_addr != sourceFilterAddress().s_addr) {
+      && fromAddressAndPort.sin_addr.s_addr != sourceFilterAddress().s_addr) {
     return True;
   }
 
@@ -322,20 +335,20 @@ Boolean Groupsock::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
   bytesRead = numBytes;
 
   int numMembers = 0;
-  if (!wasLoopedBackFromUs(env(), fromAddress)) {
+  if (!wasLoopedBackFromUs(env(), fromAddressAndPort)) {
     statsIncoming.countPacket(numBytes);
     statsGroupIncoming.countPacket(numBytes);
     numMembers =
       outputToAllMembersExcept(NULL, ttl(),
 			       buffer, bytesRead,
-			       fromAddress.sin_addr.s_addr);
+			       fromAddressAndPort.sin_addr.s_addr);
     if (numMembers > 0) {
       statsRelayedIncoming.countPacket(numBytes);
       statsGroupRelayedIncoming.countPacket(numBytes);
     }
   }
   if (DebugLevel >= 3) {
-    env() << *this << ": read " << bytesRead << " bytes from " << AddressString(fromAddress).val();
+    env() << *this << ": read " << bytesRead << " bytes from " << AddressString(fromAddressAndPort).val() << ", port " << ntohs(fromAddressAndPort.sin_port);
     if (numMembers > 0) {
       env() << "; relayed to " << numMembers << " members";
     }
@@ -346,10 +359,10 @@ Boolean Groupsock::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
 }
 
 Boolean Groupsock::wasLoopedBackFromUs(UsageEnvironment& env,
-				       struct sockaddr_in& fromAddress) {
-  if (fromAddress.sin_addr.s_addr
-      == ourIPAddress(env)) {
-    if (fromAddress.sin_port == sourcePortNum()) {
+				       struct sockaddr_in& fromAddressAndPort) {
+  if (fromAddressAndPort.sin_addr.s_addr == ourIPAddress(env) ||
+      fromAddressAndPort.sin_addr.s_addr == 0x7F000001/*127.0.0.1*/) {
+    if (fromAddressAndPort.sin_port == sourcePortNum()) {
 #ifdef DEBUG_LOOPBACK_CHECKING
       if (DebugLevel >= 3) {
 	env() << *this << ": got looped-back packet\n";
@@ -362,6 +375,32 @@ Boolean Groupsock::wasLoopedBackFromUs(UsageEnvironment& env,
   return False;
 }
 
+destRecord* Groupsock
+::lookupDestRecordFromDestination(struct sockaddr_in const& destAddrAndPort) const {
+  for (destRecord* dest = fDests; dest != NULL; dest = dest->fNext) {
+    if (destAddrAndPort.sin_addr.s_addr == dest->fGroupEId.groupAddress().s_addr
+	&& destAddrAndPort.sin_port == dest->fGroupEId.portNum()) {
+      return dest;
+    }
+  }
+  return NULL;
+}
+
+void Groupsock::removeDestinationFrom(destRecord*& dests, unsigned sessionId) {
+  destRecord** destsPtr = &dests;
+  while (*destsPtr != NULL) {
+    if (sessionId == (*destsPtr)->fSessionId) {
+      // Remove the record pointed to by *destsPtr :
+      destRecord* next = (*destsPtr)->fNext;
+      (*destsPtr)->fNext = NULL;
+      delete (*destsPtr);
+      *destsPtr = next;
+    } else {
+      destsPtr = &((*destsPtr)->fNext);
+    }
+  }
+}
+
 int Groupsock::outputToAllMembersExcept(DirectedNetInterface* exceptInterface,
 					u_int8_t ttlToFwd,
 					unsigned char* data, unsigned size,
@@ -420,7 +459,8 @@ int Groupsock::outputToAllMembersExcept(DirectedNetInterface* exceptInterface,
 
       if (fDests != NULL) {
 	trailer->address() = fDests->fGroupEId.groupAddress().s_addr;
-	trailer->port() = fDests->fPort; // structure copy, outputs in network order
+	Port destPort(ntohs(fDests->fGroupEId.portNum()));
+	trailer->port() = destPort; // structure copy
       }
       trailer->ttl() = ttlToFwd;
       trailer->command() = tunnelCmd;
@@ -514,9 +554,7 @@ static Boolean setGroupsockBySocket(UsageEnvironment& env, int sock,
       = (sockets->Lookup((char*)(long)sock) != 0);
     if (alreadyExists) {
       char buf[100];
-      sprintf(buf,
-	      "Attempting to replace an existing socket (%d",
-	      sock);
+      sprintf(buf, "Attempting to replace an existing socket (%d)", sock);
       env.setResultMsg(buf);
       break;
     }
diff --git a/groupsock/GroupsockHelper.cpp b/groupsock/GroupsockHelper.cpp
index 856b9c1..bfea860 100644
--- a/groupsock/GroupsockHelper.cpp
+++ b/groupsock/GroupsockHelper.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Helper routines to implement 'group sockets'
 // Implementation
 
@@ -29,6 +29,11 @@ extern "C" int initializeWinsockIfNecessary();
 #include <fcntl.h>
 #define initializeWinsockIfNecessary() 1
 #endif
+#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
+#else
+#include <signal.h>
+#define USE_SIGNALS 1
+#endif
 #include <stdio.h>
 
 // By default, use INADDR_ANY for the sending and receiving interfaces:
@@ -181,17 +186,29 @@ Boolean makeSocketNonBlocking(int sock) {
 #endif
 }
 
-Boolean makeSocketBlocking(int sock) {
+Boolean makeSocketBlocking(int sock, unsigned writeTimeoutInMilliseconds) {
+  Boolean result;
 #if defined(__WIN32__) || defined(_WIN32)
   unsigned long arg = 0;
-  return ioctlsocket(sock, FIONBIO, &arg) == 0;
+  result = ioctlsocket(sock, FIONBIO, &arg) == 0;
 #elif defined(VXWORKS)
   int arg = 0;
-  return ioctl(sock, FIONBIO, (int)&arg) == 0;
+  result = ioctl(sock, FIONBIO, (int)&arg) == 0;
 #else
   int curFlags = fcntl(sock, F_GETFL, 0);
-  return fcntl(sock, F_SETFL, curFlags&(~O_NONBLOCK)) >= 0;
+  result = fcntl(sock, F_SETFL, curFlags&(~O_NONBLOCK)) >= 0;
+#endif
+
+  if (writeTimeoutInMilliseconds > 0) {
+#ifdef SO_SNDTIMEO
+    struct timeval tv;
+    tv.tv_sec = writeTimeoutInMilliseconds/1000;
+    tv.tv_usec = (writeTimeoutInMilliseconds%1000)*1000;
+    setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof tv);
 #endif
+  }
+
+  return result;
 }
 
 int setupStreamSocket(UsageEnvironment& env,
@@ -301,39 +318,54 @@ int readSocket(UsageEnvironment& env,
 }
 
 Boolean writeSocket(UsageEnvironment& env,
-		    int socket, struct in_addr address, Port port,
+		    int socket, struct in_addr address, portNumBits portNum,
 		    u_int8_t ttlArg,
 		    unsigned char* buffer, unsigned bufferSize) {
-	do {
-		if (ttlArg != 0) {
-			// Before sending, set the socket's TTL:
+  // Before sending, set the socket's TTL:
 #if defined(__WIN32__) || defined(_WIN32)
 #define TTL_TYPE int
 #else
 #define TTL_TYPE u_int8_t
 #endif
-			TTL_TYPE ttl = (TTL_TYPE)ttlArg;
-			if (setsockopt(socket, IPPROTO_IP, IP_MULTICAST_TTL,
-				       (const char*)&ttl, sizeof ttl) < 0) {
-				socketErr(env, "setsockopt(IP_MULTICAST_TTL) error: ");
-				break;
-			}
-		}
-
-		MAKE_SOCKADDR_IN(dest, address.s_addr, port.num());
-		int bytesSent = sendto(socket, (char*)buffer, bufferSize, 0,
-			               (struct sockaddr*)&dest, sizeof dest);
-		if (bytesSent != (int)bufferSize) {
-			char tmpBuf[100];
-			sprintf(tmpBuf, "writeSocket(%d), sendTo() error: wrote %d bytes instead of %u: ", socket, bytesSent, bufferSize);
-			socketErr(env, tmpBuf);
-			break;
-		}
-
-		return True;
-	} while (0);
-
-	return False;
+  TTL_TYPE ttl = (TTL_TYPE)ttlArg;
+  if (setsockopt(socket, IPPROTO_IP, IP_MULTICAST_TTL,
+		 (const char*)&ttl, sizeof ttl) < 0) {
+    socketErr(env, "setsockopt(IP_MULTICAST_TTL) error: ");
+    return False;
+  }
+
+  return writeSocket(env, socket, address, portNum, buffer, bufferSize);
+}
+
+Boolean writeSocket(UsageEnvironment& env,
+		    int socket, struct in_addr address, portNumBits portNum,
+		    unsigned char* buffer, unsigned bufferSize) {
+  do {
+    MAKE_SOCKADDR_IN(dest, address.s_addr, portNum);
+    int bytesSent = sendto(socket, (char*)buffer, bufferSize, 0,
+			   (struct sockaddr*)&dest, sizeof dest);
+    if (bytesSent != (int)bufferSize) {
+      char tmpBuf[100];
+      sprintf(tmpBuf, "writeSocket(%d), sendTo() error: wrote %d bytes instead of %u: ", socket, bytesSent, bufferSize);
+      socketErr(env, tmpBuf);
+      break;
+    }
+    
+    return True;
+  } while (0);
+
+  return False;
+}
+
+void ignoreSigPipeOnSocket(int socketNum) {
+  #ifdef USE_SIGNALS
+  #ifdef SO_NOSIGPIPE
+  int set_option = 1;
+  setsockopt(socketNum, SOL_SOCKET, SO_NOSIGPIPE, &set_option, sizeof set_option);
+  #else
+  signal(SIGPIPE, SIG_IGN);
+  #endif
+  #endif
 }
 
 static unsigned getBufferSize(UsageEnvironment& env, int bufOptName,
@@ -595,7 +627,7 @@ netAddressBits ourIPAddress(UsageEnvironment& env) {
       unsigned char testString[] = "hostIdTest";
       unsigned testStringLength = sizeof testString;
 
-      if (!writeSocket(env, sock, testAddr, testPort, 0,
+      if (!writeSocket(env, sock, testAddr, testPort.num(), 0,
 		       testString, testStringLength)) break;
 
       // Block until the socket is readable (with a 5-second timeout):
@@ -696,7 +728,9 @@ char const* timestampString() {
 
 #if !defined(_WIN32_WCE)
   static char timeString[9]; // holds hh:mm:ss plus trailing '\0'
-  char const* ctimeResult = ctime((time_t*)&tvNow.tv_sec);
+
+  time_t tvNow_t = tvNow.tv_sec;
+  char const* ctimeResult = ctime(&tvNow_t);
   if (ctimeResult == NULL) {
     sprintf(timeString, "??:??:??");
   } else {
diff --git a/groupsock/IOHandlers.cpp b/groupsock/IOHandlers.cpp
index abd8664..224db4f 100644
--- a/groupsock/IOHandlers.cpp
+++ b/groupsock/IOHandlers.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // IO event handlers
 // Implementation
 
diff --git a/groupsock/Makefile.tail b/groupsock/Makefile.tail
index 23d98dc..89a8593 100644
--- a/groupsock/Makefile.tail
+++ b/groupsock/Makefile.tail
@@ -39,7 +39,7 @@ install1: libgroupsock.$(LIB_SUFFIX)
 	  install -m 644 include/*.hh include/*.h $(DESTDIR)$(PREFIX)/include/groupsock
 	  install -m 644 libgroupsock.$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)
 install_shared_libraries: libgroupsock.$(LIB_SUFFIX)
-	  ln -s libgroupsock.$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/libgroupsock.$(SHORT_LIB_SUFFIX)
-	  ln -s libgroupsock.$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/libgroupsock.so
+	  ln -fs libgroupsock.$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/libgroupsock.$(SHORT_LIB_SUFFIX)
+	  ln -fs libgroupsock.$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/libgroupsock.so
 
 ##### Any additional, platform-specific rules come here:
diff --git a/groupsock/NetAddress.cpp b/groupsock/NetAddress.cpp
index d250643..0cd5aa6 100644
--- a/groupsock/NetAddress.cpp
+++ b/groupsock/NetAddress.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Network Addresses
 // Implementation
 
diff --git a/groupsock/NetInterface.cpp b/groupsock/NetInterface.cpp
index a1d4592..c07607d 100644
--- a/groupsock/NetInterface.cpp
+++ b/groupsock/NetInterface.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Network Interfaces
 // Implementation
 
diff --git a/groupsock/include/GroupEId.hh b/groupsock/include/GroupEId.hh
index c279b9e..d343c88 100644
--- a/groupsock/include/GroupEId.hh
+++ b/groupsock/include/GroupEId.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "multikit" Multicast Application Shell
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // "Group Endpoint Id"
 // C++ header
 
@@ -29,70 +29,36 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "NetAddress.hh"
 #endif
 
-const u_int8_t MAX_TTL = 255;
-
-class Scope {
-    public:
-    	Scope(u_int8_t ttl = 0, const char* publicKey = NULL);
-    	Scope(const Scope& orig);
-    	Scope& operator=(const Scope& rightSide);
-	~Scope();
-
-	u_int8_t ttl() const
-		{ return fTTL; }
-
-	const char* publicKey() const
-		{ return fPublicKey; }
-	unsigned publicKeySize() const;
-
-    private:
-    	void assign(u_int8_t ttl, const char* publicKey);
-    	void clean();
-
-    	u_int8_t fTTL;
-    	char* fPublicKey;
-};
-
 class GroupEId {
 public:
   GroupEId(struct in_addr const& groupAddr,
-	   portNumBits portNum, Scope const& scope,
-	   unsigned numSuccessiveGroupAddrs = 1);
+	   portNumBits portNum, u_int8_t ttl);
       // used for a 'source-independent multicast' group
   GroupEId(struct in_addr const& groupAddr,
 	   struct in_addr const& sourceFilterAddr,
-	   portNumBits portNum,
-	   unsigned numSuccessiveGroupAddrs = 1);
+	   portNumBits portNum);
       // used for a 'source-specific multicast' group
-  GroupEId(); // used only as a temp constructor prior to initialization
 
   struct in_addr const& groupAddress() const { return fGroupAddress; }
   struct in_addr const& sourceFilterAddress() const { return fSourceFilterAddress; }
 
   Boolean isSSM() const;
 
-  unsigned numSuccessiveGroupAddrs() const {
-    // could be >1 for hier encoding
-    return fNumSuccessiveGroupAddrs;
-  }
-
   portNumBits portNum() const { return fPortNum; }
 
-  const Scope& scope() const { return fScope; }
+  u_int8_t ttl() const { return fTTL; }
 
 private:
   void init(struct in_addr const& groupAddr,
 	    struct in_addr const& sourceFilterAddr,
 	    portNumBits portNum,
-	    Scope const& scope,
-	    unsigned numSuccessiveGroupAddrs);
+	    u_int8_t ttl);
 
 private:
   struct in_addr fGroupAddress;
   struct in_addr fSourceFilterAddress;
-  unsigned fNumSuccessiveGroupAddrs;
-  portNumBits fPortNum;
-  Scope fScope;
+  portNumBits fPortNum; // in network byte order
+  u_int8_t fTTL;
 };
 
 #endif
diff --git a/groupsock/include/Groupsock.hh b/groupsock/include/Groupsock.hh
index 4a6b15b..481c1db 100644
--- a/groupsock/include/Groupsock.hh
+++ b/groupsock/include/Groupsock.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // 'Group sockets'
 // C++ header
 
@@ -41,8 +41,12 @@ public:
   OutputSocket(UsageEnvironment& env);
   virtual ~OutputSocket();
 
-  Boolean write(netAddressBits address, Port port, u_int8_t ttl,
-		unsigned char* buffer, unsigned bufferSize);
+  virtual Boolean write(netAddressBits address, portNumBits portNum/*in network order*/, u_int8_t ttl,
+			unsigned char* buffer, unsigned bufferSize);
+  Boolean write(struct sockaddr_in& addressAndPort, u_int8_t ttl,
+		unsigned char* buffer, unsigned bufferSize) {
+    return write(addressAndPort.sin_addr.s_addr, addressAndPort.sin_port, ttl, buffer, bufferSize);
+  }
 
 protected:
   OutputSocket(UsageEnvironment& env, Port port);
@@ -52,23 +56,23 @@ protected:
 private: // redefined virtual function
   virtual Boolean handleRead(unsigned char* buffer, unsigned bufferMaxSize,
 			     unsigned& bytesRead,
-			     struct sockaddr_in& fromAddress);
+			     struct sockaddr_in& fromAddressAndPort);
 
 private:
   Port fSourcePort;
-  u_int8_t fLastSentTTL;
+  unsigned fLastSentTTL;
 };
 
 class destRecord {
 public:
-  destRecord(struct in_addr const& addr, Port const& port, u_int8_t ttl,
+  destRecord(struct in_addr const& addr, Port const& port, u_int8_t ttl, unsigned sessionId,
 	     destRecord* next);
   virtual ~destRecord();
 
 public:
   destRecord* fNext;
   GroupEId fGroupEId;
-  Port fPort;
+  unsigned fSessionId;
 };
 
 // A "Groupsock" is used to both send and receive packets.
@@ -86,19 +90,26 @@ public:
       // used for a 'source-specific multicast' group
   virtual ~Groupsock();
 
+  virtual destRecord* createNewDestRecord(struct in_addr const& addr, Port const& port, u_int8_t ttl, unsigned sessionId, destRecord* next);
+      // Can be redefined by subclasses that also subclass "destRecord"
+
   void changeDestinationParameters(struct in_addr const& newDestAddr,
-				   Port newDestPort, int newDestTTL);
+				   Port newDestPort, int newDestTTL,
+				   unsigned sessionId = 0);
       // By default, the destination address, port and ttl for
       // outgoing packets are those that were specified in
       // the constructor.  This works OK for multicast sockets,
       // but for unicast we usually want the destination port
       // number, at least, to be different from the source port.
-      // (If a parameter is 0 (or ~0 for ttl), then no change made.)
+      // (If a parameter is 0 (or ~0 for ttl), then no change is made to that parameter.)
+      // (If no existing "destRecord" exists with this "sessionId", then we add a new "destRecord".)
+  unsigned lookupSessionIdFromDestination(struct sockaddr_in const& destAddrAndPort) const;
+      // returns 0 if not found
 
   // As a special case, we also allow multiple destinations (addresses & ports)
   // (This can be used to implement multi-unicast.)
-  void addDestination(struct in_addr const& addr, Port const& port);
-  void removeDestination(struct in_addr const& addr, Port const& port);
+  virtual void addDestination(struct in_addr const& addr, Port const& port, unsigned sessionId);
+  virtual void removeDestination(unsigned sessionId);
   void removeAllDestinations();
 
   struct in_addr const& groupAddress() const {
@@ -112,13 +123,12 @@ public:
     return fIncomingGroupEId.isSSM();
   }
 
-  u_int8_t ttl() const { return fTTL; }
+  u_int8_t ttl() const { return fIncomingGroupEId.ttl(); }
 
   void multicastSendOnly(); // send, but don't receive any multicast packets
 
-  Boolean output(UsageEnvironment& env, u_int8_t ttl,
-		 unsigned char* buffer, unsigned bufferSize,
-		 DirectedNetInterface* interfaceNotToFwdBackTo = NULL);
+  virtual Boolean output(UsageEnvironment& env, unsigned char* buffer, unsigned bufferSize,
+			 DirectedNetInterface* interfaceNotToFwdBackTo = NULL);
 
   DirectedNetInterfaceSet& members() { return fMembers; }
 
@@ -134,24 +144,28 @@ public:
   NetInterfaceTrafficStats statsGroupRelayedIncoming; // *not* static
   NetInterfaceTrafficStats statsGroupRelayedOutgoing; // *not* static
 
-  Boolean wasLoopedBackFromUs(UsageEnvironment& env,
-			      struct sockaddr_in& fromAddress);
+  Boolean wasLoopedBackFromUs(UsageEnvironment& env, struct sockaddr_in& fromAddressAndPort);
 
 public: // redefined virtual functions
   virtual Boolean handleRead(unsigned char* buffer, unsigned bufferMaxSize,
 			     unsigned& bytesRead,
-			     struct sockaddr_in& fromAddress);
+			     struct sockaddr_in& fromAddressAndPort);
+
+protected:
+  destRecord* lookupDestRecordFromDestination(struct sockaddr_in const& destAddrAndPort) const;
 
 private:
+  void removeDestinationFrom(destRecord*& dests, unsigned sessionId);
+    // used to implement (the public) "removeDestination()", and "changeDestinationParameters()"
   int outputToAllMembersExcept(DirectedNetInterface* exceptInterface,
 			       u_int8_t ttlToFwd,
 			       unsigned char* data, unsigned size,
 			       netAddressBits sourceAddr);
 
+protected:
+  destRecord* fDests;
 private:
   GroupEId fIncomingGroupEId;
-  destRecord* fDests;
-  u_int8_t fTTL;
   DirectedNetInterfaceSet fMembers;
 };
 
diff --git a/groupsock/include/GroupsockHelper.hh b/groupsock/include/GroupsockHelper.hh
index 68bc307..666dc42 100644
--- a/groupsock/include/GroupsockHelper.hh
+++ b/groupsock/include/GroupsockHelper.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Helper routines to implement 'group sockets'
 // C++ header
 
@@ -34,10 +34,17 @@ int readSocket(UsageEnvironment& env,
 	       struct sockaddr_in& fromAddress);
 
 Boolean writeSocket(UsageEnvironment& env,
-		    int socket, struct in_addr address, Port port,
+		    int socket, struct in_addr address, portNumBits portNum/*network byte order*/,
 		    u_int8_t ttlArg,
 		    unsigned char* buffer, unsigned bufferSize);
 
+Boolean writeSocket(UsageEnvironment& env,
+		    int socket, struct in_addr address, portNumBits portNum/*network byte order*/,
+		    unsigned char* buffer, unsigned bufferSize);
+    // An optimized version of "writeSocket" that omits the "setsockopt()" call to set the TTL.
+
+void ignoreSigPipeOnSocket(int socketNum);
+
 unsigned getSendBufferSize(UsageEnvironment& env, int socket);
 unsigned getReceiveBufferSize(UsageEnvironment& env, int socket);
 unsigned setSendBufferTo(UsageEnvironment& env,
@@ -50,7 +57,8 @@ unsigned increaseReceiveBufferTo(UsageEnvironment& env,
 				 int socket, unsigned requestedSize);
 
 Boolean makeSocketNonBlocking(int sock);
-Boolean makeSocketBlocking(int sock);
+Boolean makeSocketBlocking(int sock, unsigned writeTimeoutInMilliseconds = 0);
+  // A "writeTimeoutInMilliseconds" value of 0 means: Don't timeout
 
 Boolean socketJoinGroup(UsageEnvironment& env, int socket,
 			netAddressBits groupAddress);
diff --git a/groupsock/include/IOHandlers.hh b/groupsock/include/IOHandlers.hh
index dd00ea3..b22e761 100644
--- a/groupsock/include/IOHandlers.hh
+++ b/groupsock/include/IOHandlers.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // IO event handlers
 // C++ header
 
diff --git a/groupsock/include/NetAddress.hh b/groupsock/include/NetAddress.hh
index 986d9c1..b9d333c 100644
--- a/groupsock/include/NetAddress.hh
+++ b/groupsock/include/NetAddress.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Network Addresses
 // C++ header
 
@@ -39,69 +39,68 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 typedef u_int32_t netAddressBits;
 
 class NetAddress {
-    public:
-	NetAddress(u_int8_t const* data,
-		   unsigned length = 4 /* default: 32 bits */);
-	NetAddress(unsigned length = 4); // sets address data to all-zeros
-	NetAddress(NetAddress const& orig);
-	NetAddress& operator=(NetAddress const& rightSide);
-	virtual ~NetAddress();
-
-	unsigned length() const { return fLength; }
-	u_int8_t const* data() const // always in network byte order
-		{ return fData; }
-
-    private:
-	void assign(u_int8_t const* data, unsigned length);
-	void clean();
-
-	unsigned fLength;
-	u_int8_t* fData;
+public:
+  NetAddress(u_int8_t const* data,
+	     unsigned length = 4 /* default: 32 bits */);
+  NetAddress(unsigned length = 4); // sets address data to all-zeros
+  NetAddress(NetAddress const& orig);
+  NetAddress& operator=(NetAddress const& rightSide);
+  virtual ~NetAddress();
+  
+  unsigned length() const { return fLength; }
+  u_int8_t const* data() const // always in network byte order
+  { return fData; }
+  
+private:
+  void assign(u_int8_t const* data, unsigned length);
+  void clean();
+  
+  unsigned fLength;
+  u_int8_t* fData;
 };
 
 class NetAddressList {
-    public:
-	NetAddressList(char const* hostname);
-	NetAddressList(NetAddressList const& orig);
-	NetAddressList& operator=(NetAddressList const& rightSide);
-	virtual ~NetAddressList();
-
-	unsigned numAddresses() const { return fNumAddresses; }
-
-	NetAddress const* firstAddress() const;
-
-	// Used to iterate through the addresses in a list:
-	class Iterator {
-	    public:
-		Iterator(NetAddressList const& addressList);
-		NetAddress const* nextAddress(); // NULL iff none
-	    private:
-		NetAddressList const& fAddressList;
-		unsigned fNextIndex;
-	};
-
-    private:
-	void assign(netAddressBits numAddresses, NetAddress** addressArray);
-	void clean();
-
-	friend class Iterator;
-	unsigned fNumAddresses;
-	NetAddress** fAddressArray;
+public:
+  NetAddressList(char const* hostname);
+  NetAddressList(NetAddressList const& orig);
+  NetAddressList& operator=(NetAddressList const& rightSide);
+  virtual ~NetAddressList();
+  
+  unsigned numAddresses() const { return fNumAddresses; }
+  
+  NetAddress const* firstAddress() const;
+  
+  // Used to iterate through the addresses in a list:
+  class Iterator {
+  public:
+    Iterator(NetAddressList const& addressList);
+    NetAddress const* nextAddress(); // NULL iff none
+  private:
+    NetAddressList const& fAddressList;
+    unsigned fNextIndex;
+  };
+  
+private:
+  void assign(netAddressBits numAddresses, NetAddress** addressArray);
+  void clean();
+  
+  friend class Iterator;
+  unsigned fNumAddresses;
+  NetAddress** fAddressArray;
 };
 
 typedef u_int16_t portNumBits;
 
 class Port {
-    public:
-	Port(portNumBits num /* in host byte order */);
-
-	portNumBits num() const // in network byte order
-		{ return fPortNum; }
-
-    private:
-	portNumBits fPortNum; // stored in network byte order
+public:
+  Port(portNumBits num /* in host byte order */);
+  
+  portNumBits num() const { return fPortNum; } // in network byte order
+  
+private:
+  portNumBits fPortNum; // stored in network byte order
 #ifdef IRIX
-	portNumBits filler; // hack to overcome a bug in IRIX C++ compiler
+  portNumBits filler; // hack to overcome a bug in IRIX C++ compiler
 #endif
 };
 
@@ -110,34 +109,32 @@ UsageEnvironment& operator<<(UsageEnvironment& s, const Port& p);
 
 // A generic table for looking up objects by (address1, address2, port)
 class AddressPortLookupTable {
-    public:
-	AddressPortLookupTable();
-	virtual ~AddressPortLookupTable();
-
-	void* Add(netAddressBits address1, netAddressBits address2,
-		  Port port, void* value);
-		// Returns the old value if different, otherwise 0
-	Boolean Remove(netAddressBits address1, netAddressBits address2,
-		       Port port);
-	void* Lookup(netAddressBits address1, netAddressBits address2,
-		     Port port);
-		// Returns 0 if not found
-
-	// Used to iterate through the entries in the table
-	class Iterator {
-	    public:
-		Iterator(AddressPortLookupTable& table);
-		virtual ~Iterator();
-
-		void* next(); // NULL iff none
-
-	    private:
-		HashTable::Iterator* fIter;
-	};
-
-    private:
-	friend class Iterator;
-	HashTable* fTable;
+public:
+  AddressPortLookupTable();
+  virtual ~AddressPortLookupTable();
+  
+  void* Add(netAddressBits address1, netAddressBits address2, Port port, void* value);
+      // Returns the old value if different, otherwise 0
+  Boolean Remove(netAddressBits address1, netAddressBits address2, Port port);
+  void* Lookup(netAddressBits address1, netAddressBits address2, Port port);
+      // Returns 0 if not found
+  void* RemoveNext() { return fTable->RemoveNext(); }
+
+  // Used to iterate through the entries in the table
+  class Iterator {
+  public:
+    Iterator(AddressPortLookupTable& table);
+    virtual ~Iterator();
+    
+    void* next(); // NULL iff none
+    
+  private:
+    HashTable::Iterator* fIter;
+  };
+  
+private:
+  friend class Iterator;
+  HashTable* fTable;
 };
 
 
diff --git a/groupsock/include/NetCommon.h b/groupsock/include/NetCommon.h
index d794e0e..eec71fd 100644
--- a/groupsock/include/NetCommon.h
+++ b/groupsock/include/NetCommon.h
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 /* "groupsock" interface
- * Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+ * Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
  * Common include files, typically used for networking
  */
 
@@ -57,9 +57,15 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 /* Definitions of size-specific types: */
 typedef __int64 int64_t;
 typedef unsigned __int64 u_int64_t;
+
+typedef int int32_t;
 typedef unsigned u_int32_t;
+
+typedef short int16_t;
 typedef unsigned short u_int16_t;
+
 typedef unsigned char u_int8_t;
+
 // For "uintptr_t" and "intptr_t", we assume that if they're not already defined, then this must be
 // an old, 32-bit version of Windows:
 #if !defined(_MSC_STDINT_H_) && !defined(_UINTPTR_T_DEFINED) && !defined(_UINTPTR_T_DECLARED) && !defined(_UINTPTR_T)
diff --git a/groupsock/include/NetInterface.hh b/groupsock/include/NetInterface.hh
index b0ecc22..bb00b0b 100644
--- a/groupsock/include/NetInterface.hh
+++ b/groupsock/include/NetInterface.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Network Interfaces
 // C++ header
 
diff --git a/groupsock/include/TunnelEncaps.hh b/groupsock/include/TunnelEncaps.hh
index 62efad5..3916f73 100644
--- a/groupsock/include/TunnelEncaps.hh
+++ b/groupsock/include/TunnelEncaps.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "mTunnel" multicast access service
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Encapsulation trailer for tunnels
 // C++ header
 
diff --git a/groupsock/include/groupsock_version.hh b/groupsock/include/groupsock_version.hh
index cbf51d6..87659e0 100644
--- a/groupsock/include/groupsock_version.hh
+++ b/groupsock/include/groupsock_version.hh
@@ -1,10 +1,10 @@
 // Version information for the "groupsock" library
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2015 Live Networks, Inc.  All rights reserved.
 
 #ifndef _GROUPSOCK_VERSION_HH
 #define _GROUPSOCK_VERSION_HH
 
-#define GROUPSOCK_LIBRARY_VERSION_STRING	"2014.01.13"
-#define GROUPSOCK_LIBRARY_VERSION_INT		1389571200
+#define GROUPSOCK_LIBRARY_VERSION_STRING	"2015.12.22"
+#define GROUPSOCK_LIBRARY_VERSION_INT		1450742400
 
 #endif
diff --git a/liveMedia/AACAudioMatroskaFileServerMediaSubsession.cpp b/liveMedia/AACAudioMatroskaFileServerMediaSubsession.cpp
deleted file mode 100644
index d92f8e9..0000000
--- a/liveMedia/AACAudioMatroskaFileServerMediaSubsession.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an AAC audio track within a Matroska file.
-// Implementation
-
-#include "AACAudioMatroskaFileServerMediaSubsession.hh"
-#include "MPEG4GenericRTPSink.hh"
-#include "MatroskaDemuxedTrack.hh"
-
-AACAudioMatroskaFileServerMediaSubsession* AACAudioMatroskaFileServerMediaSubsession
-::createNew(MatroskaFileServerDemux& demux, unsigned trackNumber) {
-  return new AACAudioMatroskaFileServerMediaSubsession(demux, trackNumber);
-}
-
-AACAudioMatroskaFileServerMediaSubsession
-::AACAudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber)
-  : FileServerMediaSubsession(demux.envir(), demux.fileName(), False),
-    fOurDemux(demux), fTrackNumber(trackNumber) {
-  // The Matroska file's 'Codec Private' data is assumed to be the AAC configuration information.
-  // Use this to generate a 'config string':
-  MatroskaTrack* track = fOurDemux.lookup(fTrackNumber);
-  fConfigStr = new char[2*track->codecPrivateSize + 1]; // 2 hex digits per byte, plus the trailing '\0'
-  for (unsigned i = 0; i < track->codecPrivateSize; ++i) sprintf(&fConfigStr[2*i], "%02X", track->codecPrivate[i]);
-}
-
-AACAudioMatroskaFileServerMediaSubsession
-::~AACAudioMatroskaFileServerMediaSubsession() {
-  delete[] fConfigStr;
-}
-
-float AACAudioMatroskaFileServerMediaSubsession::duration() const { return fOurDemux.fileDuration(); }
-
-void AACAudioMatroskaFileServerMediaSubsession
-::seekStreamSource(FramedSource* inputSource, double& seekNPT, double /*streamDuration*/, u_int64_t& /*numBytes*/) {
-  ((MatroskaDemuxedTrack*)inputSource)->seekToTime(seekNPT);
-}
-
-FramedSource* AACAudioMatroskaFileServerMediaSubsession
-::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
-  estBitrate = 96; // kbps, estimate
-
-  return fOurDemux.newDemuxedTrack(clientSessionId, fTrackNumber);
-}
-
-RTPSink* AACAudioMatroskaFileServerMediaSubsession
-::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) {
-  MatroskaTrack* track = fOurDemux.lookup(fTrackNumber);
-  return MPEG4GenericRTPSink::createNew(envir(), rtpGroupsock,
-                                        rtpPayloadTypeIfDynamic,
-                                        track->samplingFrequency,
-                                        "audio", "AAC-hbr", fConfigStr,
-                                        track->numChannels);
-}
diff --git a/liveMedia/AACAudioMatroskaFileServerMediaSubsession.hh b/liveMedia/AACAudioMatroskaFileServerMediaSubsession.hh
deleted file mode 100644
index 23f4515..0000000
--- a/liveMedia/AACAudioMatroskaFileServerMediaSubsession.hh
+++ /dev/null
@@ -1,55 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an AAC audio track within a Matroska file.
-// C++ header
-
-#ifndef _AAC_AUDIO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-#define _AAC_AUDIO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-
-#ifndef _FILE_SERVER_MEDIA_SUBSESSION_HH
-#include "FileServerMediaSubsession.hh"
-#endif
-#ifndef _MATROSKA_FILE_SERVER_DEMUX_HH
-#include "MatroskaFileServerDemux.hh"
-#endif
-
-class AACAudioMatroskaFileServerMediaSubsession: public FileServerMediaSubsession {
-public:
-  static AACAudioMatroskaFileServerMediaSubsession*
-  createNew(MatroskaFileServerDemux& demux, unsigned trackNumber);
-
-private:
-  AACAudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber);
-      // called only by createNew();
-  virtual ~AACAudioMatroskaFileServerMediaSubsession();
-
-private: // redefined virtual functions
-  virtual float duration() const;
-  virtual void seekStreamSource(FramedSource* inputSource, double& seekNPT, double streamDuration, u_int64_t& numBytes);
-  virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
-					      unsigned& estBitrate);
-  virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
-
-private:
-  MatroskaFileServerDemux& fOurDemux;
-  unsigned fTrackNumber;
-  char* fConfigStr;
-};
-
-#endif
diff --git a/liveMedia/AC3AudioFileServerMediaSubsession.cpp b/liveMedia/AC3AudioFileServerMediaSubsession.cpp
index 8eb6e6b..208bc73 100644
--- a/liveMedia/AC3AudioFileServerMediaSubsession.cpp
+++ b/liveMedia/AC3AudioFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an AC3 audio file.
 // Implementation
diff --git a/liveMedia/AC3AudioMatroskaFileServerMediaSubsession.cpp b/liveMedia/AC3AudioMatroskaFileServerMediaSubsession.cpp
deleted file mode 100644
index 7b99a5a..0000000
--- a/liveMedia/AC3AudioMatroskaFileServerMediaSubsession.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an AC3 audio track within a Matroska file.
-// Implementation
-
-#include "AC3AudioMatroskaFileServerMediaSubsession.hh"
-#include "AC3AudioRTPSink.hh"
-#include "MatroskaDemuxedTrack.hh"
-
-AC3AudioMatroskaFileServerMediaSubsession* AC3AudioMatroskaFileServerMediaSubsession
-::createNew(MatroskaFileServerDemux& demux, unsigned trackNumber) {
-  return new AC3AudioMatroskaFileServerMediaSubsession(demux, trackNumber);
-}
-
-AC3AudioMatroskaFileServerMediaSubsession
-::AC3AudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber)
-  : FileServerMediaSubsession(demux.envir(), demux.fileName(), False),
-    fOurDemux(demux), fTrackNumber(trackNumber) {
-}
-
-AC3AudioMatroskaFileServerMediaSubsession
-::~AC3AudioMatroskaFileServerMediaSubsession() {
-}
-
-float AC3AudioMatroskaFileServerMediaSubsession::duration() const { return fOurDemux.fileDuration(); }
-
-void AC3AudioMatroskaFileServerMediaSubsession
-::seekStreamSource(FramedSource* inputSource, double& seekNPT, double /*streamDuration*/, u_int64_t& /*numBytes*/) {
-  ((MatroskaDemuxedTrack*)inputSource)->seekToTime(seekNPT);
-}
-
-FramedSource* AC3AudioMatroskaFileServerMediaSubsession
-::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
-  estBitrate = 48; // kbps, estimate
-
-  return fOurDemux.newDemuxedTrack(clientSessionId, fTrackNumber);
-}
-
-RTPSink* AC3AudioMatroskaFileServerMediaSubsession
-::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) {
-  MatroskaTrack* track = fOurDemux.lookup(fTrackNumber);
-  return AC3AudioRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, track->samplingFrequency);
-}
diff --git a/liveMedia/AC3AudioRTPSink.cpp b/liveMedia/AC3AudioRTPSink.cpp
index 871501c..f1e5c06 100644
--- a/liveMedia/AC3AudioRTPSink.cpp
+++ b/liveMedia/AC3AudioRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for AC3 audio
 // Implementation
 
diff --git a/liveMedia/AC3AudioRTPSource.cpp b/liveMedia/AC3AudioRTPSource.cpp
index cf27299..d846d45 100644
--- a/liveMedia/AC3AudioRTPSource.cpp
+++ b/liveMedia/AC3AudioRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // AC3 Audio RTP Sources
 // Implementation
 
diff --git a/liveMedia/AC3AudioStreamFramer.cpp b/liveMedia/AC3AudioStreamFramer.cpp
index dbb72de..5800040 100644
--- a/liveMedia/AC3AudioStreamFramer.cpp
+++ b/liveMedia/AC3AudioStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an AC3 audio elementary stream into frames
 // Implementation
 
diff --git a/liveMedia/ADTSAudioFileServerMediaSubsession.cpp b/liveMedia/ADTSAudioFileServerMediaSubsession.cpp
index a3a1bd2..ca69949 100644
--- a/liveMedia/ADTSAudioFileServerMediaSubsession.cpp
+++ b/liveMedia/ADTSAudioFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an AAC audio file in ADTS format
 // Implementation
diff --git a/liveMedia/ADTSAudioFileSource.cpp b/liveMedia/ADTSAudioFileSource.cpp
index 8c2fa04..1470d67 100644
--- a/liveMedia/ADTSAudioFileSource.cpp
+++ b/liveMedia/ADTSAudioFileSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A source object for AAC audio files in ADTS format
 // Implementation
 
@@ -120,7 +120,7 @@ void ADTSAudioFileSource::doGetNextFrame() {
   if (fread(headers, 1, sizeof headers, fFid) < sizeof headers
       || feof(fFid) || ferror(fFid)) {
     // The input source has ended:
-    handleClosure(this);
+    handleClosure();
     return;
   }
 
diff --git a/liveMedia/AMRAudioFileServerMediaSubsession.cpp b/liveMedia/AMRAudioFileServerMediaSubsession.cpp
index 4572b96..d12c49d 100644
--- a/liveMedia/AMRAudioFileServerMediaSubsession.cpp
+++ b/liveMedia/AMRAudioFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an AMR audio file.
 // Implementation
diff --git a/liveMedia/AMRAudioFileSink.cpp b/liveMedia/AMRAudioFileSink.cpp
index 4a68569..f549201 100644
--- a/liveMedia/AMRAudioFileSink.cpp
+++ b/liveMedia/AMRAudioFileSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // AMR Audio File sinks
 // Implementation
 
diff --git a/liveMedia/AMRAudioFileSource.cpp b/liveMedia/AMRAudioFileSource.cpp
index 6d020d9..23fff99 100644
--- a/liveMedia/AMRAudioFileSource.cpp
+++ b/liveMedia/AMRAudioFileSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A source object for AMR audio files (as defined in RFC 4867, section 5)
 // Implementation
 
@@ -115,14 +115,14 @@ static unsigned short const frameSizeWideband[16] = {
 // as we now do with ByteStreamFileSource. #####
 void AMRAudioFileSource::doGetNextFrame() {
   if (feof(fFid) || ferror(fFid)) {
-    handleClosure(this);
+    handleClosure();
     return;
   }
 
   // Begin by reading the 1-byte frame header (and checking it for validity)
   while (1) {
     if (fread(&fLastFrameHeader, 1, 1, fFid) < 1) {
-      handleClosure(this);
+      handleClosure();
       return;
     }
     if ((fLastFrameHeader&0x83) != 0) {
diff --git a/liveMedia/AMRAudioRTPSink.cpp b/liveMedia/AMRAudioRTPSink.cpp
index acabe93..44d975e 100644
--- a/liveMedia/AMRAudioRTPSink.cpp
+++ b/liveMedia/AMRAudioRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for AMR audio (RFC 4867)
 // Implementation
 
diff --git a/liveMedia/AMRAudioRTPSource.cpp b/liveMedia/AMRAudioRTPSource.cpp
index 8bbd0a3..0f49d89 100644
--- a/liveMedia/AMRAudioRTPSource.cpp
+++ b/liveMedia/AMRAudioRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // AMR Audio RTP Sources (RFC 4867)
 // Implementation
 
diff --git a/liveMedia/AMRAudioSource.cpp b/liveMedia/AMRAudioSource.cpp
index 8f4d978..4ef853a 100644
--- a/liveMedia/AMRAudioSource.cpp
+++ b/liveMedia/AMRAudioSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A source object for AMR audio sources
 // Implementation
 
diff --git a/liveMedia/AVIFileSink.cpp b/liveMedia/AVIFileSink.cpp
index 6c0e7fc..6ea3c22 100644
--- a/liveMedia/AVIFileSink.cpp
+++ b/liveMedia/AVIFileSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A sink that generates an AVI file from a composite media session
 // Implementation
 
@@ -178,7 +178,7 @@ AVIFileSink::~AVIFileSink() {
   MediaSubsessionIterator iter(fInputSession);
   MediaSubsession* subsession;
   while ((subsession = iter.next()) != NULL) {
-    subsession->readSource()->stopGettingFrames();
+    if (subsession->readSource() != NULL) subsession->readSource()->stopGettingFrames();
 
     AVISubsessionIOState* ioState
       = (AVISubsessionIOState*)(subsession->miscPtr);
diff --git a/liveMedia/AudioRTPSink.cpp b/liveMedia/AudioRTPSink.cpp
index d053dd1..ea38ea3 100644
--- a/liveMedia/AudioRTPSink.cpp
+++ b/liveMedia/AudioRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic RTP sink for audio codecs (abstract base class)
 // Implementation
 
diff --git a/liveMedia/Base64.cpp b/liveMedia/Base64.cpp
index 866f9d9..0e27ba5 100644
--- a/liveMedia/Base64.cpp
+++ b/liveMedia/Base64.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Base64 encoding and decoding
 // implementation
 
diff --git a/liveMedia/BasicUDPSink.cpp b/liveMedia/BasicUDPSink.cpp
index 473801d..cfb41a3 100644
--- a/liveMedia/BasicUDPSink.cpp
+++ b/liveMedia/BasicUDPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simple UDP sink (i.e., without RTP or other headers added); one frame per packet
 // Implementation
 
@@ -72,7 +72,7 @@ void BasicUDPSink::afterGettingFrame1(unsigned frameSize, unsigned numTruncatedB
   }
 
   // Send the packet:
-  fGS->output(envir(), fGS->ttl(), fOutputBuffer, frameSize);
+  fGS->output(envir(), fOutputBuffer, frameSize);
 
   // Figure out the time at which the next packet should be sent, based
   // on the duration of the payload that we just read:
diff --git a/liveMedia/BasicUDPSource.cpp b/liveMedia/BasicUDPSource.cpp
index 85d9a8c..47904b0 100644
--- a/liveMedia/BasicUDPSource.cpp
+++ b/liveMedia/BasicUDPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simple UDP source, where every UDP payload is a complete frame
 // Implementation
 
diff --git a/liveMedia/BitVector.cpp b/liveMedia/BitVector.cpp
index 691c4f1..307ca10 100644
--- a/liveMedia/BitVector.cpp
+++ b/liveMedia/BitVector.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Bit Vector data structure
 // Implementation
 
diff --git a/liveMedia/ByteStreamFileSource.cpp b/liveMedia/ByteStreamFileSource.cpp
index f21924e..c3d4efd 100644
--- a/liveMedia/ByteStreamFileSource.cpp
+++ b/liveMedia/ByteStreamFileSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A file source that is a plain byte stream (rather than frames)
 // Implementation
 
@@ -57,8 +57,11 @@ void ByteStreamFileSource::seekToByteAbsolute(u_int64_t byteNumber, u_int64_t nu
   fLimitNumBytesToStream = fNumBytesToStream > 0;
 }
 
-void ByteStreamFileSource::seekToByteRelative(int64_t offset) {
+void ByteStreamFileSource::seekToByteRelative(int64_t offset, u_int64_t numBytesToStream) {
   SeekFile64(fFid, offset, SEEK_CUR);
+
+  fNumBytesToStream = numBytesToStream;
+  fLimitNumBytesToStream = fNumBytesToStream > 0;
 }
 
 void ByteStreamFileSource::seekToEnd() {
@@ -91,7 +94,7 @@ ByteStreamFileSource::~ByteStreamFileSource() {
 
 void ByteStreamFileSource::doGetNextFrame() {
   if (feof(fFid) || ferror(fFid) || (fLimitNumBytesToStream && fNumBytesToStream == 0)) {
-    handleClosure(this);
+    handleClosure();
     return;
   }
 
@@ -142,7 +145,7 @@ void ByteStreamFileSource::doReadFromFile() {
   }
 #endif
   if (fFrameSize == 0) {
-    handleClosure(this);
+    handleClosure();
     return;
   }
   fNumBytesToStream -= fFrameSize;
diff --git a/liveMedia/ByteStreamMemoryBufferSource.cpp b/liveMedia/ByteStreamMemoryBufferSource.cpp
index 5f311cd..3b8db17 100644
--- a/liveMedia/ByteStreamMemoryBufferSource.cpp
+++ b/liveMedia/ByteStreamMemoryBufferSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class for streaming data from a (static) memory buffer, as if it were a file.
 // Implementation
 
@@ -56,7 +56,7 @@ void ByteStreamMemoryBufferSource::seekToByteAbsolute(u_int64_t byteNumber, u_in
   fLimitNumBytesToStream = fNumBytesToStream > 0;
 }
 
-void ByteStreamMemoryBufferSource::seekToByteRelative(int64_t offset) {
+void ByteStreamMemoryBufferSource::seekToByteRelative(int64_t offset, u_int64_t numBytesToStream) {
   int64_t newIndex = fCurIndex + offset;
   if (newIndex < 0) {
     fCurIndex = 0;
@@ -64,11 +64,14 @@ void ByteStreamMemoryBufferSource::seekToByteRelative(int64_t offset) {
     fCurIndex = (u_int64_t)offset;
     if (fCurIndex > fBufferSize) fCurIndex = fBufferSize;
   }
+
+  fNumBytesToStream = numBytesToStream;
+  fLimitNumBytesToStream = fNumBytesToStream > 0;
 }
 
 void ByteStreamMemoryBufferSource::doGetNextFrame() {
   if (fCurIndex >= fBufferSize || (fLimitNumBytesToStream && fNumBytesToStream == 0)) {
-    handleClosure(this);
+    handleClosure();
     return;
   }
 
diff --git a/liveMedia/ByteStreamMultiFileSource.cpp b/liveMedia/ByteStreamMultiFileSource.cpp
index 985a96a..ca8c704 100644
--- a/liveMedia/ByteStreamMultiFileSource.cpp
+++ b/liveMedia/ByteStreamMultiFileSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A source that consists of multiple byte-stream files, read sequentially
 // Implementation
 
@@ -96,7 +96,7 @@ void ByteStreamMultiFileSource::doGetNextFrame() {
   } while (0);
 
   // An error occurred; consider ourselves closed:
-  handleClosure(this);
+  handleClosure();
 }
 
 void ByteStreamMultiFileSource
diff --git a/liveMedia/DVVideoFileServerMediaSubsession.cpp b/liveMedia/DVVideoFileServerMediaSubsession.cpp
index d8e820b..9086e50 100644
--- a/liveMedia/DVVideoFileServerMediaSubsession.cpp
+++ b/liveMedia/DVVideoFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a DV video file.
 // Implementation
@@ -88,3 +88,16 @@ void DVVideoFileServerMediaSubsession
     fileSource->seekToByteAbsolute(seekByteNumber, numBytes);
   }
 }
+
+void DVVideoFileServerMediaSubsession
+::setStreamSourceDuration(FramedSource* inputSource, double streamDuration, u_int64_t& numBytes) {
+  // First, get the file source from "inputSource" (a framer):
+  DVVideoStreamFramer* framer = (DVVideoStreamFramer*)inputSource;
+  ByteStreamFileSource* fileSource = (ByteStreamFileSource*)(framer->inputSource());
+
+  // Then figure out how many bytes to limit the streaming to:
+  if (fFileDuration > 0.0) {
+    numBytes = (u_int64_t)(((int64_t)fFileSize*streamDuration)/fFileDuration);
+    fileSource->seekToByteRelative(0, numBytes);
+  }
+}
diff --git a/liveMedia/DVVideoRTPSink.cpp b/liveMedia/DVVideoRTPSink.cpp
index 12bd64d..22cde13 100644
--- a/liveMedia/DVVideoRTPSink.cpp
+++ b/liveMedia/DVVideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for DV video (RFC 3189)
 // (Thanks to Ben Hutchings for prototyping this.)
 // Implementation
diff --git a/liveMedia/DVVideoRTPSource.cpp b/liveMedia/DVVideoRTPSource.cpp
index ebfc2f3..2cffda9 100644
--- a/liveMedia/DVVideoRTPSource.cpp
+++ b/liveMedia/DVVideoRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // DV Video RTP Sources
 // Implementation
 
diff --git a/liveMedia/DVVideoStreamFramer.cpp b/liveMedia/DVVideoStreamFramer.cpp
index 0a3d719..53a0b74 100644
--- a/liveMedia/DVVideoStreamFramer.cpp
+++ b/liveMedia/DVVideoStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that parses a DV input stream into DV frames to deliver to the downstream object
 // Implementation
 // (Thanks to Ben Hutchings for his help, including a prototype implementation.)
diff --git a/liveMedia/DarwinInjector.cpp b/liveMedia/DarwinInjector.cpp
deleted file mode 100644
index 758a188..0000000
--- a/liveMedia/DarwinInjector.cpp
+++ /dev/null
@@ -1,349 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// An object that redirects one or more RTP/RTCP streams - forming a single
-// multimedia session - into a 'Darwin Streaming Server' (for subsequent
-// reflection to potentially arbitrarily many remote RTSP clients).
-// Implementation
-
-#include "DarwinInjector.hh"
-#include <GroupsockHelper.hh>
-
-////////// SubstreamDescriptor definition //////////
-
-class SubstreamDescriptor {
-public:
-  SubstreamDescriptor(RTPSink* rtpSink, RTCPInstance* rtcpInstance, unsigned trackId);
-  ~SubstreamDescriptor();
-
-  SubstreamDescriptor*& next() { return fNext; }
-  RTPSink* rtpSink() const { return fRTPSink; }
-  RTCPInstance* rtcpInstance() const { return fRTCPInstance; }
-  char const* sdpLines() const { return fSDPLines; }
-
-private:
-  SubstreamDescriptor* fNext;
-  RTPSink* fRTPSink;
-  RTCPInstance* fRTCPInstance;
-  char* fSDPLines;
-};
-
-
-////////// DarwinInjector implementation //////////
-
-DarwinInjector* DarwinInjector::createNew(UsageEnvironment& env,
-					  char const* applicationName,
-					  int verbosityLevel) {
-  return new DarwinInjector(env, applicationName, verbosityLevel);
-}
-
-Boolean DarwinInjector::lookupByName(UsageEnvironment& env, char const* name,
-				     DarwinInjector*& result) {
-  result = NULL; // unless we succeed
-
-  Medium* medium;
-  if (!Medium::lookupByName(env, name, medium)) return False;
-
-  if (!medium->isDarwinInjector()) {
-    env.setResultMsg(name, " is not a 'Darwin injector'");
-    return False;
-  }
-
-  result = (DarwinInjector*)medium;
-  return True;
-}
-
-DarwinInjector::DarwinInjector(UsageEnvironment& env,
-			       char const* applicationName, int verbosityLevel)
-  : Medium(env),
-    fApplicationName(strDup(applicationName)), fVerbosityLevel(verbosityLevel),
-    fRTSPClient(NULL), fSubstreamSDPSizes(0),
-    fHeadSubstream(NULL), fTailSubstream(NULL), fSession(NULL), fLastTrackId(0), fResultString(NULL) {
-}
-
-DarwinInjector::~DarwinInjector() {
-  if (fSession != NULL) { // close down and delete the session
-    fRTSPClient->sendTeardownCommand(*fSession, NULL);
-    Medium::close(fSession);
-  }
-
-  delete fHeadSubstream;
-  delete[] (char*)fApplicationName;
-  Medium::close(fRTSPClient);
-}
-
-void DarwinInjector::addStream(RTPSink* rtpSink, RTCPInstance* rtcpInstance) {
-  if (rtpSink == NULL) return; // "rtpSink" should be non-NULL
-
-  SubstreamDescriptor* newDescriptor = new SubstreamDescriptor(rtpSink, rtcpInstance, ++fLastTrackId);
-  if (fHeadSubstream == NULL) {
-    fHeadSubstream = fTailSubstream = newDescriptor;
-  } else {
-    fTailSubstream->next() = newDescriptor;
-    fTailSubstream = newDescriptor;
-  }
-
-  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, -1),
-      fOurDarwinInjector(ourDarwinInjector) {}
-  virtual ~RTSPClientForDarwinInjector() {}
-  DarwinInjector* fOurDarwinInjector;
-};
-
-Boolean DarwinInjector
-::setDestination(char const* remoteRTSPServerNameOrAddress,
-		 char const* remoteFileName,
-		 char const* sessionName,
-		 char const* sessionInfo,
-		 portNumBits remoteRTSPServerPortNumber,
-		 char const* remoteUserName,
-		 char const* remotePassword,
-		 char const* sessionAuthor,
-		 char const* sessionCopyright,
-		 int timeout) {
-  char* sdp = NULL;
-  char* url = NULL;
-  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 = new RTSPClientForDarwinInjector(envir(), url, fVerbosityLevel, fApplicationName, this);
-    if (fRTSPClient == NULL) break;
-
-    // Get the remote RTSP server's IP address:
-    struct in_addr addr;
-    {
-      NetAddressList addresses(remoteRTSPServerNameOrAddress);
-      if (addresses.numAddresses() == 0) break;
-      NetAddress const* address = addresses.firstAddress();
-      addr.s_addr = *(unsigned*)(address->data());
-    }
-    AddressString remoteRTSPServerAddressStr(addr);
-
-    // Construct a SDP description for the session that we'll be streaming:
-    char const* const sdpFmt =
-      "v=0\r\n"
-      "o=- %u %u IN IP4 127.0.0.1\r\n"
-      "s=%s\r\n"
-      "i=%s\r\n"
-      "c=IN IP4 %s\r\n"
-      "t=0 0\r\n"
-      "a=x-qt-text-nam:%s\r\n"
-      "a=x-qt-text-inf:%s\r\n"
-      "a=x-qt-text-cmt:source application:%s\r\n"
-      "a=x-qt-text-aut:%s\r\n"
-      "a=x-qt-text-cpy:%s\r\n";
-      // plus, %s for each substream SDP
-    unsigned sdpLen = strlen(sdpFmt)
-      + 20 /* max int len */ + 20 /* max int len */
-      + strlen(sessionName)
-      + strlen(sessionInfo)
-      + strlen(remoteRTSPServerAddressStr.val())
-      + strlen(sessionName)
-      + strlen(sessionInfo)
-      + strlen(fApplicationName)
-      + strlen(sessionAuthor)
-      + strlen(sessionCopyright)
-      + fSubstreamSDPSizes;
-    unsigned const sdpSessionId = our_random32();
-    unsigned const sdpVersion = sdpSessionId;
-    sdp = new char[sdpLen];
-    sprintf(sdp, sdpFmt,
-	    sdpSessionId, sdpVersion, // o= line
-	    sessionName, // s= line
-	    sessionInfo, // i= line
-	    remoteRTSPServerAddressStr.val(), // c= line
-	    sessionName, // a=x-qt-text-nam: line
-	    sessionInfo, // a=x-qt-text-inf: line
-	    fApplicationName, // a=x-qt-text-cmt: line
-	    sessionAuthor, // a=x-qt-text-aut: line
-	    sessionCopyright // a=x-qt-text-cpy: line
-	    );
-    char* p = &sdp[strlen(sdp)];
-    SubstreamDescriptor* ss;
-    for (ss = fHeadSubstream; ss != NULL; ss = ss->next()) {
-      sprintf(p, "%s", ss->sdpLines());
-      p += strlen(p);
-    }
-
-    // Do a RTSP "ANNOUNCE" with this SDP description:
-    Authenticator auth;
-    Authenticator* authToUse = NULL;
-    if (remoteUserName[0] != '\0' || remotePassword[0] != '\0') {
-      auth.setUsernameAndPassword(remoteUserName, remotePassword);
-      authToUse = &auth;
-    }
-    fWatchVariable = 0;
-    (void)fRTSPClient->sendAnnounceCommand(sdp, genericResponseHandler, authToUse);
-
-    // 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;
-
-    ss = fHeadSubstream;
-    MediaSubsessionIterator iter(*fSession);
-    MediaSubsession* subsession;
-    ss = fHeadSubstream;
-    unsigned streamChannelId = 0;
-    while ((subsession = iter.next()) != NULL) {
-      if (!subsession->initiate()) 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:
-      ss->rtpSink()->setStreamSocket(fRTSPClient->socketNum(), streamChannelId++);
-      if (ss->rtcpInstance() != NULL) {
-	ss->rtcpInstance()->setStreamSocket(fRTSPClient->socketNum(),
-					    streamChannelId++);
-      }
-      ss = ss->next();
-    }
-    if (subsession != NULL) break; // an error occurred above
-
-    // Tell the RTSP server to start:
-    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);
-
-    success = True;
-  } while (0);
-
-  delete[] sdp;
-  delete[] url;
-  return success;
-}
-
-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 //////////
-
-SubstreamDescriptor::SubstreamDescriptor(RTPSink* rtpSink,
-					 RTCPInstance* rtcpInstance, unsigned trackId)
-  : fNext(NULL), fRTPSink(rtpSink), fRTCPInstance(rtcpInstance) {
-  // Create the SDP description for this substream
-  char const* mediaType = fRTPSink->sdpMediaType();
-  unsigned char rtpPayloadType = fRTPSink->rtpPayloadType();
-  char const* rtpPayloadFormatName = fRTPSink->rtpPayloadFormatName();
-  unsigned rtpTimestampFrequency = fRTPSink->rtpTimestampFrequency();
-  unsigned numChannels = fRTPSink->numChannels();
-  char* rtpmapLine;
-  if (rtpPayloadType >= 96) {
-    char* encodingParamsPart;
-    if (numChannels != 1) {
-      encodingParamsPart = new char[1 + 20 /* max int len */];
-      sprintf(encodingParamsPart, "/%d", numChannels);
-    } else {
-      encodingParamsPart = strDup("");
-    }
-    char const* const rtpmapFmt = "a=rtpmap:%d %s/%d%s\r\n";
-    unsigned rtpmapFmtSize = strlen(rtpmapFmt)
-      + 3 /* max char len */ + strlen(rtpPayloadFormatName)
-      + 20 /* max int len */ + strlen(encodingParamsPart);
-    rtpmapLine = new char[rtpmapFmtSize];
-    sprintf(rtpmapLine, rtpmapFmt,
-            rtpPayloadType, rtpPayloadFormatName,
-            rtpTimestampFrequency, encodingParamsPart);
-    delete[] encodingParamsPart;
-  } else {
-    // Static payload type => no "a=rtpmap:" line
-    rtpmapLine = strDup("");
-  }
-  unsigned rtpmapLineSize = strlen(rtpmapLine);
-  char const* auxSDPLine = fRTPSink->auxSDPLine();
-  if (auxSDPLine == NULL) auxSDPLine = "";
-  unsigned auxSDPLineSize = strlen(auxSDPLine);
-
-  char const* const sdpFmt =
-    "m=%s 0 RTP/AVP %u\r\n"
-    "%s" // "a=rtpmap:" line (if present)
-    "%s" // auxilliary (e.g., "a=fmtp:") line (if present)
-    "a=control:trackID=%u\r\n";
-  unsigned sdpFmtSize = strlen(sdpFmt)
-    + strlen(mediaType) + 3 /* max char len */
-    + rtpmapLineSize
-    + auxSDPLineSize
-    + 20 /* max int len */;
-  char* sdpLines = new char[sdpFmtSize];
-  sprintf(sdpLines, sdpFmt,
-          mediaType, // m= <media>
-          rtpPayloadType, // m= <fmt list>
-          rtpmapLine, // a=rtpmap:... (if present)
-          auxSDPLine, // optional extra SDP line
-          trackId); // a=control:<track-id>
-  fSDPLines = strDup(sdpLines);
-  delete[] sdpLines;
-  delete[] rtpmapLine;
-}
-
-SubstreamDescriptor::~SubstreamDescriptor() {
-  delete fSDPLines;
-  delete fNext;
-}
diff --git a/liveMedia/DeviceSource.cpp b/liveMedia/DeviceSource.cpp
index cfb0835..87f6a8a 100644
--- a/liveMedia/DeviceSource.cpp
+++ b/liveMedia/DeviceSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A template for a MediaSource encapsulating an audio/video input device
 //
 // NOTE: Sections of this code labeled "%%% TO BE WRITTEN %%%" are incomplete, and need to be written by the programmer
@@ -80,7 +80,7 @@ void DeviceSource::doGetNextFrame() {
 
   // Note: If, for some reason, the source device stops being readable (e.g., it gets closed), then you do the following:
   if (0 /* the source stops being readable */ /*%%% TO BE WRITTEN %%%*/) {
-    handleClosure(this);
+    handleClosure();
     return;
   }
 
diff --git a/liveMedia/DigestAuthentication.cpp b/liveMedia/DigestAuthentication.cpp
index 06a09bb..a92eb26 100644
--- a/liveMedia/DigestAuthentication.cpp
+++ b/liveMedia/DigestAuthentication.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class used for digest authentication.
 // Implementation
 
@@ -48,6 +48,19 @@ Authenticator& Authenticator::operator=(const Authenticator& rightSide) {
   return *this;
 }
 
+Boolean Authenticator::operator<(const Authenticator* rightSide) {
+  // Returns True if "rightSide" is 'newer' than us:
+  if (rightSide != NULL && rightSide != this &&
+      (rightSide->realm() != NULL || rightSide->nonce() != NULL ||
+       username() == NULL || password() == NULL ||
+       strcmp(rightSide->username(), username()) != 0 ||
+       strcmp(rightSide->password(), password()) != 0)) {
+    return True;
+  }
+
+  return False;
+}
+
 Authenticator::~Authenticator() {
   reset();
 }
@@ -145,6 +158,9 @@ void Authenticator::assignRealmAndNonce(char const* realm, char const* nonce) {
 }
 
 void Authenticator::assignUsernameAndPassword(char const* username, char const* password, Boolean passwordIsMD5) {
+  if (username == NULL) username = "";
+  if (password == NULL) password = "";
+
   fUsername = strDup(username);
   fPassword = strDup(password);
   fPasswordIsMD5 = passwordIsMD5;
diff --git a/liveMedia/EBMLNumber.cpp b/liveMedia/EBMLNumber.cpp
index 577f977..62651ba 100644
--- a/liveMedia/EBMLNumber.cpp
+++ b/liveMedia/EBMLNumber.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // EBML numbers (ids and sizes)
 // Implementation
 
@@ -135,6 +135,9 @@ char const* EBMLId::stringName() const {
     case MATROSKA_ID_CUE_CLUSTER_POSITION: { return "Cue Cluster Position"; }
     case MATROSKA_ID_CUE_BLOCK_NUMBER: { return "Cue Block Number"; }
     case MATROSKA_ID_TAGS: { return "Tags"; }
+    case MATROSKA_ID_SEEK_PRE_ROLL: { return "SeekPreRoll"; }
+    case MATROSKA_ID_CODEC_DELAY: { return "CodecDelay"; }
+    case MATROSKA_ID_DISCARD_PADDING: { return "DiscardPadding"; }
     default: { return "*****unknown*****"; }
   }
 }
diff --git a/liveMedia/EBMLNumber.hh b/liveMedia/EBMLNumber.hh
index 3e1fd6f..c8fa46c 100644
--- a/liveMedia/EBMLNumber.hh
+++ b/liveMedia/EBMLNumber.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // EBML numbers (ids and sizes)
 // C++ header
 
@@ -121,6 +121,9 @@ public:
 #define MATROSKA_ID_CUE_CLUSTER_POSITION 0xF1
 #define MATROSKA_ID_CUE_BLOCK_NUMBER 0x5378
 #define MATROSKA_ID_TAGS 0x1254C367
+#define MATROSKA_ID_SEEK_PRE_ROLL 0x56BB
+#define MATROSKA_ID_CODEC_DELAY 0x56AA
+#define MATROSKA_ID_DISCARD_PADDING 0x75A2
 
 class EBMLId: public EBMLNumber {
 public:
diff --git a/liveMedia/FileServerMediaSubsession.cpp b/liveMedia/FileServerMediaSubsession.cpp
index 5404919..4cc5383 100644
--- a/liveMedia/FileServerMediaSubsession.cpp
+++ b/liveMedia/FileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a file.
 // Implementation
diff --git a/liveMedia/FileSink.cpp b/liveMedia/FileSink.cpp
index 04ae66f..0986d55 100644
--- a/liveMedia/FileSink.cpp
+++ b/liveMedia/FileSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // File sinks
 // Implementation
 
diff --git a/liveMedia/FramedFileSource.cpp b/liveMedia/FramedFileSource.cpp
index 1f21646..7594942 100644
--- a/liveMedia/FramedFileSource.cpp
+++ b/liveMedia/FramedFileSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Framed File Sources
 // Implementation
 
diff --git a/liveMedia/FramedFilter.cpp b/liveMedia/FramedFilter.cpp
index fa1ae66..e16c000 100644
--- a/liveMedia/FramedFilter.cpp
+++ b/liveMedia/FramedFilter.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Framed Filters
 // Implementation
 
diff --git a/liveMedia/FramedSource.cpp b/liveMedia/FramedSource.cpp
index 30a59a4..02e583c 100644
--- a/liveMedia/FramedSource.cpp
+++ b/liveMedia/FramedSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Framed Sources
 // Implementation
 
@@ -94,9 +94,13 @@ void FramedSource::afterGetting(FramedSource* source) {
 
 void FramedSource::handleClosure(void* clientData) {
   FramedSource* source = (FramedSource*)clientData;
-  source->fIsCurrentlyAwaitingData = False; // because we got a close instead
-  if (source->fOnCloseFunc != NULL) {
-    (*(source->fOnCloseFunc))(source->fOnCloseClientData);
+  source->handleClosure();
+}
+
+void FramedSource::handleClosure() {
+  fIsCurrentlyAwaitingData = False; // because we got a close instead
+  if (fOnCloseFunc != NULL) {
+    (*fOnCloseFunc)(fOnCloseClientData);
   }
 }
 
diff --git a/liveMedia/GSMAudioRTPSink.cpp b/liveMedia/GSMAudioRTPSink.cpp
index aa81693..d2c71ef 100644
--- a/liveMedia/GSMAudioRTPSink.cpp
+++ b/liveMedia/GSMAudioRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for GSM audio
 // Implementation
 
diff --git a/liveMedia/GenericMediaServer.cpp b/liveMedia/GenericMediaServer.cpp
new file mode 100644
index 0000000..c063472
--- /dev/null
+++ b/liveMedia/GenericMediaServer.cpp
@@ -0,0 +1,396 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A generic media server class, used to implement a RTSP server, and any other server that uses
+//  "ServerMediaSession" objects to describe media to be served.
+// Implementation
+
+#include "GenericMediaServer.hh"
+#include <GroupsockHelper.hh>
+#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
+#define snprintf _snprintf
+#endif
+
+////////// GenericMediaServer implementation //////////
+
+void GenericMediaServer::addServerMediaSession(ServerMediaSession* serverMediaSession) {
+  if (serverMediaSession == NULL) return;
+  
+  char const* sessionName = serverMediaSession->streamName();
+  if (sessionName == NULL) sessionName = "";
+  removeServerMediaSession(sessionName); // in case an existing "ServerMediaSession" with this name already exists
+  
+  fServerMediaSessions->Add(sessionName, (void*)serverMediaSession);
+}
+
+ServerMediaSession* GenericMediaServer
+::lookupServerMediaSession(char const* streamName, Boolean /*isFirstLookupInSession*/) {
+  // Default implementation:
+  return (ServerMediaSession*)(fServerMediaSessions->Lookup(streamName));
+}
+
+void GenericMediaServer::removeServerMediaSession(ServerMediaSession* serverMediaSession) {
+  if (serverMediaSession == NULL) return;
+  
+  fServerMediaSessions->Remove(serverMediaSession->streamName());
+  if (serverMediaSession->referenceCount() == 0) {
+    Medium::close(serverMediaSession);
+  } else {
+    serverMediaSession->deleteWhenUnreferenced() = True;
+  }
+}
+
+void GenericMediaServer::removeServerMediaSession(char const* streamName) {
+  removeServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName)));
+}
+
+void GenericMediaServer::closeAllClientSessionsForServerMediaSession(ServerMediaSession* serverMediaSession) {
+  if (serverMediaSession == NULL) return;
+  
+  HashTable::Iterator* iter = HashTable::Iterator::create(*fClientSessions);
+  GenericMediaServer::ClientSession* clientSession;
+  char const* key; // dummy
+  while ((clientSession = (GenericMediaServer::ClientSession*)(iter->next(key))) != NULL) {
+    if (clientSession->fOurServerMediaSession == serverMediaSession) {
+      delete clientSession;
+    }
+  }
+  delete iter;
+}
+
+void GenericMediaServer::closeAllClientSessionsForServerMediaSession(char const* streamName) {
+  closeAllClientSessionsForServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName)));
+}
+
+void GenericMediaServer::deleteServerMediaSession(ServerMediaSession* serverMediaSession) {
+  if (serverMediaSession == NULL) return;
+  
+  closeAllClientSessionsForServerMediaSession(serverMediaSession);
+  removeServerMediaSession(serverMediaSession);
+}
+
+void GenericMediaServer::deleteServerMediaSession(char const* streamName) {
+  deleteServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName)));
+}
+
+GenericMediaServer
+::GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort,
+		     unsigned reclamationSeconds)
+  : Medium(env),
+    fServerSocket(ourSocket), fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),
+    fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
+    fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
+    fClientSessions(HashTable::create(STRING_HASH_KEYS)) {
+  ignoreSigPipeOnSocket(fServerSocket); // so that clients on the same host that are killed don't also kill us
+  
+  // Arrange to handle connections from others:
+  env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);
+}
+
+GenericMediaServer::~GenericMediaServer() {
+  // Turn off background read handling:
+  envir().taskScheduler().turnOffBackgroundReadHandling(fServerSocket);
+  ::closeSocket(fServerSocket);
+}
+
+void GenericMediaServer::cleanup() {
+  // This member function must be called in the destructor of any subclass of
+  //"GenericMediaServer".  (We don't call this in the destructor of "GenericMediaServer" itself,
+  // because by that time, the subclass destructor will already have been called, and this may
+  // affect (break) the destruction of the "ClientSession" and "ClientConnection" objects, which
+  // themselves will have been subclassed.)
+
+  // Close all client session objects:
+  GenericMediaServer::ClientSession* clientSession;
+  while ((clientSession = (GenericMediaServer::ClientSession*)fClientSessions->getFirst()) != NULL) {
+    delete clientSession;
+  }
+  delete fClientSessions;
+  
+  // Close all client connection objects:
+  GenericMediaServer::ClientConnection* connection;
+  while ((connection = (GenericMediaServer::ClientConnection*)fClientConnections->getFirst()) != NULL) {
+    delete connection;
+  }
+  delete fClientConnections;
+  
+  // Delete all server media sessions
+  ServerMediaSession* serverMediaSession;
+  while ((serverMediaSession = (ServerMediaSession*)fServerMediaSessions->getFirst()) != NULL) {
+    removeServerMediaSession(serverMediaSession); // will delete it, because it no longer has any 'client session' objects using it
+  }
+  delete fServerMediaSessions;
+}
+
+#define LISTEN_BACKLOG_SIZE 20
+
+int GenericMediaServer::setUpOurSocket(UsageEnvironment& env, Port& ourPort) {
+  int ourSocket = -1;
+  
+  do {
+    // The following statement is enabled by default.
+    // Don't disable it (by defining ALLOW_SERVER_PORT_REUSE) unless you know what you're doing.
+#if !defined(ALLOW_SERVER_PORT_REUSE) && !defined(ALLOW_RTSP_SERVER_PORT_REUSE)
+    // ALLOW_RTSP_SERVER_PORT_REUSE is for backwards-compatibility #####
+    NoReuse dummy(env); // Don't use this socket if there's already a local server using it
+#endif
+    
+    ourSocket = setupStreamSocket(env, ourPort);
+    if (ourSocket < 0) break;
+    
+    // Make sure we have a big send buffer:
+    if (!increaseSendBufferTo(env, ourSocket, 50*1024)) break;
+    
+    // Allow multiple simultaneous connections:
+    if (listen(ourSocket, LISTEN_BACKLOG_SIZE) < 0) {
+      env.setResultErrMsg("listen() failed: ");
+      break;
+    }
+    
+    if (ourPort.num() == 0) {
+      // bind() will have chosen a port for us; return it also:
+      if (!getSourcePort(env, ourSocket, ourPort)) break;
+    }
+    
+    return ourSocket;
+  } while (0);
+  
+  if (ourSocket != -1) ::closeSocket(ourSocket);
+  return -1;
+}
+
+void GenericMediaServer::incomingConnectionHandler(void* instance, int /*mask*/) {
+  GenericMediaServer* server = (GenericMediaServer*)instance;
+  server->incomingConnectionHandler();
+}
+void GenericMediaServer::incomingConnectionHandler() {
+  incomingConnectionHandlerOnSocket(fServerSocket);
+}
+
+void GenericMediaServer::incomingConnectionHandlerOnSocket(int serverSocket) {
+  struct sockaddr_in clientAddr;
+  SOCKLEN_T clientAddrLen = sizeof clientAddr;
+  int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
+  if (clientSocket < 0) {
+    int err = envir().getErrno();
+    if (err != EWOULDBLOCK) {
+      envir().setResultErrMsg("accept() failed: ");
+    }
+    return;
+  }
+  ignoreSigPipeOnSocket(clientSocket); // so that clients on the same host that are killed don't also kill us
+  makeSocketNonBlocking(clientSocket);
+  increaseSendBufferTo(envir(), clientSocket, 50*1024);
+  
+#ifdef DEBUG
+  envir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";
+#endif
+  
+  // Create a new object for handling this connection:
+  (void)createNewClientConnection(clientSocket, clientAddr);
+}
+
+
+////////// GenericMediaServer::ClientConnection implementation //////////
+
+GenericMediaServer::ClientConnection
+::ClientConnection(GenericMediaServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
+  : fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr) {
+  // Add ourself to our 'client connections' table:
+  fOurServer.fClientConnections->Add((char const*)this, this);
+  
+  // Arrange to handle incoming requests:
+  resetRequestBuffer();
+  envir().taskScheduler()
+    .setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);
+}
+
+GenericMediaServer::ClientConnection::~ClientConnection() {
+  // Remove ourself from the server's 'client connections' hash table before we go:
+  fOurServer.fClientConnections->Remove((char const*)this);
+  
+  closeSockets();
+}
+
+void GenericMediaServer::ClientConnection::closeSockets() {
+  // Turn off background handling on our socket:
+  envir().taskScheduler().disableBackgroundHandling(fOurSocket);
+  ::closeSocket(fOurSocket);
+
+  fOurSocket = -1;
+}
+
+void GenericMediaServer::ClientConnection::incomingRequestHandler(void* instance, int /*mask*/) {
+  ClientConnection* connection = (ClientConnection*)instance;
+  connection->incomingRequestHandler();
+}
+
+void GenericMediaServer::ClientConnection::incomingRequestHandler() {
+  struct sockaddr_in dummy; // 'from' address, meaningless in this case
+  
+  int bytesRead = readSocket(envir(), fOurSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy);
+  handleRequestBytes(bytesRead);
+}
+
+void GenericMediaServer::ClientConnection::resetRequestBuffer() {
+  fRequestBytesAlreadySeen = 0;
+  fRequestBufferBytesLeft = sizeof fRequestBuffer;
+}
+
+
+////////// GenericMediaServer::ClientSession implementation //////////
+
+GenericMediaServer::ClientSession
+::ClientSession(GenericMediaServer& ourServer, u_int32_t sessionId)
+  : fOurServer(ourServer), fOurSessionId(sessionId), fOurServerMediaSession(NULL),
+    fLivenessCheckTask(NULL) {
+  noteLiveness();
+}
+
+GenericMediaServer::ClientSession::~ClientSession() {
+  // Turn off any liveness checking:
+  envir().taskScheduler().unscheduleDelayedTask(fLivenessCheckTask);
+
+  // Remove ourself from the server's 'client sessions' hash table before we go:
+  char sessionIdStr[8+1];
+  sprintf(sessionIdStr, "%08X", fOurSessionId);
+  fOurServer.fClientSessions->Remove(sessionIdStr);
+  
+  if (fOurServerMediaSession != NULL) {
+    fOurServerMediaSession->decrementReferenceCount();
+    if (fOurServerMediaSession->referenceCount() == 0
+	&& fOurServerMediaSession->deleteWhenUnreferenced()) {
+      fOurServer.removeServerMediaSession(fOurServerMediaSession);
+      fOurServerMediaSession = NULL;
+    }
+  }
+}
+
+void GenericMediaServer::ClientSession::noteLiveness() {
+#ifdef DEBUG
+  char const* streamName
+    = (fOurServerMediaSession == NULL) ? "???" : fOurServerMediaSession->streamName();
+  fprintf(stderr, "Client session (id \"%08X\", stream name \"%s\"): Liveness indication\n",
+	  fOurSessionId, streamName);
+#endif
+  if (fOurServer.fReclamationSeconds > 0) {
+    envir().taskScheduler().rescheduleDelayedTask(fLivenessCheckTask,
+						  fOurServer.fReclamationSeconds*1000000,
+						  (TaskFunc*)livenessTimeoutTask, this);
+  }
+}
+
+void GenericMediaServer::ClientSession::noteClientLiveness(ClientSession* clientSession) {
+  clientSession->noteLiveness();
+}
+
+void GenericMediaServer::ClientSession::livenessTimeoutTask(ClientSession* clientSession) {
+  // If this gets called, the client session is assumed to have timed out, so delete it:
+#ifdef DEBUG
+  char const* streamName
+    = (clientSession->fOurServerMediaSession == NULL) ? "???" : clientSession->fOurServerMediaSession->streamName();
+  fprintf(stderr, "Client session (id \"%08X\", stream name \"%s\") has timed out (due to inactivity)\n",
+	  clientSession->fOurSessionId, streamName);
+#endif
+  delete clientSession;
+}
+
+GenericMediaServer::ClientSession* GenericMediaServer::createNewClientSessionWithId() {
+  u_int32_t sessionId;
+  char sessionIdStr[8+1];
+
+  // Choose a random (unused) 32-bit integer for the session id
+  // (it will be encoded as a 8-digit hex number).  (We avoid choosing session id 0,
+  // because that has a special use by some servers.)
+  do {
+    sessionId = (u_int32_t)our_random32();
+    snprintf(sessionIdStr, sizeof sessionIdStr, "%08X", sessionId);
+  } while (sessionId == 0 || lookupClientSession(sessionIdStr) != NULL);
+
+  ClientSession* clientSession = createNewClientSession(sessionId);
+  fClientSessions->Add(sessionIdStr, clientSession);
+
+  return clientSession;
+}
+
+GenericMediaServer::ClientSession*
+GenericMediaServer::lookupClientSession(u_int32_t sessionId) {
+  char sessionIdStr[8+1];
+  snprintf(sessionIdStr, sizeof sessionIdStr, "%08X", sessionId);
+  return lookupClientSession(sessionIdStr);
+}
+
+GenericMediaServer::ClientSession*
+GenericMediaServer::lookupClientSession(char const* sessionIdStr) {
+  return (GenericMediaServer::ClientSession*)fClientSessions->Lookup(sessionIdStr);
+}
+
+
+////////// ServerMediaSessionIterator implementation //////////
+
+GenericMediaServer::ServerMediaSessionIterator
+::ServerMediaSessionIterator(GenericMediaServer& server)
+  : fOurIterator((server.fServerMediaSessions == NULL)
+		 ? NULL : HashTable::Iterator::create(*server.fServerMediaSessions)) {
+}
+
+GenericMediaServer::ServerMediaSessionIterator::~ServerMediaSessionIterator() {
+  delete fOurIterator;
+}
+
+ServerMediaSession* GenericMediaServer::ServerMediaSessionIterator::next() {
+  if (fOurIterator == NULL) return NULL;
+
+  char const* key; // dummy
+  return (ServerMediaSession*)(fOurIterator->next(key));
+}
+
+
+////////// UserAuthenticationDatabase implementation //////////
+
+UserAuthenticationDatabase::UserAuthenticationDatabase(char const* realm,
+						       Boolean passwordsAreMD5)
+  : fTable(HashTable::create(STRING_HASH_KEYS)),
+    fRealm(strDup(realm == NULL ? "LIVE555 Streaming Media" : realm)),
+    fPasswordsAreMD5(passwordsAreMD5) {
+}
+
+UserAuthenticationDatabase::~UserAuthenticationDatabase() {
+  delete[] fRealm;
+  
+  // Delete the allocated 'password' strings that we stored in the table, and then the table itself:
+  char* password;
+  while ((password = (char*)fTable->RemoveNext()) != NULL) {
+    delete[] password;
+  }
+  delete fTable;
+}
+
+void UserAuthenticationDatabase::addUserRecord(char const* username,
+					       char const* password) {
+  fTable->Add(username, (void*)(strDup(password)));
+}
+
+void UserAuthenticationDatabase::removeUserRecord(char const* username) {
+  char* password = (char*)(fTable->Lookup(username));
+  fTable->Remove(username);
+  delete[] password;
+}
+
+char const* UserAuthenticationDatabase::lookupPassword(char const* username) {
+  return (char const*)(fTable->Lookup(username));
+}
diff --git a/liveMedia/H261VideoRTPSource.cpp b/liveMedia/H261VideoRTPSource.cpp
index 48c1462..06b07ab 100644
--- a/liveMedia/H261VideoRTPSource.cpp
+++ b/liveMedia/H261VideoRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // H.261 Video RTP Sources
 // Implementation
 
diff --git a/liveMedia/H263plusVideoFileServerMediaSubsession.cpp b/liveMedia/H263plusVideoFileServerMediaSubsession.cpp
index 121e157..be13deb 100644
--- a/liveMedia/H263plusVideoFileServerMediaSubsession.cpp
+++ b/liveMedia/H263plusVideoFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a H263 video file.
 // Implementation
diff --git a/liveMedia/H263plusVideoRTPSink.cpp b/liveMedia/H263plusVideoRTPSink.cpp
index 5f587b9..65c6e50 100644
--- a/liveMedia/H263plusVideoRTPSink.cpp
+++ b/liveMedia/H263plusVideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for H.263+ video (RFC 4629)
 // Implementation
 
@@ -63,8 +63,7 @@ void H263plusVideoRTPSink
       return;
     }
     if (frameStart[0] != 0 || frameStart[1] != 0) {
-      envir() << "H263plusVideoRTPSink::doSpecialFrameHandling(): unexpected non-zero first two bytes: "
-	      << (void*)(frameStart[0]) << "," << (void*)(frameStart[1]) << "\n";
+      envir() << "H263plusVideoRTPSink::doSpecialFrameHandling(): unexpected non-zero first two bytes!\n";
     }
     frameStart[0] = specialHeader>>8;
     frameStart[1] = (unsigned char)specialHeader;
diff --git a/liveMedia/H263plusVideoRTPSource.cpp b/liveMedia/H263plusVideoRTPSource.cpp
index 67f00df..e538bd6 100644
--- a/liveMedia/H263plusVideoRTPSource.cpp
+++ b/liveMedia/H263plusVideoRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // H.263+ Video RTP Sources
 // Implementation
 
diff --git a/liveMedia/H263plusVideoStreamFramer.cpp b/liveMedia/H263plusVideoStreamFramer.cpp
index 17ce365..1b23375 100644
--- a/liveMedia/H263plusVideoStreamFramer.cpp
+++ b/liveMedia/H263plusVideoStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Author Bernhard Feiten
 // A filter that breaks up an H.263plus video stream into frames.
 //
diff --git a/liveMedia/H263plusVideoStreamParser.cpp b/liveMedia/H263plusVideoStreamParser.cpp
index 04c8af4..e59539f 100644
--- a/liveMedia/H263plusVideoStreamParser.cpp
+++ b/liveMedia/H263plusVideoStreamParser.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Author Bernhard Feiten
 // A filter that breaks up an H.263plus video stream into frames.
 // Based on MPEG4IP/mp4creator/h263.c
@@ -197,7 +197,7 @@ int H263plusVideoStreamParser::parseH263Frame( )
             ((row = fStates[(unsigned char)row][*(bufferIndex++)]) != -1)); // Start code was not found
 
    if (row != -1) {
-      fprintf(stderr, "%s: Buffer too small (%lu)\n",
+      fprintf(stderr, "%s: Buffer too small (%u)\n",
          "h263reader:", bufferEnd - fTo + ADDITIONAL_BYTES_NEEDED);
       return 0;
    }
@@ -632,7 +632,7 @@ static int LoadNextH263Object(  FILE           *inputFileHandle,
   // This table and the following loop implements a state machine enabling
   // us to read bytes from the file untill (and inclusing) the requested
   // start code (00 00 8X) is found
-  int8_t        row = 0;
+  char        row = 0;
   u_int8_t     *bufferStart = frameBuffer;
   // The buffer end which will allow the loop to leave place for
   // the additionalBytesNeeded
diff --git a/liveMedia/H263plusVideoStreamParser.hh b/liveMedia/H263plusVideoStreamParser.hh
index 6e8b400..475a5e7 100644
--- a/liveMedia/H263plusVideoStreamParser.hh
+++ b/liveMedia/H263plusVideoStreamParser.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an H263 video stream into frames.
 // derived from MPEG4IP h263.c
 // Author Benhard Feiten
diff --git a/liveMedia/H264VideoFileServerMediaSubsession.cpp b/liveMedia/H264VideoFileServerMediaSubsession.cpp
index b860d06..0d431d6 100644
--- a/liveMedia/H264VideoFileServerMediaSubsession.cpp
+++ b/liveMedia/H264VideoFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a H264 video file.
 // Implementation
@@ -70,7 +70,7 @@ void H264VideoFileServerMediaSubsession::checkForAuxSDPLine1() {
 
     // Signal the event loop that we're done:
     setDoneFlag();
-  } else {
+  } else if (!fDoneFlag) {
     // try again after a brief delay:
     int uSecsToDelay = 100000; // 100 ms
     nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecsToDelay,
diff --git a/liveMedia/H264VideoFileSink.cpp b/liveMedia/H264VideoFileSink.cpp
index af7bc74..5c0a098 100644
--- a/liveMedia/H264VideoFileSink.cpp
+++ b/liveMedia/H264VideoFileSink.cpp
@@ -14,13 +14,12 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // H.264 Video File sinks
 // Implementation
 
 #include "H264VideoFileSink.hh"
 #include "OutputFile.hh"
-#include "H264VideoRTPSource.hh"
 
 ////////// H264VideoFileSink //////////
 
@@ -28,8 +27,8 @@ H264VideoFileSink
 ::H264VideoFileSink(UsageEnvironment& env, FILE* fid,
 		    char const* sPropParameterSetsStr,
 		    unsigned bufferSize, char const* perFrameFileNamePrefix)
-  : FileSink(env, fid, bufferSize, perFrameFileNamePrefix),
-    fSPropParameterSetsStr(sPropParameterSetsStr), fHaveWrittenFirstFrame(False) {
+  : H264or5VideoFileSink(env, fid, bufferSize, perFrameFileNamePrefix,
+			 sPropParameterSetsStr, NULL, NULL) {
 }
 
 H264VideoFileSink::~H264VideoFileSink() {
@@ -58,25 +57,3 @@ H264VideoFileSink::createNew(UsageEnvironment& env, char const* fileName,
 
   return NULL;
 }
-
-void H264VideoFileSink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, 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
-  }
-
-  // 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:
-  FileSink::afterGettingFrame(frameSize, numTruncatedBytes, presentationTime);
-}
diff --git a/liveMedia/H264VideoMatroskaFileServerMediaSubsession.cpp b/liveMedia/H264VideoMatroskaFileServerMediaSubsession.cpp
deleted file mode 100644
index 56f693a..0000000
--- a/liveMedia/H264VideoMatroskaFileServerMediaSubsession.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an H264 video track within a Matroska file.
-// Implementation
-
-#include "H264VideoMatroskaFileServerMediaSubsession.hh"
-#include "H264VideoStreamDiscreteFramer.hh"
-#include "MatroskaDemuxedTrack.hh"
-
-H264VideoMatroskaFileServerMediaSubsession* H264VideoMatroskaFileServerMediaSubsession
-::createNew(MatroskaFileServerDemux& demux, unsigned trackNumber) {
-  return new H264VideoMatroskaFileServerMediaSubsession(demux, trackNumber);
-}
-
-#define CHECK_PTR if (ptr >= limit) return
-#define NUM_BYTES_REMAINING (unsigned)(limit - ptr)
-
-H264VideoMatroskaFileServerMediaSubsession
-::H264VideoMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber)
-  : H264VideoFileServerMediaSubsession(demux.envir(), demux.fileName(), False),
-    fOurDemux(demux), fTrackNumber(trackNumber),
-    fSPSSize(0), fSPS(NULL), fPPSSize(0), fPPS(NULL) {
-  // Use our track's 'Codec Private' data: Bytes 5 and beyond contain SPS and PPSs:
-  unsigned numSPSandPPSBytes;
-  u_int8_t* SPSandPPSBytes;
-  MatroskaTrack* track = fOurDemux.lookup(fTrackNumber);
-
-  if (track->codecPrivateSize >= 6) {
-    numSPSandPPSBytes = track->codecPrivateSize - 5;
-    SPSandPPSBytes = &track->codecPrivate[5];
-  } else {
-    numSPSandPPSBytes = 0;
-    SPSandPPSBytes = NULL;
-  }
-
-  // Extract, from "SPSandPPSBytes", one SPS NAL unit, and one PPS NAL unit.
-  // (I hope one is all we need of each.)
-  if (numSPSandPPSBytes == 0 || SPSandPPSBytes == NULL) return; // sanity check
-  unsigned i;
-  u_int8_t* ptr = SPSandPPSBytes;
-  u_int8_t* limit = &SPSandPPSBytes[numSPSandPPSBytes];
-
-  unsigned numSPSs = (*ptr++)&0x1F; CHECK_PTR;
-  for (i = 0; i < numSPSs; ++i) {
-    unsigned spsSize = (*ptr++)<<8; CHECK_PTR;
-    spsSize |= *ptr++; CHECK_PTR;
-    
-    if (spsSize > NUM_BYTES_REMAINING) return;
-    u_int8_t nal_unit_type = ptr[0]&0x1F;
-    if (fSPS == NULL && nal_unit_type == 7/*sanity check*/) { // save the first one
-      fSPSSize = spsSize;
-      fSPS = new u_int8_t[spsSize];
-      memmove(fSPS, ptr, spsSize);
-    }
-    ptr += spsSize;
-  }
-  
-  unsigned numPPSs = (*ptr++)&0x1F; CHECK_PTR;
-  for (i = 0; i < numPPSs; ++i) {
-    unsigned ppsSize = (*ptr++)<<8; CHECK_PTR;
-    ppsSize |= *ptr++; CHECK_PTR;
-    
-    if (ppsSize > NUM_BYTES_REMAINING) return;
-    u_int8_t nal_unit_type = ptr[0]&0x1F;
-    if (fPPS == NULL && nal_unit_type == 8/*sanity check*/) { // save the first one
-      fPPSSize = ppsSize;
-      fPPS = new u_int8_t[ppsSize];
-      memmove(fPPS, ptr, ppsSize);
-    }
-    ptr += ppsSize;
-  }
-}
-
-H264VideoMatroskaFileServerMediaSubsession
-::~H264VideoMatroskaFileServerMediaSubsession() {
-  delete[] fSPS;
-  delete[] fPPS;
-}
-
-float H264VideoMatroskaFileServerMediaSubsession::duration() const { return fOurDemux.fileDuration(); }
-
-void H264VideoMatroskaFileServerMediaSubsession
-::seekStreamSource(FramedSource* inputSource, double& seekNPT, double /*streamDuration*/, u_int64_t& /*numBytes*/) {
-  // "inputSource" is a framer. *Its* source is the demuxed track that we seek on:
-  H264VideoStreamDiscreteFramer* framer = (H264VideoStreamDiscreteFramer*)inputSource;
-
-  MatroskaDemuxedTrack* demuxedTrack = (MatroskaDemuxedTrack*)(framer->inputSource());
-  demuxedTrack->seekToTime(seekNPT);
-}
-
-FramedSource* H264VideoMatroskaFileServerMediaSubsession
-::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
-  // Allow for the possibility of very large NAL units being fed to our "RTPSink" objects:
-  OutPacketBuffer::maxSize = 300000; // bytes
-  estBitrate = 500; // kbps, estimate
-
-  // Create the video source:
-  FramedSource* baseH264VideoSource = fOurDemux.newDemuxedTrack(clientSessionId, fTrackNumber);
-  if (baseH264VideoSource == NULL) return NULL;
-  
-  // Create a framer for the Video stream:
-  H264VideoStreamDiscreteFramer* framer
-    = H264VideoStreamDiscreteFramer::createNew(envir(), baseH264VideoSource);
-  framer->setVPSandSPSandPPS(NULL, 0, fSPS, fSPSSize, fPPS, fPPSSize);
-
-  return framer;
-}
diff --git a/liveMedia/H264VideoMatroskaFileServerMediaSubsession.hh b/liveMedia/H264VideoMatroskaFileServerMediaSubsession.hh
deleted file mode 100644
index 1b8e88d..0000000
--- a/liveMedia/H264VideoMatroskaFileServerMediaSubsession.hh
+++ /dev/null
@@ -1,59 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an H264 video track within a Matroska file.
-// C++ header
-
-#ifndef _H264_VIDEO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-#define _H264_VIDEO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-
-#ifndef _H264_VIDEO_FILE_SERVER_MEDIA_SUBSESSION_HH
-#include "H264VideoFileServerMediaSubsession.hh"
-#endif
-#ifndef _MATROSKA_FILE_SERVER_DEMUX_HH
-#include "MatroskaFileServerDemux.hh"
-#endif
-
-class H264VideoMatroskaFileServerMediaSubsession: public H264VideoFileServerMediaSubsession {
-public:
-  static H264VideoMatroskaFileServerMediaSubsession*
-  createNew(MatroskaFileServerDemux& demux, unsigned trackNumber);
-
-private:
-  H264VideoMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber);
-      // called only by createNew();
-  virtual ~H264VideoMatroskaFileServerMediaSubsession();
-
-private: // redefined virtual functions
-  virtual float duration() const;
-  virtual void seekStreamSource(FramedSource* inputSource, double& seekNPT, double streamDuration, u_int64_t& numBytes);
-  virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
-                                              unsigned& estBitrate);
-
-private:
-  MatroskaFileServerDemux& fOurDemux;
-  unsigned fTrackNumber;
-
-  // We store one SPS, and one PPS, for use in our input 'framer's:
-  unsigned fSPSSize;
-  u_int8_t* fSPS;
-  unsigned fPPSSize;
-  u_int8_t* fPPS;
-};
-
-#endif
diff --git a/liveMedia/H264VideoRTPSink.cpp b/liveMedia/H264VideoRTPSink.cpp
index 0c57151..cc26425 100644
--- a/liveMedia/H264VideoRTPSink.cpp
+++ b/liveMedia/H264VideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for H.264 video (RFC 3984)
 // Implementation
 
@@ -97,8 +97,18 @@ char const* H264VideoRTPSink::auxSDPLine() {
   }
 
   // Set up the "a=fmtp:" SDP line for this stream:
+  u_int8_t* spsWEB = new u_int8_t[spsSize]; // "WEB" means "Without Emulation Bytes"
+  unsigned spsWEBSize = removeH264or5EmulationBytes(spsWEB, spsSize, sps, spsSize);
+  if (spsWEBSize < 4) { // Bad SPS size => assume our source isn't ready
+    delete[] spsWEB;
+    return NULL;
+  }
+  u_int32_t profileLevelId = (spsWEB[1]<<16) | (spsWEB[2]<<8) | spsWEB[3];
+  delete[] spsWEB;
+
   char* sps_base64 = base64Encode((char*)sps, spsSize);
   char* pps_base64 = base64Encode((char*)pps, ppsSize);
+
   char const* fmtpFmt =
     "a=fmtp:%d packetization-mode=1"
     ";profile-level-id=%06X"
@@ -110,8 +120,9 @@ char const* H264VideoRTPSink::auxSDPLine() {
   char* fmtp = new char[fmtpFmtSize];
   sprintf(fmtp, fmtpFmt,
           rtpPayloadType(),
-	  framerSource->profileLevelId(),
+	  profileLevelId,
           sps_base64, pps_base64);
+
   delete[] sps_base64;
   delete[] pps_base64;
 
diff --git a/liveMedia/H264VideoRTPSource.cpp b/liveMedia/H264VideoRTPSource.cpp
index 7998660..68cd9e7 100644
--- a/liveMedia/H264VideoRTPSource.cpp
+++ b/liveMedia/H264VideoRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // H.264 Video RTP Sources
 // Implementation
 
@@ -67,51 +67,48 @@ Boolean H264VideoRTPSource
                        unsigned& resultSpecialHeaderSize) {
   unsigned char* headerStart = packet->data();
   unsigned packetSize = packet->dataSize();
+  unsigned numBytesToSkip;
 
-  // The header has a minimum size of 0, since the NAL header is used
-  // as a payload header
-  unsigned expectedHeaderSize = 0;
-
-  // Check if the type field is 28 (FU-A) or 29 (FU-B)
+  // Check the 'nal_unit_type' for special 'aggregation' or 'fragmentation' packets:
+  if (packetSize < 1) return False;
   fCurPacketNALUnitType = (headerStart[0]&0x1F);
   switch (fCurPacketNALUnitType) {
   case 24: { // STAP-A
-    expectedHeaderSize = 1; // discard the type byte
+    numBytesToSkip = 1; // discard the type byte
     break;
   }
   case 25: case 26: case 27: { // STAP-B, MTAP16, or MTAP24
-    expectedHeaderSize = 3; // discard the type byte, and the initial DON
+    numBytesToSkip = 3; // discard the type byte, and the initial DON
     break;
   }
   case 28: case 29: { // // FU-A or FU-B
     // For these NALUs, the first two bytes are the FU indicator and the FU header.
-    // If the start bit is set, we reconstruct the original NAL header:
+    // If the start bit is set, we reconstruct the original NAL header into byte 1:
+    if (packetSize < 2) return False;
     unsigned char startBit = headerStart[1]&0x80;
     unsigned char endBit = headerStart[1]&0x40;
     if (startBit) {
-      expectedHeaderSize = 1;
-      if (packetSize < expectedHeaderSize) return False;
-
-      headerStart[1] = (headerStart[0]&0xE0)+(headerStart[1]&0x1F);
       fCurrentPacketBeginsFrame = True;
+
+      headerStart[1] = (headerStart[0]&0xE0)|(headerStart[1]&0x1F);
+      numBytesToSkip = 1;
     } else {
-      // If the startbit is not set, both the FU indicator and header
-      // can be discarded
-      expectedHeaderSize = 2;
-      if (packetSize < expectedHeaderSize) return False;
+      // The start bit is not set, so we skip both the FU indicator and header:
       fCurrentPacketBeginsFrame = False;
+      numBytesToSkip = 2;
     }
     fCurrentPacketCompletesFrame = (endBit != 0);
     break;
   }
   default: {
-    // This packet contains one or more complete, decodable NAL units
+    // This packet contains one complete NAL unit:
     fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame = True;
+    numBytesToSkip = 0;
     break;
   }
   }
 
-  resultSpecialHeaderSize = expectedHeaderSize;
+  resultSpecialHeaderSize = numBytesToSkip;
   return True;
 }
 
diff --git a/liveMedia/H264VideoStreamDiscreteFramer.cpp b/liveMedia/H264VideoStreamDiscreteFramer.cpp
index ad15947..6b95c5f 100644
--- a/liveMedia/H264VideoStreamDiscreteFramer.cpp
+++ b/liveMedia/H264VideoStreamDiscreteFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "H264VideoStreamFramer" that takes only complete,
 // discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
diff --git a/liveMedia/H264VideoStreamFramer.cpp b/liveMedia/H264VideoStreamFramer.cpp
index 2be9d47..bf7f5cb 100644
--- a/liveMedia/H264VideoStreamFramer.cpp
+++ b/liveMedia/H264VideoStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up a H.264 Video Elementary Stream into NAL units.
 // Implementation
 
diff --git a/liveMedia/H264or5VideoFileSink.cpp b/liveMedia/H264or5VideoFileSink.cpp
new file mode 100644
index 0000000..f73d769
--- /dev/null
+++ b/liveMedia/H264or5VideoFileSink.cpp
@@ -0,0 +1,65 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// H.264 or H.265 Video File sinks
+// Implementation
+
+#include "H264or5VideoFileSink.hh"
+#include "H264VideoRTPSource.hh" // for "parseSPropParameterSets()"
+
+////////// H264or5VideoFileSink //////////
+
+H264or5VideoFileSink
+::H264or5VideoFileSink(UsageEnvironment& env, FILE* fid,
+		       unsigned bufferSize, char const* perFrameFileNamePrefix,
+		       char const* sPropParameterSetsStr1,
+                       char const* sPropParameterSetsStr2,
+                       char const* sPropParameterSetsStr3)
+  : FileSink(env, fid, bufferSize, perFrameFileNamePrefix),
+    fHaveWrittenFirstFrame(False) {
+  fSPropParameterSetsStr[0] = sPropParameterSetsStr1;
+  fSPropParameterSetsStr[1] = sPropParameterSetsStr2;
+  fSPropParameterSetsStr[2] = sPropParameterSetsStr3;
+}
+
+H264or5VideoFileSink::~H264or5VideoFileSink() {
+}
+
+void H264or5VideoFileSink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime) {
+  unsigned char const start_code[4] = {0x00, 0x00, 0x00, 0x01};
+
+  if (!fHaveWrittenFirstFrame) {
+    // If we have NAL units encoded in "sprop parameter strings", prepend these to the file:
+    for (unsigned j = 0; j < 3; ++j) {
+      unsigned numSPropRecords;
+      SPropRecord* sPropRecords
+	= parseSPropParameterSets(fSPropParameterSetsStr[j], 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
+  }
+
+  // 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:
+  FileSink::afterGettingFrame(frameSize, numTruncatedBytes, presentationTime);
+}
diff --git a/liveMedia/H264or5VideoRTPSink.cpp b/liveMedia/H264or5VideoRTPSink.cpp
index 8e8742a..b8c16d3 100644
--- a/liveMedia/H264or5VideoRTPSink.cpp
+++ b/liveMedia/H264or5VideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for H.264 or H.265 video
 // Implementation
 
@@ -40,6 +40,7 @@ public:
 
 private: // redefined virtual functions:
   virtual void doGetNextFrame();
+  virtual void doStopGettingFrames();
 
 private:
   static void afterGettingFrame(void* clientData, unsigned frameSize,
@@ -50,6 +51,7 @@ private:
                           unsigned numTruncatedBytes,
                           struct timeval presentationTime,
                           unsigned durationInMicroseconds);
+  void reset();
 
 private:
   int fHNumber;
@@ -161,10 +163,9 @@ H264or5Fragmenter::H264or5Fragmenter(int hNumber,
 				     unsigned inputBufferMax, unsigned maxOutputPacketSize)
   : FramedFilter(env, inputSource),
     fHNumber(hNumber),
-    fInputBufferSize(inputBufferMax+1), fMaxOutputPacketSize(maxOutputPacketSize),
-    fNumValidDataBytes(1), fCurDataOffset(1), fSaveNumTruncatedBytes(0),
-    fLastFragmentCompletedNALUnit(True) {
+    fInputBufferSize(inputBufferMax+1), fMaxOutputPacketSize(maxOutputPacketSize) {
   fInputBuffer = new unsigned char[fInputBufferSize];
+  reset();
 }
 
 H264or5Fragmenter::~H264or5Fragmenter() {
@@ -263,6 +264,12 @@ void H264or5Fragmenter::doGetNextFrame() {
   }
 }
 
+void H264or5Fragmenter::doStopGettingFrames() {
+  // Make sure that we don't have any stale data fragments lying around, should we later resume:
+  reset();
+  FramedFilter::doStopGettingFrames();
+}
+
 void H264or5Fragmenter::afterGettingFrame(void* clientData, unsigned frameSize,
 					  unsigned numTruncatedBytes,
 					  struct timeval presentationTime,
@@ -284,3 +291,9 @@ void H264or5Fragmenter::afterGettingFrame1(unsigned frameSize,
   // Deliver data to the client:
   doGetNextFrame();
 }
+
+void H264or5Fragmenter::reset() {
+  fNumValidDataBytes = fCurDataOffset = 1;
+  fSaveNumTruncatedBytes = 0;
+  fLastFragmentCompletedNALUnit = True;
+}
diff --git a/liveMedia/H264or5VideoStreamDiscreteFramer.cpp b/liveMedia/H264or5VideoStreamDiscreteFramer.cpp
index 0f5c1b7..dae5368 100644
--- a/liveMedia/H264or5VideoStreamDiscreteFramer.cpp
+++ b/liveMedia/H264or5VideoStreamDiscreteFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "H264or5VideoStreamFramer" that takes only complete,
 // discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
@@ -79,11 +79,7 @@ void H264or5VideoStreamDiscreteFramer
     saveCopyOfPPS(fTo, frameSize);
   }
 
-  // Next, check whether this NAL unit ends the current 'access unit' (basically, a video frame).
-  //  Unfortunately, we can't do this reliably, because we don't yet know anything about the
-  // *next* NAL unit that we'll see.  So, we guess this as best as we can, by assuming that
-  // if this NAL unit is a VCL NAL unit, then it ends the current 'access unit'.
-  if (isVCL(nal_unit_type)) fPictureEndMarker = True;
+  fPictureEndMarker = nalUnitEndsAccessUnit(nal_unit_type);
 
   // Finally, complete delivery to the client:
   fFrameSize = frameSize;
@@ -92,3 +88,15 @@ void H264or5VideoStreamDiscreteFramer
   fDurationInMicroseconds = durationInMicroseconds;
   afterGetting(this);
 }
+
+Boolean H264or5VideoStreamDiscreteFramer::nalUnitEndsAccessUnit(u_int8_t nal_unit_type) {
+  // Check whether this NAL unit ends the current 'access unit' (basically, a video frame).
+  //  Unfortunately, we can't do this reliably, because we don't yet know anything about the
+  // *next* NAL unit that we'll see.  So, we guess this as best as we can, by assuming that
+  // if this NAL unit is a VCL NAL unit, then it ends the current 'access unit'.
+  //
+  // This will be wrong if you are streaming multiple 'slices' per picture.  In that case,
+  // you can define a subclass that reimplements this virtual function to do the right thing.
+
+  return isVCL(nal_unit_type);
+}
diff --git a/liveMedia/H264or5VideoStreamFramer.cpp b/liveMedia/H264or5VideoStreamFramer.cpp
index 055f62d..b027275 100644
--- a/liveMedia/H264or5VideoStreamFramer.cpp
+++ b/liveMedia/H264or5VideoStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up a H.264 or H.265 Video Elementary Stream into NAL units.
 // Implementation
 
@@ -53,7 +53,9 @@ private:
   void analyze_seq_parameter_set_data(unsigned& num_units_in_tick, unsigned& time_scale);
   void profile_tier_level(BitVector& bv, unsigned max_sub_layers_minus1);
   void analyze_vui_parameters(BitVector& bv, unsigned& num_units_in_tick, unsigned& time_scale);
+  void analyze_hrd_parameters(BitVector& bv);
   void analyze_sei_data(u_int8_t nal_unit_type);
+  void analyze_sei_payload(unsigned payloadType, unsigned payloadSize, u_int8_t* payload);
 
 private:
   int fHNumber; // 264 or 265
@@ -61,6 +63,10 @@ private:
   Boolean fHaveSeenFirstStartCode, fHaveSeenFirstByteOfNALUnit;
   u_int8_t fFirstByteOfNALUnit;
   double fParsedFrameRate;
+  // variables set & used in the specification:
+  unsigned cpb_removal_delay_length_minus1, dpb_output_delay_length_minus1;
+  Boolean CpbDpbDelaysPresentFlag, pic_struct_present_flag;
+  double DeltaTfiDivisor;
 };
 
 
@@ -73,10 +79,7 @@ H264or5VideoStreamFramer
     fHNumber(hNumber),
     fLastSeenVPS(NULL), fLastSeenVPSSize(0),
     fLastSeenSPS(NULL), fLastSeenSPSSize(0),
-    fLastSeenPPS(NULL), fLastSeenPPSSize(0),
-    fProfileLevelId(0) {
-  for (unsigned i = 0; i < 12; ++i) fProfileTierLevelHeaderBytes[i] = 0;
-
+    fLastSeenPPS(NULL), fLastSeenPPSSize(0) {
   fParser = createParser
     ? new H264or5VideoStreamParser(hNumber, this, inputSource, includeStartCodeInOutput)
     : NULL;
@@ -99,16 +102,6 @@ void H264or5VideoStreamFramer::saveCopyOfVPS(u_int8_t* from, unsigned size) {
   memmove(fLastSeenVPS, from, size);
 
   fLastSeenVPSSize = size;
-
-  // We also make another copy - without 'emulation bytes', to extract parameters that we need:
-  u_int8_t vps[VPS_MAX_SIZE];
-  unsigned vpsSize
-    = removeH264or5EmulationBytes(vps, VPS_MAX_SIZE, fLastSeenVPS, fLastSeenVPSSize);
-
-  // Extract the first 12 'profile_tier_level' bytes:
-  if (vpsSize >= 6/*'profile_tier_level' offset*/ + 12/*num 'profile_tier_level' bytes*/) {
-    memmove(fProfileTierLevelHeaderBytes, &vps[6], 12);
-  }
 }
 
 #define SPS_MAX_SIZE 1000 // larger than the largest possible SPS (Sequence Parameter Set) NAL unit
@@ -120,22 +113,6 @@ void H264or5VideoStreamFramer::saveCopyOfSPS(u_int8_t* from, unsigned size) {
   memmove(fLastSeenSPS, from, size);
 
   fLastSeenSPSSize = size;
-
-  // We also make another copy - without 'emulation bytes', to extract parameters that we need:
-  u_int8_t sps[SPS_MAX_SIZE];
-  unsigned spsSize
-    = removeH264or5EmulationBytes(sps, SPS_MAX_SIZE, fLastSeenSPS, fLastSeenSPSSize);
-  if (fHNumber == 264) {
-    // Extract the first 3 bytes of the SPS (after the nal_unit_header byte) as 'profile_level_id'
-    if (spsSize >= 1/*'profile_level_id' offset within SPS*/ + 3/*num bytes needed*/) {
-      fProfileLevelId = (sps[1]<<16) | (sps[2]<<8) | sps[3];
-    }
-  } else { // 265
-    // Extract the first 12 'profile_tier_level' bytes:
-    if (spsSize >= 3/*'profile_tier_level' offset*/ + 12/*num 'profile_tier_level' bytes*/) {
-      memmove(fProfileTierLevelHeaderBytes, &sps[3], 12);
-    }
-  }
 }
 
 void H264or5VideoStreamFramer::saveCopyOfPPS(u_int8_t* from, unsigned size) {
@@ -173,7 +150,10 @@ H264or5VideoStreamParser
 ::H264or5VideoStreamParser(int hNumber, H264or5VideoStreamFramer* usingSource,
 			   FramedSource* inputSource, Boolean includeStartCodeInOutput)
   : MPEGVideoStreamParser(usingSource, inputSource),
-    fHNumber(hNumber), fOutputStartCodeSize(includeStartCodeInOutput ? 4 : 0), fHaveSeenFirstStartCode(False), fHaveSeenFirstByteOfNALUnit(False), fParsedFrameRate(0.0) {
+    fHNumber(hNumber), fOutputStartCodeSize(includeStartCodeInOutput ? 4 : 0), fHaveSeenFirstStartCode(False), fHaveSeenFirstByteOfNALUnit(False), fParsedFrameRate(0.0),
+    cpb_removal_delay_length_minus1(23), dpb_output_delay_length_minus1(23),
+    CpbDpbDelaysPresentFlag(0), pic_struct_present_flag(0),
+    DeltaTfiDivisor(2.0) {
 }
 
 H264or5VideoStreamParser::~H264or5VideoStreamParser() {
@@ -389,7 +369,10 @@ void H264or5VideoStreamParser
     (void)bv.get_expGolomb(); // chroma_sample_loc_type_bottom_field
   }
   if (fHNumber == 265) {
-    bv.skipBits(3); // neutral_chroma_indication_flag, field_seq_flag, frame_field_info_present_flag
+    bv.skipBits(2); // neutral_chroma_indication_flag, field_seq_flag
+    Boolean frame_field_info_present_flag = bv.get1BitBoolean();
+    DEBUG_PRINT(frame_field_info_present_flag);
+    pic_struct_present_flag = frame_field_info_present_flag; // hack to make H.265 like H.264
     Boolean default_display_window_flag = bv.get1BitBoolean();
     DEBUG_PRINT(default_display_window_flag);
     if (default_display_window_flag) {
@@ -417,8 +400,50 @@ void H264or5VideoStreamParser
 	unsigned vui_num_ticks_poc_diff_one_minus1 = bv.get_expGolomb();
 	DEBUG_PRINT(vui_num_ticks_poc_diff_one_minus1);
       }
+      return; // For H.265, don't bother parsing any more of this #####
     }
   }
+  // The following is H.264 only: #####
+  Boolean nal_hrd_parameters_present_flag = bv.get1BitBoolean();
+  DEBUG_PRINT(nal_hrd_parameters_present_flag);
+  if (nal_hrd_parameters_present_flag) analyze_hrd_parameters(bv);
+  Boolean vcl_hrd_parameters_present_flag = bv.get1BitBoolean();
+  DEBUG_PRINT(vcl_hrd_parameters_present_flag);
+  if (vcl_hrd_parameters_present_flag) analyze_hrd_parameters(bv);
+  CpbDpbDelaysPresentFlag = nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag;
+  if (CpbDpbDelaysPresentFlag) {
+    bv.skipBits(1); // low_delay_hrd_flag
+  }
+  pic_struct_present_flag = bv.get1BitBoolean();
+  DEBUG_PRINT(pic_struct_present_flag);
+}
+
+void H264or5VideoStreamParser::analyze_hrd_parameters(BitVector& bv) {
+  DEBUG_TAB;
+  unsigned cpb_cnt_minus1 = bv.get_expGolomb();
+  DEBUG_PRINT(cpb_cnt_minus1);
+  unsigned bit_rate_scale = bv.getBits(4);
+  DEBUG_PRINT(bit_rate_scale);
+  unsigned cpb_size_scale = bv.getBits(4);
+  DEBUG_PRINT(cpb_size_scale);
+  for (unsigned SchedSelIdx = 0; SchedSelIdx <= cpb_cnt_minus1; ++SchedSelIdx) {
+    DEBUG_TAB;
+    DEBUG_PRINT(SchedSelIdx);
+    unsigned bit_rate_value_minus1 = bv.get_expGolomb();
+    DEBUG_PRINT(bit_rate_value_minus1);
+    unsigned cpb_size_value_minus1 = bv.get_expGolomb();
+    DEBUG_PRINT(cpb_size_value_minus1);
+    Boolean cbr_flag = bv.get1BitBoolean();
+    DEBUG_PRINT(cbr_flag);
+  }
+  unsigned initial_cpb_removal_delay_length_minus1 = bv.getBits(5);
+  DEBUG_PRINT(initial_cpb_removal_delay_length_minus1);
+  cpb_removal_delay_length_minus1 = bv.getBits(5);
+  DEBUG_PRINT(cpb_removal_delay_length_minus1);
+  dpb_output_delay_length_minus1 = bv.getBits(5);
+  DEBUG_PRINT(dpb_output_delay_length_minus1);
+  unsigned time_offset_length = bv.getBits(5);
+  DEBUG_PRINT(time_offset_length);
 }
 
 void H264or5VideoStreamParser
@@ -674,12 +699,12 @@ void H264or5VideoStreamParser
     }
     unsigned num_short_term_ref_pic_sets = bv.get_expGolomb();
     DEBUG_PRINT(num_short_term_ref_pic_sets);
+    unsigned num_negative_pics = 0, prev_num_negative_pics = 0;
+    unsigned num_positive_pics = 0, prev_num_positive_pics = 0;
     for (i = 0; i < num_short_term_ref_pic_sets; ++i) {
       // short_term_ref_pic_set(i):
       DEBUG_TAB;
       DEBUG_PRINT(i);
-      unsigned num_negative_pics = 0;
-      unsigned num_positive_pics = 0;
       Boolean inter_ref_pic_set_prediction_flag = False;
       if (i != 0) {
 	inter_ref_pic_set_prediction_flag = bv.get1BitBoolean();
@@ -693,15 +718,18 @@ void H264or5VideoStreamParser
 	}
 	bv.skipBits(1); // delta_rps_sign
 	(void)bv.get_expGolomb(); // abs_delta_rps_minus1
-	for (unsigned j = 0; j < num_negative_pics+num_positive_pics; ++j) {
+	unsigned NumDeltaPocs = prev_num_negative_pics + prev_num_positive_pics; // correct???
+	for (unsigned j = 0; j < NumDeltaPocs; ++j) {
 	  DEBUG_PRINT(j);
 	  Boolean used_by_curr_pic_flag = bv.get1BitBoolean();
 	  DEBUG_PRINT(used_by_curr_pic_flag);
 	  if (!used_by_curr_pic_flag) bv.skipBits(1); // use_delta_flag[j]
 	}
       } else {
+	prev_num_negative_pics = num_negative_pics;
 	num_negative_pics = bv.get_expGolomb();
 	DEBUG_PRINT(num_negative_pics);
+	prev_num_positive_pics = num_positive_pics;
 	num_positive_pics = bv.get_expGolomb();
 	DEBUG_PRINT(num_positive_pics);
 	unsigned k;
@@ -848,10 +876,63 @@ void H264or5VideoStreamParser::analyze_sei_data(u_int8_t nal_unit_type) {
     }
     fprintf(stderr, "\tpayloadType %d (\"%s\"); payloadSize %d\n", payloadType, description, payloadSize);
 #endif
+
+    analyze_sei_payload(payloadType, payloadSize, &sei[j]);
     j += payloadSize;
   }
 }
 
+void H264or5VideoStreamParser
+::analyze_sei_payload(unsigned payloadType, unsigned payloadSize, u_int8_t* payload) {
+  if (payloadType == 1/* pic_timing, for both H.264 and H.265 */) {
+    BitVector bv(payload, 0, 8*payloadSize);
+
+    DEBUG_TAB;
+    if (CpbDpbDelaysPresentFlag) {
+      unsigned cpb_removal_delay = bv.getBits(cpb_removal_delay_length_minus1 + 1);
+      DEBUG_PRINT(cpb_removal_delay);
+      unsigned dpb_output_delay = bv.getBits(dpb_output_delay_length_minus1 + 1);
+      DEBUG_PRINT(dpb_output_delay);
+    }
+    if (pic_struct_present_flag) {
+      unsigned pic_struct = bv.getBits(4);
+      DEBUG_PRINT(pic_struct);
+      // Use this to set "DeltaTfiDivisor" (which is used to compute the frame rate):
+      double prevDeltaTfiDivisor = DeltaTfiDivisor; 
+      if (fHNumber == 264) {
+	DeltaTfiDivisor =
+	  pic_struct == 0 ? 2.0 :
+	  pic_struct <= 2 ? 1.0 :
+	  pic_struct <= 4 ? 2.0 :
+	  pic_struct <= 6 ? 3.0 :
+	  pic_struct == 7 ? 4.0 :
+	  pic_struct == 8 ? 6.0 :
+	  2.0;
+      } else { // H.265
+	DeltaTfiDivisor =
+	  pic_struct == 0 ? 2.0 :
+	  pic_struct <= 2 ? 1.0 :
+	  pic_struct <= 4 ? 2.0 :
+	  pic_struct <= 6 ? 3.0 :
+	  pic_struct == 7 ? 2.0 :
+	  pic_struct == 8 ? 3.0 :
+	  pic_struct <= 12 ? 1.0 :
+	  2.0;
+      }
+      // If "DeltaTfiDivisor" has changed, and we've already computed the frame rate, then
+      // adjust it, based on the new value of "DeltaTfiDivisor":
+      if (DeltaTfiDivisor != prevDeltaTfiDivisor && fParsedFrameRate != 0.0) {
+	  usingSource()->fFrameRate = fParsedFrameRate
+	    = fParsedFrameRate*(prevDeltaTfiDivisor/DeltaTfiDivisor);
+#ifdef DEBUG
+	  fprintf(stderr, "Changed frame rate to %f fps\n", usingSource()->fFrameRate);
+#endif
+      }
+    }
+    // Ignore the rest of the payload (timestamps) for now... #####
+  }
+}
+
 void H264or5VideoStreamParser::flushInput() {
   fHaveSeenFirstStartCode = False;
   fHaveSeenFirstByteOfNALUnit = False;
@@ -964,7 +1045,7 @@ unsigned H264or5VideoStreamParser::parse() {
     // Now that we have found (& copied) a NAL unit, process it if it's of special interest to us:
     if (isVPS(nal_unit_type)) { // Video parameter set
       // First, save a copy of this NAL unit, in case the downstream object wants to see it:
-      usingSource()->saveCopyOfVPS(fStartOfFrame + fOutputStartCodeSize, fTo - fStartOfFrame - fOutputStartCodeSize);
+      usingSource()->saveCopyOfVPS(fStartOfFrame + fOutputStartCodeSize, curFrameSize() - fOutputStartCodeSize);
 
       if (fParsedFrameRate == 0.0) {
 	// We haven't yet parsed a frame rate from the stream.
@@ -972,19 +1053,20 @@ unsigned H264or5VideoStreamParser::parse() {
 	unsigned num_units_in_tick, time_scale;
 	analyze_video_parameter_set_data(num_units_in_tick, time_scale);
 	if (time_scale > 0 && num_units_in_tick > 0) {
-	  usingSource()->fFrameRate = fParsedFrameRate = time_scale/(2.0*num_units_in_tick);
+	  usingSource()->fFrameRate = fParsedFrameRate
+	    = time_scale/(DeltaTfiDivisor*num_units_in_tick);
 #ifdef DEBUG
 	  fprintf(stderr, "Set frame rate to %f fps\n", usingSource()->fFrameRate);
 #endif
 	} else {
 #ifdef DEBUG
-	  fprintf(stderr, "\tThis \"Picture Parameter Set\" NAL unit contained no frame rate information, so we use a default frame rate of %f fps\n", usingSource()->fFrameRate);
+	  fprintf(stderr, "\tThis \"Video Parameter Set\" NAL unit contained no frame rate information, so we use a default frame rate of %f fps\n", usingSource()->fFrameRate);
 #endif
 	}
       }
     } else if (isSPS(nal_unit_type)) { // Sequence parameter set
       // First, save a copy of this NAL unit, in case the downstream object wants to see it:
-      usingSource()->saveCopyOfSPS(fStartOfFrame + fOutputStartCodeSize, fTo - fStartOfFrame - fOutputStartCodeSize);
+      usingSource()->saveCopyOfSPS(fStartOfFrame + fOutputStartCodeSize, curFrameSize() - fOutputStartCodeSize);
 
       if (fParsedFrameRate == 0.0) {
 	// We haven't yet parsed a frame rate from the stream.
@@ -992,7 +1074,8 @@ unsigned H264or5VideoStreamParser::parse() {
 	unsigned num_units_in_tick, time_scale;
 	analyze_seq_parameter_set_data(num_units_in_tick, time_scale);
 	if (time_scale > 0 && num_units_in_tick > 0) {
-	  usingSource()->fFrameRate = fParsedFrameRate = time_scale/(2.0*num_units_in_tick);
+	  usingSource()->fFrameRate = fParsedFrameRate
+	    = time_scale/(DeltaTfiDivisor*num_units_in_tick);
 #ifdef DEBUG
 	  fprintf(stderr, "Set frame rate to %f fps\n", usingSource()->fFrameRate);
 #endif
@@ -1004,7 +1087,7 @@ unsigned H264or5VideoStreamParser::parse() {
       }
     } else if (isPPS(nal_unit_type)) { // Picture parameter set
       // Save a copy of this NAL unit, in case the downstream object wants to see it:
-      usingSource()->saveCopyOfPPS(fStartOfFrame + fOutputStartCodeSize, fTo - fStartOfFrame - fOutputStartCodeSize);
+      usingSource()->saveCopyOfPPS(fStartOfFrame + fOutputStartCodeSize, curFrameSize() - fOutputStartCodeSize);
     } else if (isSEI(nal_unit_type)) { // Supplemental enhancement information (SEI)
       analyze_sei_data(nal_unit_type);
       // Later, perhaps adjust "fPresentationTime" if we saw a "pic_timing" SEI payload??? #####
diff --git a/liveMedia/H265VideoFileServerMediaSubsession.cpp b/liveMedia/H265VideoFileServerMediaSubsession.cpp
index 89dbfea..23a390c 100644
--- a/liveMedia/H265VideoFileServerMediaSubsession.cpp
+++ b/liveMedia/H265VideoFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a H265 video file.
 // Implementation
@@ -70,7 +70,7 @@ void H265VideoFileServerMediaSubsession::checkForAuxSDPLine1() {
 
     // Signal the event loop that we're done:
     setDoneFlag();
-  } else {
+  } else if (!fDoneFlag) {
     // try again after a brief delay:
     int uSecsToDelay = 100000; // 100 ms
     nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecsToDelay,
diff --git a/liveMedia/H265VideoFileSink.cpp b/liveMedia/H265VideoFileSink.cpp
new file mode 100644
index 0000000..35bf32f
--- /dev/null
+++ b/liveMedia/H265VideoFileSink.cpp
@@ -0,0 +1,63 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// H.265 Video File sinks
+// Implementation
+
+#include "H265VideoFileSink.hh"
+#include "OutputFile.hh"
+
+////////// H265VideoFileSink //////////
+
+H265VideoFileSink
+::H265VideoFileSink(UsageEnvironment& env, FILE* fid,
+		    char const* sPropVPSStr,
+                    char const* sPropSPSStr,
+                    char const* sPropPPSStr,
+		    unsigned bufferSize, char const* perFrameFileNamePrefix)
+  : H264or5VideoFileSink(env, fid, bufferSize, perFrameFileNamePrefix,
+			 sPropVPSStr, sPropSPSStr, sPropPPSStr) {
+}
+
+H265VideoFileSink::~H265VideoFileSink() {
+}
+
+H265VideoFileSink*
+H265VideoFileSink::createNew(UsageEnvironment& env, char const* fileName,
+			     char const* sPropVPSStr,
+			     char const* sPropSPSStr,
+			     char const* sPropPPSStr,
+			     unsigned bufferSize, Boolean oneFilePerFrame) {
+  do {
+    FILE* fid;
+    char const* perFrameFileNamePrefix;
+    if (oneFilePerFrame) {
+      // Create the fid for each frame
+      fid = NULL;
+      perFrameFileNamePrefix = fileName;
+    } else {
+      // Normal case: create the fid once
+      fid = OpenOutputFile(env, fileName);
+      if (fid == NULL) break;
+      perFrameFileNamePrefix = NULL;
+    }
+
+    return new H265VideoFileSink(env, fid, sPropVPSStr, sPropSPSStr, sPropPPSStr, bufferSize, perFrameFileNamePrefix);
+  } while (0);
+
+  return NULL;
+}
diff --git a/liveMedia/H265VideoMatroskaFileServerMediaSubsession.cpp b/liveMedia/H265VideoMatroskaFileServerMediaSubsession.cpp
deleted file mode 100644
index e014072..0000000
--- a/liveMedia/H265VideoMatroskaFileServerMediaSubsession.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an H265 video track within a Matroska file.
-// Implementation
-
-#include "H265VideoMatroskaFileServerMediaSubsession.hh"
-#include "H265VideoStreamDiscreteFramer.hh"
-#include "MatroskaDemuxedTrack.hh"
-
-H265VideoMatroskaFileServerMediaSubsession* H265VideoMatroskaFileServerMediaSubsession
-::createNew(MatroskaFileServerDemux& demux, unsigned trackNumber) {
-  return new H265VideoMatroskaFileServerMediaSubsession(demux, trackNumber);
-}
-
-#define CHECK_PTR if (ptr >= limit) return
-#define NUM_BYTES_REMAINING (unsigned)(limit - ptr)
-
-H265VideoMatroskaFileServerMediaSubsession
-::H265VideoMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber)
-  : H265VideoFileServerMediaSubsession(demux.envir(), demux.fileName(), False),
-    fOurDemux(demux), fTrackNumber(trackNumber),
-    fVPSSize(0), fVPS(NULL), fSPSSize(0), fSPS(NULL), fPPSSize(0), fPPS(NULL) {
-  // Our track's 'Codec Private' data should contain VPS, SPS, and PPS NAL units.  Copy these:
-  unsigned numVPS_SPS_PPSBytes = 0;
-  u_int8_t* VPS_SPS_PPSBytes = NULL;
-  MatroskaTrack* track = fOurDemux.lookup(fTrackNumber);
-
-  if (track->codecPrivateUsesH264FormatForH265) {
-    // The data uses the H.264-style format (but including VPS NAL unit(s)).
-    // The VPS,SPS,PPS NAL unit information starts at byte #5:
-    if (track->codecPrivateSize >= 6) {
-      numVPS_SPS_PPSBytes = track->codecPrivateSize - 5;
-      VPS_SPS_PPSBytes = &track->codecPrivate[5];
-    }
-  } else {
-    // The data uses the proper H.265-style format.
-    // The VPS,SPS,PPS NAL unit information starts at byte #22:
-    if (track->codecPrivateSize >= 23) {
-      numVPS_SPS_PPSBytes = track->codecPrivateSize - 22;
-      VPS_SPS_PPSBytes = &track->codecPrivate[22];
-    }
-  }
-
-  // Extract, from "VPS_SPS_PPSBytes", one VPS NAL unit, one SPS NAL unit, and one PPS NAL unit.
-  // (I hope one is all we need of each.)
-  if (numVPS_SPS_PPSBytes == 0 || VPS_SPS_PPSBytes == NULL) return; // sanity check
-  unsigned i;
-  u_int8_t* ptr = VPS_SPS_PPSBytes;
-  u_int8_t* limit = &VPS_SPS_PPSBytes[numVPS_SPS_PPSBytes];
-
-  if (track->codecPrivateUsesH264FormatForH265) {
-    // The data uses the H.264-style format (but including VPS NAL unit(s)).
-    while (NUM_BYTES_REMAINING > 0) {
-      unsigned numNALUnits = (*ptr++)&0x1F; CHECK_PTR;
-      for (i = 0; i < numNALUnits; ++i) {
-	unsigned nalUnitLength = (*ptr++)<<8; CHECK_PTR;
-	nalUnitLength |= *ptr++; CHECK_PTR;
-
-	if (nalUnitLength > NUM_BYTES_REMAINING) return;
-	u_int8_t nal_unit_type = (ptr[0]&0x7E)>>1;
-	if (nal_unit_type == 32) { // VPS
-	  fVPSSize = nalUnitLength;
-	  delete[] fVPS; fVPS = new u_int8_t[nalUnitLength];
-	  memmove(fVPS, ptr, nalUnitLength);
-	} else if (nal_unit_type == 33) { // SPS
-	  fSPSSize = nalUnitLength;
-	  delete[] fSPS; fSPS = new u_int8_t[nalUnitLength];
-	  memmove(fSPS, ptr, nalUnitLength);
-	} else if (nal_unit_type == 34) { // PPS
-	  fPPSSize = nalUnitLength;
-	  delete[] fPPS; fPPS = new u_int8_t[nalUnitLength];
-	  memmove(fPPS, ptr, nalUnitLength);
-	}
-	ptr += nalUnitLength;
-      }
-    }
-  } else {
-    // The data uses the proper H.265-style format.
-    unsigned numOfArrays = *ptr++; CHECK_PTR;
-    for (unsigned j = 0; j < numOfArrays; ++j) {
-      ++ptr; CHECK_PTR; // skip the 'array_completeness'|'reserved'|'NAL_unit_type' byte
-
-      unsigned numNalus = (*ptr++)<<8; CHECK_PTR;
-      numNalus |= *ptr++; CHECK_PTR;
-
-      for (i = 0; i < numNalus; ++i) {
-	unsigned nalUnitLength = (*ptr++)<<8; CHECK_PTR;
-	nalUnitLength |= *ptr++; CHECK_PTR;
-
-	if (nalUnitLength > NUM_BYTES_REMAINING) return;
-	u_int8_t nal_unit_type = (ptr[0]&0x7E)>>1;
-	if (nal_unit_type == 32) { // VPS
-	  fVPSSize = nalUnitLength;
-	  delete[] fVPS; fVPS = new u_int8_t[nalUnitLength];
-	  memmove(fVPS, ptr, nalUnitLength);
-	} else if (nal_unit_type == 33) { // SPS
-	  fSPSSize = nalUnitLength;
-	  delete[] fSPS; fSPS = new u_int8_t[nalUnitLength];
-	  memmove(fSPS, ptr, nalUnitLength);
-	} else if (nal_unit_type == 34) { // PPS
-	  fPPSSize = nalUnitLength;
-	  delete[] fPPS; fPPS = new u_int8_t[nalUnitLength];
-	  memmove(fPPS, ptr, nalUnitLength);
-	}
-	ptr += nalUnitLength;
-      }
-    }
-  }
-}
-
-H265VideoMatroskaFileServerMediaSubsession
-::~H265VideoMatroskaFileServerMediaSubsession() {
-  delete[] fVPS;
-  delete[] fSPS;
-  delete[] fPPS;
-}
-
-float H265VideoMatroskaFileServerMediaSubsession::duration() const { return fOurDemux.fileDuration(); }
-
-void H265VideoMatroskaFileServerMediaSubsession
-::seekStreamSource(FramedSource* inputSource, double& seekNPT, double /*streamDuration*/, u_int64_t& /*numBytes*/) {
-  // "inputSource" is a framer. *Its* source is the demuxed track that we seek on:
-  H265VideoStreamDiscreteFramer* framer = (H265VideoStreamDiscreteFramer*)inputSource;
-
-  MatroskaDemuxedTrack* demuxedTrack = (MatroskaDemuxedTrack*)(framer->inputSource());
-  demuxedTrack->seekToTime(seekNPT);
-}
-
-FramedSource* H265VideoMatroskaFileServerMediaSubsession
-::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
-  // Allow for the possibility of very large NAL units being fed to our "RTPSink" objects:
-  OutPacketBuffer::maxSize = 300000; // bytes
-  estBitrate = 500; // kbps, estimate
-
-  // Create the video source:
-  FramedSource* baseH265VideoSource = fOurDemux.newDemuxedTrack(clientSessionId, fTrackNumber);
-  if (baseH265VideoSource == NULL) return NULL;
-  
-  // Create a framer for the Video stream:
-  H265VideoStreamDiscreteFramer* framer
-    = H265VideoStreamDiscreteFramer::createNew(envir(), baseH265VideoSource);
-  framer->setVPSandSPSandPPS(fVPS, fVPSSize, fSPS, fSPSSize, fPPS, fPPSSize);
-
-  return framer;
-}
diff --git a/liveMedia/H265VideoMatroskaFileServerMediaSubsession.hh b/liveMedia/H265VideoMatroskaFileServerMediaSubsession.hh
deleted file mode 100644
index 527e004..0000000
--- a/liveMedia/H265VideoMatroskaFileServerMediaSubsession.hh
+++ /dev/null
@@ -1,61 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an H265 video track within a Matroska file.
-// C++ header
-
-#ifndef _H265_VIDEO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-#define _H265_VIDEO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-
-#ifndef _H265_VIDEO_FILE_SERVER_MEDIA_SUBSESSION_HH
-#include "H265VideoFileServerMediaSubsession.hh"
-#endif
-#ifndef _MATROSKA_FILE_SERVER_DEMUX_HH
-#include "MatroskaFileServerDemux.hh"
-#endif
-
-class H265VideoMatroskaFileServerMediaSubsession: public H265VideoFileServerMediaSubsession {
-public:
-  static H265VideoMatroskaFileServerMediaSubsession*
-  createNew(MatroskaFileServerDemux& demux, unsigned trackNumber);
-
-private:
-  H265VideoMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber);
-      // called only by createNew();
-  virtual ~H265VideoMatroskaFileServerMediaSubsession();
-
-private: // redefined virtual functions
-  virtual float duration() const;
-  virtual void seekStreamSource(FramedSource* inputSource, double& seekNPT, double streamDuration, u_int64_t& numBytes);
-  virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
-                                              unsigned& estBitrate);
-
-private:
-  MatroskaFileServerDemux& fOurDemux;
-  unsigned fTrackNumber;
-
-  // We store one VPS, one SPS, and one PPS, for use in our input 'framer's:
-  unsigned fVPSSize;
-  u_int8_t* fVPS;
-  unsigned fSPSSize;
-  u_int8_t* fSPS;
-  unsigned fPPSSize;
-  u_int8_t* fPPS;
-};
-
-#endif
diff --git a/liveMedia/H265VideoRTPSink.cpp b/liveMedia/H265VideoRTPSink.cpp
index b1f75e0..8ad124f 100644
--- a/liveMedia/H265VideoRTPSink.cpp
+++ b/liveMedia/H265VideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for H.265 video
 // Implementation
 
@@ -115,18 +115,31 @@ char const* H265VideoRTPSink::auxSDPLine() {
     if (framerSource == NULL) return NULL; // we don't yet have a source
 
     framerSource->getVPSandSPSandPPS(vps, vpsSize, sps, spsSize, pps, ppsSize);
-    if (vps == NULL || sps == NULL || pps == NULL) return NULL; // our source isn't ready
+    if (vps == NULL || sps == NULL || pps == NULL) {
+      return NULL; // our source isn't ready
+    }
   }
 
   // Set up the "a=fmtp:" SDP line for this stream.
-  // First, extract from our 'profile_tier_level' bytes (that were set by our upstream 'framer')
-  // several parameters that we'll put in this line:
-  u_int8_t const* profileTierLevelHeaderBytes = framerSource->profileTierLevelHeaderBytes();
-  unsigned profile_space = profileTierLevelHeaderBytes[0]>>6; // general_profile_space
-  unsigned profile_id = profileTierLevelHeaderBytes[0]&0x1F; // general_profile_idc
-  unsigned tier_flag = (profileTierLevelHeaderBytes[0]>>5)&0x1; // general_tier_flag
-  unsigned level_id = profileTierLevelHeaderBytes[11]; // general_level_idc
+  u_int8_t* vpsWEB = new u_int8_t[vpsSize]; // "WEB" means "Without Emulation Bytes"
+  unsigned vpsWEBSize = removeH264or5EmulationBytes(vpsWEB, vpsSize, vps, vpsSize);
+  if (vpsWEBSize < 6/*'profile_tier_level' offset*/ + 12/*num 'profile_tier_level' bytes*/) {
+    // Bad VPS size => assume our source isn't ready
+    delete[] vpsWEB;
+    return NULL;
+  }
+  u_int8_t const* profileTierLevelHeaderBytes = &vpsWEB[6];
+  unsigned profileSpace  = profileTierLevelHeaderBytes[0]>>6; // general_profile_space
+  unsigned profileId = profileTierLevelHeaderBytes[0]&0x1F; // general_profile_idc
+  unsigned tierFlag = (profileTierLevelHeaderBytes[0]>>5)&0x1; // general_tier_flag
+  unsigned levelId = profileTierLevelHeaderBytes[11]; // general_level_idc
   u_int8_t const* interop_constraints = &profileTierLevelHeaderBytes[5];
+  char interopConstraintsStr[100];
+  sprintf(interopConstraintsStr, "%02X%02X%02X%02X%02X%02X", 
+	  interop_constraints[0], interop_constraints[1], interop_constraints[2],
+	  interop_constraints[3], interop_constraints[4], interop_constraints[5]);
+  delete[] vpsWEB;
+
   char* sprop_vps = base64Encode((char*)vps, vpsSize);
   char* sprop_sps = base64Encode((char*)sps, spsSize);
   char* sprop_pps = base64Encode((char*)pps, ppsSize);
@@ -136,31 +149,30 @@ char const* H265VideoRTPSink::auxSDPLine() {
     ";profile-id=%u"
     ";tier-flag=%u"
     ";level-id=%u"
-    ";interop-constraints=%02X%02X%02X%02X%02X%02X"
-    ";tx-mode=SST"
+    ";interop-constraints=%s"
     ";sprop-vps=%s"
     ";sprop-sps=%s"
     ";sprop-pps=%s\r\n";
   unsigned fmtpFmtSize = strlen(fmtpFmt)
-    + 3 /* max num chars: rtpPayloadType */ + 1 /* num chars: profile_space */
-    + 2 /* max num chars: profile_id */
-    + 1 /* num chars: tier_flag */
-    + 3 /* max num chars: level_id */
-    + 12 /* num chars: interop_constraints */
+    + 3 /* max num chars: rtpPayloadType */ + 20 /* max num chars: profile_space */
+    + 20 /* max num chars: profile_id */
+    + 20 /* max num chars: tier_flag */
+    + 20 /* max num chars: level_id */
+    + strlen(interopConstraintsStr)
     + strlen(sprop_vps)
     + strlen(sprop_sps)
     + strlen(sprop_pps);
   char* fmtp = new char[fmtpFmtSize];
   sprintf(fmtp, fmtpFmt,
-          rtpPayloadType(), profile_space,
-	  profile_id,
-	  tier_flag,
-	  level_id,
-	  interop_constraints[0], interop_constraints[1], interop_constraints[2],
-	  interop_constraints[3], interop_constraints[4], interop_constraints[5],
+          rtpPayloadType(), profileSpace,
+	  profileId,
+	  tierFlag,
+	  levelId,
+	  interopConstraintsStr,
 	  sprop_vps,
 	  sprop_sps,
 	  sprop_pps);
+
   delete[] sprop_vps;
   delete[] sprop_sps;
   delete[] sprop_pps;
diff --git a/liveMedia/H265VideoRTPSource.cpp b/liveMedia/H265VideoRTPSource.cpp
new file mode 100644
index 0000000..a29b6a9
--- /dev/null
+++ b/liveMedia/H265VideoRTPSource.cpp
@@ -0,0 +1,218 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// H.265 Video RTP Sources
+// Implementation
+
+#include "H265VideoRTPSource.hh"
+
+////////// H265BufferedPacket and H265BufferedPacketFactory //////////
+
+class H265BufferedPacket: public BufferedPacket {
+public:
+  H265BufferedPacket(H265VideoRTPSource& ourSource);
+  virtual ~H265BufferedPacket();
+
+private: // redefined virtual functions
+  virtual unsigned nextEnclosedFrameSize(unsigned char*& framePtr,
+					 unsigned dataSize);
+private:
+  H265VideoRTPSource& fOurSource;
+};
+
+class H265BufferedPacketFactory: public BufferedPacketFactory {
+private: // redefined virtual functions
+  virtual BufferedPacket* createNewPacket(MultiFramedRTPSource* ourSource);
+};
+
+
+///////// H265VideoRTPSource implementation ////////
+
+H265VideoRTPSource*
+H265VideoRTPSource::createNew(UsageEnvironment& env, Groupsock* RTPgs,
+			      unsigned char rtpPayloadFormat,
+			      Boolean expectDONFields,		     
+			      unsigned rtpTimestampFrequency) {
+  return new H265VideoRTPSource(env, RTPgs, rtpPayloadFormat,
+				expectDONFields, rtpTimestampFrequency);
+}
+
+H265VideoRTPSource
+::H265VideoRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
+		     unsigned char rtpPayloadFormat,
+		     Boolean expectDONFields,		     
+		     unsigned rtpTimestampFrequency)
+  : MultiFramedRTPSource(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency,
+			 new H265BufferedPacketFactory),
+    fExpectDONFields(expectDONFields),
+    fPreviousNALUnitDON(0), fCurrentNALUnitAbsDon((u_int64_t)(~0)) {
+}
+
+H265VideoRTPSource::~H265VideoRTPSource() {
+}
+
+Boolean H265VideoRTPSource
+::processSpecialHeader(BufferedPacket* packet,
+                       unsigned& resultSpecialHeaderSize) {
+  unsigned char* headerStart = packet->data();
+  unsigned packetSize = packet->dataSize();
+  u_int16_t DONL = 0;
+  unsigned numBytesToSkip;
+
+  // Check the Payload Header's 'nal_unit_type' for special aggregation or fragmentation packets:
+  if (packetSize < 2) return False;
+  fCurPacketNALUnitType = (headerStart[0]&0x7E)>>1;
+  switch (fCurPacketNALUnitType) {
+  case 48: { // Aggregation Packet (AP)
+    // We skip over the 2-byte Payload Header, and the DONL header (if any).
+    if (fExpectDONFields) {
+      if (packetSize < 4) return False;
+      DONL = (headerStart[2]<<8)|headerStart[3];
+      numBytesToSkip = 4;
+    } else {
+      numBytesToSkip = 2;
+    }
+    break;
+  }
+  case 49: { // Fragmentation Unit (FU)
+    // This NALU begins with the 2-byte Payload Header, the 1-byte FU header, and (optionally)
+    // the 2-byte DONL header.
+    // If the start bit is set, we reconstruct the original NAL header at the end of these
+    // 3 (or 5) bytes, and skip over the first 1 (or 3) bytes.
+    if (packetSize < 3) return False;
+    u_int8_t startBit = headerStart[2]&0x80; // from the FU header
+    u_int8_t endBit = headerStart[2]&0x40; // from the FU header
+    if (startBit) {
+      fCurrentPacketBeginsFrame = True;
+
+      u_int8_t nal_unit_type = headerStart[2]&0x3F; // the last 6 bits of the FU header
+      u_int8_t newNALHeader[2];
+      newNALHeader[0] = (headerStart[0]&0x81)|(nal_unit_type<<1);
+      newNALHeader[1] = headerStart[1];
+
+      if (fExpectDONFields) {
+	if (packetSize < 5) return False;
+	DONL = (headerStart[3]<<8)|headerStart[4];
+	headerStart[3] = newNALHeader[0];
+	headerStart[4] = newNALHeader[1];
+	numBytesToSkip = 3;
+      } else {
+	headerStart[1] = newNALHeader[0];
+	headerStart[2] = newNALHeader[1];
+	numBytesToSkip = 1;
+      }
+    } else {
+      // The start bit is not set, so we skip over all headers:
+      fCurrentPacketBeginsFrame = False;
+      if (fExpectDONFields) {
+	if (packetSize < 5) return False;
+	DONL = (headerStart[3]<<8)|headerStart[4];
+	numBytesToSkip = 5;
+      } else {
+	numBytesToSkip = 3;
+      }
+    }
+    fCurrentPacketCompletesFrame = (endBit != 0);
+    break;
+  }
+  default: {
+    // This packet contains one complete NAL unit:
+    fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame = True;
+    numBytesToSkip = 0;
+    break;
+  }
+  }
+
+  computeAbsDonFromDON(DONL);
+  resultSpecialHeaderSize = numBytesToSkip;
+  return True;
+}
+
+char const* H265VideoRTPSource::MIMEtype() const {
+  return "video/H265";
+}
+
+void H265VideoRTPSource::computeAbsDonFromDON(u_int16_t DON) {
+  if (!fExpectDONFields) {
+    // Without DON fields in the input stream, we just increment our "AbsDon" count each time:
+    ++fCurrentNALUnitAbsDon;
+  } else {
+    if (fCurrentNALUnitAbsDon == (u_int64_t)(~0)) {
+      // This is the very first NAL unit, so "AbsDon" is just "DON":
+      fCurrentNALUnitAbsDon = (u_int64_t)DON;
+    } else {
+      // Use the previous NAL unit's DON and the current DON to compute "AbsDon":
+      //     AbsDon[n] = AbsDon[n-1] + (DON[n] - DON[n-1]) mod 2^16
+      short signedDiff16 = (short)(DON - fPreviousNALUnitDON);
+      int64_t signedDiff64 = (int64_t)signedDiff16;
+      fCurrentNALUnitAbsDon += signedDiff64;
+    }
+
+    fPreviousNALUnitDON = DON; // for next time
+  }
+}
+
+
+////////// H265BufferedPacket and H265BufferedPacketFactory implementation //////////
+
+H265BufferedPacket::H265BufferedPacket(H265VideoRTPSource& ourSource)
+  : fOurSource(ourSource) {
+}
+
+H265BufferedPacket::~H265BufferedPacket() {
+}
+
+unsigned H265BufferedPacket
+::nextEnclosedFrameSize(unsigned char*& framePtr, unsigned dataSize) {
+  unsigned resultNALUSize = 0; // if an error occurs
+
+  switch (fOurSource.fCurPacketNALUnitType) {
+  case 48: { // Aggregation Packet (AP)
+    if (useCount() > 0) {
+      // We're other than the first NAL unit inside this Aggregation Packet.
+      // Update our 'decoding order number':
+      u_int16_t DONL = 0;
+      if (fOurSource.fExpectDONFields) {
+	// There's a 1-byte DOND field next:
+	if (dataSize < 1) break;
+	u_int8_t DOND = framePtr[0];
+	DONL = fOurSource.fPreviousNALUnitDON + (u_int16_t)(DOND + 1);
+	++framePtr;
+	--dataSize;
+      }
+      fOurSource.computeAbsDonFromDON(DONL);
+    }
+
+    // The next 2 bytes are the NAL unit size:
+    if (dataSize < 2) break;
+    resultNALUSize = (framePtr[0]<<8)|framePtr[1];
+    framePtr += 2;
+    break;
+  }
+  default: {
+    // Common case: We use the entire packet data:
+    return dataSize;
+  }
+  }
+
+  return (resultNALUSize <= dataSize) ? resultNALUSize : dataSize;
+}
+
+BufferedPacket* H265BufferedPacketFactory
+::createNewPacket(MultiFramedRTPSource* ourSource) {
+  return new H265BufferedPacket((H265VideoRTPSource&)(*ourSource));
+}
diff --git a/liveMedia/H265VideoStreamDiscreteFramer.cpp b/liveMedia/H265VideoStreamDiscreteFramer.cpp
index 3611e2b..c6b8cf0 100644
--- a/liveMedia/H265VideoStreamDiscreteFramer.cpp
+++ b/liveMedia/H265VideoStreamDiscreteFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "H265VideoStreamFramer" that takes only complete,
 // discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
diff --git a/liveMedia/H265VideoStreamFramer.cpp b/liveMedia/H265VideoStreamFramer.cpp
index f69f0ec..02d52bf 100644
--- a/liveMedia/H265VideoStreamFramer.cpp
+++ b/liveMedia/H265VideoStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up a H.265 Video Elementary Stream into NAL units.
 // Implementation
 
diff --git a/liveMedia/InputFile.cpp b/liveMedia/InputFile.cpp
index 96ee20c..b91fb81 100644
--- a/liveMedia/InputFile.cpp
+++ b/liveMedia/InputFile.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Common routines for opening/closing named input files
 // Implementation
 
diff --git a/liveMedia/JPEGVideoRTPSink.cpp b/liveMedia/JPEGVideoRTPSink.cpp
index 60b694f..9076e67 100644
--- a/liveMedia/JPEGVideoRTPSink.cpp
+++ b/liveMedia/JPEGVideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for JPEG video (RFC 2435)
 // Implementation
 
diff --git a/liveMedia/JPEGVideoRTPSource.cpp b/liveMedia/JPEGVideoRTPSource.cpp
index 8b4dde3..4e92427 100644
--- a/liveMedia/JPEGVideoRTPSource.cpp
+++ b/liveMedia/JPEGVideoRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // JPEG Video (RFC 2435) RTP Sources
 // Implementation
 
diff --git a/liveMedia/JPEGVideoSource.cpp b/liveMedia/JPEGVideoSource.cpp
index 1ff853f..fa7e587 100644
--- a/liveMedia/JPEGVideoSource.cpp
+++ b/liveMedia/JPEGVideoSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // JPEG video sources
 // Implementation
 
diff --git a/liveMedia/Locale.cpp b/liveMedia/Locale.cpp
index 0bf1963..8e675de 100644
--- a/liveMedia/Locale.cpp
+++ b/liveMedia/Locale.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Support for temporarily setting the locale (e.g., to "C" or "POSIX") for (e.g.) parsing or printing
 // floating-point numbers in protocol headers, or calling toupper()/tolower() on human-input strings.
 // Implementation
diff --git a/liveMedia/MP3ADU.cpp b/liveMedia/MP3ADU.cpp
index e398149..14c6605 100644
--- a/liveMedia/MP3ADU.cpp
+++ b/liveMedia/MP3ADU.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // 'ADU' MP3 streams (for improved loss-tolerance)
 // Implementation
 
@@ -162,7 +162,7 @@ void ADUFromMP3Source::doGetNextFrame() {
 
     if (!doGetNextFrame1()) {
       // An internal error occurred; act as if our source went away:
-      FramedSource::handleClosure(this);
+      handleClosure();
     }
   }
 }
@@ -512,7 +512,7 @@ void SegmentQueue::enqueueNewSegment(FramedSource* inputSource,
 				     FramedSource* usingSource) {
   if (isFull()) {
     usingSource->envir() << "SegmentQueue::enqueueNewSegment() overflow\n";
-    FramedSource::handleClosure(usingSource);
+    usingSource->handleClosure();
     return;
   }
 
diff --git a/liveMedia/MP3ADURTPSink.cpp b/liveMedia/MP3ADURTPSink.cpp
index 483c0b6..b52b32b 100644
--- a/liveMedia/MP3ADURTPSink.cpp
+++ b/liveMedia/MP3ADURTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for 'ADUized' MP3 frames ("mpa-robust")
 // Implementation
 
diff --git a/liveMedia/MP3ADURTPSource.cpp b/liveMedia/MP3ADURTPSource.cpp
index 4229e98..696cafd 100644
--- a/liveMedia/MP3ADURTPSource.cpp
+++ b/liveMedia/MP3ADURTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP source for 'ADUized' MP3 frames ("mpa-robust")
 // Implementation
 
diff --git a/liveMedia/MP3ADUTranscoder.cpp b/liveMedia/MP3ADUTranscoder.cpp
index 4d3a9d5..4557a9e 100644
--- a/liveMedia/MP3ADUTranscoder.cpp
+++ b/liveMedia/MP3ADUTranscoder.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Transcoder for ADUized MP3 frames
 // Implementation
 
@@ -82,7 +82,7 @@ void MP3ADUTranscoder::afterGettingFrame1(unsigned numBytesRead,
   fFrameSize = TranscodeMP3ADU(fOrigADU, numBytesRead, fOutBitrate,
 			    fTo, fMaxSize, fAvailableBytesForBackpointer);
   if (fFrameSize == 0) { // internal error - bad ADU data?
-    handleClosure(this);
+    handleClosure();
     return;
   }
 
diff --git a/liveMedia/MP3ADUdescriptor.cpp b/liveMedia/MP3ADUdescriptor.cpp
index fffa087..5669647 100644
--- a/liveMedia/MP3ADUdescriptor.cpp
+++ b/liveMedia/MP3ADUdescriptor.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Descriptor preceding frames of 'ADU' MP3 streams (for improved loss-tolerance)
 // Implementation
 
diff --git a/liveMedia/MP3ADUdescriptor.hh b/liveMedia/MP3ADUdescriptor.hh
index a87fbf3..c4576be 100644
--- a/liveMedia/MP3ADUdescriptor.hh
+++ b/liveMedia/MP3ADUdescriptor.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Descriptor preceding frames of 'ADU' MP3 streams (for improved loss-tolerance)
 // C++ header
 
diff --git a/liveMedia/MP3ADUinterleaving.cpp b/liveMedia/MP3ADUinterleaving.cpp
index 723740d..eb3077e 100644
--- a/liveMedia/MP3ADUinterleaving.cpp
+++ b/liveMedia/MP3ADUinterleaving.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Interleaving of MP3 ADUs
 // Implementation
 
diff --git a/liveMedia/MP3AudioFileServerMediaSubsession.cpp b/liveMedia/MP3AudioFileServerMediaSubsession.cpp
index f5b0cba..30460c0 100644
--- a/liveMedia/MP3AudioFileServerMediaSubsession.cpp
+++ b/liveMedia/MP3AudioFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a MP3 audio file.
 // (Actually, any MPEG-1 or MPEG-2 audio file should work.)
diff --git a/liveMedia/MP3AudioMatroskaFileServerMediaSubsession.cpp b/liveMedia/MP3AudioMatroskaFileServerMediaSubsession.cpp
index 53bdb90..4f86d9d 100644
--- a/liveMedia/MP3AudioMatroskaFileServerMediaSubsession.cpp
+++ b/liveMedia/MP3AudioMatroskaFileServerMediaSubsession.cpp
@@ -14,25 +14,27 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an MP3 audio track within a Matroska file.
 // (Actually, MPEG-1 or MPEG-2 audio file should also work.)
 // Implementation
 
+#include "FileServerMediaSubsession.hh"
 #include "MP3AudioMatroskaFileServerMediaSubsession.hh"
 #include "MatroskaDemuxedTrack.hh"
 
 MP3AudioMatroskaFileServerMediaSubsession* MP3AudioMatroskaFileServerMediaSubsession
-::createNew(MatroskaFileServerDemux& demux, unsigned trackNumber, Boolean generateADUs, Interleaving* interleaving) {
-  return new MP3AudioMatroskaFileServerMediaSubsession(demux, trackNumber, generateADUs, interleaving);
+::createNew(MatroskaFileServerDemux& demux, MatroskaTrack* track,
+	    Boolean generateADUs, Interleaving* interleaving) {
+  return new MP3AudioMatroskaFileServerMediaSubsession(demux, track, generateADUs, interleaving);
 }
 
 MP3AudioMatroskaFileServerMediaSubsession
-::MP3AudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber,
+::MP3AudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, MatroskaTrack* track,
 					    Boolean generateADUs, Interleaving* interleaving)
   : MP3AudioFileServerMediaSubsession(demux.envir(), demux.fileName(), False, generateADUs, interleaving),
-    fOurDemux(demux), fTrackNumber(trackNumber) {
+    fOurDemux(demux), fTrackNumber(track->trackNumber) {
   fFileDuration = fOurDemux.fileDuration();
 }
 
diff --git a/liveMedia/MP3AudioMatroskaFileServerMediaSubsession.hh b/liveMedia/MP3AudioMatroskaFileServerMediaSubsession.hh
index c429c67..5216997 100644
--- a/liveMedia/MP3AudioMatroskaFileServerMediaSubsession.hh
+++ b/liveMedia/MP3AudioMatroskaFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an MP3 audio track within a Matroska file.
 // (Actually, MPEG-1 or MPEG-2 audio should also work.)
@@ -33,12 +33,13 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 class MP3AudioMatroskaFileServerMediaSubsession: public MP3AudioFileServerMediaSubsession {
 public:
   static MP3AudioMatroskaFileServerMediaSubsession*
-  createNew(MatroskaFileServerDemux& demux, unsigned trackNumber, Boolean generateADUs, Interleaving* interleaving);
+  createNew(MatroskaFileServerDemux& demux, MatroskaTrack* track,
+	    Boolean generateADUs = False, Interleaving* interleaving = NULL);
       // Note: "interleaving" is used only if "generateADUs" is True,
       // (and a value of NULL means 'no interleaving')
 
 private:
-  MP3AudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber,
+  MP3AudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, MatroskaTrack* track,
 					    Boolean generateADUs, Interleaving* interleaving);
       // called only by createNew();
   virtual ~MP3AudioMatroskaFileServerMediaSubsession();
diff --git a/liveMedia/MP3FileSource.cpp b/liveMedia/MP3FileSource.cpp
index d6ddebd..84fcf6c 100644
--- a/liveMedia/MP3FileSource.cpp
+++ b/liveMedia/MP3FileSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP3 File Sources
 // Implementation
 
@@ -99,7 +99,6 @@ void MP3FileSource::seekWithinFile(double seekNPT, double streamDuration) {
       fNumBytesToStream = endByteNumber - seekByteNumber;
       fLimitNumBytesToStream = True;
     }
-  } else {
   }
 }
 
@@ -111,7 +110,7 @@ void MP3FileSource::getAttributes() const {
 
 void MP3FileSource::doGetNextFrame() {
   if (!doGetNextFrame1()) {
-    handleClosure(this);
+    handleClosure();
     return;
   }
 
diff --git a/liveMedia/MP3Internals.cpp b/liveMedia/MP3Internals.cpp
index 2bb71f2..80f3f7b 100644
--- a/liveMedia/MP3Internals.cpp
+++ b/liveMedia/MP3Internals.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP3 internal implementation details
 // Implementation
 
diff --git a/liveMedia/MP3Internals.hh b/liveMedia/MP3Internals.hh
index 28472de..90724f5 100644
--- a/liveMedia/MP3Internals.hh
+++ b/liveMedia/MP3Internals.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP3 internal implementation details
 // C++ header
 
diff --git a/liveMedia/MP3InternalsHuffman.cpp b/liveMedia/MP3InternalsHuffman.cpp
index e88bde3..3f578ad 100644
--- a/liveMedia/MP3InternalsHuffman.cpp
+++ b/liveMedia/MP3InternalsHuffman.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP3 internal implementation details (Huffman encoding)
 // Implementation
 
diff --git a/liveMedia/MP3InternalsHuffman.hh b/liveMedia/MP3InternalsHuffman.hh
index 926cfab..ce7d989 100644
--- a/liveMedia/MP3InternalsHuffman.hh
+++ b/liveMedia/MP3InternalsHuffman.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP3 internal implementation details (Huffman encoding)
 // C++ header
 
diff --git a/liveMedia/MP3InternalsHuffmanTable.cpp b/liveMedia/MP3InternalsHuffmanTable.cpp
index 8ba869d..e1902ab 100644
--- a/liveMedia/MP3InternalsHuffmanTable.cpp
+++ b/liveMedia/MP3InternalsHuffmanTable.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP3 internal implementation details (Huffman encoding)
 // Table
 
diff --git a/liveMedia/MP3StreamState.cpp b/liveMedia/MP3StreamState.cpp
index ddcbf52..c5aac48 100644
--- a/liveMedia/MP3StreamState.cpp
+++ b/liveMedia/MP3StreamState.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class encapsulating the state of a MP3 stream
 // Implementation
 
diff --git a/liveMedia/MP3StreamState.hh b/liveMedia/MP3StreamState.hh
index 9ce5f49..0358f8f 100644
--- a/liveMedia/MP3StreamState.hh
+++ b/liveMedia/MP3StreamState.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class encapsulating the state of a MP3 stream
 // C++ header
 
diff --git a/liveMedia/MP3Transcoder.cpp b/liveMedia/MP3Transcoder.cpp
index 8c437d4..ec82c17 100644
--- a/liveMedia/MP3Transcoder.cpp
+++ b/liveMedia/MP3Transcoder.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP3 Transcoder
 // Implementation
 
diff --git a/liveMedia/MPEG1or2AudioRTPSink.cpp b/liveMedia/MPEG1or2AudioRTPSink.cpp
index 316cadb..ec9585e 100644
--- a/liveMedia/MPEG1or2AudioRTPSink.cpp
+++ b/liveMedia/MPEG1or2AudioRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for MPEG audio (RFC 2250)
 // Implementation
 
diff --git a/liveMedia/MPEG1or2AudioRTPSource.cpp b/liveMedia/MPEG1or2AudioRTPSource.cpp
index c3773a5..f43930b 100644
--- a/liveMedia/MPEG1or2AudioRTPSource.cpp
+++ b/liveMedia/MPEG1or2AudioRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG-1 or MPEG-2 Audio RTP Sources
 // Implementation
 
diff --git a/liveMedia/MPEG1or2AudioStreamFramer.cpp b/liveMedia/MPEG1or2AudioStreamFramer.cpp
index 26fcee5..e7539f9 100644
--- a/liveMedia/MPEG1or2AudioStreamFramer.cpp
+++ b/liveMedia/MPEG1or2AudioStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an MPEG (1,2) audio elementary stream into frames
 // Implementation
 
diff --git a/liveMedia/MPEG1or2Demux.cpp b/liveMedia/MPEG1or2Demux.cpp
index 0e8ee73..8a507eb 100644
--- a/liveMedia/MPEG1or2Demux.cpp
+++ b/liveMedia/MPEG1or2Demux.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Demultiplexer for a MPEG 1 or 2 Program Stream
 // Implementation
 
@@ -160,8 +160,7 @@ void MPEG1or2Demux::registerReadInterest(u_int8_t streamIdTag,
 
   // Make sure this stream is not already being read:
   if (out.isCurrentlyAwaitingData) {
-    envir() << "MPEG1or2Demux::registerReadInterest(): attempt to read stream id "
-	    << (void*)streamIdTag << " more than once!\n";
+    envir() << "MPEG1or2Demux::registerReadInterest(): attempt to read stream more than once!\n";
     envir().internalError();
   }
 
@@ -459,9 +458,7 @@ void MPEGProgramStreamParser::parsePackHeader() {
     unsigned char pack_stuffing_length = getBits(3);
     skipBytes(pack_stuffing_length);
   } else { // unknown
-    fUsingDemux->envir() << "StreamParser::parsePack() saw strange byte "
-			  << (void*)nextByte
-			  << " following pack_start_code\n";
+    fUsingDemux->envir() << "StreamParser::parsePack() saw strange byte following pack_start_code\n";
   }
 
   // Check for a System Header next:
diff --git a/liveMedia/MPEG1or2DemuxedElementaryStream.cpp b/liveMedia/MPEG1or2DemuxedElementaryStream.cpp
index b0d9020..23fb0bb 100644
--- a/liveMedia/MPEG1or2DemuxedElementaryStream.cpp
+++ b/liveMedia/MPEG1or2DemuxedElementaryStream.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A MPEG 1 or 2 Elementary Stream, demultiplexed from a Program Stream
 // Implementation
 
diff --git a/liveMedia/MPEG1or2DemuxedServerMediaSubsession.cpp b/liveMedia/MPEG1or2DemuxedServerMediaSubsession.cpp
index d6b32d8..724bc65 100644
--- a/liveMedia/MPEG1or2DemuxedServerMediaSubsession.cpp
+++ b/liveMedia/MPEG1or2DemuxedServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a MPEG-1 or 2 demuxer.
 // Implementation
diff --git a/liveMedia/MPEG1or2FileServerDemux.cpp b/liveMedia/MPEG1or2FileServerDemux.cpp
index 036b128..e22f8ae 100644
--- a/liveMedia/MPEG1or2FileServerDemux.cpp
+++ b/liveMedia/MPEG1or2FileServerDemux.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A server demultiplexer for a MPEG 1 or 2 Program Stream
 // Implementation
 
diff --git a/liveMedia/MPEG1or2VideoFileServerMediaSubsession.cpp b/liveMedia/MPEG1or2VideoFileServerMediaSubsession.cpp
index ba86f13..eeaf456 100644
--- a/liveMedia/MPEG1or2VideoFileServerMediaSubsession.cpp
+++ b/liveMedia/MPEG1or2VideoFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a MPEG-1 or 2 Elementary Stream video file.
 // Implementation
diff --git a/liveMedia/MPEG1or2VideoRTPSink.cpp b/liveMedia/MPEG1or2VideoRTPSink.cpp
index 8b597ac..646a942 100644
--- a/liveMedia/MPEG1or2VideoRTPSink.cpp
+++ b/liveMedia/MPEG1or2VideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for MPEG video (RFC 2250)
 // Implementation
 
diff --git a/liveMedia/MPEG1or2VideoRTPSource.cpp b/liveMedia/MPEG1or2VideoRTPSource.cpp
index 8b7eed0..0a7d613 100644
--- a/liveMedia/MPEG1or2VideoRTPSource.cpp
+++ b/liveMedia/MPEG1or2VideoRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG-1 or MPEG-2 Video RTP Sources
 // Implementation
 
diff --git a/liveMedia/MPEG1or2VideoStreamDiscreteFramer.cpp b/liveMedia/MPEG1or2VideoStreamDiscreteFramer.cpp
index ded2d83..62b80ab 100644
--- a/liveMedia/MPEG1or2VideoStreamDiscreteFramer.cpp
+++ b/liveMedia/MPEG1or2VideoStreamDiscreteFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "MPEG1or2VideoStreamFramer" that takes only
 // complete, discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
diff --git a/liveMedia/MPEG1or2VideoStreamFramer.cpp b/liveMedia/MPEG1or2VideoStreamFramer.cpp
index fa8f7ce..836b565 100644
--- a/liveMedia/MPEG1or2VideoStreamFramer.cpp
+++ b/liveMedia/MPEG1or2VideoStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an MPEG 1 or 2 video elementary stream into
 //   frames for: Video_Sequence_Header, GOP_Header, Picture_Header
 // Implementation
diff --git a/liveMedia/MPEG2IndexFromTransportStream.cpp b/liveMedia/MPEG2IndexFromTransportStream.cpp
index e9005b4..084e3f8 100644
--- a/liveMedia/MPEG2IndexFromTransportStream.cpp
+++ b/liveMedia/MPEG2IndexFromTransportStream.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that produces a sequence of I-frame indices from a MPEG-2 Transport Stream
 // Implementation
 
@@ -202,7 +202,13 @@ void MPEG2IFrameIndexFromTransportStream
   // Figure out how much of this Transport Packet contains PES data:
   u_int8_t adaptation_field_control = (fInputBuffer[3]&0x30)>>4;
   u_int8_t totalHeaderSize
-    = adaptation_field_control == 1 ? 4 : 5 + fInputBuffer[4];
+    = adaptation_field_control <= 1 ? 4 : 5 + fInputBuffer[4];
+  if ((adaptation_field_control == 2 && totalHeaderSize != TRANSPORT_PACKET_SIZE) ||
+      (adaptation_field_control == 3 && totalHeaderSize >= TRANSPORT_PACKET_SIZE)) {
+    envir() << "Bad \"adaptation_field_length\": " << fInputBuffer[4] << "\n";
+    doGetNextFrame();
+    return;
+  }
 
   // Check for a PCR:
   if (totalHeaderSize > 5 && (fInputBuffer[5]&0x10) != 0) {
@@ -241,7 +247,7 @@ void MPEG2IFrameIndexFromTransportStream
   // or packets with no data, or packets that duplicate the previous packet:
   u_int8_t continuity_counter = fInputBuffer[3]&0x0F;
   if ((PID != fVideo_PID) ||
-      !(adaptation_field_control == 1  || adaptation_field_control == 3) ||
+      !(adaptation_field_control == 1 || adaptation_field_control == 3) ||
       continuity_counter == fLastContinuityCounter) {
     doGetNextFrame();
     return;
@@ -250,8 +256,9 @@ void MPEG2IFrameIndexFromTransportStream
 
   // Also, if this is the start of a PES packet, then skip over the PES header:
   Boolean payload_unit_start_indicator = (fInputBuffer[1]&0x40) != 0;
-  if (payload_unit_start_indicator) {
-    // Note: The following works only for MPEG-2 data #####
+  if (payload_unit_start_indicator && totalHeaderSize < TRANSPORT_PACKET_SIZE - 8 
+      && fInputBuffer[totalHeaderSize] == 0x00 && fInputBuffer[totalHeaderSize+1] == 0x00
+      && fInputBuffer[totalHeaderSize+2] == 0x01) {
     u_int8_t PES_header_data_length = fInputBuffer[totalHeaderSize+8];
     totalHeaderSize += 9 + PES_header_data_length;
     if (totalHeaderSize >= TRANSPORT_PACKET_SIZE) {
@@ -303,7 +310,7 @@ void MPEG2IFrameIndexFromTransportStream::handleInputClosure1() {
     doGetNextFrame();
   } else {
     // Handle closure in the regular way:
-    FramedSource::handleClosure(this);
+    handleClosure();
   }
 }
 
diff --git a/liveMedia/MPEG2TransportFileServerMediaSubsession.cpp b/liveMedia/MPEG2TransportFileServerMediaSubsession.cpp
index b113aaf..65210b9 100644
--- a/liveMedia/MPEG2TransportFileServerMediaSubsession.cpp
+++ b/liveMedia/MPEG2TransportFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a MPEG-2 Transport Stream file.
 // Implementation
diff --git a/liveMedia/MPEG2TransportStreamFramer.cpp b/liveMedia/MPEG2TransportStreamFramer.cpp
index d0be467..18c7fb1 100644
--- a/liveMedia/MPEG2TransportStreamFramer.cpp
+++ b/liveMedia/MPEG2TransportStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that passes through (unchanged) chunks that contain an integral number
 // of MPEG-2 Transport Stream packets, but returning (in "fDurationInMicroseconds")
 // an updated estimate of the time gap between chunks.
@@ -104,7 +104,7 @@ void MPEG2TransportStreamFramer::setPCRLimit(float pcrLimit) {
 void MPEG2TransportStreamFramer::doGetNextFrame() {
   if (fLimitNumTSPacketsToStream) {
     if (fNumTSPacketsToStream == 0) {
-      handleClosure(this);
+      handleClosure();
       return;
     }
     if (fNumTSPacketsToStream*TRANSPORT_PACKET_SIZE < fMaxSize) {
@@ -146,7 +146,7 @@ void MPEG2TransportStreamFramer::afterGettingFrame1(unsigned frameSize,
   fFrameSize = numTSPackets*TRANSPORT_PACKET_SIZE; // an integral # of TS packets
   if (fFrameSize == 0) {
     // We didn't read a complete TS packet; assume that the input source has closed.
-    handleClosure(this);
+    handleClosure();
     return;
   }
 
@@ -157,7 +157,7 @@ void MPEG2TransportStreamFramer::afterGettingFrame1(unsigned frameSize,
   }
   if (syncBytePosition == fFrameSize) {
     envir() << "No Transport Stream sync byte in data.";
-    handleClosure(this);
+    handleClosure();
     return;
   } else if (syncBytePosition > 0) {
     // There's a sync byte, but not at the start of the data.  Move the good data
@@ -180,7 +180,7 @@ void MPEG2TransportStreamFramer::afterGettingFrame1(unsigned frameSize,
   for (unsigned i = 0; i < numTSPackets; ++i) {
     if (!updateTSPacketDurationEstimate(&fTo[i*TRANSPORT_PACKET_SIZE], timeNow)) {
       // We hit a preset limit (based on PCR) within the stream.  Handle this as if the input source has closed:
-      handleClosure(this);
+      handleClosure();
       return;
     }
   }
diff --git a/liveMedia/MPEG2TransportStreamFromESSource.cpp b/liveMedia/MPEG2TransportStreamFromESSource.cpp
index b189d9f..4008e8b 100644
--- a/liveMedia/MPEG2TransportStreamFromESSource.cpp
+++ b/liveMedia/MPEG2TransportStreamFromESSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter for converting one or more MPEG Elementary Streams
 // to a MPEG-2 Transport Stream
 // Implementation
@@ -33,7 +33,7 @@ public:
   InputESSourceRecord(MPEG2TransportStreamFromESSource& parent,
 		      FramedSource* inputSource,
 		      u_int8_t streamId, int mpegVersion,
-		      InputESSourceRecord* next);
+		      InputESSourceRecord* next, int16_t PID = -1);
   virtual ~InputESSourceRecord();
 
   InputESSourceRecord* next() const { return fNext; }
@@ -68,6 +68,7 @@ private:
   unsigned fInputBufferBytesAvailable;
   Boolean fInputBufferInUse;
   MPEG1or2Demux::SCR fSCR;
+  int16_t fPID;
 };
 
 
@@ -79,22 +80,23 @@ MPEG2TransportStreamFromESSource* MPEG2TransportStreamFromESSource
 }
 
 void MPEG2TransportStreamFromESSource
-::addNewVideoSource(FramedSource* inputSource, int mpegVersion) {
+::addNewVideoSource(FramedSource* inputSource, int mpegVersion, int16_t PID) {
   u_int8_t streamId = 0xE0 | (fVideoSourceCounter++&0x0F);
-  addNewInputSource(inputSource, streamId, mpegVersion);
+  addNewInputSource(inputSource, streamId, mpegVersion, PID);
   fHaveVideoStreams = True;
 }
 
 void MPEG2TransportStreamFromESSource
-::addNewAudioSource(FramedSource* inputSource, int mpegVersion) {
+::addNewAudioSource(FramedSource* inputSource, int mpegVersion, int16_t PID) {
   u_int8_t streamId = 0xC0 | (fAudioSourceCounter++&0x0F);
-  addNewInputSource(inputSource, streamId, mpegVersion);
+  addNewInputSource(inputSource, streamId, mpegVersion, PID);
 }
 
 MPEG2TransportStreamFromESSource
 ::MPEG2TransportStreamFromESSource(UsageEnvironment& env)
   : MPEG2TransportStreamMultiplexor(env),
-    fInputSources(NULL), fVideoSourceCounter(0), fAudioSourceCounter(0) {
+    fInputSources(NULL), fVideoSourceCounter(0), fAudioSourceCounter(0),
+    fAwaitingBackgroundDelivery(False) {
   fHaveVideoStreams = False; // unless we add a video source
 }
 
@@ -123,14 +125,16 @@ void MPEG2TransportStreamFromESSource
 	break;
       }
     }
+    fAwaitingBackgroundDelivery = False;
   }
 
   if (isCurrentlyAwaitingData()) {
     // Try to deliver one filled-in buffer to the client:
     for (sourceRec = fInputSources; sourceRec != NULL;
 	 sourceRec = sourceRec->next()) {
-      if (sourceRec->deliverBufferToClient()) break;
+      if (sourceRec->deliverBufferToClient()) return;
     }
+    fAwaitingBackgroundDelivery = True;
   }
 
   // No filled-in buffers are available. Ask each of our inputs for data:
@@ -138,15 +142,14 @@ void MPEG2TransportStreamFromESSource
        sourceRec = sourceRec->next()) {
     sourceRec->askForNewData();
   }
-
 }
 
 void MPEG2TransportStreamFromESSource
 ::addNewInputSource(FramedSource* inputSource,
-		    u_int8_t streamId, int mpegVersion) {
+		    u_int8_t streamId, int mpegVersion, int16_t PID) {
   if (inputSource == NULL) return;
   fInputSources = new InputESSourceRecord(*this, inputSource, streamId,
-					  mpegVersion, fInputSources);
+					  mpegVersion, fInputSources, PID);
 }
 
 
@@ -156,9 +159,9 @@ InputESSourceRecord
 ::InputESSourceRecord(MPEG2TransportStreamFromESSource& parent,
 		      FramedSource* inputSource,
 		      u_int8_t streamId, int mpegVersion,
-		      InputESSourceRecord* next)
+		      InputESSourceRecord* next, int16_t PID)
   : fNext(next), fParent(parent), fInputSource(inputSource),
-    fStreamId(streamId), fMPEGVersion(mpegVersion) {
+    fStreamId(streamId), fMPEGVersion(mpegVersion), fPID(PID) {
   fInputBuffer = new unsigned char[INPUT_BUFFER_SIZE];
   reset();
 }
@@ -216,7 +219,7 @@ Boolean InputESSourceRecord::deliverBufferToClient() {
 
   // Do the delivery:
   fParent.handleNewBuffer(fInputBuffer, fInputBufferBytesAvailable,
-			 fMPEGVersion, fSCR);
+			 fMPEGVersion, fSCR, fPID);
 
   return True;
 }
@@ -255,5 +258,8 @@ void InputESSourceRecord
   fParent.fPresentationTime = presentationTime;
 
   // Now that we have new input data, check if we can deliver to the client:
-  fParent.awaitNewBuffer(NULL);
+  if (fParent.fAwaitingBackgroundDelivery) {
+    fParent.fAwaitingBackgroundDelivery = False;
+    fParent.awaitNewBuffer(NULL);
+  }
 }
diff --git a/liveMedia/MPEG2TransportStreamFromPESSource.cpp b/liveMedia/MPEG2TransportStreamFromPESSource.cpp
index 7e08614..a936800 100644
--- a/liveMedia/MPEG2TransportStreamFromPESSource.cpp
+++ b/liveMedia/MPEG2TransportStreamFromPESSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter for converting a stream of MPEG PES packets to a MPEG-2 Transport Stream
 // Implementation
 
diff --git a/liveMedia/MPEG2TransportStreamIndexFile.cpp b/liveMedia/MPEG2TransportStreamIndexFile.cpp
index 99187e9..dd6f754 100644
--- a/liveMedia/MPEG2TransportStreamIndexFile.cpp
+++ b/liveMedia/MPEG2TransportStreamIndexFile.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class that encapsulates MPEG-2 Transport Stream 'index files'/
 // These index files are used to implement 'trick play' operations
 // (seek-by-time, fast forward, reverse play) on Transport Stream files.
diff --git a/liveMedia/MPEG2TransportStreamMultiplexor.cpp b/liveMedia/MPEG2TransportStreamMultiplexor.cpp
index 81ffb90..0be578d 100644
--- a/liveMedia/MPEG2TransportStreamMultiplexor.cpp
+++ b/liveMedia/MPEG2TransportStreamMultiplexor.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class for generating MPEG-2 Transport Stream from one or more input
 // Elementary Stream data sources
 // Implementation
@@ -91,7 +91,7 @@ void MPEG2TransportStreamMultiplexor::doGetNextFrame() {
 
 void MPEG2TransportStreamMultiplexor
 ::handleNewBuffer(unsigned char* buffer, unsigned bufferSize,
-		  int mpegVersion, MPEG1or2Demux::SCR scr) {
+		  int mpegVersion, MPEG1or2Demux::SCR scr, int16_t PID) {
   if (bufferSize < 4) return;
   fInputBuffer = buffer;
   fInputBufferSize = bufferSize;
@@ -106,7 +106,10 @@ void MPEG2TransportStreamMultiplexor
     setProgramStreamMap(fInputBufferSize);
     fInputBufferSize = 0; // then, ignore the buffer
   } else {
-    fCurrentPID = stream_id;
+    if (PID == -1)
+      fCurrentPID = stream_id;
+    else
+      fCurrentPID = (u_int8_t)PID;
 
     // Set the stream's type:
     u_int8_t& streamType = fPIDState[fCurrentPID].streamType; // alias
@@ -234,10 +237,10 @@ void MPEG2TransportStreamMultiplexor
   }
 }
 
-static u_int32_t calculateCRC(u_int8_t* data, unsigned dataLength); // forward
-
 #define PAT_PID 0
+#ifndef OUR_PROGRAM_NUMBER
 #define OUR_PROGRAM_NUMBER 1
+#endif
 #define OUR_PROGRAM_MAP_PID 0x30
 
 void MPEG2TransportStreamMultiplexor::deliverPATPacket() {
@@ -427,8 +430,8 @@ static u_int32_t const CRC32[256] = {
   0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
 };
 
-static u_int32_t calculateCRC(u_int8_t* data, unsigned dataLength) {
-  u_int32_t crc = 0xFFFFFFFF;
+u_int32_t calculateCRC(u_int8_t const* data, unsigned dataLength, u_int32_t initialValue) {
+  u_int32_t crc = initialValue;
 
   while (dataLength-- > 0) {
     crc = (crc<<8) ^ CRC32[(crc>>24) ^ (u_int32_t)(*data++)];
diff --git a/liveMedia/MPEG2TransportStreamTrickModeFilter.cpp b/liveMedia/MPEG2TransportStreamTrickModeFilter.cpp
index 88d897c..5c4125a 100644
--- a/liveMedia/MPEG2TransportStreamTrickModeFilter.cpp
+++ b/liveMedia/MPEG2TransportStreamTrickModeFilter.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that converts a MPEG Transport Stream file - with corresponding index file
 // - to a corresponding Video Elementary Stream.  It also uses a "scale" parameter
 // to implement 'trick mode' (fast forward or reverse play, using I-frames) on
@@ -262,5 +262,5 @@ void MPEG2TransportStreamTrickModeFilter::onSourceClosure(void* clientData) {
 
 void MPEG2TransportStreamTrickModeFilter::onSourceClosure1() {
   fIndexFile->stopReading();
-  FramedSource::handleClosure(this);
+  handleClosure();
 }
diff --git a/liveMedia/MPEG2TransportUDPServerMediaSubsession.cpp b/liveMedia/MPEG2TransportUDPServerMediaSubsession.cpp
index 5c82b54..d320349 100644
--- a/liveMedia/MPEG2TransportUDPServerMediaSubsession.cpp
+++ b/liveMedia/MPEG2TransportUDPServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an incoming UDP (or RTP/UDP) MPEG-2 Transport Stream
 // Implementation
diff --git a/liveMedia/MPEG4ESVideoRTPSink.cpp b/liveMedia/MPEG4ESVideoRTPSink.cpp
index 9f49863..bdc43e5 100644
--- a/liveMedia/MPEG4ESVideoRTPSink.cpp
+++ b/liveMedia/MPEG4ESVideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for MPEG-4 Elementary Stream video (RFC 3016)
 // Implementation
 
@@ -65,8 +65,8 @@ void MPEG4ESVideoRTPSink
   if (fragmentationOffset == 0) {
     // Begin by inspecting the 4-byte code at the start of the frame:
     if (numBytesInFrame < 4) return; // shouldn't happen
-    unsigned startCode = (frameStart[0]<<24) | (frameStart[1]<<16)
-      | (frameStart[2]<<8) | frameStart[3];
+    u_int32_t startCode
+      = (frameStart[0]<<24) | (frameStart[1]<<16) | (frameStart[2]<<8) | frameStart[3];
 
     fVOPIsPresent = startCode == VOP_START_CODE;
   }
diff --git a/liveMedia/MPEG4ESVideoRTPSource.cpp b/liveMedia/MPEG4ESVideoRTPSource.cpp
index 07d6915..12c2ee7 100644
--- a/liveMedia/MPEG4ESVideoRTPSource.cpp
+++ b/liveMedia/MPEG4ESVideoRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP4V-ES video RTP stream sources
 // Implementation
 
diff --git a/liveMedia/MPEG4GenericRTPSink.cpp b/liveMedia/MPEG4GenericRTPSink.cpp
index f1de0f3..a723422 100644
--- a/liveMedia/MPEG4GenericRTPSink.cpp
+++ b/liveMedia/MPEG4GenericRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG4-GENERIC ("audio", "video", or "application") RTP stream sinks
 // Implementation
 
diff --git a/liveMedia/MPEG4GenericRTPSource.cpp b/liveMedia/MPEG4GenericRTPSource.cpp
index 55e19aa..2064a60 100644
--- a/liveMedia/MPEG4GenericRTPSource.cpp
+++ b/liveMedia/MPEG4GenericRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG4-GENERIC ("audio", "video", or "application") RTP stream sources
 // Implementation
 
diff --git a/liveMedia/MPEG4LATMAudioRTPSink.cpp b/liveMedia/MPEG4LATMAudioRTPSink.cpp
index efb8a24..2b9a50b 100644
--- a/liveMedia/MPEG4LATMAudioRTPSink.cpp
+++ b/liveMedia/MPEG4LATMAudioRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for MPEG-4 audio, using LATM multiplexing (RFC 3016)
 // Implementation
 
diff --git a/liveMedia/MPEG4LATMAudioRTPSource.cpp b/liveMedia/MPEG4LATMAudioRTPSource.cpp
index af4fdc3..17954f0 100644
--- a/liveMedia/MPEG4LATMAudioRTPSource.cpp
+++ b/liveMedia/MPEG4LATMAudioRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG-4 audio, using LATM multiplexing
 // Implementation
 
diff --git a/liveMedia/MPEG4VideoFileServerMediaSubsession.cpp b/liveMedia/MPEG4VideoFileServerMediaSubsession.cpp
index 0d6472a..6ef85c8 100644
--- a/liveMedia/MPEG4VideoFileServerMediaSubsession.cpp
+++ b/liveMedia/MPEG4VideoFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a MPEG-4 video file.
 // Implementation
@@ -73,7 +73,7 @@ void MPEG4VideoFileServerMediaSubsession::checkForAuxSDPLine1() {
 
     // Signal the event loop that we're done:
     setDoneFlag();
-  } else {
+  } else if (!fDoneFlag) {
     // try again after a brief delay:
     int uSecsToDelay = 100000; // 100 ms
     nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecsToDelay,
diff --git a/liveMedia/MPEG4VideoStreamDiscreteFramer.cpp b/liveMedia/MPEG4VideoStreamDiscreteFramer.cpp
index ebb89c3..7e9b97a 100644
--- a/liveMedia/MPEG4VideoStreamDiscreteFramer.cpp
+++ b/liveMedia/MPEG4VideoStreamDiscreteFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "MPEG4VideoStreamFramer" that takes only complete,
 // discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
diff --git a/liveMedia/MPEG4VideoStreamFramer.cpp b/liveMedia/MPEG4VideoStreamFramer.cpp
index 700f64c..e6c15ea 100644
--- a/liveMedia/MPEG4VideoStreamFramer.cpp
+++ b/liveMedia/MPEG4VideoStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an MPEG-4 video elementary stream into
 //   frames for:
 // - Visual Object Sequence (VS) Header + Visual Object (VO) Header
diff --git a/liveMedia/MPEGVideoStreamFramer.cpp b/liveMedia/MPEGVideoStreamFramer.cpp
index 58f5d4d..46c15d3 100644
--- a/liveMedia/MPEGVideoStreamFramer.cpp
+++ b/liveMedia/MPEGVideoStreamFramer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an MPEG video elementary stream into
 //   headers and frames
 // Implementation
@@ -142,6 +142,11 @@ void MPEGVideoStreamFramer::doGetNextFrame() {
   continueReadProcessing();
 }
 
+void MPEGVideoStreamFramer::doStopGettingFrames() {
+  flushInput();
+  FramedFilter::doStopGettingFrames();
+}
+
 void MPEGVideoStreamFramer
 ::continueReadProcessing(void* clientData,
 			 unsigned char* /*ptr*/, unsigned /*size*/,
diff --git a/liveMedia/MPEGVideoStreamParser.cpp b/liveMedia/MPEGVideoStreamParser.cpp
index e0eda9f..15c7d7d 100644
--- a/liveMedia/MPEGVideoStreamParser.cpp
+++ b/liveMedia/MPEGVideoStreamParser.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // An abstract parser for MPEG video streams
 // Implementation
 
diff --git a/liveMedia/MPEGVideoStreamParser.hh b/liveMedia/MPEGVideoStreamParser.hh
index 7e09098..0eee206 100644
--- a/liveMedia/MPEGVideoStreamParser.hh
+++ b/liveMedia/MPEGVideoStreamParser.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // An abstract parser for MPEG video streams
 // C++ header
 
diff --git a/liveMedia/Makefile.tail b/liveMedia/Makefile.tail
index b52eb9f..14f51f8 100644
--- a/liveMedia/Makefile.tail
+++ b/liveMedia/Makefile.tail
@@ -23,16 +23,17 @@ DV_SINK_OBJS = DVVideoRTPSink.$(OBJ)
 AC3_SINK_OBJS = AC3AudioRTPSink.$(OBJ)
 
 MISC_SOURCE_OBJS = MediaSource.$(OBJ) FramedSource.$(OBJ) FramedFileSource.$(OBJ) FramedFilter.$(OBJ) ByteStreamFileSource.$(OBJ) ByteStreamMultiFileSource.$(OBJ) ByteStreamMemoryBufferSource.$(OBJ) BasicUDPSource.$(OBJ) DeviceSource.$(OBJ) AudioInputDevice.$(OBJ) WAVAudioFileSource.$(OBJ) $(MPEG_SOURCE_OBJS) $(H263_SOURCE_OBJS) $(AC3_SOURCE_OBJS) $(DV_SOURCE_OBJS) JPEGVideoSource.$(OBJ) AMRAudioSource.$(OBJ) AMRAudioFileSource.$(OBJ) InputFile.$(OBJ) StreamReplicator.$(OBJ)
-MISC_SINK_OBJS = MediaSink.$(OBJ) FileSink.$(OBJ) BasicUDPSink.$(OBJ) AMRAudioFileSink.$(OBJ) H264VideoFileSink.$(OBJ) $(MPEG_SINK_OBJS) $(H263_SINK_OBJS) $(H264_OR_5_SINK_OBJS) $(DV_SINK_OBJS) $(AC3_SINK_OBJS) VorbisAudioRTPSink.$(OBJ) TheoraVideoRTPSink.$(OBJ) VP8VideoRTPSink.$(OBJ) GSMAudioRTPSink.$(OBJ) JPEGVideoRTPSink.$(OBJ) SimpleRTPSink.$(OBJ) AMRAudioRTPSink.$(OBJ) T140TextRTPSink.$(OBJ) TCPStreamSink.$(OBJ) OutputFile.$(OBJ)
+MISC_SINK_OBJS = MediaSink.$(OBJ) FileSink.$(OBJ) BasicUDPSink.$(OBJ) AMRAudioFileSink.$(OBJ) H264or5VideoFileSink.$(OBJ) H264VideoFileSink.$(OBJ) H265VideoFileSink.$(OBJ) OggFileSink.$(OBJ) $(MPEG_SINK_OBJS) $(H263_SINK_OBJS) $(H264_OR_5_SINK_OBJS) $(DV_SINK_OBJS) $(AC3_SINK_OBJS) VorbisAudioRTPSink.$(OBJ) TheoraVideoRTPSink.$(OBJ) VP8VideoRTPSink.$(OBJ) VP9VideoRTPSink.$(OBJ) GSMAudioRTPSink.$(OBJ) JPEGVideoRTPSink.$(OBJ) SimpleRTPSink.$(OBJ) AMRAudioRTPSink.$(OBJ) T140TextRTPSink.$(OBJ) TCPStreamSink.$(OBJ) OutputFile.$(OBJ)
 MISC_FILTER_OBJS = uLawAudioFilter.$(OBJ)
 TRANSPORT_STREAM_TRICK_PLAY_OBJS = MPEG2IndexFromTransportStream.$(OBJ) MPEG2TransportStreamIndexFile.$(OBJ) MPEG2TransportStreamTrickModeFilter.$(OBJ)
 
-RTP_SOURCE_OBJS = RTPSource.$(OBJ) MultiFramedRTPSource.$(OBJ) SimpleRTPSource.$(OBJ) H261VideoRTPSource.$(OBJ) H264VideoRTPSource.$(OBJ) QCELPAudioRTPSource.$(OBJ) AMRAudioRTPSource.$(OBJ) JPEGVideoRTPSource.$(OBJ) VorbisAudioRTPSource.$(OBJ) VP8VideoRTPSource.$(OBJ)
+RTP_SOURCE_OBJS = RTPSource.$(OBJ) MultiFramedRTPSource.$(OBJ) SimpleRTPSource.$(OBJ) H261VideoRTPSource.$(OBJ) H264VideoRTPSource.$(OBJ) H265VideoRTPSource.$(OBJ) QCELPAudioRTPSource.$(OBJ) AMRAudioRTPSource.$(OBJ) JPEGVideoRTPSource.$(OBJ) VorbisAudioRTPSource.$(OBJ) TheoraVideoRTPSource.$(OBJ) VP8VideoRTPSource.$(OBJ) VP9VideoRTPSource.$(OBJ)
 RTP_SINK_OBJS = RTPSink.$(OBJ) MultiFramedRTPSink.$(OBJ) AudioRTPSink.$(OBJ) VideoRTPSink.$(OBJ) TextRTPSink.$(OBJ)
 RTP_INTERFACE_OBJS = RTPInterface.$(OBJ)
 RTP_OBJS = $(RTP_SOURCE_OBJS) $(RTP_SINK_OBJS) $(RTP_INTERFACE_OBJS)
 
 RTCP_OBJS = RTCP.$(OBJ) rtcp_from_spec.$(OBJ)
+GENERIC_MEDIA_SERVER_OBJS = GenericMediaServer.$(OBJ)
 RTSP_OBJS = RTSPServer.$(OBJ) RTSPClient.$(OBJ) RTSPCommon.$(OBJ) RTSPServerSupportingHTTPStreaming.$(OBJ) RTSPRegisterSender.$(OBJ)
 SIP_OBJS = SIPClient.$(OBJ)
 
@@ -42,16 +43,18 @@ QUICKTIME_OBJS = QuickTimeFileSink.$(OBJ) QuickTimeGenericRTPSource.$(OBJ)
 AVI_OBJS = AVIFileSink.$(OBJ)
 
 MATROSKA_FILE_OBJS = MatroskaFile.$(OBJ) MatroskaFileParser.$(OBJ) EBMLNumber.$(OBJ) MatroskaDemuxedTrack.$(OBJ)
-MATROSKA_SERVER_MEDIA_SUBSESSION_VIDEO_OBJS = H264VideoMatroskaFileServerMediaSubsession.$(OBJ) H265VideoMatroskaFileServerMediaSubsession.$(OBJ) VP8VideoMatroskaFileServerMediaSubsession.$(OBJ)
-MATROSKA_SERVER_MEDIA_SUBSESSION_AUDIO_OBJS = AACAudioMatroskaFileServerMediaSubsession.$(OBJ) AC3AudioMatroskaFileServerMediaSubsession.$(OBJ) MP3AudioMatroskaFileServerMediaSubsession.$(OBJ) VorbisAudioMatroskaFileServerMediaSubsession.$(OBJ)
-MATROSKA_SERVER_MEDIA_SUBSESSION_TEXT_OBJS = T140TextMatroskaFileServerMediaSubsession.$(OBJ)
-MATROSKA_SERVER_MEDIA_SUBSESSION_OBJS = $(MATROSKA_SERVER_MEDIA_SUBSESSION_VIDEO_OBJS) $(MATROSKA_SERVER_MEDIA_SUBSESSION_AUDIO_OBJS) $(MATROSKA_SERVER_MEDIA_SUBSESSION_TEXT_OBJS)
+MATROSKA_SERVER_MEDIA_SUBSESSION_OBJS = MatroskaFileServerMediaSubsession.$(OBJ) MP3AudioMatroskaFileServerMediaSubsession.$(OBJ)
 MATROSKA_RTSP_SERVER_OBJS = MatroskaFileServerDemux.$(OBJ) $(MATROSKA_SERVER_MEDIA_SUBSESSION_OBJS)
 MATROSKA_OBJS = $(MATROSKA_FILE_OBJS) $(MATROSKA_RTSP_SERVER_OBJS)
 
-MISC_OBJS = DarwinInjector.$(OBJ) BitVector.$(OBJ) StreamParser.$(OBJ) DigestAuthentication.$(OBJ) ourMD5.$(OBJ) Base64.$(OBJ) Locale.$(OBJ)
+OGG_FILE_OBJS = OggFile.$(OBJ) OggFileParser.$(OBJ) OggDemuxedTrack.$(OBJ)
+OGG_SERVER_MEDIA_SUBSESSION_OBJS = OggFileServerMediaSubsession.$(OBJ)
+OGG_RTSP_SERVER_OBJS = OggFileServerDemux.$(OBJ) $(OGG_SERVER_MEDIA_SUBSESSION_OBJS)
+OGG_OBJS = $(OGG_FILE_OBJS) $(OGG_RTSP_SERVER_OBJS)
 
-LIVEMEDIA_LIB_OBJS = Media.$(OBJ) $(MISC_SOURCE_OBJS) $(MISC_SINK_OBJS) $(MISC_FILTER_OBJS) $(RTP_OBJS) $(RTCP_OBJS) $(RTSP_OBJS) $(SIP_OBJS) $(SESSION_OBJS) $(QUICKTIME_OBJS) $(AVI_OBJS) $(TRANSPORT_STREAM_TRICK_PLAY_OBJS) $(MATROSKA_OBJS) $(MISC_OBJS)
+MISC_OBJS = BitVector.$(OBJ) StreamParser.$(OBJ) DigestAuthentication.$(OBJ) ourMD5.$(OBJ) Base64.$(OBJ) Locale.$(OBJ)
+
+LIVEMEDIA_LIB_OBJS = Media.$(OBJ) $(MISC_SOURCE_OBJS) $(MISC_SINK_OBJS) $(MISC_FILTER_OBJS) $(RTP_OBJS) $(RTCP_OBJS) $(GENERIC_MEDIA_SERVER_OBJS) $(RTSP_OBJS) $(SIP_OBJS) $(SESSION_OBJS) $(QUICKTIME_OBJS) $(AVI_OBJS) $(TRANSPORT_STREAM_TRICK_PLAY_OBJS) $(MATROSKA_OBJS) $(OGG_OBJS) $(MISC_OBJS)
 
 $(LIVEMEDIA_LIB): $(LIVEMEDIA_LIB_OBJS) \
     $(PLATFORM_SPECIFIC_LIB_OBJS)
@@ -71,7 +74,7 @@ include/FramedFilter.hh:	include/FramedSource.hh
 RTPSource.$(CPP):	include/RTPSource.hh
 include/RTPSource.hh:		include/FramedSource.hh include/RTPInterface.hh
 include/RTPInterface.hh:	include/Media.hh
-MultiFramedRTPSource.$(CPP):	include/MultiFramedRTPSource.hh
+MultiFramedRTPSource.$(CPP):	include/MultiFramedRTPSource.hh include/RTCP.hh
 include/MultiFramedRTPSource.hh:	include/RTPSource.hh
 SimpleRTPSource.$(CPP):	include/SimpleRTPSource.hh
 include/SimpleRTPSource.hh:	include/MultiFramedRTPSource.hh
@@ -79,16 +82,22 @@ H261VideoRTPSource.$(CPP):	include/H261VideoRTPSource.hh
 include/H261VideoRTPSource.hh:	include/MultiFramedRTPSource.hh
 H264VideoRTPSource.$(CPP):      include/H264VideoRTPSource.hh include/Base64.hh
 include/H264VideoRTPSource.hh:  include/MultiFramedRTPSource.hh
+H265VideoRTPSource.$(CPP):      include/H265VideoRTPSource.hh
+include/H265VideoRTPSource.hh:  include/MultiFramedRTPSource.hh
 QCELPAudioRTPSource.$(CPP):	include/QCELPAudioRTPSource.hh include/MultiFramedRTPSource.hh include/FramedFilter.hh
 include/QCELPAudioRTPSource.hh:		include/RTPSource.hh
 AMRAudioRTPSource.$(CPP):	include/AMRAudioRTPSource.hh include/MultiFramedRTPSource.hh
 include/AMRAudioRTPSource.hh:		include/RTPSource.hh include/AMRAudioSource.hh
 JPEGVideoRTPSource.$(CPP):	include/JPEGVideoRTPSource.hh
 include/JPEGVideoRTPSource.hh:	include/MultiFramedRTPSource.hh
-VorbisAudioRTPSource.$(CPP):	include/VorbisAudioRTPSource.hh
+VorbisAudioRTPSource.$(CPP):	include/VorbisAudioRTPSource.hh include/Base64.hh
 include/VorbisAudioRTPSource.hh:	include/MultiFramedRTPSource.hh
+TheoraVideoRTPSource.$(CPP):	include/TheoraVideoRTPSource.hh
+include/TheoraVideoRTPSource.hh:	include/MultiFramedRTPSource.hh
 VP8VideoRTPSource.$(CPP):	include/VP8VideoRTPSource.hh
 include/VP8VideoRTPSource.hh:	include/MultiFramedRTPSource.hh
+VP9VideoRTPSource.$(CPP):	include/VP9VideoRTPSource.hh
+include/VP9VideoRTPSource.hh:	include/MultiFramedRTPSource.hh
 ByteStreamFileSource.$(CPP):	include/ByteStreamFileSource.hh include/InputFile.hh
 include/ByteStreamFileSource.hh:	include/FramedFileSource.hh
 ByteStreamMultiFileSource.$(CPP):	include/ByteStreamMultiFileSource.hh
@@ -203,9 +212,15 @@ 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 include/H264VideoRTPSource.hh
-include/H264VideoFileSink.hh:   include/FileSink.hh
-RTPSink.$(CPP):		include/RTPSink.hh
+H264or5VideoFileSink.$(CPP):	include/H264or5VideoFileSink.hh include/H264VideoRTPSource.hh
+include/H264or5VideoFileSink.hh:   include/FileSink.hh
+H264VideoFileSink.$(CPP):       include/H264VideoFileSink.hh include/OutputFile.hh
+include/H264VideoFileSink.hh:   include/H264or5VideoFileSink.hh
+H265VideoFileSink.$(CPP):       include/H265VideoFileSink.hh include/OutputFile.hh
+include/H265VideoFileSink.hh:   include/H264or5VideoFileSink.hh
+OggFileSink.$(CPP):		include/OggFileSink.hh include/OutputFile.hh include/VorbisAudioRTPSource.hh include/MPEG2TransportStreamMultiplexor.hh include/FramedSource.hh
+include/OggFileSink.hh:		include/FileSink.hh
+RTPSink.$(CPP):			include/RTPSink.hh
 include/RTPSink.hh:		include/MediaSink.hh include/RTPInterface.hh
 MultiFramedRTPSink.$(CPP):	include/MultiFramedRTPSink.hh
 include/MultiFramedRTPSink.hh:		include/RTPSink.hh
@@ -241,12 +256,14 @@ include/DVVideoRTPSink.hh:	include/VideoRTPSink.hh include/DVVideoStreamFramer.h
 include/DVVideoStreamFramer.hh:	include/FramedFilter.hh
 AC3AudioRTPSink.$(CPP):		include/AC3AudioRTPSink.hh
 include/AC3AudioRTPSink.hh:	include/AudioRTPSink.hh
-VorbisAudioRTPSink.$(CPP):	include/VorbisAudioRTPSink.hh include/Base64.hh
+VorbisAudioRTPSink.$(CPP):	include/VorbisAudioRTPSink.hh include/Base64.hh include/VorbisAudioRTPSource.hh
 include/VorbisAudioRTPSink.hh:	include/AudioRTPSink.hh
-TheoraVideoRTPSink.$(CPP):	include/TheoraVideoRTPSink.hh include/Base64.hh
+TheoraVideoRTPSink.$(CPP):	include/TheoraVideoRTPSink.hh include/Base64.hh include/VorbisAudioRTPSource.hh include/VorbisAudioRTPSink.hh
 include/TheoraVideoRTPSink.hh:	include/VideoRTPSink.hh
 VP8VideoRTPSink.$(CPP):		include/VP8VideoRTPSink.hh
 include/VP8VideoRTPSink.hh:	include/VideoRTPSink.hh
+VP9VideoRTPSink.$(CPP):		include/VP9VideoRTPSink.hh
+include/VP9VideoRTPSink.hh:	include/VideoRTPSink.hh
 GSMAudioRTPSink.$(CPP):		include/GSMAudioRTPSink.hh
 include/GSMAudioRTPSink.hh:	include/AudioRTPSink.hh
 JPEGVideoRTPSink.$(CPP):	include/JPEGVideoRTPSink.hh include/JPEGVideoSource.hh
@@ -271,10 +288,12 @@ include/MPEG2TransportStreamTrickModeFilter.hh:	include/FramedFilter.hh include/
 RTCP.$(CPP):		include/RTCP.hh rtcp_from_spec.h
 include/RTCP.hh:		include/RTPSink.hh include/RTPSource.hh
 rtcp_from_spec.$(C):	rtcp_from_spec.h
+GenericMediaServer.$(CPP):	include/GenericMediaServer.hh
+include/GenericMediaServer.hh:	include/ServerMediaSession.hh
 RTSPServer.$(CPP):	include/RTSPServer.hh include/RTSPCommon.hh include/RTSPRegisterSender.hh include/ProxyServerMediaSession.hh include/Base64.hh
-include/RTSPServer.hh:		include/ServerMediaSession.hh include/DigestAuthentication.hh include/RTSPCommon.hh
-include/ServerMediaSession.hh:	include/Media.hh include/FramedSource.hh include/RTPInterface.hh
-RTSPClient.$(CPP):	include/RTSPClient.hh  include/RTSPCommon.hh include/Base64.hh include/Locale.hh ourMD5.hh
+include/RTSPServer.hh:		include/GenericMediaServer.hh include/DigestAuthentication.hh
+include/ServerMediaSession.hh:	include/RTCP.hh
+RTSPClient.$(CPP):	include/RTSPClient.hh  include/RTSPCommon.hh include/Base64.hh include/Locale.hh include/ourMD5.hh
 include/RTSPClient.hh:		include/MediaSession.hh include/DigestAuthentication.hh
 RTSPCommon.$(CPP):	include/RTSPCommon.hh include/Locale.hh
 RTSPServerSupportingHTTPStreaming.$(CPP):	include/RTSPServerSupportingHTTPStreaming.hh include/RTSPCommon.hh
@@ -323,52 +342,49 @@ include/AC3AudioFileServerMediaSubsession.hh:	include/FileServerMediaSubsession.
 MPEG2TransportUDPServerMediaSubsession.$(CPP):	include/MPEG2TransportUDPServerMediaSubsession.hh include/BasicUDPSource.hh include/SimpleRTPSource.hh include/MPEG2TransportStreamFramer.hh include/SimpleRTPSink.hh
 include/MPEG2TransportUDPServerMediaSubsession.hh:	include/OnDemandServerMediaSubsession.hh
 ProxyServerMediaSession.$(CPP):		include/liveMedia.hh include/RTSPCommon.hh
-include/ProxyServerMediaSession.hh:	include/ServerMediaSession.hh include/MediaSession.hh include/RTSPClient.hh
+include/ProxyServerMediaSession.hh:	include/ServerMediaSession.hh include/MediaSession.hh include/RTSPClient.hh include/MediaTranscodingTable.hh
+include/MediaTranscodingTable.hh:	include/FramedFilter.hh include/MediaSession.hh
 QuickTimeFileSink.$(CPP):	include/QuickTimeFileSink.hh include/InputFile.hh include/OutputFile.hh include/QuickTimeGenericRTPSource.hh include/H263plusVideoRTPSource.hh include/MPEG4GenericRTPSource.hh include/MPEG4LATMAudioRTPSource.hh
 include/QuickTimeFileSink.hh:	include/MediaSession.hh
 QuickTimeGenericRTPSource.$(CPP):	include/QuickTimeGenericRTPSource.hh
 include/QuickTimeGenericRTPSource.hh:	include/MultiFramedRTPSource.hh
 AVIFileSink.$(CPP):	include/AVIFileSink.hh include/InputFile.hh include/OutputFile.hh
 include/AVIFileSink.hh:	include/MediaSession.hh
-MatroskaFile.$(CPP): MatroskaFileParser.hh MatroskaDemuxedTrack.hh include/ByteStreamFileSource.hh
+MatroskaFile.$(CPP): MatroskaFileParser.hh MatroskaDemuxedTrack.hh include/ByteStreamFileSource.hh include/H264VideoStreamDiscreteFramer.hh include/H265VideoStreamDiscreteFramer.hh include/MPEG1or2AudioRTPSink.hh include/MPEG4GenericRTPSink.hh include/AC3AudioRTPSink.hh include/SimpleRTPSink.hh include/VorbisAudioRTPSink.hh include/H264VideoRTPSink.hh include/H265VideoRTPSink.hh include/VP8VideoRTPSink.hh include/VP9VideoRTPSink.hh include/T140TextRTPSink.hh
 MatroskaFileParser.hh:	StreamParser.hh include/MatroskaFile.hh EBMLNumber.hh
-include/MatroskaFile.hh: include/Media.hh
+include/MatroskaFile.hh: include/RTPSink.hh
 MatroskaDemuxedTrack.hh:	include/FramedSource.hh
 MatroskaFileParser.$(CPP): MatroskaFileParser.hh MatroskaDemuxedTrack.hh include/ByteStreamFileSource.hh
 EBMLNumber.$(CPP): EBMLNumber.hh
 MatroskaDemuxedTrack.$(CPP): MatroskaDemuxedTrack.hh include/MatroskaFile.hh
-H264VideoMatroskaFileServerMediaSubsession.$(CPP): H264VideoMatroskaFileServerMediaSubsession.hh include/H264VideoStreamDiscreteFramer.hh
-H264VideoMatroskaFileServerMediaSubsession.hh: include/H264VideoFileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh MatroskaDemuxedTrack.hh
-H265VideoMatroskaFileServerMediaSubsession.$(CPP): H265VideoMatroskaFileServerMediaSubsession.hh include/H265VideoStreamDiscreteFramer.hh
-H265VideoMatroskaFileServerMediaSubsession.hh: include/H265VideoFileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh MatroskaDemuxedTrack.hh
-VP8VideoMatroskaFileServerMediaSubsession.$(CPP): VP8VideoMatroskaFileServerMediaSubsession.hh include/VP8VideoRTPSink.hh
-VP8VideoMatroskaFileServerMediaSubsession.hh: include/FileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh MatroskaDemuxedTrack.hh
-AACAudioMatroskaFileServerMediaSubsession.$(CPP): AACAudioMatroskaFileServerMediaSubsession.hh include/MPEG4GenericRTPSink.hh
-AACAudioMatroskaFileServerMediaSubsession.hh: include/FileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh MatroskaDemuxedTrack.hh
-AC3AudioMatroskaFileServerMediaSubsession.$(CPP): AC3AudioMatroskaFileServerMediaSubsession.hh include/AC3AudioRTPSink.hh
-AC3AudioMatroskaFileServerMediaSubsession.hh: include/FileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh MatroskaDemuxedTrack.hh
-VorbisAudioMatroskaFileServerMediaSubsession.$(CPP): VorbisAudioMatroskaFileServerMediaSubsession.hh include/VorbisAudioRTPSink.hh
-VorbisAudioMatroskaFileServerMediaSubsession.hh: include/FileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh MatroskaDemuxedTrack.hh
-MP3AudioMatroskaFileServerMediaSubsession.$(CPP): MP3AudioMatroskaFileServerMediaSubsession.hh
-MP3AudioMatroskaFileServerMediaSubsession.hh: include/MP3AudioFileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh MatroskaDemuxedTrack.hh
-T140TextMatroskaFileServerMediaSubsession.$(CPP): T140TextMatroskaFileServerMediaSubsession.hh include/T140TextRTPSink.hh
-T140TextMatroskaFileServerMediaSubsession.hh: include/FileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh MatroskaDemuxedTrack.hh
-MatroskaFileServerDemux.$(CPP): include/MatroskaFileServerDemux.hh H264VideoMatroskaFileServerMediaSubsession.hh H265VideoMatroskaFileServerMediaSubsession.hh VP8VideoMatroskaFileServerMediaSubsession.hh AACAudioMatroskaFileServerMediaSubsession.hh AC3AudioMatroskaFileServerMediaSubsession.hh VorbisAudioMatroskaFileServerMediaSubsession.hh MP3AudioMatroskaFileServerMediaSubsession.hh T140TextMatroskaFileServerMediaSubsession.hh
+MatroskaFileServerMediaSubsession.$(CPP): MatroskaFileServerMediaSubsession.hh MatroskaDemuxedTrack.hh include/FramedFilter.hh
+MatroskaFileServerMediaSubsession.hh: include/FileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh
+MP3AudioMatroskaFileServerMediaSubsession.$(CPP): MP3AudioMatroskaFileServerMediaSubsession.hh MatroskaDemuxedTrack.hh
+MP3AudioMatroskaFileServerMediaSubsession.hh: include/MP3AudioFileServerMediaSubsession.hh include/MatroskaFileServerDemux.hh
+MatroskaFileServerDemux.$(CPP): include/MatroskaFileServerDemux.hh MP3AudioMatroskaFileServerMediaSubsession.hh MatroskaFileServerMediaSubsession.hh
 include/MatroskaFileServerDemux.hh: include/ServerMediaSession.hh include/MatroskaFile.hh
-DarwinInjector.$(CPP):	include/DarwinInjector.hh
-include/DarwinInjector.hh:	include/RTSPClient.hh include/RTCP.hh
+OggFile.$(CPP): OggFileParser.hh OggDemuxedTrack.hh include/ByteStreamFileSource.hh include/VorbisAudioRTPSink.hh include/SimpleRTPSink.hh include/TheoraVideoRTPSink.hh
+OggFileParser.hh:	StreamParser.hh include/OggFile.hh
+include/OggFile.hh: include/RTPSink.hh
+OggDemuxedTrack.hh:	include/FramedSource.hh
+OggFileParser.$(CPP): OggFileParser.hh OggDemuxedTrack.hh
+OggDemuxedTrack.$(CPP): OggDemuxedTrack.hh include/OggFile.hh
+OggFileServerMediaSubsession.$(CPP): OggFileServerMediaSubsession.hh OggDemuxedTrack.hh include/FramedFilter.hh
+OggFileServerMediaSubsession.hh: include/FileServerMediaSubsession.hh include/OggFileServerDemux.hh
+OggFileServerDemux.$(CPP): include/OggFileServerDemux.hh OggFileServerMediaSubsession.hh
+include/OggFileServerDemux.hh: include/ServerMediaSession.hh include/OggFile.hh
 BitVector.$(CPP):	include/BitVector.hh
 StreamParser.$(CPP):	StreamParser.hh
-DigestAuthentication.$(CPP):	include/DigestAuthentication.hh ourMD5.hh
-ourMD5.$(CPP):	ourMD5.hh
+DigestAuthentication.$(CPP):	include/DigestAuthentication.hh include/ourMD5.hh
+ourMD5.$(CPP):	include/ourMD5.hh
 Base64.$(CPP):	include/Base64.hh
 Locale.$(CPP):	include/Locale.hh
 
-include/liveMedia.hh:: include/MPEG1or2AudioRTPSink.hh include/MP3ADURTPSink.hh include/MPEG1or2VideoRTPSink.hh include/MPEG4ESVideoRTPSink.hh include/BasicUDPSink.hh include/AMRAudioFileSink.hh include/H264VideoFileSink.hh include/GSMAudioRTPSink.hh include/H263plusVideoRTPSink.hh include/H264VideoRTPSink.hh include/H265VideoRTPSink.hh include/DVVideoRTPSource.hh include/DVVideoRTPSink.hh include/DVVideoStreamFramer.hh include/H264VideoStreamFramer.hh include/H265VideoStreamFramer.hh include/H264VideoStreamDiscreteFramer.hh include/H265VideoStreamDiscreteFramer.hh include/JPEGVideoRTPSink.hh include/SimpleRTPSink.hh include/uLawAudioFilter.hh include/MPEG2IndexFromTransportStream.hh include/MPEG2TransportStreamTrickModeFilter.hh include/ByteStreamMultiFileSource.hh include/ByteStreamMemoryBufferSource.hh include/BasicUDPSource.hh include/SimpleRTPSource.hh include/MPEG1or2AudioRTPSource.hh include/MPEG4LATMAudioRTPSource.hh include/MPEG4LATMAudioRTPSink.hh include/MPEG4ESVideoRTPSource.hh include/MPEG4GenericRTPSource.hh include/MP3ADURTPSource.hh include/QCELPAudioRTPSource.hh include/AMRAudioRTPSource.hh include/JPEGVideoRTPSource.hh include/JPEGVideoSource.hh include/MPEG1or2VideoRTPSource.hh include/VorbisAudioRTPSource.hh include/VP8VideoRTPSource.hh
+include/liveMedia.hh:: include/MPEG1or2AudioRTPSink.hh include/MP3ADURTPSink.hh include/MPEG1or2VideoRTPSink.hh include/MPEG4ESVideoRTPSink.hh include/BasicUDPSink.hh include/AMRAudioFileSink.hh include/H264VideoFileSink.hh include/H265VideoFileSink.hh include/OggFileSink.hh include/GSMAudioRTPSink.hh include/H263plusVideoRTPSink.hh include/H264VideoRTPSink.hh include/H265VideoRTPSink.hh include/DVVideoRTPSource.hh include/DVVideoRTPSink.hh include/DVVideoStreamFramer.hh include/H264VideoStreamFramer.hh include/H265VideoStreamFramer.hh include/H264VideoStreamDiscreteFramer.hh include/H265VideoStreamDiscreteFramer.hh include/JPEGVideoRTPSink.hh include/SimpleRTPSink.hh include/uLawAudioFilter.hh include/MPEG2IndexFromTransportStream.hh include/MPEG2TransportStreamTrickModeFilter.hh include/ByteStreamMultiFileSource.hh include/ByteStreamMemoryBufferSource.hh include/BasicUDPSource.hh include/SimpleRTPSource.hh include/MPEG1or2AudioRTPSource.hh include/MPEG4LATMAudioRTPSource.hh include/MPEG4LATMAudioRTPSink.hh include/MPEG4ESVideoRTPSource.hh include/MPEG4GenericRTPSource.hh include/MP3ADURTPSource.hh include/QCELPAudioRTPSource.hh include/AMRAudioRTPSource.hh include/JPEGVideoRTPSource.hh include/JPEGVideoSource.hh include/MPEG1or2VideoRTPSource.hh include/VorbisAudioRTPSource.hh include/TheoraVideoRTPSource.hh include/VP8VideoRTPSource.hh include/VP9VideoRTPSource.hh
 
-include/liveMedia.hh::	include/MPEG2TransportStreamFromPESSource.hh include/MPEG2TransportStreamFromESSource.hh include/MPEG2TransportStreamFramer.hh include/ADTSAudioFileSource.hh include/H261VideoRTPSource.hh include/H263plusVideoRTPSource.hh include/H264VideoRTPSource.hh include/MP3FileSource.hh include/MP3ADU.hh include/MP3ADUinterleaving.hh include/MP3Transcoder.hh include/MPEG1or2DemuxedElementaryStream.hh include/MPEG1or2AudioStreamFramer.hh include/MPEG1or2VideoStreamDiscreteFramer.hh include/MPEG4VideoStreamDiscreteFramer.hh include/H263plusVideoStreamFramer.hh include/AC3AudioStreamFramer.hh include/AC3AudioRTPSource.hh include/AC3AudioRTPSink.hh include/VorbisAudioRTPSink.hh include/TheoraVideoRTPSink.hh include/VP8VideoRTPSink.hh include/MPEG4GenericRTPSink.hh include/DeviceSource.hh include/AudioInputDevice.hh include/WAVAudioFileSource.hh include/StreamReplicator.hh include/RTSPRegisterSender.hh
+include/liveMedia.hh::	include/MPEG2TransportStreamFromPESSource.hh include/MPEG2TransportStreamFromESSource.hh include/MPEG2TransportStreamFramer.hh include/ADTSAudioFileSource.hh include/H261VideoRTPSource.hh include/H263plusVideoRTPSource.hh include/H264VideoRTPSource.hh include/H265VideoRTPSource.hh include/MP3FileSource.hh include/MP3ADU.hh include/MP3ADUinterleaving.hh include/MP3Transcoder.hh include/MPEG1or2DemuxedElementaryStream.hh include/MPEG1or2AudioStreamFramer.hh include/MPEG1or2VideoStreamDiscreteFramer.hh include/MPEG4VideoStreamDiscreteFramer.hh include/H263plusVideoStreamFramer.hh include/AC3AudioStreamFramer.hh include/AC3AudioRTPSource.hh include/AC3AudioRTPSink.hh include/VorbisAudioRTPSink.hh include/TheoraVideoRTPSink.hh include/VP8VideoRTPSink.hh include/VP9VideoRTPSink.hh include/MPEG4GenericRTPSink.hh include/DeviceSource.hh include/AudioInputDevice.hh include/WAVAudioFileSource.hh include/StreamReplicator.hh include/RTSPRegisterSender.hh
 
-include/liveMedia.hh:: include/RTSPServerSupportingHTTPStreaming.hh include/RTSPClient.hh include/SIPClient.hh include/QuickTimeFileSink.hh include/QuickTimeGenericRTPSource.hh include/AVIFileSink.hh include/PassiveServerMediaSubsession.hh include/MPEG4VideoFileServerMediaSubsession.hh include/H264VideoFileServerMediaSubsession.hh include/H265VideoFileServerMediaSubsession.hh include/WAVAudioFileServerMediaSubsession.hh include/AMRAudioFileServerMediaSubsession.hh include/AMRAudioFileSource.hh include/AMRAudioRTPSink.hh include/T140TextRTPSink.hh include/TCPStreamSink.hh include/MP3AudioFileServerMediaSubsession.hh include/MPEG1or2VideoFileServerMediaSubsession.hh include/MPEG1or2FileServerDemux.hh include/MPEG2TransportFileServerMediaSubsession.hh include/H263plusVideoFileServerMediaSubsession.hh include/ADTSAudioFileServerMediaSubsession.hh include/DVVideoFileServerMediaSubsession.hh include/AC3AudioFileServerMediaSubsession.hh include/MPEG2TransportUDPServerMediaSubsession.hh include/MatroskaFileServerDemux.hh include/ProxyServerMediaSession.hh include/DarwinInjector.hh
+include/liveMedia.hh:: include/RTSPServerSupportingHTTPStreaming.hh include/RTSPClient.hh include/SIPClient.hh include/QuickTimeFileSink.hh include/QuickTimeGenericRTPSource.hh include/AVIFileSink.hh include/PassiveServerMediaSubsession.hh include/MPEG4VideoFileServerMediaSubsession.hh include/H264VideoFileServerMediaSubsession.hh include/H265VideoFileServerMediaSubsession.hh include/WAVAudioFileServerMediaSubsession.hh include/AMRAudioFileServerMediaSubsession.hh include/AMRAudioFileSource.hh include/AMRAudioRTPSink.hh include/T140TextRTPSink.hh include/TCPStreamSink.hh include/MP3AudioFileServerMediaSubsession.hh include/MPEG1or2VideoFileServerMediaSubsession.hh include/MPEG1or2FileServerDemux.hh include/MPEG2TransportFileServerMediaSubsession.hh include/H263plusVideoFileServerMediaSubsession.hh include/ADTSAudioFileServerMediaSubsession.hh include/DVVideoFileServerMediaSubsession.hh include/AC3AudioFileServerMediaSubsession.hh include/MPEG2TransportUDPServerMediaSubsession.hh include/MatroskaFileServerDemux.hh include/OggFileServerDemux.hh include/ProxyServerMediaSession.hh
 
 clean:
 	-rm -rf *.$(OBJ) $(ALL) core *.core *~ include/*~
@@ -379,7 +395,7 @@ install1: $(LIVEMEDIA_LIB)
 	 install -m 644 include/*.hh $(DESTDIR)$(PREFIX)/include/liveMedia
 	 install -m 644 $(LIVEMEDIA_LIB) $(DESTDIR)$(LIBDIR)
 install_shared_libraries: $(LIVEMEDIA_LIB)
-	 ln -s $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).$(SHORT_LIB_SUFFIX)
-	 ln -s $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).so
+	 ln -fs $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).$(SHORT_LIB_SUFFIX)
+	 ln -fs $(NAME).$(LIB_SUFFIX) $(DESTDIR)$(LIBDIR)/$(NAME).so
 
 ##### Any additional, platform-specific rules come here:
diff --git a/liveMedia/MatroskaDemuxedTrack.cpp b/liveMedia/MatroskaDemuxedTrack.cpp
index 193bcf4..608acd1 100644
--- a/liveMedia/MatroskaDemuxedTrack.cpp
+++ b/liveMedia/MatroskaDemuxedTrack.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A media track, demultiplexed from a Matroska file
 // Implementation
 
@@ -27,7 +27,8 @@ void MatroskaDemuxedTrack::seekToTime(double& seekNPT) {
 
 MatroskaDemuxedTrack::MatroskaDemuxedTrack(UsageEnvironment& env, unsigned trackNumber, MatroskaDemux& sourceDemux)
   : FramedSource(env),
-    fOurTrackNumber(trackNumber), fOurSourceDemux(sourceDemux), fDurationImbalance(0) {
+    fOurTrackNumber(trackNumber), fOurSourceDemux(sourceDemux), fDurationImbalance(0),
+    fOpusTrackNumber(0) {
   fPrevPresentationTime.tv_sec = 0; fPrevPresentationTime.tv_usec = 0;
 }
 
@@ -41,6 +42,6 @@ void MatroskaDemuxedTrack::doGetNextFrame() {
 
 char const* MatroskaDemuxedTrack::MIMEtype() const {
   MatroskaTrack* track = fOurSourceDemux.fOurFile.lookup(fOurTrackNumber);
-  if (track == NULL) return NULL; // shouldn't happen
+  if (track == NULL) return "(unknown)"; // shouldn't happen
   return track->mimeType;
 }
diff --git a/liveMedia/MatroskaDemuxedTrack.hh b/liveMedia/MatroskaDemuxedTrack.hh
index 90fa3e9..649dfc1 100644
--- a/liveMedia/MatroskaDemuxedTrack.hh
+++ b/liveMedia/MatroskaDemuxedTrack.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A media track, demultiplexed from a Matroska file
 // C++ header
 
@@ -58,6 +58,7 @@ private:
   MatroskaDemux& fOurSourceDemux;
   struct timeval fPrevPresentationTime;
   int fDurationImbalance;
+  unsigned fOpusTrackNumber; // hack for Opus audio
 };
 
 #endif
diff --git a/liveMedia/MatroskaFile.cpp b/liveMedia/MatroskaFile.cpp
index c4ec667..5a4c912 100644
--- a/liveMedia/MatroskaFile.cpp
+++ b/liveMedia/MatroskaFile.cpp
@@ -14,13 +14,26 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class that encapsulates a Matroska file.
 // Implementation
 
 #include "MatroskaFileParser.hh"
 #include "MatroskaDemuxedTrack.hh"
 #include <ByteStreamFileSource.hh>
+#include <H264VideoStreamDiscreteFramer.hh>
+#include <H265VideoStreamDiscreteFramer.hh>
+#include <MPEG1or2AudioRTPSink.hh>
+#include <MPEG4GenericRTPSink.hh>
+#include <AC3AudioRTPSink.hh>
+#include <SimpleRTPSink.hh>
+#include <VorbisAudioRTPSink.hh>
+#include <H264VideoRTPSink.hh>
+#include <H265VideoRTPSink.hh>
+#include <VP8VideoRTPSink.hh>
+#include <VP9VideoRTPSink.hh>
+#include <TheoraVideoRTPSink.hh>
+#include <T140TextRTPSink.hh>
 
 ////////// CuePoint definition //////////
 
@@ -156,7 +169,7 @@ void MatroskaFile::handleEndOfTrackHeaderParsing() {
     MatroskaTrackTable::Iterator iter(*fTrackTable);
     MatroskaTrack* track;
     while ((track = iter.next()) != NULL) {
-      if (!track->isEnabled || track->trackType == 0 || track->codecID == NULL) continue; // track not enabled, or not fully-defined
+      if (!track->isEnabled || track->trackType == 0 || track->mimeType[0] == '\0') continue; // track not enabled, or not fully-defined
 
       trackChoice[numEnabledTracks].trackNumber = track->trackNumber;
       trackChoice[numEnabledTracks].trackType = track->trackType;
@@ -231,6 +244,355 @@ float MatroskaFile::fileDuration() {
   return segmentDuration()*(timecodeScale()/1000000000.0f);
 }
 
+FramedSource* MatroskaFile
+::createSourceForStreaming(FramedSource* baseSource, unsigned trackNumber,
+			   unsigned& estBitrate, unsigned& numFiltersInFrontOfTrack) {
+  if (baseSource == NULL) return NULL;
+
+  FramedSource* result = baseSource; // by default
+  estBitrate = 100; // by default
+  numFiltersInFrontOfTrack = 0; // by default
+
+  // Look at the track's MIME type to set its estimated bitrate (for use by RTCP).
+  // (Later, try to be smarter about figuring out the bitrate.) #####
+  // Some MIME types also require adding a special 'framer' in front of the source.
+  MatroskaTrack* track = lookup(trackNumber);
+  if (track != NULL) { // should always be true
+    if (strcmp(track->mimeType, "audio/MPEG") == 0) {
+      estBitrate = 128;
+    } else if (strcmp(track->mimeType, "audio/AAC") == 0) {
+      estBitrate = 96;
+    } else if (strcmp(track->mimeType, "audio/AC3") == 0) {
+      estBitrate = 48;
+    } else if (strcmp(track->mimeType, "audio/VORBIS") == 0) {
+      estBitrate = 96;
+    } else if (strcmp(track->mimeType, "video/H264") == 0) {
+      estBitrate = 500;
+      // Allow for the possibility of very large NAL units being fed to the sink object:
+      OutPacketBuffer::increaseMaxSizeTo(300000); // bytes
+
+      // Add a framer in front of the source:
+      result = H264VideoStreamDiscreteFramer::createNew(envir(), result);
+      ++numFiltersInFrontOfTrack;
+    } else if (strcmp(track->mimeType, "video/H265") == 0) {
+      estBitrate = 500;
+      // Allow for the possibility of very large NAL units being fed to the sink object:
+      OutPacketBuffer::increaseMaxSizeTo(300000); // bytes
+
+      // Add a framer in front of the source:
+      result = H265VideoStreamDiscreteFramer::createNew(envir(), result);
+      ++numFiltersInFrontOfTrack;
+    } else if (strcmp(track->mimeType, "video/VP8") == 0) {
+      estBitrate = 500;
+    } else if (strcmp(track->mimeType, "video/VP9") == 0) {
+      estBitrate = 500;
+    } else if (strcmp(track->mimeType, "video/THEORA") == 0) {
+      estBitrate = 500;
+    } else if (strcmp(track->mimeType, "text/T140") == 0) {
+      estBitrate = 48;
+    }
+  }
+
+  return result;
+}
+
+#define getPrivByte(b) if (n == 0) break; else do {b = *p++; --n;} while (0) /* Vorbis/Theora configuration header parsing */
+#define CHECK_PTR if (ptr >= limit) break /* H.264/H.265 parsing */
+#define NUM_BYTES_REMAINING (unsigned)(limit - ptr) /* H.264/H.265 parsing */
+
+RTPSink* MatroskaFile
+::createRTPSinkForTrackNumber(unsigned trackNumber, Groupsock* rtpGroupsock,
+			      unsigned char rtpPayloadTypeIfDynamic) {
+  RTPSink* result = NULL; // default value, if an error occurs
+
+  do {
+    MatroskaTrack* track = lookup(trackNumber);
+    if (track == NULL) break;
+
+    if (strcmp(track->mimeType, "audio/MPEG") == 0) {
+      result = MPEG1or2AudioRTPSink::createNew(envir(), rtpGroupsock);
+    } else if (strcmp(track->mimeType, "audio/AAC") == 0) {
+      // The Matroska file's 'Codec Private' data is assumed to be the AAC configuration
+      // information.  Use this to generate a hexadecimal 'config' string for the new RTP sink:
+      char* configStr = new char[2*track->codecPrivateSize + 1]; if (configStr == NULL) break;
+          // 2 hex digits per byte, plus the trailing '\0'
+      for (unsigned i = 0; i < track->codecPrivateSize; ++i) {
+	sprintf(&configStr[2*i], "%02X", track->codecPrivate[i]);
+      }
+
+      result = MPEG4GenericRTPSink::createNew(envir(), rtpGroupsock,
+					      rtpPayloadTypeIfDynamic,
+					      track->samplingFrequency,
+					      "audio", "AAC-hbr", configStr,
+					      track->numChannels);
+      delete[] configStr;
+    } else if (strcmp(track->mimeType, "audio/AC3") == 0) {
+      result = AC3AudioRTPSink
+	::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, track->samplingFrequency);
+    } else if (strcmp(track->mimeType, "audio/OPUS") == 0) {
+      result = SimpleRTPSink
+	::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+		    48000, "audio", "OPUS", 2, False/*only 1 Opus 'packet' in each RTP packet*/);
+    } else if (strcmp(track->mimeType, "audio/VORBIS") == 0 || strcmp(track->mimeType, "video/THEORA") == 0) {
+      // The Matroska file's 'Codec Private' data is assumed to be the codec configuration
+      // information, containing the "Identification", "Comment", and "Setup" headers.
+      // Extract these headers now:
+      u_int8_t* identificationHeader = NULL; unsigned identificationHeaderSize = 0;
+      u_int8_t* commentHeader = NULL; unsigned commentHeaderSize = 0;
+      u_int8_t* setupHeader = NULL; unsigned setupHeaderSize = 0;
+      Boolean isTheora = strcmp(track->mimeType, "video/THEORA") == 0; // otherwise, Vorbis
+
+      do {
+	u_int8_t* p = track->codecPrivate;
+	unsigned n = track->codecPrivateSize;
+	if (n == 0 || p == NULL) break; // we have no 'Codec Private' data
+
+	u_int8_t numHeaders;
+	getPrivByte(numHeaders);
+	unsigned headerSize[3]; // we don't handle any more than 2+1 headers
+
+	// Extract the sizes of each of these headers:
+	unsigned sizesSum = 0;
+	Boolean success = True;
+	unsigned i;
+	for (i = 0; i < numHeaders && i < 3; ++i) {
+	  unsigned len = 0;
+	  u_int8_t c;
+
+	  do {
+	    success = False;
+	    getPrivByte(c);
+	    success = True;
+	    
+	    len += c;
+	  } while (c == 255);
+	  if (!success || len == 0) break;
+	  
+	  headerSize[i] = len;
+	  sizesSum += len;
+	}
+	if (!success) break;
+	
+	// Compute the implicit size of the final header:
+	if (numHeaders < 3) {
+	  int finalHeaderSize = n - sizesSum;
+	  if (finalHeaderSize <= 0) break; // error in data; give up
+	  
+	  headerSize[numHeaders] = (unsigned)finalHeaderSize;
+	  ++numHeaders; // include the final header now
+	} else {
+	  numHeaders = 3; // The maximum number of headers that we handle
+	}
+	
+	// Then, extract and classify each header:
+	for (i = 0; i < numHeaders; ++i) {
+	  success = False;
+	  unsigned newHeaderSize = headerSize[i];
+	  u_int8_t* newHeader = new u_int8_t[newHeaderSize];
+	  if (newHeader == NULL) break;
+	  
+	  u_int8_t* hdr = newHeader;
+	  while (newHeaderSize-- > 0) {
+	    success = False;
+	    getPrivByte(*hdr++);
+	    success = True;
+	  }
+	  if (!success) {
+	    delete[] newHeader;
+	    break;
+	  }
+	  
+	  u_int8_t headerType = newHeader[0];
+	  if (headerType == 1 || (isTheora && headerType == 0x80)) { // "identification" header
+	    delete[] identificationHeader; identificationHeader = newHeader;
+	    identificationHeaderSize = headerSize[i];
+	  } else if (headerType == 3 || (isTheora && headerType == 0x81)) { // "comment" header
+	    delete[] commentHeader; commentHeader = newHeader;
+	    commentHeaderSize = headerSize[i];
+	  } else if (headerType == 5 || (isTheora && headerType == 0x82)) { // "setup" header
+	    delete[] setupHeader; setupHeader = newHeader;
+	    setupHeaderSize = headerSize[i];
+	  } else {
+	    delete[] newHeader; // because it was a header type that we don't understand
+	  }
+	}
+	if (!success) break;
+
+	if (isTheora) {
+	  result = TheoraVideoRTPSink
+	    ::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+			identificationHeader, identificationHeaderSize,
+			commentHeader, commentHeaderSize,
+			setupHeader, setupHeaderSize);
+	} else { // Vorbis
+	  result = VorbisAudioRTPSink
+	    ::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+			track->samplingFrequency, track->numChannels,
+			identificationHeader, identificationHeaderSize,
+			commentHeader, commentHeaderSize,
+			setupHeader, setupHeaderSize);
+	}
+      } while (0);
+
+      delete[] identificationHeader; delete[] commentHeader; delete[] setupHeader;
+    } else if (strcmp(track->mimeType, "video/H264") == 0) {
+      // Use our track's 'Codec Private' data: Bytes 5 and beyond contain SPS and PPSs:
+      u_int8_t* SPS = NULL; unsigned SPSSize = 0;
+      u_int8_t* PPS = NULL; unsigned PPSSize = 0;
+      u_int8_t* SPSandPPSBytes = NULL; unsigned numSPSandPPSBytes = 0;
+
+      do {
+	if (track->codecPrivateSize < 6) break;
+
+	numSPSandPPSBytes = track->codecPrivateSize - 5;
+	SPSandPPSBytes = &track->codecPrivate[5];
+
+	// Extract, from "SPSandPPSBytes", one SPS NAL unit, and one PPS NAL unit.
+	// (I hope one is all we need of each.)
+	unsigned i;
+	u_int8_t* ptr = SPSandPPSBytes;
+	u_int8_t* limit = &SPSandPPSBytes[numSPSandPPSBytes];
+	
+	unsigned numSPSs = (*ptr++)&0x1F; CHECK_PTR;
+	for (i = 0; i < numSPSs; ++i) {
+	  unsigned spsSize = (*ptr++)<<8; CHECK_PTR;
+	  spsSize |= *ptr++; CHECK_PTR;
+	  
+	  if (spsSize > NUM_BYTES_REMAINING) break;
+	  u_int8_t nal_unit_type = ptr[0]&0x1F;
+	  if (SPS == NULL && nal_unit_type == 7/*sanity check*/) { // save the first one
+	    SPSSize = spsSize;
+	    SPS = new u_int8_t[spsSize];
+	    memmove(SPS, ptr, spsSize);
+	  }
+	  ptr += spsSize;
+	}
+	
+	unsigned numPPSs = (*ptr++)&0x1F; CHECK_PTR;
+	for (i = 0; i < numPPSs; ++i) {
+	  unsigned ppsSize = (*ptr++)<<8; CHECK_PTR;
+	  ppsSize |= *ptr++; CHECK_PTR;
+	  
+	  if (ppsSize > NUM_BYTES_REMAINING) break;
+	  u_int8_t nal_unit_type = ptr[0]&0x1F;
+	  if (PPS == NULL && nal_unit_type == 8/*sanity check*/) { // save the first one
+	    PPSSize = ppsSize;
+	    PPS = new u_int8_t[ppsSize];
+	    memmove(PPS, ptr, ppsSize);
+	  }
+	  ptr += ppsSize;
+	}
+      } while (0);
+
+      result = H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+					   SPS, SPSSize, PPS, PPSSize);
+
+      delete[] SPS; delete[] PPS;
+    } else if (strcmp(track->mimeType, "video/H265") == 0) {
+      u_int8_t* VPS = NULL; unsigned VPSSize = 0;
+      u_int8_t* SPS = NULL; unsigned SPSSize = 0;
+      u_int8_t* PPS = NULL; unsigned PPSSize = 0;
+      u_int8_t* VPS_SPS_PPSBytes = NULL; unsigned numVPS_SPS_PPSBytes = 0;
+      unsigned i;
+
+      do {
+	if (track->codecPrivateUsesH264FormatForH265) {
+	  // The data uses the H.264-style format (but including VPS NAL unit(s)).
+	  // The VPS,SPS,PPS NAL unit information starts at byte #5:
+	  if (track->codecPrivateSize >= 6) {
+	    numVPS_SPS_PPSBytes = track->codecPrivateSize - 5;
+	    VPS_SPS_PPSBytes = &track->codecPrivate[5];
+	  }
+	} else {
+	  // The data uses the proper H.265-style format.
+	  // The VPS,SPS,PPS NAL unit information starts at byte #22:
+	  if (track->codecPrivateSize >= 23) {
+	    numVPS_SPS_PPSBytes = track->codecPrivateSize - 22;
+	    VPS_SPS_PPSBytes = &track->codecPrivate[22];
+	  }
+	}
+	
+	// Extract, from "VPS_SPS_PPSBytes", one VPS NAL unit, one SPS NAL unit, and one PPS NAL unit.
+	// (I hope one is all we need of each.)
+	if (numVPS_SPS_PPSBytes == 0 || VPS_SPS_PPSBytes == NULL) break; // sanity check
+	u_int8_t* ptr = VPS_SPS_PPSBytes;
+	u_int8_t* limit = &VPS_SPS_PPSBytes[numVPS_SPS_PPSBytes];
+	
+	if (track->codecPrivateUsesH264FormatForH265) {
+	  // The data uses the H.264-style format (but including VPS NAL unit(s)).
+	  while (NUM_BYTES_REMAINING > 0) {
+	    unsigned numNALUnits = (*ptr++)&0x1F; CHECK_PTR;
+	    for (i = 0; i < numNALUnits; ++i) {
+	      unsigned nalUnitLength = (*ptr++)<<8; CHECK_PTR;
+	      nalUnitLength |= *ptr++; CHECK_PTR;
+	      
+	      if (nalUnitLength > NUM_BYTES_REMAINING) break;
+	      u_int8_t nal_unit_type = (ptr[0]&0x7E)>>1;
+	      if (nal_unit_type == 32) { // VPS
+		VPSSize = nalUnitLength;
+		delete[] VPS; VPS = new u_int8_t[nalUnitLength];
+		memmove(VPS, ptr, nalUnitLength);
+	      } else if (nal_unit_type == 33) { // SPS
+		SPSSize = nalUnitLength;
+		delete[] SPS; SPS = new u_int8_t[nalUnitLength];
+		memmove(SPS, ptr, nalUnitLength);
+	      } else if (nal_unit_type == 34) { // PPS
+		PPSSize = nalUnitLength;
+		delete[] PPS; PPS = new u_int8_t[nalUnitLength];
+		memmove(PPS, ptr, nalUnitLength);
+	      }
+	      ptr += nalUnitLength;
+	    }
+	  }
+	} else {
+	  // The data uses the proper H.265-style format.
+	  unsigned numOfArrays = *ptr++; CHECK_PTR;
+	  for (unsigned j = 0; j < numOfArrays; ++j) {
+	    ++ptr; CHECK_PTR; // skip the 'array_completeness'|'reserved'|'NAL_unit_type' byte
+	    
+	    unsigned numNalus = (*ptr++)<<8; CHECK_PTR;
+	    numNalus |= *ptr++; CHECK_PTR;
+	    
+	    for (i = 0; i < numNalus; ++i) {
+	      unsigned nalUnitLength = (*ptr++)<<8; CHECK_PTR;
+	      nalUnitLength |= *ptr++; CHECK_PTR;
+	      
+	      if (nalUnitLength > NUM_BYTES_REMAINING) break;
+	      u_int8_t nal_unit_type = (ptr[0]&0x7E)>>1;
+	      if (nal_unit_type == 32) { // VPS
+		VPSSize = nalUnitLength;
+		delete[] VPS; VPS = new u_int8_t[nalUnitLength];
+		memmove(VPS, ptr, nalUnitLength);
+	      } else if (nal_unit_type == 33) { // SPS
+		SPSSize = nalUnitLength;
+		delete[] SPS; SPS = new u_int8_t[nalUnitLength];
+		memmove(SPS, ptr, nalUnitLength);
+	      } else if (nal_unit_type == 34) { // PPS
+		PPSSize = nalUnitLength;
+		delete[] PPS; PPS = new u_int8_t[nalUnitLength];
+		memmove(PPS, ptr, nalUnitLength);
+	      }
+	      ptr += nalUnitLength;
+	    }
+	  }
+	}
+      } while (0);
+
+      result = H265VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+					   VPS, VPSSize, SPS, SPSSize, PPS, PPSSize);
+      delete[] VPS; delete[] SPS; delete[] PPS;
+    } else if (strcmp(track->mimeType, "video/VP8") == 0) {
+      result = VP8VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
+    } else if (strcmp(track->mimeType, "video/VP9") == 0) {
+      result = VP9VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
+    } else if (strcmp(track->mimeType, "text/T140") == 0) {
+      result = T140TextRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
+    }
+  } while (0);
+
+  return result;
+}
+
 void MatroskaFile::addTrack(MatroskaTrack* newTrack, unsigned trackNumber) {
   fTrackTable->add(newTrack, trackNumber);
 }
@@ -301,7 +663,8 @@ MatroskaTrack::MatroskaTrack()
     defaultDuration(0),
     name(NULL), language(NULL), codecID(NULL),
     samplingFrequency(0), numChannels(2), mimeType(""),
-    codecPrivateSize(0), codecPrivate(NULL), codecPrivateUsesH264FormatForH265(False),
+    codecPrivateSize(0), codecPrivate(NULL),
+    codecPrivateUsesH264FormatForH265(False), codecIsOpus(False),
     headerStrippedBytesSize(0), headerStrippedBytes(NULL),
     subframeSizeSize(0) {
 }
@@ -359,9 +722,9 @@ FramedSource* MatroskaDemux::newDemuxedTrack(unsigned& resultTrackNumber) {
 FramedSource* MatroskaDemux::newDemuxedTrackByTrackNumber(unsigned trackNumber) {
   if (trackNumber == 0) return NULL;
 
-  FramedSource* track = new MatroskaDemuxedTrack(envir(), trackNumber, *this);
-  fDemuxedTracksTable->Add((char const*)trackNumber, track);
-  return track;
+  FramedSource* trackSource = new MatroskaDemuxedTrack(envir(), trackNumber, *this);
+  fDemuxedTracksTable->Add((char const*)trackNumber, trackSource);
+  return trackSource;
 }
 
 MatroskaDemuxedTrack* MatroskaDemux::lookupDemuxedTrack(unsigned trackNumber) {
@@ -372,7 +735,7 @@ void MatroskaDemux::removeTrack(unsigned trackNumber) {
   fDemuxedTracksTable->Remove((char const*)trackNumber);
   if (fDemuxedTracksTable->numEntries() == 0) {
     // We no longer have any demuxed tracks, so delete ourselves now:
-    delete this;
+    Medium::close(this);
   }
 }
 
@@ -407,7 +770,7 @@ void MatroskaDemux::handleEndOfFile() {
 
   for (i = 0; i < numTracks; ++i) {
     if (tracks[i] == NULL) continue; // sanity check; shouldn't happen
-    FramedSource::handleClosure(tracks[i]);
+    tracks[i]->handleClosure();
   }
 
   delete[] tracks;
@@ -426,10 +789,6 @@ CuePoint::~CuePoint() {
   delete fSubTree[0]; delete fSubTree[1];
 }
 
-#ifndef ABS
-#define ABS(x) (x)<0 ? -(x) : (x)
-#endif
-
 void CuePoint::addCuePoint(CuePoint*& root, double cueTime, u_int64_t clusterOffsetInFile, unsigned blockNumWithinCluster,
 			   Boolean& needToReviseBalanceOfParent) {
   needToReviseBalanceOfParent = False; // by default; may get changed below
diff --git a/liveMedia/MatroskaFileParser.cpp b/liveMedia/MatroskaFileParser.cpp
index 9e0f666..83b9151 100644
--- a/liveMedia/MatroskaFileParser.cpp
+++ b/liveMedia/MatroskaFileParser.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A parser for a Matroska file.
 // Implementation
 
@@ -100,7 +100,7 @@ void MatroskaFileParser::continueParsing() {
     }
   }
 
-  // We successfully parsed the file's 'Track' headers.  Call our 'done' function now:
+  // We successfully parsed the file.  Call our 'done' function now:
   if (fOnEndFunc != NULL) (*fOnEndFunc)(fOnEndClientData);
 }
 
@@ -433,6 +433,32 @@ Boolean MatroskaFileParser::parseTrack() {
 #endif
 	  if (track != NULL) {
 	    delete[] track->codecID; track->codecID = codecID;
+
+	    // Also set the track's "mimeType" field, if we can deduce it from the "codecID":
+	    if (strncmp(codecID, "A_MPEG", 6) == 0) {
+	      track->mimeType = "audio/MPEG";
+	    } else if (strncmp(codecID, "A_AAC", 5) == 0) {
+	      track->mimeType = "audio/AAC";
+	    } else if (strncmp(codecID, "A_AC3", 5) == 0) {
+	      track->mimeType = "audio/AC3";
+	    } else if (strncmp(codecID, "A_VORBIS", 8) == 0) {
+	      track->mimeType = "audio/VORBIS";
+	    } else if (strcmp(codecID, "A_OPUS") == 0) {
+	      track->mimeType = "audio/OPUS";
+	      track->codecIsOpus = True;
+	    } else if (strcmp(codecID, "V_MPEG4/ISO/AVC") == 0) {
+	      track->mimeType = "video/H264";
+	    } else if (strcmp(codecID, "V_MPEGH/ISO/HEVC") == 0) {
+	      track->mimeType = "video/H265";
+	    } else if (strncmp(codecID, "V_VP8", 5) == 0) {
+	      track->mimeType = "video/VP8";
+	    } else if (strncmp(codecID, "V_VP9", 5) == 0) {
+	      track->mimeType = "video/VP9";
+	    } else if (strncmp(codecID, "V_THEORA", 8) == 0) {
+	      track->mimeType = "video/THEORA";
+	    } else if (strncmp(codecID, "S_TEXT", 6) == 0) {
+	      track->mimeType = "text/T140";
+	    }
 	  } else {
 	    delete[] codecID;
 	  }
@@ -458,7 +484,7 @@ Boolean MatroskaFileParser::parseTrack() {
 	    if (track->codecID != NULL) {
 	      if (strcmp(track->codecID, "V_MPEG4/ISO/AVC") == 0) { // H.264
 		// Byte 4 of the 'codec private' data contains 'lengthSizeMinusOne':
-		if (codecPrivateSize >= 5) track->subframeSizeSize = (codecPrivate[4])&0x3 + 1;
+		if (codecPrivateSize >= 5) track->subframeSizeSize = (codecPrivate[4]&0x3) + 1;
 	      } else if (strcmp(track->codecID, "V_MPEGH/ISO/HEVC") == 0) { // H.265
 		// H.265 'codec private' data is *supposed* to use the format that's described in
 		// http://lists.matroska.org/pipermail/matroska-devel/2013-September/004567.html
@@ -472,13 +498,13 @@ Boolean MatroskaFileParser::parseTrack() {
 		  track->codecPrivateUsesH264FormatForH265 = True;
 		  
 		  // Byte 4 of the 'codec private' data contains 'lengthSizeMinusOne':
-		  if (codecPrivateSize >= 5) track->subframeSizeSize = (codecPrivate[4])&0x3 + 1;
+		  if (codecPrivateSize >= 5) track->subframeSizeSize = (codecPrivate[4]&0x3) + 1;
 		} else {
 		  // This looks like the 'correct' format:
 		  track->codecPrivateUsesH264FormatForH265 = False;
 
 		  // Byte 21 of the 'codec private' data contains 'lengthSizeMinusOne':
-		  track->subframeSizeSize = (codecPrivate[21])&0x3 + 1;
+		  track->subframeSizeSize = (codecPrivate[21]&0x3) + 1;
 		}
 	      }
 	    }
@@ -990,22 +1016,40 @@ Boolean MatroskaFileParser::deliverFrameWithinBlock() {
       return False;
     }
 
-    unsigned frameSize = fFrameSizesWithinBlock[fNextFrameNumberToDeliver];
-    if (track->haveSubframes()) {
-      // The next "track->subframeSizeSize" bytes contain the length of a 'subframe':
-      if (fCurOffsetWithinFrame + track->subframeSizeSize > frameSize) break; // sanity check
-      unsigned subframeSize = 0;
-      for (unsigned i = 0; i < track->subframeSizeSize; ++i) {
-	u_int8_t c;
-	getCommonFrameBytes(track, &c, 1, 0);
-	if (fCurFrameNumBytesToGet > 0) { // it'll be 1
-	  c = get1Byte();
-	  ++fCurOffsetWithinFrame;
+    unsigned frameSize;
+    u_int8_t const* specialFrameSource = NULL;
+    u_int8_t const opusCommentHeader[16]
+      = {'O','p','u','s','T','a','g','s', 0, 0, 0, 0, 0, 0, 0, 0};
+    if (track->codecIsOpus && demuxedTrack->fOpusTrackNumber < 2) {
+      // Special case for Opus audio.  The first frame (the 'configuration' header) comes from
+      // the 'private data'.  The second frame (the 'comment' header) comes is synthesized by
+      // us here:
+      if (demuxedTrack->fOpusTrackNumber == 0) {
+	specialFrameSource = track->codecPrivate;
+	frameSize = track->codecPrivateSize;
+      } else { // demuxedTrack->fOpusTrackNumber == 1
+	specialFrameSource = opusCommentHeader;
+	frameSize = sizeof opusCommentHeader;
+      }
+      ++demuxedTrack->fOpusTrackNumber;
+    } else {
+      frameSize = fFrameSizesWithinBlock[fNextFrameNumberToDeliver];
+      if (track->haveSubframes()) {
+	// The next "track->subframeSizeSize" bytes contain the length of a 'subframe':
+	if (fCurOffsetWithinFrame + track->subframeSizeSize > frameSize) break; // sanity check
+	unsigned subframeSize = 0;
+	for (unsigned i = 0; i < track->subframeSizeSize; ++i) {
+	  u_int8_t c;
+	  getCommonFrameBytes(track, &c, 1, 0);
+	  if (fCurFrameNumBytesToGet > 0) { // it'll be 1
+	    c = get1Byte();
+	    ++fCurOffsetWithinFrame;
+	  }
+	  subframeSize = subframeSize*256 + c;
 	}
-	subframeSize = subframeSize*256 + c;
+	if (subframeSize == 0 || fCurOffsetWithinFrame + subframeSize > frameSize) break; // sanity check
+	frameSize = subframeSize;
       }
-      if (subframeSize == 0 || fCurOffsetWithinFrame + subframeSize > frameSize) break; // sanity check
-      frameSize = subframeSize;
     }
 
     // Compute the presentation time of this frame (from the cluster timecode, the block timecode, and the default duration):
@@ -1023,12 +1067,17 @@ Boolean MatroskaFileParser::deliverFrameWithinBlock() {
     struct timeval presentationTime;
     presentationTime.tv_sec = (unsigned)pt;
     presentationTime.tv_usec = (unsigned)((pt - presentationTime.tv_sec)*1000000);
-    unsigned durationInMicroseconds = track->defaultDuration/1000;
-    if (track->haveSubframes()) {
-      // If this is a 'subframe', use a duration of 0 instead (unless it's the last 'subframe'):
-      if (fCurOffsetWithinFrame + frameSize + track->subframeSizeSize < fFrameSizesWithinBlock[fNextFrameNumberToDeliver]) {
-	// There's room for at least one more subframe after this, so give this subframe a duration of 0
-	durationInMicroseconds = 0;
+    unsigned durationInMicroseconds;
+    if (specialFrameSource != NULL) {
+      durationInMicroseconds = 0;
+    } else { // normal case
+      durationInMicroseconds = track->defaultDuration/1000;
+      if (track->haveSubframes()) {
+	// If this is a 'subframe', use a duration of 0 instead (unless it's the last 'subframe'):
+	if (fCurOffsetWithinFrame + frameSize + track->subframeSizeSize < fFrameSizesWithinBlock[fNextFrameNumberToDeliver]) {
+	  // There's room for at least one more subframe after this, so give this subframe a duration of 0
+	  durationInMicroseconds = 0;
+	}
       }
     }
 
@@ -1069,8 +1118,19 @@ Boolean MatroskaFileParser::deliverFrameWithinBlock() {
     getCommonFrameBytes(track, demuxedTrack->to(), demuxedTrack->frameSize(), demuxedTrack->numTruncatedBytes());
 
     // Next, deliver (and/or skip) bytes from the input file:
-    fCurrentParseState = DELIVERING_FRAME_BYTES;
-    setParseState();
+    if (specialFrameSource != NULL) {
+      memmove(demuxedTrack->to(), specialFrameSource, demuxedTrack->frameSize());
+#ifdef DEBUG
+      fprintf(stderr, "\tdelivered special frame: %d bytes", demuxedTrack->frameSize());
+      if (demuxedTrack->numTruncatedBytes() > 0) fprintf(stderr, " (%d bytes truncated)", demuxedTrack->numTruncatedBytes());
+      fprintf(stderr, " @%u.%06u (%.06f from start); duration %u us\n", demuxedTrack->presentationTime().tv_sec, demuxedTrack->presentationTime().tv_usec, demuxedTrack->presentationTime().tv_sec+demuxedTrack->presentationTime().tv_usec/1000000.0-fPresentationTimeOffset, demuxedTrack->durationInMicroseconds());
+#endif
+      setParseState();
+      FramedSource::afterGetting(demuxedTrack); // completes delivery
+    } else { // normal case
+      fCurrentParseState = DELIVERING_FRAME_BYTES;
+      setParseState();
+    }
     return True;
   } while (0);
 
diff --git a/liveMedia/MatroskaFileParser.hh b/liveMedia/MatroskaFileParser.hh
index cab91c5..7668b88 100644
--- a/liveMedia/MatroskaFileParser.hh
+++ b/liveMedia/MatroskaFileParser.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A parser for a Matroska file.
 // C++ header
 
diff --git a/liveMedia/MatroskaFileServerDemux.cpp b/liveMedia/MatroskaFileServerDemux.cpp
index 340aadc..e94fdc2 100644
--- a/liveMedia/MatroskaFileServerDemux.cpp
+++ b/liveMedia/MatroskaFileServerDemux.cpp
@@ -14,19 +14,13 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A server demultiplexor for a Matroska file
 // Implementation
 
 #include "MatroskaFileServerDemux.hh"
 #include "MP3AudioMatroskaFileServerMediaSubsession.hh"
-#include "AACAudioMatroskaFileServerMediaSubsession.hh"
-#include "AC3AudioMatroskaFileServerMediaSubsession.hh"
-#include "VorbisAudioMatroskaFileServerMediaSubsession.hh"
-#include "H264VideoMatroskaFileServerMediaSubsession.hh"
-#include "H265VideoMatroskaFileServerMediaSubsession.hh"
-#include "VP8VideoMatroskaFileServerMediaSubsession.hh"
-#include "T140TextMatroskaFileServerMediaSubsession.hh"
+#include "MatroskaFileServerMediaSubsession.hh"
 
 void MatroskaFileServerDemux
 ::createNew(UsageEnvironment& env, char const* fileName,
@@ -65,30 +59,10 @@ ServerMediaSubsession* MatroskaFileServerDemux
 
   // Use the track's "codecID" string to figure out which "ServerMediaSubsession" subclass to use:
   ServerMediaSubsession* result = NULL;
-  if (strncmp(track->codecID, "A_MPEG", 6) == 0) {
-    track->mimeType = "audio/MPEG";
-    result = MP3AudioMatroskaFileServerMediaSubsession::createNew(*this, track->trackNumber, False, NULL);
-  } else if (strncmp(track->codecID, "A_AAC", 5) == 0) {
-    track->mimeType = "audio/AAC";
-    result = AACAudioMatroskaFileServerMediaSubsession::createNew(*this, track->trackNumber);
-  } else if (strncmp(track->codecID, "A_AC3", 5) == 0) {
-    track->mimeType = "audio/AC3";
-    result = AC3AudioMatroskaFileServerMediaSubsession::createNew(*this, track->trackNumber);
-  } else if (strncmp(track->codecID, "A_VORBIS", 8) == 0) {
-    track->mimeType = "audio/VORBIS";
-    result = VorbisAudioMatroskaFileServerMediaSubsession::createNew(*this, track->trackNumber);
-  } else if (strcmp(track->codecID, "V_MPEG4/ISO/AVC") == 0) {
-    track->mimeType = "video/H264";
-    result = H264VideoMatroskaFileServerMediaSubsession::createNew(*this, track->trackNumber);
-  } else if (strcmp(track->codecID, "V_MPEGH/ISO/HEVC") == 0) {
-    track->mimeType = "video/H265";
-    result = H265VideoMatroskaFileServerMediaSubsession::createNew(*this, track->trackNumber);
-  } else if (strncmp(track->codecID, "V_VP8", 5) == 0) {
-    track->mimeType = "video/VP8";
-    result = VP8VideoMatroskaFileServerMediaSubsession::createNew(*this, track->trackNumber);
-  } else if (strncmp(track->codecID, "S_TEXT", 6) == 0) {
-    track->mimeType = "text/T140";
-    result = T140TextMatroskaFileServerMediaSubsession::createNew(*this, track->trackNumber);
+  if (strcmp(track->mimeType, "audio/MPEG") == 0) {
+    result = MP3AudioMatroskaFileServerMediaSubsession::createNew(*this, track);
+  } else {
+    result = MatroskaFileServerMediaSubsession::createNew(*this, track);
   }
 
   if (result != NULL) {
diff --git a/liveMedia/MatroskaFileServerMediaSubsession.cpp b/liveMedia/MatroskaFileServerMediaSubsession.cpp
new file mode 100644
index 0000000..c4bc1de
--- /dev/null
+++ b/liveMedia/MatroskaFileServerMediaSubsession.cpp
@@ -0,0 +1,65 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
+// on demand, from a track within a Matroska file.
+// Implementation
+
+#include "MatroskaFileServerMediaSubsession.hh"
+#include "MatroskaDemuxedTrack.hh"
+#include "FramedFilter.hh"
+
+MatroskaFileServerMediaSubsession* MatroskaFileServerMediaSubsession
+::createNew(MatroskaFileServerDemux& demux, MatroskaTrack* track) {
+  return new MatroskaFileServerMediaSubsession(demux, track);
+}
+
+MatroskaFileServerMediaSubsession
+::MatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, MatroskaTrack* track)
+  : FileServerMediaSubsession(demux.envir(), demux.fileName(), False),
+    fOurDemux(demux), fTrack(track), fNumFiltersInFrontOfTrack(0) {
+}
+
+MatroskaFileServerMediaSubsession::~MatroskaFileServerMediaSubsession() {
+}
+
+float MatroskaFileServerMediaSubsession::duration() const { return fOurDemux.fileDuration(); }
+
+void MatroskaFileServerMediaSubsession
+::seekStreamSource(FramedSource* inputSource, double& seekNPT, double /*streamDuration*/, u_int64_t& /*numBytes*/) {
+  for (unsigned i = 0; i < fNumFiltersInFrontOfTrack; ++i) {
+    // "inputSource" is a filter.  Go back to *its* source:
+    inputSource = ((FramedFilter*)inputSource)->inputSource();
+  }
+  ((MatroskaDemuxedTrack*)inputSource)->seekToTime(seekNPT);
+}
+
+FramedSource* MatroskaFileServerMediaSubsession
+::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
+  FramedSource* baseSource = fOurDemux.newDemuxedTrack(clientSessionId, fTrack->trackNumber);
+  if (baseSource == NULL) return NULL;
+  
+  return fOurDemux.ourMatroskaFile()
+    ->createSourceForStreaming(baseSource, fTrack->trackNumber,
+			       estBitrate, fNumFiltersInFrontOfTrack);
+}
+
+RTPSink* MatroskaFileServerMediaSubsession
+::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) {
+  return fOurDemux.ourMatroskaFile()
+    ->createRTPSinkForTrackNumber(fTrack->trackNumber, rtpGroupsock, rtpPayloadTypeIfDynamic);
+}
diff --git a/liveMedia/AC3AudioMatroskaFileServerMediaSubsession.hh b/liveMedia/MatroskaFileServerMediaSubsession.hh
similarity index 67%
rename from liveMedia/AC3AudioMatroskaFileServerMediaSubsession.hh
rename to liveMedia/MatroskaFileServerMediaSubsession.hh
index be9835b..43aaada 100644
--- a/liveMedia/AC3AudioMatroskaFileServerMediaSubsession.hh
+++ b/liveMedia/MatroskaFileServerMediaSubsession.hh
@@ -14,13 +14,13 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an AC3 audio track within a Matroska file.
+// on demand, from a track within a Matroska file.
 // C++ header
 
-#ifndef _AC3_AUDIO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-#define _AC3_AUDIO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
+#ifndef _MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
+#define _MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
 
 #ifndef _FILE_SERVER_MEDIA_SUBSESSION_HH
 #include "FileServerMediaSubsession.hh"
@@ -29,26 +29,27 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "MatroskaFileServerDemux.hh"
 #endif
 
-class AC3AudioMatroskaFileServerMediaSubsession: public FileServerMediaSubsession {
+class MatroskaFileServerMediaSubsession: public FileServerMediaSubsession {
 public:
-  static AC3AudioMatroskaFileServerMediaSubsession*
-  createNew(MatroskaFileServerDemux& demux, unsigned trackNumber);
+  static MatroskaFileServerMediaSubsession*
+  createNew(MatroskaFileServerDemux& demux, MatroskaTrack* track);
 
-private:
-  AC3AudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber);
-      // called only by createNew();
-  virtual ~AC3AudioMatroskaFileServerMediaSubsession();
+protected:
+  MatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, MatroskaTrack* track);
+      // called only by createNew(), or by subclass constructors
+  virtual ~MatroskaFileServerMediaSubsession();
 
-private: // redefined virtual functions
+protected: // redefined virtual functions
   virtual float duration() const;
   virtual void seekStreamSource(FramedSource* inputSource, double& seekNPT, double streamDuration, u_int64_t& numBytes);
   virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
 					      unsigned& estBitrate);
   virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
 
-private:
+protected:
   MatroskaFileServerDemux& fOurDemux;
-  unsigned fTrackNumber;
+  MatroskaTrack* fTrack;
+  unsigned fNumFiltersInFrontOfTrack;
 };
 
 #endif
diff --git a/liveMedia/Media.cpp b/liveMedia/Media.cpp
index 058e746..c0f90f0 100644
--- a/liveMedia/Media.cpp
+++ b/liveMedia/Media.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Media
 // Implementation
 
@@ -87,10 +87,6 @@ Boolean Medium::isServerMediaSession() const {
   return False; // default implementation
 }
 
-Boolean Medium::isDarwinInjector() const {
-  return False; // default implementation
-}
-
 
 ////////// _Tables implementation //////////
 
diff --git a/liveMedia/MediaSession.cpp b/liveMedia/MediaSession.cpp
index 8019233..88829a7 100644
--- a/liveMedia/MediaSession.cpp
+++ b/liveMedia/MediaSession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A data structure that represents a session that consists of
 // potentially multiple (audio and/or video) sub-sessions
 // Implementation
@@ -61,8 +61,8 @@ MediaSession::MediaSession(UsageEnvironment& env)
     fSubsessionsHead(NULL), fSubsessionsTail(NULL),
     fConnectionEndpointName(NULL),
     fMaxPlayStartTime(0.0f), fMaxPlayEndTime(0.0f), fAbsStartTime(NULL), fAbsEndTime(NULL),
-    fScale(1.0f), fMediaSessionType(NULL), fSessionName(NULL), fSessionDescription(NULL),
-    fControlPath(NULL) {
+    fScale(1.0f), fSpeed(1.0f),
+    fMediaSessionType(NULL), fSessionName(NULL), fSessionDescription(NULL), fControlPath(NULL) {
   fSourceFilterAddr.s_addr = 0;
 
   // Get our host name, and use this for the RTCP CNAME:
@@ -206,6 +206,7 @@ Boolean MediaSession::initializeWithSDP(char const* sdpDescription) {
       if (subsession->parseSDPLine_c(sdpLine)) continue;
       if (subsession->parseSDPLine_b(sdpLine)) continue;
       if (subsession->parseSDPAttribute_rtpmap(sdpLine)) continue;
+      if (subsession->parseSDPAttribute_rtcpmux(sdpLine)) continue;
       if (subsession->parseSDPAttribute_control(sdpLine)) continue;
       if (subsession->parseSDPAttribute_range(sdpLine)) continue;
       if (subsession->parseSDPAttribute_fmtp(sdpLine)) continue;
@@ -569,6 +570,27 @@ void MediaSubsessionIterator::reset() {
   fNextPtr = fOurSession.fSubsessionsHead;
 }
 
+
+////////// SDPAttribute definition //////////
+
+class SDPAttribute {
+public:
+  SDPAttribute(char const* strValue, Boolean valueIsHexadecimal);
+  virtual ~SDPAttribute();
+
+  char const* strValue() const { return fStrValue; }
+  char const* strValueToLower() const { return fStrValueToLower; }
+  int intValue() const { return fIntValue; }
+  Boolean valueIsHexadecimal() const { return fValueIsHexadecimal; }
+
+private:
+  char* fStrValue;
+  char* fStrValueToLower;
+  int fIntValue;
+  Boolean fValueIsHexadecimal;
+};
+
+
 ////////// MediaSubsession //////////
 
 MediaSubsession::MediaSubsession(MediaSession& parent)
@@ -577,22 +599,24 @@ MediaSubsession::MediaSubsession(MediaSession& parent)
     fConnectionEndpointName(NULL),
     fClientPortNum(0), fRTPPayloadFormat(0xFF),
     fSavedSDPLines(NULL), fMediumName(NULL), fCodecName(NULL), fProtocolName(NULL),
-    fRTPTimestampFrequency(0), fControlPath(NULL),
+    fRTPTimestampFrequency(0), fMultiplexRTCPWithRTP(False), fControlPath(NULL),
     fSourceFilterAddr(parent.sourceFilterAddr()), fBandwidth(0),
-    fAuxiliarydatasizelength(0), fConstantduration(0), fConstantsize(0),
-    fCRC(0), fCtsdeltalength(0), fDe_interleavebuffersize(0), fDtsdeltalength(0),
-    fIndexdeltalength(0), fIndexlength(0), fInterleaving(0), fMaxdisplacement(0),
-    fObjecttype(0), fOctetalign(0), fProfile_level_id(0), fRobustsorting(0),
-    fSizelength(0), fStreamstateindication(0), fStreamtype(0),
-    fCpresent(False), fRandomaccessindication(False),
-    fConfig(NULL), fMode(NULL), fSpropParameterSets(NULL), fEmphasis(NULL), fChannelOrder(NULL),
     fPlayStartTime(0.0), fPlayEndTime(0.0), fAbsStartTime(NULL), fAbsEndTime(NULL),
     fVideoWidth(0), fVideoHeight(0), fVideoFPS(0), fNumChannels(1), fScale(1.0f), fNPT_PTS_Offset(0.0f),
+    fAttributeTable(HashTable::create(STRING_HASH_KEYS)),
     fRTPSocket(NULL), fRTCPSocket(NULL),
     fRTPSource(NULL), fRTCPInstance(NULL), fReadSource(NULL),
     fReceiveRawMP3ADUs(False), fReceiveRawJPEGFrames(False),
     fSessionId(NULL) {
   rtpInfo.seqNum = 0; rtpInfo.timestamp = 0; rtpInfo.infoIsNew = False;
+
+  // A few attributes have unusual default values.  Set these now:
+  setAttribute("profile-level-id", "0", True/*value is hexadecimal*/); // used with "video/H264"
+    // This won't work for MPEG-4 (unless the value is <10), because for MPEG-4, the value
+    // is assumed to be a decimal string, not a hexadecimal string.  NEED TO FIX #####
+  setAttribute("profile-id", "1"); // used with "video/H265"
+  setAttribute("level-id", "93"); // used with "video/H265"
+  setAttribute("interop-constraints", "B00000000000"); // used with "video/H265"
 }
 
 MediaSubsession::~MediaSubsession() {
@@ -601,15 +625,20 @@ MediaSubsession::~MediaSubsession() {
   delete[] fConnectionEndpointName; delete[] fSavedSDPLines;
   delete[] fMediumName; delete[] fCodecName; delete[] fProtocolName;
   delete[] fControlPath;
-  delete[] fConfig; delete[] fMode; delete[] fSpropParameterSets; delete[] fEmphasis; delete[] fChannelOrder;
   delete[] fAbsStartTime; delete[] fAbsEndTime;
   delete[] fSessionId;
 
+  // Empty and delete our 'attributes table':
+  SDPAttribute* attr;
+  while ((attr = (SDPAttribute*)fAttributeTable->RemoveNext()) != NULL) {
+    delete attr;
+  }
+  delete fAttributeTable;
+
   delete fNext;
 }
 
 void MediaSubsession::addFilter(FramedFilter* filter){
-  if (filter == NULL || filter->inputSource() != fReadSource) return; // sanity check
   fReadSource = filter;
 }
 
@@ -662,7 +691,7 @@ Boolean MediaSubsession::initiate(int useSpecialRTPoffset) {
     if (fClientPortNum != 0 && (honorSDPPortChoice || IsMulticastAddress(tempAddr.s_addr))) {
       // The sockets' port numbers were specified for us.  Use these:
       Boolean const protocolIsRTP = strcmp(fProtocolName, "RTP") == 0;
-      if (protocolIsRTP) {
+      if (protocolIsRTP && !fMultiplexRTCPWithRTP) {
 	fClientPortNum = fClientPortNum&~1;
 	    // use an even-numbered port for RTP, and the next (odd-numbered) port for RTCP
       }
@@ -677,17 +706,24 @@ Boolean MediaSubsession::initiate(int useSpecialRTPoffset) {
       }
       
       if (protocolIsRTP) {
-	// Set our RTCP port to be the RTP port +1
-	portNumBits const rtcpPortNum = fClientPortNum|1;
-	if (isSSM()) {
-	  fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, rtcpPortNum);
+	if (fMultiplexRTCPWithRTP) {
+	  // Use the RTP 'groupsock' object for RTCP as well:
+	  fRTCPSocket = fRTPSocket;
 	} else {
-	  fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255);
+	  // Set our RTCP port to be the RTP port + 1:
+	  portNumBits const rtcpPortNum = fClientPortNum|1;
+	  if (isSSM()) {
+	    fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, rtcpPortNum);
+	  } else {
+	    fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255);
+	  }
 	}
       }
     } else {
       // Port numbers were not specified in advance, so we use ephemeral port numbers.
       // Create sockets until we get a port-number pair (even: RTP; even+1: RTCP).
+      // (However, if we're multiplexing RTCP with RTP, then we create only one socket,
+      // and the port number  can be even or odd.)
       // We need to make sure that we don't keep trying to use the same bad port numbers over
       // and over again, so we store bad sockets in a table, and delete them all when we're done.
       HashTable* socketHashTable = HashTable::create(ONE_WORD_HASH_KEYS);
@@ -708,12 +744,21 @@ Boolean MediaSubsession::initiate(int useSpecialRTPoffset) {
 	  break;
 	}
 
-	// Get the client port number, and check whether it's even (for RTP):
+	// Get the client port number:
 	Port clientPort(0);
 	if (!getSourcePort(env(), fRTPSocket->socketNum(), clientPort)) {
 	  break;
 	}
 	fClientPortNum = ntohs(clientPort.num()); 
+
+	if (fMultiplexRTCPWithRTP) {
+	  // Use this RTP 'groupsock' object for RTCP as well:
+	  fRTCPSocket = fRTPSocket;
+	  success = True;
+	  break;
+	}	  
+
+	// To be usable for RTP, the client port number must be even:
 	if ((fClientPortNum&1) != 0) { // it's odd
 	  // Record this socket in our table, and keep trying:
 	  unsigned key = (unsigned)fClientPortNum;
@@ -807,8 +852,9 @@ void MediaSubsession::deInitiate() {
   Medium::close(fReadSource); // this is assumed to also close fRTPSource
   fReadSource = NULL; fRTPSource = NULL;
 
-  delete fRTPSocket; fRTPSocket = NULL;
-  delete fRTCPSocket; fRTCPSocket = NULL;
+  delete fRTPSocket;
+  if (fRTCPSocket != fRTPSocket) delete fRTCPSocket;
+  fRTPSocket = NULL; fRTCPSocket = NULL;
 }
 
 Boolean MediaSubsession::setClientPortNum(unsigned short portNum) {
@@ -821,6 +867,34 @@ Boolean MediaSubsession::setClientPortNum(unsigned short portNum) {
   return True;
 }
 
+char const* MediaSubsession::attrVal_str(char const* attrName) const {
+  SDPAttribute* attr = (SDPAttribute*)(fAttributeTable->Lookup(attrName));
+  if (attr == NULL) return "";
+
+  return attr->strValue();
+}
+
+char const* MediaSubsession::attrVal_strToLower(char const* attrName) const {
+  SDPAttribute* attr = (SDPAttribute*)(fAttributeTable->Lookup(attrName));
+  if (attr == NULL) return "";
+
+  return attr->strValueToLower();
+}
+
+unsigned MediaSubsession::attrVal_int(char const* attrName) const {
+  SDPAttribute* attr = (SDPAttribute*)(fAttributeTable->Lookup(attrName));
+  if (attr == NULL) return 0;
+
+  return attr->intValue();
+}
+
+char const* MediaSubsession::fmtp_config() const {
+  char const* result = attrVal_str("config");
+  if (result[0] == '\0') result = attrVal_str("configuration");
+
+  return result;
+}
+
 netAddressBits MediaSubsession::connectionEndpointAddress() const {
   do {
     // Get the endpoint name from with us, or our parent session:
@@ -855,7 +929,7 @@ void MediaSubsession::setDestinations(netAddressBits defaultDestAddress) {
     Port destPort(serverPortNum);
     fRTPSocket->changeDestinationParameters(destAddr, destPort, destTTL);
   }
-  if (fRTCPSocket != NULL && !isSSM()) {
+  if (fRTCPSocket != NULL && !isSSM() && !fMultiplexRTCPWithRTP) {
     // Note: For SSM sessions, the dest address for RTCP was already set.
     Port destPort(serverPortNum+1);
     fRTCPSocket->changeDestinationParameters(destAddr, destPort, destTTL);
@@ -903,6 +977,21 @@ double MediaSubsession::getNormalPlayTime(struct timeval const& presentationTime
   }
 }
 
+void MediaSubsession
+::setAttribute(char const* name, char const* value, Boolean valueIsHexadecimal) {
+  // Replace any existing attribute record with this name (except that the 'valueIsHexadecimal'
+  // property will be inherited from it, if it exists).
+  SDPAttribute* oldAttr = (SDPAttribute*)fAttributeTable->Lookup(name);
+  if (oldAttr != NULL) {
+    valueIsHexadecimal = oldAttr->valueIsHexadecimal();
+    fAttributeTable->Remove(name);
+    delete oldAttr;
+  }
+
+  SDPAttribute* newAttr = new SDPAttribute(value, valueIsHexadecimal);
+  (void)fAttributeTable->Add(name, newAttr);
+}
+
 Boolean MediaSubsession::parseSDPLine_c(char const* sdpLine) {
   // Check for "c=IN IP4 <connection-endpoint>"
   // or "c=IN IP4 <connection-endpoint>/<ttl+numAddresses>"
@@ -959,6 +1048,15 @@ Boolean MediaSubsession::parseSDPAttribute_rtpmap(char const* sdpLine) {
   return parseSuccess;
 }
 
+Boolean MediaSubsession::parseSDPAttribute_rtcpmux(char const* sdpLine) {
+  if (strncmp(sdpLine, "a=rtcp-mux", 10) == 0) {
+    fMultiplexRTCPWithRTP = True;
+    return True;
+  }
+
+  return False;
+}
+
 Boolean MediaSubsession::parseSDPAttribute_control(char const* sdpLine) {
   // Check for a "a=control:<control-path>" line:
   Boolean parseSuccess = False;
@@ -1003,116 +1101,42 @@ Boolean MediaSubsession::parseSDPAttribute_range(char const* sdpLine) {
 
 Boolean MediaSubsession::parseSDPAttribute_fmtp(char const* sdpLine) {
   // Check for a "a=fmtp:" line:
-  // TEMP: We check only for a handful of expected parameter names #####
-  // Later: (i) check that payload format number matches; #####
-  //        (ii) look for other parameters also (generalize?) #####
+  // Later: Check that payload format number matches; #####
   do {
     if (strncmp(sdpLine, "a=fmtp:", 7) != 0) break; sdpLine += 7;
     while (isdigit(*sdpLine)) ++sdpLine;
 
     // The remaining "sdpLine" should be a sequence of
     //     <name>=<value>;
+    // or
+    //     <name>;
     // parameter assignments.  Look at each of these.
-    // First, convert the line to lower-case, to ease comparison:
-    char* const lineCopy = strDup(sdpLine); char* line = lineCopy;
-    {
-      Locale l("POSIX");
-      for (char* c = line; *c != '\0'; ++c) *c = tolower(*c);
-    }
-    while (*line != '\0' && *line != '\r' && *line != '\n') {
-      unsigned u;
-      char* valueStr = strDupSize(line);
-      if (sscanf(line, " auxiliarydatasizelength = %u", &u) == 1) {
-	fAuxiliarydatasizelength = u;
-      } else if (sscanf(line, " constantduration = %u", &u) == 1) {
-	fConstantduration = u;
-      } else if (sscanf(line, " constantsize; = %u", &u) == 1) {
-	fConstantsize = u;
-      } else if (sscanf(line, " crc = %u", &u) == 1) {
-	fCRC = u;
-      } else if (sscanf(line, " ctsdeltalength = %u", &u) == 1) {
-	fCtsdeltalength = u;
-      } else if (sscanf(line, " de-interleavebuffersize = %u", &u) == 1) {
-	fDe_interleavebuffersize = u;
-      } else if (sscanf(line, " dtsdeltalength = %u", &u) == 1) {
-	fDtsdeltalength = u;
-      } else if (sscanf(line, " indexdeltalength = %u", &u) == 1) {
-	fIndexdeltalength = u;
-      } else if (sscanf(line, " indexlength = %u", &u) == 1) {
-	fIndexlength = u;
-      } else if (sscanf(line, " interleaving = %u", &u) == 1) {
-	fInterleaving = u;
-      } else if (sscanf(line, " maxdisplacement = %u", &u) == 1) {
-	fMaxdisplacement = u;
-      } else if (sscanf(line, " objecttype = %u", &u) == 1) {
-	fObjecttype = u;
-      } else if (sscanf(line, " octet-align = %u", &u) == 1) {
-	fOctetalign = u;
-      } else if (sscanf(line, " profile-level-id = %x", &u) == 1) {
-	// Note that the "profile-level-id" parameter is assumed to be hexadecimal
-	fProfile_level_id = u;
-      } else if (sscanf(line, " robust-sorting = %u", &u) == 1) {
-	fRobustsorting = u;
-      } else if (sscanf(line, " sizelength = %u", &u) == 1) {
-	fSizelength = u;
-      } else if (sscanf(line, " streamstateindication = %u", &u) == 1) {
-	fStreamstateindication = u;
-      } else if (sscanf(line, " streamtype = %u", &u) == 1) {
-	fStreamtype = u;
-      } else if (sscanf(line, " cpresent = %u", &u) == 1) {
-	fCpresent = u != 0;
-      } else if (sscanf(line, " randomaccessindication = %u", &u) == 1) {
-	fRandomaccessindication = u != 0;
-      } else if (sscanf(sdpLine, " config = %[^; \t\r\n]", valueStr) == 1 ||
-		 sscanf(sdpLine, " configuration = %[^; \t\r\n]", valueStr) == 1) {
-	// Note: We used "sdpLine" here, because the value may be case-sensitive (if it's Base-64).
-	delete[] fConfig; fConfig = strDup(valueStr);
-      } else if (sscanf(line, " mode = %[^; \t\r\n]", valueStr) == 1) {
-	delete[] fMode; fMode = strDup(valueStr);
-      } else if (sscanf(sdpLine, " sprop-parameter-sets = %[^; \t\r\n]", valueStr) == 1) {
-	// Note: We used "sdpLine" here, because the value is case-sensitive.
-	delete[] fSpropParameterSets; fSpropParameterSets = strDup(valueStr);
-      } else if (sscanf(line, " emphasis = %[^; \t\r\n]", valueStr) == 1) {
-	delete[] fEmphasis; fEmphasis = strDup(valueStr);
-      } else if (sscanf(sdpLine, " channel-order = %[^; \t\r\n]", valueStr) == 1) {
-	// Note: We used "sdpLine" here, because the value is case-sensitive.
-	delete[] fChannelOrder; fChannelOrder = strDup(valueStr);
-      } else if (sscanf(line, " width = %u", &u) == 1) {
-	// A non-standard parameter, but one that's often used:
-	fVideoWidth = u;
-      } else if (sscanf(line, " height = %u", &u) == 1) {
-	// A non-standard parameter, but one that's often used:
-	fVideoHeight = u;
-      } else {
-	// Some of the above parameters are Boolean.  Check whether the parameter
-	// names appear alone, without a "= 1" at the end:
-	if (sscanf(line, " %[^; \t\r\n]", valueStr) == 1) {
-	  if (strcmp(valueStr, "octet-align") == 0) {
-	    fOctetalign = 1;
-	  } else if (strcmp(valueStr, "cpresent") == 0) {
-            fCpresent = True;
-	  } else if (strcmp(valueStr, "crc") == 0) {
-	    fCRC = 1;
-	  } else if (strcmp(valueStr, "robust-sorting") == 0) {
-	    fRobustsorting = 1;
-	  } else if (strcmp(valueStr, "randomaccessindication") == 0) {
-	    fRandomaccessindication = True;
-	  }
+    unsigned const sdpLineLen = strlen(sdpLine);
+    char* nameStr = new char[sdpLineLen+1];
+    char* valueStr = new char[sdpLineLen+1];
+
+    while (*sdpLine != '\0' && *sdpLine != '\r' && *sdpLine != '\n') {
+      int sscanfResult = sscanf(sdpLine, " %[^=; \t\r\n] = %[^; \t\r\n]", nameStr, valueStr);
+      if (sscanfResult >= 1) {
+	// <name> or <name>=<value>
+	// Convert <name> to lower-case, to ease comparison:
+	Locale l("POSIX");
+	for (char* c = nameStr; *c != '\0'; ++c) *c = tolower(*c);
+	
+	if (sscanfResult == 1) {
+	  // <name>
+	  setAttribute(nameStr);
+	} else {
+	  // <name>=<value>
+	  setAttribute(nameStr, valueStr);
 	}
       }
-      delete[] valueStr;
 
       // Move to the next parameter assignment string:
-      while (*line != '\0' && *line != '\r' && *line != '\n'
-	     && *line != ';') ++line;
-      while (*line == ';') ++line;
-
-      // Do the same with sdpLine; needed for finding case sensitive values:
-      while (*sdpLine != '\0' && *sdpLine != '\r' && *sdpLine != '\n'
-	     && *sdpLine != ';') ++sdpLine;
+      while (*sdpLine != '\0' && *sdpLine != '\r' && *sdpLine != '\n' && *sdpLine != ';') ++sdpLine;
       while (*sdpLine == ';') ++sdpLine;
     }
-    delete[] lineCopy;
+    delete[] nameStr; delete[] valueStr;
     return True;
   } while (0);
 
@@ -1184,15 +1208,19 @@ Boolean MediaSubsession::createSourceObjects(int useSpecialRTPoffset) {
 	fReadSource =
 	  AMRAudioRTPSource::createNew(env(), fRTPSocket, fRTPSource,
 				       fRTPPayloadFormat, False /*isWideband*/,
-				       fNumChannels, fOctetalign != 0, fInterleaving,
-				       fRobustsorting != 0, fCRC != 0);
+				       fNumChannels, attrVal_bool("octet-align"),
+				       attrVal_unsigned("interleaving"),
+				       attrVal_bool("robust-sorting"),
+				       attrVal_bool("crc"));
 	// Note that fReadSource will differ from fRTPSource in this case
       } else if (strcmp(fCodecName, "AMR-WB") == 0) { // AMR audio (wideband)
 	fReadSource =
 	  AMRAudioRTPSource::createNew(env(), fRTPSocket, fRTPSource,
 				       fRTPPayloadFormat, True /*isWideband*/,
-				       fNumChannels, fOctetalign != 0, fInterleaving,
-				       fRobustsorting != 0, fCRC != 0);
+				       fNumChannels, attrVal_bool("octet-align"),
+				       attrVal_unsigned("interleaving"),
+				       attrVal_bool("robust-sorting"),
+				       attrVal_bool("crc"));
 	// Note that fReadSource will differ from fRTPSource in this case
       } else if (strcmp(fCodecName, "MPA") == 0) { // MPEG-1 or 2 audio
 	fReadSource = fRTPSource
@@ -1236,11 +1264,19 @@ Boolean MediaSubsession::createSourceObjects(int useSpecialRTPoffset) {
 	  = VorbisAudioRTPSource::createNew(env(), fRTPSocket,
 					    fRTPPayloadFormat,
 					    fRTPTimestampFrequency);
+      } else if (strcmp(fCodecName, "THEORA") == 0) { // Theora video
+	fReadSource = fRTPSource
+	  = TheoraVideoRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat);
       } else if (strcmp(fCodecName, "VP8") == 0) { // VP8 video
 	fReadSource = fRTPSource
 	  = VP8VideoRTPSource::createNew(env(), fRTPSocket,
 					 fRTPPayloadFormat,
 					 fRTPTimestampFrequency);
+      } else if (strcmp(fCodecName, "VP9") == 0) { // VP9 video
+	fReadSource = fRTPSource
+	  = VP9VideoRTPSource::createNew(env(), fRTPSocket,
+					 fRTPPayloadFormat,
+					 fRTPTimestampFrequency);
       } else if (strcmp(fCodecName, "AC3") == 0 || strcmp(fCodecName, "EAC3") == 0) { // AC3 audio
 	fReadSource = fRTPSource
 	  = AC3AudioRTPSource::createNew(env(), fRTPSocket,
@@ -1256,9 +1292,10 @@ Boolean MediaSubsession::createSourceObjects(int useSpecialRTPoffset) {
 	  = MPEG4GenericRTPSource::createNew(env(), fRTPSocket,
 					     fRTPPayloadFormat,
 					     fRTPTimestampFrequency,
-					     fMediumName, fMode,
-					     fSizelength, fIndexlength,
-					     fIndexdeltalength);
+					     fMediumName, attrVal_strToLower("mode"),
+					     attrVal_unsigned("sizelength"),
+					     attrVal_unsigned("indexlength"),
+					     attrVal_unsigned("indexdeltalength"));
       } else if (strcmp(fCodecName, "MPV") == 0) { // MPEG-1 or 2 video
 	fReadSource = fRTPSource
 	  = MPEG1or2VideoRTPSource::createNew(env(), fRTPSocket,
@@ -1286,6 +1323,13 @@ Boolean MediaSubsession::createSourceObjects(int useSpecialRTPoffset) {
 	  = H264VideoRTPSource::createNew(env(), fRTPSocket,
 					  fRTPPayloadFormat,
 					  fRTPTimestampFrequency);
+      } else if (strcmp(fCodecName, "H265") == 0) {
+	Boolean expectDONFields = attrVal_unsigned("sprop-depack-buf-nalus") > 0;
+	fReadSource = fRTPSource
+	  = H265VideoRTPSource::createNew(env(), fRTPSocket,
+					  fRTPPayloadFormat,
+					  expectDONFields,
+					  fRTPTimestampFrequency);
       } else if (strcmp(fCodecName, "DV") == 0) {
 	fReadSource = fRTPSource
 	  = DVVideoRTPSource::createNew(env(), fRTPSocket,
@@ -1330,6 +1374,7 @@ Boolean MediaSubsession::createSourceObjects(int useSpecialRTPoffset) {
 		   || strcmp(fCodecName, "L16") == 0 // 16-bit linear audio
 		   || strcmp(fCodecName, "L20") == 0 // 20-bit linear audio (RFC 3190)
 		   || strcmp(fCodecName, "L24") == 0 // 24-bit linear audio (RFC 3190)
+		   || strcmp(fCodecName, "G722") == 0 // G.722 audio (RFC 3551)
 		   || strcmp(fCodecName, "G726-16") == 0 // G.726, 16 kbps
 		   || strcmp(fCodecName, "G726-24") == 0 // G.726, 24 kbps
 		   || strcmp(fCodecName, "G726-32") == 0 // G.726, 32 kbps
@@ -1374,3 +1419,32 @@ Boolean MediaSubsession::createSourceObjects(int useSpecialRTPoffset) {
 
   return False; // an error occurred
 }
+
+
+////////// SDPAttribute implementation //////////
+
+SDPAttribute::SDPAttribute(char const* strValue, Boolean valueIsHexadecimal)
+  : fStrValue(strDup(strValue)), fStrValueToLower(NULL), fValueIsHexadecimal(valueIsHexadecimal) {
+  if (fStrValue == NULL) {
+    // No value was given for this attribute, so consider it to be a Boolean, with value True:
+    fIntValue = 1;
+  } else {
+    // Create a 'tolower' version of "fStrValue", in case it's needed:
+    Locale l("POSIX");
+    size_t strSize;
+
+    fStrValueToLower = strDupSize(fStrValue, strSize);
+    for (unsigned i = 0; i < strSize-1; ++i) fStrValueToLower[i] = tolower(fStrValue[i]);
+    fStrValueToLower[strSize-1] = '\0';
+    
+    // Try to parse "fStrValueToLower" as an integer.  If we can't, assume an integer value of 0:
+    if (sscanf(fStrValueToLower, valueIsHexadecimal ? "%x" : "%d", &fIntValue) != 1) {
+      fIntValue = 0;
+    }
+  }
+}
+
+SDPAttribute::~SDPAttribute() {
+  delete[] fStrValue;
+  delete[] fStrValueToLower;
+}
diff --git a/liveMedia/MediaSink.cpp b/liveMedia/MediaSink.cpp
index 46694bd..1402ba4 100644
--- a/liveMedia/MediaSink.cpp
+++ b/liveMedia/MediaSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Media Sinks
 // Implementation
 
@@ -112,11 +112,12 @@ Boolean MediaSink::isRTPSink() const {
 
 unsigned OutPacketBuffer::maxSize = 60000; // by default
 
-OutPacketBuffer::OutPacketBuffer(unsigned preferredPacketSize,
-				 unsigned maxPacketSize)
+OutPacketBuffer
+::OutPacketBuffer(unsigned preferredPacketSize, unsigned maxPacketSize, unsigned maxBufferSize)
   : fPreferred(preferredPacketSize), fMax(maxPacketSize),
     fOverflowDataSize(0) {
-  unsigned maxNumPackets = (maxSize + (maxPacketSize-1))/maxPacketSize;
+  if (maxBufferSize == 0) maxBufferSize = maxSize;
+  unsigned maxNumPackets = (maxBufferSize + (maxPacketSize-1))/maxPacketSize;
   fLimit = maxNumPackets*maxPacketSize;
   fBuf = new unsigned char[fLimit];
   resetPacketStart();
diff --git a/liveMedia/MediaSource.cpp b/liveMedia/MediaSource.cpp
index d5b5947..5358ba1 100644
--- a/liveMedia/MediaSource.cpp
+++ b/liveMedia/MediaSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Media Sources
 // Implementation
 
diff --git a/liveMedia/MultiFramedRTPSink.cpp b/liveMedia/MultiFramedRTPSink.cpp
index e58fe24..378eaa1 100644
--- a/liveMedia/MultiFramedRTPSink.cpp
+++ b/liveMedia/MultiFramedRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for a common kind of payload format: Those which pack multiple,
 // complete codec frames (as many as possible) into each RTP packet.
 // Implementation
@@ -44,7 +44,7 @@ MultiFramedRTPSink::MultiFramedRTPSink(UsageEnvironment& env,
 	    rtpPayloadFormatName, numChannels),
     fOutBuf(NULL), fCurFragmentationOffset(0), fPreviousFrameEndedFragmentation(False),
     fOnSendErrorFunc(NULL), fOnSendErrorData(NULL) {
-  setPacketSizes(1000, 1448);
+  setPacketSizes(1000, 1456);
       // Default max packet size (1500, minus allowance for IP, UDP, UMTP headers)
       // (Also, make it a multiple of 4 bytes, just in case that matters.)
 }
diff --git a/liveMedia/MultiFramedRTPSource.cpp b/liveMedia/MultiFramedRTPSource.cpp
index cda2d71..fa16e69 100644
--- a/liveMedia/MultiFramedRTPSource.cpp
+++ b/liveMedia/MultiFramedRTPSource.cpp
@@ -14,12 +14,13 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP source for a common kind of payload format: Those that pack multiple,
 // complete codec frames (as many as possible) into each RTP packet.
 // Implementation
 
 #include "MultiFramedRTPSource.hh"
+#include "RTCP.hh"
 #include "GroupsockHelper.hh"
 #include <string.h>
 
@@ -104,6 +105,10 @@ Boolean MultiFramedRTPSource
 }
 
 void MultiFramedRTPSource::doStopGettingFrames() {
+  if (fPacketReadInProgress != NULL) {
+    fReorderingBuffer->freePacket(fPacketReadInProgress);
+    fPacketReadInProgress = NULL;
+  }
   envir().taskScheduler().unscheduleDelayedTask(nextTask());
   fRTPInterface.stopNetworkReading();
   fReorderingBuffer->reset();
@@ -144,7 +149,7 @@ void MultiFramedRTPSource::doGetNextFrame1() {
 	// Something's wrong with the header; reject the packet:
 	fReorderingBuffer->releaseUsedPacket(nextPacket);
 	fNeedDelivery = True;
-	break;
+	continue;
       }
       nextPacket->skip(specialHeaderSize);
     }
@@ -167,7 +172,7 @@ void MultiFramedRTPSource::doGetNextFrame1() {
       // This packet is unusable; reject it:
       fReorderingBuffer->releaseUsedPacket(nextPacket);
       fNeedDelivery = True;
-      break;
+      continue;
     }
 
     // The packet is usable. Deliver all or part of it to our caller:
@@ -183,7 +188,7 @@ void MultiFramedRTPSource::doGetNextFrame1() {
       fReorderingBuffer->releaseUsedPacket(nextPacket);
     }
 
-    if (fCurrentPacketCompletesFrame) {
+    if (fCurrentPacketCompletesFrame && fFrameSize > 0) {
       // We have all the data that the client wants.
       if (fNumTruncatedBytes > 0) {
 	envir() << "MultiFramedRTPSource::doGetNextFrame1(): The total received frame size exceeds the client's buffer size ("
@@ -231,10 +236,11 @@ void MultiFramedRTPSource::networkReadHandler1() {
   // Read the network packet, and perform sanity checks on the RTP header:
   Boolean readSuccess = False;
   do {
+    struct sockaddr_in fromAddress;
     Boolean packetReadWasIncomplete = fPacketReadInProgress != NULL;
-    if (!bPacket->fillInData(fRTPInterface, packetReadWasIncomplete)) {
-      if (bPacket->bytesAvailable() == 0) {
-	envir() << "MultiFramedRTPSource error: Hit limit when reading incoming packet over TCP. Increase \"MAX_PACKET_SIZE\"\n";
+    if (!bPacket->fillInData(fRTPInterface, fromAddress, packetReadWasIncomplete)) {
+      if (bPacket->bytesAvailable() == 0) { // should not happen??
+	envir() << "MultiFramedRTPSource internal error: Hit limit when reading incoming packet over TCP\n";
       }
       fPacketReadInProgress = NULL;
       break;
@@ -262,9 +268,22 @@ void MultiFramedRTPSource::networkReadHandler1() {
     // Check the RTP version number (it should be 2):
     if ((rtpHdr&0xC0000000) != 0x80000000) break;
 
+    // Check the Payload Type.
+    unsigned char rtpPayloadType = (unsigned char)((rtpHdr&0x007F0000)>>16);
+    if (rtpPayloadType != rtpPayloadFormat()) {
+      if (fRTCPInstanceForMultiplexedRTCPPackets != NULL
+	  && rtpPayloadType >= 64 && rtpPayloadType <= 95) {
+	// This is a multiplexed RTCP packet, and we've been asked to deliver such packets.
+	// Do so now:
+	fRTCPInstanceForMultiplexedRTCPPackets
+	  ->injectReport(bPacket->data()-12, bPacket->dataSize()+12, fromAddress);
+      }
+      break;
+    }
+
     // Skip over any CSRC identifiers in the header:
-    unsigned cc = (rtpHdr>>24)&0xF;
-    if (bPacket->dataSize() < cc) break;
+    unsigned cc = (rtpHdr>>24)&0x0F;
+    if (bPacket->dataSize() < cc*4) break;
     ADVANCE(cc*4);
 
     // Check for (& ignore) any RTP header extension
@@ -284,11 +303,6 @@ void MultiFramedRTPSource::networkReadHandler1() {
       if (bPacket->dataSize() < numPaddingBytes) break;
       bPacket->removePadding(numPaddingBytes);
     }
-    // Check the Payload Type.
-    if ((unsigned char)((rtpHdr&0x007F0000)>>16)
-	!= rtpPayloadFormat()) {
-      break;
-    }
 
     // The rest of the packet is the usable data.  Record and save it:
     if (rtpSSRC != fLastReceivedSSRC) {
@@ -328,7 +342,7 @@ void MultiFramedRTPSource::networkReadHandler1() {
 
 ////////// BufferedPacket and BufferedPacketFactory implementation /////
 
-#define MAX_PACKET_SIZE 20000
+#define MAX_PACKET_SIZE 65536
 
 BufferedPacket::BufferedPacket()
   : fPacketSize(MAX_PACKET_SIZE),
@@ -371,14 +385,20 @@ void BufferedPacket
   frameDurationInMicroseconds = 0; // by default.  Subclasses should correct this.
 }
 
-Boolean BufferedPacket::fillInData(RTPInterface& rtpInterface, Boolean& packetReadWasIncomplete) {
+Boolean BufferedPacket::fillInData(RTPInterface& rtpInterface, struct sockaddr_in& fromAddress,
+				   Boolean& packetReadWasIncomplete) {
   if (!packetReadWasIncomplete) reset();
 
-  unsigned numBytesRead;
-  struct sockaddr_in fromAddress;
   unsigned const maxBytesToRead = bytesAvailable();
   if (maxBytesToRead == 0) return False; // exceeded buffer size when reading over TCP
-  if (!rtpInterface.handleRead(&fBuf[fTail], maxBytesToRead, numBytesRead, fromAddress, packetReadWasIncomplete)) {
+
+  unsigned numBytesRead;
+  int tcpSocketNum; // not used
+  unsigned char tcpStreamChannelId; // not used
+  if (!rtpInterface.handleRead(&fBuf[fTail], maxBytesToRead,
+			       numBytesRead, fromAddress,
+			       tcpSocketNum, tcpStreamChannelId,
+			       packetReadWasIncomplete)) {
     return False;
   }
   fTail += numBytesRead;
diff --git a/liveMedia/MatroskaDemuxedTrack.cpp b/liveMedia/OggDemuxedTrack.cpp
similarity index 54%
copy from liveMedia/MatroskaDemuxedTrack.cpp
copy to liveMedia/OggDemuxedTrack.cpp
index 193bcf4..03a65e9 100644
--- a/liveMedia/MatroskaDemuxedTrack.cpp
+++ b/liveMedia/OggDemuxedTrack.cpp
@@ -14,33 +14,30 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A media track, demultiplexed from a Matroska file
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A media track, demultiplexed from an Ogg file
 // Implementation
 
-#include "MatroskaDemuxedTrack.hh"
-#include "MatroskaFile.hh"
+#include "OggDemuxedTrack.hh"
+#include "OggFile.hh"
 
-void MatroskaDemuxedTrack::seekToTime(double& seekNPT) {
-  fOurSourceDemux.seekToTime(seekNPT);
-}
-
-MatroskaDemuxedTrack::MatroskaDemuxedTrack(UsageEnvironment& env, unsigned trackNumber, MatroskaDemux& sourceDemux)
+OggDemuxedTrack::OggDemuxedTrack(UsageEnvironment& env, unsigned trackNumber, OggDemux& sourceDemux)
   : FramedSource(env),
-    fOurTrackNumber(trackNumber), fOurSourceDemux(sourceDemux), fDurationImbalance(0) {
-  fPrevPresentationTime.tv_sec = 0; fPrevPresentationTime.tv_usec = 0;
+    fOurTrackNumber(trackNumber), fOurSourceDemux(sourceDemux),
+    fCurrentPageIsContinuation(False) {
+  fNextPresentationTime.tv_sec = 0; fNextPresentationTime.tv_usec = 0;
 }
 
-MatroskaDemuxedTrack::~MatroskaDemuxedTrack() {
+OggDemuxedTrack::~OggDemuxedTrack() {
   fOurSourceDemux.removeTrack(fOurTrackNumber);
 }
 
-void MatroskaDemuxedTrack::doGetNextFrame() {
+void OggDemuxedTrack::doGetNextFrame() {
   fOurSourceDemux.continueReading();
 }
 
-char const* MatroskaDemuxedTrack::MIMEtype() const {
-  MatroskaTrack* track = fOurSourceDemux.fOurFile.lookup(fOurTrackNumber);
-  if (track == NULL) return NULL; // shouldn't happen
+char const* OggDemuxedTrack::MIMEtype() const {
+  OggTrack* track = fOurSourceDemux.fOurFile.lookup(fOurTrackNumber);
+  if (track == NULL) return "(unknown)"; // shouldn't happen
   return track->mimeType;
 }
diff --git a/liveMedia/MatroskaDemuxedTrack.hh b/liveMedia/OggDemuxedTrack.hh
similarity index 57%
copy from liveMedia/MatroskaDemuxedTrack.hh
copy to liveMedia/OggDemuxedTrack.hh
index 90fa3e9..5e5cbff 100644
--- a/liveMedia/MatroskaDemuxedTrack.hh
+++ b/liveMedia/OggDemuxedTrack.hh
@@ -14,50 +14,45 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A media track, demultiplexed from a Matroska file
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A media track, demultiplexed from an Ogg file
 // C++ header
 
-#ifndef _MATROSKA_DEMUXED_TRACK_HH
-#define _MATROSKA_DEMUXED_TRACK_HH
+#ifndef _OGG_DEMUXED_TRACK_HH
+#define _OGG_DEMUXED_TRACK_HH
 
 #ifndef _FRAMED_SOURCE_HH
 #include "FramedSource.hh"
 #endif
 
-class MatroskaDemux; // forward
+class OggDemux; // forward
 
-class MatroskaDemuxedTrack: public FramedSource {
-public:
-  void seekToTime(double& seekNPT);
-
-private: // We are created only by a MatroskaDemux (a friend)
-  friend class MatroskaDemux;
-  MatroskaDemuxedTrack(UsageEnvironment& env, unsigned trackNumber, MatroskaDemux& sourceDemux);
-  virtual ~MatroskaDemuxedTrack();
+class OggDemuxedTrack: public FramedSource {
+private: // We are created only by a OggDemux (a friend)
+  friend class OggDemux;
+  OggDemuxedTrack(UsageEnvironment& env, unsigned trackNumber, OggDemux& sourceDemux);
+  virtual ~OggDemuxedTrack();
 
 private:
   // redefined virtual functions:
   virtual void doGetNextFrame();
   virtual char const* MIMEtype() const;
 
-private: // We are accessed only by MatroskaDemux and by MatroskaFileParser (a friend)
-  friend class MatroskaFileParser;
-  unsigned char* to() { return fTo; }
-  unsigned maxSize() { return fMaxSize; }
+private: // We are accessed only by OggDemux and by OggFileParser (a friend)
+  friend class OggFileParser;
+  unsigned char*& to() { return fTo; }
+  unsigned& maxSize() { return fMaxSize; }
   unsigned& frameSize() { return fFrameSize; }
   unsigned& numTruncatedBytes() { return fNumTruncatedBytes; }
   struct timeval& presentationTime() { return fPresentationTime; }
   unsigned& durationInMicroseconds() { return fDurationInMicroseconds; }
-
-  struct timeval& prevPresentationTime() { return fPrevPresentationTime; }
-  int& durationImbalance() { return fDurationImbalance; }
+  struct timeval& nextPresentationTime() { return fNextPresentationTime; }
 
 private:
   unsigned fOurTrackNumber;
-  MatroskaDemux& fOurSourceDemux;
-  struct timeval fPrevPresentationTime;
-  int fDurationImbalance;
+  OggDemux& fOurSourceDemux;
+  Boolean fCurrentPageIsContinuation;
+  struct timeval fNextPresentationTime;
 };
 
 #endif
diff --git a/liveMedia/OggFile.cpp b/liveMedia/OggFile.cpp
new file mode 100644
index 0000000..3083470
--- /dev/null
+++ b/liveMedia/OggFile.cpp
@@ -0,0 +1,328 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A class that encapsulates an Ogg file.
+// Implementation
+
+#include "OggFileParser.hh"
+#include "OggDemuxedTrack.hh"
+#include "ByteStreamFileSource.hh"
+#include "VorbisAudioRTPSink.hh"
+#include "SimpleRTPSink.hh"
+#include "TheoraVideoRTPSink.hh"
+
+////////// OggTrackTable definition /////////
+
+// For looking up and iterating over the file's tracks:
+
+class OggTrackTable {
+public:
+  OggTrackTable();
+  virtual ~OggTrackTable();
+
+  void add(OggTrack* newTrack);
+  OggTrack* lookup(u_int32_t trackNumber);
+
+  unsigned numTracks() const;
+
+private:
+  friend class OggTrackTableIterator;
+  HashTable* fTable;
+};
+
+
+////////// OggFile implementation //////////
+
+void OggFile::createNew(UsageEnvironment& env, char const* fileName,
+			onCreationFunc* onCreation, void* onCreationClientData) {
+  new OggFile(env, fileName, onCreation, onCreationClientData);
+}
+
+OggTrack* OggFile::lookup(u_int32_t trackNumber) {
+  return fTrackTable->lookup(trackNumber);
+}
+
+OggDemux* OggFile::newDemux() {
+  OggDemux* demux = new OggDemux(*this);
+  fDemuxesTable->Add((char const*)demux, demux);
+
+  return demux;
+}
+
+unsigned OggFile::numTracks() const {
+  return fTrackTable->numTracks();
+}
+
+FramedSource* OggFile
+::createSourceForStreaming(FramedSource* baseSource, u_int32_t trackNumber,
+                           unsigned& estBitrate, unsigned& numFiltersInFrontOfTrack) {
+  if (baseSource == NULL) return NULL;
+
+  FramedSource* result = baseSource; // by default
+  numFiltersInFrontOfTrack = 0; // by default
+
+  // Look at the track's MIME type to set its estimated bitrate (for use by RTCP).
+  // (Later, try to be smarter about figuring out the bitrate.) #####
+  // Some MIME types also require adding a special 'framer' in front of the source.
+  OggTrack* track = lookup(trackNumber);
+  if (track != NULL) { // should always be true
+    estBitrate = track->estBitrate;
+  }
+
+  return result;
+}
+
+RTPSink* OggFile
+::createRTPSinkForTrackNumber(u_int32_t trackNumber, Groupsock* rtpGroupsock,
+                              unsigned char rtpPayloadTypeIfDynamic) {
+  OggTrack* track = lookup(trackNumber);
+  if (track == NULL || track->mimeType == NULL) return NULL;
+
+  RTPSink* result = NULL; // default value for unknown media types
+
+  if (strcmp(track->mimeType, "audio/VORBIS") == 0) {
+    // For Vorbis audio, we use the special "identification", "comment", and "setup" headers
+    // that we read when we initially read the headers at the start of the file:
+    result = VorbisAudioRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+					   track->samplingFrequency, track->numChannels,
+					   track->vtoHdrs.header[0], track->vtoHdrs.headerSize[0],
+					   track->vtoHdrs.header[1], track->vtoHdrs.headerSize[1],
+					   track->vtoHdrs.header[2], track->vtoHdrs.headerSize[2]);
+  } else if (strcmp(track->mimeType, "audio/OPUS") == 0) {
+    result = SimpleRTPSink
+      ::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+		  48000, "audio", "OPUS", 2, False/*only 1 Opus 'packet' in each RTP packet*/);
+  } else if (strcmp(track->mimeType, "video/THEORA") == 0) {
+    // For Theora video, we use the special "identification", "comment", and "setup" headers
+    // that we read when we initially read the headers at the start of the file:
+    result = TheoraVideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+					   track->vtoHdrs.header[0], track->vtoHdrs.headerSize[0],
+					   track->vtoHdrs.header[1], track->vtoHdrs.headerSize[1],
+					   track->vtoHdrs.header[2], track->vtoHdrs.headerSize[2]);
+  }
+
+  return result;
+}
+
+
+OggFile::OggFile(UsageEnvironment& env, char const* fileName,
+		 onCreationFunc* onCreation, void* onCreationClientData)
+  : Medium(env),
+    fFileName(strDup(fileName)),
+    fOnCreation(onCreation), fOnCreationClientData(onCreationClientData) {
+  fTrackTable = new OggTrackTable;
+  fDemuxesTable = HashTable::create(ONE_WORD_HASH_KEYS);
+
+  FramedSource* inputSource = ByteStreamFileSource::createNew(envir(), fileName);
+  if (inputSource == NULL) {
+    // The specified input file does not exist!
+    fParserForInitialization = NULL;
+    handleEndOfBosPageParsing(); // we have no file, and thus no tracks, but we still need to signal this
+  } else {
+    // Initialize ourselves by parsing the file's headers:
+    fParserForInitialization
+      = new OggFileParser(*this, inputSource, handleEndOfBosPageParsing, this);
+  }
+}
+
+OggFile::~OggFile() {
+  delete fParserForInitialization;
+
+  // Delete any outstanding "OggDemux"s, and the table for them:
+  OggDemux* demux;
+  while ((demux = (OggDemux*)fDemuxesTable->RemoveNext()) != NULL) {
+    delete demux;
+  }
+  delete fDemuxesTable;
+  delete fTrackTable;
+
+  delete[] (char*)fFileName;
+}
+
+void OggFile::handleEndOfBosPageParsing(void* clientData) {
+  ((OggFile*)clientData)->handleEndOfBosPageParsing();
+}
+
+void OggFile::handleEndOfBosPageParsing() {
+  // Delete our parser, because it's done its job now:
+  delete fParserForInitialization; fParserForInitialization = NULL;
+
+  // Finally, signal our caller that we've been created and initialized:
+  if (fOnCreation != NULL) (*fOnCreation)(this, fOnCreationClientData);
+}
+
+void OggFile::addTrack(OggTrack* newTrack) {
+  fTrackTable->add(newTrack);
+}
+
+void OggFile::removeDemux(OggDemux* demux) {
+  fDemuxesTable->Remove((char const*)demux);
+}
+
+
+////////// OggTrackTable implementation /////////
+
+OggTrackTable::OggTrackTable()
+  : fTable(HashTable::create(ONE_WORD_HASH_KEYS)) {
+}
+
+OggTrackTable::~OggTrackTable() {
+  // Remove and delete all of our "OggTrack" descriptors, and the hash table itself:
+  OggTrack* track;
+  while ((track = (OggTrack*)fTable->RemoveNext()) != NULL) {
+    delete track;
+  }
+  delete fTable;
+}
+
+void OggTrackTable::add(OggTrack* newTrack) {
+  OggTrack* existingTrack
+    = (OggTrack*)fTable->Add((char const*)newTrack->trackNumber, newTrack);
+  delete existingTrack; // if any
+}
+
+OggTrack* OggTrackTable::lookup(u_int32_t trackNumber) {
+  return (OggTrack*)fTable->Lookup((char const*)trackNumber);
+}
+
+unsigned OggTrackTable::numTracks() const { return fTable->numEntries(); }
+
+OggTrackTableIterator::OggTrackTableIterator(OggTrackTable& ourTable) {
+  fIter = HashTable::Iterator::create(*(ourTable.fTable));
+}
+
+OggTrackTableIterator::~OggTrackTableIterator() {
+  delete fIter;
+}
+
+OggTrack* OggTrackTableIterator::next() {
+  char const* key;
+  return (OggTrack*)fIter->next(key);
+}
+
+
+////////// OggTrack implementation //////////
+
+OggTrack::OggTrack()
+  : trackNumber(0), mimeType(NULL),
+    samplingFrequency(48000), numChannels(2), estBitrate(100) { // default settings
+  vtoHdrs.header[0] = vtoHdrs.header[1] = vtoHdrs.header[2] = NULL;
+  vtoHdrs.headerSize[0] = vtoHdrs.headerSize[1] = vtoHdrs.headerSize[2] = 0;
+
+  vtoHdrs.vorbis_mode_count = 0;
+  vtoHdrs.vorbis_mode_blockflag = NULL;
+}
+
+OggTrack::~OggTrack() {
+  delete[] vtoHdrs.header[0]; delete[] vtoHdrs.header[1]; delete[] vtoHdrs.header[2];
+  delete[] vtoHdrs.vorbis_mode_blockflag;
+}
+
+
+///////// OggDemux implementation /////////
+
+FramedSource* OggDemux::newDemuxedTrack(u_int32_t& resultTrackNumber) {
+  OggTrack* nextTrack;
+  do {
+    nextTrack = fIter->next();
+  } while (nextTrack != NULL && nextTrack->mimeType == NULL);
+
+  if (nextTrack == NULL) { // no more tracks
+    resultTrackNumber = 0;
+    return NULL;
+  }
+
+  resultTrackNumber = nextTrack->trackNumber;
+  FramedSource* trackSource = new OggDemuxedTrack(envir(), resultTrackNumber, *this);
+  fDemuxedTracksTable->Add((char const*)resultTrackNumber, trackSource);
+  return trackSource;
+}
+
+FramedSource* OggDemux::newDemuxedTrackByTrackNumber(unsigned trackNumber) {
+  if (trackNumber == 0) return NULL;
+
+  FramedSource* trackSource = new OggDemuxedTrack(envir(), trackNumber, *this);
+  fDemuxedTracksTable->Add((char const*)trackNumber, trackSource);
+  return trackSource;
+}
+
+OggDemuxedTrack* OggDemux::lookupDemuxedTrack(u_int32_t trackNumber) {
+  return (OggDemuxedTrack*)fDemuxedTracksTable->Lookup((char const*)trackNumber);
+}
+
+OggDemux::OggDemux(OggFile& ourFile)
+  : Medium(ourFile.envir()),
+    fOurFile(ourFile), fDemuxedTracksTable(HashTable::create(ONE_WORD_HASH_KEYS)),
+    fIter(new OggTrackTableIterator(*fOurFile.fTrackTable)) {
+  FramedSource* fileSource = ByteStreamFileSource::createNew(envir(), ourFile.fileName());
+  fOurParser = new OggFileParser(ourFile, fileSource, handleEndOfFile, this, this);
+}
+
+OggDemux::~OggDemux() {
+  // Begin by acting as if we've reached the end of the source file.
+  // This should cause all of our demuxed tracks to get closed.
+  handleEndOfFile();
+
+  // Then delete our table of "OggDemuxedTrack"s
+  // - but not the "OggDemuxedTrack"s themselves; that should have already happened:
+  delete fDemuxedTracksTable;
+
+  delete fIter;
+  delete fOurParser;
+  fOurFile.removeDemux(this);
+}
+
+void OggDemux::removeTrack(u_int32_t trackNumber) {
+  fDemuxedTracksTable->Remove((char const*)trackNumber);
+  if (fDemuxedTracksTable->numEntries() == 0) {
+    // We no longer have any demuxed tracks, so delete ourselves now:
+    delete this;
+  }
+}
+
+void OggDemux::continueReading() {
+  fOurParser->continueParsing();
+}
+
+void OggDemux::handleEndOfFile(void* clientData) {
+  ((OggDemux*)clientData)->handleEndOfFile();
+}
+
+void OggDemux::handleEndOfFile() {
+  // Iterate through all of our 'demuxed tracks', handling 'end of input' on each one.
+  // Hack: Because this can cause the hash table to get modified underneath us,
+  // we don't call the handlers until after we've first iterated through all of the tracks.
+  unsigned numTracks = fDemuxedTracksTable->numEntries();
+  if (numTracks == 0) return;
+  OggDemuxedTrack** tracks = new OggDemuxedTrack*[numTracks];
+
+  HashTable::Iterator* iter = HashTable::Iterator::create(*fDemuxedTracksTable);
+  unsigned i;
+  char const* trackNumber;
+
+  for (i = 0; i < numTracks; ++i) {
+    tracks[i] = (OggDemuxedTrack*)iter->next(trackNumber);
+  }
+  delete iter;
+
+  for (i = 0; i < numTracks; ++i) {
+    if (tracks[i] == NULL) continue; // sanity check; shouldn't happen
+    tracks[i]->handleClosure();
+  }
+
+  delete[] tracks;
+}
diff --git a/liveMedia/OggFileParser.cpp b/liveMedia/OggFileParser.cpp
new file mode 100644
index 0000000..473bc17
--- /dev/null
+++ b/liveMedia/OggFileParser.cpp
@@ -0,0 +1,1032 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A parser for an Ogg file.
+// Implementation
+
+#include "OggFileParser.hh"
+#include "OggDemuxedTrack.hh"
+#include <GroupsockHelper.hh> // for "gettimeofday()
+
+PacketSizeTable::PacketSizeTable(unsigned number_page_segments)
+  : numCompletedPackets(0), totSizes(0), nextPacketNumToDeliver(0),
+    lastPacketIsIncomplete(False) {
+  size = new unsigned[number_page_segments];
+  for (unsigned i = 0; i < number_page_segments; ++i) size[i] = 0;
+}
+
+PacketSizeTable::~PacketSizeTable() {
+  delete[] size;
+}
+
+OggFileParser::OggFileParser(OggFile& ourFile, FramedSource* inputSource,
+			     FramedSource::onCloseFunc* onEndFunc, void* onEndClientData,
+			     OggDemux* ourDemux)
+  : StreamParser(inputSource, onEndFunc, onEndClientData, continueParsing, this),
+    fOurFile(ourFile), fInputSource(inputSource),
+    fOnEndFunc(onEndFunc), fOnEndClientData(onEndClientData),
+    fOurDemux(ourDemux), fNumUnfulfilledTracks(0),
+    fPacketSizeTable(NULL), fCurrentTrackNumber(0), fSavedPacket(NULL) {
+  if (ourDemux == NULL) {
+    // Initialization
+    fCurrentParseState = PARSING_START_OF_FILE;
+    continueParsing();
+  } else {
+    fCurrentParseState = PARSING_AND_DELIVERING_PAGES;
+    // In this case, parsing (of page data) doesn't start until a client starts reading from a track.
+  }
+}
+
+OggFileParser::~OggFileParser() {
+  delete[] fSavedPacket;
+  delete fPacketSizeTable;
+  Medium::close(fInputSource);
+}
+
+void OggFileParser::continueParsing(void* clientData, unsigned char* ptr, unsigned size, struct timeval presentationTime) {
+  ((OggFileParser*)clientData)->continueParsing();
+}
+
+void OggFileParser::continueParsing() {
+  if (fInputSource != NULL) {
+    if (fInputSource->isCurrentlyAwaitingData()) return;
+        // Our input source is currently being read. Wait until that read completes
+
+    if (!parse()) {
+      // We didn't complete the parsing, because we had to read more data from the source,
+      // or because we're waiting for another read from downstream.
+      // Once that happens, we'll get called again.
+      return;
+    }
+  }
+
+  // We successfully parsed the file.  Call our 'done' function now:
+  if (fOnEndFunc != NULL) (*fOnEndFunc)(fOnEndClientData);
+}
+
+Boolean OggFileParser::parse() {
+  try {
+    while (1) {
+      switch (fCurrentParseState) {
+        case PARSING_START_OF_FILE: {
+	  if (parseStartOfFile()) return True;
+	}
+        case PARSING_AND_DELIVERING_PAGES: {
+	  parseAndDeliverPages();
+        }
+        case DELIVERING_PACKET_WITHIN_PAGE: {
+	  if (deliverPacketWithinPage()) return False;
+	}
+      }
+    }
+  } catch (int /*e*/) {
+#ifdef DEBUG
+    fprintf(stderr, "OggFileParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n");
+#endif
+    return False; // the parsing got interrupted
+  }
+}
+
+Boolean OggFileParser::parseStartOfFile() {
+#ifdef DEBUG
+  fprintf(stderr, "parsing start of file\n");
+#endif
+  // Read and parse each 'page', until we see the first non-BOS page, or until we have
+  // collected all required headers for Vorbis, Theora, or Opus track(s) (if any).
+  u_int8_t header_type_flag;
+  do {
+    header_type_flag = parseInitialPage();
+  } while ((header_type_flag&0x02) != 0 || needHeaders());
+  
+#ifdef DEBUG
+  fprintf(stderr, "Finished parsing start of file\n");
+#endif
+  return True;
+}
+
+static u_int32_t byteSwap(u_int32_t x) {
+  return (x<<24)|((x<<8)&0x00FF0000)|((x>>8)&0x0000FF00)|(x>>24);
+}
+
+u_int8_t OggFileParser::parseInitialPage() {
+  u_int8_t header_type_flag;
+  u_int32_t bitstream_serial_number;
+  parseStartOfPage(header_type_flag, bitstream_serial_number);
+
+  // If this is a BOS page, examine the first 8 bytes of the first 'packet', to see whether
+  // the track data type is one that we know how to stream:
+  OggTrack* track;
+  if ((header_type_flag&0x02) != 0) { // BOS
+    char const* mimeType = NULL; // if unknown
+    if (fPacketSizeTable != NULL && fPacketSizeTable->size[0] >= 8) { // sanity check
+      char buf[8];
+      testBytes((u_int8_t*)buf, 8);
+
+      if (strncmp(&buf[1], "vorbis", 6) == 0) {
+	mimeType = "audio/VORBIS";
+	++fNumUnfulfilledTracks;
+      } else if (strncmp(buf, "OpusHead", 8) == 0) {
+	mimeType = "audio/OPUS";
+	++fNumUnfulfilledTracks;
+      } else if (strncmp(&buf[1], "theora", 6) == 0) {
+	mimeType = "video/THEORA";
+	++fNumUnfulfilledTracks;
+      }
+    }
+
+    // Add a new track descriptor for this track:
+    track = new OggTrack;
+    track->trackNumber = bitstream_serial_number;
+    track->mimeType = mimeType;
+    fOurFile.addTrack(track);
+  } else { // not a BOS page
+    // Because this is not a BOS page, the specified track should already have been seen:
+    track = fOurFile.lookup(bitstream_serial_number);
+  }
+
+  if (track != NULL) { // sanity check
+#ifdef DEBUG
+    fprintf(stderr, "This track's MIME type: %s\n",
+	    track->mimeType == NULL ? "(unknown)" : track->mimeType);
+#endif
+    if (track->mimeType != NULL &&
+	(strcmp(track->mimeType, "audio/VORBIS") == 0 ||
+	 strcmp(track->mimeType, "video/THEORA") == 0 ||
+	 strcmp(track->mimeType, "audio/OPUS") == 0)) {
+      // Special-case handling of Vorbis, Theora, or Opus tracks:
+      // Make a copy of each packet, until we get the three special headers that we need:
+      Boolean isVorbis = strcmp(track->mimeType, "audio/VORBIS") == 0;
+      Boolean isTheora = strcmp(track->mimeType, "video/THEORA") == 0;
+
+      for (unsigned j = 0; j < fPacketSizeTable->numCompletedPackets && track->weNeedHeaders(); ++j) {
+	unsigned const packetSize = fPacketSizeTable->size[j];
+	if (packetSize == 0) continue; // sanity check
+
+	delete[] fSavedPacket/*if any*/; fSavedPacket = new u_int8_t[packetSize];
+	getBytes(fSavedPacket, packetSize);
+	fPacketSizeTable->totSizes -= packetSize;
+
+	// The start of the packet tells us whether its a header that we know about:
+	Boolean headerIsKnown = False;
+	unsigned index = 0;
+	if (isVorbis) {
+	  u_int8_t const firstByte = fSavedPacket[0];
+
+	  headerIsKnown = firstByte == 1 || firstByte == 3 || firstByte == 5;
+	  index = (firstByte-1)/2; // 1, 3, or 5 => 0, 1, or 2
+	} else if (isTheora) {
+	  u_int8_t const firstByte = fSavedPacket[0];
+
+	  headerIsKnown = firstByte == 0x80 || firstByte == 0x81 || firstByte == 0x82;
+	  index = firstByte &~0x80; // 0x80, 0x81, or 0x82 => 0, 1, or 2
+	} else { // Opus
+	  if (strncmp((char const*)fSavedPacket, "OpusHead", 8) == 0) {
+	    headerIsKnown = True;
+	    index = 0; // "identification" header
+	  } else if (strncmp((char const*)fSavedPacket, "OpusTags", 8) == 0) {
+	    headerIsKnown = True;
+	    index = 1; // "comment" header
+	  }
+	}
+	if (headerIsKnown) {
+#ifdef DEBUG
+	  char const* headerName[3] = { "identification", "comment", "setup" };
+	  fprintf(stderr, "Saved %d-byte %s \"%s\" header\n", packetSize, track->mimeType,
+		  headerName[index]);
+#endif
+	  // This is a header, but first check it for validity:
+	  if (!validateHeader(track, fSavedPacket, packetSize)) continue;
+
+	  // Save this header (deleting any old header of the same type that we'd saved before)
+	  delete[] track->vtoHdrs.header[index];
+	  track->vtoHdrs.header[index] = fSavedPacket;
+	  fSavedPacket = NULL;
+	  track->vtoHdrs.headerSize[index] = packetSize;
+
+	  if (!track->weNeedHeaders()) {
+	    // We now have all of the needed Vorbis, Theora, or Opus headers for this track:
+	    --fNumUnfulfilledTracks;
+	  }
+	  // Note: The above code won't work if a required header is fragmented over
+	  // more than one 'page'.  We assume that that won't ever happen...
+	}
+      }
+    }
+  }
+
+  // Skip over any remaining packet data bytes:
+  if (fPacketSizeTable->totSizes > 0) {
+#ifdef DEBUG
+    fprintf(stderr, "Skipping %d remaining packet data bytes\n", fPacketSizeTable->totSizes);
+#endif
+    skipBytes(fPacketSizeTable->totSizes);
+  }
+
+  return header_type_flag;
+}
+
+// A simple bit vector class for reading bits in little-endian order.
+// (We can't use our usual "BitVector" class, because that's big-endian.)
+class LEBitVector {
+public:
+  LEBitVector(u_int8_t const* p, unsigned numBytes)
+    : fPtr(p), fEnd(&p[numBytes]), fNumBitsRemainingInCurrentByte(8) {
+  }
+
+  u_int32_t getBits(unsigned numBits/*<=32*/) {
+    if (noMoreBits()) {
+      return 0;
+    } else if (numBits == fNumBitsRemainingInCurrentByte) {
+      u_int32_t result = (*fPtr++)>>(8-fNumBitsRemainingInCurrentByte);
+      fNumBitsRemainingInCurrentByte = 8;
+
+      return result;
+    } else if (numBits < fNumBitsRemainingInCurrentByte) {
+      u_int8_t mask = 0xFF>>(8-numBits);
+      u_int32_t result = ((*fPtr)>>(8-fNumBitsRemainingInCurrentByte)) & mask;
+      fNumBitsRemainingInCurrentByte -= numBits;
+
+      return result;
+    } else { // numBits > fNumBitsRemainingInCurrentByte
+      // Do two recursive calls to get the result:
+      unsigned nbr = fNumBitsRemainingInCurrentByte;
+      u_int32_t firstBits = getBits(nbr);
+      u_int32_t nextBits = getBits(numBits - nbr);
+
+      return (nextBits<<nbr) | firstBits;
+    }
+  }
+
+  void skipBits(unsigned numBits) {
+    while (numBits > 32) {
+      (void)getBits(32);
+      numBits -= 32;
+    }
+    (void)getBits(numBits);
+  }
+
+  unsigned numBitsRemaining() { return (fEnd-fPtr-1)*8 + fNumBitsRemainingInCurrentByte; }
+  Boolean noMoreBits() const { return fPtr >= fEnd; }
+
+private:
+  u_int8_t const* fPtr;
+  u_int8_t const* fEnd;
+  unsigned fNumBitsRemainingInCurrentByte; // 1..8
+};
+
+static unsigned ilog(int n) {
+  if (n < 0) return 0;
+
+  unsigned x = (unsigned)n;
+  unsigned result = 0;
+
+  while (x > 0) {
+    ++result;
+    x >>= 1;
+  }
+
+  return result;
+}
+
+static unsigned lookup1_values(unsigned codebook_entries, unsigned codebook_dimensions) {
+  // "the greatest integer value for which [return_value] to the power of [codebook_dimensions]
+  //  is less than or equal to [codebook_entries]"
+  unsigned return_value = 0;
+  unsigned powerValue;
+
+  do {
+    ++return_value;
+    // Compute powerValue = return_value ** codebook_dimensions
+    if (return_value == 1) powerValue = 1; // optimization
+    else {
+      powerValue = 1;
+      for (unsigned i = 0; i < codebook_dimensions; ++i) {
+	powerValue *= return_value;
+      }
+    }
+  } while (powerValue <= codebook_entries);
+  return_value -= 1;
+
+  return return_value;
+}
+
+static Boolean parseVorbisSetup_codebook(LEBitVector& bv) {
+  if (bv.noMoreBits()) return False;
+
+  unsigned sync = bv.getBits(24);
+  if (sync != 0x564342) return False;
+  unsigned codebook_dimensions = bv.getBits(16);
+  unsigned codebook_entries = bv.getBits(24);
+  unsigned ordered = bv.getBits(1);
+#ifdef DEBUG_SETUP_HEADER
+  fprintf(stderr, "\t\t\tcodebook_dimensions: %d; codebook_entries: %d, ordered: %d\n",
+	  codebook_dimensions, codebook_entries, ordered);
+#endif
+  if (!ordered) {
+    unsigned sparse = bv.getBits(1);
+#ifdef DEBUG_SETUP_HEADER
+    fprintf(stderr, "\t\t\t!ordered: sparse %d\n", sparse);
+#endif
+    for (unsigned i = 0; i < codebook_entries; ++i) {
+      unsigned codewordLength;
+
+      if (sparse) {
+	unsigned flag = bv.getBits(1);
+	if (flag) {
+	  codewordLength = bv.getBits(5) + 1;
+	} else {
+	  codewordLength = 0;
+	}
+      } else {
+	codewordLength = bv.getBits(5) + 1;
+      }
+#ifdef DEBUG_SETUP_HEADER
+      fprintf(stderr, "\t\t\t\tcodeword length[%d]:\t%d\n", i, codewordLength);
+#else
+      codewordLength = codewordLength; // to prevent compiler warning
+#endif
+    }
+  } else { // ordered
+#ifdef DEBUG_SETUP_HEADER
+    fprintf(stderr, "\t\t\tordered:\n");
+#endif
+    unsigned current_entry = 0;
+    unsigned current_length = bv.getBits(5) + 1;
+    do {
+      unsigned number = bv.getBits(ilog(codebook_entries - current_entry));
+#ifdef DEBUG_SETUP_HEADER
+      fprintf(stderr, "\t\t\t\tcodeword length[%d..%d]:\t%d\n",
+	      current_entry, current_entry + number - 1, current_length);
+#endif
+      current_entry += number;
+      if (current_entry > codebook_entries) {
+	fprintf(stderr, "Vorbis codebook parsing error: current_entry %d > codebook_entries %d!\n", current_entry, codebook_entries);
+	return False;
+      }
+      ++current_length;
+    } while (current_entry < codebook_entries);
+  }
+
+  unsigned codebook_lookup_type = bv.getBits(4);
+#ifdef DEBUG_SETUP_HEADER
+  fprintf(stderr, "\t\t\tcodebook_lookup_type: %d\n", codebook_lookup_type);
+#endif
+  if (codebook_lookup_type > 2) {
+    fprintf(stderr, "Vorbis codebook parsing error: codebook_lookup_type %d!\n", codebook_lookup_type);
+    return False;
+  } else if (codebook_lookup_type > 0) { // 1 or 2
+    bv.skipBits(32+32); // "codebook_minimum_value" and "codebook_delta_value"
+    unsigned codebook_value_bits = bv.getBits(4) + 1;
+    bv.skipBits(1); // "codebook_lookup_p"
+    unsigned codebook_lookup_values;
+    if (codebook_lookup_type == 1) {
+      codebook_lookup_values = lookup1_values(codebook_entries, codebook_dimensions);
+    } else { // 2
+      codebook_lookup_values = codebook_entries*codebook_dimensions;
+    }
+
+    bv.skipBits(codebook_lookup_values*codebook_value_bits); // "codebook_multiplicands"
+  }
+
+  return True;
+}
+
+static Boolean parseVorbisSetup_codebooks(LEBitVector& bv) {
+  if (bv.noMoreBits()) return False;
+
+  unsigned vorbis_codebook_count = bv.getBits(8) + 1;
+#ifdef DEBUG_SETUP_HEADER
+  fprintf(stderr, "\tCodebooks: vorbis_codebook_count: %d\n", vorbis_codebook_count);
+#endif
+  for (unsigned i = 0; i < vorbis_codebook_count; ++i) {
+#ifdef DEBUG_SETUP_HEADER
+    fprintf(stderr, "\t\tCodebook %d:\n", i);
+#endif
+    if (!parseVorbisSetup_codebook(bv)) return False;
+  }
+
+  return True;
+}
+
+static Boolean parseVorbisSetup_timeDomainTransforms(LEBitVector& bv) {
+  if (bv.noMoreBits()) return False;
+
+  unsigned vorbis_time_count = bv.getBits(6) + 1;
+#ifdef DEBUG_SETUP_HEADER
+  fprintf(stderr, "\tTime domain transforms: vorbis_time_count: %d\n", vorbis_time_count);
+#endif
+  for (unsigned i = 0; i < vorbis_time_count; ++i) {
+    unsigned val = bv.getBits(16);
+    if (val != 0) {
+      fprintf(stderr, "Vorbis Time domain transforms, read non-zero value %d\n", val);
+      return False;
+    }
+  }
+
+  return True;
+}
+
+static Boolean parseVorbisSetup_floors(LEBitVector& bv) {
+  if (bv.noMoreBits()) return False;
+
+  unsigned vorbis_floor_count = bv.getBits(6) + 1;
+#ifdef DEBUG_SETUP_HEADER
+  fprintf(stderr, "\tFloors: vorbis_floor_count: %d\n", vorbis_floor_count);
+#endif
+  for (unsigned i = 0; i < vorbis_floor_count; ++i) {
+    unsigned floorType = bv.getBits(16);
+    if (floorType == 0) {
+      bv.skipBits(8+16+16+6+8);
+      unsigned floor0_number_of_books = bv.getBits(4) + 1;
+      bv.skipBits(floor0_number_of_books*8);
+    } else if (floorType == 1) {
+      unsigned floor1_partitions = bv.getBits(5);
+
+      unsigned* floor1_partition_class_list = new unsigned[floor1_partitions];
+      unsigned maximum_class = 0, j;
+      for (j = 0; j < floor1_partitions; ++j) {
+	floor1_partition_class_list[j] = bv.getBits(4);
+	if (floor1_partition_class_list[j] > maximum_class) maximum_class = floor1_partition_class_list[j];
+      }
+
+      unsigned* floor1_class_dimensions = new unsigned[maximum_class + 1];
+      for (j = 0; j <= maximum_class; ++j) {
+	floor1_class_dimensions[j] = bv.getBits(3) + 1;
+	unsigned floor1_class_subclasses = bv.getBits(2);
+	if (floor1_class_subclasses != 0) {
+	  bv.skipBits(8); // "floor1_class_masterbooks[j]"
+	}
+
+	unsigned twoExp_floor1_class_subclasses = 1 << floor1_class_subclasses;
+	bv.skipBits(twoExp_floor1_class_subclasses*8); // "floor1_subclass_books[j][*]"
+      }
+
+      bv.skipBits(2); // "floor1_multiplier"
+      unsigned rangebits = bv.getBits(4);
+      for (j = 0; j < floor1_partitions; ++j) {
+	unsigned current_class_number = floor1_partition_class_list[j];
+	bv.skipBits(floor1_class_dimensions[current_class_number] * rangebits);
+      }
+
+      delete[] floor1_partition_class_list;
+      delete[] floor1_class_dimensions;
+    } else { // floorType > 1
+      fprintf(stderr, "Vorbis Floors, read bad floor type %d\n", floorType);
+      return False;
+    }
+  }
+
+  return True;
+}
+
+static Boolean parseVorbisSetup_residues(LEBitVector& bv) {
+  if (bv.noMoreBits()) return False;
+
+  unsigned vorbis_residue_count = bv.getBits(6) + 1;
+#ifdef DEBUG_SETUP_HEADER
+  fprintf(stderr, "\tResidues: vorbis_residue_count: %d\n", vorbis_residue_count);
+#endif
+  for (unsigned i = 0; i < vorbis_residue_count; ++i) {
+    unsigned vorbis_residue_type = bv.getBits(16);
+    if (vorbis_residue_type > 2) {
+      fprintf(stderr, "Vorbis Residues, read bad vorbis_residue_type: %d\n", vorbis_residue_type);
+      return False;
+    } else {
+      bv.skipBits(24+24+24); // "residue_begin", "residue_end", "residue_partition_size"
+      unsigned residue_classifications = bv.getBits(6) + 1;
+      bv.skipBits(8); // "residue_classbook"
+
+      u_int8_t* residue_cascade = new u_int8_t[residue_classifications];
+      unsigned j;
+      for (j = 0; j < residue_classifications; ++j) {
+	u_int8_t high_bits = 0;
+	u_int8_t low_bits = bv.getBits(3);
+	unsigned bitflag = bv.getBits(1);
+	if (bitflag) {
+	  high_bits = bv.getBits(5);
+	}
+
+	residue_cascade[j] = (high_bits<<3) | low_bits;
+      }
+
+      for (j = 0; j < residue_classifications; ++j) {
+	u_int8_t const cascade = residue_cascade[j];
+	u_int8_t mask = 0x80;
+	while (mask != 0) {
+	  if ((cascade&mask) != 0) bv.skipBits(8); // "residue_books[j][*]"
+	  mask >>= 1;
+	}
+      }
+
+      delete[] residue_cascade;
+    }
+  }
+
+  return True;
+}
+
+static Boolean parseVorbisSetup_mappings(LEBitVector& bv, unsigned audio_channels) {
+  if (bv.noMoreBits()) return False;
+
+  unsigned vorbis_mapping_count = bv.getBits(6) + 1;
+#ifdef DEBUG_SETUP_HEADER
+  fprintf(stderr, "\tMappings: vorbis_mapping_count: %d\n", vorbis_mapping_count);
+#endif
+  for (unsigned i = 0; i < vorbis_mapping_count; ++i) {
+    unsigned vorbis_mapping_type = bv.getBits(16);
+    if (vorbis_mapping_type != 0) {
+      fprintf(stderr, "Vorbis Mappings, read bad vorbis_mapping_type: %d\n", vorbis_mapping_type);
+      return False;
+    }
+
+    unsigned vorbis_mapping_submaps = 1;
+    if (bv.getBits(1)) vorbis_mapping_submaps = bv.getBits(4) + 1;
+
+    if (bv.getBits(1)) { // "square polar channel mapping is in use"
+      unsigned vorbis_mapping_coupling_steps = bv.getBits(8) + 1;
+
+      for (unsigned j = 0; j < vorbis_mapping_coupling_steps; ++j) {
+	unsigned ilog_audio_channels_minus_1 = ilog(audio_channels - 1);
+	bv.skipBits(2*ilog_audio_channels_minus_1); // "vorbis_mapping_magnitude", "vorbis_mapping_angle"
+      }
+    }
+
+    unsigned reserved = bv.getBits(2);
+    if (reserved != 0) {
+      fprintf(stderr, "Vorbis Mappings, read bad 'reserved' field\n");
+      return False;
+    }
+
+    if (vorbis_mapping_submaps > 1) {
+      for (unsigned j = 0; j < audio_channels; ++j) {
+	unsigned vorbis_mapping_mux = bv.getBits(4);
+
+	fprintf(stderr, "\t\t\t\tvorbis_mapping_mux[%d]: %d\n", j, vorbis_mapping_mux);
+	if (vorbis_mapping_mux >= vorbis_mapping_submaps) {
+	  fprintf(stderr, "Vorbis Mappings, read bad \"vorbis_mapping_mux\" %d (>= \"vorbis_mapping_submaps\" %d)\n", vorbis_mapping_mux, vorbis_mapping_submaps);
+	  return False;
+	}
+      }
+    }
+
+    bv.skipBits(vorbis_mapping_submaps*(8+8+8)); // "the floor and residue numbers"
+  }
+
+  return True;
+}
+
+static Boolean parseVorbisSetup_modes(LEBitVector& bv, OggTrack* track) {
+  if (bv.noMoreBits()) return False;
+
+  unsigned vorbis_mode_count = bv.getBits(6) + 1;
+  unsigned ilog_vorbis_mode_count_minus_1 = ilog(vorbis_mode_count - 1);
+#ifdef DEBUG_SETUP_HEADER
+  fprintf(stderr, "\tModes: vorbis_mode_count: %d (ilog(%d-1):%d)\n",
+	  vorbis_mode_count, vorbis_mode_count, ilog_vorbis_mode_count_minus_1);
+#endif
+  track->vtoHdrs.vorbis_mode_count = vorbis_mode_count;
+  track->vtoHdrs.ilog_vorbis_mode_count_minus_1 = ilog_vorbis_mode_count_minus_1;
+  track->vtoHdrs.vorbis_mode_blockflag = new u_int8_t[vorbis_mode_count];
+
+  for (unsigned i = 0; i < vorbis_mode_count; ++i) {
+    track->vtoHdrs.vorbis_mode_blockflag[i] = (u_int8_t)bv.getBits(1);
+#ifdef DEBUG_SETUP_HEADER
+    fprintf(stderr, "\t\tMode %d: vorbis_mode_blockflag: %d\n", i, track->vtoHdrs.vorbis_mode_blockflag[i]);
+#endif
+    bv.skipBits(16+16+8); // "vorbis_mode_windowtype", "vorbis_mode_transformtype", "vorbis_mode_mapping"
+  }
+
+  return True;
+}
+
+static Boolean parseVorbisSetupHeader(OggTrack* track, u_int8_t const* p, unsigned headerSize) {
+  LEBitVector bv(p, headerSize);
+  do {
+    if (!parseVorbisSetup_codebooks(bv)) break;
+    if (!parseVorbisSetup_timeDomainTransforms(bv)) break;
+    if (!parseVorbisSetup_floors(bv)) break;
+    if (!parseVorbisSetup_residues(bv)) break;
+    if (!parseVorbisSetup_mappings(bv, track->numChannels)) break;
+    if (!parseVorbisSetup_modes(bv, track)) break;
+    unsigned framingFlag = bv.getBits(1);
+    if (framingFlag == 0) {
+      fprintf(stderr, "Vorbis \"setup\" header did not end with a 'framing flag'!\n");
+      break;
+    }
+
+    return True;
+  } while (0);
+
+  // An error occurred:
+  return False;
+}
+
+#ifdef DEBUG
+#define CHECK_PTR if (p >= pEnd) return False
+#define printComment(p, len) do { for (unsigned k = 0; k < len; ++k) { CHECK_PTR; fprintf(stderr, "%c", *p++); } } while (0)
+#endif
+
+static Boolean validateCommentHeader(u_int8_t const *p, unsigned headerSize,
+				     unsigned isOpus = 0) {
+  if (headerSize < 15+isOpus) { // need 7+isOpus + 4(vendor_length) + 4(user_comment_list_length)
+    fprintf(stderr, "\"comment\" header is too short (%d bytes)\n", headerSize);
+    return False;
+  }
+      
+#ifdef DEBUG
+  u_int8_t const* pEnd = &p[headerSize];
+  p += 7+isOpus;
+
+  u_int32_t vendor_length = (p[3]<<24)|(p[2]<<16)|(p[1]<<8)|p[0]; p += 4;
+  fprintf(stderr, "\tvendor_string:");
+  printComment(p, vendor_length);
+  fprintf(stderr, "\n");
+  
+  u_int32_t user_comment_list_length = (p[3]<<24)|(p[2]<<16)|(p[1]<<8)|p[0]; p += 4;
+  for (unsigned i = 0; i < user_comment_list_length; ++i) {
+    CHECK_PTR; u_int32_t length = (p[3]<<24)|(p[2]<<16)|(p[1]<<8)|p[0]; p += 4;
+    fprintf(stderr, "\tuser_comment[%d]:", i);
+    printComment(p, length);
+    fprintf(stderr, "\n");
+  }
+#endif
+
+  return True;
+}
+
+static unsigned blocksizeFromExponent(unsigned exponent) {
+  unsigned result = 1;
+  for (unsigned i = 0; i < exponent; ++i) result = 2*result;
+  return result;
+}
+
+Boolean OggFileParser::validateHeader(OggTrack* track, u_int8_t const* p, unsigned headerSize) {
+  // Assert: headerSize >= 7 (because we've already checked "<packet_type>XXXXXX" or "OpusXXXX")
+  if (strcmp(track->mimeType, "audio/VORBIS") == 0) {
+    u_int8_t const firstByte = p[0]; 
+
+    if (firstByte == 1) { // "identification" header
+      if (headerSize < 30) {
+	fprintf(stderr, "Vorbis \"identification\" header is too short (%d bytes)\n", headerSize);
+	return False;
+      } else if ((p[29]&0x1) != 1) {
+	fprintf(stderr, "Vorbis \"identification\" header: 'framing_flag' is not set\n");
+	return False;
+      }
+      
+      p += 7;
+      u_int32_t vorbis_version = (p[3]<<24)|(p[2]<<16)|(p[1]<<8)|p[0]; p += 4;
+      if (vorbis_version != 0) {
+	fprintf(stderr, "Vorbis \"identification\" header has a bad 'vorbis_version': 0x%08x\n", vorbis_version);
+	return False;
+      }
+      
+      u_int8_t audio_channels = *p++;
+      if (audio_channels == 0) {
+	fprintf(stderr, "Vorbis \"identification\" header: 'audio_channels' is 0!\n");
+	return False;
+      }
+      track->numChannels = audio_channels;
+      
+      u_int32_t audio_sample_rate = (p[3]<<24)|(p[2]<<16)|(p[1]<<8)|p[0]; p += 4;
+      if (audio_sample_rate == 0) {
+	fprintf(stderr, "Vorbis \"identification\" header: 'audio_sample_rate' is 0!\n");
+	return False;
+      }
+      track->samplingFrequency = audio_sample_rate;
+      
+      p += 4; // skip over 'bitrate_maximum'
+      u_int32_t bitrate_nominal = (p[3]<<24)|(p[2]<<16)|(p[1]<<8)|p[0]; p += 4;
+      if (bitrate_nominal > 0) track->estBitrate = (bitrate_nominal+500)/1000; // round
+      
+      p += 4; // skip over 'bitrate_maximum'
+      
+      // Note the two 'block sizes' (samples per packet), and their durations in microseconds:
+      u_int8_t blocksizeBits = *p++;
+      unsigned& blocksize_0 = track->vtoHdrs.blocksize[0]; // alias
+      unsigned& blocksize_1 = track->vtoHdrs.blocksize[1]; // alias
+      blocksize_0 = blocksizeFromExponent(blocksizeBits&0x0F);
+      blocksize_1 = blocksizeFromExponent(blocksizeBits>>4);
+      
+      double uSecsPerSample = 1000000.0/(track->samplingFrequency*2);
+          // Why the "2"?  I don't know, but it seems to be necessary
+      track->vtoHdrs.uSecsPerPacket[0] = (unsigned)(uSecsPerSample*blocksize_0);
+      track->vtoHdrs.uSecsPerPacket[1] = (unsigned)(uSecsPerSample*blocksize_1);
+#ifdef DEBUG
+      fprintf(stderr, "\t%u Hz, %u-channel, %u kbps (est), block sizes: %u,%u (%u,%u us)\n",
+	      track->samplingFrequency, track->numChannels, track->estBitrate,
+	      blocksize_0, blocksize_1,
+	      track->vtoHdrs.uSecsPerPacket[0], track->vtoHdrs.uSecsPerPacket[1]);
+#endif
+      // To be valid, "blocksize_0" must be <= "blocksize_1", and both must be in [64,8192]:
+      if (!(blocksize_0 <= blocksize_1 && blocksize_0 >= 64 && blocksize_1 <= 8192)) {
+	fprintf(stderr, "Invalid Vorbis \"blocksize_0\" (%d) and/or \"blocksize_1\" (%d)!\n",
+		blocksize_0, blocksize_1);
+	return False;
+      }
+    } else if (firstByte == 3) { // "comment" header
+      if (!validateCommentHeader(p, headerSize)) return False;
+    } else if (firstByte == 5) { // "setup" header
+      // Parse the "setup" header to get the values that we want:
+      // "vorbis_mode_count", and "vorbis_mode_blockflag" for each mode.  Unfortunately these come
+      // near the end of the header, so we have to parse lots of other crap first.
+      p += 7;
+      if (!parseVorbisSetupHeader(track, p, headerSize)) {
+	fprintf(stderr, "Failed to parse Vorbis \"setup\" header!\n");
+	return False;
+      }
+    }
+  } else if (strcmp(track->mimeType, "video/THEORA") == 0) {
+    u_int8_t const firstByte = p[0]; 
+
+    if (firstByte == 0x80) { // "identification" header
+      if (headerSize < 42) {
+	fprintf(stderr, "Theora \"identification\" header is too short (%d bytes)\n", headerSize);
+	return False;
+      } else if ((p[41]&0x7) != 0) {
+	fprintf(stderr, "Theora \"identification\" header: 'res' bits are non-zero\n");
+	return False;
+      }
+
+      track->vtoHdrs.KFGSHIFT = ((p[40]&3)<<3) | (p[41]>>5);
+      u_int32_t FRN = (p[22]<<24) | (p[23]<<16) | (p[24]<<8) | p[25]; // Frame rate numerator
+      u_int32_t FRD = (p[26]<<24) | (p[27]<<16) | (p[28]<<8) | p[29]; // Frame rate numerator
+#ifdef DEBUG
+      fprintf(stderr, "\tKFGSHIFT %d, Frame rate numerator %d, Frame rate denominator %d\n", track->vtoHdrs.KFGSHIFT, FRN, FRD);
+#endif
+      if (FRN == 0 || FRD == 0) {
+	fprintf(stderr, "Theora \"identification\" header: Bad FRN and/or FRD values: %d, %d\n", FRN, FRD);
+	return False;
+      }
+      track->vtoHdrs.uSecsPerFrame = (unsigned)((1000000.0*FRD)/FRN);
+#ifdef DEBUG
+      fprintf(stderr, "\t\t=> %u microseconds per frame\n", track->vtoHdrs.uSecsPerFrame);
+#endif
+    } else if (firstByte == 0x81) { // "comment" header
+      if (!validateCommentHeader(p, headerSize)) return False;
+    } else if (firstByte == 0x82) { // "setup" header
+      // We don't care about the contents of the Theora "setup" header; just assume it's valid
+    }
+  } else { // Opus audio
+    if (strncmp((char const*)p, "OpusHead", 8) == 0) { // "identification" header
+      // Just check the size, and the 'major' number of the version byte:
+      if (headerSize < 19 || (p[8]&0xF0) != 0) return False;
+    } else { // comment header
+      if (!validateCommentHeader(p, headerSize, 1/*isOpus*/)) return False;
+    }
+  }
+  
+  return True;
+}
+
+void OggFileParser::parseAndDeliverPages() {
+#ifdef DEBUG
+  fprintf(stderr, "parsing and delivering data\n");
+#endif
+  while (parseAndDeliverPage()) {}
+}
+
+Boolean OggFileParser::parseAndDeliverPage() {
+  u_int8_t header_type_flag;
+  u_int32_t bitstream_serial_number;
+  parseStartOfPage(header_type_flag, bitstream_serial_number);
+
+  OggDemuxedTrack* demuxedTrack = fOurDemux->lookupDemuxedTrack(bitstream_serial_number);
+  if (demuxedTrack == NULL) { // this track is not being read
+#ifdef DEBUG
+    fprintf(stderr, "\tIgnoring page from unread track; skipping %d remaining packet data bytes\n",
+	    fPacketSizeTable->totSizes);
+#endif
+    skipBytes(fPacketSizeTable->totSizes);
+    return True;
+  } else if (fPacketSizeTable->totSizes == 0) {
+    // This page is empty (has no packets).  Skip it and continue
+#ifdef DEBUG
+    fprintf(stderr, "\t[track: %s] Skipping empty page\n", demuxedTrack->MIMEtype());
+#endif
+    return True;
+  }
+
+  // Start delivering packets next:
+  demuxedTrack->fCurrentPageIsContinuation = (header_type_flag&0x01) != 0;
+  fCurrentTrackNumber = bitstream_serial_number;
+  fCurrentParseState = DELIVERING_PACKET_WITHIN_PAGE;
+  saveParserState();
+  return False;
+}
+
+Boolean OggFileParser::deliverPacketWithinPage() {
+  OggDemuxedTrack* demuxedTrack = fOurDemux->lookupDemuxedTrack(fCurrentTrackNumber);
+  if (demuxedTrack == NULL) return False; // should not happen
+
+  unsigned packetNum = fPacketSizeTable->nextPacketNumToDeliver;
+  unsigned packetSize = fPacketSizeTable->size[packetNum];
+
+  if (!demuxedTrack->isCurrentlyAwaitingData()) {
+    // Someone has been reading this stream, but isn't right now.
+    // We can't deliver this frame until he asks for it, so punt for now.
+    // The next time he asks for a frame, he'll get it.
+#ifdef DEBUG
+    fprintf(stderr, "\t[track: %s] Deferring delivery of packet %d (%d bytes%s)\n",
+	    demuxedTrack->MIMEtype(), packetNum, packetSize,
+	    packetNum == fPacketSizeTable->numCompletedPackets ? " (incomplete)" : "");
+#endif
+    return True;
+  }
+
+  // Deliver the next packet:
+#ifdef DEBUG
+  fprintf(stderr, "\t[track: %s] Delivering packet %d (%d bytes%s)\n", demuxedTrack->MIMEtype(),
+	  packetNum, packetSize,
+	  packetNum == fPacketSizeTable->numCompletedPackets ? " (incomplete)" : "");
+#endif
+  unsigned numBytesDelivered
+    = packetSize < demuxedTrack->maxSize() ? packetSize : demuxedTrack->maxSize();
+  getBytes(demuxedTrack->to(), numBytesDelivered);
+  u_int8_t firstByte = numBytesDelivered > 0 ? demuxedTrack->to()[0] : 0x00;
+  u_int8_t secondByte = numBytesDelivered > 1 ? demuxedTrack->to()[1] : 0x00;
+  demuxedTrack->to() += numBytesDelivered;
+
+  if (demuxedTrack->fCurrentPageIsContinuation) { // the previous page's read was incomplete
+    demuxedTrack->frameSize() += numBytesDelivered;
+  } else {
+    // This is the first delivery for this "doGetNextFrame()" call.
+    demuxedTrack->frameSize() = numBytesDelivered;
+  }
+  if (packetSize > demuxedTrack->maxSize()) {
+    demuxedTrack->numTruncatedBytes() += packetSize - demuxedTrack->maxSize();
+  }
+  demuxedTrack->maxSize() -= numBytesDelivered;
+
+  // Figure out the duration and presentation time of this frame.
+  unsigned durationInMicroseconds;
+  OggTrack* track = fOurFile.lookup(demuxedTrack->fOurTrackNumber);
+
+  if (strcmp(track->mimeType, "audio/VORBIS") == 0) {
+    if ((firstByte&0x01) != 0) { // This is a header packet
+      durationInMicroseconds = 0;
+    } else { // This is a data packet.
+      // Parse the first byte to figure out its duration.
+      // Extract the next "track->vtoHdrs.ilog_vorbis_mode_count_minus_1" bits of the first byte:
+      u_int8_t const mask = 0xFE<<(track->vtoHdrs.ilog_vorbis_mode_count_minus_1);
+      u_int8_t const modeNumber = (firstByte&~mask)>>1;
+      if (modeNumber >= track->vtoHdrs.vorbis_mode_count) {
+	fprintf(stderr, "Error: Bad mode number %d (>= vorbis_mode_count %d) in Vorbis packet!\n",
+		modeNumber, track->vtoHdrs.vorbis_mode_count);
+	durationInMicroseconds = 0;
+      } else {
+	unsigned blockNumber = track->vtoHdrs.vorbis_mode_blockflag[modeNumber];
+	durationInMicroseconds = track->vtoHdrs.uSecsPerPacket[blockNumber];
+      }
+    }
+  } else if (strcmp(track->mimeType, "video/THEORA") == 0) {
+    if ((firstByte&0x80) != 0) { // This is a header packet
+      durationInMicroseconds = 0;
+    } else { // This is a data packet.
+      durationInMicroseconds = track->vtoHdrs.uSecsPerFrame;
+    }
+  } else { // "audio/OPUS"
+    if (firstByte == 0x4F/*'O'*/ && secondByte == 0x70/*'p*/) { // This is a header packet
+      durationInMicroseconds = 0;
+    } else { // This is a data packet.
+      // Parse the first byte to figure out the duration of each frame, and then (if necessary)
+      // parse the second byte to figure out how many frames are in this packet:
+      u_int8_t config = firstByte >> 3;
+      u_int8_t c = firstByte & 0x03;
+      unsigned const configDuration[32] = { // in microseconds
+	10000, 20000, 40000, 60000, // config 0..3
+	10000, 20000, 40000, 60000, // config 4..7
+	10000, 20000, 40000, 60000, // config 8..11
+	10000, 20000, // config 12..13
+	10000, 20000, // config 14..15
+	2500, 5000, 10000, 20000, // config 16..19
+	2500, 5000, 10000, 20000, // config 20..23
+	2500, 5000, 10000, 20000, // config 24..27
+	2500, 5000, 10000, 20000  // config 28..31
+      };
+      unsigned const numFramesInPacket = c == 0 ? 1 : c == 3 ? (secondByte&0x3F) : 2;
+      durationInMicroseconds = numFramesInPacket*configDuration[config];
+    }
+  }
+
+  if (demuxedTrack->nextPresentationTime().tv_sec == 0 && demuxedTrack->nextPresentationTime().tv_usec == 0) {
+    // This is the first delivery.  Initialize "demuxedTrack->nextPresentationTime()":
+    gettimeofday(&demuxedTrack->nextPresentationTime(), NULL);
+  }
+  demuxedTrack->presentationTime() = demuxedTrack->nextPresentationTime();
+  demuxedTrack->durationInMicroseconds() = durationInMicroseconds;
+
+  demuxedTrack->nextPresentationTime().tv_usec += durationInMicroseconds;
+  while (demuxedTrack->nextPresentationTime().tv_usec >= 1000000) {
+    ++demuxedTrack->nextPresentationTime().tv_sec;
+    demuxedTrack->nextPresentationTime().tv_usec -= 1000000;    
+  }
+  saveParserState();
+
+  // And check whether there's a next packet in this page:
+  if (packetNum == fPacketSizeTable->numCompletedPackets) {
+    // This delivery was for an incomplete packet, at the end of the page.
+    // Return without completing delivery:
+    fCurrentParseState = PARSING_AND_DELIVERING_PAGES;
+    return False;
+  }
+ 
+  if (packetNum < fPacketSizeTable->numCompletedPackets-1
+      || fPacketSizeTable->lastPacketIsIncomplete) {
+    // There is at least one more packet (possibly incomplete) left in this packet.
+    // Deliver it next:
+    ++fPacketSizeTable->nextPacketNumToDeliver;
+  } else {
+    // Start parsing a new page next:
+    fCurrentParseState = PARSING_AND_DELIVERING_PAGES;
+  }
+  
+  FramedSource::afterGetting(demuxedTrack); // completes delivery
+  return True;
+}
+
+void OggFileParser::parseStartOfPage(u_int8_t& header_type_flag,
+				     u_int32_t& bitstream_serial_number) {
+  saveParserState();
+  // First, make sure we start with the 'capture_pattern': 0x4F676753 ('OggS'):
+  while (test4Bytes() != 0x4F676753) {
+    skipBytes(1);
+    saveParserState(); // ensures forward progress through the file
+  }
+  skipBytes(4);
+#ifdef DEBUG
+  fprintf(stderr, "\nSaw Ogg page header:\n");
+#endif
+
+  u_int8_t stream_structure_version = get1Byte();
+  if (stream_structure_version != 0) {
+    fprintf(stderr, "Saw page with unknown Ogg file version number: 0x%02x\n", stream_structure_version);
+  }
+
+  header_type_flag = get1Byte();
+#ifdef DEBUG
+  fprintf(stderr, "\theader_type_flag: 0x%02x (", header_type_flag);
+  if (header_type_flag&0x01) fprintf(stderr, "continuation ");
+  if (header_type_flag&0x02) fprintf(stderr, "bos ");
+  if (header_type_flag&0x04) fprintf(stderr, "eos ");
+  fprintf(stderr, ")\n");
+#endif  
+
+  u_int32_t granule_position1 = byteSwap(get4Bytes());
+  u_int32_t granule_position2 = byteSwap(get4Bytes());
+  bitstream_serial_number = byteSwap(get4Bytes());
+  u_int32_t page_sequence_number = byteSwap(get4Bytes());
+  u_int32_t CRC_checksum = byteSwap(get4Bytes());
+  u_int8_t number_page_segments = get1Byte();
+#ifdef DEBUG
+  fprintf(stderr, "\tgranule_position 0x%08x%08x, bitstream_serial_number 0x%08x, page_sequence_number 0x%08x, CRC_checksum 0x%08x, number_page_segments %d\n", granule_position2, granule_position1, bitstream_serial_number, page_sequence_number, CRC_checksum, number_page_segments);
+#else
+  // Dummy statements to prevent 'unused variable' compiler warnings:
+#define DUMMY_STATEMENT(x) do {x = x;} while (0)
+  DUMMY_STATEMENT(granule_position1);
+  DUMMY_STATEMENT(granule_position2);
+  DUMMY_STATEMENT(page_sequence_number);
+  DUMMY_STATEMENT(CRC_checksum);
+#endif  
+  
+  // Look at the "segment_table" to count the sizes of the packets in this page:
+  delete fPacketSizeTable/*if any*/; fPacketSizeTable = new PacketSizeTable(number_page_segments);
+  u_int8_t lacing_value = 0;
+#ifdef DEBUG
+  fprintf(stderr, "\tsegment_table\n");
+#endif
+  for (unsigned i = 0; i < number_page_segments; ++i) {
+    lacing_value = get1Byte();
+#ifdef DEBUG
+    fprintf(stderr, "\t\t%d:\t%d", i, lacing_value);
+#endif
+    fPacketSizeTable->totSizes += lacing_value;
+    fPacketSizeTable->size[fPacketSizeTable->numCompletedPackets] += lacing_value;
+    if (lacing_value < 255) {
+      // This completes a packet:
+#ifdef DEBUG
+      fprintf(stderr, " (->%d)", fPacketSizeTable->size[fPacketSizeTable->numCompletedPackets]);
+#endif
+      ++fPacketSizeTable->numCompletedPackets;
+    }
+#ifdef DEBUG
+    fprintf(stderr, "\n");
+#endif
+  }
+
+  fPacketSizeTable->lastPacketIsIncomplete = lacing_value == 255;
+}
diff --git a/liveMedia/OggFileParser.hh b/liveMedia/OggFileParser.hh
new file mode 100644
index 0000000..e0592fc
--- /dev/null
+++ b/liveMedia/OggFileParser.hh
@@ -0,0 +1,91 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A parser for an Ogg file
+// C++ header
+
+#ifndef _OGG_FILE_PARSER_HH
+
+#ifndef _STREAM_PARSER_HH
+#include "StreamParser.hh"
+#endif
+#ifndef _OGG_FILE_HH
+#include "OggFile.hh"
+#endif
+
+// An enum representing the current state of the parser:
+enum OggParseState {
+  PARSING_START_OF_FILE,
+  PARSING_AND_DELIVERING_PAGES,
+  DELIVERING_PACKET_WITHIN_PAGE
+};
+
+// A structure that counts the sizes of 'packets' given by each page's "segment_table":
+class PacketSizeTable {
+public:
+  PacketSizeTable(unsigned number_page_segments);
+  ~PacketSizeTable();
+
+  unsigned numCompletedPackets; // will be <= "number_page_segments"
+  unsigned* size; // an array of sizes of each of the packets
+  unsigned totSizes;
+  unsigned nextPacketNumToDeliver;
+  Boolean lastPacketIsIncomplete; // iff the last segment's 'lacing' was 255
+};
+
+class OggFileParser: public StreamParser {
+public:
+  OggFileParser(OggFile& ourFile, FramedSource* inputSource,
+		FramedSource::onCloseFunc* onEndFunc, void* onEndClientData,
+		OggDemux* ourDemux = NULL);
+  virtual ~OggFileParser();
+
+  // StreamParser 'client continue' function:
+  static void continueParsing(void* clientData, unsigned char* ptr, unsigned size, struct timeval presentationTime);
+  void continueParsing();
+
+private:
+  Boolean needHeaders() { return fNumUnfulfilledTracks > 0; }
+
+  // Parsing functions:
+  Boolean parse(); // returns True iff we have finished parsing all BOS pages (on initialization)
+
+  Boolean parseStartOfFile();
+  u_int8_t parseInitialPage(); // returns the 'header_type_flag' byte
+  void parseAndDeliverPages();
+  Boolean parseAndDeliverPage();
+  Boolean deliverPacketWithinPage();
+  void parseStartOfPage(u_int8_t& header_type_flag, u_int32_t& bitstream_serial_number);
+
+  Boolean validateHeader(OggTrack* track, u_int8_t const* p, unsigned headerSize);
+
+private:
+  // General state for parsing:
+  OggFile& fOurFile;
+  FramedSource* fInputSource;
+  FramedSource::onCloseFunc* fOnEndFunc;
+  void* fOnEndClientData;
+  OggDemux* fOurDemux;
+  OggParseState fCurrentParseState;
+
+  unsigned fNumUnfulfilledTracks;
+  PacketSizeTable* fPacketSizeTable;
+  u_int32_t fCurrentTrackNumber;
+  u_int8_t* fSavedPacket; // used to temporarily save a copy of a 'packet' from a page
+};
+
+#endif
diff --git a/liveMedia/OggFileServerDemux.cpp b/liveMedia/OggFileServerDemux.cpp
new file mode 100644
index 0000000..31f8980
--- /dev/null
+++ b/liveMedia/OggFileServerDemux.cpp
@@ -0,0 +1,109 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A server demultiplexor for a Ogg file
+// Implementation
+
+#include "OggFileServerDemux.hh"
+#include "OggFileServerMediaSubsession.hh"
+
+void OggFileServerDemux
+::createNew(UsageEnvironment& env, char const* fileName,
+	    onCreationFunc* onCreation, void* onCreationClientData) {
+  (void)new OggFileServerDemux(env, fileName,
+			       onCreation, onCreationClientData);
+}
+
+ServerMediaSubsession* OggFileServerDemux::newServerMediaSubsession() {
+  u_int32_t dummyResultTrackNumber;
+  return newServerMediaSubsession(dummyResultTrackNumber);
+}
+
+ServerMediaSubsession* OggFileServerDemux
+::newServerMediaSubsession(u_int32_t& resultTrackNumber) {
+  resultTrackNumber = 0;
+
+  OggTrack* nextTrack = fIter->next();
+  if (nextTrack == NULL) return NULL;
+
+  return newServerMediaSubsessionByTrackNumber(nextTrack->trackNumber);
+}
+
+ServerMediaSubsession* OggFileServerDemux
+::newServerMediaSubsessionByTrackNumber(u_int32_t trackNumber) {
+  OggTrack* track = fOurOggFile->lookup(trackNumber);
+  if (track == NULL) return NULL;
+
+  ServerMediaSubsession* result = OggFileServerMediaSubsession::createNew(*this, track);
+  if (result != NULL) {
+#ifdef DEBUG
+    fprintf(stderr, "Created 'ServerMediaSubsession' object for track #%d: (%s)\n", track->trackNumber, track->mimeType);
+#endif
+  }
+
+  return result;
+}
+
+FramedSource* OggFileServerDemux::newDemuxedTrack(unsigned clientSessionId, u_int32_t trackNumber) {
+  OggDemux* demuxToUse = NULL;
+
+  if (clientSessionId != 0 && clientSessionId == fLastClientSessionId) {
+    demuxToUse = fLastCreatedDemux; // use the same demultiplexor as before
+      // Note: This code relies upon the fact that the creation of streams for different
+      // client sessions do not overlap - so all demuxed tracks are created for one "OggDemux" at a time.
+      // Also, the "clientSessionId != 0" test is a hack, because 'session 0' is special; its audio and video streams
+      // are created and destroyed one-at-a-time, rather than both streams being
+      // created, and then (later) both streams being destroyed (as is the case
+      // for other ('real') session ids).  Because of this, a separate demultiplexor is used for each 'session 0' track.
+  }
+
+  if (demuxToUse == NULL) demuxToUse = fOurOggFile->newDemux();
+
+  fLastClientSessionId = clientSessionId;
+  fLastCreatedDemux = demuxToUse;
+
+  return demuxToUse->newDemuxedTrackByTrackNumber(trackNumber);
+}
+
+OggFileServerDemux
+::OggFileServerDemux(UsageEnvironment& env, char const* fileName,
+		     onCreationFunc* onCreation, void* onCreationClientData)
+  : Medium(env),
+    fFileName(fileName), fOnCreation(onCreation), fOnCreationClientData(onCreationClientData),
+    fIter(NULL/*until the OggFile is created*/),
+    fLastClientSessionId(0), fLastCreatedDemux(NULL) {
+  OggFile::createNew(env, fileName, onOggFileCreation, this);
+}
+
+OggFileServerDemux::~OggFileServerDemux() {
+  Medium::close(fOurOggFile);
+
+  delete fIter;
+}
+
+void OggFileServerDemux::onOggFileCreation(OggFile* newFile, void* clientData) {
+  ((OggFileServerDemux*)clientData)->onOggFileCreation(newFile);
+}
+
+void OggFileServerDemux::onOggFileCreation(OggFile* newFile) {
+  fOurOggFile = newFile;
+
+  fIter = new OggTrackTableIterator(fOurOggFile->trackTable());
+
+  // Now, call our own creation notification function:
+  if (fOnCreation != NULL) (*fOnCreation)(this, fOnCreationClientData);
+}
diff --git a/liveMedia/OggFileServerMediaSubsession.cpp b/liveMedia/OggFileServerMediaSubsession.cpp
new file mode 100644
index 0000000..acda9b0
--- /dev/null
+++ b/liveMedia/OggFileServerMediaSubsession.cpp
@@ -0,0 +1,54 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
+// on demand, from a track within an Ogg file.
+// Implementation
+
+#include "OggFileServerMediaSubsession.hh"
+#include "OggDemuxedTrack.hh"
+#include "FramedFilter.hh"
+
+OggFileServerMediaSubsession* OggFileServerMediaSubsession
+::createNew(OggFileServerDemux& demux, OggTrack* track) {
+  return new OggFileServerMediaSubsession(demux, track);
+}
+
+OggFileServerMediaSubsession
+::OggFileServerMediaSubsession(OggFileServerDemux& demux, OggTrack* track)
+  : FileServerMediaSubsession(demux.envir(), demux.fileName(), False),
+    fOurDemux(demux), fTrack(track), fNumFiltersInFrontOfTrack(0) {
+}
+
+OggFileServerMediaSubsession::~OggFileServerMediaSubsession() {
+}
+
+FramedSource* OggFileServerMediaSubsession
+::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
+  FramedSource* baseSource = fOurDemux.newDemuxedTrack(clientSessionId, fTrack->trackNumber);
+  if (baseSource == NULL) return NULL;
+  
+  return fOurDemux.ourOggFile()
+    ->createSourceForStreaming(baseSource, fTrack->trackNumber,
+			       estBitrate, fNumFiltersInFrontOfTrack);
+}
+
+RTPSink* OggFileServerMediaSubsession
+::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) {
+  return fOurDemux.ourOggFile()
+    ->createRTPSinkForTrackNumber(fTrack->trackNumber, rtpGroupsock, rtpPayloadTypeIfDynamic);
+}
diff --git a/liveMedia/VP8VideoMatroskaFileServerMediaSubsession.hh b/liveMedia/OggFileServerMediaSubsession.hh
similarity index 55%
rename from liveMedia/VP8VideoMatroskaFileServerMediaSubsession.hh
rename to liveMedia/OggFileServerMediaSubsession.hh
index 5542fb6..fd5516a 100644
--- a/liveMedia/VP8VideoMatroskaFileServerMediaSubsession.hh
+++ b/liveMedia/OggFileServerMediaSubsession.hh
@@ -14,41 +14,40 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from a VP8 Video track within a Matroska file.
+// on demand, from a track within an Ogg file.
 // C++ header
 
-#ifndef _VP8_VIDEO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-#define _VP8_VIDEO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
+#ifndef _OGG_FILE_SERVER_MEDIA_SUBSESSION_HH
+#define _OGG_FILE_SERVER_MEDIA_SUBSESSION_HH
 
 #ifndef _FILE_SERVER_MEDIA_SUBSESSION_HH
 #include "FileServerMediaSubsession.hh"
 #endif
-#ifndef _MATROSKA_FILE_SERVER_DEMUX_HH
-#include "MatroskaFileServerDemux.hh"
+#ifndef _OGG_FILE_SERVER_DEMUX_HH
+#include "OggFileServerDemux.hh"
 #endif
 
-class VP8VideoMatroskaFileServerMediaSubsession: public FileServerMediaSubsession {
+class OggFileServerMediaSubsession: public FileServerMediaSubsession {
 public:
-  static VP8VideoMatroskaFileServerMediaSubsession*
-  createNew(MatroskaFileServerDemux& demux, unsigned trackNumber);
+  static OggFileServerMediaSubsession*
+  createNew(OggFileServerDemux& demux, OggTrack* track);
 
-private:
-  VP8VideoMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber);
-      // called only by createNew();
-  virtual ~VP8VideoMatroskaFileServerMediaSubsession();
+protected:
+  OggFileServerMediaSubsession(OggFileServerDemux& demux, OggTrack* track);
+      // called only by createNew(), or by subclass constructors
+  virtual ~OggFileServerMediaSubsession();
 
-private: // redefined virtual functions
-  virtual float duration() const;
-  virtual void seekStreamSource(FramedSource* inputSource, double& seekNPT, double streamDuration, u_int64_t& numBytes);
+protected: // redefined virtual functions
   virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
 					      unsigned& estBitrate);
   virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
 
-private:
-  MatroskaFileServerDemux& fOurDemux;
-  unsigned fTrackNumber;
+protected:
+  OggFileServerDemux& fOurDemux;
+  OggTrack* fTrack;
+  unsigned fNumFiltersInFrontOfTrack;
 };
 
 #endif
diff --git a/liveMedia/OggFileSink.cpp b/liveMedia/OggFileSink.cpp
new file mode 100644
index 0000000..61e29e5
--- /dev/null
+++ b/liveMedia/OggFileSink.cpp
@@ -0,0 +1,273 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// 'Ogg' File Sink (recording a single media track only)
+// Implementation
+
+#include "OggFileSink.hh"
+#include "OutputFile.hh"
+#include "VorbisAudioRTPSource.hh" // for "parseVorbisOrTheoraConfigStr()"
+#include "MPEG2TransportStreamMultiplexor.hh" // for calculateCRC()
+#include "FramedSource.hh"
+
+OggFileSink* OggFileSink
+::createNew(UsageEnvironment& env, char const* fileName,
+	    unsigned samplingFrequency, char const* configStr,
+	    unsigned bufferSize, Boolean oneFilePerFrame) {
+  do {
+    FILE* fid;
+    char const* perFrameFileNamePrefix;
+    if (oneFilePerFrame) {
+      // Create the fid for each frame
+      fid = NULL;
+      perFrameFileNamePrefix = fileName;
+    } else {
+      // Normal case: create the fid once
+      fid = OpenOutputFile(env, fileName);
+      if (fid == NULL) break;
+      perFrameFileNamePrefix = NULL;
+    }
+
+    return new OggFileSink(env, fid, samplingFrequency, configStr, bufferSize, perFrameFileNamePrefix);
+  } while (0);
+
+  return NULL;
+}
+
+OggFileSink::OggFileSink(UsageEnvironment& env, FILE* fid,
+			 unsigned samplingFrequency, char const* configStr,
+			 unsigned bufferSize, char const* perFrameFileNamePrefix)
+  : FileSink(env, fid, bufferSize, perFrameFileNamePrefix),
+    fSamplingFrequency(samplingFrequency), fConfigStr(configStr),
+    fHaveWrittenFirstFrame(False), fHaveSeenEOF(False),
+    fGranulePosition(0), fGranulePositionAdjustment(0), fPageSequenceNumber(0),
+    fIsTheora(False), fGranuleIncrementPerFrame(1),
+    fAltFrameSize(0), fAltNumTruncatedBytes(0) {
+  fAltBuffer = new unsigned char[bufferSize];
+
+  // Initialize our 'Ogg page header' array with constant values:
+  u_int8_t* p = fPageHeaderBytes;
+  *p++=0x4f; *p++=0x67; *p++=0x67; *p++=0x53; // bytes 0..3: 'capture_pattern': "OggS"
+  *p++=0; // byte 4: 'stream_structure_version': 0
+  *p++=0; // byte 5: 'header_type_flag': set on each write
+  *p++=0; *p++=0; *p++=0; *p++=0; *p++=0; *p++=0; *p++=0; *p++=0;
+      // bytes 6..13: 'granule_position': set on each write
+  *p++=1; *p++=0; *p++=0; *p++=0; // bytes 14..17: 'bitstream_serial_number': 1
+  *p++=0; *p++=0; *p++=0; *p++=0; // bytes 18..21: 'page_sequence_number': set on each write
+  *p++=0; *p++=0; *p++=0; *p++=0; // bytes 22..25: 'CRC_checksum': set on each write
+  *p=0; // byte 26: 'number_page_segments': set on each write
+}
+
+OggFileSink::~OggFileSink() {
+  // We still have the previously-arrived frame, so write it to the file before we end:
+  fHaveSeenEOF = True;
+  OggFileSink::addData(fAltBuffer, fAltFrameSize, fAltPresentationTime);
+
+  delete[] fAltBuffer;
+}
+
+Boolean OggFileSink::continuePlaying() {
+  // Identical to "FileSink::continuePlaying()",
+  // except that we use our own 'on source closure' function:
+  if (fSource == NULL) return False;
+
+  fSource->getNextFrame(fBuffer, fBufferSize,
+			FileSink::afterGettingFrame, this,
+			ourOnSourceClosure, this);
+  return True;
+}
+
+#define PAGE_DATA_MAX_SIZE (255*255)
+
+void OggFileSink::addData(unsigned char const* data, unsigned dataSize,
+			  struct timeval presentationTime) {
+  if (dataSize == 0) return;
+
+  // Set "fGranulePosition" for this frame:
+  if (fIsTheora) {
+    // Special case for Theora: "fGranulePosition" is supposed to be made up of a pair:
+    //   (frame count to last key frame) | (frame count since last key frame)
+    // However, because there appears to be no easy way to figure out which frames are key frames,
+    // we just assume that all frames are key frames.
+    if (!(data[0] >= 0x80 && data[0] <= 0x82)) { // for header pages, "fGranulePosition" remains 0
+      fGranulePosition += fGranuleIncrementPerFrame;
+    }
+  } else {
+    double ptDiff
+      = (presentationTime.tv_sec - fFirstPresentationTime.tv_sec)
+      + (presentationTime.tv_usec - fFirstPresentationTime.tv_usec)/1000000.0;
+    int64_t newGranulePosition
+      = (int64_t)(fSamplingFrequency*ptDiff) + fGranulePositionAdjustment;
+    if (newGranulePosition < fGranulePosition) {
+      // Update "fGranulePositionAdjustment" so that "fGranulePosition" remains monotonic
+      fGranulePositionAdjustment += fGranulePosition - newGranulePosition;
+    } else {
+      fGranulePosition = newGranulePosition;
+    }
+  }
+
+  // Write the frame to the file as a single Ogg 'page' (or perhaps as multiple pages
+  // if it's too big for a single page).  We don't aggregate more than one frame within
+  // an Ogg page because that's not legal for some headers, and because that would make
+  // it difficult for us to properly set the 'eos' (end of stream) flag on the last page.
+
+  // First, figure out how many pages to write here
+  // (a page can contain no more than PAGE_DATA_MAX_SIZE bytes)
+  unsigned numPagesToWrite = dataSize/PAGE_DATA_MAX_SIZE + 1;
+      // Note that if "dataSize" is a integral multiple of PAGE_DATA_MAX_SIZE, there will
+      // be an extra 0-size page at the end
+  for (unsigned i = 0; i < numPagesToWrite; ++i) {
+    // First, fill in the changeable parts of our 'page header' array;
+    u_int8_t header_type_flag = 0x0;
+    if (!fHaveWrittenFirstFrame && i == 0) {
+      header_type_flag |= 0x02; // 'bos'
+      fHaveWrittenFirstFrame = True; // for the future
+    }
+    if (i > 0) header_type_flag |= 0x01; // 'continuation'
+    if (fHaveSeenEOF && i == numPagesToWrite-1) header_type_flag |= 0x04; // 'eos'
+    fPageHeaderBytes[5] = header_type_flag;
+    
+    if (i < numPagesToWrite-1) {
+      // For pages where the frame does not end, set 'granule_position' in the header to -1:
+      fPageHeaderBytes[6] = fPageHeaderBytes[7] = fPageHeaderBytes[8] = fPageHeaderBytes[9] =
+      fPageHeaderBytes[10] = fPageHeaderBytes[11] = fPageHeaderBytes[12] = fPageHeaderBytes[13]
+	= 0xFF;
+    } else {
+      fPageHeaderBytes[6] = (u_int8_t)fGranulePosition;
+      fPageHeaderBytes[7] = (u_int8_t)(fGranulePosition>>8);
+      fPageHeaderBytes[8] = (u_int8_t)(fGranulePosition>>16);
+      fPageHeaderBytes[9] = (u_int8_t)(fGranulePosition>>24);
+      fPageHeaderBytes[10] = (u_int8_t)(fGranulePosition>>32);
+      fPageHeaderBytes[11] = (u_int8_t)(fGranulePosition>>40);
+      fPageHeaderBytes[12] = (u_int8_t)(fGranulePosition>>48);
+      fPageHeaderBytes[13] = (u_int8_t)(fGranulePosition>>56);
+    }
+
+    fPageHeaderBytes[18] = (u_int8_t)fPageSequenceNumber;
+    fPageHeaderBytes[19] = (u_int8_t)(fPageSequenceNumber>>8);
+    fPageHeaderBytes[20] = (u_int8_t)(fPageSequenceNumber>>16);
+    fPageHeaderBytes[21] = (u_int8_t)(fPageSequenceNumber>>24);
+    ++fPageSequenceNumber;
+
+    unsigned pageDataSize;
+    u_int8_t number_page_segments;
+    if (dataSize >= PAGE_DATA_MAX_SIZE) {
+      pageDataSize = PAGE_DATA_MAX_SIZE;
+      number_page_segments = 255;
+    } else {
+      pageDataSize = dataSize;
+      number_page_segments = (pageDataSize+255)/255; // so that we don't end with a lacing of 255
+    }
+    fPageHeaderBytes[26] = number_page_segments;
+
+    u_int8_t segment_table[255];
+    for (unsigned j = 0; j < (unsigned)(number_page_segments-1); ++j) {
+      segment_table[j] = 255;
+    }
+    segment_table[number_page_segments-1] = pageDataSize%255;
+
+    // Compute the CRC from the 'page header' array, the 'segment_table', and the frame data:
+    u_int32_t crc = 0;
+    fPageHeaderBytes[22] = fPageHeaderBytes[23] = fPageHeaderBytes[24] = fPageHeaderBytes[25] = 0;
+    crc = calculateCRC(fPageHeaderBytes, 27, 0);
+    crc = calculateCRC(segment_table, number_page_segments, crc);
+    crc = calculateCRC(data, pageDataSize, crc);
+    fPageHeaderBytes[22] = (u_int8_t)crc;
+    fPageHeaderBytes[23] = (u_int8_t)(crc>>8);
+    fPageHeaderBytes[24] = (u_int8_t)(crc>>16);
+    fPageHeaderBytes[25] = (u_int8_t)(crc>>24);
+
+    // Then write out the 'page header' array:
+    FileSink::addData(fPageHeaderBytes, 27, presentationTime);
+
+    // Then write out the 'segment_table':
+    FileSink::addData(segment_table, number_page_segments, presentationTime);
+
+    // Then add frame data, to complete the page:
+    FileSink::addData(data, pageDataSize, presentationTime);
+    data += pageDataSize;
+    dataSize -= pageDataSize;
+  }
+}
+
+void OggFileSink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime) {
+  if (!fHaveWrittenFirstFrame) {
+    fFirstPresentationTime = presentationTime;
+
+    // If we have a 'config string' representing 'packed configuration headers'
+    // ("identification", "comment", "setup"), unpack them and prepend them to the file:
+    if (fConfigStr != NULL && fConfigStr[0] != '\0') {
+      u_int8_t* identificationHdr; unsigned identificationHdrSize;
+      u_int8_t* commentHdr; unsigned commentHdrSize;
+      u_int8_t* setupHdr; unsigned setupHdrSize;
+      u_int32_t identField;
+      parseVorbisOrTheoraConfigStr(fConfigStr,
+				   identificationHdr, identificationHdrSize,
+				   commentHdr, commentHdrSize,
+				   setupHdr, setupHdrSize,
+				   identField);
+      if (identificationHdrSize >= 42
+	  && strncmp((const char*)&identificationHdr[1], "theora", 6) == 0) {
+	// Hack for Theora video: Parse the "identification" hdr to get the "KFGSHIFT" parameter:
+	fIsTheora = True;
+	u_int8_t const KFGSHIFT = ((identificationHdr[40]&3)<<3) | (identificationHdr[41]>>5);
+	fGranuleIncrementPerFrame = (u_int64_t)(1 << KFGSHIFT);
+      }
+      OggFileSink::addData(identificationHdr, identificationHdrSize, presentationTime);
+      OggFileSink::addData(commentHdr, commentHdrSize, presentationTime);
+      
+      // Hack: Handle the "setup" header as if had arrived in the previous delivery, so it'll get
+      // written properly below:
+      if (setupHdrSize > fBufferSize) {
+	fAltFrameSize = fBufferSize;
+	fAltNumTruncatedBytes = setupHdrSize - fBufferSize;
+      } else {
+	fAltFrameSize = setupHdrSize;
+	fAltNumTruncatedBytes = 0;
+      }
+      memmove(fAltBuffer, setupHdr, fAltFrameSize);
+      fAltPresentationTime = presentationTime;
+      
+      delete[] identificationHdr;
+      delete[] commentHdr;
+      delete[] setupHdr;
+    }
+  }
+
+  // Save this input frame for next time, and instead write the previous input frame now:
+  unsigned char* tmpPtr = fBuffer; fBuffer = fAltBuffer; fAltBuffer = tmpPtr;
+  unsigned prevFrameSize = fAltFrameSize; fAltFrameSize = frameSize;
+  unsigned prevNumTruncatedBytes = fAltNumTruncatedBytes; fAltNumTruncatedBytes = numTruncatedBytes;
+  struct timeval prevPresentationTime = fAltPresentationTime; fAltPresentationTime = presentationTime;
+
+  // Call the parent class to complete the normal file write with the (previous) input frame:
+  FileSink::afterGettingFrame(prevFrameSize, prevNumTruncatedBytes, prevPresentationTime);
+}
+
+void OggFileSink::ourOnSourceClosure(void* clientData) {
+  ((OggFileSink*)clientData)->ourOnSourceClosure();
+}
+
+void OggFileSink::ourOnSourceClosure() {
+  fHaveSeenEOF = True;
+
+  // We still have the previously-arrived frame, so write it to the file before we end:
+  OggFileSink::addData(fAltBuffer, fAltFrameSize, fAltPresentationTime);
+
+  // Handle the closure for real:
+  onSourceClosure();
+}
diff --git a/liveMedia/OnDemandServerMediaSubsession.cpp b/liveMedia/OnDemandServerMediaSubsession.cpp
index 38462a8..872deb6 100644
--- a/liveMedia/OnDemandServerMediaSubsession.cpp
+++ b/liveMedia/OnDemandServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand.
 // Implementation
@@ -25,10 +25,19 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 OnDemandServerMediaSubsession
 ::OnDemandServerMediaSubsession(UsageEnvironment& env,
 				Boolean reuseFirstSource,
-				portNumBits initialPortNum)
+				portNumBits initialPortNum,
+				Boolean multiplexRTCPWithRTP)
   : ServerMediaSubsession(env),
-    fSDPLines(NULL), fReuseFirstSource(reuseFirstSource), fInitialPortNum(initialPortNum), fLastStreamToken(NULL) {
+    fSDPLines(NULL), fReuseFirstSource(reuseFirstSource),
+    fMultiplexRTCPWithRTP(multiplexRTCPWithRTP), fLastStreamToken(NULL),
+    fAppHandlerTask(NULL), fAppHandlerClientData(NULL) {
   fDestinationsHashTable = HashTable::create(ONE_WORD_HASH_KEYS);
+  if (fMultiplexRTCPWithRTP) {
+    fInitialPortNum = initialPortNum;
+  } else {
+    // Make sure RTP ports are even-numbered:
+    fInitialPortNum = (initialPortNum+1)&~1;
+  }
   gethostname(fCNAME, sizeof fCNAME);
   fCNAME[sizeof fCNAME-1] = '\0'; // just in case
 }
@@ -59,13 +68,14 @@ OnDemandServerMediaSubsession::sdpLines() {
 
     struct in_addr dummyAddr;
     dummyAddr.s_addr = 0;
-    Groupsock dummyGroupsock(envir(), dummyAddr, 0, 0);
+    Groupsock* dummyGroupsock = createGroupsock(dummyAddr, 0);
     unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamic
-    RTPSink* dummyRTPSink
-      = createNewRTPSink(&dummyGroupsock, rtpPayloadType, inputSource);
+    RTPSink* dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource);
+    if (dummyRTPSink != NULL && dummyRTPSink->estimatedBitrate() > 0) estBitrate = dummyRTPSink->estimatedBitrate();
 
     setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate);
     Medium::close(dummyRTPSink);
+    delete dummyGroupsock;
     closeStreamSource(inputSource);
   }
 
@@ -105,66 +115,75 @@ void OnDemandServerMediaSubsession
 
     // Create 'groupsock' and 'sink' objects for the destination,
     // using previously unused server port numbers:
-    RTPSink* rtpSink;
-    BasicUDPSink* udpSink;
-    Groupsock* rtpGroupsock;
-    Groupsock* rtcpGroupsock;
-    portNumBits serverPortNum;
-    if (clientRTCPPort.num() == 0) {
-      // We're streaming raw UDP (not RTP). Create a single groupsock:
-      NoReuse dummy(envir()); // ensures that we skip over ports that are already in use
-      for (serverPortNum = fInitialPortNum; ; ++serverPortNum) {
-	struct in_addr dummyAddr; dummyAddr.s_addr = 0;
-
-	serverRTPPort = serverPortNum;
-	rtpGroupsock = new Groupsock(envir(), dummyAddr, serverRTPPort, 255);
-	if (rtpGroupsock->socketNum() >= 0) break; // success
-      }
-
-      rtcpGroupsock = NULL;
-      rtpSink = NULL;
-      udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock);
-    } else {
-      // Normal case: We're streaming RTP (over UDP or TCP).  Create a pair of
-      // groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even):
-      NoReuse dummy(envir()); // ensures that we skip over ports that are already in use
-      for (portNumBits serverPortNum = fInitialPortNum; ; serverPortNum += 2) {
-	struct in_addr dummyAddr; dummyAddr.s_addr = 0;
-
-	serverRTPPort = serverPortNum;
-	rtpGroupsock = new Groupsock(envir(), dummyAddr, serverRTPPort, 255);
-	if (rtpGroupsock->socketNum() < 0) {
-	  delete rtpGroupsock;
-	  continue; // try again
+    RTPSink* rtpSink = NULL;
+    BasicUDPSink* udpSink = NULL;
+    Groupsock* rtpGroupsock = NULL;
+    Groupsock* rtcpGroupsock = NULL;
+
+    if (clientRTPPort.num() != 0 || tcpSocketNum >= 0) { // Normal case: Create destinations
+      portNumBits serverPortNum;
+      if (clientRTCPPort.num() == 0) {
+	// We're streaming raw UDP (not RTP). Create a single groupsock:
+	NoReuse dummy(envir()); // ensures that we skip over ports that are already in use
+	for (serverPortNum = fInitialPortNum; ; ++serverPortNum) {
+	  struct in_addr dummyAddr; dummyAddr.s_addr = 0;
+	  
+	  serverRTPPort = serverPortNum;
+	  rtpGroupsock = createGroupsock(dummyAddr, serverRTPPort);
+	  if (rtpGroupsock->socketNum() >= 0) break; // success
 	}
 
-	serverRTCPPort = serverPortNum+1;
-	rtcpGroupsock = new Groupsock(envir(), dummyAddr, serverRTCPPort, 255);
-	if (rtcpGroupsock->socketNum() < 0) {
-	  delete rtpGroupsock;
-	  delete rtcpGroupsock;
-	  continue; // try again
+	udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock);
+      } else {
+	// Normal case: We're streaming RTP (over UDP or TCP).  Create a pair of
+	// groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even).
+	// (If we're multiplexing RTCP and RTP over the same port number, it can be odd or even.)
+	NoReuse dummy(envir()); // ensures that we skip over ports that are already in use
+	for (portNumBits serverPortNum = fInitialPortNum; ; ++serverPortNum) {
+	  struct in_addr dummyAddr; dummyAddr.s_addr = 0;
+
+	  serverRTPPort = serverPortNum;
+	  rtpGroupsock = createGroupsock(dummyAddr, serverRTPPort);
+	  if (rtpGroupsock->socketNum() < 0) {
+	    delete rtpGroupsock;
+	    continue; // try again
+	  }
+
+	  if (fMultiplexRTCPWithRTP) {
+	    // Use the RTP 'groupsock' object for RTCP as well:
+	    serverRTCPPort = serverRTPPort;
+	    rtcpGroupsock = rtpGroupsock;
+	  } else {
+	    // Create a separate 'groupsock' object (with the next (odd) port number) for RTCP:
+	    serverRTCPPort = ++serverPortNum;
+	    rtcpGroupsock = createGroupsock(dummyAddr, serverRTCPPort);
+	    if (rtcpGroupsock->socketNum() < 0) {
+	      delete rtpGroupsock;
+	      delete rtcpGroupsock;
+	      continue; // try again
+	    }
+	  }
+
+	  break; // success
 	}
 
-	break; // success
+	unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamic
+	rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);
+	if (rtpSink != NULL && rtpSink->estimatedBitrate() > 0) streamBitrate = rtpSink->estimatedBitrate();
       }
 
-      unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamic
-      rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);
-      udpSink = NULL;
-    }
-
-    // Turn off the destinations for each groupsock.  They'll get set later
-    // (unless TCP is used instead):
-    if (rtpGroupsock != NULL) rtpGroupsock->removeAllDestinations();
-    if (rtcpGroupsock != NULL) rtcpGroupsock->removeAllDestinations();
-
-    if (rtpGroupsock != NULL) {
-      // Try to use a big send buffer for RTP -  at least 0.1 second of
-      // specified bandwidth and at least 50 KB
-      unsigned rtpBufSize = streamBitrate * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes
-      if (rtpBufSize < 50 * 1024) rtpBufSize = 50 * 1024;
-      increaseSendBufferTo(envir(), rtpGroupsock->socketNum(), rtpBufSize);
+      // Turn off the destinations for each groupsock.  They'll get set later
+      // (unless TCP is used instead):
+      if (rtpGroupsock != NULL) rtpGroupsock->removeAllDestinations();
+      if (rtcpGroupsock != NULL) rtcpGroupsock->removeAllDestinations();
+
+      if (rtpGroupsock != NULL) {
+	// Try to use a big send buffer for RTP -  at least 0.1 second of
+	// specified bandwidth and at least 50 KB
+	unsigned rtpBufSize = streamBitrate * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes
+	if (rtpBufSize < 50 * 1024) rtpBufSize = 50 * 1024;
+	increaseSendBufferTo(envir(), rtpGroupsock->socketNum(), rtpBufSize);
+      }
     }
 
     // Set up the state of the stream.  The stream will get started later:
@@ -196,7 +215,7 @@ void OnDemandServerMediaSubsession::startStream(unsigned clientSessionId,
   Destinations* destinations
     = (Destinations*)(fDestinationsHashTable->Lookup((char const*)clientSessionId));
   if (streamState != NULL) {
-    streamState->startPlaying(destinations,
+    streamState->startPlaying(destinations, clientSessionId,
 			      rtcpRRHandler, rtcpRRHandlerClientData,
 			      serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
     RTPSink* rtpSink = streamState->rtpSink(); // alias
@@ -245,11 +264,19 @@ void OnDemandServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
   }
 }
 
-void OnDemandServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void* streamToken) {
+void OnDemandServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void* streamToken,
+						   double streamEndTime, u_int64_t& numBytes) {
+  numBytes = 0; // by default: unknown
+
   StreamState* streamState = (StreamState*)streamToken;
   if (streamState != NULL && streamState->mediaSource() != NULL) {
     // Because we're not seeking here, get the current NPT, and remember it as the new 'start' NPT:
     streamState->startNPT() = getCurrentNPT(streamToken);
+
+    double duration = streamEndTime - streamState->startNPT();
+    if (duration < 0.0) duration = 0.0;
+    setStreamSourceDuration(streamState->mediaSource(), duration, numBytes);
+
     RTPSink* rtpSink = streamState->rtpSink(); // alias
     if (rtpSink != NULL) rtpSink->resetPresentationTimes();
   }
@@ -277,7 +304,7 @@ float OnDemandServerMediaSubsession::getCurrentNPT(void* streamToken) {
 
     return streamState->startNPT()
       + (rtpSink->mostRecentPresentationTime().tv_sec - rtpSink->initialPresentationTime().tv_sec)
-      + (rtpSink->mostRecentPresentationTime().tv_sec - rtpSink->initialPresentationTime().tv_sec)/1000000.0f;
+      + (rtpSink->mostRecentPresentationTime().tv_usec - rtpSink->initialPresentationTime().tv_usec)/1000000.0f;
   } while (0);
 
   return 0.0;
@@ -290,6 +317,20 @@ FramedSource* OnDemandServerMediaSubsession::getStreamSource(void* streamToken)
   return streamState->mediaSource();
 }
 
+void OnDemandServerMediaSubsession
+::getRTPSinkandRTCP(void* streamToken,
+		    RTPSink const*& rtpSink, RTCPInstance const*& rtcp) {
+  if (streamToken == NULL) {
+    rtpSink = NULL;
+    rtcp = NULL;
+    return;
+  }
+
+  StreamState* streamState = (StreamState*)streamToken;
+  rtpSink = streamState->rtpSink();
+  rtcp = streamState->rtcpInstance();
+}
+
 void OnDemandServerMediaSubsession::deleteStream(unsigned clientSessionId,
 						 void*& streamToken) {
   StreamState* streamState = (StreamState*)streamToken;
@@ -301,7 +342,7 @@ void OnDemandServerMediaSubsession::deleteStream(unsigned clientSessionId,
     fDestinationsHashTable->Remove((char const*)clientSessionId);
 
     // Stop streaming to these destinations:
-    if (streamState != NULL) streamState->endPlaying(destinations);
+    if (streamState != NULL) streamState->endPlaying(destinations, clientSessionId);
   }
 
   // Delete the "StreamState" structure if it's no longer being used:
@@ -326,6 +367,7 @@ char const* OnDemandServerMediaSubsession
 void OnDemandServerMediaSubsession::seekStreamSource(FramedSource* /*inputSource*/,
 						     double& /*seekNPT*/, double /*streamDuration*/, u_int64_t& numBytes) {
   // Default implementation: Do nothing
+  numBytes = 0;
 }
 
 void OnDemandServerMediaSubsession::seekStreamSource(FramedSource* /*inputSource*/,
@@ -340,10 +382,44 @@ void OnDemandServerMediaSubsession
   // Default implementation: Do nothing
 }
 
+void OnDemandServerMediaSubsession
+::setStreamSourceDuration(FramedSource* /*inputSource*/, double /*streamDuration*/, u_int64_t& numBytes) {
+  // Default implementation: Do nothing
+  numBytes = 0;
+}
+
 void OnDemandServerMediaSubsession::closeStreamSource(FramedSource *inputSource) {
   Medium::close(inputSource);
 }
 
+Groupsock* OnDemandServerMediaSubsession
+::createGroupsock(struct in_addr const& addr, Port port) {
+  // Default implementation; may be redefined by subclasses:
+  return new Groupsock(envir(), addr, port, 255);
+}
+
+RTCPInstance* OnDemandServerMediaSubsession
+::createRTCP(Groupsock* RTCPgs, unsigned totSessionBW, /* in kbps */
+	     unsigned char const* cname, RTPSink* sink) {
+  // Default implementation; may be redefined by subclasses:
+  return RTCPInstance::createNew(envir(), RTCPgs, totSessionBW, cname, sink, NULL/*we're a server*/);
+}
+
+void OnDemandServerMediaSubsession
+::setRTCPAppPacketHandler(RTCPAppHandlerFunc* handler, void* clientData) {
+  fAppHandlerTask = handler;
+  fAppHandlerClientData = clientData;
+}
+
+void OnDemandServerMediaSubsession
+::sendRTCPAppPacket(u_int8_t subtype, char const* name,
+		    u_int8_t* appDependentData, unsigned appDependentDataSize) {
+  StreamState* streamState = (StreamState*)fLastStreamToken;
+  if (streamState != NULL) {
+    streamState->sendRTCPAppPacket(subtype, name, appDependentData, appDependentDataSize);
+  }
+}
+
 void OnDemandServerMediaSubsession
 ::setSDPLinesFromRTPSink(RTPSink* rtpSink, FramedSource* inputSource, unsigned estBitrate) {
   if (rtpSink == NULL) return;
@@ -352,6 +428,7 @@ void OnDemandServerMediaSubsession
   unsigned char rtpPayloadType = rtpSink->rtpPayloadType();
   AddressString ipAddressStr(fServerAddressForSDP);
   char* rtpmapLine = rtpSink->rtpmapLine();
+  char const* rtcpmuxLine = fMultiplexRTCPWithRTP ? "a=rtcp-mux\r\n" : "";
   char const* rangeLine = rangeSDPLine();
   char const* auxSDPLine = getAuxSDPLine(rtpSink, inputSource);
   if (auxSDPLine == NULL) auxSDPLine = "";
@@ -363,12 +440,14 @@ void OnDemandServerMediaSubsession
     "%s"
     "%s"
     "%s"
+    "%s"
     "a=control:%s\r\n";
   unsigned sdpFmtSize = strlen(sdpFmt)
     + strlen(mediaType) + 5 /* max short len */ + 3 /* max char len */
     + strlen(ipAddressStr.val())
     + 20 /* max int len */
     + strlen(rtpmapLine)
+    + strlen(rtcpmuxLine)
     + strlen(rangeLine)
     + strlen(auxSDPLine)
     + strlen(trackId());
@@ -380,6 +459,7 @@ void OnDemandServerMediaSubsession
 	  ipAddressStr.val(), // c= address
 	  estBitrate, // b=AS:<bandwidth>
 	  rtpmapLine, // a=rtpmap:... (if present)
+	  rtcpmuxLine, // a=rtcp-mux:... (if present)
 	  rangeLine, // a=range:... (if present)
 	  auxSDPLine, // optional extra SDP line
 	  trackId()); // a=control:<track-id>
@@ -423,7 +503,7 @@ StreamState::~StreamState() {
 }
 
 void StreamState
-::startPlaying(Destinations* dests,
+::startPlaying(Destinations* dests, unsigned clientSessionId,
 	       TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData,
 	       ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
 	       void* serverRequestAlternativeByteHandlerClientData) {
@@ -431,11 +511,9 @@ void StreamState
 
   if (fRTCPInstance == NULL && fRTPSink != NULL) {
     // Create (and start) a 'RTCP instance' for this RTP sink:
-    fRTCPInstance
-      = RTCPInstance::createNew(fRTPSink->envir(), fRTCPgs,
-				fTotalBW, (unsigned char*)fMaster.fCNAME,
-				fRTPSink, NULL /* we're a server */);
+    fRTCPInstance = fMaster.createRTCP(fRTCPgs, fTotalBW, (unsigned char*)fMaster.fCNAME, fRTPSink);
         // Note: This starts RTCP running automatically
+    fRTCPInstance->setAppHandler(fMaster.fAppHandlerTask, fMaster.fAppHandlerClientData);
   }
 
   if (dests->isTCP) {
@@ -455,8 +533,10 @@ void StreamState
   } else {
     // Tell the RTP and RTCP 'groupsocks' about this destination
     // (in case they don't already have it):
-    if (fRTPgs != NULL) fRTPgs->addDestination(dests->addr, dests->rtpPort);
-    if (fRTCPgs != NULL) fRTCPgs->addDestination(dests->addr, dests->rtcpPort);
+    if (fRTPgs != NULL) fRTPgs->addDestination(dests->addr, dests->rtpPort, clientSessionId);
+    if (fRTCPgs != NULL && !(fRTCPgs == fRTPgs && dests->rtcpPort.num() == dests->rtpPort.num())) {
+      fRTCPgs->addDestination(dests->addr, dests->rtcpPort, clientSessionId);
+    }
     if (fRTCPInstance != NULL) {
       fRTCPInstance->setSpecificRRHandler(dests->addr.s_addr, dests->rtcpPort,
 					  rtcpRRHandler, rtcpRRHandlerClientData);
@@ -486,7 +566,7 @@ void StreamState::pause() {
   fAreCurrentlyPlaying = False;
 }
 
-void StreamState::endPlaying(Destinations* dests) {
+void StreamState::endPlaying(Destinations* dests, unsigned clientSessionId) {
 #if 0
   // The following code is temporarily disabled, because it erroneously sends RTCP "BYE"s to all clients if multiple
   // clients are streaming from the same data source (i.e., if "reuseFirstSource" is True), and we don't want that to happen
@@ -510,14 +590,21 @@ void StreamState::endPlaying(Destinations* dests) {
     }
   } else {
     // Tell the RTP and RTCP 'groupsocks' to stop using these destinations:
-    if (fRTPgs != NULL) fRTPgs->removeDestination(dests->addr, dests->rtpPort);
-    if (fRTCPgs != NULL) fRTCPgs->removeDestination(dests->addr, dests->rtcpPort);
+    if (fRTPgs != NULL) fRTPgs->removeDestination(clientSessionId);
+    if (fRTCPgs != NULL && fRTCPgs != fRTPgs) fRTCPgs->removeDestination(clientSessionId);
     if (fRTCPInstance != NULL) {
       fRTCPInstance->unsetSpecificRRHandler(dests->addr.s_addr, dests->rtcpPort);
     }
   }
 }
 
+void StreamState::sendRTCPAppPacket(u_int8_t subtype, char const* name,
+				    u_int8_t* appDependentData, unsigned appDependentDataSize) {
+  if (fRTCPInstance != NULL) {
+    fRTCPInstance->sendAppPacket(subtype, name, appDependentData, appDependentDataSize);
+  }
+}
+
 void StreamState::reclaim() {
   // Delete allocated media objects
   Medium::close(fRTCPInstance) /* will send a RTCP BYE */; fRTCPInstance = NULL;
@@ -527,6 +614,7 @@ void StreamState::reclaim() {
   fMaster.closeStreamSource(fMediaSource); fMediaSource = NULL;
   if (fMaster.fLastStreamToken == this) fMaster.fLastStreamToken = NULL;
 
-  delete fRTPgs; fRTPgs = NULL;
-  delete fRTCPgs; fRTCPgs = NULL;
+  delete fRTPgs;
+  if (fRTCPgs != fRTPgs) delete fRTCPgs;
+  fRTPgs = NULL; fRTCPgs = NULL;
 }
diff --git a/liveMedia/OutputFile.cpp b/liveMedia/OutputFile.cpp
index d974367..8af20dd 100644
--- a/liveMedia/OutputFile.cpp
+++ b/liveMedia/OutputFile.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Common routines for opening/closing named output files
 // Implementation
 
diff --git a/liveMedia/PassiveServerMediaSubsession.cpp b/liveMedia/PassiveServerMediaSubsession.cpp
index 384fc33..85c6231 100644
--- a/liveMedia/PassiveServerMediaSubsession.cpp
+++ b/liveMedia/PassiveServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that represents an existing
 // 'RTPSink', rather than one that creates new 'RTPSink's on demand.
 // Implementation
@@ -60,6 +60,13 @@ PassiveServerMediaSubsession::~PassiveServerMediaSubsession() {
   delete fClientRTCPSourceRecords;
 }
 
+Boolean PassiveServerMediaSubsession::rtcpIsMuxed() {
+  if (fRTCPInstance == NULL) return False;
+
+  // Check whether RTP and RTCP use the same "groupsock" object:
+  return &(fRTPSink.groupsockBeingUsed()) == fRTCPInstance->RTCPgs();
+}
+
 char const*
 PassiveServerMediaSubsession::sdpLines() {
   if (fSDPLines == NULL ) {
@@ -74,6 +81,7 @@ PassiveServerMediaSubsession::sdpLines() {
     unsigned estBitrate
       = fRTCPInstance == NULL ? 50 : fRTCPInstance->totSessionBW();
     char* rtpmapLine = fRTPSink.rtpmapLine();
+    char const* rtcpmuxLine = rtcpIsMuxed() ? "a=rtcp-mux\r\n" : "";
     char const* rangeLine = rangeSDPLine();
     char const* auxSDPLine = fRTPSink.auxSDPLine();
     if (auxSDPLine == NULL) auxSDPLine = "";
@@ -85,12 +93,14 @@ PassiveServerMediaSubsession::sdpLines() {
       "%s"
       "%s"
       "%s"
+      "%s"
       "a=control:%s\r\n";
     unsigned sdpFmtSize = strlen(sdpFmt)
       + strlen(mediaType) + 5 /* max short len */ + 3 /* max char len */
       + strlen(groupAddressStr.val()) + 3 /* max char len */
       + 20 /* max int len */
       + strlen(rtpmapLine)
+      + strlen(rtcpmuxLine)
       + strlen(rangeLine)
       + strlen(auxSDPLine)
       + strlen(trackId());
@@ -103,6 +113,7 @@ PassiveServerMediaSubsession::sdpLines() {
 	    ttl, // c= TTL
 	    estBitrate, // b=AS:<bandwidth>
 	    rtpmapLine, // a=rtpmap:... (if present)
+	    rtcpmuxLine, // a=rtcp-mux:... (if present)
 	    rangeLine, // a=range:... (if present)
 	    auxSDPLine, // optional extra SDP line
 	    trackId()); // a=control:<track-id>
@@ -196,6 +207,13 @@ float PassiveServerMediaSubsession::getCurrentNPT(void* streamToken) {
   return (float)(timeNow.tv_sec - creationTime.tv_sec + (timeNow.tv_usec - creationTime.tv_usec)/1000000.0);
 }
 
+void PassiveServerMediaSubsession
+::getRTPSinkandRTCP(void* streamToken,
+		    RTPSink const*& rtpSink, RTCPInstance const*& rtcp) {
+  rtpSink = &fRTPSink;
+  rtcp = fRTCPInstance;
+}
+
 void PassiveServerMediaSubsession::deleteStream(unsigned clientSessionId, void*& /*streamToken*/) {
   // Lookup and remove the 'RTCPSourceRecord' for this client.  Also turn off RTCP "RR" handling:
   RTCPSourceRecord* source = (RTCPSourceRecord*)(fClientRTCPSourceRecords->Lookup((char const*)clientSessionId));
diff --git a/liveMedia/ProxyServerMediaSession.cpp b/liveMedia/ProxyServerMediaSession.cpp
index 15cd6ac..f1f78f8 100644
--- a/liveMedia/ProxyServerMediaSession.cpp
+++ b/liveMedia/ProxyServerMediaSession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A subclass of "ServerMediaSession" that can be used to create a (unicast) RTSP servers that acts as a 'proxy' for
 // another (unicast or multicast) RTSP/RTP stream.
 // Implementation
@@ -31,10 +31,11 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 
 class ProxyServerMediaSubsession: public OnDemandServerMediaSubsession {
 public:
-  ProxyServerMediaSubsession(MediaSubsession& mediaSubsession);
+  ProxyServerMediaSubsession(MediaSubsession& mediaSubsession,
+			     portNumBits initialPortNum, Boolean multiplexRTCPWithRTP);
   virtual ~ProxyServerMediaSubsession();
 
-  char const* codecName() const { return fClientMediaSubsession.codecName(); }
+  char const* codecName() const { return fCodecName; }
 
 private: // redefined virtual functions
   virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
@@ -43,6 +44,9 @@ private: // redefined virtual functions
   virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,
                                     unsigned char rtpPayloadTypeIfDynamic,
                                     FramedSource* inputSource);
+  virtual Groupsock* createGroupsock(struct in_addr const& addr, Port port);
+  virtual RTCPInstance* createRTCP(Groupsock* RTCPgs, unsigned totSessionBW, /* in kbps */
+				   unsigned char const* cname, RTPSink* sink);
 
 private:
   static void subsessionByeHandler(void* clientData);
@@ -53,6 +57,7 @@ private:
 private:
   friend class ProxyRTSPClient;
   MediaSubsession& fClientMediaSubsession; // the 'client' media subsession object that corresponds to this 'server' media subsession
+  char const* fCodecName;  // copied from "fClientMediaSubsession" once it's been set up
   ProxyServerMediaSubsession* fNext; // used when we're part of a queue
   Boolean fHaveSetupStream;
 };
@@ -75,27 +80,33 @@ defaultCreateNewProxyRTSPClientFunc(ProxyServerMediaSession& ourServerMediaSessi
 }
 
 ProxyServerMediaSession* ProxyServerMediaSession
-::createNew(UsageEnvironment& env, RTSPServer* ourRTSPServer,
+::createNew(UsageEnvironment& env, GenericMediaServer* ourMediaServer,
 	    char const* inputStreamURL, char const* streamName,
 	    char const* username, char const* password,
-	    portNumBits tunnelOverHTTPPortNum, int verbosityLevel, int socketNumToServer) {
-  return new ProxyServerMediaSession(env, ourRTSPServer, inputStreamURL, streamName, username, password,
-				     tunnelOverHTTPPortNum, verbosityLevel, socketNumToServer);
+	    portNumBits tunnelOverHTTPPortNum, int verbosityLevel, int socketNumToServer,
+	    MediaTranscodingTable* transcodingTable) {
+  return new ProxyServerMediaSession(env, ourMediaServer, inputStreamURL, streamName, username, password,
+				     tunnelOverHTTPPortNum, verbosityLevel, socketNumToServer,
+				     transcodingTable);
 }
 
 
 ProxyServerMediaSession
-::ProxyServerMediaSession(UsageEnvironment& env, RTSPServer* ourRTSPServer,
+::ProxyServerMediaSession(UsageEnvironment& env, GenericMediaServer* ourMediaServer,
 			  char const* inputStreamURL, char const* streamName,
 			  char const* username, char const* password,
 			  portNumBits tunnelOverHTTPPortNum, int verbosityLevel,
 			  int socketNumToServer,
-			  createNewProxyRTSPClientFunc* ourCreateNewProxyRTSPClientFunc)
+			  MediaTranscodingTable* transcodingTable,
+			  createNewProxyRTSPClientFunc* ourCreateNewProxyRTSPClientFunc,
+			  portNumBits initialPortNum, Boolean multiplexRTCPWithRTP)
   : ServerMediaSession(env, streamName, NULL, NULL, False, NULL),
-    describeCompletedFlag(0), fOurRTSPServer(ourRTSPServer), fClientMediaSession(NULL),
+    describeCompletedFlag(0), fOurMediaServer(ourMediaServer), fClientMediaSession(NULL),
     fVerbosityLevel(verbosityLevel),
     fPresentationTimeSessionNormalizer(new PresentationTimeSessionNormalizer(envir())),
-    fCreateNewProxyRTSPClientFunc(ourCreateNewProxyRTSPClientFunc) {
+    fCreateNewProxyRTSPClientFunc(ourCreateNewProxyRTSPClientFunc),
+    fTranscodingTable(transcodingTable),
+    fInitialPortNum(initialPortNum), fMultiplexRTCPWithRTP(multiplexRTCPWithRTP) {
   // Open a RTSP connection to the input stream, and send a "DESCRIBE" command.
   // We'll use the SDP description in the response to set ourselves up.
   fProxyRTSPClient
@@ -112,18 +123,33 @@ ProxyServerMediaSession::~ProxyServerMediaSession() {
   }
 
   // Begin by sending a "TEARDOWN" command (without checking for a response):
-  if (fProxyRTSPClient != NULL) fProxyRTSPClient->sendTeardownCommand(*fClientMediaSession, NULL, fProxyRTSPClient->auth());
+  if (fProxyRTSPClient != NULL && fClientMediaSession != NULL) {
+    fProxyRTSPClient->sendTeardownCommand(*fClientMediaSession, NULL, fProxyRTSPClient->auth());
+  }
 
   // Then delete our state:
+  Medium::close(fTranscodingTable);
   Medium::close(fClientMediaSession);
   Medium::close(fProxyRTSPClient);
-  delete fPresentationTimeSessionNormalizer;
+  Medium::close(fPresentationTimeSessionNormalizer);
 }
 
 char const* ProxyServerMediaSession::url() const {
   return fProxyRTSPClient == NULL ? NULL : fProxyRTSPClient->url();
 }
 
+Groupsock* ProxyServerMediaSession::createGroupsock(struct in_addr const& addr, Port port) {
+  // Default implementation; may be redefined by subclasses:
+  return new Groupsock(envir(), addr, port, 255);
+}
+
+RTCPInstance* ProxyServerMediaSession
+::createRTCP(Groupsock* RTCPgs, unsigned totSessionBW, /* in kbps */
+	     unsigned char const* cname, RTPSink* sink) {
+  // Default implementation; may be redefined by subclasses:
+  return RTCPInstance::createNew(envir(), RTCPgs, totSessionBW, cname, sink, NULL/*we're a server*/);
+}
+
 void ProxyServerMediaSession::continueAfterDESCRIBE(char const* sdpDescription) {
   describeCompletedFlag = 1;
 
@@ -135,7 +161,8 @@ void ProxyServerMediaSession::continueAfterDESCRIBE(char const* sdpDescription)
 
     MediaSubsessionIterator iter(*fClientMediaSession);
     for (MediaSubsession* mss = iter.next(); mss != NULL; mss = iter.next()) {
-      ServerMediaSubsession* smss = new ProxyServerMediaSubsession(*mss);
+      ServerMediaSubsession* smss
+	= new ProxyServerMediaSubsession(*mss, fInitialPortNum, fMultiplexRTCPWithRTP);
       addSubsession(smss);
       if (fVerbosityLevel > 0) {
 	envir() << *this << " added new \"ProxyServerMediaSubsession\" for "
@@ -147,9 +174,9 @@ void ProxyServerMediaSession::continueAfterDESCRIBE(char const* sdpDescription)
 
 void ProxyServerMediaSession::resetDESCRIBEState() {
   // Delete all of our "ProxyServerMediaSubsession"s; they'll get set up again once we get a response to the new "DESCRIBE".
-  if (fOurRTSPServer != NULL) {
-    // First, close any RTSP client connections that may have already been set up:
-    fOurRTSPServer->closeAllClientSessionsForServerMediaSession(this);
+  if (fOurMediaServer != NULL) {
+    // First, close any client connections that may have already been set up:
+    fOurMediaServer->closeAllClientSessionsForServerMediaSession(this);
   }
   deleteAllSubsessions();
 
@@ -174,9 +201,12 @@ static void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char*
 }
 
 static void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString) {
-  if (resultCode == 0) {
-    ((ProxyRTSPClient*)rtspClient)->continueAfterSETUP();
-  }
+  ((ProxyRTSPClient*)rtspClient)->continueAfterSETUP(resultCode);
+  delete[] resultString;
+}
+
+static void continueAfterPLAY(RTSPClient* rtspClient, int resultCode, char* resultString) {
+  ((ProxyRTSPClient*)rtspClient)->continueAfterPLAY(resultCode);
   delete[] resultString;
 }
 
@@ -185,8 +215,8 @@ static void continueAfterOPTIONS(RTSPClient* rtspClient, int resultCode, char* r
   if (resultCode == 0) {
     // Note whether the server told us that it supports the "GET_PARAMETER" command:
     serverSupportsGetParameter = RTSPOptionIsSupported("GET_PARAMETER", resultString);
-  }
-  ((ProxyRTSPClient*)rtspClient)->continueAfterLivenessCommand(resultCode, serverSupportsGetParameter);
+  } 
+ ((ProxyRTSPClient*)rtspClient)->continueAfterLivenessCommand(resultCode, serverSupportsGetParameter);
   delete[] resultString;
 }
 
@@ -211,7 +241,7 @@ ProxyRTSPClient::ProxyRTSPClient(ProxyServerMediaSession& ourServerMediaSession,
 	       tunnelOverHTTPPortNum == (portNumBits)(~0) ? 0 : tunnelOverHTTPPortNum, socketNumToServer),
     fOurServerMediaSession(ourServerMediaSession), fOurURL(strDup(rtspURL)), fStreamRTPOverTCP(tunnelOverHTTPPortNum != 0),
     fSetupQueueHead(NULL), fSetupQueueTail(NULL), fNumSetupsDone(0), fNextDESCRIBEDelay(1),
-    fServerSupportsGetParameter(False), fLastCommandWasPLAY(False),
+    fServerSupportsGetParameter(False), fLastCommandWasPLAY(False), fResetOnNextLivenessTest(False),
     fLivenessCommandTask(NULL), fDESCRIBECommandTask(NULL), fSubsessionTimerTask(NULL) { 
   if (username != NULL && password != NULL) {
     fOurAuthenticator = new Authenticator(username, password);
@@ -258,6 +288,11 @@ void ProxyRTSPClient::continueAfterDESCRIBE(char const* sdpDescription) {
 }
 
 void ProxyRTSPClient::continueAfterLivenessCommand(int resultCode, Boolean serverSupportsGetParameter) {
+  if (fResetOnNextLivenessTest) {
+    // Hack: We've arranged to reset the connection with the server (regardless of "resultCode"):
+    fResetOnNextLivenessTest = False;
+    resultCode = 2; // arbitrary > 0
+  }
   if (resultCode != 0) {
     // The periodic 'liveness' command failed, suggesting that the back-end stream is no longer alive.
     // We handle this by resetting our connection state with this server.  Any current clients will be closed, but
@@ -290,12 +325,21 @@ void ProxyRTSPClient::continueAfterLivenessCommand(int resultCode, Boolean serve
 
 #define SUBSESSION_TIMEOUT_SECONDS 10 // how many seconds to wait for the last track's "SETUP" to be done (note below)
 
-void ProxyRTSPClient::continueAfterSETUP() {
+void ProxyRTSPClient::continueAfterSETUP(int resultCode) {
+  if (resultCode != 0) {
+    // The "SETUP" command failed, so arrange to reset the state after the next RTSP 'liveness'
+    // command.  (We don't do this now, because it deletes the "ProxyServerMediaSubsession",
+    // and we can't do that during "ProxyServerMediaSubsession::createNewStreamSource()".)
+    fResetOnNextLivenessTest = True;
+    envir().taskScheduler().rescheduleDelayedTask(fLivenessCommandTask, 0, sendLivenessCommand, this);
+    return;
+  }
+
   if (fVerbosityLevel > 0) {
-    envir() << *this << "::continueAfterSETUP(): head codec: " << fSetupQueueHead->fClientMediaSubsession.codecName()
+    envir() << *this << "::continueAfterSETUP(): head codec: " << fSetupQueueHead->codecName()
 	    << "; numSubsessions " << fSetupQueueHead->fParentSession->numSubsessions() << "\n\tqueue:";
     for (ProxyServerMediaSubsession* p = fSetupQueueHead; p != NULL; p = p->fNext) {
-      envir() << "\t" << p->fClientMediaSubsession.codecName();
+      envir() << "\t" << p->codecName();
     }
     envir() << "\n";
   }
@@ -317,7 +361,7 @@ void ProxyRTSPClient::continueAfterSETUP() {
     if (fNumSetupsDone >= smss->fParentSession->numSubsessions()) {
       // We've now finished setting up each of our subsessions (i.e., 'tracks').
       // Continue by sending a "PLAY" command (an 'aggregate' "PLAY" command, on the whole session):
-      sendPlayCommand(smss->fClientMediaSubsession.parentSession(), NULL, -1.0f, -1.0f, 1.0f, fOurAuthenticator);
+      sendPlayCommand(smss->fClientMediaSubsession.parentSession(), ::continueAfterPLAY, -1.0f, -1.0f, 1.0f, fOurAuthenticator);
           // the "-1.0f" "start" parameter causes the "PLAY" to be sent without a "Range:" header, in case we'd already done
           // a "PLAY" before (as a result of a 'subsession timeout' (note below))
       fLastCommandWasPLAY = True;
@@ -332,6 +376,17 @@ void ProxyRTSPClient::continueAfterSETUP() {
   }
 }
 
+void ProxyRTSPClient::continueAfterPLAY(int resultCode) {
+  if (resultCode != 0) {
+    // The "PLAY" command failed, so arrange to reset the state after the next RTSP 'liveness'
+    // command.  (We don't do this now, because it deletes the "ProxyServerMediaSubsession",
+    // and we can't do that during "ProxyServerMediaSubsession::createNewStreamSource()".)
+    fResetOnNextLivenessTest = True;
+    envir().taskScheduler().rescheduleDelayedTask(fLivenessCommandTask, 0, sendLivenessCommand, this);
+    return;
+  }
+}
+
 void ProxyRTSPClient::scheduleLivenessCommand() {
   // Delay a random time before sending another 'liveness' command.
   unsigned delayMax = sessionTimeoutParameter(); // if the server specified a maximum time between 'liveness' probes, then use that
@@ -398,16 +453,20 @@ void ProxyRTSPClient::subsessionTimeout(void* clientData) {
 void ProxyRTSPClient::handleSubsessionTimeout() {
   // We still have one or more subsessions ('tracks') left to "SETUP".  But we can't wait any longer for them.  Send a "PLAY" now:
   MediaSession* sess = fOurServerMediaSession.fClientMediaSession;
-  if (sess != NULL) sendPlayCommand(*sess, NULL, -1.0f, -1.0f, 1.0f, fOurAuthenticator);
+  if (sess != NULL) sendPlayCommand(*sess, ::continueAfterPLAY, -1.0f, -1.0f, 1.0f, fOurAuthenticator);
   fLastCommandWasPLAY = True;
 }
 
 
 //////// "ProxyServerMediaSubsession" implementation //////////
 
-ProxyServerMediaSubsession::ProxyServerMediaSubsession(MediaSubsession& mediaSubsession)
-  : OnDemandServerMediaSubsession(mediaSubsession.parentSession().envir(), True/*reuseFirstSource*/),
-    fClientMediaSubsession(mediaSubsession), fNext(NULL), fHaveSetupStream(False) {
+ProxyServerMediaSubsession
+::ProxyServerMediaSubsession(MediaSubsession& mediaSubsession,
+			     portNumBits initialPortNum, Boolean multiplexRTCPWithRTP)
+  : OnDemandServerMediaSubsession(mediaSubsession.parentSession().envir(), True/*reuseFirstSource*/,
+				  initialPortNum, multiplexRTCPWithRTP),
+    fClientMediaSubsession(mediaSubsession), fCodecName(strDup(mediaSubsession.codecName())),
+    fNext(NULL), fHaveSetupStream(False) {
 }
 
 UsageEnvironment& operator<<(UsageEnvironment& env, const ProxyServerMediaSubsession& psmss) { // used for debugging
@@ -418,6 +477,8 @@ ProxyServerMediaSubsession::~ProxyServerMediaSubsession() {
   if (verbosityLevel() > 0) {
     envir() << *this << "::~ProxyServerMediaSubsession()\n";
   }
+
+  delete[] (char*)fCodecName;
 }
 
 FramedSource* ProxyServerMediaSubsession::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
@@ -437,26 +498,45 @@ FramedSource* ProxyServerMediaSubsession::createNewStreamSource(unsigned clientS
     }
 
     if (fClientMediaSubsession.readSource() != NULL) {
-      // Add to the front of all data sources a filter that will 'normalize' their frames' presentation times,
-      // before the frames get re-transmitted by our server:
-      char const* const codecName = fClientMediaSubsession.codecName();
+      // First, check whether we have defined a 'transcoder' filter to be used with this codec:
+      if (sms->fTranscodingTable != NULL) {
+	char* outputCodecName;
+	FramedFilter* transcoder
+	  = sms->fTranscodingTable->lookupTranscoder(fClientMediaSubsession, outputCodecName);
+	if (transcoder != NULL) {
+	  fClientMediaSubsession.addFilter(transcoder);
+	  delete[] (char*)fCodecName; fCodecName = outputCodecName;
+	}
+      }
+
+      // Then, add to the front of all data sources a filter that will 'normalize' their frames'
+      // presentation times, before the frames get re-transmitted by our server:
       FramedFilter* normalizerFilter = sms->fPresentationTimeSessionNormalizer
-	->createNewPresentationTimeSubsessionNormalizer(fClientMediaSubsession.readSource(), fClientMediaSubsession.rtpSource(),
-							codecName);
+	->createNewPresentationTimeSubsessionNormalizer(fClientMediaSubsession.readSource(),
+							fClientMediaSubsession.rtpSource(),
+							fCodecName);
       fClientMediaSubsession.addFilter(normalizerFilter);
 
-      // Some data sources require a 'framer' object to be added, before they can be fed into a "RTPSink".  Adjust for this now:
-      if (strcmp(codecName, "H264") == 0) {
-	fClientMediaSubsession.addFilter(H264VideoStreamDiscreteFramer::createNew(envir(), fClientMediaSubsession.readSource()));
-      } else if (strcmp(codecName, "MP4V-ES") == 0) {
+      // Some data sources require a 'framer' object to be added, before they can be fed into
+      // a "RTPSink".  Adjust for this now:
+      if (strcmp(fCodecName, "H264") == 0) {
+	fClientMediaSubsession.addFilter(H264VideoStreamDiscreteFramer
+					 ::createNew(envir(), fClientMediaSubsession.readSource()));
+      } else if (strcmp(fCodecName, "H265") == 0) {
+	fClientMediaSubsession.addFilter(H265VideoStreamDiscreteFramer
+					 ::createNew(envir(), fClientMediaSubsession.readSource()));
+      } else if (strcmp(fCodecName, "MP4V-ES") == 0) {
 	fClientMediaSubsession.addFilter(MPEG4VideoStreamDiscreteFramer
-					 ::createNew(envir(), fClientMediaSubsession.readSource(), True/* leave PTs unmodified*/));
-      } else if (strcmp(codecName, "MPV") == 0) {
-	fClientMediaSubsession.addFilter(MPEG1or2VideoStreamDiscreteFramer::createNew(envir(), fClientMediaSubsession.readSource(),
-										      False, 5.0, True/* leave PTs unmodified*/));
-      } else if (strcmp(codecName, "DV") == 0) {
-	fClientMediaSubsession.addFilter(DVVideoStreamFramer::createNew(envir(), fClientMediaSubsession.readSource(),
-									False, True/* leave PTs unmodified*/));
+					 ::createNew(envir(), fClientMediaSubsession.readSource(),
+						     True/* leave PTs unmodified*/));
+      } else if (strcmp(fCodecName, "MPV") == 0) {
+	fClientMediaSubsession.addFilter(MPEG1or2VideoStreamDiscreteFramer
+					 ::createNew(envir(), fClientMediaSubsession.readSource(),
+						     False, 5.0, True/* leave PTs unmodified*/));
+      } else if (strcmp(fCodecName, "DV") == 0) {
+	fClientMediaSubsession.addFilter(DVVideoStreamFramer
+					 ::createNew(envir(), fClientMediaSubsession.readSource(),
+						     False, True/* leave PTs unmodified*/));
       }
     }
 
@@ -493,7 +573,7 @@ FramedSource* ProxyServerMediaSubsession::createNewStreamSource(unsigned clientS
       // have been called here), so we know that the substream was previously "PAUSE"d.  Send "PLAY" downstream once again,
       // to resume the stream:
       if (!proxyRTSPClient->fLastCommandWasPLAY) { // so that we send only one "PLAY"; not one for each subsession
-	proxyRTSPClient->sendPlayCommand(fClientMediaSubsession.parentSession(), NULL, -1.0f/*resume from previous point*/,
+	proxyRTSPClient->sendPlayCommand(fClientMediaSubsession.parentSession(), ::continueAfterPLAY, -1.0f/*resume from previous point*/,
 					 -1.0f, 1.0f, proxyRTSPClient->auth());
 	proxyRTSPClient->fLastCommandWasPLAY = True;
       }
@@ -530,70 +610,85 @@ RTPSink* ProxyServerMediaSubsession
   }
 
   // Create (and return) the appropriate "RTPSink" object for our codec:
+  // (Note: The configuration string might not be correct if a transcoder is used. FIX!) #####
   RTPSink* newSink;
-  char const* const codecName = fClientMediaSubsession.codecName();
-  if (strcmp(codecName, "AC3") == 0 || strcmp(codecName, "EAC3") == 0) {
+  if (strcmp(fCodecName, "AC3") == 0 || strcmp(fCodecName, "EAC3") == 0) {
     newSink = AC3AudioRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
 					 fClientMediaSubsession.rtpTimestampFrequency()); 
 #if 0 // This code does not work; do *not* enable it:
-  } else if (strcmp(codecName, "AMR") == 0 || strcmp(codecName, "AMR-WB") == 0) {
-    Boolean isWideband = strcmp(codecName, "AMR-WB") == 0;
+  } else if (strcmp(fCodecName, "AMR") == 0 || strcmp(fCodecName, "AMR-WB") == 0) {
+    Boolean isWideband = strcmp(fCodecName, "AMR-WB") == 0;
     newSink = AMRAudioRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
 					 isWideband, fClientMediaSubsession.numChannels());
 #endif
-  } else if (strcmp(codecName, "DV") == 0) {
+  } else if (strcmp(fCodecName, "DV") == 0) {
     newSink = DVVideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
-  } else if (strcmp(codecName, "GSM") == 0) {
+  } else if (strcmp(fCodecName, "GSM") == 0) {
     newSink = GSMAudioRTPSink::createNew(envir(), rtpGroupsock);
-  } else if (strcmp(codecName, "H263-1998") == 0 || strcmp(codecName, "H263-2000") == 0) {
+  } else if (strcmp(fCodecName, "H263-1998") == 0 || strcmp(fCodecName, "H263-2000") == 0) {
     newSink = H263plusVideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
 					      fClientMediaSubsession.rtpTimestampFrequency()); 
-  } else if (strcmp(codecName, "H264") == 0) {
+  } else if (strcmp(fCodecName, "H264") == 0) {
     newSink = H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
 					  fClientMediaSubsession.fmtp_spropparametersets());
-  } else if (strcmp(codecName, "JPEG") == 0) {
+  } else if (strcmp(fCodecName, "H265") == 0) {
+    newSink = H265VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+					  fClientMediaSubsession.fmtp_spropvps(),
+					  fClientMediaSubsession.fmtp_spropsps(),
+					  fClientMediaSubsession.fmtp_sproppps());
+  } else if (strcmp(fCodecName, "JPEG") == 0) {
     newSink = SimpleRTPSink::createNew(envir(), rtpGroupsock, 26, 90000, "video", "JPEG",
 				       1/*numChannels*/, False/*allowMultipleFramesPerPacket*/, False/*doNormalMBitRule*/);
-  } else if (strcmp(codecName, "MP4A-LATM") == 0) {
+  } else if (strcmp(fCodecName, "MP4A-LATM") == 0) {
     newSink = MPEG4LATMAudioRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
 					       fClientMediaSubsession.rtpTimestampFrequency(),
 					       fClientMediaSubsession.fmtp_config(),
 					       fClientMediaSubsession.numChannels());
-  } else if (strcmp(codecName, "MP4V-ES") == 0) {
+  } else if (strcmp(fCodecName, "MP4V-ES") == 0) {
     newSink = MPEG4ESVideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
 					     fClientMediaSubsession.rtpTimestampFrequency(),
-					     fClientMediaSubsession.fmtp_profile_level_id(), fClientMediaSubsession.fmtp_config()); 
-  } else if (strcmp(codecName, "MPA") == 0) {
+					     fClientMediaSubsession.attrVal_unsigned("profile-level-id"),
+					     fClientMediaSubsession.fmtp_config()); 
+  } else if (strcmp(fCodecName, "MPA") == 0) {
     newSink = MPEG1or2AudioRTPSink::createNew(envir(), rtpGroupsock);
-  } else if (strcmp(codecName, "MPA-ROBUST") == 0) {
+  } else if (strcmp(fCodecName, "MPA-ROBUST") == 0) {
     newSink = MP3ADURTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
-  } else if (strcmp(codecName, "MPEG4-GENERIC") == 0) {
+  } else if (strcmp(fCodecName, "MPEG4-GENERIC") == 0) {
     newSink = MPEG4GenericRTPSink::createNew(envir(), rtpGroupsock,
 					     rtpPayloadTypeIfDynamic, fClientMediaSubsession.rtpTimestampFrequency(),
-					     fClientMediaSubsession.mediumName(), fClientMediaSubsession.fmtp_mode(),
+					     fClientMediaSubsession.mediumName(),
+					     fClientMediaSubsession.attrVal_str("mode"),
 					     fClientMediaSubsession.fmtp_config(), fClientMediaSubsession.numChannels());
-  } else if (strcmp(codecName, "MPV") == 0) {
+  } else if (strcmp(fCodecName, "MPV") == 0) {
     newSink = MPEG1or2VideoRTPSink::createNew(envir(), rtpGroupsock);
-  } else if (strcmp(codecName, "T140") == 0) {
+  } else if (strcmp(fCodecName, "OPUS") == 0) {
+    newSink = SimpleRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+				       48000, "audio", "OPUS", 2, False/*only 1 Opus 'packet' in each RTP packet*/);
+  } else if (strcmp(fCodecName, "T140") == 0) {
     newSink = T140TextRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
-  } else if (strcmp(codecName, "VORBIS") == 0) {
+  } else if (strcmp(fCodecName, "THEORA") == 0) {
+    newSink = TheoraVideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
+					    fClientMediaSubsession.fmtp_config()); 
+  } else if (strcmp(fCodecName, "VORBIS") == 0) {
     newSink = VorbisAudioRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic,
 					    fClientMediaSubsession.rtpTimestampFrequency(), fClientMediaSubsession.numChannels(),
 					    fClientMediaSubsession.fmtp_config()); 
-  } else if (strcmp(codecName, "VP8") == 0) {
+  } else if (strcmp(fCodecName, "VP8") == 0) {
     newSink = VP8VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
-  } else if (strcmp(codecName, "AMR") == 0 || strcmp(codecName, "AMR-WB") == 0) {
+  } else if (strcmp(fCodecName, "VP9") == 0) {
+    newSink = VP9VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
+  } else if (strcmp(fCodecName, "AMR") == 0 || strcmp(fCodecName, "AMR-WB") == 0) {
     // Proxying of these codecs is currently *not* supported, because the data received by the "RTPSource" object is not in a
     // form that can be fed directly into a corresponding "RTPSink" object.
     if (verbosityLevel() > 0) {
       envir() << "\treturns NULL (because we currently don't support the proxying of \""
-	      << fClientMediaSubsession.mediumName() << "/" << codecName << "\" streams)\n";
+	      << fClientMediaSubsession.mediumName() << "/" << fCodecName << "\" streams)\n";
     }
     return NULL;
-  } else if (strcmp(codecName, "QCELP") == 0 ||
-	     strcmp(codecName, "H261") == 0 ||
-	     strcmp(codecName, "H263-1998") == 0 || strcmp(codecName, "H263-2000") == 0 ||
-	     strcmp(codecName, "X-QT") == 0 || strcmp(codecName, "X-QUICKTIME") == 0) {
+  } else if (strcmp(fCodecName, "QCELP") == 0 ||
+	     strcmp(fCodecName, "H261") == 0 ||
+	     strcmp(fCodecName, "H263-1998") == 0 || strcmp(fCodecName, "H263-2000") == 0 ||
+	     strcmp(fCodecName, "X-QT") == 0 || strcmp(fCodecName, "X-QUICKTIME") == 0) {
     // This codec requires a specialized RTP payload format; however, we don't yet have an appropriate "RTPSink" subclass for it:
     if (verbosityLevel() > 0) {
       envir() << "\treturns NULL (because we don't have a \"RTPSink\" subclass for this RTP payload format)\n";
@@ -604,12 +699,12 @@ RTPSink* ProxyServerMediaSubsession
     Boolean allowMultipleFramesPerPacket = True; // by default
     Boolean doNormalMBitRule = True; // by default
     // Some codecs change the above default parameters:
-    if (strcmp(codecName, "MP2T") == 0) {
+    if (strcmp(fCodecName, "MP2T") == 0) {
       doNormalMBitRule = False; // no RTP 'M' bit
     }
     newSink = SimpleRTPSink::createNew(envir(), rtpGroupsock,
 				       rtpPayloadTypeIfDynamic, fClientMediaSubsession.rtpTimestampFrequency(),
-				       fClientMediaSubsession.mediumName(), fClientMediaSubsession.codecName(),
+				       fClientMediaSubsession.mediumName(), fCodecName,
 				       fClientMediaSubsession.numChannels(), allowMultipleFramesPerPacket, doNormalMBitRule);
   }
 
@@ -619,10 +714,11 @@ RTPSink* ProxyServerMediaSubsession
 
   // Also tell our "PresentationTimeSubsessionNormalizer" object about the "RTPSink", so it can enable RTCP "SR" reports later:
   PresentationTimeSubsessionNormalizer* ssNormalizer;
-  if (strcmp(codecName, "H264") == 0 ||
-      strcmp(codecName, "MP4V-ES") == 0 ||
-      strcmp(codecName, "MPV") == 0 ||
-      strcmp(codecName, "DV") == 0) {
+  if (strcmp(fCodecName, "H264") == 0 ||
+      strcmp(fCodecName, "H265") == 0 ||
+      strcmp(fCodecName, "MP4V-ES") == 0 ||
+      strcmp(fCodecName, "MPV") == 0 ||
+      strcmp(fCodecName, "DV") == 0) {
     // There was a separate 'framer' object in front of the "PresentationTimeSubsessionNormalizer", so go back one object to get it:
     ssNormalizer = (PresentationTimeSubsessionNormalizer*)(((FramedFilter*)inputSource)->inputSource());
   } else {
@@ -633,6 +729,18 @@ RTPSink* ProxyServerMediaSubsession
   return newSink;
 }
 
+Groupsock* ProxyServerMediaSubsession::createGroupsock(struct in_addr const& addr, Port port) {
+  ProxyServerMediaSession* parentSession = (ProxyServerMediaSession*)fParentSession;
+  return parentSession->createGroupsock(addr, port);
+}
+
+RTCPInstance* ProxyServerMediaSubsession
+::createRTCP(Groupsock* RTCPgs, unsigned totSessionBW, /* in kbps */
+	     unsigned char const* cname, RTPSink* sink) {
+  ProxyServerMediaSession* parentSession = (ProxyServerMediaSession*)fParentSession;
+  return parentSession->createRTCP(RTCPgs, totSessionBW, cname, sink);
+}
+
 void ProxyServerMediaSubsession::subsessionByeHandler(void* clientData) {
   ((ProxyServerMediaSubsession*)clientData)->subsessionByeHandler();
 }
@@ -644,7 +752,9 @@ void ProxyServerMediaSubsession::subsessionByeHandler() {
 
   // This "BYE" signals that our input source has (effectively) closed, so pass this onto the front-end clients:
   fHaveSetupStream = False; // hack to stop "PAUSE" getting sent by:
-  FramedSource::handleClosure(fClientMediaSubsession.readSource());
+  if (fClientMediaSubsession.readSource() != NULL) {
+    fClientMediaSubsession.readSource()->handleClosure();
+  }
 
   // And then treat this as if we had lost connection to the back-end server,
   // and can reestablish streaming from it only by sending another "DESCRIBE":
@@ -665,20 +775,21 @@ PresentationTimeSessionNormalizer::PresentationTimeSessionNormalizer(UsageEnviro
 
 PresentationTimeSessionNormalizer::~PresentationTimeSessionNormalizer() {
   while (fSubsessionNormalizers != NULL) {
-    delete fSubsessionNormalizers;
+    Medium::close(fSubsessionNormalizers);
   }
 }
 
-PresentationTimeSubsessionNormalizer*
-PresentationTimeSessionNormalizer::createNewPresentationTimeSubsessionNormalizer(FramedSource* inputSource, RTPSource* rtpSource,
-										 char const* codecName) {
+PresentationTimeSubsessionNormalizer* PresentationTimeSessionNormalizer
+::createNewPresentationTimeSubsessionNormalizer(FramedSource* inputSource, RTPSource* rtpSource,
+						char const* codecName) {
   fSubsessionNormalizers
     = new PresentationTimeSubsessionNormalizer(*this, inputSource, rtpSource, codecName, fSubsessionNormalizers);
   return fSubsessionNormalizers;
 }
 
-void PresentationTimeSessionNormalizer::normalizePresentationTime(PresentationTimeSubsessionNormalizer* ssNormalizer,
-								  struct timeval& toPT, struct timeval const& fromPT) {
+void PresentationTimeSessionNormalizer
+::normalizePresentationTime(PresentationTimeSubsessionNormalizer* ssNormalizer,
+			    struct timeval& toPT, struct timeval const& fromPT) {
   Boolean const hasBeenSynced = ssNormalizer->fRTPSource->hasBeenSynchronizedUsingRTCP();
 
   if (!hasBeenSynced) {
diff --git a/liveMedia/QCELPAudioRTPSource.cpp b/liveMedia/QCELPAudioRTPSource.cpp
index 051d298..b99cff9 100644
--- a/liveMedia/QCELPAudioRTPSource.cpp
+++ b/liveMedia/QCELPAudioRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Qualcomm "PureVoice" (aka. "QCELP") Audio RTP Sources
 // Implementation
 
diff --git a/liveMedia/QuickTimeFileSink.cpp b/liveMedia/QuickTimeFileSink.cpp
index e54f446..d2eaf9e 100644
--- a/liveMedia/QuickTimeFileSink.cpp
+++ b/liveMedia/QuickTimeFileSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A sink that generates a QuickTime file from a composite media session
 // Implementation
 
@@ -318,7 +318,7 @@ QuickTimeFileSink::~QuickTimeFileSink() {
   MediaSubsessionIterator iter(fInputSession);
   MediaSubsession* subsession;
   while ((subsession = iter.next()) != NULL) {
-    subsession->readSource()->stopGettingFrames();
+    if (subsession->readSource() != NULL) subsession->readSource()->stopGettingFrames();
 
     SubsessionIOState* ioState
       = (SubsessionIOState*)(subsession->miscPtr);
@@ -355,6 +355,12 @@ QuickTimeFileSink::createNew(UsageEnvironment& env,
   return newSink;
 }
 
+void QuickTimeFileSink
+::noteRecordedFrame(MediaSubsession& /*inputSubsession*/,
+		    unsigned /*packetDataSize*/, struct timeval const& /*presentationTime*/) {
+  // Default implementation: Do nothing
+}
+
 Boolean QuickTimeFileSink::startPlaying(afterPlayingFunc* afterFunc,
 					void* afterClientData) {
   // Make sure we're not already being played:
@@ -717,6 +723,8 @@ void SubsessionIOState::afterGettingFrame(unsigned packetDataSize,
   fLastPacketRTPSeqNum = rtpSeqNum;
 
   // Now, continue working with the frame that we just got
+  fOurSink.noteRecordedFrame(fOurSubsession, packetDataSize, presentationTime);
+
   if (fBuffer->bytesInUse() == 0) {
     fBuffer->setPresentationTime(presentationTime);
   }
@@ -1036,8 +1044,8 @@ void SubsessionIOState::useFrameForHinting(unsigned frameSize,
     }
   } else if (hackm4a_generic) {
     // Synthesize a special header, so that this frame can be in its own RTP packet.
-    unsigned const sizeLength = fOurSubsession.fmtp_sizelength();
-    unsigned const indexLength = fOurSubsession.fmtp_indexlength();
+    unsigned const sizeLength = fOurSubsession.attrVal_unsigned("sizelength");
+    unsigned const indexLength = fOurSubsession.attrVal_unsigned("indexlength");
     if (sizeLength + indexLength != 16) {
       envir() << "Warning: unexpected 'sizeLength' " << sizeLength
 	      << " and 'indexLength' " << indexLength
@@ -2010,13 +2018,20 @@ addAtom(stss); // Sync-Sample
   unsigned numEntries = 0, numSamplesSoFar = 0;
   if (fCurrentIOState->fHeadSyncFrame != NULL) {
     SyncFrame* currentSyncFrame = fCurrentIOState->fHeadSyncFrame;
-    while(currentSyncFrame != NULL) {
+
+    // First, count the number of frames (to use as a sanity check; see below):
+    unsigned totNumFrames = 0;
+    for (ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk; chunk != NULL; chunk = chunk->fNextChunk) totNumFrames += chunk->fNumFrames;
+
+    while (currentSyncFrame != NULL) {
+      if (currentSyncFrame->sfFrameNum >= totNumFrames) break; // sanity check
+      
       ++numEntries;
       size += addWord(currentSyncFrame->sfFrameNum);
       currentSyncFrame = currentSyncFrame->nextSyncFrame;
     }
   } else {
-    // Then, run through the chunk descriptors, counting up the total nuber of samples:
+    // First, run through the chunk descriptors, counting up the total number of samples:
     unsigned const samplesPerFrame = fCurrentIOState->fQTSamplesPerFrame;
     ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
     while (chunk != NULL) {
diff --git a/liveMedia/QuickTimeGenericRTPSource.cpp b/liveMedia/QuickTimeGenericRTPSource.cpp
index 01a2b42..e6386ae 100644
--- a/liveMedia/QuickTimeGenericRTPSource.cpp
+++ b/liveMedia/QuickTimeGenericRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP Sources containing generic QuickTime stream data, as defined in
 //     <http://developer.apple.com/quicktime/icefloe/dispatch026.html>
 // Implementation
diff --git a/liveMedia/RTCP.cpp b/liveMedia/RTCP.cpp
index acf69c3..2559181 100644
--- a/liveMedia/RTCP.cpp
+++ b/liveMedia/RTCP.cpp
@@ -14,13 +14,16 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTCP
 // Implementation
 
 #include "RTCP.hh"
 #include "GroupsockHelper.hh"
 #include "rtcp_from_spec.h"
+#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
+#define snprintf _snprintf
+#endif
 
 ////////// RTCPMemberDatabase //////////
 
@@ -99,7 +102,7 @@ void RTCPMemberDatabase::reapOldMembers(unsigned threshold) {
 #ifdef DEBUG
         fprintf(stderr, "reap: removing SSRC 0x%x\n", oldSSRC);
 #endif
-      fOurRTCPInstance.removeSSRC(oldSSRC, True);
+	fOurRTCPInstance.removeSSRC(oldSSRC, True);
     }
   } while (foundOldMember);
 }
@@ -113,14 +116,14 @@ static double dTimeNow() {
     return (double) (timeNow.tv_sec + timeNow.tv_usec/1000000.0);
 }
 
-static unsigned const maxRTCPPacketSize = 1450;
+static unsigned const maxRTCPPacketSize = 1456;
 	// bytes (1500, minus some allowance for IP, UDP, UMTP headers)
-static unsigned const preferredPacketSize = 1000; // bytes
+static unsigned const preferredRTCPPacketSize = 1000; // bytes
 
 RTCPInstance::RTCPInstance(UsageEnvironment& env, Groupsock* RTCPgs,
 			   unsigned totSessionBW,
 			   unsigned char const* cname,
-			   RTPSink* sink, RTPSource const* source,
+			   RTPSink* sink, RTPSource* source,
 			   Boolean isSSMSource)
   : Medium(env), fRTCPInterface(this, RTCPgs), fTotSessionBW(totSessionBW),
     fSink(sink), fSource(source), fIsSSMSource(isSSMSource),
@@ -132,7 +135,8 @@ RTCPInstance::RTCPInstance(UsageEnvironment& env, Groupsock* RTCPgs,
     fByeHandlerTask(NULL), fByeHandlerClientData(NULL),
     fSRHandlerTask(NULL), fSRHandlerClientData(NULL),
     fRRHandlerTask(NULL), fRRHandlerClientData(NULL),
-    fSpecificRRHandlerTable(NULL) {
+    fSpecificRRHandlerTable(NULL),
+    fAppHandlerTask(NULL), fAppHandlerClientData(NULL) {
 #ifdef DEBUG
   fprintf(stderr, "RTCPInstance[%p]::RTCPInstance()\n", this);
 #endif
@@ -151,17 +155,19 @@ RTCPInstance::RTCPInstance(UsageEnvironment& env, Groupsock* RTCPgs,
   if (fKnownMembers == NULL || fInBuf == NULL) return;
   fNumBytesAlreadyRead = 0;
 
-  // A hack to save buffer space, because RTCP packets are always small:
-  unsigned savedMaxSize = OutPacketBuffer::maxSize;
-  OutPacketBuffer::maxSize = maxRTCPPacketSize;
-  fOutBuf = new OutPacketBuffer(preferredPacketSize, maxRTCPPacketSize);
-  OutPacketBuffer::maxSize = savedMaxSize;
+  fOutBuf = new OutPacketBuffer(preferredRTCPPacketSize, maxRTCPPacketSize, maxRTCPPacketSize);
   if (fOutBuf == NULL) return;
 
-  // Arrange to handle incoming reports from others:
-  TaskScheduler::BackgroundHandlerProc* handler
-    = (TaskScheduler::BackgroundHandlerProc*)&incomingReportHandler;
-  fRTCPInterface.startNetworkReading(handler);
+  if (fSource != NULL && fSource->RTPgs() == RTCPgs) {
+    // We're receiving RTCP reports that are multiplexed with RTP, so ask the RTP source
+    // to give them to us:
+    fSource->registerForMultiplexedRTCPPackets(this);
+  } else {
+    // Arrange to handle incoming reports from the network:
+    TaskScheduler::BackgroundHandlerProc* handler
+      = (TaskScheduler::BackgroundHandlerProc*)&incomingReportHandler;
+    fRTCPInterface.startNetworkReading(handler);
+  }
 
   // Send our first report.
   fTypeOfEvent = EVENT_REPORT;
@@ -182,6 +188,14 @@ RTCPInstance::~RTCPInstance() {
   fTypeOfEvent = EVENT_BYE; // not used, but...
   sendBYE();
 
+  if (fSource != NULL && fSource->RTPgs() == fRTCPInterface.gs()) {
+    // We were receiving RTCP reports that were multiplexed with RTP, so tell the RTP source
+    // to stop giving them to us:
+    fSource->deregisterForMultiplexedRTCPPackets();
+    fRTCPInterface.forgetOurGroupsock();
+      // so that the "fRTCPInterface" destructor doesn't turn off background read handling
+  }
+
   if (fSpecificRRHandlerTable != NULL) {
     AddressPortLookupTable::Iterator iter(*fSpecificRRHandlerTable);
     RRHandlerRecord* rrHandler;
@@ -196,10 +210,42 @@ RTCPInstance::~RTCPInstance() {
   delete[] fInBuf;
 }
 
+void RTCPInstance::noteArrivingRR(struct sockaddr_in const& fromAddressAndPort,
+				  int tcpSocketNum, unsigned char tcpStreamChannelId) {
+  // If a 'RR handler' was set, call it now:
+
+  // Specific RR handler:
+  if (fSpecificRRHandlerTable != NULL) {
+    netAddressBits fromAddr;
+    portNumBits fromPortNum;
+    if (tcpSocketNum < 0) {
+      // Normal case: We read the RTCP packet over UDP
+      fromAddr = fromAddressAndPort.sin_addr.s_addr;
+      fromPortNum = ntohs(fromAddressAndPort.sin_port);
+    } else {
+      // Special case: We read the RTCP packet over TCP (interleaved)
+      // Hack: Use the TCP socket and channel id to look up the handler
+      fromAddr = tcpSocketNum;
+      fromPortNum = tcpStreamChannelId;
+    }
+    Port fromPort(fromPortNum);
+    RRHandlerRecord* rrHandler
+      = (RRHandlerRecord*)(fSpecificRRHandlerTable->Lookup(fromAddr, (~0), fromPort));
+    if (rrHandler != NULL) {
+      if (rrHandler->rrHandlerTask != NULL) {
+	(*(rrHandler->rrHandlerTask))(rrHandler->rrHandlerClientData);
+      }
+    }
+  }
+  
+  // General RR handler:
+  if (fRRHandlerTask != NULL) (*fRRHandlerTask)(fRRHandlerClientData);
+}
+
 RTCPInstance* RTCPInstance::createNew(UsageEnvironment& env, Groupsock* RTCPgs,
 				      unsigned totSessionBW,
 				      unsigned char const* cname,
-				      RTPSink* sink, RTPSource const* source,
+				      RTPSink* sink, RTPSource* source,
 				      Boolean isSSMSource) {
   return new RTCPInstance(env, RTCPgs, totSessionBW, cname, sink, source,
 			  isSSMSource);
@@ -280,6 +326,46 @@ void RTCPInstance
   }
 }
 
+void RTCPInstance::setAppHandler(RTCPAppHandlerFunc* handlerTask, void* clientData) {
+  fAppHandlerTask = handlerTask;
+  fAppHandlerClientData = clientData;
+}
+
+void RTCPInstance::sendAppPacket(u_int8_t subtype, char const* name,
+				 u_int8_t* appDependentData, unsigned appDependentDataSize) {
+  // Set up the first 4 bytes: V,PT,subtype,PT,length:
+  u_int32_t rtcpHdr = 0x80000000; // version 2, no padding
+  rtcpHdr |= (subtype&0x1F)<<24;
+  rtcpHdr |= (RTCP_PT_APP<<16);
+  unsigned length = 2 + (appDependentDataSize+3)/4;
+  rtcpHdr |= (length&0xFFFF);
+  fOutBuf->enqueueWord(rtcpHdr);
+
+  // Set up the next 4 bytes: SSRC:
+  fOutBuf->enqueueWord(fSource != NULL ? fSource->SSRC() : fSink != NULL ? fSink->SSRC() : 0);
+
+  // Set up the next 4 bytes: name:
+  char nameBytes[4];
+  nameBytes[0] = nameBytes[1] = nameBytes[2] = nameBytes[3] = '\0'; // by default
+  if (name != NULL) {
+    snprintf(nameBytes, 4, "%s", name);
+  }
+  fOutBuf->enqueue((u_int8_t*)nameBytes, 4);
+
+  // Set up the remaining bytes (if any): application-dependent data (+ padding):
+  if (appDependentData != NULL && appDependentDataSize > 0) {
+    fOutBuf->enqueue(appDependentData, appDependentDataSize);
+
+    unsigned modulo = appDependentDataSize%4;
+    unsigned paddingSize = modulo == 0 ? 0 : 4-modulo;
+    u_int8_t const paddingByte = 0x00;
+    for (unsigned i = 0; i < paddingSize; ++i) fOutBuf->enqueue(&paddingByte, 1);
+  }
+
+  // Finally, send the packet:
+  sendBuiltPacket();
+}
+
 void RTCPInstance::setStreamSocket(int sockNum,
 				   unsigned char streamChannelId) {
   // Turn off background read handling:
@@ -308,6 +394,14 @@ void RTCPInstance::addStreamSocket(int sockNum,
   fRTCPInterface.startNetworkReading(handler);
 }
 
+void RTCPInstance
+::injectReport(u_int8_t const* packet, unsigned packetSize, struct sockaddr_in const& fromAddress) {
+  if (packetSize > maxRTCPPacketSize) packetSize = maxRTCPPacketSize;
+  memmove(fInBuf, packet, packetSize);
+
+  processIncomingReport(packetSize, fromAddress, -1, 0xFF); // assume report received over UDP
+}
+
 static unsigned const IP_UDP_HDR_SIZE = 28;
     // overhead (bytes) of IP and UDP hdrs
 
@@ -320,20 +414,23 @@ void RTCPInstance::incomingReportHandler(RTCPInstance* instance,
 
 void RTCPInstance::incomingReportHandler1() {
   do {
-    Boolean callByeHandler = False;
-    int tcpReadStreamSocketNum = fRTCPInterface.nextTCPReadStreamSocketNum();
-    unsigned char tcpReadStreamChannelId = fRTCPInterface.nextTCPReadStreamChannelId();
-    unsigned packetSize = 0;
-    unsigned numBytesRead;
-    struct sockaddr_in fromAddress;
-    Boolean packetReadWasIncomplete;
     if (fNumBytesAlreadyRead >= maxRTCPPacketSize) {
       envir() << "RTCPInstance error: Hit limit when reading incoming packet over TCP. Increase \"maxRTCPPacketSize\"\n";
       break;
     }
+
+    unsigned numBytesRead;
+    struct sockaddr_in fromAddress;
+    int tcpSocketNum;
+    unsigned char tcpStreamChannelId;
+    Boolean packetReadWasIncomplete;
     Boolean readResult
       = fRTCPInterface.handleRead(&fInBuf[fNumBytesAlreadyRead], maxRTCPPacketSize - fNumBytesAlreadyRead,
-				  numBytesRead, fromAddress, packetReadWasIncomplete);
+				  numBytesRead, fromAddress,
+				  tcpSocketNum, tcpStreamChannelId,
+				  packetReadWasIncomplete);
+
+    unsigned packetSize = 0;
     if (packetReadWasIncomplete) {
       fNumBytesAlreadyRead += numBytesRead;
       return; // more reads are needed to get the entire packet
@@ -359,35 +456,50 @@ void RTCPInstance::incomingReportHandler1() {
       }
     }
 
-    unsigned char* pkt = fInBuf;
     if (fIsSSMSource && !packetWasFromOurHost) {
-      // This packet is assumed to have been received via unicast (because we're a SSM source, and SSM receivers send back RTCP "RR"
-      // packets via unicast).  'Reflect' the packet by resending it to the multicast group, so that any other receivers can also
-      // get to see it.
+      // This packet is assumed to have been received via unicast (because we're a SSM source,
+      // and SSM receivers send back RTCP "RR" packets via unicast).
+      // 'Reflect' the packet by resending it to the multicast group, so that any other receivers
+      // can also get to see it.
 
       // NOTE: Denial-of-service attacks are possible here.
       // Users of this software may wish to add their own,
       // application-specific mechanism for 'authenticating' the
       // validity of this packet before reflecting it.
 
-      // NOTE: The test for "!packetWasFromOurHost" means that we won't reflect RTCP packets that come from other processes on
-      // the same host as us.  The reason for this is that the 'packet size' test above is not 100% reliable; some packets
-      // that were truly looped back from us might not be detected as such, and this might lead to infinite forwarding/receiving
-      // of some packets.  To avoid this possibility, we only reflect RTCP packets that we know for sure originated elsewhere.
-      // (Note, though, that if we ever re-enable the code in "Groupsock::multicastSendOnly()", then we could remove the test for
-      // "!packetWasFromOurHost".)
-      fRTCPInterface.sendPacket(pkt, packetSize);
+      // NOTE: The test for "!packetWasFromOurHost" means that we won't reflect RTCP packets
+      // that come from other processes on the same host as us.  The reason for this is that the
+      // 'packet size' test above is not 100% reliable; some packets that were truly looped back
+      // from us might not be detected as such, and this might lead to infinite
+      // forwarding/receiving of some packets.  To avoid this possibility, we reflect only
+      // RTCP packets that we know for sure originated elsewhere.
+      // (Note, though, that if we ever re-enable the code in "Groupsock::multicastSendOnly()",
+      // then we could remove the test for "!packetWasFromOurHost".)
+      fRTCPInterface.sendPacket(fInBuf, packetSize);
       fHaveJustSentPacket = True;
       fLastPacketSentSize = packetSize;
     }
 
+    processIncomingReport(packetSize, fromAddress, tcpSocketNum, tcpStreamChannelId);
+  } while (0);
+}
+
+void RTCPInstance
+::processIncomingReport(unsigned packetSize, struct sockaddr_in const& fromAddressAndPort,
+			int tcpSocketNum, unsigned char tcpStreamChannelId) {
+  do {
+    Boolean callByeHandler = False;
+    unsigned char* pkt = fInBuf;
+
 #ifdef DEBUG
-    fprintf(stderr, "[%p]saw incoming RTCP packet", this);
-    if (tcpReadStreamSocketNum < 0) {
-      // Note that "fromAddress" is valid only if we're receiving over UDP (not over TCP):
-      fprintf(stderr, " (from address %s, port %d)", AddressString(fromAddress).val(), ntohs(fromAddress.sin_port));
+    fprintf(stderr, "[%p]saw incoming RTCP packet (from ", this);
+    if (tcpSocketNum < 0) {
+      // Note that "fromAddressAndPort" is valid only if we're receiving over UDP (not over TCP):
+      fprintf(stderr, "address %s, port %d", AddressString(fromAddressAndPort).val(), ntohs(fromAddressAndPort.sin_port));
+    } else {
+      fprintf(stderr, "TCP socket #%d, stream channel id %d", tcpSocketNum, tcpStreamChannelId);
     }
-    fprintf(stderr, "\n");
+    fprintf(stderr, ")\n");
     for (unsigned i = 0; i < packetSize; ++i) {
       if (i%4 == 0) fprintf(stderr, " ");
       fprintf(stderr, "%02x", pkt[i]);
@@ -399,10 +511,11 @@ void RTCPInstance::incomingReportHandler1() {
     // Check the RTCP packet for validity:
     // It must at least contain a header (4 bytes), and this header
     // must be version=2, with no padding bit, and a payload type of
-    // SR (200) or RR (201):
+    // SR (200), RR (201), or APP (204):
     if (packetSize < 4) break;
     unsigned rtcpHdr = ntohl(*(u_int32_t*)pkt);
-    if ((rtcpHdr & 0xE0FE0000) != (0x80000000 | (RTCP_PT_SR<<16))) {
+    if ((rtcpHdr & 0xE0FE0000) != (0x80000000 | (RTCP_PT_SR<<16)) &&
+	(rtcpHdr & 0xE0FF0000) != (0x80000000 | (RTCP_PT_APP<<16))) {
 #ifdef DEBUG
       fprintf(stderr, "rejected bad RTCP packet: header 0x%08x\n", rtcpHdr);
 #endif
@@ -415,8 +528,8 @@ void RTCPInstance::incomingReportHandler1() {
     unsigned reportSenderSSRC = 0;
     Boolean packetOK = False;
     while (1) {
-      unsigned rc = (rtcpHdr>>24)&0x1F;
-      unsigned pt = (rtcpHdr>>16)&0xFF;
+      u_int8_t rc = (rtcpHdr>>24)&0x1F;
+      u_int8_t pt = (rtcpHdr>>16)&0xFF;
       unsigned length = 4*(rtcpHdr&0xFFFF); // doesn't count hdr
       ADVANCE(4); // skip over the header
       if (length > packetSize) break;
@@ -424,6 +537,15 @@ void RTCPInstance::incomingReportHandler1() {
       // Assume that each RTCP subpacket begins with a 4-byte SSRC:
       if (length < 4) break; length -= 4;
       reportSenderSSRC = ntohl(*(u_int32_t*)pkt); ADVANCE(4);
+#ifdef HACK_FOR_CHROME_WEBRTC_BUG
+      if (reportSenderSSRC == 0x00000001 && pt == RTCP_PT_RR) {
+	// Chrome (and Opera) WebRTC receivers have a bug that causes them to always send
+	// SSRC 1 in their "RR"s.  To work around this (to help us distinguish between different
+	// receivers), we use a fake SSRC in this case consisting of the IP address, XORed with
+	// the port number:
+	reportSenderSSRC = fromAddressAndPort.sin_addr.s_addr^fromAddressAndPort.sin_port;
+      }
+#endif
 
       Boolean subPacketOK = False;
       switch (pt) {
@@ -470,7 +592,7 @@ void RTCPInstance::incomingReportHandler1() {
                 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,
+                transmissionStats.noteIncomingRR(reportSenderSSRC, fromAddressAndPort,
 						 lossStats,
 						 highestReceived, jitter,
 						 timeLastSR, timeSinceLastSR);
@@ -483,34 +605,7 @@ void RTCPInstance::incomingReportHandler1() {
           }
 
 	  if (pt == RTCP_PT_RR) { // i.e., we didn't fall through from 'SR'
-	    // If a 'RR handler' was set, call it now:
-
-	    // Specific RR handler:
-	    if (fSpecificRRHandlerTable != NULL) {
-	      netAddressBits fromAddr;
-	      portNumBits fromPortNum;
-	      if (tcpReadStreamSocketNum < 0) {
-		// Normal case: We read the RTCP packet over UDP
-		fromAddr = fromAddress.sin_addr.s_addr;
-		fromPortNum = ntohs(fromAddress.sin_port);
-	      } else {
-		// Special case: We read the RTCP packet over TCP (interleaved)
-		// Hack: Use the TCP socket and channel id to look up the handler
-		fromAddr = tcpReadStreamSocketNum;
-		fromPortNum = tcpReadStreamChannelId;
-	      }
-	      Port fromPort(fromPortNum);
-	      RRHandlerRecord* rrHandler
-		= (RRHandlerRecord*)(fSpecificRRHandlerTable->Lookup(fromAddr, (~0), fromPort));
-	      if (rrHandler != NULL) {
-		if (rrHandler->rrHandlerTask != NULL) {
-		  (*(rrHandler->rrHandlerTask))(rrHandler->rrHandlerClientData);
-		}
-	      }
-	    }
-
-	    // General RR handler:
-	    if (fRRHandlerTask != NULL) (*fRRHandlerTask)(fRRHandlerClientData);
+	    noteArrivingRR(fromAddressAndPort, tcpSocketNum, tcpStreamChannelId);
 	  }
 
 	  subPacketOK = True;
@@ -538,20 +633,161 @@ void RTCPInstance::incomingReportHandler1() {
 	  typeOfPacket = PACKET_BYE;
 	  break;
 	}
-	// Later handle SDES, APP, and compound RTCP packets #####
-        default:
+        case RTCP_PT_APP: {
+	  u_int8_t& subtype = rc; // In "APP" packets, the "rc" field gets used as "subtype"
+#ifdef DEBUG
+	  fprintf(stderr, "APP (subtype 0x%02x)\n", subtype);
+#endif
+	  if (length < 4) {
 #ifdef DEBUG
-	  fprintf(stderr, "UNSUPPORTED TYPE(0x%x)\n", pt);
+	    fprintf(stderr, "\tError: No \"name\" field!\n");
+#endif
+	    break;
+	  }
+#ifdef DEBUG
+	  fprintf(stderr, "\tname:%c%c%c%c\n", pkt[0], pkt[1], pkt[2], pkt[3]);
+#endif
+	  u_int32_t nameBytes = (pkt[0]<<24)|(pkt[1]<<16)|(pkt[2]<<8)|(pkt[3]);
+	  ADVANCE(4); // skip over "name", to the 'application-dependent data'
+#ifdef DEBUG
+	  fprintf(stderr, "\tapplication-dependent data size: %d bytes\n", length);
+#endif
+
+	  // If an 'APP' packet handler was set, call it now:
+	  if (fAppHandlerTask != NULL) {
+	    (*fAppHandlerTask)(fAppHandlerClientData, subtype, nameBytes, pkt, length);
+	  }
+	  subPacketOK = True;
+	  typeOfPacket = PACKET_RTCP_APP;
+	  break;
+	}
+	// Other RTCP packet types that we don't yet handle:
+        case RTCP_PT_SDES: {
+#ifdef DEBUG
+	  // 'Handle' SDES packets only in debugging code, by printing out the 'SDES items':
+	  fprintf(stderr, "SDES\n");
+
+	  // Process each 'chunk':
+	  Boolean chunkOK = False;
+	  ADVANCE(-4); length += 4; // hack so that we see the first SSRC/CSRC again
+	  while (length >= 8) { // A valid chunk must be at least 8 bytes long
+	    chunkOK = False; // until we learn otherwise
+
+	    u_int32_t SSRC_CSRC = ntohl(*(u_int32_t*)pkt); ADVANCE(4); length -= 4;
+	    fprintf(stderr, "\tSSRC/CSRC: 0x%08x\n", SSRC_CSRC);
+
+	    // Process each 'SDES item' in the chunk:
+	    u_int8_t itemType = *pkt; ADVANCE(1); --length;
+	    while (itemType != 0) {
+	      unsigned itemLen = *pkt; ADVANCE(1); --length;
+	      // Make sure "itemLen" allows for at least 1 zero byte at the end of the chunk:
+	      if (itemLen + 1 > length || pkt[itemLen] != 0) break;
+
+	      fprintf(stderr, "\t\t%s:%s\n",
+		      itemType == 1 ? "CNAME" :
+		      itemType == 2 ? "NAME" :
+		      itemType == 3 ? "EMAIL" :
+		      itemType == 4 ? "PHONE" :
+		      itemType == 5 ? "LOC" :
+		      itemType == 6 ? "TOOL" :
+		      itemType == 7 ? "NOTE" :
+		      itemType == 8 ? "PRIV" :
+		      "(unknown)",
+		      itemType < 8 ? (char*)pkt // hack, because we know it's '\0'-terminated
+		      : "???"/* don't try to print out PRIV or unknown items */);
+	      ADVANCE(itemLen); length -= itemLen;
+
+	      itemType = *pkt; ADVANCE(1); --length;
+	    }
+	    if (itemType != 0) break; // bad 'SDES item'
+
+	    // Thus, itemType == 0.  This zero 'type' marks the end of the list of SDES items.
+	    // Skip over remaining zero padding bytes, so that this chunk ends on a 4-byte boundary:
+	    while (length%4 > 0 && *pkt == 0) { ADVANCE(1); --length; }
+	    if (length%4 > 0) break; // Bad (non-zero) padding byte
+
+	    chunkOK = True;
+	  }
+	  if (!chunkOK || length > 0) break; // bad chunk, or not enough bytes for the last chunk
 #endif
 	  subPacketOK = True;
 	  break;
+	}
+        case RTCP_PT_RTPFB: {
+#ifdef DEBUG
+	  fprintf(stderr, "RTPFB(unhandled)\n");
+#endif
+	  subPacketOK = True;
+	  break;
+	}
+        case RTCP_PT_PSFB: {
+#ifdef DEBUG
+	  fprintf(stderr, "PSFB(unhandled)\n");
+	  // Temporary code to show "Receiver Estimated Maximum Bitrate" (REMB) feedback reports:
+	  //#####
+	  if (length >= 12 && pkt[4] == 'R' && pkt[5] == 'E' && pkt[6] == 'M' && pkt[7] == 'B') {
+	    u_int8_t exp = pkt[9]>>2;
+	    u_int32_t mantissa = ((pkt[9]&0x03)<<16)|(pkt[10]<<8)|pkt[11];
+	    double remb = (double)mantissa;
+	    while (exp > 0) {
+	      remb *= 2.0;
+	      exp /= 2;
+	    }
+	    fprintf(stderr, "\tReceiver Estimated Max Bitrate (REMB): %g bps\n", remb);
+	  }
+#endif
+	  subPacketOK = True;
+	  break;
+	}
+        case RTCP_PT_XR: {
+#ifdef DEBUG
+	  fprintf(stderr, "XR(unhandled)\n");
+#endif
+	  subPacketOK = True;
+	  break;
+	}
+        case RTCP_PT_AVB: {
+#ifdef DEBUG
+	  fprintf(stderr, "AVB(unhandled)\n");
+#endif
+	  subPacketOK = True;
+	  break;
+	}
+        case RTCP_PT_RSI: {
+#ifdef DEBUG
+	  fprintf(stderr, "RSI(unhandled)\n");
+#endif
+	  subPacketOK = True;
+	  break;
+	}
+        case RTCP_PT_TOKEN: {
+#ifdef DEBUG
+	  fprintf(stderr, "TOKEN(unhandled)\n");
+#endif
+	  subPacketOK = True;
+	  break;
+	}
+        case RTCP_PT_IDMS: {
+#ifdef DEBUG
+	  fprintf(stderr, "IDMS(unhandled)\n");
+#endif
+	  subPacketOK = True;
+	  break;
+	}
+        default: {
+#ifdef DEBUG
+	  fprintf(stderr, "UNKNOWN TYPE(0x%x)\n", pt);
+#endif
+	  subPacketOK = True;
+	  break;
+	}
       }
       if (!subPacketOK) break;
 
       // need to check for (& handle) SSRC collision! #####
 
 #ifdef DEBUG
-      fprintf(stderr, "validated RTCP subpacket (type %d): %d, %d, %d, 0x%08x\n", typeOfPacket, rc, pt, length, reportSenderSSRC);
+      fprintf(stderr, "validated RTCP subpacket: rc:%d, pt:%d, bytes remaining:%d, report sender SSRC:0x%08x\n", rc, pt, length, reportSenderSSRC);
 #endif
 
       // Skip over any remaining bytes in this subpacket:
@@ -707,7 +943,8 @@ Boolean RTCPInstance::addReport(Boolean alwaysAdd) {
     }
 
     addSR();
-  } else if (fSource != NULL) {
+  }
+  if (fSource != NULL) {
     if (!alwaysAdd) {
       if (!fSource->enableRTCPReports()) return False;
     }
diff --git a/liveMedia/RTPInterface.cpp b/liveMedia/RTPInterface.cpp
index 3d88d55..212a551 100644
--- a/liveMedia/RTPInterface.cpp
+++ b/liveMedia/RTPInterface.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // An abstraction of a network interface used for RTP (or RTCP).
 // (This allows the RTP-over-TCP hack (RFC 2326, section 10.12) to
 // be implemented transparently.)
@@ -138,6 +138,7 @@ void RTPInterface::setStreamSocket(int sockNum,
   fGS->removeAllDestinations();
   envir().taskScheduler().disableBackgroundHandling(fGS->socketNum()); // turn off any reading on our datagram socket
   fGS->reset(); // and close our datagram socket, because we won't be using it anymore
+
   addStreamSocket(sockNum, streamChannelId);
 }
 
@@ -171,19 +172,28 @@ static void deregisterSocket(UsageEnvironment& env, int sockNum, unsigned char s
 
 void RTPInterface::removeStreamSocket(int sockNum,
 				      unsigned char streamChannelId) {
-  for (tcpStreamRecord** streamsPtr = &fTCPStreams; *streamsPtr != NULL;
-       streamsPtr = &((*streamsPtr)->fNext)) {
-    if ((*streamsPtr)->fStreamSocketNum == sockNum
-	&& (*streamsPtr)->fStreamChannelId == streamChannelId) {
-      deregisterSocket(envir(), sockNum, streamChannelId);
-
-      // Then remove the record pointed to by *streamsPtr :
-      tcpStreamRecord* next = (*streamsPtr)->fNext;
-      (*streamsPtr)->fNext = NULL;
-      delete (*streamsPtr);
-      *streamsPtr = next;
-      return;
+  while (1) {
+    tcpStreamRecord** streamsPtr = &fTCPStreams;
+
+    while (*streamsPtr != NULL) {
+      if ((*streamsPtr)->fStreamSocketNum == sockNum
+	  && (streamChannelId == 0xFF || streamChannelId == (*streamsPtr)->fStreamChannelId)) {
+	// Delete the record pointed to by *streamsPtr :
+	tcpStreamRecord* next = (*streamsPtr)->fNext;
+	(*streamsPtr)->fNext = NULL;
+	delete (*streamsPtr);
+	*streamsPtr = next;
+
+	// And 'deregister' this socket,channelId pair:
+	deregisterSocket(envir(), sockNum, streamChannelId);
+
+	if (streamChannelId != 0xFF) return; // we're done
+	break; // start again from the beginning of the list, in case the list has changed
+      } else {
+	streamsPtr = &((*streamsPtr)->fNext);
+      }
     }
+    if (*streamsPtr == NULL) break;
   }
 }
 
@@ -202,13 +212,14 @@ Boolean RTPInterface::sendPacket(unsigned char* packet, unsigned packetSize) {
   Boolean success = True; // we'll return False instead if any of the sends fail
 
   // Normal case: Send as a UDP packet:
-  if (!fGS->output(envir(), fGS->ttl(), packet, packetSize)) success = False;
+  if (!fGS->output(envir(), packet, packetSize)) success = False;
 
   // Also, send over each of our TCP sockets:
-  for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;
-       streams = streams->fNext) {
+  tcpStreamRecord* nextStream;
+  for (tcpStreamRecord* stream = fTCPStreams; stream != NULL; stream = nextStream) {
+    nextStream = stream->fNext; // Set this now, in case the following deletes "stream":
     if (!sendRTPorRTCPPacketOverTCP(packet, packetSize,
-				    streams->fStreamSocketNum, streams->fStreamChannelId)) {
+				    stream->fStreamSocketNum, stream->fStreamChannelId)) {
       success = False;
     }
   }
@@ -235,14 +246,20 @@ void RTPInterface
 }
 
 Boolean RTPInterface::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
-				 unsigned& bytesRead, struct sockaddr_in& fromAddress, Boolean& packetReadWasIncomplete) {
+				 unsigned& bytesRead, struct sockaddr_in& fromAddress,
+				 int& tcpSocketNum, unsigned char& tcpStreamChannelId,
+				 Boolean& packetReadWasIncomplete) {
   packetReadWasIncomplete = False; // by default
   Boolean readSuccess;
   if (fNextTCPReadStreamSocketNum < 0) {
     // Normal case: read from the (datagram) 'groupsock':
+    tcpSocketNum = -1;
     readSuccess = fGS->handleRead(buffer, bufferMaxSize, bytesRead, fromAddress);
   } else {
     // Read from the TCP connection:
+    tcpSocketNum = fNextTCPReadStreamSocketNum;
+    tcpStreamChannelId = fNextTCPReadStreamChannelId;
+
     bytesRead = 0;
     unsigned totBytesToRead = fNextTCPReadSize;
     if (totBytesToRead > bufferMaxSize) totBytesToRead = bufferMaxSize;
@@ -280,7 +297,7 @@ Boolean RTPInterface::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
 
 void RTPInterface::stopNetworkReading() {
   // Normal case
-  envir().taskScheduler().turnOffBackgroundReadHandling(fGS->socketNum());
+  if (fGS != NULL) envir().taskScheduler().turnOffBackgroundReadHandling(fGS->socketNum());
 
   // Also turn off read handling on each of our TCP connections:
   for (tcpStreamRecord* streams = fTCPStreams; streams != NULL; streams = streams->fNext) {
@@ -324,6 +341,10 @@ Boolean RTPInterface::sendRTPorRTCPPacketOverTCP(u_int8_t* packet, unsigned pack
   return False;
 }
 
+#ifndef RTPINTERFACE_BLOCKING_WRITE_TIMEOUT_MS
+#define RTPINTERFACE_BLOCKING_WRITE_TIMEOUT_MS 500
+#endif
+
 Boolean RTPInterface::sendDataOverTCP(int socketNum, u_int8_t const* data, unsigned dataSize, Boolean forceSendToSucceed) {
   int sendResult = send(socketNum, (char const*)data, dataSize, 0/*flags*/);
   if (sendResult < (int)dataSize) {
@@ -338,11 +359,29 @@ Boolean RTPInterface::sendDataOverTCP(int socketNum, u_int8_t const* data, unsig
 #ifdef DEBUG_SEND
       fprintf(stderr, "sendDataOverTCP: resending %d-byte send (blocking)\n", numBytesRemainingToSend); fflush(stderr);
 #endif
-      makeSocketBlocking(socketNum);
+      makeSocketBlocking(socketNum, RTPINTERFACE_BLOCKING_WRITE_TIMEOUT_MS);
       sendResult = send(socketNum, (char const*)(&data[numBytesSentSoFar]), numBytesRemainingToSend, 0/*flags*/);
+      if ((unsigned)sendResult != numBytesRemainingToSend) {
+	// The blocking "send()" failed, or timed out.  In either case, we assume that the
+	// TCP connection has failed (or is 'hanging' indefinitely), and we stop using it
+	// (for both RTP and RTP).
+	// (If we kept using the socket here, the RTP or RTCP packet write would be in an
+	//  incomplete, inconsistent state.)
+#ifdef DEBUG_SEND
+	fprintf(stderr, "sendDataOverTCP: blocking send() failed (delivering %d bytes out of %d); closing socket %d\n", sendResult, numBytesRemainingToSend, socketNum); fflush(stderr);
+#endif
+	removeStreamSocket(socketNum, 0xFF);
+	return False;
+      }
       makeSocketNonBlocking(socketNum);
-      return sendResult == (int)numBytesRemainingToSend;
+
+      return True;
+    } else if (sendResult < 0 && envir().getErrno() != EAGAIN) {
+      // Because the "send()" call failed, assume that the socket is now unusable, so stop
+      // using it (for both RTP and RTCP):
+      removeStreamSocket(socketNum, 0xFF);
     }
+
     return False;
   }
 
@@ -420,7 +459,7 @@ void SocketDescriptor
 #endif
   fSubChannelHashTable->Remove((char const*)(long)streamChannelId);
 
-  if (fSubChannelHashTable->IsEmpty()) {
+  if (fSubChannelHashTable->IsEmpty() || streamChannelId == 0xFF) {
     // No more interfaces are using us, so it's curtains for us now:
     if (fAreInReadHandlerLoop) {
       fDeleteMyselfNext = True; // we can't delete ourself yet, but we'll do so from "tcpReadHandler()" below
diff --git a/liveMedia/RTPSink.cpp b/liveMedia/RTPSink.cpp
index 4646bf1..2d85a3b 100644
--- a/liveMedia/RTPSink.cpp
+++ b/liveMedia/RTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP Sinks
 // Implementation
 
@@ -52,7 +52,7 @@ RTPSink::RTPSink(UsageEnvironment& env,
     fRTPPayloadType(rtpPayloadType),
     fPacketCount(0), fOctetCount(0), fTotalOctetCount(0),
     fTimestampFrequency(rtpTimestampFrequency), fNextTimestampHasBeenPreset(False), fEnableRTCPReports(True),
-    fNumChannels(numChannels) {
+    fNumChannels(numChannels), fEstimatedBitrate(0) {
   fRTPPayloadFormatName
     = strDup(rtpPayloadFormatName == NULL ? "???" : rtpPayloadFormatName);
   gettimeofday(&fCreationTime, NULL);
@@ -69,6 +69,9 @@ RTPSink::RTPSink(UsageEnvironment& env,
 RTPSink::~RTPSink() {
   delete fTransmissionStatsDB;
   delete[] (char*)fRTPPayloadFormatName;
+  fRTPInterface.forgetOurGroupsock();
+    // so that the "fRTCPInterface" destructor doesn't turn off background read handling (in case
+    // its 'groupsock' is being shared with something else that does background read handling).
 }
 
 u_int32_t RTPSink::convertToRTPTimestamp(struct timeval tv) {
diff --git a/liveMedia/RTPSource.cpp b/liveMedia/RTPSource.cpp
index 64b21f8..1946d99 100644
--- a/liveMedia/RTPSource.cpp
+++ b/liveMedia/RTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP Sources
 // Implementation
 
@@ -54,6 +54,7 @@ RTPSource::RTPSource(UsageEnvironment& env, Groupsock* RTPgs,
   : FramedSource(env),
     fRTPInterface(this, RTPgs),
     fCurPacketHasBeenSynchronizedUsingRTCP(False), fLastReceivedSSRC(0),
+    fRTCPInstanceForMultiplexedRTCPPackets(NULL),
     fRTPPayloadFormat(rtpPayloadFormat), fTimestampFrequency(rtpTimestampFrequency),
     fSSRC(our_random32()), fEnableRTCPReports(True) {
   fReceptionStatsDB = new RTPReceptionStatsDB();
diff --git a/liveMedia/RTSPClient.cpp b/liveMedia/RTSPClient.cpp
index e51c12c..3731783 100644
--- a/liveMedia/RTSPClient.cpp
+++ b/liveMedia/RTSPClient.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic RTSP client
 // Implementation
 
@@ -37,7 +37,7 @@ RTSPClient* RTSPClient::createNew(UsageEnvironment& env, char const* rtspURL,
 }
 
 unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
 }
 
@@ -47,7 +47,7 @@ unsigned RTSPClient::sendOptionsCommand(responseHandler* responseHandler, Authen
 }
 
 unsigned RTSPClient::sendAnnounceCommand(char const* sdpDescription, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "ANNOUNCE", responseHandler, NULL, NULL, False, 0.0, 0.0, 0.0, sdpDescription));
 }
 
@@ -55,7 +55,7 @@ unsigned RTSPClient::sendSetupCommand(MediaSubsession& subsession, responseHandl
                                       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;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
 
   u_int32_t booleanFlags = 0;
   if (streamUsingTCP) booleanFlags |= 0x1;
@@ -67,7 +67,7 @@ unsigned RTSPClient::sendSetupCommand(MediaSubsession& subsession, responseHandl
 unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
                                      double start, double end, float scale,
                                      Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   sendDummyUDPPackets(session); // hack to improve NAT traversal
   return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, &session, NULL, 0, start, end, scale));
 }
@@ -75,7 +75,7 @@ unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* res
 unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
                                      double start, double end, float scale,
                                      Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   sendDummyUDPPackets(subsession); // hack to improve NAT traversal
   return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, NULL, &subsession, 0, start, end, scale));
 }
@@ -83,7 +83,7 @@ unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandle
 unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
 				     char const* absStartTime, char const* absEndTime, float scale,
                                      Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   sendDummyUDPPackets(session); // hack to improve NAT traversal
   return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, &session, NULL));
 }
@@ -91,45 +91,45 @@ unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* res
 unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
 				     char const* absStartTime, char const* absEndTime, float scale,
                                      Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   sendDummyUDPPackets(subsession); // hack to improve NAT traversal
   return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, NULL, &subsession));
 }
 
 unsigned RTSPClient::sendPauseCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, &session));
 }
 
 unsigned RTSPClient::sendPauseCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, NULL, &subsession));
 }
 
 unsigned RTSPClient::sendRecordCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, &session));
 }
 
 unsigned RTSPClient::sendRecordCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, NULL, &subsession));
 }
 
 unsigned RTSPClient::sendTeardownCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, &session));
 }
 
 unsigned RTSPClient::sendTeardownCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, NULL, &subsession));
 }
 
 unsigned RTSPClient::sendSetParameterCommand(MediaSession& session, responseHandler* responseHandler,
                                              char const* parameterName, char const* parameterValue,
                                              Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) 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));
@@ -139,7 +139,7 @@ unsigned RTSPClient::sendSetParameterCommand(MediaSession& session, responseHand
 
 unsigned RTSPClient::sendGetParameterCommand(MediaSession& session, responseHandler* responseHandler, char const* parameterName,
                                              Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
 
   // We assume that:
   //    parameterName is NULL means: Send no body in the request.
@@ -175,8 +175,23 @@ void RTSPClient::sendDummyUDPPackets(MediaSubsession& subsession, unsigned numDu
   if (subsession.rtcpInstance() != NULL) gs2 = subsession.rtcpInstance()->RTCPgs();
   u_int32_t const dummy = 0xFEEDFACE;
   for (unsigned i = 0; i < numDummyPackets; ++i) {
-    if (gs1 != NULL) gs1->output(envir(), 255, (unsigned char*)&dummy, sizeof dummy);
-    if (gs2 != NULL) gs2->output(envir(), 255, (unsigned char*)&dummy, sizeof dummy);
+    if (gs1 != NULL) gs1->output(envir(), (unsigned char*)&dummy, sizeof dummy);
+    if (gs2 != NULL) gs2->output(envir(), (unsigned char*)&dummy, sizeof dummy);
+  }
+}
+
+void RTSPClient::setSpeed(MediaSession& session, float speed) { 
+  // Optionally set download speed for session to be used later on PLAY command:
+  // The user should call this function after the MediaSession is instantiated, but before the
+  // first "sendPlayCommand()" is called.
+  if (&session != NULL) {
+    session.speed() = speed;
+    MediaSubsessionIterator iter(session);
+    MediaSubsession* subsession;
+
+    while ((subsession = iter.next()) != NULL) {
+      subsession->speed() = speed;
+    }
   }
 }
 
@@ -210,6 +225,27 @@ Boolean RTSPClient::lookupByName(UsageEnvironment& env,
   return True;
 }
 
+static void copyUsernameOrPasswordStringFromURL(char* dest, char const* src, unsigned len) {
+  // Normally, we just copy from the source to the destination.  However, if the source contains
+  // %-encoded characters, then we decode them while doing the copy:
+  while (len > 0) {
+    int nBefore = 0;
+    int nAfter = 0;
+    
+    if (*src == '%' && len >= 3 && sscanf(src+1, "%n%2hhx%n", &nBefore, dest, &nAfter) == 1) {
+      unsigned codeSize = nAfter - nBefore; // should be 1 or 2
+
+      ++dest;
+      src += (1 + codeSize);
+      len -= (1 + codeSize);
+    } else {
+      *dest++ = *src++;
+      --len;
+    }
+  }
+  *dest = '\0';
+}
+
 Boolean RTSPClient::parseRTSPURL(UsageEnvironment& env, char const* url,
 				 char*& username, char*& password,
 				 NetAddress& address,
@@ -243,15 +279,13 @@ Boolean RTSPClient::parseRTSPURL(UsageEnvironment& env, char const* url,
 	char const* usernameStart = from;
 	unsigned usernameLen = colonPasswordStart - usernameStart;
 	username = new char[usernameLen + 1] ; // allow for the trailing '\0'
-	for (unsigned i = 0; i < usernameLen; ++i) username[i] = usernameStart[i];
-	username[usernameLen] = '\0';
+	copyUsernameOrPasswordStringFromURL(username, usernameStart, usernameLen);
 
 	char const* passwordStart = colonPasswordStart;
 	if (passwordStart < p) ++passwordStart; // skip over the ':'
 	unsigned passwordLen = p - passwordStart;
 	password = new char[passwordLen + 1]; // allow for the trailing '\0'
-	for (unsigned j = 0; j < passwordLen; ++j) password[j] = passwordStart[j];
-	password[passwordLen] = '\0';
+	copyUsernameOrPasswordStringFromURL(password, passwordStart, passwordLen);
 
 	from = p + 1; // skip over the '@'
 	break;
@@ -325,8 +359,10 @@ RTSPClient::RTSPClient(UsageEnvironment& env, char const* rtspURL,
 		       int verbosityLevel, char const* applicationName,
 		       portNumBits tunnelOverHTTPPortNum, int socketNumToServer)
   : Medium(env),
-    fVerbosityLevel(verbosityLevel), fCSeq(1), fServerAddress(0),
-    fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum), fUserAgentHeaderStr(NULL), fUserAgentHeaderStrLen(0),
+    desiredMaxIncomingPacketSize(0), fVerbosityLevel(verbosityLevel), fCSeq(1),
+    fAllowBasicAuthentication(True), fServerAddress(0),
+    fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum),
+    fUserAgentHeaderStr(NULL), fUserAgentHeaderStrLen(0),
     fInputSocketNum(-1), fOutputSocketNum(-1), fBaseURL(NULL), fTCPStreamIdCount(0),
     fLastSessionId(NULL), fSessionTimeoutParameter(0), fSessionCookieCounter(0), fHTTPTunnelingConnectionIsPending(False) {
   setBaseURL(rtspURL);
@@ -539,6 +575,19 @@ static char* createSessionString(char const* sessionId) {
   return sessionStr;
 }
 
+// Add support for faster download thru "speed:" option on PLAY
+static char* createSpeedString(float speed) {
+  char buf[100];
+  if (speed == 1.0f ) {
+    // This is the default value; we don't need a "Speed:" header:
+    buf[0] = '\0';
+  } else {
+    sprintf(buf, "Speed: %.3f\r\n",speed);
+  }
+
+  return strDup(buf);
+}
+
 static char* createScaleString(float scale, float currentScale) {
   char buf[100];
   if (scale == 1.0f && currentScale == 1.0f) {
@@ -638,14 +687,14 @@ Boolean RTSPClient::setRequestFields(RequestRecord* request,
       Boolean requestMulticastStreaming
 	= IsMulticastAddress(connectionAddress) || (connectionAddress == 0 && forceMulticastOnUnspecified);
       transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast";
-      portTypeStr = ";client_port";
+      portTypeStr = requestMulticastStreaming ? ";port" : ";client_port";
       rtpNumber = subsession.clientPortNum();
       if (rtpNumber == 0) {
 	envir().setResultMsg("Client port number unknown\n");
 	delete[] cmdURL;
 	return False;
       }
-      rtcpNumber = rtpNumber + 1;
+      rtcpNumber = subsession.rtcpIsMuxed() ? rtpNumber : rtpNumber + 1;
     }
     unsigned transportSize = strlen(transportFmt)
       + strlen(transportTypeStr) + strlen(modeStr) + strlen(portTypeStr) + 2*5 /* max port len */;
@@ -656,11 +705,15 @@ Boolean RTSPClient::setRequestFields(RequestRecord* request,
     // 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)];
+    // Optionally include a "Blocksize:" string:
+    char* blocksizeStr = createBlocksizeString(streamUsingTCP);
+
+    // The "Transport:" and "Session:" (if present) and "Blocksize:" (if present) headers
+    // make up the 'extra headers':
+    extraHeaders = new char[transportSize + strlen(sessionStr) + strlen(blocksizeStr)];
     extraHeadersWereAllocated = True;
-    sprintf(extraHeaders, "%s%s", transportStr, sessionStr);
-    delete[] transportStr; delete[] sessionStr;
+    sprintf(extraHeaders, "%s%s%s", transportStr, sessionStr, blocksizeStr);
+    delete[] transportStr; delete[] sessionStr; delete[] blocksizeStr;
   } else if (strcmp(request->commandName(), "GET") == 0 || strcmp(request->commandName(), "POST") == 0) {
     // We will be sending a HTTP (not a RTSP) request.
     // Begin by re-parsing our RTSP URL, to get the stream name (which we'll use as our 'cmdURL'
@@ -750,14 +803,17 @@ Boolean RTSPClient::setRequestFields(RequestRecord* request,
     }
     
     if (strcmp(request->commandName(), "PLAY") == 0) {
-      // Create "Session:", "Scale:", and "Range:" headers; these make up the 'extra headers':
+      // Create possible "Session:", "Scale:", "Speed:", and "Range:" headers;
+      // these make up the 'extra headers':
       char* sessionStr = createSessionString(sessionId);
       char* scaleStr = createScaleString(request->scale(), originalScale);
+      float speed = request->session() != NULL ? request->session()->speed() : request->subsession()->speed();
+      char* speedStr = createSpeedString(speed);
       char* rangeStr = createRangeString(request->start(), request->end(), request->absStartTime(), request->absEndTime());
-      extraHeaders = new char[strlen(sessionStr) + strlen(scaleStr) + strlen(rangeStr) + 1];
+      extraHeaders = new char[strlen(sessionStr) + strlen(scaleStr) + strlen(speedStr) + strlen(rangeStr) + 1];
       extraHeadersWereAllocated = True;
-      sprintf(extraHeaders, "%s%s%s", sessionStr, scaleStr, rangeStr);
-      delete[] sessionStr; delete[] scaleStr; delete[] rangeStr;
+      sprintf(extraHeaders, "%s%s%s%s", sessionStr, scaleStr, speedStr, rangeStr);
+      delete[] sessionStr; delete[] scaleStr; delete[] speedStr; delete[] rangeStr;
     } else {
       // Create a "Session:" header; this makes up our 'extra headers':
       extraHeaders = createSessionString(sessionId);
@@ -888,6 +944,28 @@ char* RTSPClient::createAuthenticatorString(char const* cmd, char const* url) {
   return strDup("");
 }
 
+char* RTSPClient::createBlocksizeString(Boolean streamUsingTCP) {
+  char* blocksizeStr;
+  u_int16_t maxPacketSize = desiredMaxIncomingPacketSize;
+
+  // Allow for the RTP header (if streaming over TCP)
+  // or the IP/UDP/RTP headers (if streaming over UDP):
+  u_int16_t const headerAllowance = streamUsingTCP ? 12 : 50/*conservative*/;
+  if (maxPacketSize < headerAllowance) {
+    maxPacketSize = 0;
+  } else {
+    maxPacketSize -= headerAllowance;
+  }
+
+  if (maxPacketSize > 0) {
+    blocksizeStr = new char[25]; // more than enough space
+    sprintf(blocksizeStr, "Blocksize: %u\r\n", maxPacketSize);
+  } else {
+    blocksizeStr = strDup("");
+  }
+  return blocksizeStr;
+}
+
 void RTSPClient::handleRequestError(RequestRecord* request) {
   int resultCode = -envir().getErrno();
   if (resultCode == 0) {
@@ -1039,6 +1117,11 @@ Boolean RTSPClient::parseScaleParam(char const* paramStr, float& scale) {
   return sscanf(paramStr, "%f", &scale) == 1;
 }
 
+Boolean RTSPClient::parseSpeedParam(char const* paramStr, float& speed) {
+  Locale l("C", Numeric);
+  return sscanf(paramStr, "%f", &speed) >= 1;
+}
+
 Boolean RTSPClient::parseRTPInfoParams(char const*& paramsStr, u_int16_t& seqNum, u_int32_t& timestamp) {
   if (paramsStr == NULL || paramsStr[0] == '\0') return False;
   while (paramsStr[0] == ',') ++paramsStr;
@@ -1125,15 +1208,22 @@ Boolean RTSPClient::handleSETUPResponse(MediaSubsession& subsession, char const*
 }
 
 Boolean RTSPClient::handlePLAYResponse(MediaSession& session, MediaSubsession& subsession,
-                                       char const* scaleParamsStr, char const* rangeParamsStr, char const* rtpInfoParamsStr) {
-  Boolean scaleOK = False, rangeOK = False;
+                                       char const* scaleParamsStr, char const* speedParamsStr,
+                                       char const* rangeParamsStr, char const* rtpInfoParamsStr) {
+  Boolean scaleOK = False, rangeOK = False, speedOK = 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(),
-						     session._absStartTime(), session._absEndTime())) break;
+      if (speedParamsStr != NULL && !parseSpeedParam(speedParamsStr, session.speed())) break;
+      speedOK = True;
+      Boolean startTimeIsNow;
+      if (rangeParamsStr != NULL &&
+	  !parseRangeParam(rangeParamsStr,
+			   session.playStartTime(), session.playEndTime(),
+			   session._absStartTime(), session._absEndTime(),
+			   startTimeIsNow)) break;
       rangeOK = True;
 
       MediaSubsessionIterator iter(session);
@@ -1153,8 +1243,14 @@ Boolean RTSPClient::handlePLAYResponse(MediaSession& session, MediaSubsession& s
       // 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(),
-						     subsession._absStartTime(), subsession._absEndTime())) break;
+      if (speedParamsStr != NULL && !parseSpeedParam(speedParamsStr, session.speed())) break;
+      speedOK = True;
+      Boolean startTimeIsNow;
+      if (rangeParamsStr != NULL &&
+	  !parseRangeParam(rangeParamsStr,
+			   subsession._playStartTime(), subsession._playEndTime(),
+			   subsession._absStartTime(), subsession._absEndTime(),
+			   startTimeIsNow)) break;
       rangeOK = True;
 
       u_int16_t seqNum; u_int32_t timestamp;
@@ -1174,6 +1270,8 @@ Boolean RTSPClient::handlePLAYResponse(MediaSession& session, MediaSubsession& s
   // An error occurred:
   if (!scaleOK) {
     envir().setResultMsg("Bad \"Scale:\" header");
+  } else if (!speedOK) {
+    envir().setResultMsg("Bad \"Speed:\" header");
   } else if (!rangeOK) {
     envir().setResultMsg("Bad \"Range:\" header");
   } else {
@@ -1187,7 +1285,7 @@ Boolean RTSPClient::handleTEARDOWNResponse(MediaSession& /*session*/, MediaSubse
   return True;
 }
 
-Boolean RTSPClient::handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString) {
+Boolean RTSPClient::handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString, char* resultValueStringEnd) {
   do {
     // If "parameterName" is non-empty, it may be (possibly followed by ':' and whitespace) at the start of the result string:
     if (parameterName != NULL && parameterName[0] != '\0') {
@@ -1196,15 +1294,26 @@ Boolean RTSPClient::handleGET_PARAMETERResponse(char const* parameterName, char*
       unsigned parameterNameLen = strlen(parameterName);
       // ASSERT: parameterNameLen >= 2;
       parameterNameLen -= 2; // because of the trailing \r\n
+      if (resultValueString + parameterNameLen > resultValueStringEnd) break; // not enough space
       if (_strncasecmp(resultValueString, parameterName, parameterNameLen) == 0) {
 	resultValueString += parameterNameLen;
+	// ASSERT: resultValueString <= resultValueStringEnd
+	if (resultValueString == resultValueStringEnd) break;
+
 	if (resultValueString[0] == ':') ++resultValueString;
-	while (resultValueString[0] == ' ' || resultValueString[0] == '\t') ++resultValueString;
+	while (resultValueString < resultValueStringEnd
+	       && (resultValueString[0] == ' ' || resultValueString[0] == '\t')) {
+	  ++resultValueString;
+	}
       }
     }
 
     // The rest of "resultValueStr" should be our desired result, but first trim off any \r and/or \n characters at the end:
+    char saved = *resultValueStringEnd;
+    *resultValueStringEnd = '\0';
     unsigned resultLen = strlen(resultValueString);
+    *resultValueStringEnd = saved;
+    
     while (resultLen > 0 && (resultValueString[resultLen-1] == '\r' || resultValueString[resultLen-1] == '\n')) --resultLen;
     resultValueString[resultLen] = '\0';
 
@@ -1220,23 +1329,34 @@ Boolean RTSPClient::handleAuthenticationFailure(char const* paramsStr) {
   if (paramsStr == NULL) return False; // There was no "WWW-Authenticate:" header; we can't proceed.
 
   // Fill in "fCurrentAuthenticator" with the information from the "WWW-Authenticate:" header:
-  Boolean alreadyHadRealm = fCurrentAuthenticator.realm() != NULL;
+  Boolean realmHasChanged = False; // by default
+  Boolean isStale = False; // by default
   char* realm = strDupSize(paramsStr);
   char* nonce = strDupSize(paramsStr);
+  char* stale = strDupSize(paramsStr);
   Boolean success = True;
-  if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"", realm, nonce) == 2) {
+  if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\", stale=%[a-zA-Z]", realm, nonce, stale) == 3) {
+    realmHasChanged = fCurrentAuthenticator.realm() == NULL || strcmp(fCurrentAuthenticator.realm(), realm) != 0;
+    isStale = _strncasecmp(stale, "true", 4) == 0;
+    fCurrentAuthenticator.setRealmAndNonce(realm, nonce);
+  } else if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"", realm, nonce) == 2) {
+    realmHasChanged = fCurrentAuthenticator.realm() == NULL || strcmp(fCurrentAuthenticator.realm(), realm) != 0;
     fCurrentAuthenticator.setRealmAndNonce(realm, nonce);
-  } else if (sscanf(paramsStr, "Basic realm=\"%[^\"]\"", realm) == 1) {
+  } else if (sscanf(paramsStr, "Basic realm=\"%[^\"]\"", realm) == 1 && fAllowBasicAuthentication) {
+    realmHasChanged = fCurrentAuthenticator.realm() == NULL || strcmp(fCurrentAuthenticator.realm(), realm) != 0;
     fCurrentAuthenticator.setRealmAndNonce(realm, NULL); // Basic authentication
   } else {
     success = False; // bad "WWW-Authenticate:" header
   }
-  delete[] realm; delete[] nonce;
-
-  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;
+  delete[] realm; delete[] nonce; delete[] stale;
+
+  if (success) {
+    if ((!realmHasChanged && !isStale) || fCurrentAuthenticator.username() == NULL || fCurrentAuthenticator.password() == NULL) {
+      // We already tried with the same realm (and a non-stale nonce),
+      // 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;
+    }
   }
 
   return success;
@@ -1521,6 +1641,7 @@ void RTSPClient::handleResponseBytes(int newBytesRead) {
     char const* sessionParamsStr = NULL;
     char const* transportParamsStr = NULL;
     char const* scaleParamsStr = NULL;
+    char const* speedParamsStr = NULL;
     char const* rangeParamsStr = NULL;
     char const* rtpInfoParamsStr = NULL;
     char const* wwwAuthenticateParamsStr = NULL;
@@ -1533,8 +1654,12 @@ void RTSPClient::handleResponseBytes(int newBytesRead) {
       strncpy(headerDataCopy, fResponseBuffer, fResponseBytesAlreadySeen);
       headerDataCopy[fResponseBytesAlreadySeen] = '\0';
       
-      char* lineStart = headerDataCopy;
-      char* nextLineStart = getLine(lineStart);
+      char* lineStart;
+      char* nextLineStart = headerDataCopy;
+      do {
+	lineStart = nextLineStart;
+	nextLineStart = getLine(lineStart);
+      } while (lineStart[0] == '\0' && nextLineStart != NULL); // skip over any blank lines at the start
       if (!parseResponseCode(lineStart, responseCode, responseStr)) {
 	// This does not appear to be a RTSP response; perhaps it's a RTSP request instead?
 	handleIncomingRequest();
@@ -1590,6 +1715,7 @@ void RTSPClient::handleResponseBytes(int newBytesRead) {
 	} 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, "Speed:", 6, speedParamsStr)) {
 	} else if (checkForHeader(lineStart, "Range:", 6, rangeParamsStr)) {
 	} else if (checkForHeader(lineStart, "RTP-Info:", 9, rtpInfoParamsStr)) {
 	} else if (checkForHeader(lineStart, "WWW-Authenticate:", 17, headerParamsStr)) {
@@ -1659,11 +1785,11 @@ void RTSPClient::handleResponseBytes(int newBytesRead) {
 	  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;
+	    if (!handlePLAYResponse(*foundRequest->session(), *foundRequest->subsession(), scaleParamsStr, speedParamsStr, 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;
+	    if (!handleGET_PARAMETERResponse(foundRequest->contentStr(), bodyStart, responseEnd)) break;
 	  }
 	} else if (responseCode == 401 && handleAuthenticationFailure(wwwAuthenticateParamsStr)) {
 	  // We need to resend the command, with an "Authorization:" header:
@@ -1682,7 +1808,7 @@ void RTSPClient::handleResponseBytes(int newBytesRead) {
 	
 	if (needToResendCommand) {
 	  resetResponseBuffer();
-	  if (!resendCommand(foundRequest)) break;
+	  (void)resendCommand(foundRequest);
 	  delete[] headerDataCopy;
 	  return; // without calling our response handler; the response to the resent command will do that
 	}
diff --git a/liveMedia/RTSPCommon.cpp b/liveMedia/RTSPCommon.cpp
index 25d371c..28898c9 100644
--- a/liveMedia/RTSPCommon.cpp
+++ b/liveMedia/RTSPCommon.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Common routines used by both RTSP clients and servers
 // Implementation
 
@@ -25,12 +25,6 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include <ctype.h> // for "isxdigit()
 #include <time.h> // for "strftime()" and "gmtime()"
 
-#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
-#else
-#include <signal.h>
-#define USE_SIGNALS 1
-#endif
-
 static void decodeURL(char* url) {
   // Replace (in place) any %<hex><hex> sequences with the appropriate 8-bit character.
   char* cursor = url;
@@ -214,35 +208,42 @@ Boolean parseRTSPRequestString(char const* reqStr,
   return True;
 }
 
-Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime) {
+Boolean parseRangeParam(char const* paramStr,
+			double& rangeStart, double& rangeEnd,
+			char*& absStartTime, char*& absEndTime,
+			Boolean& startTimeIsNow) {
   delete[] absStartTime; delete[] absEndTime;
   absStartTime = absEndTime = NULL; // by default, unless "paramStr" is a "clock=..." string
+  startTimeIsNow = False; // by default
   double start, end;
-  int numCharsMatched = 0;
+  int numCharsMatched1 = 0, numCharsMatched2 = 0, numCharsMatched3 = 0, numCharsMatched4 = 0;
   Locale l("C", Numeric);
   if (sscanf(paramStr, "npt = %lf - %lf", &start, &end) == 2) {
     rangeStart = start;
     rangeEnd = end;
-  } else if (sscanf(paramStr, "npt = %lf -", &start) == 1) {
-    if (start < 0.0) {
-      // special case for "npt = -<endtime>", which seems to match here:
-      rangeStart = 0.0;
+  } else if (sscanf(paramStr, "npt = %n%lf -", &numCharsMatched1, &start) == 1) {
+    if (paramStr[numCharsMatched1] == '-') {
+      // special case for "npt = -<endtime>", which matches here:
+      rangeStart = 0.0; startTimeIsNow = True;
       rangeEnd = -start;
     } else {
       rangeStart = start;
       rangeEnd = 0.0;
     }
-  } else if (strcmp(paramStr, "npt=now-") == 0) {
-    rangeStart = 0.0;
+  } else if (sscanf(paramStr, "npt = now - %lf", &end) == 1) {
+      rangeStart = 0.0; startTimeIsNow = True;
+      rangeEnd = end;
+  } else if (sscanf(paramStr, "npt = now -%n", &numCharsMatched2) == 0 && numCharsMatched2 > 0) {
+    rangeStart = 0.0; startTimeIsNow = True;
     rangeEnd = 0.0;
-  } else if (sscanf(paramStr, "clock = %n", &numCharsMatched) == 0 && numCharsMatched > 0) {
+  } else if (sscanf(paramStr, "clock = %n", &numCharsMatched3) == 0 && numCharsMatched3 > 0) {
     rangeStart = rangeEnd = 0.0;
 
-    char const* utcTimes = &paramStr[numCharsMatched];
+    char const* utcTimes = &paramStr[numCharsMatched3];
     size_t len = strlen(utcTimes) + 1;
     char* as = new char[len];
     char* ae = new char[len];
-    int sscanfResult = sscanf(utcTimes, "%[^-]-%s", as, ae);
+    int sscanfResult = sscanf(utcTimes, "%[^-]-%[^\r\n]", as, ae);
     if (sscanfResult == 2) {
       absStartTime = as;
       absEndTime = ae;
@@ -253,7 +254,7 @@ Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeE
       delete[] as; delete[] ae;
       return False;
     }
-  } else if (sscanf(paramStr, "smtpe = %n", &numCharsMatched) == 0 && numCharsMatched > 0) {
+  } else if (sscanf(paramStr, "smtpe = %n", &numCharsMatched4) == 0 && numCharsMatched4 > 0) {
     // We accept "smtpe=" parameters, but currently do not interpret them.
   } else {
     return False; // The header is malformed
@@ -262,7 +263,10 @@ Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeE
   return True;
 }
 
-Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime) {
+Boolean parseRangeHeader(char const* buf,
+			 double& rangeStart, double& rangeEnd,
+			 char*& absStartTime, char*& absEndTime,
+			 Boolean& startTimeIsNow) {
   // First, find "Range:"
   while (1) {
     if (*buf == '\0') return False; // not found
@@ -270,10 +274,9 @@ Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd,
     ++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, absStartTime, absEndTime);
+  return parseRangeParam(fields, rangeStart, rangeEnd, absStartTime, absEndTime, startTimeIsNow);
 }
 
 Boolean parseScaleHeader(char const* buf, float& scale) {
@@ -287,7 +290,6 @@ Boolean parseScaleHeader(char const* buf, float& scale) {
     ++buf;
   }
 
-  // Then, run through each of the fields, looking for ones we handle:
   char const* fields = buf + 6;
   while (*fields == ' ') ++fields;
   float sc;
@@ -359,14 +361,3 @@ char const* dateHeader() {
 #endif
   return buf;
 }
-
-void ignoreSigPipeOnSocket(int socketNum) {
-#ifdef USE_SIGNALS
-#ifdef SO_NOSIGPIPE
-  int set_option = 1;
-  setsockopt(socketNum, SOL_SOCKET, SO_NOSIGPIPE, &set_option, sizeof set_option);
-#else
-  signal(SIGPIPE, SIG_IGN);
-#endif
-#endif
-}
diff --git a/liveMedia/RTSPRegisterSender.cpp b/liveMedia/RTSPRegisterSender.cpp
index 9d5b610..d8285f8 100644
--- a/liveMedia/RTSPRegisterSender.cpp
+++ b/liveMedia/RTSPRegisterSender.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A special object which, when created, sends a custom RTSP "REGISTER" command to a specified client.
 // Implementation
 
@@ -89,11 +89,11 @@ Boolean RTSPRegisterSender::setRequestFields(RequestRecord* request,
       sprintf(proxyURLSuffixParameterStr, proxyURLSuffixParameterFmt, request_REGISTER->proxyURLSuffix());
     }
 
-    char const* transportHeaderFmt = "Transport: reuse_connection=%d; preferred_delivery_protocol=%s%s\r\n";
+    char const* transportHeaderFmt = "Transport: %spreferred_delivery_protocol=%s%s\r\n";
     unsigned transportHeaderSize = strlen(transportHeaderFmt) + 100/*conservative*/ + strlen(proxyURLSuffixParameterStr);
     char* transportHeaderStr = new char[transportHeaderSize];
     sprintf(transportHeaderStr, transportHeaderFmt,
-	    request_REGISTER->reuseConnection(),
+	    request_REGISTER->reuseConnection() ? "reuse_connection; " : "",
 	    request_REGISTER->requestStreamingViaTCP() ? "interleaved" : "udp",
 	    proxyURLSuffixParameterStr);
     delete[] proxyURLSuffixParameterStr;
diff --git a/liveMedia/RTSPServer.cpp b/liveMedia/RTSPServer.cpp
index 99b5b12..86f3eed 100644
--- a/liveMedia/RTSPServer.cpp
+++ b/liveMedia/RTSPServer.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A RTSP server
 // Implementation
 
@@ -30,11 +30,11 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 RTSPServer*
 RTSPServer::createNew(UsageEnvironment& env, Port ourPort,
 		      UserAuthenticationDatabase* authDatabase,
-		      unsigned reclamationTestSeconds) {
+		      unsigned reclamationSeconds) {
   int ourSocket = setUpOurSocket(env, ourPort);
   if (ourSocket == -1) return NULL;
   
-  return new RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
+  return new RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationSeconds);
 }
 
 Boolean RTSPServer::lookupByName(UsageEnvironment& env,
@@ -54,64 +54,6 @@ Boolean RTSPServer::lookupByName(UsageEnvironment& env,
   return True;
 }
 
-void RTSPServer::addServerMediaSession(ServerMediaSession* serverMediaSession) {
-  if (serverMediaSession == NULL) return;
-  
-  char const* sessionName = serverMediaSession->streamName();
-  if (sessionName == NULL) sessionName = "";
-  removeServerMediaSession(sessionName); // in case an existing "ServerMediaSession" with this name already exists
-  
-  fServerMediaSessions->Add(sessionName, (void*)serverMediaSession);
-}
-
-ServerMediaSession* RTSPServer::lookupServerMediaSession(char const* streamName) {
-  return (ServerMediaSession*)(fServerMediaSessions->Lookup(streamName));
-}
-
-void RTSPServer::removeServerMediaSession(ServerMediaSession* serverMediaSession) {
-  if (serverMediaSession == NULL) return;
-  
-  fServerMediaSessions->Remove(serverMediaSession->streamName());
-  if (serverMediaSession->referenceCount() == 0) {
-    Medium::close(serverMediaSession);
-  } else {
-    serverMediaSession->deleteWhenUnreferenced() = True;
-  }
-}
-
-void RTSPServer::removeServerMediaSession(char const* streamName) {
-  removeServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName)));
-}
-
-void RTSPServer::closeAllClientSessionsForServerMediaSession(ServerMediaSession* serverMediaSession) {
-  if (serverMediaSession == NULL) return;
-  
-  HashTable::Iterator* iter = HashTable::Iterator::create(*fClientSessions);
-  RTSPServer::RTSPClientSession* clientSession;
-  char const* key; // dummy
-  while ((clientSession = (RTSPServer::RTSPClientSession*)(iter->next(key))) != NULL) {
-    if (clientSession->fOurServerMediaSession == serverMediaSession) {
-      delete clientSession;
-    }
-  }
-  delete iter;
-}
-
-void RTSPServer::closeAllClientSessionsForServerMediaSession(char const* streamName) {
-  closeAllClientSessionsForServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName)));
-}
-
-void RTSPServer::deleteServerMediaSession(ServerMediaSession* serverMediaSession) {
-  if (serverMediaSession == NULL) return;
-  
-  closeAllClientSessionsForServerMediaSession(serverMediaSession);
-  removeServerMediaSession(serverMediaSession);
-}
-
-void RTSPServer::deleteServerMediaSession(char const* streamName) {
-  deleteServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName)));
-}
-
 void rtspRegisterResponseHandler(RTSPClient* rtspClient, int resultCode, char* resultString); // forward
 
 // A class that represents the state of a "REGISTER" request in progress:
@@ -135,7 +77,7 @@ public:
     ourServer.fPendingRegisterRequests->Add((char const*)this, this);
   }
 
-  virtual ~RegisterRequestRecord(){
+  virtual ~RegisterRequestRecord() {
     // Remove ourself from the server's 'pending REGISTER requests' hash table before we go:
     fOurServer.fPendingRegisterRequests->Remove((char const*)this);
   }
@@ -222,7 +164,7 @@ char* RTSPServer::rtspURLPrefix(int clientSocket) const {
   
   char urlBuffer[100]; // more than big enough for "rtsp://<ip-address>:<port>/"
   
-  portNumBits portNumHostOrder = ntohs(fRTSPServerPort.num());
+  portNumBits portNumHostOrder = ntohs(fServerPort.num());
   if (portNumHostOrder == 554 /* the default port number */) {
     sprintf(urlBuffer, "rtsp://%s/", AddressString(ourAddress).val());
   } else {
@@ -245,7 +187,7 @@ Boolean RTSPServer::setUpTunnelingOverHTTP(Port httpPort) {
   if (fHTTPServerSocket >= 0) {
     fHTTPServerPort = httpPort;
     envir().taskScheduler().turnOnBackgroundReadHandling(fHTTPServerSocket,
-							 (TaskScheduler::BackgroundHandlerProc*)&incomingConnectionHandlerHTTP, this);
+							 incomingConnectionHandlerHTTP, this);
     return True;
   }
   
@@ -256,42 +198,6 @@ portNumBits RTSPServer::httpServerPortNum() const {
   return ntohs(fHTTPServerPort.num());
 }
 
-#define LISTEN_BACKLOG_SIZE 20
-
-int RTSPServer::setUpOurSocket(UsageEnvironment& env, Port& ourPort) {
-  int ourSocket = -1;
-  
-  do {
-    // The following statement is enabled by default.
-    // Don't disable it (by defining ALLOW_RTSP_SERVER_PORT_REUSE) unless you know what you're doing.
-#ifndef ALLOW_RTSP_SERVER_PORT_REUSE
-    NoReuse dummy(env); // Don't use this socket if there's already a local server using it
-#endif
-    
-    ourSocket = setupStreamSocket(env, ourPort);
-    if (ourSocket < 0) break;
-    
-    // Make sure we have a big send buffer:
-    if (!increaseSendBufferTo(env, ourSocket, 50*1024)) break;
-    
-    // Allow multiple simultaneous connections:
-    if (listen(ourSocket, LISTEN_BACKLOG_SIZE) < 0) {
-      env.setResultErrMsg("listen() failed: ");
-      break;
-    }
-    
-    if (ourPort.num() == 0) {
-      // bind() will have chosen a port for us; return it also:
-      if (!getSourcePort(env, ourSocket, ourPort)) break;
-    }
-    
-    return ourSocket;
-  } while (0);
-  
-  if (ourSocket != -1) ::closeSocket(ourSocket);
-  return -1;
-}
-
 char const* RTSPServer::allowedCommandNames() {
   return "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER";
 }
@@ -327,51 +233,38 @@ Boolean RTSPServer::specialClientUserAccessCheck(int /*clientSocket*/, struct so
 RTSPServer::RTSPServer(UsageEnvironment& env,
 		       int ourSocket, Port ourPort,
 		       UserAuthenticationDatabase* authDatabase,
-		       unsigned reclamationTestSeconds)
-  : Medium(env),
-    fRTSPServerPort(ourPort), fRTSPServerSocket(ourSocket), fHTTPServerSocket(-1), fHTTPServerPort(0),
-    fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
-    fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
+		       unsigned reclamationSeconds)
+  : GenericMediaServer(env, ourSocket, ourPort, reclamationSeconds),
+    fHTTPServerSocket(-1), fHTTPServerPort(0),
     fClientConnectionsForHTTPTunneling(NULL), // will get created if needed
-    fClientSessions(HashTable::create(STRING_HASH_KEYS)),
+    fTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)),
     fPendingRegisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)), fRegisterRequestCounter(0),
-    fAuthDB(authDatabase), fReclamationTestSeconds(reclamationTestSeconds) {
-  ignoreSigPipeOnSocket(ourSocket); // so that clients on the same host that are killed don't also kill us
-  
-  // Arrange to handle connections from others:
-  env.taskScheduler().turnOnBackgroundReadHandling(fRTSPServerSocket,
-						   (TaskScheduler::BackgroundHandlerProc*)&incomingConnectionHandlerRTSP, this);
+    fAuthDB(authDatabase), fAllowStreamingRTPOverTCP(True) {
 }
 
+// A data structure that is used to implement "fTCPStreamingDatabase"
+// (and the "noteTCPStreamingOnSocket()" and "stopTCPStreamingOnSocket()" member functions):
+class streamingOverTCPRecord {
+public:
+  streamingOverTCPRecord(u_int32_t sessionId, unsigned trackNum, streamingOverTCPRecord* next)
+    : fNext(next), fSessionId(sessionId), fTrackNum(trackNum) {
+  }
+  virtual ~streamingOverTCPRecord() {
+    delete fNext;
+  }
+
+  streamingOverTCPRecord* fNext;
+  u_int32_t fSessionId;
+  unsigned fTrackNum;
+};
+
 RTSPServer::~RTSPServer() {
-  // Turn off background read handling:
-  envir().taskScheduler().turnOffBackgroundReadHandling(fRTSPServerSocket);
-  ::closeSocket(fRTSPServerSocket);
-  
+  // Turn off background HTTP read handling (if any):
   envir().taskScheduler().turnOffBackgroundReadHandling(fHTTPServerSocket);
   ::closeSocket(fHTTPServerSocket);
+  delete fClientConnectionsForHTTPTunneling;
   
-  // Close all client connection objects:
-  RTSPServer::RTSPClientConnection* connection;
-  while ((connection = (RTSPServer::RTSPClientConnection*)fClientConnections->getFirst()) != NULL) {
-    delete connection;
-  }
-  delete fClientConnections;
-  delete fClientConnectionsForHTTPTunneling; // all content was already removed as a result of the loop above
-  
-  // Close all client session objects:
-  RTSPServer::RTSPClientSession* clientSession;
-  while ((clientSession = (RTSPServer::RTSPClientSession*)fClientSessions->getFirst()) != NULL) {
-    delete clientSession;
-  }
-  delete fClientSessions;
-  
-  // Delete all server media sessions
-  ServerMediaSession* serverMediaSession;
-  while ((serverMediaSession = (ServerMediaSession*)fServerMediaSessions->getFirst()) != NULL) {
-    removeServerMediaSession(serverMediaSession); // will delete it, because it no longer has any 'client session' objects using it
-  }
-  delete fServerMediaSessions;
+  cleanup(); // Removes all "ClientSession" and "ClientConnection" objects, and their tables.
   
   // Delete any pending REGISTER requests:
   RegisterRequestRecord* registerRequest;
@@ -379,48 +272,93 @@ RTSPServer::~RTSPServer() {
     delete registerRequest;
   }
   delete fPendingRegisterRequests;
+  
+  // Empty out and close "fTCPStreamingDatabase":
+  streamingOverTCPRecord* sotcp;
+  while ((sotcp = (streamingOverTCPRecord*)fTCPStreamingDatabase->getFirst()) != NULL) {
+    delete sotcp;
+  }
+  delete fTCPStreamingDatabase;
 }
 
 Boolean RTSPServer::isRTSPServer() const {
   return True;
 }
 
-void RTSPServer::incomingConnectionHandlerRTSP(void* instance, int /*mask*/) {
+void RTSPServer::incomingConnectionHandlerHTTP(void* instance, int /*mask*/) {
   RTSPServer* server = (RTSPServer*)instance;
-  server->incomingConnectionHandlerRTSP1();
+  server->incomingConnectionHandlerHTTP();
 }
-void RTSPServer::incomingConnectionHandlerRTSP1() {
-  incomingConnectionHandler(fRTSPServerSocket);
+void RTSPServer::incomingConnectionHandlerHTTP() {
+  incomingConnectionHandlerOnSocket(fHTTPServerSocket);
 }
 
-void RTSPServer::incomingConnectionHandlerHTTP(void* instance, int /*mask*/) {
-  RTSPServer* server = (RTSPServer*)instance;
-  server->incomingConnectionHandlerHTTP1();
-}
-void RTSPServer::incomingConnectionHandlerHTTP1() {
-  incomingConnectionHandler(fHTTPServerSocket);
+void RTSPServer
+::noteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum) {
+  streamingOverTCPRecord* sotcpCur
+    = (streamingOverTCPRecord*)fTCPStreamingDatabase->Lookup((char const*)socketNum);
+  streamingOverTCPRecord* sotcpNew
+    = new streamingOverTCPRecord(clientSession->fOurSessionId, trackNum, sotcpCur);
+  fTCPStreamingDatabase->Add((char const*)socketNum, sotcpNew);
 }
 
-void RTSPServer::incomingConnectionHandler(int serverSocket) {
-  struct sockaddr_in clientAddr;
-  SOCKLEN_T clientAddrLen = sizeof clientAddr;
-  int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
-  if (clientSocket < 0) {
-    int err = envir().getErrno();
-    if (err != EWOULDBLOCK) {
-      envir().setResultErrMsg("accept() failed: ");
+void RTSPServer
+::unnoteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum) {
+  if (socketNum < 0) return;
+  streamingOverTCPRecord* sotcpHead
+    = (streamingOverTCPRecord*)fTCPStreamingDatabase->Lookup((char const*)socketNum);
+  if (sotcpHead == NULL) return;
+
+  // Look for a record of the (session,track); remove it if found:
+  streamingOverTCPRecord* sotcp = sotcpHead;
+  streamingOverTCPRecord* sotcpPrev = sotcpHead;
+  do {
+    if (sotcp->fSessionId == clientSession->fOurSessionId && sotcp->fTrackNum == trackNum) break;
+    sotcpPrev = sotcp;
+    sotcp = sotcp->fNext;
+  } while (sotcp != NULL);
+  if (sotcp == NULL) return; // not found
+  
+  if (sotcp == sotcpHead) {
+    // We found it at the head of the list.  Remove it and reinsert the tail into the hash table:
+    sotcpHead = sotcp->fNext;
+    sotcp->fNext = NULL;
+    delete sotcp;
+
+    if (sotcpHead == NULL) {
+      // There were no more entries on the list.  Remove the original entry from the hash table:
+      fTCPStreamingDatabase->Remove((char const*)socketNum);
+    } else {
+      // Add the rest of the list into the hash table (replacing the original):
+      fTCPStreamingDatabase->Add((char const*)socketNum, sotcpHead);
     }
-    return;
+  } else {
+    // We found it on the list, but not at the head.  Unlink it:
+    sotcpPrev->fNext = sotcp->fNext;
+    sotcp->fNext = NULL;
+    delete sotcp;
+  }
+}
+
+void RTSPServer::stopTCPStreamingOnSocket(int socketNum) {
+  // Close any stream that is streaming over "socketNum" (using RTP/RTCP-over-TCP streaming):
+  streamingOverTCPRecord* sotcp
+    = (streamingOverTCPRecord*)fTCPStreamingDatabase->Lookup((char const*)socketNum);
+  if (sotcp != NULL) {
+    do {
+      RTSPClientSession* clientSession
+	= (RTSPServer::RTSPClientSession*)lookupClientSession(sotcp->fSessionId);
+      if (clientSession != NULL) {
+	clientSession->deleteStreamByTrack(sotcp->fTrackNum);
+      }
+
+      streamingOverTCPRecord* sotcpNext = sotcp->fNext;
+      sotcp->fNext = NULL;
+      delete sotcp;
+      sotcp = sotcpNext;
+    } while (sotcp != NULL);
+    fTCPStreamingDatabase->Remove((char const*)socketNum);
   }
-  makeSocketNonBlocking(clientSocket);
-  increaseSendBufferTo(envir(), clientSocket, 50*1024);
-  
-#ifdef DEBUG
-  envir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";
-#endif
-  
-  // Create a new object for handling this RTSP connection:
-  (void)createNewClientConnection(clientSocket, clientAddr);
 }
 
 
@@ -428,29 +366,20 @@ void RTSPServer::incomingConnectionHandler(int serverSocket) {
 
 RTSPServer::RTSPClientConnection
 ::RTSPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
-  : fOurServer(ourServer), fIsActive(True),
-    fClientInputSocket(clientSocket), fClientOutputSocket(clientSocket), fClientAddr(clientAddr),
-    fRecursionCount(0), fOurSessionCookie(NULL) {
-  // Add ourself to our 'client connections' table:
-  fOurServer.fClientConnections->Add((char const*)this, this);
-  
-  // Arrange to handle incoming requests:
+  : GenericMediaServer::ClientConnection(ourServer, clientSocket, clientAddr),
+    fOurRTSPServer(ourServer), fClientInputSocket(fOurSocket), fClientOutputSocket(fOurSocket),
+    fIsActive(True), fRecursionCount(0), fOurSessionCookie(NULL) {
   resetRequestBuffer();
-  envir().taskScheduler().setBackgroundHandling(fClientInputSocket, SOCKET_READABLE|SOCKET_EXCEPTION,
-						(TaskScheduler::BackgroundHandlerProc*)&incomingRequestHandler, this);
 }
 
 RTSPServer::RTSPClientConnection::~RTSPClientConnection() {
-  // Remove ourself from the server's 'client connections' hash table before we go:
-  fOurServer.fClientConnections->Remove((char const*)this);
-  
   if (fOurSessionCookie != NULL) {
     // We were being used for RTSP-over-HTTP tunneling. Also remove ourselves from the 'session cookie' hash table before we go:
-    fOurServer.fClientConnectionsForHTTPTunneling->Remove(fOurSessionCookie);
+    fOurRTSPServer.fClientConnectionsForHTTPTunneling->Remove(fOurSessionCookie);
     delete[] fOurSessionCookie;
   }
   
-  closeSockets();
+  closeSocketsRTSP();
 }
 
 // Special mechanism for handling our custom "REGISTER" command:
@@ -471,7 +400,7 @@ RTSPServer::RTSPClientConnection::ParamsForREGISTER::~ParamsForREGISTER() {
 void RTSPServer::RTSPClientConnection::handleCmd_OPTIONS() {
   snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
 	   "RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sPublic: %s\r\n\r\n",
-	   fCurrentCSeq, dateHeader(), fOurServer.allowedCommandNames());
+	   fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames());
 }
 
 void RTSPServer::RTSPClientConnection
@@ -492,14 +421,12 @@ void RTSPServer::RTSPClientConnection
 
 void RTSPServer::RTSPClientConnection
 ::handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
+  ServerMediaSession* session = NULL;
   char* sdpDescription = NULL;
   char* rtspURL = NULL;
   do {
-    char urlTotalSuffix[RTSP_PARAM_STRING_MAX];
-    if (strlen(urlPreSuffix) + strlen(urlSuffix) + 2 > sizeof urlTotalSuffix) {
-      handleCmd_bad();
-      break;
-    }
+    char urlTotalSuffix[2*RTSP_PARAM_STRING_MAX];
+        // enough space for urlPreSuffix/urlSuffix'\0'
     urlTotalSuffix[0] = '\0';
     if (urlPreSuffix[0] != '\0') {
       strcat(urlTotalSuffix, urlPreSuffix);
@@ -513,12 +440,16 @@ void RTSPServer::RTSPClientConnection
     // for "application/sdp", because that's what we're sending back #####
     
     // Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
-    ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
+    session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
     if (session == NULL) {
       handleCmd_notFound();
       break;
     }
     
+    // Increment the "ServerMediaSession" object's reference count, in case someone removes it
+    // while we're using it:
+    session->incrementReferenceCount();
+
     // Then, assemble a SDP description for this session:
     sdpDescription = session->generateSDPDescription();
     if (sdpDescription == NULL) {
@@ -531,7 +462,7 @@ void RTSPServer::RTSPClientConnection
     
     // Also, generate our RTSP URL, for the "Content-Base:" header
     // (which is necessary to ensure that the correct URL gets used in subsequent "SETUP" requests).
-    rtspURL = fOurServer.rtspURL(session, fClientInputSocket);
+    rtspURL = fOurRTSPServer.rtspURL(session, fClientInputSocket);
     
     snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
 	     "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
@@ -547,6 +478,14 @@ void RTSPServer::RTSPClientConnection
 	     sdpDescription);
   } while (0);
   
+  if (session != NULL) {
+    // Decrement its reference count, now that we're done using it:
+    session->decrementReferenceCount();
+    if (session->referenceCount() == 0 && session->deleteWhenUnreferenced()) {
+      fOurServer.removeServerMediaSession(session);
+    }
+  }
+
   delete[] sdpDescription;
   delete[] rtspURL;
 }
@@ -577,7 +516,7 @@ void RTSPServer
 ::RTSPClientConnection::handleCmd_REGISTER(char const* url, char const* urlSuffix, char const* fullRequestStr,
 					   Boolean reuseConnection, Boolean deliverViaTCP, char const* proxyURLSuffix) {
   char* responseStr;
-  if (fOurServer.weImplementREGISTER(proxyURLSuffix, responseStr)) {
+  if (fOurRTSPServer.weImplementREGISTER(proxyURLSuffix, responseStr)) {
     // The "REGISTER" command - if we implement it - may require access control:
     if (!authenticationOK("REGISTER", urlSuffix, fullRequestStr)) return;
     
@@ -600,13 +539,13 @@ void RTSPServer::RTSPClientConnection::handleCmd_bad() {
   // Don't do anything with "fCurrentCSeq", because it might be nonsense
   snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
 	   "RTSP/1.0 400 Bad Request\r\n%sAllow: %s\r\n\r\n",
-	   dateHeader(), fOurServer.allowedCommandNames());
+	   dateHeader(), fOurRTSPServer.allowedCommandNames());
 }
 
 void RTSPServer::RTSPClientConnection::handleCmd_notSupported() {
   snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
 	   "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n%sAllow: %s\r\n\r\n",
-	   fCurrentCSeq, dateHeader(), fOurServer.allowedCommandNames());
+	   fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames());
 }
 
 void RTSPServer::RTSPClientConnection::handleCmd_notFound() {
@@ -705,11 +644,11 @@ void RTSPServer::RTSPClientConnection::handleHTTPCmd_OPTIONS() {
 void RTSPServer::RTSPClientConnection::handleHTTPCmd_TunnelingGET(char const* sessionCookie) {
   // Record ourself as having this 'session cookie', so that a subsequent HTTP "POST" command (with the same 'session cookie')
   // can find us:
-  if (fOurServer.fClientConnectionsForHTTPTunneling == NULL) {
-    fOurServer.fClientConnectionsForHTTPTunneling = HashTable::create(STRING_HASH_KEYS);
+  if (fOurRTSPServer.fClientConnectionsForHTTPTunneling == NULL) {
+    fOurRTSPServer.fClientConnectionsForHTTPTunneling = HashTable::create(STRING_HASH_KEYS);
   }
   delete[] fOurSessionCookie; fOurSessionCookie = strDup(sessionCookie);
-  fOurServer.fClientConnectionsForHTTPTunneling->Add(sessionCookie, (void*)this);
+  fOurRTSPServer.fClientConnectionsForHTTPTunneling->Add(sessionCookie, (void*)this);
 #ifdef DEBUG
   fprintf(stderr, "Handled HTTP \"GET\" request (client output socket: %d)\n", fClientOutputSocket);
 #endif
@@ -729,11 +668,11 @@ Boolean RTSPServer::RTSPClientConnection
 ::handleHTTPCmd_TunnelingPOST(char const* sessionCookie, unsigned char const* extraData, unsigned extraDataSize) {
   // Use the "sessionCookie" string to look up the separate "RTSPClientConnection" object that should have been used to handle
   // an earlier HTTP "GET" request:
-  if (fOurServer.fClientConnectionsForHTTPTunneling == NULL) {
-    fOurServer.fClientConnectionsForHTTPTunneling = HashTable::create(STRING_HASH_KEYS);
+  if (fOurRTSPServer.fClientConnectionsForHTTPTunneling == NULL) {
+    fOurRTSPServer.fClientConnectionsForHTTPTunneling = HashTable::create(STRING_HASH_KEYS);
   }
   RTSPServer::RTSPClientConnection* prevClientConnection
-    = (RTSPServer::RTSPClientConnection*)(fOurServer.fClientConnectionsForHTTPTunneling->Lookup(sessionCookie));
+    = (RTSPServer::RTSPClientConnection*)(fOurRTSPServer.fClientConnectionsForHTTPTunneling->Lookup(sessionCookie));
   if (prevClientConnection == NULL) {
     // There was no previous HTTP "GET" request; treat this "POST" request as bad:
     handleHTTPCmd_notSupported();
@@ -756,40 +695,29 @@ void RTSPServer::RTSPClientConnection::handleHTTPCmd_StreamingGET(char const* /*
 }
 
 void RTSPServer::RTSPClientConnection::resetRequestBuffer() {
-  fRequestBytesAlreadySeen = 0;
-  fRequestBufferBytesLeft = sizeof fRequestBuffer;
+  ClientConnection::resetRequestBuffer();
+  
   fLastCRLF = &fRequestBuffer[-3]; // hack: Ensures that we don't think we have end-of-msg if the data starts with <CR><LF>
   fBase64RemainderCount = 0;
 }
 
-void RTSPServer::RTSPClientConnection::closeSockets() {
+void RTSPServer::RTSPClientConnection::closeSocketsRTSP() {
+  // First, tell our server to stop any streaming that it might be doing over our output socket:
+  fOurRTSPServer.stopTCPStreamingOnSocket(fClientOutputSocket);
+
   // Turn off background handling on our input socket (and output socket, if different); then close it (or them):
   if (fClientOutputSocket != fClientInputSocket) {
     envir().taskScheduler().disableBackgroundHandling(fClientOutputSocket);
     ::closeSocket(fClientOutputSocket);
   }
+  fClientOutputSocket = -1;
   
-  envir().taskScheduler().disableBackgroundHandling(fClientInputSocket);
-  ::closeSocket(fClientInputSocket);
-  
-  fClientInputSocket = fClientOutputSocket = -1;
-}
-
-void RTSPServer::RTSPClientConnection::incomingRequestHandler(void* instance, int /*mask*/) {
-  RTSPClientConnection* session = (RTSPClientConnection*)instance;
-  session->incomingRequestHandler1();
-}
-
-void RTSPServer::RTSPClientConnection::incomingRequestHandler1() {
-  struct sockaddr_in dummy; // 'from' address, meaningless in this case
-  
-  int bytesRead = readSocket(envir(), fClientInputSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy);
-  handleRequestBytes(bytesRead);
+  closeSockets(); // closes fClientInputSocket
 }
 
 void RTSPServer::RTSPClientConnection::handleAlternativeRequestByte(void* instance, u_int8_t requestByte) {
-  RTSPClientConnection* session = (RTSPClientConnection*)instance;
-  session->handleAlternativeRequestByte1(requestByte);
+  RTSPClientConnection* connection = (RTSPClientConnection*)instance;
+  connection->handleAlternativeRequestByte1(requestByte);
 }
 
 void RTSPServer::RTSPClientConnection::handleAlternativeRequestByte1(u_int8_t requestByte) {
@@ -799,10 +727,10 @@ void RTSPServer::RTSPClientConnection::handleAlternativeRequestByte1(u_int8_t re
   } else if (requestByte == 0xFE) {
     // Another hack: The new handler of the input TCP socket no longer needs it, so take back control of it:
     envir().taskScheduler().setBackgroundHandling(fClientInputSocket, SOCKET_READABLE|SOCKET_EXCEPTION,
-						  (TaskScheduler::BackgroundHandlerProc*)&incomingRequestHandler, this);
+						  incomingRequestHandler, this);
   } else {
     // Normal case: Add this character to our buffer; then try to handle the data that we have buffered so far:
-    if (fRequestBufferBytesLeft == 0 || fRequestBytesAlreadySeen >= RTSP_BUFFER_SIZE) return;
+    if (fRequestBufferBytesLeft == 0 || fRequestBytesAlreadySeen >= REQUEST_BUFFER_SIZE) return;
     fRequestBuffer[fRequestBytesAlreadySeen] = requestByte;
     handleRequestBytes(1);
   }
@@ -826,15 +754,13 @@ static void parseTransportHeaderForREGISTER(char const* buf,
     ++buf;
   }
   
-  int reuseConnectionNum;
-
   // Then, run through each of the fields, looking for ones we handle:
   char const* fields = buf + 10;
   while (*fields == ' ') ++fields;
   char* field = strDupSize(fields);
   while (sscanf(fields, "%[^;\r\n]", field) == 1) {
-    if (sscanf(field, "reuse_connection = %d", &reuseConnectionNum) == 1) {
-      reuseConnection = reuseConnectionNum != 0;
+    if (strcmp(field, "reuse_connection") == 0) {
+      reuseConnection = True;
     } else if (_strncasecmp(field, "preferred_delivery_protocol=udp", 31) == 0) {
       deliverViaTCP = False;
     } else if (_strncasecmp(field, "preferred_delivery_protocol=interleaved", 39) == 0) {
@@ -910,25 +836,27 @@ void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
 	// Then copy any remaining (undecoded) bytes to the end:
 	for (unsigned j = 0; j < newBase64RemainderCount; ++j) *to++ = (ptr-fBase64RemainderCount+numBytesToDecode)[j];
 	
-	newBytesRead = decodedSize + newBase64RemainderCount; // adjust to allow for the size of the new decoded data (+ remainder)
+	newBytesRead = decodedSize - fBase64RemainderCount + newBase64RemainderCount;
+	  // adjust to allow for the size of the new decoded data (+ remainder)
 	delete[] decodedBytes;
       }
       fBase64RemainderCount = newBase64RemainderCount;
-      if (fBase64RemainderCount > 0) break; // because we know that we have more input bytes still to receive
     }
     
-    // Look for the end of the message: <CR><LF><CR><LF>
-    unsigned char *tmpPtr = fLastCRLF + 2;
-    if (tmpPtr < fRequestBuffer) tmpPtr = fRequestBuffer;
-    while (tmpPtr < &ptr[newBytesRead-1]) {
-      if (*tmpPtr == '\r' && *(tmpPtr+1) == '\n') {
-	if (tmpPtr - fLastCRLF == 2) { // This is it:
-	  endOfMsg = True;
-	  break;
+    unsigned char* tmpPtr = fLastCRLF + 2;
+    if (fBase64RemainderCount == 0) { // no more Base-64 bytes remain to be read/decoded
+      // Look for the end of the message: <CR><LF><CR><LF>
+      if (tmpPtr < fRequestBuffer) tmpPtr = fRequestBuffer;
+      while (tmpPtr < &ptr[newBytesRead-1]) {
+	if (*tmpPtr == '\r' && *(tmpPtr+1) == '\n') {
+	  if (tmpPtr - fLastCRLF == 2) { // This is it:
+	    endOfMsg = True;
+	    break;
+	  }
+	  fLastCRLF = tmpPtr;
 	}
-	fLastCRLF = tmpPtr;
+	++tmpPtr;
       }
-      ++tmpPtr;
     }
     
     fRequestBufferBytesLeft -= newBytesRead;
@@ -953,9 +881,10 @@ void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
 						    sessionIdStr, sizeof sessionIdStr,
 						    contentLength);
     fLastCRLF[2] = '\r'; // restore its value
+    Boolean playAfterSetup = False;
     if (parseSucceeded) {
 #ifdef DEBUG
-      fprintf(stderr, "parseRTSPRequestString() succeeded, returning cmdName \"%s\", urlPreSuffix \"%s\", urlSuffix \"%s\", CSeq \"%s\", Content-Length %u, with %ld bytes following the message.\n", cmdName, urlPreSuffix, urlSuffix, cseq, contentLength, ptr + newBytesRead - (tmpPtr + 2));
+      fprintf(stderr, "parseRTSPRequestString() succeeded, returning cmdName \"%s\", urlPreSuffix \"%s\", urlSuffix \"%s\", CSeq \"%s\", Content-Length %u, with %d bytes following the message.\n", cmdName, urlPreSuffix, urlSuffix, cseq, contentLength, ptr + newBytesRead - (tmpPtr + 2));
 #endif
       // If there was a "Content-Length:" header, then make sure we've received all of the data that it specified:
       if (ptr + newBytesRead < tmpPtr + 2 + contentLength) break; // we still need more data; subsequent reads will give it to us 
@@ -964,7 +893,8 @@ void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
       // current ongoing, then use this command to indicate 'liveness' on that client session:
       Boolean const requestIncludedSessionId = sessionIdStr[0] != '\0';
       if (requestIncludedSessionId) {
-	clientSession = (RTSPServer::RTSPClientSession*)(fOurServer.fClientSessions->Lookup(sessionIdStr));
+	clientSession
+	  = (RTSPServer::RTSPClientSession*)(fOurRTSPServer.lookupClientSession(sessionIdStr));
 	if (clientSession != NULL) clientSession->noteLiveness();
       }
     
@@ -972,7 +902,14 @@ void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
       // Handle the specified command (beginning with commands that are session-independent):
       fCurrentCSeq = cseq;
       if (strcmp(cmdName, "OPTIONS") == 0) {
-	handleCmd_OPTIONS();
+	// If the "OPTIONS" command included a "Session:" id for a session that doesn't exist,
+	// then treat this as an error:
+	if (requestIncludedSessionId && clientSession == NULL) {
+	  handleCmd_sessionNotFound();
+	} else {
+	  // Normal case:
+	  handleCmd_OPTIONS();
+	}
       } else if (urlPreSuffix[0] == '\0' && urlSuffix[0] == '*' && urlSuffix[1] == '\0') {
 	// The special "*" URL means: an operation on the entire server.  This works only for GET_PARAMETER and SET_PARAMETER:
 	if (strcmp(cmdName, "GET_PARAMETER") == 0) {
@@ -985,23 +922,33 @@ void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
       } else if (strcmp(cmdName, "DESCRIBE") == 0) {
 	handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
       } else if (strcmp(cmdName, "SETUP") == 0) {
+	Boolean areAuthenticated = True;
+
 	if (!requestIncludedSessionId) {
-	  // No session id was present in the request.  So create a new "RTSPClientSession" object
-	  // for this request.  Choose a random (unused) 32-bit integer for the session id
-	  // (it will be encoded as a 8-digit hex number).  (We avoid choosing session id 0,
-	  // because that has a special use (by "OnDemandServerMediaSubsession").)
-	  u_int32_t sessionId;
-	  do {
-	    sessionId = (u_int32_t)our_random32();
-	    sprintf(sessionIdStr, "%08X", sessionId);
-	  } while (sessionId == 0 || fOurServer.fClientSessions->Lookup(sessionIdStr) != NULL);
-	  clientSession = fOurServer.createNewClientSession(sessionId);
-	  fOurServer.fClientSessions->Add(sessionIdStr, clientSession);
+	  // No session id was present in the request.
+	  // So create a new "RTSPClientSession" object for this request.
+
+	  // But first, make sure that we're authenticated to perform this command:
+	  char urlTotalSuffix[2*RTSP_PARAM_STRING_MAX];
+	      // enough space for urlPreSuffix/urlSuffix'\0'
+	  urlTotalSuffix[0] = '\0';
+	  if (urlPreSuffix[0] != '\0') {
+	    strcat(urlTotalSuffix, urlPreSuffix);
+	    strcat(urlTotalSuffix, "/");
+	  }
+	  strcat(urlTotalSuffix, urlSuffix);
+	  if (authenticationOK("SETUP", urlTotalSuffix, (char const*)fRequestBuffer)) {
+	    clientSession
+	      = (RTSPServer::RTSPClientSession*)fOurRTSPServer.createNewClientSessionWithId();
+	  } else {
+	    areAuthenticated = False;
+	  }
 	}
 	if (clientSession != NULL) {
 	  clientSession->handleCmd_SETUP(this, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
-	} else {
-	    handleCmd_sessionNotFound();
+	  playAfterSetup = clientSession->fStreamAfterSETUP;
+	} else if (areAuthenticated) {
+	  handleCmd_sessionNotFound();
 	}
       } else if (strcmp(cmdName, "TEARDOWN") == 0
 		 || strcmp(cmdName, "PLAY") == 0
@@ -1093,7 +1040,7 @@ void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
 #endif
     send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
     
-    if (clientSession != NULL && clientSession->fStreamAfterSETUP && strcmp(cmdName, "SETUP") == 0) {
+    if (playAfterSetup) {
       // The client has asked for streaming to commence now, rather than after a
       // subsequent "PLAY" command.  So, simulate the effect of a "PLAY" command:
       clientSession->handleCmd_withinSession(this, "PLAY", urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
@@ -1169,13 +1116,13 @@ static Boolean parseAuthorizationHeader(char const* buf,
 
 Boolean RTSPServer::RTSPClientConnection
 ::authenticationOK(char const* cmdName, char const* urlSuffix, char const* fullRequestStr) {
-  if (!fOurServer.specialClientAccessCheck(fClientInputSocket, fClientAddr, urlSuffix)) {
+  if (!fOurRTSPServer.specialClientAccessCheck(fClientInputSocket, fClientAddr, urlSuffix)) {
     setRTSPResponse("401 Unauthorized");
     return False;
   }
   
   // If we weren't set up with an authentication database, we're OK:
-  UserAuthenticationDatabase* authDB = fOurServer.getAuthenticationDatabaseForCommand(cmdName);
+  UserAuthenticationDatabase* authDB = fOurRTSPServer.getAuthenticationDatabaseForCommand(cmdName);
   if (authDB == NULL) return True;
   
   char const* username = NULL; char const* realm = NULL; char const* nonce = NULL;
@@ -1221,7 +1168,7 @@ Boolean RTSPServer::RTSPClientConnection
   if (success) {
     // The user has been authenticated.
     // Now allow subclasses a chance to validate the user against the IP address and/or URL suffix.
-    if (!fOurServer.specialClientUserAccessCheck(fClientInputSocket, fClientAddr, urlSuffix, username)) {
+    if (!fOurRTSPServer.specialClientUserAccessCheck(fClientInputSocket, fClientAddr, urlSuffix, username)) {
       // Note: We don't return a "WWW-Authenticate" header here, because the user is valid,
       // even though the server has decided that they should not have access.
       setRTSPResponse("401 Unauthorized");
@@ -1313,7 +1260,7 @@ void RTSPServer::RTSPClientConnection
   envir().taskScheduler().disableBackgroundHandling(fClientInputSocket);
   fClientInputSocket = newSocketNum;
   envir().taskScheduler().setBackgroundHandling(fClientInputSocket, SOCKET_READABLE|SOCKET_EXCEPTION,
-						(TaskScheduler::BackgroundHandlerProc*)&incomingRequestHandler, this);
+						incomingRequestHandler, this);
   
   // Also write any extra data to our buffer, and handle it:
   if (extraDataSize > 0 && extraDataSize <= fRequestBufferBytesLeft/*sanity check; should always be true*/) {
@@ -1333,7 +1280,7 @@ void RTSPServer::RTSPClientConnection::continueHandlingREGISTER1(ParamsForREGIST
   // Reuse our socket if requested:
   int socketNumToBackEndServer = params->fReuseConnection ? fClientOutputSocket : -1;
 
-  RTSPServer* ourServer = &fOurServer; // copy the pointer now, in case we "delete this" below
+  RTSPServer* ourServer = &fOurRTSPServer; // copy the pointer now, in case we "delete this" below
   
   if (socketNumToBackEndServer >= 0) {
     // Because our socket will no longer be used by the server to handle incoming requests, we can now delete this
@@ -1353,35 +1300,37 @@ void RTSPServer::RTSPClientConnection::continueHandlingREGISTER1(ParamsForREGIST
 
 RTSPServer::RTSPClientSession
 ::RTSPClientSession(RTSPServer& ourServer, u_int32_t sessionId)
-  : fOurServer(ourServer), fOurSessionId(sessionId), fOurServerMediaSession(NULL), fIsMulticast(False), fStreamAfterSETUP(False),
-    fTCPStreamIdCount(0), fLivenessCheckTask(NULL), fNumStreamStates(0), fStreamStates(NULL) {
-  noteLiveness();
+  : GenericMediaServer::ClientSession(ourServer, sessionId),
+    fOurRTSPServer(ourServer), fIsMulticast(False), fStreamAfterSETUP(False),
+    fTCPStreamIdCount(0), fNumStreamStates(0), fStreamStates(NULL) {
 }
 
 RTSPServer::RTSPClientSession::~RTSPClientSession() {
-  // Turn off any liveness checking:
-  envir().taskScheduler().unscheduleDelayedTask(fLivenessCheckTask);
-  
-  // Remove ourself from the server's 'client sessions' hash table before we go:
-  char sessionIdStr[9];
-  sprintf(sessionIdStr, "%08X", fOurSessionId);
-  fOurServer.fClientSessions->Remove(sessionIdStr);
-  
   reclaimStreamStates();
+}
+
+void RTSPServer::RTSPClientSession::deleteStreamByTrack(unsigned trackNum) {
+  if (trackNum >= fNumStreamStates) return; // sanity check; shouldn't happen
+  if (fStreamStates[trackNum].subsession != NULL) {
+    fStreamStates[trackNum].subsession->deleteStream(fOurSessionId, fStreamStates[trackNum].streamToken);
+    fStreamStates[trackNum].subsession = NULL;
+  }
   
-  if (fOurServerMediaSession != NULL) {
-    fOurServerMediaSession->decrementReferenceCount();
-    if (fOurServerMediaSession->referenceCount() == 0
-	&& fOurServerMediaSession->deleteWhenUnreferenced()) {
-      fOurServer.removeServerMediaSession(fOurServerMediaSession);
-      fOurServerMediaSession = NULL;
+  // Optimization: If all subsessions have now been deleted, then we can delete ourself now:
+  Boolean noSubsessionsRemain = True;
+  for (unsigned i = 0; i < fNumStreamStates; ++i) {
+    if (fStreamStates[i].subsession != NULL) {
+      noSubsessionsRemain = False;
+      break;
     }
   }
+  if (noSubsessionsRemain) delete this;
 }
 
 void RTSPServer::RTSPClientSession::reclaimStreamStates() {
   for (unsigned i = 0; i < fNumStreamStates; ++i) {
     if (fStreamStates[i].subsession != NULL) {
+      fOurRTSPServer.unnoteTCPStreamingOnSocket(fStreamStates[i].tcpSocketNum, this, i);
       fStreamStates[i].subsession->deleteStream(fOurSessionId, fStreamStates[i].streamToken);
     }
   }
@@ -1484,7 +1433,8 @@ void RTSPServer::RTSPClientSession
   
   do {
     // First, make sure the specified stream name exists:
-    ServerMediaSession* sms = fOurServer.lookupServerMediaSession(streamName);
+    ServerMediaSession* sms
+      = fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
     if (sms == NULL) {
       // Check for the special case (noted above), before we give up:
       if (urlPreSuffix[0] == '\0') {
@@ -1497,7 +1447,7 @@ void RTSPServer::RTSPClientSession
       trackId = NULL;
       
       // Check again:
-      sms = fOurServer.lookupServerMediaSession(streamName);
+      sms = fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
     }
     if (sms == NULL) {
       if (fOurServerMediaSession == NULL) {
@@ -1532,19 +1482,20 @@ void RTSPServer::RTSPClientSession
       for (unsigned i = 0; i < fNumStreamStates; ++i) {
 	subsession = iter.next();
 	fStreamStates[i].subsession = subsession;
+	fStreamStates[i].tcpSocketNum = -1; // for now; may get set for RTP-over-TCP streaming
 	fStreamStates[i].streamToken = NULL; // for now; it may be changed by the "getStreamParameters()" call that comes later
       }
     }
     
     // Look up information for the specified subsession (track):
     ServerMediaSubsession* subsession = NULL;
-    unsigned streamNum;
+    unsigned trackNum;
     if (trackId != NULL && trackId[0] != '\0') { // normal case
-      for (streamNum = 0; streamNum < fNumStreamStates; ++streamNum) {
-	subsession = fStreamStates[streamNum].subsession;
+      for (trackNum = 0; trackNum < fNumStreamStates; ++trackNum) {
+	subsession = fStreamStates[trackNum].subsession;
 	if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break;
       }
-      if (streamNum >= fNumStreamStates) {
+      if (trackNum >= fNumStreamStates) {
 	// The specified track id doesn't exist, so this request fails:
 	ourClientConnection->handleCmd_notFound();
 	break;
@@ -1556,11 +1507,20 @@ void RTSPServer::RTSPClientSession
 	ourClientConnection->handleCmd_bad();
 	break;
       }
-      streamNum = 0;
-      subsession = fStreamStates[streamNum].subsession;
+      trackNum = 0;
+      subsession = fStreamStates[trackNum].subsession;
     }
     // ASSERT: subsession != NULL
     
+    void*& token = fStreamStates[trackNum].streamToken; // alias
+    if (token != NULL) {
+      // We already handled a "SETUP" for this track (to the same client),
+      // so stop any existing streaming of it, before we set it up again:
+      subsession->pauseStream(fOurSessionId, token);
+      fOurRTSPServer.unnoteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
+      subsession->deleteStream(fOurSessionId, token);
+    }
+
     // Look for a "Transport:" header in the request string, to extract client parameters:
     StreamingMode streamingMode;
     char* streamingModeString = NULL; // set when RAW_UDP streaming is specified
@@ -1590,7 +1550,8 @@ void RTSPServer::RTSPClientSession
     // This isn't legal, but some clients do this to combine "SETUP" and "PLAY":
     double rangeStart = 0.0, rangeEnd = 0.0;
     char* absStart = NULL; char* absEnd = NULL;
-    if (parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd)) {
+    Boolean startTimeIsNow;
+    if (parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd, startTimeIsNow)) {
       delete[] absStart; delete[] absEnd;
       fStreamAfterSETUP = True;
     } else if (parsePlayNowHeader(fullRequestStr)) {
@@ -1600,7 +1561,11 @@ void RTSPServer::RTSPClientSession
     }
     
     // Then, get server parameters from the 'subsession':
-    int tcpSocketNum = streamingMode == RTP_TCP ? ourClientConnection->fClientOutputSocket : -1;
+    if (streamingMode == RTP_TCP) {
+      // Note that we'll be streaming over the RTSP TCP connection:
+      fStreamStates[trackNum].tcpSocketNum = ourClientConnection->fClientOutputSocket;
+      fOurRTSPServer.noteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
+    }
     netAddressBits destinationAddress = 0;
     u_int8_t destinationTTL = 255;
 #ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING
@@ -1630,18 +1595,18 @@ void RTSPServer::RTSPClientSession
     
     subsession->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr,
 				    clientRTPPort, clientRTCPPort,
-				    tcpSocketNum, rtpChannelId, rtcpChannelId,
+				    fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,
 				    destinationAddress, destinationTTL, fIsMulticast,
 				    serverRTPPort, serverRTCPPort,
-				    fStreamStates[streamNum].streamToken);
+				    fStreamStates[trackNum].streamToken);
     SendingInterfaceAddr = origSendingInterfaceAddr;
     ReceivingInterfaceAddr = origReceivingInterfaceAddr;
     
     AddressString destAddrStr(destinationAddress);
     AddressString sourceAddrStr(sourceAddr);
     char timeoutParameterString[100];
-    if (fOurServer.fReclamationTestSeconds > 0) {
-      sprintf(timeoutParameterString, ";timeout=%u", fOurServer.fReclamationTestSeconds);
+    if (fOurRTSPServer.fReclamationSeconds > 0) {
+      sprintf(timeoutParameterString, ";timeout=%u", fOurRTSPServer.fReclamationSeconds);
     } else {
       timeoutParameterString[0] = '\0';
     }
@@ -1695,16 +1660,20 @@ void RTSPServer::RTSPClientSession
 	    break;
 	  }
           case RTP_TCP: {
-	    snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
-		     "RTSP/1.0 200 OK\r\n"
-		     "CSeq: %s\r\n"
-		     "%s"
-		     "Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n"
-		     "Session: %08X%s\r\n\r\n",
-		     ourClientConnection->fCurrentCSeq,
-		     dateHeader(),
-		     destAddrStr.val(), sourceAddrStr.val(), rtpChannelId, rtcpChannelId,
-		     fOurSessionId, timeoutParameterString);
+	    if (!fOurRTSPServer.fAllowStreamingRTPOverTCP) {
+	      ourClientConnection->handleCmd_unsupportedTransport();
+	    } else {
+	      snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
+		       "RTSP/1.0 200 OK\r\n"
+		       "CSeq: %s\r\n"
+		       "%s"
+		       "Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n"
+		       "Session: %08X%s\r\n\r\n",
+		       ourClientConnection->fCurrentCSeq,
+		       dateHeader(),
+		       destAddrStr.val(), sourceAddrStr.val(), rtpChannelId, rtcpChannelId,
+		       fOurSessionId, timeoutParameterString);
+	    }
 	    break;
 	  }
           case RAW_UDP: {
@@ -1797,6 +1766,7 @@ void RTSPServer::RTSPClientSession
     if (subsession == NULL /* means: aggregated operation */
 	|| subsession == fStreamStates[i].subsession) {
       if (fStreamStates[i].subsession != NULL) {
+	fOurRTSPServer.unnoteTCPStreamingOnSocket(fStreamStates[i].tcpSocketNum, this, i);
 	fStreamStates[i].subsession->deleteStream(fOurSessionId, fStreamStates[i].streamToken);
 	fStreamStates[i].subsession = NULL;
       }
@@ -1820,7 +1790,8 @@ void RTSPServer::RTSPClientSession
 void RTSPServer::RTSPClientSession
 ::handleCmd_PLAY(RTSPServer::RTSPClientConnection* ourClientConnection,
 		 ServerMediaSubsession* subsession, char const* fullRequestStr) {
-  char* rtspURL = fOurServer.rtspURL(fOurServerMediaSession, ourClientConnection->fClientInputSocket);
+  char* rtspURL
+    = fOurRTSPServer.rtspURL(fOurServerMediaSession, ourClientConnection->fClientInputSocket);
   unsigned rtspURLSize = strlen(rtspURL);
   
   // Parse the client's "Scale:" header, if any:
@@ -1847,7 +1818,9 @@ void RTSPServer::RTSPClientSession
   float duration = 0.0;
   double rangeStart = 0.0, rangeEnd = 0.0;
   char* absStart = NULL; char* absEnd = NULL;
-  Boolean sawRangeHeader = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd);
+  Boolean startTimeIsNow;
+  Boolean sawRangeHeader
+    = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd, startTimeIsNow);
   
   if (sawRangeHeader && absStart == NULL/*not seeking by 'absolute' time*/) {
     // Use this information, plus the stream's duration (if known), to create our own "Range:" header, for the response:
@@ -1859,8 +1832,8 @@ void RTSPServer::RTSPClientSession
       duration = -duration;
     }
     
-    // Make sure that "rangeStart" and "rangeEnd" (from the client's "Range:" header) have sane values
-    // before we send back our own "Range:" header in our response:
+    // Make sure that "rangeStart" and "rangeEnd" (from the client's "Range:" header)
+    // have sane values, before we send back our own "Range:" header in our response:
     if (rangeStart < 0.0) rangeStart = 0.0;
     else if (rangeStart > duration) rangeStart = duration;
     if (rangeEnd < 0.0) rangeEnd = 0.0;
@@ -1887,66 +1860,49 @@ void RTSPServer::RTSPClientSession
   unsigned i, numRTPInfoItems = 0;
   
   // Do any required seeking/scaling on each subsession, before starting streaming.
-  // (However, we don't do this if the "PLAY" request was for just a single subsession of a multiple-subsession stream;
-  //  for such streams, seeking/scaling can be done only with an aggregate "PLAY".)
+  // (However, we don't do this if the "PLAY" request was for just a single subsession
+  // of a multiple-subsession stream; for such streams, seeking/scaling can be done
+  // only with an aggregate "PLAY".)
   for (i = 0; i < fNumStreamStates; ++i) {
     if (subsession == NULL /* means: aggregated operation */ || fNumStreamStates == 1) {
-      if (sawScaleHeader) {
-	if (fStreamStates[i].subsession != NULL) {
+      if (fStreamStates[i].subsession != NULL) {
+	if (sawScaleHeader) {
 	  fStreamStates[i].subsession->setStreamScale(fOurSessionId, fStreamStates[i].streamToken, scale);
 	}
-      }
-      if (sawRangeHeader) {
 	if (absStart != NULL) {
 	  // Special case handling for seeking by 'absolute' time:
-	  
-	  if (fStreamStates[i].subsession != NULL) {
-	    fStreamStates[i].subsession->seekStream(fOurSessionId, fStreamStates[i].streamToken, absStart, absEnd);
-	  }
+	
+	  fStreamStates[i].subsession->seekStream(fOurSessionId, fStreamStates[i].streamToken, absStart, absEnd);
 	} else {
 	  // Seeking by relative (NPT) time:
 	  
-	  double streamDuration = 0.0; // by default; means: stream until the end of the media
-	  if (rangeEnd > 0.0 && (rangeEnd+0.001) < duration) { // the 0.001 is because we limited the values to 3 decimal places
-	    // We want the stream to end early.  Set the duration we want:
-	    streamDuration = rangeEnd - rangeStart;
-	    if (streamDuration < 0.0) streamDuration = -streamDuration; // should happen only if scale < 0.0
-	  }
-	  if (fStreamStates[i].subsession != NULL) {
-	    u_int64_t numBytes;
+	  u_int64_t numBytes;
+	  if (!sawRangeHeader || startTimeIsNow) {
+	    // We're resuming streaming without seeking, so we just do a 'null' seek
+	    // (to get our NPT, and to specify when to end streaming):
+	    fStreamStates[i].subsession->nullSeekStream(fOurSessionId, fStreamStates[i].streamToken,
+							rangeEnd, numBytes);
+	  } else {
+	    // We do a real 'seek':
+	    double streamDuration = 0.0; // by default; means: stream until the end of the media
+	    if (rangeEnd > 0.0 && (rangeEnd+0.001) < duration) {
+	      // the 0.001 is because we limited the values to 3 decimal places
+	      // We want the stream to end early.  Set the duration we want:
+	      streamDuration = rangeEnd - rangeStart;
+	      if (streamDuration < 0.0) streamDuration = -streamDuration;
+	          // should happen only if scale < 0.0
+	    }
 	    fStreamStates[i].subsession->seekStream(fOurSessionId, fStreamStates[i].streamToken,
 						    rangeStart, streamDuration, numBytes);
 	  }
 	}
-      } else {
-	// No "Range:" header was specified in the "PLAY", so we do a 'null' seek (i.e., we don't seek at all):
-	if (fStreamStates[i].subsession != NULL) {
-	  fStreamStates[i].subsession->nullSeekStream(fOurSessionId, fStreamStates[i].streamToken);
-	}
       }
     }
   }
   
   // Create the "Range:" header that we'll send back in our response.
   // (Note that we do this after seeking, in case the seeking operation changed the range start time.)
-  char* rangeHeader;
-  if (!sawRangeHeader) {
-    // There wasn't a "Range:" header in the request, so, in our response, begin the range with the current NPT (normal play time):
-    float curNPT = 0.0;
-    for (i = 0; i < fNumStreamStates; ++i) {
-      if (subsession == NULL /* means: aggregated operation */
-	  || subsession == fStreamStates[i].subsession) {
-	if (fStreamStates[i].subsession == NULL) continue;
-	float npt = fStreamStates[i].subsession->getCurrentNPT(fStreamStates[i].streamToken);
-	if (npt > curNPT) curNPT = npt;
-	// Note: If this is an aggregate "PLAY" on a multi-subsession stream, then it's conceivable that the NPTs of each subsession
-	// may differ (if there has been a previous seek on just one subsession).  In this (unusual) case, we just return the
-	// largest NPT; I hope that turns out OK...
-      }
-    }
-    
-    sprintf(buf, "Range: npt=%.3f-\r\n", curNPT);
-  } else if (absStart != NULL) {
+  if (absStart != NULL) {
     // We're seeking by 'absolute' time:
     if (absEnd == NULL) {
       sprintf(buf, "Range: clock=%s-\r\n", absStart);
@@ -1956,13 +1912,31 @@ void RTSPServer::RTSPClientSession
     delete[] absStart; delete[] absEnd;
   } else {
     // We're seeking by relative (NPT) time:
+    if (!sawRangeHeader || startTimeIsNow) {
+      // We didn't seek, so in our response, begin the range with the current NPT (normal play time):
+      float curNPT = 0.0;
+      for (i = 0; i < fNumStreamStates; ++i) {
+	if (subsession == NULL /* means: aggregated operation */
+	    || subsession == fStreamStates[i].subsession) {
+	  if (fStreamStates[i].subsession == NULL) continue;
+	  float npt = fStreamStates[i].subsession->getCurrentNPT(fStreamStates[i].streamToken);
+	  if (npt > curNPT) curNPT = npt;
+	  // Note: If this is an aggregate "PLAY" on a multi-subsession stream,
+	  // then it's conceivable that the NPTs of each subsession may differ
+	  // (if there has been a previous seek on just one subsession).
+	  // In this (unusual) case, we just return the largest NPT; I hope that turns out OK...
+	}
+      }
+      rangeStart = curNPT;
+    }
+
     if (rangeEnd == 0.0 && scale >= 0.0) {
       sprintf(buf, "Range: npt=%.3f-\r\n", rangeStart);
     } else {
       sprintf(buf, "Range: npt=%.3f-%.3f\r\n", rangeStart, rangeEnd);
     }
   }
-  rangeHeader = strDup(buf);
+  char* rangeHeader = strDup(buf);
   
   // Now, start streaming:
   for (i = 0; i < fNumStreamStates; ++i) {
@@ -2057,126 +2031,37 @@ void RTSPServer::RTSPClientSession
   setRTSPResponse(ourClientConnection, "200 OK", fOurSessionId);
 }
 
-RTSPServer::RTSPClientConnection*
+GenericMediaServer::ClientConnection*
 RTSPServer::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
   return new RTSPClientConnection(*this, clientSocket, clientAddr);
 }
 
-RTSPServer::RTSPClientSession*
+GenericMediaServer::ClientSession*
 RTSPServer::createNewClientSession(u_int32_t sessionId) {
   return new RTSPClientSession(*this, sessionId);
 }
 
-void RTSPServer::RTSPClientSession::noteLiveness() {
-  if (fOurServer.fReclamationTestSeconds > 0) {
-    envir().taskScheduler()
-      .rescheduleDelayedTask(fLivenessCheckTask,
-			     fOurServer.fReclamationTestSeconds*1000000,
-			     (TaskFunc*)livenessTimeoutTask, this);
-  }
-}
-
-void RTSPServer::RTSPClientSession
-::noteClientLiveness(RTSPClientSession* clientSession) {
-#ifdef DEBUG
-  char const* streamName
-    = (clientSession->fOurServerMediaSession == NULL) ? "???" : clientSession->fOurServerMediaSession->streamName();
-  fprintf(stderr, "RTSP client session (id \"%08X\", stream name \"%s\"): Liveness indication\n",
-	  clientSession->fOurSessionId, streamName);
-#endif
-  clientSession->noteLiveness();
-}
-
-void RTSPServer::RTSPClientSession
-::livenessTimeoutTask(RTSPClientSession* clientSession) {
-  // If this gets called, the client session is assumed to have timed out,
-  // so delete it:
-#ifdef DEBUG
-  char const* streamName
-    = (clientSession->fOurServerMediaSession == NULL) ? "???" : clientSession->fOurServerMediaSession->streamName();
-  fprintf(stderr, "RTSP client session (id \"%08X\", stream name \"%s\") has timed out (due to inactivity)\n",
-	  clientSession->fOurSessionId, streamName);
-#endif
-  delete clientSession;
-}
-
-
-////////// ServerMediaSessionIterator implementation //////////
-
-RTSPServer::ServerMediaSessionIterator
-::ServerMediaSessionIterator(RTSPServer& server)
-  : fOurIterator((server.fServerMediaSessions == NULL)
-		 ? NULL : HashTable::Iterator::create(*server.fServerMediaSessions)) {
-}
-
-RTSPServer::ServerMediaSessionIterator::~ServerMediaSessionIterator() {
-  delete fOurIterator;
-}
-
-ServerMediaSession* RTSPServer::ServerMediaSessionIterator::next() {
-  if (fOurIterator == NULL) return NULL;
-  
-  char const* key; // dummy
-  return (ServerMediaSession*)(fOurIterator->next(key));
-}
-
-
-////////// UserAuthenticationDatabase implementation //////////
-
-UserAuthenticationDatabase::UserAuthenticationDatabase(char const* realm,
-						       Boolean passwordsAreMD5)
-  : fTable(HashTable::create(STRING_HASH_KEYS)),
-    fRealm(strDup(realm == NULL ? "LIVE555 Streaming Media" : realm)),
-    fPasswordsAreMD5(passwordsAreMD5) {
-}
-
-UserAuthenticationDatabase::~UserAuthenticationDatabase() {
-  delete[] fRealm;
-  
-  // Delete the allocated 'password' strings that we stored in the table, and then the table itself:
-  char* password;
-  while ((password = (char*)fTable->RemoveNext()) != NULL) {
-    delete[] password;
-  }
-  delete fTable;
-}
-
-void UserAuthenticationDatabase::addUserRecord(char const* username,
-					       char const* password) {
-  fTable->Add(username, (void*)(strDup(password)));
-}
-
-void UserAuthenticationDatabase::removeUserRecord(char const* username) {
-  char* password = (char*)(fTable->Lookup(username));
-  fTable->Remove(username);
-  delete[] password;
-}
-
-char const* UserAuthenticationDatabase::lookupPassword(char const* username) {
-  return (char const*)(fTable->Lookup(username));
-}
-
 
 ///////// RTSPServerWithREGISTERProxying implementation /////////
 
 RTSPServerWithREGISTERProxying* RTSPServerWithREGISTERProxying
 ::createNew(UsageEnvironment& env, Port ourPort,
 	    UserAuthenticationDatabase* authDatabase, UserAuthenticationDatabase* authDatabaseForREGISTER,
-	    unsigned reclamationTestSeconds,
+	    unsigned reclamationSeconds,
 	    Boolean streamRTPOverTCP, int verbosityLevelForProxying) {
   int ourSocket = setUpOurSocket(env, ourPort);
   if (ourSocket == -1) return NULL;
   
-  return new RTSPServerWithREGISTERProxying(env, ourSocket, ourPort, authDatabase, authDatabaseForREGISTER, reclamationTestSeconds,
+  return new RTSPServerWithREGISTERProxying(env, ourSocket, ourPort, authDatabase, authDatabaseForREGISTER, reclamationSeconds,
 					    streamRTPOverTCP, verbosityLevelForProxying);
 }
 
 RTSPServerWithREGISTERProxying
 ::RTSPServerWithREGISTERProxying(UsageEnvironment& env, int ourSocket, Port ourPort,
 				 UserAuthenticationDatabase* authDatabase, UserAuthenticationDatabase* authDatabaseForREGISTER,
-				 unsigned reclamationTestSeconds,
+				 unsigned reclamationSeconds,
 				 Boolean streamRTPOverTCP, int verbosityLevelForProxying)
-  : RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds),
+  : RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationSeconds),
     fStreamRTPOverTCP(streamRTPOverTCP), fVerbosityLevelForProxying(verbosityLevelForProxying),
     fRegisteredProxyCounter(0), fAllowedCommandNames(NULL), fAuthDBForREGISTER(authDatabaseForREGISTER) {
 }
diff --git a/liveMedia/RTSPServerSupportingHTTPStreaming.cpp b/liveMedia/RTSPServerSupportingHTTPStreaming.cpp
index 275c64d..0849bb1 100644
--- a/liveMedia/RTSPServerSupportingHTTPStreaming.cpp
+++ b/liveMedia/RTSPServerSupportingHTTPStreaming.cpp
@@ -14,10 +14,11 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A server that supports both RTSP, and HTTP streaming (using Apple's "HTTP Live Streaming" protocol)
 // Implementation
 
+#include "RTSPServer.hh"
 #include "RTSPServerSupportingHTTPStreaming.hh"
 #include "RTSPCommon.hh"
 #ifndef _WIN32_WCE
@@ -43,7 +44,7 @@ RTSPServerSupportingHTTPStreaming
 RTSPServerSupportingHTTPStreaming::~RTSPServerSupportingHTTPStreaming() {
 }
 
-RTSPServer::RTSPClientConnection*
+GenericMediaServer::ClientConnection*
 RTSPServerSupportingHTTPStreaming::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
   return new RTSPClientConnectionSupportingHTTPStreaming(*this, clientSocket, clientAddr);
 }
@@ -51,11 +52,12 @@ RTSPServerSupportingHTTPStreaming::createNewClientConnection(int clientSocket, s
 RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming
 ::RTSPClientConnectionSupportingHTTPStreaming(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
   : RTSPClientConnection(ourServer, clientSocket, clientAddr),
-    fClientSessionId(0), fPlaylistSource(NULL), fTCPSink(NULL) {
+    fClientSessionId(0), fStreamSource(NULL), fPlaylistSource(NULL), fTCPSink(NULL) {
 }
 
 RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming::~RTSPClientConnectionSupportingHTTPStreaming() {
   Medium::close(fPlaylistSource);
+  Medium::close(fStreamSource);
   Medium::close(fTCPSink);
 }
 
@@ -112,7 +114,7 @@ void RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStream
       u_int8_t destinationTTL = 0;
       Boolean isMulticast = False;
       void* streamToken;
-      subsession->getStreamParameters(fClientSessionId, 0, clientRTPPort,clientRTCPPort, 0,0,0, destinationAddress,destinationTTL, isMulticast, serverRTPPort,serverRTCPPort, streamToken);
+      subsession->getStreamParameters(fClientSessionId, 0, clientRTPPort,clientRTCPPort, -1,0,0, destinationAddress,destinationTTL, isMulticast, serverRTPPort,serverRTCPPort, streamToken);
       
       // Seek the stream source to the desired place, with the desired duration, and (as a side effect) get the number of bytes:
       double dOffsetInSeconds = (double)offsetInSeconds;
@@ -144,10 +146,14 @@ void RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStream
       fResponseBuffer[0] = '\0'; // We've already sent the response.  This tells the calling code not to send it again.
       
       // Ask the media source to deliver - to the TCP sink - the desired data:
-      FramedSource* mediaSource = subsession->getStreamSource(streamToken);
-      if (mediaSource != NULL) {
+      if (fStreamSource != NULL) { // sanity check
+	if (fTCPSink != NULL) fTCPSink->stopPlaying();
+	Medium::close(fStreamSource);
+      }
+      fStreamSource = subsession->getStreamSource(streamToken);
+      if (fStreamSource != NULL) {
 	if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
-	fTCPSink->startPlaying(*mediaSource, afterStreaming, this);
+	fTCPSink->startPlaying(*fStreamSource, afterStreaming, this);
       }
     } while(0);
 
diff --git a/liveMedia/SIPClient.cpp b/liveMedia/SIPClient.cpp
index 131b2ff..452fdcf 100644
--- a/liveMedia/SIPClient.cpp
+++ b/liveMedia/SIPClient.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic SIP client
 // Implementation
 
@@ -86,7 +86,7 @@ SIPClient::SIPClient(UsageEnvironment& env,
 
   // Now, find out our source port number.  Hack: Do this by first trying to
   // send a 0-length packet, so that the "getSourcePort()" call will work.
-  fOurSocket->output(envir(), 255, (unsigned char*)"", 0);
+  fOurSocket->output(envir(), (unsigned char*)"", 0);
   Port srcPort(0);
   getSourcePort(env, fOurSocket->socketNum(), srcPort);
   if (srcPort.num() != 0) {
@@ -892,8 +892,7 @@ Boolean SIPClient::sendRequest(char const* requestString,
   }
   // NOTE: We should really check that "requestLength" is not #####
   // too large for UDP (see RFC 3261, section 18.1.1) #####
-  return fOurSocket->output(envir(), 255, (unsigned char*)requestString,
-			    requestLength);
+  return fOurSocket->output(envir(), (unsigned char*)requestString, requestLength);
 }
 
 unsigned SIPClient::getResponse(char*& responseBuffer,
diff --git a/liveMedia/ServerMediaSession.cpp b/liveMedia/ServerMediaSession.cpp
index 2093b29..c9c2a8d 100644
--- a/liveMedia/ServerMediaSession.cpp
+++ b/liveMedia/ServerMediaSession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A data structure that represents a session that consists of
 // potentially multiple (audio and/or video) sub-sessions
 // (This data structure is used for media *streamers* - i.e., servers.
@@ -24,6 +24,9 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "ServerMediaSession.hh"
 #include <GroupsockHelper.hh>
 #include <math.h>
+#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
+#define snprintf _snprintf
+#endif
 
 ////////// ServerMediaSession //////////
 
@@ -272,30 +275,35 @@ char* ServerMediaSession::generateSDPDescription() {
       + strlen(fDescriptionSDPString)
       + strlen(fInfoSDPString)
       + strlen(fMiscSDPLines);
+    sdpLength += 1000; // in case the length of the "subsession->sdpLines()" calls below change
     sdp = new char[sdpLength];
     if (sdp == NULL) break;
 
     // Generate the SDP prefix (session-level lines):
-    sprintf(sdp, sdpPrefixFmt,
-	    fCreationTime.tv_sec, fCreationTime.tv_usec, // o= <session id>
-	    1, // o= <version> // (needs to change if params are modified)
-	    ipAddressStr.val(), // o= <address>
-	    fDescriptionSDPString, // s= <description>
-	    fInfoSDPString, // i= <info>
-	    libNameStr, libVersionStr, // a=tool:
-	    sourceFilterLine, // a=source-filter: incl (if a SSM session)
-	    rangeLine, // a=range: line
-	    fDescriptionSDPString, // a=x-qt-text-nam: line
-	    fInfoSDPString, // a=x-qt-text-inf: line
-	    fMiscSDPLines); // miscellaneous session SDP lines (if any)
+    snprintf(sdp, sdpLength, sdpPrefixFmt,
+	     fCreationTime.tv_sec, fCreationTime.tv_usec, // o= <session id>
+	     1, // o= <version> // (needs to change if params are modified)
+	     ipAddressStr.val(), // o= <address>
+	     fDescriptionSDPString, // s= <description>
+	     fInfoSDPString, // i= <info>
+	     libNameStr, libVersionStr, // a=tool:
+	     sourceFilterLine, // a=source-filter: incl (if a SSM session)
+	     rangeLine, // a=range: line
+	     fDescriptionSDPString, // a=x-qt-text-nam: line
+	     fInfoSDPString, // a=x-qt-text-inf: line
+	     fMiscSDPLines); // miscellaneous session SDP lines (if any)
 
     // Then, add the (media-level) lines for each subsession:
     char* mediaSDP = sdp;
     for (subsession = fSubsessionsHead; subsession != NULL;
 	 subsession = subsession->fNext) {
-      mediaSDP += strlen(mediaSDP);
+      unsigned mediaSDPLength = strlen(mediaSDP);
+      mediaSDP += mediaSDPLength;
+      sdpLength -= mediaSDPLength;
+      if (sdpLength <= 1) break; // the SDP has somehow become too long
+
       char const* sdpLines = subsession->sdpLines();
-      if (sdpLines != NULL) sprintf(mediaSDP, "%s", sdpLines);
+      if (sdpLines != NULL) snprintf(mediaSDP, sdpLength, "%s", sdpLines);
     }
   } while (0);
 
@@ -304,7 +312,7 @@ char* ServerMediaSession::generateSDPDescription() {
 }
 
 
-////////// ServerMediaSessionIterator //////////
+////////// ServerMediaSubsessionIterator //////////
 
 ServerMediaSubsessionIterator
 ::ServerMediaSubsessionIterator(ServerMediaSession& session)
@@ -367,8 +375,10 @@ void ServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
   delete[] absStart; absStart = NULL;
   delete[] absEnd; absEnd = NULL;
 }
-void ServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void* /*streamToken*/) {
+void ServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void* /*streamToken*/,
+					   double streamEndTime, u_int64_t& numBytes) {
   // default implementation: do nothing
+  numBytes = 0;
 }
 void ServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/,
 					   void* /*streamToken*/, float /*scale*/) {
diff --git a/liveMedia/SimpleRTPSink.cpp b/liveMedia/SimpleRTPSink.cpp
index c5fd2f5..f2e8d23 100644
--- a/liveMedia/SimpleRTPSink.cpp
+++ b/liveMedia/SimpleRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simple RTP sink that packs frames into each outgoing
 //     packet, without any fragmentation or special headers.
 // Implementation
diff --git a/liveMedia/SimpleRTPSource.cpp b/liveMedia/SimpleRTPSource.cpp
index 976bb1a..bdb16cc 100644
--- a/liveMedia/SimpleRTPSource.cpp
+++ b/liveMedia/SimpleRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A RTP source for a simple RTP payload format that
 //     - doesn't have any special headers following the RTP header
 //     - doesn't have any special framing apart from the packet data itself
diff --git a/liveMedia/StreamParser.cpp b/liveMedia/StreamParser.cpp
index e1cd82e..1f38176 100644
--- a/liveMedia/StreamParser.cpp
+++ b/liveMedia/StreamParser.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Abstract class for parsing a byte stream
 // Implementation
 
@@ -85,7 +85,7 @@ unsigned StreamParser::getBits(unsigned numBits) {
     lastByte >>= (fRemainingUnparsedBits - numBits);
     fRemainingUnparsedBits -= numBits;
 
-    return (unsigned)lastByte &~ ((~0)<<numBits);
+    return (unsigned)lastByte &~ ((~0u)<<numBits);
   } else {
     unsigned char lastByte;
     if (fRemainingUnparsedBits > 0) {
@@ -102,7 +102,7 @@ unsigned StreamParser::getBits(unsigned numBits) {
 
     result >>= (32 - remainingBits);
     result |= (lastByte << remainingBits);
-    if (numBits < 32) result &=~ ((~0)<<numBits);
+    if (numBits < 32) result &=~ ((~0u)<<numBits);
 
     unsigned const numRemainingBytes = (remainingBits+7)/8;
     fCurParserIndex += numRemainingBytes;
diff --git a/liveMedia/StreamParser.hh b/liveMedia/StreamParser.hh
index 6c5ad06..e766f55 100644
--- a/liveMedia/StreamParser.hh
+++ b/liveMedia/StreamParser.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Abstract class for parsing a byte stream
 // C++ header
 
@@ -74,7 +74,7 @@ protected: // we're a virtual base class
     fRemainingUnparsedBits = 0;
     return curBank()[fCurParserIndex++];
   }
-  u_int8_t test1Byte(unsigned numBytes) { // as above, but doesn't advance ptr
+  u_int8_t test1Byte() { // as above, but doesn't advance ptr
     ensureValidBytes(1);
     return nextToParse()[0];
   }
diff --git a/liveMedia/StreamReplicator.cpp b/liveMedia/StreamReplicator.cpp
index 87618c5..a047635 100644
--- a/liveMedia/StreamReplicator.cpp
+++ b/liveMedia/StreamReplicator.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // An class that can be used to create (possibly multiple) 'replicas' of an incoming stream.
 // Implementation.
 
@@ -38,7 +38,6 @@ private:
 private:
   StreamReplicator& fOurReplicator;
   int fFrameIndex; // 0 or 1, depending upon which frame we're currently requesting; could also be -1 if we've stopped playing
-  Boolean fDeliveryInProgress;
 
   // Replicas that are currently awaiting data are kept in a (singly-linked) list:
   StreamReplica* fNext;
@@ -69,7 +68,7 @@ FramedSource* StreamReplicator::createStreamReplica() {
 
 void StreamReplicator::getNextFrame(StreamReplica* replica) {
   if (fInputSourceHasClosed) { // handle closure instead
-    FramedSource::handleClosure(replica);
+    replica->handleClosure();
     return;
   }
 
@@ -104,12 +103,15 @@ void StreamReplicator::getNextFrame(StreamReplica* replica) {
 }
 
 void StreamReplicator::deactivateStreamReplica(StreamReplica* replicaBeingDeactivated) {
+  if (replicaBeingDeactivated->fFrameIndex == -1) return; // this replica has already been deactivated (or was never activated at all)
+
   // Assert: fNumActiveReplicas > 0
-  if (fNumReplicas == 0) fprintf(stderr, "StreamReplicator::deactivateStreamReplica() Internal Error!\n"); // should not happen
+  if (fNumActiveReplicas == 0) fprintf(stderr, "StreamReplicator::deactivateStreamReplica() Internal Error!\n"); // should not happen
   --fNumActiveReplicas;
+  replicaBeingDeactivated->fFrameIndex = -1;
 
-  if (replicaBeingDeactivated->fDeliveryInProgress) --fNumDeliveriesMadeSoFar;
-    // hack in case we're called while in the middle of a frame delivery to us
+  // Forget about any frame delivery that might have just been made to this replica:
+  if (replicaBeingDeactivated->fFrameIndex != fFrameIndex && fNumDeliveriesMadeSoFar > 0) --fNumDeliveriesMadeSoFar;
 
   // Check whether the replica being deactivated is the 'master' replica, or is enqueued awaiting a frame:
   if (replicaBeingDeactivated == fMasterReplica) {
@@ -177,26 +179,29 @@ void StreamReplicator::deactivateStreamReplica(StreamReplica* replicaBeingDeacti
 	}
       }
     }
+
+    // Check for the possibility that - now that a replica has been deactivated - all other
+    // replicas have received the current frame, and so now we need to complete delivery to
+    // the master replica:
+    if (fMasterReplica != NULL && fInputSource != NULL && !fInputSource->isCurrentlyAwaitingData()) deliverReceivedFrame();
   }
 
   if (fNumActiveReplicas == 0 && fInputSource != NULL) fInputSource->stopGettingFrames(); // tell our source to stop too
 }
 
 void StreamReplicator::removeStreamReplica(StreamReplica* replicaBeingRemoved) {
+  // First, handle the replica that's being removed the same way that we would if it were merely being deactivated:
+  deactivateStreamReplica(replicaBeingRemoved);
+
   // Assert: fNumReplicas > 0
   if (fNumReplicas == 0) fprintf(stderr, "StreamReplicator::removeStreamReplica() Internal Error!\n"); // should not happen
   --fNumReplicas;
 
   // If this was the last replica, then delete ourselves (if we were set up to do so):
   if (fNumReplicas == 0 && fDeleteWhenLastReplicaDies) {
-    delete this;
+    Medium::close(this);
     return;
   }
-
-  // Now handle the replica that's being removed the same way that we would if it were merely being deactivated:
-  if (replicaBeingRemoved->fFrameIndex != -1) { // i.e., we haven't already done this
-    deactivateStreamReplica(replicaBeingRemoved);
-  }
 }
 
 void StreamReplicator::afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes,
@@ -228,16 +233,16 @@ void StreamReplicator::onSourceClosure() {
   while ((replica = fReplicasAwaitingCurrentFrame) != NULL) {
     fReplicasAwaitingCurrentFrame = replica->fNext;
     replica->fNext = NULL;
-    FramedSource::handleClosure(replica);
+    replica->handleClosure();
   }
   while ((replica = fReplicasAwaitingNextFrame) != NULL) {
     fReplicasAwaitingNextFrame = replica->fNext;
     replica->fNext = NULL;
-    FramedSource::handleClosure(replica);
+    replica->handleClosure();
   }
   if ((replica = fMasterReplica) != NULL) {
     fMasterReplica = NULL;
-    FramedSource::handleClosure(replica);
+    replica->handleClosure();
   }
 }
 
@@ -250,8 +255,6 @@ void StreamReplicator::deliverReceivedFrame() {
     fReplicasAwaitingCurrentFrame = replica->fNext;
     replica->fNext = NULL;
     
-    replica->fDeliveryInProgress = True;
-
     // Assert: fMasterReplica != NULL
     if (fMasterReplica == NULL) fprintf(stderr, "StreamReplicator::deliverReceivedFrame() Internal Error 1!\n"); // shouldn't happen
     StreamReplica::copyReceivedFrame(replica, fMasterReplica);
@@ -263,8 +266,6 @@ void StreamReplicator::deliverReceivedFrame() {
 
     // Complete delivery to this replica:
     FramedSource::afterGetting(replica);
-
-    replica->fDeliveryInProgress = False;
   }
 
   if (fNumDeliveriesMadeSoFar == fNumActiveReplicas - 1 && fMasterReplica != NULL) {
@@ -292,6 +293,7 @@ void StreamReplicator::deliverReceivedFrame() {
     fReplicasAwaitingCurrentFrame = fReplicasAwaitingNextFrame;
     fReplicasAwaitingNextFrame = NULL;
     
+    // Complete delivery to the 'master' replica (thereby completing all deliveries for this frame):
     FramedSource::afterGetting(replica);
   }
 }
@@ -302,7 +304,7 @@ void StreamReplicator::deliverReceivedFrame() {
 StreamReplica::StreamReplica(StreamReplicator& ourReplicator)
   : FramedSource(ourReplicator.envir()),
     fOurReplicator(ourReplicator),
-    fFrameIndex(-1/*we haven't started playing yet*/), fDeliveryInProgress(False), fNext(NULL) {
+    fFrameIndex(-1/*we haven't started playing yet*/), fNext(NULL) {
 }
 
 StreamReplica::~StreamReplica() {
@@ -314,10 +316,7 @@ void StreamReplica::doGetNextFrame() {
 }
 
 void StreamReplica::doStopGettingFrames() {
-  if (fFrameIndex != -1) { // we had been activated
-    fFrameIndex = -1; // When we start reading again, this will tell the replicator that we were previously inactive.
-    fOurReplicator.deactivateStreamReplica(this);
-  }
+  fOurReplicator.deactivateStreamReplica(this);
 }
 
 void StreamReplica::copyReceivedFrame(StreamReplica* toReplica, StreamReplica* fromReplica) {
diff --git a/liveMedia/T140TextMatroskaFileServerMediaSubsession.cpp b/liveMedia/T140TextMatroskaFileServerMediaSubsession.cpp
deleted file mode 100644
index ab89b2c..0000000
--- a/liveMedia/T140TextMatroskaFileServerMediaSubsession.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an text (subtitle) track within a Matroska file.
-// Implementation
-
-#include "T140TextMatroskaFileServerMediaSubsession.hh"
-#include "T140TextRTPSink.hh"
-#include "MatroskaDemuxedTrack.hh"
-
-T140TextMatroskaFileServerMediaSubsession* T140TextMatroskaFileServerMediaSubsession
-::createNew(MatroskaFileServerDemux& demux, unsigned trackNumber) {
-  return new T140TextMatroskaFileServerMediaSubsession(demux, trackNumber);
-}
-
-T140TextMatroskaFileServerMediaSubsession
-::T140TextMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber)
-  : FileServerMediaSubsession(demux.envir(), demux.fileName(), False),
-    fOurDemux(demux), fTrackNumber(trackNumber) {
-}
-
-T140TextMatroskaFileServerMediaSubsession
-::~T140TextMatroskaFileServerMediaSubsession() {
-}
-
-float T140TextMatroskaFileServerMediaSubsession::duration() const { return fOurDemux.fileDuration(); }
-
-void T140TextMatroskaFileServerMediaSubsession
-::seekStreamSource(FramedSource* inputSource, double& seekNPT, double /*streamDuration*/, u_int64_t& /*numBytes*/) {
-  ((MatroskaDemuxedTrack*)inputSource)->seekToTime(seekNPT);
-}
-
-FramedSource* T140TextMatroskaFileServerMediaSubsession
-::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
-  estBitrate = 48; // kbps, estimate
-
-  return fOurDemux.newDemuxedTrack(clientSessionId, fTrackNumber);
-}
-
-RTPSink* T140TextMatroskaFileServerMediaSubsession
-::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) {
-  return T140TextRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
-}
diff --git a/liveMedia/T140TextMatroskaFileServerMediaSubsession.hh b/liveMedia/T140TextMatroskaFileServerMediaSubsession.hh
deleted file mode 100644
index e0e0edb..0000000
--- a/liveMedia/T140TextMatroskaFileServerMediaSubsession.hh
+++ /dev/null
@@ -1,54 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from an text (subtitle) track within a Matroska file.
-// C++ header
-
-#ifndef _T140_TEXT_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-#define _T140_TEXT_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-
-#ifndef _FILE_SERVER_MEDIA_SUBSESSION_HH
-#include "FileServerMediaSubsession.hh"
-#endif
-#ifndef _MATROSKA_FILE_SERVER_DEMUX_HH
-#include "MatroskaFileServerDemux.hh"
-#endif
-
-class T140TextMatroskaFileServerMediaSubsession: public FileServerMediaSubsession {
-public:
-  static T140TextMatroskaFileServerMediaSubsession*
-  createNew(MatroskaFileServerDemux& demux, unsigned trackNumber);
-
-private:
-  T140TextMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber);
-      // called only by createNew();
-  virtual ~T140TextMatroskaFileServerMediaSubsession();
-
-private: // redefined virtual functions
-  virtual float duration() const;
-  virtual void seekStreamSource(FramedSource* inputSource, double& seekNPT, double streamDuration, u_int64_t& numBytes);
-  virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
-					      unsigned& estBitrate);
-  virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
-
-private:
-  MatroskaFileServerDemux& fOurDemux;
-  unsigned fTrackNumber;
-};
-
-#endif
diff --git a/liveMedia/T140TextRTPSink.cpp b/liveMedia/T140TextRTPSink.cpp
index 24c8a18..a88daec 100644
--- a/liveMedia/T140TextRTPSink.cpp
+++ b/liveMedia/T140TextRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for T.140 text (RFC 2793)
 // Implementation
 
@@ -180,5 +180,5 @@ void T140IdleFilter::onSourceClosure() {
   envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);
   fIdleTimerTask = NULL;
 
-  FramedSource::handleClosure(this);
+  handleClosure();
 }
diff --git a/liveMedia/TCPStreamSink.cpp b/liveMedia/TCPStreamSink.cpp
index 8f83dfa..3a6a1bf 100644
--- a/liveMedia/TCPStreamSink.cpp
+++ b/liveMedia/TCPStreamSink.cpp
@@ -14,11 +14,12 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A sink representing a TCP output stream
 // Implementation
 
 #include "TCPStreamSink.hh"
+#include <GroupsockHelper.hh> // for "ignoreSigPipeOnSocket()"
 
 TCPStreamSink* TCPStreamSink::createNew(UsageEnvironment& env, int socketNum) {
   return new TCPStreamSink(env, socketNum);
@@ -29,9 +30,12 @@ TCPStreamSink::TCPStreamSink(UsageEnvironment& env, int socketNum)
     fUnwrittenBytesStart(0), fUnwrittenBytesEnd(0),
     fInputSourceIsOpen(False), fOutputSocketIsWritable(True),
     fOutputSocketNum(socketNum) {
+  ignoreSigPipeOnSocket(socketNum);
 }
 
 TCPStreamSink::~TCPStreamSink() {
+  // Turn off any pending background handling of our output socket:
+  envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum);
 }
 
 Boolean TCPStreamSink::continuePlaying() {
@@ -68,9 +72,7 @@ void TCPStreamSink::processBuffer() {
   // Then, read from our input source, if we can (& we're not already reading from it):
   if (fInputSourceIsOpen && freeBufferSpace() >= TCP_STREAM_SINK_MIN_READ_SIZE && !fSource->isCurrentlyAwaitingData()) {
     fSource->getNextFrame(&fBuffer[fUnwrittenBytesEnd], freeBufferSpace(), afterGettingFrame, this, ourOnSourceClosure, this);
-  }
-
-  if (!fInputSourceIsOpen && numUnwrittenBytes() == 0) {
+  } else if (!fInputSourceIsOpen && numUnwrittenBytes() == 0) {
     // We're now done:
     onSourceClosure();
   }
diff --git a/liveMedia/TextRTPSink.cpp b/liveMedia/TextRTPSink.cpp
index 48333d2..636b86a 100644
--- a/liveMedia/TextRTPSink.cpp
+++ b/liveMedia/TextRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic RTP sink for text codecs (abstract base class)
 // Implementation
 
diff --git a/liveMedia/TheoraVideoRTPSink.cpp b/liveMedia/TheoraVideoRTPSink.cpp
index 9bef5c4..0964ad0 100644
--- a/liveMedia/TheoraVideoRTPSink.cpp
+++ b/liveMedia/TheoraVideoRTPSink.cpp
@@ -1,98 +1,105 @@
-/*
- * Theora Video RTP packetizer
- * Copied from live555's VorbisAudioRTPSink
- */
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// RTP sink for Theora video
+// Implementation
 
 #include "TheoraVideoRTPSink.hh"
 #include "Base64.hh"
+#include "VorbisAudioRTPSource.hh" // for parseVorbisOrTheoraConfigStr()
+#include "VorbisAudioRTPSink.hh" // for generateVorbisOrTheoraConfigStr()
+
+TheoraVideoRTPSink* TheoraVideoRTPSink
+::createNew(UsageEnvironment& env, Groupsock* RTPgs, u_int8_t rtpPayloadFormat,
+	    u_int8_t* identificationHeader, unsigned identificationHeaderSize,
+	    u_int8_t* commentHeader, unsigned commentHeaderSize,
+	    u_int8_t* setupHeader, unsigned setupHeaderSize,
+	    u_int32_t identField) {
+  return new TheoraVideoRTPSink(env, RTPgs,
+				rtpPayloadFormat,
+				identificationHeader, identificationHeaderSize,
+				commentHeader, commentHeaderSize,
+				setupHeader, setupHeaderSize, identField);
+}
+
+TheoraVideoRTPSink* TheoraVideoRTPSink
+::createNew(UsageEnvironment& env, Groupsock* RTPgs, u_int8_t rtpPayloadFormat,
+            char const* configStr) {
+  // Begin by decoding and unpacking the configuration string:
+  u_int8_t* identificationHeader; unsigned identificationHeaderSize;
+  u_int8_t* commentHeader; unsigned commentHeaderSize;
+  u_int8_t* setupHeader; unsigned setupHeaderSize;
+  u_int32_t identField;
 
-TheoraVideoRTPSink::TheoraVideoRTPSink(UsageEnvironment& env, Groupsock* RTPgs,
-				       u_int8_t rtpPayloadFormat,
-				       u_int32_t rtpTimestampFrequency,
-				       unsigned width, unsigned height, enum PixFmt pf,
-				       u_int8_t* identificationHeader, unsigned identificationHeaderSize,
-				       u_int8_t* commentHeader, unsigned commentHeaderSize,
-				       u_int8_t* setupHeader, unsigned setupHeaderSize,
-				       u_int32_t identField)
-  : VideoRTPSink(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency, "theora"),
+  parseVorbisOrTheoraConfigStr(configStr,
+                               identificationHeader, identificationHeaderSize,
+                               commentHeader, commentHeaderSize,
+                               setupHeader, setupHeaderSize,
+                               identField);
+
+  TheoraVideoRTPSink* resultSink
+    = new TheoraVideoRTPSink(env, RTPgs, rtpPayloadFormat,
+                             identificationHeader, identificationHeaderSize,
+                             commentHeader, commentHeaderSize,
+                             setupHeader, setupHeaderSize,
+                             identField);
+  delete[] identificationHeader; delete[] commentHeader; delete[] setupHeader;
+
+  return resultSink;
+}
+
+TheoraVideoRTPSink
+::TheoraVideoRTPSink(UsageEnvironment& env, Groupsock* RTPgs, u_int8_t rtpPayloadFormat,
+		     u_int8_t* identificationHeader, unsigned identificationHeaderSize,
+		     u_int8_t* commentHeader, unsigned commentHeaderSize,
+		     u_int8_t* setupHeader, unsigned setupHeaderSize,
+		     u_int32_t identField)
+  : VideoRTPSink(env, RTPgs, rtpPayloadFormat, 90000, "THEORA"),
     fIdent(identField), fFmtpSDPLine(NULL) {
   static const char *pf_to_str[] = {
     "YCbCr-4:2:0",
+    "Reserved",
     "YCbCr-4:2:2",
     "YCbCr-4:4:4",
   };
   
-  // Create packed configuration headers, and encode this data into a "a=fmtp:" SDP line that we'll use to describe it:
-  
-  // First, count how many headers (<=3) are included, and how many bytes will be used to encode these headers' sizes:
-  unsigned numHeaders = 0;
-  unsigned sizeSize[2]; // The number of bytes used to encode the lengths of the first two headers (but not the length of the 3rd)
-  sizeSize[0] = sizeSize[1] = 0;
-  if (identificationHeaderSize > 0) {
-    sizeSize[numHeaders++] = identificationHeaderSize < 128 ? 1 : identificationHeaderSize < 16384 ? 2 : 3;
-  }
-  if (commentHeaderSize > 0) {
-    sizeSize[numHeaders++] = commentHeaderSize < 128 ? 1 : commentHeaderSize < 16384 ? 2 : 3;
-  }
-  if (setupHeaderSize > 0) {
-    ++numHeaders;
-  } else {
-    sizeSize[1] = 0; // We have at most two headers, so the second one's length isn't encoded
+  unsigned width = 1280; // default value
+  unsigned height = 720; // default value
+  unsigned pf = 0; // default value
+  if (identificationHeaderSize >= 42) {
+    // Parse this header to get the "width", "height", "pf" (pixel format), and
+    // 'nominal bitrate' parameters:
+    u_int8_t* p = identificationHeader; // alias
+    width = (p[14]<<16)|(p[15]<<8)|p[16];
+    height = (p[17]<<16)|(p[18]<<8)|p[19];
+    pf = (p[41]&0x18)>>3;
+    unsigned nominalBitrate = (p[37]<<16)|(p[38]<<8)|p[39];
+    if (nominalBitrate > 0) estimatedBitrate() = nominalBitrate/1000;
   }
-  if (numHeaders == 0) return; // With no headers, we can't set up a configuration
-  if (numHeaders == 1) sizeSize[0] = 0; // With only one header, its length isn't encoded
-  
-  // Then figure out the size of the packed configuration headers, and allocate space for this:
-  unsigned length = identificationHeaderSize + commentHeaderSize + setupHeaderSize; // The "length" field in the packed headers
-  if (length > (unsigned)0xFFFF) return; // too big for a 16-bit field; we can't handle this
-  unsigned packedHeadersSize
-    = 4 // "Number of packed headers" field
-    + 3 // "ident" field
-    + 2 // "length" field
-    + 1 // "n. of headers" field
-    + sizeSize[0] + sizeSize[1] // "length1" and "length2" (if present) fields
-    + length;
-  u_int8_t* packedHeaders = new u_int8_t[packedHeadersSize];
-  if (packedHeaders == NULL) return;
-  
-  // Fill in the 'packed headers':
-  u_int8_t* p = packedHeaders;
-  *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 1; // "Number of packed headers": 1
-  *p++ = fIdent>>16; *p++ = fIdent>>8; *p++ = fIdent; // "Ident" (24 bits)
-  *p++ = length>>8; *p++ = length; // "length" (16 bits)
-  *p++ = numHeaders-1; // "n. of headers"
-  if (numHeaders > 1) {
-    // Fill in the "length1" header:
-    unsigned length1 = identificationHeaderSize > 0 ? identificationHeaderSize : commentHeaderSize;
-    if (length1 >= 16384) {
-      *p++ = 0x80; // flag, but no more, because we know length1 <= 32767
-    }
-    if (length1 >= 128) {
-      *p++ = 0x80|((length1&0x3F80)>>7); // flag + the second 7 bits
-    }
-    *p++ = length1&0x7F; // the low 7 bits
-    
-    if (numHeaders > 2) { // numHeaders == 3
-      // Fill in the "length2" header (for the 'Comment' header):
-      unsigned length2 = commentHeaderSize;
-      if (length2 >= 16384) {
-	*p++ = 0x80; // flag, but no more, because we know length2 <= 32767
-      }
-      if (length2 >= 128) {
-	*p++ = 0x80|((length2&0x3F80)>>7); // flag + the second 7 bits
-      }
-      *p++ = length2&0x7F; // the low 7 bits
-    }
-  }
-  // Copy each header:
-  if (identificationHeader != NULL) memmove(p, identificationHeader, identificationHeaderSize); p += identificationHeaderSize;
-  if (commentHeader != NULL) memmove(p, commentHeader, commentHeaderSize); p += commentHeaderSize;
-  if (setupHeader != NULL) memmove(p, setupHeader, setupHeaderSize);
-  
-  // Having set up the 'packed configuration headers', Base-64-encode this, and put it in our "a=fmtp:" SDP line:
-  char* base64PackedHeaders = base64Encode((char const*)packedHeaders, packedHeadersSize);
-  delete[] packedHeaders;
-  
+
+  // Generate a 'config' string from the supplied configuration headers:
+  char* base64PackedHeaders
+    = generateVorbisOrTheoraConfigStr(identificationHeader, identificationHeaderSize,
+                                      commentHeader, commentHeaderSize,
+                                      setupHeader, setupHeaderSize,
+                                      identField);
+  if (base64PackedHeaders == NULL) return;
+
+  // Then use this 'config' string to construct our "a=fmtp:" SDP line:
   unsigned fmtpSDPLineMaxSize = 200 + strlen(base64PackedHeaders);// 200 => more than enough space
   fFmtpSDPLine = new char[fmtpSDPLineMaxSize];
   sprintf(fFmtpSDPLine, "a=fmtp:%d sampling=%s;width=%u;height=%u;delivery-method=out_band/rtsp;configuration=%s\r\n", rtpPayloadType(), pf_to_str[pf], width, height, base64PackedHeaders);
@@ -103,22 +110,6 @@ TheoraVideoRTPSink::~TheoraVideoRTPSink() {
   delete[] fFmtpSDPLine;
 }
 
-TheoraVideoRTPSink*
-TheoraVideoRTPSink::createNew(UsageEnvironment& env, Groupsock* RTPgs,
-			      u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency,
-			      unsigned width, unsigned height, enum PixFmt pf,
-			      u_int8_t* identificationHeader, unsigned identificationHeaderSize,
-			      u_int8_t* commentHeader, unsigned commentHeaderSize,
-			      u_int8_t* setupHeader, unsigned setupHeaderSize,
-			      u_int32_t identField) {
-  return new TheoraVideoRTPSink(env, RTPgs,
-				rtpPayloadFormat, rtpTimestampFrequency,
-				width, height, pf,
-				identificationHeader, identificationHeaderSize,
-				commentHeader, commentHeaderSize,
-				setupHeader, setupHeaderSize, identField);
-}
-
 char const* TheoraVideoRTPSink::auxSDPLine() {
   return fFmtpSDPLine;
 }
diff --git a/liveMedia/VorbisAudioRTPSource.cpp b/liveMedia/TheoraVideoRTPSource.cpp
similarity index 62%
copy from liveMedia/VorbisAudioRTPSource.cpp
copy to liveMedia/TheoraVideoRTPSource.cpp
index 7ba026a..759b65c 100644
--- a/liveMedia/VorbisAudioRTPSource.cpp
+++ b/liveMedia/TheoraVideoRTPSource.cpp
@@ -14,52 +14,50 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// Vorbis Audio RTP Sources
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// Theora Video RTP Sources
 // Implementation
 
-#include "VorbisAudioRTPSource.hh"
+#include "TheoraVideoRTPSource.hh"
 
-////////// VorbisBufferedPacket and VorbisBufferedPacketFactory //////////
+////////// TheoraBufferedPacket and TheoraBufferedPacketFactory //////////
 
-class VorbisBufferedPacket: public BufferedPacket {
+class TheoraBufferedPacket: public BufferedPacket {
 public:
-  VorbisBufferedPacket();
-  virtual ~VorbisBufferedPacket();
+  TheoraBufferedPacket();
+  virtual ~TheoraBufferedPacket();
 
 private: // redefined virtual functions
   virtual unsigned nextEnclosedFrameSize(unsigned char*& framePtr,
 					 unsigned dataSize);
 };
 
-class VorbisBufferedPacketFactory: public BufferedPacketFactory {
+class TheoraBufferedPacketFactory: public BufferedPacketFactory {
 private: // redefined virtual functions
   virtual BufferedPacket* createNewPacket(MultiFramedRTPSource* ourSource);
 };
 
 
-///////// MPEG4VorbisAudioRTPSource implementation ////////
+///////// TheoraVideoRTPSource implementation ////////
 
-VorbisAudioRTPSource*
-VorbisAudioRTPSource::createNew(UsageEnvironment& env, Groupsock* RTPgs,
-				unsigned char rtpPayloadFormat,
-				unsigned rtpTimestampFrequency) {
-  return new VorbisAudioRTPSource(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency);
+TheoraVideoRTPSource*
+TheoraVideoRTPSource::createNew(UsageEnvironment& env, Groupsock* RTPgs,
+				unsigned char rtpPayloadFormat) {
+  return new TheoraVideoRTPSource(env, RTPgs, rtpPayloadFormat);
 }
 
-VorbisAudioRTPSource
-::VorbisAudioRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
-		     unsigned char rtpPayloadFormat,
-		     unsigned rtpTimestampFrequency)
-  : MultiFramedRTPSource(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency,
-			 new VorbisBufferedPacketFactory),
+TheoraVideoRTPSource
+::TheoraVideoRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
+		       unsigned char rtpPayloadFormat)
+  : MultiFramedRTPSource(env, RTPgs, rtpPayloadFormat, 90000,
+			 new TheoraBufferedPacketFactory),
     fCurPacketIdent(0) {
 }
 
-VorbisAudioRTPSource::~VorbisAudioRTPSource() {
+TheoraVideoRTPSource::~TheoraVideoRTPSource() {
 }
 
-Boolean VorbisAudioRTPSource
+Boolean TheoraVideoRTPSource
 ::processSpecialHeader(BufferedPacket* packet,
                        unsigned& resultSpecialHeaderSize) {
   unsigned char* headerStart = packet->data();
@@ -71,8 +69,8 @@ Boolean VorbisAudioRTPSource
   // The first 3 bytes of the header are the "Ident" field:
   fCurPacketIdent = (headerStart[0]<<16) | (headerStart[1]<<8) | headerStart[2];
 
-  // The 4th byte is F|VDT|numPkts.
-  // Reject any packet with VDT == 3:
+  // The 4th byte is F|TDT|numPkts.
+  // Reject any packet with TDT == 3:
   if ((headerStart[3]&0x30) == 0x30) return False;
 
   u_int8_t F = headerStart[3]>>6;
@@ -82,20 +80,20 @@ Boolean VorbisAudioRTPSource
   return True;
 }
 
-char const* VorbisAudioRTPSource::MIMEtype() const {
-  return "audio/VORBIS";
+char const* TheoraVideoRTPSource::MIMEtype() const {
+  return "video/THEORA";
 }
 
 
-////////// VorbisBufferedPacket and VorbisBufferedPacketFactory implementation //////////
+////////// TheoraBufferedPacket and TheoraBufferedPacketFactory implementation //////////
 
-VorbisBufferedPacket::VorbisBufferedPacket() {
+TheoraBufferedPacket::TheoraBufferedPacket() {
 }
 
-VorbisBufferedPacket::~VorbisBufferedPacket() {
+TheoraBufferedPacket::~TheoraBufferedPacket() {
 }
 
-unsigned VorbisBufferedPacket
+unsigned TheoraBufferedPacket
 ::nextEnclosedFrameSize(unsigned char*& framePtr, unsigned dataSize) {
   if (dataSize < 2) {
     // There's not enough space for a 2-byte header.  TARFU!  Just return the data that's left:
@@ -109,7 +107,7 @@ unsigned VorbisBufferedPacket
   return frameSize;
 }
 
-BufferedPacket* VorbisBufferedPacketFactory
+BufferedPacket* TheoraBufferedPacketFactory
 ::createNewPacket(MultiFramedRTPSource* /*ourSource*/) {
-  return new VorbisBufferedPacket();
+  return new TheoraBufferedPacket();
 }
diff --git a/liveMedia/VP8VideoMatroskaFileServerMediaSubsession.cpp b/liveMedia/VP8VideoMatroskaFileServerMediaSubsession.cpp
deleted file mode 100644
index 124d069..0000000
--- a/liveMedia/VP8VideoMatroskaFileServerMediaSubsession.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from a VP8 Video track within a Matroska file.
-// Implementation
-
-#include "VP8VideoMatroskaFileServerMediaSubsession.hh"
-#include "VP8VideoRTPSink.hh"
-#include "MatroskaDemuxedTrack.hh"
-
-VP8VideoMatroskaFileServerMediaSubsession* VP8VideoMatroskaFileServerMediaSubsession
-::createNew(MatroskaFileServerDemux& demux, unsigned trackNumber) {
-  return new VP8VideoMatroskaFileServerMediaSubsession(demux, trackNumber);
-}
-
-VP8VideoMatroskaFileServerMediaSubsession
-::VP8VideoMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber)
-  : FileServerMediaSubsession(demux.envir(), demux.fileName(), False),
-    fOurDemux(demux), fTrackNumber(trackNumber) {
-}
-
-VP8VideoMatroskaFileServerMediaSubsession
-::~VP8VideoMatroskaFileServerMediaSubsession() {
-}
-
-float VP8VideoMatroskaFileServerMediaSubsession::duration() const { return fOurDemux.fileDuration(); }
-
-void VP8VideoMatroskaFileServerMediaSubsession
-::seekStreamSource(FramedSource* inputSource, double& seekNPT, double /*streamDuration*/, u_int64_t& /*numBytes*/) {
-  ((MatroskaDemuxedTrack*)inputSource)->seekToTime(seekNPT);
-}
-
-FramedSource* VP8VideoMatroskaFileServerMediaSubsession
-::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
-  estBitrate = 500; // kbps, estimate
-
-  return fOurDemux.newDemuxedTrack(clientSessionId, fTrackNumber);
-}
-
-RTPSink* VP8VideoMatroskaFileServerMediaSubsession
-::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) {
-  return VP8VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
-}
diff --git a/liveMedia/VP8VideoRTPSink.cpp b/liveMedia/VP8VideoRTPSink.cpp
index 30d7bf0..fb32e08 100644
--- a/liveMedia/VP8VideoRTPSink.cpp
+++ b/liveMedia/VP8VideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for VP8 video
 // Implementation
 
diff --git a/liveMedia/VP8VideoRTPSource.cpp b/liveMedia/VP8VideoRTPSource.cpp
index 6e31e67..19f2218 100644
--- a/liveMedia/VP8VideoRTPSource.cpp
+++ b/liveMedia/VP8VideoRTPSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // VP8 Video RTP Sources
 // Implementation
 
@@ -38,6 +38,8 @@ VP8VideoRTPSource
 VP8VideoRTPSource::~VP8VideoRTPSource() {
 }
 
+#define incrHeader do { ++resultSpecialHeaderSize; ++headerStart; if (--packetSize == 0) return False; } while (0)
+
 Boolean VP8VideoRTPSource
 ::processSpecialHeader(BufferedPacket* packet,
                        unsigned& resultSpecialHeaderSize) {
@@ -48,7 +50,7 @@ Boolean VP8VideoRTPSource
   if (packetSize == 0) return False; // error
   resultSpecialHeaderSize = 1; // unless we learn otherwise
 
-  u_int8_t const byte1 = headerStart[0];
+  u_int8_t const byte1 = *headerStart;
   Boolean const X = (byte1&0x80) != 0;
   Boolean const S = (byte1&0x10) != 0;
   u_int8_t const PartID = byte1&0x0F;
@@ -57,23 +59,23 @@ Boolean VP8VideoRTPSource
   fCurrentPacketCompletesFrame = packet->rtpMarkerBit(); // RTP header's "M" bit
 
   if (X) {
-    ++resultSpecialHeaderSize;
+    incrHeader;
 
-    u_int8_t const byte2 = headerStart[1];
+    u_int8_t const byte2 = *headerStart;
     Boolean const I = (byte2&0x80) != 0;
     Boolean const L = (byte2&0x40) != 0;
     Boolean const T = (byte2&0x20) != 0;
     Boolean const K = (byte2&0x10) != 0;
 
     if (I) {
-      ++resultSpecialHeaderSize;
-      if (headerStart[2]&0x80) { // extension flag in the PictureID is set
-	++resultSpecialHeaderSize;
+      incrHeader;
+      if ((*headerStart)&0x80) { // extension flag in the PictureID is set
+	incrHeader;
       }
     }
 
-    if (L) ++resultSpecialHeaderSize;
-    if (T||K) ++resultSpecialHeaderSize;
+    if (L) incrHeader;
+    if (T||K) incrHeader;
   }
   
   return True;
diff --git a/liveMedia/VP8VideoRTPSink.cpp b/liveMedia/VP9VideoRTPSink.cpp
similarity index 60%
copy from liveMedia/VP8VideoRTPSink.cpp
copy to liveMedia/VP9VideoRTPSink.cpp
index 30d7bf0..7d37eee 100644
--- a/liveMedia/VP8VideoRTPSink.cpp
+++ b/liveMedia/VP9VideoRTPSink.cpp
@@ -14,55 +14,58 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// RTP sink for VP8 video
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// RTP sink for VP9 video
 // Implementation
 
-#include "VP8VideoRTPSink.hh"
+#include "VP9VideoRTPSink.hh"
 
-VP8VideoRTPSink
-::VP8VideoRTPSink(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat)
-  : VideoRTPSink(env, RTPgs, rtpPayloadFormat, 90000, "VP8") {
+VP9VideoRTPSink
+::VP9VideoRTPSink(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat)
+  : VideoRTPSink(env, RTPgs, rtpPayloadFormat, 90000, "VP9") {
 }
 
-VP8VideoRTPSink::~VP8VideoRTPSink() {
+VP9VideoRTPSink::~VP9VideoRTPSink() {
 }
 
-VP8VideoRTPSink*
-VP8VideoRTPSink::createNew(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat) {
-  return new VP8VideoRTPSink(env, RTPgs, rtpPayloadFormat);
+VP9VideoRTPSink*
+VP9VideoRTPSink::createNew(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat) {
+  return new VP9VideoRTPSink(env, RTPgs, rtpPayloadFormat);
 }
 
-Boolean VP8VideoRTPSink
+Boolean VP9VideoRTPSink
 ::frameCanAppearAfterPacketStart(unsigned char const* /*frameStart*/,
 				 unsigned /*numBytesInFrame*/) const {
   // A packet can contain only one frame
   return False;
 }
 
-void VP8VideoRTPSink
+void VP9VideoRTPSink
 ::doSpecialFrameHandling(unsigned fragmentationOffset,
 			 unsigned char* /*frameStart*/,
 			 unsigned /*numBytesInFrame*/,
 			 struct timeval framePresentationTime,
 			 unsigned numRemainingBytes) {
-  // Set the "VP8 Payload Descriptor" (just the minimal required 1-byte version):
-  u_int8_t vp8PayloadDescriptor = fragmentationOffset == 0 ? 0x10 : 0x00;
-    // X = R = N = 0; PartID = 0; S = 1 iff this is the first (or only) fragment of the frame
-  setSpecialHeaderBytes(&vp8PayloadDescriptor, 1);
+  // Set the "VP9 Payload Descriptor" (just the minimal required 1-byte version):
+  u_int8_t vp9PayloadDescriptor = fragmentationOffset == 0 ? 0x10 : 0x00;
+    // I = L = F = V = U = 0; S = 1 iff this is the first (or only) fragment of the frame
 
   if (numRemainingBytes == 0) {
     // This packet contains the last (or only) fragment of the frame.
-    // Set the RTP 'M' ('marker') bit:
+    // Set the E bit:
+    vp9PayloadDescriptor |= 0x08;
+    // Also set the RTP 'M' ('marker') bit:
     setMarkerBit();
   }
 
+  setSpecialHeaderBytes(&vp9PayloadDescriptor, 1);
+
   // Also set the RTP timestamp:
   setTimestamp(framePresentationTime);
 }
 
 
-unsigned VP8VideoRTPSink::specialHeaderSize() const {
-  // We include only the required 1-byte form of the "VP8 Payload Descriptor":
+unsigned VP9VideoRTPSink::specialHeaderSize() const {
+  // We include only the required 1-byte form of the "VP9 Payload Descriptor":
   return 1;
 }
diff --git a/liveMedia/VP9VideoRTPSource.cpp b/liveMedia/VP9VideoRTPSource.cpp
new file mode 100644
index 0000000..39bee29
--- /dev/null
+++ b/liveMedia/VP9VideoRTPSource.cpp
@@ -0,0 +1,108 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// VP9 Video RTP Sources
+// Implementation
+
+#include "VP9VideoRTPSource.hh"
+
+VP9VideoRTPSource*
+VP9VideoRTPSource::createNew(UsageEnvironment& env, Groupsock* RTPgs,
+			      unsigned char rtpPayloadFormat,
+			      unsigned rtpTimestampFrequency) {
+  return new VP9VideoRTPSource(env, RTPgs, rtpPayloadFormat,
+			       rtpTimestampFrequency);
+}
+
+VP9VideoRTPSource
+::VP9VideoRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
+		     unsigned char rtpPayloadFormat,
+		     unsigned rtpTimestampFrequency)
+  : MultiFramedRTPSource(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency) {
+}
+
+VP9VideoRTPSource::~VP9VideoRTPSource() {
+}
+
+#define incrHeader do { ++resultSpecialHeaderSize; ++headerStart; if (--packetSize == 0) return False; } while (0)
+
+Boolean VP9VideoRTPSource
+::processSpecialHeader(BufferedPacket* packet,
+                       unsigned& resultSpecialHeaderSize) {
+  unsigned char* headerStart = packet->data();
+  unsigned packetSize = packet->dataSize();
+
+  // Figure out the size of the special header.
+  if (packetSize == 0) return False; // error
+  resultSpecialHeaderSize = 1; // unless we learn otherwise
+
+  u_int8_t const byte1 = *headerStart;
+  Boolean const I = (byte1&0x80) != 0;
+  Boolean const L = (byte1&0x40) != 0;
+  Boolean const F = (byte1&0x20) != 0;
+  Boolean const B = (byte1&0x10) != 0;
+  Boolean const E = (byte1&0x08) != 0;
+  Boolean const V = (byte1&0x04) != 0;
+  Boolean const U = (byte1&0x02) != 0;
+
+  fCurrentPacketBeginsFrame = B;
+  fCurrentPacketCompletesFrame = E;
+      // use this instead of the RTP header's 'M' bit (which might not be accurate)
+
+  if (I) { // PictureID present
+    incrHeader;
+    Boolean const M = ((*headerStart)&0x80) != 0;
+    if (M) incrHeader;
+  }
+
+  if (L) { // Layer indices present
+    incrHeader;
+    if (F) { // Reference indices present
+      incrHeader;
+      unsigned R = (*headerStart)&0x03;
+      while (R-- > 0) {
+	incrHeader;
+	Boolean const X = ((*headerStart)&0x10) != 0;
+	if (X) incrHeader;
+      }
+    }
+  }
+
+  if (V) { // Scalability Structure (SS) present
+    incrHeader;
+    unsigned patternLength = *headerStart;
+    while (patternLength-- > 0) {
+      incrHeader;
+      unsigned R = (*headerStart)&0x03;
+      while (R-- > 0) {
+	incrHeader;
+	Boolean const X = ((*headerStart)&0x10) != 0;
+	if (X) incrHeader;
+      }
+    }
+  }
+
+  if (U) { // Scalability Structure Update (SU) present
+    return False; // This structure isn't yet defined in the VP9 payload format I-D
+  }
+  
+  return True;
+}
+
+char const* VP9VideoRTPSource::MIMEtype() const {
+  return "video/VP9";
+}
diff --git a/liveMedia/VideoRTPSink.cpp b/liveMedia/VideoRTPSink.cpp
index 82d5a10..818da19 100644
--- a/liveMedia/VideoRTPSink.cpp
+++ b/liveMedia/VideoRTPSink.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic RTP sink for video codecs (abstract base class)
 // Implementation
 
diff --git a/liveMedia/VorbisAudioMatroskaFileServerMediaSubsession.cpp b/liveMedia/VorbisAudioMatroskaFileServerMediaSubsession.cpp
deleted file mode 100644
index 1913d5b..0000000
--- a/liveMedia/VorbisAudioMatroskaFileServerMediaSubsession.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from a Vorbis audio track within a Matroska file.
-// Implementation
-
-#include "VorbisAudioMatroskaFileServerMediaSubsession.hh"
-#include "VorbisAudioRTPSink.hh"
-#include "MatroskaDemuxedTrack.hh"
-
-VorbisAudioMatroskaFileServerMediaSubsession* VorbisAudioMatroskaFileServerMediaSubsession
-::createNew(MatroskaFileServerDemux& demux, unsigned trackNumber) {
-  return new VorbisAudioMatroskaFileServerMediaSubsession(demux, trackNumber);
-}
-
-#define getPrivByte(b) if (n == 0) break; else do {b = *p++; --n;} while (0)
-
-VorbisAudioMatroskaFileServerMediaSubsession
-::VorbisAudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber)
-  : FileServerMediaSubsession(demux.envir(), demux.fileName(), False),
-    fOurDemux(demux), fTrackNumber(trackNumber),
-    fIdentificationHeader(NULL), fIdentificationHeaderSize(0),
-    fCommentHeader(NULL), fCommentHeaderSize(0),
-    fSetupHeader(NULL), fSetupHeaderSize(0),
-    fEstBitrate(96/* kbps, default guess */) {
-  MatroskaTrack* track = fOurDemux.lookup(fTrackNumber);
-
-  // The Matroska file's 'Codec Private' data is assumed to be the Vorbis configuration information,
-  // containing the "Identification", "Comment", and "Setup" headers.  Extract these headers now:
-  do {
-    u_int8_t* p = track->codecPrivate;
-    unsigned n = track->codecPrivateSize;
-    if (n == 0 || p == NULL) break; // we have no 'Codec Private' data
-
-    u_int8_t numHeaders;
-    getPrivByte(numHeaders);
-    unsigned headerSize[3]; // we don't handle any more than 2+1 headers
-
-    // Extract the sizes of each of these headers:
-    unsigned sizesSum = 0;
-    Boolean success = True;
-    unsigned i;
-    for (i = 0; i < numHeaders && i < 3; ++i) {
-      unsigned len = 0;
-      u_int8_t c;
-
-      do {
-	success = False;
-	getPrivByte(c);
-	success = True;
-
-	len += c;
-      } while (c == 255);
-      if (!success || len == 0) break;
-
-      headerSize[i] = len;
-      sizesSum += len;
-    }
-    if (!success) break;
-
-    // Compute the implicit size of the final header:
-    if (numHeaders < 3) {
-      int finalHeaderSize = n - sizesSum;
-      if (finalHeaderSize <= 0) break; // error in data; give up
-
-      headerSize[numHeaders] = (unsigned)finalHeaderSize;
-      ++numHeaders; // include the final header now
-    } else {
-      numHeaders = 3; // The maximum number of headers that we handle
-    }
-
-    // Then, extract and classify each header:
-    for (i = 0; i < numHeaders; ++i) {
-      success = False;
-      unsigned newHeaderSize = headerSize[i];
-      u_int8_t* newHeader = new u_int8_t[newHeaderSize];
-      if (newHeader == NULL) break;
-      
-      u_int8_t* hdr = newHeader;
-      while (newHeaderSize-- > 0) {
-	success = False;
-	getPrivByte(*hdr++);
-	success = True;
-      }
-      if (!success) {
-	delete[] newHeader;
-	break;
-      }
-
-      u_int8_t headerType = newHeader[0];
-      if (headerType == 1) {
-	delete[] fIdentificationHeader; fIdentificationHeader = newHeader;
-	fIdentificationHeaderSize = headerSize[i];
-
-	if (fIdentificationHeaderSize >= 28) {
-	  // Get the 'bitrate' values from this header, and use them to set "fEstBitrate":
-	  u_int32_t val;
-	  u_int8_t* p;
-
-	  p = &fIdentificationHeader[16];
-	  val = ((p[3]*256 + p[2])*256 + p[1])*256 + p[0]; // i.e., little-endian
-	  int bitrate_maximum = (int)val;
-	  if (bitrate_maximum < 0) bitrate_maximum = 0;
-
-	  p = &fIdentificationHeader[20];
-	  val = ((p[3]*256 + p[2])*256 + p[1])*256 + p[0]; // i.e., little-endian
-	  int bitrate_nominal = (int)val;
-	  if (bitrate_nominal < 0) bitrate_nominal = 0;
-
-	  p = &fIdentificationHeader[24];
-	  val = ((p[3]*256 + p[2])*256 + p[1])*256 + p[0]; // i.e., little-endian
-	  int bitrate_minimum = (int)val;
-	  if (bitrate_minimum < 0) bitrate_minimum = 0;
-
-	  int bitrate
-	    = bitrate_nominal>0 ? bitrate_nominal : bitrate_maximum>0 ? bitrate_maximum : bitrate_minimum>0 ? bitrate_minimum : 0;
-	  if (bitrate > 0) fEstBitrate = ((unsigned)bitrate)/1000;
-	}
-      } else if (headerType == 3) {
-	delete[] fCommentHeader; fCommentHeader = newHeader;
-	fCommentHeaderSize = headerSize[i];
-      } else if (headerType == 5) {
-	delete[] fSetupHeader; fSetupHeader = newHeader;
-	fSetupHeaderSize = headerSize[i];
-      } else {
-	delete[] newHeader; // because it was a header type that we don't understand
-      }
-    }
-    if (!success) break;
-  } while (0);
-}
-
-VorbisAudioMatroskaFileServerMediaSubsession
-::~VorbisAudioMatroskaFileServerMediaSubsession() {
-  delete[] fIdentificationHeader;
-  delete[] fCommentHeader;
-  delete[] fSetupHeader;
-}
-
-float VorbisAudioMatroskaFileServerMediaSubsession::duration() const { return fOurDemux.fileDuration(); }
-
-void VorbisAudioMatroskaFileServerMediaSubsession
-::seekStreamSource(FramedSource* inputSource, double& seekNPT, double /*streamDuration*/, u_int64_t& /*numBytes*/) {
-  ((MatroskaDemuxedTrack*)inputSource)->seekToTime(seekNPT);
-}
-
-FramedSource* VorbisAudioMatroskaFileServerMediaSubsession
-::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
-  estBitrate = fEstBitrate; // kbps, estimate
-
-  return fOurDemux.newDemuxedTrack(clientSessionId, fTrackNumber);
-}
-
-RTPSink* VorbisAudioMatroskaFileServerMediaSubsession
-::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) {
-  MatroskaTrack* track = fOurDemux.lookup(fTrackNumber);
-  return VorbisAudioRTPSink::createNew(envir(), rtpGroupsock,
-				       rtpPayloadTypeIfDynamic, track->samplingFrequency, track->numChannels,
-				       fIdentificationHeader, fIdentificationHeaderSize,
-				       fCommentHeader, fCommentHeaderSize,
-				       fSetupHeader, fSetupHeaderSize);
-}
diff --git a/liveMedia/VorbisAudioMatroskaFileServerMediaSubsession.hh b/liveMedia/VorbisAudioMatroskaFileServerMediaSubsession.hh
deleted file mode 100644
index b53e55e..0000000
--- a/liveMedia/VorbisAudioMatroskaFileServerMediaSubsession.hh
+++ /dev/null
@@ -1,60 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
-// on demand, from a Vorbis audio track within a Matroska file.
-// C++ header
-
-#ifndef _VORBIS_AUDIO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-#define _VORBIS_AUDIO_MATROSKA_FILE_SERVER_MEDIA_SUBSESSION_HH
-
-#ifndef _FILE_SERVER_MEDIA_SUBSESSION_HH
-#include "FileServerMediaSubsession.hh"
-#endif
-#ifndef _MATROSKA_FILE_SERVER_DEMUX_HH
-#include "MatroskaFileServerDemux.hh"
-#endif
-
-class VorbisAudioMatroskaFileServerMediaSubsession: public FileServerMediaSubsession {
-public:
-  static VorbisAudioMatroskaFileServerMediaSubsession*
-  createNew(MatroskaFileServerDemux& demux, unsigned trackNumber);
-
-private:
-  VorbisAudioMatroskaFileServerMediaSubsession(MatroskaFileServerDemux& demux, unsigned trackNumber);
-      // called only by createNew();
-  virtual ~VorbisAudioMatroskaFileServerMediaSubsession();
-
-private: // redefined virtual functions
-  virtual float duration() const;
-  virtual void seekStreamSource(FramedSource* inputSource, double& seekNPT, double streamDuration, u_int64_t& numBytes);
-  virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
-					      unsigned& estBitrate);
-  virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
-
-private:
-  MatroskaFileServerDemux& fOurDemux;
-  unsigned fTrackNumber;
-
-  u_int8_t* fIdentificationHeader; unsigned fIdentificationHeaderSize;
-  u_int8_t* fCommentHeader; unsigned fCommentHeaderSize;
-  u_int8_t* fSetupHeader; unsigned fSetupHeaderSize;
-
-  unsigned fEstBitrate; // in kbps
-};
-
-#endif
diff --git a/liveMedia/VorbisAudioRTPSink.cpp b/liveMedia/VorbisAudioRTPSink.cpp
index 8fd72b8..9ee5261 100644
--- a/liveMedia/VorbisAudioRTPSink.cpp
+++ b/liveMedia/VorbisAudioRTPSink.cpp
@@ -14,94 +14,101 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for Vorbis audio
 // Implementation
 
 #include "VorbisAudioRTPSink.hh"
 #include "Base64.hh"
+#include "VorbisAudioRTPSource.hh" // for parseVorbisOrTheoraConfigStr()
+
+VorbisAudioRTPSink* VorbisAudioRTPSink
+::createNew(UsageEnvironment& env, Groupsock* RTPgs,
+	    u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency, unsigned numChannels,
+	    u_int8_t* identificationHeader, unsigned identificationHeaderSize,
+	    u_int8_t* commentHeader, unsigned commentHeaderSize,
+	    u_int8_t* setupHeader, unsigned setupHeaderSize,
+	    u_int32_t identField) {
+  return new VorbisAudioRTPSink(env, RTPgs,
+				rtpPayloadFormat, rtpTimestampFrequency, numChannels,
+				identificationHeader, identificationHeaderSize,
+				commentHeader, commentHeaderSize,
+				setupHeader, setupHeaderSize,
+				identField);
+}
 
-VorbisAudioRTPSink::VorbisAudioRTPSink(UsageEnvironment& env, Groupsock* RTPgs,
-				       u_int8_t rtpPayloadFormat,
-				       u_int32_t rtpTimestampFrequency,
-				       unsigned numChannels,
-				       u_int8_t* identificationHeader, unsigned identificationHeaderSize,
-				       u_int8_t* commentHeader, unsigned commentHeaderSize,
-				       u_int8_t* setupHeader, unsigned setupHeaderSize,
-				       u_int32_t identField)
-  : AudioRTPSink(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency, "VORBIS", numChannels),
-    fIdent(identField), fFmtpSDPLine(NULL) {
-  // Create packed configuration headers, and encode this data into a "a=fmtp:" SDP line that we'll use to describe it:
-  
-  // First, count how many headers (<=3) are included, and how many bytes will be used to encode these headers' sizes:
-  unsigned numHeaders = 0;
-  unsigned sizeSize[2]; // The number of bytes used to encode the lengths of the first two headers (but not the length of the 3rd)
-  sizeSize[0] = sizeSize[1] = 0;
-  if (identificationHeaderSize > 0) {
-    sizeSize[numHeaders++] = identificationHeaderSize < 128 ? 1 : identificationHeaderSize < 16384 ? 2 : 3;
-  }
-  if (commentHeaderSize > 0) {
-    sizeSize[numHeaders++] = commentHeaderSize < 128 ? 1 : commentHeaderSize < 16384 ? 2 : 3;
-  }
-  if (setupHeaderSize > 0) {
-    ++numHeaders;
-  } else {
-    sizeSize[1] = 0; // We have at most two headers, so the second one's length isn't encoded
-  }
-  if (numHeaders == 0) return; // With no headers, we can't set up a configuration
-  if (numHeaders == 1) sizeSize[0] = 0; // With only one header, its length isn't encoded
-
-  // Then figure out the size of the packed configuration headers, and allocate space for this:
-  unsigned length = identificationHeaderSize + commentHeaderSize + setupHeaderSize; // The "length" field in the packed headers
-  if (length > (unsigned)0xFFFF) return; // too big for a 16-bit field; we can't handle this
-  unsigned packedHeadersSize
-    = 4 // "Number of packed headers" field
-    + 3 // "ident" field
-    + 2 // "length" field
-    + 1 // "n. of headers" field
-    + sizeSize[0] + sizeSize[1] // "length1" and "length2" (if present) fields
-    + length;
-  u_int8_t* packedHeaders = new u_int8_t[packedHeadersSize];
-  if (packedHeaders == NULL) return;
+VorbisAudioRTPSink* VorbisAudioRTPSink
+::createNew(UsageEnvironment& env, Groupsock* RTPgs,u_int8_t rtpPayloadFormat,
+	    u_int32_t rtpTimestampFrequency, unsigned numChannels,
+	    char const* configStr) {
+  // Begin by decoding and unpacking the configuration string:
+  u_int8_t* identificationHeader; unsigned identificationHeaderSize;
+  u_int8_t* commentHeader; unsigned commentHeaderSize;
+  u_int8_t* setupHeader; unsigned setupHeaderSize;
+  u_int32_t identField;
+
+  parseVorbisOrTheoraConfigStr(configStr,
+			       identificationHeader, identificationHeaderSize,
+			       commentHeader, commentHeaderSize,
+			       setupHeader, setupHeaderSize,
+			       identField);
+
+  VorbisAudioRTPSink* resultSink
+    = new VorbisAudioRTPSink(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency, numChannels,
+			     identificationHeader, identificationHeaderSize,
+			     commentHeader, commentHeaderSize,
+			     setupHeader, setupHeaderSize,
+			     identField);
+  delete[] identificationHeader; delete[] commentHeader; delete[] setupHeader;
 
-  // Fill in the 'packed headers':
-  u_int8_t* p = packedHeaders;
-  *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 1; // "Number of packed headers": 1
-  *p++ = fIdent>>16; *p++ = fIdent>>8; *p++ = fIdent; // "Ident" (24 bits)
-  *p++ = length>>8; *p++ = length; // "length" (16 bits)
-  *p++ = numHeaders-1; // "n. of headers"
-  if (numHeaders > 1) {
-    // Fill in the "length1" header:
-    unsigned length1 = identificationHeaderSize > 0 ? identificationHeaderSize : commentHeaderSize;
-    if (length1 >= 16384) {
-      *p++ = 0x80; // flag, but no more, because we know length1 <= 32767
-    }
-    if (length1 >= 128) {
-      *p++ = 0x80|((length1&0x3F80)>>7); // flag + the second 7 bits
-    }
-    *p++ = length1&0x7F; // the low 7 bits
+  return resultSink;
+}
 
-    if (numHeaders > 2) { // numHeaders == 3
-      // Fill in the "length2" header (for the 'Comment' header):
-      unsigned length2 = commentHeaderSize;
-      if (length2 >= 16384) {
-	*p++ = 0x80; // flag, but no more, because we know length2 <= 32767
-      }
-      if (length2 >= 128) {
-	*p++ = 0x80|((length2&0x3F80)>>7); // flag + the second 7 bits
-      }
-      *p++ = length2&0x7F; // the low 7 bits
-    }
+VorbisAudioRTPSink
+::VorbisAudioRTPSink(UsageEnvironment& env, Groupsock* RTPgs, u_int8_t rtpPayloadFormat,
+		     u_int32_t rtpTimestampFrequency, unsigned numChannels,
+		     u_int8_t* identificationHeader, unsigned identificationHeaderSize,
+		     u_int8_t* commentHeader, unsigned commentHeaderSize,
+		     u_int8_t* setupHeader, unsigned setupHeaderSize,
+		     u_int32_t identField)
+  : AudioRTPSink(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency, "VORBIS", numChannels),
+    fIdent(identField), fFmtpSDPLine(NULL) {
+  if (identificationHeaderSize >= 28) {
+    // Get the 'bitrate' values from this header, and use them to set our estimated bitrate:
+    u_int32_t val;
+    u_int8_t* p;
+    
+    p = &identificationHeader[16];
+    val = ((p[3]*256 + p[2])*256 + p[1])*256 + p[0]; // i.e., little-endian
+    int bitrate_maximum = (int)val;
+    if (bitrate_maximum < 0) bitrate_maximum = 0;
+    
+    p = &identificationHeader[20];
+    val = ((p[3]*256 + p[2])*256 + p[1])*256 + p[0]; // i.e., little-endian
+    int bitrate_nominal = (int)val;
+    if (bitrate_nominal < 0) bitrate_nominal = 0;
+    
+    p = &identificationHeader[24];
+    val = ((p[3]*256 + p[2])*256 + p[1])*256 + p[0]; // i.e., little-endian
+    int bitrate_minimum = (int)val;
+    if (bitrate_minimum < 0) bitrate_minimum = 0;
+    
+    int bitrate
+      = bitrate_nominal > 0 ? bitrate_nominal
+      : bitrate_maximum > 0 ? bitrate_maximum
+      : bitrate_minimum > 0 ? bitrate_minimum : 0;
+    if (bitrate > 0) estimatedBitrate() = ((unsigned)bitrate)/1000;
   }
-  // Copy each header:
-  if (identificationHeader != NULL) memmove(p, identificationHeader, identificationHeaderSize); p += identificationHeaderSize;
-  if (commentHeader != NULL) memmove(p, commentHeader, commentHeaderSize); p += commentHeaderSize;
-  if (setupHeader != NULL) memmove(p, setupHeader, setupHeaderSize);
   
-  // Having set up the 'packed configuration headers', Base-64-encode this, and put it in our "a=fmtp:" SDP line:
-  char* base64PackedHeaders = base64Encode((char const*)packedHeaders, packedHeadersSize);
-  delete[] packedHeaders;
+  // Generate a 'config' string from the supplied configuration headers:
+  char* base64PackedHeaders
+    = generateVorbisOrTheoraConfigStr(identificationHeader, identificationHeaderSize,
+				      commentHeader, commentHeaderSize,
+				      setupHeader, setupHeaderSize,
+				      identField);
+  if (base64PackedHeaders == NULL) return;
   
+  // Then use this 'config' string to construct our "a=fmtp:" SDP line:
   unsigned fmtpSDPLineMaxSize = 50 + strlen(base64PackedHeaders); // 50 => more than enough space
   fFmtpSDPLine = new char[fmtpSDPLineMaxSize];
   sprintf(fFmtpSDPLine, "a=fmtp:%d configuration=%s\r\n", rtpPayloadType(), base64PackedHeaders);
@@ -112,106 +119,6 @@ VorbisAudioRTPSink::~VorbisAudioRTPSink() {
   delete[] fFmtpSDPLine;
 }
 
-VorbisAudioRTPSink*
-VorbisAudioRTPSink::createNew(UsageEnvironment& env, Groupsock* RTPgs,
-			      u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency, unsigned numChannels,
-			      u_int8_t* identificationHeader, unsigned identificationHeaderSize,
-			      u_int8_t* commentHeader, unsigned commentHeaderSize,
-			      u_int8_t* setupHeader, unsigned setupHeaderSize) {
-  return new VorbisAudioRTPSink(env, RTPgs,
-				rtpPayloadFormat, rtpTimestampFrequency, numChannels,
-				identificationHeader, identificationHeaderSize,
-				commentHeader, commentHeaderSize,
-				setupHeader, setupHeaderSize);
-}
-
-#define ADVANCE(n) do { p += (n); rem -= (n); } while (0)
-#define GET_ENCODED_VAL(n) do { u_int8_t byte; n = 0; do { if (rem == 0) break; byte = *p; n = (n*128) + (byte&0x7F); ADVANCE(1); } while (byte&0x80); } while (0); if (rem == 0) break
-
-VorbisAudioRTPSink*
-VorbisAudioRTPSink::createNew(UsageEnvironment& env, Groupsock* RTPgs,
-			      u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency, unsigned numChannels,
-			      char const* configStr) {
-  u_int8_t* identificationHeader = NULL; unsigned identificationHeaderSize = 0;
-  u_int8_t* commentHeader = NULL; unsigned commentHeaderSize = 0;
-  u_int8_t* setupHeader = NULL; unsigned setupHeaderSize = 0;
-  VorbisAudioRTPSink* resultSink = NULL;
-
-  // Begin by Base64-decoding the configuration string:
-  unsigned configDataSize;
-  u_int8_t* configData = base64Decode(configStr, configDataSize);
-  u_int8_t* p = configData;
-  unsigned rem = configDataSize;
-
-  do {
-    if (rem < 4) break;
-    u_int32_t numPackedHeaders = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; ADVANCE(4);
-
-    if (numPackedHeaders == 0) break;
-    // Use the first 'packed header' only.
-
-    if (rem < 3) break;
-    u_int32_t ident = (p[0]<<16)|(p[1]<<8)|p[2]; ADVANCE(3);
-
-    if (rem < 2) break;
-    u_int16_t length = (p[0]<<8)|p[1]; ADVANCE(2);
-
-    unsigned numHeaders;
-    GET_ENCODED_VAL(numHeaders);
-
-    Boolean success = False;
-    for (unsigned i = 0; i < numHeaders+1 && i < 3; ++i) {
-      success = False;
-      unsigned headerSize;
-      if (i < numHeaders) {
-	// The header size is encoded:
-	GET_ENCODED_VAL(headerSize);
-	if (headerSize > length) break;
-	length -= headerSize;
-      } else {
-	// The last header is implicit:
-	headerSize = length;
-      }
-
-      // Allocate space for the header bytes; we'll fill it in later
-      if (i == 0) {
-	identificationHeaderSize = headerSize;
-	identificationHeader = new u_int8_t[identificationHeaderSize];
-      } else if (i == 1) {
-	commentHeaderSize = headerSize;
-	commentHeader = new u_int8_t[commentHeaderSize];
-      } else { // i == 2
-	setupHeaderSize = headerSize;
-	setupHeader = new u_int8_t[setupHeaderSize];
-      }
-
-      success = True;
-    }
-    if (!success) break;
-
-    // Copy the remaining config bytes into the appropriate 'header' buffers:
-    if (identificationHeader != NULL) {
-      memmove(identificationHeader, p, identificationHeaderSize); ADVANCE(identificationHeaderSize);
-      if (commentHeader != NULL) {
-	memmove(commentHeader, p, commentHeaderSize); ADVANCE(commentHeaderSize);
-	if (setupHeader != NULL) {
-	  memmove(setupHeader, p, setupHeaderSize); ADVANCE(setupHeaderSize);
-	}
-      }
-    }
-
-    resultSink = new VorbisAudioRTPSink(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency, numChannels,
-					identificationHeader, identificationHeaderSize,
-					commentHeader, commentHeaderSize,
-					setupHeader, setupHeaderSize,
-					ident);
-  } while (0);
-
-  delete[] configData;
-  delete[] identificationHeader; delete[] commentHeader; delete[] setupHeader;
-  return resultSink;
-}
-
 char const* VorbisAudioRTPSink::auxSDPLine() {
   return fFmtpSDPLine;
 }
@@ -225,7 +132,7 @@ void VorbisAudioRTPSink
   // Set the 4-byte "payload header", as defined in RFC 5215, section 2.2:
   u_int8_t header[4];
 
-  // The three bytes of the header are our "Ident":
+  // The first three bytes of the header are our "Ident":
   header[0] = fIdent>>16; header[1] = fIdent>>8; header[2] = fIdent;
 
   // The final byte contains the "F", "VDT", and "numPkts" fields:
@@ -276,3 +183,84 @@ unsigned VorbisAudioRTPSink::specialHeaderSize() const {
 unsigned VorbisAudioRTPSink::frameSpecificHeaderSize() const {
   return 2;
 }
+
+
+////////// generateVorbisOrTheoraConfigStr() implementation //////////
+
+char* generateVorbisOrTheoraConfigStr(u_int8_t* identificationHeader, unsigned identificationHeaderSize,
+                                      u_int8_t* commentHeader, unsigned commentHeaderSize,
+                                      u_int8_t* setupHeader, unsigned setupHeaderSize,
+                                      u_int32_t identField) {
+  // First, count how many headers (<=3) are included, and how many bytes will be used
+  // to encode these headers' sizes:
+  unsigned numHeaders = 0;
+  unsigned sizeSize[2]; // The number of bytes used to encode the lengths of the first two headers (but not the length of the 3rd)
+  sizeSize[0] = sizeSize[1] = 0;
+  if (identificationHeaderSize > 0) {
+    sizeSize[numHeaders++] = identificationHeaderSize < 128 ? 1 : identificationHeaderSize < 16384 ? 2 : 3;
+  }
+  if (commentHeaderSize > 0) {
+    sizeSize[numHeaders++] = commentHeaderSize < 128 ? 1 : commentHeaderSize < 16384 ? 2 : 3;
+  }
+  if (setupHeaderSize > 0) {
+    ++numHeaders;
+  } else {
+    sizeSize[1] = 0; // We have at most two headers, so the second one's length isn't encoded
+  }
+  if (numHeaders == 0) return NULL; // With no headers, we can't set up a configuration
+  if (numHeaders == 1) sizeSize[0] = 0; // With only one header, its length isn't encoded
+
+  // Then figure out the size of the packed configuration headers, and allocate space for this:
+  unsigned length = identificationHeaderSize + commentHeaderSize + setupHeaderSize;
+      // The "length" field in the packed headers
+  if (length > (unsigned)0xFFFF) return NULL; // too big for a 16-bit field; we can't handle this
+  unsigned packedHeadersSize
+    = 4 // "Number of packed headers" field
+    + 3 // "ident" field
+    + 2 // "length" field
+    + 1 // "n. of headers" field
+    + sizeSize[0] + sizeSize[1] // "length1" and "length2" (if present) fields
+    + length;
+  u_int8_t* packedHeaders = new u_int8_t[packedHeadersSize];
+  if (packedHeaders == NULL) return NULL;
+
+  // Fill in the 'packed headers':
+  u_int8_t* p = packedHeaders;
+  *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 1; // "Number of packed headers": 1
+  *p++ = identField>>16; *p++ = identField>>8; *p++ = identField; // "Ident" (24 bits)
+  *p++ = length>>8; *p++ = length; // "length" (16 bits)
+  *p++ = numHeaders-1; // "n. of headers"
+  if (numHeaders > 1) {
+    // Fill in the "length1" header:
+    unsigned length1 = identificationHeaderSize > 0 ? identificationHeaderSize : commentHeaderSize;
+    if (length1 >= 16384) {
+      *p++ = 0x80; // flag, but no more, because we know length1 <= 32767
+    }
+    if (length1 >= 128) {
+      *p++ = 0x80|((length1&0x3F80)>>7); // flag + the second 7 bits
+    }
+    *p++ = length1&0x7F; // the low 7 bits
+
+    if (numHeaders > 2) { // numHeaders == 3
+      // Fill in the "length2" header (for the 'Comment' header):
+      unsigned length2 = commentHeaderSize;
+      if (length2 >= 16384) {
+	*p++ = 0x80; // flag, but no more, because we know length2 <= 32767
+      }
+      if (length2 >= 128) {
+	*p++ = 0x80|((length2&0x3F80)>>7); // flag + the second 7 bits
+      }
+      *p++ = length2&0x7F; // the low 7 bits
+    }
+  }
+  // Copy each header:
+  if (identificationHeader != NULL) memmove(p, identificationHeader, identificationHeaderSize); p += identificationHeaderSize;
+  if (commentHeader != NULL) memmove(p, commentHeader, commentHeaderSize); p += commentHeaderSize;
+  if (setupHeader != NULL) memmove(p, setupHeader, setupHeaderSize);
+  
+  // Having set up the 'packed configuration headers', Base-64-encode this, for our result:
+  char* base64PackedHeaders = base64Encode((char const*)packedHeaders, packedHeadersSize);
+  delete[] packedHeaders;
+  
+  return base64PackedHeaders;
+}
diff --git a/liveMedia/VorbisAudioRTPSource.cpp b/liveMedia/VorbisAudioRTPSource.cpp
index 7ba026a..af488f2 100644
--- a/liveMedia/VorbisAudioRTPSource.cpp
+++ b/liveMedia/VorbisAudioRTPSource.cpp
@@ -14,11 +14,12 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Vorbis Audio RTP Sources
 // Implementation
 
 #include "VorbisAudioRTPSource.hh"
+#include "Base64.hh"
 
 ////////// VorbisBufferedPacket and VorbisBufferedPacketFactory //////////
 
@@ -38,7 +39,7 @@ private: // redefined virtual functions
 };
 
 
-///////// MPEG4VorbisAudioRTPSource implementation ////////
+///////// VorbisAudioRTPSource implementation ////////
 
 VorbisAudioRTPSource*
 VorbisAudioRTPSource::createNew(UsageEnvironment& env, Groupsock* RTPgs,
@@ -113,3 +114,84 @@ BufferedPacket* VorbisBufferedPacketFactory
 ::createNewPacket(MultiFramedRTPSource* /*ourSource*/) {
   return new VorbisBufferedPacket();
 }
+
+
+////////// parseVorbisOrTheoraConfigStr() implementation //////////
+
+#define ADVANCE(n) do { p += (n); rem -= (n); } while (0)
+#define GET_ENCODED_VAL(n) do { u_int8_t byte; n = 0; do { if (rem == 0) break; byte = *p; n = (n*128) + (byte&0x7F); ADVANCE(1); } while (byte&0x80); } while (0); if (rem == 0) break
+
+void parseVorbisOrTheoraConfigStr(char const* configStr,
+                                  u_int8_t*& identificationHdr, unsigned& identificationHdrSize,
+                                  u_int8_t*& commentHdr, unsigned& commentHdrSize,
+                                  u_int8_t*& setupHdr, unsigned& setupHdrSize,
+				  u_int32_t& identField) {
+  identificationHdr = commentHdr = setupHdr = NULL; // default values, if an error occur
+  identificationHdrSize = commentHdrSize = setupHdrSize = 0; // ditto
+  identField = 0; // ditto
+
+  // Begin by Base64-decoding the configuration string:
+  unsigned configDataSize;
+  u_int8_t* configData = base64Decode(configStr, configDataSize);
+  u_int8_t* p = configData;
+  unsigned rem = configDataSize;
+
+  do {
+    if (rem < 4) break;
+    u_int32_t numPackedHeaders = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; ADVANCE(4);
+    if (numPackedHeaders == 0) break;
+
+    // Use the first 'packed header' only.
+    if (rem < 3) break;
+    identField = (p[0]<<16)|(p[1]<<8)|p[2]; ADVANCE(3);
+
+    if (rem < 2) break;
+    u_int16_t length = (p[0]<<8)|p[1]; ADVANCE(2);
+
+    unsigned numHeaders;
+    GET_ENCODED_VAL(numHeaders);
+
+    Boolean success = False;
+    for (unsigned i = 0; i < numHeaders+1 && i < 3; ++i) {
+      success = False;
+      unsigned headerSize;
+      if (i < numHeaders) {
+        // The header size is encoded:
+	GET_ENCODED_VAL(headerSize);
+        if (headerSize > length) break;
+        length -= headerSize;
+      } else {
+	// The last header is implicit:
+        headerSize = length;
+      }
+
+      // Allocate space for the header bytes; we'll fill it in later
+      if (i == 0) {
+	identificationHdrSize = headerSize;
+        identificationHdr = new u_int8_t[identificationHdrSize];
+      } else if (i == 1) {
+        commentHdrSize = headerSize;
+	commentHdr = new u_int8_t[commentHdrSize];
+      } else { // i == 2
+        setupHdrSize = headerSize;
+        setupHdr = new u_int8_t[setupHdrSize];
+      }
+
+      success = True;
+    }
+    if (!success) break;
+
+    // Copy the remaining config bytes into the appropriate 'header' buffers:
+    if (identificationHdr != NULL) {
+      memmove(identificationHdr, p, identificationHdrSize); ADVANCE(identificationHdrSize);
+      if (commentHdr != NULL) {
+        memmove(commentHdr, p, commentHdrSize); ADVANCE(commentHdrSize);
+	if (setupHdr != NULL) {
+          memmove(setupHdr, p, setupHdrSize); ADVANCE(setupHdrSize);
+        }
+      }
+    }
+  } while (0);
+
+  delete[] configData;
+}
diff --git a/liveMedia/WAVAudioFileServerMediaSubsession.cpp b/liveMedia/WAVAudioFileServerMediaSubsession.cpp
index 15bb457..314de94 100644
--- a/liveMedia/WAVAudioFileServerMediaSubsession.cpp
+++ b/liveMedia/WAVAudioFileServerMediaSubsession.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an WAV audio file.
 // Implementation
@@ -56,11 +56,27 @@ void WAVAudioFileServerMediaSubsession
   unsigned seekSampleNumber = (unsigned)(seekNPT*fSamplingFrequency);
   unsigned seekByteNumber = seekSampleNumber*((fNumChannels*fBitsPerSample)/8);
 
+  wavSource->seekToPCMByte(seekByteNumber);
+
+  setStreamSourceDuration(inputSource, streamDuration, numBytes);
+}
+
+void WAVAudioFileServerMediaSubsession
+::setStreamSourceDuration(FramedSource* inputSource, double streamDuration, u_int64_t& numBytes) {
+  WAVAudioFileSource* wavSource;
+  if (fBitsPerSample > 8) {
+    // "inputSource" is a filter; its input source is the original WAV file source:
+    wavSource = (WAVAudioFileSource*)(((FramedFilter*)inputSource)->inputSource());
+  } else {
+    // "inputSource" is the original WAV file source:
+    wavSource = (WAVAudioFileSource*)inputSource;
+  }
+
   unsigned numDurationSamples = (unsigned)(streamDuration*fSamplingFrequency);
   unsigned numDurationBytes = numDurationSamples*((fNumChannels*fBitsPerSample)/8);
   numBytes = (u_int64_t)numDurationBytes;
 
-  wavSource->seekToPCMByte(seekByteNumber, numDurationBytes);
+  wavSource->limitNumBytesToStream(numDurationBytes);
 }
 
 void WAVAudioFileServerMediaSubsession
diff --git a/liveMedia/WAVAudioFileSource.cpp b/liveMedia/WAVAudioFileSource.cpp
index 671d7b4..b60a488 100644
--- a/liveMedia/WAVAudioFileSource.cpp
+++ b/liveMedia/WAVAudioFileSource.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A WAV audio file source
 // Implementation
 
@@ -65,12 +65,14 @@ void WAVAudioFileSource::setScaleFactor(int scale) {
   }
 }
 
-void WAVAudioFileSource::seekToPCMByte(unsigned byteNumber, unsigned numBytesToStream) {
+void WAVAudioFileSource::seekToPCMByte(unsigned byteNumber) {
   byteNumber += fWAVHeaderSize;
   if (byteNumber > fFileSize) byteNumber = fFileSize;
 
   SeekFile64(fFid, byteNumber, SEEK_SET);
+}
 
+void WAVAudioFileSource::limitNumBytesToStream(unsigned numBytesToStream) {
   fNumBytesToStream = numBytesToStream;
   fLimitNumBytesToStream = fNumBytesToStream > 0;
 }
@@ -222,7 +224,7 @@ WAVAudioFileSource::~WAVAudioFileSource() {
 
 void WAVAudioFileSource::doGetNextFrame() {
   if (feof(fFid) || ferror(fFid) || (fLimitNumBytesToStream && fNumBytesToStream == 0)) {
-    handleClosure(this);
+    handleClosure();
     return;
   }
 
@@ -240,6 +242,7 @@ void WAVAudioFileSource::doGetNextFrame() {
 }
 
 void WAVAudioFileSource::doStopGettingFrames() {
+  envir().taskScheduler().unscheduleDelayedTask(nextTask());
 #ifndef READ_FROM_FILES_SYNCHRONOUSLY
   envir().taskScheduler().turnOffBackgroundReadHandling(fileno(fFid));
   fHaveStartedReading = False;
@@ -280,7 +283,7 @@ void WAVAudioFileSource::doReadFromFile() {
     }
 #endif
     if (numBytesRead == 0) {
-     handleClosure(this);
+     handleClosure();
       return;
     }
     fFrameSize += numBytesRead;
diff --git a/liveMedia/include/AC3AudioFileServerMediaSubsession.hh b/liveMedia/include/AC3AudioFileServerMediaSubsession.hh
index ecb0b0e..deab94c 100644
--- a/liveMedia/include/AC3AudioFileServerMediaSubsession.hh
+++ b/liveMedia/include/AC3AudioFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an AC3 audio file.
 // C++ header
diff --git a/liveMedia/include/AC3AudioRTPSink.hh b/liveMedia/include/AC3AudioRTPSink.hh
index ba2ba5a..8e5cf92 100644
--- a/liveMedia/include/AC3AudioRTPSink.hh
+++ b/liveMedia/include/AC3AudioRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for AC3 audio
 // C++ header
 
diff --git a/liveMedia/include/AC3AudioRTPSource.hh b/liveMedia/include/AC3AudioRTPSource.hh
index 4525094..c89bf5e 100644
--- a/liveMedia/include/AC3AudioRTPSource.hh
+++ b/liveMedia/include/AC3AudioRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // AC3 Audio RTP Sources
 // C++ header
 
diff --git a/liveMedia/include/AC3AudioStreamFramer.hh b/liveMedia/include/AC3AudioStreamFramer.hh
index 9b87797..d8954d1 100644
--- a/liveMedia/include/AC3AudioStreamFramer.hh
+++ b/liveMedia/include/AC3AudioStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an AC3 audio elementary stream into frames
 // C++ header
 
diff --git a/liveMedia/include/ADTSAudioFileServerMediaSubsession.hh b/liveMedia/include/ADTSAudioFileServerMediaSubsession.hh
index 68f136d..efaf5af 100644
--- a/liveMedia/include/ADTSAudioFileServerMediaSubsession.hh
+++ b/liveMedia/include/ADTSAudioFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an AAC audio file in ADTS format
 // C++ header
diff --git a/liveMedia/include/ADTSAudioFileSource.hh b/liveMedia/include/ADTSAudioFileSource.hh
index c49876a..59c86d8 100644
--- a/liveMedia/include/ADTSAudioFileSource.hh
+++ b/liveMedia/include/ADTSAudioFileSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A source object for AAC audio files in ADTS format
 // C++ header
 
diff --git a/liveMedia/include/AMRAudioFileServerMediaSubsession.hh b/liveMedia/include/AMRAudioFileServerMediaSubsession.hh
index 9a6fc5d..14ab235 100644
--- a/liveMedia/include/AMRAudioFileServerMediaSubsession.hh
+++ b/liveMedia/include/AMRAudioFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an AMR audio file.
 // C++ header
diff --git a/liveMedia/include/AMRAudioFileSink.hh b/liveMedia/include/AMRAudioFileSink.hh
index 6d24417..4efffc9 100644
--- a/liveMedia/include/AMRAudioFileSink.hh
+++ b/liveMedia/include/AMRAudioFileSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // AMR Audio File Sinks
 // C++ header
 
diff --git a/liveMedia/include/AMRAudioFileSource.hh b/liveMedia/include/AMRAudioFileSource.hh
index 468ace2..9dd1bf7 100644
--- a/liveMedia/include/AMRAudioFileSource.hh
+++ b/liveMedia/include/AMRAudioFileSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A source object for AMR audio files (as defined in RFC 4867, section 5)
 // C++ header
 
diff --git a/liveMedia/include/AMRAudioRTPSink.hh b/liveMedia/include/AMRAudioRTPSink.hh
index 82ea9b3..c868d5b 100644
--- a/liveMedia/include/AMRAudioRTPSink.hh
+++ b/liveMedia/include/AMRAudioRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for AMR audio (RFC 4867)
 // C++ header
 
diff --git a/liveMedia/include/AMRAudioRTPSource.hh b/liveMedia/include/AMRAudioRTPSource.hh
index dd3868e..327445e 100644
--- a/liveMedia/include/AMRAudioRTPSource.hh
+++ b/liveMedia/include/AMRAudioRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // AMR Audio RTP Sources (RFC 4867)
 // C++ header
 
diff --git a/liveMedia/include/AMRAudioSource.hh b/liveMedia/include/AMRAudioSource.hh
index f7d7f87..3956af9 100644
--- a/liveMedia/include/AMRAudioSource.hh
+++ b/liveMedia/include/AMRAudioSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A source object for AMR audio sources
 // C++ header
 
diff --git a/liveMedia/include/AVIFileSink.hh b/liveMedia/include/AVIFileSink.hh
index 5c319c9..f0e2558 100644
--- a/liveMedia/include/AVIFileSink.hh
+++ b/liveMedia/include/AVIFileSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A sink that generates an AVI file from a composite media session
 // C++ header
 
diff --git a/liveMedia/include/AudioInputDevice.hh b/liveMedia/include/AudioInputDevice.hh
index e0a1a3f..10f9df9 100644
--- a/liveMedia/include/AudioInputDevice.hh
+++ b/liveMedia/include/AudioInputDevice.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Generic audio input device (such as a microphone, or an input sound card)
 // C++ header
 
diff --git a/liveMedia/include/AudioRTPSink.hh b/liveMedia/include/AudioRTPSink.hh
index f00f865..e0d4617 100644
--- a/liveMedia/include/AudioRTPSink.hh
+++ b/liveMedia/include/AudioRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic RTP sink for audio codecs (abstract base class)
 // C++ header
 
diff --git a/liveMedia/include/Base64.hh b/liveMedia/include/Base64.hh
index feda65d..79b0a3a 100644
--- a/liveMedia/include/Base64.hh
+++ b/liveMedia/include/Base64.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Base64 encoding and decoding
 // C++ header
 
diff --git a/liveMedia/include/BasicUDPSink.hh b/liveMedia/include/BasicUDPSink.hh
index 14f6e60..7adee01 100644
--- a/liveMedia/include/BasicUDPSink.hh
+++ b/liveMedia/include/BasicUDPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simple UDP sink (i.e., without RTP or other headers added); one frame per packet
 // C++ header
 
diff --git a/liveMedia/include/BasicUDPSource.hh b/liveMedia/include/BasicUDPSource.hh
index f823122..8d47320 100644
--- a/liveMedia/include/BasicUDPSource.hh
+++ b/liveMedia/include/BasicUDPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simple UDP source, where every UDP payload is a complete frame
 // C++ header
 
diff --git a/liveMedia/include/BitVector.hh b/liveMedia/include/BitVector.hh
index 477c3e4..68a4eb6 100644
--- a/liveMedia/include/BitVector.hh
+++ b/liveMedia/include/BitVector.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Bit Vector data structure
 // C++ header
 
diff --git a/liveMedia/include/ByteStreamFileSource.hh b/liveMedia/include/ByteStreamFileSource.hh
index a594f73..c6edce2 100644
--- a/liveMedia/include/ByteStreamFileSource.hh
+++ b/liveMedia/include/ByteStreamFileSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A file source that is a plain byte stream (rather than frames)
 // C++ header
 
@@ -46,7 +46,7 @@ public:
 
   void seekToByteAbsolute(u_int64_t byteNumber, u_int64_t numBytesToStream = 0);
     // if "numBytesToStream" is >0, then we limit the stream to that number of bytes, before treating it as EOF
-  void seekToByteRelative(int64_t offset);
+  void seekToByteRelative(int64_t offset, u_int64_t numBytesToStream = 0);
   void seekToEnd(); // to force EOF handling on the next read
 
 protected:
diff --git a/liveMedia/include/ByteStreamMemoryBufferSource.hh b/liveMedia/include/ByteStreamMemoryBufferSource.hh
index 6906df0..8dd2fcc 100644
--- a/liveMedia/include/ByteStreamMemoryBufferSource.hh
+++ b/liveMedia/include/ByteStreamMemoryBufferSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class for streaming data from a (static) memory buffer, as if it were a file.
 // C++ header
 
@@ -39,7 +39,7 @@ public:
 
   void seekToByteAbsolute(u_int64_t byteNumber, u_int64_t numBytesToStream = 0);
     // if "numBytesToStream" is >0, then we limit the stream to that number of bytes, before treating it as EOF
-  void seekToByteRelative(int64_t offset);
+  void seekToByteRelative(int64_t offset, u_int64_t numBytesToStream = 0);
 
 protected:
   ByteStreamMemoryBufferSource(UsageEnvironment& env,
diff --git a/liveMedia/include/ByteStreamMultiFileSource.hh b/liveMedia/include/ByteStreamMultiFileSource.hh
index 00bf85d..e41f380 100644
--- a/liveMedia/include/ByteStreamMultiFileSource.hh
+++ b/liveMedia/include/ByteStreamMultiFileSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A source that consists of multiple byte-stream files, read sequentially
 // C++ header
 
diff --git a/liveMedia/include/DVVideoFileServerMediaSubsession.hh b/liveMedia/include/DVVideoFileServerMediaSubsession.hh
index b35515c..34a44b5 100644
--- a/liveMedia/include/DVVideoFileServerMediaSubsession.hh
+++ b/liveMedia/include/DVVideoFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a DV video file.
 // C++ header
@@ -39,6 +39,7 @@ private:
 private: // redefined virtual functions
   virtual char const* getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource);
   virtual void seekStreamSource(FramedSource* inputSource, double& seekNPT, double streamDuration, u_int64_t& numBytes);
+  virtual void setStreamSourceDuration(FramedSource* inputSource, double streamDuration, u_int64_t& numBytes);
   virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate);
   virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
   virtual float duration() const;
diff --git a/liveMedia/include/DVVideoRTPSink.hh b/liveMedia/include/DVVideoRTPSink.hh
index f07b204..5b767d5 100644
--- a/liveMedia/include/DVVideoRTPSink.hh
+++ b/liveMedia/include/DVVideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for DV video (RFC 3189)
 // (Thanks to Ben Hutchings for prototyping this.)
 // C++ header
diff --git a/liveMedia/include/DVVideoRTPSource.hh b/liveMedia/include/DVVideoRTPSource.hh
index 66f4f3b..0983618 100644
--- a/liveMedia/include/DVVideoRTPSource.hh
+++ b/liveMedia/include/DVVideoRTPSource.hh
@@ -14,11 +14,11 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // DV Video RTP Sources
 // C++ header
 
-#ifndef _DV_VIDEOO_RTP_SOURCE_HH
+#ifndef _DV_VIDEO_RTP_SOURCE_HH
 #define _DV_VIDEO_RTP_SOURCE_HH
 
 #ifndef _MULTI_FRAMED_RTP_SOURCE_HH
diff --git a/liveMedia/include/DVVideoStreamFramer.hh b/liveMedia/include/DVVideoStreamFramer.hh
index 3a0ccdb..73ccc00 100644
--- a/liveMedia/include/DVVideoStreamFramer.hh
+++ b/liveMedia/include/DVVideoStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that parses a DV input stream into DV frames to deliver to the downstream object
 // C++ header
 
diff --git a/liveMedia/include/DarwinInjector.hh b/liveMedia/include/DarwinInjector.hh
deleted file mode 100644
index 56027fc..0000000
--- a/liveMedia/include/DarwinInjector.hh
+++ /dev/null
@@ -1,106 +0,0 @@
-/**********
-This library is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation; either version 2.1 of the License, or (at your
-option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
-
-This library is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
-more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this library; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
-**********/
-// "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// An object that redirects one or more RTP/RTCP streams - forming a single
-// multimedia session - into a 'Darwin Streaming Server' (for subsequent
-// reflection to potentially arbitrarily many remote RTSP clients).
-// C++ header
-
-#ifndef _DARWIN_INJECTOR_HH
-#define _DARWIN_INJECTOR_HH
-
-#ifndef _RTSP_CLIENT_HH
-#include <RTSPClient.hh>
-#endif
-
-#ifndef _RTCP_HH
-#include <RTCP.hh>
-#endif
-
-/*
-To use a "DarwinInjector":
-  1/ Create RTP sinks and RTCP instances for each audio or video subsession.
-       Note: These can use 0.0.0.0 for the address, and 0 for the port number,
-       of each 'groupsock')
-  2/ Call "addStream()" for each.
-  3/ Call "setDestination()" to specify the remote Darwin Streaming Server.
-     Note: You must have 'write' permission on the Darwin Streaming Server.
-       This can be set up using a "qtaccess" file in the server's 'movies'
-       directory.  For example, the following "qtaccess" file allows anyone to
-       play streams from the server, but allows only valid users to
-       inject streams *into* the server:
-           <Limit WRITE>
-           require valid-user
-           </Limit>
-           require any-user
-     Use the "remoteUserName" and "remotePassword" parameters to
-     "setDestination()", as appropriate.
-  4/ Call "startPlaying" on each RTP sink (from the corresponding 'source').
-*/
-
-class SubstreamDescriptor; // forward
-
-class DarwinInjector: public Medium {
-public:
-  static DarwinInjector* createNew(UsageEnvironment& env,
-				   char const* applicationName = "DarwinInjector",
-				   int verbosityLevel = 0);
-
-  static Boolean lookupByName(UsageEnvironment& env, char const* name,
-			      DarwinInjector*& result);
-
-  void addStream(RTPSink* rtpSink, RTCPInstance* rtcpInstance);
-
-  Boolean setDestination(char const* remoteRTSPServerNameOrAddress,
-			 char const* remoteFileName,
-			 char const* sessionName = "",
-			 char const* sessionInfo = "",
-			 portNumBits remoteRTSPServerPortNumber = 554,
-			 char const* remoteUserName = "",
-			 char const* remotePassword = "",
-			 char const* sessionAuthor = "",
-			 char const* sessionCopyright = "",
-			 int timeout = -1);
-
-private: // redefined virtual functions
-  virtual Boolean isDarwinInjector() const;
-
-private:
-  DarwinInjector(UsageEnvironment& env,
-		 char const* applicationName, int verbosityLevel);
-      // called only by createNew()
-
-  virtual ~DarwinInjector();
-
-  static void genericResponseHandler(RTSPClient* rtspClient, int responseCode, char* responseString);
-  void genericResponseHandler1(int responseCode, char* responseString);
-
-private:
-  char const* fApplicationName;
-  int fVerbosityLevel;
-  RTSPClient* fRTSPClient;
-  unsigned fSubstreamSDPSizes;
-  SubstreamDescriptor* fHeadSubstream;
-  SubstreamDescriptor* fTailSubstream;
-  MediaSession* fSession;
-  unsigned fLastTrackId;
-  char fWatchVariable;
-  int fResultCode;
-  char* fResultString;
-};
-
-#endif
diff --git a/liveMedia/include/DeviceSource.hh b/liveMedia/include/DeviceSource.hh
index adc937f..3d00c40 100644
--- a/liveMedia/include/DeviceSource.hh
+++ b/liveMedia/include/DeviceSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A template for a MediaSource encapsulating an audio/video input device
 // 
 // NOTE: Sections of this code labeled "%%% TO BE WRITTEN %%%" are incomplete, and needto be written by the programmer
diff --git a/liveMedia/include/DigestAuthentication.hh b/liveMedia/include/DigestAuthentication.hh
index a3656c0..364d944 100644
--- a/liveMedia/include/DigestAuthentication.hh
+++ b/liveMedia/include/DigestAuthentication.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class used for digest authentication.
 // C++ header
 
@@ -37,6 +37,7 @@ public:
       // by md5(<username>:<realm>:<actual-password>)
   Authenticator(const Authenticator& orig);
   Authenticator& operator=(const Authenticator& rightSide);
+  Boolean operator<(const Authenticator* rightSide);
   virtual ~Authenticator();
 
   void reset();
diff --git a/liveMedia/include/FileServerMediaSubsession.hh b/liveMedia/include/FileServerMediaSubsession.hh
index 42d1756..4485772 100644
--- a/liveMedia/include/FileServerMediaSubsession.hh
+++ b/liveMedia/include/FileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a file.
 // C++ header
@@ -22,6 +22,9 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #ifndef _FILE_SERVER_MEDIA_SUBSESSION_HH
 #define _FILE_SERVER_MEDIA_SUBSESSION_HH
 
+#ifndef _SERVER_MEDIA_SESSION_HH
+#include "ServerMediaSession.hh"
+#endif
 #ifndef _ON_DEMAND_SERVER_MEDIA_SUBSESSION_HH
 #include "OnDemandServerMediaSubsession.hh"
 #endif
diff --git a/liveMedia/include/FileSink.hh b/liveMedia/include/FileSink.hh
index 2f3bc6d..c8bbc07 100644
--- a/liveMedia/include/FileSink.hh
+++ b/liveMedia/include/FileSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // File Sinks
 // C++ header
 
@@ -37,8 +37,8 @@ public:
   //   file name suffix).  The default behavior ("oneFilePerFrame" == False)
   //   is to output all incoming data into a single file.
 
-  void addData(unsigned char const* data, unsigned dataSize,
-	       struct timeval presentationTime);
+  virtual 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)
 
 protected:
diff --git a/liveMedia/include/FramedFileSource.hh b/liveMedia/include/FramedFileSource.hh
index 8ad76d8..dc97d15 100644
--- a/liveMedia/include/FramedFileSource.hh
+++ b/liveMedia/include/FramedFileSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Framed File Sources
 // C++ header
 
diff --git a/liveMedia/include/FramedFilter.hh b/liveMedia/include/FramedFilter.hh
index fe74fa7..b18cd9f 100644
--- a/liveMedia/include/FramedFilter.hh
+++ b/liveMedia/include/FramedFilter.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Framed Filters
 // C++ header
 
diff --git a/liveMedia/include/FramedSource.hh b/liveMedia/include/FramedSource.hh
index 1ca3fea..e7a69a3 100644
--- a/liveMedia/include/FramedSource.hh
+++ b/liveMedia/include/FramedSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Framed Sources
 // C++ header
 
@@ -45,6 +45,7 @@ public:
 		    void* onCloseClientData);
 
   static void handleClosure(void* clientData);
+  void handleClosure();
       // This should be called (on ourself) if the source is discovered
       // to be closed (i.e., no longer readable)
 
diff --git a/liveMedia/include/GSMAudioRTPSink.hh b/liveMedia/include/GSMAudioRTPSink.hh
index 9267a8c..3e58786 100644
--- a/liveMedia/include/GSMAudioRTPSink.hh
+++ b/liveMedia/include/GSMAudioRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for GSM audio
 // C++ header
 
diff --git a/liveMedia/include/GenericMediaServer.hh b/liveMedia/include/GenericMediaServer.hh
new file mode 100644
index 0000000..a5329ad
--- /dev/null
+++ b/liveMedia/include/GenericMediaServer.hh
@@ -0,0 +1,189 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A generic media server class, used to implement a RTSP server, and any other server that uses
+//  "ServerMediaSession" objects to describe media to be served.
+// C++ header
+
+#ifndef _GENERIC_MEDIA_SERVER_HH
+#define _GENERIC_MEDIA_SERVER_HH
+
+#ifndef _SERVER_MEDIA_SESSION_HH
+#include "ServerMediaSession.hh"
+#endif
+
+#ifndef REQUEST_BUFFER_SIZE
+#define REQUEST_BUFFER_SIZE 20000 // for incoming requests
+#endif
+#ifndef RESPONSE_BUFFER_SIZE
+#define RESPONSE_BUFFER_SIZE 20000
+#endif
+
+class GenericMediaServer: public Medium {
+public:
+  void addServerMediaSession(ServerMediaSession* serverMediaSession);
+
+  virtual ServerMediaSession*
+  lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession = True);
+
+  void removeServerMediaSession(ServerMediaSession* serverMediaSession);
+      // Removes the "ServerMediaSession" object from our lookup table, so it will no longer be accessible by new clients.
+      // (However, any *existing* client sessions that use this "ServerMediaSession" object will continue streaming.
+      //  The "ServerMediaSession" object will not get deleted until all of these client sessions have closed.)
+      // (To both delete the "ServerMediaSession" object *and* close all client sessions that use it,
+      //  call "deleteServerMediaSession(serverMediaSession)" instead.)
+  void removeServerMediaSession(char const* streamName);
+     // ditto
+
+  void closeAllClientSessionsForServerMediaSession(ServerMediaSession* serverMediaSession);
+      // Closes (from the server) all client sessions that are currently using this "ServerMediaSession" object.
+      // Note, however, that the "ServerMediaSession" object remains accessible by new clients.
+  void closeAllClientSessionsForServerMediaSession(char const* streamName);
+     // ditto
+
+  void deleteServerMediaSession(ServerMediaSession* serverMediaSession);
+      // Equivalent to:
+      //     "closeAllClientSessionsForServerMediaSession(serverMediaSession); removeServerMediaSession(serverMediaSession);"
+  void deleteServerMediaSession(char const* streamName);
+      // Equivalent to:
+      //     "closeAllClientSessionsForServerMediaSession(streamName); removeServerMediaSession(streamName);
+
+protected:
+  GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort,
+		     unsigned reclamationSeconds);
+      // If "reclamationSeconds" > 0, then the "ClientSession" state for each client will get
+      // reclaimed if no activity from the client is detected in at least "reclamationSeconds".
+  // we're an abstract base class
+  virtual ~GenericMediaServer();
+  void cleanup(); // MUST be called in the destructor of any subclass of us
+
+  static int setUpOurSocket(UsageEnvironment& env, Port& ourPort);
+
+  static void incomingConnectionHandler(void*, int /*mask*/);
+  void incomingConnectionHandler();
+  void incomingConnectionHandlerOnSocket(int serverSocket);
+
+public: // should be protected, but some old compilers complain otherwise
+  // The state of a TCP connection used by a client:
+  class ClientConnection {
+  protected:
+    ClientConnection(GenericMediaServer& ourServer, int clientSocket, struct sockaddr_in clientAddr);
+    virtual ~ClientConnection();
+
+    UsageEnvironment& envir() { return fOurServer.envir(); }
+    void closeSockets();
+
+    static void incomingRequestHandler(void*, int /*mask*/);
+    void incomingRequestHandler();
+    virtual void handleRequestBytes(int newBytesRead) = 0;
+    void resetRequestBuffer();
+
+  protected:
+    friend class GenericMediaServer;
+    friend class ClientSession;
+    friend class RTSPServer; // needed to make some broken Windows compilers work; remove this in the future when we end support for Windows
+    GenericMediaServer& fOurServer;
+    int fOurSocket;
+    struct sockaddr_in fClientAddr;
+    unsigned char fRequestBuffer[REQUEST_BUFFER_SIZE];
+    unsigned char fResponseBuffer[RESPONSE_BUFFER_SIZE];
+    unsigned fRequestBytesAlreadySeen, fRequestBufferBytesLeft;
+  };
+
+  // The state of an individual client session (using one or more sequential TCP connections) handled by a server:
+  class ClientSession {
+  protected:
+    ClientSession(GenericMediaServer& ourServer, u_int32_t sessionId);
+    virtual ~ClientSession();
+
+    UsageEnvironment& envir() { return fOurServer.envir(); }
+    void noteLiveness();
+    static void noteClientLiveness(ClientSession* clientSession);
+    static void livenessTimeoutTask(ClientSession* clientSession);
+
+  protected:
+    friend class GenericMediaServer;
+    friend class ClientConnection;
+    GenericMediaServer& fOurServer;
+    u_int32_t fOurSessionId;
+    ServerMediaSession* fOurServerMediaSession;
+    TaskToken fLivenessCheckTask;
+  };
+
+protected:
+  virtual ClientConnection* createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) = 0;
+  virtual ClientSession* createNewClientSession(u_int32_t sessionId) = 0;
+
+  ClientSession* createNewClientSessionWithId();
+      // Generates a new (unused) random session id, and calls the "createNewClientSession()"
+      // virtual function with this session id as parameter.
+
+  // Lookup a "ClientSession" object by sessionId (integer, and string):
+  ClientSession* lookupClientSession(u_int32_t sessionId);
+  ClientSession* lookupClientSession(char const* sessionIdStr);
+
+  // An iterator over our "ServerMediaSession" objects:
+  class ServerMediaSessionIterator {
+  public:
+    ServerMediaSessionIterator(GenericMediaServer& server);
+    virtual ~ServerMediaSessionIterator();
+    ServerMediaSession* next();
+  private:
+    HashTable::Iterator* fOurIterator;
+  };
+
+protected:
+  friend class ClientConnection;
+  friend class ClientSession;	
+  friend class ServerMediaSessionIterator;
+  int fServerSocket;
+  Port fServerPort;
+  unsigned fReclamationSeconds;
+
+private:
+  HashTable* fServerMediaSessions; // maps 'stream name' strings to "ServerMediaSession" objects
+  HashTable* fClientConnections; // the "ClientConnection" objects that we're using
+  HashTable* fClientSessions; // maps 'session id' strings to "ClientSession" objects
+};
+
+// A data structure used for optional user/password authentication:
+
+class UserAuthenticationDatabase {
+public:
+  UserAuthenticationDatabase(char const* realm = NULL,
+			     Boolean passwordsAreMD5 = False);
+    // If "passwordsAreMD5" is True, then each password stored into, or removed from,
+    // the database is actually the value computed
+    // by md5(<username>:<realm>:<actual-password>)
+  virtual ~UserAuthenticationDatabase();
+
+  virtual void addUserRecord(char const* username, char const* password);
+  virtual void removeUserRecord(char const* username);
+
+  virtual char const* lookupPassword(char const* username);
+      // returns NULL if the user name was not present
+
+  char const* realm() { return fRealm; }
+  Boolean passwordsAreMD5() { return fPasswordsAreMD5; }
+
+protected:
+  HashTable* fTable;
+  char* fRealm;
+  Boolean fPasswordsAreMD5;
+};
+
+#endif
diff --git a/liveMedia/include/H261VideoRTPSource.hh b/liveMedia/include/H261VideoRTPSource.hh
index f66151c..881f029 100644
--- a/liveMedia/include/H261VideoRTPSource.hh
+++ b/liveMedia/include/H261VideoRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // H.261 Video RTP Sources
 // C++ header
 
diff --git a/liveMedia/include/H263plusVideoFileServerMediaSubsession.hh b/liveMedia/include/H263plusVideoFileServerMediaSubsession.hh
index d1df495..d0b52bc 100644
--- a/liveMedia/include/H263plusVideoFileServerMediaSubsession.hh
+++ b/liveMedia/include/H263plusVideoFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a H.263 video file.
 // C++ header
diff --git a/liveMedia/include/H263plusVideoRTPSink.hh b/liveMedia/include/H263plusVideoRTPSink.hh
index 3b49b65..7b4d854 100644
--- a/liveMedia/include/H263plusVideoRTPSink.hh
+++ b/liveMedia/include/H263plusVideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for H.263+ video (RFC 4629)
 // C++ header
 
diff --git a/liveMedia/include/H263plusVideoRTPSource.hh b/liveMedia/include/H263plusVideoRTPSource.hh
index d49ad00..135e0b4 100644
--- a/liveMedia/include/H263plusVideoRTPSource.hh
+++ b/liveMedia/include/H263plusVideoRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // H.263+ Video RTP Sources
 // C++ header
 
diff --git a/liveMedia/include/H263plusVideoStreamFramer.hh b/liveMedia/include/H263plusVideoStreamFramer.hh
index 05fb3d3..c64f914 100644
--- a/liveMedia/include/H263plusVideoStreamFramer.hh
+++ b/liveMedia/include/H263plusVideoStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an H263 video elementary stream into frames.
 // Author Benhard Feiten
 
diff --git a/liveMedia/include/H264VideoFileServerMediaSubsession.hh b/liveMedia/include/H264VideoFileServerMediaSubsession.hh
index 465ae96..2d8f409 100644
--- a/liveMedia/include/H264VideoFileServerMediaSubsession.hh
+++ b/liveMedia/include/H264VideoFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a H264 Elementary Stream video file.
 // C++ header
diff --git a/liveMedia/include/H264VideoFileSink.hh b/liveMedia/include/H264VideoFileSink.hh
index 73cd921..00c4c31 100644
--- a/liveMedia/include/H264VideoFileSink.hh
+++ b/liveMedia/include/H264VideoFileSink.hh
@@ -14,25 +14,27 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // H.264 Video File Sinks
 // C++ header
 
 #ifndef _H264_VIDEO_FILE_SINK_HH
 #define _H264_VIDEO_FILE_SINK_HH
 
-#ifndef _FILE_SINK_HH
-#include "FileSink.hh"
+#ifndef _H264_OR_5_VIDEO_FILE_SINK_HH
+#include "H264or5VideoFileSink.hh"
 #endif
 
-class H264VideoFileSink: public FileSink {
+class H264VideoFileSink: public H264or5VideoFileSink {
 public:
   static H264VideoFileSink* createNew(UsageEnvironment& env, char const* fileName,
 				      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
+      // "sPropParameterSetsStr" is an optional 'SDP format' string
+      // (comma-separated Base64-encoded) representing SPS and/or PPS NAL-units
+      // to prepend to the output
 				      unsigned bufferSize = 100000,
 				      Boolean oneFilePerFrame = False);
-  // See "FileSink.hh" for a description of these parameters.
+      // See "FileSink.hh" for a description of these parameters.
 
 protected:
   H264VideoFileSink(UsageEnvironment& env, FILE* fid,
@@ -40,13 +42,6 @@ protected:
 		    unsigned bufferSize, char const* perFrameFileNamePrefix);
       // called only by createNew()
   virtual ~H264VideoFileSink();
-
-protected: // redefined virtual functions:
-  virtual void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime);
-
-private:
-  char const* fSPropParameterSetsStr;
-  Boolean fHaveWrittenFirstFrame;
 };
 
 #endif
diff --git a/liveMedia/include/H264VideoRTPSink.hh b/liveMedia/include/H264VideoRTPSink.hh
index 13021d8..9b4681c 100644
--- a/liveMedia/include/H264VideoRTPSink.hh
+++ b/liveMedia/include/H264VideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for H.264 video (RFC 3984)
 // C++ header
 
@@ -34,11 +34,13 @@ public:
 	    u_int8_t const* sps, unsigned spsSize, u_int8_t const* pps, unsigned ppsSize);
     // an optional variant of "createNew()", useful if we know, in advance,
     // the stream's SPS and PPS NAL units.
+    // This avoids us having to 'pre-read' from the input source in order to get these values.
   static H264VideoRTPSink*
   createNew(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat,
 	    char const* sPropParameterSetsStr);
     // an optional variant of "createNew()", useful if we know, in advance,
     // the stream's SPS and PPS NAL units.
+    // This avoids us having to 'pre-read' from the input source in order to get these values.
 
 protected:
   H264VideoRTPSink(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat,
diff --git a/liveMedia/include/H264VideoRTPSource.hh b/liveMedia/include/H264VideoRTPSource.hh
index b7a6212..530d4d2 100644
--- a/liveMedia/include/H264VideoRTPSource.hh
+++ b/liveMedia/include/H264VideoRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // H.264 Video RTP Sources
 // C++ header
 
diff --git a/liveMedia/include/H264VideoStreamDiscreteFramer.hh b/liveMedia/include/H264VideoStreamDiscreteFramer.hh
index ffbac92..716b464 100644
--- a/liveMedia/include/H264VideoStreamDiscreteFramer.hh
+++ b/liveMedia/include/H264VideoStreamDiscreteFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "H264VideoStreamFramer" that takes only complete,
 // discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
diff --git a/liveMedia/include/H264VideoStreamFramer.hh b/liveMedia/include/H264VideoStreamFramer.hh
index deca92a..f629b91 100644
--- a/liveMedia/include/H264VideoStreamFramer.hh
+++ b/liveMedia/include/H264VideoStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up a H.264 Video Elementary Stream into NAL units.
 // C++ header
 
diff --git a/liveMedia/include/H264VideoFileSink.hh b/liveMedia/include/H264or5VideoFileSink.hh
similarity index 55%
copy from liveMedia/include/H264VideoFileSink.hh
copy to liveMedia/include/H264or5VideoFileSink.hh
index 73cd921..fd83a08 100644
--- a/liveMedia/include/H264VideoFileSink.hh
+++ b/liveMedia/include/H264or5VideoFileSink.hh
@@ -14,38 +14,32 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// H.264 Video File Sinks
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// H.264 or H.265 Video File Sinks
 // C++ header
 
-#ifndef _H264_VIDEO_FILE_SINK_HH
-#define _H264_VIDEO_FILE_SINK_HH
+#ifndef _H264_OR_5_VIDEO_FILE_SINK_HH
+#define _H264_OR_5_VIDEO_FILE_SINK_HH
 
 #ifndef _FILE_SINK_HH
 #include "FileSink.hh"
 #endif
 
-class H264VideoFileSink: public FileSink {
-public:
-  static H264VideoFileSink* createNew(UsageEnvironment& env, char const* fileName,
-				      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 = 100000,
-				      Boolean oneFilePerFrame = False);
-  // See "FileSink.hh" for a description of these parameters.
-
+class H264or5VideoFileSink: public FileSink {
 protected:
-  H264VideoFileSink(UsageEnvironment& env, FILE* fid,
-		    char const* sPropParameterSetsStr,
-		    unsigned bufferSize, char const* perFrameFileNamePrefix);
-      // called only by createNew()
-  virtual ~H264VideoFileSink();
+  H264or5VideoFileSink(UsageEnvironment& env, FILE* fid,
+		       unsigned bufferSize, char const* perFrameFileNamePrefix,
+		       char const* sPropParameterSetsStr1,
+		       char const* sPropParameterSetsStr2 = NULL,
+		       char const* sPropParameterSetsStr3 = NULL);
+      // we're an abstract base class
+  virtual ~H264or5VideoFileSink();
 
 protected: // redefined virtual functions:
   virtual void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime);
 
 private:
-  char const* fSPropParameterSetsStr;
+  char const* fSPropParameterSetsStr[3];
   Boolean fHaveWrittenFirstFrame;
 };
 
diff --git a/liveMedia/include/H264or5VideoRTPSink.hh b/liveMedia/include/H264or5VideoRTPSink.hh
index 00ca921..c7752cc 100644
--- a/liveMedia/include/H264or5VideoRTPSink.hh
+++ b/liveMedia/include/H264or5VideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for H.264 or H.265 video
 // C++ header
 
diff --git a/liveMedia/include/H264or5VideoStreamDiscreteFramer.hh b/liveMedia/include/H264or5VideoStreamDiscreteFramer.hh
index dc83567..dd88ed7 100644
--- a/liveMedia/include/H264or5VideoStreamDiscreteFramer.hh
+++ b/liveMedia/include/H264or5VideoStreamDiscreteFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "H264or5VideoStreamFramer" that takes only complete,
 // discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
@@ -47,6 +47,8 @@ protected:
                           unsigned numTruncatedBytes,
                           struct timeval presentationTime,
                           unsigned durationInMicroseconds);
+
+  virtual Boolean nalUnitEndsAccessUnit(u_int8_t nal_unit_type);
 };
 
 #endif
diff --git a/liveMedia/include/H264or5VideoStreamFramer.hh b/liveMedia/include/H264or5VideoStreamFramer.hh
index 0c631ed..9ffc541 100644
--- a/liveMedia/include/H264or5VideoStreamFramer.hh
+++ b/liveMedia/include/H264or5VideoStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up a H.264 or H.265 Video Elementary Stream into NAL units.
 // C++ header
 
@@ -48,11 +48,6 @@ public:
     saveCopyOfPPS(pps, ppsSize);
   }
 
-  u_int32_t profileLevelId() const { return fProfileLevelId; }
-      // used for H.264 only
-  u_int8_t const* profileTierLevelHeaderBytes() const { return fProfileTierLevelHeaderBytes; }
-      // used for H.265 only
-
 protected:
   H264or5VideoStreamFramer(int hNumber, // 264 or 265
 			   UsageEnvironment& env, FramedSource* inputSource,
@@ -79,8 +74,6 @@ protected:
   unsigned fLastSeenSPSSize;
   u_int8_t* fLastSeenPPS;
   unsigned fLastSeenPPSSize;
-  u_int32_t fProfileLevelId;  // set/used for H.264 only
-  u_int8_t fProfileTierLevelHeaderBytes[12]; // set/used for H.265 only
   struct timeval fNextPresentationTime; // the presentation time to be used for the next NAL unit to be parsed/delivered after this
   friend class H264or5VideoStreamParser; // hack
 };
diff --git a/liveMedia/include/H265VideoFileServerMediaSubsession.hh b/liveMedia/include/H265VideoFileServerMediaSubsession.hh
index f34a1eb..e3314c4 100644
--- a/liveMedia/include/H265VideoFileServerMediaSubsession.hh
+++ b/liveMedia/include/H265VideoFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a H265 Elementary Stream video file.
 // C++ header
diff --git a/liveMedia/include/H264VideoFileSink.hh b/liveMedia/include/H265VideoFileSink.hh
similarity index 53%
copy from liveMedia/include/H264VideoFileSink.hh
copy to liveMedia/include/H265VideoFileSink.hh
index 73cd921..f52dede 100644
--- a/liveMedia/include/H264VideoFileSink.hh
+++ b/liveMedia/include/H265VideoFileSink.hh
@@ -14,39 +14,38 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// H.264 Video File Sinks
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// H.265 Video File Sinks
 // C++ header
 
-#ifndef _H264_VIDEO_FILE_SINK_HH
-#define _H264_VIDEO_FILE_SINK_HH
+#ifndef _H265_VIDEO_FILE_SINK_HH
+#define _H265_VIDEO_FILE_SINK_HH
 
-#ifndef _FILE_SINK_HH
-#include "FileSink.hh"
+#ifndef _H264_OR_5_VIDEO_FILE_SINK_HH
+#include "H264or5VideoFileSink.hh"
 #endif
 
-class H264VideoFileSink: public FileSink {
+class H265VideoFileSink: public H264or5VideoFileSink {
 public:
-  static H264VideoFileSink* createNew(UsageEnvironment& env, char const* fileName,
-				      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
+  static H265VideoFileSink* createNew(UsageEnvironment& env, char const* fileName,
+				      char const* sPropVPSStr = NULL,
+				      char const* sPropSPSStr = NULL,
+				      char const* sPropPPSStr = NULL,
+      // The "sProp*Str" parameters are optional 'SDP format' strings
+      // (comma-separated Base64-encoded) representing VPS, SPS, and/or PPS NAL-units
+      // to prepend to the output
 				      unsigned bufferSize = 100000,
 				      Boolean oneFilePerFrame = False);
-  // See "FileSink.hh" for a description of these parameters.
+      // See "FileSink.hh" for a description of these parameters.
 
 protected:
-  H264VideoFileSink(UsageEnvironment& env, FILE* fid,
-		    char const* sPropParameterSetsStr,
+  H265VideoFileSink(UsageEnvironment& env, FILE* fid,
+		    char const* sPropVPSStr,
+		    char const* sPropSPSStr,
+		    char const* sPropPPSStr,
 		    unsigned bufferSize, char const* perFrameFileNamePrefix);
       // called only by createNew()
-  virtual ~H264VideoFileSink();
-
-protected: // redefined virtual functions:
-  virtual void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime);
-
-private:
-  char const* fSPropParameterSetsStr;
-  Boolean fHaveWrittenFirstFrame;
+  virtual ~H265VideoFileSink();
 };
 
 #endif
diff --git a/liveMedia/include/H265VideoRTPSink.hh b/liveMedia/include/H265VideoRTPSink.hh
index a90df4a..4c4c42b 100644
--- a/liveMedia/include/H265VideoRTPSink.hh
+++ b/liveMedia/include/H265VideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for H.265 video
 // C++ header
 
@@ -36,11 +36,13 @@ public:
 	    u_int8_t const* pps, unsigned ppsSize);
     // an optional variant of "createNew()", useful if we know, in advance,
     // the stream's VPS, SPS and PPS NAL units.
+    // This avoids us having to 'pre-read' from the input source in order to get these values.
   static H265VideoRTPSink*
   createNew(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat,
-	    char const* sPropVPSStr, char const* sPropSPSStr=NULL, char const* sPropPPSStr=NULL);
+	    char const* sPropVPSStr, char const* sPropSPSStr, char const* sPropPPSStr);
     // an optional variant of "createNew()", useful if we know, in advance,
     // the stream's VPS, SPS and PPS NAL units.
+    // This avoids us having to 'pre-read' from the input source in order to get these values.
 
 protected:
   H265VideoRTPSink(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat,
diff --git a/liveMedia/include/MPEG1or2AudioRTPSource.hh b/liveMedia/include/H265VideoRTPSource.hh
similarity index 57%
copy from liveMedia/include/MPEG1or2AudioRTPSource.hh
copy to liveMedia/include/H265VideoRTPSource.hh
index e0c8d3b..1bb5c59 100644
--- a/liveMedia/include/MPEG1or2AudioRTPSource.hh
+++ b/liveMedia/include/H265VideoRTPSource.hh
@@ -14,38 +14,54 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// MPEG-1 or MPEG-2 Audio RTP Sources
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// H.265 Video RTP Sources
 // C++ header
 
-#ifndef _MPEG_1OR2_AUDIO_RTP_SOURCE_HH
-#define _MPEG_1OR2_AUDIO_RTP_SOURCE_HH
+#ifndef _H265_VIDEO_RTP_SOURCE_HH
+#define _H265_VIDEO_RTP_SOURCE_HH
 
 #ifndef _MULTI_FRAMED_RTP_SOURCE_HH
 #include "MultiFramedRTPSource.hh"
 #endif
 
-class MPEG1or2AudioRTPSource: public MultiFramedRTPSource {
+class H265VideoRTPSource: public MultiFramedRTPSource {
 public:
-  static MPEG1or2AudioRTPSource*
+  static H265VideoRTPSource*
   createNew(UsageEnvironment& env, Groupsock* RTPgs,
-	    unsigned char rtpPayloadFormat = 14,
+	    unsigned char rtpPayloadFormat,
+	    Boolean expectDONFields = False,
 	    unsigned rtpTimestampFrequency = 90000);
+      // "expectDONFields" is True iff we expect incoming H.265/RTP packets to contain
+      // DONL and DOND fields.  I.e., if "tx-mode == "MST" or sprop-depack-buf-nalus > 0".
 
-protected:
-  virtual ~MPEG1or2AudioRTPSource();
+  u_int64_t currentNALUnitAbsDon() const { return fCurrentNALUnitAbsDon; }
+      // the 'absolute decoding order number (AbsDon)' for the most-recently delivered NAL unit
 
-private:
-  MPEG1or2AudioRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
+protected:
+  H265VideoRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
 		     unsigned char rtpPayloadFormat,
+		     Boolean expectDONFields,		     
 		     unsigned rtpTimestampFrequency);
       // called only by createNew()
 
-private:
+  virtual ~H265VideoRTPSource();
+
+protected:
   // redefined virtual functions:
   virtual Boolean processSpecialHeader(BufferedPacket* packet,
                                        unsigned& resultSpecialHeaderSize);
   virtual char const* MIMEtype() const;
+
+private:
+  void computeAbsDonFromDON(u_int16_t DON);
+
+private:
+  friend class H265BufferedPacket;
+  Boolean fExpectDONFields;
+  unsigned char fCurPacketNALUnitType;
+  u_int16_t fPreviousNALUnitDON;
+  u_int64_t fCurrentNALUnitAbsDon;
 };
 
 #endif
diff --git a/liveMedia/include/H265VideoStreamDiscreteFramer.hh b/liveMedia/include/H265VideoStreamDiscreteFramer.hh
index 4995c7f..b4230cb 100644
--- a/liveMedia/include/H265VideoStreamDiscreteFramer.hh
+++ b/liveMedia/include/H265VideoStreamDiscreteFramer.hh
@@ -14,15 +14,15 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "H265VideoStreamFramer" that takes only complete,
 // discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
 // "H265VideoStreamFramer".
 // C++ header
 
-#ifndef _H264_VIDEO_STREAM_DISCRETE_FRAMER_HH
-#define _H264_VIDEO_STREAM_DISCRETE_FRAMER_HH
+#ifndef _H265_VIDEO_STREAM_DISCRETE_FRAMER_HH
+#define _H265_VIDEO_STREAM_DISCRETE_FRAMER_HH
 
 #ifndef _H264_OR_5_VIDEO_STREAM_DISCRETE_FRAMER_HH
 #include "H264or5VideoStreamDiscreteFramer.hh"
diff --git a/liveMedia/include/H265VideoStreamFramer.hh b/liveMedia/include/H265VideoStreamFramer.hh
index 5d6940b..0e7348a 100644
--- a/liveMedia/include/H265VideoStreamFramer.hh
+++ b/liveMedia/include/H265VideoStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up a H.265 Video Elementary Stream into NAL units.
 // C++ header
 
diff --git a/liveMedia/include/InputFile.hh b/liveMedia/include/InputFile.hh
index 0b5d9ee..4e83598 100644
--- a/liveMedia/include/InputFile.hh
+++ b/liveMedia/include/InputFile.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Common routines for opening/closing named input files
 // C++ header
 
diff --git a/liveMedia/include/JPEGVideoRTPSink.hh b/liveMedia/include/JPEGVideoRTPSink.hh
index ae2579e..50dd30b 100644
--- a/liveMedia/include/JPEGVideoRTPSink.hh
+++ b/liveMedia/include/JPEGVideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for JPEG video (RFC 2435)
 // C++ header
 
diff --git a/liveMedia/include/JPEGVideoRTPSource.hh b/liveMedia/include/JPEGVideoRTPSource.hh
index 7703560..3eb85ab 100644
--- a/liveMedia/include/JPEGVideoRTPSource.hh
+++ b/liveMedia/include/JPEGVideoRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // JPEG Video (RFC 2435) RTP Sources
 // C++ header
 
diff --git a/liveMedia/include/JPEGVideoSource.hh b/liveMedia/include/JPEGVideoSource.hh
index 46b5e10..246f4b6 100644
--- a/liveMedia/include/JPEGVideoSource.hh
+++ b/liveMedia/include/JPEGVideoSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // JPEG video sources
 // C++ header
 
diff --git a/liveMedia/include/Locale.hh b/liveMedia/include/Locale.hh
index 392d696..b327948 100644
--- a/liveMedia/include/Locale.hh
+++ b/liveMedia/include/Locale.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Support for temporarily setting the locale (e.g., to "C" or "POSIX") for (e.g.) parsing or printing
 // floating-point numbers in protocol headers, or calling toupper()/tolower() on human-input strings.
 // C++ header
diff --git a/liveMedia/include/MP3ADU.hh b/liveMedia/include/MP3ADU.hh
index 3223481..a0f1133 100644
--- a/liveMedia/include/MP3ADU.hh
+++ b/liveMedia/include/MP3ADU.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // 'ADU' MP3 streams (for improved loss-tolerance)
 // C++ header
 
diff --git a/liveMedia/include/MP3ADURTPSink.hh b/liveMedia/include/MP3ADURTPSink.hh
index aa04f11..f8a7bbb 100644
--- a/liveMedia/include/MP3ADURTPSink.hh
+++ b/liveMedia/include/MP3ADURTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for 'ADUized' MP3 frames ("mpa-robust")
 // C++ header
 
diff --git a/liveMedia/include/MP3ADURTPSource.hh b/liveMedia/include/MP3ADURTPSource.hh
index 348b23e..479b01d 100644
--- a/liveMedia/include/MP3ADURTPSource.hh
+++ b/liveMedia/include/MP3ADURTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP source for 'ADUized' MP3 frames ("mpa-robust")
 // C++ header
 
diff --git a/liveMedia/include/MP3ADUTranscoder.hh b/liveMedia/include/MP3ADUTranscoder.hh
index 74784ed..4be2a82 100644
--- a/liveMedia/include/MP3ADUTranscoder.hh
+++ b/liveMedia/include/MP3ADUTranscoder.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Transcoder for ADUized MP3 frames
 // C++ header
 
diff --git a/liveMedia/include/MP3ADUinterleaving.hh b/liveMedia/include/MP3ADUinterleaving.hh
index b1ed288..5225cea 100644
--- a/liveMedia/include/MP3ADUinterleaving.hh
+++ b/liveMedia/include/MP3ADUinterleaving.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Interleaving of MP3 ADUs
 // C++ header
 
diff --git a/liveMedia/include/MP3AudioFileServerMediaSubsession.hh b/liveMedia/include/MP3AudioFileServerMediaSubsession.hh
index 4cf914d..6781532 100644
--- a/liveMedia/include/MP3AudioFileServerMediaSubsession.hh
+++ b/liveMedia/include/MP3AudioFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an MP3 audio file.
 // (Actually, any MPEG-1 or MPEG-2 audio file should work.)
diff --git a/liveMedia/include/MP3FileSource.hh b/liveMedia/include/MP3FileSource.hh
index 5148842..32bf3d4 100644
--- a/liveMedia/include/MP3FileSource.hh
+++ b/liveMedia/include/MP3FileSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP3 File Sources
 // C++ header
 
diff --git a/liveMedia/include/MP3Transcoder.hh b/liveMedia/include/MP3Transcoder.hh
index 4c04596..6d4ff5e 100644
--- a/liveMedia/include/MP3Transcoder.hh
+++ b/liveMedia/include/MP3Transcoder.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP3 Transcoder
 // C++ header
 
diff --git a/liveMedia/include/MPEG1or2AudioRTPSink.hh b/liveMedia/include/MPEG1or2AudioRTPSink.hh
index 16d0675..af2b44f 100644
--- a/liveMedia/include/MPEG1or2AudioRTPSink.hh
+++ b/liveMedia/include/MPEG1or2AudioRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for MPEG audio (RFC 2250)
 // C++ header
 
diff --git a/liveMedia/include/MPEG1or2AudioRTPSource.hh b/liveMedia/include/MPEG1or2AudioRTPSource.hh
index e0c8d3b..613d033 100644
--- a/liveMedia/include/MPEG1or2AudioRTPSource.hh
+++ b/liveMedia/include/MPEG1or2AudioRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG-1 or MPEG-2 Audio RTP Sources
 // C++ header
 
diff --git a/liveMedia/include/MPEG1or2AudioStreamFramer.hh b/liveMedia/include/MPEG1or2AudioStreamFramer.hh
index 85b4923..e884d95 100644
--- a/liveMedia/include/MPEG1or2AudioStreamFramer.hh
+++ b/liveMedia/include/MPEG1or2AudioStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an MPEG (1,2) audio elementary stream into frames
 // C++ header
 
diff --git a/liveMedia/include/MPEG1or2Demux.hh b/liveMedia/include/MPEG1or2Demux.hh
index 53a76c5..5940cbc 100644
--- a/liveMedia/include/MPEG1or2Demux.hh
+++ b/liveMedia/include/MPEG1or2Demux.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Demultiplexer for a MPEG 1 or 2 Program Stream
 // C++ header
 
diff --git a/liveMedia/include/MPEG1or2DemuxedElementaryStream.hh b/liveMedia/include/MPEG1or2DemuxedElementaryStream.hh
index 523b8b2..6e928eb 100644
--- a/liveMedia/include/MPEG1or2DemuxedElementaryStream.hh
+++ b/liveMedia/include/MPEG1or2DemuxedElementaryStream.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A MPEG 1 or 2 Elementary Stream, demultiplexed from a Program Stream
 // C++ header
 
diff --git a/liveMedia/include/MPEG1or2DemuxedServerMediaSubsession.hh b/liveMedia/include/MPEG1or2DemuxedServerMediaSubsession.hh
index 7117ff6..b2851da 100644
--- a/liveMedia/include/MPEG1or2DemuxedServerMediaSubsession.hh
+++ b/liveMedia/include/MPEG1or2DemuxedServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a MPEG-1 or 2 demuxer.
 // C++ header
diff --git a/liveMedia/include/MPEG1or2FileServerDemux.hh b/liveMedia/include/MPEG1or2FileServerDemux.hh
index 359ba06..02e7bfd 100644
--- a/liveMedia/include/MPEG1or2FileServerDemux.hh
+++ b/liveMedia/include/MPEG1or2FileServerDemux.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A server demultiplexer for a MPEG 1 or 2 Program Stream
 // C++ header
 
diff --git a/liveMedia/include/MPEG1or2VideoFileServerMediaSubsession.hh b/liveMedia/include/MPEG1or2VideoFileServerMediaSubsession.hh
index 93a46a9..cd9772c 100644
--- a/liveMedia/include/MPEG1or2VideoFileServerMediaSubsession.hh
+++ b/liveMedia/include/MPEG1or2VideoFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a MPEG-1 or 2 Elementary Stream video file.
 // C++ header
diff --git a/liveMedia/include/MPEG1or2VideoRTPSink.hh b/liveMedia/include/MPEG1or2VideoRTPSink.hh
index 2e91e90..f31cdf3 100644
--- a/liveMedia/include/MPEG1or2VideoRTPSink.hh
+++ b/liveMedia/include/MPEG1or2VideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for MPEG video (RFC 2250)
 // C++ header
 
diff --git a/liveMedia/include/MPEG1or2VideoRTPSource.hh b/liveMedia/include/MPEG1or2VideoRTPSource.hh
index 20d58c9..7e57fdf 100644
--- a/liveMedia/include/MPEG1or2VideoRTPSource.hh
+++ b/liveMedia/include/MPEG1or2VideoRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG-1 or MPEG-2 Video RTP Sources
 // C++ header
 
diff --git a/liveMedia/include/MPEG1or2VideoStreamDiscreteFramer.hh b/liveMedia/include/MPEG1or2VideoStreamDiscreteFramer.hh
index ac77367..b395b47 100644
--- a/liveMedia/include/MPEG1or2VideoStreamDiscreteFramer.hh
+++ b/liveMedia/include/MPEG1or2VideoStreamDiscreteFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "MPEG1or2VideoStreamFramer" that takes only
 // complete, discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
diff --git a/liveMedia/include/MPEG1or2VideoStreamFramer.hh b/liveMedia/include/MPEG1or2VideoStreamFramer.hh
index 7d8516d..4a89a6c 100644
--- a/liveMedia/include/MPEG1or2VideoStreamFramer.hh
+++ b/liveMedia/include/MPEG1or2VideoStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an MPEG 1 or 2 video elementary stream into
 //   frames for: Video_Sequence_Header, GOP_Header, Picture_Header
 // C++ header
diff --git a/liveMedia/include/MPEG2IndexFromTransportStream.hh b/liveMedia/include/MPEG2IndexFromTransportStream.hh
index d3ef8e8..cc3af93 100644
--- a/liveMedia/include/MPEG2IndexFromTransportStream.hh
+++ b/liveMedia/include/MPEG2IndexFromTransportStream.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that produces a sequence of I-frame indices from a MPEG-2 Transport Stream
 // C++ header
 
diff --git a/liveMedia/include/MPEG2TransportFileServerMediaSubsession.hh b/liveMedia/include/MPEG2TransportFileServerMediaSubsession.hh
index 71596ec..72408fd 100644
--- a/liveMedia/include/MPEG2TransportFileServerMediaSubsession.hh
+++ b/liveMedia/include/MPEG2TransportFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a MPEG-2 Transport Stream file.
 // C++ header
@@ -40,7 +40,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 
 class ClientTrickPlayState; // forward
 
-class MPEG2TransportFileServerMediaSubsession: public FileServerMediaSubsession{
+class MPEG2TransportFileServerMediaSubsession: public FileServerMediaSubsession {
 public:
   static MPEG2TransportFileServerMediaSubsession*
   createNew(UsageEnvironment& env,
diff --git a/liveMedia/include/MPEG2TransportStreamFramer.hh b/liveMedia/include/MPEG2TransportStreamFramer.hh
index a9e2dff..61fed50 100644
--- a/liveMedia/include/MPEG2TransportStreamFramer.hh
+++ b/liveMedia/include/MPEG2TransportStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that passes through (unchanged) chunks that contain an integral number
 // of MPEG-2 Transport Stream packets, but returning (in "fDurationInMicroseconds")
 // an updated estimate of the time gap between chunks.
diff --git a/liveMedia/include/MPEG2TransportStreamFromESSource.hh b/liveMedia/include/MPEG2TransportStreamFromESSource.hh
index bc0a44d..4887437 100644
--- a/liveMedia/include/MPEG2TransportStreamFromESSource.hh
+++ b/liveMedia/include/MPEG2TransportStreamFromESSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter for converting one or more MPEG Elementary Streams
 // to a MPEG-2 Transport Stream
 // C++ header
@@ -30,9 +30,12 @@ class MPEG2TransportStreamFromESSource: public MPEG2TransportStreamMultiplexor {
 public:
   static MPEG2TransportStreamFromESSource* createNew(UsageEnvironment& env);
 
-  void addNewVideoSource(FramedSource* inputSource, int mpegVersion);
+  void addNewVideoSource(FramedSource* inputSource, int mpegVersion, int16_t PID = -1);
       // Note: For MPEG-4 video, set "mpegVersion" to 4; for H.264 video, set "mpegVersion" to 5.
-  void addNewAudioSource(FramedSource* inputSource, int mpegVersion);
+  void addNewAudioSource(FramedSource* inputSource, int mpegVersion, int16_t PID = -1);
+      // Note: In these functions, if "PID" is not -1, then it (currently, just the low 8 bits)
+      // is used as the stream's PID.  Otherwise (if "PID" is -1) the 'stream_id' is used as
+      // the PID.
 
 protected:
   MPEG2TransportStreamFromESSource(UsageEnvironment& env);
@@ -40,7 +43,7 @@ protected:
   virtual ~MPEG2TransportStreamFromESSource();
 
   void addNewInputSource(FramedSource* inputSource,
-			 u_int8_t streamId, int mpegVersion);
+			 u_int8_t streamId, int mpegVersion, int16_t PID = -1);
   // used to implement addNew*Source() above
 
 private:
@@ -52,6 +55,7 @@ private:
   friend class InputESSourceRecord;
   class InputESSourceRecord* fInputSources;
   unsigned fVideoSourceCounter, fAudioSourceCounter;
+  Boolean fAwaitingBackgroundDelivery;
 };
 
 #endif
diff --git a/liveMedia/include/MPEG2TransportStreamFromPESSource.hh b/liveMedia/include/MPEG2TransportStreamFromPESSource.hh
index 769dcf0..7a6868e 100644
--- a/liveMedia/include/MPEG2TransportStreamFromPESSource.hh
+++ b/liveMedia/include/MPEG2TransportStreamFromPESSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter for converting a stream of MPEG PES packets to a MPEG-2 Transport Stream
 // C++ header
 
diff --git a/liveMedia/include/MPEG2TransportStreamIndexFile.hh b/liveMedia/include/MPEG2TransportStreamIndexFile.hh
index c483575..93e04b7 100644
--- a/liveMedia/include/MPEG2TransportStreamIndexFile.hh
+++ b/liveMedia/include/MPEG2TransportStreamIndexFile.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class that encapsulates MPEG-2 Transport Stream 'index files'/
 // These index files are used to implement 'trick play' operations
 // (seek-by-time, fast forward, reverse play) on Transport Stream files.
diff --git a/liveMedia/include/MPEG2TransportStreamMultiplexor.hh b/liveMedia/include/MPEG2TransportStreamMultiplexor.hh
index aac6709..321b303 100644
--- a/liveMedia/include/MPEG2TransportStreamMultiplexor.hh
+++ b/liveMedia/include/MPEG2TransportStreamMultiplexor.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class for generating MPEG-2 Transport Stream from one or more input
 // Elementary Stream data sources
 // C++ header
@@ -40,9 +40,12 @@ protected:
       // implemented by subclasses
 
   void handleNewBuffer(unsigned char* buffer, unsigned bufferSize,
-		       int mpegVersion, MPEG1or2Demux::SCR scr);
-  // called by "awaitNewBuffer()"
-  // Note: For MPEG-4 video, set "mpegVersion" to 4; for H.264 video, set "mpegVersion" to 5. 
+		       int mpegVersion, MPEG1or2Demux::SCR scr, int16_t PID = -1);
+      // called by "awaitNewBuffer()"
+      // Note: For MPEG-4 video, set "mpegVersion" to 4; for H.264 video, set "mpegVersion" to 5. 
+      // The buffer is assumed to be a PES packet, with a proper PES header.
+      // If "PID" is not -1, then it (currently, only the low 8 bits) is used as the stream's PID,
+      // otherwise the "stream_id" in the PES header is reused to be the stream's PID.
 
 private:
   // Redefined virtual functions:
@@ -77,4 +80,9 @@ private:
   Boolean fIsFirstAdaptationField;
 };
 
+
+// The CRC calculation function that Transport Streams use.  We make this function public
+// here in case it's useful elsewhere:
+u_int32_t calculateCRC(u_int8_t const* data, unsigned dataLength, u_int32_t initialValue = 0xFFFFFFFF);
+
 #endif
diff --git a/liveMedia/include/MPEG2TransportStreamTrickModeFilter.hh b/liveMedia/include/MPEG2TransportStreamTrickModeFilter.hh
index 46b55e8..da76b58 100644
--- a/liveMedia/include/MPEG2TransportStreamTrickModeFilter.hh
+++ b/liveMedia/include/MPEG2TransportStreamTrickModeFilter.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.// A filter that converts a MPEG Transport Stream file - with corresponding index file
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.// A filter that converts a MPEG Transport Stream file - with corresponding index file
 // - to a corresponding Video Elementary Stream.  It also uses a "scale" parameter
 // to implement 'trick mode' (fast forward or reverse play, using I-frames) on
 // the video stream.
diff --git a/liveMedia/include/MPEG2TransportUDPServerMediaSubsession.hh b/liveMedia/include/MPEG2TransportUDPServerMediaSubsession.hh
index c2e3c02..3d435cc 100644
--- a/liveMedia/include/MPEG2TransportUDPServerMediaSubsession.hh
+++ b/liveMedia/include/MPEG2TransportUDPServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an incoming UDP (or RTP/UDP) MPEG-2 Transport Stream
 // C++ header
diff --git a/liveMedia/include/MPEG4ESVideoRTPSink.hh b/liveMedia/include/MPEG4ESVideoRTPSink.hh
index 76b0254..b423553 100644
--- a/liveMedia/include/MPEG4ESVideoRTPSink.hh
+++ b/liveMedia/include/MPEG4ESVideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for MPEG-4 Elementary Stream video (RFC 3016)
 // C++ header
 
diff --git a/liveMedia/include/MPEG4ESVideoRTPSource.hh b/liveMedia/include/MPEG4ESVideoRTPSource.hh
index 2420acc..b78ec7d 100644
--- a/liveMedia/include/MPEG4ESVideoRTPSource.hh
+++ b/liveMedia/include/MPEG4ESVideoRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MP4V-ES video RTP stream sources
 // C++ header
 
diff --git a/liveMedia/include/MPEG4GenericRTPSink.hh b/liveMedia/include/MPEG4GenericRTPSink.hh
index 1b9e0ed..28bd08e 100644
--- a/liveMedia/include/MPEG4GenericRTPSink.hh
+++ b/liveMedia/include/MPEG4GenericRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG4-GENERIC ("audio", "video", or "application") RTP stream sinks
 // C++ header
 
diff --git a/liveMedia/include/MPEG4GenericRTPSource.hh b/liveMedia/include/MPEG4GenericRTPSource.hh
index f38cef1..932bccf 100644
--- a/liveMedia/include/MPEG4GenericRTPSource.hh
+++ b/liveMedia/include/MPEG4GenericRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG4-GENERIC ("audio", "video", or "application") RTP stream sources
 // C++ header
 
diff --git a/liveMedia/include/MPEG4LATMAudioRTPSink.hh b/liveMedia/include/MPEG4LATMAudioRTPSink.hh
index 54bedbd..2d283e3 100644
--- a/liveMedia/include/MPEG4LATMAudioRTPSink.hh
+++ b/liveMedia/include/MPEG4LATMAudioRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for MPEG-4 audio, using LATM multiplexing (RFC 3016)
 // (Note that the initial 'size' field is assumed to be present at the start of
 //  each frame.)
diff --git a/liveMedia/include/MPEG4LATMAudioRTPSource.hh b/liveMedia/include/MPEG4LATMAudioRTPSource.hh
index 9048c0e..f9f6e1d 100644
--- a/liveMedia/include/MPEG4LATMAudioRTPSource.hh
+++ b/liveMedia/include/MPEG4LATMAudioRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // MPEG-4 audio, using LATM multiplexing
 // C++ header
 
diff --git a/liveMedia/include/MPEG4VideoFileServerMediaSubsession.hh b/liveMedia/include/MPEG4VideoFileServerMediaSubsession.hh
index b44c202..f9b4ca2 100644
--- a/liveMedia/include/MPEG4VideoFileServerMediaSubsession.hh
+++ b/liveMedia/include/MPEG4VideoFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from a MPEG-4 video file.
 // C++ header
diff --git a/liveMedia/include/MPEG4VideoStreamDiscreteFramer.hh b/liveMedia/include/MPEG4VideoStreamDiscreteFramer.hh
index d8cad73..feff0f3 100644
--- a/liveMedia/include/MPEG4VideoStreamDiscreteFramer.hh
+++ b/liveMedia/include/MPEG4VideoStreamDiscreteFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simplified version of "MPEG4VideoStreamFramer" that takes only complete,
 // discrete frames (rather than an arbitrary byte stream) as input.
 // This avoids the parsing and data copying overhead of the full
diff --git a/liveMedia/include/MPEG4VideoStreamFramer.hh b/liveMedia/include/MPEG4VideoStreamFramer.hh
index 3078b3d..75c59a1 100644
--- a/liveMedia/include/MPEG4VideoStreamFramer.hh
+++ b/liveMedia/include/MPEG4VideoStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an MPEG-4 video elementary stream into
 //   frames for:
 // - Visual Object Sequence (VS) Header + Visual Object (VO) Header
diff --git a/liveMedia/include/MPEGVideoStreamFramer.hh b/liveMedia/include/MPEGVideoStreamFramer.hh
index b624396..85aa7be 100644
--- a/liveMedia/include/MPEGVideoStreamFramer.hh
+++ b/liveMedia/include/MPEGVideoStreamFramer.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A filter that breaks up an MPEG video elementary stream into
 //   headers and frames
 // C++ header
@@ -54,6 +54,7 @@ protected:
 
 private: // redefined virtual functions
   virtual void doGetNextFrame();
+  virtual void doStopGettingFrames();
 
 private:
   void reset();
diff --git a/liveMedia/include/MatroskaFile.hh b/liveMedia/include/MatroskaFile.hh
index a0b2e5f..df174b7 100644
--- a/liveMedia/include/MatroskaFile.hh
+++ b/liveMedia/include/MatroskaFile.hh
@@ -14,15 +14,15 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A class that encapsulates a Matroska file.
 // C++ header
 
 #ifndef _MATROSKA_FILE_HH
 #define _MATROSKA_FILE_HH
 
-#ifndef _MEDIA_HH
-#include "Media.hh"
+#ifndef _RTP_SINK_HH
+#include "RTPSink.hh"
 #endif
 #ifndef _HASH_TABLE_HH
 #include "HashTable.hh"
@@ -56,6 +56,17 @@ public:
   unsigned chosenAudioTrackNumber() { return fChosenAudioTrackNumber; }
   unsigned chosenSubtitleTrackNumber() { return fChosenSubtitleTrackNumber; }
 
+  FramedSource*
+  createSourceForStreaming(FramedSource* baseSource, unsigned trackNumber,
+			   unsigned& estBitrate, unsigned& numFiltersInFrontOfTrack);
+    // Takes a data source (which must be a demultiplexed track from this file) and returns
+    // a (possibly modified) data source that can be used for streaming.
+
+  RTPSink* createRTPSinkForTrackNumber(unsigned trackNumber, Groupsock* rtpGroupsock,
+				       unsigned char rtpPayloadTypeIfDynamic);
+    // Creates a "RTPSink" object that would be appropriate for streaming the specified track,
+    // or NULL if no appropriate "RTPSink" exists
+
 private:
   MatroskaFile(UsageEnvironment& env, char const* fileName, onCreationFunc* onCreation, void* onCreationClientData,
 	       char const* preferredLanguage);
@@ -117,6 +128,7 @@ public:
   unsigned codecPrivateSize;
   u_int8_t* codecPrivate;
   Boolean codecPrivateUsesH264FormatForH265; // a hack specifically for H.265 video tracks
+  Boolean codecIsOpus; // a hack for Opus audio
   unsigned headerStrippedBytesSize;
   u_int8_t* headerStrippedBytes;
   unsigned subframeSizeSize; // 0 means: frames do not have subframes (the default behavior)
diff --git a/liveMedia/include/MatroskaFileServerDemux.hh b/liveMedia/include/MatroskaFileServerDemux.hh
index e7d75bf..53c2802 100644
--- a/liveMedia/include/MatroskaFileServerDemux.hh
+++ b/liveMedia/include/MatroskaFileServerDemux.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A server demultiplexor for a Matroska file
 // C++ header
 
@@ -54,7 +54,6 @@ public:
   MatroskaFile* ourMatroskaFile() { return fOurMatroskaFile; }
   char const* fileName() const { return fFileName; }
   float fileDuration() const { return fOurMatroskaFile->fileDuration(); }
-  MatroskaTrack* lookup(unsigned trackNumber) { return fOurMatroskaFile->lookup(trackNumber); } // shortcut
 
   FramedSource* newDemuxedTrack(unsigned clientSessionId, unsigned trackNumber);
     // Used by the "ServerMediaSubsession" objects to implement their "createNewStreamSource()" virtual function.
diff --git a/liveMedia/include/Media.hh b/liveMedia/include/Media.hh
index 7705a14..f20029b 100644
--- a/liveMedia/include/Media.hh
+++ b/liveMedia/include/Media.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Medium
 // C++ header
 
@@ -68,7 +68,6 @@ public:
   virtual Boolean isRTSPServer() const;
   virtual Boolean isMediaSession() const;
   virtual Boolean isServerMediaSession() const;
-  virtual Boolean isDarwinInjector() const;
 
 protected:
   friend class MediaLookupTable;
@@ -120,7 +119,7 @@ private:
 class _Tables {
 public:
   static _Tables* getOurTables(UsageEnvironment& env, Boolean createIfNotPresent = True);
-      // returns a pointer to an "ourTables" structure (creating it if necessary)
+      // returns a pointer to a "_Tables" 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 3552756..048dde1 100644
--- a/liveMedia/include/MediaSession.hh
+++ b/liveMedia/include/MediaSession.hh
@@ -14,19 +14,21 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A data structure that represents a session that consists of
 // potentially multiple (audio and/or video) sub-sessions
 // (This data structure is used for media *receivers* - i.e., clients.
 //  For media streamers, use "ServerMediaSession" instead.)
 // C++ header
 
-/* NOTE: To support receiving your own custom RTP payload format, you must first define a new subclass of "MultiFramedRTPSource"
-   (or "BasicUDPSource") that implements it.  Then define your own subclass of "MediaSession" and "MediaSubsession", as follows:
+/* NOTE: To support receiving your own custom RTP payload format, you must first define a new
+   subclass of "MultiFramedRTPSource" (or "BasicUDPSource") that implements it.
+   Then define your own subclass of "MediaSession" and "MediaSubsession", as follows:
    - In your subclass of "MediaSession" (named, for example, "myMediaSession"):
        - Define and implement your own static member function
            static myMediaSession* createNew(UsageEnvironment& env, char const* sdpDescription);
-	 and call this - instead of "MediaSession::createNew()" - in your application, when you create a new "MediaSession" object.
+	 and call this - instead of "MediaSession::createNew()" - in your application,
+	 when you create a new "MediaSession" object.
        - Reimplement the "createNewMediaSubsession()" virtual function, as follows:
            MediaSubsession* myMediaSession::createNewMediaSubsession() { return new myMediaSubsession(*this); }
    - In your subclass of "MediaSubsession" (named, for example, "myMediaSubsession"):
@@ -69,6 +71,7 @@ public:
   char const* CNAME() const { return fCNAME; }
   struct in_addr const& sourceFilterAddr() const { return fSourceFilterAddr; }
   float& scale() { return fScale; }
+  float& speed() { return fSpeed; }
   char* mediaSessionType() const { return fMediaSessionType; }
   char* sessionName() const { return fSessionName; }
   char* sessionDescription() const { return fSessionDescription; }
@@ -130,6 +133,7 @@ protected:
   char* fAbsEndTime;
   struct in_addr fSourceFilterAddr; // used for SSM
   float fScale; // set from a RTSP "Scale:" header
+  float fSpeed;
   char* fMediaSessionType; // holds a=type value
   char* fSessionName; // holds s=<session name> value
   char* fSessionDescription; // holds i=<session description> value
@@ -170,10 +174,12 @@ public:
   unsigned videoFPS() const { return fVideoFPS; }
   unsigned numChannels() const { return fNumChannels; }
   float& scale() { return fScale; }
+  float& speed() { return fSpeed; }
 
   RTPSource* rtpSource() { return fRTPSource; }
   RTCPInstance* rtcpInstance() { return fRTCPInstance; }
   unsigned rtpTimestampFrequency() const { return fRTPTimestampFrequency; }
+  Boolean rtcpIsMuxed() const { return fMultiplexRTCPWithRTP; }
   FramedSource* readSource() { return fReadSource; }
     // This is the source that client sinks read from.  It is usually
     // (but not necessarily) the same as "rtpSource()"
@@ -201,8 +207,8 @@ public:
       // description does not specfy a client port number - an ephemeral
       // (even) port number is chosen.)  This routine must *not* be
       // called after initiate().
-  void receiveRawMP3ADUs() { fReceiveRawMP3ADUs = True; } // optional hack for audio/MPA-ROBUST; must not be called after Initiate()
-  void receiveRawJPEGFrames() { fReceiveRawJPEGFrames = True; } // optional hack for video/JPEG; must not be called after Initiate()
+  void receiveRawMP3ADUs() { fReceiveRawMP3ADUs = True; } // optional hack for audio/MPA-ROBUST; must not be called after initiate()
+  void receiveRawJPEGFrames() { fReceiveRawJPEGFrames = True; } // optional hack for video/JPEG; must not be called after initiate()
   char*& connectionEndpointName() { return fConnectionEndpointName; }
   char const* connectionEndpointName() const {
     return fConnectionEndpointName;
@@ -211,33 +217,23 @@ public:
   // 'Bandwidth' parameter, set in the "b=" SDP line:
   unsigned bandwidth() const { return fBandwidth; }
 
-  // Various parameters set in "a=fmtp:" SDP lines:
-  unsigned fmtp_auxiliarydatasizelength() const { return fAuxiliarydatasizelength; }
-  unsigned fmtp_constantduration() const { return fConstantduration; }
-  unsigned fmtp_constantsize() const { return fConstantsize; }
-  unsigned fmtp_crc() const { return fCRC; }
-  unsigned fmtp_ctsdeltalength() const { return fCtsdeltalength; }
-  unsigned fmtp_de_interleavebuffersize() const { return fDe_interleavebuffersize; }
-  unsigned fmtp_dtsdeltalength() const { return fDtsdeltalength; }
-  unsigned fmtp_indexdeltalength() const { return fIndexdeltalength; }
-  unsigned fmtp_indexlength() const { return fIndexlength; }
-  unsigned fmtp_interleaving() const { return fInterleaving; }
-  unsigned fmtp_maxdisplacement() const { return fMaxdisplacement; }
-  unsigned fmtp_objecttype() const { return fObjecttype; }
-  unsigned fmtp_octetalign() const { return fOctetalign; }
-  unsigned fmtp_profile_level_id() const { return fProfile_level_id; }
-  unsigned fmtp_robustsorting() const { return fRobustsorting; }
-  unsigned fmtp_sizelength() const { return fSizelength; }
-  unsigned fmtp_streamstateindication() const { return fStreamstateindication; }
-  unsigned fmtp_streamtype() const { return fStreamtype; }
-  Boolean fmtp_cpresent() const { return fCpresent; }
-  Boolean fmtp_randomaccessindication() const { return fRandomaccessindication; }
-  char const* fmtp_config() const { return fConfig; }
+  // General SDP attribute accessor functions:
+  char const* attrVal_str(char const* attrName) const;
+      // returns "" if attribute doesn't exist (and has no default value), or is not a string
+  char const* attrVal_strToLower(char const* attrName) const;
+      // returns "" if attribute doesn't exist (and has no default value), or is not a string
+  unsigned attrVal_int(char const* attrName) const;
+      // also returns 0 if attribute doesn't exist (and has no default value)
+  unsigned attrVal_unsigned(char const* attrName) const { return (unsigned)attrVal_int(attrName); }
+  Boolean attrVal_bool(char const* attrName) const { return attrVal_int(attrName) != 0; }
+
+  // Old, now-deprecated SDP attribute accessor functions, kept here for backwards-compatibility:
+  char const* fmtp_config() const;
   char const* fmtp_configuration() const { return fmtp_config(); }
-  char const* fmtp_mode() const { return fMode; }
-  char const* fmtp_spropparametersets() const { return fSpropParameterSets; }
-  char const* fmtp_emphasis() const { return fEmphasis; }
-  char const* fmtp_channelorder() const { return fChannelOrder; }
+  char const* fmtp_spropparametersets() const { return attrVal_str("sprop-parameter-sets"); }
+  char const* fmtp_spropvps() const { return attrVal_str("sprop-vps"); }
+  char const* fmtp_spropsps() const { return attrVal_str("sprop-sps"); }
+  char const* fmtp_sproppps() const { return attrVal_str("sprop-pps"); }
 
   netAddressBits connectionEndpointAddress() const;
       // Converts "fConnectionEndpointName" to an address (or 0 if unknown)
@@ -281,9 +277,12 @@ protected:
   UsageEnvironment& env() { return fParent.envir(); }
   void setNext(MediaSubsession* next) { fNext = next; }
 
+  void setAttribute(char const* name, char const* value = NULL, Boolean valueIsHexadecimal = False);
+
   Boolean parseSDPLine_c(char const* sdpLine);
   Boolean parseSDPLine_b(char const* sdpLine);
   Boolean parseSDPAttribute_rtpmap(char const* sdpLine);
+  Boolean parseSDPAttribute_rtcpmux(char const* sdpLine);
   Boolean parseSDPAttribute_control(char const* sdpLine);
   Boolean parseSDPAttribute_range(char const* sdpLine);
   Boolean parseSDPAttribute_fmtp(char const* sdpLine);
@@ -309,20 +308,11 @@ protected:
   char* fCodecName;
   char* fProtocolName;
   unsigned fRTPTimestampFrequency;
+  Boolean fMultiplexRTCPWithRTP;
   char* fControlPath; // holds optional a=control: string
   struct in_addr fSourceFilterAddr; // used for SSM
   unsigned fBandwidth; // in kilobits-per-second, from b= line
 
-  // Parameters set by "a=fmtp:" SDP lines:
-  unsigned fAuxiliarydatasizelength, fConstantduration, fConstantsize;
-  unsigned fCRC, fCtsdeltalength, fDe_interleavebuffersize, fDtsdeltalength;
-  unsigned fIndexdeltalength, fIndexlength, fInterleaving;
-  unsigned fMaxdisplacement, fObjecttype;
-  unsigned fOctetalign, fProfile_level_id, fRobustsorting;
-  unsigned fSizelength, fStreamstateindication, fStreamtype;
-  Boolean fCpresent, fRandomaccessindication;
-  char *fConfig, *fMode, *fSpropParameterSets, *fEmphasis, *fChannelOrder;
-
   double fPlayStartTime;
   double fPlayEndTime;
   char* fAbsStartTime;
@@ -334,7 +324,9 @@ protected:
   unsigned fNumChannels;
      // optionally set by "a=rtpmap:" lines for audio sessions.  Default: 1
   float fScale; // set from a RTSP "Scale:" header
+  float fSpeed;
   double fNPT_PTS_Offset; // set by "getNormalPlayTime()"; add this to a PTS to get NPT
+  HashTable* fAttributeTable; // for "a=fmtp:" attributes.  (Later an array by payload type #####)
 
   // Fields set or used by initiate():
   Groupsock* fRTPSocket; Groupsock* fRTCPSocket; // works even for unicast
diff --git a/liveMedia/include/MediaSink.hh b/liveMedia/include/MediaSink.hh
index e7430c1..367083a 100644
--- a/liveMedia/include/MediaSink.hh
+++ b/liveMedia/include/MediaSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Media Sinks
 // C++ header
 
@@ -70,10 +70,13 @@ private:
 // A data structure that a sink may use for an output packet:
 class OutPacketBuffer {
 public:
-  OutPacketBuffer(unsigned preferredPacketSize, unsigned maxPacketSize);
+  OutPacketBuffer(unsigned preferredPacketSize, unsigned maxPacketSize,
+		  unsigned maxBufferSize = 0);
+      // if "maxBufferSize" is >0, use it - instead of "maxSize" to compute the buffer size
   ~OutPacketBuffer();
 
   static unsigned maxSize;
+  static void increaseMaxSizeTo(unsigned newMaxSize) { if (newMaxSize > OutPacketBuffer::maxSize) OutPacketBuffer::maxSize = newMaxSize; }
 
   unsigned char* curPtr() const {return &fBuf[fPacketStart + fCurOffset];}
   unsigned totalBytesAvailable() const {
diff --git a/liveMedia/include/MediaSource.hh b/liveMedia/include/MediaSource.hh
index d608a40..a8ad5df 100644
--- a/liveMedia/include/MediaSource.hh
+++ b/liveMedia/include/MediaSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Media Sources
 // C++ header
 
diff --git a/liveMedia/include/MediaTranscodingTable.hh b/liveMedia/include/MediaTranscodingTable.hh
new file mode 100644
index 0000000..694a0c6
--- /dev/null
+++ b/liveMedia/include/MediaTranscodingTable.hh
@@ -0,0 +1,57 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A class that implements a database that can be accessed to create
+// "FramedFilter" (subclass) objects that transcode one codec into another.
+// The implementation of this class just returns NULL for each codec lookup;
+// To actually implement transcoding, you would subclass it.
+// C++ header
+
+#ifndef _MEDIA_TRANSCODING_TABLE_HH
+#define _MEDIA_TRANSCODING_TABLE_HH
+
+#ifndef _FRAMED_FILTER_HH
+#include "FramedFilter.hh"
+#endif
+#ifndef _MEDIA_SESSION_HH
+#include "MediaSession.hh"
+#endif
+
+class MediaTranscodingTable: public Medium {
+public:
+  virtual FramedFilter*
+  lookupTranscoder(MediaSubsession& /*inputCodecDescription*/, // in
+		   char*& outputCodecName/* out; must be delete[]d later */) {
+    // Default implementation: Return NULL (indicating: no transcoding).
+    // You would reimplement this virtual function in a subclass to return a new 'transcoding'
+    // "FramedFilter" (subclass) object for each ("mediumName","codecName") that you wish to
+    // transcode (or return NULL for no transcoding).
+    // (Note that "inputCodecDescription" must have a non-NULL "readSource()"; this is used
+    //  as the input to the new "FramedFilter" (subclass) object.)
+    outputCodecName = NULL;
+    return NULL;
+  }
+
+protected: // we are to be subclassed only
+  MediaTranscodingTable(UsageEnvironment& env)
+    : Medium(env) {
+  }
+  virtual ~MediaTranscodingTable() {
+  }
+};
+
+#endif
diff --git a/liveMedia/include/MultiFramedRTPSink.hh b/liveMedia/include/MultiFramedRTPSink.hh
index 594f339..7215818 100644
--- a/liveMedia/include/MultiFramedRTPSink.hh
+++ b/liveMedia/include/MultiFramedRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for a common kind of payload format: Those which pack multiple,
 // complete codec frames (as many as possible) into each RTP packet.
 // C++ header
diff --git a/liveMedia/include/MultiFramedRTPSource.hh b/liveMedia/include/MultiFramedRTPSource.hh
index 97c0dee..424fc3f 100644
--- a/liveMedia/include/MultiFramedRTPSource.hh
+++ b/liveMedia/include/MultiFramedRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP source for a common kind of payload format: Those which pack multiple,
 // complete codec frames (as many as possible) into each RTP packet.
 // C++ header
@@ -91,7 +91,7 @@ public:
   Boolean hasUsableData() const { return fTail > fHead; }
   unsigned useCount() const { return fUseCount; }
 
-  Boolean fillInData(RTPInterface& rtpInterface, Boolean& packetReadWasIncomplete);
+  Boolean fillInData(RTPInterface& rtpInterface, struct sockaddr_in& fromAddress, Boolean& packetReadWasIncomplete);
   void assignMiscParams(unsigned short rtpSeqNo, unsigned rtpTimestamp,
 			struct timeval presentationTime,
 			Boolean hasBeenSyncedUsingRTCP,
diff --git a/liveMedia/include/OggFile.hh b/liveMedia/include/OggFile.hh
new file mode 100644
index 0000000..ee9bcc8
--- /dev/null
+++ b/liveMedia/include/OggFile.hh
@@ -0,0 +1,177 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A class that encapsulates an Ogg file
+// C++ header
+
+#ifndef _OGG_FILE_HH
+#define _OGG_FILE_HH
+
+#ifndef _RTP_SINK_HH
+#include "RTPSink.hh"
+#endif
+#ifndef _HASH_TABLE_HH
+#include "HashTable.hh"
+#endif
+
+class OggTrack; // forward
+class OggDemux; // forward
+
+class OggFile: public Medium {
+public:
+  typedef void (onCreationFunc)(OggFile* newFile, void* clientData);
+  static void createNew(UsageEnvironment& env, char const* fileName,
+			onCreationFunc* onCreation, void* onCreationClientData);
+      // Note: Unlike most "createNew()" functions, this one doesn't return a new object
+      // immediately.  Instead, because this class requires file reading (to parse the
+      // Ogg track headers) before a new object can be initialized, the creation of a new object
+      // is signalled by calling - from the event loop - an 'onCreationFunc' that is passed as
+      // a parameter to "createNew()".
+
+  OggTrack* lookup(u_int32_t trackNumber);
+
+  OggDemux* newDemux();
+      // Creates a demultiplexor for extracting tracks from this file.
+      // (Separate clients will typically have separate demultiplexors.)
+
+  char const* fileName() const { return fFileName; }
+  unsigned numTracks() const;
+
+  FramedSource*
+  createSourceForStreaming(FramedSource* baseSource, u_int32_t trackNumber,
+                           unsigned& estBitrate, unsigned& numFiltersInFrontOfTrack);
+    // Takes a data source (which must be a demultiplexed track from this file) and returns
+    // a (possibly modified) data source that can be used for streaming.
+
+  RTPSink* createRTPSinkForTrackNumber(u_int32_t trackNumber, Groupsock* rtpGroupsock,
+                                       unsigned char rtpPayloadTypeIfDynamic);
+    // Creates a "RTPSink" object that would be appropriate for streaming the specified track,
+    // or NULL if no appropriate "RTPSink" exists
+
+  class OggTrackTable& trackTable() { return *fTrackTable; }
+
+private:
+  OggFile(UsageEnvironment& env, char const* fileName, onCreationFunc* onCreation, void* onCreationClientData);
+    // called only by createNew()
+  virtual ~OggFile();
+
+  static void handleEndOfBosPageParsing(void* clientData);
+  void handleEndOfBosPageParsing();
+
+  void addTrack(OggTrack* newTrack);
+  void removeDemux(OggDemux* demux);
+
+private:
+  friend class OggFileParser;
+  friend class OggDemux;
+  char const* fFileName;
+  onCreationFunc* fOnCreation;
+  void* fOnCreationClientData;
+
+  class OggTrackTable* fTrackTable;
+  HashTable* fDemuxesTable;
+  class OggFileParser* fParserForInitialization;
+};
+
+class OggTrack {
+public:
+  OggTrack();
+  virtual ~OggTrack();
+
+  // track parameters
+  u_int32_t trackNumber; // bitstream serial number
+  char const* mimeType; // NULL if not known
+
+  unsigned samplingFrequency, numChannels; // for audio tracks
+  unsigned estBitrate; // estimate, in kbps (for RTCP)
+
+  // Special headers for Vorbis audio, Theora video, and Opus audio tracks:
+  struct _vtoHdrs {
+    u_int8_t* header[3]; // "identification", "comment", "setup"
+    unsigned headerSize[3];
+
+    // Fields specific to Vorbis audio:
+    unsigned blocksize[2]; // samples per frame (packet)
+    unsigned uSecsPerPacket[2]; // computed as (blocksize[i]*1000000)/samplingFrequency
+    unsigned vorbis_mode_count;
+    unsigned ilog_vorbis_mode_count_minus_1;
+    u_int8_t* vorbis_mode_blockflag;
+        // an array (of size "vorbis_mode_count") of indexes into the (2-entry) "blocksize" array
+
+    // Fields specific to Theora video:
+    u_int8_t KFGSHIFT;
+    unsigned uSecsPerFrame;
+
+  } vtoHdrs;
+
+  Boolean weNeedHeaders() const {
+    return
+      vtoHdrs.header[0] == NULL ||
+      vtoHdrs.header[1] == NULL ||
+      (vtoHdrs.header[2] == NULL && strcmp(mimeType, "audio/OPUS") != 0);
+    }
+};
+
+class OggTrackTableIterator {
+public:
+  OggTrackTableIterator(class OggTrackTable& ourTable);
+  virtual ~OggTrackTableIterator();
+
+  OggTrack* next();
+
+private:
+  HashTable::Iterator* fIter;
+};
+
+class OggDemux: public Medium {
+public:
+  FramedSource* newDemuxedTrack(u_int32_t& resultTrackNumber);
+    // Returns a new stream ("FramedSource" subclass) that represents the next media track
+    // from the file.  This function returns NULL when no more media tracks exist.
+
+  FramedSource* newDemuxedTrackByTrackNumber(unsigned trackNumber);
+      // As above, but creates a new stream for a specific track number within the Matroska file.
+      // (You should not call this function more than once with the same track number.)
+
+      // Note: We assume that:
+      // - Every track created by "newDemuxedTrack()" is later read
+      // - All calls to "newDemuxedTrack()" are made before any track is read
+
+protected:
+  friend class OggFile;
+  friend class OggFileParser;
+  class OggDemuxedTrack* lookupDemuxedTrack(u_int32_t trackNumber);
+
+  OggDemux(OggFile& ourFile);
+  virtual ~OggDemux();
+
+private:
+  friend class OggDemuxedTrack;
+  void removeTrack(u_int32_t trackNumber);
+  void continueReading(); // called by a demuxed track to tell us that it has a pending read ("doGetNextFrame()")
+
+  static void handleEndOfFile(void* clientData);
+  void handleEndOfFile();
+
+private:
+  OggFile& fOurFile;
+  class OggFileParser* fOurParser;
+  HashTable* fDemuxedTracksTable;
+  OggTrackTableIterator* fIter;
+};
+
+#endif
diff --git a/liveMedia/include/MatroskaFileServerDemux.hh b/liveMedia/include/OggFileServerDemux.hh
similarity index 50%
copy from liveMedia/include/MatroskaFileServerDemux.hh
copy to liveMedia/include/OggFileServerDemux.hh
index e7d75bf..51dd0ad 100644
--- a/liveMedia/include/MatroskaFileServerDemux.hh
+++ b/liveMedia/include/OggFileServerDemux.hh
@@ -14,72 +14,68 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// A server demultiplexor for a Matroska file
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// A server demultiplexor for an Ogg file
 // C++ header
 
-#ifndef _MATROSKA_FILE_SERVER_DEMUX_HH
-#define _MATROSKA_FILE_SERVER_DEMUX_HH
+#ifndef _OGG_FILE_SERVER_DEMUX_HH
+#define _OGG_FILE_SERVER_DEMUX_HH
 
 #ifndef _SERVER_MEDIA_SESSION_HH
 #include "ServerMediaSession.hh"
 #endif
 
-#ifndef _MATROSKA_FILE_HH
-#include "MatroskaFile.hh"
+#ifndef _OGG_FILE_HH
+#include "OggFile.hh"
 #endif
 
-class MatroskaFileServerDemux: public Medium {
+class OggFileServerDemux: public Medium {
 public:
-  typedef void (onCreationFunc)(MatroskaFileServerDemux* newDemux, void* clientData);
+  typedef void (onCreationFunc)(OggFileServerDemux* newDemux, void* clientData);
   static void createNew(UsageEnvironment& env, char const* fileName,
-			onCreationFunc* onCreation, void* onCreationClientData,
-			char const* preferredLanguage = "eng");
+			onCreationFunc* onCreation, void* onCreationClientData);
     // Note: Unlike most "createNew()" functions, this one doesn't return a new object immediately.  Instead, because this class
-    // requires file reading (to parse the Matroska 'Track' headers) before a new object can be initialized, the creation of a new
+    // requires file reading (to parse the Ogg 'Track' headers) before a new object can be initialized, the creation of a new
     // object is signalled by calling - from the event loop - an 'onCreationFunc' that is passed as a parameter to "createNew()". 
 
   ServerMediaSubsession* newServerMediaSubsession();
-  ServerMediaSubsession* newServerMediaSubsession(unsigned& resultTrackNumber);
-    // Returns a new "ServerMediaSubsession" object that represents the next preferred media track
-    // (video, audio, subtitle - in that order) from the file. (Preferred media tracks are based on the file's language preference.)
-    // This function returns NULL when no more media tracks exist.
+  ServerMediaSubsession* newServerMediaSubsession(u_int32_t& resultTrackNumber);
+    // Returns a new "ServerMediaSubsession" object that represents the next media track
+    // from the file.  This function returns NULL when no more media tracks exist.
 
-  ServerMediaSubsession* newServerMediaSubsessionByTrackNumber(unsigned trackNumber);
-    // As above, but creates a new "ServerMediaSubsession" object for a specific track number within the Matroska file.
-    // (You should not call this function more than once with the same track number.)
+  ServerMediaSubsession* newServerMediaSubsessionByTrackNumber(u_int32_t trackNumber);
+  // As above, but creates a new "ServerMediaSubsession" object for a specific track number
+  // within the Ogg file.
+  // (You should not call this function more than once with the same track number.)
 
   // The following public: member functions are called only by the "ServerMediaSubsession" objects:
 
-  MatroskaFile* ourMatroskaFile() { return fOurMatroskaFile; }
+  OggFile* ourOggFile() { return fOurOggFile; }
   char const* fileName() const { return fFileName; }
-  float fileDuration() const { return fOurMatroskaFile->fileDuration(); }
-  MatroskaTrack* lookup(unsigned trackNumber) { return fOurMatroskaFile->lookup(trackNumber); } // shortcut
 
-  FramedSource* newDemuxedTrack(unsigned clientSessionId, unsigned trackNumber);
+  FramedSource* newDemuxedTrack(unsigned clientSessionId, u_int32_t trackNumber);
     // Used by the "ServerMediaSubsession" objects to implement their "createNewStreamSource()" virtual function.
 
 private:
-  MatroskaFileServerDemux(UsageEnvironment& env, char const* fileName,
-			  onCreationFunc* onCreation, void* onCreationClientData,
-			  char const* preferredLanguage);
+  OggFileServerDemux(UsageEnvironment& env, char const* fileName,
+		     onCreationFunc* onCreation, void* onCreationClientData);
       // called only by createNew()
-  virtual ~MatroskaFileServerDemux();
+  virtual ~OggFileServerDemux();
 
-  static void onMatroskaFileCreation(MatroskaFile* newFile, void* clientData);
-  void onMatroskaFileCreation(MatroskaFile* newFile);
+  static void onOggFileCreation(OggFile* newFile, void* clientData);
+  void onOggFileCreation(OggFile* newFile);
 private:
   char const* fFileName; 
   onCreationFunc* fOnCreation;
   void* fOnCreationClientData;
-  MatroskaFile* fOurMatroskaFile;
+  OggFile* fOurOggFile;
 
   // Used to implement "newServerMediaSubsession()":
-  u_int8_t fNextTrackTypeToCheck;
+  OggTrackTableIterator* fIter;
 
   // Used to set up demuxing, to implement "newDemuxedTrack()":
   unsigned fLastClientSessionId;
-  MatroskaDemux* fLastCreatedDemux;
+  OggDemux* fLastCreatedDemux;
 };
 
 #endif
diff --git a/liveMedia/include/OggFileSink.hh b/liveMedia/include/OggFileSink.hh
new file mode 100644
index 0000000..bbb2a9a
--- /dev/null
+++ b/liveMedia/include/OggFileSink.hh
@@ -0,0 +1,79 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// 'Ogg' File Sink (recording a single media track only)
+// C++ header
+
+#ifndef _OGG_FILE_SINK_HH
+#define _OGG_FILE_SINK_HH
+
+#ifndef _FILE_SINK_HH
+#include "FileSink.hh"
+#endif
+
+class OggFileSink: public FileSink {
+public:
+  static OggFileSink* createNew(UsageEnvironment& env, char const* fileName,
+				unsigned samplingFrequency = 0, // used for granule_position
+				char const* configStr = NULL,
+      // "configStr" is an optional 'SDP format' string (Base64-encoded)
+      // representing 'packed configuration headers' ("identification", "comment", "setup")
+      // to prepend to the output.  (For 'Vorbis" audio and 'Theora' video.)
+				unsigned bufferSize = 100000,
+				Boolean oneFilePerFrame = False);
+      // See "FileSink.hh" for a description of these parameters.
+
+protected:
+  OggFileSink(UsageEnvironment& env, FILE* fid, unsigned samplingFrequency, char const* configStr,
+	      unsigned bufferSize, char const* perFrameFileNamePrefix);
+      // called only by createNew()
+  virtual ~OggFileSink();
+
+protected: // redefined virtual functions:
+  virtual Boolean continuePlaying();
+  virtual void addData(unsigned char const* data, unsigned dataSize,
+		       struct timeval presentationTime);
+  virtual void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
+				 struct timeval presentationTime);
+
+private:
+  static void ourOnSourceClosure(void* clientData);
+  void ourOnSourceClosure();
+
+private:
+  unsigned fSamplingFrequency;
+  char const* fConfigStr;
+  Boolean fHaveWrittenFirstFrame, fHaveSeenEOF;
+  struct timeval fFirstPresentationTime;
+  int64_t fGranulePosition;
+  int64_t fGranulePositionAdjustment; // used to ensure that "fGranulePosition" stays monotonic
+  u_int32_t fPageSequenceNumber;
+  u_int8_t fPageHeaderBytes[27];
+      // the header of each Ogg page, through the "number_page_segments" byte
+
+  // Special fields used for Theora video:
+  Boolean fIsTheora;
+  u_int64_t fGranuleIncrementPerFrame; // == 1 << KFGSHIFT
+
+  // Because the last Ogg page before EOF needs to have a special 'eos' bit set in the header,
+  // we need to defer the writing of each incoming frame.  To do this, we maintain a 2nd buffer:
+  unsigned char* fAltBuffer;
+  unsigned fAltFrameSize, fAltNumTruncatedBytes;
+  struct timeval fAltPresentationTime;
+};
+
+#endif
diff --git a/liveMedia/include/OnDemandServerMediaSubsession.hh b/liveMedia/include/OnDemandServerMediaSubsession.hh
index d916d8c..8a8affd 100644
--- a/liveMedia/include/OnDemandServerMediaSubsession.hh
+++ b/liveMedia/include/OnDemandServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand.
 // C++ header
@@ -38,7 +38,8 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 class OnDemandServerMediaSubsession: public ServerMediaSubsession {
 protected: // we're a virtual base class
   OnDemandServerMediaSubsession(UsageEnvironment& env, Boolean reuseFirstSource,
-				portNumBits initialPortNum = 6970);
+				portNumBits initialPortNum = 6970,
+				Boolean multiplexRTCPWithRTP = False);
   virtual ~OnDemandServerMediaSubsession();
 
 protected: // redefined virtual functions
@@ -66,10 +67,13 @@ protected: // redefined virtual functions
   virtual void pauseStream(unsigned clientSessionId, void* streamToken);
   virtual void seekStream(unsigned clientSessionId, void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes);
   virtual void seekStream(unsigned clientSessionId, void* streamToken, char*& absStart, char*& absEnd);
-  virtual void nullSeekStream(unsigned clientSessionId, void* streamToken);
+  virtual void nullSeekStream(unsigned clientSessionId, void* streamToken,
+			      double streamEndTime, u_int64_t& numBytes);
   virtual void setStreamScale(unsigned clientSessionId, void* streamToken, float scale);
   virtual float getCurrentNPT(void* streamToken);
   virtual FramedSource* getStreamSource(void* streamToken);
+  virtual void getRTPSinkandRTCP(void* streamToken,
+				 RTPSink const*& rtpSink, RTCPInstance const*& rtcp);
   virtual void deleteStream(unsigned clientSessionId, void*& streamToken);
 
 protected: // new virtual functions, possibly redefined by subclasses
@@ -85,6 +89,7 @@ protected: // new virtual functions, possibly redefined by subclasses
     // "absEnd" should be either NULL (for no end time), or a string of the same form as "absStart".
     // These strings may be modified in-place, or can be reassigned to a newly-allocated value (after delete[]ing the original).
   virtual void setStreamSourceScale(FramedSource* inputSource, float scale);
+  virtual void setStreamSourceDuration(FramedSource* inputSource, double streamDuration, u_int64_t& numBytes);
   virtual void closeStreamSource(FramedSource* inputSource);
 
 protected: // new virtual functions, defined by all subclasses
@@ -95,6 +100,31 @@ protected: // new virtual functions, defined by all subclasses
 				    unsigned char rtpPayloadTypeIfDynamic,
 				    FramedSource* inputSource) = 0;
 
+protected: // new virtual functions, may be redefined by a subclass:
+  virtual Groupsock* createGroupsock(struct in_addr const& addr, Port port);
+  virtual RTCPInstance* createRTCP(Groupsock* RTCPgs, unsigned totSessionBW, /* in kbps */
+				   unsigned char const* cname, RTPSink* sink);
+
+public:
+  void multiplexRTCPWithRTP() { fMultiplexRTCPWithRTP = True; }
+    // An alternative to passing the "multiplexRTCPWithRTP" parameter as True in the constructor
+
+  void setRTCPAppPacketHandler(RTCPAppHandlerFunc* handler, void* clientData);
+    // Sets a handler to be called if a RTCP "APP" packet arrives from any future client.
+    // (Any current clients are not affected; any "APP" packets from them will continue to be
+    // handled by whatever handler existed when the client sent its first RTSP "PLAY" command.)
+    // (Call with (NULL, NULL) to remove an existing handler - for future clients only)
+
+  void sendRTCPAppPacket(u_int8_t subtype, char const* name,
+			 u_int8_t* appDependentData, unsigned appDependentDataSize);
+    // Sends a custom RTCP "APP" packet to the most recent client (if "reuseFirstSource" was False),
+    // or to all current clients (if "reuseFirstSource" was True).
+    // The parameters correspond to their
+    // respective fields as described in the RTP/RTCP definition (RFC 3550).
+    // Note that only the low-order 5 bits of "subtype" are used, and only the first 4 bytes
+    // of "name" are used.  (If "name" has fewer than 4 bytes, or is NULL,
+    // then the remaining bytes are '\0'.)
+
 private:
   void setSDPLinesFromRTPSink(RTPSink* rtpSink, FramedSource* inputSource,
 			      unsigned estBitrate);
@@ -107,8 +137,11 @@ protected:
 private:
   Boolean fReuseFirstSource;
   portNumBits fInitialPortNum;
+  Boolean fMultiplexRTCPWithRTP;
   void* fLastStreamToken;
   char fCNAME[100]; // for RTCP
+  RTCPAppHandlerFunc* fAppHandlerTask;
+  void* fAppHandlerClientData;
   friend class StreamState;
 };
 
@@ -147,12 +180,14 @@ public:
 	      Groupsock* rtpGS, Groupsock* rtcpGS);
   virtual ~StreamState();
 
-  void startPlaying(Destinations* destinations,
+  void startPlaying(Destinations* destinations, unsigned clientSessionId,
 		    TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData,
 		    ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
                     void* serverRequestAlternativeByteHandlerClientData);
   void pause();
-  void endPlaying(Destinations* destinations);
+  void sendRTCPAppPacket(u_int8_t subtype, char const* name,
+			 u_int8_t* appDependentData, unsigned appDependentDataSize);
+  void endPlaying(Destinations* destinations, unsigned clientSessionId);
   void reclaim();
 
   unsigned& referenceCount() { return fReferenceCount; }
@@ -161,6 +196,7 @@ public:
   Port const& serverRTCPPort() const { return fServerRTCPPort; }
 
   RTPSink* rtpSink() const { return fRTPSink; }
+  RTCPInstance* rtcpInstance() const { return fRTCPInstance; }
 
   float streamDuration() const { return fStreamDuration; }
 
diff --git a/liveMedia/include/OutputFile.hh b/liveMedia/include/OutputFile.hh
index caca04a..da1bd06 100644
--- a/liveMedia/include/OutputFile.hh
+++ b/liveMedia/include/OutputFile.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Common routines for opening/closing named output files
 // C++ header
 
diff --git a/liveMedia/include/PassiveServerMediaSubsession.hh b/liveMedia/include/PassiveServerMediaSubsession.hh
index 76062ff..809ca5b 100644
--- a/liveMedia/include/PassiveServerMediaSubsession.hh
+++ b/liveMedia/include/PassiveServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that represents an existing
 // 'RTPSink', rather than one that creates new 'RTPSink's on demand.
 // C++ header
@@ -43,6 +43,8 @@ protected:
       // called only by createNew();
   virtual ~PassiveServerMediaSubsession();
 
+  virtual Boolean rtcpIsMuxed();
+
 protected: // redefined virtual functions
   virtual char const* sdpLines();
   virtual void getStreamParameters(unsigned clientSessionId,
@@ -66,6 +68,8 @@ protected: // redefined virtual functions
 			   ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
                            void* serverRequestAlternativeByteHandlerClientData);
   virtual float getCurrentNPT(void* streamToken);
+  virtual void getRTPSinkandRTCP(void* streamToken,
+				 RTPSink const*& rtpSink, RTCPInstance const*& rtcp);
   virtual void deleteStream(unsigned clientSessionId, void*& streamToken);
 
 protected:
diff --git a/liveMedia/include/ProxyServerMediaSession.hh b/liveMedia/include/ProxyServerMediaSession.hh
index 9069957..07c3002 100644
--- a/liveMedia/include/ProxyServerMediaSession.hh
+++ b/liveMedia/include/ProxyServerMediaSession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A subclass of "ServerMediaSession" that can be used to create a (unicast) RTSP servers that acts as a 'proxy' for
 // another (unicast or multicast) RTSP/RTP stream.
 // C++ header
@@ -31,6 +31,9 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #ifndef _RTSP_CLIENT_HH
 #include "RTSPClient.hh"
 #endif
+#ifndef _MEDIA_TRANSCODING_TABLE_HH
+#include "MediaTranscodingTable.hh"
+#endif
 
 // A subclass of "RTSPClient", used to refer to the particular "ProxyServerMediaSession" object being used.
 // It is used only within the implementation of "ProxyServerMediaSession", but is defined here, in case developers wish to
@@ -45,7 +48,8 @@ public:
 
   void continueAfterDESCRIBE(char const* sdpDescription);
   void continueAfterLivenessCommand(int resultCode, Boolean serverSupportsGetParameter);
-  void continueAfterSETUP();
+  void continueAfterSETUP(int resultCode);
+  void continueAfterPLAY(int resultCode);
 
 private:
   void reset();
@@ -71,7 +75,7 @@ private:
   class ProxyServerMediaSubsession *fSetupQueueHead, *fSetupQueueTail;
   unsigned fNumSetupsDone;
   unsigned fNextDESCRIBEDelay; // in seconds
-  Boolean fServerSupportsGetParameter, fLastCommandWasPLAY;
+  Boolean fServerSupportsGetParameter, fLastCommandWasPLAY, fResetOnNextLivenessTest;
   TaskToken fLivenessCommandTask, fDESCRIBECommandTask, fSubsessionTimerTask;
 };
 
@@ -92,14 +96,15 @@ defaultCreateNewProxyRTSPClientFunc(ProxyServerMediaSession& ourServerMediaSessi
 class ProxyServerMediaSession: public ServerMediaSession {
 public:
   static ProxyServerMediaSession* createNew(UsageEnvironment& env,
-					    RTSPServer* ourRTSPServer, // Note: We can be used by just one "RTSPServer"
+					    GenericMediaServer* ourMediaServer, // Note: We can be used by just one server
 					    char const* inputStreamURL, // the "rtsp://" URL of the stream we'll be proxying
 					    char const* streamName = NULL,
 					    char const* username = NULL, char const* password = NULL,
 					    portNumBits tunnelOverHTTPPortNum = 0,
 					        // for streaming the *proxied* (i.e., back-end) stream
 					    int verbosityLevel = 0,
-					    int socketNumToServer = -1);
+					    int socketNumToServer = -1,
+					    MediaTranscodingTable* transcodingTable = NULL);
       // Hack: "tunnelOverHTTPPortNum" == 0xFFFF (i.e., all-ones) means: Stream RTP/RTCP-over-TCP, but *not* using HTTP
       // "verbosityLevel" == 1 means display basic proxy setup info; "verbosityLevel" == 2 means display RTSP client protocol also.
       // If "socketNumToServer" is >= 0, then it is the socket number of an already-existing TCP connection to the server.
@@ -116,13 +121,16 @@ public:
     // This can be used - along with "describeCompletdFlag" - to check whether the back-end "DESCRIBE" completed *successfully*.
 
 protected:
-  ProxyServerMediaSession(UsageEnvironment& env, RTSPServer* ourRTSPServer,
+  ProxyServerMediaSession(UsageEnvironment& env, GenericMediaServer* ourMediaServer,
 			  char const* inputStreamURL, char const* streamName,
 			  char const* username, char const* password,
 			  portNumBits tunnelOverHTTPPortNum, int verbosityLevel,
 			  int socketNumToServer,
+			  MediaTranscodingTable* transcodingTable,
 			  createNewProxyRTSPClientFunc* ourCreateNewProxyRTSPClientFunc
-			  = defaultCreateNewProxyRTSPClientFunc);
+			  = defaultCreateNewProxyRTSPClientFunc,
+			  portNumBits initialPortNum = 6970,
+			  Boolean multiplexRTCPWithRTP = False);
 
   // If you subclass "ProxyRTSPClient", then you will also need to define your own function
   // - with signature "createNewProxyRTSPClientFunc" (see above) - that creates a new object
@@ -131,8 +139,14 @@ protected:
   // constructor by passing your new function as the "ourCreateNewProxyRTSPClientFunc"
   // parameter.
 
+  // Subclasses may redefine the following functions, if they want "ProxyServerSubsession"s
+  // to create subclassed "Groupsock" and/or "RTCPInstance" objects:
+  virtual Groupsock* createGroupsock(struct in_addr const& addr, Port port);
+  virtual RTCPInstance* createRTCP(Groupsock* RTCPgs, unsigned totSessionBW, /* in kbps */
+				   unsigned char const* cname, RTPSink* sink);
+
 protected:
-  RTSPServer* fOurRTSPServer;
+  GenericMediaServer* fOurMediaServer;
   ProxyRTSPClient* fProxyRTSPClient;
   MediaSession* fClientMediaSession;
 
@@ -146,6 +160,9 @@ private:
   int fVerbosityLevel;
   class PresentationTimeSessionNormalizer* fPresentationTimeSessionNormalizer;
   createNewProxyRTSPClientFunc* fCreateNewProxyRTSPClientFunc;
+  MediaTranscodingTable* fTranscodingTable;
+  portNumBits fInitialPortNum;
+  Boolean fMultiplexRTCPWithRTP;
 };
 
 
diff --git a/liveMedia/include/QCELPAudioRTPSource.hh b/liveMedia/include/QCELPAudioRTPSource.hh
index 9cd000a..e72ae6e 100644
--- a/liveMedia/include/QCELPAudioRTPSource.hh
+++ b/liveMedia/include/QCELPAudioRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Qualcomm "PureVoice" (aka. "QCELP") Audio RTP Sources
 // C++ header
 
diff --git a/liveMedia/include/QuickTimeFileSink.hh b/liveMedia/include/QuickTimeFileSink.hh
index 95c672e..0bea22f 100644
--- a/liveMedia/include/QuickTimeFileSink.hh
+++ b/liveMedia/include/QuickTimeFileSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A sink that generates a QuickTime file from a composite media session
 // C++ header
 
@@ -45,7 +45,7 @@ public:
 
   unsigned numActiveSubsessions() const { return fNumSubsessions; }
 
-private:
+protected:
   QuickTimeFileSink(UsageEnvironment& env, MediaSession& inputSession,
 		    char const* outputFileName, unsigned bufferSize,
 		    unsigned short movieWidth, unsigned short movieHeight,
@@ -55,6 +55,10 @@ private:
       // called only by createNew()
   virtual ~QuickTimeFileSink();
 
+  virtual void noteRecordedFrame(MediaSubsession& inputSubsession,
+				 unsigned packetDataSize, struct timeval const& presentationTime);
+
+private:
   Boolean continuePlaying();
   static void afterGettingFrame(void* clientData, unsigned frameSize,
 				unsigned numTruncatedBytes,
diff --git a/liveMedia/include/QuickTimeGenericRTPSource.hh b/liveMedia/include/QuickTimeGenericRTPSource.hh
index 1d140e0..12d3b82 100644
--- a/liveMedia/include/QuickTimeGenericRTPSource.hh
+++ b/liveMedia/include/QuickTimeGenericRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP Sources containing generic QuickTime stream data, as defined in
 //     <http://developer.apple.com/quicktime/icefloe/dispatch026.html>
 // C++ header
diff --git a/liveMedia/include/RTCP.hh b/liveMedia/include/RTCP.hh
index 34bc323..a1069ed 100644
--- a/liveMedia/include/RTCP.hh
+++ b/liveMedia/include/RTCP.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTCP
 // C++ header
 
@@ -39,6 +39,10 @@ private:
   unsigned char fData[2 + 0xFF]; // first 2 bytes are tag and length
 };
 
+typedef void RTCPAppHandlerFunc(void* clientData,
+				u_int8_t subtype, u_int32_t nameBytes/*big-endian order*/,
+				u_int8_t* appDependentData, unsigned appDependentDataSize);
+
 class RTCPMemberDatabase; // forward
 
 class RTCPInstance: public Medium {
@@ -47,7 +51,7 @@ public:
 				 unsigned totSessionBW, /* in kbps */
 				 unsigned char const* cname,
 				 RTPSink* sink,
-				 RTPSource const* source,
+				 RTPSource* source,
 				 Boolean isSSMSource = False);
 
   static Boolean lookupByName(UsageEnvironment& env, char const* instanceName,
@@ -71,16 +75,26 @@ public:
       // (To remove an existing "BYE" handler, call "setByeHandler()" again, with a "handlerTask" of NULL.)
   void setSRHandler(TaskFunc* handlerTask, void* clientData);
   void setRRHandler(TaskFunc* handlerTask, void* clientData);
-      // Assigns a handler routine to be called if a "SR" or "RR"
+      // Assigns a handler routine to be called if a "SR" or "RR" packet
       // (respectively) arrives.  Unlike "setByeHandler()", the handler will
       // be called once for each incoming "SR" or "RR".  (To turn off handling,
-      // call the function again with "handlerTask" (and "clientData") as NULL.
+      // call the function again with "handlerTask" (and "clientData") as NULL.)
   void setSpecificRRHandler(netAddressBits fromAddress, Port fromPort,
 			    TaskFunc* handlerTask, void* clientData);
       // Like "setRRHandler()", but applies only to "RR" packets that come from
       // a specific source address and port.  (Note that if both a specific
       // and a general "RR" handler function is set, then both will be called.)
   void unsetSpecificRRHandler(netAddressBits fromAddress, Port fromPort); // equivalent to setSpecificRRHandler(..., NULL, NULL);
+  void setAppHandler(RTCPAppHandlerFunc* handlerTask, void* clientData);
+      // Assigns a handler routine to be called whenever an "APP" packet arrives.  (To turn off
+      // handling, call the function again with "handlerTask" (and "clientData") as NULL.)
+  void sendAppPacket(u_int8_t subtype, char const* name,
+		     u_int8_t* appDependentData, unsigned appDependentDataSize);
+      // Sends a custom RTCP "APP" packet to the peer(s).  The parameters correspond to their
+      // respective fields as described in the RTP/RTCP definition (RFC 3550).
+      // Note that only the low-order 5 bits of "subtype" are used, and only the first 4 bytes
+      // of "name" are used.  (If "name" has fewer than 4 bytes, or is NULL,
+      // then the remaining bytes are '\0'.)
 
   Groupsock* RTCPgs() const { return fRTCPInterface.gs(); }
 
@@ -97,14 +111,22 @@ public:
 					    handlerClientData);
   }
 
+  void injectReport(u_int8_t const* packet, unsigned packetSize, struct sockaddr_in const& fromAddress);
+    // Allows an outside party to inject an RTCP report (from other than the network interface)
+
 protected:
   RTCPInstance(UsageEnvironment& env, Groupsock* RTPgs, unsigned totSessionBW,
 	       unsigned char const* cname,
-	       RTPSink* sink, RTPSource const* source,
+	       RTPSink* sink, RTPSource* source,
 	       Boolean isSSMSource);
       // called only by createNew()
   virtual ~RTCPInstance();
 
+  virtual void noteArrivingRR(struct sockaddr_in const& fromAddressAndPort,
+			      int tcpSocketNum, unsigned char tcpStreamChannelId);
+
+  void incomingReportHandler1();
+
 private:
   // redefined virtual functions:
   virtual Boolean isRTCPInstance() const;
@@ -126,17 +148,18 @@ private:
   void onExpire1();
 
   static void incomingReportHandler(RTCPInstance* instance, int /*mask*/);
-  void incomingReportHandler1();
+  void processIncomingReport(unsigned packetSize, struct sockaddr_in const& fromAddressAndPort,
+			     int tcpSocketNum, unsigned char tcpStreamChannelId);
   void onReceive(int typeOfPacket, int totPacketSize, u_int32_t ssrc);
 
 private:
-  unsigned char* fInBuf;
+  u_int8_t* fInBuf;
   unsigned fNumBytesAlreadyRead;
   OutPacketBuffer* fOutBuf;
   RTPInterface fRTCPInterface;
   unsigned fTotSessionBW;
   RTPSink* fSink;
-  RTPSource const* fSource;
+  RTPSource* fSource;
   Boolean fIsSSMSource;
 
   SDESItem fCNAME;
@@ -165,6 +188,8 @@ private:
   TaskFunc* fRRHandlerTask;
   void* fRRHandlerClientData;
   AddressPortLookupTable* fSpecificRRHandlerTable;
+  RTCPAppHandlerFunc* fAppHandlerTask;
+  void* fAppHandlerClientData;
 
 public: // because this stuff is used by an external "C" function
   void schedule(double nextTime);
@@ -186,6 +211,13 @@ const unsigned char RTCP_PT_RR = 201;
 const unsigned char RTCP_PT_SDES = 202;
 const unsigned char RTCP_PT_BYE = 203;
 const unsigned char RTCP_PT_APP = 204;
+const unsigned char RTCP_PT_RTPFB = 205; // Generic RTP Feedback [RFC4585]
+const unsigned char RTCP_PT_PSFB = 206; // Payload-specific [RFC4585]
+const unsigned char RTCP_PT_XR = 207; // extended report [RFC3611]
+const unsigned char RTCP_PT_AVB = 208; // AVB RTCP packet ["Standard for Layer 3 Transport Protocol for Time Sensitive Applications in Local Area Networks." Work in progress.]
+const unsigned char RTCP_PT_RSI = 209; // Receiver Summary Information [RFC5760]
+const unsigned char RTCP_PT_TOKEN = 210; // Port Mapping [RFC6284]
+const unsigned char RTCP_PT_IDMS = 211; // IDMS Settings [RFC7272]
 
 // SDES tags:
 const unsigned char RTCP_SDES_END = 0;
diff --git a/liveMedia/include/RTPInterface.hh b/liveMedia/include/RTPInterface.hh
index 4df41e7..f50ac82 100644
--- a/liveMedia/include/RTPInterface.hh
+++ b/liveMedia/include/RTPInterface.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // An abstraction of a network interface used for RTP (or RTCP).
 // (This allows the RTP-over-TCP hack (RFC 2326, section 10.12) to
 // be implemented transparently.)
@@ -70,7 +70,17 @@ public:
   void startNetworkReading(TaskScheduler::BackgroundHandlerProc*
                            handlerProc);
   Boolean handleRead(unsigned char* buffer, unsigned bufferMaxSize,
-		     unsigned& bytesRead, struct sockaddr_in& fromAddress, Boolean& packetReadWasIncomplete);
+		     // out parameters:
+		     unsigned& bytesRead, struct sockaddr_in& fromAddress,
+		     int& tcpSocketNum, unsigned char& tcpStreamChannelId,
+		     Boolean& packetReadWasIncomplete);
+  // Note: If "tcpSocketNum" < 0, then the packet was received over UDP, and "tcpStreamChannelId"
+  //   is undefined (and irrelevant).
+
+
+  // Otherwise (if "tcpSocketNum" >= 0), the packet was received (interleaved) over TCP, and
+  //   "tcpStreamChannelId" will return the channel id.
+
   void stopNetworkReading();
 
   UsageEnvironment& envir() const { return fOwner->envir(); }
@@ -81,9 +91,10 @@ public:
     fAuxReadHandlerClientData = handlerClientData;
   }
 
-  // A hack for supporting handlers for RTCP packets arriving interleaved over TCP:
-  int nextTCPReadStreamSocketNum() const { return fNextTCPReadStreamSocketNum; }
-  unsigned char nextTCPReadStreamChannelId() const { return fNextTCPReadStreamChannelId; }
+  void forgetOurGroupsock() { fGS = NULL; }
+    // This may be called - *only immediately prior* to deleting this - to prevent our destructor
+    // from turning off background reading on the 'groupsock'.  (This is in case the 'groupsock'
+    // is also being read from elsewhere.)
 
 private:
   // Helper functions for sending a RTP or RTCP packet over a TCP connection:
diff --git a/liveMedia/include/RTPSink.hh b/liveMedia/include/RTPSink.hh
index ca27436..63fc2c5 100644
--- a/liveMedia/include/RTPSink.hh
+++ b/liveMedia/include/RTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP Sinks
 // C++ header
 
@@ -84,6 +84,7 @@ public:
   void removeStreamSocket(int sockNum, unsigned char streamChannelId) {
     fRTPInterface.removeStreamSocket(sockNum, streamChannelId);
   }
+  unsigned& estimatedBitrate() { return fEstimatedBitrate; } // kbps; usually 0 (i.e., unset)
 
 protected:
   RTPSink(UsageEnvironment& env,
@@ -124,6 +125,7 @@ private:
   char const* fRTPPayloadFormatName;
   unsigned fNumChannels;
   struct timeval fCreationTime;
+  unsigned fEstimatedBitrate; // set on creation if known; otherwise 0
 
   RTPTransmissionStatsDB* fTransmissionStatsDB;
 };
@@ -168,7 +170,7 @@ private:
 private:
   friend class Iterator;
   unsigned fNumReceivers;
-    RTPSink& fOurRTPSink;
+  RTPSink& fOurRTPSink;
   HashTable* fTable;
 };
 
@@ -185,8 +187,8 @@ public:
   unsigned roundTripDelay() const;
       // The round-trip delay (in units of 1/65536 seconds) computed from
       // the most recently-received RTCP RR packet.
-  struct timeval timeCreated() const {return fTimeCreated;}
-  struct timeval lastTimeReceived() const {return fTimeReceived;}
+  struct timeval const& timeCreated() const {return fTimeCreated;}
+  struct timeval const& lastTimeReceived() const {return fTimeReceived;}
   void getTotalOctetCount(u_int32_t& hi, u_int32_t& lo);
   void getTotalPacketCount(u_int32_t& hi, u_int32_t& lo);
 
diff --git a/liveMedia/include/RTPSource.hh b/liveMedia/include/RTPSource.hh
index 98820a3..f433cf6 100644
--- a/liveMedia/include/RTPSource.hh
+++ b/liveMedia/include/RTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP Sources
 // C++ header
 
@@ -49,6 +49,10 @@ public:
   u_int32_t SSRC() const { return fSSRC; }
       // Note: This is *our* SSRC, not the SSRC in incoming RTP packets.
      // later need a means of changing the SSRC if there's a collision #####
+  void registerForMultiplexedRTCPPackets(class RTCPInstance* rtcpInstance) {
+    fRTCPInstanceForMultiplexedRTCPPackets = rtcpInstance;
+  }
+  void deregisterForMultiplexedRTCPPackets() { registerForMultiplexedRTCPPackets(NULL); }
 
   unsigned timestampFrequency() const {return fTimestampFrequency;}
 
@@ -93,6 +97,7 @@ protected:
   Boolean fCurPacketMarkerBit;
   Boolean fCurPacketHasBeenSynchronizedUsingRTCP;
   u_int32_t fLastReceivedSSRC;
+  class RTCPInstance* fRTCPInstanceForMultiplexedRTCPPackets;
 
 private:
   // redefined virtual functions:
diff --git a/liveMedia/include/RTSPClient.hh b/liveMedia/include/RTSPClient.hh
index 52931ba..725f30c 100644
--- a/liveMedia/include/RTSPClient.hh
+++ b/liveMedia/include/RTSPClient.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic RTSP client - for a single "rtsp://" URL
 // C++ header
 
@@ -153,6 +153,10 @@ public:
       // Our implementation automatically does this just prior to sending each "PLAY" command;
       // You should not call these functions yourself unless you know what you're doing.
 
+  void setSpeed(MediaSession& session, float speed = 1.0f);
+      // Set (recorded) media download speed to given value to support faster download using 'Speed:'
+      // option on 'PLAY' command.
+
   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
@@ -173,6 +177,10 @@ public:
   void setUserAgentString(char const* userAgentName);
       // sets an alternative string to be used in RTSP "User-Agent:" headers
 
+  void disallowBasicAuthentication() { fAllowBasicAuthentication = False; }
+      // call this if you don't want the server to request 'Basic' authentication
+      // (which would cause the client to send usernames and passwords over the net).
+
   unsigned sessionTimeoutParameter() const { return fSessionTimeoutParameter; }
 
   char const* url() const { return fBaseURL; }
@@ -262,6 +270,7 @@ private:
   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);
+  char* createBlocksizeString(Boolean streamUsingTCP);
   void handleRequestError(RequestRecord* request);
   Boolean parseResponseCode(char const* line, unsigned& responseCode, char const*& responseString);
   void handleIncomingRequest();
@@ -270,13 +279,15 @@ private:
 			       char*& serverAddressStr, portNumBits& serverPortNum,
 			       unsigned char& rtpChannelId, unsigned char& rtcpChannelId);
   Boolean parseScaleParam(char const* paramStr, float& scale);
+  Boolean parseSpeedParam(char const* paramStr, float& speed);
   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);
+                             char const* scaleParamsStr, const char* speedParamsStr,
+			     char const* rangeParamsStr, char const* rtpInfoParamsStr);
   Boolean handleTEARDOWNResponse(MediaSession& session, MediaSubsession& subsession);
-  Boolean handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString);
+  Boolean handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString, char* resultValueStringEnd);
   Boolean handleAuthenticationFailure(char const* wwwAuthenticateParamsStr);
   Boolean resendCommand(RequestRecord* request);
   char const* sessionURL(MediaSession const& session) const;
@@ -302,10 +313,16 @@ private:
   void incomingDataHandler1();
   void handleResponseBytes(int newBytesRead);
 
+public:
+  u_int16_t desiredMaxIncomingPacketSize;
+    // If set to a value >0, then a "Blocksize:" header with this value (minus an allowance for
+    // IP, UDP, and RTP headers) will be sent with each "SETUP" request.
+
 protected:
   int fVerbosityLevel;
   unsigned fCSeq; // sequence number, used in consecutive requests
   Authenticator fCurrentAuthenticator;
+  Boolean fAllowBasicAuthentication;
   netAddressBits fServerAddress;
 
 private:
@@ -341,7 +358,7 @@ public:
 						    Port ourPort = 0, UserAuthenticationDatabase* authDatabase = NULL,
 						    int verbosityLevel = 0, char const* applicationName = NULL);
       // If ourPort.num() == 0, we'll choose the port number ourself.  (Use the following function to get it.)
-  portNumBits serverPortNum() const { return ntohs(fRTSPServerPort.num()); }
+  portNumBits serverPortNum() const { return ntohs(fServerPort.num()); }
 
 protected:
   HandlerServerForREGISTERCommand(UsageEnvironment& env, onRTSPClientCreationFunc* creationFunc, int ourSocket, Port ourPort,
diff --git a/liveMedia/include/RTSPCommon.hh b/liveMedia/include/RTSPCommon.hh
index 1a3fc85..4979043 100644
--- a/liveMedia/include/RTSPCommon.hh
+++ b/liveMedia/include/RTSPCommon.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Common routines used by both RTSP clients and servers
 // C++ header
 
@@ -51,8 +51,8 @@ Boolean parseRTSPRequestString(char const *reqStr, unsigned reqStrSize,
 			       unsigned resultSessionIdMaxSize,
 			       unsigned& contentLength);
 
-Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime);
-Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime);
+Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime, Boolean& startTimeIsNow);
+Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime, Boolean& startTimeIsNow);
 
 Boolean parseScaleHeader(char const* buf, float& scale);
 
@@ -62,6 +62,4 @@ Boolean RTSPOptionIsSupported(char const* commandName, char const* optionsRespon
 
 char const* dateHeader(); // A "Date:" header that can be used in a RTSP (or HTTP) response 
 
-void ignoreSigPipeOnSocket(int socketNum);
-
 #endif
diff --git a/liveMedia/include/RTSPRegisterSender.hh b/liveMedia/include/RTSPRegisterSender.hh
index ca49c25..7f99e8d 100644
--- a/liveMedia/include/RTSPRegisterSender.hh
+++ b/liveMedia/include/RTSPRegisterSender.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A special object which, when created, sends a custom RTSP "REGISTER" command to a specified client.
 // C++ header
 
diff --git a/liveMedia/include/RTSPServer.hh b/liveMedia/include/RTSPServer.hh
index bf68cb6..c2ddb99 100644
--- a/liveMedia/include/RTSPServer.hh
+++ b/liveMedia/include/RTSPServer.hh
@@ -14,94 +14,35 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A RTSP server
 // C++ header
 
 #ifndef _RTSP_SERVER_HH
 #define _RTSP_SERVER_HH
 
-#ifndef _SERVER_MEDIA_SESSION_HH
-#include "ServerMediaSession.hh"
-#endif
-#ifndef _NET_ADDRESS_HH
-#include <NetAddress.hh>
+#ifndef _GENERIC_MEDIA_SERVER_HH
+#include "GenericMediaServer.hh"
 #endif
 #ifndef _DIGEST_AUTHENTICATION_HH
 #include "DigestAuthentication.hh"
 #endif
 
-// A data structure used for optional user/password authentication:
-
-class UserAuthenticationDatabase {
-public:
-  UserAuthenticationDatabase(char const* realm = NULL,
-			     Boolean passwordsAreMD5 = False);
-    // If "passwordsAreMD5" is True, then each password stored into, or removed from,
-    // the database is actually the value computed
-    // by md5(<username>:<realm>:<actual-password>)
-  virtual ~UserAuthenticationDatabase();
-
-  virtual void addUserRecord(char const* username, char const* password);
-  virtual void removeUserRecord(char const* username);
-
-  virtual char const* lookupPassword(char const* username);
-      // returns NULL if the user name was not present
-
-  char const* realm() { return fRealm; }
-  Boolean passwordsAreMD5() { return fPasswordsAreMD5; }
-
-protected:
-  HashTable* fTable;
-  char* fRealm;
-  Boolean fPasswordsAreMD5;
-};
-
-#ifndef RTSP_BUFFER_SIZE
-#define RTSP_BUFFER_SIZE 10000 // for incoming requests, and outgoing responses
-#endif
-
-class RTSPServer: public Medium {
+class RTSPServer: public GenericMediaServer {
 public:
   static RTSPServer* createNew(UsageEnvironment& env, Port ourPort = 554,
 			       UserAuthenticationDatabase* authDatabase = NULL,
-			       unsigned reclamationTestSeconds = 65);
+			       unsigned reclamationSeconds = 65);
       // If ourPort.num() == 0, we'll choose the port number
       // Note: The caller is responsible for reclaiming "authDatabase"
-      // If "reclamationTestSeconds" > 0, then the "RTSPClientSession" state for
+      // If "reclamationSeconds" > 0, then the "RTSPClientSession" state for
       //     each client will get reclaimed (and the corresponding RTP stream(s)
       //     torn down) if no RTSP commands - or RTCP "RR" packets - from the
-      //     client are received in at least "reclamationTestSeconds" seconds.
+      //     client are received in at least "reclamationSeconds" seconds.
 
   static Boolean lookupByName(UsageEnvironment& env, char const* name,
 			      RTSPServer*& resultServer);
 
-  void addServerMediaSession(ServerMediaSession* serverMediaSession);
-
-  virtual ServerMediaSession* lookupServerMediaSession(char const* streamName);
-
-  void removeServerMediaSession(ServerMediaSession* serverMediaSession);
-      // Removes the "ServerMediaSession" object from our lookup table, so it will no longer be accessible by new RTSP clients.
-      // (However, any *existing* RTSP client sessions that use this "ServerMediaSession" object will continue streaming.
-      //  The "ServerMediaSession" object will not get deleted until all of these RTSP client sessions have closed.)
-      // (To both delete the "ServerMediaSession" object *and* close all RTSP client sessions that use it,
-      //  call "deleteServerMediaSession(serverMediaSession)" instead.)
-  void removeServerMediaSession(char const* streamName);
-     // ditto
-
-  void closeAllClientSessionsForServerMediaSession(ServerMediaSession* serverMediaSession);
-      // Closes (from the server) all RTSP client sessions that are currently using this "ServerMediaSession" object.
-      // Note, however, that the "ServerMediaSession" object remains accessible by new RTSP clients.
-  void closeAllClientSessionsForServerMediaSession(char const* streamName);
-     // ditto
-
-  void deleteServerMediaSession(ServerMediaSession* serverMediaSession);
-      // Equivalent to:
-      //     "closeAllClientSessionsForServerMediaSession(serverMediaSession); removeServerMediaSession(serverMediaSession);"
-  void deleteServerMediaSession(char const* streamName);
-      // Equivalent to:
-      //     "closeAllClientSessionsForServerMediaSession(streamName); removeServerMediaSession(streamName);
-
   typedef void (responseHandlerForREGISTER)(RTSPServer* rtspServer, unsigned requestId, int resultCode, char* resultString);
   unsigned registerStream(ServerMediaSession* serverMediaSession,
 			  char const* remoteClientNameOrAddress, portNumBits remoteClientPortNum,
@@ -136,6 +77,10 @@ public:
       // Changes the server's authentication database to "newDB", returning a pointer to the old database (if there was one).
       // "newDB" may be NULL (you can use this to disable authentication at runtime, if desired).
 
+  void disableStreamingRTPOverTCP() {
+    fAllowStreamingRTPOverTCP = False;
+  }
+
   Boolean setUpTunnelingOverHTTP(Port httpPort);
       // (Attempts to) enable RTSP-over-HTTP tunneling on the specified port.
       // Returns True iff the specified port can be used in this way (i.e., it's not already being used for a separate HTTP server).
@@ -146,12 +91,10 @@ protected:
   RTSPServer(UsageEnvironment& env,
 	     int ourSocket, Port ourPort,
 	     UserAuthenticationDatabase* authDatabase,
-	     unsigned reclamationTestSeconds);
+	     unsigned reclamationSeconds);
       // called only by createNew();
   virtual ~RTSPServer();
 
-  static int setUpOurSocket(UsageEnvironment& env, Port& ourPort);
-
   virtual char const* allowedCommandNames(); // used to implement "RTSPClientConnection::handleCmd_OPTIONS()"
   virtual Boolean weImplementREGISTER(char const* proxyURLSuffix, char*& responseStr);
       // used to implement "RTSPClientConnection::handleCmd_REGISTER()"
@@ -175,12 +118,10 @@ private: // redefined virtual functions
   virtual Boolean isRTSPServer() const;
 
 public: // should be protected, but some old compilers complain otherwise
-  class RTSPClientSession; // forward
   // The state of a TCP connection used by a RTSP client:
-  class RTSPClientConnection {
+  class RTSPClientSession; // forward
+  class RTSPClientConnection: public GenericMediaServer::ClientConnection {
   public:
-    RTSPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr);
-    virtual ~RTSPClientConnection();
     // A data structure that's used to implement the "REGISTER" command:
     class ParamsForREGISTER {
     public:
@@ -195,8 +136,16 @@ public: // should be protected, but some old compilers complain otherwise
       Boolean fReuseConnection, fDeliverViaTCP;
       char* fProxyURLSuffix;
     };
+  protected: // redefined virtual functions:
+    virtual void handleRequestBytes(int newBytesRead);
+
   protected:
+    RTSPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr);
+    virtual ~RTSPClientConnection();
+
+    friend class RTSPServer;
     friend class RTSPClientSession;
+
     // Make the handler functions for each command virtual, to allow subclasses to reimplement them, if necessary:
     virtual void handleCmd_OPTIONS();
         // You probably won't need to subclass/reimplement this function; reimplement "RTSPServer::allowedCommandNames()" instead.
@@ -224,14 +173,10 @@ public: // should be protected, but some old compilers complain otherwise
     virtual Boolean handleHTTPCmd_TunnelingPOST(char const* sessionCookie, unsigned char const* extraData, unsigned extraDataSize);
     virtual void handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* fullRequestStr);
   protected:
-    UsageEnvironment& envir() { return fOurServer.envir(); }
     void resetRequestBuffer();
-    void closeSockets();
-    static void incomingRequestHandler(void*, int /*mask*/);
-    void incomingRequestHandler1();
+    void closeSocketsRTSP();
     static void handleAlternativeRequestByte(void*, u_int8_t requestByte);
     void handleAlternativeRequestByte1(u_int8_t requestByte);
-    void handleRequestBytes(int newBytesRead);
     Boolean authenticationOK(char const* cmdName, char const* urlSuffix, char const* fullRequestStr);
     void changeClientInputSocket(int newSocketNum, unsigned char const* extraData, unsigned extraDataSize);
       // used to implement RTSP-over-HTTP tunneling
@@ -244,14 +189,11 @@ public: // should be protected, but some old compilers complain otherwise
     void setRTSPResponse(char const* responseStr, char const* contentStr);
     void setRTSPResponse(char const* responseStr, u_int32_t sessionId, char const* contentStr);
 
-    RTSPServer& fOurServer;
+    RTSPServer& fOurRTSPServer; // same as ::fOurServer
+    int& fClientInputSocket; // aliased to ::fOurSocket
+    int fClientOutputSocket;
     Boolean fIsActive;
-    int fClientInputSocket, fClientOutputSocket;
-    struct sockaddr_in fClientAddr;
-    unsigned char fRequestBuffer[RTSP_BUFFER_SIZE];
-    unsigned fRequestBytesAlreadySeen, fRequestBufferBytesLeft;
     unsigned char* fLastCRLF;
-    unsigned char fResponseBuffer[RTSP_BUFFER_SIZE];
     unsigned fRecursionCount;
     char const* fCurrentCSeq;
     Authenticator fCurrentAuthenticator; // used if access control is needed
@@ -260,11 +202,11 @@ public: // should be protected, but some old compilers complain otherwise
   };
 
   // The state of an individual client session (using one or more sequential TCP connections) handled by a RTSP server:
-  class RTSPClientSession {
-  public:
+  class RTSPClientSession: public GenericMediaServer::ClientSession {
+  protected:
     RTSPClientSession(RTSPServer& ourServer, u_int32_t sessionId);
     virtual ~RTSPClientSession();
-  protected:
+
     friend class RTSPServer;
     friend class RTSPClientConnection;
     // Make the handler functions for each command virtual, to allow subclasses to redefine them:
@@ -285,12 +227,9 @@ public: // should be protected, but some old compilers complain otherwise
     virtual void handleCmd_SET_PARAMETER(RTSPClientConnection* ourClientConnection,
 					 ServerMediaSubsession* subsession, char const* fullRequestStr);
   protected:
-    UsageEnvironment& envir() { return fOurServer.envir(); }
+    void deleteStreamByTrack(unsigned trackNum);
     void reclaimStreamStates();
     Boolean isMulticast() const { return fIsMulticast; }
-    void noteLiveness();
-    static void noteClientLiveness(RTSPClientSession* clientSession);
-    static void livenessTimeoutTask(RTSPClientSession* clientSession);
 
     // Shortcuts for setting up a RTSP response (prior to sending it):
     void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr) { ourClientConnection->setRTSPResponse(responseStr); }
@@ -299,70 +238,50 @@ public: // should be protected, but some old compilers complain otherwise
     void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr, u_int32_t sessionId, char const* contentStr) { ourClientConnection->setRTSPResponse(responseStr, sessionId, contentStr); }
 
   protected:
-    RTSPServer& fOurServer;
-    u_int32_t fOurSessionId;
-    ServerMediaSession* fOurServerMediaSession;
+    RTSPServer& fOurRTSPServer; // same as ::fOurServer
     Boolean fIsMulticast, fStreamAfterSETUP;
     unsigned char fTCPStreamIdCount; // used for (optional) RTP/TCP
     Boolean usesTCPTransport() const { return fTCPStreamIdCount > 0; }
-    TaskToken fLivenessCheckTask;
     unsigned fNumStreamStates;
     struct streamState {
       ServerMediaSubsession* subsession;
+      int tcpSocketNum;
       void* streamToken;
     } * fStreamStates;
   };
 
-protected:
+protected: // redefined virtual functions
   // If you subclass "RTSPClientConnection", then you must also redefine this virtual function in order
   // to create new objects of your subclass:
-  virtual RTSPClientConnection*
-  createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr);
+  virtual ClientConnection* createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr);
 
+protected:
   // If you subclass "RTSPClientSession", then you must also redefine this virtual function in order
   // to create new objects of your subclass:
-  virtual RTSPClientSession*
-  createNewClientSession(u_int32_t sessionId);
-
-  // An iterator over our "ServerMediaSession" objects:
-  class ServerMediaSessionIterator {
-  public:
-    ServerMediaSessionIterator(RTSPServer& server);
-    virtual ~ServerMediaSessionIterator();
-    ServerMediaSession* next();
-  private:
-    HashTable::Iterator* fOurIterator;
-  };
+  virtual ClientSession* createNewClientSession(u_int32_t sessionId);
 
 private:
-  static void incomingConnectionHandlerRTSP(void*, int /*mask*/);
-  void incomingConnectionHandlerRTSP1();
-
   static void incomingConnectionHandlerHTTP(void*, int /*mask*/);
-  void incomingConnectionHandlerHTTP1();
+  void incomingConnectionHandlerHTTP();
 
-  void incomingConnectionHandler(int serverSocket);
-
-protected:
-  Port fRTSPServerPort;
+  void noteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum);
+  void unnoteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum);
+  void stopTCPStreamingOnSocket(int socketNum);
 
 private:
   friend class RTSPClientConnection;
   friend class RTSPClientSession;
-  friend class ServerMediaSessionIterator;
   friend class RegisterRequestRecord;
-  int fRTSPServerSocket;
   int fHTTPServerSocket; // for optional RTSP-over-HTTP tunneling
   Port fHTTPServerPort; // ditto
-  HashTable* fServerMediaSessions; // maps 'stream name' strings to "ServerMediaSession" objects
-  HashTable* fClientConnections; // the "ClientConnection" objects that we're using
   HashTable* fClientConnectionsForHTTPTunneling; // maps client-supplied 'session cookie' strings to "RTSPClientConnection"s
     // (used only for optional RTSP-over-HTTP tunneling)
-  HashTable* fClientSessions; // maps 'session id' strings to "RTSPClientSession" objects
+  HashTable* fTCPStreamingDatabase;
+    // maps TCP socket numbers to ids of sessions that are streaming over it (RTP/RTCP-over-TCP)
   HashTable* fPendingRegisterRequests;
   unsigned fRegisterRequestCounter;
   UserAuthenticationDatabase* fAuthDB;
-  unsigned fReclamationTestSeconds;
+  Boolean fAllowStreamingRTPOverTCP; // by default, True
 };
 
 
@@ -373,14 +292,14 @@ public:
   static RTSPServerWithREGISTERProxying* createNew(UsageEnvironment& env, Port ourPort = 554,
 						   UserAuthenticationDatabase* authDatabase = NULL,
 						   UserAuthenticationDatabase* authDatabaseForREGISTER = NULL,
-						   unsigned reclamationTestSeconds = 65,
+						   unsigned reclamationSeconds = 65,
 						   Boolean streamRTPOverTCP = False,
 						   int verbosityLevelForProxying = 0);
 
 protected:
   RTSPServerWithREGISTERProxying(UsageEnvironment& env, int ourSocket, Port ourPort,
 				 UserAuthenticationDatabase* authDatabase, UserAuthenticationDatabase* authDatabaseForREGISTER,
-				 unsigned reclamationTestSeconds,
+				 unsigned reclamationSeconds,
 				 Boolean streamRTPOverTCP, int verbosityLevelForProxying);
   // called only by createNew();
   virtual ~RTSPServerWithREGISTERProxying();
diff --git a/liveMedia/include/RTSPServerSupportingHTTPStreaming.hh b/liveMedia/include/RTSPServerSupportingHTTPStreaming.hh
index 1bf8920..0b3ba7a 100644
--- a/liveMedia/include/RTSPServerSupportingHTTPStreaming.hh
+++ b/liveMedia/include/RTSPServerSupportingHTTPStreaming.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A server that supports both RTSP, and HTTP streaming (using Apple's "HTTP Live Streaming" protocol)
 // C++ header
 
@@ -48,7 +48,7 @@ protected:
   virtual ~RTSPServerSupportingHTTPStreaming();
 
 protected: // redefined virtual functions
-  virtual RTSPClientConnection* createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr);
+  virtual ClientConnection* createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr);
 
 public: // should be protected, but some old compilers complain otherwise
   class RTSPClientConnectionSupportingHTTPStreaming: public RTSPServer::RTSPClientConnection {
@@ -64,6 +64,7 @@ public: // should be protected, but some old compilers complain otherwise
 
   private:
     u_int32_t fClientSessionId;
+    FramedSource* fStreamSource;
     ByteStreamMemoryBufferSource* fPlaylistSource;
     TCPStreamSink* fTCPSink;
   };
diff --git a/liveMedia/include/SIPClient.hh b/liveMedia/include/SIPClient.hh
index 4a9ef2c..bc2501e 100644
--- a/liveMedia/include/SIPClient.hh
+++ b/liveMedia/include/SIPClient.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic SIP client
 // C++ header
 
diff --git a/liveMedia/include/ServerMediaSession.hh b/liveMedia/include/ServerMediaSession.hh
index dbeefc5..8f81959 100644
--- a/liveMedia/include/ServerMediaSession.hh
+++ b/liveMedia/include/ServerMediaSession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A data structure that represents a session that consists of
 // potentially multiple (audio and/or video) sub-sessions
 // (This data structure is used for media *streamers* - i.e., servers.
@@ -24,17 +24,8 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #ifndef _SERVER_MEDIA_SESSION_HH
 #define _SERVER_MEDIA_SESSION_HH
 
-#ifndef _MEDIA_HH
-#include "Media.hh"
-#endif
-#ifndef _FRAMED_SOURCE_HH
-#include "FramedSource.hh"
-#endif
-#ifndef _GROUPEID_HH
-#include "GroupEId.hh"
-#endif
-#ifndef _RTP_INTERFACE_HH
-#include "RTPInterface.hh" // for ServerRequestAlternativeByteHandler
+#ifndef _RTCP_HH
+#include "RTCP.hh"
 #endif
 
 class ServerMediaSubsession; // forward
@@ -148,7 +139,8 @@ public:
 			   ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
 			   void* serverRequestAlternativeByteHandlerClientData) = 0;
   virtual void pauseStream(unsigned clientSessionId, void* streamToken);
-  virtual void seekStream(unsigned clientSessionId, void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes);
+  virtual void seekStream(unsigned clientSessionId, void* streamToken, double& seekNPT,
+			  double streamDuration, u_int64_t& numBytes);
      // This routine is used to seek by relative (i.e., NPT) time.
      // "streamDuration", if >0.0, specifies how much data to stream, past "seekNPT".  (If <=0.0, all remaining data is streamed.)
      // "numBytes" returns the size (in bytes) of the data to be streamed, or 0 if unknown or unlimited.
@@ -157,11 +149,18 @@ public:
      // "absStart" should be a string of the form "YYYYMMDDTHHMMSSZ" or "YYYYMMDDTHHMMSS.<frac>Z".
      // "absEnd" should be either NULL (for no end time), or a string of the same form as "absStart".
      // These strings may be modified in-place, or can be reassigned to a newly-allocated value (after delete[]ing the original).
-  virtual void nullSeekStream(unsigned clientSessionId, void* streamToken);
+  virtual void nullSeekStream(unsigned clientSessionId, void* streamToken,
+			      double streamEndTime, u_int64_t& numBytes);
      // Called whenever we're handling a "PLAY" command without a specified start time.
   virtual void setStreamScale(unsigned clientSessionId, void* streamToken, float scale);
   virtual float getCurrentNPT(void* streamToken);
   virtual FramedSource* getStreamSource(void* streamToken);
+  virtual void getRTPSinkandRTCP(void* streamToken,
+				 RTPSink const*& rtpSink, RTCPInstance const*& rtcp) = 0;
+     // Returns pointers to the "RTPSink" and "RTCPInstance" objects for "streamToken".
+     // (This can be useful if you want to get the associated 'Groupsock' objects, for example.)
+     // You must not delete these objects, or start/stop playing them; instead, that is done
+     // using the "startStream()" and "deleteStream()" functions.
   virtual void deleteStream(unsigned clientSessionId, void*& streamToken);
 
   virtual void testScaleFactor(float& scale); // sets "scale" to the actual supported scale
diff --git a/liveMedia/include/SimpleRTPSink.hh b/liveMedia/include/SimpleRTPSink.hh
index 808fc4a..babcee3 100644
--- a/liveMedia/include/SimpleRTPSink.hh
+++ b/liveMedia/include/SimpleRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A simple RTP sink that packs frames into each outgoing
 //     packet, without any fragmentation or special headers.
 // C++ header
diff --git a/liveMedia/include/SimpleRTPSource.hh b/liveMedia/include/SimpleRTPSource.hh
index 1c4194a..49f1fb6 100644
--- a/liveMedia/include/SimpleRTPSource.hh
+++ b/liveMedia/include/SimpleRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A RTP source for a simple RTP payload format that
 //     - doesn't have any special headers following the RTP header
 //       (if necessary, the "offset" parameter can be used to specify a
diff --git a/liveMedia/include/StreamReplicator.hh b/liveMedia/include/StreamReplicator.hh
index 2fdc3a7..3993c5e 100644
--- a/liveMedia/include/StreamReplicator.hh
+++ b/liveMedia/include/StreamReplicator.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // An class that can be used to create (possibly multiple) 'replicas' of an incoming stream.
 // C++ header
 
diff --git a/liveMedia/include/T140TextRTPSink.hh b/liveMedia/include/T140TextRTPSink.hh
index 6a3d178..2b2d9b0 100644
--- a/liveMedia/include/T140TextRTPSink.hh
+++ b/liveMedia/include/T140TextRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for T.140 text (RFC 2793)
 // C++ header
 
diff --git a/liveMedia/include/TCPStreamSink.hh b/liveMedia/include/TCPStreamSink.hh
index c039d13..69abd79 100644
--- a/liveMedia/include/TCPStreamSink.hh
+++ b/liveMedia/include/TCPStreamSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A sink representing a TCP output stream
 // C++ header
 
diff --git a/liveMedia/include/TextRTPSink.hh b/liveMedia/include/TextRTPSink.hh
index 32678ce..3dc8f3e 100644
--- a/liveMedia/include/TextRTPSink.hh
+++ b/liveMedia/include/TextRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic RTP sink for text codecs (abstract base class)
 // C++ header
 
diff --git a/liveMedia/include/TheoraVideoRTPSink.hh b/liveMedia/include/TheoraVideoRTPSink.hh
index 1bd8733..28f4cc2 100644
--- a/liveMedia/include/TheoraVideoRTPSink.hh
+++ b/liveMedia/include/TheoraVideoRTPSink.hh
@@ -1,7 +1,22 @@
-/*
- * Theora Video RTP packetizer
- * Copied from live555's VorbisAudioRTPSink
- */
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+**********/
+// "liveMedia"
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// RTP sink for Theora video
+// C++ header
 
 #ifndef _THEORA_VIDEO_RTP_SINK_HH
 #define _THEORA_VIDEO_RTP_SINK_HH
@@ -12,25 +27,23 @@
 
 class TheoraVideoRTPSink: public VideoRTPSink {
 public:
-  enum PixFmt {
-    YUV420,
-    YUV422,
-    YUV444,
-  };
-  
-  static TheoraVideoRTPSink* createNew(UsageEnvironment& env,
-				       Groupsock* RTPgs,
-				       u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency,
-				       unsigned width, unsigned height, enum  PixFmt pf,
-				       // The following headers provide the 'configuration' information, for the SDP description:
-				       u_int8_t* identificationHeader, unsigned identificationHeaderSize,
-				       u_int8_t* commentHeader, unsigned commentHeaderSize,
-				       u_int8_t* setupHeader, unsigned setupHeaderSize, u_int32_t identField);
+  static TheoraVideoRTPSink*
+  createNew(UsageEnvironment& env, Groupsock* RTPgs, u_int8_t rtpPayloadFormat,
+	    // The following headers provide the 'configuration' information, for the SDP description:
+	    u_int8_t* identificationHeader, unsigned identificationHeaderSize,
+	    u_int8_t* commentHeader, unsigned commentHeaderSize,
+	    u_int8_t* setupHeader, unsigned setupHeaderSize,
+	    u_int32_t identField = 0xFACADE);
   
+  static TheoraVideoRTPSink*
+  createNew(UsageEnvironment& env, Groupsock* RTPgs, u_int8_t rtpPayloadFormat,
+            char const* configStr);
+  // an optional variant of "createNew()" that takes a Base-64-encoded 'configuration' string,
+  // rather than the raw configuration headers as parameter.
+
 protected:
   TheoraVideoRTPSink(UsageEnvironment& env, Groupsock* RTPgs,
-		     u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency,
-		     unsigned width, unsigned height, enum  PixFmt pf,
+		     u_int8_t rtpPayloadFormat,
 		     u_int8_t* identificationHeader, unsigned identificationHeaderSize,
 		     u_int8_t* commentHeader, unsigned commentHeaderSize,
 		     u_int8_t* setupHeader, unsigned setupHeaderSize,
diff --git a/liveMedia/include/VorbisAudioRTPSource.hh b/liveMedia/include/TheoraVideoRTPSource.hh
similarity index 73%
copy from liveMedia/include/VorbisAudioRTPSource.hh
copy to liveMedia/include/TheoraVideoRTPSource.hh
index 7242b77..cdb863d 100644
--- a/liveMedia/include/VorbisAudioRTPSource.hh
+++ b/liveMedia/include/TheoraVideoRTPSource.hh
@@ -14,33 +14,31 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// Vorbis Audio RTP Sources
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// Theora Video Audio RTP Sources
 // C++ header
 
-#ifndef _VORBIS_AUDIO_RTP_SOURCE_HH
-#define _VORBIS_AUDIO_RTP_SOURCE_HH
+#ifndef _THEORA_VIDEO_RTP_SOURCE_HH
+#define _THEORA_VIDEO_RTP_SOURCE_HH
 
 #ifndef _MULTI_FRAMED_RTP_SOURCE_HH
 #include "MultiFramedRTPSource.hh"
 #endif
 
-class VorbisAudioRTPSource: public MultiFramedRTPSource {
+class TheoraVideoRTPSource: public MultiFramedRTPSource {
 public:
-  static VorbisAudioRTPSource*
+  static TheoraVideoRTPSource*
   createNew(UsageEnvironment& env, Groupsock* RTPgs,
-	    unsigned char rtpPayloadFormat,
-	    unsigned rtpTimestampFrequency);
+	    unsigned char rtpPayloadFormat);
 
   u_int32_t curPacketIdent() const { return fCurPacketIdent; } // The current "Ident" field; only the low-order 24 bits are used
 
 protected:
-  VorbisAudioRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
-		       unsigned char rtpPayloadFormat,
-		       unsigned rtpTimestampFrequency);
+  TheoraVideoRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
+		       unsigned char rtpPayloadFormat);
       // called only by createNew()
 
-  virtual ~VorbisAudioRTPSource();
+  virtual ~TheoraVideoRTPSource();
 
 protected:
   // redefined virtual functions:
diff --git a/liveMedia/include/VP8VideoRTPSink.hh b/liveMedia/include/VP8VideoRTPSink.hh
index e745d5e..97cc35f 100644
--- a/liveMedia/include/VP8VideoRTPSink.hh
+++ b/liveMedia/include/VP8VideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for VP8 video
 // C++ header
 
diff --git a/liveMedia/include/VP8VideoRTPSource.hh b/liveMedia/include/VP8VideoRTPSource.hh
index 744e3d1..31ed426 100644
--- a/liveMedia/include/VP8VideoRTPSource.hh
+++ b/liveMedia/include/VP8VideoRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // VP8 Video RTP Sources
 // C++ header
 
diff --git a/liveMedia/include/VP8VideoRTPSink.hh b/liveMedia/include/VP9VideoRTPSink.hh
similarity index 81%
copy from liveMedia/include/VP8VideoRTPSink.hh
copy to liveMedia/include/VP9VideoRTPSink.hh
index e745d5e..6a877dc 100644
--- a/liveMedia/include/VP8VideoRTPSink.hh
+++ b/liveMedia/include/VP9VideoRTPSink.hh
@@ -14,26 +14,26 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// RTP sink for VP8 video
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// RTP sink for VP9 video
 // C++ header
 
-#ifndef _VP8_VIDEO_RTP_SINK_HH
-#define _VP8_VIDEO_RTP_SINK_HH
+#ifndef _VP9_VIDEO_RTP_SINK_HH
+#define _VP9_VIDEO_RTP_SINK_HH
 
 #ifndef _VIDEO_RTP_SINK_HH
 #include "VideoRTPSink.hh"
 #endif
 
-class VP8VideoRTPSink: public VideoRTPSink {
+class VP9VideoRTPSink: public VideoRTPSink {
 public:
-  static VP8VideoRTPSink* createNew(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat);
+  static VP9VideoRTPSink* createNew(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat);
 
 protected:
-  VP8VideoRTPSink(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat);
+  VP9VideoRTPSink(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat);
 	// called only by createNew()
 
-  virtual ~VP8VideoRTPSink();
+  virtual ~VP9VideoRTPSink();
 
 private: // redefined virtual functions:
   virtual void doSpecialFrameHandling(unsigned fragmentationOffset,
diff --git a/liveMedia/include/VP8VideoRTPSource.hh b/liveMedia/include/VP9VideoRTPSource.hh
similarity index 80%
copy from liveMedia/include/VP8VideoRTPSource.hh
copy to liveMedia/include/VP9VideoRTPSource.hh
index 744e3d1..5c5bdc6 100644
--- a/liveMedia/include/VP8VideoRTPSource.hh
+++ b/liveMedia/include/VP9VideoRTPSource.hh
@@ -14,31 +14,31 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
-// VP8 Video RTP Sources
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
+// VP9 Video RTP Sources
 // C++ header
 
-#ifndef _VP8_VIDEO_RTP_SOURCE_HH
-#define _VP8_VIDEO_RTP_SOURCE_HH
+#ifndef _VP9_VIDEO_RTP_SOURCE_HH
+#define _VP9_VIDEO_RTP_SOURCE_HH
 
 #ifndef _MULTI_FRAMED_RTP_SOURCE_HH
 #include "MultiFramedRTPSource.hh"
 #endif
 
-class VP8VideoRTPSource: public MultiFramedRTPSource {
+class VP9VideoRTPSource: public MultiFramedRTPSource {
 public:
-  static VP8VideoRTPSource*
+  static VP9VideoRTPSource*
   createNew(UsageEnvironment& env, Groupsock* RTPgs,
 	    unsigned char rtpPayloadFormat,
 	    unsigned rtpTimestampFrequency = 90000);
 
 protected:
-  VP8VideoRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
+  VP9VideoRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
 		    unsigned char rtpPayloadFormat,
 		    unsigned rtpTimestampFrequency);
       // called only by createNew()
 
-  virtual ~VP8VideoRTPSource();
+  virtual ~VP9VideoRTPSource();
 
 protected:
   // redefined virtual functions:
diff --git a/liveMedia/include/VideoRTPSink.hh b/liveMedia/include/VideoRTPSink.hh
index c763092..b129bce 100644
--- a/liveMedia/include/VideoRTPSink.hh
+++ b/liveMedia/include/VideoRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A generic RTP sink for video codecs (abstract base class)
 // C++ header
 
diff --git a/liveMedia/include/VorbisAudioRTPSink.hh b/liveMedia/include/VorbisAudioRTPSink.hh
index d88c7bd..d434c2d 100644
--- a/liveMedia/include/VorbisAudioRTPSink.hh
+++ b/liveMedia/include/VorbisAudioRTPSink.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // RTP sink for Vorbis audio
 // C++ header
 
@@ -27,26 +27,29 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 
 class VorbisAudioRTPSink: public AudioRTPSink {
 public:
-  static VorbisAudioRTPSink* createNew(UsageEnvironment& env,
-				       Groupsock* RTPgs,
-				       u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency, unsigned numChannels,
-				       // The following headers provide the 'configuration' information, for the SDP description:
-				       u_int8_t* identificationHeader, unsigned identificationHeaderSize,
-				       u_int8_t* commentHeader, unsigned commentHeaderSize,
-				       u_int8_t* setupHeader, unsigned setupHeaderSize);
-  static VorbisAudioRTPSink* createNew(UsageEnvironment& env,
-				       Groupsock* RTPgs,
-				       u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency, unsigned numChannels,
-				       char const* configStr);
+  static VorbisAudioRTPSink*
+  createNew(UsageEnvironment& env, Groupsock* RTPgs, u_int8_t rtpPayloadFormat,
+	    u_int32_t rtpTimestampFrequency, unsigned numChannels,
+	    // The following headers provide the 'configuration' information, for the SDP description:
+	    u_int8_t* identificationHeader, unsigned identificationHeaderSize,
+	    u_int8_t* commentHeader, unsigned commentHeaderSize,
+	    u_int8_t* setupHeader, unsigned setupHeaderSize,
+	    u_int32_t identField = 0xFACADE);
+
+  static VorbisAudioRTPSink*
+  createNew(UsageEnvironment& env, Groupsock* RTPgs, u_int8_t rtpPayloadFormat,
+	    u_int32_t rtpTimestampFrequency, unsigned numChannels,
+	    char const* configStr);
     // an optional variant of "createNew()" that takes a Base-64-encoded 'configuration' string,
-    // rather than the raw configuration headers. as parameter.
+    // rather than the raw configuration headers as parameter.
+
 protected:
   VorbisAudioRTPSink(UsageEnvironment& env, Groupsock* RTPgs,
 		     u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency, unsigned numChannels,
 		     u_int8_t* identificationHeader, unsigned identificationHeaderSize,
 		     u_int8_t* commentHeader, unsigned commentHeaderSize,
 		     u_int8_t* setupHeader, unsigned setupHeaderSize,
-		     u_int32_t identField = 0xFACADE);
+		     u_int32_t identField);
 	// called only by createNew()
 
   virtual ~VorbisAudioRTPSink();
@@ -63,9 +66,20 @@ private: // redefined virtual functions:
 						 unsigned numBytesInFrame) const;
   virtual unsigned specialHeaderSize() const;
   virtual unsigned frameSpecificHeaderSize() const;
-#endif
 
 private:
   u_int32_t fIdent; // "Ident" field used by this stream.  (Only the low 24 bits of this are used.)
   char* fFmtpSDPLine;
 };
+
+
+// A general function used by both "VorbisAudioRTPSink" and "TheoraVideoRTPSink" to construct
+// a Base64-encoded 'config' string (for SDP) from "identification", "comment", "setup" headers.
+// (Note: The result string was heap-allocated, and the caller should delete[] it afterwards.)
+
+char* generateVorbisOrTheoraConfigStr(u_int8_t* identificationHeader, unsigned identificationHeaderSize,
+				      u_int8_t* commentHeader, unsigned commentHeaderSize,
+				      u_int8_t* setupHeader, unsigned setupHeaderSize,
+				      u_int32_t identField);
+
+#endif
diff --git a/liveMedia/include/VorbisAudioRTPSource.hh b/liveMedia/include/VorbisAudioRTPSource.hh
index 7242b77..6489cca 100644
--- a/liveMedia/include/VorbisAudioRTPSource.hh
+++ b/liveMedia/include/VorbisAudioRTPSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Vorbis Audio RTP Sources
 // C++ header
 
@@ -52,4 +52,15 @@ private:
   u_int32_t fCurPacketIdent; // only the low-order 24 bits are used
 };
 
+void parseVorbisOrTheoraConfigStr(char const* configStr,
+				  u_int8_t*& identificationHdr, unsigned& identificationHdrSize,
+				  u_int8_t*& commentHdr, unsigned& commentHdrSize,
+				  u_int8_t*& setupHdr, unsigned& setupHdrSize,
+				  u_int32_t& identField);
+    // Returns (in each of the result parameters) unpacked Vorbis or Theora
+    // "identification", "comment", and "setup" headers that were specified in a
+    // "config" string (in the SDP description for a Vorbis/RTP or Theora/RTP stream).
+    // Each of the "*Hdr" result arrays are dynamically allocated by this routine,
+    // and must be delete[]d by the caller.
+
 #endif
diff --git a/liveMedia/include/WAVAudioFileServerMediaSubsession.hh b/liveMedia/include/WAVAudioFileServerMediaSubsession.hh
index 41787fc..a42cc9d 100644
--- a/liveMedia/include/WAVAudioFileServerMediaSubsession.hh
+++ b/liveMedia/include/WAVAudioFileServerMediaSubsession.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
 // on demand, from an WAV audio file.
 // C++ header
@@ -43,6 +43,8 @@ protected:
 protected: // redefined virtual functions
   virtual void seekStreamSource(FramedSource* inputSource, double& seekNPT, double streamDuration, u_int64_t& numBytes);
   virtual void setStreamSourceScale(FramedSource* inputSource, float scale);
+  virtual void setStreamSourceDuration(FramedSource* inputSource, double streamDuration, u_int64_t& numBytes);
+
   virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
 					      unsigned& estBitrate);
   virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,
diff --git a/liveMedia/include/WAVAudioFileSource.hh b/liveMedia/include/WAVAudioFileSource.hh
index 65c385e..c195af0 100644
--- a/liveMedia/include/WAVAudioFileSource.hh
+++ b/liveMedia/include/WAVAudioFileSource.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // A WAV audio file source
 // NOTE: Samples are returned in little-endian order (the same order in which
 // they were stored in the file).
@@ -44,7 +44,8 @@ public:
 
   unsigned numPCMBytes() const;
   void setScaleFactor(int scale);
-  void seekToPCMByte(unsigned byteNumber, unsigned numBytesToStream);
+  void seekToPCMByte(unsigned byteNumber);
+  void limitNumBytesToStream(unsigned numBytesToStream);
       // if "numBytesToStream" is >0, then we limit the stream to that number of bytes, before treating it as EOF
 
   unsigned char getAudioFormat();
diff --git a/liveMedia/include/liveMedia.hh b/liveMedia/include/liveMedia.hh
index bf61c33..1f0b9bc 100644
--- a/liveMedia/include/liveMedia.hh
+++ b/liveMedia/include/liveMedia.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Inclusion of header files representing the interface
 // for the entire library
 //
@@ -30,6 +30,8 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "MPEG4ESVideoRTPSink.hh"
 #include "AMRAudioFileSink.hh"
 #include "H264VideoFileSink.hh"
+#include "H265VideoFileSink.hh"
+#include "OggFileSink.hh"
 #include "BasicUDPSink.hh"
 #include "GSMAudioRTPSink.hh"
 #include "H263plusVideoRTPSink.hh"
@@ -63,7 +65,9 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "JPEGVideoSource.hh"
 #include "MPEG1or2VideoRTPSource.hh"
 #include "VorbisAudioRTPSource.hh"
+#include "TheoraVideoRTPSource.hh"
 #include "VP8VideoRTPSource.hh"
+#include "VP9VideoRTPSource.hh"
 #include "MPEG2TransportStreamFromPESSource.hh"
 #include "MPEG2TransportStreamFromESSource.hh"
 #include "MPEG2TransportStreamFramer.hh"
@@ -71,6 +75,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "H261VideoRTPSource.hh"
 #include "H263plusVideoRTPSource.hh"
 #include "H264VideoRTPSource.hh"
+#include "H265VideoRTPSource.hh"
 #include "MP3FileSource.hh"
 #include "MP3ADU.hh"
 #include "MP3ADUinterleaving.hh"
@@ -82,7 +87,9 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "AC3AudioRTPSource.hh"
 #include "AC3AudioRTPSink.hh"
 #include "VorbisAudioRTPSink.hh"
+#include "TheoraVideoRTPSink.hh"
 #include "VP8VideoRTPSink.hh"
+#include "VP9VideoRTPSink.hh"
 #include "MPEG4GenericRTPSink.hh"
 #include "MPEG1or2VideoStreamDiscreteFramer.hh"
 #include "MPEG4VideoStreamDiscreteFramer.hh"
@@ -117,7 +124,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 #include "AC3AudioFileServerMediaSubsession.hh"
 #include "MPEG2TransportUDPServerMediaSubsession.hh"
 #include "MatroskaFileServerDemux.hh"
+#include "OggFileServerDemux.hh"
 #include "ProxyServerMediaSession.hh"
-#include "DarwinInjector.hh"
 
 #endif
diff --git a/liveMedia/include/liveMedia_version.hh b/liveMedia/include/liveMedia_version.hh
index a38163b..df4b6d7 100644
--- a/liveMedia/include/liveMedia_version.hh
+++ b/liveMedia/include/liveMedia_version.hh
@@ -1,10 +1,10 @@
 // Version information for the "liveMedia" library
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2015 Live Networks, Inc.  All rights reserved.
 
 #ifndef _LIVEMEDIA_VERSION_HH
 #define _LIVEMEDIA_VERSION_HH
 
-#define LIVEMEDIA_LIBRARY_VERSION_STRING	"2014.01.13"
-#define LIVEMEDIA_LIBRARY_VERSION_INT		1389571200
+#define LIVEMEDIA_LIBRARY_VERSION_STRING	"2015.12.22"
+#define LIVEMEDIA_LIBRARY_VERSION_INT		1450742400
 
 #endif
diff --git a/liveMedia/ourMD5.hh b/liveMedia/include/ourMD5.hh
similarity index 70%
rename from liveMedia/ourMD5.hh
rename to liveMedia/include/ourMD5.hh
index 9bd15ac..1cd7bbf 100644
--- a/liveMedia/ourMD5.hh
+++ b/liveMedia/include/ourMD5.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Because MD5 may not be implemented (at least, with the same interface) on all systems,
 // we have our own implementation.
 // C++ header
@@ -26,4 +26,13 @@ extern char* our_MD5Data(unsigned char const* data, unsigned dataSize, char* out
     // "outputDigest" must be either NULL (in which case this function returns a heap-allocated
     // buffer, which should be later delete[]d by the caller), or else it must point to
     // a (>=)33-byte buffer (which this function will also return).
+
+extern unsigned char* our_MD5DataRaw(unsigned char const* data, unsigned dataSize,
+				     unsigned char* outputDigest);
+    // Like "ourMD5Data()", except that it returns the digest in 'raw' binary form, rather than
+    // as an ASCII hex string.
+    // "outputDigest" must be either NULL (in which case this function returns a heap-allocated
+    // buffer, which should be later delete[]d by the caller), or else it must point to
+    // a (>=)16-byte buffer (which this function will also return).
+
 #endif
diff --git a/liveMedia/include/uLawAudioFilter.hh b/liveMedia/include/uLawAudioFilter.hh
index d6066bc..92441bb 100644
--- a/liveMedia/include/uLawAudioFilter.hh
+++ b/liveMedia/include/uLawAudioFilter.hh
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Filters for converting between raw PCM audio and uLaw
 // C++ header
 
diff --git a/liveMedia/ourMD5.cpp b/liveMedia/ourMD5.cpp
index 751b0ab..12b21d2 100644
--- a/liveMedia/ourMD5.cpp
+++ b/liveMedia/ourMD5.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Because MD5 may not be implemented (at least, with the same interface) on all systems,
 // we have our own implementation.
 // Implementation
@@ -36,11 +36,11 @@ public:
 
   void addData(unsigned char const* inputData, unsigned inputDataSize);
   void end(char* outputDigest /*must point to an array of size DIGEST_SIZE_AS_STRING*/);
-
-private:
   void finalize(unsigned char* outputDigestInBytes);
       // Like "end()", except that the argument is a byte array, of size DIGEST_SIZE_IN_BYTES.
       // This function is used to implement "end()".
+
+private:
   void zeroize(); // to remove potentially sensitive information
   void transform64Bytes(unsigned char const block[64]); // does the actual MD5 transform
 
@@ -61,6 +61,18 @@ char* our_MD5Data(unsigned char const* data, unsigned dataSize, char* outputDige
   return outputDigest;
 }
 
+unsigned char* our_MD5DataRaw(unsigned char const* data, unsigned dataSize,
+			      unsigned char* outputDigest) {
+  MD5Context ctx;
+
+  ctx.addData(data, dataSize);
+
+  if (outputDigest == NULL) outputDigest = new unsigned char[DIGEST_SIZE_IN_BYTES];
+  ctx.finalize(outputDigest);
+
+  return outputDigest;
+}
+
 
 ////////// MD5Context implementation //////////
 
diff --git a/liveMedia/rtcp_from_spec.h b/liveMedia/rtcp_from_spec.h
index 912d2d9..629971f 100644
--- a/liveMedia/rtcp_from_spec.h
+++ b/liveMedia/rtcp_from_spec.h
@@ -40,6 +40,7 @@ typedef void* packet;
 #define PACKET_RTP 1
 #define PACKET_RTCP_REPORT 2
 #define PACKET_BYE 3
+#define PACKET_RTCP_APP 4
 
 /* The code from the spec calls drand48(), but we have drand30() instead */
 #define drand48 drand30
diff --git a/liveMedia/uLawAudioFilter.cpp b/liveMedia/uLawAudioFilter.cpp
index efacad6..041e67e 100644
--- a/liveMedia/uLawAudioFilter.cpp
+++ b/liveMedia/uLawAudioFilter.cpp
@@ -14,7 +14,7 @@ along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
 // "liveMedia"
-// Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
+// Copyright (c) 1996-2016 Live Networks, Inc.  All rights reserved.
 // Filters for converting between raw PCM audio and uLaw
 // Implementation
 
diff --git a/mediaServer/DynamicRTSPServer.cpp b/mediaServer/DynamicRTSPServer.cpp
index 1159dec..a5a23f6 100644
--- a/mediaServer/DynamicRTSPServer.cpp
+++ b/mediaServer/DynamicRTSPServer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A subclass of "RTSPServer" that creates "ServerMediaSession"s on demand,
 // based on whether or not the specified stream name exists as a file
 // Implementation
@@ -44,8 +44,8 @@ DynamicRTSPServer::~DynamicRTSPServer() {
 static ServerMediaSession* createNewSMS(UsageEnvironment& env,
 					char const* fileName, FILE* fid); // forward
 
-ServerMediaSession*
-DynamicRTSPServer::lookupServerMediaSession(char const* streamName) {
+ServerMediaSession* DynamicRTSPServer
+::lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession) {
   // First, check whether the specified "streamName" exists as a local file:
   FILE* fid = fopen(streamName, "rb");
   Boolean fileExists = fid != NULL;
@@ -59,28 +59,52 @@ DynamicRTSPServer::lookupServerMediaSession(char const* streamName) {
     if (smsExists) {
       // "sms" was created for a file that no longer exists. Remove it:
       removeServerMediaSession(sms);
+      sms = NULL;
     }
+
     return NULL;
   } else {
-    if (!smsExists) {
-      // Create a new "ServerMediaSession" object for streaming from the named file.
-      sms = createNewSMS(envir(), streamName, fid);
+    if (smsExists && isFirstLookupInSession) { 
+      // Remove the existing "ServerMediaSession" and create a new one, in case the underlying
+      // file has changed in some way:
+      removeServerMediaSession(sms); 
+      sms = NULL;
+    } 
+
+    if (sms == NULL) {
+      sms = createNewSMS(envir(), streamName, fid); 
       addServerMediaSession(sms);
     }
+
     fclose(fid);
     return sms;
   }
 }
 
 // Special code for handling Matroska files:
-static char newMatroskaDemuxWatchVariable;
-static MatroskaFileServerDemux* demux;
-static void onMatroskaDemuxCreation(MatroskaFileServerDemux* newDemux, void* /*clientData*/) {
-  demux = newDemux;
-  newMatroskaDemuxWatchVariable = 1;
+struct MatroskaDemuxCreationState {
+  MatroskaFileServerDemux* demux;
+  char watchVariable;
+};
+static void onMatroskaDemuxCreation(MatroskaFileServerDemux* newDemux, void* clientData) {
+  MatroskaDemuxCreationState* creationState = (MatroskaDemuxCreationState*)clientData;
+  creationState->demux = newDemux;
+  creationState->watchVariable = 1;
 }
 // END Special code for handling Matroska files:
 
+// Special code for handling Ogg files:
+struct OggDemuxCreationState {
+  OggFileServerDemux* demux;
+  char watchVariable;
+};
+static void onOggDemuxCreation(OggFileServerDemux* newDemux, void* clientData) {
+  OggDemuxCreationState* creationState = (OggDemuxCreationState*)clientData;
+  creationState->demux = newDemux;
+  creationState->watchVariable = 1;
+}
+// END Special code for handling Ogg files:
+
 #define NEW_SMS(description) do {\
 char const* descStr = description\
     ", streamed by the LIVE555 Media Server";\
@@ -181,15 +205,33 @@ static ServerMediaSession* createNewSMS(UsageEnvironment& env,
     sms->addSubsession(DVVideoFileServerMediaSubsession::createNew(env, fileName, reuseSource));
   } else if (strcmp(extension, ".mkv") == 0 || strcmp(extension, ".webm") == 0) {
     // Assumed to be a Matroska file (note that WebM ('.webm') files are also Matroska files)
+    OutPacketBuffer::maxSize = 100000; // allow for some possibly large VP8 or VP9 frames
     NEW_SMS("Matroska video+audio+(optional)subtitles");
 
-    // Create a Matroska file server demultiplexor for the specified file.  (We enter the event loop to wait for this to complete.)
-    newMatroskaDemuxWatchVariable = 0;
-    MatroskaFileServerDemux::createNew(env, fileName, onMatroskaDemuxCreation, NULL);
-    env.taskScheduler().doEventLoop(&newMatroskaDemuxWatchVariable);
+    // Create a Matroska file server demultiplexor for the specified file.
+    // (We enter the event loop to wait for this to complete.)
+    MatroskaDemuxCreationState creationState;
+    creationState.watchVariable = 0;
+    MatroskaFileServerDemux::createNew(env, fileName, onMatroskaDemuxCreation, &creationState);
+    env.taskScheduler().doEventLoop(&creationState.watchVariable);
+
+    ServerMediaSubsession* smss;
+    while ((smss = creationState.demux->newServerMediaSubsession()) != NULL) {
+      sms->addSubsession(smss);
+    }
+  } else if (strcmp(extension, ".ogg") == 0 || strcmp(extension, ".ogv") == 0 || strcmp(extension, ".opus") == 0) {
+    // Assumed to be an Ogg file
+    NEW_SMS("Ogg video and/or audio");
+
+    // Create a Ogg file server demultiplexor for the specified file.
+    // (We enter the event loop to wait for this to complete.)
+    OggDemuxCreationState creationState;
+    creationState.watchVariable = 0;
+    OggFileServerDemux::createNew(env, fileName, onOggDemuxCreation, &creationState);
+    env.taskScheduler().doEventLoop(&creationState.watchVariable);
 
     ServerMediaSubsession* smss;
-    while ((smss = demux->newServerMediaSubsession()) != NULL) {
+    while ((smss = creationState.demux->newServerMediaSubsession()) != NULL) {
       sms->addSubsession(smss);
     }
   }
diff --git a/mediaServer/DynamicRTSPServer.hh b/mediaServer/DynamicRTSPServer.hh
index bb58817..3478f40 100644
--- a/mediaServer/DynamicRTSPServer.hh
+++ b/mediaServer/DynamicRTSPServer.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A subclass of "RTSPServer" that creates "ServerMediaSession"s on demand,
 // based on whether or not the specified stream name exists as a file
 // Header file
@@ -38,7 +38,8 @@ protected:
   virtual ~DynamicRTSPServer();
 
 protected: // redefined virtual functions
-  virtual ServerMediaSession* lookupServerMediaSession(char const* streamName);
+  virtual ServerMediaSession*
+  lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession);
 };
 
 #endif
diff --git a/mediaServer/live555MediaServer.cpp b/mediaServer/live555MediaServer.cpp
index af805c9..58ef0ca 100644
--- a/mediaServer/live555MediaServer.cpp
+++ b/mediaServer/live555MediaServer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // LIVE555 Media Server
 // main program
 
@@ -68,6 +68,7 @@ int main(int argc, char** argv) {
   *env << "\t\".mkv\" => a Matroska audio+video+(optional)subtitles file\n";
   *env << "\t\".mp3\" => a MPEG-1 or 2 Audio file\n";
   *env << "\t\".mpg\" => a MPEG-1 or 2 Program Stream (audio+video) file\n";
+  *env << "\t\".ogg\" or \".ogv\" or \".opus\" => an Ogg audio and/or video file\n";
   *env << "\t\".ts\" => a MPEG Transport Stream file\n";
   *env << "\t\t(a \".tsx\" index file - if present - provides server 'trick play' support)\n";
   *env << "\t\".vob\" => a VOB (MPEG-2 video with AC-3 audio) file\n";
diff --git a/mediaServer/version.hh b/mediaServer/version.hh
index 4081652..e6876fa 100644
--- a/mediaServer/version.hh
+++ b/mediaServer/version.hh
@@ -1,10 +1,10 @@
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // Version information for the LIVE555 Media Server application
 // Header file
 
 #ifndef _MEDIA_SERVER_VERSION_HH
 #define _MEDIA_SERVER_VERSION_HH
 
-#define MEDIA_SERVER_VERSION_STRING "0.80"
+#define MEDIA_SERVER_VERSION_STRING "0.88"
 
 #endif
diff --git a/proxyServer/live555ProxyServer.cpp b/proxyServer/live555ProxyServer.cpp
index 3182175..3d02d9a 100644
--- a/proxyServer/live555ProxyServer.cpp
+++ b/proxyServer/live555ProxyServer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // LIVE555 Proxy Server
 // main program
 
diff --git a/testProgs/MPEG2TransportStreamIndexer.cpp b/testProgs/MPEG2TransportStreamIndexer.cpp
index 0c84736..850baf6 100644
--- a/testProgs/MPEG2TransportStreamIndexer.cpp
+++ b/testProgs/MPEG2TransportStreamIndexer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A program that reads an existing MPEG-2 Transport Stream file,
 // and generates a separate index file that can be used - by our RTSP server
 // implementation - to support 'trick play' operations when streaming the
diff --git a/testProgs/Makefile.tail b/testProgs/Makefile.tail
index 7432620..9c3fad3 100644
--- a/testProgs/Makefile.tail
+++ b/testProgs/Makefile.tail
@@ -1,6 +1,6 @@
 ##### End of variables to change
 
-MULTICAST_STREAMER_APPS = testMP3Streamer$(EXE) testMPEG1or2VideoStreamer$(EXE) testMPEG1or2AudioVideoStreamer$(EXE) testMPEG2TransportStreamer$(EXE) testMPEG4VideoStreamer$(EXE) testH264VideoStreamer$(EXE) testH265VideoStreamer$(EXE) testDVVideoStreamer$(EXE) testWAVAudioStreamer$(EXE) testAMRAudioStreamer$(EXE) vobStreamer$(EXE)
+MULTICAST_STREAMER_APPS = testMP3Streamer$(EXE) testMPEG1or2VideoStreamer$(EXE) testMPEG1or2AudioVideoStreamer$(EXE) testMPEG2TransportStreamer$(EXE) testMPEG4VideoStreamer$(EXE) testH264VideoStreamer$(EXE) testH265VideoStreamer$(EXE) testDVVideoStreamer$(EXE) testWAVAudioStreamer$(EXE) testAMRAudioStreamer$(EXE) testMKVStreamer$(EXE) testOggStreamer$(EXE) vobStreamer$(EXE)
 MULTICAST_RECEIVER_APPS = testMP3Receiver$(EXE) testMPEG1or2VideoReceiver$(EXE) testMPEG2TransportReceiver$(EXE) sapWatch$(EXE)
 MULTICAST_MISC_APPS = testRelay$(EXE) testReplicator$(EXE)
 MULTICAST_APPS = $(MULTICAST_STREAMER_APPS) $(MULTICAST_RECEIVER_APPS) $(MULTICAST_MISC_APPS)
@@ -39,6 +39,8 @@ DV_VIDEO_STREAMER_OBJS = testDVVideoStreamer.$(OBJ)
 WAV_AUDIO_STREAMER_OBJS = testWAVAudioStreamer.$(OBJ)
 AMR_AUDIO_STREAMER_OBJS	= testAMRAudioStreamer.$(OBJ)
 ON_DEMAND_RTSP_SERVER_OBJS	= testOnDemandRTSPServer.$(OBJ)
+MKV_STREAMER_OBJS	= testMKVStreamer.$(OBJ)
+OGG_STREAMER_OBJS	= testOggStreamer.$(OBJ)
 VOB_STREAMER_OBJS	= vobStreamer.$(OBJ)
 TEST_RTSP_CLIENT_OBJS    = testRTSPClient.$(OBJ)
 OPEN_RTSP_OBJS    = openRTSP.$(OBJ) playCommon.$(OBJ)
@@ -103,6 +105,10 @@ testAMRAudioStreamer$(EXE):	$(AMR_AUDIO_STREAMER_OBJS) $(LOCAL_LIBS)
 	$(LINK)$@ $(CONSOLE_LINK_OPTS) $(AMR_AUDIO_STREAMER_OBJS) $(LIBS)
 testOnDemandRTSPServer$(EXE):	$(ON_DEMAND_RTSP_SERVER_OBJS) $(LOCAL_LIBS)
 	$(LINK)$@ $(CONSOLE_LINK_OPTS) $(ON_DEMAND_RTSP_SERVER_OBJS) $(LIBS)
+testMKVStreamer$(EXE):	$(MKV_STREAMER_OBJS) $(LOCAL_LIBS)
+	$(LINK)$@ $(CONSOLE_LINK_OPTS) $(MKV_STREAMER_OBJS) $(LIBS)
+testOggStreamer$(EXE):	$(OGG_STREAMER_OBJS) $(LOCAL_LIBS)
+	$(LINK)$@ $(CONSOLE_LINK_OPTS) $(OGG_STREAMER_OBJS) $(LIBS)
 vobStreamer$(EXE):	$(VOB_STREAMER_OBJS) $(LOCAL_LIBS)
 	$(LINK)$@ $(CONSOLE_LINK_OPTS) $(VOB_STREAMER_OBJS) $(LIBS)
 testRTSPClient$(EXE):	$(TEST_RTSP_CLIENT_OBJS) $(LOCAL_LIBS)
diff --git a/testProgs/openRTSP.cpp b/testProgs/openRTSP.cpp
index 85aaeba..0fd2675 100644
--- a/testProgs/openRTSP.cpp
+++ b/testProgs/openRTSP.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A RTSP client application that opens a RTSP URL argument,
 // and extracts and records the data from each incoming RTP stream.
 //
diff --git a/testProgs/playCommon.cpp b/testProgs/playCommon.cpp
index 3c0ee93..c78b432 100644
--- a/testProgs/playCommon.cpp
+++ b/testProgs/playCommon.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A common framework, used for the "openRTSP" and "playSIP" applications
 // Implementation
 //
@@ -54,6 +54,7 @@ void shutdown(int exitCode = 1);
 void signalHandlerShutdown(int sig);
 void checkForPacketArrival(void* clientData);
 void checkInterPacketGaps(void* clientData);
+void checkSessionTimeoutBrokenServer(void* clientData);
 void beginQOSMeasurement();
 
 char const* progName;
@@ -63,6 +64,7 @@ Authenticator* ourAuthenticator = NULL;
 char const* streamURL = NULL;
 MediaSession* session = NULL;
 TaskToken sessionTimerTask = NULL;
+TaskToken sessionTimeoutBrokenServerTask = NULL;
 TaskToken arrivalCheckTimerTask = NULL;
 TaskToken interPacketGapCheckTimerTask = NULL;
 TaskToken qosMeasurementTimerTask = NULL;
@@ -91,6 +93,8 @@ Boolean sendOptionsRequest = True;
 Boolean sendOptionsRequestOnly = False;
 Boolean oneFilePerFrame = False;
 Boolean notifyOnPacketArrival = False;
+Boolean sendKeepAlivesToBrokenServers = False;
+unsigned sessionTimeoutParameter = 0;
 Boolean streamUsingTCP = False;
 Boolean forceMulticastOnUnspecified = False;
 unsigned short desiredPortNum = 0;
@@ -136,7 +140,7 @@ void usage() {
        << "]" << (supportCodecSelection ? " [-A <audio-codec-rtp-payload-format-code>|-M <mime-subtype-name>]" : "")
        << " [-s <initial-seek-time>]|[-U <absolute-seek-time>] [-z <scale>] [-g user-agent]"
        << " [-k <username-for-REGISTER> <password-for-REGISTER>]"
-       << " [-P <interval-in-seconds>]"
+       << " [-P <interval-in-seconds>] [-K]"
        << " [-w <width> -h <height>] [-f <frames-per-second>] [-y] [-H] [-Q [<measurement-interval>]] [-F <filename-prefix>] [-b <file-sink-buffer-size>] [-B <input-socket-buffer-size>] [-I <input-interface-ip-address>] [-m] [<url>|-R [<port-num>]] (or " << progName << " -o [-V] <url>)\n";
   shutdown();
 }
@@ -363,6 +367,11 @@ int main(int argc, char** argv) {
       break;
     }
 
+    case 'K': { // Send periodic 'keep-alive' requests to keep broken server sessions alive
+      sendKeepAlivesToBrokenServers = True;
+      break;
+    }
+
     case 'A': { // specify a desired audio RTP payload format
       unsigned formatArg;
       if (sscanf(argv[2], "%u", &formatArg) != 1
@@ -705,9 +714,14 @@ void continueAfterDESCRIBE(RTSPClient*, int resultCode, char* resultString) {
 	     << "\" subsession: " << env->getResultMsg() << "\n";
       } else {
 	*env << "Created receiver for \"" << subsession->mediumName()
-	     << "/" << subsession->codecName()
-	     << "\" subsession (client ports " << subsession->clientPortNum()
-	     << "-" << subsession->clientPortNum()+1 << ")\n";
+	     << "/" << subsession->codecName() << "\" subsession (";
+	if (subsession->rtcpIsMuxed()) {
+	  *env << "client port " << subsession->clientPortNum();
+	} else {
+	  *env << "client ports " << subsession->clientPortNum()
+	       << "-" << subsession->clientPortNum()+1;
+	}
+	*env << ")\n";
 	madeProgress = True;
 	
 	if (subsession->rtpSource() != NULL) {
@@ -756,12 +770,18 @@ void continueAfterDESCRIBE(RTSPClient*, int resultCode, char* resultString) {
 
 MediaSubsession *subsession;
 Boolean madeProgress = False;
-void continueAfterSETUP(RTSPClient*, int resultCode, char* resultString) {
+void continueAfterSETUP(RTSPClient* client, int resultCode, char* resultString) {
   if (resultCode == 0) {
       *env << "Setup \"" << subsession->mediumName()
 	   << "/" << subsession->codecName()
-	   << "\" subsession (client ports " << subsession->clientPortNum()
-	   << "-" << subsession->clientPortNum()+1 << ")\n";
+	   << "\" subsession (";
+      if (subsession->rtcpIsMuxed()) {
+	*env << "client port " << subsession->clientPortNum();
+      } else {
+	*env << "client ports " << subsession->clientPortNum()
+	     << "-" << subsession->clientPortNum()+1;
+      }
+      *env << ")\n";
       madeProgress = True;
   } else {
     *env << "Failed to setup \"" << subsession->mediumName()
@@ -770,6 +790,8 @@ void continueAfterSETUP(RTSPClient*, int resultCode, char* resultString) {
   }
   delete[] resultString;
 
+  sessionTimeoutParameter = client->sessionTimeoutParameter();
+
   // Set up the next subsession, if any:
   setupStreams();
 }
@@ -842,25 +864,49 @@ void createOutputFiles(char const* periodicFilenameSuffix) {
 	// (unless the '-P <interval-in-seconds>' option was given):
 	sprintf(outFileName, "stdout");
       }
-      FileSink* fileSink;
-      if (strcmp(subsession->mediumName(), "audio") == 0 &&
-	  (strcmp(subsession->codecName(), "AMR") == 0 ||
-	   strcmp(subsession->codecName(), "AMR-WB") == 0)) {
-	// For AMR audio streams, we use a special sink that inserts AMR frame hdrs:
-	fileSink = AMRAudioFileSink::createNew(*env, outFileName,
-					       fileSinkBufferSize, oneFilePerFrame);
-      } else if (strcmp(subsession->mediumName(), "video") == 0 &&
-		 (strcmp(subsession->codecName(), "H264") == 0)) {
-	// For H.264 video stream, we use a special sink that adds 'start codes', and (at the start) the SPS and PPS NAL units:
-	fileSink = H264VideoFileSink::createNew(*env, outFileName,
-						subsession->fmtp_spropparametersets(),
-						fileSinkBufferSize, oneFilePerFrame);
-      } else {
+
+      FileSink* fileSink = NULL;
+      Boolean createOggFileSink = False; // by default
+      if (strcmp(subsession->mediumName(), "video") == 0) {
+	if (strcmp(subsession->codecName(), "H264") == 0) {
+	  // For H.264 video stream, we use a special sink that adds 'start codes',
+	  // and (at the start) the SPS and PPS NAL units:
+	  fileSink = H264VideoFileSink::createNew(*env, outFileName,
+						  subsession->fmtp_spropparametersets(),
+						  fileSinkBufferSize, oneFilePerFrame);
+	} else if (strcmp(subsession->codecName(), "H265") == 0) {
+	  // For H.265 video stream, we use a special sink that adds 'start codes',
+	  // and (at the start) the VPS, SPS, and PPS NAL units:
+	  fileSink = H265VideoFileSink::createNew(*env, outFileName,
+						  subsession->fmtp_spropvps(),
+						  subsession->fmtp_spropsps(),
+						  subsession->fmtp_sproppps(),
+						  fileSinkBufferSize, oneFilePerFrame);
+	} else if (strcmp(subsession->codecName(), "THEORA") == 0) {
+	  createOggFileSink = True;
+	}
+      } else if (strcmp(subsession->mediumName(), "audio") == 0) {
+	if (strcmp(subsession->codecName(), "AMR") == 0 ||
+	    strcmp(subsession->codecName(), "AMR-WB") == 0) {
+	  // For AMR audio streams, we use a special sink that inserts AMR frame hdrs:
+	  fileSink = AMRAudioFileSink::createNew(*env, outFileName,
+						 fileSinkBufferSize, oneFilePerFrame);
+	} else if (strcmp(subsession->codecName(), "VORBIS") == 0 ||
+		   strcmp(subsession->codecName(), "OPUS") == 0) {
+	  createOggFileSink = True;
+	}
+      }
+      if (createOggFileSink) {
+	fileSink = OggFileSink
+	  ::createNew(*env, outFileName,
+		      subsession->rtpTimestampFrequency(), subsession->fmtp_config());
+      } else if (fileSink == NULL) {
 	// Normal case:
 	fileSink = FileSink::createNew(*env, outFileName,
 				       fileSinkBufferSize, oneFilePerFrame);
       }
       subsession->sink = fileSink;
+
       if (subsession->sink == NULL) {
 	*env << "Failed to create FileSink for \"" << outFileName
 	     << "\": " << env->getResultMsg() << "\n";
@@ -974,6 +1020,7 @@ void continueAfterPLAY(RTSPClient*, int resultCode, char* resultString) {
     *env << "Failed to start playing session: " << resultString << "\n";
     delete[] resultString;
     shutdown();
+    return;
   } else {
     *env << "Started playing session\n";
   }
@@ -1019,9 +1066,12 @@ void continueAfterPLAY(RTSPClient*, int resultCode, char* resultString) {
 #endif
   }
 
+  sessionTimeoutBrokenServerTask = NULL;
+
   // Watch for incoming packets (if desired):
   checkForPacketArrival(NULL);
   checkInterPacketGaps(NULL);
+  checkSessionTimeoutBrokenServer(NULL);
 }
 
 void closeMediaSinks() {
@@ -1078,6 +1128,7 @@ void sessionAfterPlaying(void* /*clientData*/) {
     if (env != NULL) {
       env->taskScheduler().unscheduleDelayedTask(periodicFileOutputTask);
       env->taskScheduler().unscheduleDelayedTask(sessionTimerTask);
+      env->taskScheduler().unscheduleDelayedTask(sessionTimeoutBrokenServerTask);
       env->taskScheduler().unscheduleDelayedTask(arrivalCheckTimerTask);
       env->taskScheduler().unscheduleDelayedTask(interPacketGapCheckTimerTask);
       env->taskScheduler().unscheduleDelayedTask(qosMeasurementTimerTask);
@@ -1320,6 +1371,7 @@ void shutdown(int exitCode) {
   if (env != NULL) {
     env->taskScheduler().unscheduleDelayedTask(periodicFileOutputTask);
     env->taskScheduler().unscheduleDelayedTask(sessionTimerTask);
+    env->taskScheduler().unscheduleDelayedTask(sessionTimeoutBrokenServerTask);
     env->taskScheduler().unscheduleDelayedTask(arrivalCheckTimerTask);
     env->taskScheduler().unscheduleDelayedTask(interPacketGapCheckTimerTask);
     env->taskScheduler().unscheduleDelayedTask(qosMeasurementTimerTask);
@@ -1451,3 +1503,21 @@ void checkInterPacketGaps(void* /*clientData*/) {
 				 (TaskFunc*)checkInterPacketGaps, NULL);
   }
 }
+
+void checkSessionTimeoutBrokenServer(void* /*clientData*/) {
+  if (!sendKeepAlivesToBrokenServers) return; // we're not checking
+
+  // Send an "OPTIONS" request, starting with the second call
+  if (sessionTimeoutBrokenServerTask != NULL) {
+    getOptions(NULL);
+  }
+  
+  unsigned sessionTimeout = sessionTimeoutParameter == 0 ? 60/*default*/ : sessionTimeoutParameter;
+  unsigned secondsUntilNextKeepAlive = sessionTimeout <= 5 ? 1 : sessionTimeout - 5;
+      // Reduce the interval a little, to be on the safe side
+
+  sessionTimeoutBrokenServerTask 
+    = env->taskScheduler().scheduleDelayedTask(secondsUntilNextKeepAlive*1000000,
+			 (TaskFunc*)checkSessionTimeoutBrokenServer, NULL);
+					       
+}
diff --git a/testProgs/playCommon.hh b/testProgs/playCommon.hh
index 68d20cb..c719773 100644
--- a/testProgs/playCommon.hh
+++ b/testProgs/playCommon.hh
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A common framework, used for the "openRTSP" and "playSIP" applications
 // Interfaces
 
diff --git a/testProgs/playSIP.cpp b/testProgs/playSIP.cpp
index a6bef00..9756958 100644
--- a/testProgs/playSIP.cpp
+++ b/testProgs/playSIP.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A SIP client test program that opens a SIP URL argument,
 // and extracts the data from each incoming RTP stream.
 
diff --git a/testProgs/registerRTSPStream.cpp b/testProgs/registerRTSPStream.cpp
index 89d0606..4658c08 100644
--- a/testProgs/registerRTSPStream.cpp
+++ b/testProgs/registerRTSPStream.cpp
@@ -13,11 +13,9 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A demonstration application that uses our custom RTSP "REGISTER" command to register a stream
 // (given by "rtsp://" URL) with a RTSP client or proxy server
-// splits it into Audio (AC3) and Video (MPEG) Elementary Streams,
-// and streams both using RTP.
 // main program
 
 #include "liveMedia.hh"
diff --git a/testProgs/sapWatch.cpp b/testProgs/sapWatch.cpp
index b3449cb..c4aa1e3 100644
--- a/testProgs/sapWatch.cpp
+++ b/testProgs/sapWatch.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A program that receives and prints SDP/SAP announcements
 // (on the default SDP/SAP directory: 224.2.127.254/9875)
 
diff --git a/testProgs/test.mov b/testProgs/test.mov
new file mode 100644
index 0000000..abf63ad
Binary files /dev/null and b/testProgs/test.mov differ
diff --git a/testProgs/test.txt b/testProgs/test.txt
new file mode 100644
index 0000000..5c6983a
--- /dev/null
+++ b/testProgs/test.txt
@@ -0,0 +1,26 @@
+File size: 5016383
+mdat  <4997601 interior bytes>
+moov
+1:	mvhd :00000000:d29dcd1b:d29dcd1b:00015f90:005121ba:00010000:01000000:00000000:00000000:00010000:00000000:00000000:00000000:00010000:00000000:00000000:00000000:40000000:00000000:00000000:00000000:00000000:00000000:00000000:00000002
+1:	trak
+2:		tkhd :0000000f:d29dcd1b:d29dcd1b:00000001:00000000:005121ba:00000000:00000000:00000000:01000000:00010000:00000000:00000000:00000000:00010000:00000000:00000000:00000000:40000000:00f00000:00b40000
+2:		edts
+3:			elst :00000000:0000000c:0006e42d:00000000:00010000:0006e4ad:00000bff:00010000:0006e4ae:000017ff:00010000:0006e4ac:000023ff:00010000:0006e4ad:00002fff:00010000:0006e4ac:00003bff:00010000:0006e4ae:000047ff:00010000:0006e4ad:000053ff:00010000:0006e4ac:00005fff:00010000:0006e4ac:00006bff:00010000:0006e4ae:000077ff:00010000:0003ca50:000083ff:00010000
+2:		mdia
+3:			mdhd :00000000:d29dcd1b:d29dcd1b:00000258:00008a77:00000000
+3:			hdlr :00000000:6d686c72:76696465:6170706c:00000000:00000000:19417070:6c652056:6964656f:204d6564:69612048:616e646c:6572
+3:			minf
+4:				vmhd :00000001:00408000:80008000
+4:				hdlr :00000000:64686c72:616c6973:6170706c:00000000:00000000:18417070:6c652041:6c696173:20446174:61204861:6e646c65:72
+4:				dinf
+5:					dref :00000000:00000001
+6:						alis :00000001
+4:				stbl
+5:					stsd :00000000:00000001
+6:						avc1 :00000000:00000001:00000000:6170706c:00000000:00000000:00f000b4:00480000:00480000:00000000:00010548:2e323634:00000000:00000000:00000000:00000000:00000000:00000000:00000018:ffff
+7:							avcC :014d4033:ffe10018:674d4033:92540c04:b4200000:03004000:000cd1e3:06540000:01000668:ee3c8000:00
+5:					stts :00000000:00000003:00000023:00000018:00000001:00000017:000005a1:00000018
+5:					stss :00000000:00000026:00000001:00000033:00000065:00000097:000000c9:000000f5:00000129:00000157:0000015b:0000018d:000001bf:000001e7:000001f1:0000021f:00000223:00000255:00000287:000002b0:000002b9:000002eb:0000031d:0000034f:00000381:000003b3:000003dc:000003e5:00000416:00000417:00000449:00000476:0000047b:000004ad:000004df:00000511:0000053f:00000543:00000575:000005a7
+5:					stsc :00000000:00000003:00000001:00000001:00000001:00000313:00000002:00000001:00000314:00000001:00000001
+5:					stsz :00000000:00000000:000005c5:000028ff:0000086f:000009ab:00000d94:00000d2f:00000c36:00000b9e:00000d13:00000d3a:00000c25:00000d19:00000e8d:00000c75:00000d6c:00000da3:00000e31:00000c1e:00000c4a:00000ee3:00000e20:00000c15:00000cb8:00000cdd:00000df7:00000c93:00000e32:00002f7e:0000093e:00000d7e:000010e9:00001205:000010de:00000e1a:00000fda:00000dbe:00000e02:00000dce:00001211:00000e9b:00000dc5:00000ee6:00000c0b:00000d52:00000eb1:00000e11:00000e7a:00000bb0:00000eb1:00000ffd:00001178:00004fc4:000005d1:00000912:00000b4e:00000c9b:00000de3:00000bc0:00001060:0000156e:00000632:000007ba:000009ff:00000838:000009e0:0000094a:000008e3:00000a05:00000bb9:00000b15:00000d14:00000b4c:00000b4b:00000c95:00000d49:00000b8c:00000bc1:000009fc:000008f5:0000075b:000008cb:00000757:00000a16:00000666:00000671:00000539:000005d3:0000066b:00000446:00000580:00001252:000006d5:000008a6:00000788:0000061e:00000655:000006d7:000006f5:00000714:00000670:00000869:000019ba:00000355:000004d3:0000057d:00000736:000005e1:00000673:00000616:000006b4:00000723:000008d5:000006b9:00000788:0000083a:00001862:00000a10:0000097e:00000b47:00000b8c:00000aeb:00000b17:00000abf:00000a23:00000a8b:00000b5d:00000a1c:0000098a:00000992:000009b3:000009a8:00000973:00000b25:00000a2d:00000a83:00000b16:000009f7:00000b9a:00000afa:00000aa1:00000aa9:00000baa:00000b69:00000a14:00000b7d:00000b65:00000af1:00000b06:00000ade:00000acf:00000a72:0000296a:000007d5:000008a2:00000a61:00000b3e:00000ade:00000b62:00000c3a:00000b9e:00000c70:00000c3c:00000bfc:00000c0f:0000127a:00000c8b:00000d00:000009d6:000008d5:000004d1:00000b96:00000ed4:000010d5:00001791:000005b8:00000656:0000079b:00000a0d:000005f4:000007fc:000007ef:000008ed:00000829:000008f1:00000965:00000a00:00000797:00000854:0000086a:0000084a:00000855:00000876:0000072c:00000842:00000840:00000861:0000076f:00000847:00000877:000008fa:000007a4:00003610:00000298:000005df:000005d6:0000088e:00000775:000006a0:0000089c:00000637:00000879:0000051b:00000835:0000092c:0000072a:00000769:00000709:000007b8:00000854:0000089e:00000793:000014f4:00001341:0000141c:000012e7:000013fe:00001458:000014d4:000016a8:000016c1:0000184d:00001892:0000177d:00001a26:00001c70:00001d27:00001e28:00001f93:00002005:00002234:00002325:00001fea:00002011:00002027:00001fdc:000038c4:0000204d:00002291:000022c8:000021fd:0000218b:00002197:00002094:0000204b:00002149:000020bd:000020f6:000020de:0000213f:00002177:000021ea:00002114:000022c8:00002335:00002305:00002447:00002684:000023ca:000016bb:000004f8:0000098d:000008b6:00000a63:000009bc:00000990:0000095e:000009b0:00000a4f:00000987:000009d7:00000984:00000931:000009ca:0000095d:00000986:00000a60:000009fd:00000b54:00000ba4:00000b5a:00000a56:00000ab8:00000b35:00000abc:00000b7a:00000bfb:00000bab:00002d1c:00001674:00000917:00000b70:00000d30:00000b65:00000b2f:00000b91:00000adb:00000c9b:00000cfd:00000c71:00000aa5:00000cc7:00000c0d:00000b53:00000c0e:00000b7d:00000af6:00000c12:00000a67:00000a4b:00000be2:00000b48:00000c9e:00000a45:00000bfb:00000b5a:00000b30:00000a69:00000a3f:0000230e:00001d72:00001c5c:00001d4a:00001d0c:00001c51:00001cd9:00001db0:00001cd9:00001d68:00001dc4:00001e87:00001ec3:00001f1c:00001ee5:000037bb:00001a7f:00001a9b:00001e79:000025c7:00000ffc:0000123b:00001313:000013f1:000015c2:00001752:0000182d:00001966:00001af5:00001a90:00001b70:00001ceb:00001928:00001a72:00001a88:00001958:00001852:00001bcd:00001ac5:0000197f:000018d0:000017aa:0000176e:000016d6:0000158e:00001608:00001607:0000160e:00001688:000016f9:0000163d:00001580:000014f0:00001421:000013a8:000013b8:000013f3:0000150c:00001524:0000159f:0000168b:0000182f:00001a2d:00001a32:00001ae6:0000198a:00001b07:00001c11:00001b4b:000036a7:000018b1:000017fd:000017c3:00001a03:0000188f:00001867:0000256a:0000200a:000020ab:000020de:00002023:00002032:000020a3:00001f46:00001dfa:00001cad:00001bd7:00001c12:00001c10:00001c66:00001c29:00001cd8:00001c72:00001ca4:00001caf:00001ceb:00001e0a:00001bac:00001ca3:00001d21:00001d76:00001e5f:00001f62:00002059:00001d47:0000060b:00000601:0000099c:00000b13:00000b95:00000a8d:00000af6:000009c6:00000a43:00000a7d:00000ab7:00000ad7:00000b5d:00000a9b:0000326a:00000322:000006e3:00000981:000008f4:00000819:00000bc4:00000a07:00000959:000009e6:00000a1e:000008f8:0000096e:000009fe:000009ac:00000a1c:000009c7:000008ff:00000ad7:00000a86:0000097e:00000939:000009d2:00000aaf:00000977:00000941:0000090b:000009da:00000a1b:00000921:0000091c:00000909:000007fb:0000088f:00000a7c:0000086b:00000962:000009ba:000008bd:000008ac:0000378f:0000032f:000005e2:00000927:000008a2:0000095d:000009de:00000cc0:00000b0c:00000bd0:000035ca:0000052c:000008ba:00000796:00000a80:00000a89:0000097e:000008a4:00000949:00000837:00000ae9:00000a95:0000093b:0000099d:00000a74:00000b27:000009f5:00000acb:0000095f:00000d1d:000005d2:0000062a:00000803:000007ec:00000774:00000938:000009e0:00000991:00000a71:00000866:00000633:00000689:00000646:000003a0:000004cb:00000366:00000377:000003ef:0000033f:0000039c:0000035a:000003bb:000003ad:000003a3:000002e0:00000359:00001fc1:0000062b:0000077a:00000762:000018dd:00000518:000007e5:00000798:0000081b:000007de:000007a5:00000774:00000841:000008cb:000008de:00000933:00000a0a:00000925:00000a3d:00000ad7:00000a3f:00000a21:0000085d:00000730:00000740:00000874:00000844:000011b5:000003ee:00000484:000006dc:00000683:0000056a:00000552:000005fa:0000062e:000005b1:00000650:0000058b:00000512:000005d3:00000591:00000552:000005de:00000562:00000588:000005f9:00000542:000005a8:0000061c:00000594:0000057b:000006bd:00000697:00002117:0000030e:000004ba:000004c2:00000736:0000069b:0000062c:00000707:0000064f:000005ae:00000db3:00000627:0000068b:000006f9:000006b8:000008a5:00000841:000006dd:00000943:00000a2f:00000906:00000859:00000898:000008ad:000008b7:00000885:00000854:00000839:000008a6:000008a2:0000097c:00000a2a:00000969:00001fa4:00000ae2:0000097f:00000e2f:00000cb0:00000c81:00000c4c:00000c15:00000e88:00000d72:00000d3d:00000cf3:00000d1b:00000dcd:00000d40:00000c81:00000ca3:00003782:0000052d:0000096c:00000983:00000a7f:00000d5d:00000bae:00000bb2:000015a9:00000614:00000746:000008d0:00000828:000007be:000009a8:00000707:00000687:00000804:000007b7:000007ff:00000892:00000877:00000809:0000084c:000007ac:00000790:00000807:0000070f:00000922:000008cc:000008bb:000007e2:000008dc:000006c2:000006bc:000007a3:00000944:00000689:00000812:000007b8:00000693:00003ba2:00000a2a:00000d32:00000dbd:00000f49:00000f39:00000fc9:00000ff3:00000fce:00002f20:00000bee:00000df8:00000e8f:00000ebf:00000f49:00001188:00000fa4:00000f15:0000100d:00000f9e:00000f9c:00001081:00000fed:00001095:000010ae:000010be:00001016:00001009:00000f6c:00001060:00000ee6:00000e76:00000f3e:00000fde:00000e44:00000e6b:00000ee5:00000ebb:00000f1b:00000e96:00000eb1:00000dad:00001cc9:00000535:00000714:0000093d:00000762:00000979:0000094c:00000868:00000869:00000878:000008fd:00000a48:00000aa9:0000089a:000007c7:00000922:000005c6:00002ff0:0000036e:000005d7:0000108d:00000696:00000695:00000745:0000072d:0000068c:0000081c:000007d4:0000081f:00000864:000017c9:00000bd5:00000cf7:00000d33:00000e78:00001081:00000f0e:000010c3:00000e09:00000d03:00000e5d:00000e10:00000e5f:00000d32:00000ce2:00000ce4:00000ce2:00000d15:00000e0f:000011d0:000007b8:0000079d:0000080e:000008af:0000081c:000007bf:0000081b:000007a1:000007a1:000007ec:000007d6:000007cd:00000789:000006ed:000006cf:00000835:0000080f:00001d29:000004b6:00000703:00000796:00000853:0000080d:0000079b:00000802:00000778:0000070e:0000164a:00000733:00000883:000007b5:000008b6:00000837:00000a1d:00000896:00000836:000008c8:0000092a:000007fd:000008e4:0000092b:00000856:00000827:000008bf:0000093d:000008e3:00000942:0000091b:00000858:00000837:000007a3:00002171:000007b8:00000a8d:00000ccf:00000c6a:00000be7:00000c95:00000cb2:00000c4b:00000ccd:00000d21:00000bb8:00000c62:00000c31:00000bbf:00000c28:000033d9:00000599:00000949:00000bf8:00000abb:00000b76:00000bd8:00000d9e:00000cac:00001cd2:0000059c:0000085a:00000a76:0000092c:000008df:00000929:00000a5c:000009ad:00000800:00000899:000009fc:000009c0:0000082b:00000921:0000089f:000008d1:00000850:00000890:00000824:000009b1:00000953:0000096e:000007c0:00000989:000008df:000009da:000009e1:00000986:00000905:00001a1f:0000055a:000008ad:00000b89:00000bcd:00000910:00000a7c:00000ae7:00000ad5:00000979:00000a1c:00002f37:0000041a:000006cc:00000818:00000b6a:000008af:00000816:000009c8:00000776:00000940:000009b5:00000767:00002981:000004f5:00000792:00000a2a:000009c3:000009c2:00000a13:00000b38:00000a0e:00000a0c:000009f5:00000c8e:00000b57:000009c7:00000a68:00000d71:000009d4:00000ca7:00000b9a:00000a33:00000ca7:0000091a:00000b76:00000d16:00002143:000006aa:00000974:00000bfc:00000aee:00000a1b:00000aac:00000ac9:000009ff:00000af1:00000c06:00000a37:00000b27:00000c4e:00003a47:00000434:00000785:000009e2:00000932:00000a8f:000009cf:00000a71:00000ad1:00000b2d:00000aa6:00000bf1:00000b18:00000aa2:00000ca1:00000aef:0000099f:00000af0:00000952:000009fa:00000b85:000009f1:00000a3c:00000baa:0000095a:00000a0b:00000a24:00001347:00000712:00000800:0000082f:00000a15:00000957:00000951:00000954:00000917:00000843:000007d5:000008a5:00000703:00000652:000016c0:000005fb:000006ff:00000788:0000073e:00000792:0000088f:00000863:00000905:000018f9:0000055b:000007d5:00000961:00000915:000009e0:00000a78:000009ee:00000a3a:00000a52:00000980:000008b3:0000098a:000007ee:0000084a:00000860:00000846:00000868:00000866:000007ee:000007fd:000008e2:00000819:00000790:00000757:00000873:00001eab:00000923:00000c39:00000a44:00000a9a:00000c94:00000b95:00000bee:00000b4d:00000c1e:00000d28:000009d1:00000a99:00000bcc:00000b9a:00000d53:00000d44:00000c5c:00000c3e:00000cfa:00000b9b:00000b95:00000ab0:00003c65:00002973:000007e7:00000b4c:00000c05:00000b8f:00000bf5:00000c52:00000d30:00000e71:00000f05:0000102f:00000be8:00000bad:00000bb6:00000b68:00000c55:00000d1a:00000c5a:00000ce8:00000cce:00000b76:00000b62:00000b83:00000a55:00000c5d:00000b43:00000ab2:00000a04:00001903:0000096a:00000a66:00000c7f:00000b9d:00000be0:00000bb2:00000b2d:00000afa:00000af5:00000a08:000009b2:00000a33:00000960:00000c71:00000aa2:00000a9b:00000bbf:00000c8e:00000be5:00000bc6:00000b8b:00002459:0000057c:000008d8:0000086c:00000a7b:00000a97:00000a03:000009b9:00000a31:00000954:00000924:00000933:00000899:00000885:0000084e:00000956:0000085b:00000810:00000860:00000817:00000816:00001285:000004df:00000778:0000063e:000007c0:0000077f:00000670:000005a8:0000070c:00000624:000005da:00000762:0000073c:00000648:000006e5:0000061c:000006bb:00000711:0000064c:0000058f:000006da:0000066d:00000594:00000650:00002b86:000008cf:00000db4:00000e10:00000e48:0000263e:00000a26:00000da6:00000e56:00000c2a:00000d81:00000ee5:00000faa:000010dd:00000f60:00000ed2:00000e04:00000f5b:00002004:00000b8f:00000cb1:00000eed:00000eba:00000f82:00000e26:00001002:00001004:00000e4f:00000ea3:00000f1c:00000e4e:00000ef1:00000f88:00000e55:00000e7c:00000f16:00000e20:00000ebf:00000e6c:00000d5e:00000e63:00000f00:00000e37:00000e4e:00000ee2:00000df9:00000da9:0000117e:00000eab:00000f76:00001023:00000f8c:00000dee:00000fce:00000e9f:00002ff6:00000a2d:00000bdd:00000f27:00000f3b:00000ea9:00000df2:00000e9f:00000d74:00000e62:00000f53:00000e8a:00000e62:00000f49:00000e37:00000f18:00000f1b:00000dc3:00000df8:00000f19:00000d9b:00000e91:00000f5b:00000e33:00000ef9:00000f49:00000e02:00000e61:00000f3f:00000eaf:00000f0b:00000f2c:00000dc9:00000db9:00000dca:0000101a:00000f06:0000101f:00000cf9:00000dbd:00000eef:00000d77:00000de9:00000eb1:00000d0f:00000e10:00000f1a:00000e4b:00000ee0:00000f44:00002e55:00000961:00000d93:00000c39:00000ca3:00000d83:00000f3d:00000eca:00000dd6:00000de2:00000db5:00001abe:00000cb0:00000d4d:00000e07:00000f94:00000f34:00000ec1:00000e36:00000e70:00000e62:00000d2c:00000cf9:00000d6e:00000bb6:00000bf5:00000bd2:00000a2f:00000bb7:00001fe4:00000a6f:00000e98:00000fb7:00000dd7:00000edd:00000fdc:00000df4:00000e8c:00000fa8:00000e98:00000eeb:00001006:00000e67:00000e6c:00000f81:000010d2:00000f41:000011f0:00000f2a:00000fd8:000037a0:0000083e:00000cba:00000e75:00000e1c:00000e93:00000ecd:00000d78:00000e80:000010aa:00000de2:00001e10:000006ac:00000988:00000a3c:000009b6:0000087f:00000997:00000ba4:00000af7:00000c98:000009da:00000ad5:000007b0:000008c1:00001fbd:00000821:00000baa:00000a8c:00000d67:00000cbf:00000c17:00000cdb:00000bdf:00000b9f:00000bf0:00000d0a:00000bcc:00000bc6:00000c8c:00000cd3:00000b3c:00000bbd:00000b43:00000b78:00000c14:00001f0f:00000682:000007f0:00000a25:00001cd8:000006e9:00000862:00000891:00000909:000009bc:00000966:000008ec:00000958:00000951:00000982:00000a63:000008ff:000009e6:000010ba:000006f9:00000909:00000711:0000074c:0000076e:00000723:00000714:00000712:00000717:00000700:00000738:0000083a:00000803:0000073f:000007e3:00000743:000007a0:00000680:00000727:0000073a:00000793:000007b1:00000899:000007eb:00000956:000009a2:000009c2:00000919:00000894:00000795:00000902:000008c8:00000875:0000086a:0000074f:00001a12:000003f2:00000676:00000658:000006e8:0000081c:000007fa:00000789:000007f0:00000708:00000979:00000742:00000769:0000075c:000007ed:000007c9:0000081e:000008ab:0000082d:00001d3c:00000744:00000939:000007f6:0000090c:0000075a:00000c64:00000b7a:00000884:00000887:00000bb4:0000096b:00000843:00000ace:00000956:0000086d:00000a5c:000008e3:00000876:000007f7:00000bab:0000095a:000008f6:000007ec:00000809:000009eb:000009c3:0000088a:000009ab:000008f9:00000889:000030e8:000003f2:00000580:00000841:000007fa:00000864:00000918:000008e1:00000848:00000a4c:00000aef:0000092e:00000a43:00000993:000009e2:00000a4f:000009d5:00001953:00000be7:00000c4c:00000c96:00000c26:00000bbd:00000e64:00000dbc:00000c06:00000cce:00000ae6:00000b49:00000b00:00000ba6
+5:					co64 :00000000:000005c4:00000000:00000010:00000000:0000290f:00000000:0000317e:00000000:00003b29:00000000:000048bd:00000000:000055ec:00000000:00006222:00000000:00006dc0:00000000:00007ad3:00000000:0000880d:00000000:00009432:00000000:0000a14b:00000000:0000afd8:00000000:0000bc4d:00000000:0000c9b9:00000000:0000d75c:00000000:0000e58d:00000000:0000f1ab:00000000:0000fdf5:00000000:00010cd8:00000000:00011af8:00000000:0001270d:00000000:000133c5:00000000:000140a2:00000000:00014e99:00000000:00015b2c:00000000:0001695e:00000000:000198dc:00000000:0001a21a:00000000:0001af98:00000000:0001c081:00000000:0001d286:00000000:0001e364:00000000:0001f17e:00000000:00020158:00000000:00020f16:00000000:00021d18:00000000:00022ae6:00000000:00023cf7:00000000:00024b92:00000000:00025957:00000000:0002683d:00000000:00027448:00000000:0002819a:00000000:0002904b:00000000:00029e5c:00000000:0002acd6:00000000:0002b886:00000000:0002c737:00000000:0002d734:00000000:0002e8ac:00000000:00033870:00000000:00033e41:00000000:00034753:00000000:000352a1:00000000:00035f3c:00000000:00036d1f:00000000:000378df:00000000:0003893f:00000000:00039ead:00000000:0003a4df:00000000:0003ac99:00000000:0003b698:00000000:0003bed0:00000000:0003c8b0:00000000:0003d1fa:00000000:0003dadd:00000000:0003e4e2:00000000:0003f09b:00000000:0003fbb0:00000000:000408c4:00000000:00041410:00000000:00041f5b:00000000:00042bf0:00000000:00043939:00000000:000444c5:00000000:00045086:00000000:00045a82:00000000:00046377:00000000:00046ad2:00000000:0004739d:00000000:00047af4:00000000:0004850a:00000000:00048b70:00000000:000491e1:00000000:0004971a:00000000:00049ced:00000000:0004a358:00000000:0004a79e:00000000:0004ad1e:00000000:0004bf70:00000000:0004c645:00000000:0004ceeb:00000000:0004d673:00000000:0004dc91:00000000:0004e2e6:00000000:0004e9bd:00000000:0004f0b2:00000000:0004f7c6:00000000:0004fe36:00000000:0005069f:00000000:00052059:00000000:000523ae:00000000:00052881:00000000:00052dfe:00000000:00053534:00000000:00053b15:00000000:00054188:00000000:0005479e:00000000:00054e52:00000000:00055575:00000000:00055e4a:00000000:00056503:00000000:00056c8b:00000000:000574c5:00000000:00058d27:00000000:00059737:00000000:0005a0b5:00000000:0005abfc:00000000:0005b788:00000000:0005c273:00000000:0005cd8a:00000000:0005d849:00000000:0005e26c:00000000:0005ecf7:00000000:0005f854:00000000:00060270:00000000:00060bfa:00000000:0006158c:00000000:00061f3f:00000000:000628e7:00000000:0006325a:00000000:00063d7f:00000000:000647ac:00000000:0006522f:00000000:00065d45:00000000:0006673c:00000000:000672d6:00000000:00067dd0:00000000:00068871:00000000:0006931a:00000000:00069ec4:00000000:0006aa2d:00000000:0006b441:00000000:0006bfbe:00000000:0006cb23:00000000:0006d614:00000000:0006e11a:00000000:0006ebf8:00000000:0006f6c7:00000000:00070139:00000000:00072aa3:00000000:00073278:00000000:00073b1a:00000000:0007457b:00000000:000750b9:00000000:00075b97:00000000:000766f9:00000000:00077333:00000000:00077ed1:00000000:00078b41:00000000:0007977d:00000000:0007a379:00000000:0007af88:00000000:0007c202:00000000:0007ce8d:00000000:0007db8d:00000000:0007e563:00000000:0007ee38:00000000:0007f309:00000000:0007fe9f:00000000:00080d73:00000000:00081e48:00000000:000835d9:00000000:00083b91:00000000:000841e7:00000000:00084982:00000000:0008538f:00000000:00085983:00000000:0008617f:00000000:0008696e:00000000:0008725b:00000000:00087a84:00000000:00088375:00000000:00088cda:00000000:000896da:00000000:00089e71:00000000:0008a6c5:00000000:0008af2f:00000000:0008b779:00000000:0008bfce:00000000:0008c844:00000000:0008cf70:00000000:0008d7b2:00000000:0008dff2:00000000:0008e853:00000000:0008efc2:00000000:0008f809:00000000:00090080:00000000:0009097a:00000000:0009111e:00000000:0009472e:00000000:000949c6:00000000:00094fa5:00000000:0009557b:00000000:00095e09:00000000:0009657e:00000000:00096c1e:00000000:000974ba:00000000:00097af1:00000000:0009836a:00000000:00098885:00000000:000990ba:00000000:000999e6:00000000:0009a110:00000000:0009a879:00000000:0009af82:00000000:0009b73a:00000000:0009bf8e:00000000:0009c82c:00000000:0009cfbf:00000000:0009e4b3:00000000:0009f7f4:00000000:000a0c10:00000000:000a1ef7:00000000:000a32f5:00000000:000a474d:00000000:000a5c21:00000000:000a72c9:00000000:000a898a:00000000:000aa1d7:00000000:000aba69:00000000:000ad1e6:00000000:000aec0c:00000000:000b087c:00000000:000b25a3:00000000:000b43cb:00000000:000b635e:00000000:000b8363:00000000:000ba597:00000000:000bc8bc:00000000:000be8a6:00000000:000c08b7:00000000:000c28de:00000000:000c48ba:00000000:000c817e:00000000:000ca1cb:00000000:000cc45c:00000000:000ce724:00000000:000d0921:00000000:000d2aac:00000000:000d4c43:00000000:000d6cd7:00000000:000d8d22:00000000:000dae6b:00000000:000dcf28:00000000:000df01e:00000000:000e10fc:00000000:000e323b:00000000:000e53b2:00000000:000e759c:00000000:000e96b0:00000000:000eb978:00000000:000edcad:00000000:000effb2:00000000:000f23f9:00000000:000f4a7d:00000000:000f6e47:00000000:000f8502:00000000:000f89fa:00000000:000f9387:00000000:000f9c3d:00000000:000fa6a0:00000000:000fb05c:00000000:000fb9ec:00000000:000fc34a:00000000:000fccfa:00000000:000fd749:00000000:000fe0d0:00000000:000feaa7:00000000:000ff42b:00000000:000ffd5c:00000000:00100726:00000000:00101083:00000000:00101a09:00000000:00102469:00000000:00102e66:00000000:001039ba:00000000:0010455e:00000000:001050b8:00000000:00105b0e:00000000:001065c6:00000000:001070fb:00000000:00107bb7:00000000:00108731:00000000:0010932c:00000000:00109ed7:00000000:0010cbf3:00000000:0010e267:00000000:0010eb7e:00000000:0010f6ee:00000000:0011041e:00000000:00110f83:00000000:00111ab2:00000000:00112643:00000000:0011311e:00000000:00113db9:00000000:00114ab6:00000000:00115727:00000000:001161cc:00000000:00116e93:00000000:00117aa0:00000000:001185f3:00000000:00119201:00000000:00119d7e:00000000:0011a874:00000000:0011b486:00000000:0011beed:00000000:0011c938:00000000:0011d51a:00000000:0011e062:00000000:0011ed00:00000000:0011f745:00000000:00120340:00000000:00120e9a:00000000:001219ca:00000000:00122433:00000000:00122e72:00000000:00125180:00000000:00126ef2:00000000:00128b4e:00000000:0012a898:00000000:0012c5a4:00000000:0012e1f5:00000000:0012fece:00000000:00131c7e:00000000:00133957:00000000:001356bf:00000000:00137483:00000000:0013930a:00000000:0013b1cd:00000000:0013d0e9:00000000:0013efce:00000000:00142789:00000000:00144208:00000000:00145ca3:00000000:00147b1c:00000000:0014a0e3:00000000:0014b0df:00000000:0014c31a:00000000:0014d62d:00000000:0014ea1e:00000000:0014ffe0:00000000:00151732:00000000:00152f5f:00000000:001548c5:00000000:001563ba:00000000:00157e4a:00000000:001599ba:00000000:0015b6a5:00000000:0015cfcd:00000000:0015ea3f:00000000:001604c7:00000000:00161e1f:00000000:00163671:00000000:0016523e:00000000:00166d03:00000000:00168682:00000000:00169f52:00000000:0016b6fc:00000000:0016ce6a:00000000:0016e540:00000000:0016face:00000000:001710d6:00000000:001726dd:00000000:00173ceb:00000000:00175373:00000000:00176a6c:00000000:001780a9:00000000:00179629:00000000:0017ab19:00000000:0017bf3a:00000000:0017d2e2:00000000:0017e69a:00000000:0017fa8d:00000000:00180f99:00000000:001824bd:00000000:00183a5c:00000000:001850e7:00000000:00186916:00000000:00188343:00000000:00189d75:00000000:0018b85b:00000000:0018d1e5:00000000:0018ecec:00000000:001908fd:00000000:00192448:00000000:00195aef:00000000:001973a0:00000000:00198b9d:00000000:0019a360:00000000:0019bd63:00000000:0019d5f2:00000000:0019ee59:00000000:001a13c3:00000000:001a33cd:00000000:001a5478:00000000:001a7556:00000000:001a9579:00000000:001ab5ab:00000000:001ad64e:00000000:001af594:00000000:001b138e:00000000:001b303b:00000000:001b4c12:00000000:001b6824:00000000:001b8434:00000000:001ba09a:00000000:001bbcc3:00000000:001bd99b:00000000:001bf60d:00000000:001c12b1:00000000:001c2f60:00000000:001c4c4b:00000000:001c6a55:00000000:001c8601:00000000:001ca2a4:00000000:001cbfc5:00000000:001cdd3b:00000000:001cfb9a:00000000:001d1afc:00000000:001d3b55:00000000:001d589c:00000000:001d5ea7:00000000:001d64a8:00000000:001d6e44:00000000:001d7957:00000000:001d84ec:00000000:001d8f79:00000000:001d9a6f:00000000:001da435:00000000:001dae78:00000000:001db8f5:00000000:001dc3ac:00000000:001dce83:00000000:001dd9e0:00000000:001de47b:00000000:001e16e5:00000000:001e1a07:00000000:001e20ea:00000000:001e2a6b:00000000:001e335f:00000000:001e3b78:00000000:001e473c:00000000:001e5143:00000000:001e5a9c:00000000:001e6482:00000000:001e6ea0:00000000:001e7798:00000000:001e8106:00000000:001e8b04:00000000:001e94b0:00000000:001e9ecc:00000000:001ea893:00000000:001eb192:00000000:001ebc69:00000000:001ec6ef:00000000:001ed06d:00000000:001ed9a6:00000000:001ee378:00000000:001eee27:00000000:001ef79e:00000000:001f00df:00000000:001f09ea:00000000:001f13c4:00000000:001f1ddf:00000000:001f2700:00000000:001f301c:00000000:001f3925:00000000:001f4120:00000000:001f49af:00000000:001f542b:00000000:001f5c96:00000000:001f65f8:00000000:001f6fb2:00000000:001f786f:00000000:001f811b:00000000:001fb8aa:00000000:001fbbd9:00000000:001fc1bb:00000000:001fcae2:00000000:001fd384:00000000:001fdce1:00000000:001fe6bf:00000000:001ff37f:00000000:001ffe8b:00000000:00200a5b:00000000:00204025:00000000:00204551:00000000:00204e0b:00000000:002055a1:00000000:00206021:00000000:00206aaa:00000000:00207428:00000000:00207ccc:00000000:00208615:00000000:00208e4c:00000000:00209935:00000000:0020a3ca:00000000:0020ad05:00000000:0020b6a2:00000000:0020c116:00000000:0020cc3d:00000000:0020d632:00000000:0020e0fd:00000000:0020ea5c:00000000:0020f779:00000000:0020fd4b:00000000:00210375:00000000:00210b78:00000000:00211364:00000000:00211ad8:00000000:00212410:00000000:00212df0:00000000:00213781:00000000:002141f2:00000000:00214a58:00000000:0021508b:00000000:00215714:00000000:00215d5a:00000000:002160fa:00000000:002165c5:00000000:0021692b:00000000:00216ca2:00000000:00217091:00000000:002173d0:00000000:0021776c:00000000:00217ac6:00000000:00217e81:00000000:0021822e:00000000:002185d1:00000000:002188b1:00000000:00218c0a:00000000:0021abcb:00000000:0021b1f6:00000000:0021b970:00000000:0021c0d2:00000000:0021d9af:00000000:0021dec7:00000000:0021e6ac:00000000:0021ee44:00000000:0021f65f:00000000:0021fe3d:00000000:002205e2:00000000:00220d56:00000000:00221597:00000000:00221e62:00000000:00222740:00000000:00223073:00000000:00223a7d:00000000:002243a2:00000000:00224ddf:00000000:002258b6:00000000:002262f5:00000000:00226d16:00000000:00227573:00000000:00227ca3:00000000:002283e3:00000000:00228c57:00000000:0022949b:00000000:0022a650:00000000:0022aa3e:00000000:0022aec2:00000000:0022b59e:00000000:0022bc21:00000000:0022c18b:00000000:0022c6dd:00000000:0022ccd7:00000000:0022d305:00000000:0022d8b6:00000000:0022df06:00000000:0022e491:00000000:0022e9a3:00000000:0022ef76:00000000:0022f507:00000000:0022fa59:00000000:00230037:00000000:00230599:00000000:00230b21:00000000:0023111a:00000000:0023165c:00000000:00231c04:00000000:00232220:00000000:002327b4:00000000:00232d2f:00000000:002333ec:00000000:00233a83:00000000:00235b9a:00000000:00235ea8:00000000:00236362:00000000:00236824:00000000:00236f5a:00000000:002375f5:00000000:00237c21:00000000:00238328:00000000:00238977:00000000:00238f25:00000000:00239cd8:00000000:0023a2ff:00000000:0023a98a:00000000:0023b083:00000000:0023b73b:00000000:0023bfe0:00000000:0023c821:00000000:0023cefe:00000000:0023d841:00000000:0023e270:00000000:0023eb76:00000000:0023f3cf:00000000:0023fc67:00000000:00240514:00000000:00240dcb:00000000:00241650:00000000:00241ea4:00000000:002426dd:00000000:00242f83:00000000:00243825:00000000:002441a1:00000000:00244bcb:00000000:00245534:00000000:002474d8:00000000:00247fba:00000000:00248939:00000000:00249768:00000000:0024a418:00000000:0024b099:00000000:0024bce5:00000000:0024c8fa:00000000:0024d782:00000000:0024e4f4:00000000:0024f231:00000000:0024ff24:00000000:00250c3f:00000000:00251a0c:00000000:0025274c:00000000:002533cd:00000000:00254070:00000000:002577f2:00000000:00257d1f:00000000:0025868b:00000000:0025900e:00000000:00259a8d:00000000:0025a7ea:00000000:0025b398:00000000:0025bf4a:00000000:0025d4f3:00000000:0025db07:00000000:0025e24d:00000000:0025eb1d:00000000:0025f345:00000000:0025fb03:00000000:002604ab:00000000:00260bb2:00000000:00261239:00000000:00261a3d:00000000:002621f4:00000000:002629f3:00000000:00263285:00000000:00263afc:00000000:00264305:00000000:00264b51:00000000:002652fd:00000000:00265a8d:00000000:00266294:00000000:002669a3:00000000:002672c5:00000000:00267b91:00000000:0026844c:00000000:00268c2e:00000000:0026950a:00000000:00269bcc:00000000:0026a288:00000000:0026aa2b:00000000:0026b36f:00000000:0026b9f8:00000000:0026c20a:00000000:0026c9c2:00000000:0026d055:00000000:00270bf7:00000000:00271621:00000000:00272353:00000000:00273110:00000000:00274059:00000000:00274f92:00000000:00275f5b:00000000:00276f4e:00000000:00277f1c:00000000:0027ae3c:00000000:0027ba2a:00000000:0027c822:00000000:0027d6b1:00000000:0027e570:00000000:0027f4b9:00000000:00280641:00000000:002815e5:00000000:002824fa:00000000:00283507:00000000:002844a5:00000000:00285441:00000000:002864c2:00000000:002874af:00000000:00288544:00000000:002895f2:00000000:0028a6b0:00000000:0028b6c6:00000000:0028c6cf:00000000:0028d63b:00000000:0028e69b:00000000:0028f581:00000000:002903f7:00000000:00291335:00000000:00292313:00000000:00293157:00000000:00293fc2:00000000:00294ea7:00000000:00295d62:00000000:00296c7d:00000000:00297b13:00000000:002989c4:00000000:00299771:00000000:0029b43a:00000000:0029b96f:00000000:0029c083:00000000:0029c9c0:00000000:0029d122:00000000:0029da9b:00000000:0029e3e7:00000000:0029ec4f:00000000:0029f4b8:00000000:0029fd30:00000000:002a062d:00000000:002a1075:00000000:002a1b1e:00000000:002a23b8:00000000:002a2b7f:00000000:002a34a1:00000000:002a3a67:00000000:002a6a57:00000000:002a6dc5:00000000:002a739c:00000000:002a8429:00000000:002a8abf:00000000:002a9154:00000000:002a9899:00000000:002a9fc6:00000000:002aa652:00000000:002aae6e:00000000:002ab642:00000000:002abe61:00000000:002ac6c5:00000000:002ade8e:00000000:002aea63:00000000:002af75a:00000000:002b048d:00000000:002b1305:00000000:002b2386:00000000:002b3294:00000000:002b4357:00000000:002b5160:00000000:002b5e63:00000000:002b6cc0:00000000:002b7ad0:00000000:002b892f:00000000:002b9661:00000000:002ba343:00000000:002bb027:00000000:002bbd09:00000000:002bca1e:00000000:002bd82d:00000000:002be9fd:00000000:002bf1b5:00000000:002bf952:00000000:002c0160:00000000:002c0a0f:00000000:002c122b:00000000:002c19ea:00000000:002c2205:00000000:002c3147:00000000:002c3933:00000000:002c4109:00000000:002c48d6:00000000:002c505f:00000000:002c574c:00000000:002c5e1b:00000000:002c6650:00000000:002c6e5f:00000000:002c8b88:00000000:002c903e:00000000:002c9741:00000000:002c9ed7:00000000:002ca72a:00000000:002caf37:00000000:002cb6d2:00000000:002cbed4:00000000:002cc64c:00000000:002ccd5a:00000000:002ce3a4:00000000:002cead7:00000000:002cf35a:00000000:002cfb0f:00000000:002d03c5:00000000:002d0bfc:00000000:002d1619:00000000:002d1eaf:00000000:002d26e5:00000000:002d2fad:00000000:002d38d7:00000000:002d40d4:00000000:002d49b8:00000000:002d52e3:00000000:002d5b39:00000000:002d6360:00000000:002d6c1f:00000000:002d755c:00000000:002d7e3f:00000000:002d8781:00000000:002d909c:00000000:002d98f4:00000000:002da12b:00000000:002da8ce:00000000:002dca3f:00000000:002dd1f7:00000000:002ddc84:00000000:002de953:00000000:002df5bd:00000000:002e01a4:00000000:002e0e39:00000000:002e1aeb:00000000:002e2736:00000000:002e3403:00000000:002e4124:00000000:002e4cdc:00000000:002e593e:00000000:002e656f:00000000:002e712e:00000000:002e7d56:00000000:002eb12f:00000000:002eb6c8:00000000:002ec011:00000000:002ecc09:00000000:002ed6c4:00000000:002ee23a:00000000:002eee12:00000000:002efbb0:00000000:002f085c:00000000:002f252e:00000000:002f2aca:00000000:002f3324:00000000:002f3d9a:00000000:002f46c6:00000000:002f4fa5:00000000:002f58ce:00000000:002f632a:00000000:002f6cd7:00000000:002f74d7:00000000:002f7d70:00000000:002f876c:00000000:002f912c:00000000:002f9957:00000000:002fa278:00000000:002fab17:00000000:002fb3e8:00000000:002fbc38:00000000:002fc4c8:00000000:002fccec:00000000:002fd69d:00000000:002fdff0:00000000:002fe95e:00000000:002ff11e:00000000:002ffaa7:00000000:00300386:00000000:00300d60:00000000:00301741:00000000:003020c7:00000000:003029cc:00000000:003043eb:00000000:00304945:00000000:003051f2:00000000:00305d7b:00000000:00306948:00000000:00307258:00000000:00307cd4:00000000:003087bb:00000000:00309290:00000000:00309c09:00000000:0030a625:00000000:0030d55c:00000000:0030d976:00000000:0030e042:00000000:0030e85a:00000000:0030f3c4:00000000:0030fc73:00000000:00310489:00000000:00310e51:00000000:003115c7:00000000:00311f07:00000000:003128bc:00000000:00313023:00000000:003159a4:00000000:00315e99:00000000:0031662b:00000000:00317055:00000000:00317a18:00000000:003183da:00000000:00318ded:00000000:00319925:00000000:0031a333:00000000:0031ad3f:00000000:0031b734:00000000:0031c3c2:00000000:0031cf19:00000000:0031d8e0:00000000:0031e348:00000000:0031f0b9:00000000:0031fa8d:00000000:00320734:00000000:003212ce:00000000:00321d01:00000000:003229a8:00000000:003232c2:00000000:00323e38:00000000:00324b4e:00000000:00326c91:00000000:0032733b:00000000:00327caf:00000000:003288ab:00000000:00329399:00000000:00329db4:00000000:0032a860:00000000:0032b329:00000000:0032bd28:00000000:0032c819:00000000:0032d41f:00000000:0032de56:00000000:0032e97d:00000000:0032f5cb:00000000:00333012:00000000:00333446:00000000:00333bcb:00000000:003345ad:00000000:00334edf:00000000:0033596e:00000000:0033633d:00000000:00336dae:00000000:0033787f:00000000:003383ac:00000000:00338e52:00000000:00339a43:00000000:0033a55b:00000000:0033affd:00000000:0033bc9e:00000000:0033c78d:00000000:0033d12c:00000000:0033dc1c:00000000:0033e56e:00000000:0033ef68:00000000:0033faed:00000000:003404de:00000000:00340f1a:00000000:00341ac4:00000000:0034241e:00000000:00342e29:00000000:0034384d:00000000:00344b94:00000000:003452a6:00000000:00345aa6:00000000:003462d5:00000000:00346cea:00000000:00347641:00000000:00347f92:00000000:003488e6:00000000:003491fd:00000000:00349a40:00000000:0034a215:00000000:0034aaba:00000000:0034b1bd:00000000:0034b80f:00000000:0034cecf:00000000:0034d4ca:00000000:0034dbc9:00000000:0034e351:00000000:0034ea8f:00000000:0034f221:00000000:0034fab0:00000000:00350313:00000000:00350c18:00000000:00352511:00000000:00352a6c:00000000:00353241:00000000:00353ba2:00000000:003544b7:00000000:00354e97:00000000:0035590f:00000000:003562fd:00000000:00356d37:00000000:00357789:00000000:00358109:00000000:003589bc:00000000:00359346:00000000:00359b34:00000000:0035a37e:00000000:0035abde:00000000:0035b424:00000000:0035bc8c:00000000:0035c4f2:00000000:0035cce0:00000000:0035d4dd:00000000:0035ddbf:00000000:0035e5d8:00000000:0035ed68:00000000:0035f4bf:00000000:0035fd32:00000000:00361bdd:00000000:00362500:00000000:00363139:00000000:00363b7d:00000000:00364617:00000000:003652ab:00000000:00365e40:00000000:00366a2e:00000000:0036757b:00000000:00368199:00000000:00368ec1:00000000:00369892:00000000:0036a32b:00000000:0036aef7:00000000:0036ba91:00000000:0036c7e4:00000000:0036d528:00000000:0036e184:00000000:0036edc2:00000000:0036fabc:00000000:00370657:00000000:003711ec:00000000:00371c9c:00000000:00375901:00000000:00378274:00000000:00378a5b:00000000:003795a7:00000000:0037a1ac:00000000:0037ad3b:00000000:0037b930:00000000:0037c582:00000000:0037d2b2:00000000:0037e123:00000000:0037f028:00000000:00380057:00000000:00380c3f:00000000:003817ec:00000000:003823a2:00000000:00382f0a:00000000:00383b5f:00000000:00384879:00000000:003854d3:00000000:003861bb:00000000:00386e89:00000000:003879ff:00000000:00388561:00000000:003890e4:00000000:00389b39:00000000:0038a796:00000000:0038b2d9:00000000:0038bd8b:00000000:0038c78f:00000000:0038e092:00000000:0038e9fc:00000000:0038f462:00000000:003900e1:00000000:00390c7e:00000000:0039185e:00000000:00392410:00000000:00392f3d:00000000:00393a37:00000000:0039452c:00000000:00394f34:00000000:003958e6:00000000:00396319:00000000:00396c79:00000000:003978ea:00000000:0039838c:00000000:00398e27:00000000:003999e6:00000000:0039a674:00000000:0039b259:00000000:0039be1f:00000000:0039c9aa:00000000:0039ee03:00000000:0039f37f:00000000:0039fc57:00000000:003a04c3:00000000:003a0f3e:00000000:003a19d5:00000000:003a23d8:00000000:003a2d91:00000000:003a37c2:00000000:003a4116:00000000:003a4a3a:00000000:003a536d:00000000:003a5c06:00000000:003a648b:00000000:003a6cd9:00000000:003a762f:00000000:003a7e8a:00000000:003a869a:00000000:003a8efa:00000000:003a9711:00000000:003a9f27:00000000:003ab1ac:00000000:003ab68b:00000000:003abe03:00000000:003ac441:00000000:003acc01:00000000:003ad380:00000000:003ad9f0:00000000:003adf98:00000000:003ae6a4:00000000:003aecc8:00000000:003af2a2:00000000:003afa04:00000000:003b0140:00000000:003b0788:00000000:003b0e6d:00000000:003b1489:00000000:003b1b44:00000000:003b2255:00000000:003b28a1:00000000:003b2e30:00000000:003b350a:00000000:003b3b77:00000000:003b410b:00000000:003b475b:00000000:003b72e1:00000000:003b7bb0:00000000:003b8964:00000000:003b9774:00000000:003ba5bc:00000000:003bcbfa:00000000:003bd620:00000000:003be3c6:00000000:003bf21c:00000000:003bfe46:00000000:003c0bc7:00000000:003c1aac:00000000:003c2a56:00000000:003c3b33:00000000:003c4a93:00000000:003c5965:00000000:003c6769:00000000:003c76c4:00000000:003c96c8:00000000:003ca257:00000000:003caf08:00000000:003cbdf5:00000000:003cccaf:00000000:003cdc31:00000000:003cea57:00000000:003cfa59:00000000:003d0a5d:00000000:003d18ac:00000000:003d274f:00000000:003d366b:00000000:003d44b9:00000000:003d53aa:00000000:003d6332:00000000:003d7187:00000000:003d8003:00000000:003d8f19:00000000:003d9d39:00000000:003dabf8:00000000:003dba64:00000000:003dc7c2:00000000:003dd625:00000000:003de525:00000000:003df35c:00000000:003e01aa:00000000:003e108c:00000000:003e1e85:00000000:003e2c2e:00000000:003e3dac:00000000:003e4c57:00000000:003e5bcd:00000000:003e6bf0:00000000:003e7b7c:00000000:003e896a:00000000:003e9938:00000000:003ea7d7:00000000:003ed7cd:00000000:003ee1fa:00000000:003eedd7:00000000:003efcfe:00000000:003f0c39:00000000:003f1ae2:00000000:003f28d4:00000000:003f3773:00000000:003f44e7:00000000:003f5349:00000000:003f629c:00000000:003f7126:00000000:003f7f88:00000000:003f8ed1:00000000:003f9d08:00000000:003fac20:00000000:003fbb3b:00000000:003fc8fe:00000000:003fd6f6:00000000:003fe60f:00000000:003ff3aa:00000000:0040023b:00000000:00401196:00000000:00401fc9:00000000:00402ec2:00000000:00403e0b:00000000:00404c0d:00000000:00405a6e:00000000:004069ad:00000000:0040785c:00000000:00408767:00000000:00409693:00000000:0040a45c:00000000:0040b215:00000000:0040bfdf:00000000:0040cff9:00000000:0040deff:00000000:0040ef1e:00000000:0040fc17:00000000:004109d4:00000000:004118c3:00000000:0041263a:00000000:00413423:00000000:004142d4:00000000:00414fe3:00000000:00415df3:00000000:00416d0d:00000000:00417b58:00000000:00418a38:00000000:0041997c:00000000:0041c7d1:00000000:0041d132:00000000:0041dec5:00000000:0041eafe:00000000:0041f7a1:00000000:00420524:00000000:00421461:00000000:0042232b:00000000:00423101:00000000:00423ee3:00000000:00424c98:00000000:00426756:00000000:00427406:00000000:00428153:00000000:00428f5a:00000000:00429eee:00000000:0042ae22:00000000:0042bce3:00000000:0042cb19:00000000:0042d989:00000000:0042e7eb:00000000:0042f517:00000000:00430210:00000000:00430f7e:00000000:00431b34:00000000:00432729:00000000:004332fb:00000000:00433d2a:00000000:004348e1:00000000:004368c5:00000000:00437334:00000000:004381cc:00000000:00439183:00000000:00439f5a:00000000:0043ae37:00000000:0043be13:00000000:0043cc07:00000000:0043da93:00000000:0043ea3b:00000000:0043f8d3:00000000:004407be:00000000:004417c4:00000000:0044262b:00000000:00443497:00000000:00444418:00000000:004454ea:00000000:0044642b:00000000:0044761b:00000000:00448545:00000000:0044951d:00000000:0044ccbd:00000000:0044d4fb:00000000:0044e1b5:00000000:0044f02a:00000000:0044fe46:00000000:00450cd9:00000000:00451ba6:00000000:0045291e:00000000:0045379e:00000000:00454848:00000000:0045562a:00000000:0045743a:00000000:00457ae6:00000000:0045846e:00000000:00458eaa:00000000:00459860:00000000:0045a0df:00000000:0045aa76:00000000:0045b61a:00000000:0045c111:00000000:0045cda9:00000000:0045d783:00000000:0045e258:00000000:0045ea08:00000000:0045f2c9:00000000:00461286:00000000:00461aa7:00000000:00462651:00000000:004630dd:00000000:00463e44:00000000:00464b03:00000000:0046571a:00000000:004663f5:00000000:00466fd4:00000000:00467b73:00000000:00468763:00000000:0046946d:00000000:0046a039:00000000:0046abff:00000000:0046b88b:00000000:0046c55e:00000000:0046d09a:00000000:0046dc57:00000000:0046e79a:00000000:0046f312:00000000:0046ff26:00000000:00471e35:00000000:004724b7:00000000:00472ca7:00000000:004736cc:00000000:004753a4:00000000:00475a8d:00000000:004762ef:00000000:00476b80:00000000:00477489:00000000:00477e45:00000000:004787ab:00000000:00479097:00000000:004799ef:00000000:0047a340:00000000:0047acc2:00000000:0047b725:00000000:0047c024:00000000:0047ca0a:00000000:0047dac4:00000000:0047e1bd:00000000:0047eac6:00000000:0047f1d7:00000000:0047f923:00000000:00480091:00000000:004807b4:00000000:00480ec8:00000000:004815da:00000000:00481cf1:00000000:004823f1:00000000:00482b29:00000000:00483363:00000000:00483b66:00000000:004842a5:00000000:00484a88:00000000:004851cb:00000000:0048596b:00000000:00485feb:00000000:00486712:00000000:00486e4c:00000000:004875df:00000000:00487d90:00000000:00488629:00000000:00488e14:00000000:0048976a:00000000:0048a10c:00000000:0048aace:00000000:0048b3e7:00000000:0048bc7b:00000000:0048c410:00000000:0048cd12:00000000:0048d5da:00000000:0048de4f:00000000:0048e6b9:00000000:0048ee08:00000000:0049081a:00000000:00490c0c:00000000:00491282:00000000:004918da:00000000:00491fc2:00000000:004927de:00000000:00492fd8:00000000:00493761:00000000:00493f51:00000000:00494659:00000000:00494fd2:00000000:00495714:00000000:00495e7d:00000000:004965d9:00000000:00496dc6:00000000:0049758f:00000000:00497dad:00000000:00498658:00000000:00498e85:00000000:0049abc1:00000000:0049b305:00000000:0049bc3e:00000000:0049c434:00000000:0049cd40:00000000:0049d49a:00000000:0049e0fe:00000000:0049ec78:00000000:0049f4fc:00000000:0049fd83:00000000:004a0937:00000000:004a12a2:00000000:004a1ae5:00000000:004a25b3:00000000:004a2f09:00000000:004a3776:00000000:004a41d2:00000000:004a4ab5:00000000:004a532b:00000000:004a5b22:00000000:004a66cd:00000000:004a7027:00000000:004a791d:00000000:004a8109:00000000:004a8912:00000000:004a92fd:00000000:004a9cc0:00000000:004aa54a:00000000:004aaef5:00000000:004ab7ee:00000000:004ac077:00000000:004af15f:00000000:004af551:00000000:004afad1:00000000:004b0312:00000000:004b0b0c:00000000:004b1370:00000000:004b1c88:00000000:004b2569:00000000:004b2db1:00000000:004b37fd:00000000:004b42ec:00000000:004b4c1a:00000000:004b565d:00000000:004b5ff0:00000000:004b69d2:00000000:004b7421:00000000:004b7df6:00000000:004b9749:00000000:004ba330:00000000:004baf7c:00000000:004bbc12:00000000:004bc838:00000000:004bd3f5:00000000:004be259:00000000:004bf015:00000000:004bfc1b:00000000:004c08e9:00000000:004c13cf:00000000:004c1f18:00000000:004c2a18
diff --git a/testProgs/testAMRAudioStreamer.cpp b/testProgs/testAMRAudioStreamer.cpp
index 9476da9..c00fcce 100644
--- a/testProgs/testAMRAudioStreamer.cpp
+++ b/testProgs/testAMRAudioStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that reads an AMR audio file (as defined in RFC 3267)
 // and streams it using RTP
 // main program
diff --git a/testProgs/testDVVideoStreamer.cpp b/testProgs/testDVVideoStreamer.cpp
index c280bd8..0f0f75a 100644
--- a/testProgs/testDVVideoStreamer.cpp
+++ b/testProgs/testDVVideoStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that reads a DV Video Elementary Stream file,
 // and streams it using RTP
 // main program
diff --git a/testProgs/testGSMStreamer.cpp b/testProgs/testGSMStreamer.cpp
index ac4716a..2977a18 100644
--- a/testProgs/testGSMStreamer.cpp
+++ b/testProgs/testGSMStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that streams GSM audio via RTP/RTCP
 // main program
 
diff --git a/testProgs/testH264VideoStreamer.cpp b/testProgs/testH264VideoStreamer.cpp
index efb52b1..237f0a1 100644
--- a/testProgs/testH264VideoStreamer.cpp
+++ b/testProgs/testH264VideoStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that reads a H.264 Elementary Stream video file
 // and streams it using RTP
 // main program
diff --git a/testProgs/testH264VideoToTransportStream.cpp b/testProgs/testH264VideoToTransportStream.cpp
index 656e78f..89b5160 100644
--- a/testProgs/testH264VideoToTransportStream.cpp
+++ b/testProgs/testH264VideoToTransportStream.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A program that converts a H.264 (Elementary Stream) video file into a Transport Stream file.
 // main program
 
diff --git a/testProgs/testH265VideoStreamer.cpp b/testProgs/testH265VideoStreamer.cpp
index 6234e96..8ae44ce 100644
--- a/testProgs/testH265VideoStreamer.cpp
+++ b/testProgs/testH265VideoStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that reads a H.265 Elementary Stream video file
 // and streams it using RTP
 // main program
diff --git a/testProgs/testH265VideoToTransportStream.cpp b/testProgs/testH265VideoToTransportStream.cpp
index 0e8c0dc..57c9ac0 100644
--- a/testProgs/testH265VideoToTransportStream.cpp
+++ b/testProgs/testH265VideoToTransportStream.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A program that converts a H.265 (Elementary Stream) video file into a Transport Stream file.
 // main program
 
diff --git a/testProgs/testMKVStreamer.cpp b/testProgs/testMKVStreamer.cpp
new file mode 100644
index 0000000..8471a29
--- /dev/null
+++ b/testProgs/testMKVStreamer.cpp
@@ -0,0 +1,178 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+**********/
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
+// A test program that reads a ".mkv" (i.e., Matroska) file, demultiplexes each track
+// (video, audio, subtitles), and streams each track using RTP multicast.
+// main program
+
+#include <liveMedia.hh>
+#include <BasicUsageEnvironment.hh>
+#include <GroupsockHelper.hh>
+
+UsageEnvironment* env;
+char const* inputFileName = "test.mkv";
+struct in_addr destinationAddress;
+RTSPServer* rtspServer;
+ServerMediaSession* sms;
+MatroskaFile* matroskaFile;
+MatroskaDemux* matroskaDemux;
+
+// An array of structures representing the state of the video, audio, and subtitle tracks:
+static struct {
+  unsigned trackNumber;
+  FramedSource* source;
+  RTPSink* sink;
+  RTCPInstance* rtcp;
+} trackState[3];
+
+void onMatroskaFileCreation(MatroskaFile* newFile, void* clientData); // forward
+
+int main(int argc, char** argv) {
+  // Begin by setting up our usage environment:
+  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
+  env = BasicUsageEnvironment::createNew(*scheduler);
+
+  // Define our destination (multicast) IP address:
+  destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*env);
+    // Note: This is a multicast address.  If you wish instead to stream
+    // using unicast, then you should use the "testOnDemandRTSPServer"
+    // test program - not this test program - as a model.
+
+  // Create our RTSP server.  (Receivers will need to use RTSP to access the stream.)
+  rtspServer = RTSPServer::createNew(*env, 8554);
+  if (rtspServer == NULL) {
+    *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
+    exit(1);
+  }
+  sms = ServerMediaSession::createNew(*env, "testStream", inputFileName,
+				      "Session streamed by \"testMKVStreamer\"",
+				      True /*SSM*/);
+
+  // Arrange to create a "MatroskaFile" object for the specified file.
+  // (Note that this object is not created immediately, but instead via a callback.)
+  MatroskaFile::createNew(*env, inputFileName, onMatroskaFileCreation, NULL, "jpn");
+
+  env->taskScheduler().doEventLoop(); // does not return
+
+  return 0; // only to prevent compiler warning
+}
+
+void play(); // forward
+
+void onMatroskaFileCreation(MatroskaFile* newFile, void* /*clientData*/) {
+  matroskaFile = newFile;
+
+  // Create a new demultiplexor for the file:
+  matroskaDemux = matroskaFile->newDemux();
+
+  // Create source streams, "RTPSink"s, and "RTCPInstance"s for each preferred track;
+  unsigned short rtpPortNum = 44444;
+  const unsigned char ttl = 255;
+
+  const unsigned maxCNAMElen = 100;
+  unsigned char CNAME[maxCNAMElen+1];
+  gethostname((char*)CNAME, maxCNAMElen);
+  CNAME[maxCNAMElen] = '\0'; // just in case
+
+  for (unsigned i = 0; i < 3; ++i) {
+    unsigned trackNumber;
+    FramedSource* baseSource = matroskaDemux->newDemuxedTrack(trackNumber);
+    trackState[i].trackNumber = trackNumber;
+
+    unsigned estBitrate, numFiltersInFrontOfTrack;
+    trackState[i].source = matroskaFile
+      ->createSourceForStreaming(baseSource, trackNumber, estBitrate, numFiltersInFrontOfTrack);
+    trackState[i].sink = NULL; // by default; may get changed below
+    trackState[i].rtcp = NULL; // ditto
+    
+    if (trackState[i].source != NULL) {
+      Groupsock* rtpGroupsock = new Groupsock(*env, destinationAddress, rtpPortNum, ttl);
+      Groupsock* rtcpGroupsock = new Groupsock(*env, destinationAddress, rtpPortNum+1, ttl);
+      rtpPortNum += 2;
+
+      trackState[i].sink
+	= matroskaFile->createRTPSinkForTrackNumber(trackNumber, rtpGroupsock, 96+i);
+      if (trackState[i].sink != NULL) {
+	if (trackState[i].sink->estimatedBitrate() > 0) {
+	  estBitrate = trackState[i].sink->estimatedBitrate(); // hack
+	}
+	trackState[i].rtcp
+	  = RTCPInstance::createNew(*env, rtcpGroupsock, estBitrate, CNAME,
+				    trackState[i].sink, NULL /* we're a server */,
+				    True /* we're a SSM source */);
+          // Note: This starts RTCP running automatically
+
+	// Having set up a track for streaming, add it to our RTSP server's "ServerMediaSession":
+	sms->addSubsession(PassiveServerMediaSubsession::createNew(*trackState[i].sink, trackState[i].rtcp));
+      }
+    }
+  }
+
+  if (sms->numSubsessions() == 0) {
+    *env << "Error: The Matroska file \"" << inputFileName << "\" has no streamable tracks\n";
+    *env << "(Perhaps the file does not exist, or is not a 'Matroska' file.)\n";
+    exit(1);
+  }
+
+  rtspServer->addServerMediaSession(sms);
+
+  char* url = rtspServer->rtspURL(sms);
+  *env << "Play this stream using the URL \"" << url << "\"\n";
+  delete[] url;
+
+  // Start the streaming:
+  play();
+}
+
+void afterPlaying(void* /*clientData*/) {
+  *env << "...done reading from file\n";
+
+  // Stop playing all "RTPSink"s, then close the source streams
+  // (which will also close the demultiplexor itself):
+  unsigned i;
+  for (i = 0; i < 3; ++i) {
+    if (trackState[i].sink != NULL) trackState[i].sink->stopPlaying();
+    Medium::close(trackState[i].source); trackState[i].source = NULL;
+  }
+
+  // Create a new demultiplexor from our Matroska file, then new data sources for each track:
+  matroskaDemux = matroskaFile->newDemux();
+  for (i = 0; i < 3; ++i) {
+    if (trackState[i].trackNumber != 0) {
+      FramedSource* baseSource
+	= matroskaDemux->newDemuxedTrackByTrackNumber(trackState[i].trackNumber);
+
+      unsigned estBitrate, numFiltersInFrontOfTrack;
+      trackState[i].source = matroskaFile
+	->createSourceForStreaming(baseSource, trackState[i].trackNumber,
+				   estBitrate, numFiltersInFrontOfTrack);
+    }
+  }
+
+  // Start playing once again:
+  play();
+}
+
+void play() {
+  *env << "Beginning to read from file...\n";
+
+  // Start playing each track's RTP sink from its corresponding source:
+  for (unsigned i = 0; i < 3; ++i) {
+    if (trackState[i].sink != NULL && trackState[i].source != NULL) {
+      trackState[i].sink->startPlaying(*trackState[i].source, afterPlaying, NULL);
+    }
+  }
+}
diff --git a/testProgs/testMP3Receiver.cpp b/testProgs/testMP3Receiver.cpp
index d3bf560..cea66b6 100644
--- a/testProgs/testMP3Receiver.cpp
+++ b/testProgs/testMP3Receiver.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that receives a RTP/RTCP multicast MP3 stream,
 // and outputs the resulting MP3 file stream to 'stdout'
 // main program
diff --git a/testProgs/testMP3Streamer.cpp b/testProgs/testMP3Streamer.cpp
index 362c1db..7085bb0 100644
--- a/testProgs/testMP3Streamer.cpp
+++ b/testProgs/testMP3Streamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that streams a MP3 file via RTP/RTCP
 // main program
 
diff --git a/testProgs/testMPEG1or2AudioVideoStreamer.cpp b/testProgs/testMPEG1or2AudioVideoStreamer.cpp
index f039a81..7000445 100644
--- a/testProgs/testMPEG1or2AudioVideoStreamer.cpp
+++ b/testProgs/testMPEG1or2AudioVideoStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that reads a MPEG-1 or 2 Program Stream file,
 // splits it into Audio and Video Elementary Streams,
 // and streams both using RTP
diff --git a/testProgs/testMPEG1or2ProgramToTransportStream.cpp b/testProgs/testMPEG1or2ProgramToTransportStream.cpp
index 642303f..87a1726 100644
--- a/testProgs/testMPEG1or2ProgramToTransportStream.cpp
+++ b/testProgs/testMPEG1or2ProgramToTransportStream.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A program that converts a MPEG-1 or 2 Program Stream file into
 // a Transport Stream file.
 // main program
diff --git a/testProgs/testMPEG1or2Splitter.cpp b/testProgs/testMPEG1or2Splitter.cpp
index defd9c5..0593929 100644
--- a/testProgs/testMPEG1or2Splitter.cpp
+++ b/testProgs/testMPEG1or2Splitter.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that splits a MPEG-1 or 2 Program Stream file into
 // video and audio output files.
 // main program
diff --git a/testProgs/testMPEG1or2VideoReceiver.cpp b/testProgs/testMPEG1or2VideoReceiver.cpp
index 359b4a6..b8ca6de 100644
--- a/testProgs/testMPEG1or2VideoReceiver.cpp
+++ b/testProgs/testMPEG1or2VideoReceiver.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that receives a RTP/RTCP multicast MPEG video stream,
 // and outputs the resulting MPEG file stream to 'stdout'
 // main program
@@ -87,7 +87,7 @@ int main(int argc, char** argv) {
   sessionState.source = MPEG1or2VideoRTPSource::createNew(*env, &rtpGroupsock);
 
   // Create (and start) a 'RTCP instance' for the RTP source:
-  const unsigned estimatedSessionBandwidth = 160; // in kbps; for RTCP b/w share
+  const unsigned estimatedSessionBandwidth = 4500; // in kbps; for RTCP b/w share
   const unsigned maxCNAMElen = 100;
   unsigned char CNAME[maxCNAMElen+1];
   gethostname((char*)CNAME, maxCNAMElen);
diff --git a/testProgs/testMPEG1or2VideoStreamer.cpp b/testProgs/testMPEG1or2VideoStreamer.cpp
index 9c4836f..929dcc9 100644
--- a/testProgs/testMPEG1or2VideoStreamer.cpp
+++ b/testProgs/testMPEG1or2VideoStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that reads a MPEG-1 or 2 Video Elementary Stream file,
 // and streams it using RTP
 // main program
diff --git a/testProgs/testMPEG2TransportReceiver.cpp b/testProgs/testMPEG2TransportReceiver.cpp
index f517387..3d4f899 100644
--- a/testProgs/testMPEG2TransportReceiver.cpp
+++ b/testProgs/testMPEG2TransportReceiver.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that receives a RTP/RTCP multicast MPEG-2 Transport Stream,
 // and outputs the resulting Transport Stream data to 'stdout'
 // main program
@@ -87,7 +87,7 @@ int main(int argc, char** argv) {
   sessionState.source = SimpleRTPSource::createNew(*env, &rtpGroupsock, 33, 90000, "video/MP2T", 0, False /*no 'M' bit*/);
 
   // Create (and start) a 'RTCP instance' for the RTP source:
-  const unsigned estimatedSessionBandwidth = 160; // in kbps; for RTCP b/w share
+  const unsigned estimatedSessionBandwidth = 5000; // in kbps; for RTCP b/w share
   const unsigned maxCNAMElen = 100;
   unsigned char CNAME[maxCNAMElen+1];
   gethostname((char*)CNAME, maxCNAMElen);
diff --git a/testProgs/testMPEG2TransportStreamTrickPlay.cpp b/testProgs/testMPEG2TransportStreamTrickPlay.cpp
index 92dbe56..41d0616 100644
--- a/testProgs/testMPEG2TransportStreamTrickPlay.cpp
+++ b/testProgs/testMPEG2TransportStreamTrickPlay.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A program that tests 'trick mode' operations on a MPEG-2 Transport Stream file,
 // by generating a new Transport Stream file that represents the result of the
 // 'trick mode' operation (seeking and/or fast forward/reverse play).
diff --git a/testProgs/testMPEG2TransportStreamer.cpp b/testProgs/testMPEG2TransportStreamer.cpp
index e29cc3b..5bfd8b3 100644
--- a/testProgs/testMPEG2TransportStreamer.cpp
+++ b/testProgs/testMPEG2TransportStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that reads a MPEG-2 Transport Stream file,
 // and streams it using RTP
 // main program
diff --git a/testProgs/testMPEG4VideoStreamer.cpp b/testProgs/testMPEG4VideoStreamer.cpp
index 2852ba9..407d933 100644
--- a/testProgs/testMPEG4VideoStreamer.cpp
+++ b/testProgs/testMPEG4VideoStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that reads a MPEG-4 Video Elementary Stream file,
 // and streams it using RTP
 // main program
diff --git a/testProgs/testOggStreamer.cpp b/testProgs/testOggStreamer.cpp
new file mode 100644
index 0000000..85f10bd
--- /dev/null
+++ b/testProgs/testOggStreamer.cpp
@@ -0,0 +1,182 @@
+/**********
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 of the License, or (at your
+option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+**********/
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
+// A test program that reads a ".ogg" (i.e., Ogg) file, demultiplexes each track
+// (audio and/or video), and streams each track using RTP multicast.
+// main program
+
+#include <liveMedia.hh>
+#include <BasicUsageEnvironment.hh>
+#include <GroupsockHelper.hh>
+
+UsageEnvironment* env;
+char const* inputFileName = "test.ogg";
+struct in_addr destinationAddress;
+RTSPServer* rtspServer;
+ServerMediaSession* sms;
+OggFile* oggFile;
+OggDemux* oggDemux;
+unsigned numTracks;
+
+// A structure representing the state of a track:
+struct TrackState {
+  u_int32_t trackNumber;
+  FramedSource* source;
+  RTPSink* sink;
+  RTCPInstance* rtcp;
+};
+TrackState* trackState;
+
+void onOggFileCreation(OggFile* newFile, void* clientData); // forward
+
+int main(int argc, char** argv) {
+  // Begin by setting up our usage environment:
+  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
+  env = BasicUsageEnvironment::createNew(*scheduler);
+
+  // Define our destination (multicast) IP address:
+  destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*env);
+    // Note: This is a multicast address.  If you wish instead to stream
+    // using unicast, then you should use the "testOnDemandRTSPServer"
+    // test program - not this test program - as a model.
+
+  // Create our RTSP server.  (Receivers will need to use RTSP to access the stream.)
+  rtspServer = RTSPServer::createNew(*env, 8554);
+  if (rtspServer == NULL) {
+    *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
+    exit(1);
+  }
+  sms = ServerMediaSession::createNew(*env, "testStream", inputFileName,
+                                      "Session streamed by \"testMKVStreamer\"",
+                                      True /*SSM*/);
+
+  // Arrange to create an "OggFile" object for the specified file.
+  // (Note that this object is not created immediately, but instead via a callback.)
+  OggFile::createNew(*env, inputFileName, onOggFileCreation, NULL);
+
+  env->taskScheduler().doEventLoop(); // does not return
+
+  return 0; // only to prevent compiler warning
+}
+
+void play(); // forward
+
+void onOggFileCreation(OggFile* newFile, void* clientData) {
+  oggFile = newFile;
+
+  // Create a new demultiplexor for the file:
+  oggDemux = oggFile->newDemux();
+
+  // Create source streams, "RTPSink"s, and "RTCPInstance"s for each preferred track;
+  unsigned short rtpPortNum = 22222;
+  const unsigned char ttl = 255;
+
+  const unsigned maxCNAMElen = 100;
+  unsigned char CNAME[maxCNAMElen+1];
+  gethostname((char*)CNAME, maxCNAMElen);
+  CNAME[maxCNAMElen] = '\0'; // just in case
+
+  numTracks = oggFile->numTracks();
+  trackState = new TrackState[numTracks];
+  for (unsigned i = 0; i < numTracks; ++i) {
+    u_int32_t trackNumber;
+    FramedSource* baseSource = oggDemux->newDemuxedTrack(trackNumber);
+    trackState[i].trackNumber = trackNumber;
+
+    unsigned estBitrate, numFiltersInFrontOfTrack;
+    trackState[i].source = oggFile
+      ->createSourceForStreaming(baseSource, trackNumber, estBitrate, numFiltersInFrontOfTrack);
+    trackState[i].sink = NULL; // by default; may get changed below
+    trackState[i].rtcp = NULL; // ditto
+
+    if (trackState[i].source != NULL) {
+      Groupsock* rtpGroupsock = new Groupsock(*env, destinationAddress, rtpPortNum, ttl);
+      Groupsock* rtcpGroupsock = new Groupsock(*env, destinationAddress, rtpPortNum+1, ttl);
+      rtpPortNum += 2;
+
+      trackState[i].sink
+        = oggFile->createRTPSinkForTrackNumber(trackNumber, rtpGroupsock, 96+i);
+      if (trackState[i].sink != NULL) {
+        if (trackState[i].sink->estimatedBitrate() > 0) {
+          estBitrate = trackState[i].sink->estimatedBitrate(); // hack
+        }
+	trackState[i].rtcp
+	  = RTCPInstance::createNew(*env, rtcpGroupsock, estBitrate, CNAME,
+				    trackState[i].sink, NULL /* we're a server */,
+				    True /* we're a SSM source */);
+	  // Note: This starts RTCP running automatically
+
+	// Having set up a track for streaming, add it to our RTSP server's "ServerMediaSession":
+	sms->addSubsession(PassiveServerMediaSubsession::createNew(*trackState[i].sink, trackState[i].rtcp));
+      }
+    }
+  }
+
+  if (sms->numSubsessions() == 0) {
+    *env << "Error: The Ogg file \"" << inputFileName << "\" has no streamable tracks\n";
+    *env << "(Perhaps the file does not exist, is not an 'Ogg' file, or has no tracks that we know how to stream.)\n";
+    exit(1);
+  }
+
+  rtspServer->addServerMediaSession(sms);
+
+  char* url = rtspServer->rtspURL(sms);
+  *env << "Play this stream using the URL \"" << url << "\"\n";
+  delete[] url;
+
+  // Start the streaming:
+  play();
+}
+
+void afterPlaying(void* /*clientData*/) {
+  *env << "...done reading from file\n";
+
+  // Stop playing all "RTPSink"s, then close the source streams
+  // (which will also close the demultiplexor itself):
+  unsigned i;
+  for (i = 0; i < numTracks; ++i) {
+    if (trackState[i].sink != NULL) trackState[i].sink->stopPlaying();
+    Medium::close(trackState[i].source); trackState[i].source = NULL;
+  }
+
+  // Create a new demultiplexor from our Ogg file, then new data sources for each track:
+  oggDemux = oggFile->newDemux();
+  for (i = 0; i < numTracks; ++i) {
+    if (trackState[i].trackNumber != 0) {
+      FramedSource* baseSource
+	= oggDemux->newDemuxedTrack(trackState[i].trackNumber);
+
+      unsigned estBitrate, numFiltersInFrontOfTrack;
+      trackState[i].source
+	= oggFile->createSourceForStreaming(baseSource, trackState[i].trackNumber,
+					    estBitrate, numFiltersInFrontOfTrack);
+    }
+  }
+
+  // Start playing once again:
+  play();
+}
+
+void play() {
+  *env << "Beginning to read from file...\n";
+
+  // Start playing each track's RTP sink from its corresponding source:
+  for (unsigned i = 0; i < numTracks; ++i) {
+    if (trackState[i].sink != NULL && trackState[i].source != NULL) {
+      trackState[i].sink->startPlaying(*trackState[i].source, afterPlaying, NULL);
+    }
+  }
+}
diff --git a/testProgs/testOnDemandRTSPServer.cpp b/testProgs/testOnDemandRTSPServer.cpp
index dcfaf6c..680c2de 100644
--- a/testProgs/testOnDemandRTSPServer.cpp
+++ b/testProgs/testOnDemandRTSPServer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that demonstrates how to stream - via unicast RTP
 // - various kinds of file on demand, using a built-in RTSP server.
 // main program
@@ -36,11 +36,18 @@ Boolean iFramesOnly = False;
 static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,
 			   char const* streamName, char const* inputFileName); // fwd
 
-static char newMatroskaDemuxWatchVariable;
-static MatroskaFileServerDemux* demux;
+static char newDemuxWatchVariable;
+
+static MatroskaFileServerDemux* matroskaDemux;
 static void onMatroskaDemuxCreation(MatroskaFileServerDemux* newDemux, void* /*clientData*/) {
-  demux = newDemux;
-  newMatroskaDemuxWatchVariable = 1;
+  matroskaDemux = newDemux;
+  newDemuxWatchVariable = 1;
+}
+
+static OggFileServerDemux* oggDemux;
+static void onOggDemuxCreation(OggFileServerDemux* newDemux, void* /*clientData*/) {
+  oggDemux = newDemux;
+  newDemuxWatchVariable = 1;
 }
 
 int main(int argc, char** argv) {
@@ -296,13 +303,13 @@ int main(int argc, char** argv) {
       = ServerMediaSession::createNew(*env, streamName, streamName,
 				      descriptionString);
 
-    newMatroskaDemuxWatchVariable = 0;
+    newDemuxWatchVariable = 0;
     MatroskaFileServerDemux::createNew(*env, inputFileName, onMatroskaDemuxCreation, NULL);
-    env->taskScheduler().doEventLoop(&newMatroskaDemuxWatchVariable);
+    env->taskScheduler().doEventLoop(&newDemuxWatchVariable);
 
     Boolean sessionHasTracks = False;
     ServerMediaSubsession* smss;
-    while ((smss = demux->newServerMediaSubsession()) != NULL) {
+    while ((smss = matroskaDemux->newServerMediaSubsession()) != NULL) {
       sms->addSubsession(smss);
       sessionHasTracks = True;
     }
@@ -323,13 +330,66 @@ int main(int argc, char** argv) {
       = ServerMediaSession::createNew(*env, streamName, streamName,
 				      descriptionString);
 
-    newMatroskaDemuxWatchVariable = 0;
+    newDemuxWatchVariable = 0;
     MatroskaFileServerDemux::createNew(*env, inputFileName, onMatroskaDemuxCreation, NULL);
-    env->taskScheduler().doEventLoop(&newMatroskaDemuxWatchVariable);
+    env->taskScheduler().doEventLoop(&newDemuxWatchVariable);
+
+    Boolean sessionHasTracks = False;
+    ServerMediaSubsession* smss;
+    while ((smss = matroskaDemux->newServerMediaSubsession()) != NULL) {
+      sms->addSubsession(smss);
+      sessionHasTracks = True;
+    }
+    if (sessionHasTracks) {
+      rtspServer->addServerMediaSession(sms);
+    }
+    // otherwise, because the stream has no tracks, we don't add a ServerMediaSession to the server.
+
+    announceStream(rtspServer, sms, streamName, inputFileName);
+  }
+
+  // An Ogg ('.ogg') file, with video and/or audio streams:
+  {
+    char const* streamName = "oggFileTest";
+    char const* inputFileName = "test.ogg";
+    ServerMediaSession* sms
+      = ServerMediaSession::createNew(*env, streamName, streamName,
+				      descriptionString);
+
+    newDemuxWatchVariable = 0;
+    OggFileServerDemux::createNew(*env, inputFileName, onOggDemuxCreation, NULL);
+    env->taskScheduler().doEventLoop(&newDemuxWatchVariable);
+
+    Boolean sessionHasTracks = False;
+    ServerMediaSubsession* smss;
+    while ((smss = oggDemux->newServerMediaSubsession()) != NULL) {
+      sms->addSubsession(smss);
+      sessionHasTracks = True;
+    }
+    if (sessionHasTracks) {
+      rtspServer->addServerMediaSession(sms);
+    }
+    // otherwise, because the stream has no tracks, we don't add a ServerMediaSession to the server.
+
+    announceStream(rtspServer, sms, streamName, inputFileName);
+  }
+
+  // An Opus ('.opus') audio file:
+  // (Note: ".opus' files are special types of Ogg files, so we use the same code as the Ogg ('.ogg') file code above.)
+  {
+    char const* streamName = "opusFileTest";
+    char const* inputFileName = "test.opus";
+    ServerMediaSession* sms
+      = ServerMediaSession::createNew(*env, streamName, streamName,
+				      descriptionString);
+
+    newDemuxWatchVariable = 0;
+    OggFileServerDemux::createNew(*env, inputFileName, onOggDemuxCreation, NULL);
+    env->taskScheduler().doEventLoop(&newDemuxWatchVariable);
 
     Boolean sessionHasTracks = False;
     ServerMediaSubsession* smss;
-    while ((smss = demux->newServerMediaSubsession()) != NULL) {
+    while ((smss = oggDemux->newServerMediaSubsession()) != NULL) {
       sms->addSubsession(smss);
       sessionHasTracks = True;
     }
diff --git a/testProgs/testRTSPClient.cpp b/testProgs/testRTSPClient.cpp
index 52746ac..aa48714 100644
--- a/testProgs/testRTSPClient.cpp
+++ b/testProgs/testRTSPClient.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A demo application, showing how to create and run a RTSP client (that can potentially receive multiple streams concurrently).
 //
 // NOTE: This code - although it builds a running application - is intended only to illustrate how to develop your own RTSP
@@ -238,8 +238,13 @@ void setupNextSubsession(RTSPClient* rtspClient) {
       env << *rtspClient << "Failed to initiate the \"" << *scs.subsession << "\" subsession: " << env.getResultMsg() << "\n";
       setupNextSubsession(rtspClient); // give up on this subsession; go to the next one
     } else {
-      env << *rtspClient << "Initiated the \"" << *scs.subsession
-	  << "\" subsession (client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1 << ")\n";
+      env << *rtspClient << "Initiated the \"" << *scs.subsession << "\" subsession (";
+      if (scs.subsession->rtcpIsMuxed()) {
+	env << "client port " << scs.subsession->clientPortNum();
+      } else {
+	env << "client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1;
+      }
+      env << ")\n";
 
       // Continue setting up this subsession, by sending a RTSP "SETUP" command:
       rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP, False, REQUEST_STREAMING_OVER_TCP);
@@ -267,8 +272,13 @@ void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultStri
       break;
     }
 
-    env << *rtspClient << "Set up the \"" << *scs.subsession
-	<< "\" subsession (client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1 << ")\n";
+    env << *rtspClient << "Set up the \"" << *scs.subsession << "\" subsession (";
+    if (scs.subsession->rtcpIsMuxed()) {
+      env << "client port " << scs.subsession->clientPortNum();
+    } else {
+      env << "client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1;
+    }
+    env << ")\n";
 
     // Having successfully setup the subsession, create a data sink for it, and call "startPlaying()" on it.
     // (This will prepare the data sink to receive data; the actual flow of data from the client won't start happening until later,
@@ -283,7 +293,7 @@ void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultStri
     }
 
     env << *rtspClient << "Created a data sink for the \"" << *scs.subsession << "\" subsession\n";
-    scs.subsession->miscPtr = rtspClient; // a hack to let subsession handle functions get the "RTSPClient" from the subsession 
+    scs.subsession->miscPtr = rtspClient; // a hack to let subsession handler functions get the "RTSPClient" from the subsession 
     scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
 				       subsessionAfterPlaying, scs.subsession);
     // Also set a handler to be called if a RTCP "BYE" arrives for this subsession:
diff --git a/testProgs/testRelay.cpp b/testProgs/testRelay.cpp
index 189b2ff..735ed43 100644
--- a/testProgs/testRelay.cpp
+++ b/testProgs/testRelay.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that receives a UDP multicast stream
 // and retransmits it to another (multicast or unicast) address & port
 // main program
diff --git a/testProgs/testReplicator.cpp b/testProgs/testReplicator.cpp
index 6d8a69a..f0919e1 100644
--- a/testProgs/testReplicator.cpp
+++ b/testProgs/testReplicator.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A demo application that receives a UDP multicast stream, replicates it (using the "StreamReplicator" class),
 // and retransmits one replica stream to another (multicast or unicast) address & port,
 // and writes the other replica stream to a file.
diff --git a/testProgs/testWAVAudioStreamer.cpp b/testProgs/testWAVAudioStreamer.cpp
index 12d742d..6d9c20d 100644
--- a/testProgs/testWAVAudioStreamer.cpp
+++ b/testProgs/testWAVAudioStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that streams a WAV audio file via RTP/RTCP
 // main program
 
diff --git a/testProgs/vobStreamer.cpp b/testProgs/vobStreamer.cpp
index b883a1a..b64ddd7 100644
--- a/testProgs/vobStreamer.cpp
+++ b/testProgs/vobStreamer.cpp
@@ -13,7 +13,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 **********/
-// Copyright (c) 1996-2014, Live Networks, Inc.  All rights reserved
+// Copyright (c) 1996-2016, Live Networks, Inc.  All rights reserved
 // A test program that reads a VOB file
 // splits it into Audio (AC3) and Video (MPEG) Elementary Streams,
 // and streams both using RTP.

-- 
liblivemedia packaging



More information about the pkg-multimedia-commits mailing list