aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/video_coding/utility
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/modules/video_coding/utility')
-rw-r--r--webrtc/modules/video_coding/utility/OWNERS5
-rw-r--r--webrtc/modules/video_coding/utility/frame_dropper.cc370
-rw-r--r--webrtc/modules/video_coding/utility/include/frame_dropper.h98
-rw-r--r--webrtc/modules/video_coding/utility/include/mock/mock_frame_dropper.h41
-rw-r--r--webrtc/modules/video_coding/utility/include/moving_average.h71
-rw-r--r--webrtc/modules/video_coding/utility/include/qp_parser.h30
-rw-r--r--webrtc/modules/video_coding/utility/include/quality_scaler.h67
-rw-r--r--webrtc/modules/video_coding/utility/include/vp8_header_parser.h77
-rw-r--r--webrtc/modules/video_coding/utility/qp_parser.cc28
-rw-r--r--webrtc/modules/video_coding/utility/quality_scaler.cc156
-rw-r--r--webrtc/modules/video_coding/utility/quality_scaler_unittest.cc419
-rw-r--r--webrtc/modules/video_coding/utility/video_coding_utility.gyp33
-rw-r--r--webrtc/modules/video_coding/utility/vp8_header_parser.cc207
13 files changed, 1602 insertions, 0 deletions
diff --git a/webrtc/modules/video_coding/utility/OWNERS b/webrtc/modules/video_coding/utility/OWNERS
new file mode 100644
index 0000000000..3ee6b4bf5f
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/OWNERS
@@ -0,0 +1,5 @@
+
+# These are for the common case of adding or renaming files. If you're doing
+# structural changes, please get a review from a reviewer in this file.
+per-file *.gyp=*
+per-file *.gypi=*
diff --git a/webrtc/modules/video_coding/utility/frame_dropper.cc b/webrtc/modules/video_coding/utility/frame_dropper.cc
new file mode 100644
index 0000000000..5262c5b88a
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/frame_dropper.cc
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/video_coding/utility/include/frame_dropper.h"
+
+#include "webrtc/system_wrappers/include/trace.h"
+
+namespace webrtc
+{
+
+const float kDefaultKeyFrameSizeAvgKBits = 0.9f;
+const float kDefaultKeyFrameRatio = 0.99f;
+const float kDefaultDropRatioAlpha = 0.9f;
+const float kDefaultDropRatioMax = 0.96f;
+const float kDefaultMaxTimeToDropFrames = 4.0f; // In seconds.
+
+FrameDropper::FrameDropper()
+:
+_keyFrameSizeAvgKbits(kDefaultKeyFrameSizeAvgKBits),
+_keyFrameRatio(kDefaultKeyFrameRatio),
+_dropRatio(kDefaultDropRatioAlpha, kDefaultDropRatioMax),
+_enabled(true),
+_max_time_drops(kDefaultMaxTimeToDropFrames)
+{
+ Reset();
+}
+
+FrameDropper::FrameDropper(float max_time_drops)
+:
+_keyFrameSizeAvgKbits(kDefaultKeyFrameSizeAvgKBits),
+_keyFrameRatio(kDefaultKeyFrameRatio),
+_dropRatio(kDefaultDropRatioAlpha, kDefaultDropRatioMax),
+_enabled(true),
+_max_time_drops(max_time_drops)
+{
+ Reset();
+}
+
+void
+FrameDropper::Reset()
+{
+ _keyFrameRatio.Reset(0.99f);
+ _keyFrameRatio.Apply(1.0f, 1.0f/300.0f); // 1 key frame every 10th second in 30 fps
+ _keyFrameSizeAvgKbits.Reset(0.9f);
+ _keyFrameCount = 0;
+ _accumulator = 0.0f;
+ _accumulatorMax = 150.0f; // assume 300 kb/s and 0.5 s window
+ _targetBitRate = 300.0f;
+ _incoming_frame_rate = 30;
+ _keyFrameSpreadFrames = 0.5f * _incoming_frame_rate;
+ _dropNext = false;
+ _dropRatio.Reset(0.9f);
+ _dropRatio.Apply(0.0f, 0.0f); // Initialize to 0
+ _dropCount = 0;
+ _windowSize = 0.5f;
+ _wasBelowMax = true;
+ _fastMode = false; // start with normal (non-aggressive) mode
+ // Cap for the encoder buffer level/accumulator, in secs.
+ _cap_buffer_size = 3.0f;
+ // Cap on maximum amount of dropped frames between kept frames, in secs.
+ _max_time_drops = 4.0f;
+}
+
+void
+FrameDropper::Enable(bool enable)
+{
+ _enabled = enable;
+}
+
+void
+FrameDropper::Fill(size_t frameSizeBytes, bool deltaFrame)
+{
+ if (!_enabled)
+ {
+ return;
+ }
+ float frameSizeKbits = 8.0f * static_cast<float>(frameSizeBytes) / 1000.0f;
+ if (!deltaFrame && !_fastMode) // fast mode does not treat key-frames any different
+ {
+ _keyFrameSizeAvgKbits.Apply(1, frameSizeKbits);
+ _keyFrameRatio.Apply(1.0, 1.0);
+ if (frameSizeKbits > _keyFrameSizeAvgKbits.filtered())
+ {
+ // Remove the average key frame size since we
+ // compensate for key frames when adding delta
+ // frames.
+ frameSizeKbits -= _keyFrameSizeAvgKbits.filtered();
+ }
+ else
+ {
+ // Shouldn't be negative, so zero is the lower bound.
+ frameSizeKbits = 0;
+ }
+ if (_keyFrameRatio.filtered() > 1e-5 &&
+ 1 / _keyFrameRatio.filtered() < _keyFrameSpreadFrames)
+ {
+ // We are sending key frames more often than our upper bound for
+ // how much we allow the key frame compensation to be spread
+ // out in time. Therefor we must use the key frame ratio rather
+ // than keyFrameSpreadFrames.
+ _keyFrameCount =
+ static_cast<int32_t>(1 / _keyFrameRatio.filtered() + 0.5);
+ }
+ else
+ {
+ // Compensate for the key frame the following frames
+ _keyFrameCount = static_cast<int32_t>(_keyFrameSpreadFrames + 0.5);
+ }
+ }
+ else
+ {
+ // Decrease the keyFrameRatio
+ _keyFrameRatio.Apply(1.0, 0.0);
+ }
+ // Change the level of the accumulator (bucket)
+ _accumulator += frameSizeKbits;
+ CapAccumulator();
+}
+
+void
+FrameDropper::Leak(uint32_t inputFrameRate)
+{
+ if (!_enabled)
+ {
+ return;
+ }
+ if (inputFrameRate < 1)
+ {
+ return;
+ }
+ if (_targetBitRate < 0.0f)
+ {
+ return;
+ }
+ _keyFrameSpreadFrames = 0.5f * inputFrameRate;
+ // T is the expected bits per frame (target). If all frames were the same size,
+ // we would get T bits per frame. Notice that T is also weighted to be able to
+ // force a lower frame rate if wanted.
+ float T = _targetBitRate / inputFrameRate;
+ if (_keyFrameCount > 0)
+ {
+ // Perform the key frame compensation
+ if (_keyFrameRatio.filtered() > 0 &&
+ 1 / _keyFrameRatio.filtered() < _keyFrameSpreadFrames)
+ {
+ T -= _keyFrameSizeAvgKbits.filtered() * _keyFrameRatio.filtered();
+ }
+ else
+ {
+ T -= _keyFrameSizeAvgKbits.filtered() / _keyFrameSpreadFrames;
+ }
+ _keyFrameCount--;
+ }
+ _accumulator -= T;
+ if (_accumulator < 0.0f)
+ {
+ _accumulator = 0.0f;
+ }
+ UpdateRatio();
+}
+
+void
+FrameDropper::UpdateNack(uint32_t nackBytes)
+{
+ if (!_enabled)
+ {
+ return;
+ }
+ _accumulator += static_cast<float>(nackBytes) * 8.0f / 1000.0f;
+}
+
+void
+FrameDropper::FillBucket(float inKbits, float outKbits)
+{
+ _accumulator += (inKbits - outKbits);
+}
+
+void
+FrameDropper::UpdateRatio()
+{
+ if (_accumulator > 1.3f * _accumulatorMax)
+ {
+ // Too far above accumulator max, react faster
+ _dropRatio.UpdateBase(0.8f);
+ }
+ else
+ {
+ // Go back to normal reaction
+ _dropRatio.UpdateBase(0.9f);
+ }
+ if (_accumulator > _accumulatorMax)
+ {
+ // We are above accumulator max, and should ideally
+ // drop a frame. Increase the dropRatio and drop
+ // the frame later.
+ if (_wasBelowMax)
+ {
+ _dropNext = true;
+ }
+ if (_fastMode)
+ {
+ // always drop in aggressive mode
+ _dropNext = true;
+ }
+
+ _dropRatio.Apply(1.0f, 1.0f);
+ _dropRatio.UpdateBase(0.9f);
+ }
+ else
+ {
+ _dropRatio.Apply(1.0f, 0.0f);
+ }
+ _wasBelowMax = _accumulator < _accumulatorMax;
+}
+
+// This function signals when to drop frames to the caller. It makes use of the dropRatio
+// to smooth out the drops over time.
+bool
+FrameDropper::DropFrame()
+{
+ if (!_enabled)
+ {
+ return false;
+ }
+ if (_dropNext)
+ {
+ _dropNext = false;
+ _dropCount = 0;
+ }
+
+ if (_dropRatio.filtered() >= 0.5f) // Drops per keep
+ {
+ // limit is the number of frames we should drop between each kept frame
+ // to keep our drop ratio. limit is positive in this case.
+ float denom = 1.0f - _dropRatio.filtered();
+ if (denom < 1e-5)
+ {
+ denom = (float)1e-5;
+ }
+ int32_t limit = static_cast<int32_t>(1.0f / denom - 1.0f + 0.5f);
+ // Put a bound on the max amount of dropped frames between each kept
+ // frame, in terms of frame rate and window size (secs).
+ int max_limit = static_cast<int>(_incoming_frame_rate *
+ _max_time_drops);
+ if (limit > max_limit) {
+ limit = max_limit;
+ }
+ if (_dropCount < 0)
+ {
+ // Reset the _dropCount since it was negative and should be positive.
+ if (_dropRatio.filtered() > 0.4f)
+ {
+ _dropCount = -_dropCount;
+ }
+ else
+ {
+ _dropCount = 0;
+ }
+ }
+ if (_dropCount < limit)
+ {
+ // As long we are below the limit we should drop frames.
+ _dropCount++;
+ return true;
+ }
+ else
+ {
+ // Only when we reset _dropCount a frame should be kept.
+ _dropCount = 0;
+ return false;
+ }
+ }
+ else if (_dropRatio.filtered() > 0.0f &&
+ _dropRatio.filtered() < 0.5f) // Keeps per drop
+ {
+ // limit is the number of frames we should keep between each drop
+ // in order to keep the drop ratio. limit is negative in this case,
+ // and the _dropCount is also negative.
+ float denom = _dropRatio.filtered();
+ if (denom < 1e-5)
+ {
+ denom = (float)1e-5;
+ }
+ int32_t limit = -static_cast<int32_t>(1.0f / denom - 1.0f + 0.5f);
+ if (_dropCount > 0)
+ {
+ // Reset the _dropCount since we have a positive
+ // _dropCount, and it should be negative.
+ if (_dropRatio.filtered() < 0.6f)
+ {
+ _dropCount = -_dropCount;
+ }
+ else
+ {
+ _dropCount = 0;
+ }
+ }
+ if (_dropCount > limit)
+ {
+ if (_dropCount == 0)
+ {
+ // Drop frames when we reset _dropCount.
+ _dropCount--;
+ return true;
+ }
+ else
+ {
+ // Keep frames as long as we haven't reached limit.
+ _dropCount--;
+ return false;
+ }
+ }
+ else
+ {
+ _dropCount = 0;
+ return false;
+ }
+ }
+ _dropCount = 0;
+ return false;
+
+ // A simpler version, unfiltered and quicker
+ //bool dropNext = _dropNext;
+ //_dropNext = false;
+ //return dropNext;
+}
+
+void
+FrameDropper::SetRates(float bitRate, float incoming_frame_rate)
+{
+ // Bit rate of -1 means infinite bandwidth.
+ _accumulatorMax = bitRate * _windowSize; // bitRate * windowSize (in seconds)
+ if (_targetBitRate > 0.0f && bitRate < _targetBitRate && _accumulator > _accumulatorMax)
+ {
+ // Rescale the accumulator level if the accumulator max decreases
+ _accumulator = bitRate / _targetBitRate * _accumulator;
+ }
+ _targetBitRate = bitRate;
+ CapAccumulator();
+ _incoming_frame_rate = incoming_frame_rate;
+}
+
+float
+FrameDropper::ActualFrameRate(uint32_t inputFrameRate) const
+{
+ if (!_enabled)
+ {
+ return static_cast<float>(inputFrameRate);
+ }
+ return inputFrameRate * (1.0f - _dropRatio.filtered());
+}
+
+// Put a cap on the accumulator, i.e., don't let it grow beyond some level.
+// This is a temporary fix for screencasting where very large frames from
+// encoder will cause very slow response (too many frame drops).
+void FrameDropper::CapAccumulator() {
+ float max_accumulator = _targetBitRate * _cap_buffer_size;
+ if (_accumulator > max_accumulator) {
+ _accumulator = max_accumulator;
+ }
+}
+
+}
diff --git a/webrtc/modules/video_coding/utility/include/frame_dropper.h b/webrtc/modules/video_coding/utility/include/frame_dropper.h
new file mode 100644
index 0000000000..2b78a7264f
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/include/frame_dropper.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_INCLUDE_FRAME_DROPPER_H_
+#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_INCLUDE_FRAME_DROPPER_H_
+
+#include <cstddef>
+
+#include "webrtc/base/exp_filter.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc
+{
+
+// The Frame Dropper implements a variant of the leaky bucket algorithm
+// for keeping track of when to drop frames to avoid bit rate
+// over use when the encoder can't keep its bit rate.
+class FrameDropper
+{
+public:
+ FrameDropper();
+ explicit FrameDropper(float max_time_drops);
+ virtual ~FrameDropper() {}
+
+ // Resets the FrameDropper to its initial state.
+ // This means that the frameRateWeight is set to its
+ // default value as well.
+ virtual void Reset();
+
+ virtual void Enable(bool enable);
+ // Answers the question if it's time to drop a frame
+ // if we want to reach a given frame rate. Must be
+ // called for every frame.
+ //
+ // Return value : True if we should drop the current frame
+ virtual bool DropFrame();
+ // Updates the FrameDropper with the size of the latest encoded
+ // frame. The FrameDropper calculates a new drop ratio (can be
+ // seen as the probability to drop a frame) and updates its
+ // internal statistics.
+ //
+ // Input:
+ // - frameSizeBytes : The size of the latest frame
+ // returned from the encoder.
+ // - deltaFrame : True if the encoder returned
+ // a key frame.
+ virtual void Fill(size_t frameSizeBytes, bool deltaFrame);
+
+ virtual void Leak(uint32_t inputFrameRate);
+
+ void UpdateNack(uint32_t nackBytes);
+
+ // Sets the target bit rate and the frame rate produced by
+ // the camera.
+ //
+ // Input:
+ // - bitRate : The target bit rate
+ virtual void SetRates(float bitRate, float incoming_frame_rate);
+
+ // Return value : The current average frame rate produced
+ // if the DropFrame() function is used as
+ // instruction of when to drop frames.
+ virtual float ActualFrameRate(uint32_t inputFrameRate) const;
+
+private:
+ void FillBucket(float inKbits, float outKbits);
+ void UpdateRatio();
+ void CapAccumulator();
+
+ rtc::ExpFilter _keyFrameSizeAvgKbits;
+ rtc::ExpFilter _keyFrameRatio;
+ float _keyFrameSpreadFrames;
+ int32_t _keyFrameCount;
+ float _accumulator;
+ float _accumulatorMax;
+ float _targetBitRate;
+ bool _dropNext;
+ rtc::ExpFilter _dropRatio;
+ int32_t _dropCount;
+ float _windowSize;
+ float _incoming_frame_rate;
+ bool _wasBelowMax;
+ bool _enabled;
+ bool _fastMode;
+ float _cap_buffer_size;
+ float _max_time_drops;
+}; // end of VCMFrameDropper class
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_VIDEO_CODING_UTILITY_INCLUDE_FRAME_DROPPER_H_
diff --git a/webrtc/modules/video_coding/utility/include/mock/mock_frame_dropper.h b/webrtc/modules/video_coding/utility/include/mock/mock_frame_dropper.h
new file mode 100644
index 0000000000..1e31e5442a
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/include/mock/mock_frame_dropper.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+#ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_INCLUDE_MOCK_MOCK_FRAME_DROPPER_H_
+#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_INCLUDE_MOCK_MOCK_FRAME_DROPPER_H_
+
+#include <string>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webrtc/modules/video_coding/utility/include/frame_dropper.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class MockFrameDropper : public FrameDropper {
+ public:
+ MOCK_METHOD0(Reset,
+ void());
+ MOCK_METHOD1(Enable,
+ void(bool enable));
+ MOCK_METHOD0(DropFrame,
+ bool());
+ MOCK_METHOD2(Fill,
+ void(size_t frameSizeBytes, bool deltaFrame));
+ MOCK_METHOD1(Leak,
+ void(uint32_t inputFrameRate));
+ MOCK_METHOD2(SetRates,
+ void(float bitRate, float incoming_frame_rate));
+ MOCK_CONST_METHOD1(ActualFrameRate,
+ float(uint32_t inputFrameRate));
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_VIDEO_CODING_UTILITY_INCLUDE_MOCK_MOCK_FRAME_DROPPER_H_
diff --git a/webrtc/modules/video_coding/utility/include/moving_average.h b/webrtc/modules/video_coding/utility/include/moving_average.h
new file mode 100644
index 0000000000..49c42c4ed4
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/include/moving_average.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_MOVING_AVERAGE_H_
+#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_MOVING_AVERAGE_H_
+
+#include <list>
+
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+template<class T>
+class MovingAverage {
+ public:
+ MovingAverage();
+ void AddSample(T sample);
+ bool GetAverage(size_t num_samples, T* average);
+ void Reset();
+ int size();
+
+ private:
+ T sum_;
+ std::list<T> samples_;
+};
+
+template<class T>
+MovingAverage<T>::MovingAverage() : sum_(static_cast<T>(0)) {
+}
+
+template<class T>
+void MovingAverage<T>::AddSample(T sample) {
+ samples_.push_back(sample);
+ sum_ += sample;
+}
+
+template<class T>
+bool MovingAverage<T>::GetAverage(size_t num_samples, T* avg) {
+ if (num_samples > samples_.size())
+ return false;
+
+ // Remove old samples.
+ while (num_samples < samples_.size()) {
+ sum_ -= samples_.front();
+ samples_.pop_front();
+ }
+
+ *avg = sum_ / static_cast<T>(num_samples);
+ return true;
+}
+
+template<class T>
+void MovingAverage<T>::Reset() {
+ sum_ = static_cast<T>(0);
+ samples_.clear();
+}
+
+template<class T>
+int MovingAverage<T>::size() {
+ return samples_.size();
+}
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_VIDEO_CODING_MOVING_AVERAGE_SCALER_H_
diff --git a/webrtc/modules/video_coding/utility/include/qp_parser.h b/webrtc/modules/video_coding/utility/include/qp_parser.h
new file mode 100644
index 0000000000..805b37b45c
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/include/qp_parser.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_QP_PARSER_H_
+#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_QP_PARSER_H_
+
+#include "webrtc/modules/video_coding/main/source/encoded_frame.h"
+
+namespace webrtc {
+
+class QpParser {
+ public:
+ QpParser() {}
+ ~QpParser() {}
+
+ // Parses an encoded |frame| and extracts the |qp|.
+ // Returns true on success, false otherwise.
+ bool GetQp(const VCMEncodedFrame& frame, int* qp);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_VIDEO_CODING_UTILITY_QP_PARSER_H_
diff --git a/webrtc/modules/video_coding/utility/include/quality_scaler.h b/webrtc/modules/video_coding/utility/include/quality_scaler.h
new file mode 100644
index 0000000000..29a1496c05
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/include/quality_scaler.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
+#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
+
+#include "webrtc/common_video/libyuv/include/scaler.h"
+#include "webrtc/modules/video_coding/utility/include/moving_average.h"
+
+namespace webrtc {
+class QualityScaler {
+ public:
+ static const int kDefaultLowQpDenominator;
+ static const int kDefaultMinDownscaleDimension;
+ struct Resolution {
+ int width;
+ int height;
+ };
+
+ QualityScaler();
+ void Init(int low_qp_threshold,
+ int high_qp_threshold,
+ bool use_framerate_reduction);
+ void SetMinResolution(int min_width, int min_height);
+ void ReportFramerate(int framerate);
+ void ReportQP(int qp);
+ void ReportDroppedFrame();
+ void Reset(int framerate, int bitrate, int width, int height);
+ void OnEncodeFrame(const VideoFrame& frame);
+ Resolution GetScaledResolution() const;
+ const VideoFrame& GetScaledFrame(const VideoFrame& frame);
+ int GetTargetFramerate() const;
+ int downscale_shift() const { return downscale_shift_; }
+
+ private:
+ void AdjustScale(bool up);
+ void ClearSamples();
+
+ Scaler scaler_;
+ VideoFrame scaled_frame_;
+
+ size_t num_samples_;
+ int framerate_;
+ int target_framerate_;
+ int low_qp_threshold_;
+ int high_qp_threshold_;
+ MovingAverage<int> framedrop_percent_;
+ MovingAverage<int> average_qp_;
+ Resolution res_;
+
+ int downscale_shift_;
+ int framerate_down_;
+ bool use_framerate_reduction_;
+ int min_width_;
+ int min_height_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
diff --git a/webrtc/modules/video_coding/utility/include/vp8_header_parser.h b/webrtc/modules/video_coding/utility/include/vp8_header_parser.h
new file mode 100644
index 0000000000..88796ecd0e
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/include/vp8_header_parser.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_VP8_PARSE_HEADER_H_
+#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_VP8_PARSE_HEADER_H_
+
+namespace webrtc {
+
+namespace vp8 {
+
+enum {
+ MB_FEATURE_TREE_PROBS = 3,
+ NUM_MB_SEGMENTS = 4,
+ NUM_REF_LF_DELTAS = 4,
+ NUM_MODE_LF_DELTAS = 4,
+};
+
+typedef struct VP8BitReader VP8BitReader;
+struct VP8BitReader {
+ // Boolean decoder.
+ uint32_t value_; // Current value.
+ uint32_t range_; // Current range minus 1. In [127, 254] interval.
+ int bits_; // Number of valid bits left.
+ // Read buffer.
+ const uint8_t* buf_; // Next byte to be read.
+ const uint8_t* buf_end_; // End of read buffer.
+ int eof_; // True if input is exhausted.
+};
+
+const uint8_t kVP8Log2Range[128] = {
+ 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0
+};
+
+// range = ((range - 1) << kVP8Log2Range[range]) + 1
+const uint8_t kVP8NewRange[128] = {
+ 127, 127, 191, 127, 159, 191, 223, 127,
+ 143, 159, 175, 191, 207, 223, 239, 127,
+ 135, 143, 151, 159, 167, 175, 183, 191,
+ 199, 207, 215, 223, 231, 239, 247, 127,
+ 131, 135, 139, 143, 147, 151, 155, 159,
+ 163, 167, 171, 175, 179, 183, 187, 191,
+ 195, 199, 203, 207, 211, 215, 219, 223,
+ 227, 231, 235, 239, 243, 247, 251, 127,
+ 129, 131, 133, 135, 137, 139, 141, 143,
+ 145, 147, 149, 151, 153, 155, 157, 159,
+ 161, 163, 165, 167, 169, 171, 173, 175,
+ 177, 179, 181, 183, 185, 187, 189, 191,
+ 193, 195, 197, 199, 201, 203, 205, 207,
+ 209, 211, 213, 215, 217, 219, 221, 223,
+ 225, 227, 229, 231, 233, 235, 237, 239,
+ 241, 243, 245, 247, 249, 251, 253, 127
+};
+
+// Gets the QP, QP range: [0, 127].
+// Returns true on success, false otherwise.
+bool GetQp(const uint8_t* buf, size_t length, int* qp);
+
+} // namespace vp8
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_VIDEO_CODING_UTILITY_VP8_PARSE_HEADER_H_
diff --git a/webrtc/modules/video_coding/utility/qp_parser.cc b/webrtc/modules/video_coding/utility/qp_parser.cc
new file mode 100644
index 0000000000..62ce31351e
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/qp_parser.cc
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/video_coding/utility/include/qp_parser.h"
+
+#include "webrtc/common_types.h"
+#include "webrtc/modules/video_coding/utility/include/vp8_header_parser.h"
+
+namespace webrtc {
+
+bool QpParser::GetQp(const VCMEncodedFrame& frame, int* qp) {
+ switch (frame.CodecSpecific()->codecType) {
+ case kVideoCodecVP8:
+ // QP range: [0, 127].
+ return vp8::GetQp(frame.Buffer(), frame.Length(), qp);
+ default:
+ return false;
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/video_coding/utility/quality_scaler.cc b/webrtc/modules/video_coding/utility/quality_scaler.cc
new file mode 100644
index 0000000000..ec7715230e
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/quality_scaler.cc
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
+
+namespace webrtc {
+
+static const int kMinFps = 10;
+static const int kMeasureSeconds = 5;
+static const int kFramedropPercentThreshold = 60;
+
+const int QualityScaler::kDefaultLowQpDenominator = 3;
+// Note that this is the same for width and height to permit 120x90 in both
+// portrait and landscape mode.
+const int QualityScaler::kDefaultMinDownscaleDimension = 90;
+
+QualityScaler::QualityScaler()
+ : num_samples_(0),
+ low_qp_threshold_(-1),
+ downscale_shift_(0),
+ framerate_down_(false),
+ min_width_(kDefaultMinDownscaleDimension),
+ min_height_(kDefaultMinDownscaleDimension) {
+}
+
+void QualityScaler::Init(int low_qp_threshold,
+ int high_qp_threshold,
+ bool use_framerate_reduction) {
+ ClearSamples();
+ low_qp_threshold_ = low_qp_threshold;
+ high_qp_threshold_ = high_qp_threshold;
+ use_framerate_reduction_ = use_framerate_reduction;
+ target_framerate_ = -1;
+}
+
+void QualityScaler::SetMinResolution(int min_width, int min_height) {
+ min_width_ = min_width;
+ min_height_ = min_height;
+}
+
+// Report framerate(fps) to estimate # of samples.
+void QualityScaler::ReportFramerate(int framerate) {
+ num_samples_ = static_cast<size_t>(
+ kMeasureSeconds * (framerate < kMinFps ? kMinFps : framerate));
+ framerate_ = framerate;
+}
+
+void QualityScaler::ReportQP(int qp) {
+ framedrop_percent_.AddSample(0);
+ average_qp_.AddSample(qp);
+}
+
+void QualityScaler::ReportDroppedFrame() {
+ framedrop_percent_.AddSample(100);
+}
+
+void QualityScaler::OnEncodeFrame(const VideoFrame& frame) {
+ // Should be set through InitEncode -> Should be set by now.
+ assert(low_qp_threshold_ >= 0);
+ assert(num_samples_ > 0);
+ res_.width = frame.width();
+ res_.height = frame.height();
+
+ // Update scale factor.
+ int avg_drop = 0;
+ int avg_qp = 0;
+
+ // When encoder consistently overshoots, framerate reduction and spatial
+ // resizing will be triggered to get a smoother video.
+ if ((framedrop_percent_.GetAverage(num_samples_, &avg_drop) &&
+ avg_drop >= kFramedropPercentThreshold) ||
+ (average_qp_.GetAverage(num_samples_, &avg_qp) &&
+ avg_qp > high_qp_threshold_)) {
+ // Reducing frame rate before spatial resolution change.
+ // Reduce frame rate only when it is above a certain number.
+ // Only one reduction is allowed for now.
+ // TODO(jackychen): Allow more than one framerate reduction.
+ if (use_framerate_reduction_ && !framerate_down_ && framerate_ >= 20) {
+ target_framerate_ = framerate_ / 2;
+ framerate_down_ = true;
+ // If frame rate has been updated, clear the buffer. We don't want
+ // spatial resolution to change right after frame rate change.
+ ClearSamples();
+ } else {
+ AdjustScale(false);
+ }
+ } else if (average_qp_.GetAverage(num_samples_, &avg_qp) &&
+ avg_qp <= low_qp_threshold_) {
+ if (use_framerate_reduction_ && framerate_down_) {
+ target_framerate_ = -1;
+ framerate_down_ = false;
+ ClearSamples();
+ } else {
+ AdjustScale(true);
+ }
+ }
+
+ assert(downscale_shift_ >= 0);
+ for (int shift = downscale_shift_;
+ shift > 0 && (res_.width / 2 >= min_width_) &&
+ (res_.height / 2 >= min_height_);
+ --shift) {
+ res_.width /= 2;
+ res_.height /= 2;
+ }
+}
+
+QualityScaler::Resolution QualityScaler::GetScaledResolution() const {
+ return res_;
+}
+
+int QualityScaler::GetTargetFramerate() const {
+ return target_framerate_;
+}
+
+const VideoFrame& QualityScaler::GetScaledFrame(const VideoFrame& frame) {
+ Resolution res = GetScaledResolution();
+ if (res.width == frame.width())
+ return frame;
+
+ scaler_.Set(frame.width(),
+ frame.height(),
+ res.width,
+ res.height,
+ kI420,
+ kI420,
+ kScaleBox);
+ if (scaler_.Scale(frame, &scaled_frame_) != 0)
+ return frame;
+
+ scaled_frame_.set_ntp_time_ms(frame.ntp_time_ms());
+ scaled_frame_.set_timestamp(frame.timestamp());
+ scaled_frame_.set_render_time_ms(frame.render_time_ms());
+
+ return scaled_frame_;
+}
+
+void QualityScaler::ClearSamples() {
+ framedrop_percent_.Reset();
+ average_qp_.Reset();
+}
+
+void QualityScaler::AdjustScale(bool up) {
+ downscale_shift_ += up ? -1 : 1;
+ if (downscale_shift_ < 0)
+ downscale_shift_ = 0;
+ ClearSamples();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
new file mode 100644
index 0000000000..2ce1107472
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+namespace {
+static const int kNumSeconds = 10;
+static const int kWidth = 1920;
+static const int kHalfWidth = kWidth / 2;
+static const int kHeight = 1080;
+static const int kFramerate = 30;
+static const int kLowQp = 15;
+static const int kNormalQp = 30;
+static const int kHighQp = 40;
+static const int kMaxQp = 56;
+} // namespace
+
+class QualityScalerTest : public ::testing::Test {
+ public:
+ // Temporal and spatial resolution.
+ struct Resolution {
+ int framerate;
+ int width;
+ int height;
+ };
+ protected:
+ enum ScaleDirection {
+ kKeepScaleAtHighQp,
+ kScaleDown,
+ kScaleDownAboveHighQp,
+ kScaleUp
+ };
+ enum BadQualityMetric { kDropFrame, kReportLowQP };
+
+ QualityScalerTest() {
+ input_frame_.CreateEmptyFrame(
+ kWidth, kHeight, kWidth, kHalfWidth, kHalfWidth);
+ qs_.Init(kMaxQp / QualityScaler::kDefaultLowQpDenominator, kHighQp, false);
+ qs_.ReportFramerate(kFramerate);
+ qs_.OnEncodeFrame(input_frame_);
+ }
+
+ bool TriggerScale(ScaleDirection scale_direction) {
+ qs_.OnEncodeFrame(input_frame_);
+ int initial_width = qs_.GetScaledResolution().width;
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ switch (scale_direction) {
+ case kScaleUp:
+ qs_.ReportQP(kLowQp);
+ break;
+ case kScaleDown:
+ qs_.ReportDroppedFrame();
+ break;
+ case kKeepScaleAtHighQp:
+ qs_.ReportQP(kHighQp);
+ break;
+ case kScaleDownAboveHighQp:
+ qs_.ReportQP(kHighQp + 1);
+ break;
+ }
+ qs_.OnEncodeFrame(input_frame_);
+ if (qs_.GetScaledResolution().width != initial_width)
+ return true;
+ }
+
+ return false;
+ }
+
+ void ExpectOriginalFrame() {
+ EXPECT_EQ(&input_frame_, &qs_.GetScaledFrame(input_frame_))
+ << "Using scaled frame instead of original input.";
+ }
+
+ void ExpectScaleUsingReportedResolution() {
+ qs_.OnEncodeFrame(input_frame_);
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ const VideoFrame& scaled_frame = qs_.GetScaledFrame(input_frame_);
+ EXPECT_EQ(res.width, scaled_frame.width());
+ EXPECT_EQ(res.height, scaled_frame.height());
+ }
+
+ void ContinuouslyDownscalesByHalfDimensionsAndBackUp();
+
+ void DoesNotDownscaleFrameDimensions(int width, int height);
+
+ Resolution TriggerResolutionChange(BadQualityMetric dropframe_lowqp,
+ int num_second,
+ int initial_framerate);
+
+ void VerifyQualityAdaptation(int initial_framerate, int seconds,
+ bool expect_spatial_resize,
+ bool expect_framerate_reduction);
+
+ void DownscaleEndsAt(int input_width,
+ int input_height,
+ int end_width,
+ int end_height);
+
+ QualityScaler qs_;
+ VideoFrame input_frame_;
+};
+
+TEST_F(QualityScalerTest, UsesOriginalFrameInitially) {
+ ExpectOriginalFrame();
+}
+
+TEST_F(QualityScalerTest, ReportsOriginalResolutionInitially) {
+ qs_.OnEncodeFrame(input_frame_);
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ EXPECT_EQ(input_frame_.width(), res.width);
+ EXPECT_EQ(input_frame_.height(), res.height);
+}
+
+TEST_F(QualityScalerTest, DownscalesAfterContinuousFramedrop) {
+ EXPECT_TRUE(TriggerScale(kScaleDown)) << "No downscale within " << kNumSeconds
+ << " seconds.";
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ EXPECT_LT(res.width, input_frame_.width());
+ EXPECT_LT(res.height, input_frame_.height());
+}
+
+TEST_F(QualityScalerTest, KeepsScaleAtHighQp) {
+ EXPECT_FALSE(TriggerScale(kKeepScaleAtHighQp))
+ << "Downscale at high threshold which should keep scale.";
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ EXPECT_EQ(res.width, input_frame_.width());
+ EXPECT_EQ(res.height, input_frame_.height());
+}
+
+TEST_F(QualityScalerTest, DownscalesAboveHighQp) {
+ EXPECT_TRUE(TriggerScale(kScaleDownAboveHighQp))
+ << "No downscale within " << kNumSeconds << " seconds.";
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ EXPECT_LT(res.width, input_frame_.width());
+ EXPECT_LT(res.height, input_frame_.height());
+}
+
+TEST_F(QualityScalerTest, DownscalesAfterTwoThirdsFramedrop) {
+ for (int i = 0; i < kFramerate * kNumSeconds / 3; ++i) {
+ qs_.ReportQP(kNormalQp);
+ qs_.ReportDroppedFrame();
+ qs_.ReportDroppedFrame();
+ qs_.OnEncodeFrame(input_frame_);
+ if (qs_.GetScaledResolution().width < input_frame_.width())
+ return;
+ }
+
+ FAIL() << "No downscale within " << kNumSeconds << " seconds.";
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleOnNormalQp) {
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ qs_.ReportQP(kNormalQp);
+ qs_.OnEncodeFrame(input_frame_);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution().width)
+ << "Unexpected scale on half framedrop.";
+ }
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleAfterHalfFramedrop) {
+ for (int i = 0; i < kFramerate * kNumSeconds / 2; ++i) {
+ qs_.ReportQP(kNormalQp);
+ qs_.OnEncodeFrame(input_frame_);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution().width)
+ << "Unexpected scale on half framedrop.";
+
+ qs_.ReportDroppedFrame();
+ qs_.OnEncodeFrame(input_frame_);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution().width)
+ << "Unexpected scale on half framedrop.";
+ }
+}
+
+void QualityScalerTest::ContinuouslyDownscalesByHalfDimensionsAndBackUp() {
+ const int initial_min_dimension = input_frame_.width() < input_frame_.height()
+ ? input_frame_.width()
+ : input_frame_.height();
+ int min_dimension = initial_min_dimension;
+ int current_shift = 0;
+ // Drop all frames to force-trigger downscaling.
+ while (min_dimension >= 2 * QualityScaler::kDefaultMinDownscaleDimension) {
+ EXPECT_TRUE(TriggerScale(kScaleDown)) << "No downscale within "
+ << kNumSeconds << " seconds.";
+ qs_.OnEncodeFrame(input_frame_);
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ min_dimension = res.width < res.height ? res.width : res.height;
+ ++current_shift;
+ ASSERT_EQ(input_frame_.width() >> current_shift, res.width);
+ ASSERT_EQ(input_frame_.height() >> current_shift, res.height);
+ ExpectScaleUsingReportedResolution();
+ }
+
+ // Make sure we can scale back with good-quality frames.
+ while (min_dimension < initial_min_dimension) {
+ EXPECT_TRUE(TriggerScale(kScaleUp)) << "No upscale within " << kNumSeconds
+ << " seconds.";
+ qs_.OnEncodeFrame(input_frame_);
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ min_dimension = res.width < res.height ? res.width : res.height;
+ --current_shift;
+ ASSERT_EQ(input_frame_.width() >> current_shift, res.width);
+ ASSERT_EQ(input_frame_.height() >> current_shift, res.height);
+ ExpectScaleUsingReportedResolution();
+ }
+
+ // Verify we don't start upscaling after further low use.
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ qs_.ReportQP(kLowQp);
+ ExpectOriginalFrame();
+ }
+}
+
+TEST_F(QualityScalerTest, ContinuouslyDownscalesByHalfDimensionsAndBackUp) {
+ ContinuouslyDownscalesByHalfDimensionsAndBackUp();
+}
+
+TEST_F(QualityScalerTest,
+ ContinuouslyDownscalesOddResolutionsByHalfDimensionsAndBackUp) {
+ const int kOddWidth = 517;
+ const int kHalfOddWidth = (kOddWidth + 1) / 2;
+ const int kOddHeight = 1239;
+ input_frame_.CreateEmptyFrame(
+ kOddWidth, kOddHeight, kOddWidth, kHalfOddWidth, kHalfOddWidth);
+ ContinuouslyDownscalesByHalfDimensionsAndBackUp();
+}
+
+void QualityScalerTest::DoesNotDownscaleFrameDimensions(int width, int height) {
+ input_frame_.CreateEmptyFrame(
+ width, height, width, (width + 1) / 2, (width + 1) / 2);
+
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ qs_.ReportDroppedFrame();
+ qs_.OnEncodeFrame(input_frame_);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution().width)
+ << "Unexpected scale of minimal-size frame.";
+ }
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleFrom1PxWidth) {
+ DoesNotDownscaleFrameDimensions(1, kHeight);
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleFrom1PxHeight) {
+ DoesNotDownscaleFrameDimensions(kWidth, 1);
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleFrom1Px) {
+ DoesNotDownscaleFrameDimensions(1, 1);
+}
+
+QualityScalerTest::Resolution QualityScalerTest::TriggerResolutionChange(
+ BadQualityMetric dropframe_lowqp, int num_second, int initial_framerate) {
+ QualityScalerTest::Resolution res;
+ res.framerate = initial_framerate;
+ qs_.OnEncodeFrame(input_frame_);
+ res.width = qs_.GetScaledResolution().width;
+ res.height = qs_.GetScaledResolution().height;
+ for (int i = 0; i < kFramerate * num_second; ++i) {
+ switch (dropframe_lowqp) {
+ case kReportLowQP:
+ qs_.ReportQP(kLowQp);
+ break;
+ case kDropFrame:
+ qs_.ReportDroppedFrame();
+ break;
+ }
+ qs_.OnEncodeFrame(input_frame_);
+ // Simulate the case when SetRates is called right after reducing
+ // framerate.
+ qs_.ReportFramerate(initial_framerate);
+ res.framerate = qs_.GetTargetFramerate();
+ if (res.framerate != -1)
+ qs_.ReportFramerate(res.framerate);
+ res.width = qs_.GetScaledResolution().width;
+ res.height = qs_.GetScaledResolution().height;
+ }
+ return res;
+}
+
+void QualityScalerTest::VerifyQualityAdaptation(
+ int initial_framerate, int seconds, bool expect_spatial_resize,
+ bool expect_framerate_reduction) {
+ const int kDisabledBadQpThreshold = kMaxQp + 1;
+ qs_.Init(kMaxQp / QualityScaler::kDefaultLowQpDenominator,
+ kDisabledBadQpThreshold, true);
+ qs_.OnEncodeFrame(input_frame_);
+ int init_width = qs_.GetScaledResolution().width;
+ int init_height = qs_.GetScaledResolution().height;
+
+ // Test reducing framerate by dropping frame continuously.
+ QualityScalerTest::Resolution res = TriggerResolutionChange(
+ kDropFrame, seconds, initial_framerate);
+
+ if (expect_framerate_reduction) {
+ EXPECT_LT(res.framerate, initial_framerate);
+ } else {
+ // No framerate reduction, video decimator should be disabled.
+ EXPECT_EQ(-1, res.framerate);
+ }
+
+ if (expect_spatial_resize) {
+ EXPECT_LT(res.width, init_width);
+ EXPECT_LT(res.height, init_height);
+ } else {
+ EXPECT_EQ(init_width, res.width);
+ EXPECT_EQ(init_height, res.height);
+ }
+
+ // The "seconds * 1.5" is to ensure spatial resolution to recover.
+ // For example, in 10 seconds test, framerate reduction happens in the first
+ // 5 seconds from 30fps to 15fps and causes the buffer size to be half of the
+ // original one. Then it will take only 75 samples to downscale (twice in 150
+ // samples). So to recover the resolution changes, we need more than 10
+ // seconds (i.e, seconds * 1.5). This is because the framerate increases
+ // before spatial size recovers, so it will take 150 samples to recover
+ // spatial size (300 for twice).
+ res = TriggerResolutionChange(kReportLowQP, seconds * 1.5, initial_framerate);
+ EXPECT_EQ(-1, res.framerate);
+ EXPECT_EQ(init_width, res.width);
+ EXPECT_EQ(init_height, res.height);
+}
+
+// In 5 seconds test, only framerate adjusting should happen.
+TEST_F(QualityScalerTest, ChangeFramerateOnly) {
+ VerifyQualityAdaptation(kFramerate, 5, false, true);
+}
+
+// In 10 seconds test, framerate adjusting and scaling are both
+// triggered, it shows that scaling would happen after framerate
+// adjusting.
+TEST_F(QualityScalerTest, ChangeFramerateAndSpatialSize) {
+ VerifyQualityAdaptation(kFramerate, 10, true, true);
+}
+
+// When starting from a low framerate, only spatial size will be changed.
+TEST_F(QualityScalerTest, ChangeSpatialSizeOnly) {
+ qs_.ReportFramerate(kFramerate >> 1);
+ VerifyQualityAdaptation(kFramerate >> 1, 10, true, false);
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleBelow2xDefaultMinDimensionsWidth) {
+ DoesNotDownscaleFrameDimensions(
+ 2 * QualityScaler::kDefaultMinDownscaleDimension - 1, 1000);
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleBelow2xDefaultMinDimensionsHeight) {
+ DoesNotDownscaleFrameDimensions(
+ 1000, 2 * QualityScaler::kDefaultMinDownscaleDimension - 1);
+}
+
+void QualityScalerTest::DownscaleEndsAt(int input_width,
+ int input_height,
+ int end_width,
+ int end_height) {
+ // Create a frame with 2x expected end width/height to verify that we can
+ // scale down to expected end width/height.
+ input_frame_.CreateEmptyFrame(input_width, input_height, input_width,
+ (input_width + 1) / 2, (input_width + 1) / 2);
+
+ int last_width = input_width;
+ int last_height = input_height;
+ // Drop all frames to force-trigger downscaling.
+ while (true) {
+ TriggerScale(kScaleDown);
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ if (last_width == res.width) {
+ EXPECT_EQ(last_height, res.height);
+ EXPECT_EQ(end_width, res.width);
+ EXPECT_EQ(end_height, res.height);
+ break;
+ }
+ last_width = res.width;
+ last_height = res.height;
+ }
+}
+
+TEST_F(QualityScalerTest, DefaultDownscalesTo160x90) {
+ DownscaleEndsAt(320, 180, 160, 90);
+}
+
+TEST_F(QualityScalerTest, DefaultDownscalesTo90x160) {
+ DownscaleEndsAt(180, 320, 90, 160);
+}
+
+TEST_F(QualityScalerTest, DefaultDownscalesFrom1280x720To160x90) {
+ DownscaleEndsAt(1280, 720, 160, 90);
+}
+
+TEST_F(QualityScalerTest, DefaultDoesntDownscaleBelow160x90) {
+ DownscaleEndsAt(320 - 1, 180 - 1, 320 - 1, 180 - 1);
+}
+
+TEST_F(QualityScalerTest, DefaultDoesntDownscaleBelow90x160) {
+ DownscaleEndsAt(180 - 1, 320 - 1, 180 - 1, 320 - 1);
+}
+
+TEST_F(QualityScalerTest, RespectsMinResolutionWidth) {
+ // Should end at 200x100, as width can't go lower.
+ qs_.SetMinResolution(200, 10);
+ DownscaleEndsAt(1600, 800, 200, 100);
+}
+
+TEST_F(QualityScalerTest, RespectsMinResolutionHeight) {
+ // Should end at 100x200, as height can't go lower.
+ qs_.SetMinResolution(10, 200);
+ DownscaleEndsAt(800, 1600, 100, 200);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/video_coding/utility/video_coding_utility.gyp b/webrtc/modules/video_coding/utility/video_coding_utility.gyp
new file mode 100644
index 0000000000..f0764bb7bf
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/video_coding_utility.gyp
@@ -0,0 +1,33 @@
+# Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+
+{
+ 'includes': [
+ '../../../build/common.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'video_coding_utility',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers',
+ ],
+ 'sources': [
+ 'frame_dropper.cc',
+ 'include/frame_dropper.h',
+ 'include/moving_average.h',
+ 'include/qp_parser.h',
+ 'include/quality_scaler.h',
+ 'include/vp8_header_parser.h',
+ 'qp_parser.cc',
+ 'quality_scaler.cc',
+ 'vp8_header_parser.cc',
+ ],
+ },
+ ], # targets
+}
diff --git a/webrtc/modules/video_coding/utility/vp8_header_parser.cc b/webrtc/modules/video_coding/utility/vp8_header_parser.cc
new file mode 100644
index 0000000000..dc5a0e5d15
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/vp8_header_parser.cc
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+#include <stdint.h>
+#include <stdio.h>
+
+#include "webrtc/modules/video_coding/utility/include/vp8_header_parser.h"
+
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+namespace vp8 {
+namespace {
+const size_t kCommonPayloadHeaderLength = 3;
+const size_t kKeyPayloadHeaderLength = 10;
+} // namespace
+
+static uint32_t BSwap32(uint32_t x) {
+ return (x >> 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x << 24);
+}
+
+static void VP8LoadFinalBytes(VP8BitReader* const br) {
+ // Only read 8bits at a time.
+ if (br->buf_ < br->buf_end_) {
+ br->bits_ += 8;
+ br->value_ = static_cast<uint32_t>(*br->buf_++) | (br->value_ << 8);
+ } else if (!br->eof_) {
+ br->value_ <<= 8;
+ br->bits_ += 8;
+ br->eof_ = 1;
+ }
+}
+
+static void VP8LoadNewBytes(VP8BitReader* const br) {
+ int BITS = 24;
+ // Read 'BITS' bits at a time.
+ if (br->buf_ + sizeof(uint32_t) <= br->buf_end_) {
+ uint32_t bits;
+ const uint32_t in_bits = *(const uint32_t*)(br->buf_);
+ br->buf_ += BITS >> 3;
+#if defined(WEBRTC_ARCH_BIG_ENDIAN)
+ bits = static_cast<uint32_t>(in_bits);
+ if (BITS != 8 * sizeof(uint32_t))
+ bits >>= (8 * sizeof(uint32_t) - BITS);
+#else
+ bits = BSwap32(in_bits);
+ bits >>= 32 - BITS;
+#endif
+ br->value_ = bits | (br->value_ << BITS);
+ br->bits_ += BITS;
+ } else {
+ VP8LoadFinalBytes(br);
+ }
+}
+
+static void VP8InitBitReader(VP8BitReader* const br,
+ const uint8_t* const start,
+ const uint8_t* const end) {
+ br->range_ = 255 - 1;
+ br->buf_ = start;
+ br->buf_end_ = end;
+ br->value_ = 0;
+ br->bits_ = -8; // To load the very first 8bits.
+ br->eof_ = 0;
+ VP8LoadNewBytes(br);
+}
+
+// Read a bit with proba 'prob'.
+static int VP8GetBit(VP8BitReader* const br, int prob) {
+ uint8_t range = br->range_;
+ if (br->bits_ < 0) {
+ VP8LoadNewBytes(br);
+ }
+
+ const int pos = br->bits_;
+ const uint8_t split = (range * prob) >> 8;
+ const uint8_t value = static_cast<uint8_t>(br->value_ >> pos);
+ int bit;
+ if (value > split) {
+ range -= split + 1;
+ br->value_ -= static_cast<uint32_t>(split + 1) << pos;
+ bit = 1;
+ } else {
+ range = split;
+ bit = 0;
+ }
+ if (range <= static_cast<uint8_t>(0x7e)) {
+ const int shift = kVP8Log2Range[range];
+ range = kVP8NewRange[range];
+ br->bits_ -= shift;
+ }
+ br->range_ = range;
+ return bit;
+}
+
+static uint32_t VP8GetValue(VP8BitReader* const br, int bits) {
+ uint32_t v = 0;
+ while (bits-- > 0) {
+ v |= VP8GetBit(br, 0x80) << bits;
+ }
+ return v;
+}
+
+static uint32_t VP8Get(VP8BitReader* const br) {
+ return VP8GetValue(br, 1);
+}
+
+static int32_t VP8GetSignedValue(VP8BitReader* const br, int bits) {
+ const int value = VP8GetValue(br, bits);
+ return VP8Get(br) ? -value : value;
+}
+
+static void ParseSegmentHeader(VP8BitReader* br) {
+ int use_segment = VP8Get(br);
+ if (use_segment) {
+ int update_map = VP8Get(br);
+ if (VP8Get(br)) {
+ int s;
+ VP8Get(br);
+ for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
+ VP8Get(br) ? VP8GetSignedValue(br, 7) : 0;
+ }
+ for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
+ VP8Get(br) ? VP8GetSignedValue(br, 6) : 0;
+ }
+ }
+ if (update_map) {
+ int s;
+ for (s = 0; s < MB_FEATURE_TREE_PROBS; ++s) {
+ VP8Get(br) ? VP8GetValue(br, 8) : 255;
+ }
+ }
+ }
+}
+
+static void ParseFilterHeader(VP8BitReader* br) {
+ VP8Get(br);
+ VP8GetValue(br, 6);
+ VP8GetValue(br, 3);
+ int use_lf_delta = VP8Get(br);
+ if (use_lf_delta) {
+ if (VP8Get(br)) {
+ int i;
+ for (i = 0; i < NUM_REF_LF_DELTAS; ++i) {
+ if (VP8Get(br)) {
+ VP8GetSignedValue(br, 6);
+ }
+ }
+ for (i = 0; i < NUM_MODE_LF_DELTAS; ++i) {
+ if (VP8Get(br)) {
+ VP8GetSignedValue(br, 6);
+ }
+ }
+ }
+ }
+}
+
+bool GetQp(const uint8_t* buf, size_t length, int* qp) {
+ if (length < kCommonPayloadHeaderLength) {
+ LOG(LS_WARNING) << "Failed to get QP, invalid length.";
+ return false;
+ }
+ VP8BitReader br;
+ const uint32_t bits = buf[0] | (buf[1] << 8) | (buf[2] << 16);
+ int key_frame = !(bits & 1);
+ // Size of first partition in bytes.
+ uint32_t partition_length = (bits >> 5);
+ size_t header_length = kCommonPayloadHeaderLength;
+ if (key_frame) {
+ header_length = kKeyPayloadHeaderLength;
+ }
+ if (header_length + partition_length > length) {
+ LOG(LS_WARNING) << "Failed to get QP, invalid length: " << length;
+ return false;
+ }
+ buf += header_length;
+
+ VP8InitBitReader(&br, buf, buf + partition_length);
+ if (key_frame) {
+ // Color space and pixel type.
+ VP8Get(&br);
+ VP8Get(&br);
+ }
+ ParseSegmentHeader(&br);
+ ParseFilterHeader(&br);
+ // Number of coefficient data partitions.
+ VP8GetValue(&br, 2);
+ // Base QP.
+ const int base_q0 = VP8GetValue(&br, 7);
+ if (br.eof_ == 1) {
+ LOG(LS_WARNING) << "Failed to get QP, end of file reached.";
+ return false;
+ }
+ *qp = base_q0;
+ return true;
+}
+
+} // namespace vp8
+
+} // namespace webrtc