| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef COBALT_MEDIA_FILTERS_VIDEO_CADENCE_ESTIMATOR_H_ |
| #define COBALT_MEDIA_FILTERS_VIDEO_CADENCE_ESTIMATOR_H_ |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/basictypes.h" |
| #include "base/time/time.h" |
| #include "cobalt/media/base/media_export.h" |
| #include "starboard/types.h" |
| |
| namespace cobalt { |
| namespace media { |
| |
| // Estimates whether a given frame duration and render interval length have a |
| // render cadence which would allow for optimal uniformity of displayed frame |
| // durations over time. |
| // |
| // Cadence is the ideal repeating frame pattern for a group of frames; currently |
| // VideoCadenceEstimator supports N-frame ([a1:a2:..:aN]) cadences where N <= 5. |
| // 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. |
| // |
| // 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. |
| // |
| // For N-frame cadence, the idea is similar, we just round the perfect cadence |
| // to some K/N, where K is an integer, and distribute [floor(K/N), floor(K/N)+1] |
| // into the cadence vector as evenly as possible. For example, 23.97fps in |
| // 60Hz, the perfect cadence is 2.50313, we can round it to 2.5 = 5/2, and we |
| // can then construct the cadence vector as [2:3]. |
| // |
| // 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. |
| // |
| // In practice this works out to the following for common setups if we use |
| // cadence based selection: |
| // |
| // 29.5fps in 60Hz, ~17ms max drift => exhausted in ~1 second. |
| // 29.9fps in 60Hz, ~17ms max drift => exhausted in ~16.4 seconds. |
| // 24fps in 59.9Hz, ~21ms max drift => exhausted in ~12.6 seconds. |
| // 24.9fps in 60Hz, ~20ms max drift => exhausted in ~4.0 seconds. |
| // 59.9fps in 60Hz, ~8.3ms max drift => exhausted in ~8.2 seconds. |
| // 24.9fps in 50Hz, ~20ms max drift => exhausted in ~20.5 seconds. |
| // 120fps in 59.9Hz, ~8.3ms max drift => exhausted in ~8.2 seconds. |
| // |
| class MEDIA_EXPORT VideoCadenceEstimator { |
| public: |
| using Cadence = std::vector<int>; |
| |
| // 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_max_drift|. |
| explicit VideoCadenceEstimator(base::TimeDelta minimum_time_until_max_drift); |
| ~VideoCadenceEstimator(); |
| |
| // Clears stored cadence information. |
| void Reset(); |
| |
| // 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 if the cadence changes. |
| // |
| // Cadence changes will not take affect until enough render intervals have |
| // elapsed. For the purposes of hysteresis, each UpdateCadenceEstimate() call |
| // is assumed to elapse one |render_interval| worth of time. |
| // |
| // Returns true if the cadence has changed since the last call. |
| bool UpdateCadenceEstimate(base::TimeDelta render_interval, |
| base::TimeDelta frame_duration, |
| base::TimeDelta frame_duration_deviation, |
| base::TimeDelta max_acceptable_drift); |
| |
| // Returns true if a useful cadence was found. |
| bool has_cadence() const { return !cadence_.empty(); } |
| |
| // Given a |frame_number|, where zero is the most recently rendered frame, |
| // returns the ideal cadence for that frame. |
| // |
| // 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; |
| } |
| |
| size_t cadence_size_for_testing() const { return cadence_.size(); } |
| std::string GetCadenceForTesting() const { return CadenceToString(cadence_); } |
| |
| private: |
| // Attempts to find an N-frame cadence. Returns the cadence vector if cadence |
| // is found and sets |time_until_max_drift| for the computed cadence. If |
| // multiple cadences satisfying the max drift constraint exist, we are going |
| // to return the one with largest |time_until_max_drift|. |
| // For details on the math and algorithm, see https://goo.gl/QK0vbz |
| Cadence CalculateCadence(base::TimeDelta render_interval, |
| base::TimeDelta frame_duration, |
| base::TimeDelta max_acceptable_drift, |
| 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; |
| |
| // 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 |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_|. |
| Cadence pending_cadence_; |
| int render_intervals_cadence_held_; |
| base::TimeDelta cadence_hysteresis_threshold_; |
| |
| // Tracks how many times cadence has switched during a given playback, used to |
| // histogram the number of cadence changes in a playback. |
| bool first_update_call_; |
| int cadence_changes_; |
| |
| // 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_max_drift_; |
| |
| bool is_variable_frame_rate_; |
| |
| DISALLOW_COPY_AND_ASSIGN(VideoCadenceEstimator); |
| }; |
| |
| } // namespace media |
| } // namespace cobalt |
| |
| #endif // COBALT_MEDIA_FILTERS_VIDEO_CADENCE_ESTIMATOR_H_ |