1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/blink/video_frame_compositor.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/time/default_tick_clock.h"
10 #include "base/trace_event/trace_event.h"
11 #include "media/base/video_frame.h"
15 // Amount of time to wait between UpdateCurrentFrame() callbacks before starting
16 // background rendering to keep the Render() callbacks moving.
17 const int kBackgroundRenderingTimeoutMs
= 250;
19 // Returns true if the format has no Alpha channel (hence is always opaque).
20 static bool IsOpaque(const scoped_refptr
<VideoFrame
>& frame
) {
21 switch (frame
->format()) {
22 case PIXEL_FORMAT_UNKNOWN
:
23 case PIXEL_FORMAT_I420
:
24 case PIXEL_FORMAT_YV12
:
25 case PIXEL_FORMAT_YV16
:
26 case PIXEL_FORMAT_YV24
:
27 case PIXEL_FORMAT_NV12
:
28 case PIXEL_FORMAT_NV21
:
29 case PIXEL_FORMAT_UYVY
:
30 case PIXEL_FORMAT_YUY2
:
31 case PIXEL_FORMAT_XRGB
:
32 case PIXEL_FORMAT_RGB24
:
33 case PIXEL_FORMAT_MJPEG
:
35 case PIXEL_FORMAT_YV12A
:
36 case PIXEL_FORMAT_ARGB
:
37 case PIXEL_FORMAT_RGB32
:
43 VideoFrameCompositor::VideoFrameCompositor(
44 const scoped_refptr
<base::SingleThreadTaskRunner
>& compositor_task_runner
,
45 const base::Callback
<void(gfx::Size
)>& natural_size_changed_cb
,
46 const base::Callback
<void(bool)>& opacity_changed_cb
)
47 : compositor_task_runner_(compositor_task_runner
),
48 tick_clock_(new base::DefaultTickClock()),
49 natural_size_changed_cb_(natural_size_changed_cb
),
50 opacity_changed_cb_(opacity_changed_cb
),
51 background_rendering_enabled_(true),
52 background_rendering_timer_(
54 base::TimeDelta::FromMilliseconds(kBackgroundRenderingTimeoutMs
),
55 base::Bind(&VideoFrameCompositor::BackgroundRender
,
56 base::Unretained(this)),
57 // Task is not repeating, CallRender() will reset the task as needed.
61 rendered_last_frame_(false),
62 is_background_rendering_(false),
63 new_background_frame_(false),
64 // Assume 60Hz before the first UpdateCurrentFrame() call.
65 last_interval_(base::TimeDelta::FromSecondsD(1.0 / 60)),
67 background_rendering_timer_
.SetTaskRunner(compositor_task_runner_
);
70 VideoFrameCompositor::~VideoFrameCompositor() {
71 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
75 client_
->StopUsingProvider();
78 void VideoFrameCompositor::OnRendererStateUpdate(bool new_state
) {
79 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
80 DCHECK_NE(rendering_
, new_state
);
81 rendering_
= new_state
;
84 // Always start playback in background rendering mode, if |client_| kicks
85 // in right away it's okay.
87 } else if (background_rendering_enabled_
) {
88 background_rendering_timer_
.Stop();
90 DCHECK(!background_rendering_timer_
.IsRunning());
97 client_
->StartRendering();
99 client_
->StopRendering();
102 void VideoFrameCompositor::SetVideoFrameProviderClient(
103 cc::VideoFrameProvider::Client
* client
) {
104 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
106 client_
->StopUsingProvider();
109 // |client_| may now be null, so verify before calling it.
110 if (rendering_
&& client_
)
111 client_
->StartRendering();
114 scoped_refptr
<VideoFrame
> VideoFrameCompositor::GetCurrentFrame() {
115 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
116 return current_frame_
;
119 void VideoFrameCompositor::PutCurrentFrame() {
120 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
121 rendered_last_frame_
= true;
124 bool VideoFrameCompositor::UpdateCurrentFrame(base::TimeTicks deadline_min
,
125 base::TimeTicks deadline_max
) {
126 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
127 return CallRender(deadline_min
, deadline_max
, false);
130 bool VideoFrameCompositor::HasCurrentFrame() {
131 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
132 return current_frame_
;
135 void VideoFrameCompositor::Start(RenderCallback
* callback
) {
136 TRACE_EVENT0("media", "VideoFrameCompositor::Start");
138 // Called from the media thread, so acquire the callback under lock before
139 // returning in case a Stop() call comes in before the PostTask is processed.
140 base::AutoLock
lock(lock_
);
142 callback_
= callback
;
143 compositor_task_runner_
->PostTask(
144 FROM_HERE
, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate
,
145 base::Unretained(this), true));
148 void VideoFrameCompositor::Stop() {
149 TRACE_EVENT0("media", "VideoFrameCompositor::Stop");
151 // Called from the media thread, so release the callback under lock before
152 // returning to avoid a pending UpdateCurrentFrame() call occurring before
153 // the PostTask is processed.
154 base::AutoLock
lock(lock_
);
157 compositor_task_runner_
->PostTask(
158 FROM_HERE
, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate
,
159 base::Unretained(this), false));
162 void VideoFrameCompositor::PaintFrameUsingOldRenderingPath(
163 const scoped_refptr
<VideoFrame
>& frame
) {
164 if (!compositor_task_runner_
->BelongsToCurrentThread()) {
165 compositor_task_runner_
->PostTask(
167 base::Bind(&VideoFrameCompositor::PaintFrameUsingOldRenderingPath
,
168 base::Unretained(this), frame
));
172 if (ProcessNewFrame(frame
) && client_
)
173 client_
->DidReceiveFrame();
176 scoped_refptr
<VideoFrame
>
177 VideoFrameCompositor::GetCurrentFrameAndUpdateIfStale() {
178 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
179 if (client_
|| !rendering_
|| !is_background_rendering_
)
180 return current_frame_
;
182 DCHECK(!last_background_render_
.is_null());
184 const base::TimeTicks now
= tick_clock_
->NowTicks();
185 const base::TimeDelta interval
= now
- last_background_render_
;
187 // Cap updates to 250Hz which should be more than enough for everyone.
188 if (interval
< base::TimeDelta::FromMilliseconds(4))
189 return current_frame_
;
191 // Update the interval based on the time between calls and call background
192 // render which will give this information to the client.
193 last_interval_
= interval
;
196 return current_frame_
;
199 bool VideoFrameCompositor::ProcessNewFrame(
200 const scoped_refptr
<VideoFrame
>& frame
) {
201 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
203 if (frame
== current_frame_
)
206 // Set the flag indicating that the current frame is unrendered, if we get a
207 // subsequent PutCurrentFrame() call it will mark it as rendered.
208 rendered_last_frame_
= false;
210 if (current_frame_
&&
211 current_frame_
->natural_size() != frame
->natural_size()) {
212 natural_size_changed_cb_
.Run(frame
->natural_size());
215 if (!current_frame_
|| IsOpaque(current_frame_
) != IsOpaque(frame
))
216 opacity_changed_cb_
.Run(IsOpaque(frame
));
218 current_frame_
= frame
;
222 void VideoFrameCompositor::BackgroundRender() {
223 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
224 const base::TimeTicks now
= tick_clock_
->NowTicks();
225 last_background_render_
= now
;
226 bool new_frame
= CallRender(now
, now
+ last_interval_
, true);
227 if (new_frame
&& client_
)
228 client_
->DidReceiveFrame();
231 bool VideoFrameCompositor::CallRender(base::TimeTicks deadline_min
,
232 base::TimeTicks deadline_max
,
233 bool background_rendering
) {
234 DCHECK(compositor_task_runner_
->BelongsToCurrentThread());
236 base::AutoLock
lock(lock_
);
238 // Even if we no longer have a callback, return true if we have a frame
239 // which |client_| hasn't seen before.
240 return !rendered_last_frame_
&& current_frame_
;
245 // If the previous frame was never rendered and we're not in background
246 // rendering mode (nor have just exited it), let the client know.
247 if (!rendered_last_frame_
&& current_frame_
&& !background_rendering
&&
248 !is_background_rendering_
) {
249 callback_
->OnFrameDropped();
252 const bool new_frame
= ProcessNewFrame(
253 callback_
->Render(deadline_min
, deadline_max
, background_rendering
));
255 // We may create a new frame here with background rendering, but the provider
256 // has no way of knowing that a new frame had been processed, so keep track of
257 // the new frame, and return true on the next call to |CallRender|.
258 const bool had_new_background_frame
= new_background_frame_
;
259 new_background_frame_
= background_rendering
&& new_frame
;
261 is_background_rendering_
= background_rendering
;
262 last_interval_
= deadline_max
- deadline_min
;
264 // Restart the background rendering timer whether we're background rendering
265 // or not; in either case we should wait for |kBackgroundRenderingTimeoutMs|.
266 if (background_rendering_enabled_
)
267 background_rendering_timer_
.Reset();
268 return new_frame
|| had_new_background_frame
;