OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "media/blink/video_frame_compositor.h" | 5 #include "media/blink/video_frame_compositor.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/message_loop/message_loop.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" | |
9 #include "media/base/video_frame.h" | 11 #include "media/base/video_frame.h" |
10 | 12 |
11 namespace media { | 13 namespace media { |
12 | 14 |
15 // The maximum time we'll allow to elapse between Render() callbacks if there is | |
16 // an external caller requesting frames via GetCurrentFrame(); i.e. there is a | |
17 // canvas which frames are being copied into. | |
18 const int kStaleFrameThresholdMs = 250; | |
19 | |
13 static bool IsOpaque(const scoped_refptr<VideoFrame>& frame) { | 20 static bool IsOpaque(const scoped_refptr<VideoFrame>& frame) { |
14 switch (frame->format()) { | 21 switch (frame->format()) { |
15 case VideoFrame::UNKNOWN: | 22 case VideoFrame::UNKNOWN: |
16 case VideoFrame::YV12: | 23 case VideoFrame::YV12: |
17 case VideoFrame::YV12J: | 24 case VideoFrame::YV12J: |
18 case VideoFrame::YV12HD: | 25 case VideoFrame::YV12HD: |
19 case VideoFrame::YV16: | 26 case VideoFrame::YV16: |
20 case VideoFrame::I420: | 27 case VideoFrame::I420: |
21 case VideoFrame::YV24: | 28 case VideoFrame::YV24: |
22 case VideoFrame::NV12: | 29 case VideoFrame::NV12: |
23 return true; | 30 return true; |
24 | 31 |
25 case VideoFrame::YV12A: | 32 case VideoFrame::YV12A: |
26 #if defined(VIDEO_HOLE) | 33 #if defined(VIDEO_HOLE) |
27 case VideoFrame::HOLE: | 34 case VideoFrame::HOLE: |
28 #endif // defined(VIDEO_HOLE) | 35 #endif // defined(VIDEO_HOLE) |
29 case VideoFrame::NATIVE_TEXTURE: | 36 case VideoFrame::NATIVE_TEXTURE: |
30 case VideoFrame::ARGB: | 37 case VideoFrame::ARGB: |
31 break; | 38 break; |
32 } | 39 } |
33 return false; | 40 return false; |
34 } | 41 } |
35 | 42 |
36 VideoFrameCompositor::VideoFrameCompositor( | 43 VideoFrameCompositor::VideoFrameCompositor( |
37 const scoped_refptr<base::SingleThreadTaskRunner>& compositor_task_runner, | 44 const scoped_refptr<base::SingleThreadTaskRunner>& compositor_task_runner, |
38 const base::Callback<void(gfx::Size)>& natural_size_changed_cb, | 45 const base::Callback<void(gfx::Size)>& natural_size_changed_cb, |
39 const base::Callback<void(bool)>& opacity_changed_cb) | 46 const base::Callback<void(bool)>& opacity_changed_cb) |
40 : compositor_task_runner_(compositor_task_runner), | 47 : compositor_task_runner_(compositor_task_runner), |
48 tick_clock_(new base::DefaultTickClock()), | |
41 natural_size_changed_cb_(natural_size_changed_cb), | 49 natural_size_changed_cb_(natural_size_changed_cb), |
42 opacity_changed_cb_(opacity_changed_cb), | 50 opacity_changed_cb_(opacity_changed_cb), |
51 stale_frame_threshold_( | |
52 base::TimeDelta::FromMilliseconds(kStaleFrameThresholdMs)), | |
43 client_(nullptr), | 53 client_(nullptr), |
44 rendering_(false), | 54 rendering_(false), |
45 callback_(nullptr) { | 55 rendered_last_frame_(false), |
56 callback_(nullptr), | |
57 // Assume 60Hz before the first UpdateCurrentFrame() call. | |
58 last_interval_(base::TimeDelta::FromSecondsD(1.0 / 60)) { | |
46 } | 59 } |
47 | 60 |
48 VideoFrameCompositor::~VideoFrameCompositor() { | 61 VideoFrameCompositor::~VideoFrameCompositor() { |
49 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); | 62 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); |
50 DCHECK(!callback_); | 63 DCHECK(!callback_); |
51 DCHECK(!rendering_); | 64 DCHECK(!rendering_); |
52 if (client_) | 65 if (client_) |
53 client_->StopUsingProvider(); | 66 client_->StopUsingProvider(); |
54 } | 67 } |
55 | 68 |
56 void VideoFrameCompositor::OnRendererStateUpdate() { | 69 void VideoFrameCompositor::OnRendererStateUpdate(bool new_state) { |
57 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); | 70 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); |
71 DCHECK_NE(rendering_, new_state); | |
72 rendering_ = new_state; | |
73 last_frame_update_time_ = base::TimeTicks(); | |
74 | |
58 if (!client_) | 75 if (!client_) |
59 return; | 76 return; |
60 | 77 |
61 base::AutoLock lock(lock_); | 78 if (rendering_) |
62 if (callback_) { | 79 client_->StartRendering(); |
63 if (rendering_) | 80 else |
64 client_->StartRendering(); | |
65 | |
66 // TODO(dalecurtis): This will need to request the first frame so we have | |
67 // something to show, even if playback hasn't started yet. | |
68 } else if (rendering_) { | |
69 client_->StopRendering(); | 81 client_->StopRendering(); |
70 } | |
71 } | 82 } |
72 | 83 |
73 scoped_refptr<VideoFrame> | 84 scoped_refptr<VideoFrame> |
74 VideoFrameCompositor::GetCurrentFrameAndUpdateIfStale() { | 85 VideoFrameCompositor::GetCurrentFrameAndUpdateIfStale() { |
75 // TODO(dalecurtis): Implement frame refresh when stale. | |
76 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); | 86 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); |
87 if (rendering_) { | |
88 const base::TimeTicks now = tick_clock_->NowTicks(); | |
89 if (now - last_frame_update_time_ > stale_frame_threshold_) | |
90 UpdateCurrentFrame(now, now + last_interval_); | |
91 } | |
92 | |
77 return GetCurrentFrame(); | 93 return GetCurrentFrame(); |
78 } | 94 } |
79 | 95 |
80 void VideoFrameCompositor::SetVideoFrameProviderClient( | 96 void VideoFrameCompositor::SetVideoFrameProviderClient( |
81 cc::VideoFrameProvider::Client* client) { | 97 cc::VideoFrameProvider::Client* client) { |
82 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); | 98 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); |
83 if (client_) | 99 if (client_) |
84 client_->StopUsingProvider(); | 100 client_->StopUsingProvider(); |
85 client_ = client; | 101 client_ = client; |
86 OnRendererStateUpdate(); | 102 |
103 if (rendering_) | |
104 client_->StartRendering(); | |
87 } | 105 } |
88 | 106 |
89 scoped_refptr<VideoFrame> VideoFrameCompositor::GetCurrentFrame() { | 107 scoped_refptr<VideoFrame> VideoFrameCompositor::GetCurrentFrame() { |
90 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); | 108 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); |
91 return current_frame_; | 109 return current_frame_; |
92 } | 110 } |
93 | 111 |
94 void VideoFrameCompositor::PutCurrentFrame() { | 112 void VideoFrameCompositor::PutCurrentFrame() { |
95 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); | 113 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); |
96 // TODO(dalecurtis): Wire up a flag for RenderCallback::OnFrameDropped(). | 114 rendered_last_frame_ = true; |
97 } | 115 } |
98 | 116 |
99 bool VideoFrameCompositor::UpdateCurrentFrame(base::TimeTicks deadline_min, | 117 bool VideoFrameCompositor::UpdateCurrentFrame(base::TimeTicks deadline_min, |
100 base::TimeTicks deadline_max) { | 118 base::TimeTicks deadline_max) { |
101 // TODO(dalecurtis): Wire this up to RenderCallback::Render(). | 119 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); |
102 base::AutoLock lock(lock_); | 120 base::AutoLock lock(lock_); |
103 return false; | 121 if (!callback_) { |
122 LOG(ERROR) << "Got begin frame callback... but had no callback..."; | |
xhwang
2015/04/30 16:39:20
In what case can this happen? Should this be a DCH
DaleCurtis
2015/04/30 18:06:00
Left over debugging from when I was experimenting
| |
123 return false; | |
124 } | |
125 | |
126 DCHECK(rendering_); | |
127 | |
128 // If the previous frame was never rendered, let the client know. | |
129 if (!rendered_last_frame_ && current_frame_) | |
130 callback_->OnFrameDropped(); | |
131 | |
132 last_frame_update_time_ = tick_clock_->NowTicks(); | |
133 last_interval_ = deadline_max - deadline_min; | |
134 scoped_refptr<VideoFrame> frame = | |
135 callback_->Render(deadline_min, deadline_max); | |
136 | |
137 // Do nothing if the current frame has already been rendered. | |
138 if (current_frame_ == frame) | |
139 return false; | |
140 | |
141 ProcessNewFrame(frame); | |
142 return true; | |
104 } | 143 } |
105 | 144 |
106 void VideoFrameCompositor::Start(RenderCallback* callback) { | 145 void VideoFrameCompositor::Start(RenderCallback* callback) { |
107 NOTREACHED(); | 146 TRACE_EVENT0("media", __FUNCTION__); |
108 | 147 |
109 // Called from the media thread, so acquire the callback under lock before | 148 // Called from the media thread, so acquire the callback under lock before |
110 // returning in case a Stop() call comes in before the PostTask is processed. | 149 // returning in case a Stop() call comes in before the PostTask is processed. |
111 base::AutoLock lock(lock_); | 150 base::AutoLock lock(lock_); |
151 DCHECK(!callback_); | |
112 callback_ = callback; | 152 callback_ = callback; |
113 rendering_ = true; | |
114 compositor_task_runner_->PostTask( | 153 compositor_task_runner_->PostTask( |
115 FROM_HERE, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate, | 154 FROM_HERE, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate, |
116 base::Unretained(this))); | 155 base::Unretained(this), true)); |
117 } | 156 } |
118 | 157 |
119 void VideoFrameCompositor::Stop() { | 158 void VideoFrameCompositor::Stop() { |
120 NOTREACHED(); | 159 TRACE_EVENT0("media", __FUNCTION__); |
121 | 160 |
122 // Called from the media thread, so release the callback under lock before | 161 // Called from the media thread, so release the callback under lock before |
123 // returning to avoid a pending UpdateCurrentFrame() call occurring before | 162 // returning to avoid a pending UpdateCurrentFrame() call occurring before |
124 // the PostTask is processed. | 163 // the PostTask is processed. |
125 base::AutoLock lock(lock_); | 164 base::AutoLock lock(lock_); |
165 DCHECK(callback_); | |
166 | |
167 // Fire one more Render() callback if we're more than one render interval away | |
168 // to ensure that we have a good frame to display if Render() has never been | |
169 // called, or was called long enough ago that the frame is stale. We must | |
170 // always have a |current_frame_| to recover from "damage" to the video layer. | |
171 const base::TimeTicks now = tick_clock_->NowTicks(); | |
172 if (now - last_frame_update_time_ > last_interval_) { | |
173 compositor_task_runner_->PostTask( | |
174 FROM_HERE, base::Bind(&VideoFrameCompositor::ProcessNewFrame, | |
175 base::Unretained(this), | |
176 callback_->Render(now, now + last_interval_))); | |
177 } | |
178 | |
126 callback_ = nullptr; | 179 callback_ = nullptr; |
127 rendering_ = false; | |
128 compositor_task_runner_->PostTask( | 180 compositor_task_runner_->PostTask( |
129 FROM_HERE, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate, | 181 FROM_HERE, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate, |
130 base::Unretained(this))); | 182 base::Unretained(this), false)); |
131 } | 183 } |
132 | 184 |
133 void VideoFrameCompositor::PaintFrameUsingOldRenderingPath( | 185 void VideoFrameCompositor::PaintFrameUsingOldRenderingPath( |
134 const scoped_refptr<VideoFrame>& frame) { | 186 const scoped_refptr<VideoFrame>& frame) { |
135 if (!compositor_task_runner_->BelongsToCurrentThread()) { | 187 if (!compositor_task_runner_->BelongsToCurrentThread()) { |
136 compositor_task_runner_->PostTask( | 188 compositor_task_runner_->PostTask( |
137 FROM_HERE, | 189 FROM_HERE, |
138 base::Bind(&VideoFrameCompositor::PaintFrameUsingOldRenderingPath, | 190 base::Bind(&VideoFrameCompositor::PaintFrameUsingOldRenderingPath, |
139 base::Unretained(this), frame)); | 191 base::Unretained(this), frame)); |
140 return; | 192 return; |
141 } | 193 } |
142 | 194 |
143 if (current_frame_.get() && | 195 ProcessNewFrame(frame); |
196 if (client_) | |
197 client_->DidReceiveFrame(); | |
xhwang
2015/04/30 16:39:20
There's a chance that we didn't update the |curren
DaleCurtis
2015/04/30 18:06:00
Whoops yes, rolled this into ProcessNewFrame() and
| |
198 } | |
199 | |
200 void VideoFrameCompositor::ProcessNewFrame( | |
201 const scoped_refptr<VideoFrame>& frame) { | |
202 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); | |
203 | |
204 if (frame == current_frame_) | |
205 return; | |
206 | |
207 // Set the flag indicating that the current frame is unrendered, if we get a | |
208 // subsequent PutCurrentFrame() call it will mark it as rendered. | |
209 rendered_last_frame_ = false; | |
210 | |
211 if (current_frame_ && | |
144 current_frame_->natural_size() != frame->natural_size()) { | 212 current_frame_->natural_size() != frame->natural_size()) { |
145 natural_size_changed_cb_.Run(frame->natural_size()); | 213 natural_size_changed_cb_.Run(frame->natural_size()); |
146 } | 214 } |
147 | 215 |
148 if (!current_frame_.get() || IsOpaque(current_frame_) != IsOpaque(frame)) { | 216 if (!current_frame_ || IsOpaque(current_frame_) != IsOpaque(frame)) |
149 opacity_changed_cb_.Run(IsOpaque(frame)); | 217 opacity_changed_cb_.Run(IsOpaque(frame)); |
150 } | |
151 | 218 |
152 current_frame_ = frame; | 219 current_frame_ = frame; |
153 | |
154 if (client_) | |
155 client_->DidReceiveFrame(); | |
156 } | 220 } |
157 | 221 |
158 } // namespace media | 222 } // namespace media |
OLD | NEW |