OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include <GLES2/gl2.h> | |
6 #include <GLES2/gl2ext.h> | |
7 #include <GLES2/gl2extchromium.h> | |
8 | |
9 #include "content/renderer/pepper/video_decoder_adapter.h" | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/numerics/safe_conversions.h" | |
13 #include "content/public/renderer/render_thread.h" | |
14 #include "content/renderer/pepper/pepper_video_decoder_host.h" | |
15 #include "content/renderer/render_thread_impl.h" | |
16 #include "gpu/command_buffer/client/gles2_implementation.h" | |
17 #include "media/base/decoder_buffer.h" | |
18 #include "media/filters/ffmpeg_video_decoder.h" | |
19 #include "media/video/picture.h" | |
20 #include "media/video/video_decode_accelerator.h" | |
21 #include "ppapi/c/pp_errors.h" | |
22 #include "third_party/libyuv/include/libyuv.h" | |
23 #include "webkit/common/gpu/context_provider_web_context.h" | |
24 | |
25 namespace content { | |
26 | |
27 struct VideoDecoderAdapter::PendingDecode { | |
28 PendingDecode(uint32_t decode_id, | |
29 const scoped_refptr<media::DecoderBuffer>& buffer); | |
30 ~PendingDecode(); | |
31 | |
32 const uint32_t decode_id; | |
33 const scoped_refptr<media::DecoderBuffer> buffer; | |
34 }; | |
35 | |
36 VideoDecoderAdapter::PendingDecode::PendingDecode( | |
37 uint32_t decode_id, | |
38 const scoped_refptr<media::DecoderBuffer>& buffer) | |
39 : decode_id(decode_id), buffer(buffer) { | |
40 } | |
41 | |
42 VideoDecoderAdapter::PendingDecode::~PendingDecode() { | |
43 } | |
44 | |
45 struct VideoDecoderAdapter::PendingFrame { | |
46 explicit PendingFrame(uint32_t decode_id); | |
47 PendingFrame(uint32_t decode_id, const gfx::Size& size); | |
48 ~PendingFrame(); | |
49 | |
50 const uint32_t decode_id; | |
51 const gfx::Size size; | |
52 std::vector<uint8_t> argb_pixels; | |
Ami GONE FROM CHROMIUM
2014/06/06 17:14:33
Experimentation is the only authority :(
dmichael (off chromium)
2014/06/06 17:24:00
private:
DISALLOW_COPY_AND_ASSIGN(PendingFrame);
bbudge
2014/06/07 00:31:09
Done.
bbudge
2014/06/07 00:31:09
Looks like ARGB. I guess as long as there are an e
| |
53 }; | |
54 | |
55 VideoDecoderAdapter::PendingFrame::PendingFrame(uint32_t decode_id) | |
56 : decode_id(decode_id) { | |
57 } | |
58 | |
59 VideoDecoderAdapter::PendingFrame::PendingFrame(uint32_t decode_id, | |
60 const gfx::Size& size) | |
61 : decode_id(decode_id), | |
62 size(size), | |
63 argb_pixels(size.width() * size.height() * 4) { | |
64 } | |
65 | |
66 VideoDecoderAdapter::PendingFrame::~PendingFrame() { | |
67 } | |
68 | |
69 VideoDecoderAdapter::Delegate::Delegate( | |
70 const base::WeakPtr<VideoDecoderAdapter>& proxy) | |
71 : adapter_(proxy), | |
72 main_message_loop_(base::MessageLoopProxy::current()), | |
73 max_pending_decodes_(0), | |
74 num_pending_decodes_(0) { | |
75 } | |
76 | |
77 VideoDecoderAdapter::Delegate::~Delegate() { | |
78 DCHECK(pending_decodes_.empty()); | |
79 } | |
80 | |
81 void VideoDecoderAdapter::Delegate::Initialize( | |
82 media::VideoDecoderConfig config) { | |
83 DCHECK(!decoder_); | |
84 decoder_.reset( | |
85 new media::FFmpegVideoDecoder(base::MessageLoopProxy::current())); | |
86 max_pending_decodes_ = decoder_->GetMaxDecodeRequests(); | |
87 // We can use base::Unretained() safely in decoder callbacks because we call | |
88 // VideoDecoder::Stop() before deletion. Stop() guarantees there will be no | |
89 // outstanding callbacks after it returns. | |
90 decoder_->Initialize( | |
91 config, | |
92 true /* low_delay */, | |
93 base::Bind(&VideoDecoderAdapter::Delegate::OnPipelineStatus, | |
94 base::Unretained(this))); | |
95 } | |
96 | |
97 void VideoDecoderAdapter::Delegate::OnPipelineStatus( | |
98 media::PipelineStatus status) { | |
99 main_message_loop_->PostTask( | |
100 FROM_HERE, | |
101 base::Bind(&VideoDecoderAdapter::OnPipelineStatus, adapter_, status)); | |
102 } | |
103 | |
104 void VideoDecoderAdapter::Delegate::Decode( | |
105 uint32_t decode_id, | |
106 scoped_refptr<media::DecoderBuffer> buffer) { | |
107 DCHECK(decoder_); | |
108 if (num_pending_decodes_ < max_pending_decodes_) { | |
Ami GONE FROM CHROMIUM
2014/06/06 17:14:33
num_pending_decodes_ holding a value other than
pe
bbudge
2014/06/07 00:31:09
Done.
| |
109 num_pending_decodes_++; | |
110 decoder_->Decode( | |
Ami GONE FROM CHROMIUM
2014/06/06 17:14:33
DCHECK(pending_decodes_.empty());
bbudge
2014/06/07 00:31:09
It might not be true. I think you identified a rea
| |
111 buffer, | |
112 base::Bind(&VideoDecoderAdapter::Delegate::OnDecodeComplete, | |
113 base::Unretained(this), | |
114 decode_id)); | |
115 } else { | |
116 pending_decodes_.push(PendingDecode(decode_id, buffer)); | |
117 } | |
118 } | |
119 | |
120 void VideoDecoderAdapter::Delegate::Reset() { | |
121 DCHECK(decoder_); | |
122 // Abort all pending decodes. | |
123 while (!pending_decodes_.empty()) { | |
124 PendingDecode& decode = pending_decodes_.front(); | |
dmichael (off chromium)
2014/06/06 17:24:00
nit: const& ?
bbudge
2014/06/07 00:31:09
Done.
| |
125 scoped_ptr<PendingFrame> pending_frame(new PendingFrame(decode.decode_id)); | |
126 main_message_loop_->PostTask( | |
127 FROM_HERE, | |
128 base::Bind(&VideoDecoderAdapter::OnDecodeComplete, | |
129 adapter_, | |
130 media::VideoDecoder::kAborted, | |
131 base::Passed(&pending_frame))); | |
132 pending_decodes_.pop(); | |
133 } | |
134 decoder_->Reset(base::Bind(&VideoDecoderAdapter::Delegate::OnResetComplete, | |
135 base::Unretained(this))); | |
136 } | |
137 | |
138 void VideoDecoderAdapter::Delegate::Stop() { | |
139 DCHECK(decoder_); | |
140 // Clear pending decodes now. We don't want OnDecodeComplete to call Decode | |
141 // again. | |
142 while (!pending_decodes_.empty()) | |
143 pending_decodes_.pop(); | |
Ami GONE FROM CHROMIUM
2014/06/06 17:14:33
pending_decodes_.clear() instead of the previous t
bbudge
2014/06/07 00:31:09
Can't, it's std::queue.
| |
144 decoder_->Stop(); | |
145 // This instance is deleted once we exit this scope. | |
146 } | |
147 | |
148 void VideoDecoderAdapter::Delegate::OnDecodeComplete( | |
149 uint32_t decode_id, | |
150 media::VideoDecoder::Status status, | |
151 const scoped_refptr<media::VideoFrame>& frame) { | |
152 num_pending_decodes_--; | |
153 scoped_ptr<PendingFrame> pending_frame; | |
154 if (status == media::VideoDecoder::kOk && frame && !frame->end_of_stream()) { | |
Ami GONE FROM CHROMIUM
2014/06/06 17:14:33
&& frame is unnecessary here.
bbudge
2014/06/07 00:31:09
Done.
| |
155 pending_frame.reset(new PendingFrame(decode_id, frame->coded_size())); | |
156 // Convert the VideoFrame pixels to ARGB. | |
157 libyuv::I420ToARGB(frame->data(media::VideoFrame::kYPlane), | |
158 frame->stride(media::VideoFrame::kYPlane), | |
159 frame->data(media::VideoFrame::kUPlane), | |
160 frame->stride(media::VideoFrame::kUPlane), | |
161 frame->data(media::VideoFrame::kVPlane), | |
162 frame->stride(media::VideoFrame::kVPlane), | |
163 &pending_frame->argb_pixels.front(), | |
164 frame->coded_size().width() * 4, | |
165 frame->coded_size().width(), | |
166 frame->coded_size().height()); | |
167 } else { | |
168 pending_frame.reset(new PendingFrame(decode_id)); | |
169 } | |
170 | |
171 main_message_loop_->PostTask( | |
172 FROM_HERE, | |
173 base::Bind(&VideoDecoderAdapter::OnDecodeComplete, | |
174 adapter_, | |
175 status, | |
176 base::Passed(&pending_frame))); | |
177 | |
178 if (!pending_decodes_.empty()) { | |
179 const PendingDecode& decode = pending_decodes_.front(); | |
180 Decode(decode.decode_id, decode.buffer); | |
181 pending_decodes_.pop(); | |
182 } | |
183 } | |
184 | |
185 void VideoDecoderAdapter::Delegate::OnResetComplete() { | |
186 main_message_loop_->PostTask( | |
187 FROM_HERE, base::Bind(&VideoDecoderAdapter::OnResetComplete, adapter_)); | |
188 } | |
189 | |
190 VideoDecoderAdapter::VideoDecoderAdapter(PepperVideoDecoderHost* host) | |
191 : state_(UNINITIALIZED), | |
192 host_(host), | |
193 media_message_loop_( | |
194 RenderThreadImpl::current()->GetMediaThreadMessageLoopProxy()), | |
195 context_provider_( | |
196 RenderThreadImpl::current()->SharedMainThreadContextProvider()), | |
197 num_pending_decodes_(0), | |
198 weak_ptr_factory_(this) { | |
199 DCHECK(host_); | |
200 DCHECK(media_message_loop_); | |
201 DCHECK(context_provider_); | |
202 delegate_.reset(new Delegate(weak_ptr_factory_.GetWeakPtr())); | |
203 } | |
204 | |
205 VideoDecoderAdapter::~VideoDecoderAdapter() { | |
206 DCHECK(RenderThreadImpl::current()); | |
207 DCHECK(!host_); | |
208 // Delete any remaining textures. | |
209 TextureIdMap::iterator it = texture_id_map_.begin(); | |
210 for (; it != texture_id_map_.end(); ++it) | |
211 DeleteTexture(it->second); | |
Ami GONE FROM CHROMIUM
2014/06/06 17:14:33
Yeah, I realize; still that's what I'd do.
(SEGV's
bbudge
2014/06/07 00:31:09
Done.
| |
212 | |
213 FlushCommandBuffer(); | |
214 } | |
215 | |
216 void VideoDecoderAdapter::Initialize(media::VideoCodecProfile profile) { | |
217 DCHECK(RenderThreadImpl::current()); | |
218 DCHECK_EQ(state_, UNINITIALIZED); | |
219 media::VideoCodec codec = media::kUnknownVideoCodec; | |
220 if (profile <= media::H264PROFILE_MAX) | |
221 codec = media::kCodecH264; | |
222 else if (profile <= media::VP8PROFILE_MAX) | |
223 codec = media::kCodecVP8; | |
224 DCHECK_NE(codec, media::kUnknownVideoCodec); | |
225 | |
226 media::VideoDecoderConfig config( | |
227 codec, | |
228 profile, | |
229 media::VideoFrame::YV12, | |
230 gfx::Size(32, 24), // Small sizes that won't fail. | |
231 gfx::Rect(32, 24), | |
232 gfx::Size(32, 24), | |
233 NULL /* extra_data */, | |
Ami GONE FROM CHROMIUM
2014/06/06 17:14:33
Please add a TODO.
bbudge
2014/06/07 00:31:09
Done.
| |
234 0 /* extra_data_size */, | |
235 false /* decryption */); | |
236 | |
237 media_message_loop_->PostTask( | |
238 FROM_HERE, | |
239 base::Bind(&VideoDecoderAdapter::Delegate::Initialize, | |
240 base::Unretained(delegate_.get()), | |
241 config)); | |
242 } | |
243 | |
244 void VideoDecoderAdapter::Decode(uint32_t decode_id, | |
245 const uint8_t* buffer, | |
246 uint32_t size) { | |
247 DCHECK(RenderThreadImpl::current()); | |
248 DCHECK_EQ(state_, DECODING); | |
249 | |
250 num_pending_decodes_++; | |
251 | |
252 media_message_loop_->PostTask( | |
253 FROM_HERE, | |
254 base::Bind(&VideoDecoderAdapter::Delegate::Decode, | |
255 base::Unretained(delegate_.get()), | |
256 decode_id, | |
257 media::DecoderBuffer::CopyFrom(buffer, size))); | |
258 } | |
259 | |
260 void VideoDecoderAdapter::AssignTextures( | |
261 const std::vector<uint32_t>& texture_ids) { | |
262 DCHECK(RenderThreadImpl::current()); | |
263 DCHECK_EQ(state_, DECODING); | |
264 DCHECK(texture_ids.size()); | |
Ami GONE FROM CHROMIUM
2014/06/06 17:14:33
Wouldn't such a texture be in textures_to_dismiss_
dmichael (off chromium)
2014/06/06 17:24:00
I think you should maybe reformat this to somethin
bbudge
2014/06/07 00:31:09
Done.
bbudge
2014/06/07 00:31:09
It would be in both. textures_to_dismiss_ may cont
| |
265 DCHECK_EQ(texture_ids.size(), pending_texture_mailboxes_.size()); | |
266 GLuint num_textures = base::checked_cast<GLuint>(texture_ids.size()); | |
267 std::vector<uint32_t> local_texture_ids(num_textures); | |
268 gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL(); | |
269 gles2->GenTextures(num_textures, &local_texture_ids.front()); | |
270 for (uint32_t i = 0; i < num_textures; i++) { | |
271 gles2->ActiveTexture(GL_TEXTURE0); | |
272 gles2->BindTexture(GL_TEXTURE_2D, local_texture_ids[i]); | |
273 gles2->ConsumeTextureCHROMIUM(GL_TEXTURE_2D, | |
274 pending_texture_mailboxes_[i].name); | |
275 // Map the plugin texture id to the local texture id. | |
276 texture_id_map_[texture_ids[i]] = local_texture_ids[i]; | |
277 } | |
278 pending_texture_mailboxes_.clear(); | |
279 available_textures_.insert( | |
280 available_textures_.end(), texture_ids.begin(), texture_ids.end()); | |
281 SendPictures(); | |
282 } | |
283 | |
284 void VideoDecoderAdapter::RecycleTexture(uint32_t texture_id) { | |
285 DCHECK(RenderThreadImpl::current()); | |
286 if (textures_to_dismiss_.find(texture_id) != textures_to_dismiss_.end()) { | |
287 DismissTexture(texture_id); | |
288 } else if (texture_id_map_.find(texture_id) != texture_id_map_.end()) { | |
289 available_textures_.push_back(texture_id); | |
290 SendPictures(); | |
291 } else { | |
292 NOTREACHED(); | |
293 } | |
294 } | |
295 | |
296 void VideoDecoderAdapter::Flush() { | |
297 DCHECK(RenderThreadImpl::current()); | |
298 DCHECK_EQ(state_, DECODING); | |
299 state_ = FLUSHING; | |
300 } | |
301 | |
302 void VideoDecoderAdapter::Reset() { | |
303 DCHECK(RenderThreadImpl::current()); | |
304 DCHECK_EQ(state_, DECODING); | |
305 state_ = RESETTING; | |
306 media_message_loop_->PostTask( | |
307 FROM_HERE, | |
308 base::Bind(&VideoDecoderAdapter::Delegate::Reset, | |
309 base::Unretained(delegate_.get()))); | |
310 } | |
311 | |
312 void VideoDecoderAdapter::Destroy() { | |
313 DCHECK(RenderThreadImpl::current()); | |
314 DCHECK(host_); | |
315 host_ = NULL; | |
316 state_ = DESTROYED; | |
317 // No methods can post tasks to the delegate now. | |
318 weak_ptr_factory_.InvalidateWeakPtrs(); | |
319 // No more callbacks from the delegate will be received now. | |
320 | |
321 // The callback now holds the only reference to the delegate, which will be | |
322 // deleted when Stop completes. | |
323 media_message_loop_->PostTask(FROM_HERE, | |
324 base::Bind(&VideoDecoderAdapter::Delegate::Stop, | |
325 base::Owned(delegate_.release()))); | |
dmichael (off chromium)
2014/06/06 17:24:00
You convinced me that VideoDecoderAdapter::Delegat
bbudge
2014/06/07 00:31:09
We're posting a task to the media loop to Stop the
bbudge
2014/06/07 01:01:23
Ugh, there you go being right again. Yeah, since I
| |
326 | |
327 delete this; | |
328 } | |
329 | |
330 void VideoDecoderAdapter::OnPipelineStatus(media::PipelineStatus status) { | |
331 DCHECK(RenderThreadImpl::current()); | |
332 DCHECK(host_); | |
333 | |
334 int32_t result; | |
335 switch (status) { | |
336 case media::PIPELINE_OK: | |
337 result = PP_OK; | |
338 state_ = DECODING; | |
339 break; | |
340 case media::DECODER_ERROR_NOT_SUPPORTED: | |
341 result = PP_ERROR_NOTSUPPORTED; | |
342 break; | |
343 default: | |
344 result = PP_ERROR_FAILED; | |
345 break; | |
346 } | |
347 host_->OnInitializeComplete(result); | |
348 } | |
349 | |
350 void VideoDecoderAdapter::OnDecodeComplete(media::VideoDecoder::Status status, | |
351 scoped_ptr<PendingFrame> frame) { | |
352 DCHECK(RenderThreadImpl::current()); | |
353 DCHECK(host_); | |
354 | |
355 num_pending_decodes_--; | |
356 | |
357 if (!frame->argb_pixels.empty()) { | |
358 if (texture_size_ != frame->size) { | |
359 // If the size has changed, all current textures must be dismissed. Add | |
360 // all textures to |textures_to_dismiss_| and dismiss any that aren't in | |
361 // use by the plugin. We dismiss the rest as they are recycled. | |
362 for (TextureIdMap::const_iterator it = texture_id_map_.begin(); | |
363 it != texture_id_map_.end(); | |
364 ++it) { | |
365 textures_to_dismiss_.insert(it->second); | |
366 } | |
367 for (std::vector<uint32_t>::const_iterator it = | |
368 available_textures_.begin(); | |
369 it != available_textures_.end(); | |
370 ++it) { | |
371 DismissTexture(*it); | |
372 } | |
373 available_textures_.clear(); | |
374 FlushCommandBuffer(); | |
375 | |
376 DCHECK(pending_texture_mailboxes_.empty()); | |
377 const uint32_t num_textures = 8; | |
Ami GONE FROM CHROMIUM
2014/06/06 17:14:33
In order to saturate both the decoder and the rend
bbudge
2014/06/07 00:31:09
Done.
| |
378 for (uint32_t i = 0; i < num_textures; i++) | |
379 pending_texture_mailboxes_.push_back(gpu::Mailbox::Generate()); | |
380 | |
381 host_->RequestTextures( | |
382 num_textures, frame->size, GL_TEXTURE_2D, pending_texture_mailboxes_); | |
383 texture_size_ = frame->size; | |
384 } | |
385 | |
386 pending_frames_.push(linked_ptr<PendingFrame>(frame.release())); | |
387 SendPictures(); | |
388 } else { | |
389 host_->NotifyEndOfBitstreamBuffer(frame->decode_id); | |
390 } | |
391 | |
392 switch (status) { | |
393 case media::VideoDecoder::kOk: | |
394 case media::VideoDecoder::kAborted: | |
395 // This is not necessarily an error. | |
396 case media::VideoDecoder::kNotEnoughData: | |
397 break; | |
398 case media::VideoDecoder::kDecodeError: | |
399 host_->NotifyError(PP_ERROR_RESOURCE_FAILED); | |
400 break; | |
401 case media::VideoDecoder::kDecryptError: | |
402 NOTREACHED(); | |
403 break; | |
404 // No default case, to catch unhandled status values. | |
405 } | |
406 } | |
407 | |
408 void VideoDecoderAdapter::SendPictures() { | |
409 DCHECK(RenderThreadImpl::current()); | |
410 DCHECK(host_); | |
411 | |
412 while (!pending_frames_.empty() && !available_textures_.empty()) { | |
413 const linked_ptr<PendingFrame>& frame = pending_frames_.front(); | |
414 | |
415 uint32_t texture_id = available_textures_.back(); | |
416 available_textures_.pop_back(); | |
417 | |
418 uint32_t local_texture_id = texture_id_map_[texture_id]; | |
419 gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL(); | |
420 gles2->ActiveTexture(GL_TEXTURE0); | |
421 gles2->BindTexture(GL_TEXTURE_2D, local_texture_id); | |
422 gles2->TexImage2D(GL_TEXTURE_2D, | |
423 0, | |
424 GL_RGBA, | |
425 texture_size_.width(), | |
426 texture_size_.height(), | |
427 0, | |
428 GL_RGBA, | |
429 GL_UNSIGNED_BYTE, | |
430 &frame->argb_pixels.front()); | |
431 | |
432 host_->NotifyEndOfBitstreamBuffer(frame->decode_id); | |
433 host_->PictureReady(media::Picture(texture_id, frame->decode_id)); | |
434 pending_frames_.pop(); | |
435 } | |
436 | |
437 FlushCommandBuffer(); | |
438 | |
439 if (state_ == FLUSHING && !num_pending_decodes_ && pending_frames_.empty()) { | |
440 state_ = DECODING; | |
441 host_->NotifyFlushDone(); | |
442 } | |
443 } | |
444 | |
445 void VideoDecoderAdapter::OnResetComplete() { | |
446 DCHECK(RenderThreadImpl::current()); | |
447 DCHECK(host_); | |
448 | |
449 while (!pending_frames_.empty()) { | |
450 const linked_ptr<PendingFrame>& frame = pending_frames_.front(); | |
451 host_->NotifyEndOfBitstreamBuffer(frame->decode_id); | |
452 pending_frames_.pop(); | |
453 } | |
454 | |
455 state_ = DECODING; | |
456 host_->NotifyResetDone(); | |
457 } | |
458 | |
459 void VideoDecoderAdapter::DismissTexture(uint32_t texture_id) { | |
460 DCHECK(host_); | |
461 textures_to_dismiss_.erase(texture_id); | |
462 DCHECK(texture_id_map_.find(texture_id) != texture_id_map_.end()); | |
463 DeleteTexture(texture_id_map_[texture_id]); | |
464 texture_id_map_.erase(texture_id); | |
465 host_->DismissPictureBuffer(texture_id); | |
466 } | |
467 | |
468 void VideoDecoderAdapter::DeleteTexture(uint32_t texture_id) { | |
469 gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL(); | |
470 gles2->DeleteTextures(1, &texture_id); | |
471 } | |
472 | |
473 void VideoDecoderAdapter::FlushCommandBuffer() { | |
474 context_provider_->ContextGL()->Flush(); | |
475 } | |
476 | |
477 } // namespace content | |
OLD | NEW |