Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(36)

Side by Side Diff: media/filters/decoder_stream.cc

Issue 239893002: Allow multiple concurrent Decode() requests in VideoDecoder interface. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 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/decoder_stream.h" 5 #include "media/filters/decoder_stream.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/callback_helpers.h" 8 #include "base/callback_helpers.h"
9 #include "base/debug/trace_event.h" 9 #include "base/debug/trace_event.h"
10 #include "base/location.h" 10 #include "base/location.h"
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
43 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, 43 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
44 ScopedVector<Decoder> decoders, 44 ScopedVector<Decoder> decoders,
45 const SetDecryptorReadyCB& set_decryptor_ready_cb) 45 const SetDecryptorReadyCB& set_decryptor_ready_cb)
46 : task_runner_(task_runner), 46 : task_runner_(task_runner),
47 state_(STATE_UNINITIALIZED), 47 state_(STATE_UNINITIALIZED),
48 stream_(NULL), 48 stream_(NULL),
49 decoder_selector_( 49 decoder_selector_(
50 new DecoderSelector<StreamType>(task_runner, 50 new DecoderSelector<StreamType>(task_runner,
51 decoders.Pass(), 51 decoders.Pass(),
52 set_decryptor_ready_cb)), 52 set_decryptor_ready_cb)),
53 weak_factory_(this) {} 53 pending_decode_requests_(0),
54 weak_factory_(this) {
55 }
54 56
55 template <DemuxerStream::Type StreamType> 57 template <DemuxerStream::Type StreamType>
56 DecoderStream<StreamType>::~DecoderStream() { 58 DecoderStream<StreamType>::~DecoderStream() {
57 DCHECK(state_ == STATE_UNINITIALIZED || state_ == STATE_STOPPED) << state_; 59 DCHECK(state_ == STATE_UNINITIALIZED || state_ == STATE_STOPPED) << state_;
58 } 60 }
59 61
60 template <DemuxerStream::Type StreamType> 62 template <DemuxerStream::Type StreamType>
61 void DecoderStream<StreamType>::Initialize(DemuxerStream* stream, 63 void DecoderStream<StreamType>::Initialize(DemuxerStream* stream,
62 const StatisticsCB& statistics_cb, 64 const StatisticsCB& statistics_cb,
63 const InitCB& init_cb) { 65 const InitCB& init_cb) {
(...skipping 13 matching lines...) Expand all
77 stream, 79 stream,
78 base::Bind(&DecoderStream<StreamType>::OnDecoderSelected, 80 base::Bind(&DecoderStream<StreamType>::OnDecoderSelected,
79 weak_factory_.GetWeakPtr())); 81 weak_factory_.GetWeakPtr()));
80 } 82 }
81 83
82 template <DemuxerStream::Type StreamType> 84 template <DemuxerStream::Type StreamType>
83 void DecoderStream<StreamType>::Read(const ReadCB& read_cb) { 85 void DecoderStream<StreamType>::Read(const ReadCB& read_cb) {
84 FUNCTION_DVLOG(2); 86 FUNCTION_DVLOG(2);
85 DCHECK(task_runner_->BelongsToCurrentThread()); 87 DCHECK(task_runner_->BelongsToCurrentThread());
86 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || 88 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER ||
87 state_ == STATE_ERROR) << state_; 89 state_ == STATE_ERROR || state_ == STATE_REINITIALIZING_DECODER)
xhwang 2014/04/17 01:06:47 |state_| can also be STATE_PENDING_DEMUXER_READ. I
Sergey Ulanov 2014/04/23 02:44:21 Done. Also added a unittest that coverts this case
90 << state_;
88 // No two reads in the flight at any time. 91 // No two reads in the flight at any time.
89 DCHECK(read_cb_.is_null()); 92 DCHECK(read_cb_.is_null());
90 // No read during resetting or stopping process. 93 // No read during resetting or stopping process.
91 DCHECK(reset_cb_.is_null()); 94 DCHECK(reset_cb_.is_null());
92 DCHECK(stop_cb_.is_null()); 95 DCHECK(stop_cb_.is_null());
93 96
94 if (state_ == STATE_ERROR) { 97 if (state_ == STATE_ERROR) {
95 task_runner_->PostTask(FROM_HERE, base::Bind( 98 task_runner_->PostTask(FROM_HERE, base::Bind(
96 read_cb, DECODE_ERROR, scoped_refptr<Output>())); 99 read_cb, DECODE_ERROR, scoped_refptr<Output>()));
97 return; 100 return;
98 } 101 }
99 102
100 read_cb_ = read_cb; 103 read_cb_ = read_cb;
101 104
105 if (!ready_output_buffers_.empty()) {
106 task_runner_->PostTask(FROM_HERE, base::Bind(
107 base::ResetAndReturn(&read_cb_), OK, ready_output_buffers_.front()));
108 ready_output_buffers_.pop_front();
109 }
110
111 if (state_ == STATE_REINITIALIZING_DECODER)
xhwang 2014/04/17 01:06:47 Might worth a comment how this could happen.
Sergey Ulanov 2014/04/23 02:44:21 Done.
112 return;
113
114 if (!CanDecodeAnotherBuffer())
115 return;
116
102 if (state_ == STATE_FLUSHING_DECODER) { 117 if (state_ == STATE_FLUSHING_DECODER) {
103 FlushDecoder(); 118 FlushDecoder();
104 return; 119 return;
105 } 120 }
106 121
107 scoped_refptr<Output> output = decoder_->GetDecodeOutput(); 122 if (state_ != STATE_PENDING_DEMUXER_READ)
108 123 ReadFromDemuxerStream();
109 // If the decoder has queued output ready to go we don't need a demuxer read.
110 if (output) {
111 task_runner_->PostTask(
112 FROM_HERE, base::Bind(base::ResetAndReturn(&read_cb_), OK, output));
113 return;
114 }
115
116 ReadFromDemuxerStream();
117 } 124 }
118 125
119 template <DemuxerStream::Type StreamType> 126 template <DemuxerStream::Type StreamType>
120 void DecoderStream<StreamType>::Reset(const base::Closure& closure) { 127 void DecoderStream<StreamType>::Reset(const base::Closure& closure) {
121 FUNCTION_DVLOG(2); 128 FUNCTION_DVLOG(2);
122 DCHECK(task_runner_->BelongsToCurrentThread()); 129 DCHECK(task_runner_->BelongsToCurrentThread());
123 DCHECK(state_ != STATE_UNINITIALIZED && state_ != STATE_STOPPED) << state_; 130 DCHECK(state_ != STATE_UNINITIALIZED && state_ != STATE_STOPPED) << state_;
124 DCHECK(reset_cb_.is_null()); 131 DCHECK(reset_cb_.is_null());
125 DCHECK(stop_cb_.is_null()); 132 DCHECK(stop_cb_.is_null());
126 133
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
203 return decoder_->CanReadWithoutStalling(); 210 return decoder_->CanReadWithoutStalling();
204 } 211 }
205 212
206 template <> 213 template <>
207 bool DecoderStream<DemuxerStream::AUDIO>::CanReadWithoutStalling() const { 214 bool DecoderStream<DemuxerStream::AUDIO>::CanReadWithoutStalling() const {
208 DCHECK(task_runner_->BelongsToCurrentThread()); 215 DCHECK(task_runner_->BelongsToCurrentThread());
209 return true; 216 return true;
210 } 217 }
211 218
212 template <DemuxerStream::Type StreamType> 219 template <DemuxerStream::Type StreamType>
220 bool DecoderStream<StreamType>::CanDecodeAnotherBuffer() const {
221 DCHECK(task_runner_->BelongsToCurrentThread());
222 int buffers_in_queue =
xhwang 2014/04/17 01:06:47 In media/ we use "buffer" for compressed data, e.g
Sergey Ulanov 2014/04/23 02:44:21 Renamed buffers_in_queue -> num_decodes ready_
223 static_cast<int>(ready_output_buffers_.size()) + pending_decode_requests_;
224 return buffers_in_queue < decoder_->GetMaxDecodeRequests();
xhwang 2014/04/17 01:06:47 Probably add a comment about why we choose to do t
Sergey Ulanov 2014/04/23 02:44:21 Done.
225 }
226
227 template <>
228 bool DecoderStream<DemuxerStream::AUDIO>::CanDecodeAnotherBuffer() const {
229 DCHECK(task_runner_->BelongsToCurrentThread());
230 return !pending_decode_requests_ && ready_output_buffers_.empty();
231 }
232
233 template <DemuxerStream::Type StreamType>
213 void DecoderStream<StreamType>::OnDecoderSelected( 234 void DecoderStream<StreamType>::OnDecoderSelected(
214 scoped_ptr<Decoder> selected_decoder, 235 scoped_ptr<Decoder> selected_decoder,
215 scoped_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream) { 236 scoped_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream) {
216 FUNCTION_DVLOG(2); 237 FUNCTION_DVLOG(2);
217 DCHECK(task_runner_->BelongsToCurrentThread()); 238 DCHECK(task_runner_->BelongsToCurrentThread());
218 DCHECK_EQ(state_, STATE_INITIALIZING) << state_; 239 DCHECK_EQ(state_, STATE_INITIALIZING) << state_;
219 DCHECK(!init_cb_.is_null()); 240 DCHECK(!init_cb_.is_null());
220 DCHECK(read_cb_.is_null()); 241 DCHECK(read_cb_.is_null());
221 DCHECK(reset_cb_.is_null()); 242 DCHECK(reset_cb_.is_null());
222 243
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
260 // caller. 281 // caller.
261 DCHECK(!reset_cb_.is_null()); 282 DCHECK(!reset_cb_.is_null());
262 SatisfyRead(ABORTED, NULL); 283 SatisfyRead(ABORTED, NULL);
263 } 284 }
264 285
265 template <DemuxerStream::Type StreamType> 286 template <DemuxerStream::Type StreamType>
266 void DecoderStream<StreamType>::Decode( 287 void DecoderStream<StreamType>::Decode(
267 const scoped_refptr<DecoderBuffer>& buffer) { 288 const scoped_refptr<DecoderBuffer>& buffer) {
268 FUNCTION_DVLOG(2); 289 FUNCTION_DVLOG(2);
269 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER) << state_; 290 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER) << state_;
270 DCHECK(!read_cb_.is_null()); 291 DCHECK(CanDecodeAnotherBuffer());
271 DCHECK(reset_cb_.is_null()); 292 DCHECK(reset_cb_.is_null());
272 DCHECK(stop_cb_.is_null()); 293 DCHECK(stop_cb_.is_null());
273 DCHECK(buffer); 294 DCHECK(buffer);
274 295
275 int buffer_size = buffer->end_of_stream() ? 0 : buffer->data_size(); 296 int buffer_size = buffer->end_of_stream() ? 0 : buffer->data_size();
276 297
277 TRACE_EVENT_ASYNC_BEGIN0("media", GetTraceString<StreamType>(), this); 298 TRACE_EVENT_ASYNC_BEGIN0("media", GetTraceString<StreamType>(), this);
299 ++pending_decode_requests_;
278 decoder_->Decode(buffer, 300 decoder_->Decode(buffer,
279 base::Bind(&DecoderStream<StreamType>::OnDecodeOutputReady, 301 base::Bind(&DecoderStream<StreamType>::OnDecodeOutputReady,
280 weak_factory_.GetWeakPtr(), 302 weak_factory_.GetWeakPtr(),
281 buffer_size)); 303 buffer_size));
282 } 304 }
283 305
284 template <DemuxerStream::Type StreamType> 306 template <DemuxerStream::Type StreamType>
285 void DecoderStream<StreamType>::FlushDecoder() { 307 void DecoderStream<StreamType>::FlushDecoder() {
286 Decode(DecoderBuffer::CreateEOSBuffer()); 308 Decode(DecoderBuffer::CreateEOSBuffer());
287 } 309 }
288 310
289 template <DemuxerStream::Type StreamType> 311 template <DemuxerStream::Type StreamType>
290 void DecoderStream<StreamType>::OnDecodeOutputReady( 312 void DecoderStream<StreamType>::OnDecodeOutputReady(
291 int buffer_size, 313 int buffer_size,
292 typename Decoder::Status status, 314 typename Decoder::Status status,
293 const scoped_refptr<Output>& output) { 315 const scoped_refptr<Output>& output) {
294 FUNCTION_DVLOG(2); 316 FUNCTION_DVLOG(2);
295 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER) << state_; 317 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER) << state_;
296 DCHECK(!read_cb_.is_null());
297 DCHECK(stop_cb_.is_null()); 318 DCHECK(stop_cb_.is_null());
298 DCHECK_EQ(status == Decoder::kOk, output != NULL); 319 DCHECK_EQ(status == Decoder::kOk, output != NULL);
320 DCHECK_GT(pending_decode_requests_, 0);
321
322 --pending_decode_requests_;
299 323
300 TRACE_EVENT_ASYNC_END0("media", GetTraceString<StreamType>(), this); 324 TRACE_EVENT_ASYNC_END0("media", GetTraceString<StreamType>(), this);
301 325
302 if (status == Decoder::kDecodeError) { 326 if (status == Decoder::kDecodeError) {
303 state_ = STATE_ERROR; 327 state_ = STATE_ERROR;
304 SatisfyRead(DECODE_ERROR, NULL); 328 SatisfyRead(DECODE_ERROR, NULL);
305 return; 329 return;
306 } 330 }
xhwang 2014/04/17 01:06:47 Now we could have multiple Decode() calls in fligh
Sergey Ulanov 2014/04/23 02:44:21 Done. Also added unittests for this case.
307 331
308 if (status == Decoder::kDecryptError) { 332 if (status == Decoder::kDecryptError) {
309 state_ = STATE_ERROR; 333 state_ = STATE_ERROR;
310 SatisfyRead(DECRYPT_ERROR, NULL); 334 SatisfyRead(DECRYPT_ERROR, NULL);
311 return; 335 return;
312 } 336 }
313 337
314 if (status == Decoder::kAborted) { 338 if (status == Decoder::kAborted) {
315 SatisfyRead(ABORTED, NULL); 339 SatisfyRead(ABORTED, NULL);
316 return; 340 return;
317 } 341 }
318 342
319 // Any successful decode counts! 343 // Any successful decode counts!
320 if (buffer_size > 0) { 344 if (buffer_size > 0) {
321 StreamTraits::ReportStatistics(statistics_cb_, buffer_size); 345 StreamTraits::ReportStatistics(statistics_cb_, buffer_size);
322 } 346 }
323 347
324 // Drop decoding result if Reset() was called during decoding. 348 // Drop decoding result if Reset() was called during decoding.
325 // The resetting process will be handled when the decoder is reset. 349 // The resetting process will be handled when the decoder is reset.
326 if (!reset_cb_.is_null()) { 350 if (!reset_cb_.is_null()) {
327 AbortRead(); 351 AbortRead();
xhwang 2014/04/17 01:06:47 ditto. We only need to AbortRead() once.
Sergey Ulanov 2014/04/23 02:44:21 Removed AbortRead(). Reads are aborted from Reset(
328 return; 352 return;
329 } 353 }
330 354
331 // Decoder flushed. Reinitialize the decoder. 355 // Decoder flushed. Reinitialize the decoder.
332 if (state_ == STATE_FLUSHING_DECODER && 356 if (state_ == STATE_FLUSHING_DECODER &&
333 status == Decoder::kOk && output->end_of_stream()) { 357 status == Decoder::kOk && output->end_of_stream()) {
334 ReinitializeDecoder(); 358 ReinitializeDecoder();
335 return; 359 return;
336 } 360 }
337 361
338 if (status == Decoder::kNotEnoughData) { 362 if (status == Decoder::kNotEnoughData) {
339 if (state_ == STATE_NORMAL) 363 if (state_ == STATE_NORMAL)
340 ReadFromDemuxerStream(); 364 ReadFromDemuxerStream();
341 else if (state_ == STATE_FLUSHING_DECODER) 365 else if (state_ == STATE_FLUSHING_DECODER)
342 FlushDecoder(); 366 FlushDecoder();
343 return; 367 return;
344 } 368 }
345 369
346 DCHECK(output); 370 DCHECK(output);
347 SatisfyRead(OK, output); 371
372 // Store decoded output.
373 ready_output_buffers_.push_back(output);
374 scoped_refptr<Output> extra_output;
375 while ((extra_output = decoder_->GetDecodeOutput()) != NULL) {
xhwang 2014/04/17 01:06:47 Now GetDecodeOutput() will always return NULL for
Sergey Ulanov 2014/04/23 02:44:21 Added TODO in video_decoder.h.
376 ready_output_buffers_.push_back(extra_output);
377 }
378
379 // Satisfy outstanding read request, if any.
380 if (!read_cb_.is_null()) {
381 scoped_refptr<Output> read_result = ready_output_buffers_.front();
382 ready_output_buffers_.pop_front();
383 SatisfyRead(OK, output);
384 }
348 } 385 }
349 386
350 template <DemuxerStream::Type StreamType> 387 template <DemuxerStream::Type StreamType>
351 void DecoderStream<StreamType>::ReadFromDemuxerStream() { 388 void DecoderStream<StreamType>::ReadFromDemuxerStream() {
352 FUNCTION_DVLOG(2); 389 FUNCTION_DVLOG(2);
353 DCHECK_EQ(state_, STATE_NORMAL) << state_; 390 DCHECK_EQ(state_, STATE_NORMAL) << state_;
354 DCHECK(!read_cb_.is_null()); 391 DCHECK(CanDecodeAnotherBuffer());
355 DCHECK(reset_cb_.is_null()); 392 DCHECK(reset_cb_.is_null());
356 DCHECK(stop_cb_.is_null()); 393 DCHECK(stop_cb_.is_null());
357 394
358 state_ = STATE_PENDING_DEMUXER_READ; 395 state_ = STATE_PENDING_DEMUXER_READ;
359 stream_->Read(base::Bind(&DecoderStream<StreamType>::OnBufferReady, 396 stream_->Read(base::Bind(&DecoderStream<StreamType>::OnBufferReady,
360 weak_factory_.GetWeakPtr())); 397 weak_factory_.GetWeakPtr()));
361 } 398 }
362 399
363 template <DemuxerStream::Type StreamType> 400 template <DemuxerStream::Type StreamType>
364 void DecoderStream<StreamType>::OnBufferReady( 401 void DecoderStream<StreamType>::OnBufferReady(
365 DemuxerStream::Status status, 402 DemuxerStream::Status status,
366 const scoped_refptr<DecoderBuffer>& buffer) { 403 const scoped_refptr<DecoderBuffer>& buffer) {
367 FUNCTION_DVLOG(2) << ": " << status; 404 FUNCTION_DVLOG(2) << ": " << status;
368 DCHECK(task_runner_->BelongsToCurrentThread()); 405 DCHECK(task_runner_->BelongsToCurrentThread());
369 DCHECK_EQ(state_, STATE_PENDING_DEMUXER_READ) << state_; 406 DCHECK_EQ(state_, STATE_PENDING_DEMUXER_READ) << state_;
370 DCHECK_EQ(buffer.get() != NULL, status == DemuxerStream::kOk) << status; 407 DCHECK_EQ(buffer.get() != NULL, status == DemuxerStream::kOk) << status;
371 DCHECK(!read_cb_.is_null());
372 DCHECK(stop_cb_.is_null()); 408 DCHECK(stop_cb_.is_null());
373 409
374 state_ = STATE_NORMAL; 410 state_ = STATE_NORMAL;
375 411
376 if (status == DemuxerStream::kConfigChanged) { 412 if (status == DemuxerStream::kConfigChanged) {
377 FUNCTION_DVLOG(2) << ": " << "ConfigChanged"; 413 FUNCTION_DVLOG(2) << ": " << "ConfigChanged";
378 DCHECK(stream_->SupportsConfigChanges()); 414 DCHECK(stream_->SupportsConfigChanges());
379 415
380 if (!config_change_observer_cb_.is_null()) 416 if (!config_change_observer_cb_.is_null())
381 config_change_observer_cb_.Run(); 417 config_change_observer_cb_.Run();
(...skipping 26 matching lines...) Expand all
408 return; 444 return;
409 } 445 }
410 446
411 if (!splice_observer_cb_.is_null() && !buffer->end_of_stream() && 447 if (!splice_observer_cb_.is_null() && !buffer->end_of_stream() &&
412 buffer->splice_timestamp() != kNoTimestamp()) { 448 buffer->splice_timestamp() != kNoTimestamp()) {
413 splice_observer_cb_.Run(buffer->splice_timestamp()); 449 splice_observer_cb_.Run(buffer->splice_timestamp());
414 } 450 }
415 451
416 DCHECK(status == DemuxerStream::kOk) << status; 452 DCHECK(status == DemuxerStream::kOk) << status;
417 Decode(buffer); 453 Decode(buffer);
454
455 // Read more data if the decoder supports multiple parallel decoding requests.
456 if (state_ == STATE_NORMAL && CanDecodeAnotherBuffer()) {
xhwang 2014/04/17 01:06:47 We set state_ to be STATE_NORMAL on line 410, so h
Sergey Ulanov 2014/04/23 02:44:21 Done.
457 ReadFromDemuxerStream();
458 }
418 } 459 }
419 460
420 template <DemuxerStream::Type StreamType> 461 template <DemuxerStream::Type StreamType>
421 void DecoderStream<StreamType>::ReinitializeDecoder() { 462 void DecoderStream<StreamType>::ReinitializeDecoder() {
422 FUNCTION_DVLOG(2); 463 FUNCTION_DVLOG(2);
423 DCHECK(task_runner_->BelongsToCurrentThread()); 464 DCHECK(task_runner_->BelongsToCurrentThread());
424 DCHECK_EQ(state_, STATE_FLUSHING_DECODER) << state_; 465 DCHECK_EQ(state_, STATE_FLUSHING_DECODER) << state_;
466 DCHECK_EQ(pending_decode_requests_, 0);
425 467
426 DCHECK(StreamTraits::GetDecoderConfig(*stream_).IsValidConfig()); 468 DCHECK(StreamTraits::GetDecoderConfig(*stream_).IsValidConfig());
427 state_ = STATE_REINITIALIZING_DECODER; 469 state_ = STATE_REINITIALIZING_DECODER;
428 decoder_->Initialize( 470 decoder_->Initialize(
429 StreamTraits::GetDecoderConfig(*stream_), 471 StreamTraits::GetDecoderConfig(*stream_),
430 base::Bind(&DecoderStream<StreamType>::OnDecoderReinitialized, 472 base::Bind(&DecoderStream<StreamType>::OnDecoderReinitialized,
431 weak_factory_.GetWeakPtr())); 473 weak_factory_.GetWeakPtr()));
432 } 474 }
433 475
434 template <DemuxerStream::Type StreamType> 476 template <DemuxerStream::Type StreamType>
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
510 decrypting_demuxer_stream_.reset(); 552 decrypting_demuxer_stream_.reset();
511 // Post |stop_cb_| because pending |read_cb_| and/or |reset_cb_| are also 553 // Post |stop_cb_| because pending |read_cb_| and/or |reset_cb_| are also
512 // posted in Stop(). 554 // posted in Stop().
513 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); 555 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_));
514 } 556 }
515 557
516 template class DecoderStream<DemuxerStream::VIDEO>; 558 template class DecoderStream<DemuxerStream::VIDEO>;
517 template class DecoderStream<DemuxerStream::AUDIO>; 559 template class DecoderStream<DemuxerStream::AUDIO>;
518 560
519 } // namespace media 561 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698