Chromium Code Reviews| 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 size_t 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 //------------------------------------------------------------------------------ | |
| 35 | 6 |
| 36 VideoRendererImpl::VideoRendererImpl(WebMediaPlayerDelegateImpl* delegate) | 7 VideoRendererImpl::VideoRendererImpl(WebMediaPlayerDelegateImpl* delegate) |
| 37 : delegate_(delegate), | 8 : delegate_(delegate), |
| 38 submit_reads_task_(NULL), | 9 last_converted_frame_(NULL) { |
| 39 number_of_reads_needed_(kDefaultNumberOfFrames), | |
| 40 current_frame_timestamp_( | |
| 41 base::TimeDelta::FromMicroseconds(kNoCurrentFrame)), | |
| 42 preroll_complete_(false) { | |
| 43 } | 10 } |
| 44 | 11 |
| 45 VideoRendererImpl::~VideoRendererImpl() { | 12 bool VideoRendererImpl::OnInitialize(size_t width, size_t height) { |
| 46 Stop(); | 13 video_size_.SetSize(width, height); |
| 14 bitmap_.setConfig(SkBitmap::kARGB_8888_Config, width, height); | |
| 15 if (bitmap_.allocPixels(NULL, NULL)) { | |
| 16 bitmap_.eraseRGB(0x00, 0x00, 0x00); | |
| 17 return true; | |
| 18 } | |
| 19 NOTREACHED(); | |
| 20 return false; | |
| 47 } | 21 } |
| 48 | 22 |
| 49 // static | 23 void VideoRendererImpl::OnPaintNeeded() { |
| 50 bool VideoRendererImpl::IsMediaFormatSupported( | 24 delegate_->PostRepaintTask(); |
| 51 const media::MediaFormat* media_format) { | |
| 52 int width; | |
| 53 int height; | |
| 54 return ParseMediaFormat(media_format, &width, &height); | |
| 55 } | 25 } |
| 56 | 26 |
| 57 // static | 27 // This method is always called on the renderer's thread. |
| 58 bool VideoRendererImpl::ParseMediaFormat(const media::MediaFormat* media_format, | 28 void VideoRendererImpl::Paint(skia::PlatformCanvas *canvas, |
|
scherkus (not reviewing)
2009/02/24 21:00:19
pointer goes with the type
| |
| 59 int* width_out, | 29 const gfx::Rect& dest_rect) { |
| 60 int* height_out) { | 30 scoped_refptr<media::VideoFrame> video_frame; |
| 61 DCHECK(media_format && width_out && height_out); | 31 GetCurrentFrame(&video_frame); |
| 62 std::string mime_type; | 32 if (video_frame.get()) { |
| 63 return (media_format->GetAsString(MediaFormat::kMimeType, &mime_type) && | 33 CopyToCurrentFrame(video_frame); |
| 64 mime_type.compare(media::mime_type::kUncompressedVideo) == 0 && | 34 video_frame = NULL; |
|
scherkus (not reviewing)
2009/02/24 21:00:19
This line isn't really needed due to scoped_refptr
| |
| 65 media_format->GetAsInteger(MediaFormat::kWidth, width_out) && | 35 } |
| 66 media_format->GetAsInteger(MediaFormat::kHeight, height_out)); | 36 SkMatrix matrix; |
| 37 matrix.setTranslate(static_cast<SkScalar>(dest_rect.x()), | |
| 38 static_cast<SkScalar>(dest_rect.y())); | |
| 39 if (dest_rect.width() != video_size_.width() || | |
| 40 dest_rect.height() != video_size_.height()) { | |
| 41 matrix.preScale( | |
| 42 static_cast<SkScalar>(dest_rect.width() / video_size_.width()), | |
| 43 static_cast<SkScalar>(dest_rect.height() / video_size_.height())); | |
| 44 } | |
| 45 canvas->drawBitmapMatrix(bitmap_, matrix, NULL); | |
| 67 } | 46 } |
| 68 | 47 |
| 69 void VideoRendererImpl::Stop() { | 48 void VideoRendererImpl::CopyToCurrentFrame(media::VideoFrame* video_frame) { |
| 70 AutoLock auto_lock(lock_); | 49 base::TimeDelta timestamp = video_frame->GetTimestamp(); |
| 71 DiscardAllFrames(); | 50 if (video_frame != last_converted_frame_ || |
| 72 if (submit_reads_task_) { | 51 timestamp != last_converted_timestamp_) { |
| 73 // The task is owned by the message loop, so we don't delete it here. We | 52 last_converted_frame_ = video_frame; |
| 74 // know the task won't call us because we canceled it, and we know we are | 53 last_converted_timestamp_ = timestamp; |
| 75 // on the pipeline thread, since we're in the filer's Stop method, so there | 54 media::VideoSurface frame_in; |
| 76 // is no threading problem. Just let the task be run by the message loop | 55 if (video_frame->Lock(&frame_in)) { |
| 77 // and then be killed | 56 // TODO(ralphl): Actually do the color space conversion here! |
| 78 submit_reads_task_->Cancel(); | 57 // This is temporary code to set the bits of the current_frame_ to |
| 79 submit_reads_task_ = NULL; | 58 // blue. |
| 80 } | 59 bitmap_.eraseRGB(0x00, 0x00, 0xFF); |
| 81 delegate_ = NULL; // This indicates we're no longer running | 60 video_frame->Unlock(); |
| 82 decoder_ = NULL; // Release reference to the decoder | 61 } else { |
| 83 } | 62 NOTREACHED(); |
| 84 | 63 } |
| 85 bool VideoRendererImpl::Initialize(media::VideoDecoder* decoder) { | |
| 86 int width; | |
| 87 int height; | |
| 88 if (!ParseMediaFormat(decoder_->GetMediaFormat(), &width, &height)) { | |
| 89 return false; | |
| 90 } | |
| 91 current_frame_.setConfig(SkBitmap::kARGB_8888_Config, width, height); | |
| 92 if (!current_frame_.allocPixels(NULL, NULL)) { | |
| 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(); | |
| 102 return true; | |
| 103 } | |
| 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 } | 64 } |
| 118 } | 65 } |
| 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. | |
| 128 void VideoRendererImpl::Paint(skia::PlatformCanvas *canvas, | |
| 129 const gfx::Rect& rect) { | |
| 130 VideoFrame* video_frame; | |
| 131 base::TimeDelta time_of_next_frame; | |
| 132 bool need_to_convert_frame = false; | |
| 133 { | |
| 134 AutoLock auto_lock(lock_); | |
| 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 |