blob: 463dd9d1b66ce53a3070734ff8e7de6d3b14dd82 [file] [log] [blame]
// Copyright 2017 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.
#include <memory>
#include <string>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "base/single_thread_task_runner.h"
#include "base/time/default_tick_clock.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "media/base/cdm_config.h"
#include "media/base/pipeline_status.h"
#include "media/base/video_codecs.h"
#include "media/blink/media_blink_export.h"
#include "media/mojo/mojom/video_decode_stats_recorder.mojom.h"
namespace media {
// Reports on playback smoothness and for a given video codec profile, natural
// size, and fps. When these properties change the current report will be
// finalized and a new report will be started. Ongoing reports are also
// finalized at destruction and process tear-down.
class MEDIA_BLINK_EXPORT VideoDecodeStatsReporter {
using GetPipelineStatsCB = base::Callback<PipelineStatistics(void)>;
mojom::VideoDecodeStatsRecorderPtr recorder_ptr,
GetPipelineStatsCB get_pipeline_stats_cb,
VideoCodecProfile codec_profile,
const gfx::Size& natural_size,
std::string key_system,
base::Optional<CdmConfig> cdm_config,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
const base::TickClock* tick_clock =
void OnPlaying();
void OnPaused();
void OnHidden();
void OnShown();
// Returns true if given |natural_size| is a match for our internal bucketed
// size. This allows callers to check a size change is significant enough to
// motivate recreating the reporter with a the new size.
bool MatchesBucketedNaturalSize(const gfx::Size& natural_size) const;
// NOTE: We do not listen for playback rate changes. These implicitly change
// the frame rate and surface via PipelineStatistics'
// video_frame_duration_average.
// Friends so it can see the static constants and inspect when the timer is
// running / should be running.
friend class VideoDecodeStatsReporterTest;
// Friends to grab internal state and verify construction parameters.
friend class WebMediaPlayerImplTest;
// Constants placed in header file for test visibility.
enum : int {
// Timer interval for recording stats when frame rate and other stream
// properties are steady.
kRecordingIntervalMs = 2000,
// FrameRates must remain stable for a duration greater than this amount to
// avoid being classified as a "tiny fps window". See |kMaxTinyFpsWindows|.
kTinyFpsWindowMs = 5000,
// Limits the number of consecutive "tiny fps windows", as defined by
// |kTinyFpsWindowMs|. If this limit is met, we will give up until some
// stream property (e.g. decoder config) changes before trying again. We do
// not wish to record stats for variable frame rate content.
kMaxTinyFpsWindows = 5,
// Number of consecutive samples that must bucket to the same frame rate in
// order for frame rate to be considered "stable" enough to start recording
// stats.
kRequiredStableFpsSamples = 5,
// Limits the number attempts to detect stable frame rate. When this limit
// is reached, we will give up until some stream property (e.g. decoder
// config) changes before trying again. We do not wish to record stats for
// variable frame rate content.
kMaxUnstableFpsChanges = 10,
// Main driver of report updates. Queries |get_pipeline_stats_cb_| for the
// latest stats. Detects frame rate changes and playback stalls. Sends stats
// the VideoDecodeStatsRecorder (browser process) for saving. Should only be
// called when ShouldBeReporting() == true.
void UpdateStats();
// Run |stats_cb_timer_| at the specified |interval|. If the timer is already
// running, any existing callbacks will be canceled/delayed until
// |interval| has elapsed.
void RunStatsTimerAtInterval(base::TimeDelta interval);
// Called to begin a new report following changes to stream metadata (e.g.
// natural size). Arguments used to update |frames_decoded_offset_|,
// |frames_dropped_offset_|, and |frames_decoded_power_efficient_offset| so
// that frame counts for this report begin at 0.
void StartNewRecord(uint32_t frames_decoded_offset,
uint32_t frames_dropped_offset,
uint32_t frames_decoded_power_efficient_offset);
// Reset frame rate tracking state to force a fresh attempt at detection. When
// a stable frame rate is successfully detected, UpdateStats() will begin a
// new record will begin with the detected frame rate. Note: callers must
// separately ensure the |stats_cb_timer_| is running for frame rate detection
// to occur.
void ResetFrameRateState();
// Called by UpdateStats() to verify decode is progressing and sanity check
// decode/dropped frame counts. Will manage decoded/dropped frame state and
// relax timer when no decode progress is made. Returns true iff decode is
// progressing.
bool UpdateDecodeProgress(const PipelineStatistics& stats);
// Called by UpdateStats() to do frame rate detection. Will manage frame rate
// state, stats timer, and will start new capabilities records when frame rate
// changes. Returns true iff frame rate is stable.
bool UpdateFrameRateStability(const PipelineStatistics& stats);
// Returns true if the |stats_timer_cb_| should be running. Should be called
// after any state change (e.g. |is_playing_|) as a check on whether to start
// the the timer.
bool ShouldBeReporting() const;
// Error handler callback when IPC hits an error.
void OnIpcConnectionError();
// TimeDelta wrappers around |kRecordingIntervalMs| and |kTimyFpsWindowMs|.
// Defined as a class member to avoid static initialization.
// TODO(chcunningham): convert to static constexpr when MSVC support arrives.
const base::TimeDelta kRecordingInterval;
const base::TimeDelta kTinyFpsWindowDuration;
// Pointer to the remote recorder. The recorder runs in the browser process
// and finalizes the record in the event of fast render process tear down.
mojom::VideoDecodeStatsRecorderPtr recorder_ptr_;
// Callback for retrieving playback statistics.
GetPipelineStatsCB get_pipeline_stats_cb_;
// Current video codec profile, used to index recorded stats.
const VideoCodecProfile codec_profile_;
// Current video natural size, used to index recorded stats. These dimensions
// will always be rounded to the nearest size bucket. If the original size is
// very small, the bucketed size will simply be empty. See GetSizeBucket().
const gfx::Size natural_size_;
// The name of the current key system. Empty for unencrypted playback.
const std::string key_system_;
// From CdmConfig in constructor.
const bool use_hw_secure_codecs_;
// Clock for |stats_cb_timer_| and getting current tick count (NowTicks()).
// Tests may supply a mock clock via the constructor.
const base::TickClock* tick_clock_;
// Timer for all stats callbacks. Timer interval will be dynamically set based
// on state of reporter. See calls to RunStatsTimerAtIntervalMs().
base::RepeatingTimer stats_cb_timer_;
// Latest frame duration bucketed into one of kFrameRateBuckets.
int last_observed_fps_ = 0;
// Count of consecutive samples with frame duration matching
// |last_observed_fps_|.
int num_stable_fps_samples_ = 0;
// Count of consecutive samples with frame duration NOT matching
// |last_obseved_fps_|. Used to throttle/limit attempts to stabilize FPS since
// some videos may have variable frame rate.
int num_unstable_fps_changes_ = 0;
// Count of consecutive stable FPS windows, where stable FPS was detected but
// it lasted for a very short duration before changing again.
int num_consecutive_tiny_fps_windows_ = 0;
// True whenever we fail to determine a stable FPS, or if windows of stable
// FPS are too tiny to be useful.
bool fps_stabilization_failed_ = false;
// Tick time of the most recent FPS stabilization. When FPS changes, this time
// is compared to TimeTicks::Now() to approximate the duration of the last
// stable FPS window.
base::TimeTicks last_fps_stabilized_ticks_;
// Count of frames decoded observed in last pipeline stats update. Used to
// check whether decode/playback has actually advanced.
uint32_t last_frames_decoded_ = 0;
// Count of frames dropped observed in last pipeline stats update. Used to
// verify that count never decreases.
uint32_t last_frames_dropped_ = 0;
// Notes the number of frames decoded at the start of the current video
// configuration (profile, resolution, fps). Should be subtracted from
// pipeline frames decoded stats before sending to recorder.
uint32_t frames_decoded_offset_ = 0;
// Notes the number of frames dropped at the start of the current video
// configuration (profile, resolution, fps). Should be subtracted from
// pipeline frames dropped stats before sending to recorder.
uint32_t frames_dropped_offset_ = 0;
// Notes the number of power efficiently decoded frames at the start of the
// current video configuration (profile, resolution, fps). Should be
// subtracted from pipeline frames decoded stats before sending to recorder.
uint32_t frames_decoded_power_efficient_offset_ = 0;
// Set true by OnPlaying(), false by OnPaused(). We should not run the
// |stats_cb_timer_| when not playing.
bool is_playing_ = false;
// Set true by OnHidden(), false by OnVisible(). We should not run the
// |stats_cb_timer_| when player is backgrounded.
bool is_backgrounded_ = false;
// Set false by UpdateStats() if an IPC error is encountered. Assumed true
// until an error is found.
bool is_ipc_connected_ = true;
} // namespace media