From 46a6d8dd5bd90e637cdc0cd097f67794f7b10fb8 Mon Sep 17 00:00:00 2001 From: mirabilos Date: Tue, 30 Jun 2020 13:37:00 +0200 Subject: [PATCH 1/9] repair file types (symbolic link vs. regular file) --- code/bw-test-tool/code/RtpQueue.cpp | 0 code/bw-test-tool/code/RtpQueue.h | 0 code/bw-test-tool/code/ScreamRx.cpp | 0 code/bw-test-tool/code/ScreamRx.h | 0 code/bw-test-tool/code/ScreamTx.cpp | 0 code/bw-test-tool/code/ScreamTx.h | 0 6 files changed, 0 insertions(+), 0 deletions(-) mode change 120000 => 100644 code/bw-test-tool/code/RtpQueue.cpp mode change 120000 => 100644 code/bw-test-tool/code/RtpQueue.h mode change 120000 => 100644 code/bw-test-tool/code/ScreamRx.cpp mode change 120000 => 100644 code/bw-test-tool/code/ScreamRx.h mode change 120000 => 100644 code/bw-test-tool/code/ScreamTx.cpp mode change 120000 => 100644 code/bw-test-tool/code/ScreamTx.h diff --git a/code/bw-test-tool/code/RtpQueue.cpp b/code/bw-test-tool/code/RtpQueue.cpp deleted file mode 120000 index 0b1e044..0000000 --- a/code/bw-test-tool/code/RtpQueue.cpp +++ /dev/null @@ -1,120 +0,0 @@ - #include "RtpQueue.h" -#include -#include -using namespace std; -/* -* Implements a simple RTP packet queue -*/ - -RtpQueueItem::RtpQueueItem() { - used = false; - size = 0; - seqNr = 0; -} - - -RtpQueue::RtpQueue() { - for (int n=0; n < kRtpQueueSize; n++) { - items[n] = new RtpQueueItem(); - } - head = -1; - tail = 0; - nItems = 0; - sizeOfLastFrame = 0; - bytesInQueue_ = 0; - sizeOfQueue_ = 0; - sizeOfNextRtp_ = -1; -} - -void RtpQueue::push(void *rtpPacket, int size, unsigned short seqNr, float ts) { - int ix = head+1; - if (ix == kRtpQueueSize) ix = 0; - if (items[ix]->used) { - /* - * RTP queue is full, do a drop tail i.e ignore new RTP packets - */ - return; - } - head = ix; - items[head]->seqNr = seqNr; - items[head]->size = size; - items[head]->ts = ts; - items[head]->used = true; - bytesInQueue_ += size; - sizeOfQueue_ += 1; - memcpy(items[head]->packet, rtpPacket, size); - computeSizeOfNextRtp(); -} - -bool RtpQueue::pop(void *rtpPacket, int& size, unsigned short& seqNr) { - if (items[tail]->used == false) { - return false; - sizeOfNextRtp_ = -1; - } else { - size = items[tail]->size; - memcpy(rtpPacket,items[tail]->packet,size); - seqNr = items[tail]->seqNr; - items[tail]->used = false; - tail++; if (tail == kRtpQueueSize) tail = 0; - bytesInQueue_ -= size; - sizeOfQueue_ -= 1; - computeSizeOfNextRtp(); - return true; - } -} - -void RtpQueue::computeSizeOfNextRtp() { - if (!items[tail]->used) { - sizeOfNextRtp_ = - 1; - } else { - sizeOfNextRtp_ = items[tail]->size; - } -} - -int RtpQueue::sizeOfNextRtp() { - return sizeOfNextRtp_; -} - -int RtpQueue::seqNrOfNextRtp() { - if (!items[tail]->used) { - return -1; - } else { - return items[tail]->seqNr; - } -} - -int RtpQueue::bytesInQueue() { - return bytesInQueue_; -} - -int RtpQueue::sizeOfQueue() { - return sizeOfQueue_; -} - -float RtpQueue::getDelay(float currTs) { - if (items[tail]->used == false) { - return 0; - } else { - return currTs-items[tail]->ts; - } -} - -bool RtpQueue::sendPacket(void *rtpPacket, int& size, unsigned short& seqNr) { - if (sizeOfQueue() > 0) { - pop(rtpPacket, size, seqNr); - return true; - } - return false; -} - -void RtpQueue::clear() { - for (int n=0; n < kRtpQueueSize; n++) { - items[n]->used = false; - } - head = -1; - tail = 0; - nItems = 0; - bytesInQueue_ = 0; - sizeOfQueue_ = 0; - sizeOfNextRtp_ = -1; -} diff --git a/code/bw-test-tool/code/RtpQueue.cpp b/code/bw-test-tool/code/RtpQueue.cpp new file mode 100644 index 0000000..0b1e044 --- /dev/null +++ b/code/bw-test-tool/code/RtpQueue.cpp @@ -0,0 +1,120 @@ + #include "RtpQueue.h" +#include +#include +using namespace std; +/* +* Implements a simple RTP packet queue +*/ + +RtpQueueItem::RtpQueueItem() { + used = false; + size = 0; + seqNr = 0; +} + + +RtpQueue::RtpQueue() { + for (int n=0; n < kRtpQueueSize; n++) { + items[n] = new RtpQueueItem(); + } + head = -1; + tail = 0; + nItems = 0; + sizeOfLastFrame = 0; + bytesInQueue_ = 0; + sizeOfQueue_ = 0; + sizeOfNextRtp_ = -1; +} + +void RtpQueue::push(void *rtpPacket, int size, unsigned short seqNr, float ts) { + int ix = head+1; + if (ix == kRtpQueueSize) ix = 0; + if (items[ix]->used) { + /* + * RTP queue is full, do a drop tail i.e ignore new RTP packets + */ + return; + } + head = ix; + items[head]->seqNr = seqNr; + items[head]->size = size; + items[head]->ts = ts; + items[head]->used = true; + bytesInQueue_ += size; + sizeOfQueue_ += 1; + memcpy(items[head]->packet, rtpPacket, size); + computeSizeOfNextRtp(); +} + +bool RtpQueue::pop(void *rtpPacket, int& size, unsigned short& seqNr) { + if (items[tail]->used == false) { + return false; + sizeOfNextRtp_ = -1; + } else { + size = items[tail]->size; + memcpy(rtpPacket,items[tail]->packet,size); + seqNr = items[tail]->seqNr; + items[tail]->used = false; + tail++; if (tail == kRtpQueueSize) tail = 0; + bytesInQueue_ -= size; + sizeOfQueue_ -= 1; + computeSizeOfNextRtp(); + return true; + } +} + +void RtpQueue::computeSizeOfNextRtp() { + if (!items[tail]->used) { + sizeOfNextRtp_ = - 1; + } else { + sizeOfNextRtp_ = items[tail]->size; + } +} + +int RtpQueue::sizeOfNextRtp() { + return sizeOfNextRtp_; +} + +int RtpQueue::seqNrOfNextRtp() { + if (!items[tail]->used) { + return -1; + } else { + return items[tail]->seqNr; + } +} + +int RtpQueue::bytesInQueue() { + return bytesInQueue_; +} + +int RtpQueue::sizeOfQueue() { + return sizeOfQueue_; +} + +float RtpQueue::getDelay(float currTs) { + if (items[tail]->used == false) { + return 0; + } else { + return currTs-items[tail]->ts; + } +} + +bool RtpQueue::sendPacket(void *rtpPacket, int& size, unsigned short& seqNr) { + if (sizeOfQueue() > 0) { + pop(rtpPacket, size, seqNr); + return true; + } + return false; +} + +void RtpQueue::clear() { + for (int n=0; n < kRtpQueueSize; n++) { + items[n]->used = false; + } + head = -1; + tail = 0; + nItems = 0; + bytesInQueue_ = 0; + sizeOfQueue_ = 0; + sizeOfNextRtp_ = -1; +} diff --git a/code/bw-test-tool/code/RtpQueue.h b/code/bw-test-tool/code/RtpQueue.h deleted file mode 120000 index 175d175..0000000 --- a/code/bw-test-tool/code/RtpQueue.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef RTP_QUEUE -#define RTP_QUEUE - -/* -* Implements a simple RTP packet queue, one RTP queue -* per stream {SSRC,PT} -*/ - -class RtpQueueIface { -public: - virtual void clear() = 0; - virtual int sizeOfNextRtp() = 0; - virtual int seqNrOfNextRtp() = 0; - virtual int bytesInQueue() = 0; // Number of bytes in queue - virtual int sizeOfQueue() = 0; // Number of items in queue - virtual float getDelay(float currTs) = 0; - virtual int getSizeOfLastFrame() = 0; -}; - -class RtpQueueItem { -public: - RtpQueueItem(); - char packet[2000]; - int size; - unsigned short seqNr; - float ts; - bool used; -}; - -const int kRtpQueueSize = 1024; -class RtpQueue : public RtpQueueIface { -public: - RtpQueue(); - - void push(void *rtpPacket, int size, unsigned short seqNr, float ts); - bool pop(void *rtpPacket, int &size, unsigned short &seqNr); - int sizeOfNextRtp(); - int seqNrOfNextRtp(); - int bytesInQueue(); // Number of bytes in queue - int sizeOfQueue(); // Number of items in queue - float getDelay(float currTs); - bool sendPacket(void *rtpPacket, int &size, unsigned short &seqNr); - void clear(); - int getSizeOfLastFrame() {return sizeOfLastFrame;}; - void setSizeOfLastFrame(int sz) {sizeOfLastFrame=sz;}; - void computeSizeOfNextRtp(); - - RtpQueueItem *items[kRtpQueueSize]; - int head; // Pointer to last inserted item - int tail; // Pointer to the oldest item - int nItems; - int sizeOfLastFrame; - - int bytesInQueue_; - int sizeOfQueue_; - int sizeOfNextRtp_; -}; - -#endif diff --git a/code/bw-test-tool/code/RtpQueue.h b/code/bw-test-tool/code/RtpQueue.h new file mode 100644 index 0000000..175d175 --- /dev/null +++ b/code/bw-test-tool/code/RtpQueue.h @@ -0,0 +1,59 @@ +#ifndef RTP_QUEUE +#define RTP_QUEUE + +/* +* Implements a simple RTP packet queue, one RTP queue +* per stream {SSRC,PT} +*/ + +class RtpQueueIface { +public: + virtual void clear() = 0; + virtual int sizeOfNextRtp() = 0; + virtual int seqNrOfNextRtp() = 0; + virtual int bytesInQueue() = 0; // Number of bytes in queue + virtual int sizeOfQueue() = 0; // Number of items in queue + virtual float getDelay(float currTs) = 0; + virtual int getSizeOfLastFrame() = 0; +}; + +class RtpQueueItem { +public: + RtpQueueItem(); + char packet[2000]; + int size; + unsigned short seqNr; + float ts; + bool used; +}; + +const int kRtpQueueSize = 1024; +class RtpQueue : public RtpQueueIface { +public: + RtpQueue(); + + void push(void *rtpPacket, int size, unsigned short seqNr, float ts); + bool pop(void *rtpPacket, int &size, unsigned short &seqNr); + int sizeOfNextRtp(); + int seqNrOfNextRtp(); + int bytesInQueue(); // Number of bytes in queue + int sizeOfQueue(); // Number of items in queue + float getDelay(float currTs); + bool sendPacket(void *rtpPacket, int &size, unsigned short &seqNr); + void clear(); + int getSizeOfLastFrame() {return sizeOfLastFrame;}; + void setSizeOfLastFrame(int sz) {sizeOfLastFrame=sz;}; + void computeSizeOfNextRtp(); + + RtpQueueItem *items[kRtpQueueSize]; + int head; // Pointer to last inserted item + int tail; // Pointer to the oldest item + int nItems; + int sizeOfLastFrame; + + int bytesInQueue_; + int sizeOfQueue_; + int sizeOfNextRtp_; +}; + +#endif diff --git a/code/bw-test-tool/code/ScreamRx.cpp b/code/bw-test-tool/code/ScreamRx.cpp deleted file mode 120000 index c219953..0000000 --- a/code/bw-test-tool/code/ScreamRx.cpp +++ /dev/null @@ -1,353 +0,0 @@ -#include "ScreamRx.h" -#include "ScreamTx.h" -#ifdef _MSC_VER -#define NOMINMAX -#include -#else -#include -#endif -#include -#include -#include -#include -using namespace std; - -const int kMaxRtcpSize = 900; - -ScreamRx::Stream::Stream(uint32_t ssrc_) { - ssrc = ssrc_; - receiveTimestamp = 0x0; - highestSeqNr = 0x0; - highestSeqNrTx = 0x0; - lastFeedbackT_ntp = 0; - nRtpSinceLastRtcp = 0; - firstReceived = false; - - for (int n = 0; n < kRxHistorySize; n++) { - ceBitsHist[n] = 0x00; - rxTimeHist[n] = 0; - seqNrHist[n] = 0x0000; - } - -} - -bool ScreamRx::Stream::checkIfFlushAck(int ackDiff) { - uint32_t diff = highestSeqNr - highestSeqNrTx; - return (diff >= ackDiff); -} - -void ScreamRx::Stream::receive(uint32_t time_ntp, - void* rtpPacket, - int size, - uint16_t seqNr, - bool isEcnCe, - uint8_t ceBits_) { - - /* - * Count received RTP packets since last RTCP transmitted for this SSRC - */ - nRtpSinceLastRtcp++; - - /* - * Initialize on first received packet - */ - if (firstReceived == false) { - highestSeqNr = seqNr; - highestSeqNr--; - for (int n = 0; n < kRxHistorySize; n++) { - // Initialize seqNr list properly - seqNrHist[n] = seqNr + 1; - } - firstReceived = true; - } - /* - * Update CE bits and RX time vectors - */ - int ix = seqNr % kRxHistorySize; - ceBitsHist[ix] = ceBits_; - rxTimeHist[ix] = time_ntp; - seqNrHist[ix] = seqNr; - - uint32_t seqNrExt = seqNr; - uint32_t highestSeqNrExt = highestSeqNr; - if (seqNr < highestSeqNr) { - if (highestSeqNr - seqNr > 16384) - seqNrExt += 65536; - } - if (highestSeqNr < seqNr) { - if (seqNr - highestSeqNr > 16384) - highestSeqNrExt += 65536; - } - - /* - * Update the ACK vector that indicates receiption '=1' of RTP packets prior to - * the highest received sequence number. - * The next highest SN is indicated by the least significant bit, - * this means that for the first received RTP, the ACK vector is - * 0x0, for the second received RTP, the ACK vector it is 0x1, for the third 0x3 and so - * on, provided that no packets are lost. - * A 64 bit ACK vector means that it theory it is possible to send one feedback every - * 64 RTP packets, while this can possibly work at low bitrates, it is less likely to work - * at high bitrates because the ACK clocking in SCReAM is disturbed then. - */ - if (seqNrExt >= highestSeqNrExt) { - /* - * Normal in-order reception - */ - highestSeqNr = seqNr; - } -} - -bool ScreamRx::Stream::getStandardizedFeedback(uint32_t time_ntp, - unsigned char *buf, - int &size) { - uint16_t tmp_s; - uint32_t tmp_l; - size = 0; - /* - * Write RTP sender SSRC - */ - tmp_l = htonl(ssrc); - memcpy(buf, &tmp_l, 4); - size += 4; - /* - * Write begin_seq - * always report nReportedRtpPackets RTP packets - */ - tmp_s = highestSeqNr - uint16_t(nReportedRtpPackets - 1); - tmp_s = htons(tmp_s); - memcpy(buf + 4, &tmp_s, 2); - size += 2; - - /* - * Write number of reports- 1 - */ - tmp_s = nReportedRtpPackets-1; - tmp_s = htons(tmp_s); - memcpy(buf + 6, &tmp_s, 2); - size += 2; - int ptr = 8; - - /* - * Write 16bits report element for received RTP packets - */ - uint16_t sn_lo = highestSeqNr - uint16_t(nReportedRtpPackets - 1); - - for (uint16_t k = 0; k < nReportedRtpPackets; k++) { - uint16_t sn = sn_lo + k; - - int ix = sn % kRxHistorySize; - uint32_t ato = (time_ntp - rxTimeHist[ix]); - ato = ato >> 6; // Q16->Q10 - if (ato > 8189) - ato = 0x1FFE; - - tmp_s = 0x0000; - if (seqNrHist[ix] == sn && rxTimeHist[ix] != 0) { - tmp_s = 0x8000 | ((ceBitsHist[ix] & 0x03) << 13) | (ato & 0x01FFF);; - } - - tmp_s = htons(tmp_s); - memcpy(buf + ptr, &tmp_s, 2); - size += 2; - ptr += 2; - } - /* - * Zero pad with two extra octets if the number of reported packets is odd - */ - if (nReportedRtpPackets % 2 == 1) { - tmp_s = 0x0000; - memcpy(buf + ptr, &tmp_s, 2); - size += 2; - ptr += 2; - } - - return true; -} - -ScreamRx::ScreamRx(uint32_t ssrc_, int ackDiff_, int nReportedRtpPackets_) { - lastFeedbackT_ntp = 0; - bytesReceived = 0; - lastRateComputeT_ntp = 0; - averageReceivedRate = 1e5; - rtcpFbInterval_ntp = 13107; // 20ms in NTP domain - ssrc = ssrc_; - ix = 0; - nReportedRtpPackets = nReportedRtpPackets_; - if (ackDiff_ > 0) - ackDiff = ackDiff_; - else - ackDiff = std::max(1,nReportedRtpPackets / 4); - -} - -ScreamRx::~ScreamRx() { - if (!streams.empty()) { - for (auto it = streams.begin(); it != streams.end(); ++it) { - delete (*it); - } - } -} - -bool ScreamRx::checkIfFlushAck() { - if (ackDiff==1) - return true; - if (!streams.empty()) { - for (auto it = streams.begin(); it != streams.end(); ++it) { - if ((*it)->checkIfFlushAck(ackDiff)) - return true; - } - } - return false; -} - -void ScreamRx::receive(uint32_t time_ntp, - void* rtpPacket, - uint32_t ssrc, - int size, - uint16_t seqNr, - uint8_t ceBits) { - - bytesReceived += size; - if (lastRateComputeT_ntp == 0) - lastRateComputeT_ntp = time_ntp; - if (time_ntp - lastRateComputeT_ntp > 6554) { // 100ms in NTP domain - /* - * Media rate computation (for all medias) is done at least every 100ms - * This is used for RTCP feedback rate calculation - */ - float delta = (time_ntp - lastRateComputeT_ntp) * ntp2SecScaleFactor; - lastRateComputeT_ntp = time_ntp; - averageReceivedRate = std::max(0.95f*averageReceivedRate, bytesReceived * 8 / delta); - bytesReceived = 0; - /* - * The RTCP feedback rate depends on the received media date - * Target ~2% overhead but with feedback interval limited - * to the range [2ms,100ms] - */ - float rate = 0.02f*averageReceivedRate / (100.0f * 8.0f); // RTCP overhead - rate = std::min(500.0f, std::max(10.0f, rate)); - /* - * More than one stream ?, increase the feedback rate as - * we currently don't bundle feedback packets - */ - //rate *= streams.size(); - rtcpFbInterval_ntp = uint32_t(65536.0f / rate); // Convert to NTP domain (Q16) - } - - if (!streams.empty()) { - for (auto it = streams.begin(); it != streams.end(); ++it) { - if ((*it)->isMatch(ssrc)) { - /* - * Packets for this SSRC received earlier - * stream is thus already in list - */ - (*it)->receive(time_ntp, rtpPacket, size, seqNr, ceBits == 0x03, ceBits); - return; - - } - } - } - /* - * New {SSRC,PT} - */ - Stream *stream = new Stream(ssrc); - stream->nReportedRtpPackets = nReportedRtpPackets; - stream->ix = ix++; - stream->receive(time_ntp, rtpPacket, size, seqNr, ceBits == 0x03, ceBits); - streams.push_back(stream); -} - -uint32_t ScreamRx::getRtcpFbInterval() { - return rtcpFbInterval_ntp; -} - -bool ScreamRx::isFeedback(uint32_t time_ntp) { - if (!streams.empty()) { - for (auto it = streams.begin(); it != streams.end(); ++it) { - Stream *stream = (*it); - if (stream->nRtpSinceLastRtcp >= 1) { - return true; - } - } - } - return false; -} - -int ScreamRx::getIx(uint32_t ssrc) { - if (!streams.empty()) { - for (auto it = streams.begin(); it != streams.end(); ++it) { - Stream *stream = (*it); - if (ssrc == stream->ssrc) - return stream->ix; - } - } - return -1; -} - -bool ScreamRx::createStandardizedFeedback(uint32_t time_ntp, bool isMark, unsigned char *buf, int &size) { - - uint16_t tmp_s; - uint32_t tmp_l; - buf[0] = 0x80; // TODO FMT = CCFB in 5 LSB - buf[1] = 205; - /* - * Write RTCP sender SSRC - */ - tmp_l = htonl(ssrc); - memcpy(buf + 4, &tmp_l, 4); - size = 8; - int ptr = 8; - bool isFeedback = false; - /* - * Generate RTCP feedback size until a safe sizelimit ~kMaxRtcpSize+128 byte is reached - */ - while (size < kMaxRtcpSize) { - /* - * TODO, we do the above stream fetching over again even though we have the - * stream in the first iteration, a bit unnecessary. - */ - Stream *stream = NULL; - uint32_t minT_ntp = ULONG_MAX; - for (auto it = streams.begin(); it != streams.end(); ++it) { - uint32_t diffT_ntp = time_ntp - (*it)->lastFeedbackT_ntp; - if (((*it)->nRtpSinceLastRtcp >= std::min(8,ackDiff) || diffT_ntp > 655 || isMark) && // 10ms in Q16 - (*it)->lastFeedbackT_ntp < minT_ntp) { - stream = *it; - minT_ntp = (*it)->lastFeedbackT_ntp; - } - } - if (stream == NULL) - break; - isFeedback = true; - int size_stream = 0; - stream->getStandardizedFeedback(time_ntp, &buf[ptr], size_stream); - size += size_stream; - ptr += size_stream; - stream->lastFeedbackT_ntp = time_ntp; - stream->nRtpSinceLastRtcp = 0; - stream->highestSeqNrTx = stream->highestSeqNr; - } - if (!isFeedback) - return false; - /* - * Write report timestamp - */ - tmp_l = htonl(time_ntp); - memcpy(buf + ptr, &tmp_l, 4); - size += 4; - - /* - * write length - */ - uint16_t length = size / 4 - 1; - tmp_s = htons(length); - memcpy(buf + 2, &tmp_s, 2); - - /* - * Update stream RTCP feedback status - */ - lastFeedbackT_ntp = time_ntp; - - return true; -} diff --git a/code/bw-test-tool/code/ScreamRx.cpp b/code/bw-test-tool/code/ScreamRx.cpp new file mode 100644 index 0000000..c219953 --- /dev/null +++ b/code/bw-test-tool/code/ScreamRx.cpp @@ -0,0 +1,353 @@ +#include "ScreamRx.h" +#include "ScreamTx.h" +#ifdef _MSC_VER +#define NOMINMAX +#include +#else +#include +#endif +#include +#include +#include +#include +using namespace std; + +const int kMaxRtcpSize = 900; + +ScreamRx::Stream::Stream(uint32_t ssrc_) { + ssrc = ssrc_; + receiveTimestamp = 0x0; + highestSeqNr = 0x0; + highestSeqNrTx = 0x0; + lastFeedbackT_ntp = 0; + nRtpSinceLastRtcp = 0; + firstReceived = false; + + for (int n = 0; n < kRxHistorySize; n++) { + ceBitsHist[n] = 0x00; + rxTimeHist[n] = 0; + seqNrHist[n] = 0x0000; + } + +} + +bool ScreamRx::Stream::checkIfFlushAck(int ackDiff) { + uint32_t diff = highestSeqNr - highestSeqNrTx; + return (diff >= ackDiff); +} + +void ScreamRx::Stream::receive(uint32_t time_ntp, + void* rtpPacket, + int size, + uint16_t seqNr, + bool isEcnCe, + uint8_t ceBits_) { + + /* + * Count received RTP packets since last RTCP transmitted for this SSRC + */ + nRtpSinceLastRtcp++; + + /* + * Initialize on first received packet + */ + if (firstReceived == false) { + highestSeqNr = seqNr; + highestSeqNr--; + for (int n = 0; n < kRxHistorySize; n++) { + // Initialize seqNr list properly + seqNrHist[n] = seqNr + 1; + } + firstReceived = true; + } + /* + * Update CE bits and RX time vectors + */ + int ix = seqNr % kRxHistorySize; + ceBitsHist[ix] = ceBits_; + rxTimeHist[ix] = time_ntp; + seqNrHist[ix] = seqNr; + + uint32_t seqNrExt = seqNr; + uint32_t highestSeqNrExt = highestSeqNr; + if (seqNr < highestSeqNr) { + if (highestSeqNr - seqNr > 16384) + seqNrExt += 65536; + } + if (highestSeqNr < seqNr) { + if (seqNr - highestSeqNr > 16384) + highestSeqNrExt += 65536; + } + + /* + * Update the ACK vector that indicates receiption '=1' of RTP packets prior to + * the highest received sequence number. + * The next highest SN is indicated by the least significant bit, + * this means that for the first received RTP, the ACK vector is + * 0x0, for the second received RTP, the ACK vector it is 0x1, for the third 0x3 and so + * on, provided that no packets are lost. + * A 64 bit ACK vector means that it theory it is possible to send one feedback every + * 64 RTP packets, while this can possibly work at low bitrates, it is less likely to work + * at high bitrates because the ACK clocking in SCReAM is disturbed then. + */ + if (seqNrExt >= highestSeqNrExt) { + /* + * Normal in-order reception + */ + highestSeqNr = seqNr; + } +} + +bool ScreamRx::Stream::getStandardizedFeedback(uint32_t time_ntp, + unsigned char *buf, + int &size) { + uint16_t tmp_s; + uint32_t tmp_l; + size = 0; + /* + * Write RTP sender SSRC + */ + tmp_l = htonl(ssrc); + memcpy(buf, &tmp_l, 4); + size += 4; + /* + * Write begin_seq + * always report nReportedRtpPackets RTP packets + */ + tmp_s = highestSeqNr - uint16_t(nReportedRtpPackets - 1); + tmp_s = htons(tmp_s); + memcpy(buf + 4, &tmp_s, 2); + size += 2; + + /* + * Write number of reports- 1 + */ + tmp_s = nReportedRtpPackets-1; + tmp_s = htons(tmp_s); + memcpy(buf + 6, &tmp_s, 2); + size += 2; + int ptr = 8; + + /* + * Write 16bits report element for received RTP packets + */ + uint16_t sn_lo = highestSeqNr - uint16_t(nReportedRtpPackets - 1); + + for (uint16_t k = 0; k < nReportedRtpPackets; k++) { + uint16_t sn = sn_lo + k; + + int ix = sn % kRxHistorySize; + uint32_t ato = (time_ntp - rxTimeHist[ix]); + ato = ato >> 6; // Q16->Q10 + if (ato > 8189) + ato = 0x1FFE; + + tmp_s = 0x0000; + if (seqNrHist[ix] == sn && rxTimeHist[ix] != 0) { + tmp_s = 0x8000 | ((ceBitsHist[ix] & 0x03) << 13) | (ato & 0x01FFF);; + } + + tmp_s = htons(tmp_s); + memcpy(buf + ptr, &tmp_s, 2); + size += 2; + ptr += 2; + } + /* + * Zero pad with two extra octets if the number of reported packets is odd + */ + if (nReportedRtpPackets % 2 == 1) { + tmp_s = 0x0000; + memcpy(buf + ptr, &tmp_s, 2); + size += 2; + ptr += 2; + } + + return true; +} + +ScreamRx::ScreamRx(uint32_t ssrc_, int ackDiff_, int nReportedRtpPackets_) { + lastFeedbackT_ntp = 0; + bytesReceived = 0; + lastRateComputeT_ntp = 0; + averageReceivedRate = 1e5; + rtcpFbInterval_ntp = 13107; // 20ms in NTP domain + ssrc = ssrc_; + ix = 0; + nReportedRtpPackets = nReportedRtpPackets_; + if (ackDiff_ > 0) + ackDiff = ackDiff_; + else + ackDiff = std::max(1,nReportedRtpPackets / 4); + +} + +ScreamRx::~ScreamRx() { + if (!streams.empty()) { + for (auto it = streams.begin(); it != streams.end(); ++it) { + delete (*it); + } + } +} + +bool ScreamRx::checkIfFlushAck() { + if (ackDiff==1) + return true; + if (!streams.empty()) { + for (auto it = streams.begin(); it != streams.end(); ++it) { + if ((*it)->checkIfFlushAck(ackDiff)) + return true; + } + } + return false; +} + +void ScreamRx::receive(uint32_t time_ntp, + void* rtpPacket, + uint32_t ssrc, + int size, + uint16_t seqNr, + uint8_t ceBits) { + + bytesReceived += size; + if (lastRateComputeT_ntp == 0) + lastRateComputeT_ntp = time_ntp; + if (time_ntp - lastRateComputeT_ntp > 6554) { // 100ms in NTP domain + /* + * Media rate computation (for all medias) is done at least every 100ms + * This is used for RTCP feedback rate calculation + */ + float delta = (time_ntp - lastRateComputeT_ntp) * ntp2SecScaleFactor; + lastRateComputeT_ntp = time_ntp; + averageReceivedRate = std::max(0.95f*averageReceivedRate, bytesReceived * 8 / delta); + bytesReceived = 0; + /* + * The RTCP feedback rate depends on the received media date + * Target ~2% overhead but with feedback interval limited + * to the range [2ms,100ms] + */ + float rate = 0.02f*averageReceivedRate / (100.0f * 8.0f); // RTCP overhead + rate = std::min(500.0f, std::max(10.0f, rate)); + /* + * More than one stream ?, increase the feedback rate as + * we currently don't bundle feedback packets + */ + //rate *= streams.size(); + rtcpFbInterval_ntp = uint32_t(65536.0f / rate); // Convert to NTP domain (Q16) + } + + if (!streams.empty()) { + for (auto it = streams.begin(); it != streams.end(); ++it) { + if ((*it)->isMatch(ssrc)) { + /* + * Packets for this SSRC received earlier + * stream is thus already in list + */ + (*it)->receive(time_ntp, rtpPacket, size, seqNr, ceBits == 0x03, ceBits); + return; + + } + } + } + /* + * New {SSRC,PT} + */ + Stream *stream = new Stream(ssrc); + stream->nReportedRtpPackets = nReportedRtpPackets; + stream->ix = ix++; + stream->receive(time_ntp, rtpPacket, size, seqNr, ceBits == 0x03, ceBits); + streams.push_back(stream); +} + +uint32_t ScreamRx::getRtcpFbInterval() { + return rtcpFbInterval_ntp; +} + +bool ScreamRx::isFeedback(uint32_t time_ntp) { + if (!streams.empty()) { + for (auto it = streams.begin(); it != streams.end(); ++it) { + Stream *stream = (*it); + if (stream->nRtpSinceLastRtcp >= 1) { + return true; + } + } + } + return false; +} + +int ScreamRx::getIx(uint32_t ssrc) { + if (!streams.empty()) { + for (auto it = streams.begin(); it != streams.end(); ++it) { + Stream *stream = (*it); + if (ssrc == stream->ssrc) + return stream->ix; + } + } + return -1; +} + +bool ScreamRx::createStandardizedFeedback(uint32_t time_ntp, bool isMark, unsigned char *buf, int &size) { + + uint16_t tmp_s; + uint32_t tmp_l; + buf[0] = 0x80; // TODO FMT = CCFB in 5 LSB + buf[1] = 205; + /* + * Write RTCP sender SSRC + */ + tmp_l = htonl(ssrc); + memcpy(buf + 4, &tmp_l, 4); + size = 8; + int ptr = 8; + bool isFeedback = false; + /* + * Generate RTCP feedback size until a safe sizelimit ~kMaxRtcpSize+128 byte is reached + */ + while (size < kMaxRtcpSize) { + /* + * TODO, we do the above stream fetching over again even though we have the + * stream in the first iteration, a bit unnecessary. + */ + Stream *stream = NULL; + uint32_t minT_ntp = ULONG_MAX; + for (auto it = streams.begin(); it != streams.end(); ++it) { + uint32_t diffT_ntp = time_ntp - (*it)->lastFeedbackT_ntp; + if (((*it)->nRtpSinceLastRtcp >= std::min(8,ackDiff) || diffT_ntp > 655 || isMark) && // 10ms in Q16 + (*it)->lastFeedbackT_ntp < minT_ntp) { + stream = *it; + minT_ntp = (*it)->lastFeedbackT_ntp; + } + } + if (stream == NULL) + break; + isFeedback = true; + int size_stream = 0; + stream->getStandardizedFeedback(time_ntp, &buf[ptr], size_stream); + size += size_stream; + ptr += size_stream; + stream->lastFeedbackT_ntp = time_ntp; + stream->nRtpSinceLastRtcp = 0; + stream->highestSeqNrTx = stream->highestSeqNr; + } + if (!isFeedback) + return false; + /* + * Write report timestamp + */ + tmp_l = htonl(time_ntp); + memcpy(buf + ptr, &tmp_l, 4); + size += 4; + + /* + * write length + */ + uint16_t length = size / 4 - 1; + tmp_s = htons(length); + memcpy(buf + 2, &tmp_s, 2); + + /* + * Update stream RTCP feedback status + */ + lastFeedbackT_ntp = time_ntp; + + return true; +} diff --git a/code/bw-test-tool/code/ScreamRx.h b/code/bw-test-tool/code/ScreamRx.h deleted file mode 120000 index 0cd49e0..0000000 --- a/code/bw-test-tool/code/ScreamRx.h +++ /dev/null @@ -1,150 +0,0 @@ -#ifndef SCREAM_RX -#define SCREAM_RX -#include -#include -const int kReportedRtpPackets = 64; -const int kRxHistorySize = 128; - -/* -* This module implements the receiver side of SCReAM. -* As SCReAM is a sender based congestion control, the receiver side is -* actually dumber than dumb. In essense the only thing that it does is to -* + Record receive time stamps and RTP sequence numbers for incoming RTP packets -* + Generate RTCP feedback elements -* + Calculate an appropriate RTCP feedback interval -* See https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx -* for details on how it is integrated in audio/video platforms. -* A full implementation needs the additional code for -* + Other obvious stuff such as RTP payload depacketizer, video+audio deoders, rendering, dejitterbuffers -* It is recommended that RTCP feedback for multiple streams are bundled in one RTCP packet. -* However as low bitrate media (e.g audio) requires a lower feedback rate than high bitrate media (e.g video) -* it is possible to include RTCP feedback for the audio stream more seldom. The code for this is T.B.D -* -* Internal time is represented as the mid 32 bits of the NTP timestamp (see RFC5905) -* This means that the high order 16 bits is time in seconds and the low order 16 bits -* is the fraction. The NTP time stamp is thus in Q16 i.e 1.0sec is represented -* by the value 65536. -* All internal time is measured in NTP time, this is done to avoid wraparound issues -* that can otherwise occur every 18 hour or so -*/ - -class ScreamRx { -public: - ScreamRx(uint32_t ssrc, int ackDiff = -1, int nReportedRtpPackets = kReportedRtpPackets); // SSRC of this RTCP session - ~ScreamRx(); - - /* - * One instance is created for each source SSRC - */ - class Stream { - public: - Stream(uint32_t ssrc); - - bool isMatch(uint32_t ssrc_) { return ssrc == ssrc_; }; - - bool checkIfFlushAck(int ackDiff); - - /* - * Receive RTP packet - */ - void receive(uint32_t time_ntp, - void *rtpPacket, - int size, - uint16_t seqNr, - bool isEcnCe, - uint8_t ceBits); - - /* - * Get SCReAM standardized RTCP feedback - * return FALSE if no pending feedback available - */ - bool getStandardizedFeedback(uint32_t time_ntp, - unsigned char *buf, - int &size); - - - uint32_t ssrc; // SSRC of stream (source SSRC) - uint16_t highestSeqNr; // Highest received sequence number - uint16_t highestSeqNrTx; // Highest fed back sequence number - uint32_t receiveTimestamp; // Wall clock time - uint8_t ceBitsHist[kRxHistorySize]; // Vector of CE bits for last - // received RTP packets - uint32_t rxTimeHist[kRxHistorySize]; // Receive time for last - // received RTP packets - uint16_t seqNrHist[kRxHistorySize]; // Seq Nr of last received - // packets - uint32_t lastFeedbackT_ntp; // Last time feedback transmitted for - // this SSRC - int nRtpSinceLastRtcp; // Number of RTP packets since last transmitted RTCP - - bool firstReceived; - - float timeStampConversionFactor; - - int ix; - - int nReportedRtpPackets; - }; - - /* - * Check to ensure that ACKs can cover also large holes in - * in the received sequence number space. These cases can frequently occur when - * SCReAM is used in frame discard mode i.e. when real video rate control is - * not possible - */ - bool checkIfFlushAck(); - - /* - * Function is called each time an RTP packet is received - */ - void receive(uint32_t time_ntp, - void* rtpPacket, - uint32_t ssrc, - int size, - uint16_t seqNr, - uint8_t ceBits = 0x00); - - /* - * Return TRUE if an RTP packet has been received and there is - * pending feedback - */ - bool isFeedback(uint32_t time_ntp); - - /* - * Return RTCP feedback interval (Q16) - */ - uint32_t getRtcpFbInterval(); - - /* - * Create standardized feedback according to - * https://tools.ietf.org/wg/avtcore/draft-ietf-avtcore-cc-feedback-message/ - * Current implementation implements -02 version - * It is up to the wrapper application to prepend this RTCP - * with SR or RR when needed - */ - bool createStandardizedFeedback(uint32_t time_ntp, bool isMark, unsigned char *buf, int &size); - - /* - * Get last feedback transmission time in NTP domain (Q16) - */ - uint32_t getLastFeedbackT() { return lastFeedbackT_ntp; }; - - uint32_t lastFeedbackT_ntp; - int bytesReceived; - uint32_t lastRateComputeT_ntp; - float averageReceivedRate; - uint32_t rtcpFbInterval_ntp; - uint32_t ssrc; - - int getIx(uint32_t ssrc); - int ix; - int ackDiff; - - int nReportedRtpPackets; - /* - * Variables for multiple steams handling - */ - std::list streams; -}; - -#endif diff --git a/code/bw-test-tool/code/ScreamRx.h b/code/bw-test-tool/code/ScreamRx.h new file mode 100644 index 0000000..0cd49e0 --- /dev/null +++ b/code/bw-test-tool/code/ScreamRx.h @@ -0,0 +1,150 @@ +#ifndef SCREAM_RX +#define SCREAM_RX +#include +#include +const int kReportedRtpPackets = 64; +const int kRxHistorySize = 128; + +/* +* This module implements the receiver side of SCReAM. +* As SCReAM is a sender based congestion control, the receiver side is +* actually dumber than dumb. In essense the only thing that it does is to +* + Record receive time stamps and RTP sequence numbers for incoming RTP packets +* + Generate RTCP feedback elements +* + Calculate an appropriate RTCP feedback interval +* See https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx +* for details on how it is integrated in audio/video platforms. +* A full implementation needs the additional code for +* + Other obvious stuff such as RTP payload depacketizer, video+audio deoders, rendering, dejitterbuffers +* It is recommended that RTCP feedback for multiple streams are bundled in one RTCP packet. +* However as low bitrate media (e.g audio) requires a lower feedback rate than high bitrate media (e.g video) +* it is possible to include RTCP feedback for the audio stream more seldom. The code for this is T.B.D +* +* Internal time is represented as the mid 32 bits of the NTP timestamp (see RFC5905) +* This means that the high order 16 bits is time in seconds and the low order 16 bits +* is the fraction. The NTP time stamp is thus in Q16 i.e 1.0sec is represented +* by the value 65536. +* All internal time is measured in NTP time, this is done to avoid wraparound issues +* that can otherwise occur every 18 hour or so +*/ + +class ScreamRx { +public: + ScreamRx(uint32_t ssrc, int ackDiff = -1, int nReportedRtpPackets = kReportedRtpPackets); // SSRC of this RTCP session + ~ScreamRx(); + + /* + * One instance is created for each source SSRC + */ + class Stream { + public: + Stream(uint32_t ssrc); + + bool isMatch(uint32_t ssrc_) { return ssrc == ssrc_; }; + + bool checkIfFlushAck(int ackDiff); + + /* + * Receive RTP packet + */ + void receive(uint32_t time_ntp, + void *rtpPacket, + int size, + uint16_t seqNr, + bool isEcnCe, + uint8_t ceBits); + + /* + * Get SCReAM standardized RTCP feedback + * return FALSE if no pending feedback available + */ + bool getStandardizedFeedback(uint32_t time_ntp, + unsigned char *buf, + int &size); + + + uint32_t ssrc; // SSRC of stream (source SSRC) + uint16_t highestSeqNr; // Highest received sequence number + uint16_t highestSeqNrTx; // Highest fed back sequence number + uint32_t receiveTimestamp; // Wall clock time + uint8_t ceBitsHist[kRxHistorySize]; // Vector of CE bits for last + // received RTP packets + uint32_t rxTimeHist[kRxHistorySize]; // Receive time for last + // received RTP packets + uint16_t seqNrHist[kRxHistorySize]; // Seq Nr of last received + // packets + uint32_t lastFeedbackT_ntp; // Last time feedback transmitted for + // this SSRC + int nRtpSinceLastRtcp; // Number of RTP packets since last transmitted RTCP + + bool firstReceived; + + float timeStampConversionFactor; + + int ix; + + int nReportedRtpPackets; + }; + + /* + * Check to ensure that ACKs can cover also large holes in + * in the received sequence number space. These cases can frequently occur when + * SCReAM is used in frame discard mode i.e. when real video rate control is + * not possible + */ + bool checkIfFlushAck(); + + /* + * Function is called each time an RTP packet is received + */ + void receive(uint32_t time_ntp, + void* rtpPacket, + uint32_t ssrc, + int size, + uint16_t seqNr, + uint8_t ceBits = 0x00); + + /* + * Return TRUE if an RTP packet has been received and there is + * pending feedback + */ + bool isFeedback(uint32_t time_ntp); + + /* + * Return RTCP feedback interval (Q16) + */ + uint32_t getRtcpFbInterval(); + + /* + * Create standardized feedback according to + * https://tools.ietf.org/wg/avtcore/draft-ietf-avtcore-cc-feedback-message/ + * Current implementation implements -02 version + * It is up to the wrapper application to prepend this RTCP + * with SR or RR when needed + */ + bool createStandardizedFeedback(uint32_t time_ntp, bool isMark, unsigned char *buf, int &size); + + /* + * Get last feedback transmission time in NTP domain (Q16) + */ + uint32_t getLastFeedbackT() { return lastFeedbackT_ntp; }; + + uint32_t lastFeedbackT_ntp; + int bytesReceived; + uint32_t lastRateComputeT_ntp; + float averageReceivedRate; + uint32_t rtcpFbInterval_ntp; + uint32_t ssrc; + + int getIx(uint32_t ssrc); + int ix; + int ackDiff; + + int nReportedRtpPackets; + /* + * Variables for multiple steams handling + */ + std::list streams; +}; + +#endif diff --git a/code/bw-test-tool/code/ScreamTx.cpp b/code/bw-test-tool/code/ScreamTx.cpp deleted file mode 120000 index 6e3e981..0000000 --- a/code/bw-test-tool/code/ScreamTx.cpp +++ /dev/null @@ -1,2153 +0,0 @@ -#include "RtpQueue.h" -#include "ScreamTx.h" -#include "ScreamRx.h" -#ifdef _MSC_VER -#define NOMINMAX -#include -#else -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include - -// === Some good to have features, SCReAM works also -// with these disabled -// Fast start can resume if little or no congestion detected -static const bool kEnableConsecutiveFastStart = true; -// Packet pacing reduces jitter -static const bool kEnablePacketPacing = true; - -// Rate update interval -static const uint32_t kRateAdjustInterval_ntp = 13107; // 200ms in NTP domain - -// ==== Less important tuning parameters ==== -// Min pacing interval and min pacing rate -static const float kMinPaceInterval = 0.000f; -static const float kMinimumBandwidth = 5000.0f; // bps -// Initial MSS, this is set quite low in order to make it possible to -// use SCReAM with audio only -static const int kInitMss = 100; - -// Min and max queue delay target -static const float kQueueDelayTargetMax = 0.3f; //ms - -// Congestion window validation -static const float kBytesInFlightHistInterval_ntp = 65536; // Time (s) between stores=1s in NTP doain -static const float kMaxBytesInFlightHeadRoom = 1.25f; -// Queue delay trend and shared bottleneck detection -static const uint32_t kQueueDelayFractionHistInterval_us = 3277; // 50ms in NTP domain -// video rate estimation update period -static const uint32_t kRateUpdateInterval_ntp = 3277; // 50ms in NTP domain - -// Packet reordering margin (us) -static const uint32_t kReordertime_ntp = 655; // 10ms in NTP domain -static const uint32_t kMinRtpQueueDiscardInterval_ntp = 16384; // 0.25s in NTP doain - -// Update interval for base delay history -static const uint32_t kBaseDelayUpdateInterval_ntp = 655360; // 10s in NTP doain - -// L4S alpha gain factor, for scalable congestion control -static const float kL4sG = 0.125f; - -// L4S alpha max value, for scalable congestion control -static const float kL4sAlphaMax = 0.25; - -// Min CWND in MSS -static const int kMinCwndMss = 3; - -// Compensate for max 33/65536 = 0.05% clock drift -static const uint32_t kMaxClockdriftCompensation = 33; - -ScreamTx::ScreamTx(float lossBeta_, - float ecnCeBeta_, - float queueDelayTargetMin_, - bool enableSbd_, - float gainUp_, - float gainDown_, - int cwnd_, - float packetPacingHeadroom_, - int bytesInFlightHistSize_, - bool isL4s_, - bool openWindow_, - bool enableClockDriftCompensation_ -) : - sRttSh_ntp(0), - lossBeta(lossBeta_), - ecnCeBeta(ecnCeBeta_), - queueDelayTargetMin(queueDelayTargetMin_), - enableSbd(enableSbd_), - gainUp(gainUp_), - gainDown(gainDown_), - packetPacingHeadroom(packetPacingHeadroom_), - bytesInFlightHistSize(bytesInFlightHistSize_), - isL4s(isL4s_), - openWindow(openWindow_), - enableClockDriftCompensation(enableClockDriftCompensation_), - sRtt_ntp(0), - sRtt(0.0f), - ackedOwd(0), - baseOwd(UINT32_MAX), - queueDelay(0.0), - queueDelayFractionAvg(0.0), - queueDelayTrend(0.0), - queueDelayTarget(queueDelayTargetMin), - queueDelaySbdVar(0.0), - queueDelaySbdMean(0.0), - queueDelaySbdMeanSh(0.0), - - bytesNewlyAcked(0), - mss(kInitMss), - cwnd(kInitMss * 2), - cwndMin(kInitMss * 2), - cwndMinLow(0), - lastBytesInFlightT_ntp(0), - bytesInFlightMaxLo(0), - bytesInFlightMaxHi(0), - bytesInFlightHistLoMem(0), - bytesInFlightHistHiMem(0), - maxBytesInFlight(0.0f), - - lossEvent(false), - wasLossEvent(false), - lossEventRate(0.0), - ecnCeEvent(false), - l4sAlpha(0.1f), - bytesMarkedThisRtt(0), - bytesDeliveredThisRtt(0), - lastL4sAlphaUpdateT_ntp(0), - inFastStart(true), - maxTotalBitrate(0.0f), - - paceInterval_ntp(0), - paceInterval(0.0), - rateTransmittedAvg(0.0), - - isInitialized(false), - lastSRttUpdateT_ntp(0), - lastBaseOwdAddT_ntp(0), - baseOwdResetT_ntp(0), - lastAddToQueueDelayFractionHistT_ntp(0), - lastLossEventT_ntp(0), - lastTransmitT_ntp(0), - nextTransmitT_ntp(0), - lastRateUpdateT_ntp(0), - accBytesInFlightMax(0), - nAccBytesInFlightMax(0), - rateTransmitted(0.0f), - rateAcked(0.0f), - queueDelayTrendMem(0.0f), - lastCwndUpdateT_ntp(0), - bytesInFlight(0), - bytesInFlightLog(0), - lastBaseDelayRefreshT_ntp(0), - maxRate(0.0f), - baseOwdHistMin(UINT32_MAX), - clockDriftCompensation(0), - clockDriftCompensationInc(0), - - bytesNewlyAckedLog(0), - ecnCeMarkedBytesLog(0), - isUseExtraDetailedLog(false), - fp_log(0), - completeLogItem(false) -{ - strcpy(detailedLogExtraData,""); - if (cwnd_ == 0) { - cwnd = kInitMss * 2; - } - else { - cwnd = cwnd_; - } - if (openWindow) { - cwndMin = 10000000; - cwnd = cwndMin; - } - - for (int n = 0; n < kBaseOwdHistSize; n++) - baseOwdHist[n] = UINT32_MAX; - baseOwdHistPtr = 0; - for (int n = 0; n < kQueueDelayFractionHistSize; n++) - queueDelayFractionHist[n] = 0.0f; - queueDelayFractionHistPtr = 0; - for (int n = 0; n < kQueueDelayNormHistSize; n++) - queueDelayNormHist[n] = 0.0f; - queueDelayNormHistPtr = 0; - for (int n = 0; n < kBytesInFlightHistSizeMax; n++) { - bytesInFlightHistLo[n] = 0; - bytesInFlightHistHi[n] = 0; - } - bytesInFlightHistPtr = 0; - nStreams = 0; - for (int n = 0; n < kMaxStreams; n++) - streams[n] = NULL; - - queueDelayMax = 0.0f; - queueDelayMinAvg = 0.0f; - queueDelayMin = 1000.0; - - statistics = new Statistics(); -} - -ScreamTx::~ScreamTx() { - for (int n = 0; n < nStreams; n++) - delete streams[n]; - delete statistics; -} - -ScreamTx::Statistics::Statistics() { - sumRateTx = 0.0f; - sumRateLost = 0.0f; - avgRateTx = 0.0f; - avgRtt = 0.0f; - avgQueueDelay = 0.0f; - rateLostAcc = 0.0f; - rateLostN = 0; - for (int n = 0; n < kLossRateHistSize; n++) { - lossRateHist[n] = 0.0f; - } - lossRateHistPtr = 0; -} - -void ScreamTx::Statistics::add(float rateTx, float rateLost, float rtt, float queueDelay) { - const float alpha = 0.98f; - sumRateTx += rateTx; - sumRateLost += rateLost; - if (avgRateTx == 0.0f) { - avgRateTx = rateTx; - avgRtt = rtt; - avgQueueDelay = queueDelay; - } - else { - avgRateTx = alpha * avgRateTx + (1.0f - alpha)*rateTx; - rateLostAcc += rateLost; - rateLostN++; - if (rateLostN == 10) { - rateLostAcc /= 10; - rateLostN = 0; - float lossRate = 0.0f; - if (rateTx > 0) - lossRate = rateLostAcc / rateTx * 100.0f; - lossRateHist[lossRateHistPtr] = lossRate; - lossRateHistPtr = (lossRateHistPtr + 1) % kLossRateHistSize; - } - avgRtt = alpha * avgRtt + (1.0f - alpha)*rtt; - avgQueueDelay = alpha * avgQueueDelay + (1.0f - alpha)*queueDelay; - } -} - -void ScreamTx::Statistics::getSummary(float time, char s[]) { - float lossRate = 0.0f; - for (int n = 0; n < kLossRateHistSize; n++) - lossRate += lossRateHist[n]; - lossRate /= kLossRateHistSize; - float lossRateLong = 0.0f; - if (sumRateTx > 100000.0f) { - lossRateLong = sumRateLost / sumRateTx * 100.0f; - } - sprintf(s, "%5.1f Transmit rate = %5.0fkbps, PLR = %5.2f%%(%5.2f%%), RTT = %5.3fs, Queue delay = %5.3fs", - time, - avgRateTx / 1000.0f, - lossRate, - lossRateLong, - avgRtt, - avgQueueDelay); -} - -/* -* Register new stream -*/ -void ScreamTx::registerNewStream(RtpQueueIface *rtpQueue, - uint32_t ssrc, - float priority, - float minBitrate, - float startBitrate, - float maxBitrate, - float rampUpSpeed, - float rampUpScale, - float maxRtpQueueDelay, - float txQueueSizeFactor, - float queueDelayGuard, - float lossEventRateScale, - float ecnCeEventRateScale, - bool isAdaptiveTargetRateScale) { - Stream *stream = new Stream(this, - rtpQueue, - ssrc, - priority, - minBitrate, - startBitrate, - maxBitrate, - rampUpSpeed, - rampUpScale, - maxRtpQueueDelay, - txQueueSizeFactor, - queueDelayGuard, - lossEventRateScale, - ecnCeEventRateScale, - isAdaptiveTargetRateScale); - streams[nStreams++] = stream; -} - -void ScreamTx::updateBitrateStream(uint32_t ssrc, - float minBitrate, - float maxBitrate) { - int id; - Stream *stream = getStream(ssrc, id); - stream->minBitrate = minBitrate; - stream->maxBitrate = maxBitrate; -} - -RtpQueueIface * ScreamTx::getStreamQueue(uint32_t ssrc) { - int id; - Stream* stream = getStream(ssrc, id); - if (stream == 0) - return 0; - else - return stream->rtpQueue; -} - - -/* -* New media frame -*/ -void ScreamTx::newMediaFrame(uint32_t time_ntp, uint32_t ssrc, int bytesRtp) { - if (!isInitialized) initialize(time_ntp); - - int id; - Stream *stream = getStream(ssrc, id); - stream->updateTargetBitrate(time_ntp); - if (time_ntp - lastCwndUpdateT_ntp < 32768) { // 32768 = 0.5s in NTP domain - /* - * We expect feedback at least every 500ms - * to update the target rate. - */ - stream->updateTargetBitrate(time_ntp); - } - if (time_ntp - lastBaseDelayRefreshT_ntp < sRtt_ntp * 2) { - /* - * _Very_ long periods of congestion can cause the base delay to increase - * with the effect that the queue delay is estimated wrong, therefore we seek to - * refresh the whole thing by deliberately allowing the network queue to drain - * Clear the RTP queue for 2 RTTs, this will allow the queue to drain so that we - * get a good estimate for the min queue delay. - * This funtion is executed very seldom so it should not affect overall experience too much - */ - stream->rtpQueue->clear(); - } - else { - stream->bytesRtp += bytesRtp; - /* - * Need to update MSS here, otherwise it will be nearly impossible to - * transmit video packets, this because of the small initial MSS - * which is necessary to make SCReAM work with audio only - */ - int sizeOfNextRtp = stream->rtpQueue->sizeOfNextRtp(); - mss = std::max(mss, sizeOfNextRtp); - if (!openWindow) - cwndMin = std::max(cwndMinLow,2 * mss); - cwnd = max(cwnd, cwndMin); - } -} - -/* -* Determine if OK to transmit RTP packet -*/ -float ScreamTx::isOkToTransmit(uint32_t time_ntp, uint32_t &ssrc) { - if (!isInitialized) initialize(time_ntp); - /* - * Update rateTransmitted and rateAcked if time for it - * This is used in video rate computation - * The update interval is doubled at very low bitrates, - * the reason is that the feedback interval is very low then and - * a longer window is needed to avoid aliasing effects - */ - uint32_t tmp = kRateUpdateInterval_ntp; - - if (rateAcked < 50000.0f) { - tmp *= 2; - } - - if (time_ntp - lastRateUpdateT_ntp > tmp) { - rateTransmitted = 0.0f; - rateAcked = 0.0f; - float rateRtp = 0.0f; - for (int n = 0; n < nStreams; n++) { - streams[n]->updateRate(time_ntp); - rateTransmitted += streams[n]->rateTransmitted; - rateRtp += streams[n]->rateRtp; - rateTransmittedAvg = 0.9f*rateTransmittedAvg + 0.1f*rateTransmitted; - rateAcked += streams[n]->rateAcked; - if (n == 0) - statistics->add(streams[0]->rateTransmitted, streams[0]->rateLost, sRtt, queueDelay); - } - lastRateUpdateT_ntp = time_ntp; - /* - * Adjust stream priorities - */ - adjustPriorities(time_ntp); - - /* - * Update maxRate - */ - maxRate = 0.0f; - for (int n = 0; n < nStreams; n++) - maxRate += streams[n]->getMaxRate(); - - /* - * Updated target bitrates if total RTP bitrate - * exceeds maxTotalBitrate - */ - if (maxTotalBitrate > 0) { - float tmp = maxTotalBitrate * 0.9f; - if (rateRtp > tmp) { - inFastStart = false; - /* - * Use a little safety margin because the video coder - * can occasionally send at a higher bitrate than the target rate - */ - float delta = (rateRtp - tmp) / tmp; - delta /= kRateUpDateSize; - /* - * The target bitrate for each stream should be adjusted down by the same fraction - */ - for (int n = 0; n < nStreams; n++) { - Stream* stream = streams[n]; - stream->targetBitrate *= (1.0f - delta); - stream->targetBitrateI = streams[n]->targetBitrate; - stream->lastBitrateAdjustT_ntp = time_ntp; - } - } - } - } - - /* - * Get index to the prioritized RTP queue - */ - Stream* stream = getPrioritizedStream(time_ntp); - - if (stream == NULL) - /* - * No RTP packets to transmit - */ - return -1.0f; - ssrc = stream->ssrc; - - bytesInFlightMaxHi = std::max(bytesInFlight, bytesInFlightMaxHi); - - /* - * Update bytes in flight history for congestion window validation - */ - if (time_ntp - lastBytesInFlightT_ntp > kBytesInFlightHistInterval_ntp) { - bytesInFlightMaxLo = 0; - if (nAccBytesInFlightMax > 0) { - bytesInFlightMaxLo = accBytesInFlightMax / nAccBytesInFlightMax; - } - bytesInFlightHistLo[bytesInFlightHistPtr] = bytesInFlightMaxLo; - bytesInFlightHistHi[bytesInFlightHistPtr] = bytesInFlightMaxHi; - bytesInFlightHistPtr = (bytesInFlightHistPtr + 1) % bytesInFlightHistSize; - lastBytesInFlightT_ntp = time_ntp; - accBytesInFlightMax = 0; - nAccBytesInFlightMax = 0; - bytesInFlightMaxHi = 0; - bytesInFlightHistLoMem = 0; - bytesInFlightHistHiMem = 0; - for (int n = 0; n < bytesInFlightHistSize; n++) { - bytesInFlightHistLoMem = std::max(bytesInFlightHistLoMem, bytesInFlightHistLo[n]); - bytesInFlightHistHiMem = std::max(bytesInFlightHistHiMem, bytesInFlightHistHi[n]); - } - - /* - * In addition, reset MSS, this is useful in case for instance - * a video stream is put on hold, leaving only audio packets to be - * transmitted - */ - mss = kInitMss; - if (!openWindow) - cwndMin = std::max(cwndMinLow,kMinCwndMss * mss); - cwnd = max(cwnd, cwndMin); - - /* - * Add a small clock drift compensation - * for the case that the receiver clock is faster - */ - clockDriftCompensation += clockDriftCompensationInc; - } - - int sizeOfNextRtp = stream->rtpQueue->sizeOfNextRtp(); - if (sizeOfNextRtp == -1) { - return -1.0f; - } - /* - * Determine if window is large enough to transmit - * an RTP packet - */ - bool exit = false; - if (queueDelay < queueDelayTarget) - exit = (bytesInFlight + sizeOfNextRtp) > cwnd + mss; - else - exit = (bytesInFlight + sizeOfNextRtp) > cwnd; - /* - * Enforce packet pacing - */ - float retVal = 0.0f; - uint32_t tmp_l = nextTransmitT_ntp - time_ntp; - if (kEnablePacketPacing && (nextTransmitT_ntp > time_ntp) && (tmp_l < 0xFFFF0000)) { - retVal = (nextTransmitT_ntp - time_ntp) * ntp2SecScaleFactor; - } - - /* - * A retransmission time out mechanism to avoid deadlock - */ - if (time_ntp - lastTransmitT_ntp > 32768 && lastTransmitT_ntp < time_ntp) { // 500ms in NTP domain - for (int n = 0; n < kMaxTxPackets; n++) { - stream->txPackets[n].isUsed = false; - } - bytesInFlight = 0; - exit = false; - retVal = 0.0f; - } - - if (!exit) { - /* - * Return value 0.0 = RTP packet can be immediately transmitted - */ - return retVal; - } - - return -1.0f; -} - -/* -* RTP packet transmitted -*/ -float ScreamTx::addTransmitted(uint32_t time_ntp, - uint32_t ssrc, - int size, - uint16_t seqNr, - bool isMark) { - if (!isInitialized) - initialize(time_ntp); - - - int id; - Stream *stream = getStream(ssrc, id); - - int ix = seqNr % kMaxTxPackets; - Transmitted *txPacket = &(stream->txPackets[ix]); - stream->hiSeqTx = seqNr; - txPacket->timeTx_ntp = time_ntp; - txPacket->size = size; - txPacket->seqNr = seqNr; - txPacket->isMark = isMark; - txPacket->isUsed = true; - txPacket->isAcked = false; - txPacket->isAfterReceivedEdge = false; - - /* - * Update bytesInFlight - */ - bytesInFlight += size; - bytesInFlightLog = std::max(bytesInFlightLog, bytesInFlight); - - stream->bytesTransmitted += size; - lastTransmitT_ntp = time_ntp; - stream->lastTransmitT_ntp = time_ntp; - /* - * Add credit to unserved streams - */ - addCredit(time_ntp, stream, size); - /* - * Reduce used credit for served stream - */ - subtractCredit(time_ntp, stream, size); - - /* - * Update MSS and cwndMin - */ - mss = std::max(mss, size); - if (!openWindow) - cwndMin = std::max(cwndMinLow,2 * mss); - cwnd = max(cwnd, cwndMin); - - /* - * Determine when next RTP packet can be transmitted - */ - if (kEnablePacketPacing) - nextTransmitT_ntp = time_ntp + paceInterval_ntp; - else - nextTransmitT_ntp = time_ntp; - return paceInterval; -} - -void ScreamTx::incomingStandardizedFeedback(uint32_t time_ntp, - unsigned char* buf, - int size) { - - if (!isInitialized) initialize(time_ntp); - int ptr = 2; - /* - * read length in 32bit words - */ - uint16_t length; - memcpy(&length, buf + ptr, 2); - length = ntohs(length); - ptr += 2; - /* - * read RTCP sender SSRC - */ - uint32_t ssrc_rtcp; - memcpy(&ssrc_rtcp, buf + ptr, 4); - ssrc_rtcp = ntohl(ssrc_rtcp); - ptr += 4; - /* - * read report timestamp, it is located at the very end - */ - uint32_t rts; - memcpy(&rts, buf + length * 4, 4); - rts = ntohl(rts); - //printf(" TS %X\n", rts); - - - while (ptr != size - 4) { - /* - * read RTP source (stream) SSRC - */ - uint32_t ssrc; - memcpy(&ssrc, buf + ptr, 4); - ssrc = ntohl(ssrc); - ptr += 4; - /* - * read begin_seq and end_seq - */ - uint16_t begin_seq, end_seq; - uint16_t num_reports; - memcpy(&begin_seq, buf + ptr, 2); - ptr += 2; - memcpy(&num_reports, buf + ptr, 2); - ptr += 2; - begin_seq = ntohs(begin_seq); - num_reports = ntohs(num_reports) + 1; - end_seq = begin_seq+num_reports-1; - - /* - * Validate RTCP feedback message - * Discard out of order RTCP feedback, - * they are quite rate but will mess up the entire - * feedback handling if they are not taken care of. - */ - int streamId = -1; - Stream *stream = getStream(ssrc, streamId); - if (stream == 0) { - /* - * Bogus RTCP?, the SSRC is wrong anyway, Skip - */ - return; - } - - uint16_t diff = end_seq - stream->hiSeqAck; - - if (diff > 65000 && stream->hiSeqAck != 0 && stream->timeTxAck_ntp != 0) { - /* - * Out of order received ACKs are ignored - */ - return; - } - - uint16_t N = end_seq - begin_seq; - - for (int n = 0; n <= N; n++) { - uint16_t sn = begin_seq + n; - uint16_t tmp_s; - memcpy(&tmp_s, buf + ptr, 2); - tmp_s = ntohs(tmp_s); - ptr += 2; - bool isRx = ((tmp_s & 0x8000) == 0x8000); - if (isRx) { - /* - * packet indicated as being received - */ - uint8_t ceBits = (tmp_s & 0x6FFF) >> 13; - uint32_t rxTime = (tmp_s & 0x1FFF) << 6; // Q10->Q16 - rxTime = rts - rxTime; - incomingStandardizedFeedback(time_ntp, streamId, rxTime, sn, ceBits, n == N); - } - } - /* - * Skip zeropadded two octets if odd number of octets reported for this SSRC - */ - if ((N + 1) % 2 == 1) { - ptr += 2; - } - } -} - -/* -* New incoming feedback -*/ -void ScreamTx::incomingStandardizedFeedback(uint32_t time_ntp, - int streamId, - uint32_t timestamp, - uint16_t seqNr, - uint8_t ceBits, - bool isLast) { - - Stream *stream = streams[streamId]; - accBytesInFlightMax += bytesInFlight; - nAccBytesInFlightMax++; - Transmitted *txPackets = stream->txPackets; - completeLogItem = false; - /* - * Mark received packets, given by the ACK vector - */ - bool isMark = false; - markAcked(time_ntp, txPackets, seqNr, timestamp, stream, ceBits, ecnCeMarkedBytesLog, isLast, isMark); - - /* - * Detect lost packets - */ - if (isUseExtraDetailedLog || isLast || isMark) { - detectLoss(time_ntp, txPackets, seqNr, stream); - } - - if (isLast) { - if (bytesMarkedThisRtt != 0 && time_ntp - lastLossEventT_ntp > sRtt_ntp) { - ecnCeEvent = true; - lastLossEventT_ntp = time_ntp; - } - if (isL4s) { - /* - * L4S mode compute a congestion scaling factor that is dependent on the fraction - * of ECN marked packets - */ - if (time_ntp - lastL4sAlphaUpdateT_ntp > sRtt_ntp) { - lastL4sAlphaUpdateT_ntp = time_ntp; - if (bytesDeliveredThisRtt > 0) { - float F = float(bytesMarkedThisRtt) / float(bytesDeliveredThisRtt); - float l4sG = kL4sG; - if (F > l4sAlpha) { - /* - * Adjust alpha a bit faster up than down - */ - l4sG = std::min(1.0f, l4sG*4.0f); - } - /* - * L4S alpha (backoff factor) is averaged and limited - * It makes sense to limit the backoff because - * 1) source is rate limited - * 2) delay estimation algorithm also works in parallel - * 3) L4S marking algorithm can lag behind a little and potentially overmark - */ - l4sAlpha = std::min(kL4sAlphaMax, l4sG * F + (1.0f - l4sG)*l4sAlpha); - bytesDeliveredThisRtt = 0; - bytesMarkedThisRtt = 0; - } - } - } - - if (lossEvent || ecnCeEvent) { - lastLossEventT_ntp = time_ntp; - for (int n = 0; n < nStreams; n++) { - Stream *tmp = streams[n]; - if (lossEvent) - tmp->lossEventFlag = true; - else - tmp->ecnCeEventFlag = true; - } - } - - if (lastCwndUpdateT_ntp == 0) - lastCwndUpdateT_ntp = time_ntp; - - if (time_ntp - lastCwndUpdateT_ntp > 655) { // 10ms in NTP domain - /* - * There is no gain with a too frequent CWND update - * An update every 10ms is fast enough even at very high high bitrates - */ - updateCwnd(time_ntp); - lastCwndUpdateT_ntp = time_ntp; - } - - } - if (isUseExtraDetailedLog || isLast || isMark) { - - if (fp_log && completeLogItem) { - fprintf(fp_log, " %d,%d,%d,%1.0f,%d,%d,%d,%d,%1.0f,%1.0f,%1.0f,%1.0f,%1.0f,%d", cwnd, bytesInFlight, inFastStart, rateTransmitted, streamId, seqNr, bytesNewlyAckedLog, ecnCeMarkedBytesLog, stream->rateRtp, stream->rateTransmitted, stream->rateAcked, stream->rateLost, stream->rateCe,isMark); - if (strlen(detailedLogExtraData) > 0) { - fprintf(fp_log, ",%s", detailedLogExtraData); - } - bytesNewlyAckedLog = 0; - ecnCeMarkedBytesLog = 0; - } - if (fp_log && completeLogItem) { - fprintf(fp_log, "\n"); - } - } - -} - -/* -* Mark ACKed RTP packets -*/ -void ScreamTx::markAcked(uint32_t time_ntp, - struct Transmitted *txPackets, - uint16_t seqNr, - uint32_t timestamp, - Stream *stream, - uint8_t ceBits, - int &encCeMarkedBytesLog, - bool isLast, - bool &isMark) { - - int ix = seqNr % kMaxTxPackets; - if (txPackets[ix].isUsed) { - /* - * RTP packet is in flight - */ - Transmitted *tmp = &txPackets[ix]; - - /* - * Receiption of packet given by seqNr - */ - if ((tmp->seqNr == seqNr) & !tmp->isAcked) { - bytesDeliveredThisRtt += tmp->size; - isMark = tmp->isMark; - if (ceBits == 0x03) { - /* - * Packet was CE marked, increase counter - */ - encCeMarkedBytesLog += tmp->size; - bytesMarkedThisRtt += tmp->size; - stream->bytesCe += tmp->size; - } - tmp->isAcked = true; - ackedOwd = timestamp - tmp->timeTx_ntp; - - /* - * Compute the queue delay i NTP domain (Q16) - */ - estimateOwd(time_ntp); - /* - * Small compensation for positive clock drift - */ - ackedOwd -= clockDriftCompensation; - - uint32_t qDel = ackedOwd - getBaseOwd(); - - if (qDel > 0xFFFF0000 && clockDriftCompensation != 0) { - /* - * We have the case that the clock drift compensation is too large as it gives negative queue delays - * reduce the clock drift compensation and restore qDel to 0 - */ - uint32_t diff = 0 - qDel; - clockDriftCompensation -= diff; - qDel = 0; - } - - if (qDel > 0xFFFF0000 || clockDriftCompensation > 0xFFFF0000) { - /* - * TX and RX clock diff made qDel wrap around - * reset history - */ - clockDriftCompensation = 0; - clockDriftCompensationInc = 0; - queueDelayMinAvg = 0.0f; - queueDelay = 0.0f; - for (int n = 0; n < kBaseOwdHistSize; n++) - baseOwdHist[n] = UINT32_MAX; - baseOwd = UINT32_MAX; - baseOwdHistMin = UINT32_MAX; - baseOwdResetT_ntp = time_ntp; - qDel = 0; - } - - /* - * Convert from NTP domain OWD to an OWD in [s] - */ - queueDelay = qDel * ntp2SecScaleFactor; - - uint32_t rtt = time_ntp - tmp->timeTx_ntp; - - if (fp_log && (isUseExtraDetailedLog || isLast || isMark)) { - fprintf(fp_log, "%s,%1.4f,%1.4f,", timeString, queueDelay, rtt*ntp2SecScaleFactor); - completeLogItem = true; - } - if (rtt < 1000000 && isLast) { - sRttSh_ntp = (7 * sRttSh_ntp + rtt) / 8; - if (time_ntp - lastSRttUpdateT_ntp > sRttSh_ntp) { - sRtt_ntp = (7 * sRtt_ntp + sRttSh_ntp) / 8; - lastSRttUpdateT_ntp = time_ntp; - sRtt = sRtt_ntp * ntp2SecScaleFactor; - } - } - stream->timeTxAck_ntp = tmp->timeTx_ntp; - } - } -} - -/* -* Detect lost RTP packets -*/ -void ScreamTx::detectLoss(uint32_t time_ntp, struct Transmitted *txPackets, uint16_t highestSeqNr, Stream *stream) { - /* - * Loop only through the packets that are covered by the last highest ACK, this saves complexity - * There is a faint possibility that we miss to detect large bursts of lost packets with this fix - */ - int ix1 = highestSeqNr; ix1 = ix1 % kMaxTxPackets; - int ix0 = stream->hiSeqAck - 256; - stream->hiSeqAck = highestSeqNr; - if (ix0 < 0) ix0 += kMaxTxPackets; - while (ix1 < ix0) - ix1 += kMaxTxPackets; - - /* - * Mark late packets as lost - */ - for (int m = ix0; m <= ix1; m++) { - int n = m % kMaxTxPackets; - /* - * Loop through TX packets - */ - if (txPackets[n].isUsed) { - Transmitted *tmp = &txPackets[n]; - /* - * RTP packet is in flight - */ - /* - * Wrap-around safety net - */ - uint32_t seqNrExt = tmp->seqNr; - uint32_t highestSeqNrExt = highestSeqNr; - if (seqNrExt < highestSeqNrExt && highestSeqNrExt - seqNrExt > 20000) - seqNrExt += 65536; - else if (seqNrExt > highestSeqNrExt && seqNrExt - highestSeqNrExt > 20000) - highestSeqNrExt += 65536; - - /* - * RTP packets with a sequence number lower - * than or equal to the highest received sequence number - * are treated as received even though they are not - * This advances the send window, similar to what - * SACK does in TCP - */ - if (seqNrExt <= highestSeqNrExt && tmp->isAfterReceivedEdge == false) { - bytesNewlyAcked += tmp->size; - bytesNewlyAckedLog += tmp->size; - bytesInFlight -= tmp->size; - if (bytesInFlight < 0) - bytesInFlight = 0; - stream->bytesAcked += tmp->size; - tmp->isAfterReceivedEdge = true; - } - - if (tmp->timeTx_ntp + kReordertime_ntp < stream->timeTxAck_ntp && !tmp->isAcked) { - /* - * Packet ACK is delayed more than kReordertime_ntp after an ACK of a higher SN packet - * raise a loss event and remove from TX list - */ - if (time_ntp - lastLossEventT_ntp > sRtt_ntp && lossBeta < 1.0f) { - lossEvent = true; - } - stream->bytesLost += tmp->size; - tmp->isUsed = false; - cerr << " LOSS detected by reorder timer SSRC=" << stream->ssrc << " SN=" << tmp->seqNr << endl; - stream->repairLoss = true; - } - else if (tmp->isAcked) { - tmp->isUsed = false; - } - } - } -} - -float ScreamTx::getTargetBitrate(uint32_t ssrc) { - int id; - return getStream(ssrc, id)->getTargetBitrate(); -} - -void ScreamTx::setTargetPriority(uint32_t ssrc, float priority) { - int id; - Stream *stream = getStream(ssrc, id); - if (queueDelayFractionAvg > 0.1 || !inFastStart) { - stream->targetBitrate *= priority / stream->targetPriority; - stream->targetBitrate = std::min(std::max(stream->targetBitrate, stream->minBitrate), stream->maxBitrate); - } - stream->targetPriority = priority; - stream->targetPriorityInv = 1.0f / priority; -} - -void ScreamTx::getLog(float time, char *s) { - int inFlightMax = std::max(bytesInFlight, bytesInFlightHistHiMem); - sprintf(s, "%4.3f, %4.3f, %4.3f, %4.3f, %6d, %6d, %6.0f, %1d, ", - queueDelay, queueDelayMax, queueDelayMinAvg, sRtt, - cwnd, bytesInFlightLog, rateTransmitted / 1000.0f, isInFastStart()); - bytesInFlightLog = bytesInFlight; - queueDelayMax = 0.0; - for (int n = 0; n < nStreams; n++) { - Stream *tmp = streams[n]; - char s2[200]; - sprintf(s2, "%4.3f, %6.0f, %6.0f, %6.0f, %6.0f, %5.0f, %5.0f, %5d, ", - std::max(0.0f, tmp->rtpQueue->getDelay(time)), - tmp->targetBitrate / 1000.0f, tmp->rateRtp / 1000.0f, - tmp->rateTransmitted / 1000.0f, tmp->rateAcked / 1000.0f, - tmp->rateLost / 1000.0f, tmp->rateCe / 1000.0f, - tmp->hiSeqAck); - strcat(s, s2); - } -} - -void ScreamTx::getShortLog(float time, char *s) { - int inFlightMax = std::max(bytesInFlight, bytesInFlightHistHiMem); - sprintf(s, "%4.3f, %4.3f, %6d, %6d, %6.0f, %1d, ", - queueDelay, sRtt, - cwnd, bytesInFlightLog, rateTransmitted / 1000.0f, isInFastStart()); - bytesInFlightLog = bytesInFlight; - queueDelayMax = 0.0; - for (int n = 0; n < nStreams; n++) { - Stream *tmp = streams[n]; - char s2[200]; - sprintf(s2, "%4.3f, %6.0f, %6.0f, %6.0f, %5.0f, %5.0f, ", - std::max(0.0f, tmp->rtpQueue->getDelay(time)), - tmp->targetBitrate / 1000.0f, tmp->rateRtp / 1000.0f, - tmp->rateTransmitted / 1000.0f, - tmp->rateLost / 1000.0f, tmp->rateCe / 1000.0f); - strcat(s, s2); - } -} - -void ScreamTx::getVeryShortLog(float time, char *s) { - int inFlightMax = std::max(bytesInFlight, bytesInFlightHistHiMem); - sprintf(s, "%4.3f, %4.3f, %6d, %6d, %6.0f, ", - queueDelay, sRtt, - cwnd, bytesInFlightLog, rateTransmitted / 1000.0f); - bytesInFlightLog = bytesInFlight; - queueDelayMax = 0.0; - for (int n = 0; n < 1; n++) { - Stream *tmp = streams[n]; - char s2[200]; - sprintf(s2, "%5.0f, %5.0f, ", - tmp->rateLost / 1000.0f, tmp->rateCe / 1000.0f); - strcat(s, s2); - } -} -void ScreamTx::getStatistics(float time, char *s) { - statistics->getSummary(time, s); -} - -void ScreamTx::initialize(uint32_t time_ntp) { - isInitialized = true; - lastSRttUpdateT_ntp = time_ntp; - lastBaseOwdAddT_ntp = time_ntp; - baseOwdResetT_ntp = time_ntp; - lastAddToQueueDelayFractionHistT_ntp = time_ntp; - lastLossEventT_ntp = time_ntp; - lastTransmitT_ntp = time_ntp; - nextTransmitT_ntp = time_ntp; - lastRateUpdateT_ntp = time_ntp; - lastAdjustPrioritiesT_ntp = time_ntp; - lastRttT_ntp = time_ntp; - lastBaseDelayRefreshT_ntp = time_ntp - 1; - lastL4sAlphaUpdateT_ntp = time_ntp; - initTime_ntp = time_ntp; -} - -float ScreamTx::getTotalTargetBitrate() { - float totalTargetBitrate = 0.0f; - for (int n = 0; n < nStreams; n++) { - totalTargetBitrate += streams[n]->targetBitrate; - } - return totalTargetBitrate; -} - -/* -* Update the congestion window -*/ -void ScreamTx::updateCwnd(uint32_t time_ntp) { - float dT = 0.001f; - if (lastCwndUpdateT_ntp == 0) - lastCwndUpdateT_ntp = time_ntp; - else - dT = (time_ntp - lastCwndUpdateT_ntp) * ntp2SecScaleFactor; - /* - * This adds a limit to how much the CWND can grow, it is particularly useful - * in case of short gliches in the transmission, in which a large chunk of data is delivered - * in a burst with the effect that the estimated queue delay is low because it typically depict the queue - * delay for the last non-delayed RTP packet. The rule is that the CWND cannot grow faster - * than the 1.25 times the average amount of bytes that transmitted in the given feedback interval - */ - float bytesNewlyAckedLimited = float(bytesNewlyAcked); - if (maxRate > 1.0e5f) - bytesNewlyAckedLimited = std::min(bytesNewlyAckedLimited, 1.25f*maxRate*dT / 8.0f); - else - bytesNewlyAckedLimited = 2.0f*mss; - - queueDelayMin = std::min(queueDelayMin, queueDelay); - - queueDelayMax = std::max(queueDelayMax, queueDelay); - - float time = time_ntp * ntp2SecScaleFactor; - if (queueDelayMinAvg > 0.25f*queueDelayTarget && time_ntp - baseOwdResetT_ntp > 1310720) { // 20s in NTP domain - /* - * The base OWD is likely wrong, for instance due to - * a channel change or clock drift, reset base OWD history - */ - clockDriftCompensation = 0; - clockDriftCompensationInc = 0; - queueDelayMinAvg = 0.0f; - queueDelay = 0.0f; - for (int n = 0; n < kBaseOwdHistSize; n++) - baseOwdHist[n] = UINT32_MAX; - baseOwd = UINT32_MAX; - baseOwdHistMin = UINT32_MAX; - baseOwdResetT_ntp = time_ntp; - } - /* - * An averaged version of the queue delay fraction - * neceassary in order to make video rate control robust - * against jitter - */ - queueDelayFractionAvg = 0.9f*queueDelayFractionAvg + 0.1f*getQueueDelayFraction(); - - /* - * Less frequent updates here - * + Compute pacing interval - * + Save to queue delay fraction history - * used in computeQueueDelayTrend() - * + Update queueDelayTarget - */ - if ((time_ntp - lastAddToQueueDelayFractionHistT_ntp) > - kQueueDelayFractionHistInterval_us) { - - /* - * compute paceInterval, we assume a min bw of 50kbps and a min tp of 1ms - * for stable operation - * this function implements the packet pacing - */ - paceInterval = kMinPaceInterval; - if ((queueDelayFractionAvg > 0.02f || isL4s || maxTotalBitrate > 0) && kEnablePacketPacing) { - float pacingBitrate = std::max(1.0e5f, packetPacingHeadroom*getTotalTargetBitrate()); - if (maxTotalBitrate > 0) { - pacingBitrate = std::min(pacingBitrate, maxTotalBitrate); - } - float tp = (mss * 8.0f) / pacingBitrate; - paceInterval = std::max(kMinPaceInterval, tp); - } - paceInterval_ntp = (uint32_t)(paceInterval * 65536); // paceinterval converted to NTP domain (Q16) - - if (queueDelayMin < queueDelayMinAvg) - queueDelayMinAvg = queueDelayMin; - else - queueDelayMinAvg = 0.001f*queueDelayMin + 0.999f*queueDelayMinAvg; - queueDelayMin = 1000.0f; - /* - * Need to duplicate insertion incase the feedback is sparse - */ - int nIter = (int)(time_ntp - lastAddToQueueDelayFractionHistT_ntp) / kQueueDelayFractionHistInterval_us; - for (int n = 0; n < nIter; n++) { - queueDelayFractionHist[queueDelayFractionHistPtr] = getQueueDelayFraction(); - queueDelayFractionHistPtr = (queueDelayFractionHistPtr + 1) % kQueueDelayFractionHistSize; - } - - if (time_ntp - initTime_ntp > 131072) { // 2s in NTP domain - /* - * Queue delay trend calculations are reliable after ~2s - */ - computeQueueDelayTrend(); - } - - queueDelayTrendMem = std::max(queueDelayTrendMem*0.98f, queueDelayTrend); - - /* - * Compute bytes in flight limitation - */ - int maxBytesInFlightHi = (int)(std::max(bytesInFlightMaxHi, bytesInFlightHistHiMem)); - int maxBytesInFlightLo = (int)(std::max(bytesInFlight, bytesInFlightHistLoMem)); - - maxBytesInFlight = - (maxBytesInFlightHi*(1.0f - queueDelayTrend) + maxBytesInFlightLo * queueDelayTrend)* - kMaxBytesInFlightHeadRoom; - if (enableSbd) { - /* - * Shared bottleneck detection, - */ - float queueDelayNorm = queueDelay / queueDelayTargetMin; - queueDelayNormHist[queueDelayNormHistPtr] = queueDelayNorm; - queueDelayNormHistPtr = (queueDelayNormHistPtr + 1) % kQueueDelayNormHistSize; - /* - * Compute shared bottleneck detection and update queue delay target - * if queue dela variance is sufficienctly low - */ - computeSbd(); - /* - * This function avoids the adjustment of queueDelayTarget when - * congestion occurs (indicated by high queueDelaydSbdVar and queueDelaySbdSkew) - */ - float oh = queueDelayTargetMin * (queueDelaySbdMeanSh + sqrt(queueDelaySbdVar)); - if (lossEventRate > 0.002 && queueDelaySbdMeanSh > 0.5 && queueDelaySbdVar < 0.2) { - queueDelayTarget = std::min(kQueueDelayTargetMax, oh*1.5f); - } - else { - if (queueDelaySbdVar < 0.2 && queueDelaySbdSkew < 0.05) { - queueDelayTarget = std::max(queueDelayTargetMin, std::min(kQueueDelayTargetMax, oh)); - } - else { - if (oh < queueDelayTarget) - queueDelayTarget = std::max(queueDelayTargetMin, std::max(queueDelayTarget*0.99f, oh)); - else - queueDelayTarget = std::max(queueDelayTargetMin, queueDelayTarget*0.999f); - } - } - } - lastAddToQueueDelayFractionHistT_ntp = time_ntp; - } - /* - * offTarget is a normalized deviation from the queue delay target - */ - float offTarget = (queueDelayTarget - queueDelay) / float(queueDelayTarget); - - if (lossEvent) { - /* - * loss event detected, decrease congestion window - */ - cwnd = std::max(cwndMin, (int)(lossBeta*cwnd)); - lossEvent = false; - lastCongestionDetectedT_ntp = time_ntp; - - inFastStart = false; - wasLossEvent = true; - } - else if (ecnCeEvent) { - /* - * loss event detected, decrease congestion window - */ - if (isL4s) { - cwnd = std::max(cwndMin, (int)((1.0f - l4sAlpha / 2.0f)*cwnd)); - } - else { - cwnd = std::max(cwndMin, (int)(ecnCeBeta*cwnd)); - } - ecnCeEvent = false; - lastCongestionDetectedT_ntp = time_ntp; - - inFastStart = false; - wasLossEvent = true; - } - else { - - if (time_ntp - lastRttT_ntp > sRtt_ntp) { - if (wasLossEvent) - lossEventRate = 0.99f*lossEventRate + 0.01f; - else - lossEventRate *= 0.99f; - wasLossEvent = false; - lastRttT_ntp = time_ntp; - } - - if (inFastStart) { - /* - * In fast start - */ - if (queueDelayTrend < 0.2) { - /* - * CWND is increased by the number of ACKed bytes if - * window is used to 1/1.5 = 67% - * We need to relax the rule a bit for the case that - * feedback may be sparse due to limited RTCP report interval - * In addition we put a limit for the cases where feedback becomes - * piled up (sometimes happens with e.g LTE) - */ - float bytesInFlightMargin = 1.5f; - if (bytesInFlight*bytesInFlightMargin + bytesNewlyAcked > cwnd) { - cwnd += int(bytesNewlyAckedLimited); - } - } - else { - inFastStart = false; - } - } - else { - if (offTarget > 0.0f) { - /* - * queue delay below target, increase CWND if window - * is used to 1/1.2 = 83% - */ - float bytesInFlightMargin = 1.2f; - if (bytesInFlight*bytesInFlightMargin + bytesNewlyAcked > cwnd) { - float increment = gainUp * offTarget * bytesNewlyAckedLimited * mss / cwnd; - cwnd += (int)(increment + 0.5f); - } - } - else { - if (true) { - /* - * Queue delay above target. - * Limit the CWND reduction to at most a quarter window - * this avoids unduly large reductions for the cases - * where data is queued up e.g because of retransmissions - * on lower protocol layers. - */ - float delta = -(gainDown * offTarget * bytesNewlyAcked * mss / cwnd); - delta = std::min(delta, cwnd / 4.0f); - cwnd -= (int)(delta); - - /* - * Especially when running over low bitrate bottlenecks, it is - * beneficial to reduce the target bitrate a little, it limits - * possible RTP queue delay spikes when congestion window is reduced - */ - float rateTotal = 0.0f; - for (int n = 0; n < nStreams; n++) - rateTotal += streams[n]->getMaxRate(); - if (rateTotal < 1.0e5f) { - delta = delta / cwnd; - float rateAdjustFactor = (1.0f - delta); - for (int n = 0; n < nStreams; n++) { - Stream *tmp = streams[n]; - tmp->targetBitrate = std::max(tmp->minBitrate, - std::min(tmp->maxBitrate, - tmp->targetBitrate*rateAdjustFactor)); - } - } - } - lastCongestionDetectedT_ntp = time_ntp; - } - } - } - /* - * Congestion window validation, checks that the congestion window is - * not considerably higher than the actual number of bytes in flight - */ - if (maxBytesInFlight > 5000) { - cwnd = std::min(cwnd, (int)maxBytesInFlight); - } - - if (sRtt < 0.01f && queueDelayTrend < 0.1) { - int tmp = int(rateTransmitted*0.01f / 8); - tmp = std::max(tmp, (int)(maxBytesInFlight*1.5f)); - cwnd = std::max(cwnd, tmp); - } - cwnd = std::max(cwndMin, cwnd); - - /* - * Make possible to enter fast start if OWD has been low for a while - */ - if (queueDelayTrend > 0.2) { - lastCongestionDetectedT_ntp = time_ntp; - } - else if (time_ntp - lastCongestionDetectedT_ntp > 65536 && // 5s in NTP domain - !inFastStart && kEnableConsecutiveFastStart) { - /* - * The queue delay trend has been low for more than 1.0s, resume fast start - */ - inFastStart = true; - lastCongestionDetectedT_ntp = time_ntp; - } - bytesNewlyAcked = 0; -} - -/* -* Update base OWD (if needed) and return the -* last estimated OWD (without offset compensation) -*/ -void ScreamTx::estimateOwd(uint32_t time_ntp) { - baseOwd = std::min(baseOwd, ackedOwd); - if (time_ntp - lastBaseOwdAddT_ntp >= kBaseDelayUpdateInterval_ntp) { - if (enableClockDriftCompensation) { - /* - * The clock drift compensation looks at how the baseOwd increases over time - * and calculates a compensation factor that advanced the sender clock stamps - * Only positive drift (receiver side is faster) is compensated as negative - * drift is already handled - */ - int ptr = baseOwdHistPtr; - int k = 0; - uint32_t bw0 = UINT32_MAX; - for (k = 0; k < kBaseOwdHistSize - 1; k++) { - if (baseOwdHist[ptr] != UINT32_MAX) { - bw0 = baseOwdHist[ptr]; - ptr = ptr - 1; - if (ptr < 0) ptr += kBaseOwdHistSize; - } - else - break; - } - - uint32_t bw1 = baseOwd; - if (k > 0) { - if ((bw1 > bw0)) - clockDriftCompensationInc = (bw1 - bw0) / (kBaseDelayUpdateInterval_ntp / 65536 * k); - else - clockDriftCompensationInc = 0; - clockDriftCompensationInc = std::min(kMaxClockdriftCompensation, clockDriftCompensationInc); - if (clockDriftCompensation == 0) { - /* - * Two update periods delayed with compensation so we add one update period worth of - * clock drift compensation - */ - clockDriftCompensation += clockDriftCompensationInc * (kBaseDelayUpdateInterval_ntp / 65536); - } - } - } - baseOwdHistPtr = (baseOwdHistPtr + 1) % kBaseOwdHistSize; - baseOwdHist[baseOwdHistPtr] = baseOwd; - lastBaseOwdAddT_ntp = time_ntp; - baseOwd = UINT32_MAX; - baseOwdHistMin = UINT32_MAX; - for (int n = 0; n < kBaseOwdHistSize; n++) - baseOwdHistMin = std::min(baseOwdHistMin, baseOwdHist[n]); - /* - * _Very_ long periods of congestion can cause the base delay to increase - * with the effect that the queue delay is estimated wrong, therefore we seek to - * refresh the whole thing by deliberately allowing the network queue to drain - */ - if (time_ntp - lastBaseDelayRefreshT_ntp > kBaseDelayUpdateInterval_ntp*(kBaseOwdHistSize - 1)) { - lastBaseDelayRefreshT_ntp = time_ntp; - } - } -} - -/* -* Get the base one way delay -*/ -uint32_t ScreamTx::getBaseOwd() { - return std::min(baseOwd, baseOwdHistMin); -} - -/* -* Get the queue delay fraction -*/ -float ScreamTx::getQueueDelayFraction() { - return queueDelay / queueDelayTarget; -} - -/* -* Compute congestion indicator -*/ -void ScreamTx::computeQueueDelayTrend() { - queueDelayTrend = 0.0; - int ptr = queueDelayFractionHistPtr; - float avg = 0.0f, x1, x2, a0, a1; - - for (int n = 0; n < kQueueDelayFractionHistSize; n++) { - avg += queueDelayFractionHist[ptr]; - ptr = (ptr + 1) % kQueueDelayFractionHistSize; - } - avg /= kQueueDelayFractionHistSize; - - ptr = queueDelayFractionHistPtr; - x2 = 0.0f; - a0 = 0.0f; - a1 = 0.0f; - for (int n = 0; n < kQueueDelayFractionHistSize; n++) { - x1 = queueDelayFractionHist[ptr] - avg; - a0 += x1 * x1; - a1 += x1 * x2; - x2 = x1; - ptr = (ptr + 1) % kQueueDelayFractionHistSize; - } - if (a0 > 0) { - queueDelayTrend = std::max(0.0f, std::min(1.0f, (a1 / a0)*queueDelayFractionAvg)); - } -} - -/* -* Compute indicators of shared bottleneck -*/ -void ScreamTx::computeSbd() { - float queueDelayNorm, tmp; - queueDelaySbdMean = 0.0; - queueDelaySbdMeanSh = 0.0; - queueDelaySbdVar = 0.0; - int ptr = queueDelayNormHistPtr; - for (int n = 0; n < kQueueDelayNormHistSize; n++) { - queueDelayNorm = queueDelayNormHist[ptr]; - queueDelaySbdMean += queueDelayNorm; - if (n >= kQueueDelayNormHistSize - kQueueDelayNormHistSizeSh) { - queueDelaySbdMeanSh += queueDelayNorm; - } - ptr = (ptr + 1) % kQueueDelayNormHistSize; - } - queueDelaySbdMean /= kQueueDelayNormHistSize; - queueDelaySbdMeanSh /= kQueueDelayNormHistSizeSh; - - ptr = queueDelayNormHistPtr; - for (int n = 0; n < kQueueDelayNormHistSize; n++) { - queueDelayNorm = queueDelayNormHist[ptr]; - tmp = queueDelayNorm - queueDelaySbdMean; - queueDelaySbdVar += tmp * tmp; - queueDelaySbdSkew += tmp * tmp * tmp; - ptr = (ptr + 1) % kQueueDelayNormHistSize; - } - queueDelaySbdVar /= kQueueDelayNormHistSize; - queueDelaySbdSkew /= kQueueDelayNormHistSize; -} - -/* -* true if the queueDelayTarget is increased due to -* detected competing flows -*/ -bool ScreamTx::isCompetingFlows() { - return queueDelayTarget > queueDelayTargetMin; -} - -/* -* Get queue delay trend -*/ -float ScreamTx::getQueueDelayTrend() { - return queueDelayTrend; -} - -/* -* Determine active streams -*/ -void ScreamTx::determineActiveStreams(uint32_t time_ntp) { - float surplusBitrate = 0.0f; - float sumPrio = 0.0; - bool streamSetInactive = false; - for (int n = 0; n < nStreams; n++) { - if (time_ntp - streams[n]->lastFrameT_ntp > 65536 && streams[n]->isActive) { // 1s in NTP domain - streams[n]->isActive = false; - surplusBitrate += streams[n]->targetBitrate; - streams[n]->targetBitrate = streams[n]->minBitrate; - streamSetInactive = true; - } - else { - sumPrio += streams[n]->targetPriority; - } - } - if (streamSetInactive) { - for (int n = 0; n < nStreams; n++) { - if (streams[n]->isActive) { - streams[n]->targetBitrate = std::min(streams[n]->maxBitrate, - streams[n]->targetBitrate + - surplusBitrate * streams[n]->targetPriority / sumPrio); - } - } - } -} - -/* -* Add credit to streams that was not served -*/ -void ScreamTx::addCredit(uint32_t time_ntp, Stream* servedStream, int transmittedBytes) { - /* - * Add a credit to stream(s) that did not get priority to transmit RTP packets - */ - if (nStreams == 1) - /* - * Skip if only one stream to save CPU - */ - return; - int maxCredit = 2 * mss; - for (int n = 0; n < nStreams; n++) { - Stream *tmp = streams[n]; - if (tmp != servedStream) { - int credit = (int)(transmittedBytes*tmp->targetPriority * servedStream->targetPriorityInv); - if (tmp->rtpQueue->sizeOfQueue() > 0) { - tmp->credit += credit; - } - else { - tmp->credit += credit; - if (tmp->credit > maxCredit) { - tmp->creditLost += tmp->credit - maxCredit; - tmp->credit = maxCredit; - } - } - } - } -} - -/* -* Subtract credit from served stream -*/ -void ScreamTx::subtractCredit(uint32_t time_ntp, Stream* servedStream, int transmittedBytes) { - /* - * Subtract a credit equal to the number of transmitted bytes from the stream that - * transmitted a packet - */ - if (nStreams == 1) - /* - * Skip if only one stream to save CPU - */ - return; - servedStream->credit = std::max(0, servedStream->credit - transmittedBytes); -} - -/* -* Adjust (enforce) proper prioritization between active streams -* at regular intervals. This is a necessary addon to mitigate -* issues that VBR media brings -* The function consists of equal measures or rational thinking and -* black magic, which means that there is no 100% guarantee that -* will always work. -*/ -void ScreamTx::adjustPriorities(uint32_t time_ntp) { - if (nStreams == 1 || time_ntp - lastAdjustPrioritiesT_ntp < 65536) { // 1s in NTP domain - /* - * Skip if only one stream or if adjustment done less than 5s ago - */ - return; - } - - if (queueDelayTrend > 0.02) { - /* - * Adjust only if there is some evidence of congestion - */ - int avgCreditLost = 0; - int avgCreditLostN = 0; - for (int n = 0; n < nStreams; n++) { - avgCreditLost += streams[n]->creditLost; - if (streams[n]->isActive) - avgCreditLostN++; - } - if (avgCreditLostN <= 1) { - /* - * At the most 1 steam active, skip adjustment - */ - return; - } - - avgCreditLost /= avgCreditLostN; - for (int n = 0; n < nStreams; n++) { - if (streams[n]->isActive) { - if (streams[n]->creditLost < avgCreditLost && - streams[n]->targetBitrate > streams[n]->rateRtp) { - /* - * Stream is using more of its share than the average - * bitrate is likelky too high, reduce target bitrate - * This algorithm works best when we want to ensure - * different priorities - */ - streams[n]->targetBitrate = std::max(streams[n]->minBitrate, streams[n]->targetBitrate*0.9f); - } - } - } - - for (int n = 0; n < nStreams; n++) - streams[n]->creditLost = 0; - - - lastAdjustPrioritiesT_ntp = time_ntp; - - } - if (time_ntp - lastAdjustPrioritiesT_ntp < 1310720) { // 20s in NTP domain - /* - * Clear old statistics of unused credits - */ - for (int n = 0; n < nStreams; n++) - streams[n]->creditLost = 0; - - - lastAdjustPrioritiesT_ntp = time_ntp; - } -} - -/* -* Get the prioritized stream -*/ -ScreamTx::Stream* ScreamTx::getPrioritizedStream(uint32_t time_ntp) { - /* - * Function that prioritizes between streams, this function may need - * to be modified to handle the prioritization better for e.g - * FEC, SVC etc. - */ - if (nStreams == 1) - /* - * Skip if only one stream to save CPU - */ - return streams[0]; - - int maxCredit = 1; - Stream *stream = NULL; - /* - * Pick a stream with credit higher or equal to - * the size of the next RTP packet in queue for the given stream. - */ - uint32_t maxDiff = 0; - for (int n = 0; n < nStreams; n++) { - Stream *tmp = streams[n]; - if (tmp->rtpQueue->sizeOfQueue() == 0) { - /* - * Queue empty - */ - } - else { - uint32_t diff = time_ntp - tmp->lastTransmitT_ntp; - /* - * Pick stream if it has the highest credit so far - */ - if (tmp->credit >= maxCredit && diff > maxDiff) { - stream = tmp; - maxDiff = diff; - maxCredit = tmp->credit; - } - } - } - if (stream != NULL) { - return stream; - } - /* - * If the above doesn't give a candidate.. - * Pick the stream with the highest priority that also - * has at least one RTP packet in queue. - */ - float maxPrio = 0.0; - for (int n = 0; n < nStreams; n++) { - Stream *tmp = streams[n]; - float priority = tmp->targetPriority; - if (tmp->rtpQueue->sizeOfQueue() > 0 && priority > maxPrio) { - maxPrio = priority; - stream = tmp; - } - } - return stream; -} - -ScreamTx::Stream::Stream(ScreamTx *parent_, - RtpQueueIface *rtpQueue_, - uint32_t ssrc_, - float priority_, - float minBitrate_, - float startBitrate_, - float maxBitrate_, - float rampUpSpeed_, - float rampUpScale_, - float maxRtpQueueDelay_, - float txQueueSizeFactor_, - float queueDelayGuard_, - float lossEventRateScale_, - float ecnCeEventRateScale_, - bool isAdaptiveTargetRateScale_) { - parent = parent_; - rtpQueue = rtpQueue_; - ssrc = ssrc_; - targetPriority = priority_; - targetPriorityInv = 1.0f / targetPriority; - minBitrate = minBitrate_; - maxBitrate = maxBitrate_; - targetBitrate = std::min(maxBitrate, std::max(minBitrate, startBitrate_)); - rampUpSpeed = rampUpSpeed_; - rampUpScale = rampUpScale_; - maxRtpQueueDelay = maxRtpQueueDelay_; - txQueueSizeFactor = txQueueSizeFactor_; - queueDelayGuard = queueDelayGuard_; - lossEventRateScale = lossEventRateScale_; - ecnCeEventRateScale = ecnCeEventRateScale_; - isAdaptiveTargetRateScale = isAdaptiveTargetRateScale_; - targetBitrateHistUpdateT_ntp = 0; - targetBitrateI = 1.0f; - credit = 0; - creditLost = 0; - bytesTransmitted = 0; - bytesAcked = 0; - bytesLost = 0; - hiSeqAck = 0; - hiSeqTx = 0; - rateTransmitted = 0.0f; - rateAcked = 0.0f; - rateLost = 0.0f; - rateCe = 0.0; - lossEventFlag = false; - ecnCeEventFlag = false; - txSizeBitsAvg = 0.0f; - lastRateUpdateT_ntp = 0; - lastBitrateAdjustT_ntp = 0; - lastTargetBitrateIUpdateT_ntp = 0; - bytesRtp = 0; - rateRtp = 0.0f; - timeTxAck_ntp = 0; - lastTransmitT_ntp = 0; - - for (int n = 0; n < kRateUpDateSize; n++) { - rateRtpHist[n] = 0.0f; - rateAckedHist[n] = 0.0f; - rateLostHist[n] = 0.0f; - rateCeHist[n] = 0.0f; - rateTransmittedHist[n] = 0.0f; - } - rateUpdateHistPtr = 0; - for (int n = 0; n < kTargetBitrateHistSize; n++) { - targetBitrateHist[n] = 0; - } - targetBitrateHistPtr = 0; - targetRateScale = 1.0; - isActive = false; - lastFrameT_ntp = 0; - initTime_ntp = 0; - rtpQueueDiscard = false; - lastRtpQueueDiscardT_ntp = 0; - bytesLost = 0; - bytesCe = 0; - wasRepairLoss = false; - repairLoss = false; - for (int n = 0; n < kMaxTxPackets; n++) - txPackets[n].isUsed = false; - txPacketsPtr = 0; -} - -/* -* Update the estimated max media rate -*/ -void ScreamTx::Stream::updateRate(uint32_t time_ntp) { - if (lastRateUpdateT_ntp != 0) { - float tDelta = (time_ntp - lastRateUpdateT_ntp) * ntp2SecScaleFactor; - - rateTransmittedHist[rateUpdateHistPtr] = bytesTransmitted * 8.0f / tDelta; - rateAckedHist[rateUpdateHistPtr] = bytesAcked * 8.0f / tDelta; - rateLostHist[rateUpdateHistPtr] = bytesLost * 8.0f / tDelta; - rateCeHist[rateUpdateHistPtr] = bytesCe * 8.0f / tDelta; - rateRtpHist[rateUpdateHistPtr] = bytesRtp * 8.0f / tDelta; - rateUpdateHistPtr = (rateUpdateHistPtr + 1) % kRateUpDateSize; - rateTransmitted = 0.0f; - rateAcked = 0.0f; - rateLost = 0.0f; - rateCe = 0.0f; - rateRtp = 0.0f; - for (int n = 0; n < kRateUpDateSize; n++) { - rateTransmitted += rateTransmittedHist[n]; - rateAcked += rateAckedHist[n]; - rateLost += rateLostHist[n]; - rateCe += rateCeHist[n]; - rateRtp += rateRtpHist[n]; - } - rateTransmitted /= kRateUpDateSize; - rateAcked /= kRateUpDateSize; - rateLost /= kRateUpDateSize; - rateCe /= kRateUpDateSize; - rateRtp /= kRateUpDateSize; - if (rateRtp > 0 && isAdaptiveTargetRateScale) { - /* - * Video coders are strange animals.. In certain cases the average bitrate is - * consistently lower or higher than the target bitare. This additonal scaling compensates - * for this anomaly. - */ - - const float diff = targetBitrate / rateRtp; - float alpha = 0.02f; - if (diff < 0) - alpha = 1.0; - targetRateScale *= (1.0f - alpha); - targetRateScale += alpha * targetBitrate / rateRtp; - targetRateScale = std::min(1.1f, std::max(0.8f, targetRateScale)); - } - } - - bytesTransmitted = 0; - bytesAcked = 0; - bytesRtp = 0; - bytesLost = 0; - bytesCe = 0; - lastRateUpdateT_ntp = time_ntp; -} - -/* -* Get the estimated maximum media rate -*/ -float ScreamTx::Stream::getMaxRate() { - return std::max(rateTransmitted, rateAcked); -} - -/* -* The the stream that matches SSRC -*/ -ScreamTx::Stream* ScreamTx::getStream(uint32_t ssrc, int &streamId) { - for (int n = 0; n < nStreams; n++) { - if (streams[n]->isMatch(ssrc)) { - streamId = n; - return streams[n]; - } - } - streamId = -1; - return NULL; -} - -/* -* Get the target bitrate. -* This function returns a value -1 if loss of RTP packets is detected, -* either because of loss in network or RTP queue discard -*/ -float ScreamTx::Stream::getTargetBitrate() { - - bool requestRefresh = isRtpQueueDiscard() || repairLoss; - repairLoss = false; - if (requestRefresh && !wasRepairLoss) { - wasRepairLoss = true; - return -1.0; - } - float rate = targetRateScale * targetBitrate; - /* - * Video coders are strange animals.. In certain cases a very frequent rate requests can confuse the - * rate control logic in the coder - */ - wasRepairLoss = false; - return rate; -} - -/* -* A small history of past max bitrates is maintained and the max value is picked. -* This solves a problem where consequtive rate decreases can give too low -* targetBitrateI values. -*/ -void ScreamTx::Stream::updateTargetBitrateI(float br) { - targetBitrateHist[targetBitrateHistPtr] = std::min(br, targetBitrate); - targetBitrateHistPtr = (targetBitrateHistPtr + 1) % kTargetBitrateHistSize; - targetBitrateI = std::min(br, targetBitrate); - for (int n = 0; n < kTargetBitrateHistSize; n++) { - targetBitrateI = std::max(targetBitrateI, targetBitrateHist[n]); - } -} - -/* -* Update the target bitrate, the target bitrate includes the RTP overhead -*/ -void ScreamTx::Stream::updateTargetBitrate(uint32_t time_ntp) { - /* - * Compute a maximum bitrate, this bitrates includes the RTP overhead - */ - - float br = getMaxRate(); - float rateRtpLimit = br; - if (initTime_ntp == 0) { - /* - * Initialize if the first time - */ - initTime_ntp = time_ntp; - lastRtpQueueDiscardT_ntp = time_ntp; - } - - if (lastBitrateAdjustT_ntp == 0) lastBitrateAdjustT_ntp = time_ntp; - isActive = true; - lastFrameT_ntp = time_ntp; - if (lossEventFlag || ecnCeEventFlag) { - /* - * Loss event handling - * Rate is reduced slightly to avoid that more frames than necessary - * queue up in the sender queue - */ - if (time_ntp - lastTargetBitrateIUpdateT_ntp > 2000000) { - /* - * The timing constraint avoids that targetBitrateI - * is set too low in cases where a congestion event is prolonged. - * An accurate targetBitrateI is not of extreme importance - * but helps to avoid jitter spikes when SCReAM operates - * over fixed bandwidth or slowly varying links. - */ - updateTargetBitrateI(br); - lastTargetBitrateIUpdateT_ntp = time_ntp; - } - if (lossEventFlag) - targetBitrate = std::max(minBitrate, targetBitrate*lossEventRateScale); - else if (ecnCeEventFlag) { - if (parent->isL4s) { - targetBitrate = std::max(minBitrate, targetBitrate*(1.0f - parent->l4sAlpha / 4.0f)); - } - else { - targetBitrate = std::max(minBitrate, targetBitrate*ecnCeEventRateScale); - } - } - float rtpQueueDelay = rtpQueue->getDelay(time_ntp * ntp2SecScaleFactor); - if (rtpQueueDelay > maxRtpQueueDelay && - (time_ntp - lastRtpQueueDiscardT_ntp > kMinRtpQueueDiscardInterval_ntp)) { - /* - * RTP queue is cleared as it is becoming too large, - * Function is however disabled initially as there is no reliable estimate of the - * throughput in the initial phase. - */ - rtpQueue->clear(); - cerr << time_ntp / 65536.0f << " RTP queue discarded for SSRC " << ssrc << endl; - - rtpQueueDiscard = true; - - lastRtpQueueDiscardT_ntp = time_ntp; - targetRateScale = 1.0; - txSizeBitsAvg = 0.0f; - } - - lossEventFlag = false; - ecnCeEventFlag = false; - lastBitrateAdjustT_ntp = time_ntp; - } - else { - if (time_ntp - lastBitrateAdjustT_ntp < kRateAdjustInterval_ntp) - return; - /* - * A scale factor that is dependent on the inflection point - * i.e the last known highest video bitrate - */ - float sclI = (targetBitrate - targetBitrateI) / targetBitrateI; - sclI *= 4; - sclI = sclI*sclI; - sclI = std::max(0.05f, std::min(1.0f, sclI)); - float increment = 0.0f; - - /* - * Size of RTP queue [bits] - * As this function is called immediately after a - * video frame is produced, we need to accept the new - * RTP packets in the queue, we subtract a number of bytes correspoding to the size - * of the last frame (including RTP overhead), this is simply the aggregated size - * of the RTP packets with the highest RTP timestamp - * txSizeBits is the number of bits in the RTP queue, this is limited - * to just enable small adjustments of the bitrate when the RTP queue grows - */ - int lastBytes = rtpQueue->getSizeOfLastFrame(); - int txSizeBitsLimit = (int)(targetBitrate*0.02); - int txSizeBits = std::max(0, rtpQueue->bytesInQueue() - lastBytes) * 8; - txSizeBits = std::min(txSizeBits, txSizeBitsLimit); - - const float alpha = 0.5f; - - txSizeBitsAvg = txSizeBitsAvg * alpha + txSizeBits * (1.0f - alpha); - /* - * tmp is a local scaling factor that makes rate adaptation sligthly more - * aggressive when competing flows (e.g file transfers) are detected - */ - float rampUpSpeedTmp = std::min(rampUpSpeed, targetBitrate*rampUpScale); - if (parent->isCompetingFlows()) { - rampUpSpeedTmp *= 2.0f; - } - - float rtpQueueDelay = rtpQueue->getDelay(time_ntp * ntp2SecScaleFactor); - if (rtpQueueDelay > maxRtpQueueDelay && - (time_ntp - lastRtpQueueDiscardT_ntp > kMinRtpQueueDiscardInterval_ntp)) { - /* - * RTP queue is cleared as it is becoming too large, - * Function is however disabled initially as there is no reliable estimate of the - * throughput in the initial phase. - */ - rtpQueue->clear(); - cerr << time_ntp / 65536.0f << " RTP queue discarded for SSRC " << ssrc << endl; - - rtpQueueDiscard = true; - - lastRtpQueueDiscardT_ntp = time_ntp; - targetRateScale = 1.0; - txSizeBitsAvg = 0.0f; - } - else if (parent->inFastStart && rtpQueueDelay < 0.1f) { - /* - * Increment bitrate, limited by the rampUpSpeed - */ - increment = rampUpSpeedTmp * (kRateAdjustInterval_ntp * ntp2SecScaleFactor); - /* - * Limit increase rate near the last known highest bitrate or if priority is low - */ - increment *= sclI * sqrt(targetPriority); - /* - * Limited increase if the actual coder rate is lower than the target - */ - if (targetBitrate > rateRtpLimit) { - /* - * Limit increase if the target bitrate is considerably higher than the actual - * bitrate, this is an indication of an idle source. - * It can also be the case that the encoder consistently delivers a lower rate than - * the target rate. We don't want to deadlock the bitrate rampup because of this so - * we gradually reduce the increment the larger the difference is - */ - float scale = std::max(-1.0f, 2.0f*(rateRtpLimit / targetBitrate - 1.0f)); - increment *= (1.0 + scale); - } - /* - * Add increment - */ - targetBitrate += increment; - wasFastStart = true; - } - else { - if (wasFastStart) { - wasFastStart = false; - if (time_ntp - lastTargetBitrateIUpdateT_ntp > 65536) { // 1s in NTP domain - /* - * The timing constraint avoids that targetBitrateI - * is set too low in cases where a - * congestion event is prolonged - */ - updateTargetBitrateI(br); - lastTargetBitrateIUpdateT_ntp = time_ntp; - } - } - - /* - * Update target rate - * At very low bitrates it is necessary to actively try to push the - * the bitrate up some extra - */ - float incrementScale = 1.0f + 0.05f*std::min(1.0f, 50000.0f / targetBitrate); - - float increment = incrementScale * br; - if (!parent->isL4s || parent->l4sAlpha < 0.01) { - /* - * Apply the extra precaution with respect to queue delay and - * RTP queue only if L4S is not running or when ECN marking does not occur for a longer period - * scl is based on the queue delay trend - */ - float scl = queueDelayGuard * parent->getQueueDelayTrend(); - if (parent->isCompetingFlows()) - scl *= 0.05f; - increment = increment * (1.0f - scl) - txQueueSizeFactor * txSizeBitsAvg; - } - increment -= targetBitrate; - if (txSizeBits > 12000 && increment > 0) - increment = 0; - - if (increment > 0) { - wasFastStart = true; - if (!parent->isCompetingFlows()) { - /* - * Limit the bitrate increase so that it does not go faster than rampUpSpeedTmp - * This limitation is not in effect if competing flows are detected - */ - increment *= sclI; - increment = std::min(increment, (float)(rampUpSpeedTmp*(kRateAdjustInterval_ntp * ntp2SecScaleFactor))); - } - /* - * Limited increase if the actual coder rate is lower than the target - */ - if (targetBitrate > rateRtpLimit) { - /* - * Limit increase if the target bitrate is considerably higher than the actual - * bitrate, this is an indication of an idle source. - * It can also be the case that the encoder consistently delivers a lower rate than - * the target rate. We don't want to deadlock the bitrate rampup because of this so - * we gradually reduce the increment the larger the difference is - */ - float scale = std::max(-1.0f, 2.0f*(rateRtpLimit / targetBitrate - 1.0f)); - increment *= (1.0 + scale); - } - } - else { - /* - * Avoid that the target bitrate is reduced if it actually is the media - * coder that limits the output rate e.g due to inactivity - */ - if (rateRtp < targetBitrate) - increment = 0.0f; - /* - * Also avoid that the target bitrate is reduced if - * the coder bitrate is higher - * than the target. - * The possible reason is that a large I frame is transmitted, another reason is - * complex dynamic content. - */ - if (rateRtp > targetBitrate*2.0f) - increment = 0.0f; - } - targetBitrate += increment; - - } - lastBitrateAdjustT_ntp = time_ntp; - } - - targetBitrate = std::min(maxBitrate, std::max(minBitrate, targetBitrate)); -} - -bool ScreamTx::Stream::isRtpQueueDiscard() { - bool tmp = rtpQueueDiscard; - rtpQueueDiscard = false; - return tmp; -} diff --git a/code/bw-test-tool/code/ScreamTx.cpp b/code/bw-test-tool/code/ScreamTx.cpp new file mode 100644 index 0000000..6e3e981 --- /dev/null +++ b/code/bw-test-tool/code/ScreamTx.cpp @@ -0,0 +1,2153 @@ +#include "RtpQueue.h" +#include "ScreamTx.h" +#include "ScreamRx.h" +#ifdef _MSC_VER +#define NOMINMAX +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +// === Some good to have features, SCReAM works also +// with these disabled +// Fast start can resume if little or no congestion detected +static const bool kEnableConsecutiveFastStart = true; +// Packet pacing reduces jitter +static const bool kEnablePacketPacing = true; + +// Rate update interval +static const uint32_t kRateAdjustInterval_ntp = 13107; // 200ms in NTP domain + +// ==== Less important tuning parameters ==== +// Min pacing interval and min pacing rate +static const float kMinPaceInterval = 0.000f; +static const float kMinimumBandwidth = 5000.0f; // bps +// Initial MSS, this is set quite low in order to make it possible to +// use SCReAM with audio only +static const int kInitMss = 100; + +// Min and max queue delay target +static const float kQueueDelayTargetMax = 0.3f; //ms + +// Congestion window validation +static const float kBytesInFlightHistInterval_ntp = 65536; // Time (s) between stores=1s in NTP doain +static const float kMaxBytesInFlightHeadRoom = 1.25f; +// Queue delay trend and shared bottleneck detection +static const uint32_t kQueueDelayFractionHistInterval_us = 3277; // 50ms in NTP domain +// video rate estimation update period +static const uint32_t kRateUpdateInterval_ntp = 3277; // 50ms in NTP domain + +// Packet reordering margin (us) +static const uint32_t kReordertime_ntp = 655; // 10ms in NTP domain +static const uint32_t kMinRtpQueueDiscardInterval_ntp = 16384; // 0.25s in NTP doain + +// Update interval for base delay history +static const uint32_t kBaseDelayUpdateInterval_ntp = 655360; // 10s in NTP doain + +// L4S alpha gain factor, for scalable congestion control +static const float kL4sG = 0.125f; + +// L4S alpha max value, for scalable congestion control +static const float kL4sAlphaMax = 0.25; + +// Min CWND in MSS +static const int kMinCwndMss = 3; + +// Compensate for max 33/65536 = 0.05% clock drift +static const uint32_t kMaxClockdriftCompensation = 33; + +ScreamTx::ScreamTx(float lossBeta_, + float ecnCeBeta_, + float queueDelayTargetMin_, + bool enableSbd_, + float gainUp_, + float gainDown_, + int cwnd_, + float packetPacingHeadroom_, + int bytesInFlightHistSize_, + bool isL4s_, + bool openWindow_, + bool enableClockDriftCompensation_ +) : + sRttSh_ntp(0), + lossBeta(lossBeta_), + ecnCeBeta(ecnCeBeta_), + queueDelayTargetMin(queueDelayTargetMin_), + enableSbd(enableSbd_), + gainUp(gainUp_), + gainDown(gainDown_), + packetPacingHeadroom(packetPacingHeadroom_), + bytesInFlightHistSize(bytesInFlightHistSize_), + isL4s(isL4s_), + openWindow(openWindow_), + enableClockDriftCompensation(enableClockDriftCompensation_), + sRtt_ntp(0), + sRtt(0.0f), + ackedOwd(0), + baseOwd(UINT32_MAX), + queueDelay(0.0), + queueDelayFractionAvg(0.0), + queueDelayTrend(0.0), + queueDelayTarget(queueDelayTargetMin), + queueDelaySbdVar(0.0), + queueDelaySbdMean(0.0), + queueDelaySbdMeanSh(0.0), + + bytesNewlyAcked(0), + mss(kInitMss), + cwnd(kInitMss * 2), + cwndMin(kInitMss * 2), + cwndMinLow(0), + lastBytesInFlightT_ntp(0), + bytesInFlightMaxLo(0), + bytesInFlightMaxHi(0), + bytesInFlightHistLoMem(0), + bytesInFlightHistHiMem(0), + maxBytesInFlight(0.0f), + + lossEvent(false), + wasLossEvent(false), + lossEventRate(0.0), + ecnCeEvent(false), + l4sAlpha(0.1f), + bytesMarkedThisRtt(0), + bytesDeliveredThisRtt(0), + lastL4sAlphaUpdateT_ntp(0), + inFastStart(true), + maxTotalBitrate(0.0f), + + paceInterval_ntp(0), + paceInterval(0.0), + rateTransmittedAvg(0.0), + + isInitialized(false), + lastSRttUpdateT_ntp(0), + lastBaseOwdAddT_ntp(0), + baseOwdResetT_ntp(0), + lastAddToQueueDelayFractionHistT_ntp(0), + lastLossEventT_ntp(0), + lastTransmitT_ntp(0), + nextTransmitT_ntp(0), + lastRateUpdateT_ntp(0), + accBytesInFlightMax(0), + nAccBytesInFlightMax(0), + rateTransmitted(0.0f), + rateAcked(0.0f), + queueDelayTrendMem(0.0f), + lastCwndUpdateT_ntp(0), + bytesInFlight(0), + bytesInFlightLog(0), + lastBaseDelayRefreshT_ntp(0), + maxRate(0.0f), + baseOwdHistMin(UINT32_MAX), + clockDriftCompensation(0), + clockDriftCompensationInc(0), + + bytesNewlyAckedLog(0), + ecnCeMarkedBytesLog(0), + isUseExtraDetailedLog(false), + fp_log(0), + completeLogItem(false) +{ + strcpy(detailedLogExtraData,""); + if (cwnd_ == 0) { + cwnd = kInitMss * 2; + } + else { + cwnd = cwnd_; + } + if (openWindow) { + cwndMin = 10000000; + cwnd = cwndMin; + } + + for (int n = 0; n < kBaseOwdHistSize; n++) + baseOwdHist[n] = UINT32_MAX; + baseOwdHistPtr = 0; + for (int n = 0; n < kQueueDelayFractionHistSize; n++) + queueDelayFractionHist[n] = 0.0f; + queueDelayFractionHistPtr = 0; + for (int n = 0; n < kQueueDelayNormHistSize; n++) + queueDelayNormHist[n] = 0.0f; + queueDelayNormHistPtr = 0; + for (int n = 0; n < kBytesInFlightHistSizeMax; n++) { + bytesInFlightHistLo[n] = 0; + bytesInFlightHistHi[n] = 0; + } + bytesInFlightHistPtr = 0; + nStreams = 0; + for (int n = 0; n < kMaxStreams; n++) + streams[n] = NULL; + + queueDelayMax = 0.0f; + queueDelayMinAvg = 0.0f; + queueDelayMin = 1000.0; + + statistics = new Statistics(); +} + +ScreamTx::~ScreamTx() { + for (int n = 0; n < nStreams; n++) + delete streams[n]; + delete statistics; +} + +ScreamTx::Statistics::Statistics() { + sumRateTx = 0.0f; + sumRateLost = 0.0f; + avgRateTx = 0.0f; + avgRtt = 0.0f; + avgQueueDelay = 0.0f; + rateLostAcc = 0.0f; + rateLostN = 0; + for (int n = 0; n < kLossRateHistSize; n++) { + lossRateHist[n] = 0.0f; + } + lossRateHistPtr = 0; +} + +void ScreamTx::Statistics::add(float rateTx, float rateLost, float rtt, float queueDelay) { + const float alpha = 0.98f; + sumRateTx += rateTx; + sumRateLost += rateLost; + if (avgRateTx == 0.0f) { + avgRateTx = rateTx; + avgRtt = rtt; + avgQueueDelay = queueDelay; + } + else { + avgRateTx = alpha * avgRateTx + (1.0f - alpha)*rateTx; + rateLostAcc += rateLost; + rateLostN++; + if (rateLostN == 10) { + rateLostAcc /= 10; + rateLostN = 0; + float lossRate = 0.0f; + if (rateTx > 0) + lossRate = rateLostAcc / rateTx * 100.0f; + lossRateHist[lossRateHistPtr] = lossRate; + lossRateHistPtr = (lossRateHistPtr + 1) % kLossRateHistSize; + } + avgRtt = alpha * avgRtt + (1.0f - alpha)*rtt; + avgQueueDelay = alpha * avgQueueDelay + (1.0f - alpha)*queueDelay; + } +} + +void ScreamTx::Statistics::getSummary(float time, char s[]) { + float lossRate = 0.0f; + for (int n = 0; n < kLossRateHistSize; n++) + lossRate += lossRateHist[n]; + lossRate /= kLossRateHistSize; + float lossRateLong = 0.0f; + if (sumRateTx > 100000.0f) { + lossRateLong = sumRateLost / sumRateTx * 100.0f; + } + sprintf(s, "%5.1f Transmit rate = %5.0fkbps, PLR = %5.2f%%(%5.2f%%), RTT = %5.3fs, Queue delay = %5.3fs", + time, + avgRateTx / 1000.0f, + lossRate, + lossRateLong, + avgRtt, + avgQueueDelay); +} + +/* +* Register new stream +*/ +void ScreamTx::registerNewStream(RtpQueueIface *rtpQueue, + uint32_t ssrc, + float priority, + float minBitrate, + float startBitrate, + float maxBitrate, + float rampUpSpeed, + float rampUpScale, + float maxRtpQueueDelay, + float txQueueSizeFactor, + float queueDelayGuard, + float lossEventRateScale, + float ecnCeEventRateScale, + bool isAdaptiveTargetRateScale) { + Stream *stream = new Stream(this, + rtpQueue, + ssrc, + priority, + minBitrate, + startBitrate, + maxBitrate, + rampUpSpeed, + rampUpScale, + maxRtpQueueDelay, + txQueueSizeFactor, + queueDelayGuard, + lossEventRateScale, + ecnCeEventRateScale, + isAdaptiveTargetRateScale); + streams[nStreams++] = stream; +} + +void ScreamTx::updateBitrateStream(uint32_t ssrc, + float minBitrate, + float maxBitrate) { + int id; + Stream *stream = getStream(ssrc, id); + stream->minBitrate = minBitrate; + stream->maxBitrate = maxBitrate; +} + +RtpQueueIface * ScreamTx::getStreamQueue(uint32_t ssrc) { + int id; + Stream* stream = getStream(ssrc, id); + if (stream == 0) + return 0; + else + return stream->rtpQueue; +} + + +/* +* New media frame +*/ +void ScreamTx::newMediaFrame(uint32_t time_ntp, uint32_t ssrc, int bytesRtp) { + if (!isInitialized) initialize(time_ntp); + + int id; + Stream *stream = getStream(ssrc, id); + stream->updateTargetBitrate(time_ntp); + if (time_ntp - lastCwndUpdateT_ntp < 32768) { // 32768 = 0.5s in NTP domain + /* + * We expect feedback at least every 500ms + * to update the target rate. + */ + stream->updateTargetBitrate(time_ntp); + } + if (time_ntp - lastBaseDelayRefreshT_ntp < sRtt_ntp * 2) { + /* + * _Very_ long periods of congestion can cause the base delay to increase + * with the effect that the queue delay is estimated wrong, therefore we seek to + * refresh the whole thing by deliberately allowing the network queue to drain + * Clear the RTP queue for 2 RTTs, this will allow the queue to drain so that we + * get a good estimate for the min queue delay. + * This funtion is executed very seldom so it should not affect overall experience too much + */ + stream->rtpQueue->clear(); + } + else { + stream->bytesRtp += bytesRtp; + /* + * Need to update MSS here, otherwise it will be nearly impossible to + * transmit video packets, this because of the small initial MSS + * which is necessary to make SCReAM work with audio only + */ + int sizeOfNextRtp = stream->rtpQueue->sizeOfNextRtp(); + mss = std::max(mss, sizeOfNextRtp); + if (!openWindow) + cwndMin = std::max(cwndMinLow,2 * mss); + cwnd = max(cwnd, cwndMin); + } +} + +/* +* Determine if OK to transmit RTP packet +*/ +float ScreamTx::isOkToTransmit(uint32_t time_ntp, uint32_t &ssrc) { + if (!isInitialized) initialize(time_ntp); + /* + * Update rateTransmitted and rateAcked if time for it + * This is used in video rate computation + * The update interval is doubled at very low bitrates, + * the reason is that the feedback interval is very low then and + * a longer window is needed to avoid aliasing effects + */ + uint32_t tmp = kRateUpdateInterval_ntp; + + if (rateAcked < 50000.0f) { + tmp *= 2; + } + + if (time_ntp - lastRateUpdateT_ntp > tmp) { + rateTransmitted = 0.0f; + rateAcked = 0.0f; + float rateRtp = 0.0f; + for (int n = 0; n < nStreams; n++) { + streams[n]->updateRate(time_ntp); + rateTransmitted += streams[n]->rateTransmitted; + rateRtp += streams[n]->rateRtp; + rateTransmittedAvg = 0.9f*rateTransmittedAvg + 0.1f*rateTransmitted; + rateAcked += streams[n]->rateAcked; + if (n == 0) + statistics->add(streams[0]->rateTransmitted, streams[0]->rateLost, sRtt, queueDelay); + } + lastRateUpdateT_ntp = time_ntp; + /* + * Adjust stream priorities + */ + adjustPriorities(time_ntp); + + /* + * Update maxRate + */ + maxRate = 0.0f; + for (int n = 0; n < nStreams; n++) + maxRate += streams[n]->getMaxRate(); + + /* + * Updated target bitrates if total RTP bitrate + * exceeds maxTotalBitrate + */ + if (maxTotalBitrate > 0) { + float tmp = maxTotalBitrate * 0.9f; + if (rateRtp > tmp) { + inFastStart = false; + /* + * Use a little safety margin because the video coder + * can occasionally send at a higher bitrate than the target rate + */ + float delta = (rateRtp - tmp) / tmp; + delta /= kRateUpDateSize; + /* + * The target bitrate for each stream should be adjusted down by the same fraction + */ + for (int n = 0; n < nStreams; n++) { + Stream* stream = streams[n]; + stream->targetBitrate *= (1.0f - delta); + stream->targetBitrateI = streams[n]->targetBitrate; + stream->lastBitrateAdjustT_ntp = time_ntp; + } + } + } + } + + /* + * Get index to the prioritized RTP queue + */ + Stream* stream = getPrioritizedStream(time_ntp); + + if (stream == NULL) + /* + * No RTP packets to transmit + */ + return -1.0f; + ssrc = stream->ssrc; + + bytesInFlightMaxHi = std::max(bytesInFlight, bytesInFlightMaxHi); + + /* + * Update bytes in flight history for congestion window validation + */ + if (time_ntp - lastBytesInFlightT_ntp > kBytesInFlightHistInterval_ntp) { + bytesInFlightMaxLo = 0; + if (nAccBytesInFlightMax > 0) { + bytesInFlightMaxLo = accBytesInFlightMax / nAccBytesInFlightMax; + } + bytesInFlightHistLo[bytesInFlightHistPtr] = bytesInFlightMaxLo; + bytesInFlightHistHi[bytesInFlightHistPtr] = bytesInFlightMaxHi; + bytesInFlightHistPtr = (bytesInFlightHistPtr + 1) % bytesInFlightHistSize; + lastBytesInFlightT_ntp = time_ntp; + accBytesInFlightMax = 0; + nAccBytesInFlightMax = 0; + bytesInFlightMaxHi = 0; + bytesInFlightHistLoMem = 0; + bytesInFlightHistHiMem = 0; + for (int n = 0; n < bytesInFlightHistSize; n++) { + bytesInFlightHistLoMem = std::max(bytesInFlightHistLoMem, bytesInFlightHistLo[n]); + bytesInFlightHistHiMem = std::max(bytesInFlightHistHiMem, bytesInFlightHistHi[n]); + } + + /* + * In addition, reset MSS, this is useful in case for instance + * a video stream is put on hold, leaving only audio packets to be + * transmitted + */ + mss = kInitMss; + if (!openWindow) + cwndMin = std::max(cwndMinLow,kMinCwndMss * mss); + cwnd = max(cwnd, cwndMin); + + /* + * Add a small clock drift compensation + * for the case that the receiver clock is faster + */ + clockDriftCompensation += clockDriftCompensationInc; + } + + int sizeOfNextRtp = stream->rtpQueue->sizeOfNextRtp(); + if (sizeOfNextRtp == -1) { + return -1.0f; + } + /* + * Determine if window is large enough to transmit + * an RTP packet + */ + bool exit = false; + if (queueDelay < queueDelayTarget) + exit = (bytesInFlight + sizeOfNextRtp) > cwnd + mss; + else + exit = (bytesInFlight + sizeOfNextRtp) > cwnd; + /* + * Enforce packet pacing + */ + float retVal = 0.0f; + uint32_t tmp_l = nextTransmitT_ntp - time_ntp; + if (kEnablePacketPacing && (nextTransmitT_ntp > time_ntp) && (tmp_l < 0xFFFF0000)) { + retVal = (nextTransmitT_ntp - time_ntp) * ntp2SecScaleFactor; + } + + /* + * A retransmission time out mechanism to avoid deadlock + */ + if (time_ntp - lastTransmitT_ntp > 32768 && lastTransmitT_ntp < time_ntp) { // 500ms in NTP domain + for (int n = 0; n < kMaxTxPackets; n++) { + stream->txPackets[n].isUsed = false; + } + bytesInFlight = 0; + exit = false; + retVal = 0.0f; + } + + if (!exit) { + /* + * Return value 0.0 = RTP packet can be immediately transmitted + */ + return retVal; + } + + return -1.0f; +} + +/* +* RTP packet transmitted +*/ +float ScreamTx::addTransmitted(uint32_t time_ntp, + uint32_t ssrc, + int size, + uint16_t seqNr, + bool isMark) { + if (!isInitialized) + initialize(time_ntp); + + + int id; + Stream *stream = getStream(ssrc, id); + + int ix = seqNr % kMaxTxPackets; + Transmitted *txPacket = &(stream->txPackets[ix]); + stream->hiSeqTx = seqNr; + txPacket->timeTx_ntp = time_ntp; + txPacket->size = size; + txPacket->seqNr = seqNr; + txPacket->isMark = isMark; + txPacket->isUsed = true; + txPacket->isAcked = false; + txPacket->isAfterReceivedEdge = false; + + /* + * Update bytesInFlight + */ + bytesInFlight += size; + bytesInFlightLog = std::max(bytesInFlightLog, bytesInFlight); + + stream->bytesTransmitted += size; + lastTransmitT_ntp = time_ntp; + stream->lastTransmitT_ntp = time_ntp; + /* + * Add credit to unserved streams + */ + addCredit(time_ntp, stream, size); + /* + * Reduce used credit for served stream + */ + subtractCredit(time_ntp, stream, size); + + /* + * Update MSS and cwndMin + */ + mss = std::max(mss, size); + if (!openWindow) + cwndMin = std::max(cwndMinLow,2 * mss); + cwnd = max(cwnd, cwndMin); + + /* + * Determine when next RTP packet can be transmitted + */ + if (kEnablePacketPacing) + nextTransmitT_ntp = time_ntp + paceInterval_ntp; + else + nextTransmitT_ntp = time_ntp; + return paceInterval; +} + +void ScreamTx::incomingStandardizedFeedback(uint32_t time_ntp, + unsigned char* buf, + int size) { + + if (!isInitialized) initialize(time_ntp); + int ptr = 2; + /* + * read length in 32bit words + */ + uint16_t length; + memcpy(&length, buf + ptr, 2); + length = ntohs(length); + ptr += 2; + /* + * read RTCP sender SSRC + */ + uint32_t ssrc_rtcp; + memcpy(&ssrc_rtcp, buf + ptr, 4); + ssrc_rtcp = ntohl(ssrc_rtcp); + ptr += 4; + /* + * read report timestamp, it is located at the very end + */ + uint32_t rts; + memcpy(&rts, buf + length * 4, 4); + rts = ntohl(rts); + //printf(" TS %X\n", rts); + + + while (ptr != size - 4) { + /* + * read RTP source (stream) SSRC + */ + uint32_t ssrc; + memcpy(&ssrc, buf + ptr, 4); + ssrc = ntohl(ssrc); + ptr += 4; + /* + * read begin_seq and end_seq + */ + uint16_t begin_seq, end_seq; + uint16_t num_reports; + memcpy(&begin_seq, buf + ptr, 2); + ptr += 2; + memcpy(&num_reports, buf + ptr, 2); + ptr += 2; + begin_seq = ntohs(begin_seq); + num_reports = ntohs(num_reports) + 1; + end_seq = begin_seq+num_reports-1; + + /* + * Validate RTCP feedback message + * Discard out of order RTCP feedback, + * they are quite rate but will mess up the entire + * feedback handling if they are not taken care of. + */ + int streamId = -1; + Stream *stream = getStream(ssrc, streamId); + if (stream == 0) { + /* + * Bogus RTCP?, the SSRC is wrong anyway, Skip + */ + return; + } + + uint16_t diff = end_seq - stream->hiSeqAck; + + if (diff > 65000 && stream->hiSeqAck != 0 && stream->timeTxAck_ntp != 0) { + /* + * Out of order received ACKs are ignored + */ + return; + } + + uint16_t N = end_seq - begin_seq; + + for (int n = 0; n <= N; n++) { + uint16_t sn = begin_seq + n; + uint16_t tmp_s; + memcpy(&tmp_s, buf + ptr, 2); + tmp_s = ntohs(tmp_s); + ptr += 2; + bool isRx = ((tmp_s & 0x8000) == 0x8000); + if (isRx) { + /* + * packet indicated as being received + */ + uint8_t ceBits = (tmp_s & 0x6FFF) >> 13; + uint32_t rxTime = (tmp_s & 0x1FFF) << 6; // Q10->Q16 + rxTime = rts - rxTime; + incomingStandardizedFeedback(time_ntp, streamId, rxTime, sn, ceBits, n == N); + } + } + /* + * Skip zeropadded two octets if odd number of octets reported for this SSRC + */ + if ((N + 1) % 2 == 1) { + ptr += 2; + } + } +} + +/* +* New incoming feedback +*/ +void ScreamTx::incomingStandardizedFeedback(uint32_t time_ntp, + int streamId, + uint32_t timestamp, + uint16_t seqNr, + uint8_t ceBits, + bool isLast) { + + Stream *stream = streams[streamId]; + accBytesInFlightMax += bytesInFlight; + nAccBytesInFlightMax++; + Transmitted *txPackets = stream->txPackets; + completeLogItem = false; + /* + * Mark received packets, given by the ACK vector + */ + bool isMark = false; + markAcked(time_ntp, txPackets, seqNr, timestamp, stream, ceBits, ecnCeMarkedBytesLog, isLast, isMark); + + /* + * Detect lost packets + */ + if (isUseExtraDetailedLog || isLast || isMark) { + detectLoss(time_ntp, txPackets, seqNr, stream); + } + + if (isLast) { + if (bytesMarkedThisRtt != 0 && time_ntp - lastLossEventT_ntp > sRtt_ntp) { + ecnCeEvent = true; + lastLossEventT_ntp = time_ntp; + } + if (isL4s) { + /* + * L4S mode compute a congestion scaling factor that is dependent on the fraction + * of ECN marked packets + */ + if (time_ntp - lastL4sAlphaUpdateT_ntp > sRtt_ntp) { + lastL4sAlphaUpdateT_ntp = time_ntp; + if (bytesDeliveredThisRtt > 0) { + float F = float(bytesMarkedThisRtt) / float(bytesDeliveredThisRtt); + float l4sG = kL4sG; + if (F > l4sAlpha) { + /* + * Adjust alpha a bit faster up than down + */ + l4sG = std::min(1.0f, l4sG*4.0f); + } + /* + * L4S alpha (backoff factor) is averaged and limited + * It makes sense to limit the backoff because + * 1) source is rate limited + * 2) delay estimation algorithm also works in parallel + * 3) L4S marking algorithm can lag behind a little and potentially overmark + */ + l4sAlpha = std::min(kL4sAlphaMax, l4sG * F + (1.0f - l4sG)*l4sAlpha); + bytesDeliveredThisRtt = 0; + bytesMarkedThisRtt = 0; + } + } + } + + if (lossEvent || ecnCeEvent) { + lastLossEventT_ntp = time_ntp; + for (int n = 0; n < nStreams; n++) { + Stream *tmp = streams[n]; + if (lossEvent) + tmp->lossEventFlag = true; + else + tmp->ecnCeEventFlag = true; + } + } + + if (lastCwndUpdateT_ntp == 0) + lastCwndUpdateT_ntp = time_ntp; + + if (time_ntp - lastCwndUpdateT_ntp > 655) { // 10ms in NTP domain + /* + * There is no gain with a too frequent CWND update + * An update every 10ms is fast enough even at very high high bitrates + */ + updateCwnd(time_ntp); + lastCwndUpdateT_ntp = time_ntp; + } + + } + if (isUseExtraDetailedLog || isLast || isMark) { + + if (fp_log && completeLogItem) { + fprintf(fp_log, " %d,%d,%d,%1.0f,%d,%d,%d,%d,%1.0f,%1.0f,%1.0f,%1.0f,%1.0f,%d", cwnd, bytesInFlight, inFastStart, rateTransmitted, streamId, seqNr, bytesNewlyAckedLog, ecnCeMarkedBytesLog, stream->rateRtp, stream->rateTransmitted, stream->rateAcked, stream->rateLost, stream->rateCe,isMark); + if (strlen(detailedLogExtraData) > 0) { + fprintf(fp_log, ",%s", detailedLogExtraData); + } + bytesNewlyAckedLog = 0; + ecnCeMarkedBytesLog = 0; + } + if (fp_log && completeLogItem) { + fprintf(fp_log, "\n"); + } + } + +} + +/* +* Mark ACKed RTP packets +*/ +void ScreamTx::markAcked(uint32_t time_ntp, + struct Transmitted *txPackets, + uint16_t seqNr, + uint32_t timestamp, + Stream *stream, + uint8_t ceBits, + int &encCeMarkedBytesLog, + bool isLast, + bool &isMark) { + + int ix = seqNr % kMaxTxPackets; + if (txPackets[ix].isUsed) { + /* + * RTP packet is in flight + */ + Transmitted *tmp = &txPackets[ix]; + + /* + * Receiption of packet given by seqNr + */ + if ((tmp->seqNr == seqNr) & !tmp->isAcked) { + bytesDeliveredThisRtt += tmp->size; + isMark = tmp->isMark; + if (ceBits == 0x03) { + /* + * Packet was CE marked, increase counter + */ + encCeMarkedBytesLog += tmp->size; + bytesMarkedThisRtt += tmp->size; + stream->bytesCe += tmp->size; + } + tmp->isAcked = true; + ackedOwd = timestamp - tmp->timeTx_ntp; + + /* + * Compute the queue delay i NTP domain (Q16) + */ + estimateOwd(time_ntp); + /* + * Small compensation for positive clock drift + */ + ackedOwd -= clockDriftCompensation; + + uint32_t qDel = ackedOwd - getBaseOwd(); + + if (qDel > 0xFFFF0000 && clockDriftCompensation != 0) { + /* + * We have the case that the clock drift compensation is too large as it gives negative queue delays + * reduce the clock drift compensation and restore qDel to 0 + */ + uint32_t diff = 0 - qDel; + clockDriftCompensation -= diff; + qDel = 0; + } + + if (qDel > 0xFFFF0000 || clockDriftCompensation > 0xFFFF0000) { + /* + * TX and RX clock diff made qDel wrap around + * reset history + */ + clockDriftCompensation = 0; + clockDriftCompensationInc = 0; + queueDelayMinAvg = 0.0f; + queueDelay = 0.0f; + for (int n = 0; n < kBaseOwdHistSize; n++) + baseOwdHist[n] = UINT32_MAX; + baseOwd = UINT32_MAX; + baseOwdHistMin = UINT32_MAX; + baseOwdResetT_ntp = time_ntp; + qDel = 0; + } + + /* + * Convert from NTP domain OWD to an OWD in [s] + */ + queueDelay = qDel * ntp2SecScaleFactor; + + uint32_t rtt = time_ntp - tmp->timeTx_ntp; + + if (fp_log && (isUseExtraDetailedLog || isLast || isMark)) { + fprintf(fp_log, "%s,%1.4f,%1.4f,", timeString, queueDelay, rtt*ntp2SecScaleFactor); + completeLogItem = true; + } + if (rtt < 1000000 && isLast) { + sRttSh_ntp = (7 * sRttSh_ntp + rtt) / 8; + if (time_ntp - lastSRttUpdateT_ntp > sRttSh_ntp) { + sRtt_ntp = (7 * sRtt_ntp + sRttSh_ntp) / 8; + lastSRttUpdateT_ntp = time_ntp; + sRtt = sRtt_ntp * ntp2SecScaleFactor; + } + } + stream->timeTxAck_ntp = tmp->timeTx_ntp; + } + } +} + +/* +* Detect lost RTP packets +*/ +void ScreamTx::detectLoss(uint32_t time_ntp, struct Transmitted *txPackets, uint16_t highestSeqNr, Stream *stream) { + /* + * Loop only through the packets that are covered by the last highest ACK, this saves complexity + * There is a faint possibility that we miss to detect large bursts of lost packets with this fix + */ + int ix1 = highestSeqNr; ix1 = ix1 % kMaxTxPackets; + int ix0 = stream->hiSeqAck - 256; + stream->hiSeqAck = highestSeqNr; + if (ix0 < 0) ix0 += kMaxTxPackets; + while (ix1 < ix0) + ix1 += kMaxTxPackets; + + /* + * Mark late packets as lost + */ + for (int m = ix0; m <= ix1; m++) { + int n = m % kMaxTxPackets; + /* + * Loop through TX packets + */ + if (txPackets[n].isUsed) { + Transmitted *tmp = &txPackets[n]; + /* + * RTP packet is in flight + */ + /* + * Wrap-around safety net + */ + uint32_t seqNrExt = tmp->seqNr; + uint32_t highestSeqNrExt = highestSeqNr; + if (seqNrExt < highestSeqNrExt && highestSeqNrExt - seqNrExt > 20000) + seqNrExt += 65536; + else if (seqNrExt > highestSeqNrExt && seqNrExt - highestSeqNrExt > 20000) + highestSeqNrExt += 65536; + + /* + * RTP packets with a sequence number lower + * than or equal to the highest received sequence number + * are treated as received even though they are not + * This advances the send window, similar to what + * SACK does in TCP + */ + if (seqNrExt <= highestSeqNrExt && tmp->isAfterReceivedEdge == false) { + bytesNewlyAcked += tmp->size; + bytesNewlyAckedLog += tmp->size; + bytesInFlight -= tmp->size; + if (bytesInFlight < 0) + bytesInFlight = 0; + stream->bytesAcked += tmp->size; + tmp->isAfterReceivedEdge = true; + } + + if (tmp->timeTx_ntp + kReordertime_ntp < stream->timeTxAck_ntp && !tmp->isAcked) { + /* + * Packet ACK is delayed more than kReordertime_ntp after an ACK of a higher SN packet + * raise a loss event and remove from TX list + */ + if (time_ntp - lastLossEventT_ntp > sRtt_ntp && lossBeta < 1.0f) { + lossEvent = true; + } + stream->bytesLost += tmp->size; + tmp->isUsed = false; + cerr << " LOSS detected by reorder timer SSRC=" << stream->ssrc << " SN=" << tmp->seqNr << endl; + stream->repairLoss = true; + } + else if (tmp->isAcked) { + tmp->isUsed = false; + } + } + } +} + +float ScreamTx::getTargetBitrate(uint32_t ssrc) { + int id; + return getStream(ssrc, id)->getTargetBitrate(); +} + +void ScreamTx::setTargetPriority(uint32_t ssrc, float priority) { + int id; + Stream *stream = getStream(ssrc, id); + if (queueDelayFractionAvg > 0.1 || !inFastStart) { + stream->targetBitrate *= priority / stream->targetPriority; + stream->targetBitrate = std::min(std::max(stream->targetBitrate, stream->minBitrate), stream->maxBitrate); + } + stream->targetPriority = priority; + stream->targetPriorityInv = 1.0f / priority; +} + +void ScreamTx::getLog(float time, char *s) { + int inFlightMax = std::max(bytesInFlight, bytesInFlightHistHiMem); + sprintf(s, "%4.3f, %4.3f, %4.3f, %4.3f, %6d, %6d, %6.0f, %1d, ", + queueDelay, queueDelayMax, queueDelayMinAvg, sRtt, + cwnd, bytesInFlightLog, rateTransmitted / 1000.0f, isInFastStart()); + bytesInFlightLog = bytesInFlight; + queueDelayMax = 0.0; + for (int n = 0; n < nStreams; n++) { + Stream *tmp = streams[n]; + char s2[200]; + sprintf(s2, "%4.3f, %6.0f, %6.0f, %6.0f, %6.0f, %5.0f, %5.0f, %5d, ", + std::max(0.0f, tmp->rtpQueue->getDelay(time)), + tmp->targetBitrate / 1000.0f, tmp->rateRtp / 1000.0f, + tmp->rateTransmitted / 1000.0f, tmp->rateAcked / 1000.0f, + tmp->rateLost / 1000.0f, tmp->rateCe / 1000.0f, + tmp->hiSeqAck); + strcat(s, s2); + } +} + +void ScreamTx::getShortLog(float time, char *s) { + int inFlightMax = std::max(bytesInFlight, bytesInFlightHistHiMem); + sprintf(s, "%4.3f, %4.3f, %6d, %6d, %6.0f, %1d, ", + queueDelay, sRtt, + cwnd, bytesInFlightLog, rateTransmitted / 1000.0f, isInFastStart()); + bytesInFlightLog = bytesInFlight; + queueDelayMax = 0.0; + for (int n = 0; n < nStreams; n++) { + Stream *tmp = streams[n]; + char s2[200]; + sprintf(s2, "%4.3f, %6.0f, %6.0f, %6.0f, %5.0f, %5.0f, ", + std::max(0.0f, tmp->rtpQueue->getDelay(time)), + tmp->targetBitrate / 1000.0f, tmp->rateRtp / 1000.0f, + tmp->rateTransmitted / 1000.0f, + tmp->rateLost / 1000.0f, tmp->rateCe / 1000.0f); + strcat(s, s2); + } +} + +void ScreamTx::getVeryShortLog(float time, char *s) { + int inFlightMax = std::max(bytesInFlight, bytesInFlightHistHiMem); + sprintf(s, "%4.3f, %4.3f, %6d, %6d, %6.0f, ", + queueDelay, sRtt, + cwnd, bytesInFlightLog, rateTransmitted / 1000.0f); + bytesInFlightLog = bytesInFlight; + queueDelayMax = 0.0; + for (int n = 0; n < 1; n++) { + Stream *tmp = streams[n]; + char s2[200]; + sprintf(s2, "%5.0f, %5.0f, ", + tmp->rateLost / 1000.0f, tmp->rateCe / 1000.0f); + strcat(s, s2); + } +} +void ScreamTx::getStatistics(float time, char *s) { + statistics->getSummary(time, s); +} + +void ScreamTx::initialize(uint32_t time_ntp) { + isInitialized = true; + lastSRttUpdateT_ntp = time_ntp; + lastBaseOwdAddT_ntp = time_ntp; + baseOwdResetT_ntp = time_ntp; + lastAddToQueueDelayFractionHistT_ntp = time_ntp; + lastLossEventT_ntp = time_ntp; + lastTransmitT_ntp = time_ntp; + nextTransmitT_ntp = time_ntp; + lastRateUpdateT_ntp = time_ntp; + lastAdjustPrioritiesT_ntp = time_ntp; + lastRttT_ntp = time_ntp; + lastBaseDelayRefreshT_ntp = time_ntp - 1; + lastL4sAlphaUpdateT_ntp = time_ntp; + initTime_ntp = time_ntp; +} + +float ScreamTx::getTotalTargetBitrate() { + float totalTargetBitrate = 0.0f; + for (int n = 0; n < nStreams; n++) { + totalTargetBitrate += streams[n]->targetBitrate; + } + return totalTargetBitrate; +} + +/* +* Update the congestion window +*/ +void ScreamTx::updateCwnd(uint32_t time_ntp) { + float dT = 0.001f; + if (lastCwndUpdateT_ntp == 0) + lastCwndUpdateT_ntp = time_ntp; + else + dT = (time_ntp - lastCwndUpdateT_ntp) * ntp2SecScaleFactor; + /* + * This adds a limit to how much the CWND can grow, it is particularly useful + * in case of short gliches in the transmission, in which a large chunk of data is delivered + * in a burst with the effect that the estimated queue delay is low because it typically depict the queue + * delay for the last non-delayed RTP packet. The rule is that the CWND cannot grow faster + * than the 1.25 times the average amount of bytes that transmitted in the given feedback interval + */ + float bytesNewlyAckedLimited = float(bytesNewlyAcked); + if (maxRate > 1.0e5f) + bytesNewlyAckedLimited = std::min(bytesNewlyAckedLimited, 1.25f*maxRate*dT / 8.0f); + else + bytesNewlyAckedLimited = 2.0f*mss; + + queueDelayMin = std::min(queueDelayMin, queueDelay); + + queueDelayMax = std::max(queueDelayMax, queueDelay); + + float time = time_ntp * ntp2SecScaleFactor; + if (queueDelayMinAvg > 0.25f*queueDelayTarget && time_ntp - baseOwdResetT_ntp > 1310720) { // 20s in NTP domain + /* + * The base OWD is likely wrong, for instance due to + * a channel change or clock drift, reset base OWD history + */ + clockDriftCompensation = 0; + clockDriftCompensationInc = 0; + queueDelayMinAvg = 0.0f; + queueDelay = 0.0f; + for (int n = 0; n < kBaseOwdHistSize; n++) + baseOwdHist[n] = UINT32_MAX; + baseOwd = UINT32_MAX; + baseOwdHistMin = UINT32_MAX; + baseOwdResetT_ntp = time_ntp; + } + /* + * An averaged version of the queue delay fraction + * neceassary in order to make video rate control robust + * against jitter + */ + queueDelayFractionAvg = 0.9f*queueDelayFractionAvg + 0.1f*getQueueDelayFraction(); + + /* + * Less frequent updates here + * + Compute pacing interval + * + Save to queue delay fraction history + * used in computeQueueDelayTrend() + * + Update queueDelayTarget + */ + if ((time_ntp - lastAddToQueueDelayFractionHistT_ntp) > + kQueueDelayFractionHistInterval_us) { + + /* + * compute paceInterval, we assume a min bw of 50kbps and a min tp of 1ms + * for stable operation + * this function implements the packet pacing + */ + paceInterval = kMinPaceInterval; + if ((queueDelayFractionAvg > 0.02f || isL4s || maxTotalBitrate > 0) && kEnablePacketPacing) { + float pacingBitrate = std::max(1.0e5f, packetPacingHeadroom*getTotalTargetBitrate()); + if (maxTotalBitrate > 0) { + pacingBitrate = std::min(pacingBitrate, maxTotalBitrate); + } + float tp = (mss * 8.0f) / pacingBitrate; + paceInterval = std::max(kMinPaceInterval, tp); + } + paceInterval_ntp = (uint32_t)(paceInterval * 65536); // paceinterval converted to NTP domain (Q16) + + if (queueDelayMin < queueDelayMinAvg) + queueDelayMinAvg = queueDelayMin; + else + queueDelayMinAvg = 0.001f*queueDelayMin + 0.999f*queueDelayMinAvg; + queueDelayMin = 1000.0f; + /* + * Need to duplicate insertion incase the feedback is sparse + */ + int nIter = (int)(time_ntp - lastAddToQueueDelayFractionHistT_ntp) / kQueueDelayFractionHistInterval_us; + for (int n = 0; n < nIter; n++) { + queueDelayFractionHist[queueDelayFractionHistPtr] = getQueueDelayFraction(); + queueDelayFractionHistPtr = (queueDelayFractionHistPtr + 1) % kQueueDelayFractionHistSize; + } + + if (time_ntp - initTime_ntp > 131072) { // 2s in NTP domain + /* + * Queue delay trend calculations are reliable after ~2s + */ + computeQueueDelayTrend(); + } + + queueDelayTrendMem = std::max(queueDelayTrendMem*0.98f, queueDelayTrend); + + /* + * Compute bytes in flight limitation + */ + int maxBytesInFlightHi = (int)(std::max(bytesInFlightMaxHi, bytesInFlightHistHiMem)); + int maxBytesInFlightLo = (int)(std::max(bytesInFlight, bytesInFlightHistLoMem)); + + maxBytesInFlight = + (maxBytesInFlightHi*(1.0f - queueDelayTrend) + maxBytesInFlightLo * queueDelayTrend)* + kMaxBytesInFlightHeadRoom; + if (enableSbd) { + /* + * Shared bottleneck detection, + */ + float queueDelayNorm = queueDelay / queueDelayTargetMin; + queueDelayNormHist[queueDelayNormHistPtr] = queueDelayNorm; + queueDelayNormHistPtr = (queueDelayNormHistPtr + 1) % kQueueDelayNormHistSize; + /* + * Compute shared bottleneck detection and update queue delay target + * if queue dela variance is sufficienctly low + */ + computeSbd(); + /* + * This function avoids the adjustment of queueDelayTarget when + * congestion occurs (indicated by high queueDelaydSbdVar and queueDelaySbdSkew) + */ + float oh = queueDelayTargetMin * (queueDelaySbdMeanSh + sqrt(queueDelaySbdVar)); + if (lossEventRate > 0.002 && queueDelaySbdMeanSh > 0.5 && queueDelaySbdVar < 0.2) { + queueDelayTarget = std::min(kQueueDelayTargetMax, oh*1.5f); + } + else { + if (queueDelaySbdVar < 0.2 && queueDelaySbdSkew < 0.05) { + queueDelayTarget = std::max(queueDelayTargetMin, std::min(kQueueDelayTargetMax, oh)); + } + else { + if (oh < queueDelayTarget) + queueDelayTarget = std::max(queueDelayTargetMin, std::max(queueDelayTarget*0.99f, oh)); + else + queueDelayTarget = std::max(queueDelayTargetMin, queueDelayTarget*0.999f); + } + } + } + lastAddToQueueDelayFractionHistT_ntp = time_ntp; + } + /* + * offTarget is a normalized deviation from the queue delay target + */ + float offTarget = (queueDelayTarget - queueDelay) / float(queueDelayTarget); + + if (lossEvent) { + /* + * loss event detected, decrease congestion window + */ + cwnd = std::max(cwndMin, (int)(lossBeta*cwnd)); + lossEvent = false; + lastCongestionDetectedT_ntp = time_ntp; + + inFastStart = false; + wasLossEvent = true; + } + else if (ecnCeEvent) { + /* + * loss event detected, decrease congestion window + */ + if (isL4s) { + cwnd = std::max(cwndMin, (int)((1.0f - l4sAlpha / 2.0f)*cwnd)); + } + else { + cwnd = std::max(cwndMin, (int)(ecnCeBeta*cwnd)); + } + ecnCeEvent = false; + lastCongestionDetectedT_ntp = time_ntp; + + inFastStart = false; + wasLossEvent = true; + } + else { + + if (time_ntp - lastRttT_ntp > sRtt_ntp) { + if (wasLossEvent) + lossEventRate = 0.99f*lossEventRate + 0.01f; + else + lossEventRate *= 0.99f; + wasLossEvent = false; + lastRttT_ntp = time_ntp; + } + + if (inFastStart) { + /* + * In fast start + */ + if (queueDelayTrend < 0.2) { + /* + * CWND is increased by the number of ACKed bytes if + * window is used to 1/1.5 = 67% + * We need to relax the rule a bit for the case that + * feedback may be sparse due to limited RTCP report interval + * In addition we put a limit for the cases where feedback becomes + * piled up (sometimes happens with e.g LTE) + */ + float bytesInFlightMargin = 1.5f; + if (bytesInFlight*bytesInFlightMargin + bytesNewlyAcked > cwnd) { + cwnd += int(bytesNewlyAckedLimited); + } + } + else { + inFastStart = false; + } + } + else { + if (offTarget > 0.0f) { + /* + * queue delay below target, increase CWND if window + * is used to 1/1.2 = 83% + */ + float bytesInFlightMargin = 1.2f; + if (bytesInFlight*bytesInFlightMargin + bytesNewlyAcked > cwnd) { + float increment = gainUp * offTarget * bytesNewlyAckedLimited * mss / cwnd; + cwnd += (int)(increment + 0.5f); + } + } + else { + if (true) { + /* + * Queue delay above target. + * Limit the CWND reduction to at most a quarter window + * this avoids unduly large reductions for the cases + * where data is queued up e.g because of retransmissions + * on lower protocol layers. + */ + float delta = -(gainDown * offTarget * bytesNewlyAcked * mss / cwnd); + delta = std::min(delta, cwnd / 4.0f); + cwnd -= (int)(delta); + + /* + * Especially when running over low bitrate bottlenecks, it is + * beneficial to reduce the target bitrate a little, it limits + * possible RTP queue delay spikes when congestion window is reduced + */ + float rateTotal = 0.0f; + for (int n = 0; n < nStreams; n++) + rateTotal += streams[n]->getMaxRate(); + if (rateTotal < 1.0e5f) { + delta = delta / cwnd; + float rateAdjustFactor = (1.0f - delta); + for (int n = 0; n < nStreams; n++) { + Stream *tmp = streams[n]; + tmp->targetBitrate = std::max(tmp->minBitrate, + std::min(tmp->maxBitrate, + tmp->targetBitrate*rateAdjustFactor)); + } + } + } + lastCongestionDetectedT_ntp = time_ntp; + } + } + } + /* + * Congestion window validation, checks that the congestion window is + * not considerably higher than the actual number of bytes in flight + */ + if (maxBytesInFlight > 5000) { + cwnd = std::min(cwnd, (int)maxBytesInFlight); + } + + if (sRtt < 0.01f && queueDelayTrend < 0.1) { + int tmp = int(rateTransmitted*0.01f / 8); + tmp = std::max(tmp, (int)(maxBytesInFlight*1.5f)); + cwnd = std::max(cwnd, tmp); + } + cwnd = std::max(cwndMin, cwnd); + + /* + * Make possible to enter fast start if OWD has been low for a while + */ + if (queueDelayTrend > 0.2) { + lastCongestionDetectedT_ntp = time_ntp; + } + else if (time_ntp - lastCongestionDetectedT_ntp > 65536 && // 5s in NTP domain + !inFastStart && kEnableConsecutiveFastStart) { + /* + * The queue delay trend has been low for more than 1.0s, resume fast start + */ + inFastStart = true; + lastCongestionDetectedT_ntp = time_ntp; + } + bytesNewlyAcked = 0; +} + +/* +* Update base OWD (if needed) and return the +* last estimated OWD (without offset compensation) +*/ +void ScreamTx::estimateOwd(uint32_t time_ntp) { + baseOwd = std::min(baseOwd, ackedOwd); + if (time_ntp - lastBaseOwdAddT_ntp >= kBaseDelayUpdateInterval_ntp) { + if (enableClockDriftCompensation) { + /* + * The clock drift compensation looks at how the baseOwd increases over time + * and calculates a compensation factor that advanced the sender clock stamps + * Only positive drift (receiver side is faster) is compensated as negative + * drift is already handled + */ + int ptr = baseOwdHistPtr; + int k = 0; + uint32_t bw0 = UINT32_MAX; + for (k = 0; k < kBaseOwdHistSize - 1; k++) { + if (baseOwdHist[ptr] != UINT32_MAX) { + bw0 = baseOwdHist[ptr]; + ptr = ptr - 1; + if (ptr < 0) ptr += kBaseOwdHistSize; + } + else + break; + } + + uint32_t bw1 = baseOwd; + if (k > 0) { + if ((bw1 > bw0)) + clockDriftCompensationInc = (bw1 - bw0) / (kBaseDelayUpdateInterval_ntp / 65536 * k); + else + clockDriftCompensationInc = 0; + clockDriftCompensationInc = std::min(kMaxClockdriftCompensation, clockDriftCompensationInc); + if (clockDriftCompensation == 0) { + /* + * Two update periods delayed with compensation so we add one update period worth of + * clock drift compensation + */ + clockDriftCompensation += clockDriftCompensationInc * (kBaseDelayUpdateInterval_ntp / 65536); + } + } + } + baseOwdHistPtr = (baseOwdHistPtr + 1) % kBaseOwdHistSize; + baseOwdHist[baseOwdHistPtr] = baseOwd; + lastBaseOwdAddT_ntp = time_ntp; + baseOwd = UINT32_MAX; + baseOwdHistMin = UINT32_MAX; + for (int n = 0; n < kBaseOwdHistSize; n++) + baseOwdHistMin = std::min(baseOwdHistMin, baseOwdHist[n]); + /* + * _Very_ long periods of congestion can cause the base delay to increase + * with the effect that the queue delay is estimated wrong, therefore we seek to + * refresh the whole thing by deliberately allowing the network queue to drain + */ + if (time_ntp - lastBaseDelayRefreshT_ntp > kBaseDelayUpdateInterval_ntp*(kBaseOwdHistSize - 1)) { + lastBaseDelayRefreshT_ntp = time_ntp; + } + } +} + +/* +* Get the base one way delay +*/ +uint32_t ScreamTx::getBaseOwd() { + return std::min(baseOwd, baseOwdHistMin); +} + +/* +* Get the queue delay fraction +*/ +float ScreamTx::getQueueDelayFraction() { + return queueDelay / queueDelayTarget; +} + +/* +* Compute congestion indicator +*/ +void ScreamTx::computeQueueDelayTrend() { + queueDelayTrend = 0.0; + int ptr = queueDelayFractionHistPtr; + float avg = 0.0f, x1, x2, a0, a1; + + for (int n = 0; n < kQueueDelayFractionHistSize; n++) { + avg += queueDelayFractionHist[ptr]; + ptr = (ptr + 1) % kQueueDelayFractionHistSize; + } + avg /= kQueueDelayFractionHistSize; + + ptr = queueDelayFractionHistPtr; + x2 = 0.0f; + a0 = 0.0f; + a1 = 0.0f; + for (int n = 0; n < kQueueDelayFractionHistSize; n++) { + x1 = queueDelayFractionHist[ptr] - avg; + a0 += x1 * x1; + a1 += x1 * x2; + x2 = x1; + ptr = (ptr + 1) % kQueueDelayFractionHistSize; + } + if (a0 > 0) { + queueDelayTrend = std::max(0.0f, std::min(1.0f, (a1 / a0)*queueDelayFractionAvg)); + } +} + +/* +* Compute indicators of shared bottleneck +*/ +void ScreamTx::computeSbd() { + float queueDelayNorm, tmp; + queueDelaySbdMean = 0.0; + queueDelaySbdMeanSh = 0.0; + queueDelaySbdVar = 0.0; + int ptr = queueDelayNormHistPtr; + for (int n = 0; n < kQueueDelayNormHistSize; n++) { + queueDelayNorm = queueDelayNormHist[ptr]; + queueDelaySbdMean += queueDelayNorm; + if (n >= kQueueDelayNormHistSize - kQueueDelayNormHistSizeSh) { + queueDelaySbdMeanSh += queueDelayNorm; + } + ptr = (ptr + 1) % kQueueDelayNormHistSize; + } + queueDelaySbdMean /= kQueueDelayNormHistSize; + queueDelaySbdMeanSh /= kQueueDelayNormHistSizeSh; + + ptr = queueDelayNormHistPtr; + for (int n = 0; n < kQueueDelayNormHistSize; n++) { + queueDelayNorm = queueDelayNormHist[ptr]; + tmp = queueDelayNorm - queueDelaySbdMean; + queueDelaySbdVar += tmp * tmp; + queueDelaySbdSkew += tmp * tmp * tmp; + ptr = (ptr + 1) % kQueueDelayNormHistSize; + } + queueDelaySbdVar /= kQueueDelayNormHistSize; + queueDelaySbdSkew /= kQueueDelayNormHistSize; +} + +/* +* true if the queueDelayTarget is increased due to +* detected competing flows +*/ +bool ScreamTx::isCompetingFlows() { + return queueDelayTarget > queueDelayTargetMin; +} + +/* +* Get queue delay trend +*/ +float ScreamTx::getQueueDelayTrend() { + return queueDelayTrend; +} + +/* +* Determine active streams +*/ +void ScreamTx::determineActiveStreams(uint32_t time_ntp) { + float surplusBitrate = 0.0f; + float sumPrio = 0.0; + bool streamSetInactive = false; + for (int n = 0; n < nStreams; n++) { + if (time_ntp - streams[n]->lastFrameT_ntp > 65536 && streams[n]->isActive) { // 1s in NTP domain + streams[n]->isActive = false; + surplusBitrate += streams[n]->targetBitrate; + streams[n]->targetBitrate = streams[n]->minBitrate; + streamSetInactive = true; + } + else { + sumPrio += streams[n]->targetPriority; + } + } + if (streamSetInactive) { + for (int n = 0; n < nStreams; n++) { + if (streams[n]->isActive) { + streams[n]->targetBitrate = std::min(streams[n]->maxBitrate, + streams[n]->targetBitrate + + surplusBitrate * streams[n]->targetPriority / sumPrio); + } + } + } +} + +/* +* Add credit to streams that was not served +*/ +void ScreamTx::addCredit(uint32_t time_ntp, Stream* servedStream, int transmittedBytes) { + /* + * Add a credit to stream(s) that did not get priority to transmit RTP packets + */ + if (nStreams == 1) + /* + * Skip if only one stream to save CPU + */ + return; + int maxCredit = 2 * mss; + for (int n = 0; n < nStreams; n++) { + Stream *tmp = streams[n]; + if (tmp != servedStream) { + int credit = (int)(transmittedBytes*tmp->targetPriority * servedStream->targetPriorityInv); + if (tmp->rtpQueue->sizeOfQueue() > 0) { + tmp->credit += credit; + } + else { + tmp->credit += credit; + if (tmp->credit > maxCredit) { + tmp->creditLost += tmp->credit - maxCredit; + tmp->credit = maxCredit; + } + } + } + } +} + +/* +* Subtract credit from served stream +*/ +void ScreamTx::subtractCredit(uint32_t time_ntp, Stream* servedStream, int transmittedBytes) { + /* + * Subtract a credit equal to the number of transmitted bytes from the stream that + * transmitted a packet + */ + if (nStreams == 1) + /* + * Skip if only one stream to save CPU + */ + return; + servedStream->credit = std::max(0, servedStream->credit - transmittedBytes); +} + +/* +* Adjust (enforce) proper prioritization between active streams +* at regular intervals. This is a necessary addon to mitigate +* issues that VBR media brings +* The function consists of equal measures or rational thinking and +* black magic, which means that there is no 100% guarantee that +* will always work. +*/ +void ScreamTx::adjustPriorities(uint32_t time_ntp) { + if (nStreams == 1 || time_ntp - lastAdjustPrioritiesT_ntp < 65536) { // 1s in NTP domain + /* + * Skip if only one stream or if adjustment done less than 5s ago + */ + return; + } + + if (queueDelayTrend > 0.02) { + /* + * Adjust only if there is some evidence of congestion + */ + int avgCreditLost = 0; + int avgCreditLostN = 0; + for (int n = 0; n < nStreams; n++) { + avgCreditLost += streams[n]->creditLost; + if (streams[n]->isActive) + avgCreditLostN++; + } + if (avgCreditLostN <= 1) { + /* + * At the most 1 steam active, skip adjustment + */ + return; + } + + avgCreditLost /= avgCreditLostN; + for (int n = 0; n < nStreams; n++) { + if (streams[n]->isActive) { + if (streams[n]->creditLost < avgCreditLost && + streams[n]->targetBitrate > streams[n]->rateRtp) { + /* + * Stream is using more of its share than the average + * bitrate is likelky too high, reduce target bitrate + * This algorithm works best when we want to ensure + * different priorities + */ + streams[n]->targetBitrate = std::max(streams[n]->minBitrate, streams[n]->targetBitrate*0.9f); + } + } + } + + for (int n = 0; n < nStreams; n++) + streams[n]->creditLost = 0; + + + lastAdjustPrioritiesT_ntp = time_ntp; + + } + if (time_ntp - lastAdjustPrioritiesT_ntp < 1310720) { // 20s in NTP domain + /* + * Clear old statistics of unused credits + */ + for (int n = 0; n < nStreams; n++) + streams[n]->creditLost = 0; + + + lastAdjustPrioritiesT_ntp = time_ntp; + } +} + +/* +* Get the prioritized stream +*/ +ScreamTx::Stream* ScreamTx::getPrioritizedStream(uint32_t time_ntp) { + /* + * Function that prioritizes between streams, this function may need + * to be modified to handle the prioritization better for e.g + * FEC, SVC etc. + */ + if (nStreams == 1) + /* + * Skip if only one stream to save CPU + */ + return streams[0]; + + int maxCredit = 1; + Stream *stream = NULL; + /* + * Pick a stream with credit higher or equal to + * the size of the next RTP packet in queue for the given stream. + */ + uint32_t maxDiff = 0; + for (int n = 0; n < nStreams; n++) { + Stream *tmp = streams[n]; + if (tmp->rtpQueue->sizeOfQueue() == 0) { + /* + * Queue empty + */ + } + else { + uint32_t diff = time_ntp - tmp->lastTransmitT_ntp; + /* + * Pick stream if it has the highest credit so far + */ + if (tmp->credit >= maxCredit && diff > maxDiff) { + stream = tmp; + maxDiff = diff; + maxCredit = tmp->credit; + } + } + } + if (stream != NULL) { + return stream; + } + /* + * If the above doesn't give a candidate.. + * Pick the stream with the highest priority that also + * has at least one RTP packet in queue. + */ + float maxPrio = 0.0; + for (int n = 0; n < nStreams; n++) { + Stream *tmp = streams[n]; + float priority = tmp->targetPriority; + if (tmp->rtpQueue->sizeOfQueue() > 0 && priority > maxPrio) { + maxPrio = priority; + stream = tmp; + } + } + return stream; +} + +ScreamTx::Stream::Stream(ScreamTx *parent_, + RtpQueueIface *rtpQueue_, + uint32_t ssrc_, + float priority_, + float minBitrate_, + float startBitrate_, + float maxBitrate_, + float rampUpSpeed_, + float rampUpScale_, + float maxRtpQueueDelay_, + float txQueueSizeFactor_, + float queueDelayGuard_, + float lossEventRateScale_, + float ecnCeEventRateScale_, + bool isAdaptiveTargetRateScale_) { + parent = parent_; + rtpQueue = rtpQueue_; + ssrc = ssrc_; + targetPriority = priority_; + targetPriorityInv = 1.0f / targetPriority; + minBitrate = minBitrate_; + maxBitrate = maxBitrate_; + targetBitrate = std::min(maxBitrate, std::max(minBitrate, startBitrate_)); + rampUpSpeed = rampUpSpeed_; + rampUpScale = rampUpScale_; + maxRtpQueueDelay = maxRtpQueueDelay_; + txQueueSizeFactor = txQueueSizeFactor_; + queueDelayGuard = queueDelayGuard_; + lossEventRateScale = lossEventRateScale_; + ecnCeEventRateScale = ecnCeEventRateScale_; + isAdaptiveTargetRateScale = isAdaptiveTargetRateScale_; + targetBitrateHistUpdateT_ntp = 0; + targetBitrateI = 1.0f; + credit = 0; + creditLost = 0; + bytesTransmitted = 0; + bytesAcked = 0; + bytesLost = 0; + hiSeqAck = 0; + hiSeqTx = 0; + rateTransmitted = 0.0f; + rateAcked = 0.0f; + rateLost = 0.0f; + rateCe = 0.0; + lossEventFlag = false; + ecnCeEventFlag = false; + txSizeBitsAvg = 0.0f; + lastRateUpdateT_ntp = 0; + lastBitrateAdjustT_ntp = 0; + lastTargetBitrateIUpdateT_ntp = 0; + bytesRtp = 0; + rateRtp = 0.0f; + timeTxAck_ntp = 0; + lastTransmitT_ntp = 0; + + for (int n = 0; n < kRateUpDateSize; n++) { + rateRtpHist[n] = 0.0f; + rateAckedHist[n] = 0.0f; + rateLostHist[n] = 0.0f; + rateCeHist[n] = 0.0f; + rateTransmittedHist[n] = 0.0f; + } + rateUpdateHistPtr = 0; + for (int n = 0; n < kTargetBitrateHistSize; n++) { + targetBitrateHist[n] = 0; + } + targetBitrateHistPtr = 0; + targetRateScale = 1.0; + isActive = false; + lastFrameT_ntp = 0; + initTime_ntp = 0; + rtpQueueDiscard = false; + lastRtpQueueDiscardT_ntp = 0; + bytesLost = 0; + bytesCe = 0; + wasRepairLoss = false; + repairLoss = false; + for (int n = 0; n < kMaxTxPackets; n++) + txPackets[n].isUsed = false; + txPacketsPtr = 0; +} + +/* +* Update the estimated max media rate +*/ +void ScreamTx::Stream::updateRate(uint32_t time_ntp) { + if (lastRateUpdateT_ntp != 0) { + float tDelta = (time_ntp - lastRateUpdateT_ntp) * ntp2SecScaleFactor; + + rateTransmittedHist[rateUpdateHistPtr] = bytesTransmitted * 8.0f / tDelta; + rateAckedHist[rateUpdateHistPtr] = bytesAcked * 8.0f / tDelta; + rateLostHist[rateUpdateHistPtr] = bytesLost * 8.0f / tDelta; + rateCeHist[rateUpdateHistPtr] = bytesCe * 8.0f / tDelta; + rateRtpHist[rateUpdateHistPtr] = bytesRtp * 8.0f / tDelta; + rateUpdateHistPtr = (rateUpdateHistPtr + 1) % kRateUpDateSize; + rateTransmitted = 0.0f; + rateAcked = 0.0f; + rateLost = 0.0f; + rateCe = 0.0f; + rateRtp = 0.0f; + for (int n = 0; n < kRateUpDateSize; n++) { + rateTransmitted += rateTransmittedHist[n]; + rateAcked += rateAckedHist[n]; + rateLost += rateLostHist[n]; + rateCe += rateCeHist[n]; + rateRtp += rateRtpHist[n]; + } + rateTransmitted /= kRateUpDateSize; + rateAcked /= kRateUpDateSize; + rateLost /= kRateUpDateSize; + rateCe /= kRateUpDateSize; + rateRtp /= kRateUpDateSize; + if (rateRtp > 0 && isAdaptiveTargetRateScale) { + /* + * Video coders are strange animals.. In certain cases the average bitrate is + * consistently lower or higher than the target bitare. This additonal scaling compensates + * for this anomaly. + */ + + const float diff = targetBitrate / rateRtp; + float alpha = 0.02f; + if (diff < 0) + alpha = 1.0; + targetRateScale *= (1.0f - alpha); + targetRateScale += alpha * targetBitrate / rateRtp; + targetRateScale = std::min(1.1f, std::max(0.8f, targetRateScale)); + } + } + + bytesTransmitted = 0; + bytesAcked = 0; + bytesRtp = 0; + bytesLost = 0; + bytesCe = 0; + lastRateUpdateT_ntp = time_ntp; +} + +/* +* Get the estimated maximum media rate +*/ +float ScreamTx::Stream::getMaxRate() { + return std::max(rateTransmitted, rateAcked); +} + +/* +* The the stream that matches SSRC +*/ +ScreamTx::Stream* ScreamTx::getStream(uint32_t ssrc, int &streamId) { + for (int n = 0; n < nStreams; n++) { + if (streams[n]->isMatch(ssrc)) { + streamId = n; + return streams[n]; + } + } + streamId = -1; + return NULL; +} + +/* +* Get the target bitrate. +* This function returns a value -1 if loss of RTP packets is detected, +* either because of loss in network or RTP queue discard +*/ +float ScreamTx::Stream::getTargetBitrate() { + + bool requestRefresh = isRtpQueueDiscard() || repairLoss; + repairLoss = false; + if (requestRefresh && !wasRepairLoss) { + wasRepairLoss = true; + return -1.0; + } + float rate = targetRateScale * targetBitrate; + /* + * Video coders are strange animals.. In certain cases a very frequent rate requests can confuse the + * rate control logic in the coder + */ + wasRepairLoss = false; + return rate; +} + +/* +* A small history of past max bitrates is maintained and the max value is picked. +* This solves a problem where consequtive rate decreases can give too low +* targetBitrateI values. +*/ +void ScreamTx::Stream::updateTargetBitrateI(float br) { + targetBitrateHist[targetBitrateHistPtr] = std::min(br, targetBitrate); + targetBitrateHistPtr = (targetBitrateHistPtr + 1) % kTargetBitrateHistSize; + targetBitrateI = std::min(br, targetBitrate); + for (int n = 0; n < kTargetBitrateHistSize; n++) { + targetBitrateI = std::max(targetBitrateI, targetBitrateHist[n]); + } +} + +/* +* Update the target bitrate, the target bitrate includes the RTP overhead +*/ +void ScreamTx::Stream::updateTargetBitrate(uint32_t time_ntp) { + /* + * Compute a maximum bitrate, this bitrates includes the RTP overhead + */ + + float br = getMaxRate(); + float rateRtpLimit = br; + if (initTime_ntp == 0) { + /* + * Initialize if the first time + */ + initTime_ntp = time_ntp; + lastRtpQueueDiscardT_ntp = time_ntp; + } + + if (lastBitrateAdjustT_ntp == 0) lastBitrateAdjustT_ntp = time_ntp; + isActive = true; + lastFrameT_ntp = time_ntp; + if (lossEventFlag || ecnCeEventFlag) { + /* + * Loss event handling + * Rate is reduced slightly to avoid that more frames than necessary + * queue up in the sender queue + */ + if (time_ntp - lastTargetBitrateIUpdateT_ntp > 2000000) { + /* + * The timing constraint avoids that targetBitrateI + * is set too low in cases where a congestion event is prolonged. + * An accurate targetBitrateI is not of extreme importance + * but helps to avoid jitter spikes when SCReAM operates + * over fixed bandwidth or slowly varying links. + */ + updateTargetBitrateI(br); + lastTargetBitrateIUpdateT_ntp = time_ntp; + } + if (lossEventFlag) + targetBitrate = std::max(minBitrate, targetBitrate*lossEventRateScale); + else if (ecnCeEventFlag) { + if (parent->isL4s) { + targetBitrate = std::max(minBitrate, targetBitrate*(1.0f - parent->l4sAlpha / 4.0f)); + } + else { + targetBitrate = std::max(minBitrate, targetBitrate*ecnCeEventRateScale); + } + } + float rtpQueueDelay = rtpQueue->getDelay(time_ntp * ntp2SecScaleFactor); + if (rtpQueueDelay > maxRtpQueueDelay && + (time_ntp - lastRtpQueueDiscardT_ntp > kMinRtpQueueDiscardInterval_ntp)) { + /* + * RTP queue is cleared as it is becoming too large, + * Function is however disabled initially as there is no reliable estimate of the + * throughput in the initial phase. + */ + rtpQueue->clear(); + cerr << time_ntp / 65536.0f << " RTP queue discarded for SSRC " << ssrc << endl; + + rtpQueueDiscard = true; + + lastRtpQueueDiscardT_ntp = time_ntp; + targetRateScale = 1.0; + txSizeBitsAvg = 0.0f; + } + + lossEventFlag = false; + ecnCeEventFlag = false; + lastBitrateAdjustT_ntp = time_ntp; + } + else { + if (time_ntp - lastBitrateAdjustT_ntp < kRateAdjustInterval_ntp) + return; + /* + * A scale factor that is dependent on the inflection point + * i.e the last known highest video bitrate + */ + float sclI = (targetBitrate - targetBitrateI) / targetBitrateI; + sclI *= 4; + sclI = sclI*sclI; + sclI = std::max(0.05f, std::min(1.0f, sclI)); + float increment = 0.0f; + + /* + * Size of RTP queue [bits] + * As this function is called immediately after a + * video frame is produced, we need to accept the new + * RTP packets in the queue, we subtract a number of bytes correspoding to the size + * of the last frame (including RTP overhead), this is simply the aggregated size + * of the RTP packets with the highest RTP timestamp + * txSizeBits is the number of bits in the RTP queue, this is limited + * to just enable small adjustments of the bitrate when the RTP queue grows + */ + int lastBytes = rtpQueue->getSizeOfLastFrame(); + int txSizeBitsLimit = (int)(targetBitrate*0.02); + int txSizeBits = std::max(0, rtpQueue->bytesInQueue() - lastBytes) * 8; + txSizeBits = std::min(txSizeBits, txSizeBitsLimit); + + const float alpha = 0.5f; + + txSizeBitsAvg = txSizeBitsAvg * alpha + txSizeBits * (1.0f - alpha); + /* + * tmp is a local scaling factor that makes rate adaptation sligthly more + * aggressive when competing flows (e.g file transfers) are detected + */ + float rampUpSpeedTmp = std::min(rampUpSpeed, targetBitrate*rampUpScale); + if (parent->isCompetingFlows()) { + rampUpSpeedTmp *= 2.0f; + } + + float rtpQueueDelay = rtpQueue->getDelay(time_ntp * ntp2SecScaleFactor); + if (rtpQueueDelay > maxRtpQueueDelay && + (time_ntp - lastRtpQueueDiscardT_ntp > kMinRtpQueueDiscardInterval_ntp)) { + /* + * RTP queue is cleared as it is becoming too large, + * Function is however disabled initially as there is no reliable estimate of the + * throughput in the initial phase. + */ + rtpQueue->clear(); + cerr << time_ntp / 65536.0f << " RTP queue discarded for SSRC " << ssrc << endl; + + rtpQueueDiscard = true; + + lastRtpQueueDiscardT_ntp = time_ntp; + targetRateScale = 1.0; + txSizeBitsAvg = 0.0f; + } + else if (parent->inFastStart && rtpQueueDelay < 0.1f) { + /* + * Increment bitrate, limited by the rampUpSpeed + */ + increment = rampUpSpeedTmp * (kRateAdjustInterval_ntp * ntp2SecScaleFactor); + /* + * Limit increase rate near the last known highest bitrate or if priority is low + */ + increment *= sclI * sqrt(targetPriority); + /* + * Limited increase if the actual coder rate is lower than the target + */ + if (targetBitrate > rateRtpLimit) { + /* + * Limit increase if the target bitrate is considerably higher than the actual + * bitrate, this is an indication of an idle source. + * It can also be the case that the encoder consistently delivers a lower rate than + * the target rate. We don't want to deadlock the bitrate rampup because of this so + * we gradually reduce the increment the larger the difference is + */ + float scale = std::max(-1.0f, 2.0f*(rateRtpLimit / targetBitrate - 1.0f)); + increment *= (1.0 + scale); + } + /* + * Add increment + */ + targetBitrate += increment; + wasFastStart = true; + } + else { + if (wasFastStart) { + wasFastStart = false; + if (time_ntp - lastTargetBitrateIUpdateT_ntp > 65536) { // 1s in NTP domain + /* + * The timing constraint avoids that targetBitrateI + * is set too low in cases where a + * congestion event is prolonged + */ + updateTargetBitrateI(br); + lastTargetBitrateIUpdateT_ntp = time_ntp; + } + } + + /* + * Update target rate + * At very low bitrates it is necessary to actively try to push the + * the bitrate up some extra + */ + float incrementScale = 1.0f + 0.05f*std::min(1.0f, 50000.0f / targetBitrate); + + float increment = incrementScale * br; + if (!parent->isL4s || parent->l4sAlpha < 0.01) { + /* + * Apply the extra precaution with respect to queue delay and + * RTP queue only if L4S is not running or when ECN marking does not occur for a longer period + * scl is based on the queue delay trend + */ + float scl = queueDelayGuard * parent->getQueueDelayTrend(); + if (parent->isCompetingFlows()) + scl *= 0.05f; + increment = increment * (1.0f - scl) - txQueueSizeFactor * txSizeBitsAvg; + } + increment -= targetBitrate; + if (txSizeBits > 12000 && increment > 0) + increment = 0; + + if (increment > 0) { + wasFastStart = true; + if (!parent->isCompetingFlows()) { + /* + * Limit the bitrate increase so that it does not go faster than rampUpSpeedTmp + * This limitation is not in effect if competing flows are detected + */ + increment *= sclI; + increment = std::min(increment, (float)(rampUpSpeedTmp*(kRateAdjustInterval_ntp * ntp2SecScaleFactor))); + } + /* + * Limited increase if the actual coder rate is lower than the target + */ + if (targetBitrate > rateRtpLimit) { + /* + * Limit increase if the target bitrate is considerably higher than the actual + * bitrate, this is an indication of an idle source. + * It can also be the case that the encoder consistently delivers a lower rate than + * the target rate. We don't want to deadlock the bitrate rampup because of this so + * we gradually reduce the increment the larger the difference is + */ + float scale = std::max(-1.0f, 2.0f*(rateRtpLimit / targetBitrate - 1.0f)); + increment *= (1.0 + scale); + } + } + else { + /* + * Avoid that the target bitrate is reduced if it actually is the media + * coder that limits the output rate e.g due to inactivity + */ + if (rateRtp < targetBitrate) + increment = 0.0f; + /* + * Also avoid that the target bitrate is reduced if + * the coder bitrate is higher + * than the target. + * The possible reason is that a large I frame is transmitted, another reason is + * complex dynamic content. + */ + if (rateRtp > targetBitrate*2.0f) + increment = 0.0f; + } + targetBitrate += increment; + + } + lastBitrateAdjustT_ntp = time_ntp; + } + + targetBitrate = std::min(maxBitrate, std::max(minBitrate, targetBitrate)); +} + +bool ScreamTx::Stream::isRtpQueueDiscard() { + bool tmp = rtpQueueDiscard; + rtpQueueDiscard = false; + return tmp; +} diff --git a/code/bw-test-tool/code/ScreamTx.h b/code/bw-test-tool/code/ScreamTx.h deleted file mode 120000 index 6d7b7bb..0000000 --- a/code/bw-test-tool/code/ScreamTx.h +++ /dev/null @@ -1,709 +0,0 @@ -#ifndef SCREAM_TX -#define SCREAM_TX - -#include -#include -#include -extern "C" { -using namespace std; - -/* -* This module implements the sender side of SCReAM, -* see https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pdf -* for details on how it is integrated in audio/video platforms -* A full implementation needs the additional code for -* + RTP queue(s), one queue per stream, see SCReAM description for interface description -* + Other obvious stuff such as RTP payload packetizer, video+audio capture, coders.... -*/ - -/* -* Internal time is represented as the mid 32 bits of the NTP timestamp (see RFC5905) -* This means that the high order 16 bits is time in seconds and the low order 16 bits -* is the fraction. The NTP time stamp is thus in Q16 i.e 1.0sec is represented -* by the value 65536. -* All internal time is measured in NTP time, this is done to avoid wraparound issues -* that can otherwise occur every 18h hour or so -*/ - -// ==== Default parameters (if tuning necessary) ==== -// Connection related default parameters -// CWND scale factor upon loss event -static const float kLossBeta = 0.8f; -// CWND scale factor upon ECN-CE event -static const float kEcnCeBeta = 0.9f; -// Min and max queue delay target -static const float kQueueDelayTargetMin = 0.1f; //ms -// Enable shared botleneck detection and queue delay target adjustement -// good if SCReAM needs to compete with e.g FTP but -// Can in some cases cause self-inflicted congestion -// i.e the e2e delay can become large even though -// there is no competing traffic present -// For some reason, self-inflicted congestion is easily triggered -// when an audio + video stream is run, so bottomline is that -// this feature is a bit shaky -static const bool kEnableSbd = false; -// CWND up and down gain factors -static const float kGainUp = 1.0f; -static const float kGainDown = 2.0f; - -// Stream related default parameters -// Max video rampup speed in bps/s (bits per second increase per second) -static const float kRampUpSpeed = 200000.0f; // bps/s -// Max video rampup scale as fraction of the current target bitrate -static const float kRampUpScale = 0.2f; -// Max RTP queue delay, RTP queue is cleared if this value is exceeded -static const float kMaxRtpQueueDelay = 0.1; // 0.1s -// Compensation factor for RTP queue size -// A higher value such as 0.2 gives less jitter esp. in wireless (LTE) -// but potentially also lower link utilization -static const float kTxQueueSizeFactor = 0.2f; -// Compensation factor for detected congestion in rate computation -// A higher value such as 0.5 gives less jitter esp. in wireless (LTE) -// but potentially also lower link utilization -static const float kQueueDelayGuard = 0.1f; -// Video rate scaling due to loss events -static const float kLossEventRateScale = 0.9f; -// Video rate scaling due to ECN marking events -static const float kEcnCeEventRateScale = 0.95f; -// Headroom for packet pacing -static const float kPacketPacingHeadRoom = 1.25f; - -// Constants -/* -* Timestamp sampling rate for SCReAM feedback -*/ -static const int kTimeStampAtoScale = 1024; -const float ntp2SecScaleFactor = 1.0 / 65536; - -/* -* Max number of RTP packets in flight -* With an MSS = 1200 byte and an RTT = 50ms -* this is enough to support media bitrates up to ~800Mbps -* Note, 65536 % kMaxTxPackets must be zero -*/ -static const int kMaxTxPackets = 4096; -/* -* Max number of streams -*/ -static const int kMaxStreams = 10; -/* -* History vectors -*/ -static const int kBaseOwdHistSize = 50; -static const int kQueueDelayNormHistSize = 200; -static const int kQueueDelayNormHistSizeSh = 50; -static const int kQueueDelayFractionHistSize = 20; -static const int kBytesInFlightHistSizeMax = 60; -static const int kRateUpDateSize = 8; -static const int kTargetBitrateHistSize = 3; -static const int kLossRateHistSize = 10; - -class RtpQueueIface; -class ScreamTx { -public: - /* - * Constructor, see constant definitions above for an explanation of parameters - * cwnd > 0 sets a different initial congestion window, for example it can be set to - * initialrate/8*rtt - * cautiousPacing is set in the range [0.0..1.0]. A higher values restricts the transmission rate of large key frames - * which can be beneficial if it is evident that large key frames cause packet drops, for instance due to - * reduced buffer size in wireless modems. - * This is however at the potential cost of an overall increased transmission delay also when links are uncongested - * as the RTP packets are more likely to be buffered up on the sender side when cautiousPacing is set close to 1.0. - * lossBeta == 1.0 means that packet losses are ignored by the congestion control - * bytesInFlightHistSize can be set to a larger value than 5(s) for enhanced robustness to media coders that are idle - * for long periods - * isL4s = true changes congestion window reaction to ECN marking to a scalable function, similar to DCTCP - */ - ScreamTx(float lossBeta = kLossBeta, - float ecnCeBeta = kEcnCeBeta, - float queueDelayTargetMin = kQueueDelayTargetMin, - bool enableSbd = kEnableSbd, - float gainUp = kGainUp, - float gainDown = kGainDown, - int cwnd = 0, // An initial cwnd larger than 2*mss - float packetPacingHeadroom = kPacketPacingHeadRoom, - int bytesInFlightHistSize = 5, - bool isL4s = false, - bool openWindow = false, - bool enableClockDriftCompensation = false); - - ~ScreamTx(); - - /* - * Register a new stream {SSRC,PT} tuple, - * with a priority value in the range ]0.0..1.0] - * where 1.0 denotes the highest priority. - * It is recommended that at least one stream has prioritity 1.0. - * Bitrates are specified in bps - * See constant definitions above for an explanation of other default parameters - */ - void registerNewStream(RtpQueueIface *rtpQueue, - uint32_t ssrc, - float priority, // priority in range ]0.0 .. 1.0], 1.0 is highest - float minBitrate, // Min target bitrate - float startBitrate, // Starting bitrate - float maxBitrate, // Max target bitrate - float rampUpSpeed = kRampUpSpeed, - float rampUpScale = kRampUpScale, - float maxRtpQueueDelay = kMaxRtpQueueDelay, - float txQueueSizeFactor = kTxQueueSizeFactor, - float queueDelayGuard = kQueueDelayGuard, - float lossEventRateScale = kLossEventRateScale, - float ecnCeEventRateScale = kEcnCeEventRateScale, - bool isAdaptiveTargetRateScale = false); - - /* - * Updates the min and max bitrates for an existing stream - */ - void updateBitrateStream(uint32_t ssrc, - float minBitrate, - float maxBitrate); - - /* - * Access the configured RtpQueue of an existing stream - */ - RtpQueueIface * getStreamQueue(uint32_t ssrc); - - /* - * Call this function for each new video frame - * Note : isOkToTransmit should be called after newMediaFrame - */ - void newMediaFrame(uint32_t time_ntp, uint32_t ssrc, int bytesRtp); - - /* - * Function determines if an RTP packet with SSRC can be transmitted - * Return values : - * 0.0 : RTP packet with SSRC can be immediately transmitted - * addTransmitted must be called if packet is transmitted as a result of this - * >0.0 : Time [s] until this function should be called again - * This can be used to start a timer - * Note that a call to newMediaFrame or incomingFeedback should - * cause an immediate call to isOkToTransmit - * -1.0 : No RTP packet available to transmit or send window is not large enough - */ - float isOkToTransmit(uint32_t time_ntp, uint32_t &ssrc); - - /* - * Add packet to list of transmitted packets - * should be called when an RTP packet transmitted - * Return time until isOkToTransmit can be called again - */ - float addTransmitted(uint32_t timestamp_ntp, // Wall clock ts when packet is transmitted - uint32_t ssrc, - int size, - uint16_t seqNr, - bool isMark); - - /* New incoming feedback, this function - * triggers a CWND update - * The SCReAM timestamp is in jiffies, where the frequency is controlled - * by the timestamp clock frequency(default 1000Hz) - * The ackVector indicates recption of the 64 RTP SN prior to highestSeqNr - * Note : isOkToTransmit should be called after incomingFeedback - /* - /* Parse standardized feedback according to - * https://tools.ietf.org/wg/avtcore/draft-ietf-avtcore-cc-feedback-message/ - * Current implementation implements -02 version - * It is assumed that SR/RR or other non-CCFB feedback is stripped - */ - void incomingStandardizedFeedback(uint32_t time_ntp, - unsigned char* buf, - int size); - - void incomingStandardizedFeedback(uint32_t time_ntp, - int streamId, - uint32_t timestamp, - uint16_t seqNr, - uint8_t ceBits, - bool isLast); - /* - * Get the target bitrate for stream with SSRC - * NOTE!, Because SCReAM operates on RTP packets, the target bitrate will - * also include the RTP overhead. This means that a subsequent call to set the - * media coder target bitrate must subtract an estimate of the RTP + framing - * overhead. This is not critical for Video bitrates but can be important - * when SCReAM is used to congestion control e.g low bitrate audio streams - * Function returns -1 if a loss is detected, this signal can be used to - * request a new key frame from a video encoder - */ - float getTargetBitrate(uint32_t ssrc); - - /* - * Set target priority for a given stream, priority value should be in range ]0.0..1.0] - */ - void setTargetPriority(uint32_t ssrc, float aPriority); - - /* - * Set maxTotalBitrate - * This featire is useful if it is known that for instance a cellular modem does not support a higher uplink bitrate - * than e.g. 50Mbps. When maxTotalBitrate is set to 50Mbps, then unnecessary bandwidth probing beyond this bitrate - * is avoided, this reduces delay jitter. - */ - void setMaxTotalBitrate(float aMaxTotalBitrate) { - maxTotalBitrate = aMaxTotalBitrate; - } - - /* - * Get maxTotalBitrate - */ - float getMaxTotalBitrate() { - return maxTotalBitrate; - } - - - /* - * Get verbose log information - */ - void getLog(float time, char *s); - - /* - * Get verbose log information - */ - void getShortLog(float time, char *s); - - /* - * Get verbose log information - */ - void getVeryShortLog(float time, char *s); - - /* - * Get overall simplified statistics - */ - void getStatistics(float time, char *s); - - /* - * Set file pointer for detailed per-ACK log - */ - void setDetailedLogFp(FILE *fp) { - fp_log = fp; - } - - void setTimeString(char *s) { - strcpy(timeString,s); - } - - /* - * extra data to be appended to detailed log - */ - void setDetailedLogExtraData(char *s) { - strcpy(detailedLogExtraData,s); - } - - /* - * Get the list of log items - */ - char *getDetailedLogItemList() { - return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; - } - - /* - * Log each ACKed packet, - */ - void useExtraDetailedLog(bool isUseExtraDetailedLog_) { - isUseExtraDetailedLog = isUseExtraDetailedLog_; - } - - /* - * Set lowest possible cwndMin - */ - void setCwndMinLow(int aValue) { - cwndMinLow = aValue; - } - -private: - /* - * Struct for list of RTP packets in flight - */ - struct Transmitted { - uint32_t timeTx_ntp; - int size; - uint16_t seqNr; - bool isMark; - bool isUsed; - bool isAcked; - bool isAfterReceivedEdge; - }; - - /* - * Statistics for the network congestion control and the - * stream[0] - */ - class Statistics { - public: - Statistics(); - void getSummary(float time, char s[]); - void add(float rateTx, float rateLost, float rtt, float queueDelay); - private: - float lossRateHist[kLossRateHistSize]; - float rateLostAcc; - int rateLostN; - int lossRateHistPtr; - float avgRateTx; - float avgRtt; - float avgQueueDelay; - float sumRateTx; - float sumRateLost; - }; - - /* - * One instance is created for each {SSRC,PT} tuple - */ - class Stream { - public: - Stream(ScreamTx *parent, - RtpQueueIface *rtpQueue, - uint32_t ssrc, - float priority, - float minBitrate, - float startBitrate, - float maxBitrate, - float rampUpSpeed, - float rampUpScale, - float maxRtpQueueDelay, - float txQueueSizeFactor, - float queueDelayGuard, - float lossEventRateScale, - float ecnCeEventRateScale, - bool isAdaptiveTargetRateScale); - - float getMaxRate(); - - float getTargetBitrate(); - - void updateRate(uint32_t time_ntp); - - void updateTargetBitrateI(float br); - - void updateTargetBitrate(uint32_t time_ntp); - - bool isRtpQueueDiscard(); - - bool isMatch(uint32_t ssrc_) { return ssrc == ssrc_; }; - ScreamTx *parent; - RtpQueueIface *rtpQueue; // RTP Packet queue - uint32_t ssrc; // SSRC of stream - float rampUpSpeed; - float rampUpScale; - float maxRtpQueueDelay; - float txQueueSizeFactor; - float queueDelayGuard; - float lossEventRateScale; - float ecnCeEventRateScale; - bool isAdaptiveTargetRateScale; - - int credit; // Credit that is received if another stream gets - // priority to transmit - int creditLost; // Amount of lost (unused) credit, input to - // adjustPriorities function - float targetPriority; // Stream target priority - float targetPriorityInv;// Stream target priority inverted - int bytesTransmitted; // Number of bytes transmitted - int bytesAcked; // Number of ACKed bytes - int bytesLost; // Number of lost bytes - int bytesCe; // Number of lost bytes - float rateTransmitted; // Transmitted rate - float rateAcked; // ACKed rate - float rateLost; // Lost packets (bit)rate - float rateCe; // Lost packets (bit)rate - uint16_t hiSeqAck; // Highest sequence number ACKed - uint16_t hiSeqTx; // Highest sequence number transmitted - float minBitrate; // Min bitrate - float maxBitrate; // Max bitrate - float targetBitrate; // Target bitrate - float targetBitrateI; // Target bitrate inflection point - bool wasFastStart; // Was fast start - bool lossEventFlag; // Was loss event - bool ecnCeEventFlag; // Was ECN mark event - float txSizeBitsAvg; // Avergage nymber of bits in RTP queue - uint32_t lastBitrateAdjustT_ntp; // Last time rate was updated for this stream - uint32_t lastRateUpdateT_ntp; // Last time rate estimate was updated - uint32_t lastTargetBitrateIUpdateT_ntp; // Last time rate estimate was updated - - uint32_t timeTxAck_ntp; // timestamp when higest ACKed SN was transmitted - uint32_t lastTransmitT_ntp; - - int bytesRtp; // Number of RTP bytes from media coder - float rateRtp; // Media bitrate - float rateRtpHist[kRateUpDateSize]; - float rateAckedHist[kRateUpDateSize]; - float rateLostHist[kRateUpDateSize]; - float rateCeHist[kRateUpDateSize]; - float rateTransmittedHist[kRateUpDateSize]; - int rateUpdateHistPtr; - float targetBitrateHist[kTargetBitrateHistSize]; - int targetBitrateHistPtr; - uint32_t targetBitrateHistUpdateT_ntp; - float targetRateScale; - - bool isActive; - uint32_t lastFrameT_ntp; - uint32_t initTime_ntp; - bool rtpQueueDiscard; - uint32_t lastRtpQueueDiscardT_ntp; - bool wasRepairLoss; - bool repairLoss; - - Transmitted txPackets[kMaxTxPackets]; - int txPacketsPtr; - }; - - /* - * Initialize values - */ - void initialize(uint32_t time_ntp); - - /* - * Mark ACKed RTP packets - */ - void markAcked(uint32_t time_ntp, - struct Transmitted *txPackets, - uint16_t seqNr, - uint32_t timestamp, - Stream *stream, - uint8_t ceBits, - int &encCeMarkedBytes, - bool isLast, - bool &isMark); - - /* - * Get total target bitrate for all streams - */ - float getTotalTargetBitrate(); - - /* - * Update CWND - */ - void updateCwnd(uint32_t time_ntp); - - /* - * Detect lost RTP packets - */ - void detectLoss(uint32_t time_ntp, struct Transmitted *txPackets, uint16_t highestSeqNr, Stream *stream); - - /* - * Call this function at regular intervals to determine active streams - */ - void determineActiveStreams(uint32_t time_ntp); - - /* - * Compute 1st order prediction coefficient of queue delay multiplied by the queue delay fraction - * A value [0.0..1.0] indicates if queue delay is increasing - * This gives a rough estimate of how the queuing delay delay evolves - */ - void computeQueueDelayTrend(); - - /* - * Estimate one way delay [jiffy] and updated base delay - * Base delay is not subtracted - */ - void estimateOwd(uint32_t time_ntp); - - /* - * return base delay [jiffy] - */ - uint32_t getBaseOwd(); - - /* - * Compute indicators of shared bottleneck - */ - void computeSbd(); - - /* - * True if competing (TCP)flows detected - */ - bool isCompetingFlows(); - - /* - * Get stream with corresponding SSRC - */ - Stream* getStream(uint32_t ssrc, int &streamId); - - /* - * Adjust stream bitrates to reflect priorities - */ - void adjustPriorities(uint32_t time_ntp); - - /* - * Get the prioritized stream - * Return NULL if no stream with - * with RTP packets - */ - Stream* getPrioritizedStream(uint32_t time_ntp); - - /* - * Add credit to unserved streams - */ - void addCredit(uint32_t time_ntp, - Stream* servedStream, - int transmittedBytes); - - /* - * Subtract used credit - */ - void subtractCredit(uint32_t time_ntp, - Stream* servedStream, - int transmittedBytes); - - /* - * return 1 if in fast start - */ - int isInFastStart() { return inFastStart ? 1 : 0; }; - - /* - * Get the fraction between queue delay and the queue delay target - */ - float getQueueDelayFraction(); - - /* - * Get the queuing delay trend - */ - float getQueueDelayTrend(); - - /* - * Variables for network congestion control - */ - - /* - * Related to computation of queue delay and target queuing delay - */ - float lossBeta; - float ecnCeBeta; - float queueDelayTargetMin; - bool enableSbd; - float gainUp; - float gainDown; - float packetPacingHeadroom; - - uint32_t sRttSh_ntp; - uint32_t sRtt_ntp; - float sRtt; - uint32_t ackedOwd; - uint32_t baseOwd; - - uint32_t baseOwdHist[kBaseOwdHistSize]; - int baseOwdHistPtr; - uint32_t baseOwdHistMin; - uint32_t clockDriftCompensation; - uint32_t clockDriftCompensationInc; - - float queueDelay; - float queueDelayFractionAvg; - float queueDelayFractionHist[kQueueDelayFractionHistSize]; - int queueDelayFractionHistPtr; - float queueDelayTrend; - float queueDelayTarget; - float queueDelayNormHist[kQueueDelayNormHistSize]; - int queueDelayNormHistPtr; - float queueDelaySbdVar; - float queueDelaySbdMean; - float queueDelaySbdSkew; - float queueDelaySbdMeanSh; - float queueDelayMax; - - /* - * CWND management - */ - int bytesNewlyAcked; - int mss; // Maximum Segment Size - int cwnd; // congestion window - int cwndMin; - int cwndMinLow; - bool openWindow; - bool enableClockDriftCompensation; - int bytesInFlight; - int bytesInFlightLog; - int bytesInFlightHistLo[kBytesInFlightHistSizeMax]; - int bytesInFlightHistHi[kBytesInFlightHistSizeMax]; - int bytesInFlightHistSize; - int bytesInFlightHistPtr; - int bytesInFlightMaxLo; - int bytesInFlightHistLoMem; - int bytesInFlightMaxHi; - int bytesInFlightHistHiMem; - float maxBytesInFlight; - int accBytesInFlightMax; - int nAccBytesInFlightMax; - float rateTransmitted; - float rateAcked; - float queueDelayTrendMem; - float maxRate; - uint32_t lastCwndUpdateT_ntp; - bool isL4s; - float l4sAlpha; - int bytesMarkedThisRtt; - int bytesDeliveredThisRtt; - uint32_t lastL4sAlphaUpdateT_ntp; - float maxTotalBitrate; - - /* - * Loss event - */ - bool lossEvent; - bool wasLossEvent; - float lossEventRate; - - /* - * ECN-CE - */ - bool ecnCeEvent; - - /* - * Fast start - */ - bool inFastStart; - - /* - * Transmission scheduling - */ - uint32_t paceInterval_ntp; - float paceInterval; - float rateTransmittedAvg; - - /* - * Update control variables - */ - bool isInitialized; - uint32_t lastSRttUpdateT_ntp; - uint32_t lastBaseOwdAddT_ntp; - uint32_t baseOwdResetT_ntp; - uint32_t lastAddToQueueDelayFractionHistT_ntp; - uint32_t lastBytesInFlightT_ntp; - uint32_t lastCongestionDetectedT_ntp; - uint32_t lastLossEventT_ntp; - uint32_t lastTransmitT_ntp; - uint32_t nextTransmitT_ntp; - uint32_t lastRateUpdateT_ntp; - uint32_t lastAdjustPrioritiesT_ntp; - uint32_t lastRttT_ntp; - uint32_t lastBaseDelayRefreshT_ntp; - uint32_t initTime_ntp; - float queueDelayMin; - float queueDelayMinAvg; - - /* - * Variables for multiple steams handling - */ - Stream *streams[kMaxStreams]; - int nStreams; - - /* - * Statistics - */ - Statistics *statistics; - char detailedLogExtraData[256]; - - /* - * - */ - FILE *fp_log; - bool completeLogItem; - char timeString[100]; - bool isUseExtraDetailedLog; - int bytesNewlyAckedLog; - int ecnCeMarkedBytesLog; - - -}; -} -#endif diff --git a/code/bw-test-tool/code/ScreamTx.h b/code/bw-test-tool/code/ScreamTx.h new file mode 100644 index 0000000..6d7b7bb --- /dev/null +++ b/code/bw-test-tool/code/ScreamTx.h @@ -0,0 +1,709 @@ +#ifndef SCREAM_TX +#define SCREAM_TX + +#include +#include +#include +extern "C" { +using namespace std; + +/* +* This module implements the sender side of SCReAM, +* see https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pdf +* for details on how it is integrated in audio/video platforms +* A full implementation needs the additional code for +* + RTP queue(s), one queue per stream, see SCReAM description for interface description +* + Other obvious stuff such as RTP payload packetizer, video+audio capture, coders.... +*/ + +/* +* Internal time is represented as the mid 32 bits of the NTP timestamp (see RFC5905) +* This means that the high order 16 bits is time in seconds and the low order 16 bits +* is the fraction. The NTP time stamp is thus in Q16 i.e 1.0sec is represented +* by the value 65536. +* All internal time is measured in NTP time, this is done to avoid wraparound issues +* that can otherwise occur every 18h hour or so +*/ + +// ==== Default parameters (if tuning necessary) ==== +// Connection related default parameters +// CWND scale factor upon loss event +static const float kLossBeta = 0.8f; +// CWND scale factor upon ECN-CE event +static const float kEcnCeBeta = 0.9f; +// Min and max queue delay target +static const float kQueueDelayTargetMin = 0.1f; //ms +// Enable shared botleneck detection and queue delay target adjustement +// good if SCReAM needs to compete with e.g FTP but +// Can in some cases cause self-inflicted congestion +// i.e the e2e delay can become large even though +// there is no competing traffic present +// For some reason, self-inflicted congestion is easily triggered +// when an audio + video stream is run, so bottomline is that +// this feature is a bit shaky +static const bool kEnableSbd = false; +// CWND up and down gain factors +static const float kGainUp = 1.0f; +static const float kGainDown = 2.0f; + +// Stream related default parameters +// Max video rampup speed in bps/s (bits per second increase per second) +static const float kRampUpSpeed = 200000.0f; // bps/s +// Max video rampup scale as fraction of the current target bitrate +static const float kRampUpScale = 0.2f; +// Max RTP queue delay, RTP queue is cleared if this value is exceeded +static const float kMaxRtpQueueDelay = 0.1; // 0.1s +// Compensation factor for RTP queue size +// A higher value such as 0.2 gives less jitter esp. in wireless (LTE) +// but potentially also lower link utilization +static const float kTxQueueSizeFactor = 0.2f; +// Compensation factor for detected congestion in rate computation +// A higher value such as 0.5 gives less jitter esp. in wireless (LTE) +// but potentially also lower link utilization +static const float kQueueDelayGuard = 0.1f; +// Video rate scaling due to loss events +static const float kLossEventRateScale = 0.9f; +// Video rate scaling due to ECN marking events +static const float kEcnCeEventRateScale = 0.95f; +// Headroom for packet pacing +static const float kPacketPacingHeadRoom = 1.25f; + +// Constants +/* +* Timestamp sampling rate for SCReAM feedback +*/ +static const int kTimeStampAtoScale = 1024; +const float ntp2SecScaleFactor = 1.0 / 65536; + +/* +* Max number of RTP packets in flight +* With an MSS = 1200 byte and an RTT = 50ms +* this is enough to support media bitrates up to ~800Mbps +* Note, 65536 % kMaxTxPackets must be zero +*/ +static const int kMaxTxPackets = 4096; +/* +* Max number of streams +*/ +static const int kMaxStreams = 10; +/* +* History vectors +*/ +static const int kBaseOwdHistSize = 50; +static const int kQueueDelayNormHistSize = 200; +static const int kQueueDelayNormHistSizeSh = 50; +static const int kQueueDelayFractionHistSize = 20; +static const int kBytesInFlightHistSizeMax = 60; +static const int kRateUpDateSize = 8; +static const int kTargetBitrateHistSize = 3; +static const int kLossRateHistSize = 10; + +class RtpQueueIface; +class ScreamTx { +public: + /* + * Constructor, see constant definitions above for an explanation of parameters + * cwnd > 0 sets a different initial congestion window, for example it can be set to + * initialrate/8*rtt + * cautiousPacing is set in the range [0.0..1.0]. A higher values restricts the transmission rate of large key frames + * which can be beneficial if it is evident that large key frames cause packet drops, for instance due to + * reduced buffer size in wireless modems. + * This is however at the potential cost of an overall increased transmission delay also when links are uncongested + * as the RTP packets are more likely to be buffered up on the sender side when cautiousPacing is set close to 1.0. + * lossBeta == 1.0 means that packet losses are ignored by the congestion control + * bytesInFlightHistSize can be set to a larger value than 5(s) for enhanced robustness to media coders that are idle + * for long periods + * isL4s = true changes congestion window reaction to ECN marking to a scalable function, similar to DCTCP + */ + ScreamTx(float lossBeta = kLossBeta, + float ecnCeBeta = kEcnCeBeta, + float queueDelayTargetMin = kQueueDelayTargetMin, + bool enableSbd = kEnableSbd, + float gainUp = kGainUp, + float gainDown = kGainDown, + int cwnd = 0, // An initial cwnd larger than 2*mss + float packetPacingHeadroom = kPacketPacingHeadRoom, + int bytesInFlightHistSize = 5, + bool isL4s = false, + bool openWindow = false, + bool enableClockDriftCompensation = false); + + ~ScreamTx(); + + /* + * Register a new stream {SSRC,PT} tuple, + * with a priority value in the range ]0.0..1.0] + * where 1.0 denotes the highest priority. + * It is recommended that at least one stream has prioritity 1.0. + * Bitrates are specified in bps + * See constant definitions above for an explanation of other default parameters + */ + void registerNewStream(RtpQueueIface *rtpQueue, + uint32_t ssrc, + float priority, // priority in range ]0.0 .. 1.0], 1.0 is highest + float minBitrate, // Min target bitrate + float startBitrate, // Starting bitrate + float maxBitrate, // Max target bitrate + float rampUpSpeed = kRampUpSpeed, + float rampUpScale = kRampUpScale, + float maxRtpQueueDelay = kMaxRtpQueueDelay, + float txQueueSizeFactor = kTxQueueSizeFactor, + float queueDelayGuard = kQueueDelayGuard, + float lossEventRateScale = kLossEventRateScale, + float ecnCeEventRateScale = kEcnCeEventRateScale, + bool isAdaptiveTargetRateScale = false); + + /* + * Updates the min and max bitrates for an existing stream + */ + void updateBitrateStream(uint32_t ssrc, + float minBitrate, + float maxBitrate); + + /* + * Access the configured RtpQueue of an existing stream + */ + RtpQueueIface * getStreamQueue(uint32_t ssrc); + + /* + * Call this function for each new video frame + * Note : isOkToTransmit should be called after newMediaFrame + */ + void newMediaFrame(uint32_t time_ntp, uint32_t ssrc, int bytesRtp); + + /* + * Function determines if an RTP packet with SSRC can be transmitted + * Return values : + * 0.0 : RTP packet with SSRC can be immediately transmitted + * addTransmitted must be called if packet is transmitted as a result of this + * >0.0 : Time [s] until this function should be called again + * This can be used to start a timer + * Note that a call to newMediaFrame or incomingFeedback should + * cause an immediate call to isOkToTransmit + * -1.0 : No RTP packet available to transmit or send window is not large enough + */ + float isOkToTransmit(uint32_t time_ntp, uint32_t &ssrc); + + /* + * Add packet to list of transmitted packets + * should be called when an RTP packet transmitted + * Return time until isOkToTransmit can be called again + */ + float addTransmitted(uint32_t timestamp_ntp, // Wall clock ts when packet is transmitted + uint32_t ssrc, + int size, + uint16_t seqNr, + bool isMark); + + /* New incoming feedback, this function + * triggers a CWND update + * The SCReAM timestamp is in jiffies, where the frequency is controlled + * by the timestamp clock frequency(default 1000Hz) + * The ackVector indicates recption of the 64 RTP SN prior to highestSeqNr + * Note : isOkToTransmit should be called after incomingFeedback + /* + /* Parse standardized feedback according to + * https://tools.ietf.org/wg/avtcore/draft-ietf-avtcore-cc-feedback-message/ + * Current implementation implements -02 version + * It is assumed that SR/RR or other non-CCFB feedback is stripped + */ + void incomingStandardizedFeedback(uint32_t time_ntp, + unsigned char* buf, + int size); + + void incomingStandardizedFeedback(uint32_t time_ntp, + int streamId, + uint32_t timestamp, + uint16_t seqNr, + uint8_t ceBits, + bool isLast); + /* + * Get the target bitrate for stream with SSRC + * NOTE!, Because SCReAM operates on RTP packets, the target bitrate will + * also include the RTP overhead. This means that a subsequent call to set the + * media coder target bitrate must subtract an estimate of the RTP + framing + * overhead. This is not critical for Video bitrates but can be important + * when SCReAM is used to congestion control e.g low bitrate audio streams + * Function returns -1 if a loss is detected, this signal can be used to + * request a new key frame from a video encoder + */ + float getTargetBitrate(uint32_t ssrc); + + /* + * Set target priority for a given stream, priority value should be in range ]0.0..1.0] + */ + void setTargetPriority(uint32_t ssrc, float aPriority); + + /* + * Set maxTotalBitrate + * This featire is useful if it is known that for instance a cellular modem does not support a higher uplink bitrate + * than e.g. 50Mbps. When maxTotalBitrate is set to 50Mbps, then unnecessary bandwidth probing beyond this bitrate + * is avoided, this reduces delay jitter. + */ + void setMaxTotalBitrate(float aMaxTotalBitrate) { + maxTotalBitrate = aMaxTotalBitrate; + } + + /* + * Get maxTotalBitrate + */ + float getMaxTotalBitrate() { + return maxTotalBitrate; + } + + + /* + * Get verbose log information + */ + void getLog(float time, char *s); + + /* + * Get verbose log information + */ + void getShortLog(float time, char *s); + + /* + * Get verbose log information + */ + void getVeryShortLog(float time, char *s); + + /* + * Get overall simplified statistics + */ + void getStatistics(float time, char *s); + + /* + * Set file pointer for detailed per-ACK log + */ + void setDetailedLogFp(FILE *fp) { + fp_log = fp; + } + + void setTimeString(char *s) { + strcpy(timeString,s); + } + + /* + * extra data to be appended to detailed log + */ + void setDetailedLogExtraData(char *s) { + strcpy(detailedLogExtraData,s); + } + + /* + * Get the list of log items + */ + char *getDetailedLogItemList() { + return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; + } + + /* + * Log each ACKed packet, + */ + void useExtraDetailedLog(bool isUseExtraDetailedLog_) { + isUseExtraDetailedLog = isUseExtraDetailedLog_; + } + + /* + * Set lowest possible cwndMin + */ + void setCwndMinLow(int aValue) { + cwndMinLow = aValue; + } + +private: + /* + * Struct for list of RTP packets in flight + */ + struct Transmitted { + uint32_t timeTx_ntp; + int size; + uint16_t seqNr; + bool isMark; + bool isUsed; + bool isAcked; + bool isAfterReceivedEdge; + }; + + /* + * Statistics for the network congestion control and the + * stream[0] + */ + class Statistics { + public: + Statistics(); + void getSummary(float time, char s[]); + void add(float rateTx, float rateLost, float rtt, float queueDelay); + private: + float lossRateHist[kLossRateHistSize]; + float rateLostAcc; + int rateLostN; + int lossRateHistPtr; + float avgRateTx; + float avgRtt; + float avgQueueDelay; + float sumRateTx; + float sumRateLost; + }; + + /* + * One instance is created for each {SSRC,PT} tuple + */ + class Stream { + public: + Stream(ScreamTx *parent, + RtpQueueIface *rtpQueue, + uint32_t ssrc, + float priority, + float minBitrate, + float startBitrate, + float maxBitrate, + float rampUpSpeed, + float rampUpScale, + float maxRtpQueueDelay, + float txQueueSizeFactor, + float queueDelayGuard, + float lossEventRateScale, + float ecnCeEventRateScale, + bool isAdaptiveTargetRateScale); + + float getMaxRate(); + + float getTargetBitrate(); + + void updateRate(uint32_t time_ntp); + + void updateTargetBitrateI(float br); + + void updateTargetBitrate(uint32_t time_ntp); + + bool isRtpQueueDiscard(); + + bool isMatch(uint32_t ssrc_) { return ssrc == ssrc_; }; + ScreamTx *parent; + RtpQueueIface *rtpQueue; // RTP Packet queue + uint32_t ssrc; // SSRC of stream + float rampUpSpeed; + float rampUpScale; + float maxRtpQueueDelay; + float txQueueSizeFactor; + float queueDelayGuard; + float lossEventRateScale; + float ecnCeEventRateScale; + bool isAdaptiveTargetRateScale; + + int credit; // Credit that is received if another stream gets + // priority to transmit + int creditLost; // Amount of lost (unused) credit, input to + // adjustPriorities function + float targetPriority; // Stream target priority + float targetPriorityInv;// Stream target priority inverted + int bytesTransmitted; // Number of bytes transmitted + int bytesAcked; // Number of ACKed bytes + int bytesLost; // Number of lost bytes + int bytesCe; // Number of lost bytes + float rateTransmitted; // Transmitted rate + float rateAcked; // ACKed rate + float rateLost; // Lost packets (bit)rate + float rateCe; // Lost packets (bit)rate + uint16_t hiSeqAck; // Highest sequence number ACKed + uint16_t hiSeqTx; // Highest sequence number transmitted + float minBitrate; // Min bitrate + float maxBitrate; // Max bitrate + float targetBitrate; // Target bitrate + float targetBitrateI; // Target bitrate inflection point + bool wasFastStart; // Was fast start + bool lossEventFlag; // Was loss event + bool ecnCeEventFlag; // Was ECN mark event + float txSizeBitsAvg; // Avergage nymber of bits in RTP queue + uint32_t lastBitrateAdjustT_ntp; // Last time rate was updated for this stream + uint32_t lastRateUpdateT_ntp; // Last time rate estimate was updated + uint32_t lastTargetBitrateIUpdateT_ntp; // Last time rate estimate was updated + + uint32_t timeTxAck_ntp; // timestamp when higest ACKed SN was transmitted + uint32_t lastTransmitT_ntp; + + int bytesRtp; // Number of RTP bytes from media coder + float rateRtp; // Media bitrate + float rateRtpHist[kRateUpDateSize]; + float rateAckedHist[kRateUpDateSize]; + float rateLostHist[kRateUpDateSize]; + float rateCeHist[kRateUpDateSize]; + float rateTransmittedHist[kRateUpDateSize]; + int rateUpdateHistPtr; + float targetBitrateHist[kTargetBitrateHistSize]; + int targetBitrateHistPtr; + uint32_t targetBitrateHistUpdateT_ntp; + float targetRateScale; + + bool isActive; + uint32_t lastFrameT_ntp; + uint32_t initTime_ntp; + bool rtpQueueDiscard; + uint32_t lastRtpQueueDiscardT_ntp; + bool wasRepairLoss; + bool repairLoss; + + Transmitted txPackets[kMaxTxPackets]; + int txPacketsPtr; + }; + + /* + * Initialize values + */ + void initialize(uint32_t time_ntp); + + /* + * Mark ACKed RTP packets + */ + void markAcked(uint32_t time_ntp, + struct Transmitted *txPackets, + uint16_t seqNr, + uint32_t timestamp, + Stream *stream, + uint8_t ceBits, + int &encCeMarkedBytes, + bool isLast, + bool &isMark); + + /* + * Get total target bitrate for all streams + */ + float getTotalTargetBitrate(); + + /* + * Update CWND + */ + void updateCwnd(uint32_t time_ntp); + + /* + * Detect lost RTP packets + */ + void detectLoss(uint32_t time_ntp, struct Transmitted *txPackets, uint16_t highestSeqNr, Stream *stream); + + /* + * Call this function at regular intervals to determine active streams + */ + void determineActiveStreams(uint32_t time_ntp); + + /* + * Compute 1st order prediction coefficient of queue delay multiplied by the queue delay fraction + * A value [0.0..1.0] indicates if queue delay is increasing + * This gives a rough estimate of how the queuing delay delay evolves + */ + void computeQueueDelayTrend(); + + /* + * Estimate one way delay [jiffy] and updated base delay + * Base delay is not subtracted + */ + void estimateOwd(uint32_t time_ntp); + + /* + * return base delay [jiffy] + */ + uint32_t getBaseOwd(); + + /* + * Compute indicators of shared bottleneck + */ + void computeSbd(); + + /* + * True if competing (TCP)flows detected + */ + bool isCompetingFlows(); + + /* + * Get stream with corresponding SSRC + */ + Stream* getStream(uint32_t ssrc, int &streamId); + + /* + * Adjust stream bitrates to reflect priorities + */ + void adjustPriorities(uint32_t time_ntp); + + /* + * Get the prioritized stream + * Return NULL if no stream with + * with RTP packets + */ + Stream* getPrioritizedStream(uint32_t time_ntp); + + /* + * Add credit to unserved streams + */ + void addCredit(uint32_t time_ntp, + Stream* servedStream, + int transmittedBytes); + + /* + * Subtract used credit + */ + void subtractCredit(uint32_t time_ntp, + Stream* servedStream, + int transmittedBytes); + + /* + * return 1 if in fast start + */ + int isInFastStart() { return inFastStart ? 1 : 0; }; + + /* + * Get the fraction between queue delay and the queue delay target + */ + float getQueueDelayFraction(); + + /* + * Get the queuing delay trend + */ + float getQueueDelayTrend(); + + /* + * Variables for network congestion control + */ + + /* + * Related to computation of queue delay and target queuing delay + */ + float lossBeta; + float ecnCeBeta; + float queueDelayTargetMin; + bool enableSbd; + float gainUp; + float gainDown; + float packetPacingHeadroom; + + uint32_t sRttSh_ntp; + uint32_t sRtt_ntp; + float sRtt; + uint32_t ackedOwd; + uint32_t baseOwd; + + uint32_t baseOwdHist[kBaseOwdHistSize]; + int baseOwdHistPtr; + uint32_t baseOwdHistMin; + uint32_t clockDriftCompensation; + uint32_t clockDriftCompensationInc; + + float queueDelay; + float queueDelayFractionAvg; + float queueDelayFractionHist[kQueueDelayFractionHistSize]; + int queueDelayFractionHistPtr; + float queueDelayTrend; + float queueDelayTarget; + float queueDelayNormHist[kQueueDelayNormHistSize]; + int queueDelayNormHistPtr; + float queueDelaySbdVar; + float queueDelaySbdMean; + float queueDelaySbdSkew; + float queueDelaySbdMeanSh; + float queueDelayMax; + + /* + * CWND management + */ + int bytesNewlyAcked; + int mss; // Maximum Segment Size + int cwnd; // congestion window + int cwndMin; + int cwndMinLow; + bool openWindow; + bool enableClockDriftCompensation; + int bytesInFlight; + int bytesInFlightLog; + int bytesInFlightHistLo[kBytesInFlightHistSizeMax]; + int bytesInFlightHistHi[kBytesInFlightHistSizeMax]; + int bytesInFlightHistSize; + int bytesInFlightHistPtr; + int bytesInFlightMaxLo; + int bytesInFlightHistLoMem; + int bytesInFlightMaxHi; + int bytesInFlightHistHiMem; + float maxBytesInFlight; + int accBytesInFlightMax; + int nAccBytesInFlightMax; + float rateTransmitted; + float rateAcked; + float queueDelayTrendMem; + float maxRate; + uint32_t lastCwndUpdateT_ntp; + bool isL4s; + float l4sAlpha; + int bytesMarkedThisRtt; + int bytesDeliveredThisRtt; + uint32_t lastL4sAlphaUpdateT_ntp; + float maxTotalBitrate; + + /* + * Loss event + */ + bool lossEvent; + bool wasLossEvent; + float lossEventRate; + + /* + * ECN-CE + */ + bool ecnCeEvent; + + /* + * Fast start + */ + bool inFastStart; + + /* + * Transmission scheduling + */ + uint32_t paceInterval_ntp; + float paceInterval; + float rateTransmittedAvg; + + /* + * Update control variables + */ + bool isInitialized; + uint32_t lastSRttUpdateT_ntp; + uint32_t lastBaseOwdAddT_ntp; + uint32_t baseOwdResetT_ntp; + uint32_t lastAddToQueueDelayFractionHistT_ntp; + uint32_t lastBytesInFlightT_ntp; + uint32_t lastCongestionDetectedT_ntp; + uint32_t lastLossEventT_ntp; + uint32_t lastTransmitT_ntp; + uint32_t nextTransmitT_ntp; + uint32_t lastRateUpdateT_ntp; + uint32_t lastAdjustPrioritiesT_ntp; + uint32_t lastRttT_ntp; + uint32_t lastBaseDelayRefreshT_ntp; + uint32_t initTime_ntp; + float queueDelayMin; + float queueDelayMinAvg; + + /* + * Variables for multiple steams handling + */ + Stream *streams[kMaxStreams]; + int nStreams; + + /* + * Statistics + */ + Statistics *statistics; + char detailedLogExtraData[256]; + + /* + * + */ + FILE *fp_log; + bool completeLogItem; + char timeString[100]; + bool isUseExtraDetailedLog; + int bytesNewlyAckedLog; + int ecnCeMarkedBytesLog; + + +}; +} +#endif From 9b623e4f6356d084591caea9da86a95c3af9eb40 Mon Sep 17 00:00:00 2001 From: mirabilos Date: Tue, 30 Jun 2020 13:49:56 +0200 Subject: [PATCH 2/9] apply the changes I know are absolutely correct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • whitespace cleanup / harmonisation • const • some ordering without side effects • comments • correct type *_MAX constant --- code/ScreamTx.cpp | 2 +- code/ScreamTx.h | 14 +++++++------- code/bw-test-tool/code/RtpQueue.cpp | 4 ++-- code/bw-test-tool/code/RtpQueue.h | 10 +++++----- code/bw-test-tool/code/ScreamRx.cpp | 2 +- code/bw-test-tool/code/ScreamTx.cpp | 2 +- code/bw-test-tool/code/ScreamTx.h | 14 +++++++------- code/bw-test-tool/code/scream_receiver.cpp | 4 ++-- code/bw-test-tool/code/scream_sender.cpp | 4 ++-- 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/code/ScreamTx.cpp b/code/ScreamTx.cpp index ef2f9ed..3a881dc 100644 --- a/code/ScreamTx.cpp +++ b/code/ScreamTx.cpp @@ -1060,7 +1060,7 @@ void ScreamTx::initialize(uint32_t time_ntp) { float ScreamTx::getTotalTargetBitrate() { float totalTargetBitrate = 0.0f; for (int n = 0; n < nStreams; n++) { - totalTargetBitrate += streams[n]->targetBitrate; + totalTargetBitrate += streams[n]->targetBitrate; } return totalTargetBitrate; } diff --git a/code/ScreamTx.h b/code/ScreamTx.h index 617baa8..bc46feb 100644 --- a/code/ScreamTx.h +++ b/code/ScreamTx.h @@ -50,7 +50,7 @@ static const float kGainDown = 2.0f; // Max video rampup speed in bps/s (bits per second increase per second) static const float kRampUpSpeed = 200000.0f; // bps/s // Max video rampup scale as fraction of the current target bitrate -static const float kRampUpScale = 0.2f; +static const float kRampUpScale = 0.2f; // Max RTP queue delay, RTP queue is cleared if this value is exceeded static const float kMaxRtpQueueDelay = 0.1; // 0.1s // Compensation factor for RTP queue size @@ -279,7 +279,7 @@ class ScreamTx { void setTimeString(char *s) { strcpy(timeString,s); - } + } /* * extra data to be appended to detailed log @@ -288,20 +288,20 @@ class ScreamTx { strcpy(detailedLogExtraData,s); } - /* - * Get the list of log items + /* + * Get the list of log items */ - char *getDetailedLogItemList() { + const char *getDetailedLogItemList() { return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; } /* - * Log each ACKed packet, + * Log each ACKed packet, */ void useExtraDetailedLog(bool isUseExtraDetailedLog_) { isUseExtraDetailedLog = isUseExtraDetailedLog_; } - + /* * Set lowest possible cwndMin */ diff --git a/code/bw-test-tool/code/RtpQueue.cpp b/code/bw-test-tool/code/RtpQueue.cpp index 0b1e044..870be39 100644 --- a/code/bw-test-tool/code/RtpQueue.cpp +++ b/code/bw-test-tool/code/RtpQueue.cpp @@ -1,4 +1,4 @@ - #include "RtpQueue.h" +#include "RtpQueue.h" #include #include using namespace std; @@ -55,9 +55,9 @@ bool RtpQueue::pop(void *rtpPacket, int& size, unsigned short& seqNr) { memcpy(rtpPacket,items[tail]->packet,size); seqNr = items[tail]->seqNr; items[tail]->used = false; - tail++; if (tail == kRtpQueueSize) tail = 0; bytesInQueue_ -= size; sizeOfQueue_ -= 1; + tail++; if (tail == kRtpQueueSize) tail = 0; computeSizeOfNextRtp(); return true; } diff --git a/code/bw-test-tool/code/RtpQueue.h b/code/bw-test-tool/code/RtpQueue.h index 175d175..8af9d29 100644 --- a/code/bw-test-tool/code/RtpQueue.h +++ b/code/bw-test-tool/code/RtpQueue.h @@ -2,9 +2,9 @@ #define RTP_QUEUE /* -* Implements a simple RTP packet queue, one RTP queue -* per stream {SSRC,PT} -*/ + * Implements a simple RTP packet queue, one RTP queue + * per stream {SSRC,PT} + */ class RtpQueueIface { public: @@ -41,15 +41,15 @@ class RtpQueue : public RtpQueueIface { float getDelay(float currTs); bool sendPacket(void *rtpPacket, int &size, unsigned short &seqNr); void clear(); + void setSizeOfLastFrame(int sz) { sizeOfLastFrame = sz; }; int getSizeOfLastFrame() {return sizeOfLastFrame;}; - void setSizeOfLastFrame(int sz) {sizeOfLastFrame=sz;}; void computeSizeOfNextRtp(); RtpQueueItem *items[kRtpQueueSize]; int head; // Pointer to last inserted item int tail; // Pointer to the oldest item int nItems; - int sizeOfLastFrame; + int sizeOfLastFrame; // Size of last frame in bytes int bytesInQueue_; int sizeOfQueue_; diff --git a/code/bw-test-tool/code/ScreamRx.cpp b/code/bw-test-tool/code/ScreamRx.cpp index c219953..ec4a662 100644 --- a/code/bw-test-tool/code/ScreamRx.cpp +++ b/code/bw-test-tool/code/ScreamRx.cpp @@ -308,7 +308,7 @@ bool ScreamRx::createStandardizedFeedback(uint32_t time_ntp, bool isMark, unsign * stream in the first iteration, a bit unnecessary. */ Stream *stream = NULL; - uint32_t minT_ntp = ULONG_MAX; + uint32_t minT_ntp = UINT32_MAX; for (auto it = streams.begin(); it != streams.end(); ++it) { uint32_t diffT_ntp = time_ntp - (*it)->lastFeedbackT_ntp; if (((*it)->nRtpSinceLastRtcp >= std::min(8,ackDiff) || diffT_ntp > 655 || isMark) && // 10ms in Q16 diff --git a/code/bw-test-tool/code/ScreamTx.cpp b/code/bw-test-tool/code/ScreamTx.cpp index 6e3e981..ff0dfcf 100644 --- a/code/bw-test-tool/code/ScreamTx.cpp +++ b/code/bw-test-tool/code/ScreamTx.cpp @@ -1059,7 +1059,7 @@ void ScreamTx::initialize(uint32_t time_ntp) { float ScreamTx::getTotalTargetBitrate() { float totalTargetBitrate = 0.0f; for (int n = 0; n < nStreams; n++) { - totalTargetBitrate += streams[n]->targetBitrate; + totalTargetBitrate += streams[n]->targetBitrate; } return totalTargetBitrate; } diff --git a/code/bw-test-tool/code/ScreamTx.h b/code/bw-test-tool/code/ScreamTx.h index 6d7b7bb..88d99b1 100644 --- a/code/bw-test-tool/code/ScreamTx.h +++ b/code/bw-test-tool/code/ScreamTx.h @@ -50,7 +50,7 @@ static const float kGainDown = 2.0f; // Max video rampup speed in bps/s (bits per second increase per second) static const float kRampUpSpeed = 200000.0f; // bps/s // Max video rampup scale as fraction of the current target bitrate -static const float kRampUpScale = 0.2f; +static const float kRampUpScale = 0.2f; // Max RTP queue delay, RTP queue is cleared if this value is exceeded static const float kMaxRtpQueueDelay = 0.1; // 0.1s // Compensation factor for RTP queue size @@ -281,7 +281,7 @@ class ScreamTx { void setTimeString(char *s) { strcpy(timeString,s); - } + } /* * extra data to be appended to detailed log @@ -290,20 +290,20 @@ class ScreamTx { strcpy(detailedLogExtraData,s); } - /* - * Get the list of log items + /* + * Get the list of log items */ - char *getDetailedLogItemList() { + const char *getDetailedLogItemList() { return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; } /* - * Log each ACKed packet, + * Log each ACKed packet, */ void useExtraDetailedLog(bool isUseExtraDetailedLog_) { isUseExtraDetailedLog = isUseExtraDetailedLog_; } - + /* * Set lowest possible cwndMin */ diff --git a/code/bw-test-tool/code/scream_receiver.cpp b/code/bw-test-tool/code/scream_receiver.cpp index d9d12ac..e240907 100644 --- a/code/bw-test-tool/code/scream_receiver.cpp +++ b/code/bw-test-tool/code/scream_receiver.cpp @@ -29,12 +29,12 @@ ScreamRx *screamRx = 0; //int fd_local_rtp; -char* SENDER_IP = "192.168.0.20"; +const char *SENDER_IP = "192.168.0.20"; int INCOMING_RTP_PORT = 30122; struct sockaddr_in incoming_rtp_addr, outgoing_rtcp_addr, sender_rtcp_addr; struct sockaddr_in local_rtp_addr; -char* LOCAL_IP = "127.0.0.1"; +const char *LOCAL_IP = "127.0.0.1"; int LOCAL_PORT = 30124; int ackDiff = -1; diff --git a/code/bw-test-tool/code/scream_sender.cpp b/code/bw-test-tool/code/scream_sender.cpp index bdb78e7..502885f 100644 --- a/code/bw-test-tool/code/scream_sender.cpp +++ b/code/bw-test-tool/code/scream_sender.cpp @@ -87,9 +87,9 @@ RtpQueue *rtpQueue = 0; // We don't bother about SSRC in this implementation, it is only one stream -char *DECODER_IP = "192.168.0.21"; +const char *DECODER_IP = "192.168.0.21"; int DECODER_PORT = 30110; -char *DUMMY_IP = "217.10.68.152"; // Dest address just to punch hole in NAT +const char *DUMMY_IP = "217.10.68.152"; // Dest address just to punch hole in NAT int SIERRA_PYTHON_PORT = 35000; From 37ee23a665f427461c647ca79b4c212218074ba6 Mon Sep 17 00:00:00 2001 From: mirabilos Date: Mon, 22 Jun 2020 23:29:31 +0200 Subject: [PATCH 3/9] add build instructions for everything found in this repository (cherry picked from commit 34ecd11a6a43741ce14a1e016c106bc1fb23a240) --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a50d530..33c84b4 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,17 @@ This project includes an implementation of SCReAM, a mobile optimised congestion control algorithm for realtime interactive media. ## Algorithm -SCReAM (**S**elf-**C**locked **R**at**e** **A**daptation for **M**ultimedia) is a congestion control algorithm devised mainly for Video. -Congestion control for WebRTC media is currently being standardized in the IETF RMCAT WG, the scope of the working group is to define requirements for congestion control and also to standardize a few candidate solutions. -SCReAM is a congestion control candidate solution for WebRTC developed at Ericsson Research and optimized for good performance in wireless access. +SCReAM (**S**elf-**C**locked **R**at**e** **A**daptation for **M**ultimedia) is a congestion control algorithm devised mainly for Video. +Congestion control for WebRTC media is currently being standardized in the IETF RMCAT WG, the scope of the working group is to define requirements for congestion control and also to standardize a few candidate solutions. +SCReAM is a congestion control candidate solution for WebRTC developed at Ericsson Research and optimized for good performance in wireless access. The algorithm is an IETF experimental standard [1], a Sigcomm paper [2] and [3] explains the rationale behind the design of the algorithm in more detail. A comparison against GCC (Google Congestion Control) is shown in [4]. Final presentations are found in [5] and [6]. A short [video](https://www.youtube.com/watch?v=_jBFu-Y0wwo) exemplifies the use of SCReAM in a small vehicle, remote controlled over a public LTE network. [7] explains the rationale behind the use of SCReAM in remote controlled applications over LTE/5G. Unlike many other congestion control algorithms that are rate based i.e. they estimate the network throughput and adjust the media bitrate accordingly, SCReAM is self-clocked which essentially means that the algorithm does not send in more data into a network than what actually exits the network. -To achieve this, SCReAM implements a feedback protocol over RTCP that acknowledges received RTP packets. -A congestion window is determined from the feedback, this congestion window determines how many RTP packets that can be in flight i.e. transmitted by not yet acknowledged, an RTP queue is maintained at the sender side to temporarily store the RTP packets pending transmission, this RTP queue is mostly empty but can temporarily become larger when the link throughput decreases. -The congestion window is frequently adjusted for minimal e2e delay while still maintaining as high link utilization as possible. The use of self-clocking in SCReAM which is also the main principle in TCP has proven to work particularly well in wireless scenarios where the link throughput may change rapidly. This enables a congestion control which is robust to channel jitter, introduced by e.g. radio resource scheduling while still being able to respond promptly to reduced link throughput. +To achieve this, SCReAM implements a feedback protocol over RTCP that acknowledges received RTP packets. +A congestion window is determined from the feedback, this congestion window determines how many RTP packets that can be in flight i.e. transmitted by not yet acknowledged, an RTP queue is maintained at the sender side to temporarily store the RTP packets pending transmission, this RTP queue is mostly empty but can temporarily become larger when the link throughput decreases. +The congestion window is frequently adjusted for minimal e2e delay while still maintaining as high link utilization as possible. The use of self-clocking in SCReAM which is also the main principle in TCP has proven to work particularly well in wireless scenarios where the link throughput may change rapidly. This enables a congestion control which is robust to channel jitter, introduced by e.g. radio resource scheduling while still being able to respond promptly to reduced link throughput. SCReAM is optimized in house in a state of the art LTE system simulator for optimal performance in deployments where the LTE radio conditions are limiting. In addition, SCReAM is also optimized for good performance in simple bottleneck case such as those given in home gateway deployments. SCReAM is verified in simulator and in a testbed to operate in a rate range from ~20kbps up to 100Mbps. The fact that SCReAM maintains a RTP queue on the sender side opens up for further optimizations to congestion, for instance it is possible to discard the contents of the RTP queue and replace with an I frame in order to refresh the video quickly at congestion. @@ -22,14 +22,14 @@ Below is shown an example of SCReAM congestion control when subject to a bottlen Figure 1 : Simple bottleneck simulation SCReAM -## ECN (Explicit Congestion Notification) -SCReAM supports "classic" ECN, i.e. that the sending rate is reduced as a result of one or more ECN marked RTP packets in one RTT, similar to the guidelines in RFC3168. . +## ECN (Explicit Congestion Notification) +SCReAM supports "classic" ECN, i.e. that the sending rate is reduced as a result of one or more ECN marked RTP packets in one RTT, similar to the guidelines in RFC3168. . -In addition SCReAM also supports L4S, i.e that the sending rate is reduced proportional to the fraction of the RTP packets that are ECN marked. This enables lower network queue delay. +In addition SCReAM also supports L4S, i.e that the sending rate is reduced proportional to the fraction of the RTP packets that are ECN marked. This enables lower network queue delay. Below is shown three simulation examples with a simple 50Mbps bottleneck that changes to 25Mbps after 50s, the min RTT is 20ms. The video trace is from NVENC. -The graphs show that ECN improves on the e2e delay and that the use of L4S reduces the delay considerably more. +The graphs show that ECN improves on the e2e delay and that the use of L4S reduces the delay considerably more. L4S gives a somewhat lower media rate, the reason is that a larger headroom is added to ensure the low delay, considering the varying output rate of the video encoder. This is self-adjusting by inherent design, the average bitrate would increase if the frame size variations are smaller. @@ -38,10 +38,10 @@ Note carefully that the scales in the graphs differ, especially the delay graph ![Simple bottleneck simulation SCReAM no ECN support](https://github.com/EricssonResearch/scream/blob/master/images/scream_noecn_2.png) Figure 2 : SCReAM without ECN support - + ![Simple bottleneck simulation SCReAM with ECN support](https://github.com/EricssonResearch/scream/blob/master/images/scream_ecn_2.png) -Figure 3 : SCReAM with ECN support ECN beta = 0.8. CoDel with default settings (5ms, 100ms) +Figure 3 : SCReAM with ECN support ECN beta = 0.8. CoDel with default settings (5ms, 100ms) ![Simple bottleneck simulation SCReAM with L4S support](https://github.com/EricssonResearch/scream/blob/master/images/scream_l4s_2.png) @@ -51,9 +51,9 @@ Figure 4 : SCReAM with L4S support. L4S ramp-marker (Th_low=2ms, Th_high=10ms) The two videos below show a simple test with a simple 3Mbps bottleneck (CoDel AQM, ECN cabable). The first video is with ECN disabled in the sender, the other is with ECN enabled. SCReAM is here used with a Panasonic WV-SBV111M IP camera. One may argue that one can disable CoDel to avoid the packet losses, unfortunately one then lose the positive properties with CoDel, mentioned earlier. -[Without ECN](https://www.youtube.com/watch?v=J0po78q1QkU "Without ECN") +[Without ECN](https://www.youtube.com/watch?v=J0po78q1QkU "Without ECN") -[With ECN](https://www.youtube.com/watch?v=qIe0ubw9jPw "With ECN") +[With ECN](https://www.youtube.com/watch?v=qIe0ubw9jPw "With ECN") The green areas that uccur due to packet loss is an artifact in the conversion of the RTP dump. ## Real life test @@ -62,17 +62,17 @@ A real life test of SCReAM is performed with the following setup in a car: - Sony Camcorder placed on dashboard, HDMI output used - Antrica ANT-35000A video encoder with 1000-8000kbps encoding range and 1080p50 mode - Laptop with a SCReAM sender running -- Sony Xperia phone in WiFi tethering mode +- Sony Xperia phone in WiFi tethering mode A SCReAM receiver that logged the performance and stored the received RTP packets was running in an office. The video traffic was thus transmitted in LTE uplink.The video was played out to file with GStreamer, the jitter buffer was disabled to allow for the visibility of the delay jitter artifacts, -Below is a graph that shows the bitrate, the congestion window and the queue delay. +Below is a graph that shows the bitrate, the congestion window and the queue delay. ![Log from ](https://github.com/EricssonResearch/scream/blob/master/images/SCReAM_LTE_UL.png) Figure 5 : Trace from live drive test -The graph shows that SCReAM manages high bitrate video streaming with low e2e delay despite demanding conditions both in terms of variable throughput and in a changing output bitrate from the video encoder. Packet losses occur relatively frequently, the exact reason is unknown but seem to be related to handover events, normally packet loss should not occure in LTE-UL, however this seems to be the case with the used cellphone. +The graph shows that SCReAM manages high bitrate video streaming with low e2e delay despite demanding conditions both in terms of variable throughput and in a changing output bitrate from the video encoder. Packet losses occur relatively frequently, the exact reason is unknown but seem to be related to handover events, normally packet loss should not occure in LTE-UL, however this seems to be the case with the used cellphone. The delay increases between 1730 and 1800s, the reason here is that the available throughput was lower than the lowest possible coder bitrate. An encoder with a wider rate range would be able to make it possible to keep the delay low also in this case. A video from the experiment is found at the link below. The artifacts and overall video quality can be correlated aginst the graph above. @@ -99,17 +99,17 @@ A few support classes for experimental use are implemented in: - NetQueue : Simple delay and bandwidth limitation -For more information on how to use the code in multimedia clients or in experimental platforms, please see [https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx](https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx?raw=true) +For more information on how to use the code in multimedia clients or in experimental platforms, please see [https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx](https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx?raw=true) ## References [1] https://tools.ietf.org/html/rfc8298 -[2] Sigcomm paper http://dl.acm.org/citation.cfm?id=2631976 +[2] Sigcomm paper http://dl.acm.org/citation.cfm?id=2631976 [3] Sigcomm presentation http://conferences.sigcomm.org/sigcomm/2014/doc/slides/150.pdf -[4] IETF RMCAT presentation, comparison against Google Congestion Control (GCC) http://www.ietf.org/proceedings/90/slides/slides-90-rmcat-3.pdf +[4] IETF RMCAT presentation, comparison against Google Congestion Control (GCC) http://www.ietf.org/proceedings/90/slides/slides-90-rmcat-3.pdf [5] IETF RMCAT presentation (final for WGLC) : https://www.ietf.org/proceedings/96/slides/slides-96-rmcat-0.pdf @@ -133,3 +133,56 @@ The feedback overhead depends on the media bitrate. The table below shows the IP | 30000 | 500 | +-----------------------------------------------------+ + +# How to build this repository? + +We assume you have git already installed, since you checked this out. +Install some generic build dependencies first: + + sudo apt-get install autoconf autopoint bison build-essential cmake flex gettext libtool pkg-config yasm + +## main code + + cmake . && make + +creates `bin/scream` executable + +## `bw-test-tool` + + cd code/bw-test-tool + cmake . && make + cd ../.. + +creates `code/bw-test-tool/bin/scream_bw_test_rx` +and `code/bw-test-tool/bin/scream_bw_test_tx` + +## gscream + +### install dependencies + + sudo apt-get install gstreamer1.0-{doc,libav,tools,x,plugins-{base,good,bad,ugly}} + sudo apt-get install libglib2.0-dev liborc-0.4-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev + +### build plugins + + cd code/gscream/gst-gscreamrx/gst-plugin + ./autogen.sh && make + cd rx tx + ./autogen.sh && make + cd ../../../.. + +creates `code/gscream/gst-gscreamrx/gst-plugin/src/.libs/libgstgscreamrx.so` +and `code/gscream/gst-gscreamtx/gst-plugin/src/.libs/libgstgscreamtx.so` +which, with a `sudo make install` in the respective directories, will be +installed into `/usr/local/lib/gstreamer-1.0/`. + +### build test applications + + cd code/gscream + make + +creates `code/gscream/gscream_app_rpi_tx`, +`code/gscream/gscream_app_rx` and `code/gscream/gscream_app_tx` + +See [`code/gscream/readme.txt`](./code/gscream/readme.txt) for +what to do with these files. From 71a43ad40d922a86afb3d64e8dd458d6bf1ec534 Mon Sep 17 00:00:00 2001 From: mirabilos Date: Tue, 30 Jun 2020 13:57:20 +0200 Subject: [PATCH 4/9] apply another bugfix from the two other RtpQueue impl files still works: 58.1 Transmit rate = 202059kbps, PLR = 0.00%( 0.00%), RTT = 0.000s, Queue delay = 0.000s --- code/bw-test-tool/code/RtpQueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/bw-test-tool/code/RtpQueue.cpp b/code/bw-test-tool/code/RtpQueue.cpp index 870be39..5d42e82 100644 --- a/code/bw-test-tool/code/RtpQueue.cpp +++ b/code/bw-test-tool/code/RtpQueue.cpp @@ -48,8 +48,8 @@ void RtpQueue::push(void *rtpPacket, int size, unsigned short seqNr, float ts) { bool RtpQueue::pop(void *rtpPacket, int& size, unsigned short& seqNr) { if (items[tail]->used == false) { - return false; sizeOfNextRtp_ = -1; + return false; } else { size = items[tail]->size; memcpy(rtpPacket,items[tail]->packet,size); From c2c519e814fd19367f9d2ac718f4c4400fc881c9 Mon Sep 17 00:00:00 2001 From: mirabilos Date: Fri, 3 Jul 2020 12:22:01 +0200 Subject: [PATCH 5/9] use #include instead of symbolic links some contributors use git implementations that utterly hose symlinks --- code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.cpp | 2 +- code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.h | 2 +- code/gscream/gst-gscreamrx/gst-plugin/src/ScreamTx.h | 2 +- code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.cpp | 2 +- code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.h | 2 +- code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.cpp | 2 +- code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.h | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) mode change 120000 => 100644 code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.cpp mode change 120000 => 100644 code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.h mode change 120000 => 100644 code/gscream/gst-gscreamrx/gst-plugin/src/ScreamTx.h mode change 120000 => 100644 code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.cpp mode change 120000 => 100644 code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.h mode change 120000 => 100644 code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.cpp mode change 120000 => 100644 code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.h diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.cpp b/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.cpp deleted file mode 120000 index 9e0400c..0000000 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.cpp +++ /dev/null @@ -1 +0,0 @@ -../../../../ScreamRx.cpp \ No newline at end of file diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.cpp b/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.cpp new file mode 100644 index 0000000..e54f33d --- /dev/null +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.cpp @@ -0,0 +1 @@ +#include "../../../../ScreamRx.cpp" diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.h b/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.h deleted file mode 120000 index 85beb59..0000000 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.h +++ /dev/null @@ -1 +0,0 @@ -../../../../ScreamRx.h \ No newline at end of file diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.h b/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.h new file mode 100644 index 0000000..53510ee --- /dev/null +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamRx.h @@ -0,0 +1 @@ +#include "../../../../ScreamRx.h" diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamTx.h b/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamTx.h deleted file mode 120000 index 48065bc..0000000 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamTx.h +++ /dev/null @@ -1 +0,0 @@ -../../../../ScreamTx.h \ No newline at end of file diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamTx.h b/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamTx.h new file mode 100644 index 0000000..ab083d9 --- /dev/null +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/ScreamTx.h @@ -0,0 +1 @@ +#include "../../../../ScreamTx.h" diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.cpp b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.cpp deleted file mode 120000 index 9e0400c..0000000 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.cpp +++ /dev/null @@ -1 +0,0 @@ -../../../../ScreamRx.cpp \ No newline at end of file diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.cpp b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.cpp new file mode 100644 index 0000000..e54f33d --- /dev/null +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.cpp @@ -0,0 +1 @@ +#include "../../../../ScreamRx.cpp" diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.h b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.h deleted file mode 120000 index 85beb59..0000000 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.h +++ /dev/null @@ -1 +0,0 @@ -../../../../ScreamRx.h \ No newline at end of file diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.h b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.h new file mode 100644 index 0000000..53510ee --- /dev/null +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamRx.h @@ -0,0 +1 @@ +#include "../../../../ScreamRx.h" diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.cpp b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.cpp deleted file mode 120000 index ac02df8..0000000 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.cpp +++ /dev/null @@ -1 +0,0 @@ -../../../../ScreamTx.cpp \ No newline at end of file diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.cpp b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.cpp new file mode 100644 index 0000000..f2119f1 --- /dev/null +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.cpp @@ -0,0 +1 @@ +#include "../../../../ScreamTx.cpp" diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.h b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.h deleted file mode 120000 index 48065bc..0000000 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.h +++ /dev/null @@ -1 +0,0 @@ -../../../../ScreamTx.h \ No newline at end of file diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.h b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.h new file mode 100644 index 0000000..ab083d9 --- /dev/null +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/ScreamTx.h @@ -0,0 +1 @@ +#include "../../../../ScreamTx.h" From 38c6ed4af668863808e5e60890b3a07385abf0e3 Mon Sep 17 00:00:00 2001 From: mirabilos Date: Fri, 3 Jul 2020 12:25:25 +0200 Subject: [PATCH 6/9] normalise whitespace before merging to facilitate it --- CMakeLists.txt | 2 +- code/NetQueue.cpp | 15 ++--- code/NetQueue.h | 9 ++- code/ScreamTx.h | 59 +++++++++---------- code/VideoEnc.cpp | 1 - code/VideoEnc.h | 3 +- code/bw-test-tool/CMakeLists.txt | 2 +- code/bw-test-tool/Sierra-RV50X-logger.py | 5 +- code/bw-test-tool/code/ScreamTx.h | 59 +++++++++---------- code/bw-test-tool/plot_cdf.m | 9 ++- code/bw-test-tool/plot_thp_delay.m | 7 +-- code/gscream/gscream_app_rpi_tx.cpp | 4 +- code/gscream/gst-gscreamrx/gst-plugin/COPYING | 1 - .../gst-gscreamrx/gst-plugin/ChangeLog | 12 ++-- code/gscream/gst-gscreamrx/gst-plugin/README | 3 +- .../gst-plugin/src/gstplugin.cpp | 2 +- .../gst-gscreamrx/gst-plugin/src/gstplugin.h | 4 +- .../gst-plugin/src/gsttransform.c | 2 +- .../gst-plugin/src/gsttransform.h | 4 +- code/gscream/gst-gscreamtx/gst-plugin/COPYING | 1 - .../gst-gscreamtx/gst-plugin/ChangeLog | 12 ++-- code/gscream/gst-gscreamtx/gst-plugin/README | 3 +- .../gst-plugin/src/gstgscreamtx.cpp | 4 +- .../gst-plugin/src/gstplugin.cpp | 2 +- .../gst-plugin/src/gsttransform.c | 2 +- .../gst-plugin/src/gsttransform.h | 4 +- code/scream_v_a.cpp | 7 +-- code/stdafx.h | 1 - test_01.m | 16 +++-- test_v_a.m | 31 +++++----- 30 files changed, 135 insertions(+), 151 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23a3a2f..5efbcef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,4 +74,4 @@ INCLUDE_DIRECTORIES( ${scream_SOURCE_DIR}/../include ) -ADD_SUBDIRECTORY( code) +ADD_SUBDIRECTORY( code) diff --git a/code/NetQueue.cpp b/code/NetQueue.cpp index 4c9b298..fa00d27 100644 --- a/code/NetQueue.cpp +++ b/code/NetQueue.cpp @@ -6,6 +6,7 @@ #include using namespace std; + /* * Implements a simple RTP packet queue */ @@ -40,10 +41,10 @@ NetQueue::NetQueue(float delay_, float rate_, float jitter_, bool isL4s_) { tQueueAvg = 0.0; } -void NetQueue::insert(float time, - void *rtpPacket, +void NetQueue::insert(float time, + void *rtpPacket, unsigned int ssrc, - int size, + int size, unsigned short seqNr, bool isCe) { head++; if (head == NetQueueSize) head = 0; @@ -63,10 +64,10 @@ void NetQueue::insert(float time, nextTx = items[head]->tRelease; } -bool NetQueue::extract(float time, - void *rtpPacket, +bool NetQueue::extract(float time, + void *rtpPacket, unsigned int &ssrc, - int& size, + int& size, unsigned short& seqNr, bool& isCe) { if (items[tail]->used == false) { @@ -116,7 +117,7 @@ bool NetQueue::extract(float time, int NetQueue::sizeOfQueue() { int size = 0; for (int n=0; n < NetQueueSize; n++) { - if (items[n]->used) + if (items[n]->used) size += items[n]->size; } return size; diff --git a/code/NetQueue.h b/code/NetQueue.h index 2247a06..a435ad2 100644 --- a/code/NetQueue.h +++ b/code/NetQueue.h @@ -1,7 +1,6 @@ #ifndef NET_QUEUE #define NET_QUEUE - class NetQueueItem { public: NetQueueItem(); @@ -26,10 +25,10 @@ class NetQueue { int size, unsigned short seqNr, bool isCe = false); - bool extract(float time, - void *rtpPacket, + bool extract(float time, + void *rtpPacket, unsigned int &ssrc, - int& size, + int& size, unsigned short &seqNr, bool &isCe); int sizeOfQueue(); @@ -54,4 +53,4 @@ class NetQueue { float tQueueAvg; }; -#endif \ No newline at end of file +#endif diff --git a/code/ScreamTx.h b/code/ScreamTx.h index bc46feb..fbede5c 100644 --- a/code/ScreamTx.h +++ b/code/ScreamTx.h @@ -4,7 +4,7 @@ #include #include #include -extern "C" { + using namespace std; /* @@ -191,7 +191,7 @@ class ScreamTx { uint32_t ssrc, int size, uint16_t seqNr, - bool isMark); + bool isMark); /* New incoming feedback, this function * triggers a CWND update @@ -277,9 +277,9 @@ class ScreamTx { fp_log = fp; } - void setTimeString(char *s) { - strcpy(timeString,s); - } + void setTimeString(char *s) { + strcpy(timeString,s); + } /* * extra data to be appended to detailed log @@ -288,21 +288,21 @@ class ScreamTx { strcpy(detailedLogExtraData,s); } - /* - * Get the list of log items - */ - const char *getDetailedLogItemList() { - return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; - } - - /* - * Log each ACKed packet, - */ - void useExtraDetailedLog(bool isUseExtraDetailedLog_) { - isUseExtraDetailedLog = isUseExtraDetailedLog_; - } - - /* + /* + * Get the list of log items + */ + const char *getDetailedLogItemList() { + return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; + } + + /* + * Log each ACKed packet, + */ + void useExtraDetailedLog(bool isUseExtraDetailedLog_) { + isUseExtraDetailedLog = isUseExtraDetailedLog_; + } + + /* * Set lowest possible cwndMin */ void setCwndMinLow(int aValue) { @@ -317,7 +317,7 @@ class ScreamTx { uint32_t timeTx_ntp; int size; uint16_t seqNr; - bool isMark; + bool isMark; bool isUsed; bool isAcked; bool isAfterReceivedEdge; @@ -462,12 +462,12 @@ class ScreamTx { uint8_t ceBits, int &encCeMarkedBytes, bool isLast, - bool &isMark); + bool &isMark); - /* - * Get total target bitrate for all streams - */ - float getTotalTargetBitrate(); + /* + * Get total target bitrate for all streams + */ + float getTotalTargetBitrate(); /* * Update CWND @@ -696,12 +696,11 @@ class ScreamTx { */ FILE *fp_log; bool completeLogItem; - char timeString[100]; - bool isUseExtraDetailedLog; + char timeString[100]; + bool isUseExtraDetailedLog; int bytesNewlyAckedLog; - int ecnCeMarkedBytesLog; + int ecnCeMarkedBytesLog; }; -} #endif diff --git a/code/VideoEnc.cpp b/code/VideoEnc.cpp index 43a14dd..53c8b06 100644 --- a/code/VideoEnc.cpp +++ b/code/VideoEnc.cpp @@ -54,4 +54,3 @@ int VideoEnc::encode(float time) { rtpQueue->setSizeOfLastFrame(rtpBytes); return rtpBytes; } - diff --git a/code/VideoEnc.h b/code/VideoEnc.h index 45d29b5..2e80e5e 100644 --- a/code/VideoEnc.h +++ b/code/VideoEnc.h @@ -21,5 +21,4 @@ class VideoEnc { int ix; }; - -#endif \ No newline at end of file +#endif diff --git a/code/bw-test-tool/CMakeLists.txt b/code/bw-test-tool/CMakeLists.txt index b60cc6e..d0f0e42 100644 --- a/code/bw-test-tool/CMakeLists.txt +++ b/code/bw-test-tool/CMakeLists.txt @@ -75,4 +75,4 @@ INCLUDE_DIRECTORIES( ${scream_SOURCE_DIR}/../include ) -ADD_SUBDIRECTORY( code) +ADD_SUBDIRECTORY( code) diff --git a/code/bw-test-tool/Sierra-RV50X-logger.py b/code/bw-test-tool/Sierra-RV50X-logger.py index 4dce2bc..ce21623 100644 --- a/code/bw-test-tool/Sierra-RV50X-logger.py +++ b/code/bw-test-tool/Sierra-RV50X-logger.py @@ -1,4 +1,3 @@ - import requests import json import datetime @@ -81,12 +80,12 @@ def send_to_udp_socket(result): UDP_IP = "127.0.0.1" UDP_PORT = 35000 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.sendto(bytes(result, 'utf-8'), (UDP_IP, UDP_PORT)) + sock.sendto(bytes(result, 'utf-8'), (UDP_IP, UDP_PORT)) # params = ['Cellular IP Address', 'ESN/EID/IMEI'] params = param_ids.keys() try: - while True: + while True: result = make_request(params) #result = make_request(payload) #print('result:', json.dumps(result, indent=4)) diff --git a/code/bw-test-tool/code/ScreamTx.h b/code/bw-test-tool/code/ScreamTx.h index 88d99b1..360a02a 100644 --- a/code/bw-test-tool/code/ScreamTx.h +++ b/code/bw-test-tool/code/ScreamTx.h @@ -4,7 +4,7 @@ #include #include #include -extern "C" { + using namespace std; /* @@ -193,7 +193,7 @@ class ScreamTx { uint32_t ssrc, int size, uint16_t seqNr, - bool isMark); + bool isMark); /* New incoming feedback, this function * triggers a CWND update @@ -279,9 +279,9 @@ class ScreamTx { fp_log = fp; } - void setTimeString(char *s) { - strcpy(timeString,s); - } + void setTimeString(char *s) { + strcpy(timeString,s); + } /* * extra data to be appended to detailed log @@ -290,21 +290,21 @@ class ScreamTx { strcpy(detailedLogExtraData,s); } - /* - * Get the list of log items - */ - const char *getDetailedLogItemList() { - return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; - } - - /* - * Log each ACKed packet, - */ - void useExtraDetailedLog(bool isUseExtraDetailedLog_) { - isUseExtraDetailedLog = isUseExtraDetailedLog_; - } - - /* + /* + * Get the list of log items + */ + const char *getDetailedLogItemList() { + return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; + } + + /* + * Log each ACKed packet, + */ + void useExtraDetailedLog(bool isUseExtraDetailedLog_) { + isUseExtraDetailedLog = isUseExtraDetailedLog_; + } + + /* * Set lowest possible cwndMin */ void setCwndMinLow(int aValue) { @@ -319,7 +319,7 @@ class ScreamTx { uint32_t timeTx_ntp; int size; uint16_t seqNr; - bool isMark; + bool isMark; bool isUsed; bool isAcked; bool isAfterReceivedEdge; @@ -464,12 +464,12 @@ class ScreamTx { uint8_t ceBits, int &encCeMarkedBytes, bool isLast, - bool &isMark); + bool &isMark); - /* - * Get total target bitrate for all streams - */ - float getTotalTargetBitrate(); + /* + * Get total target bitrate for all streams + */ + float getTotalTargetBitrate(); /* * Update CWND @@ -698,12 +698,11 @@ class ScreamTx { */ FILE *fp_log; bool completeLogItem; - char timeString[100]; - bool isUseExtraDetailedLog; + char timeString[100]; + bool isUseExtraDetailedLog; int bytesNewlyAckedLog; - int ecnCeMarkedBytesLog; + int ecnCeMarkedBytesLog; }; -} #endif diff --git a/code/bw-test-tool/plot_cdf.m b/code/bw-test-tool/plot_cdf.m index a2d249f..3c5ba8b 100644 --- a/code/bw-test-tool/plot_cdf.m +++ b/code/bw-test-tool/plot_cdf.m @@ -1,12 +1,12 @@ function plot_cdf(a,Tlim,Tmax) -% This function plots the CDF of the RTT and -% queue delay from the logs given by the +% This function plots the CDF of the RTT and +% queue delay from the logs given by the % SCReAM BW test tool. % Parameters : % a : log file from SCReAM BW test tool % imported with the command % a = load(); -% where is the name of the log file +% where is the name of the log file % Tlim : xmin and xmax limits [s], e.g. [0 100] % Tmax : Max displayed value for RTT/delay % @@ -17,7 +17,7 @@ function plot_cdf(a,Tlim,Tmax) % >a = a(1:50:end,:); % subsample the log file % >figure(1); % >plot_cdf(.... -% +% T = a(:,1); ix = intersect(find(T > Tlim(1)),find(T <= Tlim(2))); @@ -35,4 +35,3 @@ function plot_cdf(a,Tlim,Tmax) xlabel('[s]'); end - diff --git a/code/bw-test-tool/plot_thp_delay.m b/code/bw-test-tool/plot_thp_delay.m index 4d613a5..2a46390 100644 --- a/code/bw-test-tool/plot_thp_delay.m +++ b/code/bw-test-tool/plot_thp_delay.m @@ -1,11 +1,11 @@ function plot_thp_delay(a,Tlim,maxThp,maxDelay) -% This function plots the thorughput +% This function plots the thorughput % RTT and estimated queue delay % Parameters: % a : log file from SCReAM BW test tool % imported with the command % a = load(); -% where is the name of the log file +% where is the name of the log file % Tlim : xmin and xmax limits [s], e.g. [0 100] % maxThp : Max thorughput [Mbps] % maxDelay : Max delay [s] @@ -17,7 +17,7 @@ function plot_thp_delay(a,Tlim,maxThp,maxDelay) % >a = a(1:50:end,:); % subsample the log file % >figure(1); % >plot_cdf(.... -% +% T = a(:,1); subplot(211);%subplot(9,1,1:5); K = 5; @@ -43,4 +43,3 @@ function plot_thp_delay(a,Tlim,maxThp,maxDelay) xlabel('T [s]'); xlim(Tlim); end - diff --git a/code/gscream/gscream_app_rpi_tx.cpp b/code/gscream/gscream_app_rpi_tx.cpp index cf07e7b..0b8fab4 100644 --- a/code/gscream/gscream_app_rpi_tx.cpp +++ b/code/gscream/gscream_app_rpi_tx.cpp @@ -39,7 +39,7 @@ int main (int argc, char *argv[]) #ifdef SCREAM gscreamtx = gst_element_factory_make ("gscreamtx", "scream-tx"); g_assert (gscreamtx); -#endif +#endif g_assert (videoencode); g_assert (capsfilter); @@ -105,7 +105,7 @@ int main (int argc, char *argv[]) srcpad = gst_element_get_static_pad (gscreamtx, "src"); #else srcpad = gst_element_get_static_pad (videopayload, "src"); -#endif +#endif if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) g_error ("Failed to link video payloader to rtpbin"); gst_object_unref (srcpad); diff --git a/code/gscream/gst-gscreamrx/gst-plugin/COPYING b/code/gscream/gst-gscreamrx/gst-plugin/COPYING index 09ec995..49b9c3c 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/COPYING +++ b/code/gscream/gst-gscreamrx/gst-plugin/COPYING @@ -1,2 +1 @@ Put your license in here! - diff --git a/code/gscream/gst-gscreamrx/gst-plugin/ChangeLog b/code/gscream/gst-gscreamrx/gst-plugin/ChangeLog index 5c25746..7ead349 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/ChangeLog +++ b/code/gscream/gst-gscreamrx/gst-plugin/ChangeLog @@ -104,7 +104,7 @@ 2006-04-20 Stefan Kost - Patch by: Johan Rydberg + Patch by: Johan Rydberg * src/gstplugin.c: (gst_plugin_template_get_type), (gst_plugin_template_base_init), (gst_plugin_template_class_init), @@ -116,7 +116,7 @@ (gst_plugin_template_get_property): * tools/make_element: remove double gst_get_, fix '_' in names - + 2006-02-26 Tim-Philipp Müller @@ -190,8 +190,8 @@ * autogen.sh: * configure.ac: * src/Makefile.am: - use proper LDFLAGS for plugins - run in maintainer mode by default + use proper LDFLAGS for plugins + run in maintainer mode by default 2004-04-22 Thomas Vander Stichele @@ -210,8 +210,8 @@ 2003-02-06 Thomas Vander Stichele - * updated for GStreamer 0.6.0 + * updated for GStreamer 0.6.0 2002-07-17 Thomas Vander Stichele - * initial creation on a flight to New York + * initial creation on a flight to New York diff --git a/code/gscream/gst-gscreamrx/gst-plugin/README b/code/gscream/gst-gscreamrx/gst-plugin/README index 1905684..1236ac2 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/README +++ b/code/gscream/gst-gscreamrx/gst-plugin/README @@ -9,7 +9,7 @@ of how to set up autotools and your source tree. This template demonstrates : - what to do in autogen.sh - how to setup configure.ac (your package name and version, GStreamer flags) -- how to setup your source dir +- how to setup your source dir - what to put in Makefile.am More features and templates might get added later on. @@ -31,4 +31,3 @@ one element called 'myfilter' too. Also look for "FIXME:" markers that point you to places where you need to edit the code. You still need to adjust the Makefile.am. - diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.cpp b/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.cpp index 44017ea..2b5cee0 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.cpp +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2005 Thomas Vander Stichele * Copyright (C) 2005 Ronald S. Bultje * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL - * + * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.h b/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.h index f1fe4ed..27c1bef 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.h +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.h @@ -3,7 +3,7 @@ * Copyright (C) 2005 Thomas Vander Stichele * Copyright (C) 2005 Ronald S. Bultje * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL - * + * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation @@ -74,7 +74,7 @@ struct _GstPluginTemplate gboolean silent; }; -struct _GstPluginTemplateClass +struct _GstPluginTemplateClass { GstElementClass parent_class; }; diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.c b/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.c index af6c75b..9352079 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.c +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.c @@ -184,7 +184,7 @@ gst_plugin_template_transform_ip (GstBaseTransform * base, GstBuffer * outbuf) if (filter->silent == FALSE) g_print ("I'm plugged, therefore I'm in.\n"); - + /* FIXME: do something interesting here. This simply copies the source * to the destination. */ diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.h b/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.h index be9ce86..ee9c5dd 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.h +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.h @@ -1,4 +1,4 @@ -/* +/* * GStreamer * Copyright (C) 2006 Stefan Kost * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL @@ -18,7 +18,7 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ - + #ifndef __GST_PLUGIN_TEMPLATE_H__ #define __GST_PLUGIN_TEMPLATE_H__ diff --git a/code/gscream/gst-gscreamtx/gst-plugin/COPYING b/code/gscream/gst-gscreamtx/gst-plugin/COPYING index 09ec995..49b9c3c 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/COPYING +++ b/code/gscream/gst-gscreamtx/gst-plugin/COPYING @@ -1,2 +1 @@ Put your license in here! - diff --git a/code/gscream/gst-gscreamtx/gst-plugin/ChangeLog b/code/gscream/gst-gscreamtx/gst-plugin/ChangeLog index 5c25746..7ead349 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/ChangeLog +++ b/code/gscream/gst-gscreamtx/gst-plugin/ChangeLog @@ -104,7 +104,7 @@ 2006-04-20 Stefan Kost - Patch by: Johan Rydberg + Patch by: Johan Rydberg * src/gstplugin.c: (gst_plugin_template_get_type), (gst_plugin_template_base_init), (gst_plugin_template_class_init), @@ -116,7 +116,7 @@ (gst_plugin_template_get_property): * tools/make_element: remove double gst_get_, fix '_' in names - + 2006-02-26 Tim-Philipp Müller @@ -190,8 +190,8 @@ * autogen.sh: * configure.ac: * src/Makefile.am: - use proper LDFLAGS for plugins - run in maintainer mode by default + use proper LDFLAGS for plugins + run in maintainer mode by default 2004-04-22 Thomas Vander Stichele @@ -210,8 +210,8 @@ 2003-02-06 Thomas Vander Stichele - * updated for GStreamer 0.6.0 + * updated for GStreamer 0.6.0 2002-07-17 Thomas Vander Stichele - * initial creation on a flight to New York + * initial creation on a flight to New York diff --git a/code/gscream/gst-gscreamtx/gst-plugin/README b/code/gscream/gst-gscreamtx/gst-plugin/README index 1905684..1236ac2 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/README +++ b/code/gscream/gst-gscreamtx/gst-plugin/README @@ -9,7 +9,7 @@ of how to set up autotools and your source tree. This template demonstrates : - what to do in autogen.sh - how to setup configure.ac (your package name and version, GStreamer flags) -- how to setup your source dir +- how to setup your source dir - what to put in Makefile.am More features and templates might get added later on. @@ -31,4 +31,3 @@ one element called 'myfilter' too. Also look for "FIXME:" markers that point you to places where you need to edit the code. You still need to adjust the Makefile.am. - diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/gstgscreamtx.cpp b/code/gscream/gst-gscreamtx/gst-plugin/src/gstgscreamtx.cpp index b160693..ff99b0c 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/gstgscreamtx.cpp +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/gstgscreamtx.cpp @@ -528,8 +528,8 @@ gst_g_scream_tx_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) //g_print("CALLBACK\n"); filter->encoder = gst_bin_get_by_name_recurse_up(GST_BIN(pipe), "video"); g_assert(filter->encoder); - - + + //g_object_set(G_OBJECT(filter->encoder), "bitrate", 200, NULL); //filter->encoder = gst_bin_get_by_name_recurse_up(GST_BIN(pipe), "encoder"); diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/gstplugin.cpp b/code/gscream/gst-gscreamtx/gst-plugin/src/gstplugin.cpp index 44017ea..2b5cee0 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/gstplugin.cpp +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/gstplugin.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2005 Thomas Vander Stichele * Copyright (C) 2005 Ronald S. Bultje * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL - * + * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.c b/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.c index af6c75b..9352079 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.c +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.c @@ -184,7 +184,7 @@ gst_plugin_template_transform_ip (GstBaseTransform * base, GstBuffer * outbuf) if (filter->silent == FALSE) g_print ("I'm plugged, therefore I'm in.\n"); - + /* FIXME: do something interesting here. This simply copies the source * to the destination. */ diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.h b/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.h index be9ce86..ee9c5dd 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.h +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.h @@ -1,4 +1,4 @@ -/* +/* * GStreamer * Copyright (C) 2006 Stefan Kost * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL @@ -18,7 +18,7 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ - + #ifndef __GST_PLUGIN_TEMPLATE_H__ #define __GST_PLUGIN_TEMPLATE_H__ diff --git a/code/scream_v_a.cpp b/code/scream_v_a.cpp index 10fe29b..7172300 100644 --- a/code/scream_v_a.cpp +++ b/code/scream_v_a.cpp @@ -23,7 +23,7 @@ int swprio = -1; //#define TRACEFILE "../traces/trace_flat.txt" /* * Mode determines how many streams should be run -* 1 = audio, 2 = video, 3 = 1+2, 4 = +* 1 = audio, 2 = video, 3 = 1+2, 4 = */ const int mode = 0x01; const double timeBase = 10000.0; @@ -192,7 +192,7 @@ int main(int argc, char* argv[]) netQueueRate->rate = 50000e3; } } - + if (time > 30 && swprio == 0) { swprio = 1; screamTx->setTargetPriority(10, 0.2); @@ -206,7 +206,7 @@ int main(int argc, char* argv[]) if (false && time > 50) netQueueRate->rate = 8e6; - + /* if ((time >= 60) && (time < 80) && isChRate) @@ -228,4 +228,3 @@ int main(int argc, char* argv[]) return 0; } - diff --git a/code/stdafx.h b/code/stdafx.h index cf009df..1847421 100644 --- a/code/stdafx.h +++ b/code/stdafx.h @@ -18,4 +18,3 @@ #include #include #include - diff --git a/test_01.m b/test_01.m index f94e4a0..317fa0b 100644 --- a/test_01.m +++ b/test_01.m @@ -5,7 +5,7 @@ function test_01(a,Tmax,Bmax,Cmax) if 1 figure(1); subplot(2,1,1); - plot(T,a(:,2),T,a(:,3)); + plot(T,a(:,2),T,a(:,3)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.5]);grid on; title('OWD [s] and OWD trend'); @@ -13,7 +13,7 @@ function test_01(a,Tmax,Bmax,Cmax) subplot(2,1,2); plot(T,a(:,14)); set(gca,'FontSize',12);grid on; - title('RTP queue delay [s]'); + title('RTP queue delay [s]'); xlim([0 Tmax]);grid on; %axis([0 Tmax 0 1.0]);grid on; xlabel('T [s]'); @@ -21,19 +21,19 @@ function test_01(a,Tmax,Bmax,Cmax) subplot(2,1,1); plot(T,a(:,8),T,a(:,10)); set(gca,'FontSize',12);grid on; - axis([0 Tmax 0 Cmax]);grid on; - title('CWND & in flight [byte]'); + axis([0 Tmax 0 Cmax]);grid on; + title('CWND & in flight [byte]'); subplot(2,1,2); plot(T,a(:,15),T,a(:,16)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax]);grid on; - title('Bitrate [bps]'); + title('Bitrate [bps]'); legend('Target bitrate','Target bitrate infl. point'); xlabel('T [s]'); end figure(3); subplot(2,1,1); - plot(T,a(:,2),T,a(:,14)); + plot(T,a(:,2),T,a(:,14)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.5]);grid on; title('OWD [s] RTP queue delay'); @@ -42,8 +42,6 @@ function test_01(a,Tmax,Bmax,Cmax) plot(T,a(:,15),T,a(:,16),T,a(:,17),T,a(:,18)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax]);grid on; - title('Bitrate [bps]'); + title('Bitrate [bps]'); legend('Target','TargetI','Transmitted', 'Acked'); xlabel('T [s]'); - - \ No newline at end of file diff --git a/test_v_a.m b/test_v_a.m index 16969f1..588a26b 100644 --- a/test_v_a.m +++ b/test_v_a.m @@ -3,26 +3,26 @@ function test_v_a(a,Tmax,I,Bmax,Cmax) K = 12; %Tmax = 100.0; - + figure(1); subplot(2,1,1); - plot(T,a(:,2),T,a(:,3));%,T,a(:,4)); + plot(T,a(:,2),T,a(:,3));%,T,a(:,4)); set(gca,'FontSize',12);grid on; - set(gca,'XTickLabel',[]); + set(gca,'XTickLabel',[]); axis([0 Tmax 0 0.2]);grid on; title('qdel[s]'); - + subplot(2,1,2); plot(T,a(:,4),T,a(:,5),T,a(:,7)*20000,'k'); set(gca,'FontSize',12);grid on; - axis([0 Tmax 0 Cmax]);grid on; - title('CWND & in flight [byte]'); + axis([0 Tmax 0 Cmax]);grid on; + title('CWND & in flight [byte]'); xlabel('T [s]'); if I>0 figure(2); subplot(2,1,1); - plot(T,a(:,8)); + plot(T,a(:,8)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.2]);grid on; set(gca,'XTickLabel',[]); @@ -31,15 +31,15 @@ function test_v_a(a,Tmax,I,Bmax,Cmax) plot(T,a(:,9)/1000,T,a(:,11)/1000,T,a(:,10)/1000); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax(1)]);grid on; - title('Bitrate [Mbps]'); + title('Bitrate [Mbps]'); legend('Target','Transmitted', 'RTP'); xlabel('T [s]'); end if I>1 - K = 6; + K = 6; figure(3); subplot(2,1,1); - plot(T,a(:,8+K)); + plot(T,a(:,8+K)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.2]);grid on; set(gca,'XTickLabel',[]); @@ -48,15 +48,15 @@ function test_v_a(a,Tmax,I,Bmax,Cmax) plot(T,a(:,9+K)/1000,T,a(:,11+K)/1000,T,a(:,10+K)/1000); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax(2)]);grid on; - title('Bitrate [kbps]'); + title('Bitrate [kbps]'); legend('Target','Transmitted', 'RTP'); xlabel('T [s]'); end - if I>2 - K = 12; + if I>2 + K = 12; figure(4); subplot(2,1,1); - plot(T,a(:,8+K)); + plot(T,a(:,8+K)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.1]);grid on; set(gca,'XTickLabel',[]); @@ -65,8 +65,7 @@ function test_v_a(a,Tmax,I,Bmax,Cmax) plot(T,a(:,9+K),T,a(:,11+K),T,a(:,10+K)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax(3)]);grid on; - title('Bitrate [kbps]'); + title('Bitrate [kbps]'); legend('Target','Transmitted', 'RTP'); xlabel('T [s]'); end - \ No newline at end of file From 781b8ef53509663fe113483825a3055de37a5a02 Mon Sep 17 00:00:00 2001 From: mirabilos Date: Fri, 3 Jul 2020 12:27:36 +0200 Subject: [PATCH 7/9] normalise whitespace before merging to facilitate it --- CMakeLists.txt | 2 +- README.md | 40 ++++++------ Sierra-RV50X-logger.py | 5 +- code/NetQueue.cpp | 15 ++--- code/NetQueue.h | 9 ++- code/ScreamTx.cpp | 2 +- code/ScreamTx.h | 61 +++++++++---------- code/VideoEnc.cpp | 1 - code/VideoEnc.h | 3 +- code/gscream/gscream_app_rpi_tx.cpp | 4 +- code/gscream/gst-gscreamrx/gst-plugin/COPYING | 1 - .../gst-gscreamrx/gst-plugin/ChangeLog | 12 ++-- code/gscream/gst-gscreamrx/gst-plugin/README | 3 +- .../gst-plugin/src/gstplugin.cpp | 2 +- .../gst-gscreamrx/gst-plugin/src/gstplugin.h | 4 +- .../gst-plugin/src/gsttransform.c | 2 +- .../gst-plugin/src/gsttransform.h | 4 +- code/gscream/gst-gscreamtx/gst-plugin/COPYING | 1 - .../gst-gscreamtx/gst-plugin/ChangeLog | 12 ++-- code/gscream/gst-gscreamtx/gst-plugin/README | 3 +- .../gst-plugin/src/gstgscreamtx.cpp | 4 +- .../gst-plugin/src/gstplugin.cpp | 2 +- .../gst-plugin/src/gsttransform.c | 2 +- .../gst-plugin/src/gsttransform.h | 4 +- code/scream_v_a.cpp | 7 +-- code/stdafx.h | 1 - plot_cdf.m | 9 ++- plot_thp_delay.m | 7 +-- test_01.m | 16 +++-- test_v_a.m | 31 +++++----- 30 files changed, 127 insertions(+), 142 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b60cc6e..d0f0e42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,4 +75,4 @@ INCLUDE_DIRECTORIES( ${scream_SOURCE_DIR}/../include ) -ADD_SUBDIRECTORY( code) +ADD_SUBDIRECTORY( code) diff --git a/README.md b/README.md index cd21c6e..db98031 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,17 @@ This project includes an implementation of SCReAM, a mobile optimised congestion control algorithm for realtime interactive media. ## Algorithm -SCReAM (**S**elf-**C**locked **R**at**e** **A**daptation for **M**ultimedia) is a congestion control algorithm devised mainly for Video. -Congestion control for WebRTC media is currently being standardized in the IETF RMCAT WG, the scope of the working group is to define requirements for congestion control and also to standardize a few candidate solutions. -SCReAM is a congestion control candidate solution for WebRTC developed at Ericsson Research and optimized for good performance in wireless access. +SCReAM (**S**elf-**C**locked **R**at**e** **A**daptation for **M**ultimedia) is a congestion control algorithm devised mainly for Video. +Congestion control for WebRTC media is currently being standardized in the IETF RMCAT WG, the scope of the working group is to define requirements for congestion control and also to standardize a few candidate solutions. +SCReAM is a congestion control candidate solution for WebRTC developed at Ericsson Research and optimized for good performance in wireless access. The algorithm is an IETF experimental standard [1], a Sigcomm paper [2] and [3] explains the rationale behind the design of the algorithm in more detail. A comparison against GCC (Google Congestion Control) is shown in [4]. Final presentations are found in [5] and [6]. A short [video](https://www.youtube.com/watch?v=_jBFu-Y0wwo) exemplifies the use of SCReAM in a small vehicle, remote controlled over a public LTE network. [7] explains the rationale behind the use of SCReAM in remote controlled applications over LTE/5G. Unlike many other congestion control algorithms that are rate based i.e. they estimate the network throughput and adjust the media bitrate accordingly, SCReAM is self-clocked which essentially means that the algorithm does not send in more data into a network than what actually exits the network. -To achieve this, SCReAM implements a feedback protocol over RTCP that acknowledges received RTP packets. -A congestion window is determined from the feedback, this congestion window determines how many RTP packets that can be in flight i.e. transmitted by not yet acknowledged, an RTP queue is maintained at the sender side to temporarily store the RTP packets pending transmission, this RTP queue is mostly empty but can temporarily become larger when the link throughput decreases. -The congestion window is frequently adjusted for minimal e2e delay while still maintaining as high link utilization as possible. The use of self-clocking in SCReAM which is also the main principle in TCP has proven to work particularly well in wireless scenarios where the link throughput may change rapidly. This enables a congestion control which is robust to channel jitter, introduced by e.g. radio resource scheduling while still being able to respond promptly to reduced link throughput. +To achieve this, SCReAM implements a feedback protocol over RTCP that acknowledges received RTP packets. +A congestion window is determined from the feedback, this congestion window determines how many RTP packets that can be in flight i.e. transmitted by not yet acknowledged, an RTP queue is maintained at the sender side to temporarily store the RTP packets pending transmission, this RTP queue is mostly empty but can temporarily become larger when the link throughput decreases. +The congestion window is frequently adjusted for minimal e2e delay while still maintaining as high link utilization as possible. The use of self-clocking in SCReAM which is also the main principle in TCP has proven to work particularly well in wireless scenarios where the link throughput may change rapidly. This enables a congestion control which is robust to channel jitter, introduced by e.g. radio resource scheduling while still being able to respond promptly to reduced link throughput. SCReAM is optimized in house in a state of the art LTE system simulator for optimal performance in deployments where the LTE radio conditions are limiting. In addition, SCReAM is also optimized for good performance in simple bottleneck case such as those given in home gateway deployments. SCReAM is verified in simulator and in a testbed to operate in a rate range from ~20kbps up to 100Mbps. The fact that SCReAM maintains a RTP queue on the sender side opens up for further optimizations to congestion, for instance it is possible to discard the contents of the RTP queue and replace with an I frame in order to refresh the video quickly at congestion. @@ -22,14 +22,14 @@ Below is shown an example of SCReAM congestion control when subject to a bottlen Figure 1 : Simple bottleneck simulation SCReAM -## ECN (Explicit Congestion Notification) -SCReAM supports "classic" ECN, i.e. that the sending rate is reduced as a result of one or more ECN marked RTP packets in one RTT, similar to the guidelines in RFC3168. . +## ECN (Explicit Congestion Notification) +SCReAM supports "classic" ECN, i.e. that the sending rate is reduced as a result of one or more ECN marked RTP packets in one RTT, similar to the guidelines in RFC3168. . -In addition SCReAM also supports L4S, i.e that the sending rate is reduced proportional to the fraction of the RTP packets that are ECN marked. This enables lower network queue delay. +In addition SCReAM also supports L4S, i.e that the sending rate is reduced proportional to the fraction of the RTP packets that are ECN marked. This enables lower network queue delay. Below is shown three simulation examples with a simple 50Mbps bottleneck that changes to 25Mbps after 50s, the min RTT is 20ms. The video trace is from NVENC. -The graphs show that ECN improves on the e2e delay and that the use of L4S reduces the delay considerably more. +The graphs show that ECN improves on the e2e delay and that the use of L4S reduces the delay considerably more. L4S gives a somewhat lower media rate, the reason is that a larger headroom is added to ensure the low delay, considering the varying output rate of the video encoder. This is self-adjusting by inherent design, the average bitrate would increase if the frame size variations are smaller. @@ -38,10 +38,10 @@ Note carefully that the scales in the graphs differ, especially the delay graph ![Simple bottleneck simulation SCReAM no ECN support](https://github.com/EricssonResearch/scream/blob/master/images/scream_noecn_2.png) Figure 2 : SCReAM without ECN support - + ![Simple bottleneck simulation SCReAM with ECN support](https://github.com/EricssonResearch/scream/blob/master/images/scream_ecn_2.png) -Figure 3 : SCReAM with ECN support ECN beta = 0.8. CoDel with default settings (5ms, 100ms) +Figure 3 : SCReAM with ECN support ECN beta = 0.8. CoDel with default settings (5ms, 100ms) ![Simple bottleneck simulation SCReAM with L4S support](https://github.com/EricssonResearch/scream/blob/master/images/scream_l4s_2.png) @@ -51,9 +51,9 @@ Figure 4 : SCReAM with L4S support. L4S ramp-marker (Th_low=2ms, Th_high=10ms) The two videos below show a simple test with a simple 3Mbps bottleneck (CoDel AQM, ECN cabable). The first video is with ECN disabled in the sender, the other is with ECN enabled. SCReAM is here used with a Panasonic WV-SBV111M IP camera. One may argue that one can disable CoDel to avoid the packet losses, unfortunately one then lose the positive properties with CoDel, mentioned earlier. -[Without ECN](https://www.youtube.com/watch?v=J0po78q1QkU "Without ECN") +[Without ECN](https://www.youtube.com/watch?v=J0po78q1QkU "Without ECN") -[With ECN](https://www.youtube.com/watch?v=qIe0ubw9jPw "With ECN") +[With ECN](https://www.youtube.com/watch?v=qIe0ubw9jPw "With ECN") The green areas that uccur due to packet loss is an artifact in the conversion of the RTP dump. ## Real life test @@ -62,17 +62,17 @@ A real life test of SCReAM is performed with the following setup in a car: - Sony Camcorder placed on dashboard, HDMI output used - Antrica ANT-35000A video encoder with 1000-8000kbps encoding range and 1080p50 mode - Laptop with a SCReAM sender running -- Sony Xperia phone in WiFi tethering mode +- Sony Xperia phone in WiFi tethering mode A SCReAM receiver that logged the performance and stored the received RTP packets was running in an office. The video traffic was thus transmitted in LTE uplink.The video was played out to file with GStreamer, the jitter buffer was disabled to allow for the visibility of the delay jitter artifacts, -Below is a graph that shows the bitrate, the congestion window and the queue delay. +Below is a graph that shows the bitrate, the congestion window and the queue delay. ![Log from ](https://github.com/EricssonResearch/scream/blob/master/images/SCReAM_LTE_UL.png) Figure 5 : Trace from live drive test -The graph shows that SCReAM manages high bitrate video streaming with low e2e delay despite demanding conditions both in terms of variable throughput and in a changing output bitrate from the video encoder. Packet losses occur relatively frequently, the exact reason is unknown but seem to be related to handover events, normally packet loss should not occure in LTE-UL, however this seems to be the case with the used cellphone. +The graph shows that SCReAM manages high bitrate video streaming with low e2e delay despite demanding conditions both in terms of variable throughput and in a changing output bitrate from the video encoder. Packet losses occur relatively frequently, the exact reason is unknown but seem to be related to handover events, normally packet loss should not occure in LTE-UL, however this seems to be the case with the used cellphone. The delay increases between 1730 and 1800s, the reason here is that the available throughput was lower than the lowest possible coder bitrate. An encoder with a wider rate range would be able to make it possible to keep the delay low also in this case. A video from the experiment is found at the link below. The artifacts and overall video quality can be correlated aginst the graph above. @@ -104,17 +104,17 @@ A few support classes for experimental use are implemented in: - NetQueue : Simple delay and bandwidth limitation -For more information on how to use the code in multimedia clients or in experimental platforms, please see [https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx](https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx?raw=true) +For more information on how to use the code in multimedia clients or in experimental platforms, please see [https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx](https://github.com/EricssonResearch/scream/blob/master/SCReAM-description.pptx?raw=true) ## References [1] https://tools.ietf.org/html/rfc8298 -[2] Sigcomm paper http://dl.acm.org/citation.cfm?id=2631976 +[2] Sigcomm paper http://dl.acm.org/citation.cfm?id=2631976 [3] Sigcomm presentation http://conferences.sigcomm.org/sigcomm/2014/doc/slides/150.pdf -[4] IETF RMCAT presentation, comparison against Google Congestion Control (GCC) http://www.ietf.org/proceedings/90/slides/slides-90-rmcat-3.pdf +[4] IETF RMCAT presentation, comparison against Google Congestion Control (GCC) http://www.ietf.org/proceedings/90/slides/slides-90-rmcat-3.pdf [5] IETF RMCAT presentation (final for WGLC) : https://www.ietf.org/proceedings/96/slides/slides-96-rmcat-0.pdf diff --git a/Sierra-RV50X-logger.py b/Sierra-RV50X-logger.py index 4dce2bc..ce21623 100644 --- a/Sierra-RV50X-logger.py +++ b/Sierra-RV50X-logger.py @@ -1,4 +1,3 @@ - import requests import json import datetime @@ -81,12 +80,12 @@ def send_to_udp_socket(result): UDP_IP = "127.0.0.1" UDP_PORT = 35000 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.sendto(bytes(result, 'utf-8'), (UDP_IP, UDP_PORT)) + sock.sendto(bytes(result, 'utf-8'), (UDP_IP, UDP_PORT)) # params = ['Cellular IP Address', 'ESN/EID/IMEI'] params = param_ids.keys() try: - while True: + while True: result = make_request(params) #result = make_request(payload) #print('result:', json.dumps(result, indent=4)) diff --git a/code/NetQueue.cpp b/code/NetQueue.cpp index 4c9b298..fa00d27 100644 --- a/code/NetQueue.cpp +++ b/code/NetQueue.cpp @@ -6,6 +6,7 @@ #include using namespace std; + /* * Implements a simple RTP packet queue */ @@ -40,10 +41,10 @@ NetQueue::NetQueue(float delay_, float rate_, float jitter_, bool isL4s_) { tQueueAvg = 0.0; } -void NetQueue::insert(float time, - void *rtpPacket, +void NetQueue::insert(float time, + void *rtpPacket, unsigned int ssrc, - int size, + int size, unsigned short seqNr, bool isCe) { head++; if (head == NetQueueSize) head = 0; @@ -63,10 +64,10 @@ void NetQueue::insert(float time, nextTx = items[head]->tRelease; } -bool NetQueue::extract(float time, - void *rtpPacket, +bool NetQueue::extract(float time, + void *rtpPacket, unsigned int &ssrc, - int& size, + int& size, unsigned short& seqNr, bool& isCe) { if (items[tail]->used == false) { @@ -116,7 +117,7 @@ bool NetQueue::extract(float time, int NetQueue::sizeOfQueue() { int size = 0; for (int n=0; n < NetQueueSize; n++) { - if (items[n]->used) + if (items[n]->used) size += items[n]->size; } return size; diff --git a/code/NetQueue.h b/code/NetQueue.h index 2247a06..a435ad2 100644 --- a/code/NetQueue.h +++ b/code/NetQueue.h @@ -1,7 +1,6 @@ #ifndef NET_QUEUE #define NET_QUEUE - class NetQueueItem { public: NetQueueItem(); @@ -26,10 +25,10 @@ class NetQueue { int size, unsigned short seqNr, bool isCe = false); - bool extract(float time, - void *rtpPacket, + bool extract(float time, + void *rtpPacket, unsigned int &ssrc, - int& size, + int& size, unsigned short &seqNr, bool &isCe); int sizeOfQueue(); @@ -54,4 +53,4 @@ class NetQueue { float tQueueAvg; }; -#endif \ No newline at end of file +#endif diff --git a/code/ScreamTx.cpp b/code/ScreamTx.cpp index 909f693..4bc97d7 100644 --- a/code/ScreamTx.cpp +++ b/code/ScreamTx.cpp @@ -1062,7 +1062,7 @@ void ScreamTx::initialize(uint32_t time_ntp) { float ScreamTx::getTotalTargetBitrate() { float totalTargetBitrate = 0.0f; for (int n = 0; n < nStreams; n++) { - totalTargetBitrate += streams[n]->targetBitrate; + totalTargetBitrate += streams[n]->targetBitrate; } return totalTargetBitrate; } diff --git a/code/ScreamTx.h b/code/ScreamTx.h index bd29cf5..f98f607 100644 --- a/code/ScreamTx.h +++ b/code/ScreamTx.h @@ -4,7 +4,7 @@ #include #include #include -extern "C" { + using namespace std; /* @@ -50,7 +50,7 @@ static const float kGainDown = 2.0f; // Max video rampup speed in bps/s (bits per second increase per second) static const float kRampUpSpeed = 200000.0f; // bps/s // Max video rampup scale as fraction of the current target bitrate -static const float kRampUpScale = 0.2f; +static const float kRampUpScale = 0.2f; // Max RTP queue delay, RTP queue is cleared if this value is exceeded static const float kMaxRtpQueueDelay = 0.1; // 0.1s // Compensation factor for RTP queue size @@ -193,7 +193,7 @@ class ScreamTx { uint32_t ssrc, int size, uint16_t seqNr, - bool isMark); + bool isMark); /* New incoming feedback, this function * triggers a CWND update @@ -279,9 +279,9 @@ class ScreamTx { fp_log = fp; } - void setTimeString(char *s) { - strcpy(timeString,s); - } + void setTimeString(char *s) { + strcpy(timeString,s); + } /* * extra data to be appended to detailed log @@ -290,21 +290,21 @@ class ScreamTx { strcpy(detailedLogExtraData,s); } - /* - * Get the list of log items - */ - char *getDetailedLogItemList() { - return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; - } - - /* - * Log each ACKed packet, - */ - void useExtraDetailedLog(bool isUseExtraDetailedLog_) { - isUseExtraDetailedLog = isUseExtraDetailedLog_; - } - - /* + /* + * Get the list of log items + */ + char *getDetailedLogItemList() { + return "\"Time [s]\",\"Estimated queue delay [s]\",\"RTT [s]\",\"Congestion window [byte]\",\"Bytes in flight [byte]\",\"Fast increase mode\",\"Total transmit bitrate [bps]\",\"Stream ID\",\"RTP SN\",\"Bytes newly ACKed\",\"Bytes newly ACKed and CE marked\",\"Media coder bitrate [bps]\",\"Transmitted bitrate [bps]\",\"ACKed bitrate [bps]\",\"Lost bitrate [bps]\",\"CE Marked bitrate [bps]\",\"Marker bit set\""; + } + + /* + * Log each ACKed packet, + */ + void useExtraDetailedLog(bool isUseExtraDetailedLog_) { + isUseExtraDetailedLog = isUseExtraDetailedLog_; + } + + /* * Set lowest possible cwndMin */ void setCwndMinLow(int aValue) { @@ -319,7 +319,7 @@ class ScreamTx { uint32_t timeTx_ntp; int size; uint16_t seqNr; - bool isMark; + bool isMark; bool isUsed; bool isAcked; bool isAfterReceivedEdge; @@ -465,12 +465,12 @@ class ScreamTx { uint8_t ceBits, int &encCeMarkedBytes, bool isLast, - bool &isMark); + bool &isMark); - /* - * Get total target bitrate for all streams - */ - float getTotalTargetBitrate(); + /* + * Get total target bitrate for all streams + */ + float getTotalTargetBitrate(); /* * Update CWND @@ -699,12 +699,11 @@ class ScreamTx { */ FILE *fp_log; bool completeLogItem; - char timeString[100]; - bool isUseExtraDetailedLog; + char timeString[100]; + bool isUseExtraDetailedLog; int bytesNewlyAckedLog; - int ecnCeMarkedBytesLog; + int ecnCeMarkedBytesLog; }; -} #endif diff --git a/code/VideoEnc.cpp b/code/VideoEnc.cpp index db532a8..bac9a9b 100644 --- a/code/VideoEnc.cpp +++ b/code/VideoEnc.cpp @@ -55,4 +55,3 @@ int VideoEnc::encode(float time) { rtpQueue->setSizeOfLastFrame(rtpBytes); return rtpBytes; } - diff --git a/code/VideoEnc.h b/code/VideoEnc.h index 45d29b5..2e80e5e 100644 --- a/code/VideoEnc.h +++ b/code/VideoEnc.h @@ -21,5 +21,4 @@ class VideoEnc { int ix; }; - -#endif \ No newline at end of file +#endif diff --git a/code/gscream/gscream_app_rpi_tx.cpp b/code/gscream/gscream_app_rpi_tx.cpp index cf07e7b..0b8fab4 100644 --- a/code/gscream/gscream_app_rpi_tx.cpp +++ b/code/gscream/gscream_app_rpi_tx.cpp @@ -39,7 +39,7 @@ int main (int argc, char *argv[]) #ifdef SCREAM gscreamtx = gst_element_factory_make ("gscreamtx", "scream-tx"); g_assert (gscreamtx); -#endif +#endif g_assert (videoencode); g_assert (capsfilter); @@ -105,7 +105,7 @@ int main (int argc, char *argv[]) srcpad = gst_element_get_static_pad (gscreamtx, "src"); #else srcpad = gst_element_get_static_pad (videopayload, "src"); -#endif +#endif if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) g_error ("Failed to link video payloader to rtpbin"); gst_object_unref (srcpad); diff --git a/code/gscream/gst-gscreamrx/gst-plugin/COPYING b/code/gscream/gst-gscreamrx/gst-plugin/COPYING index 09ec995..49b9c3c 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/COPYING +++ b/code/gscream/gst-gscreamrx/gst-plugin/COPYING @@ -1,2 +1 @@ Put your license in here! - diff --git a/code/gscream/gst-gscreamrx/gst-plugin/ChangeLog b/code/gscream/gst-gscreamrx/gst-plugin/ChangeLog index 5c25746..7ead349 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/ChangeLog +++ b/code/gscream/gst-gscreamrx/gst-plugin/ChangeLog @@ -104,7 +104,7 @@ 2006-04-20 Stefan Kost - Patch by: Johan Rydberg + Patch by: Johan Rydberg * src/gstplugin.c: (gst_plugin_template_get_type), (gst_plugin_template_base_init), (gst_plugin_template_class_init), @@ -116,7 +116,7 @@ (gst_plugin_template_get_property): * tools/make_element: remove double gst_get_, fix '_' in names - + 2006-02-26 Tim-Philipp Müller @@ -190,8 +190,8 @@ * autogen.sh: * configure.ac: * src/Makefile.am: - use proper LDFLAGS for plugins - run in maintainer mode by default + use proper LDFLAGS for plugins + run in maintainer mode by default 2004-04-22 Thomas Vander Stichele @@ -210,8 +210,8 @@ 2003-02-06 Thomas Vander Stichele - * updated for GStreamer 0.6.0 + * updated for GStreamer 0.6.0 2002-07-17 Thomas Vander Stichele - * initial creation on a flight to New York + * initial creation on a flight to New York diff --git a/code/gscream/gst-gscreamrx/gst-plugin/README b/code/gscream/gst-gscreamrx/gst-plugin/README index 1905684..1236ac2 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/README +++ b/code/gscream/gst-gscreamrx/gst-plugin/README @@ -9,7 +9,7 @@ of how to set up autotools and your source tree. This template demonstrates : - what to do in autogen.sh - how to setup configure.ac (your package name and version, GStreamer flags) -- how to setup your source dir +- how to setup your source dir - what to put in Makefile.am More features and templates might get added later on. @@ -31,4 +31,3 @@ one element called 'myfilter' too. Also look for "FIXME:" markers that point you to places where you need to edit the code. You still need to adjust the Makefile.am. - diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.cpp b/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.cpp index 44017ea..2b5cee0 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.cpp +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2005 Thomas Vander Stichele * Copyright (C) 2005 Ronald S. Bultje * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL - * + * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.h b/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.h index f1fe4ed..27c1bef 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.h +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/gstplugin.h @@ -3,7 +3,7 @@ * Copyright (C) 2005 Thomas Vander Stichele * Copyright (C) 2005 Ronald S. Bultje * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL - * + * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation @@ -74,7 +74,7 @@ struct _GstPluginTemplate gboolean silent; }; -struct _GstPluginTemplateClass +struct _GstPluginTemplateClass { GstElementClass parent_class; }; diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.c b/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.c index af6c75b..9352079 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.c +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.c @@ -184,7 +184,7 @@ gst_plugin_template_transform_ip (GstBaseTransform * base, GstBuffer * outbuf) if (filter->silent == FALSE) g_print ("I'm plugged, therefore I'm in.\n"); - + /* FIXME: do something interesting here. This simply copies the source * to the destination. */ diff --git a/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.h b/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.h index be9ce86..ee9c5dd 100644 --- a/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.h +++ b/code/gscream/gst-gscreamrx/gst-plugin/src/gsttransform.h @@ -1,4 +1,4 @@ -/* +/* * GStreamer * Copyright (C) 2006 Stefan Kost * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL @@ -18,7 +18,7 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ - + #ifndef __GST_PLUGIN_TEMPLATE_H__ #define __GST_PLUGIN_TEMPLATE_H__ diff --git a/code/gscream/gst-gscreamtx/gst-plugin/COPYING b/code/gscream/gst-gscreamtx/gst-plugin/COPYING index 09ec995..49b9c3c 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/COPYING +++ b/code/gscream/gst-gscreamtx/gst-plugin/COPYING @@ -1,2 +1 @@ Put your license in here! - diff --git a/code/gscream/gst-gscreamtx/gst-plugin/ChangeLog b/code/gscream/gst-gscreamtx/gst-plugin/ChangeLog index 5c25746..7ead349 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/ChangeLog +++ b/code/gscream/gst-gscreamtx/gst-plugin/ChangeLog @@ -104,7 +104,7 @@ 2006-04-20 Stefan Kost - Patch by: Johan Rydberg + Patch by: Johan Rydberg * src/gstplugin.c: (gst_plugin_template_get_type), (gst_plugin_template_base_init), (gst_plugin_template_class_init), @@ -116,7 +116,7 @@ (gst_plugin_template_get_property): * tools/make_element: remove double gst_get_, fix '_' in names - + 2006-02-26 Tim-Philipp Müller @@ -190,8 +190,8 @@ * autogen.sh: * configure.ac: * src/Makefile.am: - use proper LDFLAGS for plugins - run in maintainer mode by default + use proper LDFLAGS for plugins + run in maintainer mode by default 2004-04-22 Thomas Vander Stichele @@ -210,8 +210,8 @@ 2003-02-06 Thomas Vander Stichele - * updated for GStreamer 0.6.0 + * updated for GStreamer 0.6.0 2002-07-17 Thomas Vander Stichele - * initial creation on a flight to New York + * initial creation on a flight to New York diff --git a/code/gscream/gst-gscreamtx/gst-plugin/README b/code/gscream/gst-gscreamtx/gst-plugin/README index 1905684..1236ac2 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/README +++ b/code/gscream/gst-gscreamtx/gst-plugin/README @@ -9,7 +9,7 @@ of how to set up autotools and your source tree. This template demonstrates : - what to do in autogen.sh - how to setup configure.ac (your package name and version, GStreamer flags) -- how to setup your source dir +- how to setup your source dir - what to put in Makefile.am More features and templates might get added later on. @@ -31,4 +31,3 @@ one element called 'myfilter' too. Also look for "FIXME:" markers that point you to places where you need to edit the code. You still need to adjust the Makefile.am. - diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/gstgscreamtx.cpp b/code/gscream/gst-gscreamtx/gst-plugin/src/gstgscreamtx.cpp index b160693..ff99b0c 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/gstgscreamtx.cpp +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/gstgscreamtx.cpp @@ -528,8 +528,8 @@ gst_g_scream_tx_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) //g_print("CALLBACK\n"); filter->encoder = gst_bin_get_by_name_recurse_up(GST_BIN(pipe), "video"); g_assert(filter->encoder); - - + + //g_object_set(G_OBJECT(filter->encoder), "bitrate", 200, NULL); //filter->encoder = gst_bin_get_by_name_recurse_up(GST_BIN(pipe), "encoder"); diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/gstplugin.cpp b/code/gscream/gst-gscreamtx/gst-plugin/src/gstplugin.cpp index 44017ea..2b5cee0 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/gstplugin.cpp +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/gstplugin.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2005 Thomas Vander Stichele * Copyright (C) 2005 Ronald S. Bultje * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL - * + * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.c b/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.c index af6c75b..9352079 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.c +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.c @@ -184,7 +184,7 @@ gst_plugin_template_transform_ip (GstBaseTransform * base, GstBuffer * outbuf) if (filter->silent == FALSE) g_print ("I'm plugged, therefore I'm in.\n"); - + /* FIXME: do something interesting here. This simply copies the source * to the destination. */ diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.h b/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.h index be9ce86..ee9c5dd 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.h +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/gsttransform.h @@ -1,4 +1,4 @@ -/* +/* * GStreamer * Copyright (C) 2006 Stefan Kost * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL @@ -18,7 +18,7 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ - + #ifndef __GST_PLUGIN_TEMPLATE_H__ #define __GST_PLUGIN_TEMPLATE_H__ diff --git a/code/scream_v_a.cpp b/code/scream_v_a.cpp index 0b0e478..4f54621 100644 --- a/code/scream_v_a.cpp +++ b/code/scream_v_a.cpp @@ -22,7 +22,7 @@ int swprio = -1; //#define TRACEFILE "../traces/trace_no_key_smooth.txt" /* * Mode determines how many streams should be run -* 1 = audio, 2 = video, 3 = 1+2, 4 = +* 1 = audio, 2 = video, 3 = 1+2, 4 = */ const int mode = 0x01; const double timeBase = 10000.0; @@ -194,7 +194,7 @@ int main(int argc, char* argv[]) netQueueRate->rate = 50000e3; } } - + if (time > 30 && swprio == 0) { swprio = 1; screamTx->setTargetPriority(10, 0.2); @@ -208,7 +208,7 @@ int main(int argc, char* argv[]) if (false && time > 50) netQueueRate->rate = 8e6; - + /* if ((time >= 60) && (time < 80) && isChRate) @@ -230,4 +230,3 @@ int main(int argc, char* argv[]) return 0; } - diff --git a/code/stdafx.h b/code/stdafx.h index cf009df..1847421 100644 --- a/code/stdafx.h +++ b/code/stdafx.h @@ -18,4 +18,3 @@ #include #include #include - diff --git a/plot_cdf.m b/plot_cdf.m index a2d249f..3c5ba8b 100644 --- a/plot_cdf.m +++ b/plot_cdf.m @@ -1,12 +1,12 @@ function plot_cdf(a,Tlim,Tmax) -% This function plots the CDF of the RTT and -% queue delay from the logs given by the +% This function plots the CDF of the RTT and +% queue delay from the logs given by the % SCReAM BW test tool. % Parameters : % a : log file from SCReAM BW test tool % imported with the command % a = load(); -% where is the name of the log file +% where is the name of the log file % Tlim : xmin and xmax limits [s], e.g. [0 100] % Tmax : Max displayed value for RTT/delay % @@ -17,7 +17,7 @@ function plot_cdf(a,Tlim,Tmax) % >a = a(1:50:end,:); % subsample the log file % >figure(1); % >plot_cdf(.... -% +% T = a(:,1); ix = intersect(find(T > Tlim(1)),find(T <= Tlim(2))); @@ -35,4 +35,3 @@ function plot_cdf(a,Tlim,Tmax) xlabel('[s]'); end - diff --git a/plot_thp_delay.m b/plot_thp_delay.m index 4d613a5..2a46390 100644 --- a/plot_thp_delay.m +++ b/plot_thp_delay.m @@ -1,11 +1,11 @@ function plot_thp_delay(a,Tlim,maxThp,maxDelay) -% This function plots the thorughput +% This function plots the thorughput % RTT and estimated queue delay % Parameters: % a : log file from SCReAM BW test tool % imported with the command % a = load(); -% where is the name of the log file +% where is the name of the log file % Tlim : xmin and xmax limits [s], e.g. [0 100] % maxThp : Max thorughput [Mbps] % maxDelay : Max delay [s] @@ -17,7 +17,7 @@ function plot_thp_delay(a,Tlim,maxThp,maxDelay) % >a = a(1:50:end,:); % subsample the log file % >figure(1); % >plot_cdf(.... -% +% T = a(:,1); subplot(211);%subplot(9,1,1:5); K = 5; @@ -43,4 +43,3 @@ function plot_thp_delay(a,Tlim,maxThp,maxDelay) xlabel('T [s]'); xlim(Tlim); end - diff --git a/test_01.m b/test_01.m index f94e4a0..317fa0b 100644 --- a/test_01.m +++ b/test_01.m @@ -5,7 +5,7 @@ function test_01(a,Tmax,Bmax,Cmax) if 1 figure(1); subplot(2,1,1); - plot(T,a(:,2),T,a(:,3)); + plot(T,a(:,2),T,a(:,3)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.5]);grid on; title('OWD [s] and OWD trend'); @@ -13,7 +13,7 @@ function test_01(a,Tmax,Bmax,Cmax) subplot(2,1,2); plot(T,a(:,14)); set(gca,'FontSize',12);grid on; - title('RTP queue delay [s]'); + title('RTP queue delay [s]'); xlim([0 Tmax]);grid on; %axis([0 Tmax 0 1.0]);grid on; xlabel('T [s]'); @@ -21,19 +21,19 @@ function test_01(a,Tmax,Bmax,Cmax) subplot(2,1,1); plot(T,a(:,8),T,a(:,10)); set(gca,'FontSize',12);grid on; - axis([0 Tmax 0 Cmax]);grid on; - title('CWND & in flight [byte]'); + axis([0 Tmax 0 Cmax]);grid on; + title('CWND & in flight [byte]'); subplot(2,1,2); plot(T,a(:,15),T,a(:,16)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax]);grid on; - title('Bitrate [bps]'); + title('Bitrate [bps]'); legend('Target bitrate','Target bitrate infl. point'); xlabel('T [s]'); end figure(3); subplot(2,1,1); - plot(T,a(:,2),T,a(:,14)); + plot(T,a(:,2),T,a(:,14)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.5]);grid on; title('OWD [s] RTP queue delay'); @@ -42,8 +42,6 @@ function test_01(a,Tmax,Bmax,Cmax) plot(T,a(:,15),T,a(:,16),T,a(:,17),T,a(:,18)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax]);grid on; - title('Bitrate [bps]'); + title('Bitrate [bps]'); legend('Target','TargetI','Transmitted', 'Acked'); xlabel('T [s]'); - - \ No newline at end of file diff --git a/test_v_a.m b/test_v_a.m index 16969f1..588a26b 100644 --- a/test_v_a.m +++ b/test_v_a.m @@ -3,26 +3,26 @@ function test_v_a(a,Tmax,I,Bmax,Cmax) K = 12; %Tmax = 100.0; - + figure(1); subplot(2,1,1); - plot(T,a(:,2),T,a(:,3));%,T,a(:,4)); + plot(T,a(:,2),T,a(:,3));%,T,a(:,4)); set(gca,'FontSize',12);grid on; - set(gca,'XTickLabel',[]); + set(gca,'XTickLabel',[]); axis([0 Tmax 0 0.2]);grid on; title('qdel[s]'); - + subplot(2,1,2); plot(T,a(:,4),T,a(:,5),T,a(:,7)*20000,'k'); set(gca,'FontSize',12);grid on; - axis([0 Tmax 0 Cmax]);grid on; - title('CWND & in flight [byte]'); + axis([0 Tmax 0 Cmax]);grid on; + title('CWND & in flight [byte]'); xlabel('T [s]'); if I>0 figure(2); subplot(2,1,1); - plot(T,a(:,8)); + plot(T,a(:,8)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.2]);grid on; set(gca,'XTickLabel',[]); @@ -31,15 +31,15 @@ function test_v_a(a,Tmax,I,Bmax,Cmax) plot(T,a(:,9)/1000,T,a(:,11)/1000,T,a(:,10)/1000); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax(1)]);grid on; - title('Bitrate [Mbps]'); + title('Bitrate [Mbps]'); legend('Target','Transmitted', 'RTP'); xlabel('T [s]'); end if I>1 - K = 6; + K = 6; figure(3); subplot(2,1,1); - plot(T,a(:,8+K)); + plot(T,a(:,8+K)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.2]);grid on; set(gca,'XTickLabel',[]); @@ -48,15 +48,15 @@ function test_v_a(a,Tmax,I,Bmax,Cmax) plot(T,a(:,9+K)/1000,T,a(:,11+K)/1000,T,a(:,10+K)/1000); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax(2)]);grid on; - title('Bitrate [kbps]'); + title('Bitrate [kbps]'); legend('Target','Transmitted', 'RTP'); xlabel('T [s]'); end - if I>2 - K = 12; + if I>2 + K = 12; figure(4); subplot(2,1,1); - plot(T,a(:,8+K)); + plot(T,a(:,8+K)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 0.1]);grid on; set(gca,'XTickLabel',[]); @@ -65,8 +65,7 @@ function test_v_a(a,Tmax,I,Bmax,Cmax) plot(T,a(:,9+K),T,a(:,11+K),T,a(:,10+K)); set(gca,'FontSize',12);grid on; axis([0 Tmax 0 Bmax(3)]);grid on; - title('Bitrate [kbps]'); + title('Bitrate [kbps]'); legend('Target','Transmitted', 'RTP'); xlabel('T [s]'); end - \ No newline at end of file From ded7b3cbb187f89352d826154a1418ded3f71cba Mon Sep 17 00:00:00 2001 From: mirabilos Date: Fri, 3 Jul 2020 12:27:59 +0200 Subject: [PATCH 8/9] whitespace, again --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d274577..a4d2c04 100644 --- a/README.md +++ b/README.md @@ -154,4 +154,4 @@ cmake . make ``` -You need git, cmake, make and g++ installed \ No newline at end of file +You need git, cmake, make and g++ installed From 246b900979f1816c6f4551c81a9f87750b468919 Mon Sep 17 00:00:00 2001 From: mirabilos Date: Fri, 3 Jul 2020 13:02:45 +0200 Subject: [PATCH 9/9] unify RtpQueue as well, with tiny ifdefs --- code/RtpQueue.cpp | 38 +++++ code/RtpQueue.h | 11 ++ .../gst-gscreamtx/gst-plugin/src/RtpQueue.cpp | 158 +----------------- .../gst-gscreamtx/gst-plugin/src/RtpQueue.h | 66 +------- 4 files changed, 53 insertions(+), 220 deletions(-) diff --git a/code/RtpQueue.cpp b/code/RtpQueue.cpp index 5d42e82..87d6794 100644 --- a/code/RtpQueue.cpp +++ b/code/RtpQueue.cpp @@ -63,6 +63,44 @@ bool RtpQueue::pop(void *rtpPacket, int& size, unsigned short& seqNr) { } } +#ifdef GSCREAM +void RtpQueue::push(GstBuffer *buffer, int size, unsigned short seqNr, float ts) { + int ix = head+1; + if (ix == kRtpQueueSize) ix = 0; + if (items[ix]->used) { + /* + * RTP queue is full, do a drop tail i.e ignore new RTP packets + */ + return; + } + head = ix; + items[head]->seqNr = seqNr; + items[head]->size = size; + items[head]->ts = ts; + items[head]->used = true; + items[head]->buffer = buffer; + bytesInQueue_ += size; + sizeOfQueue_ += 1; + computeSizeOfNextRtp(); +} + +GstBuffer* RtpQueue::pop(unsigned short& seqNr) { + if (items[tail]->used == false) { + sizeOfNextRtp_ = -1; + return 0; + } else { + GstBuffer *buffer = items[tail]->buffer; + seqNr = items[tail]->seqNr; + items[tail]->used = false; + bytesInQueue_ -= items[tail]->size; + sizeOfQueue_ -= 1; + tail++; if (tail == kRtpQueueSize) tail = 0; + computeSizeOfNextRtp(); + return buffer; + } +} +#endif + void RtpQueue::computeSizeOfNextRtp() { if (!items[tail]->used) { sizeOfNextRtp_ = - 1; diff --git a/code/RtpQueue.h b/code/RtpQueue.h index 8af9d29..a663aee 100644 --- a/code/RtpQueue.h +++ b/code/RtpQueue.h @@ -1,6 +1,10 @@ #ifndef RTP_QUEUE #define RTP_QUEUE +#ifdef GSCREAM +#include +#endif + /* * Implements a simple RTP packet queue, one RTP queue * per stream {SSRC,PT} @@ -25,6 +29,9 @@ class RtpQueueItem { unsigned short seqNr; float ts; bool used; +#ifdef GSCREAM + GstBuffer *buffer; +#endif }; const int kRtpQueueSize = 1024; @@ -34,6 +41,10 @@ class RtpQueue : public RtpQueueIface { void push(void *rtpPacket, int size, unsigned short seqNr, float ts); bool pop(void *rtpPacket, int &size, unsigned short &seqNr); +#ifdef GSCREAM + void push(GstBuffer *buf, int size, unsigned short seqNr, float ts); + GstBuffer* pop(unsigned short &seqNr); +#endif int sizeOfNextRtp(); int seqNrOfNextRtp(); int bytesInQueue(); // Number of bytes in queue diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/RtpQueue.cpp b/code/gscream/gst-gscreamtx/gst-plugin/src/RtpQueue.cpp index e67ad2b..389e2e3 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/RtpQueue.cpp +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/RtpQueue.cpp @@ -1,156 +1,2 @@ -#include "RtpQueue.h" -#include -#include -using namespace std; -/* -* Implements a simple RTP packet queue -*/ - -RtpQueueItem::RtpQueueItem() { - used = false; - size = 0; - seqNr = 0; -} - - -RtpQueue::RtpQueue() { - for (int n=0; n < kRtpQueueSize; n++) { - items[n] = new RtpQueueItem(); - } - head = -1; - tail = 0; - nItems = 0; - sizeOfLastFrame = 0; - bytesInQueue_ = 0; - sizeOfQueue_ = 0; - sizeOfNextRtp_ = -1; -} - -void RtpQueue::push(void *rtpPacket, int size, unsigned short seqNr, float ts) { - int ix = head+1; - if (ix == kRtpQueueSize) ix = 0; - if (items[ix]->used) { - /* - * RTP queue is full, do a drop tail i.e ignore new RTP packets - */ - return; - } - head = ix; - items[head]->seqNr = seqNr; - items[head]->size = size; - items[head]->ts = ts; - items[head]->used = true; - bytesInQueue_ += size; - sizeOfQueue_ += 1; - memcpy(items[head]->packet, rtpPacket, size); - computeSizeOfNextRtp(); -} - -void RtpQueue::push(GstBuffer *buffer, int size, unsigned short seqNr, float ts) { - int ix = head+1; - if (ix == kRtpQueueSize) ix = 0; - if (items[ix]->used) { - /* - * RTP queue is full, do a drop tail i.e ignore new RTP packets - */ - return; - } - head = ix; - items[head]->seqNr = seqNr; - items[head]->size = size; - items[head]->ts = ts; - items[head]->used = true; - items[head]->buffer = buffer; - bytesInQueue_ += size; - sizeOfQueue_ += 1; - computeSizeOfNextRtp(); -} - -bool RtpQueue::pop(void *rtpPacket, int& size, unsigned short& seqNr) { - if (items[tail]->used == false) { - sizeOfNextRtp_ = -1; - return false; - } else { - size = items[tail]->size; - memcpy(rtpPacket,items[tail]->packet,size); - seqNr = items[tail]->seqNr; - items[tail]->used = false; - bytesInQueue_ -= size; - sizeOfQueue_ -= 1; - tail++; if (tail == kRtpQueueSize) tail = 0; - computeSizeOfNextRtp(); - return true; - } -} - -GstBuffer* RtpQueue::pop(unsigned short& seqNr) { - if (items[tail]->used == false) { - sizeOfNextRtp_ = -1; - return 0; - } else { - GstBuffer *buffer = items[tail]->buffer; - seqNr = items[tail]->seqNr; - items[tail]->used = false; - bytesInQueue_ -= items[tail]->size; - sizeOfQueue_ -= 1; - tail++; if (tail == kRtpQueueSize) tail = 0; - computeSizeOfNextRtp(); - return buffer; - } -} - -void RtpQueue::computeSizeOfNextRtp() { - if (!items[tail]->used) { - sizeOfNextRtp_ = - 1; - } else { - sizeOfNextRtp_ = items[tail]->size; - } -} - -int RtpQueue::sizeOfNextRtp() { - return sizeOfNextRtp_; -} - -int RtpQueue::seqNrOfNextRtp() { - if (!items[tail]->used) { - return -1; - } else { - return items[tail]->seqNr; - } -} - -int RtpQueue::bytesInQueue() { - return bytesInQueue_; -} - -int RtpQueue::sizeOfQueue() { - return sizeOfQueue_; -} - -float RtpQueue::getDelay(float currTs) { - if (items[tail]->used == false) { - return 0; - } else { - return currTs-items[tail]->ts; - } -} - -bool RtpQueue::sendPacket(void *rtpPacket, int& size, unsigned short& seqNr) { - if (sizeOfQueue() > 0) { - pop(rtpPacket, size, seqNr); - return true; - } - return false; -} - -void RtpQueue::clear() { - for (int n=0; n < kRtpQueueSize; n++) { - items[n]->used = false; - } - head = -1; - tail = 0; - nItems = 0; - bytesInQueue_ = 0; - sizeOfQueue_ = 0; - sizeOfNextRtp_ = -1; -} +#define GSCREAM +#include "../../../../RtpQueue.cpp" diff --git a/code/gscream/gst-gscreamtx/gst-plugin/src/RtpQueue.h b/code/gscream/gst-gscreamtx/gst-plugin/src/RtpQueue.h index 88780a8..e9a68d9 100644 --- a/code/gscream/gst-gscreamtx/gst-plugin/src/RtpQueue.h +++ b/code/gscream/gst-gscreamtx/gst-plugin/src/RtpQueue.h @@ -1,64 +1,2 @@ -#ifndef RTP_QUEUE -#define RTP_QUEUE - -#include - -/* - * Implements a simple RTP packet queue, one RTP queue - * per stream {SSRC,PT} - */ - -class RtpQueueIface { -public: - virtual void clear() = 0; - virtual int sizeOfNextRtp() = 0; - virtual int seqNrOfNextRtp() = 0; - virtual int bytesInQueue() = 0; // Number of bytes in queue - virtual int sizeOfQueue() = 0; // Number of items in queue - virtual float getDelay(float currTs) = 0; - virtual int getSizeOfLastFrame() = 0; -}; - -class RtpQueueItem { -public: - RtpQueueItem(); - char packet[2000]; - int size; - unsigned short seqNr; - float ts; - bool used; - GstBuffer *buffer; -}; - -const int kRtpQueueSize = 1024; -class RtpQueue : public RtpQueueIface { -public: - RtpQueue(); - - void push(void *rtpPacket, int size, unsigned short seqNr, float ts); - void push(GstBuffer *buf, int size, unsigned short seqNr, float ts); - bool pop(void *rtpPacket, int &size, unsigned short &seqNr); - GstBuffer* pop(unsigned short &seqNr); - int sizeOfNextRtp(); - int seqNrOfNextRtp(); - int bytesInQueue(); // Number of bytes in queue - int sizeOfQueue(); // Number of items in queue - float getDelay(float currTs); - bool sendPacket(void *rtpPacket, int &size, unsigned short &seqNr); - void clear(); - void setSizeOfLastFrame(int sz) { sizeOfLastFrame = sz; }; - int getSizeOfLastFrame() {return sizeOfLastFrame;}; - void computeSizeOfNextRtp(); - - RtpQueueItem *items[kRtpQueueSize]; - int head; // Pointer to last inserted item - int tail; // Pointer to the oldest item - int nItems; - int sizeOfLastFrame; // Size of last frame in bytes - - int bytesInQueue_; - int sizeOfQueue_; - int sizeOfNextRtp_; -}; - -#endif +#define GSCREAM +#include "../../../../RtpQueue.h"