OLD | NEW |
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 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
73 statistics_cb_ = statistics_cb; | 73 statistics_cb_ = statistics_cb; |
74 init_cb_ = init_cb; | 74 init_cb_ = init_cb; |
75 stream_ = stream; | 75 stream_ = stream; |
76 low_delay_ = low_delay; | 76 low_delay_ = low_delay; |
77 | 77 |
78 state_ = STATE_INITIALIZING; | 78 state_ = STATE_INITIALIZING; |
79 // TODO(xhwang): DecoderSelector only needs a config to select a decoder. | 79 // TODO(xhwang): DecoderSelector only needs a config to select a decoder. |
80 decoder_selector_->SelectDecoder( | 80 decoder_selector_->SelectDecoder( |
81 stream, low_delay, | 81 stream, low_delay, |
82 base::Bind(&DecoderStream<StreamType>::OnDecoderSelected, | 82 base::Bind(&DecoderStream<StreamType>::OnDecoderSelected, |
| 83 weak_factory_.GetWeakPtr()), |
| 84 base::Bind(&DecoderStream<StreamType>::OnDecodeOutputReady, |
83 weak_factory_.GetWeakPtr())); | 85 weak_factory_.GetWeakPtr())); |
84 } | 86 } |
85 | 87 |
86 template <DemuxerStream::Type StreamType> | 88 template <DemuxerStream::Type StreamType> |
87 void DecoderStream<StreamType>::Read(const ReadCB& read_cb) { | 89 void DecoderStream<StreamType>::Read(const ReadCB& read_cb) { |
88 FUNCTION_DVLOG(2); | 90 FUNCTION_DVLOG(2); |
89 DCHECK(task_runner_->BelongsToCurrentThread()); | 91 DCHECK(task_runner_->BelongsToCurrentThread()); |
90 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || | 92 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || |
91 state_ == STATE_ERROR || state_ == STATE_REINITIALIZING_DECODER || | 93 state_ == STATE_ERROR || state_ == STATE_REINITIALIZING_DECODER || |
92 state_ == STATE_PENDING_DEMUXER_READ) | 94 state_ == STATE_PENDING_DEMUXER_READ) |
(...skipping 13 matching lines...) Expand all Loading... |
106 scoped_refptr<Output>())); | 108 scoped_refptr<Output>())); |
107 return; | 109 return; |
108 } | 110 } |
109 | 111 |
110 if (!ready_outputs_.empty()) { | 112 if (!ready_outputs_.empty()) { |
111 task_runner_->PostTask(FROM_HERE, base::Bind( | 113 task_runner_->PostTask(FROM_HERE, base::Bind( |
112 base::ResetAndReturn(&read_cb_), OK, ready_outputs_.front())); | 114 base::ResetAndReturn(&read_cb_), OK, ready_outputs_.front())); |
113 ready_outputs_.pop_front(); | 115 ready_outputs_.pop_front(); |
114 } | 116 } |
115 | 117 |
116 // Decoder may be in reinitializing state as result of the previous Read(). | 118 if (state_ == STATE_NORMAL && CanDecodeMore()) |
117 if (state_ == STATE_REINITIALIZING_DECODER) | |
118 return; | |
119 | |
120 if (!CanDecodeMore()) | |
121 return; | |
122 | |
123 if (state_ == STATE_FLUSHING_DECODER) { | |
124 FlushDecoder(); | |
125 return; | |
126 } | |
127 | |
128 if (state_ != STATE_PENDING_DEMUXER_READ) | |
129 ReadFromDemuxerStream(); | 119 ReadFromDemuxerStream(); |
130 } | 120 } |
131 | 121 |
132 template <DemuxerStream::Type StreamType> | 122 template <DemuxerStream::Type StreamType> |
133 void DecoderStream<StreamType>::Reset(const base::Closure& closure) { | 123 void DecoderStream<StreamType>::Reset(const base::Closure& closure) { |
134 FUNCTION_DVLOG(2); | 124 FUNCTION_DVLOG(2); |
135 DCHECK(task_runner_->BelongsToCurrentThread()); | 125 DCHECK(task_runner_->BelongsToCurrentThread()); |
136 DCHECK(state_ != STATE_UNINITIALIZED && state_ != STATE_STOPPED) << state_; | 126 DCHECK(state_ != STATE_UNINITIALIZED && state_ != STATE_STOPPED) << state_; |
137 DCHECK(reset_cb_.is_null()); | 127 DCHECK(reset_cb_.is_null()); |
138 DCHECK(stop_cb_.is_null()); | 128 DCHECK(stop_cb_.is_null()); |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
209 state_ = STATE_STOPPED; | 199 state_ = STATE_STOPPED; |
210 stream_ = NULL; | 200 stream_ = NULL; |
211 decoder_.reset(); | 201 decoder_.reset(); |
212 decrypting_demuxer_stream_.reset(); | 202 decrypting_demuxer_stream_.reset(); |
213 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); | 203 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); |
214 } | 204 } |
215 | 205 |
216 template <DemuxerStream::Type StreamType> | 206 template <DemuxerStream::Type StreamType> |
217 bool DecoderStream<StreamType>::CanReadWithoutStalling() const { | 207 bool DecoderStream<StreamType>::CanReadWithoutStalling() const { |
218 DCHECK(task_runner_->BelongsToCurrentThread()); | 208 DCHECK(task_runner_->BelongsToCurrentThread()); |
219 return decoder_->CanReadWithoutStalling(); | 209 return !ready_outputs_.empty() || decoder_->CanReadWithoutStalling(); |
220 } | 210 } |
221 | 211 |
222 template <> | 212 template <> |
223 bool DecoderStream<DemuxerStream::AUDIO>::CanReadWithoutStalling() const { | 213 bool DecoderStream<DemuxerStream::AUDIO>::CanReadWithoutStalling() const { |
224 DCHECK(task_runner_->BelongsToCurrentThread()); | 214 DCHECK(task_runner_->BelongsToCurrentThread()); |
225 return true; | 215 return true; |
226 } | 216 } |
227 | 217 |
228 template <DemuxerStream::Type StreamType> | 218 template <DemuxerStream::Type StreamType> |
| 219 int DecoderStream<StreamType>::GetMaxDecodeRequests() const { |
| 220 return decoder_->GetMaxDecodeRequests(); |
| 221 } |
| 222 |
| 223 template <> |
| 224 int DecoderStream<DemuxerStream::AUDIO>::GetMaxDecodeRequests() const { |
| 225 return 1; |
| 226 } |
| 227 |
| 228 template <DemuxerStream::Type StreamType> |
229 bool DecoderStream<StreamType>::CanDecodeMore() const { | 229 bool DecoderStream<StreamType>::CanDecodeMore() const { |
230 DCHECK(task_runner_->BelongsToCurrentThread()); | 230 DCHECK(task_runner_->BelongsToCurrentThread()); |
231 | 231 |
232 // Limit total number of outputs stored in |ready_outputs_| and being decoded. | 232 // Limit total number of outputs stored in |ready_outputs_| and being decoded. |
233 // It only makes sense to saturate decoder completely when output queue is | 233 // It only makes sense to saturate decoder completely when output queue is |
234 // empty. | 234 // empty. |
235 int num_decodes = | 235 int num_decodes = |
236 static_cast<int>(ready_outputs_.size()) + pending_decode_requests_; | 236 static_cast<int>(ready_outputs_.size()) + pending_decode_requests_; |
237 return num_decodes < decoder_->GetMaxDecodeRequests(); | 237 return num_decodes < GetMaxDecodeRequests(); |
238 } | |
239 | |
240 template <> | |
241 bool DecoderStream<DemuxerStream::AUDIO>::CanDecodeMore() const { | |
242 DCHECK(task_runner_->BelongsToCurrentThread()); | |
243 return !pending_decode_requests_ && ready_outputs_.empty(); | |
244 } | 238 } |
245 | 239 |
246 template <DemuxerStream::Type StreamType> | 240 template <DemuxerStream::Type StreamType> |
247 void DecoderStream<StreamType>::OnDecoderSelected( | 241 void DecoderStream<StreamType>::OnDecoderSelected( |
248 scoped_ptr<Decoder> selected_decoder, | 242 scoped_ptr<Decoder> selected_decoder, |
249 scoped_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream) { | 243 scoped_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream) { |
250 FUNCTION_DVLOG(2); | 244 FUNCTION_DVLOG(2); |
251 DCHECK(task_runner_->BelongsToCurrentThread()); | 245 DCHECK(task_runner_->BelongsToCurrentThread()); |
252 DCHECK_EQ(state_, STATE_INITIALIZING) << state_; | 246 DCHECK_EQ(state_, STATE_INITIALIZING) << state_; |
253 DCHECK(!init_cb_.is_null()); | 247 DCHECK(!init_cb_.is_null()); |
(...skipping 29 matching lines...) Expand all Loading... |
283 const scoped_refptr<Output>& output) { | 277 const scoped_refptr<Output>& output) { |
284 DCHECK(!read_cb_.is_null()); | 278 DCHECK(!read_cb_.is_null()); |
285 base::ResetAndReturn(&read_cb_).Run(status, output); | 279 base::ResetAndReturn(&read_cb_).Run(status, output); |
286 } | 280 } |
287 | 281 |
288 template <DemuxerStream::Type StreamType> | 282 template <DemuxerStream::Type StreamType> |
289 void DecoderStream<StreamType>::Decode( | 283 void DecoderStream<StreamType>::Decode( |
290 const scoped_refptr<DecoderBuffer>& buffer) { | 284 const scoped_refptr<DecoderBuffer>& buffer) { |
291 FUNCTION_DVLOG(2); | 285 FUNCTION_DVLOG(2); |
292 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER) << state_; | 286 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER) << state_; |
293 DCHECK(CanDecodeMore()); | 287 DCHECK_LT(pending_decode_requests_, GetMaxDecodeRequests()); |
294 DCHECK(reset_cb_.is_null()); | 288 DCHECK(reset_cb_.is_null()); |
295 DCHECK(stop_cb_.is_null()); | 289 DCHECK(stop_cb_.is_null()); |
296 DCHECK(buffer); | 290 DCHECK(buffer); |
297 | 291 |
298 int buffer_size = buffer->end_of_stream() ? 0 : buffer->data_size(); | 292 int buffer_size = buffer->end_of_stream() ? 0 : buffer->data_size(); |
299 | 293 |
300 TRACE_EVENT_ASYNC_BEGIN0("media", GetTraceString<StreamType>(), this); | 294 TRACE_EVENT_ASYNC_BEGIN0("media", GetTraceString<StreamType>(), this); |
301 ++pending_decode_requests_; | 295 ++pending_decode_requests_; |
302 decoder_->Decode(buffer, | 296 decoder_->Decode(buffer, |
303 base::Bind(&DecoderStream<StreamType>::OnDecodeOutputReady, | 297 base::Bind(&DecoderStream<StreamType>::OnDecodeDone, |
304 weak_factory_.GetWeakPtr(), | 298 weak_factory_.GetWeakPtr(), |
305 buffer_size)); | 299 buffer_size, |
| 300 buffer->end_of_stream())); |
306 } | 301 } |
307 | 302 |
308 template <DemuxerStream::Type StreamType> | 303 template <DemuxerStream::Type StreamType> |
309 void DecoderStream<StreamType>::FlushDecoder() { | 304 void DecoderStream<StreamType>::FlushDecoder() { |
310 if (pending_decode_requests_ == 0) | 305 Decode(DecoderBuffer::CreateEOSBuffer()); |
311 Decode(DecoderBuffer::CreateEOSBuffer()); | |
312 } | 306 } |
313 | 307 |
314 template <DemuxerStream::Type StreamType> | 308 template <DemuxerStream::Type StreamType> |
315 void DecoderStream<StreamType>::OnDecodeOutputReady( | 309 void DecoderStream<StreamType>::OnDecodeDone(int buffer_size, |
316 int buffer_size, | 310 bool end_of_stream, |
317 typename Decoder::Status status, | 311 typename Decoder::Status status) { |
318 const scoped_refptr<Output>& output) { | 312 FUNCTION_DVLOG(2) << status; |
319 FUNCTION_DVLOG(2) << status << " " << output; | |
320 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || | 313 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || |
321 state_ == STATE_PENDING_DEMUXER_READ || state_ == STATE_ERROR) | 314 state_ == STATE_PENDING_DEMUXER_READ || state_ == STATE_ERROR) |
322 << state_; | 315 << state_; |
323 DCHECK(stop_cb_.is_null()); | 316 DCHECK(stop_cb_.is_null()); |
324 DCHECK_EQ(status == Decoder::kOk, output != NULL); | |
325 DCHECK_GT(pending_decode_requests_, 0); | 317 DCHECK_GT(pending_decode_requests_, 0); |
326 | 318 |
327 --pending_decode_requests_; | 319 --pending_decode_requests_; |
328 | 320 |
329 TRACE_EVENT_ASYNC_END0("media", GetTraceString<StreamType>(), this); | 321 TRACE_EVENT_ASYNC_END0("media", GetTraceString<StreamType>(), this); |
330 | 322 |
331 if (state_ == STATE_ERROR) { | 323 if (state_ == STATE_ERROR) { |
332 DCHECK(read_cb_.is_null()); | 324 DCHECK(read_cb_.is_null()); |
333 return; | 325 return; |
334 } | 326 } |
335 | 327 |
336 if (status == Decoder::kDecodeError) { | 328 // Drop decoding result if Reset() was called during decoding. |
337 state_ = STATE_ERROR; | 329 // The resetting process will be handled when the decoder is reset. |
338 ready_outputs_.clear(); | 330 if (!reset_cb_.is_null()) |
339 if (!read_cb_.is_null()) | 331 return; |
340 SatisfyRead(DECODE_ERROR, NULL); | 332 |
| 333 switch (status) { |
| 334 case Decoder::kDecodeError: |
| 335 case Decoder::kDecryptError: |
| 336 state_ = STATE_ERROR; |
| 337 ready_outputs_.clear(); |
| 338 if (!read_cb_.is_null()) |
| 339 SatisfyRead(DECODE_ERROR, NULL); |
| 340 break; |
| 341 |
| 342 case Decoder::kAborted: |
| 343 // Decoder can return kAborted only when Reset is pending. |
| 344 NOTREACHED(); |
| 345 break; |
| 346 |
| 347 case Decoder::kOk: |
| 348 // Any successful decode counts! |
| 349 if (buffer_size > 0) { |
| 350 StreamTraits::ReportStatistics(statistics_cb_, buffer_size); |
| 351 } |
| 352 |
| 353 if (state_ == STATE_NORMAL) { |
| 354 if (CanDecodeMore() && !end_of_stream) |
| 355 ReadFromDemuxerStream(); |
| 356 } else if (state_ == STATE_FLUSHING_DECODER) { |
| 357 if (!pending_decode_requests_) |
| 358 ReinitializeDecoder(); |
| 359 } |
| 360 break; |
| 361 } |
| 362 } |
| 363 |
| 364 template <DemuxerStream::Type StreamType> |
| 365 void DecoderStream<StreamType>::OnDecodeOutputReady( |
| 366 const scoped_refptr<Output>& output) { |
| 367 FUNCTION_DVLOG(2) << output; |
| 368 DCHECK(output); |
| 369 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || |
| 370 state_ == STATE_PENDING_DEMUXER_READ || state_ == STATE_ERROR) |
| 371 << state_; |
| 372 |
| 373 if (state_ == STATE_ERROR) { |
| 374 DCHECK(read_cb_.is_null()); |
341 return; | 375 return; |
342 } | 376 } |
343 | 377 |
344 if (status == Decoder::kDecryptError) { | |
345 state_ = STATE_ERROR; | |
346 ready_outputs_.clear(); | |
347 if (!read_cb_.is_null()) | |
348 SatisfyRead(DECRYPT_ERROR, NULL); | |
349 return; | |
350 } | |
351 | |
352 if (status == Decoder::kAborted) { | |
353 if (!read_cb_.is_null()) | |
354 SatisfyRead(ABORTED, NULL); | |
355 return; | |
356 } | |
357 | |
358 // Any successful decode counts! | |
359 if (buffer_size > 0) { | |
360 StreamTraits::ReportStatistics(statistics_cb_, buffer_size); | |
361 } | |
362 | |
363 // Drop decoding result if Reset() was called during decoding. | 378 // Drop decoding result if Reset() was called during decoding. |
364 // The resetting process will be handled when the decoder is reset. | 379 // The resetting process will be handled when the decoder is reset. |
365 if (!reset_cb_.is_null()) | 380 if (!reset_cb_.is_null()) |
366 return; | 381 return; |
367 | 382 |
368 // Decoder flushed. Reinitialize the decoder. | 383 if (state_ == STATE_FLUSHING_DECODER && output->end_of_stream()) { |
369 if (state_ == STATE_FLUSHING_DECODER && | 384 // ReinitializeDecoder() will be called from OnDecodeDone(). |
370 status == Decoder::kOk && output->end_of_stream()) { | |
371 ReinitializeDecoder(); | |
372 return; | 385 return; |
373 } | 386 } |
374 | 387 |
375 if (status == Decoder::kNotEnoughData) { | |
376 if (state_ == STATE_NORMAL) | |
377 ReadFromDemuxerStream(); | |
378 else if (state_ == STATE_FLUSHING_DECODER) | |
379 FlushDecoder(); | |
380 return; | |
381 } | |
382 | |
383 DCHECK(output); | |
384 | |
385 // Store decoded output. | 388 // Store decoded output. |
386 ready_outputs_.push_back(output); | 389 ready_outputs_.push_back(output); |
387 scoped_refptr<Output> extra_output; | |
388 while ((extra_output = decoder_->GetDecodeOutput()) != NULL) { | |
389 ready_outputs_.push_back(extra_output); | |
390 } | |
391 | 390 |
392 // Satisfy outstanding read request, if any. | 391 // Satisfy outstanding read request, if any. |
393 if (!read_cb_.is_null()) { | 392 if (!read_cb_.is_null()) { |
394 scoped_refptr<Output> read_result = ready_outputs_.front(); | 393 scoped_refptr<Output> read_result = ready_outputs_.front(); |
395 ready_outputs_.pop_front(); | 394 ready_outputs_.pop_front(); |
396 SatisfyRead(OK, output); | 395 SatisfyRead(OK, output); |
397 } | 396 } |
398 } | 397 } |
399 | 398 |
400 template <DemuxerStream::Type StreamType> | 399 template <DemuxerStream::Type StreamType> |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
453 | 452 |
454 if (!reset_cb_.is_null()) { | 453 if (!reset_cb_.is_null()) { |
455 // If we are using DecryptingDemuxerStream, we already called DDS::Reset() | 454 // If we are using DecryptingDemuxerStream, we already called DDS::Reset() |
456 // which will continue the resetting process in it's callback. | 455 // which will continue the resetting process in it's callback. |
457 if (!decrypting_demuxer_stream_) | 456 if (!decrypting_demuxer_stream_) |
458 Reset(base::ResetAndReturn(&reset_cb_)); | 457 Reset(base::ResetAndReturn(&reset_cb_)); |
459 return; | 458 return; |
460 } | 459 } |
461 | 460 |
462 if (status == DemuxerStream::kAborted) { | 461 if (status == DemuxerStream::kAborted) { |
463 SatisfyRead(DEMUXER_READ_ABORTED, NULL); | 462 if (!read_cb_.is_null()) |
| 463 SatisfyRead(DEMUXER_READ_ABORTED, NULL); |
464 return; | 464 return; |
465 } | 465 } |
466 | 466 |
467 if (!splice_observer_cb_.is_null() && !buffer->end_of_stream()) { | 467 if (!splice_observer_cb_.is_null() && !buffer->end_of_stream()) { |
468 const bool has_splice_ts = buffer->splice_timestamp() != kNoTimestamp(); | 468 const bool has_splice_ts = buffer->splice_timestamp() != kNoTimestamp(); |
469 if (active_splice_ || has_splice_ts) { | 469 if (active_splice_ || has_splice_ts) { |
470 splice_observer_cb_.Run(buffer->splice_timestamp()); | 470 splice_observer_cb_.Run(buffer->splice_timestamp()); |
471 active_splice_ = has_splice_ts; | 471 active_splice_ = has_splice_ts; |
472 } | 472 } |
473 } | 473 } |
(...skipping 13 matching lines...) Expand all Loading... |
487 DCHECK_EQ(state_, STATE_FLUSHING_DECODER) << state_; | 487 DCHECK_EQ(state_, STATE_FLUSHING_DECODER) << state_; |
488 DCHECK_EQ(pending_decode_requests_, 0); | 488 DCHECK_EQ(pending_decode_requests_, 0); |
489 | 489 |
490 DCHECK(StreamTraits::GetDecoderConfig(*stream_).IsValidConfig()); | 490 DCHECK(StreamTraits::GetDecoderConfig(*stream_).IsValidConfig()); |
491 state_ = STATE_REINITIALIZING_DECODER; | 491 state_ = STATE_REINITIALIZING_DECODER; |
492 DecoderStreamTraits<StreamType>::Initialize( | 492 DecoderStreamTraits<StreamType>::Initialize( |
493 decoder_.get(), | 493 decoder_.get(), |
494 StreamTraits::GetDecoderConfig(*stream_), | 494 StreamTraits::GetDecoderConfig(*stream_), |
495 low_delay_, | 495 low_delay_, |
496 base::Bind(&DecoderStream<StreamType>::OnDecoderReinitialized, | 496 base::Bind(&DecoderStream<StreamType>::OnDecoderReinitialized, |
| 497 weak_factory_.GetWeakPtr()), |
| 498 base::Bind(&DecoderStream<StreamType>::OnDecodeOutputReady, |
497 weak_factory_.GetWeakPtr())); | 499 weak_factory_.GetWeakPtr())); |
498 } | 500 } |
499 | 501 |
500 template <DemuxerStream::Type StreamType> | 502 template <DemuxerStream::Type StreamType> |
501 void DecoderStream<StreamType>::OnDecoderReinitialized(PipelineStatus status) { | 503 void DecoderStream<StreamType>::OnDecoderReinitialized(PipelineStatus status) { |
502 FUNCTION_DVLOG(2); | 504 FUNCTION_DVLOG(2); |
503 DCHECK(task_runner_->BelongsToCurrentThread()); | 505 DCHECK(task_runner_->BelongsToCurrentThread()); |
504 DCHECK_EQ(state_, STATE_REINITIALIZING_DECODER) << state_; | 506 DCHECK_EQ(state_, STATE_REINITIALIZING_DECODER) << state_; |
505 DCHECK(stop_cb_.is_null()); | 507 DCHECK(stop_cb_.is_null()); |
506 | 508 |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
575 decrypting_demuxer_stream_.reset(); | 577 decrypting_demuxer_stream_.reset(); |
576 // Post |stop_cb_| because pending |read_cb_| and/or |reset_cb_| are also | 578 // Post |stop_cb_| because pending |read_cb_| and/or |reset_cb_| are also |
577 // posted in Stop(). | 579 // posted in Stop(). |
578 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); | 580 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); |
579 } | 581 } |
580 | 582 |
581 template class DecoderStream<DemuxerStream::VIDEO>; | 583 template class DecoderStream<DemuxerStream::VIDEO>; |
582 template class DecoderStream<DemuxerStream::AUDIO>; | 584 template class DecoderStream<DemuxerStream::AUDIO>; |
583 | 585 |
584 } // namespace media | 586 } // namespace media |
OLD | NEW |