Add even more of the source
This should be about everything needed to build so far?
This commit is contained in:
parent
af3619d4fa
commit
849723c9cf
547 changed files with 149239 additions and 0 deletions
366
RTSPClientLib/PlayerSimulator.h
Normal file
366
RTSPClientLib/PlayerSimulator.h
Normal file
|
@ -0,0 +1,366 @@
|
|||
/*
|
||||
*
|
||||
* @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_
|
Loading…
Add table
Add a link
Reference in a new issue