359 lines
12 KiB
C++
Executable File
359 lines
12 KiB
C++
Executable File
/**********
|
|
This library is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU Lesser General Public License as published by the
|
|
Free Software Foundation; either version 2.1 of the License, or (at your
|
|
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
|
|
|
|
This library is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
|
more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this library; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
**********/
|
|
// "liveMedia"
|
|
// Copyright (c) 1996-2016 Live Networks, Inc. All rights reserved.
|
|
// RTP Sinks
|
|
// Implementation
|
|
|
|
#include "RTPSink.hh"
|
|
#include "GroupsockHelper.hh"
|
|
|
|
////////// RTPSink //////////
|
|
|
|
Boolean RTPSink::lookupByName(UsageEnvironment& env, char const* sinkName,
|
|
RTPSink*& resultSink) {
|
|
resultSink = NULL; // unless we succeed
|
|
|
|
MediaSink* sink;
|
|
if (!MediaSink::lookupByName(env, sinkName, sink)) return False;
|
|
|
|
if (!sink->isRTPSink()) {
|
|
env.setResultMsg(sinkName, " is not a RTP sink");
|
|
return False;
|
|
}
|
|
|
|
resultSink = (RTPSink*)sink;
|
|
return True;
|
|
}
|
|
|
|
Boolean RTPSink::isRTPSink() const {
|
|
return True;
|
|
}
|
|
|
|
RTPSink::RTPSink(UsageEnvironment& env,
|
|
Groupsock* rtpGS, unsigned char rtpPayloadType,
|
|
unsigned rtpTimestampFrequency,
|
|
char const* rtpPayloadFormatName,
|
|
unsigned numChannels)
|
|
: MediaSink(env), fRTPInterface(this, rtpGS),
|
|
fRTPPayloadType(rtpPayloadType),
|
|
fPacketCount(0), fOctetCount(0), fTotalOctetCount(0),
|
|
fTimestampFrequency(rtpTimestampFrequency), fNextTimestampHasBeenPreset(False), fEnableRTCPReports(True),
|
|
fNumChannels(numChannels), fEstimatedBitrate(0) {
|
|
fRTPPayloadFormatName
|
|
= strDup(rtpPayloadFormatName == NULL ? "???" : rtpPayloadFormatName);
|
|
gettimeofday(&fCreationTime, NULL);
|
|
fTotalOctetCountStartTime = fCreationTime;
|
|
resetPresentationTimes();
|
|
|
|
fSeqNo = (u_int16_t)our_random();
|
|
fSSRC = our_random32();
|
|
fTimestampBase = our_random32();
|
|
|
|
fTransmissionStatsDB = new RTPTransmissionStatsDB(*this);
|
|
}
|
|
|
|
RTPSink::~RTPSink() {
|
|
delete fTransmissionStatsDB;
|
|
delete[] (char*)fRTPPayloadFormatName;
|
|
fRTPInterface.forgetOurGroupsock();
|
|
// so that the "fRTCPInterface" destructor doesn't turn off background read handling (in case
|
|
// its 'groupsock' is being shared with something else that does background read handling).
|
|
}
|
|
|
|
u_int32_t RTPSink::convertToRTPTimestamp(struct timeval tv) {
|
|
// Begin by converting from "struct timeval" units to RTP timestamp units:
|
|
u_int32_t timestampIncrement = (fTimestampFrequency*tv.tv_sec);
|
|
timestampIncrement += (u_int32_t)(fTimestampFrequency*(tv.tv_usec/1000000.0) + 0.5); // note: rounding
|
|
|
|
// Then add this to our 'timestamp base':
|
|
if (fNextTimestampHasBeenPreset) {
|
|
// Make the returned timestamp the same as the current "fTimestampBase",
|
|
// so that timestamps begin with the value that was previously preset:
|
|
fTimestampBase -= timestampIncrement;
|
|
fNextTimestampHasBeenPreset = False;
|
|
}
|
|
|
|
u_int32_t const rtpTimestamp = fTimestampBase + timestampIncrement;
|
|
#ifdef DEBUG_TIMESTAMPS
|
|
fprintf(stderr, "fTimestampBase: 0x%08x, tv: %lu.%06ld\n\t=> RTP timestamp: 0x%08x\n",
|
|
fTimestampBase, tv.tv_sec, tv.tv_usec, rtpTimestamp);
|
|
fflush(stderr);
|
|
#endif
|
|
|
|
return rtpTimestamp;
|
|
}
|
|
|
|
u_int32_t RTPSink::presetNextTimestamp() {
|
|
struct timeval timeNow;
|
|
gettimeofday(&timeNow, NULL);
|
|
|
|
u_int32_t tsNow = convertToRTPTimestamp(timeNow);
|
|
if (!groupsockBeingUsed().hasMultipleDestinations()) {
|
|
// Don't adjust the timestamp stream if we already have another destination ongoing
|
|
fTimestampBase = tsNow;
|
|
fNextTimestampHasBeenPreset = True;
|
|
}
|
|
|
|
return tsNow;
|
|
}
|
|
|
|
void RTPSink::getTotalBitrate(unsigned& outNumBytes, double& outElapsedTime) {
|
|
struct timeval timeNow;
|
|
gettimeofday(&timeNow, NULL);
|
|
|
|
outNumBytes = fTotalOctetCount;
|
|
outElapsedTime = (double)(timeNow.tv_sec-fTotalOctetCountStartTime.tv_sec)
|
|
+ (timeNow.tv_usec-fTotalOctetCountStartTime.tv_usec)/1000000.0;
|
|
|
|
fTotalOctetCount = 0;
|
|
fTotalOctetCountStartTime = timeNow;
|
|
}
|
|
|
|
void RTPSink::resetPresentationTimes() {
|
|
fInitialPresentationTime.tv_sec = fMostRecentPresentationTime.tv_sec = 0;
|
|
fInitialPresentationTime.tv_usec = fMostRecentPresentationTime.tv_usec = 0;
|
|
}
|
|
|
|
char const* RTPSink::sdpMediaType() const {
|
|
return "data";
|
|
// default SDP media (m=) type, unless redefined by subclasses
|
|
}
|
|
|
|
char* RTPSink::rtpmapLine() const {
|
|
if (rtpPayloadType() >= 96) { // the payload format type is dynamic
|
|
char* encodingParamsPart;
|
|
if (numChannels() != 1) {
|
|
encodingParamsPart = new char[1 + 20 /* max int len */];
|
|
sprintf(encodingParamsPart, "/%d", numChannels());
|
|
} else {
|
|
encodingParamsPart = strDup("");
|
|
}
|
|
char const* const rtpmapFmt = "a=rtpmap:%d %s/%d%s\r\n";
|
|
unsigned rtpmapFmtSize = strlen(rtpmapFmt)
|
|
+ 3 /* max char len */ + strlen(rtpPayloadFormatName())
|
|
+ 20 /* max int len */ + strlen(encodingParamsPart);
|
|
char* rtpmapLine = new char[rtpmapFmtSize];
|
|
sprintf(rtpmapLine, rtpmapFmt,
|
|
rtpPayloadType(), rtpPayloadFormatName(),
|
|
rtpTimestampFrequency(), encodingParamsPart);
|
|
delete[] encodingParamsPart;
|
|
|
|
return rtpmapLine;
|
|
} else {
|
|
// The payload format is staic, so there's no "a=rtpmap:" line:
|
|
return strDup("");
|
|
}
|
|
}
|
|
|
|
char const* RTPSink::auxSDPLine() {
|
|
return NULL; // by default
|
|
}
|
|
|
|
|
|
////////// RTPTransmissionStatsDB //////////
|
|
|
|
RTPTransmissionStatsDB::RTPTransmissionStatsDB(RTPSink& rtpSink)
|
|
: fOurRTPSink(rtpSink),
|
|
fTable(HashTable::create(ONE_WORD_HASH_KEYS)) {
|
|
fNumReceivers=0;
|
|
}
|
|
|
|
RTPTransmissionStatsDB::~RTPTransmissionStatsDB() {
|
|
// First, remove and delete all stats records from the table:
|
|
RTPTransmissionStats* stats;
|
|
while ((stats = (RTPTransmissionStats*)fTable->RemoveNext()) != NULL) {
|
|
delete stats;
|
|
}
|
|
|
|
// Then, delete the table itself:
|
|
delete fTable;
|
|
}
|
|
|
|
void RTPTransmissionStatsDB
|
|
::noteIncomingRR(u_int32_t SSRC, struct sockaddr_in const& lastFromAddress,
|
|
unsigned lossStats, unsigned lastPacketNumReceived,
|
|
unsigned jitter, unsigned lastSRTime, unsigned diffSR_RRTime) {
|
|
RTPTransmissionStats* stats = lookup(SSRC);
|
|
if (stats == NULL) {
|
|
// This is the first time we've heard of this SSRC.
|
|
// Create a new record for it:
|
|
stats = new RTPTransmissionStats(fOurRTPSink, SSRC);
|
|
if (stats == NULL) return;
|
|
add(SSRC, stats);
|
|
#ifdef DEBUG_RR
|
|
fprintf(stderr, "Adding new entry for SSRC %x in RTPTransmissionStatsDB\n", SSRC);
|
|
#endif
|
|
}
|
|
|
|
stats->noteIncomingRR(lastFromAddress,
|
|
lossStats, lastPacketNumReceived, jitter,
|
|
lastSRTime, diffSR_RRTime);
|
|
}
|
|
|
|
void RTPTransmissionStatsDB::removeRecord(u_int32_t SSRC) {
|
|
RTPTransmissionStats* stats = lookup(SSRC);
|
|
if (stats != NULL) {
|
|
long SSRC_long = (long)SSRC;
|
|
fTable->Remove((char const*)SSRC_long);
|
|
--fNumReceivers;
|
|
delete stats;
|
|
}
|
|
}
|
|
|
|
RTPTransmissionStatsDB::Iterator
|
|
::Iterator(RTPTransmissionStatsDB& receptionStatsDB)
|
|
: fIter(HashTable::Iterator::create(*(receptionStatsDB.fTable))) {
|
|
}
|
|
|
|
RTPTransmissionStatsDB::Iterator::~Iterator() {
|
|
delete fIter;
|
|
}
|
|
|
|
RTPTransmissionStats*
|
|
RTPTransmissionStatsDB::Iterator::next() {
|
|
char const* key; // dummy
|
|
|
|
return (RTPTransmissionStats*)(fIter->next(key));
|
|
}
|
|
|
|
RTPTransmissionStats* RTPTransmissionStatsDB::lookup(u_int32_t SSRC) const {
|
|
long SSRC_long = (long)SSRC;
|
|
return (RTPTransmissionStats*)(fTable->Lookup((char const*)SSRC_long));
|
|
}
|
|
|
|
void RTPTransmissionStatsDB::add(u_int32_t SSRC, RTPTransmissionStats* stats) {
|
|
long SSRC_long = (long)SSRC;
|
|
fTable->Add((char const*)SSRC_long, stats);
|
|
++fNumReceivers;
|
|
}
|
|
|
|
|
|
////////// RTPTransmissionStats //////////
|
|
|
|
RTPTransmissionStats::RTPTransmissionStats(RTPSink& rtpSink, u_int32_t SSRC)
|
|
: fOurRTPSink(rtpSink), fSSRC(SSRC), fLastPacketNumReceived(0),
|
|
fPacketLossRatio(0), fTotNumPacketsLost(0), fJitter(0),
|
|
fLastSRTime(0), fDiffSR_RRTime(0), fAtLeastTwoRRsHaveBeenReceived(False), fFirstPacket(True),
|
|
fTotalOctetCount_hi(0), fTotalOctetCount_lo(0),
|
|
fTotalPacketCount_hi(0), fTotalPacketCount_lo(0) {
|
|
gettimeofday(&fTimeCreated, NULL);
|
|
|
|
fLastOctetCount = rtpSink.octetCount();
|
|
fLastPacketCount = rtpSink.packetCount();
|
|
}
|
|
|
|
RTPTransmissionStats::~RTPTransmissionStats() {}
|
|
|
|
void RTPTransmissionStats
|
|
::noteIncomingRR(struct sockaddr_in const& lastFromAddress,
|
|
unsigned lossStats, unsigned lastPacketNumReceived,
|
|
unsigned jitter, unsigned lastSRTime,
|
|
unsigned diffSR_RRTime) {
|
|
if (fFirstPacket) {
|
|
fFirstPacket = False;
|
|
fFirstPacketNumReported = lastPacketNumReceived;
|
|
} else {
|
|
fAtLeastTwoRRsHaveBeenReceived = True;
|
|
fOldLastPacketNumReceived = fLastPacketNumReceived;
|
|
fOldTotNumPacketsLost = fTotNumPacketsLost;
|
|
}
|
|
gettimeofday(&fTimeReceived, NULL);
|
|
|
|
fLastFromAddress = lastFromAddress;
|
|
fPacketLossRatio = lossStats>>24;
|
|
fTotNumPacketsLost = lossStats&0xFFFFFF;
|
|
fLastPacketNumReceived = lastPacketNumReceived;
|
|
fJitter = jitter;
|
|
fLastSRTime = lastSRTime;
|
|
fDiffSR_RRTime = diffSR_RRTime;
|
|
#ifdef DEBUG_RR
|
|
fprintf(stderr, "RTCP RR data (received at %lu.%06ld): lossStats 0x%08x, lastPacketNumReceived 0x%08x, jitter 0x%08x, lastSRTime 0x%08x, diffSR_RRTime 0x%08x\n",
|
|
fTimeReceived.tv_sec, fTimeReceived.tv_usec, lossStats, lastPacketNumReceived, jitter, lastSRTime, diffSR_RRTime);
|
|
unsigned rtd = roundTripDelay();
|
|
fprintf(stderr, "=> round-trip delay: 0x%04x (== %f seconds)\n", rtd, rtd/65536.0);
|
|
#endif
|
|
|
|
// Update our counts of the total number of octets and packets sent towards
|
|
// this receiver:
|
|
u_int32_t newOctetCount = fOurRTPSink.octetCount();
|
|
u_int32_t octetCountDiff = newOctetCount - fLastOctetCount;
|
|
fLastOctetCount = newOctetCount;
|
|
u_int32_t prevTotalOctetCount_lo = fTotalOctetCount_lo;
|
|
fTotalOctetCount_lo += octetCountDiff;
|
|
if (fTotalOctetCount_lo < prevTotalOctetCount_lo) { // wrap around
|
|
++fTotalOctetCount_hi;
|
|
}
|
|
|
|
u_int32_t newPacketCount = fOurRTPSink.packetCount();
|
|
u_int32_t packetCountDiff = newPacketCount - fLastPacketCount;
|
|
fLastPacketCount = newPacketCount;
|
|
u_int32_t prevTotalPacketCount_lo = fTotalPacketCount_lo;
|
|
fTotalPacketCount_lo += packetCountDiff;
|
|
if (fTotalPacketCount_lo < prevTotalPacketCount_lo) { // wrap around
|
|
++fTotalPacketCount_hi;
|
|
}
|
|
}
|
|
|
|
unsigned RTPTransmissionStats::roundTripDelay() const {
|
|
// Compute the round-trip delay that was indicated by the most recently-received
|
|
// RTCP RR packet. Use the method noted in the RTP/RTCP specification (RFC 3350).
|
|
|
|
if (fLastSRTime == 0) {
|
|
// Either no RTCP RR packet has been received yet, or else the
|
|
// reporting receiver has not yet received any RTCP SR packets from us:
|
|
return 0;
|
|
}
|
|
|
|
// First, convert the time that we received the last RTCP RR packet to NTP format,
|
|
// in units of 1/65536 (2^-16) seconds:
|
|
unsigned lastReceivedTimeNTP_high
|
|
= fTimeReceived.tv_sec + 0x83AA7E80; // 1970 epoch -> 1900 epoch
|
|
double fractionalPart = (fTimeReceived.tv_usec*0x0400)/15625.0; // 2^16/10^6
|
|
unsigned lastReceivedTimeNTP
|
|
= (unsigned)((lastReceivedTimeNTP_high<<16) + fractionalPart + 0.5);
|
|
|
|
int rawResult = lastReceivedTimeNTP - fLastSRTime - fDiffSR_RRTime;
|
|
if (rawResult < 0) {
|
|
// This can happen if there's clock drift between the sender and receiver,
|
|
// and if the round-trip time was very small.
|
|
rawResult = 0;
|
|
}
|
|
return (unsigned)rawResult;
|
|
}
|
|
|
|
void RTPTransmissionStats::getTotalOctetCount(u_int32_t& hi, u_int32_t& lo) {
|
|
hi = fTotalOctetCount_hi;
|
|
lo = fTotalOctetCount_lo;
|
|
}
|
|
|
|
void RTPTransmissionStats::getTotalPacketCount(u_int32_t& hi, u_int32_t& lo) {
|
|
hi = fTotalPacketCount_hi;
|
|
lo = fTotalPacketCount_lo;
|
|
}
|
|
|
|
unsigned RTPTransmissionStats::packetsReceivedSinceLastRR() const {
|
|
if (!fAtLeastTwoRRsHaveBeenReceived) return 0;
|
|
|
|
return fLastPacketNumReceived-fOldLastPacketNumReceived;
|
|
}
|
|
|
|
int RTPTransmissionStats::packetsLostBetweenRR() const {
|
|
if (!fAtLeastTwoRRsHaveBeenReceived) return 0;
|
|
|
|
return fTotNumPacketsLost - fOldTotNumPacketsLost;
|
|
}
|