From 5249cc8f77532f3af4f25ad4c5f68dfc4ae8955f Mon Sep 17 00:00:00 2001 From: "asapersson@webrtc.org" Date: Fri, 16 Dec 2011 14:31:37 +0000 Subject: [PATCH] Review URL: http://webrtc-codereview.appspot.com/295010 git-svn-id: http://webrtc.googlecode.com/svn/trunk@1219 4adac7df-926f-26a2-2b94-8c16560cd09d --- src/modules/interface/module_common_types.h | 6 + src/modules/rtp_rtcp/interface/rtp_rtcp.h | 38 +++- .../rtp_rtcp/interface/rtp_rtcp_defines.h | 9 +- src/modules/rtp_rtcp/source/rtcp_receiver.cc | 27 +++ src/modules/rtp_rtcp/source/rtcp_receiver.h | 6 + .../rtp_rtcp/source/rtcp_receiver_help.cc | 1 + .../rtp_rtcp/source/rtcp_receiver_help.h | 2 + src/modules/rtp_rtcp/source/rtcp_sender.cc | 93 ++++++++- src/modules/rtp_rtcp/source/rtcp_sender.h | 13 ++ .../rtp_rtcp/source/rtcp_sender_test.cc | 178 ++++++++++++++++++ src/modules/rtp_rtcp/source/rtcp_utility.cc | 76 ++++++++ src/modules/rtp_rtcp/source/rtcp_utility.h | 18 ++ .../rtp_rtcp/source/rtp_header_extension.cc | 148 +++++++++++++++ .../rtp_rtcp/source/rtp_header_extension.h | 69 +++++++ .../source/rtp_header_extension_test.cc | 108 +++++++++++ src/modules/rtp_rtcp/source/rtp_receiver.cc | 72 +++++++ src/modules/rtp_rtcp/source/rtp_receiver.h | 23 ++- src/modules/rtp_rtcp/source/rtp_rtcp.gypi | 2 + src/modules/rtp_rtcp/source/rtp_rtcp_impl.cc | 71 ++++++- src/modules/rtp_rtcp/source/rtp_rtcp_impl.h | 34 +++- .../rtp_rtcp/source/rtp_rtcp_tests.gypi | 3 + src/modules/rtp_rtcp/source/rtp_sender.cc | 142 ++++++++++++++ src/modules/rtp_rtcp/source/rtp_sender.h | 33 +++- .../rtp_rtcp/source/rtp_sender_test.cc | 124 ++++++++++++ src/modules/rtp_rtcp/source/rtp_utility.cc | 146 +++++++++++--- src/modules/rtp_rtcp/source/rtp_utility.h | 16 +- 26 files changed, 1408 insertions(+), 50 deletions(-) create mode 100644 src/modules/rtp_rtcp/source/rtcp_sender_test.cc create mode 100644 src/modules/rtp_rtcp/source/rtp_header_extension.cc create mode 100644 src/modules/rtp_rtcp/source/rtp_header_extension.h create mode 100644 src/modules/rtp_rtcp/source/rtp_header_extension_test.cc create mode 100644 src/modules/rtp_rtcp/source/rtp_sender_test.cc diff --git a/src/modules/interface/module_common_types.h b/src/modules/interface/module_common_types.h index 253b8a065f..0319dfe2fa 100644 --- a/src/modules/interface/module_common_types.h +++ b/src/modules/interface/module_common_types.h @@ -27,6 +27,11 @@ struct RTPHeader WebRtc_UWord16 headerLength; }; +struct RTPHeaderExtension +{ + WebRtc_Word32 transmissionTimeOffset; +}; + struct RTPAudioHeader { WebRtc_UWord8 numEnergy; // number of valid entries in arrOfEnergy @@ -118,6 +123,7 @@ struct WebRtcRTPHeader RTPHeader header; FrameType frameType; RTPTypeHeader type; + RTPHeaderExtension extension; }; class RTPFragmentationHeader diff --git a/src/modules/rtp_rtcp/interface/rtp_rtcp.h b/src/modules/rtp_rtcp/interface/rtp_rtcp.h index 8a1fb1587a..984bcd78b3 100644 --- a/src/modules/rtp_rtcp/interface/rtp_rtcp.h +++ b/src/modules/rtp_rtcp/interface/rtp_rtcp.h @@ -207,7 +207,7 @@ public: WebRtc_Word8* plType) = 0; /* - * Remove a registerd payload type from list of accepted payloads + * Remove a registered payload type from list of accepted payloads * * payloadType - payload type of codec * @@ -216,6 +216,18 @@ public: virtual WebRtc_Word32 DeRegisterReceivePayload( const WebRtc_Word8 payloadType) = 0; + /* + * (De)register RTP header extension type and id. + * + * return -1 on failure else 0 + */ + virtual WebRtc_Word32 RegisterReceiveRtpHeaderExtension( + const RTPExtensionType type, + const WebRtc_UWord8 id) = 0; + + virtual WebRtc_Word32 DeregisterReceiveRtpHeaderExtension( + const RTPExtensionType type) = 0; + /* * Get last received remote timestamp */ @@ -407,6 +419,18 @@ public: */ virtual WebRtc_Word32 DeRegisterSendPayload(const WebRtc_Word8 payloadType) = 0; + /* + * (De)register RTP header extension type and id. + * + * return -1 on failure else 0 + */ + virtual WebRtc_Word32 RegisterSendRtpHeaderExtension( + const RTPExtensionType type, + const WebRtc_UWord8 id) = 0; + + virtual WebRtc_Word32 DeregisterSendRtpHeaderExtension( + const RTPExtensionType type) = 0; + /* * get start timestamp */ @@ -735,17 +759,25 @@ public: /* * (REMB) Receiver Estimated Max Bitrate */ - virtual bool REMB() const = 0;; + virtual bool REMB() const = 0; virtual WebRtc_Word32 SetREMBStatus(const bool enable) = 0; virtual WebRtc_Word32 SetREMBData(const WebRtc_UWord32 bitrate, const WebRtc_UWord8 numberOfSSRC, const WebRtc_UWord32* SSRC) = 0; + + /* + * (IJ) Extended jitter report. + */ + virtual bool IJ() const = 0; + + virtual WebRtc_Word32 SetIJStatus(const bool enable) = 0; + /* * (TMMBR) Temporary Max Media Bit Rate */ - virtual bool TMMBR() const = 0; + virtual bool TMMBR() const = 0; /* * diff --git a/src/modules/rtp_rtcp/interface/rtp_rtcp_defines.h b/src/modules/rtp_rtcp/interface/rtp_rtcp_defines.h index abc50d963c..f376d56842 100644 --- a/src/modules/rtp_rtcp/interface/rtp_rtcp_defines.h +++ b/src/modules/rtp_rtcp/interface/rtp_rtcp_defines.h @@ -39,6 +39,12 @@ enum RTPAliveType kRtpAlive = 2 }; +enum RTPExtensionType +{ + NONE, + TRANSMISSION_TIME_OFFSET +}; + enum RTCPAppSubTypes { kAppSubtypeBwe = 0x00 @@ -60,7 +66,8 @@ enum RTCPPacketType kRtcpApp = 0x0800, kRtcpSli = 0x4000, kRtcpRpsi = 0x8000, - kRtcpRemb = 0x10000 + kRtcpRemb = 0x10000, + kRtcpTransmissionTimeOffset = 0x20000 }; enum KeyFrameRequestMethod diff --git a/src/modules/rtp_rtcp/source/rtcp_receiver.cc b/src/modules/rtp_rtcp/source/rtcp_receiver.cc index 9b76e468bb..9ddcee49d5 100644 --- a/src/modules/rtp_rtcp/source/rtcp_receiver.cc +++ b/src/modules/rtp_rtcp/source/rtcp_receiver.cc @@ -356,6 +356,9 @@ RTCPReceiver::IncomingRTCPPacket(RTCPPacketInformation& rtcpPacketInformation, case RTCPUtility::kRtcpPsfbRpsiCode: HandleRPSI(*rtcpParser, rtcpPacketInformation); break; + case RTCPUtility::kRtcpExtendedIjCode: + HandleIJ(*rtcpParser, rtcpPacketInformation); + break; case RTCPUtility::kRtcpPsfbFirCode: HandleFIR(*rtcpParser, rtcpPacketInformation); break; @@ -1151,6 +1154,30 @@ RTCPReceiver::HandlePsfbApp(RTCPUtility::RTCPParserV2& rtcpParser, } } +// no need for critsect we have _criticalSectionRTCPReceiver +void +RTCPReceiver::HandleIJ(RTCPUtility::RTCPParserV2& rtcpParser, + RTCPPacketInformation& rtcpPacketInformation) +{ + const RTCPUtility::RTCPPacket& rtcpPacket = rtcpParser.Packet(); + + RTCPUtility::RTCPPacketTypes pktType = rtcpParser.Iterate(); + while (pktType == RTCPUtility::kRtcpExtendedIjItemCode) + { + HandleIJItem(rtcpPacket, rtcpPacketInformation); + pktType = rtcpParser.Iterate(); + } +} + +void +RTCPReceiver::HandleIJItem(const RTCPUtility::RTCPPacket& rtcpPacket, + RTCPPacketInformation& rtcpPacketInformation) +{ + rtcpPacketInformation.rtcpPacketTypeFlags |= kRtcpTransmissionTimeOffset; + rtcpPacketInformation.interArrivalJitter = + rtcpPacket.ExtendedJitterReportItem.Jitter; +} + void RTCPReceiver::HandleREMBItem(RTCPUtility::RTCPParserV2& rtcpParser, RTCPPacketInformation& rtcpPacketInformation) diff --git a/src/modules/rtp_rtcp/source/rtcp_receiver.h b/src/modules/rtp_rtcp/source/rtcp_receiver.h index 5d8f14334f..8b0d6d5f72 100644 --- a/src/modules/rtp_rtcp/source/rtcp_receiver.h +++ b/src/modules/rtp_rtcp/source/rtcp_receiver.h @@ -151,6 +151,12 @@ protected: void HandleREMBItem(RTCPUtility::RTCPParserV2& rtcpParser, RTCPHelp::RTCPPacketInformation& rtcpPacketInformation); + void HandleIJ(RTCPUtility::RTCPParserV2& rtcpParser, + RTCPHelp::RTCPPacketInformation& rtcpPacketInformation); + + void HandleIJItem(const RTCPUtility::RTCPPacket& rtcpPacket, + RTCPHelp::RTCPPacketInformation& rtcpPacketInformation); + void HandleTMMBR(RTCPUtility::RTCPParserV2& rtcpParser, RTCPHelp::RTCPPacketInformation& rtcpPacketInformation); diff --git a/src/modules/rtp_rtcp/source/rtcp_receiver_help.cc b/src/modules/rtp_rtcp/source/rtcp_receiver_help.cc index d9c5a73ab2..5b224e60b5 100644 --- a/src/modules/rtp_rtcp/source/rtcp_receiver_help.cc +++ b/src/modules/rtp_rtcp/source/rtcp_receiver_help.cc @@ -30,6 +30,7 @@ RTCPPacketInformation::RTCPPacketInformation() : roundTripTime(0), lastReceivedExtendedHighSeqNum(0), jitter(0), + interArrivalJitter(0), sliPictureId(0), rpsiPictureId(0), VoIPMetric(NULL) diff --git a/src/modules/rtp_rtcp/source/rtcp_receiver_help.h b/src/modules/rtp_rtcp/source/rtcp_receiver_help.h index d83f110970..6dcaca8323 100644 --- a/src/modules/rtp_rtcp/source/rtcp_receiver_help.h +++ b/src/modules/rtp_rtcp/source/rtcp_receiver_help.h @@ -57,6 +57,8 @@ public: WebRtc_UWord32 lastReceivedExtendedHighSeqNum; WebRtc_UWord32 jitter; + WebRtc_UWord32 interArrivalJitter; + WebRtc_UWord8 sliPictureId; WebRtc_UWord64 rpsiPictureId; WebRtc_UWord32 receiverEstimatedMaxBitrate; diff --git a/src/modules/rtp_rtcp/source/rtcp_sender.cc b/src/modules/rtp_rtcp/source/rtcp_sender.cc index 2ae88fc7e6..2ab8ed2b99 100644 --- a/src/modules/rtp_rtcp/source/rtcp_sender.cc +++ b/src/modules/rtp_rtcp/source/rtcp_sender.cc @@ -41,6 +41,7 @@ RTCPSender::RTCPSender(const WebRtc_Word32 id, _REMB(false), _sendREMB(false), _TMMBR(false), + _IJ(false), _nextTimeToSendRTCP(0), _SSRC(0), _remoteSSRC(0), @@ -129,6 +130,7 @@ RTCPSender::Init() _sending = false; _sendTMMBN = false; _TMMBR = false; + _IJ = false; _REMB = false; _sendREMB = false; _SSRC = 0; @@ -282,6 +284,21 @@ RTCPSender::SetTMMBRStatus(const bool enable) return 0; } +bool +RTCPSender::IJ() const +{ + CriticalSectionScoped lock(_criticalSectionRTCPSender); + return _IJ; +} + +WebRtc_Word32 +RTCPSender::SetIJStatus(const bool enable) +{ + CriticalSectionScoped lock(_criticalSectionRTCPSender); + _IJ = enable; + return 0; +} + void RTCPSender::SetSSRC( const WebRtc_UWord32 ssrc) { @@ -828,6 +845,57 @@ RTCPSender::BuildRR(WebRtc_UWord8* rtcpbuffer, return 0; } +// From RFC 5450: Transmission Time Offsets in RTP Streams. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// hdr |V=2|P| RC | PT=IJ=195 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | inter-arrival jitter | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// . . +// | inter-arrival jitter | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// If present, this RTCP packet must be placed after a receiver report +// (inside a compound RTCP packet), and MUST have the same value for RC +// (reception report count) as the receiver report. + +WebRtc_Word32 +RTCPSender::BuildExtendedJitterReport( + WebRtc_UWord8* rtcpbuffer, + WebRtc_UWord32& pos, + const WebRtc_UWord32 jitterTransmissionTimeOffset) +{ + if (_reportBlocks.Size() > 0) + { + WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, _id, "Not implemented."); + return 0; + } + + // sanity + if(pos + 8 >= IP_PACKET_SIZE) + { + return -2; + } + // add picture loss indicator + WebRtc_UWord8 RC = 1; + rtcpbuffer[pos++]=(WebRtc_UWord8)0x80 + RC; + rtcpbuffer[pos++]=(WebRtc_UWord8)195; + + // Used fixed length of 2 + rtcpbuffer[pos++]=(WebRtc_UWord8)0; + rtcpbuffer[pos++]=(WebRtc_UWord8)(1); + + // Add inter-arrival jitter + ModuleRTPUtility::AssignUWord32ToBuffer(rtcpbuffer + pos, + jitterTransmissionTimeOffset); + pos += 4; + return 0; +} + WebRtc_Word32 RTCPSender::BuildPLI(WebRtc_UWord8* rtcpbuffer, WebRtc_UWord32& pos) { @@ -1567,7 +1635,6 @@ RTCPSender::SendRTCP(const WebRtc_UWord32 packetTypeFlags, WebRtc_UWord32 pos = 0; WebRtc_UWord8 rtcpbuffer[IP_PACKET_SIZE]; - do // only to be able to use break :) (and the critsect must be inside its own scope) { // collect the received information @@ -1576,6 +1643,7 @@ RTCPSender::SendRTCP(const WebRtc_UWord32 packetTypeFlags, WebRtc_UWord32 NTPsec = 0; WebRtc_UWord32 NTPfrac = 0; bool rtcpCompound = false; + WebRtc_UWord32 jitterTransmissionOffset = 0; { CriticalSectionScoped lock(_criticalSectionRTCPSender); @@ -1597,7 +1665,8 @@ RTCPSender::SendRTCP(const WebRtc_UWord32 packetTypeFlags, if(_rtpRtcp.ReportBlockStatistics(&received.fractionLost, &received.cumulativeLost, &received.extendedHighSeqNum, - &received.jitter) == 0) + &received.jitter, + &jitterTransmissionOffset) == 0) { hasReceived = true; @@ -1673,6 +1742,10 @@ RTCPSender::SendRTCP(const WebRtc_UWord32 packetTypeFlags, { rtcpPacketTypeFlags |= kRtcpRr; } + if (_IJ && hasReceived) + { + rtcpPacketTypeFlags |= kRtcpTransmissionTimeOffset; + } } else if(_method == kRtcpNonCompound) { if(rtcpPacketTypeFlags & kRtcpReport) @@ -1783,6 +1856,22 @@ RTCPSender::SendRTCP(const WebRtc_UWord32 packetTypeFlags, } } } + if(rtcpPacketTypeFlags & kRtcpTransmissionTimeOffset) + { + // If present, this RTCP packet must be placed after a + // receiver report. + buildVal = BuildExtendedJitterReport(rtcpbuffer, + pos, + jitterTransmissionOffset); + if(buildVal == -1) + { + return -1; // error + } + else if(buildVal == -2) + { + break; // out of buffer + } + } if(rtcpPacketTypeFlags & kRtcpPli) { buildVal = BuildPLI(rtcpbuffer, pos); diff --git a/src/modules/rtp_rtcp/source/rtcp_sender.h b/src/modules/rtp_rtcp/source/rtcp_sender.h index 109d4b5621..ef9efa37a0 100644 --- a/src/modules/rtp_rtcp/source/rtcp_sender.h +++ b/src/modules/rtp_rtcp/source/rtcp_sender.h @@ -97,6 +97,13 @@ public: WebRtc_Word32 RequestTMMBR(const WebRtc_UWord32 estimatedBW, const WebRtc_UWord32 packetOH); + /* + * Extended jitter report + */ + bool IJ() const; + + WebRtc_Word32 SetIJStatus(const bool enable); + /* * */ @@ -146,6 +153,11 @@ private: const WebRtc_UWord32 NTPfrac, const RTCPReportBlock* received = NULL); + WebRtc_Word32 BuildExtendedJitterReport( + WebRtc_UWord8* rtcpbuffer, + WebRtc_UWord32& pos, + const WebRtc_UWord32 jitterTransmissionTimeOffset); + WebRtc_Word32 BuildSDEC(WebRtc_UWord8* rtcpbuffer, WebRtc_UWord32& pos); WebRtc_Word32 BuildPLI(WebRtc_UWord8* rtcpbuffer, WebRtc_UWord32& pos); WebRtc_Word32 BuildREMB(WebRtc_UWord8* rtcpbuffer, WebRtc_UWord32& pos); @@ -188,6 +200,7 @@ private: bool _REMB; bool _sendREMB; bool _TMMBR; + bool _IJ; WebRtc_UWord32 _nextTimeToSendRTCP; diff --git a/src/modules/rtp_rtcp/source/rtcp_sender_test.cc b/src/modules/rtp_rtcp/source/rtcp_sender_test.cc new file mode 100644 index 0000000000..96478a42c5 --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtcp_sender_test.cc @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +/* + * This file includes unit tests for the RTCPSender. + */ + +#include + +#include "common_types.h" +#include "rtp_utility.h" +#include "rtcp_sender.h" +#include "rtcp_receiver.h" +#include "rtp_rtcp_impl.h" + +namespace webrtc { + +void CreateRtpPacket(const bool marker_bit, const WebRtc_UWord8 payload, + const WebRtc_UWord16 seq_num, const WebRtc_UWord32 timestamp, + const WebRtc_UWord32 ssrc, WebRtc_UWord8* array, + WebRtc_UWord16* cur_pos) { + ASSERT_TRUE(payload <= 127); + array[(*cur_pos)++] = 0x80; + array[(*cur_pos)++] = payload | (marker_bit ? 0x80 : 0); + array[(*cur_pos)++] = seq_num >> 8; + array[(*cur_pos)++] = seq_num; + array[(*cur_pos)++] = timestamp >> 24; + array[(*cur_pos)++] = timestamp >> 16; + array[(*cur_pos)++] = timestamp >> 8; + array[(*cur_pos)++] = timestamp; + array[(*cur_pos)++] = ssrc >> 24; + array[(*cur_pos)++] = ssrc >> 16; + array[(*cur_pos)++] = ssrc >> 8; + array[(*cur_pos)++] = ssrc; + // VP8 payload header + array[(*cur_pos)++] = 0x90; // X bit = 1 + array[(*cur_pos)++] = 0x20; // T bit = 1 + array[(*cur_pos)++] = 0x00; // TID = 0 + array[(*cur_pos)++] = 0x00; // Key frame + array[(*cur_pos)++] = 0x00; + array[(*cur_pos)++] = 0x00; + array[(*cur_pos)++] = 0x9d; + array[(*cur_pos)++] = 0x01; + array[(*cur_pos)++] = 0x2a; + array[(*cur_pos)++] = 128; + array[(*cur_pos)++] = 0; + array[(*cur_pos)++] = 96; + array[(*cur_pos)++] = 0; +} + +class TestTransport : public Transport, + public RtpData { + public: + TestTransport(RTCPReceiver* rtcp_receiver) : + rtcp_receiver_(rtcp_receiver) { + } + + virtual int SendPacket(int /*ch*/, const void* /*data*/, int /*len*/) { + return -1; + } + + virtual int SendRTCPPacket(int /*ch*/, const void *packet, int packet_len) { + RTCPUtility::RTCPParserV2 rtcpParser((WebRtc_UWord8*)packet, + (WebRtc_Word32)packet_len, + true); // Allow non-compound RTCP + + EXPECT_EQ(true, rtcpParser.IsValid()); + RTCPHelp::RTCPPacketInformation rtcpPacketInformation; + EXPECT_EQ(0, rtcp_receiver_->IncomingRTCPPacket(rtcpPacketInformation, + &rtcpParser)); + rtcp_packet_info_ = rtcpPacketInformation; + + return packet_len; + } + + virtual int OnReceivedPayloadData(const WebRtc_UWord8* payloadData, + const WebRtc_UWord16 payloadSize, + const WebRtcRTPHeader* rtpHeader) + {return 0;} + RTCPReceiver* rtcp_receiver_; + RTCPHelp::RTCPPacketInformation rtcp_packet_info_; +}; + +class RtcpSenderTest : public ::testing::Test { + protected: + RtcpSenderTest() { + rtp_rtcp_impl_ = new ModuleRtpRtcpImpl(0, false, + ModuleRTPUtility::GetSystemClock()); + rtcp_sender_ = new RTCPSender(0, false, ModuleRTPUtility::GetSystemClock(), + rtp_rtcp_impl_); + rtcp_receiver_ = new RTCPReceiver(0, ModuleRTPUtility::GetSystemClock(), + rtp_rtcp_impl_); + test_transport_ = new TestTransport(rtcp_receiver_); + // Initialize + EXPECT_EQ(0, rtcp_sender_->Init()); + EXPECT_EQ(0, rtcp_sender_->RegisterSendTransport(test_transport_)); + EXPECT_EQ(0, rtp_rtcp_impl_->RegisterIncomingDataCallback(test_transport_)); + } + ~RtcpSenderTest() { + delete rtcp_sender_; + delete rtcp_receiver_; + delete rtp_rtcp_impl_; + delete test_transport_; + } + + ModuleRtpRtcpImpl* rtp_rtcp_impl_; + RTCPSender* rtcp_sender_; + RTCPReceiver* rtcp_receiver_; + TestTransport* test_transport_; + + enum {kMaxPacketLength = 1500}; + uint8_t packet_[kMaxPacketLength]; +}; + +TEST_F(RtcpSenderTest, RtcpOff) { + EXPECT_EQ(0, rtcp_sender_->SetRTCPStatus(kRtcpOff)); + EXPECT_EQ(-1, rtcp_sender_->SendRTCP(kRtcpSr)); +} + +TEST_F(RtcpSenderTest, IJStatus) { + ASSERT_FALSE(rtcp_sender_->IJ()); + EXPECT_EQ(0, rtcp_sender_->SetIJStatus(true)); + ASSERT_TRUE(rtcp_sender_->IJ()); +} + +TEST_F(RtcpSenderTest, TestCompound) { + const bool marker_bit = false; + const WebRtc_UWord8 payload = 100; + const WebRtc_UWord16 seq_num = 11111; + const WebRtc_UWord32 timestamp = 1234567; + const WebRtc_UWord32 ssrc = 0x11111111; + WebRtc_UWord16 packet_length = 0; + CreateRtpPacket(marker_bit, payload, seq_num, timestamp, ssrc, packet_, + &packet_length); + EXPECT_EQ(25, packet_length); + + VideoCodec codec_inst; + strncpy(codec_inst.plName, "VP8", webrtc::kPayloadNameSize - 1); + codec_inst.codecType = webrtc::kVideoCodecVP8; + codec_inst.plType = payload; + EXPECT_EQ(0, rtp_rtcp_impl_->RegisterReceivePayload(codec_inst)); + + // Make sure RTP packet has been received. + EXPECT_EQ(0, rtp_rtcp_impl_->IncomingPacket(packet_, packet_length)); + + EXPECT_EQ(0, rtcp_sender_->SetIJStatus(true)); + EXPECT_EQ(0, rtcp_sender_->SetRTCPStatus(kRtcpCompound)); + EXPECT_EQ(0, rtcp_sender_->SendRTCP(kRtcpRr)); + + // Transmission time offset packet should be received. + ASSERT_TRUE(test_transport_->rtcp_packet_info_.rtcpPacketTypeFlags & + kRtcpTransmissionTimeOffset); +} + +TEST_F(RtcpSenderTest, TestCompound_NoRtpReceived) { + EXPECT_EQ(0, rtcp_sender_->SetIJStatus(true)); + EXPECT_EQ(0, rtcp_sender_->SetRTCPStatus(kRtcpCompound)); + EXPECT_EQ(0, rtcp_sender_->SendRTCP(kRtcpRr)); + + // Transmission time offset packet should not be received. + ASSERT_FALSE(test_transport_->rtcp_packet_info_.rtcpPacketTypeFlags & + kRtcpTransmissionTimeOffset); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} +} // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/rtcp_utility.cc b/src/modules/rtp_rtcp/source/rtcp_utility.cc index b153981f75..aa9c90773b 100644 --- a/src/modules/rtp_rtcp/source/rtcp_utility.cc +++ b/src/modules/rtp_rtcp/source/rtcp_utility.cc @@ -100,6 +100,9 @@ RTCPUtility::RTCPParserV2::Iterate() case State_BYEItem: IterateBYEItem(); break; + case State_ExtendedJitterItem: + IterateExtendedJitterItem(); + break; case State_RTPFB_NACKItem: IterateNACKItem(); break; @@ -196,6 +199,13 @@ RTCPUtility::RTCPParserV2::IterateTopLevel() } return; } + case PT_IJ: + { + // number of Report blocks + _numberOfBlocks = header.IC; + ParseIJ(); + return; + } case PT_RTPFB: // Fall through! case PT_PSFB: { @@ -266,6 +276,16 @@ RTCPUtility::RTCPParserV2::IterateBYEItem() } } +void +RTCPUtility::RTCPParserV2::IterateExtendedJitterItem() +{ + const bool success = ParseIJItem(); + if (!success) + { + Iterate(); + } +} + void RTCPUtility::RTCPParserV2::IterateNACKItem() { @@ -587,6 +607,62 @@ RTCPUtility::RTCPParserV2::ParseReportBlockItem() return true; } +/* From RFC 5450: Transmission Time Offsets in RTP Streams. + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + hdr |V=2|P| RC | PT=IJ=195 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | inter-arrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + . . + | inter-arrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +bool +RTCPUtility::RTCPParserV2::ParseIJ() +{ + const ptrdiff_t length = _ptrRTCPBlockEnd - _ptrRTCPData; + + if (length < 4) + { + return false; + } + + _ptrRTCPData += 4; // Skip header + + _packetType = kRtcpExtendedIjCode; + + // State transition + _state = State_ExtendedJitterItem; + return true; +} + +bool +RTCPUtility::RTCPParserV2::ParseIJItem() +{ + const ptrdiff_t length = _ptrRTCPBlockEnd - _ptrRTCPData; + + if (length < 4 || _numberOfBlocks <= 0) + { + _state = State_TopLevel; + EndCurrentBlock(); + return false; + } + + _packet.ExtendedJitterReportItem.Jitter = *_ptrRTCPData++ << 24; + _packet.ExtendedJitterReportItem.Jitter += *_ptrRTCPData++ << 16; + _packet.ExtendedJitterReportItem.Jitter += *_ptrRTCPData++ << 8; + _packet.ExtendedJitterReportItem.Jitter += *_ptrRTCPData++; + + _numberOfBlocks--; + _packetType = kRtcpExtendedIjItemCode; + return true; +} + bool RTCPUtility::RTCPParserV2::ParseSDES() { diff --git a/src/modules/rtp_rtcp/source/rtcp_utility.h b/src/modules/rtp_rtcp/source/rtcp_utility.h index 4bdf1359e5..542ec0d394 100644 --- a/src/modules/rtp_rtcp/source/rtcp_utility.h +++ b/src/modules/rtp_rtcp/source/rtcp_utility.h @@ -67,6 +67,12 @@ namespace RTCPUtility { WebRtc_UWord8 CNameLength; }; + struct RTCPPacketExtendedJitterReportItem + { + // RFC 5450 + WebRtc_UWord32 Jitter; + }; + struct RTCPPacketBYE { WebRtc_UWord32 SenderSSRC; @@ -203,6 +209,8 @@ namespace RTCPUtility { RTCPPacketSDESCName CName; RTCPPacketBYE BYE; + RTCPPacketExtendedJitterReportItem ExtendedJitterReportItem; + RTCPPacketRTPFBNACK NACK; RTCPPacketRTPFBNACKItem NACKItem; @@ -238,6 +246,10 @@ namespace RTCPUtility { kRtcpSdesChunkCode, kRtcpByeCode, + // RFC5450 + kRtcpExtendedIjCode, + kRtcpExtendedIjItemCode, + // RFC4585 kRtcpRtpfbNackCode, kRtcpRtpfbNackItemCode, @@ -290,6 +302,7 @@ namespace RTCPUtility { enum RTCPPT { + PT_IJ = 195, PT_SR = 200, PT_RR = 201, PT_SDES = 202, @@ -329,6 +342,7 @@ namespace RTCPUtility { State_ReportBlockItem, // SR/RR report block State_SDESChunk, // SDES chunk State_BYEItem, // BYE item + State_ExtendedJitterItem, // Extended jitter report item State_RTPFB_NACKItem, // NACK FCI item State_RTPFB_TMMBRItem, // TMMBR FCI item State_RTPFB_TMMBNItem, // TMMBN FCI item @@ -346,6 +360,7 @@ namespace RTCPUtility { void IterateReportBlockItem(); void IterateSDESChunk(); void IterateBYEItem(); + void IterateExtendedJitterItem(); void IterateNACKItem(); void IterateTMMBRItem(); void IterateTMMBNItem(); @@ -370,6 +385,9 @@ namespace RTCPUtility { bool ParseBYE(); bool ParseBYEItem(); + bool ParseIJ(); + bool ParseIJItem(); + bool ParseXR(); bool ParseXRItem(); bool ParseXRVOIPMetricItem(); diff --git a/src/modules/rtp_rtcp/source/rtp_header_extension.cc b/src/modules/rtp_rtcp/source/rtp_header_extension.cc new file mode 100644 index 0000000000..c85517f84c --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtp_header_extension.cc @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "common_types.h" +#include "rtp_header_extension.h" + +namespace webrtc { + +RtpHeaderExtensionMap::RtpHeaderExtensionMap() { +} + +RtpHeaderExtensionMap::~RtpHeaderExtensionMap() { + Erase(); +} + +void RtpHeaderExtensionMap::Erase() { + while (extensionMap_.Size() != 0) { + MapItem* item = extensionMap_.First(); + assert(item); + HeaderExtension* extension = (HeaderExtension*)item->GetItem(); + extensionMap_.Erase(item); + delete extension; + } +} + +WebRtc_Word32 RtpHeaderExtensionMap::Register(const RTPExtensionType type, + const WebRtc_UWord8 id) { + if (id < 1 || id > 14) { + return -1; + } + MapItem* item = extensionMap_.Find(id); + if (item != NULL) { + return -1; + } + HeaderExtension* extension = new HeaderExtension(type); + extensionMap_.Insert(id, extension); + return 0; +} + +WebRtc_Word32 RtpHeaderExtensionMap::Deregister(const RTPExtensionType type) { + WebRtc_UWord8 id; + if (GetId(type, &id) != 0) { + return -1; + } + MapItem* item = extensionMap_.Find(id); + if (item == NULL) { + return -1; + } + HeaderExtension* extension = (HeaderExtension*)item->GetItem(); + extensionMap_.Erase(item); + delete extension; + return 0; +} + +WebRtc_Word32 RtpHeaderExtensionMap::GetType(const WebRtc_UWord8 id, + RTPExtensionType* type) const { + assert(type); + MapItem* item = extensionMap_.Find(id); + if (item == NULL) { + return -1; + } + HeaderExtension* extension = (HeaderExtension*)item->GetItem(); + *type = extension->type; + return 0; +} + +WebRtc_Word32 RtpHeaderExtensionMap::GetId(const RTPExtensionType type, + WebRtc_UWord8* id) const { + assert(id); + MapItem* item = extensionMap_.First(); + while (item != NULL) { + HeaderExtension* extension = (HeaderExtension*)item->GetItem(); + if (extension->type == type) { + *id = item->GetId(); + return 0; + } + item = extensionMap_.Next(item); + } + return -1; +} + +WebRtc_UWord16 RtpHeaderExtensionMap::GetTotalLengthInBytes() const +{ + // Get length for each extension block. + WebRtc_UWord16 length = 0; + MapItem* item = extensionMap_.First(); + while (item != NULL) { + HeaderExtension* extension = (HeaderExtension*)item->GetItem(); + length += extension->length; + item = extensionMap_.Next(item); + } + // Add RTP extension header length. + if (length > 0) { + length += RTP_ONE_BYTE_HEADER_LENGTH_IN_BYTES; + } + return length; +} + +WebRtc_Word32 RtpHeaderExtensionMap::Size() const { + return extensionMap_.Size(); +} + +RTPExtensionType RtpHeaderExtensionMap::First() const { + MapItem* item = extensionMap_.First(); + if (item == NULL) { + return NONE; + } + HeaderExtension* extension = (HeaderExtension*)item->GetItem(); + return extension->type; +} + +RTPExtensionType RtpHeaderExtensionMap::Next(RTPExtensionType type) const +{ + WebRtc_UWord8 id; + if (GetId(type, &id) != 0) { + return NONE; + } + MapItem* item = extensionMap_.Find(id); + if (item == NULL) { + return NONE; + } + item = extensionMap_.Next(item); + if (item == NULL) { + return NONE; + } + HeaderExtension* extension = (HeaderExtension*)item->GetItem(); + return extension->type; +} + +void RtpHeaderExtensionMap::GetCopy(RtpHeaderExtensionMap* map) const { + assert(map); + MapItem* item = extensionMap_.First(); + while (item != NULL) { + HeaderExtension* extension = (HeaderExtension*)item->GetItem(); + map->Register(extension->type, item->GetId()); + item = extensionMap_.Next(item); + } +} +} // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/rtp_header_extension.h b/src/modules/rtp_rtcp/source/rtp_header_extension.h new file mode 100644 index 0000000000..090f4a6dd9 --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtp_header_extension.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_RTP_RTCP_RTP_HEADER_EXTENSION_H_ +#define WEBRTC_MODULES_RTP_RTCP_RTP_HEADER_EXTENSION_H_ + +#include "map_wrapper.h" +#include "rtp_rtcp_defines.h" +#include "typedefs.h" + +namespace webrtc { + +enum {RTP_ONE_BYTE_HEADER_EXTENSION = 0xbede}; + +enum ExtensionLength { + RTP_ONE_BYTE_HEADER_LENGTH_IN_BYTES = 4, + TRANSMISSION_TIME_OFFSET_LENGTH_IN_BYTES = 4 +}; + +struct HeaderExtension { + HeaderExtension(RTPExtensionType extension_type) + : type(extension_type), + length(0) { + if (type == TRANSMISSION_TIME_OFFSET) { + length = TRANSMISSION_TIME_OFFSET_LENGTH_IN_BYTES; + } + } + + const RTPExtensionType type; + WebRtc_UWord8 length; +}; + +class RtpHeaderExtensionMap { + public: + RtpHeaderExtensionMap(); + ~RtpHeaderExtensionMap(); + + void Erase(); + + WebRtc_Word32 Register(const RTPExtensionType type, const WebRtc_UWord8 id); + + WebRtc_Word32 Deregister(const RTPExtensionType type); + + WebRtc_Word32 GetType(const WebRtc_UWord8 id, RTPExtensionType* type) const; + + WebRtc_Word32 GetId(const RTPExtensionType type, WebRtc_UWord8* id) const; + + WebRtc_UWord16 GetTotalLengthInBytes() const; + + void GetCopy(RtpHeaderExtensionMap* map) const; + + WebRtc_Word32 Size() const; + + RTPExtensionType First() const; + + RTPExtensionType Next(RTPExtensionType type) const; + + private: + MapWrapper extensionMap_; +}; +} +#endif // WEBRTC_MODULES_RTP_RTCP_RTP_HEADER_EXTENSION_H_ diff --git a/src/modules/rtp_rtcp/source/rtp_header_extension_test.cc b/src/modules/rtp_rtcp/source/rtp_header_extension_test.cc new file mode 100644 index 0000000000..8bca4fbd5f --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtp_header_extension_test.cc @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +/* + * This file includes unit tests for the RtpHeaderExtensionMap. + */ + +#include + +#include "rtp_header_extension.h" +#include "rtp_rtcp_defines.h" +#include "typedefs.h" + +namespace webrtc { + +class RtpHeaderExtensionTest : public ::testing::Test { + protected: + RtpHeaderExtensionTest() {} + ~RtpHeaderExtensionTest() {} + + RtpHeaderExtensionMap map_; + enum {kId = 3}; +}; + +TEST_F(RtpHeaderExtensionTest, Register) { + EXPECT_EQ(0, map_.Size()); + EXPECT_EQ(0, map_.Register(TRANSMISSION_TIME_OFFSET, kId)); + EXPECT_EQ(1, map_.Size()); + EXPECT_EQ(0, map_.Deregister(TRANSMISSION_TIME_OFFSET)); + EXPECT_EQ(0, map_.Size()); +} + +TEST_F(RtpHeaderExtensionTest, RegisterIllegalArg) { + // Valid range for id: [1-14]. + EXPECT_EQ(-1, map_.Register(TRANSMISSION_TIME_OFFSET, 0)); + EXPECT_EQ(-1, map_.Register(TRANSMISSION_TIME_OFFSET, 15)); +} + +TEST_F(RtpHeaderExtensionTest, DeregisterIllegalArg) { + // Not registered. + EXPECT_EQ(-1, map_.Deregister(TRANSMISSION_TIME_OFFSET)); +} + +TEST_F(RtpHeaderExtensionTest, NonUniqueId) { + EXPECT_EQ(0, map_.Register(TRANSMISSION_TIME_OFFSET, kId)); + EXPECT_EQ(-1, map_.Register(TRANSMISSION_TIME_OFFSET, kId)); +} + +TEST_F(RtpHeaderExtensionTest, GetLength) { + EXPECT_EQ(0, map_.GetTotalLengthInBytes()); + EXPECT_EQ(0, map_.Register(TRANSMISSION_TIME_OFFSET, kId)); + EXPECT_EQ(RTP_ONE_BYTE_HEADER_LENGTH_IN_BYTES + + TRANSMISSION_TIME_OFFSET_LENGTH_IN_BYTES, + map_.GetTotalLengthInBytes()); +} + +TEST_F(RtpHeaderExtensionTest, GetType) { + RTPExtensionType typeOut; + EXPECT_EQ(-1, map_.GetType(kId, &typeOut)); + + EXPECT_EQ(0, map_.Register(TRANSMISSION_TIME_OFFSET, kId)); + EXPECT_EQ(0, map_.GetType(kId, &typeOut)); + EXPECT_EQ(TRANSMISSION_TIME_OFFSET, typeOut); +} + +TEST_F(RtpHeaderExtensionTest, GetId) { + WebRtc_UWord8 idOut; + EXPECT_EQ(-1, map_.GetId(TRANSMISSION_TIME_OFFSET, &idOut)); + + EXPECT_EQ(0, map_.Register(TRANSMISSION_TIME_OFFSET, kId)); + EXPECT_EQ(0, map_.GetId(TRANSMISSION_TIME_OFFSET, &idOut)); + EXPECT_EQ(kId, idOut); +} + +TEST_F(RtpHeaderExtensionTest, IterateTypes) { + EXPECT_EQ(NONE, map_.First()); + EXPECT_EQ(NONE, map_.Next(TRANSMISSION_TIME_OFFSET)); + + EXPECT_EQ(0, map_.Register(TRANSMISSION_TIME_OFFSET, kId)); + + EXPECT_EQ(TRANSMISSION_TIME_OFFSET, map_.First()); + EXPECT_EQ(NONE, map_.Next(TRANSMISSION_TIME_OFFSET)); +} + +TEST_F(RtpHeaderExtensionTest, GetCopy) { + EXPECT_EQ(0, map_.Register(TRANSMISSION_TIME_OFFSET, kId)); + + RtpHeaderExtensionMap mapOut; + map_.GetCopy(&mapOut); + EXPECT_EQ(1, mapOut.Size()); + EXPECT_EQ(TRANSMISSION_TIME_OFFSET, mapOut.First()); +} + +TEST_F(RtpHeaderExtensionTest, Erase) { + EXPECT_EQ(0, map_.Register(TRANSMISSION_TIME_OFFSET, kId)); + EXPECT_EQ(1, map_.Size()); + map_.Erase(); + EXPECT_EQ(0, map_.Size()); +} +} // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/rtp_receiver.cc b/src/modules/rtp_rtcp/source/rtp_receiver.cc index 570fa7e62f..18e6c98506 100644 --- a/src/modules/rtp_rtcp/source/rtp_receiver.cc +++ b/src/modules/rtp_rtcp/source/rtp_receiver.cc @@ -48,6 +48,7 @@ RTPReceiver::RTPReceiver(const WebRtc_Word32 id, _redPayloadType(-1), _payloadTypeMap(), + _rtpHeaderExtensionMap(), _SSRC(0), _numCSRCs(0), _currentRemoteCSRC(), @@ -59,9 +60,11 @@ RTPReceiver::RTPReceiver(const WebRtc_Word32 id, _jitterQ4(0), _jitterMaxQ4(0), _cumulativeLoss(0), + _jitterQ4TransmissionTimeOffset(0), _localTimeLastReceivedTimestamp(0), _lastReceivedTimestamp(0), _lastReceivedSequenceNumber(0), + _lastReceivedTransmissionTimeOffset(0), _receivedSeqFirst(0), _receivedSeqMax(0), @@ -79,6 +82,7 @@ RTPReceiver::RTPReceiver(const WebRtc_Word32 id, _lastReportCumulativeLost(0), _lastReportExtendedHighSeqNum(0), _lastReportJitter(0), + _lastReportJitterTransmissionTimeOffset(0), _nackMethod(kNackOff) { @@ -150,12 +154,14 @@ RTPReceiver::Init() _jitterQ4 = 0; _jitterMaxQ4 = 0; _cumulativeLoss = 0; + _jitterQ4TransmissionTimeOffset = 0; _useSSRCFilter = false; _SSRCFilter = 0; _localTimeLastReceivedTimestamp = 0; _lastReceivedTimestamp = 0; _lastReceivedSequenceNumber = 0; + _lastReceivedTransmissionTimeOffset = 0; _receivedSeqFirst = 0; _receivedSeqMax = 0; @@ -173,6 +179,9 @@ RTPReceiver::Init() _lastReportCumulativeLost = 0; _lastReportExtendedHighSeqNum = 0; _lastReportJitter = 0; + _lastReportJitterTransmissionTimeOffset = 0; + + _rtpHeaderExtensionMap.Erase(); // clear db bool loop = true; @@ -683,6 +692,27 @@ RTPReceiver::RemotePayload(WebRtc_Word8 payloadName[RTP_PAYLOAD_NAME_SIZE], return -1; } +WebRtc_Word32 +RTPReceiver::RegisterRtpHeaderExtension(const RTPExtensionType type, + const WebRtc_UWord8 id) +{ + CriticalSectionScoped cs(_criticalSectionRTPReceiver); + return _rtpHeaderExtensionMap.Register(type, id); +} + +WebRtc_Word32 +RTPReceiver::DeregisterRtpHeaderExtension(const RTPExtensionType type) +{ + CriticalSectionScoped cs(_criticalSectionRTPReceiver); + return _rtpHeaderExtensionMap.Deregister(type); +} + +void RTPReceiver::GetHeaderExtensionMapCopy(RtpHeaderExtensionMap* map) const +{ + CriticalSectionScoped cs(_criticalSectionRTPReceiver); + _rtpHeaderExtensionMap.GetCopy(map); +} + NACKMethod RTPReceiver::NACK() const { @@ -852,6 +882,8 @@ RTPReceiver::IncomingRTPPacket(WebRtcRTPHeader* rtpHeader, _lastReceivedTimestamp = rtpHeader->header.timestamp; } _lastReceivedSequenceNumber = rtpHeader->header.sequenceNumber; + _lastReceivedTransmissionTimeOffset = + rtpHeader->extension.transmissionTimeOffset; } } return retVal; @@ -931,6 +963,26 @@ RTPReceiver::UpdateStatistics(const WebRtcRTPHeader* rtpHeader, WebRtc_Word32 jitterDiffQ4 = (timeDiffSamples << 4) - _jitterQ4; _jitterQ4 += ((jitterDiffQ4 + 8) >> 4); } + + // Extended jitter report, RFC 5450. + // Actual network jitter, excluding the source-introduced jitter. + WebRtc_Word32 timeDiffSamplesExt = + (RTPtime - _localTimeLastReceivedTimestamp) - + ((rtpHeader->header.timestamp + + rtpHeader->extension.transmissionTimeOffset) - + (_lastReceivedTimestamp + + _lastReceivedTransmissionTimeOffset)); + + timeDiffSamplesExt = abs(timeDiffSamplesExt); + + if(timeDiffSamplesExt < 450000) // Use 5 secs video freq as border + { + // note we calculate in Q4 to avoid using float + WebRtc_Word32 jitterDiffQ4TransmissionTimeOffset = + (timeDiffSamplesExt << 4) - _jitterQ4TransmissionTimeOffset; + _jitterQ4TransmissionTimeOffset += + ((jitterDiffQ4TransmissionTimeOffset + 8) >> 4); + } } _localTimeLastReceivedTimestamp = RTPtime; } else @@ -1130,6 +1182,7 @@ RTPReceiver::CheckSSRCChanged(const WebRtcRTPHeader* rtpHeader) _lastReceivedTimestamp = 0; _lastReceivedSequenceNumber = 0; + _lastReceivedTransmissionTimeOffset = 0; if (_SSRC) // do we have a SSRC? then the stream is restarted { @@ -1461,9 +1514,11 @@ RTPReceiver::ResetStatistics() _lastReportCumulativeLost = 0; _lastReportExtendedHighSeqNum = 0; _lastReportJitter = 0; + _lastReportJitterTransmissionTimeOffset = 0; _jitterQ4 = 0; _jitterMaxQ4 = 0; _cumulativeLoss = 0; + _jitterQ4TransmissionTimeOffset = 0; _receivedSeqWraps = 0; _receivedSeqMax = 0; _receivedSeqFirst = 0; @@ -1492,6 +1547,7 @@ RTPReceiver::Statistics(WebRtc_UWord8 *fraction_lost, WebRtc_UWord32 *ext_max, WebRtc_UWord32 *jitter, WebRtc_UWord32 *max_jitter, + WebRtc_UWord32 *jitter_transmission_time_offset, bool reset) const { WebRtc_Word32 missing; @@ -1500,6 +1556,7 @@ RTPReceiver::Statistics(WebRtc_UWord8 *fraction_lost, ext_max, jitter, max_jitter, + jitter_transmission_time_offset, &missing, reset); } @@ -1510,6 +1567,7 @@ RTPReceiver::Statistics(WebRtc_UWord8 *fraction_lost, WebRtc_UWord32 *ext_max, WebRtc_UWord32 *jitter, WebRtc_UWord32 *max_jitter, + WebRtc_UWord32 *jitter_transmission_time_offset, WebRtc_Word32 *missing, bool reset) const { @@ -1555,6 +1613,11 @@ RTPReceiver::Statistics(WebRtc_UWord8 *fraction_lost, // and needs to be scaled by 1/16 *max_jitter = (_jitterMaxQ4 >> 4); } + if(jitter_transmission_time_offset) + { + *jitter_transmission_time_offset = + _lastReportJitterTransmissionTimeOffset; + } return 0; } @@ -1636,6 +1699,13 @@ RTPReceiver::Statistics(WebRtc_UWord8 *fraction_lost, // and needs to be scaled by 1/16 *max_jitter = (_jitterMaxQ4 >> 4); } + if(jitter_transmission_time_offset) + { + // note that the internal jitter value is in Q4 + // and needs to be scaled by 1/16 + *jitter_transmission_time_offset = + (_jitterQ4TransmissionTimeOffset >> 4); + } if(reset) { // store this report @@ -1643,6 +1713,8 @@ RTPReceiver::Statistics(WebRtc_UWord8 *fraction_lost, _lastReportCumulativeLost = _cumulativeLoss; // 24 bits valid _lastReportExtendedHighSeqNum = (_receivedSeqWraps<<16) + _receivedSeqMax; _lastReportJitter = (_jitterQ4 >> 4); + _lastReportJitterTransmissionTimeOffset = + (_jitterQ4TransmissionTimeOffset >> 4); // only for report blocks in RTCP SR and RR _lastReportInorderPackets = _receivedInorderPacketCount; diff --git a/src/modules/rtp_rtcp/source/rtp_receiver.h b/src/modules/rtp_rtcp/source/rtp_receiver.h index a289fbffdb..6ce639d644 100644 --- a/src/modules/rtp_rtcp/source/rtp_receiver.h +++ b/src/modules/rtp_rtcp/source/rtp_receiver.h @@ -14,7 +14,9 @@ #include "typedefs.h" #include "rtp_utility.h" +#include "rtp_header_extension.h" #include "rtp_rtcp.h" +#include "rtp_rtcp_defines.h" #include "rtp_receiver_audio.h" #include "rtp_receiver_video.h" #include "rtcp_receiver_help.h" @@ -109,15 +111,17 @@ public: WebRtc_Word32 Statistics(WebRtc_UWord8 *fraction_lost, WebRtc_UWord32 *cum_lost, WebRtc_UWord32 *ext_max, - WebRtc_UWord32 *jitter, // will be moved from JB + WebRtc_UWord32 *jitter, // will be moved from JB WebRtc_UWord32 *max_jitter, + WebRtc_UWord32 *jitter_transmission_time_offset, bool reset = false) const; WebRtc_Word32 Statistics(WebRtc_UWord8 *fraction_lost, WebRtc_UWord32 *cum_lost, WebRtc_UWord32 *ext_max, - WebRtc_UWord32 *jitter, // will be moved from JB + WebRtc_UWord32 *jitter, // will be moved from JB WebRtc_UWord32 *max_jitter, + WebRtc_UWord32 *jitter_transmission_time_offset, WebRtc_Word32 *missing, bool reset = false) const; @@ -134,6 +138,13 @@ public: WebRtc_UWord32 ByteCountReceived() const; + WebRtc_Word32 RegisterRtpHeaderExtension(const RTPExtensionType type, + const WebRtc_UWord8 id); + + WebRtc_Word32 DeregisterRtpHeaderExtension(const RTPExtensionType type); + + void GetHeaderExtensionMapCopy(RtpHeaderExtensionMap* map) const; + virtual WebRtc_UWord32 PayloadTypeToPayload(const WebRtc_UWord8 payloadType, ModuleRTPUtility::Payload*& payload) const; @@ -192,7 +203,8 @@ private: WebRtc_Word8 _redPayloadType; // - MapWrapper _payloadTypeMap; + MapWrapper _payloadTypeMap; + RtpHeaderExtensionMap _rtpHeaderExtensionMap; // SSRCs WebRtc_UWord32 _SSRC; @@ -201,17 +213,19 @@ private: WebRtc_UWord8 _numEnergy; WebRtc_UWord8 _currentRemoteEnergy[kRtpCsrcSize]; - bool _useSSRCFilter; + bool _useSSRCFilter; WebRtc_UWord32 _SSRCFilter; // stats on received RTP packets WebRtc_UWord32 _jitterQ4; mutable WebRtc_UWord32 _jitterMaxQ4; mutable WebRtc_UWord32 _cumulativeLoss; + WebRtc_UWord32 _jitterQ4TransmissionTimeOffset; WebRtc_UWord32 _localTimeLastReceivedTimestamp; WebRtc_UWord32 _lastReceivedTimestamp; WebRtc_UWord16 _lastReceivedSequenceNumber; + WebRtc_Word32 _lastReceivedTransmissionTimeOffset; WebRtc_UWord16 _receivedSeqFirst; WebRtc_UWord16 _receivedSeqMax; WebRtc_UWord16 _receivedSeqWraps; @@ -230,6 +244,7 @@ private: mutable WebRtc_UWord32 _lastReportCumulativeLost; // 24 bits valid mutable WebRtc_UWord32 _lastReportExtendedHighSeqNum; mutable WebRtc_UWord32 _lastReportJitter; + mutable WebRtc_UWord32 _lastReportJitterTransmissionTimeOffset; // NACK NACKMethod _nackMethod; diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp.gypi b/src/modules/rtp_rtcp/source/rtp_rtcp.gypi index b397598a29..e030845cbf 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp.gypi +++ b/src/modules/rtp_rtcp/source/rtp_rtcp.gypi @@ -41,6 +41,8 @@ 'rtcp_sender.h', 'rtcp_utility.cc', 'rtcp_utility.h', + 'rtp_header_extension.cc', + 'rtp_header_extension.h', 'rtp_receiver.cc', 'rtp_receiver.h', 'rtp_sender.cc', diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/src/modules/rtp_rtcp/source/rtp_rtcp_impl.cc index 23d55b2356..5014ba459b 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_impl.cc +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_impl.cc @@ -790,7 +790,10 @@ ModuleRtpRtcpImpl::IncomingPacket(const WebRtc_UWord8* incomingPacket, WebRtcRTPHeader rtpHeader; memset(&rtpHeader, 0, sizeof(rtpHeader)); - const bool validRTPHeader = rtpParser.Parse(rtpHeader); + RtpHeaderExtensionMap map; + _rtpReceiver.GetHeaderExtensionMapCopy(&map); + + const bool validRTPHeader = rtpParser.Parse(rtpHeader, &map); if(!validRTPHeader) { WEBRTC_TRACE(kTraceDebug, @@ -1668,7 +1671,11 @@ ModuleRtpRtcpImpl::StatisticsRTP(WebRtc_UWord8 *fraction_lost, { WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, _id, "StatisticsRTP()"); - WebRtc_Word32 retVal =_rtpReceiver.Statistics(fraction_lost,cum_lost,ext_max,jitter, max_jitter,(_rtcpSender.Status() == kRtcpOff)); + WebRtc_UWord32 jitter_transmission_time_offset = 0; + + WebRtc_Word32 retVal =_rtpReceiver.Statistics(fraction_lost, cum_lost, + ext_max, jitter, max_jitter, &jitter_transmission_time_offset, + (_rtcpSender.Status() == kRtcpOff)); if(retVal == -1) { WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, _id, "StatisticsRTP() no statisitics availble"); @@ -1696,17 +1703,21 @@ ModuleRtpRtcpImpl::DataCountersRTP(WebRtc_UWord32 *bytesSent, } WebRtc_Word32 -ModuleRtpRtcpImpl::ReportBlockStatistics(WebRtc_UWord8 *fraction_lost, - WebRtc_UWord32 *cum_lost, - WebRtc_UWord32 *ext_max, - WebRtc_UWord32 *jitter) +ModuleRtpRtcpImpl::ReportBlockStatistics( + WebRtc_UWord8 *fraction_lost, + WebRtc_UWord32 *cum_lost, + WebRtc_UWord32 *ext_max, + WebRtc_UWord32 *jitter, + WebRtc_UWord32 *jitter_transmission_time_offset) { WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, _id, "ReportBlockStatistics()"); WebRtc_Word32 missing = 0; WebRtc_Word32 ret = _rtpReceiver.Statistics(fraction_lost, - cum_lost,ext_max, + cum_lost, + ext_max, jitter, NULL, + jitter_transmission_time_offset, &missing, true); @@ -1793,6 +1804,52 @@ WebRtc_Word32 ModuleRtpRtcpImpl::SetREMBData(const WebRtc_UWord32 bitrate, return _rtcpSender.SetREMBData(bitrate, numberOfSSRC, SSRC); } + /* + * (IJ) Extended jitter report. + */ +bool ModuleRtpRtcpImpl::IJ() const +{ + WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, _id, "IJ()"); + + return _rtcpSender.IJ(); +} + +WebRtc_Word32 ModuleRtpRtcpImpl::SetIJStatus(const bool enable) +{ + WEBRTC_TRACE(kTraceModuleCall, + kTraceRtpRtcp, + _id, + "SetIJStatus(%s)", enable ? "true" : "false"); + + return _rtcpSender.SetIJStatus(enable); +} + +WebRtc_Word32 ModuleRtpRtcpImpl::RegisterSendRtpHeaderExtension( + const RTPExtensionType type, + const WebRtc_UWord8 id) +{ + return _rtpSender.RegisterRtpHeaderExtension(type, id); +} + +WebRtc_Word32 ModuleRtpRtcpImpl::DeregisterSendRtpHeaderExtension( + const RTPExtensionType type) +{ + return _rtpSender.DeregisterRtpHeaderExtension(type); +} + +WebRtc_Word32 ModuleRtpRtcpImpl::RegisterReceiveRtpHeaderExtension( + const RTPExtensionType type, + const WebRtc_UWord8 id) +{ + return _rtpReceiver.RegisterRtpHeaderExtension(type, id); +} + +WebRtc_Word32 ModuleRtpRtcpImpl::DeregisterReceiveRtpHeaderExtension( + const RTPExtensionType type) +{ + return _rtpReceiver.DeregisterRtpHeaderExtension(type); +} + /* * (TMMBR) Temporary Max Media Bit Rate */ diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/src/modules/rtp_rtcp/source/rtp_rtcp_impl.h index 5da611ade1..358af9cd6b 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_impl.h +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_impl.h @@ -97,6 +97,14 @@ public: virtual WebRtc_Word32 DeRegisterReceivePayload( const WebRtc_Word8 payloadType); + // register RTP header extension + virtual WebRtc_Word32 RegisterReceiveRtpHeaderExtension( + const RTPExtensionType type, + const WebRtc_UWord8 id); + + virtual WebRtc_Word32 DeregisterReceiveRtpHeaderExtension( + const RTPExtensionType type); + // get the currently configured SSRC filter virtual WebRtc_Word32 SSRCFilter(WebRtc_UWord32& allowedSSRC) const; @@ -166,6 +174,14 @@ public: virtual WebRtc_Word8 SendPayloadType() const; + // register RTP header extension + virtual WebRtc_Word32 RegisterSendRtpHeaderExtension( + const RTPExtensionType type, + const WebRtc_UWord8 id); + + virtual WebRtc_Word32 DeregisterSendRtpHeaderExtension( + const RTPExtensionType type); + // get start timestamp virtual WebRtc_UWord32 StartTimestamp() const; @@ -289,10 +305,12 @@ public: WebRtc_UWord32 *bytesReceived, WebRtc_UWord32 *packetsReceived) const; - virtual WebRtc_Word32 ReportBlockStatistics(WebRtc_UWord8 *fraction_lost, - WebRtc_UWord32 *cum_lost, - WebRtc_UWord32 *ext_max, - WebRtc_UWord32 *jitter); + virtual WebRtc_Word32 ReportBlockStatistics( + WebRtc_UWord8 *fraction_lost, + WebRtc_UWord32 *cum_lost, + WebRtc_UWord32 *ext_max, + WebRtc_UWord32 *jitter, + WebRtc_UWord32 *jitter_transmission_time_offset); // Get received RTCP report, sender info virtual WebRtc_Word32 RemoteRTCPStat( RTCPSenderInfo* senderInfo); @@ -317,6 +335,14 @@ public: virtual WebRtc_Word32 SetREMBData(const WebRtc_UWord32 bitrate, const WebRtc_UWord8 numberOfSSRC, const WebRtc_UWord32* SSRC); + + /* + * (IJ) Extended jitter report. + */ + virtual bool IJ() const; + + virtual WebRtc_Word32 SetIJStatus(const bool enable); + /* * (TMMBR) Temporary Max Media Bit Rate */ diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi index 3e511c8fa6..ec11396476 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi @@ -24,6 +24,9 @@ 'rtp_format_vp8_unittest.cc', 'rtcp_format_remb_unittest.cc', 'rtp_utility_test.cc', + 'rtp_header_extension_test.cc', + 'rtp_sender_test.cc', + 'rtcp_sender_test.cc', ], }, ], diff --git a/src/modules/rtp_rtcp/source/rtp_sender.cc b/src/modules/rtp_rtcp/source/rtp_sender.cc index 2af0dbfe34..87e8223422 100644 --- a/src/modules/rtp_rtcp/source/rtp_sender.cc +++ b/src/modules/rtp_rtcp/source/rtp_sender.cc @@ -41,6 +41,9 @@ RTPSender::RTPSender(const WebRtc_Word32 id, _payloadType(-1), _payloadTypeMap(), + _rtpHeaderExtensionMap(), + _transmissionTimeOffset(0), + _keepAliveIsActive(false), _keepAlivePayloadType(-1), _keepAliveLastSent(0), @@ -176,6 +179,8 @@ RTPSender::Init(const WebRtc_UWord32 remoteSSRC) _keepAlivePayloadType = -1; + _rtpHeaderExtensionMap.Erase(); + bool loop = true; do { @@ -264,6 +269,42 @@ RTPSender::NackOverheadRate() const { return _nackBitrate.BitrateLast(); } +WebRtc_Word32 +RTPSender::SetTransmissionTimeOffset( + const WebRtc_Word32 transmissionTimeOffset) +{ + if (transmissionTimeOffset > (0x800000 - 1) || + transmissionTimeOffset < -(0x800000 - 1)) // Word24 + { + return -1; + } + CriticalSectionScoped cs(_sendCritsect); + _transmissionTimeOffset = transmissionTimeOffset; + return 0; +} + +WebRtc_Word32 +RTPSender::RegisterRtpHeaderExtension(const RTPExtensionType type, + const WebRtc_UWord8 id) +{ + CriticalSectionScoped cs(_sendCritsect); + return _rtpHeaderExtensionMap.Register(type, id); +} + +WebRtc_Word32 +RTPSender::DeregisterRtpHeaderExtension(const RTPExtensionType type) +{ + CriticalSectionScoped cs(_sendCritsect); + return _rtpHeaderExtensionMap.Deregister(type); +} + +WebRtc_UWord16 +RTPSender::RtpHeaderExtensionTotalLength() const +{ + CriticalSectionScoped cs(_sendCritsect); + return _rtpHeaderExtensionMap.GetTotalLengthInBytes(); +} + //can be called multiple times WebRtc_Word32 RTPSender::RegisterPayload(const WebRtc_Word8 payloadName[RTP_PAYLOAD_NAME_SIZE], @@ -1114,6 +1155,8 @@ RTPSender::RTPHeaderLength() const { rtpHeaderLength += sizeof(WebRtc_UWord32)*_CSRCs; } + rtpHeaderLength += RtpHeaderExtensionTotalLength(); + return rtpHeaderLength; } @@ -1209,9 +1252,108 @@ RTPSender::BuildRTPheader(WebRtc_UWord8* dataBuffer, _sequenceNumber++; // prepare for next packet } + WebRtc_UWord16 len = BuildRTPHeaderExtension(dataBuffer + rtpHeaderLength); + if (len) + { + dataBuffer[0] |= 0x10; // set eXtension bit + rtpHeaderLength += len; + } + return rtpHeaderLength; } +WebRtc_UWord16 +RTPSender::BuildRTPHeaderExtension(WebRtc_UWord8* dataBuffer) const +{ + if (_rtpHeaderExtensionMap.Size() <= 0) { + return 0; + } + + /* RTP header extension, RFC 3550. + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | defined by profile | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | header extension | + | .... | + */ + + const WebRtc_UWord32 kPosLength = 2; + const WebRtc_UWord32 kHeaderLength = RTP_ONE_BYTE_HEADER_LENGTH_IN_BYTES; + + // Add extension ID (0xBEDE). + ModuleRTPUtility::AssignUWord16ToBuffer(dataBuffer, + RTP_ONE_BYTE_HEADER_EXTENSION); + + // Add extensions. + WebRtc_UWord16 total_block_length = 0; + + RTPExtensionType type = _rtpHeaderExtensionMap.First(); + while (type != NONE) + { + WebRtc_UWord8 block_length = 0; + if (type == TRANSMISSION_TIME_OFFSET) + { + block_length = BuildTransmissionTimeOffsetExtension( + dataBuffer + kHeaderLength + total_block_length); + } + total_block_length += block_length; + type = _rtpHeaderExtensionMap.Next(type); + } + + if (total_block_length == 0) + { + // No extension added. + return 0; + } + + // Set header length (in number of Word32, header excluded). + assert(total_block_length % 4 == 0); + ModuleRTPUtility::AssignUWord16ToBuffer(dataBuffer + kPosLength, + total_block_length / 4); + + // Total added length. + return kHeaderLength + total_block_length; +} + +WebRtc_UWord8 +RTPSender::BuildTransmissionTimeOffsetExtension(WebRtc_UWord8* dataBuffer) const +{ + // From RFC 5450: Transmission Time Offsets in RTP Streams. + // + // The transmission time is signaled to the receiver in-band using the + // general mechanism for RTP header extensions [RFC5285]. The payload + // of this extension (the transmitted value) is a 24-bit signed integer. + // When added to the RTP timestamp of the packet, it represents the + // "effective" RTP transmission time of the packet, on the RTP + // timescale. + // + // The form of the transmission offset extension block: + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | ID | len=2 | transmission offset | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // Get id defined by user. + WebRtc_UWord8 id; + if (_rtpHeaderExtensionMap.GetId(TRANSMISSION_TIME_OFFSET, &id) != 0) { + // Not registered. + return 0; + } + + int pos = 0; + const WebRtc_UWord8 len = 2; + dataBuffer[pos++] = (id << 4) + len; + ModuleRTPUtility::AssignUWord24ToBuffer(dataBuffer + pos, + _transmissionTimeOffset); + pos += 3; + assert(pos == TRANSMISSION_TIME_OFFSET_LENGTH_IN_BYTES); + return TRANSMISSION_TIME_OFFSET_LENGTH_IN_BYTES; +} + WebRtc_Word32 RTPSender::RegisterSendTransport(Transport* transport) { diff --git a/src/modules/rtp_rtcp/source/rtp_sender.h b/src/modules/rtp_rtcp/source/rtp_sender.h index 0d47926220..ff19a6d84f 100644 --- a/src/modules/rtp_rtcp/source/rtp_sender.h +++ b/src/modules/rtp_rtcp/source/rtp_sender.h @@ -12,11 +12,13 @@ #define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_SENDER_H_ #include "rtp_rtcp_config.h" // misc. defines (e.g. MAX_PACKET_LENGTH) +#include "rtp_rtcp_defines.h" #include "common_types.h" // Encryption #include "ssrc_database.h" #include "list_wrapper.h" #include "map_wrapper.h" #include "Bitrate.h" +#include "rtp_header_extension.h" #include "video_codec_information.h" #include @@ -139,6 +141,24 @@ public: VideoCodecInformation* codecInfo = NULL, const RTPVideoTypeHeader* rtpTypeHdr = NULL); + /* + * RTP header extension + */ + WebRtc_Word32 SetTransmissionTimeOffset( + const WebRtc_Word32 transmissionTimeOffset); + + WebRtc_Word32 RegisterRtpHeaderExtension(const RTPExtensionType type, + const WebRtc_UWord8 id); + + WebRtc_Word32 DeregisterRtpHeaderExtension(const RTPExtensionType type); + + WebRtc_UWord16 RtpHeaderExtensionTotalLength() const; + + WebRtc_UWord16 BuildRTPHeaderExtension(WebRtc_UWord8* dataBuffer) const; + + WebRtc_UWord8 BuildTransmissionTimeOffsetExtension( + WebRtc_UWord8* dataBuffer) const; + /* * NACK */ @@ -270,7 +290,7 @@ private: CriticalSectionWrapper* _sendCritsect; CriticalSectionWrapper* _transportCritsect; - Transport* _transport; + Transport* _transport; bool _sendingMedia; @@ -281,6 +301,9 @@ private: WebRtc_Word8 _payloadType; MapWrapper _payloadTypeMap; + RtpHeaderExtensionMap _rtpHeaderExtensionMap; + WebRtc_Word32 _transmissionTimeOffset; + bool _keepAliveIsActive; WebRtc_Word8 _keepAlivePayloadType; WebRtc_UWord32 _keepAliveLastSent; @@ -305,13 +328,13 @@ private: WebRtc_UWord32 _payloadBytesSent; // RTP variables - bool _startTimeStampForced; + bool _startTimeStampForced; WebRtc_UWord32 _startTimeStamp; - SSRCDatabase& _ssrcDB; + SSRCDatabase& _ssrcDB; WebRtc_UWord32 _remoteSSRC; - bool _sequenceNumberForced; + bool _sequenceNumberForced; WebRtc_UWord16 _sequenceNumber; - bool _ssrcForced; + bool _ssrcForced; WebRtc_UWord32 _ssrc; WebRtc_UWord32 _timeStamp; WebRtc_UWord8 _CSRCs; diff --git a/src/modules/rtp_rtcp/source/rtp_sender_test.cc b/src/modules/rtp_rtcp/source/rtp_sender_test.cc new file mode 100644 index 0000000000..b2cc39c1f8 --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtp_sender_test.cc @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * This file includes unit tests for the RTPSender. + */ + +#include + +#include "rtp_header_extension.h" +#include "rtp_rtcp_defines.h" +#include "rtp_sender.h" +#include "rtp_utility.h" +#include "typedefs.h" + +namespace webrtc { + +class RtpSenderTest : public ::testing::Test { + protected: + RtpSenderTest() + : rtp_sender_(new RTPSender(0, false, ModuleRTPUtility::GetSystemClock())), + kMarkerBit(true), + kType(TRANSMISSION_TIME_OFFSET) { + EXPECT_EQ(0, rtp_sender_->SetSequenceNumber(kSeqNum)); + } + ~RtpSenderTest() { + delete rtp_sender_; + } + + RTPSender* rtp_sender_; + const bool kMarkerBit; + RTPExtensionType kType; + enum {kId = 1}; + enum {kTypeLength = TRANSMISSION_TIME_OFFSET_LENGTH_IN_BYTES}; + enum {kPayload = 100}; + enum {kTimestamp = 10}; + enum {kSeqNum = 33}; + enum {kTimeOffset = 22222}; + enum {kMaxPacketLength = 1500}; + uint8_t packet_[kMaxPacketLength]; + + void VerifyRTPHeaderCommon(const WebRtcRTPHeader& rtp_header) { + EXPECT_EQ(kMarkerBit, rtp_header.header.markerBit); + EXPECT_EQ(kPayload, rtp_header.header.payloadType); + EXPECT_EQ(kSeqNum, rtp_header.header.sequenceNumber); + EXPECT_EQ(kTimestamp, rtp_header.header.timestamp); + EXPECT_EQ(rtp_sender_->SSRC(), rtp_header.header.ssrc); + EXPECT_EQ(0, rtp_header.header.numCSRCs); + EXPECT_EQ(0, rtp_header.header.paddingLength); + } +}; + +TEST_F(RtpSenderTest, RegisterRtpHeaderExtension) { + EXPECT_EQ(0, rtp_sender_->RtpHeaderExtensionTotalLength()); + EXPECT_EQ(0, rtp_sender_->RegisterRtpHeaderExtension(kType, kId)); + EXPECT_EQ(RTP_ONE_BYTE_HEADER_LENGTH_IN_BYTES + kTypeLength, + rtp_sender_->RtpHeaderExtensionTotalLength()); + EXPECT_EQ(0, rtp_sender_->DeregisterRtpHeaderExtension(kType)); + EXPECT_EQ(0, rtp_sender_->RtpHeaderExtensionTotalLength()); +} + +TEST_F(RtpSenderTest, BuildRTPPacket) { + WebRtc_Word32 length = rtp_sender_->BuildRTPheader(packet_, + kPayload, + kMarkerBit, + kTimestamp); + EXPECT_EQ(12, length); + + // Verify + webrtc::ModuleRTPUtility::RTPHeaderParser rtpParser(packet_, length); + webrtc::WebRtcRTPHeader rtp_header; + + RtpHeaderExtensionMap map; + map.Register(kType, kId); + const bool valid_rtp_header = rtpParser.Parse(rtp_header, &map); + + ASSERT_TRUE(valid_rtp_header); + ASSERT_FALSE(rtpParser.RTCP()); + VerifyRTPHeaderCommon(rtp_header); + EXPECT_EQ(length, rtp_header.header.headerLength); + EXPECT_EQ(0, rtp_header.extension.transmissionTimeOffset); +} + +TEST_F(RtpSenderTest, BuildRTPPacketWithExtension) { + EXPECT_EQ(0, rtp_sender_->SetTransmissionTimeOffset(kTimeOffset)); + EXPECT_EQ(0, rtp_sender_->RegisterRtpHeaderExtension(kType, kId)); + + WebRtc_Word32 length = rtp_sender_->BuildRTPheader(packet_, + kPayload, + kMarkerBit, + kTimestamp); + EXPECT_EQ(12 + rtp_sender_->RtpHeaderExtensionTotalLength(), length); + + // Verify + webrtc::ModuleRTPUtility::RTPHeaderParser rtpParser(packet_, length); + webrtc::WebRtcRTPHeader rtp_header; + + RtpHeaderExtensionMap map; + map.Register(kType, kId); + const bool valid_rtp_header = rtpParser.Parse(rtp_header, &map); + + ASSERT_TRUE(valid_rtp_header); + ASSERT_FALSE(rtpParser.RTCP()); + VerifyRTPHeaderCommon(rtp_header); + EXPECT_EQ(length, rtp_header.header.headerLength); + EXPECT_EQ(kTimeOffset, rtp_header.extension.transmissionTimeOffset); + + // Parse without map extension + webrtc::WebRtcRTPHeader rtp_header2; + const bool valid_rtp_header2 = rtpParser.Parse(rtp_header2, NULL); + + ASSERT_TRUE(valid_rtp_header2); + VerifyRTPHeaderCommon(rtp_header2); + EXPECT_EQ(length, rtp_header2.header.headerLength); + EXPECT_EQ(0, rtp_header2.extension.transmissionTimeOffset); +} +} // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/rtp_utility.cc b/src/modules/rtp_rtcp/source/rtp_utility.cc index 8e419c9869..f77a99d61c 100644 --- a/src/modules/rtp_rtcp/source/rtp_utility.cc +++ b/src/modules/rtp_rtcp/source/rtp_utility.cc @@ -507,10 +507,10 @@ ModuleRTPUtility::RTPHeaderParser::RTCP() const RTCP = true; break; case 193: - case 195: // not supported // pass through and check for a potential RTP packet break; + case 195: case 200: case 201: case 202: @@ -526,7 +526,8 @@ ModuleRTPUtility::RTPHeaderParser::RTCP() const } bool -ModuleRTPUtility::RTPHeaderParser::Parse(WebRtcRTPHeader& parsedPacket) const +ModuleRTPUtility::RTPHeaderParser::Parse( + WebRtcRTPHeader& parsedPacket, RtpHeaderExtensionMap* ptrExtensionMap) const { const ptrdiff_t length = _ptrRTPDataEnd - _ptrRTPDataBegin; @@ -588,8 +589,22 @@ ModuleRTPUtility::RTPHeaderParser::Parse(WebRtcRTPHeader& parsedPacket) const parsedPacket.type.Audio.numEnergy = parsedPacket.header.numCSRCs; parsedPacket.header.headerLength = 12 + CSRCocts; + + // If in effect, MAY be omitted for those packets for which the offset + // is zero. + parsedPacket.extension.transmissionTimeOffset = 0; + if (X) { + /* RTP header extension, RFC 3550. + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | defined by profile | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | header extension | + | .... | + */ const ptrdiff_t remain = _ptrRTPDataEnd - ptr; if (remain < 4) { @@ -609,27 +624,13 @@ ModuleRTPUtility::RTPHeaderParser::Parse(WebRtcRTPHeader& parsedPacket) const { return false; } - if(definedByProfile == RTP_AUDIO_LEVEL_UNIQUE_ID && XLen == 4) + if (definedByProfile == RTP_ONE_BYTE_HEADER_EXTENSION) { - // --- Only used for debugging --- - - /* - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | 0xBE | 0xDE | length=1 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | ID | len=0 |V| level | 0x00 | 0x00 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - - // Parse out the fields but only use it for debugging for now. - //const WebRtc_UWord8 ID = (*ptr & 0xf0) >> 4; - //const WebRtc_UWord8 len = (*ptr & 0x0f); - ptr++; - //const WebRtc_UWord8 V = (*ptr & 0x80) >> 7; - //const WebRtc_UWord8 level = (*ptr & 0x7f); - // DEBUG_PRINT("RTP_AUDIO_LEVEL_UNIQUE_ID: ID=%u, len=%u, V=%u, level=%u", ID, len, V, level); + const WebRtc_UWord8* ptrRTPDataExtensionEnd = ptr + XLen; + ParseOneByteExtensionHeader(parsedPacket, + ptrExtensionMap, + ptrRTPDataExtensionEnd, + ptr); } parsedPacket.header.headerLength += XLen; } @@ -637,6 +638,107 @@ ModuleRTPUtility::RTPHeaderParser::Parse(WebRtcRTPHeader& parsedPacket) const return true; } +void ModuleRTPUtility::RTPHeaderParser::ParseOneByteExtensionHeader( + WebRtcRTPHeader& parsedPacket, + const RtpHeaderExtensionMap* ptrExtensionMap, + const WebRtc_UWord8* ptrRTPDataExtensionEnd, + const WebRtc_UWord8* ptr) const +{ + if (!ptrExtensionMap) { + WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, "No extension map."); + return; + } + + while (ptrRTPDataExtensionEnd - ptr > 0) + { + // 0 + // 0 1 2 3 4 5 6 7 + // +-+-+-+-+-+-+-+-+ + // | ID | len | + // +-+-+-+-+-+-+-+-+ + + const WebRtc_UWord8 id = (*ptr & 0xf0) >> 4; + const WebRtc_UWord8 len = (*ptr & 0x0f); + ptr++; + + if (id == 15) { + WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, + "Ext id: 15 encountered, parsing terminated."); + return; + } + + RTPExtensionType type; + if (ptrExtensionMap->GetType(id, &type) != 0) { + WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, + "Failed to find extension id: %d", id); + return; + } + + switch (type) + { + case TRANSMISSION_TIME_OFFSET: + { + if (len != 2) + { + WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, + "Incorrect transmission time offset len: %d", len); + return; + } + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | ID | len=2 | transmission offset | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + WebRtc_Word32 transmissionTimeOffset = *ptr++ << 16; + transmissionTimeOffset += *ptr++ << 8; + transmissionTimeOffset += *ptr++; + parsedPacket.extension.transmissionTimeOffset = transmissionTimeOffset; + break; + } + //case RTP_AUDIO_LEVEL_ID; + //{ + // --- Only used for debugging --- + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | ID | len=0 |V| level | 0x00 | 0x00 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + + // Parse out the fields but only use it for debugging for now. + //const WebRtc_UWord8 V = (*ptr & 0x80) >> 7; + //const WebRtc_UWord8 level = (*ptr & 0x7f); + //DEBUG_PRINT("RTP_AUDIO_LEVEL_UNIQUE_ID: ID=%u, len=%u, V=%u, level=%u", + // ID, len, V, level); + //} + default: + { + WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, + "Extension type not implemented."); + return; + } + } + WebRtc_UWord8 num_bytes = ParsePaddingBytes(ptrRTPDataExtensionEnd, ptr); + ptr += num_bytes; + } +} + +WebRtc_UWord8 ModuleRTPUtility::RTPHeaderParser::ParsePaddingBytes( + const WebRtc_UWord8* ptrRTPDataExtensionEnd, + const WebRtc_UWord8* ptr) const { + + WebRtc_UWord8 num_zero_bytes = 0; + while (ptrRTPDataExtensionEnd - ptr > 0) { + if (*ptr != 0) { + return num_zero_bytes; + } + ptr++; + num_zero_bytes++; + } + return num_zero_bytes; +} + // RTP payload parser ModuleRTPUtility::RTPPayloadParser::RTPPayloadParser( const RtpVideoCodecTypes videoType, diff --git a/src/modules/rtp_rtcp/source/rtp_utility.h b/src/modules/rtp_rtcp/source/rtp_utility.h index 929b3296aa..3ce3537a7f 100644 --- a/src/modules/rtp_rtcp/source/rtp_utility.h +++ b/src/modules/rtp_rtcp/source/rtp_utility.h @@ -14,6 +14,7 @@ #include // size_t, ptrdiff_t #include "typedefs.h" +#include "rtp_header_extension.h" #include "rtp_rtcp_config.h" #include "rtp_rtcp_defines.h" @@ -118,10 +119,21 @@ namespace ModuleRTPUtility const WebRtc_UWord32 rtpDataLength); ~RTPHeaderParser(); - bool RTCP( ) const; - bool Parse( WebRtcRTPHeader& parsedPacket) const; + bool RTCP() const; + bool Parse(WebRtcRTPHeader& parsedPacket, + RtpHeaderExtensionMap* ptrExtensionMap = NULL) const; private: + void ParseOneByteExtensionHeader( + WebRtcRTPHeader& parsedPacket, + const RtpHeaderExtensionMap* ptrExtensionMap, + const WebRtc_UWord8* ptrRTPDataExtensionEnd, + const WebRtc_UWord8* ptr) const; + + WebRtc_UWord8 ParsePaddingBytes( + const WebRtc_UWord8* ptrRTPDataExtensionEnd, + const WebRtc_UWord8* ptr) const; + const WebRtc_UWord8* const _ptrRTPDataBegin; const WebRtc_UWord8* const _ptrRTPDataEnd; };