summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authormiu <miu@chromium.org>2015-08-28 13:08:05 -0700
committerCommit bot <commit-bot@chromium.org>2015-08-28 20:08:49 +0000
commit5bb258d1ca45372c06d67cbe83b00d4d7ba0b5d3 (patch)
tree645aa8683a8222c9fafe0359a7650bedc0ebabd7 /media
parent4d3cff16a3a2282f6c7b47772b8f57c572017179 (diff)
downloadchromium_src-5bb258d1ca45372c06d67cbe83b00d4d7ba0b5d3.zip
chromium_src-5bb258d1ca45372c06d67cbe83b00d4d7ba0b5d3.tar.gz
chromium_src-5bb258d1ca45372c06d67cbe83b00d4d7ba0b5d3.tar.bz2
Cast Streaming: Fix for excessive use of CPU by encoder.
This change limits the maximum target encode bitrate based on the resolution of the video. On low end machines, this is particularly important since encoding at too-high a bitrate can eat up all the available CPU and cause jank. BUG=524637 Review URL: https://codereview.chromium.org/1319873002 Cr-Commit-Position: refs/heads/master@{#346218}
Diffstat (limited to 'media')
-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