Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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/filters/video_renderer_impl.h" | 5 #include "media/filters/video_renderer_impl.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/callback.h" | 8 #include "base/callback.h" |
| 9 #include "base/callback_helpers.h" | 9 #include "base/callback_helpers.h" |
| 10 #include "base/debug/trace_event.h" | 10 #include "base/debug/trace_event.h" |
| 11 #include "base/location.h" | 11 #include "base/location.h" |
| 12 #include "base/single_thread_task_runner.h" | 12 #include "base/single_thread_task_runner.h" |
| 13 #include "base/threading/platform_thread.h" | 13 #include "base/threading/platform_thread.h" |
| 14 #include "media/base/buffers.h" | 14 #include "media/base/buffers.h" |
| 15 #include "media/base/limits.h" | 15 #include "media/base/limits.h" |
| 16 #include "media/base/pipeline.h" | 16 #include "media/base/pipeline.h" |
| 17 #include "media/base/video_frame.h" | 17 #include "media/base/video_frame.h" |
| 18 | 18 |
| 19 namespace media { | 19 namespace media { |
| 20 | 20 |
| 21 base::TimeDelta VideoRendererImpl::kMaxLastFrameDuration() { | 21 base::TimeDelta VideoRendererImpl::kMaxLastFrameDuration() { |
| 22 return base::TimeDelta::FromMilliseconds(250); | 22 return base::TimeDelta::FromMilliseconds(250); |
| 23 } | 23 } |
| 24 | 24 |
| 25 VideoRendererImpl::VideoRendererImpl( | 25 VideoRendererImpl::VideoRendererImpl( |
| 26 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, | 26 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
| 27 scoped_ptr<VideoFrameScheduler> scheduler, | |
| 27 ScopedVector<VideoDecoder> decoders, | 28 ScopedVector<VideoDecoder> decoders, |
| 28 const SetDecryptorReadyCB& set_decryptor_ready_cb, | 29 const SetDecryptorReadyCB& set_decryptor_ready_cb) |
| 29 const PaintCB& paint_cb, | |
| 30 bool drop_frames) | |
| 31 : task_runner_(task_runner), | 30 : task_runner_(task_runner), |
| 31 scheduler_(scheduler.Pass()), | |
| 32 video_frame_stream_(task_runner, decoders.Pass(), set_decryptor_ready_cb), | 32 video_frame_stream_(task_runner, decoders.Pass(), set_decryptor_ready_cb), |
| 33 received_end_of_stream_(false), | 33 received_end_of_stream_(false), |
| 34 frame_available_(&lock_), | |
| 35 state_(kUninitialized), | 34 state_(kUninitialized), |
| 36 thread_(), | |
| 37 pending_read_(false), | 35 pending_read_(false), |
| 38 drop_frames_(drop_frames), | |
| 39 playback_rate_(0), | 36 playback_rate_(0), |
| 40 paint_cb_(paint_cb), | |
| 41 last_timestamp_(kNoTimestamp()), | |
| 42 frames_decoded_(0), | 37 frames_decoded_(0), |
| 43 frames_dropped_(0), | 38 frames_dropped_(0), |
| 44 weak_factory_(this) { | 39 weak_factory_(this) {} |
| 45 DCHECK(!paint_cb_.is_null()); | |
| 46 } | |
| 47 | 40 |
| 48 VideoRendererImpl::~VideoRendererImpl() { | 41 VideoRendererImpl::~VideoRendererImpl() { |
| 49 base::AutoLock auto_lock(lock_); | |
| 50 CHECK(state_ == kStopped || state_ == kUninitialized) << state_; | 42 CHECK(state_ == kStopped || state_ == kUninitialized) << state_; |
| 51 CHECK(thread_.is_null()); | |
| 52 } | 43 } |
| 53 | 44 |
| 54 void VideoRendererImpl::Play(const base::Closure& callback) { | 45 void VideoRendererImpl::Play(const base::Closure& callback) { |
| 55 DCHECK(task_runner_->BelongsToCurrentThread()); | 46 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 56 base::AutoLock auto_lock(lock_); | |
| 57 DCHECK_EQ(kPrerolled, state_); | 47 DCHECK_EQ(kPrerolled, state_); |
| 58 state_ = kPlaying; | 48 state_ = kPlaying; |
| 49 | |
| 50 ScheduleVideoFrames(); | |
| 51 | |
| 52 // Changing from non-playing to playing state can trigger ended. | |
| 53 if (ShouldTransitionToEnded()) | |
| 54 TransitionToEnded(); | |
| 55 | |
| 59 callback.Run(); | 56 callback.Run(); |
| 60 } | 57 } |
| 61 | 58 |
| 62 void VideoRendererImpl::Pause(const base::Closure& callback) { | 59 void VideoRendererImpl::Pause(const base::Closure& callback) { |
| 63 DCHECK(task_runner_->BelongsToCurrentThread()); | 60 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 64 base::AutoLock auto_lock(lock_); | |
| 65 DCHECK(state_ != kUninitialized || state_ == kError); | 61 DCHECK(state_ != kUninitialized || state_ == kError); |
| 62 scheduler_->Reset(); | |
| 66 state_ = kPaused; | 63 state_ = kPaused; |
| 67 callback.Run(); | 64 callback.Run(); |
| 68 } | 65 } |
| 69 | 66 |
| 70 void VideoRendererImpl::Flush(const base::Closure& callback) { | 67 void VideoRendererImpl::Flush(const base::Closure& callback) { |
| 71 DCHECK(task_runner_->BelongsToCurrentThread()); | 68 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 72 base::AutoLock auto_lock(lock_); | |
| 73 DCHECK_EQ(state_, kPaused); | 69 DCHECK_EQ(state_, kPaused); |
| 74 flush_cb_ = callback; | 70 flush_cb_ = callback; |
| 75 state_ = kFlushing; | 71 state_ = kFlushing; |
| 76 | 72 |
| 73 scheduler_->Reset(); | |
| 74 | |
| 77 // This is necessary if the |video_frame_stream_| has already seen an end of | 75 // This is necessary if the |video_frame_stream_| has already seen an end of |
| 78 // stream and needs to drain it before flushing it. | 76 // stream and needs to drain it before flushing it. |
| 79 ready_frames_.clear(); | 77 // ready_frames_.clear(); |
| 78 unscheduled_frames_.clear(); | |
| 79 // XXX what about scheduled_frames_? | |
|
scherkus (not reviewing)
2014/04/16 23:45:02
I'm pretty sure we may have to wait for all frames
| |
| 80 | |
| 80 received_end_of_stream_ = false; | 81 received_end_of_stream_ = false; |
| 81 video_frame_stream_.Reset( | 82 video_frame_stream_.Reset( |
| 82 base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone, | 83 base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone, |
| 83 weak_factory_.GetWeakPtr())); | 84 weak_factory_.GetWeakPtr())); |
| 84 } | 85 } |
| 85 | 86 |
| 86 void VideoRendererImpl::Stop(const base::Closure& callback) { | 87 void VideoRendererImpl::Stop(const base::Closure& callback) { |
| 87 DCHECK(task_runner_->BelongsToCurrentThread()); | 88 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 88 base::AutoLock auto_lock(lock_); | |
| 89 if (state_ == kUninitialized || state_ == kStopped) { | 89 if (state_ == kUninitialized || state_ == kStopped) { |
| 90 callback.Run(); | 90 callback.Run(); |
| 91 return; | 91 return; |
| 92 } | 92 } |
| 93 | 93 |
| 94 scheduler_->Reset(); | |
| 95 | |
| 94 // TODO(scherkus): Consider invalidating |weak_factory_| and replacing | 96 // TODO(scherkus): Consider invalidating |weak_factory_| and replacing |
| 95 // task-running guards that check |state_| with DCHECK(). | 97 // task-running guards that check |state_| with DCHECK(). |
| 96 | 98 |
| 97 state_ = kStopped; | 99 state_ = kStopped; |
| 98 | 100 |
| 99 statistics_cb_.Reset(); | 101 statistics_cb_.Reset(); |
| 100 max_time_cb_.Reset(); | 102 max_time_cb_.Reset(); |
| 101 DoStopOrError_Locked(); | |
| 102 | 103 |
| 103 // Clean up our thread if present. | 104 unscheduled_frames_.clear(); |
| 104 base::PlatformThreadHandle thread_to_join = base::PlatformThreadHandle(); | 105 // XXX what about scheduled_frames_? |
| 105 if (!thread_.is_null()) { | 106 // ready_frames_.clear(); |
| 106 // Signal the thread since it's possible to get stopped with the video | |
| 107 // thread waiting for a read to complete. | |
| 108 frame_available_.Signal(); | |
| 109 std::swap(thread_, thread_to_join); | |
| 110 } | |
| 111 | |
| 112 if (!thread_to_join.is_null()) { | |
| 113 base::AutoUnlock auto_unlock(lock_); | |
| 114 base::PlatformThread::Join(thread_to_join); | |
| 115 } | |
| 116 | 107 |
| 117 video_frame_stream_.Stop(callback); | 108 video_frame_stream_.Stop(callback); |
| 118 } | 109 } |
| 119 | 110 |
| 120 void VideoRendererImpl::SetPlaybackRate(float playback_rate) { | 111 void VideoRendererImpl::SetPlaybackRate(float playback_rate) { |
| 121 DCHECK(task_runner_->BelongsToCurrentThread()); | 112 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 122 base::AutoLock auto_lock(lock_); | 113 if (playback_rate_ == playback_rate) |
| 114 return; | |
| 115 | |
| 123 playback_rate_ = playback_rate; | 116 playback_rate_ = playback_rate; |
| 117 | |
| 118 scheduler_->Reset(); | |
| 119 ScheduleVideoFrames(); | |
| 120 | |
| 121 // Changing from a zero to a non-zero playback rate can trigger ended. | |
|
scherkus (not reviewing)
2014/04/16 23:45:02
old code would have us fire ended even if playback
| |
| 122 if (ShouldTransitionToEnded()) | |
| 123 TransitionToEnded(); | |
| 124 } | 124 } |
| 125 | 125 |
| 126 void VideoRendererImpl::Preroll(base::TimeDelta time, | 126 void VideoRendererImpl::Preroll(base::TimeDelta time, |
| 127 const PipelineStatusCB& cb) { | 127 const PipelineStatusCB& cb) { |
| 128 DCHECK(task_runner_->BelongsToCurrentThread()); | 128 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 129 base::AutoLock auto_lock(lock_); | |
| 130 DCHECK(!cb.is_null()); | 129 DCHECK(!cb.is_null()); |
| 131 DCHECK(preroll_cb_.is_null()); | 130 DCHECK(preroll_cb_.is_null()); |
| 132 DCHECK(state_ == kFlushed || state_== kPaused) << "state_ " << state_; | 131 DCHECK(state_ == kFlushed || state_== kPaused) << "state_ " << state_; |
| 133 | 132 |
| 134 if (state_ == kFlushed) { | 133 if (state_ == kFlushed) { |
| 135 DCHECK(time != kNoTimestamp()); | 134 DCHECK(time != kNoTimestamp()); |
| 136 DCHECK(!pending_read_); | 135 DCHECK(!pending_read_); |
| 137 DCHECK(ready_frames_.empty()); | 136 DCHECK(unscheduled_frames_.empty()); |
| 137 DCHECK(scheduled_frames_.empty()); | |
| 138 } else { | 138 } else { |
| 139 DCHECK(time == kNoTimestamp()); | 139 DCHECK(time == kNoTimestamp()); |
| 140 } | 140 } |
| 141 | 141 |
| 142 state_ = kPrerolling; | 142 state_ = kPrerolling; |
| 143 preroll_cb_ = cb; | 143 preroll_cb_ = cb; |
| 144 preroll_timestamp_ = time; | 144 preroll_timestamp_ = time; |
| 145 | 145 |
| 146 if (ShouldTransitionToPrerolled_Locked()) { | 146 if (ShouldTransitionToPrerolled()) { |
| 147 TransitionToPrerolled_Locked(); | 147 TransitionToPrerolled(); |
| 148 return; | 148 return; |
| 149 } | 149 } |
| 150 | 150 |
| 151 AttemptRead_Locked(); | 151 AttemptRead(); |
| 152 } | 152 } |
| 153 | 153 |
| 154 void VideoRendererImpl::Initialize(DemuxerStream* stream, | 154 void VideoRendererImpl::Initialize(DemuxerStream* stream, |
| 155 const PipelineStatusCB& init_cb, | 155 const PipelineStatusCB& init_cb, |
| 156 const StatisticsCB& statistics_cb, | 156 const StatisticsCB& statistics_cb, |
| 157 const TimeCB& max_time_cb, | 157 const TimeCB& max_time_cb, |
| 158 const base::Closure& ended_cb, | 158 const base::Closure& ended_cb, |
| 159 const PipelineStatusCB& error_cb, | 159 const PipelineStatusCB& error_cb, |
| 160 const TimeDeltaCB& get_time_cb, | 160 const TimeDeltaCB& get_time_cb, |
| 161 const TimeDeltaCB& get_duration_cb) { | 161 const TimeDeltaCB& get_duration_cb) { |
| 162 DCHECK(task_runner_->BelongsToCurrentThread()); | 162 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 163 base::AutoLock auto_lock(lock_); | |
| 164 DCHECK(stream); | 163 DCHECK(stream); |
| 165 DCHECK_EQ(stream->type(), DemuxerStream::VIDEO); | 164 DCHECK_EQ(stream->type(), DemuxerStream::VIDEO); |
| 166 DCHECK(!init_cb.is_null()); | 165 DCHECK(!init_cb.is_null()); |
| 167 DCHECK(!statistics_cb.is_null()); | 166 DCHECK(!statistics_cb.is_null()); |
| 168 DCHECK(!max_time_cb.is_null()); | 167 DCHECK(!max_time_cb.is_null()); |
| 169 DCHECK(!ended_cb.is_null()); | 168 DCHECK(!ended_cb.is_null()); |
| 170 DCHECK(!get_time_cb.is_null()); | 169 DCHECK(!get_time_cb.is_null()); |
| 171 DCHECK(!get_duration_cb.is_null()); | 170 DCHECK(!get_duration_cb.is_null()); |
| 172 DCHECK_EQ(kUninitialized, state_); | 171 DCHECK_EQ(kUninitialized, state_); |
| 173 | 172 |
| 174 init_cb_ = init_cb; | 173 init_cb_ = init_cb; |
| 175 statistics_cb_ = statistics_cb; | 174 statistics_cb_ = statistics_cb; |
| 176 max_time_cb_ = max_time_cb; | 175 max_time_cb_ = max_time_cb; |
| 177 ended_cb_ = ended_cb; | 176 ended_cb_ = ended_cb; |
| 178 error_cb_ = error_cb; | 177 error_cb_ = error_cb; |
| 179 get_time_cb_ = get_time_cb; | 178 get_time_cb_ = get_time_cb; |
| 180 get_duration_cb_ = get_duration_cb; | 179 get_duration_cb_ = get_duration_cb; |
| 181 state_ = kInitializing; | 180 state_ = kInitializing; |
| 182 | 181 |
| 183 video_frame_stream_.Initialize( | 182 video_frame_stream_.Initialize( |
| 184 stream, | 183 stream, |
| 185 statistics_cb, | 184 statistics_cb, |
| 186 base::Bind(&VideoRendererImpl::OnVideoFrameStreamInitialized, | 185 base::Bind(&VideoRendererImpl::OnVideoFrameStreamInitialized, |
| 187 weak_factory_.GetWeakPtr())); | 186 weak_factory_.GetWeakPtr())); |
| 188 } | 187 } |
| 189 | 188 |
| 190 void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) { | 189 void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) { |
| 191 DCHECK(task_runner_->BelongsToCurrentThread()); | 190 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 192 base::AutoLock auto_lock(lock_); | |
| 193 | 191 |
| 194 if (state_ == kStopped) | 192 if (state_ == kStopped) |
| 195 return; | 193 return; |
| 196 | 194 |
| 197 DCHECK_EQ(state_, kInitializing); | 195 DCHECK_EQ(state_, kInitializing); |
| 198 | 196 |
| 199 if (!success) { | 197 if (!success) { |
| 200 state_ = kUninitialized; | 198 state_ = kUninitialized; |
| 201 base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED); | 199 base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED); |
| 202 return; | 200 return; |
| 203 } | 201 } |
| 204 | 202 |
| 205 // We're all good! Consider ourselves flushed. (ThreadMain() should never | 203 // We're all good! Consider ourselves flushed. (ThreadMain() should never |
| 206 // see us in the kUninitialized state). | 204 // see us in the kUninitialized state). |
| 207 // Since we had an initial Preroll(), we consider ourself flushed, because we | 205 // Since we had an initial Preroll(), we consider ourself flushed, because we |
| 208 // have not populated any buffers yet. | 206 // have not populated any buffers yet. |
| 209 state_ = kFlushed; | 207 state_ = kFlushed; |
| 210 | 208 |
| 211 // Create our video thread. | |
| 212 if (!base::PlatformThread::Create(0, this, &thread_)) { | |
| 213 NOTREACHED() << "Video thread creation failed"; | |
| 214 state_ = kError; | |
| 215 base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED); | |
| 216 return; | |
| 217 } | |
| 218 | |
| 219 #if defined(OS_WIN) | |
| 220 // Bump up our priority so our sleeping is more accurate. | |
| 221 // TODO(scherkus): find out if this is necessary, but it seems to help. | |
| 222 ::SetThreadPriority(thread_.platform_handle(), THREAD_PRIORITY_ABOVE_NORMAL); | |
| 223 #endif // defined(OS_WIN) | |
| 224 base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); | 209 base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); |
| 225 } | 210 } |
| 226 | 211 |
| 227 // PlatformThread::Delegate implementation. | |
| 228 void VideoRendererImpl::ThreadMain() { | |
| 229 base::PlatformThread::SetName("CrVideoRenderer"); | |
| 230 | |
| 231 // The number of milliseconds to idle when we do not have anything to do. | |
| 232 // Nothing special about the value, other than we're being more OS-friendly | |
| 233 // than sleeping for 1 millisecond. | |
| 234 // | |
| 235 // TODO(scherkus): switch to pure event-driven frame timing instead of this | |
| 236 // kIdleTimeDelta business http://crbug.com/106874 | |
| 237 const base::TimeDelta kIdleTimeDelta = | |
| 238 base::TimeDelta::FromMilliseconds(10); | |
| 239 | |
| 240 for (;;) { | |
| 241 base::AutoLock auto_lock(lock_); | |
| 242 | |
| 243 // Thread exit condition. | |
| 244 if (state_ == kStopped) | |
| 245 return; | |
| 246 | |
| 247 // Remain idle as long as we're not playing. | |
| 248 if (state_ != kPlaying || playback_rate_ == 0) { | |
| 249 UpdateStatsAndWait_Locked(kIdleTimeDelta); | |
| 250 continue; | |
| 251 } | |
| 252 | |
| 253 // Remain idle until we have the next frame ready for rendering. | |
| 254 if (ready_frames_.empty()) { | |
| 255 if (received_end_of_stream_) { | |
| 256 state_ = kEnded; | |
| 257 ended_cb_.Run(); | |
| 258 | |
| 259 // No need to sleep here as we idle when |state_ != kPlaying|. | |
| 260 continue; | |
| 261 } | |
| 262 | |
| 263 UpdateStatsAndWait_Locked(kIdleTimeDelta); | |
| 264 continue; | |
| 265 } | |
| 266 | |
| 267 base::TimeDelta remaining_time = | |
| 268 CalculateSleepDuration(ready_frames_.front(), playback_rate_); | |
| 269 | |
| 270 // Sleep up to a maximum of our idle time until we're within the time to | |
| 271 // render the next frame. | |
| 272 if (remaining_time.InMicroseconds() > 0) { | |
| 273 remaining_time = std::min(remaining_time, kIdleTimeDelta); | |
| 274 UpdateStatsAndWait_Locked(remaining_time); | |
| 275 continue; | |
| 276 } | |
| 277 | |
| 278 // Deadline is defined as the midpoint between this frame and the next | |
| 279 // frame, using the delta between this frame and the previous frame as the | |
| 280 // assumption for frame duration. | |
| 281 // | |
| 282 // TODO(scherkus): An improvement over midpoint might be selecting the | |
| 283 // minimum and/or maximum between the midpoint and some constants. As a | |
| 284 // thought experiment, consider what would be better than the midpoint | |
| 285 // for both the 1fps case and 120fps case. | |
| 286 // | |
| 287 // TODO(scherkus): This can be vastly improved. Use a histogram to measure | |
| 288 // the accuracy of our frame timing code. http://crbug.com/149829 | |
| 289 if (drop_frames_ && last_timestamp_ != kNoTimestamp()) { | |
| 290 base::TimeDelta now = get_time_cb_.Run(); | |
| 291 base::TimeDelta deadline = ready_frames_.front()->timestamp() + | |
| 292 (ready_frames_.front()->timestamp() - last_timestamp_) / 2; | |
| 293 | |
| 294 if (now > deadline) { | |
| 295 DropNextReadyFrame_Locked(); | |
| 296 continue; | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 // Congratulations! You've made it past the video frame timing gauntlet. | |
| 301 // | |
| 302 // At this point enough time has passed that the next frame that ready for | |
| 303 // rendering. | |
| 304 PaintNextReadyFrame_Locked(); | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 void VideoRendererImpl::PaintNextReadyFrame_Locked() { | |
| 309 lock_.AssertAcquired(); | |
| 310 | |
| 311 scoped_refptr<VideoFrame> next_frame = ready_frames_.front(); | |
| 312 ready_frames_.pop_front(); | |
| 313 frames_decoded_++; | |
| 314 | |
| 315 last_timestamp_ = next_frame->timestamp(); | |
| 316 | |
| 317 paint_cb_.Run(next_frame); | |
| 318 | |
| 319 task_runner_->PostTask( | |
| 320 FROM_HERE, | |
| 321 base::Bind(&VideoRendererImpl::AttemptRead, weak_factory_.GetWeakPtr())); | |
| 322 } | |
| 323 | |
| 324 void VideoRendererImpl::DropNextReadyFrame_Locked() { | |
| 325 TRACE_EVENT0("media", "VideoRendererImpl:frameDropped"); | |
| 326 | |
| 327 lock_.AssertAcquired(); | |
| 328 | |
| 329 last_timestamp_ = ready_frames_.front()->timestamp(); | |
| 330 ready_frames_.pop_front(); | |
| 331 frames_decoded_++; | |
| 332 frames_dropped_++; | |
| 333 | |
| 334 task_runner_->PostTask( | |
| 335 FROM_HERE, | |
| 336 base::Bind(&VideoRendererImpl::AttemptRead, weak_factory_.GetWeakPtr())); | |
| 337 } | |
| 338 | |
| 339 void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, | 212 void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, |
| 340 const scoped_refptr<VideoFrame>& frame) { | 213 const scoped_refptr<VideoFrame>& frame) { |
| 341 base::AutoLock auto_lock(lock_); | 214 LOG(ERROR) << __FUNCTION__ << " eos=" << (frame && frame->end_of_stream()); |
| 342 DCHECK_NE(state_, kUninitialized); | 215 DCHECK_NE(state_, kUninitialized); |
| 343 DCHECK_NE(state_, kFlushed); | 216 DCHECK_NE(state_, kFlushed); |
| 344 | 217 |
| 345 CHECK(pending_read_); | 218 CHECK(pending_read_); |
| 346 pending_read_ = false; | 219 pending_read_ = false; |
| 347 | 220 |
| 348 if (status == VideoFrameStream::DECODE_ERROR || | 221 if (status == VideoFrameStream::DECODE_ERROR || |
| 349 status == VideoFrameStream::DECRYPT_ERROR) { | 222 status == VideoFrameStream::DECRYPT_ERROR) { |
| 350 DCHECK(!frame.get()); | 223 DCHECK(!frame.get()); |
| 351 PipelineStatus error = PIPELINE_ERROR_DECODE; | 224 PipelineStatus error = PIPELINE_ERROR_DECODE; |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 364 // Already-queued VideoFrameStream ReadCB's can fire after various state | 237 // Already-queued VideoFrameStream ReadCB's can fire after various state |
| 365 // transitions have happened; in that case just drop those frames immediately. | 238 // transitions have happened; in that case just drop those frames immediately. |
| 366 if (state_ == kStopped || state_ == kError || state_ == kFlushing) | 239 if (state_ == kStopped || state_ == kError || state_ == kFlushing) |
| 367 return; | 240 return; |
| 368 | 241 |
| 369 if (!frame.get()) { | 242 if (!frame.get()) { |
| 370 // Abort preroll early for a NULL frame because we won't get more frames. | 243 // Abort preroll early for a NULL frame because we won't get more frames. |
| 371 // A new preroll will be requested after this one completes so there is no | 244 // A new preroll will be requested after this one completes so there is no |
| 372 // point trying to collect more frames. | 245 // point trying to collect more frames. |
| 373 if (state_ == kPrerolling) | 246 if (state_ == kPrerolling) |
| 374 TransitionToPrerolled_Locked(); | 247 TransitionToPrerolled(); |
| 375 | 248 |
| 376 return; | 249 return; |
| 377 } | 250 } |
| 378 | 251 |
| 379 if (frame->end_of_stream()) { | 252 if (frame->end_of_stream()) { |
| 380 DCHECK(!received_end_of_stream_); | 253 DCHECK(!received_end_of_stream_); |
| 381 received_end_of_stream_ = true; | 254 received_end_of_stream_ = true; |
| 382 max_time_cb_.Run(get_duration_cb_.Run()); | 255 max_time_cb_.Run(get_duration_cb_.Run()); |
| 383 | 256 |
| 384 if (state_ == kPrerolling) | 257 if (state_ == kPrerolling) |
| 385 TransitionToPrerolled_Locked(); | 258 TransitionToPrerolled(); |
| 386 | 259 |
| 387 return; | 260 return; |
| 388 } | 261 } |
| 389 | 262 |
| 390 // Maintain the latest frame decoded so the correct frame is displayed after | 263 // Maintain the latest frame decoded so the correct frame is displayed after |
| 391 // prerolling has completed. | 264 // prerolling has completed. |
| 392 if (state_ == kPrerolling && preroll_timestamp_ != kNoTimestamp() && | 265 if (state_ == kPrerolling && preroll_timestamp_ != kNoTimestamp() && |
| 393 frame->timestamp() <= preroll_timestamp_) { | 266 frame->timestamp() <= preroll_timestamp_) { |
| 394 ready_frames_.clear(); | 267 unscheduled_frames_.clear(); |
| 395 } | 268 } |
| 396 | 269 |
| 397 AddReadyFrame_Locked(frame); | |
| 398 | |
| 399 if (ShouldTransitionToPrerolled_Locked()) | |
| 400 TransitionToPrerolled_Locked(); | |
| 401 | |
| 402 // Always request more decoded video if we have capacity. This serves two | |
| 403 // purposes: | |
| 404 // 1) Prerolling while paused | |
| 405 // 2) Keeps decoding going if video rendering thread starts falling behind | |
| 406 AttemptRead_Locked(); | |
| 407 } | |
| 408 | |
| 409 bool VideoRendererImpl::ShouldTransitionToPrerolled_Locked() { | |
| 410 return state_ == kPrerolling && | |
| 411 (!video_frame_stream_.CanReadWithoutStalling() || | |
| 412 ready_frames_.size() >= static_cast<size_t>(limits::kMaxVideoFrames)); | |
| 413 } | |
| 414 | |
| 415 void VideoRendererImpl::AddReadyFrame_Locked( | |
| 416 const scoped_refptr<VideoFrame>& frame) { | |
| 417 lock_.AssertAcquired(); | |
| 418 DCHECK(!frame->end_of_stream()); | |
| 419 | |
| 420 // Adjust the incoming frame if its rendering stop time is past the duration | 270 // Adjust the incoming frame if its rendering stop time is past the duration |
| 421 // of the video itself. This is typically the last frame of the video and | 271 // of the video itself. This is typically the last frame of the video and |
| 422 // occurs if the container specifies a duration that isn't a multiple of the | 272 // occurs if the container specifies a duration that isn't a multiple of the |
| 423 // frame rate. Another way for this to happen is for the container to state | 273 // frame rate. Another way for this to happen is for the container to state |
| 424 // a smaller duration than the largest packet timestamp. | 274 // a smaller duration than the largest packet timestamp. |
| 425 base::TimeDelta duration = get_duration_cb_.Run(); | 275 base::TimeDelta duration = get_duration_cb_.Run(); |
| 426 if (frame->timestamp() > duration) { | 276 if (frame->timestamp() > duration) { |
| 427 frame->set_timestamp(duration); | 277 frame->set_timestamp(duration); |
| 428 } | 278 } |
| 429 | 279 |
| 430 ready_frames_.push_back(frame); | 280 unscheduled_frames_.push_back(frame); |
| 431 DCHECK_LE(ready_frames_.size(), | 281 DCHECK_LE(video_frame_count(), limits::kMaxVideoFrames); |
| 432 static_cast<size_t>(limits::kMaxVideoFrames)); | |
| 433 | 282 |
| 434 max_time_cb_.Run(frame->timestamp()); | 283 max_time_cb_.Run(frame->timestamp()); |
| 435 | 284 |
| 436 // Avoid needlessly waking up |thread_| unless playing. | 285 if (ShouldTransitionToPrerolled()) |
| 286 TransitionToPrerolled(); | |
| 287 | |
| 437 if (state_ == kPlaying) | 288 if (state_ == kPlaying) |
| 438 frame_available_.Signal(); | 289 ScheduleVideoFrames(); |
| 290 | |
| 291 if (ShouldTransitionToEnded()) | |
| 292 TransitionToEnded(); | |
| 293 | |
| 294 // Always request more decoded video if we have capacity. This serves two | |
| 295 // purposes: | |
| 296 // 1) Prerolling while paused | |
| 297 // 2) Keeps decoding going if video rendering thread starts falling behind | |
| 298 AttemptRead(); | |
| 299 } | |
| 300 | |
| 301 bool VideoRendererImpl::ShouldTransitionToPrerolled() { | |
| 302 return state_ == kPrerolling && | |
| 303 (!video_frame_stream_.CanReadWithoutStalling() || | |
| 304 video_frame_count() >= limits::kMaxVideoFrames); | |
| 305 } | |
| 306 | |
| 307 bool VideoRendererImpl::ShouldTransitionToEnded() { | |
| 308 LOG(ERROR) << __FUNCTION__ << " state_=" << state_ | |
| 309 << " rate=" << playback_rate_ | |
| 310 << " unsched=" << unscheduled_frames_.size() | |
| 311 << " sched=" << scheduled_frames_.size() | |
| 312 << " eos=" << received_end_of_stream_; | |
| 313 return state_ == kPlaying && playback_rate_ != 0.0f && | |
| 314 unscheduled_frames_.empty() && scheduled_frames_.empty() && | |
| 315 received_end_of_stream_; | |
| 316 } | |
| 317 | |
| 318 void VideoRendererImpl::TransitionToEnded() { | |
| 319 DCHECK_EQ(state_, kPlaying); | |
| 320 state_ = kEnded; | |
| 321 ended_cb_.Run(); | |
| 439 } | 322 } |
| 440 | 323 |
| 441 void VideoRendererImpl::AttemptRead() { | 324 void VideoRendererImpl::AttemptRead() { |
| 442 base::AutoLock auto_lock(lock_); | 325 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 443 AttemptRead_Locked(); | |
| 444 } | |
| 445 | 326 |
| 446 void VideoRendererImpl::AttemptRead_Locked() { | 327 LOG(ERROR) << __FUNCTION__ << " pending_read_=" << pending_read_ |
| 447 DCHECK(task_runner_->BelongsToCurrentThread()); | 328 << " received_eos_=" << received_end_of_stream_ |
| 448 lock_.AssertAcquired(); | 329 << " count=" << video_frame_count() << "/" |
| 330 << limits::kMaxVideoFrames << " state_=" << state_; | |
| 449 | 331 |
| 450 if (pending_read_ || received_end_of_stream_ || | 332 if (pending_read_ || received_end_of_stream_ || |
| 451 ready_frames_.size() == static_cast<size_t>(limits::kMaxVideoFrames)) { | 333 video_frame_count() == limits::kMaxVideoFrames) { |
| 334 LOG(ERROR) << __FUNCTION__ << " -> did not read"; | |
| 452 return; | 335 return; |
| 453 } | 336 } |
| 454 | 337 |
| 455 switch (state_) { | 338 switch (state_) { |
| 456 case kPaused: | 339 case kPaused: |
| 457 case kPrerolling: | 340 case kPrerolling: |
| 341 case kPrerolled: | |
|
scherkus (not reviewing)
2014/04/16 23:45:02
it also turns out that after hitting kPrerolled an
| |
| 458 case kPlaying: | 342 case kPlaying: |
| 343 LOG(ERROR) << __FUNCTION__ << " -> did read"; | |
| 459 pending_read_ = true; | 344 pending_read_ = true; |
| 460 video_frame_stream_.Read(base::Bind(&VideoRendererImpl::FrameReady, | 345 video_frame_stream_.Read(base::Bind(&VideoRendererImpl::FrameReady, |
| 461 weak_factory_.GetWeakPtr())); | 346 weak_factory_.GetWeakPtr())); |
| 462 return; | 347 return; |
| 463 | 348 |
| 464 case kUninitialized: | 349 case kUninitialized: |
| 465 case kInitializing: | 350 case kInitializing: |
| 466 case kPrerolled: | |
| 467 case kFlushing: | 351 case kFlushing: |
| 468 case kFlushed: | 352 case kFlushed: |
| 469 case kEnded: | 353 case kEnded: |
| 470 case kStopped: | 354 case kStopped: |
| 471 case kError: | 355 case kError: |
| 356 LOG(ERROR) << __FUNCTION__ << " -> did not read"; | |
| 472 return; | 357 return; |
| 473 } | 358 } |
| 474 } | 359 } |
| 475 | 360 |
| 476 void VideoRendererImpl::OnVideoFrameStreamResetDone() { | 361 void VideoRendererImpl::OnVideoFrameStreamResetDone() { |
| 477 base::AutoLock auto_lock(lock_); | |
| 478 if (state_ == kStopped) | 362 if (state_ == kStopped) |
| 479 return; | 363 return; |
| 480 | 364 |
| 481 DCHECK_EQ(kFlushing, state_); | 365 DCHECK_EQ(kFlushing, state_); |
| 482 DCHECK(!pending_read_); | 366 DCHECK(!pending_read_); |
| 483 DCHECK(ready_frames_.empty()); | 367 DCHECK(unscheduled_frames_.empty()); |
| 368 DCHECK(scheduled_frames_.empty()); | |
| 484 DCHECK(!received_end_of_stream_); | 369 DCHECK(!received_end_of_stream_); |
| 485 | 370 |
| 486 state_ = kFlushed; | 371 state_ = kFlushed; |
| 487 last_timestamp_ = kNoTimestamp(); | |
| 488 base::ResetAndReturn(&flush_cb_).Run(); | 372 base::ResetAndReturn(&flush_cb_).Run(); |
| 489 } | 373 } |
| 490 | 374 |
| 491 base::TimeDelta VideoRendererImpl::CalculateSleepDuration( | 375 void VideoRendererImpl::TransitionToPrerolled() { |
| 492 const scoped_refptr<VideoFrame>& next_frame, | |
| 493 float playback_rate) { | |
| 494 // Determine the current and next presentation timestamps. | |
| 495 base::TimeDelta now = get_time_cb_.Run(); | |
| 496 base::TimeDelta next_pts = next_frame->timestamp(); | |
| 497 | |
| 498 // Scale our sleep based on the playback rate. | |
| 499 base::TimeDelta sleep = next_pts - now; | |
| 500 return base::TimeDelta::FromMicroseconds( | |
| 501 static_cast<int64>(sleep.InMicroseconds() / playback_rate)); | |
| 502 } | |
| 503 | |
| 504 void VideoRendererImpl::DoStopOrError_Locked() { | |
| 505 lock_.AssertAcquired(); | |
| 506 last_timestamp_ = kNoTimestamp(); | |
| 507 ready_frames_.clear(); | |
| 508 } | |
| 509 | |
| 510 void VideoRendererImpl::TransitionToPrerolled_Locked() { | |
| 511 lock_.AssertAcquired(); | |
| 512 DCHECK_EQ(state_, kPrerolling); | 376 DCHECK_EQ(state_, kPrerolling); |
| 513 | 377 |
| 514 state_ = kPrerolled; | 378 state_ = kPrerolled; |
| 515 | 379 |
| 516 // Because we might remain in the prerolled state for an undetermined amount | 380 // Because we might remain in the prerolled state for an undetermined amount |
| 517 // of time (e.g., we seeked while paused), we'll paint the first prerolled | 381 // of time (e.g., we seeked while paused), we'll paint the first prerolled |
| 518 // frame. | 382 // frame. |
| 519 if (!ready_frames_.empty()) | 383 if (!unscheduled_frames_.empty()) { |
| 520 PaintNextReadyFrame_Locked(); | 384 ScheduleFirstFrameForImmediateDisplay(); |
|
scherkus (not reviewing)
2014/04/16 23:45:02
FYI here's the bit where we only display that firs
| |
| 385 AttemptRead(); | |
| 386 } | |
| 521 | 387 |
| 522 base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK); | 388 base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK); |
| 523 } | 389 } |
| 524 | 390 |
| 525 void VideoRendererImpl::UpdateStatsAndWait_Locked( | 391 void VideoRendererImpl::ScheduleVideoFrames() { |
| 526 base::TimeDelta wait_duration) { | 392 if (playback_rate_ == 0) |
| 527 lock_.AssertAcquired(); | 393 return; |
| 528 DCHECK_GE(frames_decoded_, 0); | |
| 529 DCHECK_LE(frames_dropped_, frames_decoded_); | |
| 530 | 394 |
| 531 if (frames_decoded_) { | 395 base::TimeDelta current_media_time = get_time_cb_.Run(); |
| 532 PipelineStatistics statistics; | 396 base::TimeTicks current_wall_ticks = base::TimeTicks::Now(); |
| 533 statistics.video_frames_decoded = frames_decoded_; | |
| 534 statistics.video_frames_dropped = frames_dropped_; | |
| 535 statistics_cb_.Run(statistics); | |
| 536 | 397 |
| 537 frames_decoded_ = 0; | 398 while (!unscheduled_frames_.empty()) { |
| 538 frames_dropped_ = 0; | 399 base::TimeDelta delta = |
| 400 (unscheduled_frames_.front()->timestamp() - current_media_time) / | |
| 401 playback_rate_; | |
| 402 base::TimeTicks target_wall_ticks = current_wall_ticks + delta; | |
| 403 | |
| 404 scheduled_frames_.push_back(unscheduled_frames_.front()); | |
| 405 unscheduled_frames_.pop_front(); | |
| 406 | |
| 407 scheduler_->ScheduleVideoFrame( | |
| 408 scheduled_frames_.back(), | |
| 409 target_wall_ticks, | |
| 410 base::Bind(&VideoRendererImpl::OnVideoFrameFinished, | |
| 411 weak_factory_.GetWeakPtr())); | |
| 412 } | |
| 413 } | |
| 414 | |
| 415 void VideoRendererImpl::ScheduleFirstFrameForImmediateDisplay() { | |
| 416 scheduled_frames_.push_back(unscheduled_frames_.front()); | |
| 417 unscheduled_frames_.pop_front(); | |
| 418 | |
| 419 scheduler_->ScheduleVideoFrame( | |
| 420 scheduled_frames_.back(), | |
| 421 base::TimeTicks::Now(), | |
| 422 base::Bind(&VideoRendererImpl::OnVideoFrameFinished, | |
| 423 weak_factory_.GetWeakPtr())); | |
| 424 } | |
| 425 | |
| 426 void VideoRendererImpl::OnVideoFrameFinished( | |
| 427 const scoped_refptr<VideoFrame>& frame, | |
| 428 VideoFrameScheduler::Reason reason) { | |
| 429 VideoFrameQueue::iterator iter = | |
| 430 std::find(scheduled_frames_.begin(), scheduled_frames_.end(), frame); | |
| 431 CHECK(iter != scheduled_frames_.end()); | |
| 432 | |
| 433 switch (reason) { | |
| 434 case VideoFrameScheduler::DISPLAYED: | |
| 435 ++frames_decoded_; | |
| 436 scheduled_frames_.erase(iter); | |
| 437 break; | |
| 438 | |
| 439 case VideoFrameScheduler::DROPPED: | |
| 440 ++frames_decoded_; | |
| 441 ++frames_dropped_; | |
| 442 scheduled_frames_.erase(iter); | |
| 443 break; | |
| 444 | |
| 445 case VideoFrameScheduler::RESET: | |
| 446 unscheduled_frames_.push_back(*iter); | |
| 447 scheduled_frames_.erase(iter); | |
| 448 break; | |
| 539 } | 449 } |
| 540 | 450 |
| 541 frame_available_.TimedWait(wait_duration); | 451 if (unscheduled_frames_.empty() && received_end_of_stream_) { |
|
scherkus (not reviewing)
2014/04/16 23:45:02
I'm pretty sure this will fire ended too soon.
We
| |
| 452 state_ = kEnded; | |
| 453 ended_cb_.Run(); | |
| 454 return; | |
| 455 } | |
| 456 | |
| 457 AttemptRead(); | |
| 542 } | 458 } |
| 543 | 459 |
| 544 } // namespace media | 460 } // namespace media |
| OLD | NEW |