From e7f056ec458c5dce06884ff23b812f2b2572a61c Mon Sep 17 00:00:00 2001 From: "pbos@webrtc.org" Date: Mon, 19 Aug 2013 16:09:34 +0000 Subject: [PATCH] Implementation and testing of PLI in new API. BUG=2174 R=mflodman@webrtc.org Review URL: https://webrtc-codereview.appspot.com/2011004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4567 4adac7df-926f-26a2-2b94-8c16560cd09d --- .../internal/video_receive_stream.cc | 2 + .../test/common/rtp_rtcp_observer.h | 137 ++++++ webrtc/video_engine/test/engine_tests.cc | 428 +++++++++++------- webrtc/video_engine/test/tests.gypi | 1 + 4 files changed, 413 insertions(+), 155 deletions(-) create mode 100644 webrtc/video_engine/test/common/rtp_rtcp_observer.h diff --git a/webrtc/video_engine/internal/video_receive_stream.cc b/webrtc/video_engine/internal/video_receive_stream.cc index 6f23e9c278..ebd94a7b18 100644 --- a/webrtc/video_engine/internal/video_receive_stream.cc +++ b/webrtc/video_engine/internal/video_receive_stream.cc @@ -41,6 +41,8 @@ VideoReceiveStream::VideoReceiveStream( // TODO(pbos): This is not fine grained enough... rtp_rtcp_->SetNACKStatus(channel_, config_.rtp.nack.rtp_history_ms > 0); + rtp_rtcp_->SetKeyFrameRequestMethod(channel_, + kViEKeyFrameRequestPliRtcp); assert(config_.rtp.ssrc != 0); diff --git a/webrtc/video_engine/test/common/rtp_rtcp_observer.h b/webrtc/video_engine/test/common/rtp_rtcp_observer.h new file mode 100644 index 0000000000..2bc0865326 --- /dev/null +++ b/webrtc/video_engine/test/common/rtp_rtcp_observer.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2013 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_VIDEO_ENGINE_TEST_COMMON_RTP_RTCP_OBSERVER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_RTP_RTCP_OBSERVER_H_ + +#include +#include + +#include "webrtc/typedefs.h" +#include "webrtc/video_engine/new_include/video_send_stream.h" + +namespace webrtc { +namespace test { + +class RtpRtcpObserver { + public: + newapi::Transport* SendTransport() { + return &send_transport_; + } + + newapi::Transport* ReceiveTransport() { + return &receive_transport_; + } + + void SetReceivers(newapi::PacketReceiver* send_transport_receiver, + newapi::PacketReceiver* receive_transport_receiver) { + send_transport_.SetReceiver(send_transport_receiver); + receive_transport_.SetReceiver(receive_transport_receiver); + } + + void StopSending() { + send_transport_.StopSending(); + receive_transport_.StopSending(); + } + + protected: + RtpRtcpObserver() + : lock_(CriticalSectionWrapper::CreateCriticalSection()), + send_transport_(lock_.get(), + this, + &RtpRtcpObserver::OnSendRtp, + &RtpRtcpObserver::OnSendRtcp), + receive_transport_(lock_.get(), + this, + &RtpRtcpObserver::OnReceiveRtp, + &RtpRtcpObserver::OnReceiveRtcp) {} + + enum Action { + SEND_PACKET, + DROP_PACKET, + }; + + virtual Action OnSendRtp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + virtual Action OnSendRtcp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + virtual Action OnReceiveRtp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + virtual Action OnReceiveRtcp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + + private: + class PacketTransport : public test::DirectTransport { + public: + typedef Action (RtpRtcpObserver::*PacketTransportAction)(const uint8_t*, + size_t); + PacketTransport(CriticalSectionWrapper* lock, + RtpRtcpObserver* observer, + PacketTransportAction on_rtp, + PacketTransportAction on_rtcp) + : lock_(lock), + observer_(observer), + on_rtp_(on_rtp), + on_rtcp_(on_rtcp) {} + + private: + virtual bool SendRTP(const uint8_t* packet, size_t length) OVERRIDE { + Action action; + { + CriticalSectionScoped crit_(lock_); + action = (observer_->*on_rtp_)(packet, length); + } + switch (action) { + case DROP_PACKET: + // Drop packet silently. + return true; + case SEND_PACKET: + return test::DirectTransport::SendRTP(packet, length); + } + } + virtual bool SendRTCP(const uint8_t* packet, size_t length) OVERRIDE { + Action action; + { + CriticalSectionScoped crit_(lock_); + action = (observer_->*on_rtcp_)(packet, length); + } + switch (action) { + case DROP_PACKET: + // Drop packet silently. + return true; + case SEND_PACKET: + return test::DirectTransport::SendRTCP(packet, length); + } + } + + // Pointer to shared lock instance protecting on_rtp_/on_rtcp_ calls. + CriticalSectionWrapper* lock_; + + RtpRtcpObserver* observer_; + PacketTransportAction on_rtp_, on_rtcp_; + }; + + protected: + scoped_ptr lock_; + + private: + PacketTransport send_transport_, receive_transport_; +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_RTP_RTCP_OBSERVER_H_ diff --git a/webrtc/video_engine/test/engine_tests.cc b/webrtc/video_engine/test/engine_tests.cc index 195581e9ff..d99ce0273a 100644 --- a/webrtc/video_engine/test/engine_tests.cc +++ b/webrtc/video_engine/test/engine_tests.cc @@ -7,6 +7,8 @@ * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ +#include + #include #include "testing/gtest/include/gtest/gtest.h" @@ -21,86 +23,131 @@ #include "webrtc/video_engine/test/common/frame_generator.h" #include "webrtc/video_engine/test/common/frame_generator_capturer.h" #include "webrtc/video_engine/test/common/generate_ssrcs.h" +#include "webrtc/video_engine/test/common/rtp_rtcp_observer.h" namespace webrtc { -class NackObserver { +struct EngineTestParams { + size_t width, height; + struct { + unsigned int min, start, max; + } bitrate; +}; + +class EngineTest : public ::testing::TestWithParam { public: - class SenderTransport : public test::DirectTransport { - public: - explicit SenderTransport(NackObserver* observer) : observer_(observer) {} + EngineTest() : send_stream_(NULL), receive_stream_(NULL) {} - virtual bool SendRTP(const uint8_t* packet, size_t length) OVERRIDE { - { - CriticalSectionScoped lock(observer_->crit_.get()); - if (observer_->DropSendPacket(packet, length)) - return true; - ++observer_->sent_rtp_packets_; - } + ~EngineTest() { + EXPECT_EQ(NULL, send_stream_); + EXPECT_EQ(NULL, receive_stream_); + } - return test::DirectTransport::SendRTP(packet, length); - } + protected: + void CreateCalls(newapi::Transport* sender_transport, + newapi::Transport* receiver_transport) { + newapi::VideoCall::Config sender_config(sender_transport); + newapi::VideoCall::Config receiver_config(receiver_transport); + sender_call_.reset(newapi::VideoCall::Create(sender_config)); + receiver_call_.reset(newapi::VideoCall::Create(receiver_config)); + } - NackObserver* observer_; - } sender_transport_; + void CreateTestConfigs() { + EngineTestParams params = GetParam(); + send_config_ = sender_call_->GetDefaultSendConfig(); + receive_config_ = receiver_call_->GetDefaultReceiveConfig(); - class ReceiverTransport : public test::DirectTransport { - public: - explicit ReceiverTransport(NackObserver* observer) : observer_(observer) {} + test::GenerateRandomSsrcs(&send_config_, &reserved_ssrcs_); + send_config_.codec.width = static_cast(params.width); + send_config_.codec.height = static_cast(params.height); + send_config_.codec.minBitrate = params.bitrate.min; + send_config_.codec.startBitrate = params.bitrate.start; + send_config_.codec.maxBitrate = params.bitrate.max; - bool SendRTCP(const uint8_t* packet, size_t length) { - { - CriticalSectionScoped lock(observer_->crit_.get()); + receive_config_.rtp.ssrc = send_config_.rtp.ssrcs[0]; + } - RTCPUtility::RTCPParserV2 parser(packet, length, true); - EXPECT_TRUE(parser.IsValid()); + void CreateStreams() { + assert(send_stream_ == NULL); + assert(receive_stream_ == NULL); - bool received_nack = false; - RTCPUtility::RTCPPacketTypes packet_type = parser.Begin(); - while (packet_type != RTCPUtility::kRtcpNotValidCode) { - if (packet_type == RTCPUtility::kRtcpRtpfbNackCode) - received_nack = true; + send_stream_ = sender_call_->CreateSendStream(send_config_); + receive_stream_ = receiver_call_->CreateReceiveStream(receive_config_); + } - packet_type = parser.Iterate(); - } + void CreateFrameGenerator() { + EngineTestParams params = GetParam(); + frame_generator_capturer_.reset(test::FrameGeneratorCapturer::Create( + send_stream_->Input(), + test::FrameGenerator::Create( + params.width, params.height, Clock::GetRealTimeClock()), + 30)); + } - if (received_nack) { - observer_->ReceivedNack(); - } else { - observer_->RtcpWithoutNack(); - } - } - return DirectTransport::SendRTCP(packet, length); - } + void StartSending() { + receive_stream_->StartReceive(); + send_stream_->StartSend(); + frame_generator_capturer_->Start(); + } - NackObserver* observer_; - } receiver_transport_; + void StopSending() { + frame_generator_capturer_->Stop(); + send_stream_->StopSend(); + receive_stream_->StopReceive(); + } + void DestroyStreams() { + sender_call_->DestroySendStream(send_stream_); + receiver_call_->DestroyReceiveStream(receive_stream_); + send_stream_= NULL; + receive_stream_ = NULL; + } + + void ReceivesPliAndRecovers(int rtp_history_ms); + + scoped_ptr sender_call_; + scoped_ptr receiver_call_; + + newapi::VideoSendStream::Config send_config_; + newapi::VideoReceiveStream::Config receive_config_; + + newapi::VideoSendStream* send_stream_; + newapi::VideoReceiveStream* receive_stream_; + + scoped_ptr frame_generator_capturer_; + + std::map reserved_ssrcs_; +}; + +// TODO(pbos): What are sane values here for bitrate? Are we missing any +// important resolutions? +EngineTestParams video_1080p = {1920, 1080, {300, 600, 800}}; +EngineTestParams video_720p = {1280, 720, {300, 600, 800}}; +EngineTestParams video_vga = {640, 480, {300, 600, 800}}; +EngineTestParams video_qvga = {320, 240, {300, 600, 800}}; +EngineTestParams video_4cif = {704, 576, {300, 600, 800}}; +EngineTestParams video_cif = {352, 288, {300, 600, 800}}; +EngineTestParams video_qcif = {176, 144, {300, 600, 800}}; + +class NackObserver : public test::RtpRtcpObserver { + static const int kNumberOfNacksToObserve = 4; + static const int kInverseProbabilityToStartLossBurst = 20; + static const int kMaxLossBurst = 10; + public: NackObserver() - : sender_transport_(this), - receiver_transport_(this), - crit_(CriticalSectionWrapper::CreateCriticalSection()), - received_all_retransmissions_(EventWrapper::Create()), + : received_all_retransmissions_(EventWrapper::Create()), rtp_parser_(RtpHeaderParser::Create()), drop_burst_count_(0), sent_rtp_packets_(0), - nacks_left_(4) {} + nacks_left_(kNumberOfNacksToObserve) {} EventTypeWrapper Wait() { // 2 minutes should be more than enough time for the test to finish. return received_all_retransmissions_->Wait(2 * 60 * 1000); } - void StopSending() { - sender_transport_.StopSending(); - receiver_transport_.StopSending(); - } - private: - // Decides whether a current packet should be dropped or not. A retransmitted - // packet will never be dropped. Packets are dropped in short bursts. When - // enough NACKs have been received, no further packets are dropped. - bool DropSendPacket(const uint8_t* packet, size_t length) { + virtual Action OnSendRtp(const uint8_t* packet, size_t length) OVERRIDE { EXPECT_FALSE(RtpHeaderParser::IsRtcp(packet, static_cast(length))); RTPHeader header; @@ -110,29 +157,56 @@ class NackObserver { if (dropped_packets_.find(header.sequenceNumber) != dropped_packets_.end()) { retransmitted_packets_.insert(header.sequenceNumber); - return false; + return SEND_PACKET; } // Enough NACKs received, stop dropping packets. - if (nacks_left_ == 0) - return false; + if (nacks_left_ == 0) { + ++sent_rtp_packets_; + return SEND_PACKET; + } // Still dropping packets. if (drop_burst_count_ > 0) { --drop_burst_count_; dropped_packets_.insert(header.sequenceNumber); - return true; + return DROP_PACKET; } - if (sent_rtp_packets_ > 0 && rand() % 20 == 0) { - drop_burst_count_ = rand() % 10; + // Should we start dropping packets? + if (sent_rtp_packets_ > 0 && + rand() % kInverseProbabilityToStartLossBurst == 0) { + drop_burst_count_ = rand() % kMaxLossBurst; dropped_packets_.insert(header.sequenceNumber); - return true; + return DROP_PACKET; } - return false; + ++sent_rtp_packets_; + return SEND_PACKET; } + virtual Action OnReceiveRtcp(const uint8_t* packet, size_t length) OVERRIDE { + RTCPUtility::RTCPParserV2 parser(packet, length, true); + EXPECT_TRUE(parser.IsValid()); + + bool received_nack = false; + RTCPUtility::RTCPPacketTypes packet_type = parser.Begin(); + while (packet_type != RTCPUtility::kRtcpNotValidCode) { + if (packet_type == RTCPUtility::kRtcpRtpfbNackCode) + received_nack = true; + + packet_type = parser.Iterate(); + } + + if (received_nack) { + ReceivedNack(); + } else { + RtcpWithoutNack(); + } + return SEND_PACKET; + } + + private: void ReceivedNack() { if (nacks_left_ > 0) --nacks_left_; @@ -151,7 +225,6 @@ class NackObserver { } } - scoped_ptr crit_; scoped_ptr received_all_retransmissions_; scoped_ptr rtp_parser_; @@ -164,111 +237,156 @@ class NackObserver { static const int kRequiredRtcpsWithoutNack = 2; }; -struct EngineTestParams { - size_t width, height; - struct { - unsigned int min, start, max; - } bitrate; -}; - -class EngineTest : public ::testing::TestWithParam { - public: - virtual void SetUp() { - reserved_ssrcs_.clear(); - } - - protected: - newapi::VideoCall* CreateTestCall(newapi::Transport* transport) { - newapi::VideoCall::Config call_config(transport); - return newapi::VideoCall::Create(call_config); - } - - newapi::VideoSendStream::Config CreateTestSendConfig( - newapi::VideoCall* call, - EngineTestParams params) { - newapi::VideoSendStream::Config config = call->GetDefaultSendConfig(); - - test::GenerateRandomSsrcs(&config, &reserved_ssrcs_); - - config.codec.width = static_cast(params.width); - config.codec.height = static_cast(params.height); - config.codec.minBitrate = params.bitrate.min; - config.codec.startBitrate = params.bitrate.start; - config.codec.maxBitrate = params.bitrate.max; - - return config; - } - - test::FrameGeneratorCapturer* CreateTestFrameGeneratorCapturer( - newapi::VideoSendStream* target, - EngineTestParams params) { - return test::FrameGeneratorCapturer::Create( - target->Input(), - test::FrameGenerator::Create( - params.width, params.height, Clock::GetRealTimeClock()), - 30); - } - - std::map reserved_ssrcs_; -}; - -// TODO(pbos): What are sane values here for bitrate? Are we missing any -// important resolutions? -EngineTestParams video_1080p = {1920, 1080, {300, 600, 800}}; -EngineTestParams video_720p = {1280, 720, {300, 600, 800}}; -EngineTestParams video_vga = {640, 480, {300, 600, 800}}; -EngineTestParams video_qvga = {320, 240, {300, 600, 800}}; -EngineTestParams video_4cif = {704, 576, {300, 600, 800}}; -EngineTestParams video_cif = {352, 288, {300, 600, 800}}; -EngineTestParams video_qcif = {176, 144, {300, 600, 800}}; - TEST_P(EngineTest, ReceivesAndRetransmitsNack) { - EngineTestParams params = GetParam(); - - // Set up a video call per sender and receiver. Both send RTCP, and have a set - // RTP history > 0 to enable NACK and retransmissions. NackObserver observer; - scoped_ptr sender_call( - CreateTestCall(&observer.sender_transport_)); - scoped_ptr receiver_call( - CreateTestCall(&observer.receiver_transport_)); + CreateCalls(observer.SendTransport(), observer.ReceiveTransport()); - observer.receiver_transport_.SetReceiver(sender_call->Receiver()); - observer.sender_transport_.SetReceiver(receiver_call->Receiver()); + observer.SetReceivers(receiver_call_->Receiver(), sender_call_->Receiver()); - newapi::VideoSendStream::Config send_config = - CreateTestSendConfig(sender_call.get(), params); - send_config.rtp.nack.rtp_history_ms = 1000; + CreateTestConfigs(); + int rtp_history_ms = 1000; + send_config_.rtp.nack.rtp_history_ms = rtp_history_ms; + receive_config_.rtp.nack.rtp_history_ms = rtp_history_ms; - newapi::VideoReceiveStream::Config receive_config = - receiver_call->GetDefaultReceiveConfig(); - receive_config.rtp.ssrc = send_config.rtp.ssrcs[0]; - receive_config.rtp.nack.rtp_history_ms = send_config.rtp.nack.rtp_history_ms; + CreateStreams(); + CreateFrameGenerator(); - newapi::VideoSendStream* send_stream = - sender_call->CreateSendStream(send_config); - newapi::VideoReceiveStream* receive_stream = - receiver_call->CreateReceiveStream(receive_config); - - scoped_ptr frame_generator_capturer( - CreateTestFrameGeneratorCapturer(send_stream, params)); - ASSERT_TRUE(frame_generator_capturer.get() != NULL); - - receive_stream->StartReceive(); - send_stream->StartSend(); - frame_generator_capturer->Start(); + StartSending(); + // Wait() waits for an event triggered when NACKs have been received, NACKed + // packets retransmitted and frames rendered again. EXPECT_EQ(kEventSignaled, observer.Wait()); - frame_generator_capturer->Stop(); - send_stream->StopSend(); - receive_stream->StopReceive(); + StopSending(); + + DestroyStreams(); - receiver_call->DestroyReceiveStream(receive_stream); - receiver_call->DestroySendStream(send_stream); observer.StopSending(); } +class PliObserver : public test::RtpRtcpObserver { + static const int kInverseDropProbability = 16; + public: + PliObserver(bool nack_enabled) : + renderer_(this), + rtp_header_parser_(RtpHeaderParser::Create()), + nack_enabled_(nack_enabled), + first_retransmitted_timestamp_(0), + last_send_timestamp_(0), + rendered_frame_(false), + received_pli_(false) {} + + virtual Action OnSendRtp(const uint8_t* packet, size_t length) OVERRIDE { + RTPHeader header; + EXPECT_TRUE(rtp_header_parser_->Parse(packet, length, &header)); + + // Drop all NACK retransmissions. This is to force transmission of a PLI. + if (header.timestamp < last_send_timestamp_) + return DROP_PACKET; + + if (received_pli_) { + if (first_retransmitted_timestamp_ == 0) { + first_retransmitted_timestamp_ = header.timestamp; + } + } else if (rendered_frame_ && rand() % kInverseDropProbability == 0) { + return DROP_PACKET; + } + + last_send_timestamp_ = header.timestamp; + return SEND_PACKET; + } + + virtual Action OnReceiveRtcp(const uint8_t* packet, size_t length) OVERRIDE { + RTCPUtility::RTCPParserV2 parser(packet, length, true); + EXPECT_TRUE(parser.IsValid()); + + for (RTCPUtility::RTCPPacketTypes packet_type = parser.Begin(); + packet_type != RTCPUtility::kRtcpNotValidCode; + packet_type = parser.Iterate()) { + if (!nack_enabled_) + EXPECT_NE(packet_type, RTCPUtility::kRtcpRtpfbNackCode); + + if (packet_type == RTCPUtility::kRtcpPsfbPliCode) { + received_pli_ = true; + break; + } + } + return SEND_PACKET; + } + + class ReceiverRenderer : public newapi::VideoRenderer { + public: + ReceiverRenderer(PliObserver* observer) + : rendered_retransmission_(EventWrapper::Create()), + observer_(observer) {} + + virtual void RenderFrame(const I420VideoFrame& video_frame, + int time_to_render_ms) { + CriticalSectionScoped crit_(observer_->lock_.get()); + if (observer_->first_retransmitted_timestamp_ != 0 && + video_frame.timestamp() > observer_->first_retransmitted_timestamp_) { + EXPECT_TRUE(observer_->received_pli_); + rendered_retransmission_->Set(); + } + observer_->rendered_frame_ = true; + } + scoped_ptr rendered_retransmission_; + PliObserver* observer_; + } renderer_; + + EventTypeWrapper Wait() { + // 120 seconds should be plenty of time. + return renderer_.rendered_retransmission_->Wait(2 * 60 * 1000); + } + + private: + scoped_ptr rtp_header_parser_; + bool nack_enabled_; + + uint32_t first_retransmitted_timestamp_; + uint32_t last_send_timestamp_; + + bool rendered_frame_; + bool received_pli_; +}; + +void EngineTest::ReceivesPliAndRecovers(int rtp_history_ms) { + PliObserver observer(rtp_history_ms > 0); + + CreateCalls(observer.SendTransport(), observer.ReceiveTransport()); + + observer.SetReceivers(receiver_call_->Receiver(), sender_call_->Receiver()); + + CreateTestConfigs(); + send_config_.rtp.nack.rtp_history_ms = rtp_history_ms; + receive_config_.rtp.nack.rtp_history_ms = rtp_history_ms; + receive_config_.renderer = &observer.renderer_; + + CreateStreams(); + CreateFrameGenerator(); + + StartSending(); + + // Wait() waits for an event triggered when Pli has been received and frames + // have been rendered afterwards. + EXPECT_EQ(kEventSignaled, observer.Wait()); + + StopSending(); + + DestroyStreams(); + + observer.StopSending(); +} + +TEST_P(EngineTest, ReceivesPliAndRecoversWithNack) { + ReceivesPliAndRecovers(1000); +} + +// TODO(pbos): Enable this when 2250 is resolved. +TEST_P(EngineTest, DISABLED_ReceivesPliAndRecoversWithoutNack) { + ReceivesPliAndRecovers(0); +} + INSTANTIATE_TEST_CASE_P(EngineTest, EngineTest, ::testing::Values(video_vga)); } // namespace webrtc diff --git a/webrtc/video_engine/test/tests.gypi b/webrtc/video_engine/test/tests.gypi index df0cad7bb9..486f7686d3 100644 --- a/webrtc/video_engine/test/tests.gypi +++ b/webrtc/video_engine/test/tests.gypi @@ -32,6 +32,7 @@ 'common/mac/video_renderer_mac.h', 'common/mac/video_renderer_mac.mm', 'common/null_platform_renderer.cc', + 'common/rtp_rtcp_observer.h', 'common/run_tests.cc', 'common/run_tests.h', 'common/run_loop.cc',