// Copyright (c) 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 MEDIA_CAPTURE_CONTENT_ANIMATED_CONTENT_SAMPLER_H_
#define MEDIA_CAPTURE_CONTENT_ANIMATED_CONTENT_SAMPLER_H_

#include <deque>

#include "base/time/time.h"
#include "media/capture/capture_export.h"
#include "ui/gfx/geometry/rect.h"

namespace media {

// Analyzes a sequence of events to detect the presence of constant frame rate
// animated content.  In the case where there are multiple regions of animated
// content, AnimatedContentSampler will propose sampling the one having the
// largest "smoothness" impact, according to human perception (e.g., a 24 FPS
// video versus a 60 FPS busy spinner).
//
// In addition, AnimatedContentSampler will provide rewritten frame timestamps,
// for downstream consumers, that are "truer" to the source content than to the
// local presentation hardware.
class CAPTURE_EXPORT AnimatedContentSampler {
 public:
  explicit AnimatedContentSampler(base::TimeDelta min_capture_period);
  ~AnimatedContentSampler();

  // Get/Set the target sampling period.  This is used to determine whether to
  // subsample the frames of animated content.
  base::TimeDelta target_sampling_period() const {
    return target_sampling_period_;
  }
  void SetTargetSamplingPeriod(base::TimeDelta period);

  // Examines the given presentation event metadata, along with recent history,
  // to detect animated content, updating the state of this sampler.
  // |damage_rect| is the region of a frame about to be drawn, while
  // |event_time| refers to the frame's estimated presentation time.
  void ConsiderPresentationEvent(const gfx::Rect& damage_rect,
                                 base::TimeTicks event_time);

  // Returns true if animated content has been detected and a decision has been
  // made about whether to sample the last event.
  bool HasProposal() const;

  // Returns true if the last event considered should be sampled.
  bool ShouldSample() const;

  // Returns a frame timestamp to provide to consumers of the sampled frame.
  // Only valid when ShouldSample() returns true.
  base::TimeTicks frame_timestamp() const { return frame_timestamp_; }

  // Returns the current sampling period.  This can be treated as the estimated
  // duration of the frame to be sampled.  Only valid when HasProposal()
  // returns true.
  base::TimeDelta sampling_period() const { return sampling_period_; }

  // Accessors to currently-detected animating region/period, for logging.
  const gfx::Rect& detected_region() const { return detected_region_; }
  base::TimeDelta detected_period() const { return detected_period_; }

  // Records that a frame with the given |frame_timestamp| was sampled.  This
  // method should be called when *any* sampling is taken, even if it was not
  // proposed by AnimatedContentSampler.
  void RecordSample(base::TimeTicks frame_timestamp);

 private:
  friend class AnimatedContentSamplerTest;

  // Data structure for efficient online analysis of recent event history.
  struct Observation {
    gfx::Rect damage_rect;
    base::TimeTicks event_time;

    Observation(const gfx::Rect& d, base::TimeTicks e)
        : damage_rect(d), event_time(e) {}
  };
  typedef std::deque<Observation> ObservationFifo;

  // Adds an observation to |observations_|, and prunes-out the old ones.
  void AddObservation(const gfx::Rect& damage_rect, base::TimeTicks event_time);

  // Returns the damage Rect that is responsible for the majority of the pixel
  // damage in recent event history, if there is such a Rect.  If there isn't,
  // this method could still return any Rect, so the caller must confirm the
  // returned Rect really is responsible for the majority of pixel damage.
  gfx::Rect ElectMajorityDamageRect() const;

  // Analyzes the observations relative to the current |event_time| to detect
  // stable animating content.  If detected, returns true and sets the output
  // arguments to the region of the animating content and its mean frame
  // duration.
  bool AnalyzeObservations(base::TimeTicks event_time,
                           gfx::Rect* rect,
                           base::TimeDelta* period) const;

  // Called by ConsiderPresentationEvent() when the current event is part of a
  // detected animation, to update |frame_timestamp_|.
  base::TimeTicks ComputeNextFrameTimestamp(base::TimeTicks event_time) const;

  // When the animation frame rate is greater than the target sampling rate,
  // this function determines an integer division of the animation frame rate
  // that is closest to the target sampling rate.  Returns the inverse of that
  // result (the period).  If the animation frame rate is slower or the same as
  // the target sampling rate, this function just returns |animation_period|.
  static base::TimeDelta ComputeSamplingPeriod(
      base::TimeDelta animation_period,
      base::TimeDelta target_sampling_period,
      base::TimeDelta min_capture_period);

  // The client expects frame timestamps to be at least this far apart.
  const base::TimeDelta min_capture_period_;

  // A recent history of observations in chronological order, maintained by
  // AddObservation().
  ObservationFifo observations_;

  // The region of currently-detected animated content.  If empty, that means
  // "not detected."
  gfx::Rect detected_region_;

  // The mean frame duration of currently-detected animated content.  If zero,
  // that means "not detected."
  base::TimeDelta detected_period_;

  // Target period between sampled frames.  This can be changed by the client at
  // any time (e.g., to sample high frame rate content at a lower rate).
  base::TimeDelta target_sampling_period_;

  // The sampling period computed during the last call to
  // ConsiderPresentationEvent().
  base::TimeDelta sampling_period_;

  // Indicates whether the last event caused animated content to be detected and
  // whether the current event should be sampled.
  enum {
    NOT_SAMPLING,
    START_SAMPLING,
    SHOULD_NOT_SAMPLE,
    SHOULD_SAMPLE
  } sampling_state_;

  // A token bucket that is used to decide which subset of the frames containing
  // the animated content should be sampled.  Here, the smallest discrete unit
  // of time (one microsecond) equals one token; and, tokens are only taken from
  // the bucket when at least a full sampling period's worth are present.
  base::TimeDelta token_bucket_;

  // The rewritten frame timestamp for the latest event.
  base::TimeTicks frame_timestamp_;
};

}  // namespace media

#endif  // MEDIA_CAPTURE_CONTENT_ANIMATED_CONTENT_SAMPLER_H_
