summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/cast/sender/congestion_control.cc59
-rw-r--r--media/cast/sender/congestion_control.h15
-rw-r--r--media/cast/sender/congestion_control_unittest.cc31
-rw-r--r--media/cast/sender/video_sender.cc67
-rw-r--r--media/cast/sender/video_sender.h9
-rw-r--r--media/cast/sender/video_sender_unittest.cc68
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