Darwin-Streaming-Server/APIModules/QTSSReflectorModule/RelayOutput.cpp
Darren VanBuren 849723c9cf Add even more of the source
This should be about everything needed to build so far?
2017-03-07 17:14:16 -08:00

580 lines
22 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: RelayOutput.cpp
Contains: Implementation of object described in .h file
*/
#include "RelayOutput.h"
#include "OSMemory.h"
#include "SocketUtils.h"
#include "RTSPSourceInfo.h"
static StrPtrLen sUDPDestStr("udp_destination");
static StrPtrLen sAnnouncedDestStr("announced_destination");
// STATIC DATA
OSQueue RelayOutput::sRelayOutputQueue;
OSMutex RelayOutput::sQueueMutex;
QTSS_ObjectType RelayOutput::qtssRelayOutputObjectType;
QTSS_AttributeID RelayOutput::sOutputType = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputDestAddr = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputLocalAddr = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputUDPPorts = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputRTSPPort = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputURL = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputTTL = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputCurPacketsPerSec = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputCurBitsPerSec = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputTotalPacketsSent = qtssIllegalAttrID;
QTSS_AttributeID RelayOutput::sOutputTotalBytesSent = qtssIllegalAttrID;
static char* sOutputTypeName = "output_type";
static char* sOutputDestAddrName = "output_dest_addr";
static char* sOutputLocalAddrName = "output_local_addr";
static char* sOutputUDPPortsName = "output_udp_ports";
static char* sOutputRTSPPortName = "output_rtsp_port";
static char* sOutputURLName = "output_url";
static char* sOutputTTLName = "output_ttl";
static char* sOutputCurPacketsPerSecName = "output_cur_packetspersec";
static char* sOutputCurBitsPerSecName = "output_cur_bitspersec";
static char* sOutputTotalPacketsSentName = "output_total_packets_sent";
static char* sOutputTotalBytesSentName = "output_total_bytes_sent";
void RelayOutput::Register()
{
// Create the relay output object type
(void)QTSS_CreateObjectType(&qtssRelayOutputObjectType);
// Add the static attributes to the qtssRelayOutputObjectType object
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputTypeName, NULL, qtssAttrDataTypeCharArray);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputTypeName, &sOutputType); // dest type
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputDestAddrName, NULL, qtssAttrDataTypeCharArray);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputDestAddrName, &sOutputDestAddr); // dest addr
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputLocalAddrName, NULL, qtssAttrDataTypeCharArray);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputLocalAddrName, &sOutputLocalAddr); // interface addr
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputUDPPortsName, NULL, qtssAttrDataTypeUInt16);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputUDPPortsName, &sOutputUDPPorts); // udp ports
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputRTSPPortName, NULL, qtssAttrDataTypeUInt16);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputRTSPPortName, &sOutputRTSPPort); // rtsp port
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputURLName, NULL, qtssAttrDataTypeCharArray);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputURLName, &sOutputURL); // url
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputTTLName, NULL, qtssAttrDataTypeUInt16);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputTTLName, &sOutputTTL); // ttl
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputCurPacketsPerSecName, NULL, qtssAttrDataTypeUInt32);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputCurPacketsPerSecName, &sOutputCurPacketsPerSec);// cur packets/sec
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputCurBitsPerSecName, NULL, qtssAttrDataTypeUInt32);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputCurBitsPerSecName, &sOutputCurBitsPerSec); // cur bits/sec
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputTotalPacketsSentName, NULL, qtssAttrDataTypeUInt64);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputTotalPacketsSentName, &sOutputTotalPacketsSent);// total packets
(void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputTotalBytesSentName, NULL, qtssAttrDataTypeUInt64);
(void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputTotalBytesSentName, &sOutputTotalBytesSent); // total bytes
}
RelayOutput::RelayOutput(SourceInfo* inInfo, UInt32 inWhichOutput, RelaySession* inRelaySession, Bool16 isRTSPSourceInfo)
: fRelaySession(inRelaySession),
fOutputSocket(NULL, Socket::kNonBlockingSocketType),
fNumStreams(inRelaySession->GetSourceInfo()->GetNumStreams()), // use the reflector session's source info
fOutputInfo(*inInfo->GetOutputInfo(inWhichOutput)),
fQueueElem(),
fFormatter(&fHTMLBuf[0], kMaxHTMLSize),
fPacketsPerSecond(0),
fBitsPerSecond(0),
fLastUpdateTime(0),
fTotalPacketsSent(0),
fTotalBytesSent(0),
fLastPackets(0),
fLastBytes(0),
fClientSocket(NULL),
fClient(NULL),
fDoingAnnounce(false),
fValid(true),
fOutgoingSDP(NULL),
fAnnounceTask(NULL),
fRTSPOutputInfo(NULL)
{
Assert(fNumStreams > 0);
fQueueElem.SetEnclosingObject(this);
fStreamCookieArray = NEW void*[fNumStreams];
fTrackIDArray = NEW UInt32[fNumStreams];
fOutputInfo.fPortArray = NEW UInt16[fNumStreams];//copy constructor doesn't do this
::memset(fOutputInfo.fPortArray, 0, fNumStreams * sizeof(UInt16));
// create a bookmark for each stream we'll reflect
this->InititializeBookmarks( inRelaySession->GetNumStreams() );
// Copy out all the track IDs for each stream
for (UInt32 x = 0; x < fNumStreams; x++)
{
fTrackIDArray[x] = inRelaySession->GetSourceInfo()->GetStreamInfo(x)->fTrackID;
fStreamCookieArray[x] = inRelaySession->GetStreamCookie(fTrackIDArray[x]);
}
// Copy the contents of the output port array
if (inInfo->GetOutputInfo(inWhichOutput)->fPortArray != NULL)
{
UInt32 copySize = fNumStreams;
if (fOutputInfo.fNumPorts < fNumStreams)
copySize = fOutputInfo.fNumPorts;
::memcpy(fOutputInfo.fPortArray, inInfo->GetOutputInfo(inWhichOutput)->fPortArray, copySize * sizeof(UInt16));
}
else if (fOutputInfo.fBasePort != 0)
{
for (UInt32 y = 0; y < fNumStreams; y++)
fOutputInfo.fPortArray[y] = (UInt16) (fOutputInfo.fBasePort + (y * 2) );
}
OS_Error err = BindSocket();
if (err != OS_NoErr)
{
fValid = false;
return;
}
RTSPOutputInfo* rtspInfo = NULL;
if (isRTSPSourceInfo)
{
// in the case of the announce source info, the passed in source info will have the new
// output information, but only the session's source info will have the sdp and url.
RTSPSourceInfo* rtspSourceInfo = (RTSPSourceInfo *)(inInfo);
Assert(rtspSourceInfo != NULL);
RTSPSourceInfo* sessionSourceInfo = (RTSPSourceInfo *)(inRelaySession->GetSourceInfo());
Assert(sessionSourceInfo != NULL);
rtspInfo = rtspSourceInfo->GetRTSPOutputInfo(inWhichOutput);
if (rtspInfo->fIsAnnounced)
{
fRTSPOutputInfo = NEW RTSPOutputInfo();
fRTSPOutputInfo->Copy(*rtspInfo);
fDoingAnnounce = true;
// set up rtsp socket and client
fClientSocket = new TCPClientSocket(Socket::kNonBlockingSocketType);
fClient = new RTSPClient(fClientSocket, false, RelaySession::sRelayUserAgent);
// set up the outgoing socket
fClientSocket->Set(fOutputInfo.fDestAddr, rtspInfo->fAnnouncePort);
int sndBufSize = 32 * 1024;
int rcvBufSize=1024;
fClientSocket->SetOptions(sndBufSize,rcvBufSize);
// set up the client object
StrPtrLen url;
if ((rtspInfo->fDestURl != NULL) && (strlen(rtspInfo->fDestURl) > 0))
url.Set(rtspInfo->fDestURl);
else
url.Set(sessionSourceInfo->GetSourceURL());
fClient->Set(url);
fClient->SetTransportMode(RTSPClient::kPushMode);
fClient->SetName(rtspInfo->fUserName);
fClient->SetPassword(rtspInfo->fPassword);
UInt32 len;
fOutgoingSDP = sessionSourceInfo->GetAnnounceSDP(fOutputInfo.fDestAddr, &len);
fAnnounceState = kSendingAnnounce;
fCurrentSetup = 0;
fAnnounceTask = new RelayAnnouncer(this); // this will now go and run the async announce
fAnnounceTask->Signal(Task::kStartEvent);
}
}
// Write the Output HTML
// Looks like: Relaying to: 229.49.52.102, Ports: 16898 16900 Time to live: 15
static StrPtrLen sHTMLStart("Relaying to: ");
static StrPtrLen sPorts(", Ports: ");
static StrPtrLen sTimeToLive(" Time to live: ");
static StrPtrLen sHTMLEnd("<BR>");
// First, format the destination addr as a dotted decimal string
char theIPAddrBuf[20];
StrPtrLen theIPAddr(theIPAddrBuf, 20);
struct in_addr theAddr;
theAddr.s_addr = htonl(fOutputInfo.fDestAddr);
SocketUtils::ConvertAddrToString(theAddr, &theIPAddr);
// Begin writing the HTML
fFormatter.Put(sHTMLStart);
fFormatter.Put(theIPAddr);
fFormatter.Put(sPorts);
for (UInt32 y = 0; y < fNumStreams; y++)
{
// Write all the destination ports
fFormatter.Put(fOutputInfo.fPortArray[y]);
fFormatter.PutSpace();
}
if (SocketUtils::IsMulticastIPAddr(inInfo->GetOutputInfo(inWhichOutput)->fDestAddr))
{
// Put the time to live if this is a multicast destination
fFormatter.Put(sTimeToLive);
fFormatter.Put(fOutputInfo.fTimeToLive);
}
fFormatter.Put(sHTMLEnd);
// Setup the StrPtrLen to point to the right stuff
fOutputInfoHTML.Ptr = fFormatter.GetBufPtr();
fOutputInfoHTML.Len = fFormatter.GetCurrentOffset();
OSMutexLocker locker(&sQueueMutex);
sRelayOutputQueue.EnQueue(&fQueueElem);
SetupRelayOutputObject(rtspInfo);
}
RelayOutput::~RelayOutput()
{
OSMutexLocker locker(&sQueueMutex);
sRelayOutputQueue.Remove(&fQueueElem);
if (fClientSocket)
delete fClientSocket;
if (fClient)
delete fClient;
delete [] fStreamCookieArray;
delete fOutgoingSDP;
fOutgoingSDP = NULL;
if (fAnnounceTask != NULL)
fAnnounceTask->fOutput = NULL;
if (fRTSPOutputInfo != NULL)
delete fRTSPOutputInfo;
QTSS_Object outputObject;
UInt32 len = sizeof(QTSS_Object);
for (int x = 0; QTSS_GetValue(fRelaySessionObject, RelaySession::sRelayOutputObject, x, &outputObject, &len) == QTSS_NoErr; x++)
{
Assert(outputObject != NULL);
Assert(len == sizeof(QTSS_Object));
if (outputObject == fRelayOutputObject)
{
(void)QTSS_RemoveValue(fRelaySessionObject, RelaySession::sRelayOutputObject, x);
break;
}
}
}
OS_Error RelayOutput::BindSocket()
{
OS_Error theErr = fOutputSocket.Open();
if (theErr != OS_NoErr)
return theErr;
// We don't care what local port we bind to
theErr = fOutputSocket.Bind(fOutputInfo.fLocalAddr, 0);
if (theErr != OS_NoErr)
return theErr;
// Set the ttl to be the proper value
return fOutputSocket.SetTtl(fOutputInfo.fTimeToLive);
}
Bool16 RelayOutput::Equal(SourceInfo* inInfo)
{
// First check if the Source Info matches this RelaySession
if (!fRelaySession->Equal(inInfo))
return false;
for (UInt32 x = 0; x < inInfo->GetNumOutputs(); x++)
{
if (inInfo->GetOutputInfo(x)->Equal(fOutputInfo))
{
RTSPOutputInfo* rtspOutputInfo = NULL;
if (inInfo->IsRTSPSourceInfo())
{
rtspOutputInfo = ((RTSPSourceInfo*)inInfo)->GetRTSPOutputInfo(x);
if (!rtspOutputInfo->fIsAnnounced)
rtspOutputInfo = NULL;
}
if (fRTSPOutputInfo != NULL) // announced output
{
if (!fRTSPOutputInfo->Equal(rtspOutputInfo)) // doesn't match the output
continue;
}
else if (rtspOutputInfo != NULL)
continue;
// This is a rather special purpose function... here we set this
// flag marking this particular output as a duplicate, because
// we know it is equal to this object.
// (This is used in QTSSReflectorModule.cpp:RereadRelayPrefs)
inInfo->GetOutputInfo(x)->fAlreadySetup = true;
return true;
}
}
return false;
}
QTSS_Error RelayOutput::WritePacket(StrPtrLen* inPacket, void* inStreamCookie, UInt32 inFlags, SInt64 /*packetLatenessInMSec*/, SInt64* /*timeToSendThisPacketAgain*/, UInt64* packetIDPtr, SInt64* /*arrivalTimeMSec*/, Bool16 /*firstPacket */ )
{
if (!fValid || fDoingAnnounce)
return OS_NoErr; // Not done setting up or we had an error setting up
// we don't use packetLateness becuase relays don't need to worry about TCP flow control induced transmit delay
// Look for the matching streamID
for (UInt32 x = 0; x < fNumStreams; x++)
{
if (inStreamCookie == fStreamCookieArray[x])
{
UInt16 theDestPort = fOutputInfo.fPortArray[x];
Assert((theDestPort & 1) == 0); //this should always be an RTP port (even)
if (inFlags & qtssWriteFlagsIsRTCP)
theDestPort++;
(void)fOutputSocket.SendTo(fOutputInfo.fDestAddr, theDestPort,
inPacket->Ptr, inPacket->Len);
// Update our totals
fTotalPacketsSent++;
fTotalBytesSent += inPacket->Len;
break;
}
}
// If it is time to recalculate statistics, do so
SInt64 curTime = OS::Milliseconds();
if ((fLastUpdateTime + kStatsIntervalInMilSecs) < curTime)
{
// Update packets per second
Float64 packetsPerSec = (Float64)((SInt64)fTotalPacketsSent - (SInt64)fLastPackets);
packetsPerSec *= 1000;
packetsPerSec /= (Float64)(curTime - fLastUpdateTime);
fPacketsPerSecond = (UInt32)packetsPerSec;
// Update bits per second. Win32 doesn't implement UInt64 -> Float64.
Float64 bitsPerSec = (Float64)((SInt64)fTotalBytesSent - (SInt64)fLastBytes);
bitsPerSec *= 1000 * 8;//convert from seconds to milsecs, bytes to bits
bitsPerSec /= (Float64)(curTime - fLastUpdateTime);
fBitsPerSecond = (UInt32)bitsPerSec;
fLastUpdateTime = curTime;
fLastPackets = fTotalPacketsSent;
fLastBytes = fTotalBytesSent;
}
return QTSS_NoErr;
}
SInt64 RelayOutput::RelayAnnouncer::Run()
{
OSMutexLocker locker(RelayOutput::GetQueueMutex());
SInt64 result = -1;
if (fOutput != NULL)
result = fOutput->RunAnnounce();
return result;
}
SInt64 RelayOutput::RunAnnounce()
{
OS_Error err = OS_NoErr;
SInt64 result = 1000;
if (fAnnounceState == kSendingAnnounce)
{
if (fOutgoingSDP == NULL || ::strlen(fOutgoingSDP) == 0)
err = ENOTCONN;
else
{
err = fClient->SendAnnounce(fOutgoingSDP);
if (err == OS_NoErr)
{
delete fOutgoingSDP;
fOutgoingSDP = NULL;
if (fClient->GetStatus() == 200)
fAnnounceState = kSendingSetup;
else
err = ENOTCONN;
}
}
}
while ((fAnnounceState == kSendingSetup) && (err == OS_NoErr))
{
err = fClient->SendUDPSetup(fTrackIDArray[fCurrentSetup], 10000);
if (err == OS_NoErr)
{
if (fClient->GetStatus() == 200)
{
fOutputInfo.fPortArray[fCurrentSetup] = fClient->GetServerPort(); // this got set from the Setup reply
fCurrentSetup++;
if (fCurrentSetup == fNumStreams)
fAnnounceState = kSendingPlay;
}
else
err = ENOTCONN;
}
}
if (fAnnounceState == kSendingPlay)
{
err = fClient->SendPlay(0);
if (err == OS_NoErr)
{
if (fClient->GetStatus() == 200)
fAnnounceState = kDone;
else
err = ENOTCONN;
}
}
if (fAnnounceState == kDone)
{
for (UInt32 index = 0; index < fNumStreams; index++) // source udp ports
{
UInt16 udpPort = fOutputInfo.fPortArray[index];
err = QTSS_SetValue (fRelayOutputObject, sOutputUDPPorts, index, &udpPort, sizeof(udpPort));
Assert(err == QTSS_NoErr);
}
fDoingAnnounce = false;
result = -1; // let the task die
fAnnounceTask = NULL;
}
if ((err == EINPROGRESS) || (err == EAGAIN))
{
// Request an async event
fClientSocket->GetSocket()->SetTask(fAnnounceTask);
fClientSocket->GetSocket()->RequestEvent(fClientSocket->GetEventMask() );
}
else if (err != OS_NoErr)
{
// We encountered some fatal error with the socket. Record this as a connection failure
fValid = false;
result = -1; // let the task die
fAnnounceTask = NULL;
}
if ( (-1 == result) && (NULL != fClientSocket) && (NULL != fClientSocket->GetSocket() ) )
fClientSocket->GetSocket()->SetTask(NULL); //remove fAnnounceTask from event handling code. The task can be safely deleted after detaching from the socket.
return result;
}
void RelayOutput::SetupRelayOutputObject(RTSPOutputInfo* inRTSPInfo)
{
fRelaySessionObject = fRelaySession->GetRelaySessionObject();
QTSS_Error theErr = QTSS_NoErr;
UInt32 outIndex = 0;
theErr = QTSS_LockObject (fRelaySessionObject);
Assert(theErr == QTSS_NoErr);
theErr = QTSS_CreateObjectValue (fRelaySessionObject , RelaySession::sRelayOutputObject, qtssRelayOutputObjectType, &outIndex, &fRelayOutputObject);
Assert(theErr == QTSS_NoErr);
if ((inRTSPInfo == NULL) || !inRTSPInfo->fIsAnnounced) // output type
{
theErr = QTSS_SetValue (fRelayOutputObject, sOutputType, 0, (void*)sUDPDestStr.Ptr, sUDPDestStr.Len);
Assert(theErr == QTSS_NoErr);
}
else
{
theErr = QTSS_SetValue (fRelayOutputObject, sOutputType, 0, (void*)sAnnouncedDestStr.Ptr, sAnnouncedDestStr.Len); // output type
Assert(theErr == QTSS_NoErr);
theErr = QTSS_SetValue (fRelayOutputObject, sOutputRTSPPort, 0, &inRTSPInfo->fAnnouncePort, sizeof(inRTSPInfo->fAnnouncePort)); // rtsp port
Assert(theErr == QTSS_NoErr);
theErr = QTSS_SetValue (fRelayOutputObject, sOutputURL, 0, (fClient->GetURL())->Ptr, (fClient->GetURL())->Len); // output url
Assert(theErr == QTSS_NoErr);
}
char theIPAddrBuf[20];
StrPtrLen theIPAddr(theIPAddrBuf, 20);
struct in_addr theDestAddr; // output destination address
theDestAddr.s_addr = htonl(fOutputInfo.fDestAddr);
SocketUtils::ConvertAddrToString(theDestAddr, &theIPAddr);
theErr = QTSS_SetValue (fRelayOutputObject, sOutputDestAddr, 0, (void*)theIPAddr.Ptr, theIPAddr.Len);
Assert(theErr == QTSS_NoErr);
struct in_addr theLocalAddr; // output local address
theLocalAddr.s_addr = htonl(fOutputInfo.fLocalAddr);
SocketUtils::ConvertAddrToString(theLocalAddr, &theIPAddr);
theErr = QTSS_SetValue (fRelayOutputObject, sOutputLocalAddr, 0, (void*)theIPAddr.Ptr, theIPAddr.Len);
Assert(theErr == QTSS_NoErr);
for (UInt32 index = 0; index < fNumStreams; index++) // output udp ports
{
UInt16 udpPort = fOutputInfo.fPortArray[index];
theErr = QTSS_SetValue (fRelayOutputObject, sOutputUDPPorts, index, &udpPort, sizeof(udpPort));
Assert(theErr == QTSS_NoErr);
}
theErr = QTSS_SetValue (fRelayOutputObject, sOutputTTL, 0, &(fOutputInfo.fTimeToLive), sizeof(fOutputInfo.fTimeToLive));
Assert(theErr == QTSS_NoErr);
theErr = QTSS_SetValuePtr (fRelayOutputObject, sOutputCurPacketsPerSec, &fPacketsPerSecond, sizeof(fPacketsPerSecond));
Assert(theErr == QTSS_NoErr);
theErr = QTSS_SetValuePtr (fRelayOutputObject, sOutputCurBitsPerSec, &fBitsPerSecond, sizeof(fBitsPerSecond));
Assert(theErr == QTSS_NoErr);
theErr = QTSS_SetValuePtr (fRelayOutputObject, sOutputTotalPacketsSent, &fTotalPacketsSent, sizeof(fTotalPacketsSent));
Assert(theErr == QTSS_NoErr);
theErr = QTSS_SetValuePtr (fRelayOutputObject, sOutputTotalBytesSent, &fTotalBytesSent, sizeof(fTotalBytesSent));
Assert(theErr == QTSS_NoErr);
theErr = QTSS_UnlockObject (fRelaySessionObject);
Assert(theErr == QTSS_NoErr);
}