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, | |
85 weak_factory_.GetWeakPtr())); | 83 weak_factory_.GetWeakPtr())); |
86 } | 84 } |
87 | 85 |
88 template <DemuxerStream::Type StreamType> | 86 template <DemuxerStream::Type StreamType> |
89 void DecoderStream<StreamType>::Read(const ReadCB& read_cb) { | 87 void DecoderStream<StreamType>::Read(const ReadCB& read_cb) { |
90 FUNCTION_DVLOG(2); | 88 FUNCTION_DVLOG(2); |
91 DCHECK(task_runner_->BelongsToCurrentThread()); | 89 DCHECK(task_runner_->BelongsToCurrentThread()); |
92 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || | 90 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || |
93 state_ == STATE_ERROR || state_ == STATE_REINITIALIZING_DECODER || | 91 state_ == STATE_ERROR || state_ == STATE_REINITIALIZING_DECODER || |
94 state_ == STATE_PENDING_DEMUXER_READ) | 92 state_ == STATE_PENDING_DEMUXER_READ) |
(...skipping 13 matching lines...) Expand all Loading... |
108 scoped_refptr<Output>())); | 106 scoped_refptr<Output>())); |
109 return; | 107 return; |
110 } | 108 } |
111 | 109 |
112 if (!ready_outputs_.empty()) { | 110 if (!ready_outputs_.empty()) { |
113 task_runner_->PostTask(FROM_HERE, base::Bind( | 111 task_runner_->PostTask(FROM_HERE, base::Bind( |
114 base::ResetAndReturn(&read_cb_), OK, ready_outputs_.front())); | 112 base::ResetAndReturn(&read_cb_), OK, ready_outputs_.front())); |
115 ready_outputs_.pop_front(); | 113 ready_outputs_.pop_front(); |
116 } | 114 } |
117 | 115 |
118 if (state_ == STATE_NORMAL && CanDecodeMore()) | 116 // Decoder may be in reinitializing state as result of the previous Read(). |
| 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) |
119 ReadFromDemuxerStream(); | 129 ReadFromDemuxerStream(); |
120 } | 130 } |
121 | 131 |
122 template <DemuxerStream::Type StreamType> | 132 template <DemuxerStream::Type StreamType> |
123 void DecoderStream<StreamType>::Reset(const base::Closure& closure) { | 133 void DecoderStream<StreamType>::Reset(const base::Closure& closure) { |
124 FUNCTION_DVLOG(2); | 134 FUNCTION_DVLOG(2); |
125 DCHECK(task_runner_->BelongsToCurrentThread()); | 135 DCHECK(task_runner_->BelongsToCurrentThread()); |
126 DCHECK(state_ != STATE_UNINITIALIZED && state_ != STATE_STOPPED) << state_; | 136 DCHECK(state_ != STATE_UNINITIALIZED && state_ != STATE_STOPPED) << state_; |
127 DCHECK(reset_cb_.is_null()); | 137 DCHECK(reset_cb_.is_null()); |
128 DCHECK(stop_cb_.is_null()); | 138 DCHECK(stop_cb_.is_null()); |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
199 state_ = STATE_STOPPED; | 209 state_ = STATE_STOPPED; |
200 stream_ = NULL; | 210 stream_ = NULL; |
201 decoder_.reset(); | 211 decoder_.reset(); |
202 decrypting_demuxer_stream_.reset(); | 212 decrypting_demuxer_stream_.reset(); |
203 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); | 213 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); |
204 } | 214 } |
205 | 215 |
206 template <DemuxerStream::Type StreamType> | 216 template <DemuxerStream::Type StreamType> |
207 bool DecoderStream<StreamType>::CanReadWithoutStalling() const { | 217 bool DecoderStream<StreamType>::CanReadWithoutStalling() const { |
208 DCHECK(task_runner_->BelongsToCurrentThread()); | 218 DCHECK(task_runner_->BelongsToCurrentThread()); |
209 return !ready_outputs_.empty() || decoder_->CanReadWithoutStalling(); | 219 return decoder_->CanReadWithoutStalling(); |
210 } | 220 } |
211 | 221 |
212 template <> | 222 template <> |
213 bool DecoderStream<DemuxerStream::AUDIO>::CanReadWithoutStalling() const { | 223 bool DecoderStream<DemuxerStream::AUDIO>::CanReadWithoutStalling() const { |
214 DCHECK(task_runner_->BelongsToCurrentThread()); | 224 DCHECK(task_runner_->BelongsToCurrentThread()); |
215 return true; | 225 return true; |
216 } | 226 } |
217 | 227 |
218 template <DemuxerStream::Type StreamType> | 228 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 < GetMaxDecodeRequests(); | 237 return num_decodes < decoder_->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(); |
238 } | 244 } |
239 | 245 |
240 template <DemuxerStream::Type StreamType> | 246 template <DemuxerStream::Type StreamType> |
241 void DecoderStream<StreamType>::OnDecoderSelected( | 247 void DecoderStream<StreamType>::OnDecoderSelected( |
242 scoped_ptr<Decoder> selected_decoder, | 248 scoped_ptr<Decoder> selected_decoder, |
243 scoped_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream) { | 249 scoped_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream) { |
244 FUNCTION_DVLOG(2); | 250 FUNCTION_DVLOG(2); |
245 DCHECK(task_runner_->BelongsToCurrentThread()); | 251 DCHECK(task_runner_->BelongsToCurrentThread()); |
246 DCHECK_EQ(state_, STATE_INITIALIZING) << state_; | 252 DCHECK_EQ(state_, STATE_INITIALIZING) << state_; |
247 DCHECK(!init_cb_.is_null()); | 253 DCHECK(!init_cb_.is_null()); |
(...skipping 29 matching lines...) Expand all Loading... |
277 const scoped_refptr<Output>& output) { | 283 const scoped_refptr<Output>& output) { |
278 DCHECK(!read_cb_.is_null()); | 284 DCHECK(!read_cb_.is_null()); |
279 base::ResetAndReturn(&read_cb_).Run(status, output); | 285 base::ResetAndReturn(&read_cb_).Run(status, output); |
280 } | 286 } |
281 | 287 |
282 template <DemuxerStream::Type StreamType> | 288 template <DemuxerStream::Type StreamType> |
283 void DecoderStream<StreamType>::Decode( | 289 void DecoderStream<StreamType>::Decode( |
284 const scoped_refptr<DecoderBuffer>& buffer) { | 290 const scoped_refptr<DecoderBuffer>& buffer) { |
285 FUNCTION_DVLOG(2); | 291 FUNCTION_DVLOG(2); |
286 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER) << state_; | 292 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER) << state_; |
287 DCHECK_LT(pending_decode_requests_, GetMaxDecodeRequests()); | 293 DCHECK(CanDecodeMore()); |
288 DCHECK(reset_cb_.is_null()); | 294 DCHECK(reset_cb_.is_null()); |
289 DCHECK(stop_cb_.is_null()); | 295 DCHECK(stop_cb_.is_null()); |
290 DCHECK(buffer); | 296 DCHECK(buffer); |
291 | 297 |
292 int buffer_size = buffer->end_of_stream() ? 0 : buffer->data_size(); | 298 int buffer_size = buffer->end_of_stream() ? 0 : buffer->data_size(); |
293 | 299 |
294 TRACE_EVENT_ASYNC_BEGIN0("media", GetTraceString<StreamType>(), this); | 300 TRACE_EVENT_ASYNC_BEGIN0("media", GetTraceString<StreamType>(), this); |
295 ++pending_decode_requests_; | 301 ++pending_decode_requests_; |
296 decoder_->Decode(buffer, | 302 decoder_->Decode(buffer, |
297 base::Bind(&DecoderStream<StreamType>::OnDecodeDone, | 303 base::Bind(&DecoderStream<StreamType>::OnDecodeOutputReady, |
298 weak_factory_.GetWeakPtr(), | 304 weak_factory_.GetWeakPtr(), |
299 buffer_size, | 305 buffer_size)); |
300 buffer->end_of_stream())); | |
301 } | 306 } |
302 | 307 |
303 template <DemuxerStream::Type StreamType> | 308 template <DemuxerStream::Type StreamType> |
304 void DecoderStream<StreamType>::FlushDecoder() { | 309 void DecoderStream<StreamType>::FlushDecoder() { |
305 Decode(DecoderBuffer::CreateEOSBuffer()); | 310 if (pending_decode_requests_ == 0) |
| 311 Decode(DecoderBuffer::CreateEOSBuffer()); |
306 } | 312 } |
307 | 313 |
308 template <DemuxerStream::Type StreamType> | 314 template <DemuxerStream::Type StreamType> |
309 void DecoderStream<StreamType>::OnDecodeDone(int buffer_size, | 315 void DecoderStream<StreamType>::OnDecodeOutputReady( |
310 bool end_of_stream, | 316 int buffer_size, |
311 typename Decoder::Status status) { | 317 typename Decoder::Status status, |
312 FUNCTION_DVLOG(2) << status; | 318 const scoped_refptr<Output>& output) { |
| 319 FUNCTION_DVLOG(2) << status << " " << output; |
313 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || | 320 DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER || |
314 state_ == STATE_PENDING_DEMUXER_READ || state_ == STATE_ERROR) | 321 state_ == STATE_PENDING_DEMUXER_READ || state_ == STATE_ERROR) |
315 << state_; | 322 << state_; |
316 DCHECK(stop_cb_.is_null()); | 323 DCHECK(stop_cb_.is_null()); |
| 324 DCHECK_EQ(status == Decoder::kOk, output != NULL); |
317 DCHECK_GT(pending_decode_requests_, 0); | 325 DCHECK_GT(pending_decode_requests_, 0); |
318 | 326 |
319 --pending_decode_requests_; | 327 --pending_decode_requests_; |
320 | 328 |
321 TRACE_EVENT_ASYNC_END0("media", GetTraceString<StreamType>(), this); | 329 TRACE_EVENT_ASYNC_END0("media", GetTraceString<StreamType>(), this); |
322 | 330 |
323 if (state_ == STATE_ERROR) { | 331 if (state_ == STATE_ERROR) { |
324 DCHECK(read_cb_.is_null()); | 332 DCHECK(read_cb_.is_null()); |
325 return; | 333 return; |
326 } | 334 } |
327 | 335 |
| 336 if (status == Decoder::kDecodeError) { |
| 337 state_ = STATE_ERROR; |
| 338 ready_outputs_.clear(); |
| 339 if (!read_cb_.is_null()) |
| 340 SatisfyRead(DECODE_ERROR, NULL); |
| 341 return; |
| 342 } |
| 343 |
| 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 |
328 // Drop decoding result if Reset() was called during decoding. | 363 // Drop decoding result if Reset() was called during decoding. |
329 // The resetting process will be handled when the decoder is reset. | 364 // The resetting process will be handled when the decoder is reset. |
330 if (!reset_cb_.is_null()) | 365 if (!reset_cb_.is_null()) |
331 return; | 366 return; |
332 | 367 |
333 switch (status) { | 368 // Decoder flushed. Reinitialize the decoder. |
334 case Decoder::kDecodeError: | 369 if (state_ == STATE_FLUSHING_DECODER && |
335 case Decoder::kDecryptError: | 370 status == Decoder::kOk && output->end_of_stream()) { |
336 state_ = STATE_ERROR; | 371 ReinitializeDecoder(); |
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()); | |
375 return; | 372 return; |
376 } | 373 } |
377 | 374 |
378 // Drop decoding result if Reset() was called during decoding. | 375 if (status == Decoder::kNotEnoughData) { |
379 // The resetting process will be handled when the decoder is reset. | 376 if (state_ == STATE_NORMAL) |
380 if (!reset_cb_.is_null()) | 377 ReadFromDemuxerStream(); |
381 return; | 378 else if (state_ == STATE_FLUSHING_DECODER) |
382 | 379 FlushDecoder(); |
383 if (state_ == STATE_FLUSHING_DECODER && output->end_of_stream()) { | |
384 // ReinitializeDecoder() will be called from OnDecodeDone(). | |
385 return; | 380 return; |
386 } | 381 } |
387 | 382 |
| 383 DCHECK(output); |
| 384 |
388 // Store decoded output. | 385 // Store decoded output. |
389 ready_outputs_.push_back(output); | 386 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 } |
390 | 391 |
391 // Satisfy outstanding read request, if any. | 392 // Satisfy outstanding read request, if any. |
392 if (!read_cb_.is_null()) { | 393 if (!read_cb_.is_null()) { |
393 scoped_refptr<Output> read_result = ready_outputs_.front(); | 394 scoped_refptr<Output> read_result = ready_outputs_.front(); |
394 ready_outputs_.pop_front(); | 395 ready_outputs_.pop_front(); |
395 SatisfyRead(OK, output); | 396 SatisfyRead(OK, output); |
396 } | 397 } |
397 } | 398 } |
398 | 399 |
399 template <DemuxerStream::Type StreamType> | 400 template <DemuxerStream::Type StreamType> |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
452 | 453 |
453 if (!reset_cb_.is_null()) { | 454 if (!reset_cb_.is_null()) { |
454 // If we are using DecryptingDemuxerStream, we already called DDS::Reset() | 455 // If we are using DecryptingDemuxerStream, we already called DDS::Reset() |
455 // which will continue the resetting process in it's callback. | 456 // which will continue the resetting process in it's callback. |
456 if (!decrypting_demuxer_stream_) | 457 if (!decrypting_demuxer_stream_) |
457 Reset(base::ResetAndReturn(&reset_cb_)); | 458 Reset(base::ResetAndReturn(&reset_cb_)); |
458 return; | 459 return; |
459 } | 460 } |
460 | 461 |
461 if (status == DemuxerStream::kAborted) { | 462 if (status == DemuxerStream::kAborted) { |
462 if (!read_cb_.is_null()) | 463 SatisfyRead(DEMUXER_READ_ABORTED, 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, | |
499 weak_factory_.GetWeakPtr())); | 497 weak_factory_.GetWeakPtr())); |
500 } | 498 } |
501 | 499 |
502 template <DemuxerStream::Type StreamType> | 500 template <DemuxerStream::Type StreamType> |
503 void DecoderStream<StreamType>::OnDecoderReinitialized(PipelineStatus status) { | 501 void DecoderStream<StreamType>::OnDecoderReinitialized(PipelineStatus status) { |
504 FUNCTION_DVLOG(2); | 502 FUNCTION_DVLOG(2); |
505 DCHECK(task_runner_->BelongsToCurrentThread()); | 503 DCHECK(task_runner_->BelongsToCurrentThread()); |
506 DCHECK_EQ(state_, STATE_REINITIALIZING_DECODER) << state_; | 504 DCHECK_EQ(state_, STATE_REINITIALIZING_DECODER) << state_; |
507 DCHECK(stop_cb_.is_null()); | 505 DCHECK(stop_cb_.is_null()); |
508 | 506 |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
577 decrypting_demuxer_stream_.reset(); | 575 decrypting_demuxer_stream_.reset(); |
578 // Post |stop_cb_| because pending |read_cb_| and/or |reset_cb_| are also | 576 // Post |stop_cb_| because pending |read_cb_| and/or |reset_cb_| are also |
579 // posted in Stop(). | 577 // posted in Stop(). |
580 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); | 578 task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); |
581 } | 579 } |
582 | 580 |
583 template class DecoderStream<DemuxerStream::VIDEO>; | 581 template class DecoderStream<DemuxerStream::VIDEO>; |
584 template class DecoderStream<DemuxerStream::AUDIO>; | 582 template class DecoderStream<DemuxerStream::AUDIO>; |
585 | 583 |
586 } // namespace media | 584 } // namespace media |
OLD | NEW |