1200 lines
42 KiB
C++
1200 lines
42 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: StreamingLoadTool.cpp
|
|
|
|
Contains: Tool that simulates streaming movie load
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include<string.h>
|
|
|
|
#ifndef kVersionString
|
|
#include "../revision.h"
|
|
#endif
|
|
#ifndef __Win32__
|
|
#include <unistd.h>
|
|
#include <netdb.h>
|
|
#include <signal.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/wait.h>
|
|
#endif
|
|
|
|
#include "ClientSession.h"
|
|
#include "FilePrefsSource.h"
|
|
#include "OSMemory.h"
|
|
#include "OSArrayObjectDeleter.h"
|
|
#include "SocketUtils.h"
|
|
#include "StringFormatter.h"
|
|
#include "Socket.h"
|
|
#include "OS.h"
|
|
#include "Task.h"
|
|
#include "TimeoutTask.h"
|
|
#include "SVector.h"
|
|
#ifndef __MacOSX__
|
|
#include "getopt.h"
|
|
#include "../revision.h"
|
|
#endif
|
|
|
|
#define STREAMINGLOADTOOL_DEBUG 0
|
|
#define PACKETADDSIZE 28 // IP headers = 20 + UDP headers = 8
|
|
|
|
//
|
|
// Static data
|
|
static UInt32 sConnectionsThatErrored = 0;
|
|
static UInt32 sFailedConnections = 0;
|
|
static UInt32 sSuccessfulConnections = 0;
|
|
static FILE* sLog = NULL;
|
|
|
|
static ClientSession** sClientSessionArray = NULL;
|
|
static UInt32 sNumClients = 1;
|
|
static Bool16 sNumClientsIsSpecified = false;
|
|
static Bool16 sGotSigInt = false;
|
|
static Bool16 sQuitNow = false;
|
|
static SInt64 sSigIntTime = 0;
|
|
|
|
static UInt64 sTotalBytesReceived = 0;
|
|
static UInt64 sTotalPacketsReceived = 0;
|
|
static UInt64 sTotalPacketsLost = 0;
|
|
static UInt64 sTotalOutOfOrder = 0;
|
|
static UInt64 sTotalOutOfBound = 0;
|
|
static UInt64 sTotalDuplicates = 0;
|
|
static UInt64 sTotalNumAcks = 0;
|
|
static UInt64 sTotalMalformed = 0;
|
|
static UInt64 sTotalLatePackets;
|
|
static UInt64 sTotalBufferOverflowedPackets;
|
|
static Bool16 sEnable3GPP = false;
|
|
|
|
|
|
int main(int argc, char *argv[]);
|
|
|
|
//
|
|
// Helper functions
|
|
char* GetClientTypeDescription(ClientSession::ClientType inClientType);
|
|
void DoDNSLookup(SVector<char *> &theURLlist, SVector<UInt32> &ioIPAddrs);
|
|
void RecordClientInfoBeforeDeath(ClientSession* inSession);
|
|
char* GetDeathReasonDescription(UInt32 inDeathReason);
|
|
char* GetPayloadDescription(QTSS_RTPPayloadType inPayload);
|
|
void CheckForStreamingLoadToolDotMov(SVector<UInt32> &ioIPAddrArray, SVector<char *> &theURLlist, UInt16 inPort, SVector<char *> &userList, SVector<char *> &passwordList, UInt32 verboseLevel);
|
|
UInt32 CalcStartTime(Bool16 inRandomThumb, UInt32 inMovieLength);
|
|
extern char* optarg;
|
|
|
|
#ifndef __Win32__
|
|
void sigcatcher(int sig, int /*sinfo*/, struct sigcontext* /*sctxt*/);
|
|
|
|
void sigcatcher(int sig, int /*sinfo*/, struct sigcontext* /*sctxt*/)
|
|
{
|
|
//printf("sigcatcher =%d\n", sig);
|
|
|
|
if ((sig == SIGINT) || (sig == SIGTERM))
|
|
{ // TheTime check and sQuitNow flag tests are needed test with Linux and OSX.
|
|
if (sGotSigInt && (OS::Milliseconds() > sSigIntTime + 500) )
|
|
{ if (!sQuitNow)// print only once
|
|
printf("Force quitting\n");
|
|
sQuitNow = true;
|
|
}
|
|
else
|
|
{
|
|
sSigIntTime = OS::Milliseconds();
|
|
sGotSigInt = true;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
#ifndef __Win32__
|
|
struct sigaction act;
|
|
|
|
#if defined(sun) || defined(i386) || defined (__MacOSX__) || defined(__powerpc__) || defined (__osf__) || defined (__sgi_cc__) || defined (__hpux__) || defined(__linux__)
|
|
sigemptyset(&act.sa_mask);
|
|
act.sa_flags = 0;
|
|
act.sa_handler = (void(*)(int))&sigcatcher;
|
|
#elif defined(__sgi__)
|
|
sigemptyset(&act.sa_mask);
|
|
act.sa_flags = 0;
|
|
act.sa_handler = (void(*)(...))&sigcatcher;
|
|
#else
|
|
act.sa_mask = 0;
|
|
act.sa_flags = 0;
|
|
act.sa_handler = (void(*)(...))&sigcatcher;
|
|
#endif
|
|
(void)::sigaction(SIGPIPE, &act, NULL);
|
|
(void)::sigaction(SIGINT, &act, NULL);
|
|
(void)::sigaction(SIGTERM, &act, NULL);
|
|
|
|
#if __MacOSX__ || __solaris__
|
|
//grow our pool of file descriptors to the max!
|
|
struct rlimit rl;
|
|
|
|
rl.rlim_cur = Socket::kMaxNumSockets;
|
|
rl.rlim_max = Socket::kMaxNumSockets;
|
|
|
|
setrlimit (RLIMIT_NOFILE, &rl);
|
|
#endif
|
|
#else
|
|
//
|
|
// Start Win32 DLLs
|
|
WORD wsVersion = MAKEWORD(1, 1);
|
|
WSADATA wsData;
|
|
(void)::WSAStartup(wsVersion, &wsData);
|
|
#endif
|
|
|
|
#ifdef __Win32__
|
|
char* configFilePath = "streamingloadtool.cfg";
|
|
#else
|
|
char* configFilePath = "streamingloadtool.conf";
|
|
#endif
|
|
Bool16 configFilePathIsSpecified = false;
|
|
Bool16 dropPost = false;
|
|
ClientSession::ClientType theClientType = ClientSession::kRTSPUDPClientType;
|
|
Bool16 theClientTypeIsSpecified = false;
|
|
UInt16 thePort = 554;
|
|
Bool16 thePortIsSpecified = false;
|
|
UInt32 theMovieLength = 60;
|
|
Bool16 theMovieLengthIsSpecified = false;
|
|
Bool16 runForever = false;
|
|
UInt32 theHTTPCookie = 1;
|
|
Bool16 shouldLog = false;
|
|
char* logPath = "streamingloadtool.log";
|
|
UInt32 proxyIP = 0;
|
|
Bool16 appendJunk = false;
|
|
UInt32 theReadInterval = 50;
|
|
UInt32 sockRcvBuf = 32768;
|
|
Float32 lateTolerance = 0;
|
|
char* rtpMetaInfo = NULL;
|
|
Float32 speed = 1;
|
|
UInt32 verboseLevel = 0;
|
|
char* packetPlayHeader = NULL;
|
|
UInt32 overbufferwindowInK = 0;
|
|
Bool16 randomThumb = false;
|
|
Bool16 sendOptions = false;
|
|
Bool16 requestRandomData = false;
|
|
SInt32 randomDataSize = 0;
|
|
UInt32 rtcpInterval = 5000;
|
|
UInt32 bandwidth = 0;
|
|
UInt32 guarenteedBitRate = 0;
|
|
UInt32 maxBitRate = 0;
|
|
UInt32 maxTransferDelay = 0;
|
|
Bool16 enableForcePlayoutDelay = false;
|
|
UInt32 playoutDelay = 0;
|
|
UInt32 bufferSpace = 100000;
|
|
UInt32 delayTime = 10000;
|
|
Float32 startDelayFrac = 0.5;
|
|
//
|
|
// Set up our User Agent string for the RTSP client
|
|
char theUserAgentStr[128];
|
|
::sprintf(theUserAgentStr, "StreamingLoadTool-%s",kVersionString);
|
|
RTSPClient::SetUserAgentStr(theUserAgentStr);
|
|
|
|
SVector<char *>userList;
|
|
SVector<char *>passwordList;
|
|
SVector<char *> theURLlist;
|
|
|
|
char* controlID = NULL;
|
|
char ch = 0;
|
|
//
|
|
// Read our command line options
|
|
while( (ch = getopt(argc, argv, "vf:c:i:V:u:t:p:n:l:s:")) != -1 )
|
|
{
|
|
switch( ch )
|
|
{
|
|
case '?':
|
|
case 'v':
|
|
printf("StreamingLoadTool %s, built on %s, %s.\n", kVersionString, __DATE__ , __TIME__);
|
|
printf("Usage: StreamingLoadTool [-u theURL] [-t transport] [-n port] [-l length] [-i urlid] [-f path] [-c #] [-V #]\n");
|
|
printf("-v: Print this message\n");
|
|
printf("-f: Config file to use. Defaults to streamingloadtool.conf if the URL (-u) is not specified.\n");
|
|
printf("-c: HTTP cookie to use. Overrides what is in config file\n");
|
|
printf("-V: debug verbose level\n");
|
|
printf("-i: RTSP stream URL id (i.e. trackID or streamID etc.). Default is -i trackID\n");
|
|
printf("-u: the URL (format: rtsp://etc/movie.mov).\n");
|
|
printf("-t: the transport type: udp, reliableudp, tcp, or http.\n");
|
|
printf("-p: the port.\n");
|
|
printf("-n: the number of clients.\n");
|
|
printf("-l: the movie duration.\n");
|
|
printf("-s: the user and password format: (user:password).\n");
|
|
|
|
exit(0);
|
|
break;
|
|
case 'f':
|
|
configFilePathIsSpecified = true;
|
|
configFilePath = optarg;
|
|
break;
|
|
case 'i':
|
|
controlID = optarg;
|
|
break;
|
|
case 'c':
|
|
theHTTPCookie = atoi(optarg);
|
|
break;
|
|
case 'V':
|
|
verboseLevel = atoi(optarg);
|
|
break;
|
|
case 'u':
|
|
theURLlist.push_back(optarg);
|
|
break;
|
|
case 't':
|
|
theClientTypeIsSpecified = true;
|
|
if (::strcmp("http", optarg) == 0)
|
|
theClientType = ClientSession::kRTSPHTTPClientType;
|
|
else if (::strcmp("reliableudp", optarg) == 0)
|
|
theClientType = ClientSession::kRTSPReliableUDPClientType;
|
|
else if (::strcmp("tcp", optarg) == 0)
|
|
theClientType = ClientSession::kRTSPTCPClientType;
|
|
else if (::strcmp("udp", optarg) == 0)
|
|
theClientType = ClientSession::kRTSPUDPClientType;
|
|
else if (::strcmp("3gudp", optarg) == 0)
|
|
{ theClientType = ClientSession::kRTSPUDPClientType;
|
|
sEnable3GPP = true;
|
|
}
|
|
else
|
|
{
|
|
printf("Invalid transport type: %s\n", optarg);
|
|
exit(0);
|
|
}
|
|
break;
|
|
case 'p':
|
|
thePortIsSpecified = true;
|
|
thePort = atoi(optarg);
|
|
break;
|
|
case 'n':
|
|
sNumClientsIsSpecified = true;
|
|
sNumClients = atoi(optarg);
|
|
break;
|
|
case 'l':
|
|
theMovieLengthIsSpecified = true;
|
|
theMovieLength = atoi(optarg);
|
|
break;
|
|
case 's' :
|
|
{
|
|
char *str = ::strdup(optarg);
|
|
char *user = str;
|
|
char *password = NULL;
|
|
char *colonChar = ::strchr(str, ':');
|
|
printf("optarg=%s\n",str);
|
|
if (colonChar != NULL)
|
|
{
|
|
*colonChar = '\0';
|
|
password = colonChar + 1;
|
|
}
|
|
if ((user != NULL) && (password != NULL) )
|
|
{
|
|
userList.push_back(user);
|
|
passwordList.push_back(password);
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
FilePrefsSource theFileParser(true);
|
|
// Get settings from the file
|
|
Bool16 configFileError = theFileParser.InitFromConfigFile(configFilePath);
|
|
if (configFileError && theURLlist.empty())
|
|
{
|
|
printf("Couldn't find StreamingLoadTool config file at: %s\n", configFilePath);
|
|
::exit(-1);
|
|
}
|
|
else if (!configFileError)
|
|
{
|
|
for (UInt32 x = 0; true; x++)
|
|
{
|
|
char* theValue = theFileParser.GetValueAtIndex(x);
|
|
char* theKey = theFileParser.GetKeyAtIndex(x);
|
|
|
|
if (theKey == NULL)
|
|
break;
|
|
|
|
if (theValue != NULL)
|
|
{
|
|
if (::strcmp("clienttype", theKey) == 0 && !theClientTypeIsSpecified)
|
|
{
|
|
if (::strcmp("http", theValue) == 0)
|
|
theClientType = ClientSession::kRTSPHTTPClientType;
|
|
else if (::strcmp("reliableudp", theValue) == 0)
|
|
theClientType = ClientSession::kRTSPReliableUDPClientType;
|
|
else if (::strcmp("tcp", theValue) == 0)
|
|
theClientType = ClientSession::kRTSPTCPClientType;
|
|
else if (::strcmp("udp", theValue) == 0)
|
|
theClientType = ClientSession::kRTSPUDPClientType;
|
|
else if (::strcmp("3gudp", theValue) == 0)
|
|
{ theClientType = ClientSession::kRTSPUDPClientType;
|
|
sEnable3GPP = true;
|
|
}
|
|
else
|
|
{
|
|
printf("Invalid transport type: %s\n", theValue);
|
|
exit(0);
|
|
}
|
|
}
|
|
else if (::strcmp("player", theKey) == 0)
|
|
{
|
|
if (theValue[0] != 0)
|
|
{
|
|
::strncpy(theUserAgentStr, theValue, sizeof(theUserAgentStr));
|
|
theUserAgentStr[sizeof(theUserAgentStr) -1] = 0;
|
|
RTSPClient::SetUserAgentStr(theUserAgentStr);
|
|
}
|
|
}
|
|
else if (::strcmp("droppost", theKey) == 0)
|
|
{
|
|
if (::strcmp("yes", theValue) == 0)
|
|
dropPost = true;
|
|
}
|
|
else if (::strcmp("concurrentclients", theKey) == 0 && !sNumClientsIsSpecified)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_"", &sNumClients);
|
|
}
|
|
else if (::strcmp("port", theKey) == 0 && !thePortIsSpecified)
|
|
{
|
|
UInt32 tempPort = 0;
|
|
::sscanf(theValue, "%"_U32BITARG_"", &tempPort);
|
|
thePort = (UInt16)tempPort;
|
|
}
|
|
else if (::strcmp("movielength", theKey) == 0 && !theMovieLengthIsSpecified)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_"", &theMovieLength);
|
|
}
|
|
else if (::strcmp("runforever", theKey) == 0)
|
|
{
|
|
if (::strcmp("yes", theValue) == 0)
|
|
runForever = true;
|
|
}
|
|
else if (::strcmp("shouldlog", theKey) == 0)
|
|
{
|
|
if (::strcmp("yes", theValue) == 0)
|
|
shouldLog = true;
|
|
}
|
|
else if (::strcmp("appendjunk", theKey) == 0)
|
|
{
|
|
if (::strcmp("yes", theValue) == 0)
|
|
appendJunk = true;
|
|
}
|
|
else if (::strcmp("logpath", theKey) == 0)
|
|
{
|
|
logPath = theValue;
|
|
}
|
|
else if (::strcmp("proxyip", theKey) == 0)
|
|
{
|
|
proxyIP = SocketUtils::ConvertStringToAddr(theValue);
|
|
}
|
|
else if (::strcmp("clientwindow", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_"", &sockRcvBuf);
|
|
}
|
|
else if (::strcmp("httpcookie", theKey) == 0)
|
|
{
|
|
if (theHTTPCookie == 1)
|
|
{
|
|
// Ignore if set by command line
|
|
::sscanf(theValue, "%"_U32BITARG_"", &theHTTPCookie);
|
|
theHTTPCookie *= 1000000;
|
|
}
|
|
}
|
|
else if (::strcmp("readinterval", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_"", &theReadInterval);
|
|
}
|
|
else if (::strcmp("latetolerance", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%f", &lateTolerance);
|
|
}
|
|
else if (::strcmp("rtpmetainfo", theKey) == 0)
|
|
{
|
|
if (::strlen(theValue) > 0)
|
|
rtpMetaInfo = theValue;
|
|
}
|
|
else if (::strcmp("speed", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%f", &speed);
|
|
}
|
|
else if (::strcmp("packetplayheader", theKey) == 0)
|
|
{
|
|
if (::strlen(theValue) > 0)
|
|
packetPlayHeader = theValue;
|
|
}
|
|
else if (::strcmp("overbufferwindowsize", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_"", &overbufferwindowInK);
|
|
}
|
|
else if (::strcmp("randomthumb", theKey) == 0)
|
|
{
|
|
if (::strcmp("yes", theValue) == 0)
|
|
randomThumb = true;
|
|
}
|
|
|
|
else if (::strcmp("sendoptions", theKey) == 0)
|
|
{
|
|
if (::strcmp("yes", theValue) == 0)
|
|
sendOptions = true;
|
|
}
|
|
else if (::strcmp("requestrandomdata", theKey) == 0)
|
|
{
|
|
if (::strcmp("yes", theValue) == 0)
|
|
requestRandomData = true;
|
|
}
|
|
else if (::strcmp("randomdatasize", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_S32BITARG_"", &randomDataSize);
|
|
}
|
|
else if (::strcmp("rtcpinterval", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_S32BITARG_"", &rtcpInterval);
|
|
}
|
|
else if (::strcmp("url", theKey) == 0)
|
|
{
|
|
theURLlist.push_back(theValue);
|
|
}
|
|
else if (::strcmp("user", theKey) == 0)
|
|
{
|
|
char *str = ::strdup(theValue);
|
|
char *user = str;
|
|
char *password = NULL;
|
|
char *colonChar = ::strchr(str, ':');
|
|
if (colonChar != NULL)
|
|
{
|
|
*colonChar = '\0';
|
|
password = colonChar + 1;
|
|
}
|
|
userList.push_back(user);
|
|
passwordList.push_back(password);
|
|
}
|
|
else if (::strcmp("bandwidth", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_, &bandwidth);
|
|
}
|
|
else if (::strcmp("enable3GPP", theKey) == 0)
|
|
{
|
|
if (::strcmp("yes", theValue) == 0)
|
|
sEnable3GPP = true;
|
|
}
|
|
else if (::strcmp("GBW", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_, &guarenteedBitRate);
|
|
}
|
|
else if (::strcmp("MBW", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_, &maxBitRate);
|
|
}
|
|
else if (::strcmp("MTD", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_, &maxTransferDelay);
|
|
}
|
|
else if (::strcmp("enableForcePlayoutDelay", theKey) == 0)
|
|
{
|
|
if (::strcmp("yes", theValue) == 0)
|
|
enableForcePlayoutDelay = true;
|
|
}
|
|
else if (::strcmp("playoutDelay", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_, &playoutDelay);
|
|
}
|
|
else if (::strcmp("buffer", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_"", &bufferSpace);
|
|
}
|
|
else if (::strcmp("delay", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%"_U32BITARG_"", &delayTime);
|
|
}
|
|
else if (::strcmp("startdelay", theKey) == 0)
|
|
{
|
|
::sscanf(theValue, "%f", &startDelayFrac);
|
|
if(startDelayFrac < 0)
|
|
startDelayFrac = -1.0;
|
|
}
|
|
else
|
|
printf("Found bad directive in StreamingLoadTool config file: %s\n", theKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(theURLlist.empty())
|
|
{
|
|
printf("Didn't find any URLs in StreamingLoadTool config file.\n");
|
|
exit(-1);
|
|
}
|
|
|
|
//
|
|
// Figure out what type of clients we are going to run
|
|
if ((theClientType == ClientSession::kRTSPHTTPClientType) && dropPost)
|
|
theClientType = ClientSession::kRTSPHTTPDropPostClientType;
|
|
|
|
// Do IP lookups on all the URLs
|
|
SVector<UInt32> theIPAddrArray;
|
|
theIPAddrArray.resize(theURLlist.size(), 0);
|
|
::DoDNSLookup(theURLlist, theIPAddrArray);
|
|
|
|
|
|
// Final setup before running the show
|
|
OS::Initialize();
|
|
OSThread::Initialize();
|
|
OSMemory::SetMemoryError(ENOMEM);
|
|
Socket::Initialize();
|
|
SocketUtils::Initialize();
|
|
|
|
#if !MACOSXEVENTQUEUE
|
|
::select_startevents();//initialize the select() implementation of the event queue
|
|
#endif
|
|
TaskThreadPool::AddThreads(1);
|
|
TimeoutTask::Initialize();
|
|
Socket::StartThread();
|
|
|
|
//Check for the existance of a file streamingloadtool.mov before proceeding
|
|
::CheckForStreamingLoadToolDotMov(theIPAddrArray, theURLlist, thePort, userList, passwordList, verboseLevel);
|
|
|
|
// If user specified a proxy to connect through, use that IP address, not the IP address in the URL.
|
|
if (proxyIP != 0)
|
|
{
|
|
for (UInt32 ipAddrCounter = 0; ipAddrCounter < theURLlist.size(); ipAddrCounter++)
|
|
theIPAddrArray[ipAddrCounter] = proxyIP;
|
|
}
|
|
|
|
// Open the log if we are logging
|
|
if (shouldLog)
|
|
sLog = ::fopen(logPath, "w");
|
|
|
|
sClientSessionArray = NEW ClientSession*[sNumClients];
|
|
|
|
UInt32 theCurURLIndex = 0;
|
|
UInt32 theCurUserIndex = 0;
|
|
for (UInt32 clientCount = 0; clientCount < sNumClients; clientCount++, theCurURLIndex = (theCurURLIndex + 1) % theURLlist.size(), theCurUserIndex++)
|
|
{
|
|
while(theIPAddrArray[theCurURLIndex] == 0)
|
|
theCurURLIndex = (theCurURLIndex + 1) % theURLlist.size();
|
|
if (theCurUserIndex == userList.size())
|
|
theCurUserIndex = 0;
|
|
|
|
sClientSessionArray[clientCount] = NEW ClientSession( theIPAddrArray[theCurURLIndex],
|
|
thePort,
|
|
theURLlist[theCurURLIndex],
|
|
theClientType, // Client type
|
|
theMovieLength, // Movie length
|
|
CalcStartTime(randomThumb, theMovieLength), // Movie start time
|
|
rtcpInterval,
|
|
0, // Options interval
|
|
theHTTPCookie++, // HTTP cookie
|
|
appendJunk,
|
|
theReadInterval, // Interval between data reads
|
|
sockRcvBuf, // socket recv buffer size
|
|
lateTolerance, // late tolerance
|
|
rtpMetaInfo,
|
|
speed,
|
|
verboseLevel,
|
|
packetPlayHeader,
|
|
overbufferwindowInK,
|
|
sendOptions, // send options request before the Describe
|
|
requestRandomData, // request random data in the options request
|
|
randomDataSize, // size of the random data to request
|
|
sEnable3GPP,
|
|
guarenteedBitRate,
|
|
maxBitRate,
|
|
maxTransferDelay,
|
|
enableForcePlayoutDelay,
|
|
playoutDelay,
|
|
bandwidth,
|
|
bufferSpace,
|
|
delayTime,
|
|
static_cast<UInt32>(startDelayFrac * delayTime),
|
|
controlID,
|
|
userList.empty() ? NULL : userList[theCurUserIndex],
|
|
passwordList.empty() ? NULL : passwordList[theCurUserIndex]);
|
|
|
|
#if STREAMINGLOADTOOL_DEBUG
|
|
printf("Creating a ClientSession for URL: %s\n", theURLlist[theCurURLIndex]);
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Now, all we have to do is loop, destroying and re-creating ClientSession objects when they die.
|
|
// Occassionally, lets print out status to show what is currently going on
|
|
Bool16 isStillActive = false;
|
|
UInt32 loopCount = 0;
|
|
do
|
|
{
|
|
if (sGotSigInt)
|
|
break;
|
|
|
|
OSThread::Sleep(1000);
|
|
|
|
isStillActive = false;
|
|
for (UInt32 y = 0; y < sNumClients; y++)
|
|
{
|
|
if (sClientSessionArray == NULL)
|
|
continue; //skip over NULL client sessions
|
|
|
|
if ((loopCount & 7) == 0) // Fancy way of mod'ing by 8, so the following will execute every 8 seconds
|
|
{
|
|
// if the server has not sent me any packets in the last 8 seconds, and the at least 10 seconds has elapsed since the RTSP play, then timeout
|
|
if ( (sClientSessionArray[y] != NULL && sClientSessionArray[y]->GetSessionPacketsReceived() == 0 && sClientSessionArray[y]->GetTotalPlayTimeInMsec() > 10000) )
|
|
{
|
|
sClientSessionArray[y]->Signal(Task::kTimeoutEvent); // Tell it to destroy itself if the Client is idle for a while
|
|
}
|
|
}
|
|
|
|
if ( (sClientSessionArray[y] != NULL && sClientSessionArray[y]->IsDone()) )
|
|
{
|
|
while(theIPAddrArray[theCurURLIndex] == 0)
|
|
theCurURLIndex = (theCurURLIndex + 1) % theURLlist.size();
|
|
if (theCurUserIndex == userList.size())
|
|
theCurUserIndex = 0;
|
|
|
|
::RecordClientInfoBeforeDeath(sClientSessionArray[y]);
|
|
sClientSessionArray[y]->Signal(Task::kKillEvent); // Tell it to destroy itself
|
|
sClientSessionArray[y] = NULL;
|
|
if (runForever)
|
|
{
|
|
isStillActive = true;
|
|
// If there is a new URL to fetch, kill this client and restart
|
|
sClientSessionArray[y] = NEW ClientSession( theIPAddrArray[theCurURLIndex], // IP addr
|
|
thePort, // IP port
|
|
theURLlist[theCurURLIndex],
|
|
theClientType, // Client type
|
|
theMovieLength, // Movie length
|
|
CalcStartTime(randomThumb, theMovieLength), // Movie start time
|
|
rtcpInterval,
|
|
0, // Options interval
|
|
theHTTPCookie++, // HTTP cookie
|
|
appendJunk,
|
|
theReadInterval, // Interval between data reads
|
|
sockRcvBuf, // socket recv buffer size
|
|
lateTolerance, // late tolerance
|
|
rtpMetaInfo,
|
|
speed,
|
|
verboseLevel,
|
|
packetPlayHeader,
|
|
overbufferwindowInK,
|
|
sendOptions, // send options request before the Describe
|
|
requestRandomData, // request random data in the options request
|
|
randomDataSize, // size of the random data to request
|
|
sEnable3GPP,
|
|
guarenteedBitRate,
|
|
maxBitRate,
|
|
maxTransferDelay,
|
|
enableForcePlayoutDelay,
|
|
playoutDelay,
|
|
bandwidth,
|
|
bufferSpace,
|
|
delayTime,
|
|
(startDelayFrac < 0) ? static_cast<UInt32>(kUInt32_Max) : static_cast<UInt32>(startDelayFrac * delayTime),
|
|
controlID,
|
|
userList.empty() ? NULL : userList[theCurUserIndex],
|
|
passwordList.empty() ? NULL : passwordList[theCurUserIndex]);
|
|
|
|
#if STREAMINGLOADTOOL_DEBUG
|
|
printf("Creating a ClientSession for URL: %s\n", theURLlist[theCurURLIndex]);
|
|
#endif
|
|
theCurURLIndex = (theCurURLIndex + 1) % theURLlist.size();
|
|
theCurUserIndex++;
|
|
}
|
|
}
|
|
else if (sClientSessionArray[y] != NULL) //still running
|
|
isStillActive = true;
|
|
}
|
|
|
|
if ((loopCount & 7) == 0) // Fancy way of mod'ing by 8, so the following will execute every 8 seconds
|
|
{
|
|
|
|
if ((loopCount & 127) == 0) // Fancy way of dividing by 127,
|
|
{
|
|
// Occassionally give the user lots of info
|
|
printf("StreamingLoadTool test in progress.\n");
|
|
printf("Config file: %s. Client type: %s. Num clients: %"_U32BITARG_".\n",
|
|
configFilePath, GetClientTypeDescription(theClientType), sNumClients);
|
|
printf("Movie length: %"_U32BITARG_". Run forever: %d. HTTP cookie: %"_U32BITARG_". Port: %d\n",
|
|
theMovieLength, runForever, theHTTPCookie, thePort);
|
|
if (shouldLog)
|
|
printf("Writing StreamingLoadTool log at: %s\n", logPath);
|
|
printf("Active Playing Attempts Success Errors Failed Bitrate\n");
|
|
}
|
|
|
|
UInt32 addBytes = ClientSession::GetConnectionPacketsReceived() * PACKETADDSIZE;
|
|
Float32 bitsReceived = (Float32) (ClientSession::GetConnectionBytesReceived() + addBytes) / 1000;
|
|
|
|
bitsReceived += .5;
|
|
|
|
printf("%5lu %6lu %8lu %6lu %6lu %6lu %9.0fk\n",
|
|
ClientSession:: GetActiveConnections (),
|
|
ClientSession:: GetPlayingConnections (),
|
|
ClientSession:: GetConnectionAttempts (),
|
|
sSuccessfulConnections,
|
|
sConnectionsThatErrored,
|
|
sFailedConnections,
|
|
bitsReceived// depends on 8 second update for bits per second
|
|
);
|
|
|
|
}
|
|
loopCount++;
|
|
|
|
} while (isStillActive == true);
|
|
|
|
if (sGotSigInt)
|
|
{
|
|
//
|
|
// If someone hits Ctrl-C, force all the clients to wrap it up
|
|
printf("Sending TEARDOWNs.\n");
|
|
|
|
//
|
|
// Tell all the clients to wrap it up.
|
|
for (UInt32 clientCount = 0; clientCount < sNumClients; clientCount++)
|
|
{
|
|
if (sClientSessionArray == NULL)
|
|
continue; //skip over NULL client sessions
|
|
if (sClientSessionArray[clientCount] != NULL)
|
|
sClientSessionArray[clientCount]->Signal(ClientSession::kTeardownEvent);
|
|
}
|
|
//
|
|
// Wait for the clients to complete
|
|
Bool16 isDone = false;
|
|
while (!isDone && !sQuitNow)
|
|
{
|
|
//wait until all the clients are done doing teardown
|
|
OSThread::Sleep(1000);
|
|
isDone = true;
|
|
for (UInt32 cc2 = 0; cc2 < sNumClients; cc2++)
|
|
{
|
|
if (sClientSessionArray == NULL)
|
|
continue; //skip over NULL client sessions
|
|
|
|
if (sClientSessionArray[cc2] == NULL)
|
|
continue;
|
|
|
|
if (!sClientSessionArray[cc2]->IsDone())
|
|
isDone = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We're done... now go through and delete the last sessions(not really)
|
|
for (UInt32 z = 0; z < sNumClients; z++)
|
|
{
|
|
if (sClientSessionArray == NULL)
|
|
continue; //skip over NULL client sessions
|
|
|
|
if (sClientSessionArray[z] == NULL)
|
|
continue;
|
|
|
|
::RecordClientInfoBeforeDeath(sClientSessionArray[z]);
|
|
}
|
|
|
|
if (sLog != NULL)
|
|
::fclose(sLog);
|
|
|
|
printf("%5lu %6lu %8lu %6lu %6lu %6lu %9.0fk\n",
|
|
ClientSession:: GetActiveConnections (),
|
|
ClientSession:: GetPlayingConnections (),
|
|
ClientSession:: GetConnectionAttempts (),
|
|
sSuccessfulConnections,
|
|
sConnectionsThatErrored,
|
|
sFailedConnections,
|
|
0.0// depends on 8 second update for bits per second
|
|
);
|
|
|
|
printf("StreamingLoadTool test complete. Total number of connections: %"_U32BITARG_".\n", ClientSession:: GetConnectionAttempts ());
|
|
printf(
|
|
"Total bytes received: %"_U64BITARG_
|
|
". Total packets received: %"_U64BITARG_
|
|
". Total out of order packets: %"_U64BITARG_
|
|
". Total out of bound packets: %"_U64BITARG_
|
|
". Total ACKs sent: %"_U64BITARG_
|
|
". Total malformed packets: %"_U64BITARG_,
|
|
sTotalBytesReceived,
|
|
sTotalPacketsReceived,
|
|
sTotalOutOfOrder,
|
|
sTotalOutOfBound,
|
|
sTotalNumAcks,
|
|
sTotalMalformed
|
|
);
|
|
if (sEnable3GPP)
|
|
{
|
|
printf(
|
|
". Total 3g packets lost: %"_U64BITARG_
|
|
". Total 3g duplicate packets: %"_U64BITARG_
|
|
". Total 3g late packets: %"_U64BITARG_
|
|
". Total 3g buffer-overflowed packets: %"_U64BITARG_,
|
|
sTotalPacketsLost,
|
|
sTotalDuplicates,
|
|
sTotalLatePackets,
|
|
sTotalBufferOverflowedPackets
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
printf(".\n");
|
|
|
|
}
|
|
|
|
UInt32 CalcStartTime(Bool16 inRandomThumb, UInt32 inMovieLength)
|
|
{
|
|
UInt32 theMovieLength = inMovieLength;
|
|
if (theMovieLength > 1)
|
|
theMovieLength--;
|
|
|
|
if (inRandomThumb)
|
|
return ::rand() % theMovieLength;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void CheckForStreamingLoadToolPermission(UInt32* inIPAddrArray, UInt32 inNumURLs, UInt16 inPort)
|
|
{
|
|
//Eventually check for the existance of a specially formatted sdp file (assuming the server blindly returns sdps)
|
|
}
|
|
|
|
//Currently will only authenticate with the FIRST username/password if provided
|
|
void CheckForStreamingLoadToolDotMov(SVector<UInt32> &ioIPAddrArray, SVector<char *> &theURLlist, UInt16 inPort, SVector<char *> &userList, SVector<char *> &passwordList, UInt32 verboseLevel)
|
|
{
|
|
Assert(ioIPAddrArray.size() == theURLlist.size());
|
|
printf("Checking for 'streamingloadtool.mov' on the target servers\n");
|
|
|
|
OSArrayObjectDeleter<UInt32> holder = NEW UInt32[theURLlist.size() + 1];
|
|
UInt32* uniqueIPAddrs = holder.GetObject();
|
|
::memset(uniqueIPAddrs, 0, sizeof(UInt32) * (theURLlist.size() + 1));
|
|
|
|
|
|
for (UInt32 count = 0; count < theURLlist.size(); count++)
|
|
{
|
|
if (ioIPAddrArray[count] == 0) //skip over one's that failed DNS
|
|
continue;
|
|
|
|
//check for duplicates
|
|
/*
|
|
Bool16 dup = false;
|
|
for (UInt32 x = 0; uniqueIPAddrs[x] != 0; x++)
|
|
{
|
|
if (uniqueIPAddrs[x] == ioIPAddrArray[count])
|
|
{
|
|
dup = true;
|
|
break;
|
|
}
|
|
}
|
|
if (dup)
|
|
continue;
|
|
|
|
// For tracking dups.
|
|
uniqueIPAddrs[count] = ioIPAddrArray[count];
|
|
*/
|
|
|
|
|
|
// Format the URL: rtsp://xx.xx.xx.xx/streamingloadtool.mov
|
|
char theAddrBuf[50];
|
|
StrPtrLen theAddrBufPtr(theAddrBuf, 50);
|
|
struct in_addr theAddr;
|
|
theAddr.s_addr = htonl(ioIPAddrArray[count]);
|
|
|
|
SocketUtils::ConvertAddrToString(theAddr, &theAddrBufPtr);
|
|
|
|
char theURLBuf[100];
|
|
StringFormatter theFormatter(theURLBuf, 100);
|
|
|
|
theFormatter.Put("rtsp://");
|
|
theFormatter.Put(theAddrBufPtr);
|
|
theFormatter.Put("/streamingloadtool.mov");
|
|
theFormatter.PutTerminator();
|
|
|
|
StrPtrLenDel theURL(theFormatter.GetAsCString());
|
|
|
|
// Make an RTSP client. We'll send a DESCRIBE to the server to check for this sucker
|
|
TCPClientSocket theSocket = TCPClientSocket(0); //blocking
|
|
|
|
// tell the client this is the URL to use
|
|
theSocket.Set(ioIPAddrArray[count], inPort);
|
|
|
|
RTSPClient theClient = RTSPClient(&theSocket);
|
|
theClient.SetVerboseLevel(verboseLevel);
|
|
|
|
if(userList.size() > 0)
|
|
{
|
|
theClient.SetName(userList.back());
|
|
theClient.SetPassword(passwordList.back());
|
|
}
|
|
theClient.Set(theURL);
|
|
|
|
//
|
|
// Send the DESCRIBE! Whoo hoo!
|
|
OS_Error theErr = theClient.SendDescribe();
|
|
|
|
while (theErr == EINPROGRESS || theErr == EAGAIN)
|
|
theErr = theClient.SendDescribe();
|
|
if (theErr != OS_NoErr)
|
|
{
|
|
printf("##WARNING: Error connecting to %s.\n\n", theURLlist[count]);
|
|
ioIPAddrArray[count] = 0;
|
|
continue;
|
|
}
|
|
|
|
if (theClient.GetStatus() != 200)
|
|
{
|
|
printf("##WARNING: Cannot access %s\n\n", theURL.Ptr);
|
|
ioIPAddrArray[count] = 0;
|
|
}
|
|
theClient.SendTeardown();
|
|
}
|
|
|
|
int addrCount = 0;
|
|
for (UInt32 x = 0; x < theURLlist.size(); x++)
|
|
{
|
|
if ( 0 != ioIPAddrArray[x])
|
|
addrCount++ ;
|
|
}
|
|
if (addrCount == 0)
|
|
{ printf("No valid destinations\n");
|
|
exit (-1);
|
|
}
|
|
printf("Done checking for 'streamingloadtool.mov' on all servers -- %i valid URL's\n", addrCount);
|
|
}
|
|
|
|
|
|
void DoDNSLookup(SVector<char *> &theURLlist, SVector<UInt32> &ioIPAddrs)
|
|
{
|
|
Assert(theURLlist.size() == ioIPAddrs.size());
|
|
enum { eDNSNameSize = 128 };
|
|
char theDNSName[eDNSNameSize + 1];
|
|
|
|
for (UInt32 x = 0; x < theURLlist.size(); x++)
|
|
{
|
|
// First extract the DNS name from this URL as a c-string
|
|
StrPtrLen theURL = StrPtrLen(theURLlist[x]);
|
|
StringParser theURLParser(&theURL);
|
|
StrPtrLen theDNSNamePtr;
|
|
|
|
theURLParser.ConsumeLength(NULL, 7); // skip over rtsp://
|
|
theURLParser.ConsumeUntil(&theDNSNamePtr, '/'); // grab the DNS name
|
|
StringParser theDNSParser(&theDNSNamePtr);
|
|
theDNSParser.ConsumeUntil(&theDNSNamePtr, ':'); // strip off the port number if any
|
|
|
|
|
|
if (theDNSNamePtr.Len > eDNSNameSize)
|
|
{
|
|
theDNSNamePtr.PrintStr("DSN Name Failed Lookup.\n", "\n");
|
|
printf("The DNS name is %"_U32BITARG_" in length and is longer than the allowed %d.\n",theDNSNamePtr.Len, eDNSNameSize);
|
|
return;
|
|
}
|
|
|
|
theDNSName[0] = 0;
|
|
::memcpy(theDNSName, theDNSNamePtr.Ptr, theDNSNamePtr.Len);
|
|
theDNSName[theDNSNamePtr.Len] = 0;
|
|
|
|
|
|
ioIPAddrs[x] = 0;
|
|
|
|
// Now pass that DNS name into gethostbyname.
|
|
struct hostent* theHostent = ::gethostbyname(theDNSName);
|
|
|
|
if (theHostent != NULL)
|
|
ioIPAddrs[x] = ntohl(*(UInt32*)(theHostent->h_addr_list[0]));
|
|
else
|
|
ioIPAddrs[x] = SocketUtils::ConvertStringToAddr(theDNSName);
|
|
|
|
if (ioIPAddrs[x] == 0)
|
|
{
|
|
printf("Couldn't look up host name: %s.\n", theDNSName);
|
|
//exit(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
char* GetClientTypeDescription(ClientSession::ClientType inClientType)
|
|
{
|
|
static char* kUDPString = "RTSP/UDP client";
|
|
static char* kTCPString = "RTSP/TCP client";
|
|
static char* kHTTPString = "RTSP/HTTP client";
|
|
static char* kHTTPDropPostString = "RTSP/HTTP drop post client";
|
|
static char* kReliableUDPString = "RTSP/ReliableUDP client";
|
|
|
|
switch (inClientType)
|
|
{
|
|
case ClientSession::kRTSPUDPClientType:
|
|
return kUDPString;
|
|
case ClientSession::kRTSPTCPClientType:
|
|
return kTCPString;
|
|
case ClientSession::kRTSPHTTPClientType:
|
|
return kHTTPString;
|
|
case ClientSession::kRTSPHTTPDropPostClientType:
|
|
return kHTTPDropPostString;
|
|
case ClientSession::kRTSPReliableUDPClientType:
|
|
return kReliableUDPString;
|
|
}
|
|
Assert(0);
|
|
return NULL;
|
|
}
|
|
|
|
char* GetDeathReasonDescription(UInt32 inDeathReason)
|
|
{
|
|
static char* kDiedNormallyString = "Completed normally";
|
|
static char* kTeardownFailedString = "Failure: Couldn't complete TEARDOWN";
|
|
static char* kRequestFailedString = "Failure: Failed RTSP request";
|
|
static char* kBadSDPString = "Failure: misformatted SDP";
|
|
static char* kSessionTimedoutString = "Failure: Couldn't connect to server(timeout)";
|
|
static char* kConnectionFailedString = "Failure: Server refused connection";
|
|
static char* kDiedWhilePlayingString = "Failure: Disconnected while playing";
|
|
|
|
switch (inDeathReason)
|
|
{
|
|
case ClientSession::kDiedNormally:
|
|
return kDiedNormallyString;
|
|
case ClientSession::kTeardownFailed:
|
|
return kTeardownFailedString;
|
|
case ClientSession::kRequestFailed:
|
|
return kRequestFailedString;
|
|
case ClientSession::kBadSDP:
|
|
return kBadSDPString;
|
|
case ClientSession::kSessionTimedout:
|
|
return kSessionTimedoutString;
|
|
case ClientSession::kConnectionFailed:
|
|
return kConnectionFailedString;
|
|
case ClientSession::kDiedWhilePlaying:
|
|
return kDiedWhilePlayingString;
|
|
}
|
|
Assert(0);
|
|
return NULL;
|
|
}
|
|
|
|
char* GetPayloadDescription(QTSS_RTPPayloadType inPayload)
|
|
{
|
|
static char* kSound = "Sound";
|
|
static char* kVideo = "Video";
|
|
static char* kUnknown = "Unknown";
|
|
|
|
switch (inPayload)
|
|
{
|
|
case qtssVideoPayloadType:
|
|
return kVideo;
|
|
case qtssAudioPayloadType:
|
|
return kSound;
|
|
default:
|
|
return kUnknown;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void RecordClientInfoBeforeDeath(ClientSession* inSession)
|
|
{
|
|
if (inSession->GetReasonForDying() == ClientSession::kRequestFailed)
|
|
sConnectionsThatErrored++;
|
|
else if (inSession->GetReasonForDying() != ClientSession::kDiedNormally)
|
|
sFailedConnections++;
|
|
else
|
|
sSuccessfulConnections++;
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
UInt32 theReason = inSession->GetReasonForDying();
|
|
in_addr theAddr;
|
|
theAddr.s_addr = htonl(inSession->GetSocket()->GetHostAddr());
|
|
char* theAddrStr = ::inet_ntoa(theAddr);
|
|
|
|
//
|
|
// Write a log entry for this client
|
|
if (sLog != NULL)
|
|
::fprintf(sLog, "Client complete. IP addr = %s, URL = %s. Connection status: %s. ",
|
|
theAddrStr,
|
|
inSession->GetClient()->GetURL()->Ptr,
|
|
::GetDeathReasonDescription(theReason));
|
|
|
|
if (theReason == ClientSession::kRequestFailed)
|
|
if (sLog != NULL) ::fprintf(sLog, "Failed request status: %"_U32BITARG_"", inSession->GetRequestStatus());
|
|
|
|
if (sLog != NULL) ::fprintf(sLog, "\n");
|
|
|
|
//
|
|
// If this was a successful connection, log statistics for this connection
|
|
if ((theReason == ClientSession::kDiedNormally) || (theReason == ClientSession::kTeardownFailed) || (theReason == ClientSession::kSessionTimedout))
|
|
{
|
|
|
|
UInt32 bytesReceived = 0;
|
|
for (UInt32 trackCount = 0; trackCount < inSession->GetSDPInfo()->GetNumStreams(); trackCount++)
|
|
{
|
|
if (sLog != NULL) ::fprintf(sLog,
|
|
"Track type: %s. Total packets received: %"_U32BITARG_
|
|
". Total out of order packets: %"_U32BITARG_
|
|
". Total out of bound packets: %"_U32BITARG_
|
|
". Total ACKs sent: %"_U32BITARG_
|
|
". Total malformed packets: %"_U32BITARG_,
|
|
::GetPayloadDescription(inSession->GetTrackType(trackCount)),
|
|
inSession->GetNumPacketsReceived(trackCount),
|
|
inSession->GetNumPacketsOutOfOrder(trackCount),
|
|
inSession->GetNumOutOfBoundPackets(trackCount),
|
|
inSession->GetNumAcks(trackCount),
|
|
inSession->GetNumMalformedPackets(trackCount)
|
|
);
|
|
|
|
|
|
if (sEnable3GPP)
|
|
{
|
|
if (sLog != NULL) ::fprintf(sLog,
|
|
". Total 3g packets lost: %"_U32BITARG_
|
|
". Total 3g duplicate packets: %"_U32BITARG_
|
|
". Total 3g late packets: %"_U32BITARG_
|
|
". Total 3g rate adapt buffer-overflowed packets: %"_U32BITARG_,
|
|
inSession->Get3gNumPacketsLost(trackCount),
|
|
inSession->Get3gNumDuplicates(trackCount),
|
|
inSession->Get3gNumLatePackets(trackCount),
|
|
inSession->Get3gNumBufferOverflowedPackets(trackCount)
|
|
);
|
|
}
|
|
|
|
if (sLog != NULL) ::fprintf(sLog,".\n");
|
|
|
|
|
|
bytesReceived += inSession->GetNumBytesReceived(trackCount);
|
|
bytesReceived += inSession->GetNumPacketsReceived(trackCount) * PACKETADDSIZE;
|
|
|
|
sTotalBytesReceived += inSession->GetNumBytesReceived(trackCount);
|
|
sTotalPacketsReceived += inSession->GetNumPacketsReceived(trackCount);
|
|
sTotalOutOfOrder += inSession->GetNumPacketsOutOfOrder(trackCount);
|
|
sTotalOutOfBound += inSession->GetNumOutOfBoundPackets(trackCount);
|
|
sTotalNumAcks += inSession->GetNumAcks(trackCount);
|
|
sTotalMalformed += inSession->GetNumMalformedPackets(trackCount);
|
|
sTotalPacketsLost += inSession->Get3gNumPacketsLost(trackCount);
|
|
sTotalDuplicates += inSession->Get3gNumDuplicates(trackCount);
|
|
sTotalLatePackets += inSession->Get3gNumLatePackets(trackCount);
|
|
sTotalBufferOverflowedPackets += inSession->Get3gNumBufferOverflowedPackets(trackCount);
|
|
}
|
|
UInt32 duration = (UInt32)(inSession->GetTotalPlayTimeInMsec() / 1000);
|
|
Float32 bitRate = (((Float32)bytesReceived) / ((Float32)duration) * 8) / 1024;
|
|
|
|
|
|
if (sLog != NULL) ::fprintf(sLog, "Play duration in sec: %"_U32BITARG_". Total stream bit rate in Kbits / sec: %f.\n", duration, bitRate);
|
|
}
|
|
|
|
if (sLog != NULL) ::fprintf(sLog, "\n");
|
|
}
|
|
}
|
|
|