Index: media/filters/video_cadence_estimator.h |
diff --git a/media/filters/video_cadence_estimator.h b/media/filters/video_cadence_estimator.h |
index 30853f44f27cd40ff271d9974e705ab3d02d3223..89b2436c0df79062be95b4ebca4a327eb951ed49 100644 |
--- a/media/filters/video_cadence_estimator.h |
+++ b/media/filters/video_cadence_estimator.h |
@@ -5,6 +5,8 @@ |
#ifndef MEDIA_FILTERS_VIDEO_CADENCE_ESTIMATOR_H_ |
#define MEDIA_FILTERS_VIDEO_CADENCE_ESTIMATOR_H_ |
+#include <vector> |
+ |
#include "base/time/time.h" |
#include "media/base/media_export.h" |
@@ -14,30 +16,45 @@ namespace media { |
// render cadence which would allow for optimal uniformity of displayed frame |
// durations over time. |
// |
-// Cadence is the ratio of the frame duration to render interval length. I.e. |
-// for 30fps in 60Hz the cadence would be (1/30) / (1/60) = 60 / 30 = 2. It's |
-// common that this is not an exact integer, e.g., 29.974fps in 60Hz which |
-// would have a cadence of (1/29.974) / (1/60) = ~2.0029. |
+// Cadence is the ideal repeating frame pattern for a group of frames; currently |
+// VideoCadenceEstimator supports 1-frame ([N]), 2-frame ([3:2]), and N-frame |
+// fractional ([1:0:...:0]) cadences. Details on what this means are below. |
+// |
+// The perfect cadence of a set of frames is the ratio of the frame duration to |
+// render interval length. I.e. for 30fps in 60Hz the cadence would be (1/30) / |
+// (1/60) = 60 / 30 = 2. It's common that this is not an exact integer, e.g., |
+// 29.974fps in 60Hz which would have a cadence of (1/29.974) / (1/60) = |
+// ~2.0029. |
+// |
+// The perfect cadence is always a real number. All N-cadences [a1:a2:..:aN] |
+// where aK is an integer are an approximation of the perfect cadence; i.e. the |
+// average of [a1:..:aN] will approximately equal the perfect cadence. When N=1 |
+// we have a 1-frame cadence, when N=2, we have a 2-frame cadence, etc. |
// |
-// Clamped integer cadence means we round the actual cadence (~2.0029 in the |
-// pending example) to the nearest integer value (2 in this case). If the |
-// delta between those values is small, we can choose to render frames for the |
-// integer number of render intervals; shortening or lengthening the actual |
-// rendered frame duration. Doing so ensures each frame gets an optimal amount |
-// of display time. |
+// For single frame cadence we just round the perfect cadence (~2.0029 in the |
+// previous example) to the nearest integer value (2 in this case; which is |
+// denoted as a cadence of [2]). If the delta between those values is small we |
+// can choose to render frames for the integer number of render intervals; |
+// shortening or lengthening the actual rendered frame duration. Doing so |
+// ensures each frame gets an optimal amount of display time. |
// |
-// Obviously clamping cadence like that leads to drift over time of the actual |
-// VideoFrame timestamp relative to its rendered time, so we perform some |
-// calculations to ensure we only clamp cadence when it will take some time to |
-// drift an undesirable amount; see CalculateCadence() for details on how this |
-// calculation is made. |
+// The delta between the perfect cadence and the rounded cadence leads to drift |
+// over time of the actual VideoFrame timestamp relative to its rendered time, |
+// so we perform some calculations to ensure we only use a cadence when it will |
+// take some time to drift an undesirable amount; see CalculateCadence() for |
+// details on how this calculation is made. |
// |
-// Notably, this concept can be extended to include fractional cadence when the |
-// frame duration is shorter than the render interval; e.g. 120fps in 60Hz. In |
-// this case, the first frame in each group of N frames is displayed once, while |
-// the next N - 1 frames are dropped; where N is the fractional cadence of the |
-// frame group. Using the pending example N = 120/60 = 2. See implementations |
-// of CalculateCadence() and UpdateCadenceEstimate() for more details. |
+// 2-frame cadence is an extension of 1-frame cadence. Consider the case of |
+// 24fps in 60Hz, which has a perfect cadence of 2.5; rounding up to a cadence |
+// of 3 would cause drift to accumulate unusably fast. A better approximation |
+// of this cadence would be [3:2]. |
+// |
+// Fractional cadence is a special case of N-frame cadence which can be used |
+// when the frame duration is shorter than the render interval; e.g. 120fps in |
+// 60Hz. In this case, the first frame in each group of N frames is displayed |
+// once, while the next N - 1 frames are dropped; i.e. the cadence is of the |
+// form [1:0:..:0]. Using the previous example N = 120/60 = 2, which means the |
+// cadence would be [1:0]. See CalculateFractionalCadence() for more details. |
// |
// In practice this works out to the following for common setups if we use |
// cadence based selection: |
@@ -55,18 +72,18 @@ class MEDIA_EXPORT VideoCadenceEstimator { |
// As mentioned in the introduction, the determination of whether to clamp to |
// a given cadence is based on how long it takes before a frame would have to |
// be dropped or repeated to compensate for reaching the maximum acceptable |
- // drift; this time length is controlled by |minimum_time_until_glitch|. |
- explicit VideoCadenceEstimator(base::TimeDelta minimum_time_until_glitch); |
+ // drift; this time length is controlled by |minimum_time_until_max_drift|. |
+ explicit VideoCadenceEstimator(base::TimeDelta minimum_time_until_max_drift); |
~VideoCadenceEstimator(); |
// Clears stored cadence information. |
void Reset(); |
- // Updates the estimates for |cadence_| and |fractional_cadence_| based on the |
- // given values as described in the introduction above. |
+ // Updates the estimates for |cadence_| based on the given values as described |
+ // in the introduction above. |
// |
// Clients should call this and then update the cadence for all frames via the |
- // GetCadenceForFrame() method. |
+ // GetCadenceForFrame() method if the cadence changes. |
// |
// Cadence changes will not take affect until enough render intervals have |
// elapsed. For the purposes of hysteresis, each UpdateCadenceEstimate() call |
@@ -78,67 +95,90 @@ class MEDIA_EXPORT VideoCadenceEstimator { |
base::TimeDelta max_acceptable_drift); |
// Returns true if a useful cadence was found. |
- bool has_cadence() const { return cadence_ > 0; } |
+ bool has_cadence() const { return !cadence_.empty(); } |
- // Given a frame |index|, where zero is the most recently rendered frame, |
+ // Given a |frame_number|, where zero is the most recently rendered frame, |
// returns the ideal cadence for that frame. |
- int GetCadenceForFrame(int index) const; |
+ // |
+ // Note: Callers must track the base |frame_number| relative to all frames |
+ // rendered or removed after the first frame for which cadence is detected. |
+ // The first frame after cadence is detected has a |frame_number| of 0. |
+ // |
+ // Frames which come in before the last rendered frame should be ignored in |
+ // terms of impact to the base |frame_number|. |
+ int GetCadenceForFrame(uint64_t frame_number) const; |
void set_cadence_hysteresis_threshold_for_testing(base::TimeDelta threshold) { |
cadence_hysteresis_threshold_ = threshold; |
} |
- int get_cadence_for_testing() const { |
- return cadence_ && fractional_cadence_ ? fractional_cadence_ : cadence_; |
- } |
+ size_t cadence_size_for_testing() const { return cadence_.size(); } |
+ std::string GetCadenceForTesting() const { return CadenceToString(cadence_); } |
private: |
+ using Cadence = std::vector<int>; |
+ |
+ // Attempts to find a 1-frame, 2-frame, or N-frame fractional cadence; returns |
+ // the cadence vector if cadence is found and sets |time_until_max_drift| for |
+ // the computed cadence. |
+ Cadence CalculateCadence(base::TimeDelta render_interval, |
+ base::TimeDelta frame_duration, |
+ base::TimeDelta max_acceptable_drift, |
+ base::TimeDelta* time_until_max_drift) const; |
+ |
// Calculates the clamped cadence for the given |render_interval| and |
// |frame_duration|, then calculates how long that cadence can be used before |
// exhausting |max_acceptable_drift|. If the time until exhaustion is greater |
- // than |minimum_time_until_glitch_|, returns true and sets |cadence| to the |
- // clamped cadence. If the clamped cadence is unusable, |cadence| will be set |
- // to zero. |
- // |
- // If |fractional| is true, GetCadence() will calculate the clamped cadence |
- // using the ratio of the |render_interval| to |frame_duration| instead of |
- // vice versa. |
- // |
- // Sets |time_until_glitch| to the computed glitch time. Set to zero if the |
- // clamped cadence is unusable. |
- bool CalculateCadence(base::TimeDelta render_interval, |
- base::TimeDelta frame_duration, |
- base::TimeDelta max_acceptable_drift, |
- bool fractional, |
- int* cadence, |
- base::TimeDelta* time_until_glitch); |
- |
- // The idealized cadence for all frames seen thus far; updated based upon the |
- // ratio of |frame_duration| to |render_interval|, or vice versa, as given to |
- // UpdateCadenceEstimate(). Zero if no integer cadence could be detected. |
- // |
- // Fractional cadences are handled by strongly preferring the first frame in |
- // a series if it fits within acceptable drift. E.g., with 120fps content on |
- // a 60Hz monitor we'll strongly prefer the first frame of every 2 frames. |
+ // than |minimum_time_until_max_drift_|, returns true and sets |cadence| to |
+ // the clamped cadence. If the clamped cadence is unusable, |cadence| will be |
+ // set to zero. |
// |
- // |fractional_cadence_| is the number of frames per render interval; the |
- // first of which would be rendered and the rest dropped. |
- int cadence_; |
- int fractional_cadence_; |
- |
- // Used as hysteresis to prevent oscillation between cadence and coverage |
- // based rendering methods. Pending values are updated upon each new cadence |
- // detected by UpdateCadenceEstimate(). |
+ // Sets |time_until_max_drift| to the computed glitch time. Set to zero if |
+ // the clamped cadence is unusable. |
+ bool CalculateOneFrameCadence(base::TimeDelta render_interval, |
+ base::TimeDelta frame_duration, |
+ base::TimeDelta max_acceptable_drift, |
+ Cadence* cadence, |
+ base::TimeDelta* time_until_max_drift) const; |
+ |
+ // Similar to CalculateCadence() except it tries to find the ideal number of |
+ // frames which can fit into a |render_interval|; which means doing the same |
+ // calculations as CalculateCadence() but with the ratio of |render_interval| |
+ // to |frame_duration| instead of the other way around. |
+ bool CalculateFractionalCadence(base::TimeDelta render_interval, |
+ base::TimeDelta frame_duration, |
+ base::TimeDelta max_acceptable_drift, |
+ Cadence* cadence, |
+ base::TimeDelta* time_until_max_drift) const; |
+ |
+ // Converts a cadence vector into a human readable string of the form |
+ // "[a, b, ..., z]". |
+ std::string CadenceToString(const Cadence& cadence) const; |
+ |
+ // Returns true if the drift of the rendered frame duration versus its actual |
+ // frame duration take longer than |minimum_time_until_max_drift_| to exhaust |
+ // |max_acceptable_drift|. |time_until_max_drift| is set to how long it will |
+ // take before a glitch (frame drop or repeat occurs). |
+ bool IsAcceptableCadence(base::TimeDelta rendered_frame_duration, |
+ base::TimeDelta actual_frame_duration, |
+ base::TimeDelta max_acceptable_drift, |
+ base::TimeDelta* time_until_max_drift) const; |
+ |
+ // The approximate best N-frame cadence for all frames seen thus far; updated |
+ // by UpdateCadenceEstimate(). Empty when no cadence has been detected. |
+ Cadence cadence_; |
+ |
+ // Used as hysteresis to prevent oscillation between cadence approximations |
+ // for spurious blips in the render interval or frame duration. |
// |
// Once a new cadence is detected, |render_intervals_cadence_held_| is |
- // incremented for each UpdateCadenceEstimate() call where the cadence matches |
- // one of the pending values. |render_intervals_cadence_held_| is cleared |
- // when a "new" cadence matches |cadence_| or |pending_cadence_|. |
+ // incremented for each UpdateCadenceEstimate() call where |cadence_| matches |
+ // |pending_cadence_|. |render_intervals_cadence_held_| is cleared when a |
+ // "new" cadence matches |cadence_| or |pending_cadence_|. |
// |
// Once |kMinimumCadenceDurationMs| is exceeded in render intervals, the |
- // detected cadence is set in |cadence_| and |fractional_cadence_|. |
- int pending_cadence_; |
- int pending_fractional_cadence_; |
+ // detected cadence is set in |cadence_|. |
+ Cadence pending_cadence_; |
int render_intervals_cadence_held_; |
base::TimeDelta cadence_hysteresis_threshold_; |
@@ -149,7 +189,7 @@ class MEDIA_EXPORT VideoCadenceEstimator { |
// The minimum amount of time allowed before a glitch occurs before confirming |
// cadence for a given render interval and frame duration. |
- const base::TimeDelta minimum_time_until_glitch_; |
+ const base::TimeDelta minimum_time_until_max_drift_; |
DISALLOW_COPY_AND_ASSIGN(VideoCadenceEstimator); |
}; |