diff options
Diffstat (limited to 'webrtc/modules/video_coding/utility')
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 |