diff options
author | Torne (Richard Coles) <torne@google.com> | 2014-06-20 14:52:04 +0100 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2014-06-20 14:52:04 +0100 |
commit | f8ee788a64d60abd8f2d742a5fdedde054ecd910 (patch) | |
tree | 7dc14380200b953c64e0ccd16435cdbd1dbf1205 /media/cast | |
parent | fcbbbe23a38088a52492922075e71a419c4b01ec (diff) | |
download | chromium_org-f8ee788a64d60abd8f2d742a5fdedde054ecd910.tar.gz |
Merge from Chromium at DEPS revision 278205
This commit was generated by merge_to_master.py.
Change-Id: I23f1e7ea8c154ba72e7fb594436216f861f868ab
Diffstat (limited to 'media/cast')
31 files changed, 1673 insertions, 523 deletions
diff --git a/media/cast/audio_sender/audio_sender.cc b/media/cast/audio_sender/audio_sender.cc index f5183e957d..27b42d0dc8 100644 --- a/media/cast/audio_sender/audio_sender.cc +++ b/media/cast/audio_sender/audio_sender.cc @@ -106,7 +106,7 @@ void AudioSender::SendEncodedAudioFrame( void AudioSender::ResendPackets( const MissingFramesAndPacketsMap& missing_frames_and_packets) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - transport_sender_->ResendPackets(true, missing_frames_and_packets); + transport_sender_->ResendPackets(true, missing_frames_and_packets, false); } void AudioSender::IncomingRtcpPacket(scoped_ptr<Packet> packet) { diff --git a/media/cast/cast_defines.h b/media/cast/cast_defines.h index bf6df39120..64b20c96da 100644 --- a/media/cast/cast_defines.h +++ b/media/cast/cast_defines.h @@ -68,14 +68,22 @@ enum PacketType { kTooOldPacket, }; +// kRtcpCastAllPacketsLost is used in PacketIDSet and +// on the wire to mean that ALL packets for a particular +// frame are lost. const uint16 kRtcpCastAllPacketsLost = 0xffff; +// kRtcpCastLastPacket is used in PacketIDSet to ask for +// the last packet of a frame to be retransmitted. +const uint16 kRtcpCastLastPacket = 0xfffe; + const size_t kMinLengthOfRtcp = 8; // Basic RTP header + cast header. const size_t kMinLengthOfRtp = 12 + 6; // Each uint16 represents one packet id within a cast frame. +// Can also contain kRtcpCastAllPacketsLost and kRtcpCastLastPacket. typedef std::set<uint16> PacketIdSet; // Each uint8 represents one cast frame. typedef std::map<uint8, PacketIdSet> MissingFramesAndPacketsMap; diff --git a/media/cast/cast_testing.gypi b/media/cast/cast_testing.gypi index ff6bb57e43..aef0fbd8c3 100644 --- a/media/cast/cast_testing.gypi +++ b/media/cast/cast_testing.gypi @@ -124,6 +124,42 @@ ], # source }, { + 'target_name': 'cast_benchmarks', + 'type': '<(gtest_target_type)', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + 'cast_receiver', + 'cast_rtcp', + 'cast_sender', + 'cast_test_utility', + 'cast_transport', + '<(DEPTH)/base/base.gyp:test_support_base', + '<(DEPTH)/net/net.gyp:net', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + 'sources': [ + 'test/cast_benchmarks.cc', + 'test/fake_single_thread_task_runner.cc', + 'test/fake_single_thread_task_runner.h', + 'test/fake_video_encode_accelerator.cc', + 'test/fake_video_encode_accelerator.h', + 'test/utility/test_util.cc', + 'test/utility/test_util.h', + ], # source + 'conditions': [ + ['os_posix==1 and OS!="mac" and OS!="ios" and use_allocator!="none"', + { + 'dependencies': [ + '<(DEPTH)/base/allocator/allocator.gyp:allocator', + ], + } + ], + ], + }, + { # This is a target for the collection of cast development tools. # They are built on bots but not shipped. 'target_name': 'cast_tools', diff --git a/media/cast/congestion_control/congestion_control.cc b/media/cast/congestion_control/congestion_control.cc index 39d68b39f0..d24e0ac3d0 100644 --- a/media/cast/congestion_control/congestion_control.cc +++ b/media/cast/congestion_control/congestion_control.cc @@ -2,6 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// The purpose of this file is determine what bitrate to use for mirroring. +// Ideally this should be as much as possible, without causing any frames to +// arrive late. + +// The current algorithm is to measure how much bandwidth we've been using +// recently. We also keep track of how much data has been queued up for sending +// in a virtual "buffer" (this virtual buffer represents all the buffers between +// the sender and the receiver, including retransmissions and so forth.) +// If we estimate that our virtual buffer is mostly empty, we try to use +// more bandwidth than our recent usage, otherwise we use less. + #include "media/cast/congestion_control/congestion_control.h" #include "base/logging.h" @@ -11,112 +22,176 @@ namespace media { namespace cast { -static const int64 kCongestionControlMinChangeIntervalMs = 10; -static const int64 kCongestionControlMaxChangeIntervalMs = 100; +// This means that we *try* to keep our buffer 90% empty. +// If it is less full, we increase the bandwidth, if it is more +// we decrease the bandwidth. Making this smaller makes the +// congestion control more aggressive. +static const double kTargetEmptyBufferFraction = 0.9; -// At 10 ms RTT TCP Reno would ramp 1500 * 8 * 100 = 1200 Kbit/s. -// NACK is sent after a maximum of 10 ms. -static const int kCongestionControlMaxBitrateIncreasePerMillisecond = 1200; +// This is the size of our history in frames. Larger values makes the +// congestion control adapt slower. +static const size_t kHistorySize = 100; -static const int64 kMaxElapsedTimeMs = kCongestionControlMaxChangeIntervalMs; +CongestionControl::FrameStats::FrameStats() : frame_size(0) { +} CongestionControl::CongestionControl(base::TickClock* clock, - float congestion_control_back_off, uint32 max_bitrate_configured, uint32 min_bitrate_configured, - uint32 start_bitrate) + size_t max_unacked_frames) : clock_(clock), - congestion_control_back_off_(congestion_control_back_off), max_bitrate_configured_(max_bitrate_configured), min_bitrate_configured_(min_bitrate_configured), - bitrate_(start_bitrate) { - DCHECK_GT(congestion_control_back_off, 0.0f) << "Invalid config"; - DCHECK_LT(congestion_control_back_off, 1.0f) << "Invalid config"; + last_frame_stats_(static_cast<uint32>(-1)), + last_acked_frame_(static_cast<uint32>(-1)), + last_encoded_frame_(static_cast<uint32>(-1)), + history_size_(max_unacked_frames + kHistorySize), + acked_bits_in_history_(0) { DCHECK_GE(max_bitrate_configured, min_bitrate_configured) << "Invalid config"; - DCHECK_GE(max_bitrate_configured, start_bitrate) << "Invalid config"; - DCHECK_GE(start_bitrate, min_bitrate_configured) << "Invalid config"; + frame_stats_.resize(2); + base::TimeTicks now = clock->NowTicks(); + frame_stats_[0].ack_time = now; + frame_stats_[0].sent_time = now; + frame_stats_[1].ack_time = now; + DCHECK(!frame_stats_[0].ack_time.is_null()); } CongestionControl::~CongestionControl() {} -bool CongestionControl::OnAck(base::TimeDelta rtt, uint32* new_bitrate) { - base::TimeTicks now = clock_->NowTicks(); +void CongestionControl::UpdateRtt(base::TimeDelta rtt) { + rtt_ = base::TimeDelta::FromSecondsD( + (rtt_.InSecondsF() * 7 + rtt.InSecondsF()) / 8); +} + +// Calculate how much "dead air" there is between two frames. +base::TimeDelta CongestionControl::DeadTime(const FrameStats& a, + const FrameStats& b) { + if (b.sent_time > a.ack_time) { + return b.sent_time - a.ack_time; + } else { + return base::TimeDelta(); + } +} + +double CongestionControl::CalculateSafeBitrate() { + double transmit_time = + (GetFrameStats(last_acked_frame_)->ack_time - + frame_stats_.front().sent_time - dead_time_in_history_).InSecondsF(); + + if (acked_bits_in_history_ == 0 || transmit_time <= 0.0) { + return min_bitrate_configured_; + } + return acked_bits_in_history_ / std::max(transmit_time, 1E-3); +} + +CongestionControl::FrameStats* CongestionControl::GetFrameStats( + uint32 frame_id) { + int32 offset = static_cast<int32>(frame_id - last_frame_stats_); + DCHECK_LT(offset, static_cast<int32>(kHistorySize)); + if (offset > 0) { + frame_stats_.resize(frame_stats_.size() + offset); + last_frame_stats_ += offset; + offset = 0; + } + while (frame_stats_.size() > history_size_) { + DCHECK_GT(frame_stats_.size(), 1UL); + DCHECK(!frame_stats_[0].ack_time.is_null()); + acked_bits_in_history_ -= frame_stats_[0].frame_size; + dead_time_in_history_ -= DeadTime(frame_stats_[0], frame_stats_[1]); + DCHECK_GE(acked_bits_in_history_, 0UL); + VLOG(2) << "DT: " << dead_time_in_history_.InSecondsF(); + DCHECK_GE(dead_time_in_history_.InSecondsF(), 0.0); + frame_stats_.pop_front(); + } + offset += frame_stats_.size() - 1; + if (offset < 0 || offset >= static_cast<int32>(frame_stats_.size())) { + return NULL; + } + return &frame_stats_[offset]; +} - // First feedback? - if (time_last_increase_.is_null()) { - time_last_increase_ = now; - time_last_decrease_ = now; - return false; +void CongestionControl::AckFrame(uint32 frame_id, base::TimeTicks when) { + FrameStats* frame_stats = GetFrameStats(last_acked_frame_); + while (IsNewerFrameId(frame_id, last_acked_frame_)) { + FrameStats* last_frame_stats = frame_stats; + last_acked_frame_++; + frame_stats = GetFrameStats(last_acked_frame_); + DCHECK(frame_stats); + frame_stats->ack_time = when; + acked_bits_in_history_ += frame_stats->frame_size; + dead_time_in_history_ += DeadTime(*last_frame_stats, *frame_stats); } - // Are we at the max bitrate? - if (max_bitrate_configured_ == bitrate_) - return false; - - // Make sure RTT is never less than 1 ms. - rtt = std::max(rtt, base::TimeDelta::FromMilliseconds(1)); - - base::TimeDelta elapsed_time = - std::min(now - time_last_increase_, - base::TimeDelta::FromMilliseconds(kMaxElapsedTimeMs)); - base::TimeDelta change_interval = std::max( - rtt, - base::TimeDelta::FromMilliseconds(kCongestionControlMinChangeIntervalMs)); - change_interval = std::min( - change_interval, - base::TimeDelta::FromMilliseconds(kCongestionControlMaxChangeIntervalMs)); - - // Have enough time have passed? - if (elapsed_time < change_interval) - return false; - - time_last_increase_ = now; - - // One packet per RTT multiplied by the elapsed time fraction. - // 1500 * 8 * (1000 / rtt_ms) * (elapsed_time_ms / 1000) => - // 1500 * 8 * elapsed_time_ms / rtt_ms. - uint32 bitrate_increase = - (1500 * 8 * elapsed_time.InMilliseconds()) / rtt.InMilliseconds(); - uint32 max_bitrate_increase = - kCongestionControlMaxBitrateIncreasePerMillisecond * - elapsed_time.InMilliseconds(); - bitrate_increase = std::min(max_bitrate_increase, bitrate_increase); - *new_bitrate = std::min(bitrate_increase + bitrate_, max_bitrate_configured_); - bitrate_ = *new_bitrate; - return true; } -bool CongestionControl::OnNack(base::TimeDelta rtt, uint32* new_bitrate) { - base::TimeTicks now = clock_->NowTicks(); +void CongestionControl::SendFrameToTransport(uint32 frame_id, + size_t frame_size, + base::TimeTicks when) { + last_encoded_frame_ = frame_id; + FrameStats* frame_stats = GetFrameStats(frame_id); + DCHECK(frame_stats); + frame_stats->frame_size = frame_size; + frame_stats->sent_time = when; +} - // First feedback? - if (time_last_decrease_.is_null()) { - time_last_increase_ = now; - time_last_decrease_ = now; - return false; +base::TimeTicks CongestionControl::EstimatedAckTime(uint32 frame_id, + double bitrate) { + FrameStats* frame_stats = GetFrameStats(frame_id); + DCHECK(frame_stats); + if (frame_stats->ack_time.is_null()) { + DCHECK(frame_stats->frame_size) << "frame_id: " << frame_id; + base::TimeTicks ret = EstimatedSendingTime(frame_id, bitrate); + ret += base::TimeDelta::FromSecondsD(frame_stats->frame_size / bitrate); + ret += rtt_; + base::TimeTicks now = clock_->NowTicks(); + if (ret < now) { + // This is a little counter-intuitive, but it seems to work. + // Basically, when we estimate that the ACK should have already happened, + // we figure out how long ago it should have happened and guess that the + // ACK will happen half of that time in the future. This will cause some + // over-estimation when acks are late, which is actually what we want. + return now + (now - ret) / 2; + } else { + return ret; + } + } else { + return frame_stats->ack_time; } - base::TimeDelta elapsed_time = - std::min(now - time_last_decrease_, - base::TimeDelta::FromMilliseconds(kMaxElapsedTimeMs)); - base::TimeDelta change_interval = std::max( - rtt, - base::TimeDelta::FromMilliseconds(kCongestionControlMinChangeIntervalMs)); - change_interval = std::min( - change_interval, - base::TimeDelta::FromMilliseconds(kCongestionControlMaxChangeIntervalMs)); - - // Have enough time have passed? - if (elapsed_time < change_interval) - return false; - - time_last_decrease_ = now; - time_last_increase_ = now; - - *new_bitrate = - std::max(static_cast<uint32>(bitrate_ * congestion_control_back_off_), - min_bitrate_configured_); - - bitrate_ = *new_bitrate; - return true; +} + +base::TimeTicks CongestionControl::EstimatedSendingTime(uint32 frame_id, + double bitrate) { + FrameStats* frame_stats = GetFrameStats(frame_id); + DCHECK(frame_stats); + base::TimeTicks ret = EstimatedAckTime(frame_id - 1, bitrate) - rtt_; + if (frame_stats->sent_time.is_null()) { + // Not sent yet, but we can't start sending it in the past. + return std::max(ret, clock_->NowTicks()); + } else { + return std::max(ret, frame_stats->sent_time); + } +} + +uint32 CongestionControl::GetBitrate(base::TimeTicks playout_time, + base::TimeDelta playout_delay) { + double safe_bitrate = CalculateSafeBitrate(); + // Estimate when we might start sending the next frame. + base::TimeDelta time_to_catch_up = + playout_time - + EstimatedSendingTime(last_encoded_frame_ + 1, safe_bitrate); + + double empty_buffer_fraction = + time_to_catch_up.InSecondsF() / playout_delay.InSecondsF(); + empty_buffer_fraction = std::min(empty_buffer_fraction, 1.0); + empty_buffer_fraction = std::max(empty_buffer_fraction, 0.0); + + uint32 bits_per_second = static_cast<uint32>( + safe_bitrate * empty_buffer_fraction / kTargetEmptyBufferFraction); + VLOG(3) << " FBR:" << (bits_per_second / 1E6) + << " EBF:" << empty_buffer_fraction + << " SBR:" << (safe_bitrate / 1E6); + bits_per_second = std::max(bits_per_second, min_bitrate_configured_); + bits_per_second = std::min(bits_per_second, max_bitrate_configured_); + return bits_per_second; } } // namespace cast diff --git a/media/cast/congestion_control/congestion_control.h b/media/cast/congestion_control/congestion_control.h index 236300a36b..54622ab114 100644 --- a/media/cast/congestion_control/congestion_control.h +++ b/media/cast/congestion_control/congestion_control.h @@ -5,6 +5,8 @@ #ifndef MEDIA_CAST_CONGESTION_CONTROL_CONGESTION_CONTROL_H_ #define MEDIA_CAST_CONGESTION_CONTROL_CONGESTION_CONTROL_H_ +#include <deque> + #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/time/tick_clock.h" @@ -16,28 +18,65 @@ namespace cast { class CongestionControl { public: CongestionControl(base::TickClock* clock, - float congestion_control_back_off, uint32 max_bitrate_configured, uint32 min_bitrate_configured, - uint32 start_bitrate); + size_t max_unacked_frames); virtual ~CongestionControl(); - // Don't call OnAck if the same message contain a NACK. - // Returns true if the bitrate have changed. - bool OnAck(base::TimeDelta rtt_ms, uint32* new_bitrate); + void UpdateRtt(base::TimeDelta rtt); + + // Called when an encoded frame is sent to the transport. + void SendFrameToTransport(uint32 frame_id, + size_t frame_size, + base::TimeTicks when); + + // Called when we receive an ACK for a frame. + void AckFrame(uint32 frame_id, base::TimeTicks when); - // Returns true if the bitrate have changed. - bool OnNack(base::TimeDelta rtt_ms, uint32* new_bitrate); + // Returns the bitrate we should use for the next frame. + uint32 GetBitrate(base::TimeTicks playout_time, + base::TimeDelta playout_delay); private: + struct FrameStats { + FrameStats(); + // Time this frame was sent to the transport. + base::TimeTicks sent_time; + // Time this frame was acked. + base::TimeTicks ack_time; + // Size of encoded frame in bits. + size_t frame_size; + }; + + // Calculate how much "dead air" (idle time) there is between two frames. + static base::TimeDelta DeadTime(const FrameStats& a, const FrameStats& b); + // Get the FrameStats for a given |frame_id|. + // Note: Older FrameStats will be removed automatically. + FrameStats* GetFrameStats(uint32 frame_id); + // Calculata safe bitrate. This is based on how much we've been + // sending in the past. + double CalculateSafeBitrate(); + + // For a given frame, calculate when it might be acked. + // (Or return the time it was acked, if it was.) + base::TimeTicks EstimatedAckTime(uint32 frame_id, double bitrate); + // Calculate when we start sending the data for a given frame. + // This is done by calculating when we were done sending the previous + // frame, but obvoiusly can't be less than |sent_time| (if known). + base::TimeTicks EstimatedSendingTime(uint32 frame_id, double bitrate); + base::TickClock* const clock_; // Not owned by this class. - const float congestion_control_back_off_; const uint32 max_bitrate_configured_; const uint32 min_bitrate_configured_; - uint32 bitrate_; - base::TimeTicks time_last_increase_; - base::TimeTicks time_last_decrease_; + std::deque<FrameStats> frame_stats_; + uint32 last_frame_stats_; + uint32 last_acked_frame_; + uint32 last_encoded_frame_; + base::TimeDelta rtt_; + size_t history_size_; + size_t acked_bits_in_history_; + base::TimeDelta dead_time_in_history_; DISALLOW_COPY_AND_ASSIGN(CongestionControl); }; diff --git a/media/cast/congestion_control/congestion_control_unittest.cc b/media/cast/congestion_control/congestion_control_unittest.cc index 20e023e1e4..5745eab21d 100644 --- a/media/cast/congestion_control/congestion_control_unittest.cc +++ b/media/cast/congestion_control/congestion_control_unittest.cc @@ -7,6 +7,7 @@ #include "base/test/simple_test_tick_clock.h" #include "media/cast/cast_defines.h" #include "media/cast/congestion_control/congestion_control.h" +#include "media/cast/test/fake_single_thread_task_runner.h" #include "testing/gtest/include/gtest/gtest.h" namespace media { @@ -14,168 +15,106 @@ namespace cast { static const uint32 kMaxBitrateConfigured = 5000000; static const uint32 kMinBitrateConfigured = 500000; -static const uint32 kStartBitrate = 2000000; static const int64 kStartMillisecond = INT64_C(12345678900000); -static const int64 kRttMs = 20; -static const int64 kAckRateMs = 33; +static const double kTargetEmptyBufferFraction = 0.9; class CongestionControlTest : public ::testing::Test { protected: CongestionControlTest() - : congestion_control_(&testing_clock_, - kDefaultCongestionControlBackOff, - kMaxBitrateConfigured, - kMinBitrateConfigured, - kStartBitrate) { + : task_runner_(new test::FakeSingleThreadTaskRunner(&testing_clock_)) { testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kStartMillisecond)); + congestion_control_.reset(new CongestionControl( + &testing_clock_, kMaxBitrateConfigured, kMinBitrateConfigured, 10)); } - // Returns the last bitrate of the run. - uint32 RunWithOneLossEventPerSecond(int fps, - int rtt_ms, - int runtime_in_seconds) { - const base::TimeDelta rtt = base::TimeDelta::FromMilliseconds(rtt_ms); - const base::TimeDelta ack_rate = - base::TimeDelta::FromMilliseconds(INT64_C(1000) / fps); - uint32 new_bitrate = 0; - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - - for (int seconds = 0; seconds < runtime_in_seconds; ++seconds) { - for (int i = 1; i < fps; ++i) { - testing_clock_.Advance(ack_rate); - congestion_control_.OnAck(rtt, &new_bitrate); - } - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); + void AckFrame(uint32 frame_id) { + congestion_control_->AckFrame(frame_id, testing_clock_.NowTicks()); + } + + void Run(uint32 frames, + size_t frame_size, + base::TimeDelta rtt, + base::TimeDelta frame_delay, + base::TimeDelta ack_time) { + for (frame_id_ = 0; frame_id_ < frames; frame_id_++) { + congestion_control_->UpdateRtt(rtt); + congestion_control_->SendFrameToTransport( + frame_id_, frame_size, testing_clock_.NowTicks()); + task_runner_->PostDelayedTask(FROM_HERE, + base::Bind(&CongestionControlTest::AckFrame, + base::Unretained(this), + frame_id_), + ack_time); + task_runner_->Sleep(frame_delay); } - return new_bitrate; } base::SimpleTestTickClock testing_clock_; - CongestionControl congestion_control_; + scoped_ptr<CongestionControl> congestion_control_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + uint32 frame_id_; DISALLOW_COPY_AND_ASSIGN(CongestionControlTest); }; -TEST_F(CongestionControlTest, Max) { - uint32 new_bitrate = 0; - const base::TimeDelta rtt = base::TimeDelta::FromMilliseconds(kRttMs); - const base::TimeDelta ack_rate = - base::TimeDelta::FromMilliseconds(kAckRateMs); - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - - uint32 expected_increase_bitrate = 0; - - // Expected time is 5 seconds. 500000 - 2000000 = 5 * 1500 * 8 * (1000 / 20). - for (int i = 0; i < 151; ++i) { - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_increase_bitrate += 1500 * 8 * kAckRateMs / kRttMs; - EXPECT_EQ(kStartBitrate + expected_increase_bitrate, new_bitrate); - } - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - EXPECT_EQ(kMaxBitrateConfigured, new_bitrate); -} - -TEST_F(CongestionControlTest, Min) { - uint32 new_bitrate = 0; - const base::TimeDelta rtt = base::TimeDelta::FromMilliseconds(kRttMs); - const base::TimeDelta ack_rate = - base::TimeDelta::FromMilliseconds(kAckRateMs); - EXPECT_FALSE(congestion_control_.OnNack(rtt, &new_bitrate)); - - uint32 expected_decrease_bitrate = kStartBitrate; - - // Expected number is 10. 2000 * 0.875^10 <= 500. - for (int i = 0; i < 10; ++i) { - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); - expected_decrease_bitrate = static_cast<uint32>( - expected_decrease_bitrate * kDefaultCongestionControlBackOff); - EXPECT_EQ(expected_decrease_bitrate, new_bitrate); - } - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); - EXPECT_EQ(kMinBitrateConfigured, new_bitrate); -} - -TEST_F(CongestionControlTest, Timing) { - const base::TimeDelta rtt = base::TimeDelta::FromMilliseconds(kRttMs); - const base::TimeDelta ack_rate = - base::TimeDelta::FromMilliseconds(kAckRateMs); - uint32 new_bitrate = 0; - uint32 expected_bitrate = kStartBitrate; - - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * kAckRateMs / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); - - // We should back immediately. - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); - expected_bitrate = - static_cast<uint32>(expected_bitrate * kDefaultCongestionControlBackOff); - EXPECT_EQ(expected_bitrate, new_bitrate); - - // Less than one RTT have passed don't back again. - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_FALSE(congestion_control_.OnNack(rtt, &new_bitrate)); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); - expected_bitrate = - static_cast<uint32>(expected_bitrate * kDefaultCongestionControlBackOff); - EXPECT_EQ(expected_bitrate, new_bitrate); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * 20 / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * 20 / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); - - // Test long elapsed time (300 ms). - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(300)); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * 100 / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); - - // Test many short elapsed time (1 ms). - for (int i = 0; i < 19; ++i) { - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(1)); - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - } - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(1)); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * 20 / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); -} - -TEST_F(CongestionControlTest, Convergence24fps) { - EXPECT_GE(RunWithOneLossEventPerSecond(24, kRttMs, 100), UINT32_C(3000000)); -} - -TEST_F(CongestionControlTest, Convergence24fpsLongRtt) { - EXPECT_GE(RunWithOneLossEventPerSecond(24, 100, 100), UINT32_C(500000)); -} - -TEST_F(CongestionControlTest, Convergence60fps) { - EXPECT_GE(RunWithOneLossEventPerSecond(60, kRttMs, 100), UINT32_C(3500000)); +TEST_F(CongestionControlTest, SimpleRun) { + uint32 frame_delay = 33; + uint32 frame_size = 10000 * 8; + Run(500, + frame_size, + base::TimeDelta::FromMilliseconds(10), + base::TimeDelta::FromMilliseconds(frame_delay), + base::TimeDelta::FromMilliseconds(45)); + // Empty the buffer. + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(100)); + + uint32 safe_bitrate = frame_size * 1000 / frame_delay; + uint32 bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(300), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR( + safe_bitrate / kTargetEmptyBufferFraction, bitrate, safe_bitrate * 0.05); + + bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(200), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 2 / 3, + bitrate, + safe_bitrate * 0.05); + + bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(100), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 1 / 3, + bitrate, + safe_bitrate * 0.05); + + // Add a large (100ms) frame. + congestion_control_->SendFrameToTransport( + frame_id_++, safe_bitrate * 100 / 1000, testing_clock_.NowTicks()); + + // Results should show that we have ~200ms to send + bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(300), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 2 / 3, + bitrate, + safe_bitrate * 0.05); + + // Add another large (100ms) frame. + congestion_control_->SendFrameToTransport( + frame_id_++, safe_bitrate * 100 / 1000, testing_clock_.NowTicks()); + + // Resulst should show that we have ~100ms to send + bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(300), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 1 / 3, + bitrate, + safe_bitrate * 0.05); } -TEST_F(CongestionControlTest, Convergence60fpsLongRtt) { - EXPECT_GE(RunWithOneLossEventPerSecond(60, 100, 100), UINT32_C(500000)); -} } // namespace cast } // namespace media diff --git a/media/cast/receiver/video_decoder.cc b/media/cast/receiver/video_decoder.cc index 21f49d89a5..6db3fd35f3 100644 --- a/media/cast/receiver/video_decoder.cc +++ b/media/cast/receiver/video_decoder.cc @@ -185,8 +185,8 @@ class VideoDecoder::FakeImpl : public VideoDecoder::ImplBase { virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE { base::JSONReader reader; - scoped_ptr<base::Value> values(reader.Read( - base::StringPiece(reinterpret_cast<char*>(data), len))); + scoped_ptr<base::Value> values( + reader.Read(base::StringPiece(reinterpret_cast<char*>(data)))); base::DictionaryValue* dict = NULL; values->GetAsDictionary(&dict); diff --git a/media/cast/rtcp/rtcp_receiver.cc b/media/cast/rtcp/rtcp_receiver.cc index 16395737fe..3be8e921c4 100644 --- a/media/cast/rtcp/rtcp_receiver.cc +++ b/media/cast/rtcp/rtcp_receiver.cc @@ -523,15 +523,15 @@ void RtcpReceiver::HandlePayloadSpecificCastNackItem( frame_it = ret.first; DCHECK(frame_it != missing_frames_and_packets->end()) << "Invalid state"; } - if (rtcp_field->cast_nack_item.packet_id == kRtcpCastAllPacketsLost) { + uint16 packet_id = rtcp_field->cast_nack_item.packet_id; + frame_it->second.insert(packet_id); + + if (packet_id == kRtcpCastAllPacketsLost) { // Special case all packets in a frame is missing. return; } - uint16 packet_id = rtcp_field->cast_nack_item.packet_id; uint8 bitmask = rtcp_field->cast_nack_item.bitmask; - frame_it->second.insert(packet_id); - if (bitmask) { for (int i = 1; i <= 8; ++i) { if (bitmask & 1) { diff --git a/media/cast/rtcp/rtcp_receiver_unittest.cc b/media/cast/rtcp/rtcp_receiver_unittest.cc index f898939b26..51026d1554 100644 --- a/media/cast/rtcp/rtcp_receiver_unittest.cc +++ b/media/cast/rtcp/rtcp_receiver_unittest.cc @@ -40,7 +40,8 @@ class SenderFeedbackCastVerification : public RtcpSenderFeedback { EXPECT_TRUE(frame_it != cast_feedback.missing_frames_and_packets_.end()); EXPECT_EQ(kLostFrameId, frame_it->first); - EXPECT_TRUE(frame_it->second.empty()); + EXPECT_EQ(frame_it->second.size(), 1UL); + EXPECT_EQ(*frame_it->second.begin(), kRtcpCastAllPacketsLost); ++frame_it; EXPECT_TRUE(frame_it != cast_feedback.missing_frames_and_packets_.end()); EXPECT_EQ(kFrameIdWithLostPackets, frame_it->first); diff --git a/media/cast/test/cast_benchmarks.cc b/media/cast/test/cast_benchmarks.cc new file mode 100644 index 0000000000..c3468a2cdb --- /dev/null +++ b/media/cast/test/cast_benchmarks.cc @@ -0,0 +1,770 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This program benchmarks the theoretical throughput of the cast library. +// It runs using a fake clock, simulated network and fake codecs. This allows +// tests to run much faster than real time. +// To run the program, run: +// $ ./out/Release/cast_benchmarks | tee benchmarkoutput.asc +// This may take a while, when it is done, you can view the data with +// meshlab by running: +// $ meshlab benchmarkoutput.asc +// After starting meshlab, turn on Render->Show Axis. The red axis will +// represent bandwidth (in megabits) the blue axis will be packet drop +// (in percent) and the green axis will be latency (in milliseconds). +// +// This program can also be used for profiling. On linux it has +// built-in support for this. Simply set the environment variable +// PROFILE_FILE before running it, like so: +// $ export PROFILE_FILE=cast_benchmark.profile +// Then after running the program, you can view the profile with: +// $ pprof ./out/Release/cast_benchmarks $PROFILE_FILE --gv + +#include <math.h> +#include <stdint.h> + +#include <map> +#include <vector> + +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/debug/profiler.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/threading/thread.h" +#include "base/time/tick_clock.h" +#include "media/base/audio_bus.h" +#include "media/base/video_frame.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_environment.h" +#include "media/cast/cast_receiver.h" +#include "media/cast/cast_sender.h" +#include "media/cast/logging/simple_event_subscriber.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/test/skewed_single_thread_task_runner.h" +#include "media/cast/test/skewed_tick_clock.h" +#include "media/cast/test/utility/audio_utility.h" +#include "media/cast/test/utility/default_config.h" +#include "media/cast/test/utility/test_util.h" +#include "media/cast/test/utility/udp_proxy.h" +#include "media/cast/test/utility/video_utility.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/cast_transport_sender.h" +#include "media/cast/transport/cast_transport_sender_impl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +namespace { + +static const int64 kStartMillisecond = INT64_C(1245); +static const int kAudioChannels = 2; +static const int kVideoHdWidth = 1280; +static const int kVideoHdHeight = 720; +static const int kTargetDelay = 300; + +// The tests are commonly implemented with |kFrameTimerMs| RunTask function; +// a normal video is 30 fps hence the 33 ms between frames. +static const int kFrameTimerMs = 33; + +void UpdateCastTransportStatus(transport::CastTransportStatus status) { + bool result = (status == transport::TRANSPORT_AUDIO_INITIALIZED || + status == transport::TRANSPORT_VIDEO_INITIALIZED); + EXPECT_TRUE(result); +} + +void AudioInitializationStatus(CastInitializationStatus status) { + EXPECT_EQ(STATUS_AUDIO_INITIALIZED, status); +} + +void VideoInitializationStatus(CastInitializationStatus status) { + EXPECT_EQ(STATUS_VIDEO_INITIALIZED, status); +} + +void IgnoreRawEvents(const std::vector<PacketEvent>& packet_events) { +} + +} // namespace + +// Shim that turns forwards packets from a test::PacketPipe to a +// PacketReceiverCallback. +class LoopBackPacketPipe : public test::PacketPipe { + public: + LoopBackPacketPipe(const transport::PacketReceiverCallback& packet_receiver) + : packet_receiver_(packet_receiver) {} + + virtual ~LoopBackPacketPipe() {} + + // PacketPipe implementations. + virtual void Send(scoped_ptr<transport::Packet> packet) OVERRIDE { + packet_receiver_.Run(packet.Pass()); + } + + private: + transport::PacketReceiverCallback packet_receiver_; +}; + +// Class that sends the packet direct from sender into the receiver with the +// ability to drop packets between the two. +// TODO(hubbe): Break this out and share code with end2end_unittest.cc +class LoopBackTransport : public transport::PacketSender { + public: + explicit LoopBackTransport(scoped_refptr<CastEnvironment> cast_environment) + : cast_environment_(cast_environment) {} + + void SetPacketReceiver( + const transport::PacketReceiverCallback& packet_receiver, + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + base::TickClock* clock) { + scoped_ptr<test::PacketPipe> loopback_pipe( + new LoopBackPacketPipe(packet_receiver)); + if (packet_pipe_) { + packet_pipe_->AppendToPipe(loopback_pipe.Pass()); + } else { + packet_pipe_ = loopback_pipe.Pass(); + } + packet_pipe_->InitOnIOThread(task_runner, clock); + } + + virtual bool SendPacket(transport::PacketRef packet, + const base::Closure& cb) OVERRIDE { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + scoped_ptr<Packet> packet_copy(new Packet(packet->data)); + packet_pipe_->Send(packet_copy.Pass()); + return true; + } + + void SetPacketPipe(scoped_ptr<test::PacketPipe> pipe) { + // Append the loopback pipe to the end. + pipe->AppendToPipe(packet_pipe_.Pass()); + packet_pipe_ = pipe.Pass(); + } + + private: + scoped_refptr<CastEnvironment> cast_environment_; + scoped_ptr<test::PacketPipe> packet_pipe_; +}; + +// Wraps a CastTransportSender and records some statistics about +// the data that goes through it. +class CastTransportSenderWrapper : public transport::CastTransportSender { + public: + // Takes ownership of |transport|. + void Init(CastTransportSender* transport, + uint64* encoded_video_bytes, + uint64* encoded_audio_bytes) { + transport_.reset(transport); + encoded_video_bytes_ = encoded_video_bytes; + encoded_audio_bytes_ = encoded_audio_bytes; + } + + virtual void InitializeAudio( + const transport::CastTransportAudioConfig& config) OVERRIDE { + transport_->InitializeAudio(config); + } + + virtual void InitializeVideo( + const transport::CastTransportVideoConfig& config) OVERRIDE { + transport_->InitializeVideo(config); + } + + virtual void SetPacketReceiver( + const transport::PacketReceiverCallback& packet_receiver) OVERRIDE { + transport_->SetPacketReceiver(packet_receiver); + } + + virtual void InsertCodedAudioFrame( + const transport::EncodedFrame& audio_frame) OVERRIDE { + *encoded_audio_bytes_ += audio_frame.data.size(); + transport_->InsertCodedAudioFrame(audio_frame); + } + + virtual void InsertCodedVideoFrame( + const transport::EncodedFrame& video_frame) OVERRIDE { + *encoded_video_bytes_ += video_frame.data.size(); + transport_->InsertCodedVideoFrame(video_frame); + } + + virtual void SendRtcpFromRtpSender(uint32 packet_type_flags, + uint32 ntp_seconds, + uint32 ntp_fraction, + uint32 rtp_timestamp, + const transport::RtcpDlrrReportBlock& dlrr, + uint32 sending_ssrc, + const std::string& c_name) OVERRIDE { + transport_->SendRtcpFromRtpSender(packet_type_flags, + ntp_seconds, + ntp_fraction, + rtp_timestamp, + dlrr, + sending_ssrc, + c_name); + } + + // Retransmission request. + virtual void ResendPackets( + bool is_audio, + const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list) OVERRIDE { + transport_->ResendPackets( + is_audio, missing_packets, cancel_rtx_if_not_in_list); + } + + private: + scoped_ptr<transport::CastTransportSender> transport_; + uint64* encoded_video_bytes_; + uint64* encoded_audio_bytes_; +}; + +struct MeasuringPoint { + MeasuringPoint(double bitrate_, double latency_, double percent_packet_drop_) + : bitrate(bitrate_), + latency(latency_), + percent_packet_drop(percent_packet_drop_) {} + bool operator<=(const MeasuringPoint& other) const { + return bitrate >= other.bitrate && latency <= other.latency && + percent_packet_drop <= other.percent_packet_drop; + } + bool operator>=(const MeasuringPoint& other) const { + return bitrate <= other.bitrate && latency >= other.latency && + percent_packet_drop >= other.percent_packet_drop; + } + + std::string AsString() const { + return base::StringPrintf( + "%f Mbit/s %f ms %f %% ", bitrate, latency, percent_packet_drop); + } + + double bitrate; + double latency; + double percent_packet_drop; +}; + +class RunOneBenchmark { + public: + RunOneBenchmark() + : start_time_(), + task_runner_(new test::FakeSingleThreadTaskRunner(&testing_clock_)), + testing_clock_sender_(new test::SkewedTickClock(&testing_clock_)), + task_runner_sender_( + new test::SkewedSingleThreadTaskRunner(task_runner_)), + testing_clock_receiver_(new test::SkewedTickClock(&testing_clock_)), + task_runner_receiver_( + new test::SkewedSingleThreadTaskRunner(task_runner_)), + cast_environment_sender_(new CastEnvironment( + scoped_ptr<base::TickClock>(testing_clock_sender_).Pass(), + task_runner_sender_, + task_runner_sender_, + task_runner_sender_)), + cast_environment_receiver_(new CastEnvironment( + scoped_ptr<base::TickClock>(testing_clock_receiver_).Pass(), + task_runner_receiver_, + task_runner_receiver_, + task_runner_receiver_)), + receiver_to_sender_(cast_environment_receiver_), + sender_to_receiver_(cast_environment_sender_), + video_bytes_encoded_(0), + audio_bytes_encoded_(0), + frames_sent_(0) { + testing_clock_.Advance( + base::TimeDelta::FromMilliseconds(kStartMillisecond)); + } + + void Configure(transport::VideoCodec video_codec, + transport::AudioCodec audio_codec, + int audio_sampling_frequency, + int max_number_of_video_buffers_used) { + audio_sender_config_.rtp_config.ssrc = 1; + audio_sender_config_.incoming_feedback_ssrc = 2; + audio_sender_config_.rtp_config.payload_type = 96; + audio_sender_config_.use_external_encoder = false; + audio_sender_config_.frequency = audio_sampling_frequency; + audio_sender_config_.channels = kAudioChannels; + audio_sender_config_.bitrate = kDefaultAudioEncoderBitrate; + audio_sender_config_.codec = audio_codec; + audio_sender_config_.rtp_config.max_delay_ms = kTargetDelay; + + audio_receiver_config_.feedback_ssrc = + audio_sender_config_.incoming_feedback_ssrc; + audio_receiver_config_.incoming_ssrc = audio_sender_config_.rtp_config.ssrc; + audio_receiver_config_.rtp_payload_type = + audio_sender_config_.rtp_config.payload_type; + audio_receiver_config_.frequency = audio_sender_config_.frequency; + audio_receiver_config_.channels = kAudioChannels; + audio_receiver_config_.max_frame_rate = 100; + audio_receiver_config_.codec.audio = audio_sender_config_.codec; + audio_receiver_config_.rtp_max_delay_ms = kTargetDelay; + + video_sender_config_.rtp_config.ssrc = 3; + video_sender_config_.incoming_feedback_ssrc = 4; + video_sender_config_.rtp_config.payload_type = 97; + video_sender_config_.use_external_encoder = false; + video_sender_config_.width = kVideoHdWidth; + video_sender_config_.height = kVideoHdHeight; +#if 0 + video_sender_config_.max_bitrate = 10000000; // 10Mbit max + video_sender_config_.min_bitrate = 1000000; // 1Mbit min + video_sender_config_.start_bitrate = 1000000; // 1Mbit start +#else + video_sender_config_.max_bitrate = 4000000; // 4Mbit all the time + video_sender_config_.min_bitrate = 4000000; + video_sender_config_.start_bitrate = 4000000; +#endif + video_sender_config_.max_qp = 56; + video_sender_config_.min_qp = 4; + video_sender_config_.max_frame_rate = 30; + video_sender_config_.max_number_of_video_buffers_used = + max_number_of_video_buffers_used; + video_sender_config_.codec = video_codec; + video_sender_config_.rtp_config.max_delay_ms = kTargetDelay; + + video_receiver_config_.feedback_ssrc = + video_sender_config_.incoming_feedback_ssrc; + video_receiver_config_.incoming_ssrc = video_sender_config_.rtp_config.ssrc; + video_receiver_config_.rtp_payload_type = + video_sender_config_.rtp_config.payload_type; + video_receiver_config_.codec.video = video_sender_config_.codec; + video_receiver_config_.frequency = kVideoFrequency; + video_receiver_config_.channels = 1; + video_receiver_config_.max_frame_rate = 100; + video_receiver_config_.rtp_max_delay_ms = kTargetDelay; + } + + void SetSenderClockSkew(double skew, base::TimeDelta offset) { + testing_clock_sender_->SetSkew(skew, offset); + task_runner_sender_->SetSkew(1.0 / skew); + } + + void SetReceiverClockSkew(double skew, base::TimeDelta offset) { + testing_clock_receiver_->SetSkew(skew, offset); + task_runner_receiver_->SetSkew(1.0 / skew); + } + + void Create() { + cast_receiver_ = CastReceiver::Create(cast_environment_receiver_, + audio_receiver_config_, + video_receiver_config_, + &receiver_to_sender_); + net::IPEndPoint dummy_endpoint; + transport_sender_.Init(new transport::CastTransportSenderImpl( + NULL, + testing_clock_sender_, + dummy_endpoint, + base::Bind(&UpdateCastTransportStatus), + base::Bind(&IgnoreRawEvents), + base::TimeDelta::FromSeconds(1), + task_runner_sender_, + &sender_to_receiver_), + &video_bytes_encoded_, + &audio_bytes_encoded_); + + cast_sender_ = + CastSender::Create(cast_environment_sender_, &transport_sender_); + + // Initializing audio and video senders. + cast_sender_->InitializeAudio(audio_sender_config_, + base::Bind(&AudioInitializationStatus)); + cast_sender_->InitializeVideo(video_sender_config_, + base::Bind(&VideoInitializationStatus), + CreateDefaultVideoEncodeAcceleratorCallback(), + CreateDefaultVideoEncodeMemoryCallback()); + + receiver_to_sender_.SetPacketReceiver( + cast_sender_->packet_receiver(), task_runner_, &testing_clock_); + sender_to_receiver_.SetPacketReceiver( + cast_receiver_->packet_receiver(), task_runner_, &testing_clock_); + } + + virtual ~RunOneBenchmark() { + cast_sender_.reset(); + cast_receiver_.reset(); + task_runner_->RunTasks(); + } + + void SendFakeVideoFrame() { + frames_sent_++; + cast_sender_->video_frame_input()->InsertRawVideoFrame( + media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2)), + testing_clock_sender_->NowTicks()); + } + + void RunTasks(int ms) { + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(ms)); + } + + void BasicPlayerGotVideoFrame( + const scoped_refptr<media::VideoFrame>& video_frame, + const base::TimeTicks& render_time, + bool continuous) { + video_ticks_.push_back( + std::make_pair(testing_clock_receiver_->NowTicks(), render_time)); + cast_receiver_->RequestDecodedVideoFrame(base::Bind( + &RunOneBenchmark::BasicPlayerGotVideoFrame, base::Unretained(this))); + } + + void BasicPlayerGotAudioFrame(scoped_ptr<AudioBus> audio_bus, + const base::TimeTicks& playout_time, + bool is_continuous) { + audio_ticks_.push_back( + std::make_pair(testing_clock_receiver_->NowTicks(), playout_time)); + cast_receiver_->RequestDecodedAudioFrame(base::Bind( + &RunOneBenchmark::BasicPlayerGotAudioFrame, base::Unretained(this))); + } + + void StartBasicPlayer() { + cast_receiver_->RequestDecodedVideoFrame(base::Bind( + &RunOneBenchmark::BasicPlayerGotVideoFrame, base::Unretained(this))); + cast_receiver_->RequestDecodedAudioFrame(base::Bind( + &RunOneBenchmark::BasicPlayerGotAudioFrame, base::Unretained(this))); + } + + scoped_ptr<test::PacketPipe> CreateSimplePipe(const MeasuringPoint& p) { + scoped_ptr<test::PacketPipe> pipe = test::NewBuffer(65536, p.bitrate); + pipe->AppendToPipe( + test::NewRandomDrop(p.percent_packet_drop / 100.0).Pass()); + pipe->AppendToPipe(test::NewConstantDelay(p.latency / 1000.0)); + return pipe.Pass(); + } + + void Run(const MeasuringPoint& p) { + available_bitrate_ = p.bitrate; + Configure(transport::kFakeSoftwareVideo, transport::kPcm16, 32000, 1); + receiver_to_sender_.SetPacketPipe(CreateSimplePipe(p).Pass()); + sender_to_receiver_.SetPacketPipe(CreateSimplePipe(p).Pass()); + Create(); + StartBasicPlayer(); + + for (int frame = 0; frame < 1000; frame++) { + SendFakeVideoFrame(); + RunTasks(kFrameTimerMs); + } + RunTasks(100 * kFrameTimerMs); // Empty the pipeline. + VLOG(1) << "=============INPUTS============"; + VLOG(1) << "Bitrate: " << p.bitrate << " mbit/s"; + VLOG(1) << "Latency: " << p.latency << " ms"; + VLOG(1) << "Packet drop drop: " << p.percent_packet_drop << "%"; + VLOG(1) << "=============OUTPUTS============"; + VLOG(1) << "Frames lost: " << frames_lost(); + VLOG(1) << "Late frames: " << late_frames(); + VLOG(1) << "Playout margin: " << frame_playout_buffer().AsString(); + VLOG(1) << "Video bandwidth used: " << video_bandwidth() << " mbit/s (" + << (video_bandwidth() * 100 / desired_video_bitrate()) << "%)"; + VLOG(1) << "Good run: " << SimpleGood(); + } + + // Metrics + int frames_lost() const { return frames_sent_ - video_ticks_.size(); } + + int late_frames() const { + int frames = 0; + // Ignore the first two seconds of video or so. + for (size_t i = 60; i < video_ticks_.size(); i++) { + if (video_ticks_[i].first > video_ticks_[i].second) { + frames++; + } + } + return frames; + } + + test::MeanAndError frame_playout_buffer() const { + std::vector<double> values; + for (size_t i = 0; i < video_ticks_.size(); i++) { + values.push_back( + (video_ticks_[i].second - video_ticks_[i].first).InMillisecondsF()); + } + return test::MeanAndError(values); + } + + // Mbits per second + double video_bandwidth() const { + double seconds = (kFrameTimerMs * frames_sent_ / 1000.0); + double megabits = video_bytes_encoded_ * 8 / 1000000.0; + return megabits / seconds; + } + + // Mbits per second + double audio_bandwidth() const { + double seconds = (kFrameTimerMs * frames_sent_ / 1000.0); + double megabits = audio_bytes_encoded_ * 8 / 1000000.0; + return megabits / seconds; + } + + double desired_video_bitrate() { + return std::min<double>(available_bitrate_, + video_sender_config_.max_bitrate / 1000000.0); + } + + bool SimpleGood() { + return frames_lost() <= 1 && late_frames() <= 1 && + video_bandwidth() > desired_video_bitrate() * 0.8 && + video_bandwidth() < desired_video_bitrate() * 1.2; + } + + private: + FrameReceiverConfig audio_receiver_config_; + FrameReceiverConfig video_receiver_config_; + AudioSenderConfig audio_sender_config_; + VideoSenderConfig video_sender_config_; + + base::TimeTicks start_time_; + + // These run in "test time" + base::SimpleTestTickClock testing_clock_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + + // These run on the sender timeline. + test::SkewedTickClock* testing_clock_sender_; + scoped_refptr<test::SkewedSingleThreadTaskRunner> task_runner_sender_; + + // These run on the receiver timeline. + test::SkewedTickClock* testing_clock_receiver_; + scoped_refptr<test::SkewedSingleThreadTaskRunner> task_runner_receiver_; + + scoped_refptr<CastEnvironment> cast_environment_sender_; + scoped_refptr<CastEnvironment> cast_environment_receiver_; + + LoopBackTransport receiver_to_sender_; + LoopBackTransport sender_to_receiver_; + CastTransportSenderWrapper transport_sender_; + uint64 video_bytes_encoded_; + uint64 audio_bytes_encoded_; + + scoped_ptr<CastReceiver> cast_receiver_; + scoped_ptr<CastSender> cast_sender_; + + int frames_sent_; + double available_bitrate_; + std::vector<std::pair<base::TimeTicks, base::TimeTicks> > audio_ticks_; + std::vector<std::pair<base::TimeTicks, base::TimeTicks> > video_ticks_; +}; + +enum CacheResult { FOUND_TRUE, FOUND_FALSE, NOT_FOUND }; + +template <class T> +class BenchmarkCache { + public: + CacheResult Lookup(const T& x) { + base::AutoLock key(lock_); + for (size_t i = 0; i < results_.size(); i++) { + if (results_[i].second) { + if (x <= results_[i].first) { + VLOG(2) << "TRUE because: " << x.AsString() + << " <= " << results_[i].first.AsString(); + return FOUND_TRUE; + } + } else { + if (x >= results_[i].first) { + VLOG(2) << "FALSE because: " << x.AsString() + << " >= " << results_[i].first.AsString(); + return FOUND_FALSE; + } + } + } + return NOT_FOUND; + } + + void Add(const T& x, bool result) { + base::AutoLock key(lock_); + VLOG(2) << "Cache Insert: " << x.AsString() << " = " << result; + results_.push_back(std::make_pair(x, result)); + } + + private: + base::Lock lock_; + std::vector<std::pair<T, bool> > results_; +}; + +struct SearchVariable { + SearchVariable() : base(0.0), grade(0.0) {} + SearchVariable(double b, double g) : base(b), grade(g) {} + SearchVariable blend(const SearchVariable& other, double factor) { + CHECK_GE(factor, 0); + CHECK_LE(factor, 1.0); + return SearchVariable(base * (1 - factor) + other.base * factor, + grade * (1 - factor) + other.grade * factor); + } + double value(double x) const { return base + grade * x; } + double base; + double grade; +}; + +struct SearchVector { + SearchVector blend(const SearchVector& other, double factor) { + SearchVector ret; + ret.bitrate = bitrate.blend(other.bitrate, factor); + ret.latency = latency.blend(other.latency, factor); + ret.packet_drop = packet_drop.blend(other.packet_drop, factor); + return ret; + } + + SearchVector average(const SearchVector& other) { + return blend(other, 0.5); + } + + MeasuringPoint GetMeasuringPoint(double v) const { + return MeasuringPoint( + bitrate.value(-v), latency.value(v), packet_drop.value(v)); + } + std::string AsString(double v) { return GetMeasuringPoint(v).AsString(); } + + SearchVariable bitrate; + SearchVariable latency; + SearchVariable packet_drop; +}; + +class CastBenchmark { + public: + bool RunOnePoint(const SearchVector& v, double multiplier) { + MeasuringPoint p = v.GetMeasuringPoint(multiplier); + VLOG(1) << "RUN: v = " << multiplier << " p = " << p.AsString(); + if (p.bitrate <= 0) { + return false; + } + switch (cache_.Lookup(p)) { + case FOUND_TRUE: + return true; + case FOUND_FALSE: + return false; + case NOT_FOUND: + // Keep going + break; + } + bool result = true; + for (int tries = 0; tries < 3 && result; tries++) { + RunOneBenchmark benchmark; + benchmark.Run(p); + result &= benchmark.SimpleGood(); + } + cache_.Add(p, result); + return result; + } + + void BinarySearch(SearchVector v, double accuracy) { + double min = 0.0; + double max = 1.0; + while (RunOnePoint(v, max)) { + min = max; + max *= 2; + } + + while (max - min > accuracy) { + double avg = (min + max) / 2; + if (RunOnePoint(v, avg)) { + min = avg; + } else { + max = avg; + } + } + + // Print a data point to stdout. + base::AutoLock key(lock_); + MeasuringPoint p = v.GetMeasuringPoint(min); + fprintf(stdout, "%f %f %f\n", p.bitrate, p.latency, p.percent_packet_drop); + fflush(stdout); + } + + void SpanningSearch(int max, + int x, + int y, + int skip, + SearchVector a, + SearchVector b, + SearchVector c, + double accuracy, + std::vector<linked_ptr<base::Thread> >* threads) { + static int thread_num = 0; + if (x > max) return; + if (skip > max) { + if (y > x) return; + SearchVector ab = a.blend(b, static_cast<double>(x) / max); + SearchVector ac = a.blend(c, static_cast<double>(x) / max); + SearchVector v = ab.blend(ac, x == y ? 1.0 : static_cast<double>(y) / x); + thread_num++; + (*threads)[thread_num % threads->size()]->message_loop()->PostTask( + FROM_HERE, + base::Bind(&CastBenchmark::BinarySearch, + base::Unretained(this), + v, + accuracy)); + } else { + skip *= 2; + SpanningSearch(max, x, y, skip, a, b, c, accuracy, threads); + SpanningSearch(max, x + skip, y + skip, skip, a, b, c, accuracy, threads); + SpanningSearch(max, x + skip, y, skip, a, b, c, accuracy, threads); + SpanningSearch(max, x, y + skip, skip, a, b, c, accuracy, threads); + } + } + + void Run() { + // Spanning search. + + std::vector<linked_ptr<base::Thread> > threads; + for (int i = 0; i < 16; i++) { + threads.push_back(make_linked_ptr(new base::Thread( + base::StringPrintf("cast_bench_thread_%d", i)))); + threads[i]->Start(); + } + + if (CommandLine::ForCurrentProcess()->HasSwitch("single-run")) { + SearchVector a; + a.bitrate.base = 100.0; + a.bitrate.grade = 1.0; + a.latency.grade = 1.0; + a.packet_drop.grade = 1.0; + threads[0]->message_loop()->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&CastBenchmark::RunOnePoint), + base::Unretained(this), + a, + 1.0)); + } else { + SearchVector a, b, c; + a.bitrate.base = b.bitrate.base = c.bitrate.base = 100.0; + a.bitrate.grade = 1.0; + b.latency.grade = 1.0; + c.packet_drop.grade = 1.0; + + SpanningSearch(512, + 0, + 0, + 1, + a, + b, + c, + 0.01, + &threads); + } + + for (size_t i = 0; i < threads.size(); i++) { + threads[i]->Stop(); + } + } + + private: + BenchmarkCache<MeasuringPoint> cache_; + base::Lock lock_; +}; + +} // namespace cast +} // namespace media + +int main(int argc, char** argv) { + base::AtExitManager at_exit; + CommandLine::Init(argc, argv); + media::cast::CastBenchmark benchmark; + if (getenv("PROFILE_FILE")) { + std::string profile_file(getenv("PROFILE_FILE")); + base::debug::StartProfiling(profile_file); + benchmark.Run(); + base::debug::StopProfiling(); + } else { + benchmark.Run(); + } +} diff --git a/media/cast/test/fake_single_thread_task_runner.cc b/media/cast/test/fake_single_thread_task_runner.cc index b60a1b12ea..a2e3393f55 100644 --- a/media/cast/test/fake_single_thread_task_runner.cc +++ b/media/cast/test/fake_single_thread_task_runner.cc @@ -26,6 +26,7 @@ bool FakeSingleThreadTaskRunner::PostDelayedTask( if (fail_on_next_task_) { LOG(FATAL) << "Infinite task-add loop detected."; } + CHECK(delay >= base::TimeDelta()); EXPECT_GE(delay, base::TimeDelta()); PostedTask posed_task(from_here, task, diff --git a/media/cast/test/utility/test_util.cc b/media/cast/test/utility/test_util.cc new file mode 100644 index 0000000000..461a1729d1 --- /dev/null +++ b/media/cast/test/utility/test_util.cc @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <math.h> + +#include <algorithm> + +#include "base/strings/stringprintf.h" +#include "media/cast/test/utility/test_util.h" + +namespace media { +namespace cast { +namespace test { + +MeanAndError::MeanAndError(const std::vector<double>& values) { + double sum = 0.0; + double sqr_sum = 0.0; + num_values = values.size(); + if (num_values) { + for (size_t i = 0; i < num_values; i++) { + sum += values[i]; + sqr_sum += values[i] * values[i]; + } + mean = sum / num_values; + std_dev = + sqrt(std::max(0.0, num_values * sqr_sum - sum * sum)) / num_values; + } +} + +std::string MeanAndError::AsString() const { + return base::StringPrintf("%f +/- %f", mean, std_dev); +} + +} // namespace test +} // namespace cast +} // namespace media diff --git a/media/cast/test/utility/test_util.h b/media/cast/test/utility/test_util.h new file mode 100644 index 0000000000..6420f8c71a --- /dev/null +++ b/media/cast/test/utility/test_util.h @@ -0,0 +1,30 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TEST_UTILITY_TEST_UTIL_H +#define MEDIA_CAST_TEST_UTILITY_TEST_UTIL_H + +#include <string> +#include <vector> + +namespace media { +namespace cast { +namespace test { + +class MeanAndError { + public: + MeanAndError() {} + explicit MeanAndError(const std::vector<double>& values); + std::string AsString() const; + + size_t num_values; + double mean; + double std_dev; +}; + +} // namespace test +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TEST_UTILITY_TEST_UTIL_H diff --git a/media/cast/test/utility/udp_proxy.cc b/media/cast/test/utility/udp_proxy.cc index 05c3b93891..9fc3b4a44d 100644 --- a/media/cast/test/utility/udp_proxy.cc +++ b/media/cast/test/utility/udp_proxy.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <stdlib.h> + #include "media/cast/test/utility/udp_proxy.h" #include "base/logging.h" @@ -48,7 +50,10 @@ class Buffer : public PacketPipe { : buffer_size_(0), max_buffer_size_(buffer_size), max_megabits_per_second_(max_megabits_per_second), - weak_factory_(this) {} + weak_factory_(this) { + CHECK_GT(max_buffer_size_, 0UL); + CHECK_GT(max_megabits_per_second, 0); + } virtual void Send(scoped_ptr<transport::Packet> packet) OVERRIDE { if (packet->size() + buffer_size_ <= max_buffer_size_) { @@ -95,17 +100,17 @@ scoped_ptr<PacketPipe> NewBuffer(size_t buffer_size, double bandwidth) { class RandomDrop : public PacketPipe { public: - RandomDrop(double drop_fraction) : drop_fraction_(drop_fraction) { - } + RandomDrop(double drop_fraction) + : drop_fraction_(static_cast<int>(drop_fraction * RAND_MAX)) {} virtual void Send(scoped_ptr<transport::Packet> packet) OVERRIDE { - if (base::RandDouble() >= drop_fraction_) { + if (rand() > drop_fraction_) { pipe_->Send(packet.Pass()); } } private: - double drop_fraction_; + int drop_fraction_; }; scoped_ptr<PacketPipe> NewRandomDrop(double drop_fraction) { diff --git a/media/cast/test/utility/video_utility.cc b/media/cast/test/utility/video_utility.cc index 81475d85f1..b94c99cda1 100644 --- a/media/cast/test/utility/video_utility.cc +++ b/media/cast/test/utility/video_utility.cc @@ -7,6 +7,7 @@ #include <math.h> #include <cstdio> +#include "base/rand_util.h" #include "third_party/libyuv/include/libyuv/compare.h" #include "ui/gfx/size.h" @@ -46,25 +47,43 @@ void PopulateVideoFrame(VideoFrame* frame, int start_value) { uint8* v_plane = frame->data(VideoFrame::kVPlane); // Set Y. - for (int j = 0; j < height; ++j) + for (int j = 0; j < height; ++j) { for (int i = 0; i < stride_y; ++i) { *y_plane = static_cast<uint8>(start_value + i + j); ++y_plane; } + } // Set U. - for (int j = 0; j < half_height; ++j) + for (int j = 0; j < half_height; ++j) { for (int i = 0; i < stride_u; ++i) { *u_plane = static_cast<uint8>(start_value + i + j); ++u_plane; } + } // Set V. - for (int j = 0; j < half_height; ++j) + for (int j = 0; j < half_height; ++j) { for (int i = 0; i < stride_v; ++i) { *v_plane = static_cast<uint8>(start_value + i + j); ++v_plane; } + } +} + +void PopulateVideoFrameWithNoise(VideoFrame* frame) { + int height = frame->coded_size().height(); + int stride_y = frame->stride(VideoFrame::kYPlane); + int stride_u = frame->stride(VideoFrame::kUPlane); + int stride_v = frame->stride(VideoFrame::kVPlane); + int half_height = (height + 1) / 2; + uint8* y_plane = frame->data(VideoFrame::kYPlane); + uint8* u_plane = frame->data(VideoFrame::kUPlane); + uint8* v_plane = frame->data(VideoFrame::kVPlane); + + base::RandBytes(y_plane, height * stride_y); + base::RandBytes(u_plane, half_height * stride_u); + base::RandBytes(v_plane, half_height * stride_v); } bool PopulateVideoFrameFromFile(VideoFrame* frame, FILE* video_file) { diff --git a/media/cast/test/utility/video_utility.h b/media/cast/test/utility/video_utility.h index c8539433d1..bbb98654bf 100644 --- a/media/cast/test/utility/video_utility.h +++ b/media/cast/test/utility/video_utility.h @@ -18,6 +18,9 @@ double I420PSNR(const scoped_refptr<media::VideoFrame>& frame1, // Memory is allocated within the function. void PopulateVideoFrame(VideoFrame* frame, int start_value); +// Populate a video frame with noise. +void PopulateVideoFrameWithNoise(VideoFrame* frame); + // Populate a video frame from a file. // Returns true if frame was populated, false if not (EOF). bool PopulateVideoFrameFromFile(VideoFrame* frame, FILE* video_file); diff --git a/media/cast/transport/cast_transport_sender.h b/media/cast/transport/cast_transport_sender.h index 5c1eb04b41..2556a8bd3d 100644 --- a/media/cast/transport/cast_transport_sender.h +++ b/media/cast/transport/cast_transport_sender.h @@ -92,9 +92,15 @@ class CastTransportSender : public base::NonThreadSafe { const std::string& c_name) = 0; // Retransmission request. + // |missing_packets| includes the list of frames and packets in each + // frame to be re-transmitted. + // If |cancel_rtx_if_not_in_list| is used as an optimization to cancel + // pending re-transmission requests of packets not listed in + // |missing_packets|. virtual void ResendPackets( bool is_audio, - const MissingFramesAndPacketsMap& missing_packets) = 0; + const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list) = 0; }; } // namespace transport diff --git a/media/cast/transport/cast_transport_sender_impl.cc b/media/cast/transport/cast_transport_sender_impl.cc index 06877b08ba..2f51a934e7 100644 --- a/media/cast/transport/cast_transport_sender_impl.cc +++ b/media/cast/transport/cast_transport_sender_impl.cc @@ -177,13 +177,16 @@ void CastTransportSenderImpl::SendRtcpFromRtpSender( void CastTransportSenderImpl::ResendPackets( bool is_audio, - const MissingFramesAndPacketsMap& missing_packets) { + const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list) { if (is_audio) { DCHECK(audio_sender_) << "Audio sender uninitialized"; - audio_sender_->ResendPackets(missing_packets); + audio_sender_->ResendPackets(missing_packets, + cancel_rtx_if_not_in_list); } else { DCHECK(video_sender_) << "Video sender uninitialized"; - video_sender_->ResendPackets(missing_packets); + video_sender_->ResendPackets(missing_packets, + cancel_rtx_if_not_in_list); } } diff --git a/media/cast/transport/cast_transport_sender_impl.h b/media/cast/transport/cast_transport_sender_impl.h index 0f440fb507..4fc074c0b9 100644 --- a/media/cast/transport/cast_transport_sender_impl.h +++ b/media/cast/transport/cast_transport_sender_impl.h @@ -66,7 +66,8 @@ class CastTransportSenderImpl : public CastTransportSender { const std::string& c_name) OVERRIDE; virtual void ResendPackets(bool is_audio, - const MissingFramesAndPacketsMap& missing_packets) + const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list) OVERRIDE; private: diff --git a/media/cast/transport/rtp_sender/packet_storage/packet_storage.cc b/media/cast/transport/rtp_sender/packet_storage/packet_storage.cc index 6206d02f42..a748baa27a 100644 --- a/media/cast/transport/rtp_sender/packet_storage/packet_storage.cc +++ b/media/cast/transport/rtp_sender/packet_storage/packet_storage.cc @@ -12,123 +12,52 @@ namespace media { namespace cast { namespace transport { -typedef PacketMap::iterator PacketMapIterator; - -PacketStorage::PacketStorage(int stored_frames) - : stored_frames_(stored_frames) { +PacketStorage::PacketStorage(size_t stored_frames) + : max_stored_frames_(stored_frames), + first_frame_id_in_list_(0), + last_frame_id_in_list_(0) { } PacketStorage::~PacketStorage() { } bool PacketStorage::IsValid() const { - return stored_frames_ > 0 && stored_frames_ <= kMaxStoredFrames; + return max_stored_frames_ > 0 && + static_cast<int>(max_stored_frames_) <= kMaxUnackedFrames; } -void PacketStorage::CleanupOldPackets(uint32 current_frame_id) { - uint32 frame_to_remove = current_frame_id - stored_frames_; - while (!stored_packets_.empty()) { - if (IsOlderFrameId(stored_packets_.begin()->first.first, - frame_to_remove)) { - stored_packets_.erase(stored_packets_.begin()); - } else { - break; - } - } +size_t PacketStorage::GetNumberOfStoredFrames() const { + return frames_.size(); } -void PacketStorage::StorePacket(uint32 frame_id, - uint16 packet_id, - const PacketKey& key, - PacketRef packet) { - CleanupOldPackets(frame_id); - StorageIndex index(frame_id, packet_id); - PacketMapIterator it = stored_packets_.find(index); - if (it != stored_packets_.end()) { - // We have already saved this. - DCHECK(false) << "Invalid state"; - return; +void PacketStorage::StoreFrame(uint32 frame_id, + const SendPacketVector& packets) { + if (frames_.empty()) { + first_frame_id_in_list_ = frame_id; + } else { + // Make sure frame IDs are consecutive. + DCHECK_EQ(last_frame_id_in_list_ + 1, frame_id); } - stored_packets_[index] = std::make_pair(key, packet); -} - -void PacketStorage::GetPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets, - SendPacketVector* packets_to_resend) { - - // Iterate over all frames in the list. - for (MissingFramesAndPacketsMap::const_iterator it = - missing_frames_and_packets.begin(); - it != missing_frames_and_packets.end(); - ++it) { - uint8 frame_id = it->first; - const PacketIdSet& packets_set = it->second; - bool success = false; - if (packets_set.empty()) { - VLOG(1) << "Missing all packets in frame " << static_cast<int>(frame_id); - uint16 packet_id = 0; - do { - // Get packet from storage. - success = GetPacket(frame_id, packet_id, packets_to_resend); - ++packet_id; - } while (success); - } else { - // Iterate over all of the packets in the frame. - for (PacketIdSet::const_iterator set_it = packets_set.begin(); - set_it != packets_set.end(); - ++set_it) { - GetPacket(frame_id, *set_it, packets_to_resend); - } - } - } -} + // Save new frame to the end of the list. + last_frame_id_in_list_ = frame_id; + frames_.push_back(packets); -bool PacketStorage::GetPacket32(uint32 frame_id, - uint16 packet_id, - SendPacketVector* packets) { - StorageIndex index(frame_id, packet_id); - PacketMapIterator it = stored_packets_.find(index); - if (it == stored_packets_.end()) { - return false; - } - // Minor trickery, the caller (rtp_sender.cc) really wants a copy of the - // packet so that it can update the sequence number before it sends it to - // the transport. If the packet only has one ref, we can safely let - // rtp_sender.cc have our packet and modify it. If it has more references - // then we must return a copy of it instead. This should really only happen - // when rtp_sender.cc is trying to re-send a packet that is already in the - // queue to sent. - if (it->second.second->HasOneRef()) { - packets->push_back(it->second); - } else { - packets->push_back( - std::make_pair(it->second.first, - make_scoped_refptr( - new base::RefCountedData<Packet>( - it->second.second->data)))); + // Evict the oldest frame if the list is too long. + if (frames_.size() > max_stored_frames_) { + frames_.pop_front(); + ++first_frame_id_in_list_; } - return true; } -bool PacketStorage::GetPacket(uint8 frame_id_8bit, - uint16 packet_id, - SendPacketVector* packets) { - if (stored_packets_.empty()) { - return false; - } - uint32 last_stored = stored_packets_.rbegin()->first.first; - uint32 frame_id_32bit = (last_stored & ~0xFF) | frame_id_8bit; - if (IsNewerFrameId(frame_id_32bit, last_stored)) { - frame_id_32bit -= 0x100; - } - DCHECK_EQ(frame_id_8bit, frame_id_32bit & 0xff); - DCHECK(IsOlderFrameId(frame_id_32bit, last_stored) && - IsNewerFrameId(frame_id_32bit + stored_frames_ + 1, last_stored)) - << " 32bit: " << frame_id_32bit - << " 8bit: " << static_cast<int>(frame_id_8bit) - << " last_stored: " << last_stored; - return GetPacket32(frame_id_32bit, packet_id, packets); +const SendPacketVector* PacketStorage::GetFrame8(uint8 frame_id_8bits) const { + // The requested frame ID has only 8-bits so convert the first frame ID + // in list to match. + uint8 index_8bits = first_frame_id_in_list_ & 0xFF; + index_8bits = frame_id_8bits - index_8bits; + if (index_8bits >= frames_.size()) + return NULL; + return &(frames_[index_8bits]); } } // namespace transport diff --git a/media/cast/transport/rtp_sender/packet_storage/packet_storage.h b/media/cast/transport/rtp_sender/packet_storage/packet_storage.h index 85efc664c4..037ead1edf 100644 --- a/media/cast/transport/rtp_sender/packet_storage/packet_storage.h +++ b/media/cast/transport/rtp_sender/packet_storage/packet_storage.h @@ -5,6 +5,7 @@ #ifndef MEDIA_CAST_TRANSPORT_RTP_SENDER_PACKET_STORAGE_PACKET_STORAGE_H_ #define MEDIA_CAST_TRANSPORT_RTP_SENDER_PACKET_STORAGE_PACKET_STORAGE_H_ +#include <deque> #include <list> #include <map> #include <vector> @@ -22,51 +23,34 @@ namespace media { namespace cast { namespace transport { -class StoredPacket; - -// StorageIndex contains {frame_id, packet_id}. -typedef std::pair<uint32, uint16> StorageIndex; -typedef std::map<StorageIndex, std::pair<PacketKey, PacketRef> > PacketMap; - -// Frame IDs are generally stored as 8-bit values when sent over the -// wire. This means that having a history longer than 255 frames makes -// no sense. -const int kMaxStoredFrames = 255; +// Stores a list of frames. Each frame consists a list of packets. +typedef std::deque<SendPacketVector> FrameQueue; class PacketStorage { public: - PacketStorage(int stored_frames); + explicit PacketStorage(size_t stored_frames); virtual ~PacketStorage(); // Returns true if this class is configured correctly. // (stored frames > 0 && stored_frames < kMaxStoredFrames) bool IsValid() const; - void StorePacket(uint32 frame_id, - uint16 packet_id, - const PacketKey& key, - PacketRef packet); + // Store all of the packets for a frame. + void StoreFrame(uint32 frame_id, const SendPacketVector& packets); - // Copies all missing packets into the packet list. - void GetPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets, - SendPacketVector* packets_to_resend); + // Returns a list of packets for a frame indexed by a 8-bits ID. + // It is the lowest 8 bits of a frame ID. + // Returns NULL if the frame cannot be found. + const SendPacketVector* GetFrame8(uint8 frame_id_8bits) const; - // Copies packet into the packet list. - bool GetPacket(uint8 frame_id_8bit, - uint16 packet_id, - SendPacketVector* packets); - private: - FRIEND_TEST_ALL_PREFIXES(PacketStorageTest, PacketContent); + // Get the number of stored frames. + size_t GetNumberOfStoredFrames() const; - // Same as GetPacket, but takes a 32-bit frame id. - bool GetPacket32(uint32 frame_id, - uint16 packet_id, - SendPacketVector* packets); - void CleanupOldPackets(uint32 current_frame_id); - - PacketMap stored_packets_; - int stored_frames_; + private: + const size_t max_stored_frames_; + FrameQueue frames_; + uint32 first_frame_id_in_list_; + uint32 last_frame_id_in_list_; DISALLOW_COPY_AND_ASSIGN(PacketStorage); }; diff --git a/media/cast/transport/rtp_sender/packet_storage/packet_storage_unittest.cc b/media/cast/transport/rtp_sender/packet_storage/packet_storage_unittest.cc index a43ae6e0fd..298942c80a 100644 --- a/media/cast/transport/rtp_sender/packet_storage/packet_storage_unittest.cc +++ b/media/cast/transport/rtp_sender/packet_storage/packet_storage_unittest.cc @@ -16,47 +16,97 @@ namespace media { namespace cast { namespace transport { -static int kStoredFrames = 10; +static size_t kStoredFrames = 10; -class PacketStorageTest : public ::testing::Test { - protected: - PacketStorageTest() : packet_storage_(kStoredFrames) { +// Generate |number_of_frames| and store into |*storage|. +// First frame has 1 packet, second frame has 2 packets, etc. +static void StoreFrames(size_t number_of_frames, + uint32 first_frame_id, + PacketStorage* storage) { + const base::TimeTicks kTicks; + const int kSsrc = 1; + for (size_t i = 0; i < number_of_frames; ++i) { + SendPacketVector packets; + // First frame has 1 packet, second frame has 2 packets, etc. + const size_t kNumberOfPackets = i + 1; + for (size_t j = 0; j < kNumberOfPackets; ++j) { + Packet test_packet(1, 0); + packets.push_back( + std::make_pair( + PacedPacketSender::MakePacketKey(kTicks, kSsrc, j), + new base::RefCountedData<Packet>(test_packet))); + } + storage->StoreFrame(first_frame_id, packets); + ++first_frame_id; } +} - PacketStorage packet_storage_; - - DISALLOW_COPY_AND_ASSIGN(PacketStorageTest); -}; - -TEST_F(PacketStorageTest, PacketContent) { - base::TimeTicks frame_tick; - for (uint32 frame_id = 0; frame_id < 200; ++frame_id) { - for (uint16 packet_id = 0; packet_id < 5; ++packet_id) { - Packet test_packet(frame_id + 1, packet_id); - packet_storage_.StorePacket( - frame_id, - packet_id, - PacedPacketSender::MakePacketKey(frame_tick, - 1, // ssrc - packet_id), - new base::RefCountedData<Packet>(test_packet)); - } +TEST(PacketStorageTest, NumberOfStoredFrames) { + PacketStorage storage(kStoredFrames); - for (uint32 f = 0; f <= frame_id; f++) { - for (uint16 packet_id = 0; packet_id < 5; ++packet_id) { - SendPacketVector packets; - if (packet_storage_.GetPacket32(f, packet_id, &packets)) { - EXPECT_GT(f + kStoredFrames, frame_id); - EXPECT_EQ(f + 1, packets.back().second->data.size()); - EXPECT_EQ(packet_id, packets.back().second->data[0]); - EXPECT_TRUE(packet_storage_.GetPacket(f & 0xff, packet_id, &packets)); - EXPECT_TRUE(packets.back().second->data == - packets.front().second->data); - } else { - EXPECT_LE(f + kStoredFrames, frame_id); - } - } - } + uint32 frame_id = 0; + frame_id = ~frame_id; // The maximum value of uint32. + StoreFrames(200, frame_id, &storage); + EXPECT_EQ(kStoredFrames, storage.GetNumberOfStoredFrames()); +} + +TEST(PacketStorageTest, GetFrameWrapAround8bits) { + PacketStorage storage(kStoredFrames); + + const uint32 kFirstFrameId = 250; + StoreFrames(kStoredFrames, kFirstFrameId, &storage); + EXPECT_EQ(kStoredFrames, storage.GetNumberOfStoredFrames()); + + // Expect we get the correct frames by looking at the number of + // packets. + uint32 frame_id = kFirstFrameId; + for (size_t i = 0; i < kStoredFrames; ++i) { + ASSERT_TRUE(storage.GetFrame8(frame_id)); + EXPECT_EQ(i + 1, storage.GetFrame8(frame_id)->size()); + ++frame_id; + } +} + +TEST(PacketStorageTest, GetFrameWrapAround32bits) { + PacketStorage storage(kStoredFrames); + + // First frame ID is close to the maximum value of uint32. + uint32 first_frame_id = 0xffffffff - 5; + StoreFrames(kStoredFrames, first_frame_id, &storage); + EXPECT_EQ(kStoredFrames, storage.GetNumberOfStoredFrames()); + + // Expect we get the correct frames by looking at the number of + // packets. + uint32 frame_id = first_frame_id; + for (size_t i = 0; i < kStoredFrames; ++i) { + ASSERT_TRUE(storage.GetFrame8(frame_id)); + EXPECT_EQ(i + 1, storage.GetFrame8(frame_id)->size()); + ++frame_id; + } +} + +TEST(PacketStorageTest, GetFrameTooOld) { + PacketStorage storage(kStoredFrames); + + // First frame ID is close to the maximum value of uint32. + uint32 first_frame_id = 0xffffffff - 5; + + // Store two times the capacity. + StoreFrames(2 * kStoredFrames, first_frame_id, &storage); + EXPECT_EQ(kStoredFrames, storage.GetNumberOfStoredFrames()); + + uint32 frame_id = first_frame_id; + // Old frames are evicted. + for (size_t i = 0; i < kStoredFrames; ++i) { + EXPECT_FALSE(storage.GetFrame8(frame_id)); + ++frame_id; + } + // Check recent frames are there. + for (size_t i = 0; i < kStoredFrames; ++i) { + ASSERT_TRUE(storage.GetFrame8(frame_id)); + EXPECT_EQ(kStoredFrames + i + 1, + storage.GetFrame8(frame_id)->size()); + ++frame_id; } } diff --git a/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.cc b/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.cc index 50ec42b7d2..d40f99f144 100644 --- a/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.cc +++ b/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.cc @@ -93,23 +93,22 @@ void RtpPacketizer::SendFrameAsPackets(const EncodedFrame& frame) { packet->data.insert(packet->data.end(), data_iter, data_iter + payload_length); - - PacketKey key = PacedPacketSender::MakePacketKey(frame.reference_time, - config_.ssrc, - packet_id_); - - // Store packet. - packet_storage_->StorePacket(frame.frame_id, packet_id_, key, packet); - ++packet_id_; data_iter += payload_length; + const PacketKey key = + PacedPacketSender::MakePacketKey(frame.reference_time, + config_.ssrc, + packet_id_++); + packets.push_back(make_pair(key, packet)); + // Update stats. ++send_packet_count_; send_octet_count_ += payload_length; - packets.push_back(make_pair(key, packet)); } DCHECK(packet_id_ == num_packets) << "Invalid state"; + packet_storage_->StoreFrame(frame.frame_id, packets); + // Send to network. transport_->SendPackets(packets); diff --git a/media/cast/transport/rtp_sender/rtp_sender.cc b/media/cast/transport/rtp_sender/rtp_sender.cc index 91b6298bac..2604d253de 100644 --- a/media/cast/transport/rtp_sender/rtp_sender.cc +++ b/media/cast/transport/rtp_sender/rtp_sender.cc @@ -13,6 +13,19 @@ namespace media { namespace cast { namespace transport { +namespace { + +// If there is only one referecne to the packet then copy the +// reference and return. +// Otherwise return a deep copy of the packet. +PacketRef FastCopyPacket(const PacketRef& packet) { + if (packet->HasOneRef()) + return packet; + return make_scoped_refptr(new base::RefCountedData<Packet>(packet->data)); +} + +} // namespace + RtpSender::RtpSender( base::TickClock* clock, const scoped_refptr<base::SingleThreadTaskRunner>& transport_task_runner, @@ -61,7 +74,8 @@ void RtpSender::SendFrame(const EncodedFrame& frame) { } void RtpSender::ResendPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets) { + const MissingFramesAndPacketsMap& missing_frames_and_packets, + bool cancel_rtx_if_not_in_list) { DCHECK(storage_); // Iterate over all frames in the list. for (MissingFramesAndPacketsMap::const_iterator it = @@ -73,29 +87,46 @@ void RtpSender::ResendPackets( // Set of packets that the receiver wants us to re-send. // If empty, we need to re-send all packets for this frame. const PacketIdSet& missing_packet_set = it->second; - bool success = false; - for (uint16 packet_id = 0; ; packet_id++) { - // Get packet from storage. - success = storage_->GetPacket(frame_id, packet_id, &packets_to_resend); + bool resend_all = missing_packet_set.find(kRtcpCastAllPacketsLost) != + missing_packet_set.end(); + bool resend_last = missing_packet_set.find(kRtcpCastLastPacket) != + missing_packet_set.end(); + + const SendPacketVector* stored_packets = storage_->GetFrame8(frame_id); + if (!stored_packets) + continue; - // Check that we got at least one packet. - DCHECK(packet_id != 0 || success) - << "Failed to resend frame " << static_cast<int>(frame_id); + for (SendPacketVector::const_iterator it = stored_packets->begin(); + it != stored_packets->end(); ++it) { + const PacketKey& packet_key = it->first; + const uint16 packet_id = packet_key.second.second; - if (!success) break; + // Should we resend the packet? + bool resend = resend_all; + + // Should we resend it because it's in the missing_packet_set? + if (!resend && + missing_packet_set.find(packet_id) != missing_packet_set.end()) { + resend = true; + } + + // If we were asked to resend the last packet, check if it's the + // last packet. + if (!resend && resend_last && (it + 1) == stored_packets->end()) { + resend = true; + } - if (!missing_packet_set.empty() && - missing_packet_set.find(packet_id) == missing_packet_set.end()) { - transport_->CancelSendingPacket(packets_to_resend.back().first); - packets_to_resend.pop_back(); - } else { + if (resend) { // Resend packet to the network. VLOG(3) << "Resend " << static_cast<int>(frame_id) << ":" << packet_id; // Set a unique incremental sequence number for every packet. - PacketRef packet = packets_to_resend.back().second; - UpdateSequenceNumber(&packet->data); + PacketRef packet_copy = FastCopyPacket(it->second); + UpdateSequenceNumber(&packet_copy->data); + packets_to_resend.push_back(std::make_pair(packet_key, packet_copy)); + } else if (cancel_rtx_if_not_in_list) { + transport_->CancelSendingPacket(it->first); } } transport_->ResendPackets(packets_to_resend); diff --git a/media/cast/transport/rtp_sender/rtp_sender.h b/media/cast/transport/rtp_sender/rtp_sender.h index e1fbfe23dd..bfb46cb09e 100644 --- a/media/cast/transport/rtp_sender/rtp_sender.h +++ b/media/cast/transport/rtp_sender/rtp_sender.h @@ -50,7 +50,8 @@ class RtpSender { void SendFrame(const EncodedFrame& frame); - void ResendPackets(const MissingFramesAndPacketsMap& missing_packets); + void ResendPackets(const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list); size_t send_packet_count() const { return packetizer_ ? packetizer_->send_packet_count() : 0; diff --git a/media/cast/video_sender/external_video_encoder.cc b/media/cast/video_sender/external_video_encoder.cc index 5978d8669a..ca30bcd47a 100644 --- a/media/cast/video_sender/external_video_encoder.cc +++ b/media/cast/video_sender/external_video_encoder.cc @@ -406,6 +406,14 @@ bool ExternalVideoEncoder::EncodeVideoFrame( // Inform the encoder about the new target bit rate. void ExternalVideoEncoder::SetBitRate(int new_bit_rate) { + if (!encoder_active_) { + // If we receive SetBitRate() before VEA creation callback is invoked, + // cache the new bit rate in the encoder config and use the new settings + // to initialize VEA. + video_config_.start_bitrate = new_bit_rate; + return; + } + encoder_task_runner_->PostTask( FROM_HERE, base::Bind(&LocalVideoEncodeAcceleratorClient::SetBitRate, diff --git a/media/cast/video_sender/external_video_encoder.h b/media/cast/video_sender/external_video_encoder.h index ee69b962ab..29fe0c5fcd 100644 --- a/media/cast/video_sender/external_video_encoder.h +++ b/media/cast/video_sender/external_video_encoder.h @@ -63,7 +63,7 @@ class ExternalVideoEncoder : public VideoEncoder { private: friend class LocalVideoEncodeAcceleratorClient; - const VideoSenderConfig video_config_; + VideoSenderConfig video_config_; scoped_refptr<CastEnvironment> cast_environment_; bool encoder_active_; diff --git a/media/cast/video_sender/fake_software_video_encoder.cc b/media/cast/video_sender/fake_software_video_encoder.cc index ee8fe0fd00..7c5c952641 100644 --- a/media/cast/video_sender/fake_software_video_encoder.cc +++ b/media/cast/video_sender/fake_software_video_encoder.cc @@ -45,8 +45,9 @@ bool FakeSoftwareVideoEncoder::Encode( values.SetInteger("ref", encoded_image->referenced_frame_id); values.SetInteger("id", encoded_image->frame_id); values.SetInteger("size", frame_size_); - values.SetString("data", std::string(frame_size_, ' ')); base::JSONWriter::Write(&values, &encoded_image->data); + encoded_image->data.resize( + std::max<size_t>(encoded_image->data.size(), frame_size_)); return true; } diff --git a/media/cast/video_sender/video_sender.cc b/media/cast/video_sender/video_sender.cc index 928360b6cc..cc8b158950 100644 --- a/media/cast/video_sender/video_sender.cc +++ b/media/cast/video_sender/video_sender.cc @@ -33,11 +33,11 @@ VideoSender::VideoSender( target_playout_delay_(base::TimeDelta::FromMilliseconds( video_config.rtp_config.max_delay_ms)), transport_sender_(transport_sender), - max_unacked_frames_(std::min( - kMaxUnackedFrames, - 1 + static_cast<int>( - target_playout_delay_ * video_config.max_frame_rate / - base::TimeDelta::FromSeconds(1)))), + max_unacked_frames_( + std::min(kMaxUnackedFrames, + 1 + static_cast<int>(target_playout_delay_ * + video_config.max_frame_rate / + base::TimeDelta::FromSeconds(1)))), rtcp_(cast_environment_, this, transport_sender_, @@ -55,12 +55,10 @@ VideoSender::VideoSender( last_sent_frame_id_(0), latest_acked_frame_id_(0), duplicate_ack_counter_(0), - current_requested_bitrate_(video_config.start_bitrate), congestion_control_(cast_environment->Clock(), - video_config.congestion_control_back_off, video_config.max_bitrate, video_config.min_bitrate, - video_config.start_bitrate), + max_unacked_frames_), cast_initialization_status_(STATUS_VIDEO_UNINITIALIZED), weak_factory_(this) { VLOG(1) << "max_unacked_frames " << max_unacked_frames_; @@ -123,12 +121,17 @@ void VideoSender::InsertRawVideoFrame( return; } + uint32 bitrate = congestion_control_.GetBitrate( + capture_time + target_playout_delay_, target_playout_delay_); + + video_encoder_->SetBitRate(bitrate); + if (video_encoder_->EncodeVideoFrame( video_frame, capture_time, base::Bind(&VideoSender::SendEncodedVideoFrame, weak_factory_.GetWeakPtr(), - current_requested_bitrate_))) { + bitrate))) { frames_in_encoder_++; } else { VLOG(1) << "Encoder rejected a frame. Skipping..."; @@ -192,6 +195,9 @@ void VideoSender::SendEncodedVideoFrame( SendRtcpReport(is_last_aggressive_report); } + congestion_control_.SendFrameToTransport( + frame_id, encoded_frame->data.size() * 8, last_send_time_); + transport_sender_->InsertCodedVideoFrame(*encoded_frame); } @@ -256,9 +262,8 @@ void VideoSender::ResendCheck() { if (latest_acked_frame_id_ == last_sent_frame_id_) { // Last frame acked, no point in doing anything } else { - const uint32 kickstart_frame_id = latest_acked_frame_id_ + 1; - VLOG(1) << "ACK timeout, re-sending frame " << kickstart_frame_id; - ResendFrame(kickstart_frame_id); + VLOG(1) << "ACK timeout; last acked frame: " << latest_acked_frame_id_; + ResendForKickstart(); } } ScheduleNextResendCheck(); @@ -272,6 +277,8 @@ void VideoSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { base::TimeDelta min_rtt; base::TimeDelta max_rtt; if (rtcp_.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)) { + congestion_control_.UpdateRtt(rtt); + // Don't use a RTT lower than our average. rtt = std::max(rtt, avg_rtt); @@ -295,12 +302,6 @@ void VideoSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { if (cast_feedback.missing_frames_and_packets_.empty()) { video_encoder_->LatestFrameIdToReference(cast_feedback.ack_frame_id_); - if ((latest_acked_frame_id_ + 1) == cast_feedback.ack_frame_id_) { - uint32 new_bitrate = 0; - if (congestion_control_.OnAck(rtt, &new_bitrate)) { - UpdateBitrate(new_bitrate); - } - } // We only count duplicate ACKs when we have sent newer frames. if (latest_acked_frame_id_ == cast_feedback.ack_frame_id_ && latest_acked_frame_id_ != last_sent_frame_id_) { @@ -310,34 +311,46 @@ void VideoSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { } // TODO(miu): The values "2" and "3" should be derived from configuration. if (duplicate_ack_counter_ >= 2 && duplicate_ack_counter_ % 3 == 2) { - // Resend last ACK + 1 frame. - const uint32 frame_to_resend = latest_acked_frame_id_ + 1; - VLOG(1) << "Received duplicate ACK for frame " << latest_acked_frame_id_ - << ", will re-send frame " << frame_to_resend; - ResendFrame(frame_to_resend); + VLOG(1) << "Received duplicate ACK for frame " << latest_acked_frame_id_; + ResendForKickstart(); } } else { + // Only count duplicated ACKs if there is no NACK request in between. + // This is to avoid aggresive resend. + duplicate_ack_counter_ = 0; + + // A NACK is also used to cancel pending re-transmissions. transport_sender_->ResendPackets( - false, cast_feedback.missing_frames_and_packets_); - uint32 new_bitrate = 0; - if (congestion_control_.OnNack(rtt, &new_bitrate)) { - UpdateBitrate(new_bitrate); - } + false, cast_feedback.missing_frames_and_packets_, true); } + base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + congestion_control_.AckFrame(cast_feedback.ack_frame_id_, now); + RtpTimestamp rtp_timestamp = frame_id_to_rtp_timestamp_[cast_feedback.ack_frame_id_ & 0xff]; - cast_environment_->Logging()->InsertFrameEvent( - cast_environment_->Clock()->NowTicks(), FRAME_ACK_RECEIVED, VIDEO_EVENT, - rtp_timestamp, cast_feedback.ack_frame_id_); + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_ACK_RECEIVED, + VIDEO_EVENT, + rtp_timestamp, + cast_feedback.ack_frame_id_); const bool is_acked_out_of_order = static_cast<int32>(cast_feedback.ack_frame_id_ - latest_acked_frame_id_) < 0; VLOG(2) << "Received ACK" << (is_acked_out_of_order ? " out-of-order" : "") << " for frame " << cast_feedback.ack_frame_id_; - if (!is_acked_out_of_order) + if (!is_acked_out_of_order) { + // Cancel resends of acked frames. + MissingFramesAndPacketsMap missing_frames_and_packets; + PacketIdSet missing; + while (latest_acked_frame_id_ != cast_feedback.ack_frame_id_) { + latest_acked_frame_id_++; + missing_frames_and_packets[latest_acked_frame_id_] = missing; + } + transport_sender_->ResendPackets(false, missing_frames_and_packets, true); latest_acked_frame_id_ = cast_feedback.ack_frame_id_; + } } bool VideoSender::AreTooManyFramesInFlight() const { @@ -354,21 +367,25 @@ bool VideoSender::AreTooManyFramesInFlight() const { return frames_in_flight >= max_unacked_frames_; } -void VideoSender::ResendFrame(uint32 resend_frame_id) { +void VideoSender::ResendForKickstart() { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); DCHECK(!last_send_time_.is_null()); + VLOG(1) << "Resending last packet of frame " << last_sent_frame_id_ + << " to kick-start."; + // Send the first packet of the last encoded frame to kick start + // retransmission. This gives enough information to the receiver what + // packets and frames are missing. MissingFramesAndPacketsMap missing_frames_and_packets; PacketIdSet missing; - missing_frames_and_packets.insert(std::make_pair(resend_frame_id, missing)); + missing.insert(kRtcpCastLastPacket); + missing_frames_and_packets.insert( + std::make_pair(last_sent_frame_id_, missing)); last_send_time_ = cast_environment_->Clock()->NowTicks(); - transport_sender_->ResendPackets(false, missing_frames_and_packets); -} -void VideoSender::UpdateBitrate(int new_bitrate) { - // Make sure we don't set the bitrate too insanely low. - DCHECK_GT(new_bitrate, 1000); - video_encoder_->SetBitRate(new_bitrate); - current_requested_bitrate_ = new_bitrate; + // Sending this extra packet is to kick-start the session. There is + // no need to optimize re-transmission for this case. + transport_sender_->ResendPackets(false, missing_frames_and_packets, + false); } } // namespace cast diff --git a/media/cast/video_sender/video_sender.h b/media/cast/video_sender/video_sender.h index 089680283b..30066cbbf8 100644 --- a/media/cast/video_sender/video_sender.h +++ b/media/cast/video_sender/video_sender.h @@ -84,8 +84,8 @@ class VideoSender : public RtcpSenderFeedback, void ScheduleNextResendCheck(); void ResendCheck(); - // Asks |transport_sender_| to resend all the packets for a particular frame. - void ResendFrame(uint32 resend_frame_id); + // Resend certain packets of an unacked frame to kick start re-transmission. + void ResendForKickstart(); // Returns true if there are too many frames in flight, as defined by the // configured target playout delay plus simple logic. When this is true, @@ -97,8 +97,6 @@ class VideoSender : public RtcpSenderFeedback, void SendEncodedVideoFrame(int requested_bitrate_before_encode, scoped_ptr<transport::EncodedFrame> encoded_frame); - void UpdateBitrate(int32 new_bitrate); - const scoped_refptr<CastEnvironment> cast_environment_; // The total amount of time between a frame's capture/recording on the sender @@ -161,10 +159,6 @@ class VideoSender : public RtcpSenderFeedback, // case, VideoSender will trigger a re-send of the next frame. int duplicate_ack_counter_; - // Desired encoder bitrate (in bits per second). This is updated by querying - // |congestion_control_| as each ACK is received. - int current_requested_bitrate_; - // When we get close to the max number of un-acked frames, we set lower // the bitrate drastically to ensure that we catch up. Without this we // risk getting stuck in a catch-up state forever. diff --git a/media/cast/video_sender/video_sender_unittest.cc b/media/cast/video_sender/video_sender_unittest.cc index faa3180a8c..49fae46c73 100644 --- a/media/cast/video_sender/video_sender_unittest.cc +++ b/media/cast/video_sender/video_sender_unittest.cc @@ -53,11 +53,19 @@ void CreateSharedMemory( class TestPacketSender : public transport::PacketSender { public: - TestPacketSender() : number_of_rtp_packets_(0), number_of_rtcp_packets_(0) {} + TestPacketSender() + : number_of_rtp_packets_(0), + number_of_rtcp_packets_(0), + paused_(false) {} // A singular packet implies a RTCP packet. virtual bool SendPacket(transport::PacketRef packet, const base::Closure& cb) OVERRIDE { + if (paused_) { + stored_packet_ = packet; + callback_ = cb; + return false; + } if (Rtcp::IsRtcpPacket(&packet->data[0], packet->data.size())) { ++number_of_rtcp_packets_; } else { @@ -76,9 +84,20 @@ class TestPacketSender : public transport::PacketSender { int number_of_rtcp_packets() const { return number_of_rtcp_packets_; } + void SetPause(bool paused) { + paused_ = paused; + if (!paused && stored_packet_) { + SendPacket(stored_packet_, callback_); + callback_.Run(); + } + } + private: int number_of_rtp_packets_; int number_of_rtcp_packets_; + bool paused_; + base::Closure callback_; + transport::PacketRef stored_packet_; DISALLOW_COPY_AND_ASSIGN(TestPacketSender); }; @@ -111,6 +130,7 @@ class VideoSenderTest : public ::testing::Test { task_runner_, task_runner_, task_runner_); + last_pixel_value_ = kPixelValue; net::IPEndPoint dummy_endpoint; transport_sender_.reset(new transport::CastTransportSenderImpl( NULL, @@ -179,16 +199,21 @@ class VideoSenderTest : public ::testing::Test { scoped_refptr<media::VideoFrame> video_frame = media::VideoFrame::CreateFrame( VideoFrame::I420, size, gfx::Rect(size), size, base::TimeDelta()); - PopulateVideoFrame(video_frame, kPixelValue); + PopulateVideoFrame(video_frame, last_pixel_value_++); + return video_frame; + } + + scoped_refptr<media::VideoFrame> GetLargeNewVideoFrame() { + gfx::Size size(kWidth, kHeight); + scoped_refptr<media::VideoFrame> video_frame = + media::VideoFrame::CreateFrame( + VideoFrame::I420, size, gfx::Rect(size), size, base::TimeDelta()); + PopulateVideoFrameWithNoise(video_frame); return video_frame; } void RunTasks(int during_ms) { - for (int i = 0; i < during_ms; ++i) { - // Call process the timers every 1 ms. - testing_clock_->Advance(base::TimeDelta::FromMilliseconds(1)); - task_runner_->RunTasks(); - } + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(during_ms)); } base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. @@ -197,6 +222,7 @@ class VideoSenderTest : public ::testing::Test { scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; scoped_ptr<PeerVideoSender> video_sender_; scoped_refptr<CastEnvironment> cast_environment_; + int last_pixel_value_; DISALLOW_COPY_AND_ASSIGN(VideoSenderTest); }; @@ -317,28 +343,29 @@ TEST_F(VideoSenderTest, StopSendingInTheAbsenceOfAck) { InitEncoder(false); // Send a stream of frames and don't ACK; by default we shouldn't have more // than 4 frames in flight. - // Store size in packets of frame 0, as it should be resent sue to timeout. scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); RunTasks(33); - const int size_of_frame0 = transport_.number_of_rtp_packets(); - for (int i = 1; i < 4; ++i) { + // Send 3 more frames and record the number of packets sent. + for (int i = 0; i < 3; ++i) { scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); RunTasks(33); } - const int number_of_packets_sent = transport_.number_of_rtp_packets(); - // Send 4 more frames - they should not be sent to the transport, as we have - // received any acks. + + // Send 3 more frames - they should not be encoded, as we have not received + // any acks. for (int i = 0; i < 3; ++i) { scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); RunTasks(33); } - EXPECT_EQ(number_of_packets_sent + size_of_frame0, + // We expect a frame to be retransmitted because of duplicated ACKs. + // Only one packet of the frame is re-transmitted. + EXPECT_EQ(number_of_packets_sent + 1, transport_.number_of_rtp_packets()); // Start acking and make sure we're back to steady-state. @@ -358,5 +385,141 @@ TEST_F(VideoSenderTest, StopSendingInTheAbsenceOfAck) { transport_.number_of_rtp_packets() + transport_.number_of_rtcp_packets()); } +TEST_F(VideoSenderTest, DuplicateAckRetransmit) { + InitEncoder(false); + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + + // Send 3 more frames but don't ACK. + for (int i = 0; i < 3; ++i) { + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + } + const int number_of_packets_sent = transport_.number_of_rtp_packets(); + + // Send duplicated ACKs and mix some invalid NACKs. + for (int i = 0; i < 10; ++i) { + RtcpCastMessage ack_feedback(1); + ack_feedback.media_ssrc_ = 2; + ack_feedback.ack_frame_id_ = 0; + RtcpCastMessage nack_feedback(1); + nack_feedback.media_ssrc_ = 2; + nack_feedback.missing_frames_and_packets_[255] = PacketIdSet(); + video_sender_->OnReceivedCastFeedback(ack_feedback); + video_sender_->OnReceivedCastFeedback(nack_feedback); + } + EXPECT_EQ(number_of_packets_sent, transport_.number_of_rtp_packets()); + + // Re-transmit one packet because of duplicated ACKs. + for (int i = 0; i < 3; ++i) { + RtcpCastMessage ack_feedback(1); + ack_feedback.media_ssrc_ = 2; + ack_feedback.ack_frame_id_ = 0; + video_sender_->OnReceivedCastFeedback(ack_feedback); + } + EXPECT_EQ(number_of_packets_sent + 1, transport_.number_of_rtp_packets()); +} + +TEST_F(VideoSenderTest, DuplicateAckRetransmitDoesNotCancelRetransmits) { + InitEncoder(false); + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + + // Send 2 more frames but don't ACK. + for (int i = 0; i < 2; ++i) { + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + } + // Pause the transport + transport_.SetPause(true); + + // Insert one more video frame. + video_frame = GetLargeNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + + const int number_of_packets_sent = transport_.number_of_rtp_packets(); + + // Send duplicated ACKs and mix some invalid NACKs. + for (int i = 0; i < 10; ++i) { + RtcpCastMessage ack_feedback(1); + ack_feedback.media_ssrc_ = 2; + ack_feedback.ack_frame_id_ = 0; + RtcpCastMessage nack_feedback(1); + nack_feedback.media_ssrc_ = 2; + nack_feedback.missing_frames_and_packets_[255] = PacketIdSet(); + video_sender_->OnReceivedCastFeedback(ack_feedback); + video_sender_->OnReceivedCastFeedback(nack_feedback); + } + EXPECT_EQ(number_of_packets_sent, transport_.number_of_rtp_packets()); + + // Re-transmit one packet because of duplicated ACKs. + for (int i = 0; i < 3; ++i) { + RtcpCastMessage ack_feedback(1); + ack_feedback.media_ssrc_ = 2; + ack_feedback.ack_frame_id_ = 0; + video_sender_->OnReceivedCastFeedback(ack_feedback); + } + + transport_.SetPause(false); + RunTasks(100); + EXPECT_LT(number_of_packets_sent + 1, transport_.number_of_rtp_packets()); +} + +TEST_F(VideoSenderTest, AcksCancelRetransmits) { + InitEncoder(false); + transport_.SetPause(true); + scoped_refptr<media::VideoFrame> video_frame = GetLargeNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + + // Frame should be in buffer, waiting. Now let's ack it. + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + video_sender_->OnReceivedCastFeedback(cast_feedback); + + transport_.SetPause(false); + RunTasks(33); + EXPECT_EQ(0, transport_.number_of_rtp_packets()); +} + +TEST_F(VideoSenderTest, NAcksCancelRetransmits) { + InitEncoder(false); + transport_.SetPause(true); + // Send two video frames. + scoped_refptr<media::VideoFrame> video_frame = GetLargeNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + video_frame = GetLargeNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + + // Frames should be in buffer, waiting. Now let's ack the first one and nack + // one packet in the second one. + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + PacketIdSet missing_packets; + missing_packets.insert(0); + cast_feedback.missing_frames_and_packets_[1] = missing_packets; + video_sender_->OnReceivedCastFeedback(cast_feedback); + + transport_.SetPause(false); + RunTasks(33); + // Only one packet should be retransmitted. + EXPECT_EQ(1, transport_.number_of_rtp_packets()); +} + } // namespace cast } // namespace media |