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 |