diff options
-rw-r--r-- | media/cast/sender/congestion_control.cc | 59 | ||||
-rw-r--r-- | media/cast/sender/congestion_control.h | 15 | ||||
-rw-r--r-- | media/cast/sender/congestion_control_unittest.cc | 31 | ||||
-rw-r--r-- | media/cast/sender/video_sender.cc | 67 | ||||
-rw-r--r-- | media/cast/sender/video_sender.h | 9 | ||||
-rw-r--r-- | media/cast/sender/video_sender_unittest.cc | 68 |
6 files changed, 193 insertions, 56 deletions
diff --git a/media/cast/sender/congestion_control.cc b/media/cast/sender/congestion_control.cc index bdf38df..b7be016 100644 --- a/media/cast/sender/congestion_control.cc +++ b/media/cast/sender/congestion_control.cc @@ -27,27 +27,22 @@ namespace cast { class AdaptiveCongestionControl : public CongestionControl { public: AdaptiveCongestionControl(base::TickClock* clock, - uint32 max_bitrate_configured, - uint32 min_bitrate_configured, + int max_bitrate_configured, + int min_bitrate_configured, double max_frame_rate); ~AdaptiveCongestionControl() final; + // CongestionControl implementation. void UpdateRtt(base::TimeDelta rtt) final; - void UpdateTargetPlayoutDelay(base::TimeDelta delay) final; - - // Called when an encoded frame is sent to the transport. void SendFrameToTransport(uint32 frame_id, size_t frame_size_in_bits, base::TimeTicks when) final; - - // Called when we receive an ACK for a frame. void AckFrame(uint32 frame_id, base::TimeTicks when) final; - - // Returns the bitrate we should use for the next frame. - uint32 GetBitrate(base::TimeTicks playout_time, - base::TimeDelta playout_delay) final; + int GetBitrate(base::TimeTicks playout_time, + base::TimeDelta playout_delay, + int soft_max_bitrate) final; private: struct FrameStats { @@ -78,8 +73,8 @@ class AdaptiveCongestionControl : public CongestionControl { double estimated_bitrate); base::TickClock* const clock_; // Not owned by this class. - const uint32 max_bitrate_configured_; - const uint32 min_bitrate_configured_; + const int max_bitrate_configured_; + const int min_bitrate_configured_; const double max_frame_rate_; std::deque<FrameStats> frame_stats_; uint32 last_frame_stats_; @@ -95,37 +90,33 @@ class AdaptiveCongestionControl : public CongestionControl { class FixedCongestionControl : public CongestionControl { public: - FixedCongestionControl(uint32 bitrate) : bitrate_(bitrate) {} + explicit FixedCongestionControl(int bitrate) : bitrate_(bitrate) {} ~FixedCongestionControl() final {} + // CongestionControl implementation. void UpdateRtt(base::TimeDelta rtt) final {} - void UpdateTargetPlayoutDelay(base::TimeDelta delay) final {} - - // Called when an encoded frame is sent to the transport. void SendFrameToTransport(uint32 frame_id, size_t frame_size_in_bits, base::TimeTicks when) final {} - - // Called when we receive an ACK for a frame. void AckFrame(uint32 frame_id, base::TimeTicks when) final {} - - // Returns the bitrate we should use for the next frame. - uint32 GetBitrate(base::TimeTicks playout_time, - base::TimeDelta playout_delay) final { + int GetBitrate(base::TimeTicks playout_time, + base::TimeDelta playout_delay, + int soft_max_bitrate) final { return bitrate_; } private: - uint32 bitrate_; + const int bitrate_; + DISALLOW_COPY_AND_ASSIGN(FixedCongestionControl); }; CongestionControl* NewAdaptiveCongestionControl( base::TickClock* clock, - uint32 max_bitrate_configured, - uint32 min_bitrate_configured, + int max_bitrate_configured, + int min_bitrate_configured, double max_frame_rate) { return new AdaptiveCongestionControl(clock, max_bitrate_configured, @@ -133,7 +124,7 @@ CongestionControl* NewAdaptiveCongestionControl( max_frame_rate); } -CongestionControl* NewFixedCongestionControl(uint32 bitrate) { +CongestionControl* NewFixedCongestionControl(int bitrate) { return new FixedCongestionControl(bitrate); } @@ -152,8 +143,8 @@ AdaptiveCongestionControl::FrameStats::FrameStats() : frame_size_in_bits(0) { AdaptiveCongestionControl::AdaptiveCongestionControl( base::TickClock* clock, - uint32 max_bitrate_configured, - uint32 min_bitrate_configured, + int max_bitrate_configured, + int min_bitrate_configured, double max_frame_rate) : clock_(clock), max_bitrate_configured_(max_bitrate_configured), @@ -165,6 +156,7 @@ AdaptiveCongestionControl::AdaptiveCongestionControl( history_size_(kHistorySize), acked_bits_in_history_(0) { DCHECK_GE(max_bitrate_configured, min_bitrate_configured) << "Invalid config"; + DCHECK_GT(min_bitrate_configured, 0); frame_stats_.resize(2); base::TimeTicks now = clock->NowTicks(); frame_stats_[0].ack_time = now; @@ -349,8 +341,9 @@ base::TimeTicks AdaptiveCongestionControl::EstimatedSendingTime( return estimated_sending_time; } -uint32 AdaptiveCongestionControl::GetBitrate(base::TimeTicks playout_time, - base::TimeDelta playout_delay) { +int AdaptiveCongestionControl::GetBitrate(base::TimeTicks playout_time, + base::TimeDelta playout_delay, + int soft_max_bitrate) { double safe_bitrate = CalculateSafeBitrate(); // Estimate when we might start sending the next frame. base::TimeDelta time_to_catch_up = @@ -362,13 +355,15 @@ uint32 AdaptiveCongestionControl::GetBitrate(base::TimeTicks playout_time, 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>( + int bits_per_second = static_cast<int>( 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::min(bits_per_second, soft_max_bitrate); 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; } diff --git a/media/cast/sender/congestion_control.h b/media/cast/sender/congestion_control.h index d2978b3..8e17134 100644 --- a/media/cast/sender/congestion_control.h +++ b/media/cast/sender/congestion_control.h @@ -31,18 +31,21 @@ class CongestionControl { // Called when we receive an ACK for a frame. virtual void AckFrame(uint32 frame_id, base::TimeTicks when) = 0; - // Returns the bitrate we should use for the next frame. - virtual uint32 GetBitrate(base::TimeTicks playout_time, - base::TimeDelta playout_delay) = 0; + // Returns the bitrate we should use for the next frame. |soft_max_bitrate| + // is a soft upper-bound applied to the computed target bitrate before the + // hard upper- and lower-bounds are applied. + virtual int GetBitrate(base::TimeTicks playout_time, + base::TimeDelta playout_delay, + int soft_max_bitrate) = 0; }; CongestionControl* NewAdaptiveCongestionControl( base::TickClock* clock, - uint32 max_bitrate_configured, - uint32 min_bitrate_configured, + int max_bitrate_configured, + int min_bitrate_configured, double max_frame_rate); -CongestionControl* NewFixedCongestionControl(uint32 bitrate); +CongestionControl* NewFixedCongestionControl(int bitrate); } // namespace cast } // namespace media diff --git a/media/cast/sender/congestion_control_unittest.cc b/media/cast/sender/congestion_control_unittest.cc index bf76a35..5293b58 100644 --- a/media/cast/sender/congestion_control_unittest.cc +++ b/media/cast/sender/congestion_control_unittest.cc @@ -14,8 +14,8 @@ namespace media { namespace cast { -static const uint32 kMaxBitrateConfigured = 5000000; -static const uint32 kMinBitrateConfigured = 500000; +static const int kMaxBitrateConfigured = 5000000; +static const int kMinBitrateConfigured = 500000; static const int64 kFrameDelayMs = 33; static const double kMaxFrameRate = 1000.0 / kFrameDelayMs; static const int64 kStartMillisecond = INT64_C(12345678900000); @@ -67,6 +67,9 @@ class CongestionControlTest : public ::testing::Test { DISALLOW_COPY_AND_ASSIGN(CongestionControlTest); }; +// Tests that AdaptiveCongestionControl returns reasonable bitrates based on +// estimations of network bandwidth and how much is in-flight (i.e, using the +// "target buffer fill" model). TEST_F(CongestionControlTest, SimpleRun) { uint32 frame_size = 10000 * 8; Run(500, @@ -77,23 +80,30 @@ TEST_F(CongestionControlTest, SimpleRun) { // Empty the buffer. task_runner_->Sleep(base::TimeDelta::FromMilliseconds(100)); + // Use a soft maximum bitrate limit so large it will not bound the results of + // the underlying computations. + const int soft_max_bitrate = std::numeric_limits<int>::max(); + uint32 safe_bitrate = frame_size * 1000 / kFrameDelayMs; uint32 bitrate = congestion_control_->GetBitrate( testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(300), - base::TimeDelta::FromMilliseconds(300)); + base::TimeDelta::FromMilliseconds(300), + soft_max_bitrate); 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)); + base::TimeDelta::FromMilliseconds(300), + soft_max_bitrate); 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)); + base::TimeDelta::FromMilliseconds(300), + soft_max_bitrate); EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 1 / 3, bitrate, safe_bitrate * 0.05); @@ -102,10 +112,11 @@ TEST_F(CongestionControlTest, SimpleRun) { congestion_control_->SendFrameToTransport( frame_id_++, safe_bitrate * 100 / 1000, testing_clock_.NowTicks()); - // Results should show that we have ~200ms to send + // Results should show that we have ~200ms to send. bitrate = congestion_control_->GetBitrate( testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(300), - base::TimeDelta::FromMilliseconds(300)); + base::TimeDelta::FromMilliseconds(300), + soft_max_bitrate); EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 2 / 3, bitrate, safe_bitrate * 0.05); @@ -114,15 +125,15 @@ TEST_F(CongestionControlTest, SimpleRun) { congestion_control_->SendFrameToTransport( frame_id_++, safe_bitrate * 100 / 1000, testing_clock_.NowTicks()); - // Resulst should show that we have ~100ms to send + // Results should show that we have ~100ms to send. bitrate = congestion_control_->GetBitrate( testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(300), - base::TimeDelta::FromMilliseconds(300)); + base::TimeDelta::FromMilliseconds(300), + soft_max_bitrate); EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 1 / 3, bitrate, safe_bitrate * 0.05); } - } // namespace cast } // namespace media diff --git a/media/cast/sender/video_sender.cc b/media/cast/sender/video_sender.cc index ebdde8e..3c57ebd 100644 --- a/media/cast/sender/video_sender.cc +++ b/media/cast/sender/video_sender.cc @@ -5,6 +5,7 @@ #include "media/cast/sender/video_sender.h" #include <algorithm> +#include <cmath> #include <cstring> #include "base/bind.h" @@ -192,18 +193,19 @@ void VideoSender::InsertRawVideoFrame( return; } - uint32 bitrate = congestion_control_->GetBitrate( - reference_time + target_playout_delay_, target_playout_delay_); - if (bitrate != last_bitrate_) { - video_encoder_->SetBitRate(bitrate); - last_bitrate_ = bitrate; - } - if (video_frame->visible_rect().IsEmpty()) { VLOG(1) << "Rejecting empty video frame."; return; } + const int bitrate = congestion_control_->GetBitrate( + reference_time + target_playout_delay_, target_playout_delay_, + GetMaximumTargetBitrateForFrame(*video_frame)); + if (bitrate != last_bitrate_) { + video_encoder_->SetBitRate(bitrate); + last_bitrate_ = bitrate; + } + MaybeRenderPerformanceMetricsOverlay(bitrate, frames_in_encoder_ + 1, last_reported_deadline_utilization_, @@ -248,6 +250,57 @@ void VideoSender::OnAck(uint32 frame_id) { video_encoder_->LatestFrameIdToReference(frame_id); } +// static +int VideoSender::GetMaximumTargetBitrateForFrame( + const media::VideoFrame& frame) { + enum { + // Constants used to linearly translate between lines of resolution and a + // maximum target bitrate. These values are based on observed quality + // trade-offs over a wide range of content. The math will use these values + // to compute a bitrate of 2 Mbps for 360 lines of resolution and 4 Mbps for + // 720 lines. + BITRATE_FOR_HIGH_RESOLUTION = 4000000, + BITRATE_FOR_STANDARD_RESOLUTION = 2000000, + HIGH_RESOLUTION_LINES = 720, + STANDARD_RESOLUTION_LINES = 360, + + // The smallest maximum target bitrate, regardless of what the math says. + MAX_BITRATE_LOWER_BOUND = 1000000, + + // Constants used to boost the result for high frame rate content. + HIGH_FRAME_RATE_THRESHOLD_USEC = 25000, // 40 FPS + HIGH_FRAME_RATE_BOOST_NUMERATOR = 3, + HIGH_FRAME_RATE_BOOST_DENOMINATOR = 2, + }; + + // Determine the approximate height of a 16:9 frame having the same area + // (number of pixels) as |frame|. + const gfx::Size& resolution = frame.visible_rect().size(); + const int lines_of_resolution = + ((resolution.width() * 9) == (resolution.height() * 16)) ? + resolution.height() : + static_cast<int>(sqrt(resolution.GetArea() * 9.0 / 16.0)); + + // Linearly translate from |lines_of_resolution| to a maximum target bitrate. + int64 result = lines_of_resolution - STANDARD_RESOLUTION_LINES; + result *= BITRATE_FOR_HIGH_RESOLUTION - BITRATE_FOR_STANDARD_RESOLUTION; + result /= HIGH_RESOLUTION_LINES - STANDARD_RESOLUTION_LINES; + result += BITRATE_FOR_STANDARD_RESOLUTION; + + // Boost the result for high frame rate content. + base::TimeDelta frame_duration; + if (frame.metadata()->GetTimeDelta(media::VideoFrameMetadata::FRAME_DURATION, + &frame_duration) && + frame_duration > base::TimeDelta() && + frame_duration.InMicroseconds() <= HIGH_FRAME_RATE_THRESHOLD_USEC) { + result *= HIGH_FRAME_RATE_BOOST_NUMERATOR; + result /= HIGH_FRAME_RATE_BOOST_DENOMINATOR; + } + + // Return a lower-bounded result. + return std::max<int>(result, MAX_BITRATE_LOWER_BOUND); +} + void VideoSender::OnEncodedVideoFrame( const scoped_refptr<media::VideoFrame>& video_frame, int encoder_bitrate, diff --git a/media/cast/sender/video_sender.h b/media/cast/sender/video_sender.h index ac5d6d0..826321c 100644 --- a/media/cast/sender/video_sender.h +++ b/media/cast/sender/video_sender.h @@ -64,6 +64,13 @@ class VideoSender : public FrameSender, base::TimeDelta GetInFlightMediaDuration() const final; void OnAck(uint32 frame_id) final; + // Return the maximum target bitrate that should be used for the given video + // |frame|. This will be provided to CongestionControl as a soft maximum + // limit, and should be interpreted as "the point above which the extra + // encoder CPU time + network bandwidth usage isn't warranted for the amount + // of further quality improvement to be gained." + static int GetMaximumTargetBitrateForFrame(const media::VideoFrame& frame); + private: // Called by the |video_encoder_| with the next EncodedFrame to send. void OnEncodedVideoFrame(const scoped_refptr<media::VideoFrame>& video_frame, @@ -87,7 +94,7 @@ class VideoSender : public FrameSender, // Remember what we set the bitrate to before, no need to set it again if // we get the same value. - uint32 last_bitrate_; + int last_bitrate_; PlayoutDelayChangeCB playout_delay_change_cb_; diff --git a/media/cast/sender/video_sender_unittest.cc b/media/cast/sender/video_sender_unittest.cc index 3edcea0..4e4996b 100644 --- a/media/cast/sender/video_sender_unittest.cc +++ b/media/cast/sender/video_sender_unittest.cc @@ -116,7 +116,29 @@ class PeerVideoSender : public VideoSender { transport_sender, base::Bind(&IgnorePlayoutDelayChanges)) {} using VideoSender::OnReceivedCastFeedback; + using VideoSender::GetMaximumTargetBitrateForFrame; }; + +// Creates a VideoFrame NOT backed by actual memory storage. The frame's +// metadata (i.e., size and frame duration) are all that are needed to test the +// GetMaximumTargetBitrateForFrame() logic. +scoped_refptr<VideoFrame> CreateFakeFrame(const gfx::Size& resolution, + bool high_frame_rate_in_metadata) { + const scoped_refptr<VideoFrame> frame = VideoFrame::WrapExternalData( + PIXEL_FORMAT_I420, + resolution, + gfx::Rect(resolution), + resolution, + static_cast<uint8*>(nullptr) + 1, + resolution.GetArea() * 3 / 2, + base::TimeDelta()); + const double frame_rate = high_frame_rate_in_metadata ? 60.0 : 30.0; + frame->metadata()->SetTimeDelta( + VideoFrameMetadata::FRAME_DURATION, + base::TimeDelta::FromSecondsD(1.0 / frame_rate)); + return frame; +} + } // namespace class VideoSenderTest : public ::testing::Test { @@ -593,5 +615,51 @@ TEST_F(VideoSenderTest, PopulatesResourceUtilizationInFrameMetadata) { } } +// Tests that VideoSender::GetMaximumTargetBitrateForFrame() returns the correct +// result for a number of frame resolution combinations. +TEST(VideoSenderMathTest, ComputesCorrectMaximumTargetBitratesForFrames) { + const struct { + int width; + int height; + bool high_frame_rate; + int expected_bitrate; + } kTestCases[] = { + // Standard 16:9 resolutions, non-HFR. + { 16, 9, false, 1000000 }, + { 320, 180, false, 1000000 }, + { 640, 360, false, 2000000 }, + { 800, 450, false, 2500000 }, + { 1280, 720, false, 4000000 }, + { 1920, 1080, false, 6000000 }, + { 3840, 2160, false, 12000000 }, + + // Standard 16:9 resolutions, HFR. + { 16, 9, true, 1000000 }, + { 320, 180, true, 1500000 }, + { 640, 360, true, 3000000 }, + { 800, 450, true, 3750000 }, + { 1280, 720, true, 6000000 }, + { 1920, 1080, true, 9000000 }, + { 3840, 2160, true, 18000000 }, + + // 4:3 and oddball resolutions. + { 640, 480, false, 2305555 }, + { 1024, 768, false, 3694444 }, + { 10, 5000, false, 1000000 }, + { 1234, 567, false, 3483333 }, + { 16384, 16384, true, 102399999 }, + }; + + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + const gfx::Size resolution(kTestCases[i].width, kTestCases[i].height); + SCOPED_TRACE(::testing::Message() << "resolution=" << resolution.ToString() + << ", hfr=" << kTestCases[i].high_frame_rate); + const scoped_refptr<VideoFrame> frame = + CreateFakeFrame(resolution, kTestCases[i].high_frame_rate); + EXPECT_EQ(kTestCases[i].expected_bitrate, + PeerVideoSender::GetMaximumTargetBitrateForFrame(*frame)); + } +} + } // namespace cast } // namespace media |