| 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 return false; |
| 123 |
| 124 DCHECK(rendering_); |
| 125 |
| 126 // If the previous frame was never rendered, let the client know. |
| 127 if (!rendered_last_frame_ && current_frame_) |
| 128 callback_->OnFrameDropped(); |
| 129 |
| 130 last_frame_update_time_ = tick_clock_->NowTicks(); |
| 131 last_interval_ = deadline_max - deadline_min; |
| 132 return ProcessNewFrame(callback_->Render(deadline_min, deadline_max), false); |
| 104 } | 133 } |
| 105 | 134 |
| 106 void VideoFrameCompositor::Start(RenderCallback* callback) { | 135 void VideoFrameCompositor::Start(RenderCallback* callback) { |
| 107 NOTREACHED(); | 136 TRACE_EVENT0("media", __FUNCTION__); |
| 108 | 137 |
| 109 // Called from the media thread, so acquire the callback under lock before | 138 // 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. | 139 // returning in case a Stop() call comes in before the PostTask is processed. |
| 111 base::AutoLock lock(lock_); | 140 base::AutoLock lock(lock_); |
| 141 DCHECK(!callback_); |
| 112 callback_ = callback; | 142 callback_ = callback; |
| 113 rendering_ = true; | |
| 114 compositor_task_runner_->PostTask( | 143 compositor_task_runner_->PostTask( |
| 115 FROM_HERE, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate, | 144 FROM_HERE, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate, |
| 116 base::Unretained(this))); | 145 base::Unretained(this), true)); |
| 117 } | 146 } |
| 118 | 147 |
| 119 void VideoFrameCompositor::Stop() { | 148 void VideoFrameCompositor::Stop() { |
| 120 NOTREACHED(); | 149 TRACE_EVENT0("media", __FUNCTION__); |
| 121 | 150 |
| 122 // Called from the media thread, so release the callback under lock before | 151 // Called from the media thread, so release the callback under lock before |
| 123 // returning to avoid a pending UpdateCurrentFrame() call occurring before | 152 // returning to avoid a pending UpdateCurrentFrame() call occurring before |
| 124 // the PostTask is processed. | 153 // the PostTask is processed. |
| 125 base::AutoLock lock(lock_); | 154 base::AutoLock lock(lock_); |
| 155 DCHECK(callback_); |
| 156 |
| 157 // Fire one more Render() callback if we're more than one render interval away |
| 158 // to ensure that we have a good frame to display if Render() has never been |
| 159 // called, or was called long enough ago that the frame is stale. We must |
| 160 // always have a |current_frame_| to recover from "damage" to the video layer. |
| 161 const base::TimeTicks now = tick_clock_->NowTicks(); |
| 162 if (now - last_frame_update_time_ > last_interval_) { |
| 163 compositor_task_runner_->PostTask( |
| 164 FROM_HERE, |
| 165 base::Bind(base::IgnoreResult(&VideoFrameCompositor::ProcessNewFrame), |
| 166 base::Unretained(this), |
| 167 callback_->Render(now, now + last_interval_), true)); |
| 168 } |
| 169 |
| 126 callback_ = nullptr; | 170 callback_ = nullptr; |
| 127 rendering_ = false; | |
| 128 compositor_task_runner_->PostTask( | 171 compositor_task_runner_->PostTask( |
| 129 FROM_HERE, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate, | 172 FROM_HERE, base::Bind(&VideoFrameCompositor::OnRendererStateUpdate, |
| 130 base::Unretained(this))); | 173 base::Unretained(this), false)); |
| 131 } | 174 } |
| 132 | 175 |
| 133 void VideoFrameCompositor::PaintFrameUsingOldRenderingPath( | 176 void VideoFrameCompositor::PaintFrameUsingOldRenderingPath( |
| 134 const scoped_refptr<VideoFrame>& frame) { | 177 const scoped_refptr<VideoFrame>& frame) { |
| 135 if (!compositor_task_runner_->BelongsToCurrentThread()) { | 178 if (!compositor_task_runner_->BelongsToCurrentThread()) { |
| 136 compositor_task_runner_->PostTask( | 179 compositor_task_runner_->PostTask( |
| 137 FROM_HERE, | 180 FROM_HERE, |
| 138 base::Bind(&VideoFrameCompositor::PaintFrameUsingOldRenderingPath, | 181 base::Bind(&VideoFrameCompositor::PaintFrameUsingOldRenderingPath, |
| 139 base::Unretained(this), frame)); | 182 base::Unretained(this), frame)); |
| 140 return; | 183 return; |
| 141 } | 184 } |
| 142 | 185 |
| 143 if (current_frame_.get() && | 186 ProcessNewFrame(frame, true); |
| 187 } |
| 188 |
| 189 bool VideoFrameCompositor::ProcessNewFrame( |
| 190 const scoped_refptr<VideoFrame>& frame, |
| 191 bool notify_client_of_new_frames) { |
| 192 DCHECK(compositor_task_runner_->BelongsToCurrentThread()); |
| 193 |
| 194 if (frame == current_frame_) |
| 195 return false; |
| 196 |
| 197 // Set the flag indicating that the current frame is unrendered, if we get a |
| 198 // subsequent PutCurrentFrame() call it will mark it as rendered. |
| 199 rendered_last_frame_ = false; |
| 200 |
| 201 if (current_frame_ && |
| 144 current_frame_->natural_size() != frame->natural_size()) { | 202 current_frame_->natural_size() != frame->natural_size()) { |
| 145 natural_size_changed_cb_.Run(frame->natural_size()); | 203 natural_size_changed_cb_.Run(frame->natural_size()); |
| 146 } | 204 } |
| 147 | 205 |
| 148 if (!current_frame_.get() || IsOpaque(current_frame_) != IsOpaque(frame)) { | 206 if (!current_frame_ || IsOpaque(current_frame_) != IsOpaque(frame)) |
| 149 opacity_changed_cb_.Run(IsOpaque(frame)); | 207 opacity_changed_cb_.Run(IsOpaque(frame)); |
| 150 } | |
| 151 | 208 |
| 152 current_frame_ = frame; | 209 current_frame_ = frame; |
| 210 if (notify_client_of_new_frames && client_) |
| 211 client_->DidReceiveFrame(); |
| 153 | 212 |
| 154 if (client_) | 213 return true; |
| 155 client_->DidReceiveFrame(); | |
| 156 } | 214 } |
| 157 | 215 |
| 158 } // namespace media | 216 } // namespace media |
| OLD | NEW |