Improved RTP NACK handling and dropped packet handling

This commit is contained in:
Thulinma 2020-06-01 23:15:27 +02:00
parent bd9ae56532
commit 70b0f94552
6 changed files with 273 additions and 120 deletions

View file

@ -12,6 +12,8 @@
namespace RTP{ namespace RTP{
double Packet::startRTCP = 0; double Packet::startRTCP = 0;
unsigned int MAX_SEND = 1500 - 28; unsigned int MAX_SEND = 1500 - 28;
unsigned int PACKET_REORDER_WAIT = 5;
unsigned int PACKET_DROP_TIMEOUT = 30;
unsigned int Packet::getHsize() const{ unsigned int Packet::getHsize() const{
unsigned int r = 12 + 4 * getContribCount(); unsigned int r = 12 + 4 * getContribCount();
@ -474,15 +476,16 @@ namespace RTP{
Sorter::Sorter(uint64_t trackId, void (*cb)(const uint64_t track, const Packet &p)){ Sorter::Sorter(uint64_t trackId, void (*cb)(const uint64_t track, const Packet &p)){
packTrack = trackId; packTrack = trackId;
rtpSeq = 0; rtpSeq = 0;
rtpWSeq = 0;
lostTotal = 0; lostTotal = 0;
lostCurrent = 0; lostCurrent = 0;
packTotal = 0; packTotal = 0;
packCurrent = 0; packCurrent = 0;
callback = cb; callback = cb;
} first = true;
preBuffer = true;
bool Sorter::wantSeq(uint16_t seq) const{ lastBootMS = 0;
return !rtpSeq || !(seq < rtpSeq || seq > (rtpSeq + 500) || packBuffer.count(seq)); lastNTP = 0;
} }
void Sorter::setCallback(uint64_t track, void (*cb)(const uint64_t track, const Packet &p)){ void Sorter::setCallback(uint64_t track, void (*cb)(const uint64_t track, const Packet &p)){
@ -497,57 +500,76 @@ namespace RTP{
/// Automatically sorts them, waiting when packets come in slow or not at all. /// Automatically sorts them, waiting when packets come in slow or not at all.
/// Calls the callback with packets in sorted order, whenever it becomes possible to do so. /// Calls the callback with packets in sorted order, whenever it becomes possible to do so.
void Sorter::addPacket(const Packet &pack){ void Sorter::addPacket(const Packet &pack){
if (!rtpSeq){rtpSeq = pack.getSequence();} uint16_t pSNo = pack.getSequence();
// packet is very early - assume dropped after 150 packets if (first){
while ((int16_t)(rtpSeq - ((uint16_t)pack.getSequence())) < -150){ rtpWSeq = pSNo;
rtpSeq = pSNo - 5;
first = false;
}
if (preBuffer){
//If we've buffered the first 5 packets, assume we have the first one known
if (packBuffer.size() >= 5){
preBuffer = false;
rtpSeq = packBuffer.begin()->first;
rtpWSeq = rtpSeq;
}
}else{
// packet is very early - assume dropped after PACKET_DROP_TIMEOUT packets
while ((int16_t)(rtpSeq - pSNo) < -(int)PACKET_DROP_TIMEOUT){
WARN_MSG("Giving up on packet %u", rtpSeq); WARN_MSG("Giving up on packet %u", rtpSeq);
++rtpSeq; ++rtpSeq;
++lostTotal; ++lostTotal;
++lostCurrent; ++lostCurrent;
++packTotal; ++packTotal;
++packCurrent; ++packCurrent;
}
}
//Update wanted counter if we passed it (1 of 2)
if ((int16_t)(rtpWSeq - rtpSeq) < 0){rtpWSeq = rtpSeq;}
// packet is somewhat early - ask for packet after PACKET_REORDER_WAIT packets
while ((int16_t)(rtpWSeq - pSNo) < -(int)PACKET_REORDER_WAIT){
//Only wanted if we don't already have it
if (!packBuffer.count(rtpWSeq)){
wantedSeqs.insert(rtpWSeq);
}
++rtpWSeq;
}
// send any buffered packets we may have // send any buffered packets we may have
uint16_t prertpSeq = rtpSeq;
while (packBuffer.count(rtpSeq)){ while (packBuffer.count(rtpSeq)){
outPacket(packTrack, packBuffer[rtpSeq]); outPacket(packTrack, packBuffer[rtpSeq]);
packBuffer.erase(rtpSeq); packBuffer.erase(rtpSeq);
INFO_MSG("Sent packet %u, now %zu in buffer", rtpSeq, packBuffer.size());
++rtpSeq; ++rtpSeq;
++packTotal; ++packTotal;
++packCurrent; ++packCurrent;
} }
} if (prertpSeq != rtpSeq){
// send any buffered packets we may have INFO_MSG("Sent packets %" PRIu16 "-%" PRIu16 ", now %zu in buffer", prertpSeq, rtpSeq, packBuffer.size());
while (packBuffer.count(rtpSeq)){
outPacket(packTrack, packBuffer[rtpSeq]);
packBuffer.erase(rtpSeq);
INFO_MSG("Sent packet %u, now %zu in buffer", rtpSeq, packBuffer.size());
++rtpSeq;
++packTotal;
++packCurrent;
} }
// packet is slightly early - buffer it // packet is slightly early - buffer it
if ((int16_t)(rtpSeq - (uint16_t)pack.getSequence()) < 0){ if ((int16_t)(rtpSeq - pSNo) < 0){
HIGH_MSG("Buffering early packet #%u->%u", rtpSeq, pack.getSequence()); HIGH_MSG("Buffering early packet #%u->%u", rtpSeq, pack.getSequence());
packBuffer[pack.getSequence()] = pack; packBuffer[pack.getSequence()] = pack;
} }
// packet is late // packet is late
if ((int16_t)(rtpSeq - (uint16_t)pack.getSequence()) > 0){ if ((int16_t)(rtpSeq - pSNo) > 0){
// negative difference? // negative difference?
--lostTotal; //--lostTotal;
--lostCurrent; //--lostCurrent;
++packTotal; //++packTotal;
++packCurrent; //++packCurrent;
WARN_MSG("Dropped a packet that arrived too late! (%d packets difference)", //WARN_MSG("Dropped a packet that arrived too late! (%d packets difference)", (int16_t)(rtpSeq - pSNo));
(int16_t)(rtpSeq - (uint16_t)pack.getSequence())); //return;
return;
} }
// packet is in order // packet is in order
if (rtpSeq == pack.getSequence()){ if (rtpSeq == pSNo){
outPacket(packTrack, pack); outPacket(packTrack, pack);
++rtpSeq; ++rtpSeq;
++packTotal; ++packTotal;
++packCurrent; ++packCurrent;
} }
//Update wanted counter if we passed it (2 of 2)
if ((int16_t)(rtpWSeq - rtpSeq) < 0){rtpWSeq = rtpSeq;}
} }
toDTSC::toDTSC(){ toDTSC::toDTSC(){

View file

@ -26,6 +26,8 @@ namespace SDP{
namespace RTP{ namespace RTP{
extern uint32_t MAX_SEND; extern uint32_t MAX_SEND;
extern unsigned int PACKET_REORDER_WAIT;
extern unsigned int PACKET_DROP_TIMEOUT;
/// This class is used to make RTP packets. Currently, H264, and AAC are supported. RTP /// This class is used to make RTP packets. Currently, H264, and AAC are supported. RTP
/// mechanisms, like increasing sequence numbers and setting timestamps are all taken care of in /// mechanisms, like increasing sequence numbers and setting timestamps are all taken care of in
@ -86,7 +88,6 @@ namespace RTP{
class Sorter{ class Sorter{
public: public:
Sorter(uint64_t trackId = 0, void (*callback)(const uint64_t track, const Packet &p) = 0); Sorter(uint64_t trackId = 0, void (*callback)(const uint64_t track, const Packet &p) = 0);
bool wantSeq(uint16_t seq) const;
void addPacket(const char *dat, unsigned int len); void addPacket(const char *dat, unsigned int len);
void addPacket(const Packet &pack); void addPacket(const Packet &pack);
// By default, calls the callback function, if set. // By default, calls the callback function, if set.
@ -95,9 +96,14 @@ namespace RTP{
} }
void setCallback(uint64_t track, void (*callback)(const uint64_t track, const Packet &p)); void setCallback(uint64_t track, void (*callback)(const uint64_t track, const Packet &p));
uint16_t rtpSeq; uint16_t rtpSeq;
uint16_t rtpWSeq;
bool first;
bool preBuffer;
int32_t lostTotal, lostCurrent; int32_t lostTotal, lostCurrent;
uint32_t packTotal, packCurrent; uint32_t packTotal, packCurrent;
std::set<uint16_t> wantedSeqs;
uint32_t lastNTP; ///< Middle 32 bits of last Sender Report NTP timestamp
uint64_t lastBootMS; ///< bootMS time of last Sender Report
private: private:
uint64_t packTrack; uint64_t packTrack;
std::map<uint16_t, Packet> packBuffer; std::map<uint16_t, Packet> packBuffer;

View file

@ -531,7 +531,7 @@ namespace RTP{
} }
void FECPacket::sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void *userData, void FECPacket::sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void *userData,
void callBack(void *userData, const char *payload, size_t nbytes, uint8_t channel)){ void callBack(void *userData, const char *payload, size_t nbytes, uint8_t channel), uint32_t jitter){
char *rtcpData = (char *)malloc(32); char *rtcpData = (char *)malloc(32);
if (!rtcpData){ if (!rtcpData){
FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up."); FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up.");
@ -547,9 +547,13 @@ namespace RTP{
Bit::htob24(rtcpData + 13, sorter.lostTotal); // cumulative packets lost since start Bit::htob24(rtcpData + 13, sorter.lostTotal); // cumulative packets lost since start
Bit::htobl(rtcpData + 16, Bit::htobl(rtcpData + 16,
sorter.rtpSeq | (sorter.packTotal & 0xFFFF0000ul)); // highest sequence received sorter.rtpSeq | (sorter.packTotal & 0xFFFF0000ul)); // highest sequence received
Bit::htobl(rtcpData + 20, 0); /// \TODO jitter (diff in timestamp vs packet arrival) Bit::htobl(rtcpData + 20, jitter); // jitter
Bit::htobl(rtcpData + 24, 0); /// \TODO last SR (middle 32 bits of last SR or zero) Bit::htobl(rtcpData + 24, sorter.lastNTP); // last SR NTP time (middle 32 bits)
Bit::htobl(rtcpData + 28, 0); /// \TODO delay since last SR in 2b seconds + 2b fraction if (sorter.lastBootMS){
Bit::htobl(rtcpData + 28, (Util::bootMS() - sorter.lastBootMS) * 65.536); // delay since last SR in 1/65536th of a second
}else{
Bit::htobl(rtcpData + 28, 0); // no delay since last SR yet
}
callBack(userData, rtcpData, 32, 0); callBack(userData, rtcpData, 32, 0);
sorter.lostCurrent = 0; sorter.lostCurrent = 0;
sorter.packCurrent = 0; sorter.packCurrent = 0;

View file

@ -86,7 +86,7 @@ namespace RTP{
class FECPacket : public Packet{ class FECPacket : public Packet{
public: public:
void sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void *userData, void sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void *userData,
void callBack(void *userData, const char *payload, size_t nbytes, uint8_t channel)); void callBack(void *userData, const char *payload, size_t nbytes, uint8_t channel), uint32_t jitter = 0);
}; };
}// namespace RTP }// namespace RTP

View file

@ -25,13 +25,32 @@ namespace Mist{
/* ------------------------------------------------ */ /* ------------------------------------------------ */
WebRTCTrack::WebRTCTrack() WebRTCTrack::WebRTCTrack(){
: payloadType(0), SSRC(0), ULPFECPayloadType(0), REDPayloadType(0), RTXPayloadType(0), payloadType = 0;
prevReceivedSequenceNumber(0){} SSRC = 0;
ULPFECPayloadType = 0;
REDPayloadType = 0;
RTXPayloadType = 0;
lastTransit = 0;
jitter = 0;
}
void WebRTCTrack::gotPacket(uint32_t ts){
uint32_t arrival = Util::bootMS() * rtpToDTSC.multiplier;
int transit = arrival - ts;
int d = transit - lastTransit;
lastTransit = transit;
if (d < 0) d = -d;
jitter += (1. / 16.) * ((double)d - jitter);
}
/* ------------------------------------------------ */ /* ------------------------------------------------ */
OutWebRTC::OutWebRTC(Socket::Connection &myConn) : HTTPOutput(myConn){ OutWebRTC::OutWebRTC(Socket::Connection &myConn) : HTTPOutput(myConn){
stats_jitter = 0;
stats_nacknum = 0;
stats_lossnum = 0;
stats_lossperc = 0;
lastPackMs = 0; lastPackMs = 0;
vidTrack = INVALID_TRACK_ID; vidTrack = INVALID_TRACK_ID;
prevVidTrack = INVALID_TRACK_ID; prevVidTrack = INVALID_TRACK_ID;
@ -52,6 +71,7 @@ namespace Mist{
rtcpKeyFrameDelayInMillis = 2000; rtcpKeyFrameDelayInMillis = 2000;
rtcpKeyFrameTimeoutInMillis = 0; rtcpKeyFrameTimeoutInMillis = 0;
videoBitrate = 6 * 1000 * 1000; videoBitrate = 6 * 1000 * 1000;
videoConstraint = videoBitrate;
RTP::MAX_SEND = 1350 - 28; RTP::MAX_SEND = 1350 - 28;
didReceiveKeyFrame = false; didReceiveKeyFrame = false;
doDTLS = true; doDTLS = true;
@ -114,7 +134,7 @@ namespace Mist{
"Comma separated list of video codecs you want to support in preferred order. e.g. " "Comma separated list of video codecs you want to support in preferred order. e.g. "
"H264,VP8"; "H264,VP8";
capa["optional"]["preferredvideocodec"]["default"] = "H264,VP9,VP8"; capa["optional"]["preferredvideocodec"]["default"] = "H264,VP9,VP8";
capa["optional"]["preferredvideocodec"]["type"] = "string"; capa["optional"]["preferredvideocodec"]["type"] = "str";
capa["optional"]["preferredvideocodec"]["option"] = "--webrtc-video-codecs"; capa["optional"]["preferredvideocodec"]["option"] = "--webrtc-video-codecs";
capa["optional"]["preferredvideocodec"]["short"] = "V"; capa["optional"]["preferredvideocodec"]["short"] = "V";
@ -123,7 +143,7 @@ namespace Mist{
"Comma separated list of audio codecs you want to support in preferred order. e.g. " "Comma separated list of audio codecs you want to support in preferred order. e.g. "
"opus,ALAW,ULAW"; "opus,ALAW,ULAW";
capa["optional"]["preferredaudiocodec"]["default"] = "opus,ALAW,ULAW"; capa["optional"]["preferredaudiocodec"]["default"] = "opus,ALAW,ULAW";
capa["optional"]["preferredaudiocodec"]["type"] = "string"; capa["optional"]["preferredaudiocodec"]["type"] = "str";
capa["optional"]["preferredaudiocodec"]["option"] = "--webrtc-audio-codecs"; capa["optional"]["preferredaudiocodec"]["option"] = "--webrtc-audio-codecs";
capa["optional"]["preferredaudiocodec"]["short"] = "A"; capa["optional"]["preferredaudiocodec"]["short"] = "A";
@ -131,7 +151,7 @@ namespace Mist{
capa["optional"]["bindhost"]["help"] = "Interface address or hostname to bind SRTP UDP socket " capa["optional"]["bindhost"]["help"] = "Interface address or hostname to bind SRTP UDP socket "
"to. Defaults to originating interface address."; "to. Defaults to originating interface address.";
capa["optional"]["bindhost"]["default"] = ""; capa["optional"]["bindhost"]["default"] = "";
capa["optional"]["bindhost"]["type"] = "string"; capa["optional"]["bindhost"]["type"] = "str";
capa["optional"]["bindhost"]["option"] = "--bindhost"; capa["optional"]["bindhost"]["option"] = "--bindhost";
capa["optional"]["bindhost"]["short"] = "B"; capa["optional"]["bindhost"]["short"] = "B";
@ -161,12 +181,49 @@ namespace Mist{
capa["optional"]["packetlog"]["short"] = "P"; capa["optional"]["packetlog"]["short"] = "P";
capa["optional"]["packetlog"]["default"] = 0; capa["optional"]["packetlog"]["default"] = 0;
capa["optional"]["nacktimeout"]["name"] = "RTP NACK timeout";
capa["optional"]["nacktimeout"]["help"] = "Amount of packets any track will wait for a packet to arrive before NACKing it";
capa["optional"]["nacktimeout"]["option"] = "--nacktimeout";
capa["optional"]["nacktimeout"]["short"] = "x";
capa["optional"]["nacktimeout"]["type"] = "uint";
capa["optional"]["nacktimeout"]["default"] = 5;
capa["optional"]["losttimeout"]["name"] = "RTP lost timeout";
capa["optional"]["losttimeout"]["help"] = "Amount of packets any track will wait for a packet to arrive before considering it lost";
capa["optional"]["losttimeout"]["option"] = "--losttimeout";
capa["optional"]["losttimeout"]["short"] = "l";
capa["optional"]["losttimeout"]["type"] = "uint";
capa["optional"]["losttimeout"]["default"] = 30;
capa["optional"]["nacktimeoutmobile"]["name"] = "RTP NACK timeout (mobile)";
capa["optional"]["nacktimeoutmobile"]["help"] = "Amount of packets any track will wait for a packet to arrive before NACKing it, on mobile connections";
capa["optional"]["nacktimeoutmobile"]["option"] = "--nacktimeoutmobile";
capa["optional"]["nacktimeoutmobile"]["short"] = "X";
capa["optional"]["nacktimeoutmobile"]["type"] = "uint";
capa["optional"]["nacktimeoutmobile"]["default"] = 15;
capa["optional"]["losttimeoutmobile"]["name"] = "RTP lost timeout (mobile)";
capa["optional"]["losttimeoutmobile"]["help"] = "Amount of packets any track will wait for a packet to arrive before considering it lost, on mobile connections";
capa["optional"]["losttimeoutmobile"]["option"] = "--losttimeoutmobile";
capa["optional"]["losttimeoutmobile"]["short"] = "L";
capa["optional"]["losttimeoutmobile"]["type"] = "uint";
capa["optional"]["losttimeoutmobile"]["default"] = 90;
config->addOptionsFromCapabilities(capa); config->addOptionsFromCapabilities(capa);
} }
void OutWebRTC::preWebsocketConnect(){ void OutWebRTC::preWebsocketConnect(){
HTTP::URL tmpUrl("http://" + H.GetHeader("Host")); HTTP::URL tmpUrl("http://" + H.GetHeader("Host"));
externalAddr = tmpUrl.host; externalAddr = tmpUrl.host;
if (UA.find("Mobi") != std::string::npos){
RTP::PACKET_REORDER_WAIT = config->getInteger("nacktimeoutmobile");
RTP::PACKET_DROP_TIMEOUT = config->getInteger("losttimeoutmobile");
INFO_MSG("Using mobile RTP configuration: NACK at %u, drop at %u", RTP::PACKET_REORDER_WAIT, RTP::PACKET_DROP_TIMEOUT);
}else{
RTP::PACKET_REORDER_WAIT = config->getInteger("nacktimeout");
RTP::PACKET_DROP_TIMEOUT = config->getInteger("losttimeout");
INFO_MSG("Using regular RTP configuration: NACK at %u, drop at %u", RTP::PACKET_REORDER_WAIT, RTP::PACKET_DROP_TIMEOUT);
}
} }
// This function is executed when we receive a signaling data. // This function is executed when we receive a signaling data.
@ -259,10 +316,29 @@ namespace Mist{
"Failed to handle the video bitrate change request."); "Failed to handle the video bitrate change request.");
return; return;
} }
videoConstraint = videoBitrate;
if (videoConstraint < 1024){videoConstraint = 1024;}
JSON::Value commandResult; JSON::Value commandResult;
commandResult["type"] = "on_video_bitrate"; commandResult["type"] = "on_video_bitrate";
commandResult["result"] = true; commandResult["result"] = true;
commandResult["video_bitrate"] = videoBitrate; commandResult["video_bitrate"] = videoBitrate;
commandResult["video_bitrate_constraint"] = videoConstraint;
webSock->sendFrame(commandResult.toString());
return;
}
if (command["type"] == "rtp_props"){
if (command.isMember("nack")){
RTP::PACKET_REORDER_WAIT = command["nack"].asInt();
}
if (command.isMember("drop")){
RTP::PACKET_DROP_TIMEOUT = command["drop"].asInt();
}
JSON::Value commandResult;
commandResult["type"] = "on_rtp_props";
commandResult["result"] = true;
commandResult["nack"] = RTP::PACKET_REORDER_WAIT;
commandResult["drop"] = RTP::PACKET_DROP_TIMEOUT;
webSock->sendFrame(commandResult.toString()); webSock->sendFrame(commandResult.toString());
return; return;
} }
@ -544,6 +620,18 @@ namespace Mist{
commandResult["tracks"].append(it->first); commandResult["tracks"].append(it->first);
} }
webSock->sendFrame(commandResult.toString()); webSock->sendFrame(commandResult.toString());
}else if (isPushing()){
JSON::Value commandResult;
commandResult["type"] = "on_media_receive";
commandResult["millis"] = endTime();
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
commandResult["tracks"].append(M.getCodec(it->first));
}
commandResult["stats"]["nack_num"] = stats_nacknum;
commandResult["stats"]["loss_num"] = stats_lossnum;
commandResult["stats"]["jitter_ms"] = stats_jitter;
commandResult["stats"]["loss_perc"] = stats_lossperc;
webSock->sendFrame(commandResult.toString());
} }
} }
@ -744,6 +832,8 @@ namespace Mist{
rtcpTimeoutInMillis = Util::bootMS() + 2000; rtcpTimeoutInMillis = Util::bootMS() + 2000;
rtcpKeyFrameTimeoutInMillis = Util::bootMS() + 2000; rtcpKeyFrameTimeoutInMillis = Util::bootMS() + 2000;
idleInterval = 1000;
return true; return true;
} }
@ -980,12 +1070,6 @@ namespace Mist{
// Find the WebRTCTrack corresponding to the packet we received // Find the WebRTCTrack corresponding to the packet we received
WebRTCTrack &rtcTrack = webrtcTracks[idx]; WebRTCTrack &rtcTrack = webrtcTracks[idx];
// Do not parse packets we don't care about
if (!rtcTrack.sorter.wantSeq(currSeqNum)){
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Sequence #" << currSeqNum << " not interesting, ignored" << std::endl;}
return;
}
// Decrypt the SRTP to RTP // Decrypt the SRTP to RTP
int len = (int)udp.data_len; int len = (int)udp.data_len;
if (srtpReader.unprotectRtp((uint8_t *)udp.data, &len) != 0){ if (srtpReader.unprotectRtp((uint8_t *)udp.data, &len) != 0){
@ -996,18 +1080,7 @@ namespace Mist{
RTP::Packet unprotPack(udp.data, len); RTP::Packet unprotPack(udp.data, len);
DONTEVEN_MSG("%s", unprotPack.toString().c_str()); DONTEVEN_MSG("%s", unprotPack.toString().c_str());
// Here follows a very rudimentary algo for requesting lost rtcTrack.gotPacket(unprotPack.getTimeStamp());
// packets; I guess after some experimentation a better
// algorithm should be used; this is used to trigger NACKs.
if (rtcTrack.prevReceivedSequenceNumber != 0 && (rtcTrack.prevReceivedSequenceNumber + 1) != currSeqNum){
while (rtcTrack.prevReceivedSequenceNumber < currSeqNum){
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Sending NACK for sequence #" << rtcTrack.prevReceivedSequenceNumber << std::endl;}
sendRTCPFeedbackNACK(rtcTrack, rtcTrack.prevReceivedSequenceNumber);
rtcTrack.prevReceivedSequenceNumber++;
}
}
rtcTrack.prevReceivedSequenceNumber = currSeqNum;
if (rtp_pkt.getPayloadType() == rtcTrack.REDPayloadType || rtp_pkt.getPayloadType() == rtcTrack.ULPFECPayloadType){ if (rtp_pkt.getPayloadType() == rtcTrack.REDPayloadType || rtp_pkt.getPayloadType() == rtcTrack.ULPFECPayloadType){
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "RED packet " << rtp_pkt.getPayloadType() << " #" << currSeqNum << std::endl;} if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "RED packet " << rtp_pkt.getPayloadType() << " #" << currSeqNum << std::endl;}
@ -1017,9 +1090,18 @@ namespace Mist{
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Basic packet " << rtp_pkt.getPayloadType() << " #" << currSeqNum << std::endl;} if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Basic packet " << rtp_pkt.getPayloadType() << " #" << currSeqNum << std::endl;}
rtcTrack.sorter.addPacket(unprotPack); rtcTrack.sorter.addPacket(unprotPack);
} }
}else if ((pt >= 64) && (pt < 96)){
if (pt == 77 || pt == 78 || pt == 65){ //Send NACKs for packets that we still need
while (rtcTrack.sorter.wantedSeqs.size()){
uint16_t sNum = *(rtcTrack.sorter.wantedSeqs.begin());
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Sending NACK for sequence #" << sNum << std::endl;}
stats_nacknum++;
sendRTCPFeedbackNACK(rtcTrack, sNum);
rtcTrack.sorter.wantedSeqs.erase(sNum);
}
}else{
//Decrypt feedback packet
int len = udp.data_len; int len = udp.data_len;
if (srtpReader.unprotectRtcp((uint8_t *)udp.data, &len) != 0){ if (srtpReader.unprotectRtcp((uint8_t *)udp.data, &len) != 0){
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "RTCP decrypt failure" << std::endl;} if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "RTCP decrypt failure" << std::endl;}
@ -1028,6 +1110,7 @@ namespace Mist{
} }
uint8_t fmt = udp.data[0] & 0x1F; uint8_t fmt = udp.data[0] & 0x1F;
if (pt == 77 || pt == 65){ if (pt == 77 || pt == 65){
//77/65 = nack
if (fmt == 1){ if (fmt == 1){
uint32_t pSSRC = Bit::btohl(udp.data + 8); uint32_t pSSRC = Bit::btohl(udp.data + 8);
uint16_t seq = Bit::btohs(udp.data + 12); uint16_t seq = Bit::btohs(udp.data + 12);
@ -1055,8 +1138,8 @@ namespace Mist{
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Feedback: Unimplemented (type " << fmt << ")" << std::endl;} if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Feedback: Unimplemented (type " << fmt << ")" << std::endl;}
INFO_MSG("Received unimplemented RTP feedback message (%d)", fmt); INFO_MSG("Received unimplemented RTP feedback message (%d)", fmt);
} }
} }else if (pt == 78){
if (pt == 78){ //78 = PLI
if (fmt == 1){ if (fmt == 1){
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "PLI: Picture Loss Indication ( = keyframe request = ignored)" << std::endl;} if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "PLI: Picture Loss Indication ( = keyframe request = ignored)" << std::endl;}
DONTEVEN_MSG("Received picture loss indication"); DONTEVEN_MSG("Received picture loss indication");
@ -1064,12 +1147,27 @@ namespace Mist{
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Feedback: Unimplemented (payload specific type " << fmt << ")" << std::endl;} if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Feedback: Unimplemented (payload specific type " << fmt << ")" << std::endl;}
INFO_MSG("Received unimplemented payload-specific feedback message (%d)", fmt); INFO_MSG("Received unimplemented payload-specific feedback message (%d)", fmt);
} }
}else if (pt == 72){
//72 = sender report
uint32_t SSRC = Bit::btohl(udp.data + 4);
std::map<uint64_t, WebRTCTrack>::iterator it;
for (it = webrtcTracks.begin(); it != webrtcTracks.end(); ++it){
if (it->second.SSRC == SSRC){
it->second.sorter.lastBootMS = Util::bootMS();
it->second.sorter.lastNTP = Bit::btohl(udp.data+10);;
uint32_t packets = Bit::btohl(udp.data + 20);
uint32_t bytes = Bit::btohl(udp.data + 24);
HIGH_MSG("Received sender report for track %s (%" PRIu32 " pkts, %" PRIu32 "b)", it->second.rtpToDTSC.codec.c_str(), packets, bytes);
break;
} }
} }
}else if (pt == 73){
//73 = receiver report
// \TODO Implement, maybe?
}else{ }else{
if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Unknown payload type: " << pt << std::endl;} if (packetLog.is_open()){packetLog << "[" << Util::bootMS() << "]" << "Unknown payload type: " << pt << std::endl;}
FAIL_MSG("Unknown payload type: %u", pt); WARN_MSG("Unknown RTP feedback payload type: %u", pt);
}
} }
} }
@ -1386,15 +1484,9 @@ namespace Mist{
} }
void OutWebRTC::sendRTCPFeedbackREMB(const WebRTCTrack &rtcTrack){ void OutWebRTC::sendRTCPFeedbackREMB(const WebRTCTrack &rtcTrack){
if (videoBitrate == 0){
FAIL_MSG("videoBitrate is 0, which is invalid. Resetting to our default value.");
videoBitrate = 6 * 1000 * 1000;
}
// create the `BR Exp` and `BR Mantissa parts. // create the `BR Exp` and `BR Mantissa parts.
uint32_t br_exponent = 0; uint32_t br_exponent = 0;
uint32_t br_mantissa = videoBitrate; uint32_t br_mantissa = videoConstraint;
while (br_mantissa > 0x3FFFF){ while (br_mantissa > 0x3FFFF){
br_mantissa >>= 1; br_mantissa >>= 1;
++br_exponent; ++br_exponent;
@ -1501,7 +1593,7 @@ namespace Mist{
// sequence numbers are lost it makes sense to implement this // sequence numbers are lost it makes sense to implement this
// too. // too.
void OutWebRTC::sendRTCPFeedbackNACK(const WebRTCTrack &rtcTrack, uint16_t lostSequenceNumber){ void OutWebRTC::sendRTCPFeedbackNACK(const WebRTCTrack &rtcTrack, uint16_t lostSequenceNumber){
HIGH_MSG("Requesting missing sequence number %u", lostSequenceNumber); INFO_MSG("Requesting missing sequence number %u", lostSequenceNumber);
std::vector<uint8_t> buffer; std::vector<uint8_t> buffer;
buffer.push_back(0x80 | 0x01); // V=2 (0x80) | FMT=1 (0x01) buffer.push_back(0x80 | 0x01); // V=2 (0x80) | FMT=1 (0x01)
@ -1547,8 +1639,30 @@ namespace Mist{
} }
void OutWebRTC::sendRTCPFeedbackRR(WebRTCTrack &rtcTrack){ void OutWebRTC::sendRTCPFeedbackRR(WebRTCTrack &rtcTrack){
stats_lossperc = (double)(rtcTrack.sorter.lostCurrent * 100.) / (double)(rtcTrack.sorter.lostCurrent + rtcTrack.sorter.packCurrent);
stats_jitter = rtcTrack.jitter/rtcTrack.rtpToDTSC.multiplier;
stats_lossnum = rtcTrack.sorter.lostTotal;
//If we have > 5% loss, constrain video by 10%
if (stats_lossperc > 5){
videoConstraint *= 0.9;
if (videoConstraint < 1024){videoConstraint = 1024;}
JSON::Value commandResult;
commandResult["type"] = "on_video_bitrate";
commandResult["result"] = true;
commandResult["video_bitrate"] = videoBitrate;
commandResult["video_bitrate_constraint"] = videoConstraint;
webSock->sendFrame(commandResult.toString());
}
if (stats_lossperc > 1 || stats_jitter > 20){
INFO_MSG("Receiver Report (%s): %.2f%% loss, %" PRIu32 " total lost, %.2f ms jitter", rtcTrack.rtpToDTSC.codec.c_str(), stats_lossperc, rtcTrack.sorter.lostTotal, stats_jitter);
}else{
HIGH_MSG("Receiver Report (%s): %.2f%% loss, %" PRIu32 " total lost, %.2f ms jitter", rtcTrack.rtpToDTSC.codec.c_str(), stats_lossperc, rtcTrack.sorter.lostTotal, stats_jitter);
}
((RTP::FECPacket *)&(rtcTrack.rtpPacketizer))->sendRTCP_RR(rtcTrack.sorter, SSRC, rtcTrack.SSRC, (void *)&udp, onRTPPacketizerHasRTCPDataCallback); if (packetLog.is_open()){
packetLog << "[" << Util::bootMS() << "] Receiver Report (" << rtcTrack.rtpToDTSC.codec << "): " << stats_lossperc << " percent loss, " << rtcTrack.sorter.lostTotal << " total lost, " << stats_jitter << " ms jitter" << std::endl;
}
((RTP::FECPacket *)&(rtcTrack.rtpPacketizer))->sendRTCP_RR(rtcTrack.sorter, SSRC, rtcTrack.SSRC, (void *)&udp, onRTPPacketizerHasRTCPDataCallback, (uint32_t)rtcTrack.jitter);
} }
void OutWebRTC::sendSPSPPS(size_t dtscIdx, WebRTCTrack &rtcTrack){ void OutWebRTC::sendSPSPPS(size_t dtscIdx, WebRTCTrack &rtcTrack){

View file

@ -115,8 +115,9 @@ namespace Mist{
///< stream. ///< stream.
uint8_t RTXPayloadType; ///< The retransmission payload type when we use RTX (retransmission uint8_t RTXPayloadType; ///< The retransmission payload type when we use RTX (retransmission
///< with separate SSRC/payload type) ///< with separate SSRC/payload type)
uint16_t prevReceivedSequenceNumber; ///< The previously received sequence number. This is used void gotPacket(uint32_t ts);
///< to NACK packets when we loose one. uint32_t lastTransit;
double jitter;
}; };
/* ------------------------------------------------ */ /* ------------------------------------------------ */
@ -201,6 +202,7 @@ namespace Mist{
///< to the other peer. This gets protected. ///< to the other peer. This gets protected.
uint32_t videoBitrate; ///< The bitrate to use for incoming video streams. Can be configured via uint32_t videoBitrate; ///< The bitrate to use for incoming video streams. Can be configured via
///< the signaling channel. Defaults to 6mbit. ///< the signaling channel. Defaults to 6mbit.
uint32_t videoConstraint;
size_t audTrack, vidTrack, prevVidTrack; size_t audTrack, vidTrack, prevVidTrack;
double target_rate; ///< Target playback speed rate (1.0 = normal, 0 = auto) double target_rate; ///< Target playback speed rate (1.0 = normal, 0 = auto)
@ -214,6 +216,11 @@ namespace Mist{
bool doDTLS; bool doDTLS;
bool volkswagenMode; bool volkswagenMode;
double stats_jitter;
uint64_t stats_nacknum;
uint64_t stats_lossnum;
double stats_lossperc;
#if defined(WEBRTC_PCAP) #if defined(WEBRTC_PCAP)
PCAPWriter pcapOut; ///< Used during development to write unprotected packets that can be PCAPWriter pcapOut; ///< Used during development to write unprotected packets that can be
///< inspected in e.g. wireshark. ///< inspected in e.g. wireshark.