1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/frame_sinks/video_detector.h"
#include <array>
#include <memory>
#include <utility>
#include <vector>
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "components/viz/common/features.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/service/surfaces/surface_manager.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
namespace viz {
constexpr base::TimeDelta VideoDetector::kMaxVideoTimeout;
constexpr base::TimeDelta VideoDetector::kMinVideoTimeout;
constexpr base::TimeDelta VideoDetector::kMinVideoDuration;
// Stores information about updates to a client and determines whether it's
// likely that a video is playing in it.
class VideoDetector::ClientInfo {
public:
ClientInfo() = default;
ClientInfo(const ClientInfo&) = delete;
ClientInfo& operator=(const ClientInfo&) = delete;
// Called when a Surface belonging to this client is drawn. Returns true if we
// determine that video is playing in this client.
bool ReportDrawnAndCheckForVideo(Surface* surface, base::TimeTicks now) {
uint64_t frame_index = surface->GetActiveFrameIndex();
// If |frame_index| hasn't increased, then no new frame was submitted since
// the last draw.
if (frame_index <= last_drawn_frame_index_)
return false;
last_drawn_frame_index_ = frame_index;
const CompositorFrame& frame = surface->GetActiveFrame();
if (!frame.metadata.may_contain_video) {
return false;
}
gfx::Rect damage = frame.render_pass_list.back()->damage_rect;
if (frame.render_pass_list.back()->has_per_quad_damage) {
for (auto* quad : frame.render_pass_list.back()->quad_list) {
if (quad->material != DrawQuad::Material::kTextureContent)
continue;
auto* texture_quad = TextureDrawQuad::MaterialCast(quad);
if (!texture_quad->damage_rect)
continue;
damage.Union(*texture_quad->damage_rect);
}
}
damage =
gfx::ScaleToEnclosingRect(damage, 1.f / frame.device_scale_factor());
if (damage.width() < kMinDamageWidth || damage.height() < kMinDamageHeight)
return false;
// If the buffer is full, drop the first timestamp.
if (buffer_size_ == kMinFramesPerSecond) {
buffer_start_ = (buffer_start_ + 1) % kMinFramesPerSecond;
buffer_size_--;
}
update_times_[(buffer_start_ + buffer_size_) % kMinFramesPerSecond] = now;
buffer_size_++;
const bool in_video =
(buffer_size_ == kMinFramesPerSecond) &&
(now - update_times_[buffer_start_] <= base::Seconds(1));
if (in_video && video_start_time_.is_null())
video_start_time_ = update_times_[buffer_start_];
else if (!in_video && !video_start_time_.is_null())
video_start_time_ = base::TimeTicks();
const base::TimeDelta elapsed = now - video_start_time_;
return in_video && elapsed >= kMinVideoDuration;
}
private:
// Circular buffer containing update times of the last (up to
// |kMinFramesPerSecond|) video-sized updates to this client.
std::array<base::TimeTicks, kMinFramesPerSecond> update_times_;
// Time at which the current sequence of updates that looks like video
// started. Empty if video isn't currently playing.
base::TimeTicks video_start_time_;
// Index into |update_times_| of the oldest update.
uint32_t buffer_start_ = 0;
// Number of updates stored in |update_times_|.
uint32_t buffer_size_ = 0;
// Frame index of the last drawn Surface. We use this number to determine
// whether a new frame was submitted since the last time the Surface was
// drawn.
uint64_t last_drawn_frame_index_ = 0;
};
VideoDetector::VideoDetector(
const std::vector<FrameSinkId>& registered_frame_sink_ids,
SurfaceManager* surface_manager,
const base::TickClock* tick_clock,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: tick_clock_(tick_clock),
video_inactive_timer_(tick_clock),
surface_manager_(surface_manager) {
surface_manager_->AddObserver(this);
for (auto& frame_sink_id : registered_frame_sink_ids)
client_infos_[frame_sink_id] = std::make_unique<ClientInfo>();
if (task_runner)
video_inactive_timer_.SetTaskRunner(task_runner);
}
VideoDetector::~VideoDetector() {
surface_manager_->RemoveObserver(this);
}
void VideoDetector::OnVideoActivityEnded() {
DCHECK(video_is_playing_);
video_is_playing_ = false;
for (auto& observer : observers_) {
observer->OnVideoActivityEnded();
}
}
void VideoDetector::AddObserver(
mojo::PendingRemote<mojom::VideoDetectorObserver> pending_observer) {
mojo::Remote<mojom::VideoDetectorObserver> observer(
std::move(pending_observer));
if (video_is_playing_)
observer->OnVideoActivityStarted();
observers_.Add(std::move(observer));
}
void VideoDetector::OnFrameSinkIdRegistered(const FrameSinkId& frame_sink_id) {
DCHECK(!client_infos_.count(frame_sink_id));
client_infos_[frame_sink_id] = std::make_unique<ClientInfo>();
}
void VideoDetector::OnFrameSinkIdInvalidated(const FrameSinkId& frame_sink_id) {
client_infos_.erase(frame_sink_id);
}
bool VideoDetector::OnSurfaceDamaged(const SurfaceId& surface_id,
const BeginFrameAck& ack,
HandleInteraction handle_interaction) {
return false;
}
// |surface| is scheduled to be drawn. See if it has a new frame since the
// last time it was drawn and record the damage.
void VideoDetector::OnSurfaceWillBeDrawn(Surface* surface) {
// If there is no observer, don't waste cycles detecting video activity.
if (observers_.empty())
return;
const FrameSinkId& frame_sink_id = surface->surface_id().frame_sink_id();
auto it = client_infos_.find(frame_sink_id);
// If the corresponding entry in |client_infos_| does not exist, it means the
// FrameSinkId has been invalidated and the client's CompositorFrameSink is
// destroyed so it cannot send new frames and video activity is impossible.
if (it == client_infos_.end())
return;
base::TimeTicks now = tick_clock_->NowTicks();
if (it->second->ReportDrawnAndCheckForVideo(surface, now)) {
// Avoid (re)starting the timer every frame since it has considerable
// overhead.
if (!video_inactive_timer_.IsRunning() ||
(video_inactive_timer_.desired_run_time() - now) < kMinVideoTimeout) {
video_inactive_timer_.Start(FROM_HERE, kMaxVideoTimeout, this,
&VideoDetector::OnVideoActivityEnded);
}
if (!video_is_playing_) {
video_is_playing_ = true;
for (auto& observer : observers_) {
observer->OnVideoActivityStarted();
}
}
}
}
} // namespace viz
|