OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this |
2 // source code is governed by a BSD-style license that can be found in the | 2 // source code is governed by a BSD-style license that can be found in the |
3 // LICENSE file. | 3 // LICENSE file. |
4 | 4 |
5 #include "chrome/renderer/media/video_renderer_impl.h" | 5 #include "chrome/renderer/media/video_renderer_impl.h" |
| 6 #include "media/base/buffers.h" |
| 7 #include "media/base/filter_host.h" |
| 8 #include "media/base/pipeline.h" |
| 9 |
| 10 using media::MediaFormat; |
| 11 using media::VideoFrame; |
| 12 |
| 13 |
| 14 // The amount of time allowed to pre-submit a frame. If UpdateQueue is called |
| 15 // with a time within this limit, it will skip to the next frame in the queue |
| 16 // even though the value for time it is called with is not yet at the frame's |
| 17 // timestamp. Time is specified in microseconds. |
| 18 static const int64 kFrameSkipAheadLimit = 5000; |
| 19 |
| 20 // If there are no frames in the queue, then this value is used for the |
| 21 // amount of time to sleep until the next time update callback. Time is |
| 22 // specified in microseconds. |
| 23 static const int64 kSleepIfNoFrame = 15000; |
| 24 |
| 25 // Number of reads to have pending. |
| 26 // TODO(ralph): Re-examine the pull model -- perhaps I was wrong. I think |
| 27 // that perhaps the demuxer is the point where pull becomes push. |
| 28 static const int kDefaultNumberOfFrames = 4; |
| 29 |
| 30 // Value used for the current_frame_timestamp_ to indicate that the |
| 31 // |current_frame_| member should be treated as invalid. |
| 32 static const int64 kNoCurrentFrame = -1; |
| 33 |
| 34 //------------------------------------------------------------------------------ |
6 | 35 |
7 VideoRendererImpl::VideoRendererImpl(WebMediaPlayerDelegateImpl* delegate) | 36 VideoRendererImpl::VideoRendererImpl(WebMediaPlayerDelegateImpl* delegate) |
8 : delegate_(delegate) { | 37 : delegate_(delegate), |
| 38 submit_reads_task_(NULL), |
| 39 number_of_reads_needed_(kDefaultNumberOfFrames), |
| 40 current_frame_timestamp_( |
| 41 base::TimeDelta::FromMicroseconds(kNoCurrentFrame)), |
| 42 preroll_complete_(false) { |
9 } | 43 } |
10 | 44 |
11 VideoRendererImpl::~VideoRendererImpl() { | 45 VideoRendererImpl::~VideoRendererImpl() { |
| 46 Stop(); |
| 47 } |
| 48 |
| 49 // static |
| 50 bool VideoRendererImpl::IsMediaFormatSupported( |
| 51 const media::MediaFormat* media_format) { |
| 52 int width; |
| 53 int height; |
| 54 return ParseMediaFormat(media_format, &width, &height); |
| 55 } |
| 56 |
| 57 // static |
| 58 bool VideoRendererImpl::ParseMediaFormat(const media::MediaFormat* media_format, |
| 59 int* width_out, |
| 60 int* height_out) { |
| 61 DCHECK(media_format && width_out && height_out); |
| 62 std::string mime_type; |
| 63 return (media_format->GetAsString(MediaFormat::kMimeType, &mime_type) && |
| 64 mime_type.compare(media::mime_type::kUncompressedVideo) == 0 && |
| 65 media_format->GetAsInteger(MediaFormat::kWidth, width_out) && |
| 66 media_format->GetAsInteger(MediaFormat::kHeight, height_out)); |
12 } | 67 } |
13 | 68 |
14 void VideoRendererImpl::Stop() { | 69 void VideoRendererImpl::Stop() { |
15 // TODO(scherkus): implement Stop. | 70 AutoLock auto_lock(lock_); |
16 NOTIMPLEMENTED(); | 71 DiscardAllFrames(); |
| 72 if (submit_reads_task_) { |
| 73 // The task is owned by the message loop, so we don't delete it here. We |
| 74 // know the task won't call us because we canceled it, and we know we are |
| 75 // on the pipeline thread, since we're in the filer's Stop method, so there |
| 76 // is no threading problem. Just let the task be run by the message loop |
| 77 // and then be killed |
| 78 submit_reads_task_->Cancel(); |
| 79 submit_reads_task_ = NULL; |
| 80 } |
| 81 delegate_ = NULL; // This indicates we're no longer running |
| 82 decoder_ = NULL; // Release reference to the decoder |
17 } | 83 } |
18 | 84 |
19 bool VideoRendererImpl::Initialize(media::VideoDecoder* decoder) { | 85 bool VideoRendererImpl::Initialize(media::VideoDecoder* decoder) { |
20 // TODO(scherkus): implement Initialize. | 86 int width; |
21 NOTIMPLEMENTED(); | 87 int height; |
22 return false; | 88 if (!ParseMediaFormat(decoder_->GetMediaFormat(), &width, &height)) { |
23 } | 89 return false; |
24 | 90 } |
25 bool VideoRendererImpl::IsMediaFormatSupported( | 91 current_frame_.setConfig(SkBitmap::kARGB_8888_Config, width, height); |
26 const media::MediaFormat* format) { | 92 if (!current_frame_.allocPixels(NULL, NULL)) { |
27 // TODO(hclam): check the format correctly. | 93 NOTREACHED(); |
| 94 return false; |
| 95 } |
| 96 rect_.SetRect(0, 0, width, height); |
| 97 delegate_->SetVideoRenderer(this); |
| 98 host_->SetVideoSize(width, height); |
| 99 host_->SetTimeUpdateCallback( |
| 100 NewCallback(this, &VideoRendererImpl::TimeUpdateCallback)); |
| 101 SubmitReads(); |
28 return true; | 102 return true; |
29 } | 103 } |
30 | 104 |
| 105 void VideoRendererImpl::SubmitReads() { |
| 106 int number_to_read; |
| 107 { |
| 108 AutoLock auto_lock(lock_); |
| 109 submit_reads_task_ = NULL; |
| 110 number_to_read = number_of_reads_needed_; |
| 111 number_of_reads_needed_ = 0; |
| 112 } |
| 113 while (number_to_read > 0) { |
| 114 decoder_->Read(new media::AssignableBuffer<VideoRendererImpl, |
| 115 media::VideoFrame>(this)); |
| 116 --number_to_read; |
| 117 } |
| 118 } |
| 119 |
| 120 void VideoRendererImpl::SetRect(const gfx::Rect& rect) { |
| 121 rect_ = rect; |
| 122 // TODO(ralphl) What are all these rects??? |
| 123 } |
| 124 |
| 125 // This method is always called on the renderer's thread, so it will not be |
| 126 // reentered. However, it does maniuplate the queue and the current frame |
| 127 // timestamp, so those manipulations need to happen with the lock held. |
31 void VideoRendererImpl::Paint(skia::PlatformCanvas *canvas, | 128 void VideoRendererImpl::Paint(skia::PlatformCanvas *canvas, |
32 const gfx::Rect& rect) { | 129 const gfx::Rect& rect) { |
33 // TODO(hclam): add stuff here. | 130 VideoFrame* video_frame; |
34 } | 131 base::TimeDelta time_of_next_frame; |
35 | 132 bool need_to_convert_frame = false; |
36 void VideoRendererImpl::SetRect(const gfx::Rect& rect) { | 133 { |
37 // TODO(hclam): add stuff here. | 134 AutoLock auto_lock(lock_); |
38 } | 135 UpdateQueue(host_->GetPipelineStatus()->GetTime(), NULL, &video_frame, |
| 136 &time_of_next_frame); |
| 137 if (video_frame) { |
| 138 // if the |current_frame_| bitmap already has the RGB image of the |
| 139 // front video_frame then there's on no need to call CopyToCurentFrame |
| 140 // to convert the video_frame to RBG. If we do need to convert a new |
| 141 // frame, then remember the time of the frame so we might be able to skip |
| 142 // this step if asked to repaint multiple times. Note that the |
| 143 // |current_frame_timestamp_| member needs to only be accessed with the |
| 144 // |lock_| acquired, so we set the timestamp here even though the |
| 145 // conversion won't take place until we call CopyToCurrentFrame. It's |
| 146 // not a problem because this method is the only place where the current |
| 147 // frame is updated, and it is always called on the renderer's thread. |
| 148 const base::TimeDelta frame_timestamp = video_frame->GetTimestamp(); |
| 149 need_to_convert_frame = (current_frame_timestamp_ != frame_timestamp); |
| 150 if (need_to_convert_frame) { |
| 151 current_frame_timestamp_ = frame_timestamp; |
| 152 } |
| 153 } |
| 154 } |
| 155 |
| 156 // We no longer hold the |lock_|. Don't access members other than |host_| and |
| 157 // |current_frame_|. |
| 158 if (video_frame) { |
| 159 if (need_to_convert_frame) { |
| 160 CopyToCurrentFrame(video_frame); |
| 161 } |
| 162 video_frame->Release(); |
| 163 SkMatrix matrix; |
| 164 matrix.setTranslate(static_cast<SkScalar>(rect.x()), |
| 165 static_cast<SkScalar>(rect.y())); |
| 166 // TODO(ralphl): I have no idea what's going on here. How are these |
| 167 // rects related to eachother? What does SetRect() mean? |
| 168 matrix.preScale(static_cast<SkScalar>(rect.width() / rect_.width()), |
| 169 static_cast<SkScalar>(rect.height() / rect_.height())); |
| 170 canvas->drawBitmapMatrix(current_frame_, matrix, NULL); |
| 171 } |
| 172 host_->ScheduleTimeUpdateCallback(time_of_next_frame); |
| 173 } |
| 174 |
| 175 void VideoRendererImpl::CopyToCurrentFrame(VideoFrame* video_frame) { |
| 176 media::VideoSurface frame_in; |
| 177 if (video_frame->Lock(&frame_in)) { |
| 178 // TODO(ralphl): Actually do the color space conversion here! |
| 179 // This is temporary code to set the bits of the current_frame_ to |
| 180 // blue. |
| 181 current_frame_.eraseRGB(0x00, 0x00, 0xFF); |
| 182 video_frame->Unlock(); |
| 183 } else { |
| 184 NOTREACHED(); |
| 185 } |
| 186 } |
| 187 |
| 188 // Assumes |lock_| has been acquired! |
| 189 bool VideoRendererImpl::UpdateQueue(base::TimeDelta time, |
| 190 VideoFrame* new_frame, |
| 191 VideoFrame** front_frame_out, |
| 192 base::TimeDelta* time_of_next_frame) { |
| 193 bool updated_front = false; |
| 194 |
| 195 // If a new frame is passed in then put it at the back of the queue. If the |
| 196 // queue was empty, then we've updated the front too. |
| 197 if (new_frame) { |
| 198 updated_front = queue_.empty(); |
| 199 new_frame->AddRef(); |
| 200 queue_.push_back(new_frame); |
| 201 } |
| 202 |
| 203 // Now make sure that the front of the queue is the correct frame to display |
| 204 // right now. Discard any frames that are past the current time. If any |
| 205 // frames are discarded then increment the |number_of_reads_needed_| member. |
| 206 while (queue_.size() > 1 && |
| 207 queue_.front()->GetTimestamp() + |
| 208 base::TimeDelta::FromMicroseconds(kFrameSkipAheadLimit) >= time) { |
| 209 queue_.front()->Release(); |
| 210 queue_.pop_front(); |
| 211 updated_front = true; |
| 212 ++number_of_reads_needed_; |
| 213 } |
| 214 |
| 215 // If the caller wants the front frame then return it, with the reference |
| 216 // count incremented. The caller must call Release() on the returned frame. |
| 217 if (front_frame_out) { |
| 218 if (queue_.empty()) { |
| 219 *front_frame_out = NULL; |
| 220 } else { |
| 221 *front_frame_out = queue_.front(); |
| 222 (*front_frame_out)->AddRef(); |
| 223 } |
| 224 } |
| 225 |
| 226 // If the caller wants the time of the next frame, return our best guess: |
| 227 // If no frame, then wait for a while |
| 228 // If only one frame, then use the duration of the front frame |
| 229 // If there is more than one frame, return the time of the next frame. |
| 230 if (time_of_next_frame) { |
| 231 if (queue_.empty()) { |
| 232 *time_of_next_frame = host_->GetPipelineStatus()->GetInterpolatedTime() + |
| 233 base::TimeDelta::FromMicroseconds(kSleepIfNoFrame); |
| 234 } else { |
| 235 if (queue_.size() == 1) { |
| 236 *time_of_next_frame = queue_.front()->GetTimestamp() + |
| 237 queue_.front()->GetDuration(); |
| 238 } else { |
| 239 *time_of_next_frame = queue_[1]->GetTimestamp(); |
| 240 } |
| 241 } |
| 242 } |
| 243 |
| 244 // If any frames have been removed we need to call the decoder again. Note |
| 245 // that the PostSubmitReadsTask method will only post the task if there are |
| 246 // pending reads. |
| 247 PostSubmitReadsTask(); |
| 248 |
| 249 // True if the front of the queue is a new frame. |
| 250 return updated_front; |
| 251 } |
| 252 |
| 253 // Assumes |lock_| has been acquired! |
| 254 void VideoRendererImpl::DiscardAllFrames() { |
| 255 while (!queue_.empty()) { |
| 256 queue_.front()->Release(); |
| 257 queue_.pop_front(); |
| 258 ++number_of_reads_needed_; |
| 259 } |
| 260 current_frame_timestamp_ = base::TimeDelta::FromMicroseconds(kNoCurrentFrame); |
| 261 } |
| 262 |
| 263 // Assumes |lock_| has been acquired! |
| 264 void VideoRendererImpl::PostSubmitReadsTask() { |
| 265 if (number_of_reads_needed_ > 0 && !submit_reads_task_) { |
| 266 submit_reads_task_ = NewRunnableMethod(this, |
| 267 &VideoRendererImpl::SubmitReads); |
| 268 host_->PostTask(submit_reads_task_); |
| 269 } |
| 270 } |
| 271 |
| 272 void VideoRendererImpl::TimeUpdateCallback(base::TimeDelta time) { |
| 273 AutoLock auto_lock(lock_); |
| 274 if (IsRunning() && UpdateQueue(time, NULL, NULL, NULL)) { |
| 275 delegate_->PostRepaintTask(); |
| 276 } |
| 277 } |
| 278 |
| 279 void VideoRendererImpl::OnAssignment(VideoFrame* video_frame) { |
| 280 bool call_initialized = false; |
| 281 { |
| 282 AutoLock auto_lock(lock_); |
| 283 if (IsRunning()) { |
| 284 // TODO(ralphl): if (!preroll_complete_ && EndOfStream) call_init = true |
| 285 // and preroll_complete_ = true. |
| 286 // TODO(ralphl): If(Seek()) then discard but we don't have SeekFrame(). |
| 287 if (false) { |
| 288 // TODO(ralphl): this is the seek() logic. |
| 289 DiscardAllFrames(); |
| 290 ++number_of_reads_needed_; |
| 291 PostSubmitReadsTask(); |
| 292 } else { |
| 293 if (UpdateQueue(host_->GetPipelineStatus()->GetInterpolatedTime(), |
| 294 video_frame, NULL, NULL)) { |
| 295 delegate_->PostRepaintTask(); |
| 296 } |
| 297 if (!preroll_complete_ && queue_.size() == kDefaultNumberOfFrames) { |
| 298 preroll_complete_ = true; |
| 299 call_initialized = true; |
| 300 } |
| 301 } |
| 302 } |
| 303 } |
| 304 // |lock_| no longer held. Call the pipeline if we've just entered a |
| 305 // completed preroll state. |
| 306 if (call_initialized) { |
| 307 host_->InitializationComplete(); |
| 308 } |
| 309 } |
| 310 |
OLD | NEW |