667 lines
29 KiB
C++
667 lines
29 KiB
C++
/*
|
|
*
|
|
* @APPLE_LICENSE_HEADER_START@
|
|
*
|
|
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this
|
|
* file.
|
|
*
|
|
* The Original Code and all software distributed under the License are
|
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_LICENSE_HEADER_END@
|
|
*
|
|
*/
|
|
/*
|
|
File: QTSSSplitterModule.cpp
|
|
|
|
Contains: Implementation of QTSSSplitterModule class.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
#include "QTSSSplitterModule.h"
|
|
#include "QTSSModuleUtils.h"
|
|
#include "ReflectorSession.h"
|
|
#include "OSArrayObjectDeleter.h"
|
|
#include "OSMemory.h"
|
|
|
|
//ReflectorOutput objects
|
|
#include "RTPSessionOutput.h"
|
|
#include "RelayOutput.h"
|
|
|
|
//SourceInfo objects
|
|
#include "RTSPSourceInfo.h"
|
|
|
|
// Fixes
|
|
#include "QTSSMemoryDeleter.h"
|
|
#include "FilePrefsSource.h"
|
|
|
|
// ATTRIBUTES
|
|
|
|
static QTSS_AttributeID sOutputAttr = qtssIllegalAttrID;
|
|
static QTSS_AttributeID sSessionAttr = qtssIllegalAttrID;
|
|
static QTSS_AttributeID sStreamCookieAttr = qtssIllegalAttrID;
|
|
|
|
static QTSS_AttributeID sRemoteHostRespondedWithAnErrorErr = qtssIllegalAttrID;
|
|
static QTSS_AttributeID sRemoteHostRefusedConnectionErr = qtssIllegalAttrID;
|
|
static QTSS_AttributeID sExpectedDigitFilenameErr = qtssIllegalAttrID;
|
|
static QTSS_AttributeID sBadTrackIDErr = qtssIllegalAttrID;
|
|
|
|
// STATIC DATA
|
|
|
|
static const UInt32 kSessionStartingIdleTimeInMsec = 20;
|
|
static const StrPtrLen kCacheControlHeader("no-cache");
|
|
static QTSS_PrefsObject sServerPrefs = NULL;
|
|
|
|
static OSRefTable* sSessionMap = NULL;
|
|
|
|
// Important strings
|
|
static StrPtrLen sRCFSuffix(".rcf");
|
|
static StrPtrLen sRTSPSourceStr("relay_source");
|
|
|
|
// FUNCTION PROTOTYPES
|
|
|
|
static QTSS_Error QTSSSplitterModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams);
|
|
static QTSS_Error Register(QTSS_Register_Params* inParams);
|
|
static QTSS_Error Initialize(QTSS_Initialize_Params* inParams);
|
|
static QTSS_Error Shutdown();
|
|
static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams);
|
|
static QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams);
|
|
static ReflectorSession* FindOrCreateSession(StrPtrLen* inPath, QTSS_StandardRTSP_Params* inParams);
|
|
static QTSS_Error HandleSourceInfoErr(QTSS_Error rtspSourceInfoErr, QTSS_StandardRTSP_Params* inParams,
|
|
ReflectorSession* inSession, RTSPClient* inClient);
|
|
static void DeleteSessionOnError(ReflectorSession* inSession, QTSS_ClientSessionObject inCliSession);
|
|
static void NullOutSessionAttr(QTSS_ClientSessionObject inSession);
|
|
static QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession);
|
|
static QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession);
|
|
static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams);
|
|
static void IssueTeardown(ReflectorSession* inSession);
|
|
static void RequestSocketEvent(QTSS_StreamRef inStream, UInt32 inEventMask);
|
|
|
|
// FUNCTION IMPLEMENTATIONS
|
|
|
|
QTSS_Error QTSSSplitterModule_Main(void* inPrivateArgs)
|
|
{
|
|
return _stublibrary_main(inPrivateArgs, QTSSSplitterModuleDispatch);
|
|
}
|
|
|
|
|
|
QTSS_Error QTSSSplitterModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams)
|
|
{
|
|
switch (inRole)
|
|
{
|
|
case QTSS_Register_Role:
|
|
return Register(&inParams->regParams);
|
|
case QTSS_Initialize_Role:
|
|
return Initialize(&inParams->initParams);
|
|
case QTSS_RTSPPreProcessor_Role:
|
|
return ProcessRTSPRequest(&inParams->rtspRequestParams);
|
|
case QTSS_ClientSessionClosing_Role:
|
|
return DestroySession(&inParams->clientSessionClosingParams);
|
|
case QTSS_Shutdown_Role:
|
|
return Shutdown();
|
|
}
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
|
|
QTSS_Error Register(QTSS_Register_Params* inParams)
|
|
{
|
|
// Do role & attribute setup
|
|
(void)QTSS_AddRole(QTSS_Initialize_Role);
|
|
(void)QTSS_AddRole(QTSS_Shutdown_Role);
|
|
(void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role);
|
|
(void)QTSS_AddRole(QTSS_ClientSessionClosing_Role);
|
|
|
|
// Add text messages attributes
|
|
static char* sRemoteHostRespondedWithAnErrorName = "QTSSSplitterModuleRemoteHostError";
|
|
static char* sRemoteHostRefusedConnectionName = "QTSSSplitterModuleRemoteHostRefused";
|
|
static char* sExpectedDigitFilenameName = "QTSSSplitterModuleExpectedDigitFilename";
|
|
static char* sBadTrackIDErrName = "QTSSSplitterModuleBadTrackID";
|
|
|
|
(void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sRemoteHostRespondedWithAnErrorName, NULL, qtssAttrDataTypeCharArray);
|
|
(void)QTSS_IDForAttr(qtssTextMessagesObjectType, sRemoteHostRespondedWithAnErrorName, &sRemoteHostRespondedWithAnErrorErr);
|
|
|
|
(void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sRemoteHostRefusedConnectionName, NULL, qtssAttrDataTypeCharArray);
|
|
(void)QTSS_IDForAttr(qtssTextMessagesObjectType, sRemoteHostRefusedConnectionName, &sRemoteHostRefusedConnectionErr);
|
|
|
|
(void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sExpectedDigitFilenameName, NULL, qtssAttrDataTypeCharArray);
|
|
(void)QTSS_IDForAttr(qtssTextMessagesObjectType, sExpectedDigitFilenameName, &sExpectedDigitFilenameErr);
|
|
|
|
(void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadTrackIDErrName, NULL, qtssAttrDataTypeCharArray);
|
|
(void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadTrackIDErrName, &sBadTrackIDErr);
|
|
|
|
// Add an RTP session attribute for tracking ReflectorSession objects
|
|
static char* sOutputName = "QTSSSplitterModuleOutput";
|
|
static char* sSessionName= "QTSSSplitterModuleSession";
|
|
static char* sStreamCookieName = "QTSSSplitterModuleStreamCookie";
|
|
|
|
(void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sOutputName, NULL, qtssAttrDataTypeVoidPointer);
|
|
(void)QTSS_IDForAttr(qtssClientSessionObjectType, sOutputName, &sOutputAttr);
|
|
|
|
(void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sSessionName, NULL, qtssAttrDataTypeVoidPointer);
|
|
(void)QTSS_IDForAttr(qtssClientSessionObjectType, sSessionName, &sSessionAttr);
|
|
|
|
(void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sStreamCookieName, NULL, qtssAttrDataTypeVoidPointer);
|
|
(void)QTSS_IDForAttr(qtssRTPStreamObjectType, sStreamCookieName, &sStreamCookieAttr);
|
|
|
|
// Reflector stream needs to setup some parameters too.
|
|
ReflectorStream::Register();
|
|
// RTPSessionOutput needs to do the same
|
|
RTPSessionOutput::Register();
|
|
|
|
// Tell the server our name!
|
|
static char* sModuleName = "QTSSSplitterModule";
|
|
::strcpy(inParams->outModuleName, sModuleName);
|
|
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
|
|
QTSS_Error Initialize(QTSS_Initialize_Params* inParams)
|
|
{
|
|
// Setup module utils
|
|
QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream);
|
|
sSessionMap = NEW OSRefTable();
|
|
sServerPrefs = inParams->inPrefs;
|
|
|
|
//
|
|
// Instead of passing our own module prefs object, as one might expect,
|
|
// here we pass in the QTSSReflectorModule's, because the prefs that
|
|
// apply to ReflectorStream are stored in that module's prefs
|
|
StrPtrLen theReflectorModule("QTSSReflectorModule");
|
|
QTSS_ModulePrefsObject theReflectorPrefs =
|
|
QTSSModuleUtils::GetModulePrefsObject(QTSSModuleUtils::GetModuleObjectByName(theReflectorModule));
|
|
|
|
// Call helper class initializers
|
|
ReflectorStream::Initialize(theReflectorPrefs);
|
|
ReflectorSession::Initialize();
|
|
|
|
// Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN
|
|
static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod };
|
|
QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 5);
|
|
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
QTSS_Error Shutdown()
|
|
{
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams)
|
|
{
|
|
QTSS_RTSPMethod* theMethod = NULL;
|
|
UInt32 theLen = 0;
|
|
if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0,
|
|
(void**)&theMethod, &theLen) != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod)))
|
|
{
|
|
Assert(0);
|
|
return QTSS_RequestFailed;
|
|
}
|
|
|
|
if (*theMethod == qtssDescribeMethod)
|
|
return DoDescribe(inParams);
|
|
|
|
RTPSessionOutput** theOutput = NULL;
|
|
QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen);
|
|
if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*)))
|
|
return QTSS_RequestFailed;
|
|
|
|
switch (*theMethod)
|
|
{
|
|
case qtssSetupMethod:
|
|
return DoSetup(inParams, (*theOutput)->GetReflectorSession());
|
|
case qtssPlayMethod:
|
|
return DoPlay(inParams, (*theOutput)->GetReflectorSession());
|
|
case qtssTeardownMethod:
|
|
// Tell the server that this session should be killed, and send a TEARDOWN response
|
|
(void)QTSS_Teardown(inParams->inClientSession);
|
|
(void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
|
|
break;
|
|
case qtssPauseMethod:
|
|
(void)QTSS_Pause(inParams->inClientSession);
|
|
(void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
|
|
QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams)
|
|
{
|
|
QTSS_Error theErr = QTSS_NoErr;
|
|
|
|
// If this URL doesn't end with an .rcf, don't even bother. Ah, if only the QTSSReflectorModule
|
|
// could make this same check as well
|
|
StrPtrLen theURI;
|
|
(void)QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqURI, 0, (void**)&theURI.Ptr, &theURI.Len);
|
|
if ((theURI.Len < sRCFSuffix.Len) || (!sRCFSuffix.EqualIgnoreCase(&theURI.Ptr[theURI.Len - sRCFSuffix.Len], sRCFSuffix.Len)))
|
|
return QTSS_NoErr;
|
|
|
|
// Check and see if we are in the process of setting this connection up already
|
|
ReflectorSession* theSession = NULL;
|
|
UInt32 theLen = sizeof(ReflectorSession*);
|
|
(void)QTSS_GetValue(inParams->inClientSession, sSessionAttr, 0, (void*)&theSession, &theLen);
|
|
|
|
if (theSession == NULL)
|
|
{
|
|
// If we are not already in the process of initializing and setting up this ReflectorSession,
|
|
// attempt to create one or attach to one.
|
|
|
|
// Check and see if the full path to this file matches an existing ReflectorSession
|
|
StrPtrLen thePathPtr;
|
|
OSCharArrayDeleter rcfPath(QTSSModuleUtils::GetFullPath( inParams->inRTSPRequest,
|
|
qtssRTSPReqFilePath,
|
|
&thePathPtr.Len, NULL));
|
|
|
|
thePathPtr.Ptr = rcfPath.GetObject();
|
|
theSession = FindOrCreateSession(&thePathPtr, inParams);
|
|
// If this function returned an error, this request shouldn't be handled by this module
|
|
if (theSession == NULL)
|
|
return QTSS_NoErr;
|
|
}
|
|
if (!((RTSPSourceInfo*)theSession->GetSourceInfo())->IsDescribeComplete())
|
|
{
|
|
// Only the session owner need worry about this code. this is an easy.
|
|
// Way of checking this.
|
|
Assert(theLen == sizeof(ReflectorSession*));
|
|
|
|
// If we haven't finished the describe yet, call
|
|
// Describe again on the RTSPSourceInfo object.
|
|
QTSS_Error theErr = ((RTSPSourceInfo*)theSession->GetSourceInfo())->Describe();
|
|
if (theErr != QTSS_NoErr)
|
|
return HandleSourceInfoErr(theErr, inParams, theSession,
|
|
((RTSPSourceInfo*)theSession->GetSourceInfo())->GetRTSPClient());
|
|
else
|
|
{
|
|
// Describe has completed. At this point we can setup the ReflectorSession.
|
|
// However, tell it not to consider this session completely setup yet, as
|
|
theErr = theSession->SetupReflectorSession(theSession->GetSourceInfo(), inParams, ReflectorSession::kDontMarkSetup);
|
|
if (theErr != QTSS_NoErr)
|
|
{
|
|
// If we get an error here, for some reason we couldn't bind the ports, etc, etc.
|
|
// Just abort
|
|
DeleteSessionOnError(theSession, inParams->inClientSession);
|
|
return theErr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!theSession->IsSetup())
|
|
{
|
|
// Only the session owner need worry about this code. this is an easy.
|
|
// Way of checking this.
|
|
Assert(theLen == sizeof(ReflectorSession*));
|
|
|
|
// If we get here, the DESCRIBE has completed, but if we are the owner that isn't enough.
|
|
// We need to make sure that the SETUP and PLAY requests execute as well.
|
|
theErr = ((RTSPSourceInfo*)theSession->GetSourceInfo())->SetupAndPlay();
|
|
if (theErr != QTSS_NoErr)
|
|
return HandleSourceInfoErr(theErr, inParams, theSession,
|
|
((RTSPSourceInfo*)theSession->GetSourceInfo())->GetRTSPClient());
|
|
|
|
// We've completed the SETUP and PLAY process if we are here. The ReflectorSession
|
|
// is completely setup.
|
|
theSession->ManuallyMarkSetup();
|
|
|
|
// NULL out the sSessionAttr, we don't need it anymore.
|
|
NullOutSessionAttr(inParams->inClientSession);
|
|
}
|
|
|
|
//ok, we've found or setup the proper reflector session, create an RTPSessionOutput object,
|
|
//and add it to the session's list of outputs
|
|
RTPSessionOutput* theNewOutput = NEW RTPSessionOutput(inParams->inClientSession, theSession, sServerPrefs, sStreamCookieAttr );
|
|
theSession->AddOutput(theNewOutput, true);
|
|
|
|
// And vice-versa, store this reflector session in the RTP session.
|
|
(void)QTSS_SetValue(inParams->inClientSession, sOutputAttr, 0, &theNewOutput, sizeof(theNewOutput));
|
|
|
|
// Finally, send the DESCRIBE response
|
|
|
|
//above function has signalled that this request belongs to us, so let's respond
|
|
iovec theDescribeVec[2] = { 0 };
|
|
|
|
Assert(theSession->GetLocalSDP()->Ptr != NULL);
|
|
theDescribeVec[1].iov_base = theSession->GetLocalSDP()->Ptr;
|
|
theDescribeVec[1].iov_len = theSession->GetLocalSDP()->Len;
|
|
(void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader,
|
|
kCacheControlHeader.Ptr, kCacheControlHeader.Len);
|
|
QTSSModuleUtils::SendDescribeResponse(inParams->inRTSPRequest, inParams->inClientSession,
|
|
&theDescribeVec[0], 2, theSession->GetLocalSDP()->Len);
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
ReflectorSession* FindOrCreateSession(StrPtrLen* inPath, QTSS_StandardRTSP_Params* inParams)
|
|
{
|
|
// This function assumes that inPath is NULL terminated
|
|
|
|
StrPtrLen theFileData;
|
|
RTSPSourceInfo* theInfo = NULL;
|
|
|
|
(void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileData);
|
|
if (theFileData.Len > 0)
|
|
theInfo = NEW RTSPSourceInfo(Socket::kNonBlockingSocketType);
|
|
else
|
|
return NULL;
|
|
|
|
// We need to interpret this file as a standard prefs file, so let the
|
|
// FilePrefsSource object parse it, then call ParsePrefs on the RTSPSourceInfo object,
|
|
// which will parse out the RCF metadata.
|
|
|
|
FilePrefsSource thePrefsSource(true);// Allow duplicates
|
|
(void)thePrefsSource.InitFromConfigFile(inPath->Ptr);
|
|
QTSS_Error theErr = theInfo->ParsePrefs(&thePrefsSource, 0);
|
|
if (theErr != QTSS_NoErr)
|
|
{
|
|
delete theInfo;
|
|
return NULL;
|
|
}
|
|
|
|
// Ok, look for a reflector session matching the URL specified in the RCF file.
|
|
// A unique broadcast is defined by the URL, the URL is the argument to resolve.
|
|
|
|
OSMutexLocker locker(sSessionMap->GetMutex());
|
|
OSRef* theSessionRef = sSessionMap->Resolve(theInfo->GetRTSPClient()->GetURL());
|
|
ReflectorSession* theSession = NULL;
|
|
|
|
if (theSessionRef == NULL)
|
|
{
|
|
//If this URL doesn't already have a reflector session, we must make a new
|
|
//one. We already have the proper sourceInfo object, so we only need to construct the session
|
|
|
|
theSession = NEW ReflectorSession(theInfo->GetRTSPClient()->GetURL(), theInfo);
|
|
|
|
//put the session's ID into the session map.
|
|
theErr = sSessionMap->Register(theSession->GetRef());
|
|
Assert(theErr == QTSS_NoErr);
|
|
|
|
//unless we do this, the refcount won't increment (and we'll delete the session prematurely
|
|
OSRef* debug = sSessionMap->Resolve(theInfo->GetRTSPClient()->GetURL());
|
|
Assert(debug == theSession->GetRef());
|
|
|
|
// Create a socket stream for the TCP socket in the RTSPClient object. The socket stream will
|
|
// allow this module to receive events on the socket
|
|
QTSS_StreamRef theSockStream = NULL;
|
|
theErr = QTSS_CreateStreamFromSocket(theInfo->GetRTSPClient()->GetSocket()->GetSocket()->GetSocketFD(), &theSockStream);
|
|
Assert(theErr == QTSS_NoErr);
|
|
Assert(theSockStream != NULL);
|
|
|
|
// Store the socket stream in the Reflector Session so we can get at it easily later on
|
|
theSession->SetSocketStream(theSockStream);
|
|
|
|
// This RTSP session is the "owner" of this ReflectorSession, and will be responsible
|
|
// for setting it up properly, so we should make sure this attribute gets set
|
|
UInt32 theLen = sizeof(theSession);
|
|
theErr = QTSS_SetValue(inParams->inClientSession, sSessionAttr, 0, (void*)&theSession, theLen);
|
|
Assert(theErr == QTSS_NoErr);
|
|
}
|
|
else
|
|
{
|
|
// We aren't the owner of this ReflectorSession, and only the owner needs to keep this
|
|
// RTSPSourceInfo object around.
|
|
delete theInfo;
|
|
|
|
theSession = (ReflectorSession*)theSessionRef->GetObject();
|
|
|
|
if (!theSession->IsSetup())
|
|
{
|
|
// We are not the creator of this session, and it may not be setup yet. If it isn't,
|
|
// we should simply wait for it to be setup.
|
|
sSessionMap->Release(theSession->GetRef());
|
|
|
|
// Give the owner some time to finish setting it up.
|
|
(void)QTSS_SetIdleTimer(kSessionStartingIdleTimeInMsec);
|
|
return NULL; // There isn't a completed session... yet.............
|
|
}
|
|
}
|
|
|
|
Assert(theSession != NULL);
|
|
return theSession;
|
|
}
|
|
|
|
QTSS_Error HandleSourceInfoErr(QTSS_Error rtspSourceInfoErr, QTSS_StandardRTSP_Params* inParams,
|
|
ReflectorSession* inSession, RTSPClient* inClient)
|
|
{
|
|
// If we get an EAGAIN here, the DESCRIBE hasn't completed yet
|
|
if ((rtspSourceInfoErr == EAGAIN) || (rtspSourceInfoErr == EINPROGRESS))
|
|
{
|
|
// We're making an assumption here that inClient only uses one socket to connect to
|
|
// the server. We only have one stream, so we have to make that assumption.
|
|
|
|
// Note that it is not necessary to have any kind of timeout here, because the server
|
|
// naturally times out idle connections. If the server doesn't respond for awhile,
|
|
// this session will naturally go away
|
|
inClient->GetSocket()->GetSocket()->DontAutoCleanup();
|
|
RequestSocketEvent(inSession->GetSocketStream(), inClient->GetSocket()->GetEventMask());
|
|
return QTSS_NoErr; // We'll get called in the same method again when there is more work to do
|
|
}
|
|
|
|
// We've encountered a fatal error for this session, so delete it.
|
|
DeleteSessionOnError(inSession, inParams->inClientSession);
|
|
|
|
if (rtspSourceInfoErr == QTSS_RequestFailed)
|
|
{
|
|
// This happens if the remote host responded with an error.
|
|
char tempBuf[20];
|
|
qtss_sprintf(tempBuf, "%"_U32BITARG_"", inClient->GetStatus());
|
|
StrPtrLen tempBufPtr(&tempBuf[0]);
|
|
return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssServerGatewayTimeout,
|
|
sRemoteHostRespondedWithAnErrorErr, &tempBufPtr);
|
|
}
|
|
else
|
|
return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssServerGatewayTimeout,
|
|
sRemoteHostRefusedConnectionErr);
|
|
}
|
|
|
|
void DeleteSessionOnError(ReflectorSession* inSession, QTSS_ClientSessionObject inCliSession)
|
|
{
|
|
// Make sure to destroy the socket stream as well
|
|
Assert(inSession->GetSocketStream() != NULL);
|
|
(void)QTSS_DestroySocketStream(inSession->GetSocketStream());
|
|
|
|
OSMutexLocker locker (sSessionMap->GetMutex());
|
|
//decrement the ref count
|
|
sSessionMap->Release(inSession->GetRef());
|
|
|
|
// We are here if we are the owner of this session and we encountered an error
|
|
// while trying to setup the session. We have the session map mutex, so the
|
|
// refcount at this point *must* be 0.
|
|
Assert(inSession->GetRef()->GetRefCount() == 0);
|
|
sSessionMap->UnRegister(inSession->GetRef());
|
|
delete inSession;
|
|
|
|
// Make sure the session is NULLd out, because it's deleted now!
|
|
NullOutSessionAttr(inCliSession);
|
|
}
|
|
|
|
void NullOutSessionAttr(QTSS_ClientSessionObject inSession)
|
|
{
|
|
ReflectorSession* theNull = NULL;
|
|
UInt32 theLen = sizeof(theNull);
|
|
QTSS_Error theErr = QTSS_SetValue(inSession, sSessionAttr, 0, (void*)&theNull, theLen);
|
|
Assert(theErr == QTSS_NoErr);
|
|
}
|
|
|
|
QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession)
|
|
{
|
|
//unless there is a digit at the end of this path (representing trackID), don't
|
|
//even bother with the request
|
|
char* theDigitStr = NULL;
|
|
(void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr);
|
|
QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr);
|
|
if (theDigitStr == NULL)
|
|
return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, sExpectedDigitFilenameErr);
|
|
|
|
UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10);
|
|
|
|
QTSS_Error theErr = QTSS_NoErr;
|
|
|
|
// Get info about this trackID
|
|
SourceInfo::StreamInfo* theStreamInfo = inSession->GetSourceInfo()->GetStreamInfoByTrackID(theTrackID);
|
|
// If theStreamInfo is NULL, we don't have a legit track, so return an error
|
|
if (theStreamInfo == NULL)
|
|
return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,
|
|
sBadTrackIDErr);
|
|
|
|
StrPtrLen* thePayloadName = &theStreamInfo->fPayloadName;
|
|
QTSS_RTPPayloadType thePayloadType = theStreamInfo->fPayloadType;
|
|
|
|
QTSS_RTPStreamObject newStream = NULL;
|
|
{
|
|
// Ok, this is completely crazy but I can't think of a better way to do this that's
|
|
// safe so we'll do it this way for now. Because the ReflectorStreams use this session's
|
|
// stream queue, we need to make sure that each ReflectorStream is not reflecting to this
|
|
// session while we call QTSS_AddRTPStream. One brutal way to do this is to grab each
|
|
// ReflectorStream's mutex, which will stop every reflector stream from running.
|
|
|
|
for (UInt32 x = 0; x < inSession->GetNumStreams(); x++)
|
|
inSession->GetStreamByIndex(x)->GetMutex()->Lock();
|
|
|
|
theErr = QTSS_AddRTPStream(inParams->inClientSession, inParams->inRTSPRequest, &newStream, 0);
|
|
|
|
for (UInt32 y = 0; y < inSession->GetNumStreams(); y++)
|
|
inSession->GetStreamByIndex(y)->GetMutex()->Unlock();
|
|
|
|
if (theErr != QTSS_NoErr)
|
|
return theErr;
|
|
}
|
|
|
|
// Set up dictionary items for this stream
|
|
theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayloadName->Ptr, thePayloadName->Len);
|
|
Assert(theErr == QTSS_NoErr);
|
|
theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType));
|
|
Assert(theErr == QTSS_NoErr);
|
|
theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID));
|
|
Assert(theErr == QTSS_NoErr);
|
|
|
|
// Place the stream cookie in this stream for future reference
|
|
void* theStreamCookie = inSession->GetStreamCookie(theTrackID);
|
|
Assert(theStreamCookie != NULL);
|
|
theErr = QTSS_SetValue(newStream, sStreamCookieAttr, 0, &theStreamCookie, sizeof(theStreamCookie));
|
|
Assert(theErr == QTSS_NoErr);
|
|
|
|
// Set the number of quality levels.
|
|
static UInt32 sNumQualityLevels = ReflectorSession::kNumQualityLevels;
|
|
theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels));
|
|
Assert(theErr == QTSS_NoErr);
|
|
|
|
//send the setup response
|
|
(void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader,
|
|
kCacheControlHeader.Ptr, kCacheControlHeader.Len);
|
|
(void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, newStream, 0);
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
|
|
QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession)
|
|
{
|
|
// Tell the session what the bitrate of this reflection is. This is nice for logging,
|
|
// it also allows the server to scale the TCP buffer size appropriately if we are
|
|
// interleaving the data over TCP. This must be set before calling QTSS_Play so the
|
|
// server can use it from within QTSS_Play
|
|
UInt32 bitsPerSecond = inSession->GetBitRate();
|
|
(void)QTSS_SetValue(inParams->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond));
|
|
|
|
//Server shouldn't send RTCP (reflector does it), but the server should append the server info app packet
|
|
QTSS_Error theErr = QTSS_Play(inParams->inClientSession, inParams->inRTSPRequest, qtssPlayFlagsAppendServerInfo);
|
|
if (theErr != QTSS_NoErr)
|
|
return theErr;
|
|
|
|
//and send a standard play response
|
|
(void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams)
|
|
{
|
|
// Check and see if we are in the process of tearing down this connection already
|
|
ReflectorSession* theSession = NULL;
|
|
UInt32 theLen = sizeof(ReflectorSession*);
|
|
(void)QTSS_GetValue(inParams->inClientSession, sSessionAttr, 0, (void*)&theSession, &theLen);
|
|
|
|
if (theSession != NULL)
|
|
IssueTeardown(theSession);
|
|
else
|
|
{
|
|
RTPSessionOutput** theOutput = NULL;
|
|
QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen);
|
|
if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*)) || (theOutput == NULL))
|
|
return QTSS_RequestFailed;
|
|
|
|
// This function removes the output from the ReflectorSession, then
|
|
// checks to see if the session should go away. If it should, this deletes it
|
|
theSession = (*theOutput)->GetReflectorSession();
|
|
theSession->RemoveOutput(*theOutput, true);
|
|
delete (*theOutput);
|
|
|
|
//check if the ReflectorSession should be deleted
|
|
//(it should if its ref count has dropped to 0)
|
|
OSMutexLocker locker (sSessionMap->GetMutex());
|
|
//decrement the ref count
|
|
sSessionMap->Release(theSession->GetRef());
|
|
if (theSession->GetRef()->GetRefCount() == 0)
|
|
{
|
|
sSessionMap->UnRegister(theSession->GetRef());
|
|
|
|
theLen = sizeof(theSession);
|
|
theErr = QTSS_SetValue(inParams->inClientSession, sSessionAttr, 0, (void*)&theSession, theLen);
|
|
if (theErr != QTSS_NoErr)
|
|
delete theSession;
|
|
else
|
|
IssueTeardown(theSession);
|
|
}
|
|
}
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
void IssueTeardown(ReflectorSession* inSession)
|
|
{
|
|
// Tell the RTSPSourceInfo object to initiate or continue the Teardown process
|
|
QTSS_Error theErr = ((RTSPSourceInfo*)inSession->GetSourceInfo())->Teardown();
|
|
if ((theErr == EAGAIN) || (theErr == EINPROGRESS))
|
|
{
|
|
RTSPClient* theClient = ((RTSPSourceInfo*)inSession->GetSourceInfo())->GetRTSPClient();
|
|
theClient->GetSocket()->GetSocket()->DontAutoCleanup();
|
|
RequestSocketEvent(inSession->GetSocketStream(), theClient->GetSocket()->GetEventMask());
|
|
}
|
|
else
|
|
{
|
|
// Make sure to destroy the socket stream as well
|
|
Assert(inSession->GetSocketStream() != NULL);
|
|
(void)QTSS_DestroySocketStream(inSession->GetSocketStream());
|
|
|
|
delete inSession;
|
|
}
|
|
}
|
|
|
|
void RequestSocketEvent(QTSS_StreamRef inStream, UInt32 inEventMask)
|
|
{
|
|
//
|
|
// Job of this function is to convert a CommonUtilitiesLib event mask to a QTSS Event mask
|
|
QTSS_EventType theEvent = 0;
|
|
|
|
if (inEventMask & EV_RE)
|
|
theEvent |= QTSS_ReadableEvent;
|
|
if (inEventMask & EV_WR)
|
|
theEvent |= QTSS_WriteableEvent;
|
|
|
|
QTSS_Error theErr = QTSS_RequestEvent(inStream, theEvent);
|
|
Assert(theErr == QTSS_NoErr);
|
|
}
|