366 lines
14 KiB
C++
366 lines
14 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: PlayerSimulator.h
|
|
|
|
Simulates a client; track duplicate/late/missing packets
|
|
*/
|
|
|
|
#ifndef _PLAYERSIMULATOR_H_
|
|
#define _PLAYERSIMULATOR_H_
|
|
|
|
#include "SafeStdLib.h"
|
|
#include "OSHeaders.h"
|
|
#include "OS.h"
|
|
#include "SVector.h"
|
|
|
|
/* Macros for min/max. */
|
|
#ifndef MIN
|
|
#define MIN(a,b) (((a)<(b))?(a):(b))
|
|
#endif
|
|
#ifndef MAX
|
|
#define MAX(a,b) (((a)>(b))?(a):(b))
|
|
#endif
|
|
|
|
class PlayerSimulator
|
|
{
|
|
public:
|
|
PlayerSimulator(UInt32 verboseLevel = 0)
|
|
: fTargetBufferingDelay(0), fStartPlayDelay(0), fLocalStartPlayTime(0), fCurrentMediaTime(0), fVerboseLevel(verboseLevel), fIsPlaying(false)
|
|
{}
|
|
|
|
//call Setup and then SetupTrack per stream to initialize the class. Use a targetBufferingDelay of 0 to start playing immediately
|
|
//The PlayerSimulator can be reused by calling Setup and SetupTrack again
|
|
void Setup(UInt32 numTracks, UInt32 targetBufferingDelay = 0, UInt32 startPlayDelay = 0)
|
|
{
|
|
fTargetBufferingDelay = targetBufferingDelay;
|
|
fStartPlayDelay = startPlayDelay;
|
|
fLocalStartPlayTime = 0;
|
|
fIsPlaying = false;
|
|
fTrackInfo.clear();
|
|
fTrackInfo.resize(numTracks);
|
|
}
|
|
//Use a bufferSize of 0 to have an unlimited buffer size;
|
|
void SetupTrack(UInt32 trackIndex, UInt32 samplingRate, UInt32 bufferSize = 0)
|
|
{
|
|
fTrackInfo[trackIndex].fBufferSize = bufferSize == 0 ? kUInt32_Max : bufferSize;
|
|
fTrackInfo[trackIndex].fSamplingRate = samplingRate;
|
|
}
|
|
|
|
//Please note that PlayerSimulator use track index, not track ID; payloadSize should exclude the RTP header.
|
|
//Returns true if the packet is a duplicate; the sequence number of the RTP packet MUST be in range.
|
|
Bool16 ProcessRTPPacket(UInt32 trackIndex, UInt32 packetLen, UInt32 payloadSize, UInt32 seqNum, UInt32 timeStamp)
|
|
{
|
|
Assert(trackIndex <= fTrackInfo.size());
|
|
TrackInfo &trackInfo = fTrackInfo[trackIndex];
|
|
SVector<PacketInfo> &playBuffer = trackInfo.fPlayBuffer;
|
|
|
|
Bool16 addPacketToBuffer = false;
|
|
Bool16 packetIsDuplicate = false;
|
|
|
|
if (fIsPlaying)
|
|
{
|
|
AdvanceTime();
|
|
if(seqNum < trackInfo.fNextSeqNum)
|
|
{
|
|
//definitely a late packet; try to see if the packet is one of the lost packets
|
|
UInt32 index = trackInfo.fLostPackets.find(seqNum);
|
|
if (index != trackInfo.fLostPackets.size())
|
|
{
|
|
//a late, non-duplicate packet
|
|
trackInfo.fNumLatePackets++;
|
|
trackInfo.fLostPackets.erase(index);
|
|
}
|
|
else //a late-duplicate
|
|
{
|
|
trackInfo.fNumDuplicates++;
|
|
packetIsDuplicate = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//look in the play buffer
|
|
if (playBuffer.empty() || seqNum < playBuffer.front().fSeqNum)
|
|
{
|
|
//the packet has an earlier sequence number than any other packets in the buffer; it may still be late
|
|
if (RTPTime2MediaTime(trackIndex, timeStamp) <= fCurrentMediaTime)
|
|
trackInfo.fNumLatePackets++;
|
|
if (GetFreeBufferSpace(trackIndex) >= packetLen)
|
|
playBuffer.insert(0, PacketInfo(seqNum, timeStamp, packetLen));
|
|
else
|
|
trackInfo.fNumBufferOverflowedPackets++;
|
|
}
|
|
else
|
|
addPacketToBuffer = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (playBuffer.empty())
|
|
{
|
|
//This is the first packet in the stream
|
|
if (GetFreeBufferSpace(trackIndex) >= packetLen)
|
|
playBuffer.push_back(PacketInfo(seqNum, timeStamp, packetLen));
|
|
else
|
|
trackInfo.fNumBufferOverflowedPackets++;
|
|
}
|
|
else
|
|
addPacketToBuffer = true;
|
|
}
|
|
|
|
if (addPacketToBuffer)
|
|
{
|
|
//check the buffer to see if the packet is a duplicate
|
|
UInt32 index = 0;
|
|
for(index = 0; index < playBuffer.size(); ++index)
|
|
{
|
|
if (seqNum == playBuffer[index].fSeqNum)
|
|
{
|
|
//a non-late, duplicate packet
|
|
trackInfo.fNumDuplicates++;
|
|
packetIsDuplicate = true;
|
|
break;
|
|
}
|
|
else if (seqNum < playBuffer[index].fSeqNum) //found where to insert
|
|
break;
|
|
}
|
|
|
|
if(!packetIsDuplicate)
|
|
{
|
|
//a non-late non-duplicate -- may be out of order
|
|
if (GetFreeBufferSpace(trackIndex) >= packetLen)
|
|
playBuffer.insert(index, PacketInfo(seqNum, timeStamp, packetLen));
|
|
else
|
|
trackInfo.fNumBufferOverflowedPackets++;
|
|
}
|
|
}
|
|
|
|
if(!fIsPlaying)
|
|
{
|
|
//is it time to start playing?
|
|
Bool16 haveEnoughBuffer = true;
|
|
for(UInt32 index = 0; index < fTrackInfo.size(); ++index)
|
|
{
|
|
if (fTrackInfo[index].fPlayBuffer.empty() || GetBufferingDelay(index) <= fStartPlayDelay)
|
|
{
|
|
haveEnoughBuffer = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(haveEnoughBuffer) //lets start playing!
|
|
{
|
|
fIsPlaying = true;
|
|
fLocalStartPlayTime = OS::Milliseconds();
|
|
|
|
for(UInt32 index = 0; index < fTrackInfo.size(); ++index)
|
|
{
|
|
//for each track, set the next sequence number and the earliest RTP timestamp
|
|
TrackInfo &curTrack = fTrackInfo[index];
|
|
curTrack.fRTPTimeStampBase = curTrack.fPlayBuffer.front().fTimeStamp;
|
|
curTrack.fNextSeqNum = curTrack.fPlayBuffer.front().fSeqNum;
|
|
|
|
if(fVerboseLevel >= 1)
|
|
qtss_printf("Track %"_U32BITARG_" is now playing; initial seq=%"_U32BITARG_", initial time stamp=%"_U32BITARG_"\n",
|
|
index, curTrack.fNextSeqNum, curTrack.fRTPTimeStampBase);
|
|
}
|
|
AdvanceTime();
|
|
}
|
|
}
|
|
|
|
if(fVerboseLevel >= 2)
|
|
{
|
|
if (fIsPlaying)
|
|
qtss_printf("Processed packet: track=%"_U32BITARG_", len=%"_U32BITARG_", seq=%"_U32BITARG_", time=%"_U32BITARG_"(%"_U32BITARG_"); "
|
|
"bufferingDelay=%"_U32BITARG_", FBS=%"_U32BITARG_", media time=%"_U32BITARG_"\n",
|
|
trackIndex, packetLen, seqNum, timeStamp, RTPTime2MediaTime(trackIndex, timeStamp),
|
|
GetBufferingDelay(trackIndex), GetFreeBufferSpace(trackIndex), fCurrentMediaTime);
|
|
else
|
|
qtss_printf("Processed packet: track=%"_U32BITARG_", len=%"_U32BITARG_", seq=%"_U32BITARG_", time=%"_U32BITARG_"(%"_U32BITARG_"); "
|
|
"bufferingDelay=%"_U32BITARG_", FBS=%"_U32BITARG_"\n",
|
|
trackIndex, packetLen, seqNum, timeStamp, RTPTime2MediaTime(trackIndex, timeStamp),
|
|
GetBufferingDelay(trackIndex), GetFreeBufferSpace(trackIndex));
|
|
}
|
|
|
|
return packetIsDuplicate;
|
|
}
|
|
|
|
//returns kUInt32_Max if the buffer is empty; otherwise returns the first sequence number in the buffer
|
|
UInt32 GetNextSeqNumToDecode(UInt32 trackIndex)
|
|
{
|
|
SVector<PacketInfo> &playBuffer = fTrackInfo[trackIndex].fPlayBuffer;
|
|
return playBuffer.empty() ? kUInt32_Max : playBuffer.front().fSeqNum;
|
|
}
|
|
|
|
//in milliseconds; returns kUInt32_Max if the stream is not playing or if the buffer is empty
|
|
UInt32 GetPlayoutDelay(UInt32 trackIndex)
|
|
{
|
|
SVector<PacketInfo> &playBuffer = fTrackInfo[trackIndex].fPlayBuffer;
|
|
if(!fIsPlaying || playBuffer.empty())
|
|
return kUInt32_Max;
|
|
else
|
|
{
|
|
UInt32 nextPacketTime = playBuffer.front().fTimeStamp;
|
|
return RTPTime2MediaTime(trackIndex, nextPacketTime) - fCurrentMediaTime;
|
|
}
|
|
}
|
|
|
|
//in milliseconds; returns kUInt32_Max if the buffer is empty
|
|
UInt32 GetBufferingDelay(UInt32 trackIndex)
|
|
{
|
|
SVector<PacketInfo> &playBuffer = fTrackInfo[trackIndex].fPlayBuffer;
|
|
if(playBuffer.empty())
|
|
return kUInt32_Max;
|
|
|
|
UInt64 delta = playBuffer.back().fTimeStamp - playBuffer.front().fTimeStamp;
|
|
UInt32 bufferDelay = static_cast<UInt32>((delta * 1000) / fTrackInfo[trackIndex].fSamplingRate);
|
|
if (fIsPlaying)
|
|
bufferDelay += GetPlayoutDelay(trackIndex);
|
|
return bufferDelay;
|
|
}
|
|
|
|
//in bytes
|
|
UInt32 GetFreeBufferSpace(UInt32 trackIndex)
|
|
{
|
|
SVector<PacketInfo> &playBuffer = fTrackInfo[trackIndex].fPlayBuffer;
|
|
UInt32 bytesInBuffer = 0;
|
|
for(UInt32 i = 0; i < playBuffer.size(); ++i)
|
|
bytesInBuffer += playBuffer[i].fPayloadSize;
|
|
Assert(fTrackInfo[trackIndex].fBufferSize >= bytesInBuffer);
|
|
return fTrackInfo[trackIndex].fBufferSize - bytesInBuffer;
|
|
}
|
|
|
|
//The lost packets returned by this function is NOT the same as the lost packets defined by the RTCP RR.
|
|
UInt32 GetNumPacketsLost(UInt32 trackIndex) { return fTrackInfo[trackIndex].fLostPackets.size(); }
|
|
UInt32 GetNumLatePackets(UInt32 trackIndex) { return fTrackInfo[trackIndex].fNumLatePackets; }
|
|
UInt32 GetNumBufferOverflowedPackets(UInt32 trackIndex) { return fTrackInfo[trackIndex].fNumBufferOverflowedPackets; }
|
|
UInt32 GetNumDuplicates(UInt32 trackIndex) { return fTrackInfo[trackIndex].fNumDuplicates; }
|
|
|
|
private:
|
|
struct PacketInfo
|
|
{
|
|
UInt32 fSeqNum; //kUInt32_Max is invalid sequence number
|
|
UInt32 fTimeStamp;
|
|
UInt32 fPayloadSize;
|
|
|
|
PacketInfo() : fSeqNum(0), fTimeStamp(0), fPayloadSize(0) {}
|
|
PacketInfo(UInt32 seqNum, UInt32 timeStamp, UInt32 payloadSize)
|
|
: fSeqNum(seqNum), fTimeStamp(timeStamp), fPayloadSize(payloadSize)
|
|
{}
|
|
};
|
|
struct TrackInfo
|
|
{
|
|
UInt32 fNumDuplicates;
|
|
UInt32 fNumBufferOverflowedPackets; //number of packets lost due to buffer overflow
|
|
UInt32 fNumLatePackets; //packet that did not arrive on time, but did arrive
|
|
|
|
UInt32 fBufferSize; //in bytes
|
|
UInt32 fSamplingRate; //in samples per second
|
|
|
|
//These two values have meaning only while the stream is playing.
|
|
//All packets with sequence number less than fNextSeqNum have already expired, and all other packets PROBABLY goes into the buffer
|
|
UInt32 fRTPTimeStampBase;
|
|
UInt32 fNextSeqNum;
|
|
|
|
SVector<UInt32> fLostPackets; //All the packets with seq number less than fNextSeqNum that has not yet arrived.
|
|
SVector<PacketInfo> fPlayBuffer; //Can contain only packets with seq number >= than fNextSeqNum
|
|
|
|
TrackInfo() : fNumDuplicates(0), fNumBufferOverflowedPackets(0), fNumLatePackets(0), fBufferSize(0), fSamplingRate(0),
|
|
fRTPTimeStampBase(0), fNextSeqNum(0)
|
|
{}
|
|
};
|
|
|
|
//Advances the curent media play time according to the wallclock time; remove packets in the buffer whose timestamp has expired,
|
|
//increases fNextSeqNum, and find out which packet is missing and put them on the lost packts list.
|
|
//Also updates the current media play time -- all packets with an earlier timestamp has expired, and all packets with a later timestamp
|
|
//must be in the buffer or is not received yet.
|
|
void AdvanceTime()
|
|
{
|
|
fCurrentMediaTime = LocalTime2MediaTime(OS::Milliseconds());
|
|
for(UInt32 trackIndex = 0; trackIndex < fTrackInfo.size(); ++trackIndex)
|
|
{
|
|
TrackInfo &trackInfo = fTrackInfo[trackIndex];
|
|
SVector<PacketInfo> &playBuffer = trackInfo.fPlayBuffer;
|
|
UInt32 prevTimeStamp = 0;
|
|
|
|
//go through the play buffer until a packet with a later timestamp than the current time is found
|
|
UInt32 i = 0;
|
|
for(; i < playBuffer.size(); ++i)
|
|
{
|
|
if(prevTimeStamp > playBuffer[i].fTimeStamp)
|
|
{ if(fVerboseLevel >= 2) //this can happen from out of order packets from udp network routing or retransmits from rudp. It should never happen over tcp or http.
|
|
qtss_printf("WARNING: RTP timestamp is not monotonic! seq=%"_U32BITARG_" timestamp=%"_U32BITARG_"\n", playBuffer[i].fSeqNum, playBuffer[i].fTimeStamp);
|
|
}
|
|
else
|
|
prevTimeStamp = playBuffer[i].fTimeStamp;
|
|
|
|
if (fCurrentMediaTime >= RTPTime2MediaTime(trackIndex, playBuffer[i].fTimeStamp))
|
|
{
|
|
//the packet has expired; add packets that has not yet arrived to the lost packet list
|
|
while(trackInfo.fNextSeqNum != playBuffer[i].fSeqNum)
|
|
{
|
|
trackInfo.fLostPackets.push_back(trackInfo.fNextSeqNum);
|
|
trackInfo.fNextSeqNum++;
|
|
}
|
|
trackInfo.fNextSeqNum++;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
//remove packets that have expired
|
|
playBuffer.erase(0, i);
|
|
}
|
|
}
|
|
|
|
//can be called only when the media is playing; media time is measured in milliseconds
|
|
UInt32 LocalTime2MediaTime(SInt64 localTime)
|
|
{
|
|
SInt64 delta = localTime - fLocalStartPlayTime;
|
|
Assert(fIsPlaying && (SInt64) 0 <= (SInt64) delta && delta <= (SInt64)kUInt32_Max);
|
|
return delta;
|
|
}
|
|
|
|
UInt32 RTPTime2MediaTime(UInt32 trackIndex, UInt32 RTPTimestamp)
|
|
{
|
|
TrackInfo &trackInfo = fTrackInfo[trackIndex];
|
|
UInt32 RTPTimestampBase = RTPTimestamp;
|
|
if(fIsPlaying)
|
|
RTPTimestampBase = trackInfo.fRTPTimeStampBase;
|
|
else if (!trackInfo.fPlayBuffer.empty())
|
|
RTPTimestampBase = trackInfo.fPlayBuffer.front().fTimeStamp;
|
|
UInt64 delta = RTPTimestamp - RTPTimestampBase;
|
|
return static_cast<UInt32>((delta*1000) / trackInfo.fSamplingRate);
|
|
}
|
|
|
|
SVector<TrackInfo> fTrackInfo;
|
|
UInt32 fTargetBufferingDelay; //in milliseconds
|
|
UInt32 fStartPlayDelay; //in milliseconds
|
|
SInt64 fLocalStartPlayTime; //in UNIX time(milliseconds);
|
|
UInt32 fCurrentMediaTime; //current media time(in millisecond); valid only while playing
|
|
UInt32 fVerboseLevel;
|
|
Bool16 fIsPlaying;
|
|
};
|
|
|
|
#endif //_PLAYERSIMULATOR_H_
|