1 // Copyright (c) 2012 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 "media/filters/audio_renderer_impl.h" 6 7 #include <math.h> 8 9 #include <algorithm> 10 11 #include "base/bind.h" 12 #include "base/callback.h" 13 #include "base/callback_helpers.h" 14 #include "base/logging.h" 15 #include "base/message_loop/message_loop_proxy.h" 16 #include "base/metrics/histogram.h" 17 #include "media/base/audio_buffer.h" 18 #include "media/base/audio_splicer.h" 19 #include "media/base/bind_to_loop.h" 20 #include "media/base/demuxer_stream.h" 21 #include "media/filters/audio_decoder_selector.h" 22 #include "media/filters/decrypting_demuxer_stream.h" 23 24 namespace media { 25 26 namespace { 27 28 enum AudioRendererEvent { 29 INITIALIZED, 30 RENDER_ERROR, 31 MAX_EVENTS 32 }; 33 34 void HistogramRendererEvent(AudioRendererEvent event) { 35 UMA_HISTOGRAM_ENUMERATION("Media.AudioRendererEvents", event, MAX_EVENTS); 36 } 37 38 } // namespace 39 40 AudioRendererImpl::AudioRendererImpl( 41 const scoped_refptr<base::MessageLoopProxy>& message_loop, 42 media::AudioRendererSink* sink, 43 ScopedVector<AudioDecoder> decoders, 44 const SetDecryptorReadyCB& set_decryptor_ready_cb) 45 : message_loop_(message_loop), 46 weak_factory_(this), 47 sink_(sink), 48 decoder_selector_(new AudioDecoderSelector( 49 message_loop, decoders.Pass(), set_decryptor_ready_cb)), 50 now_cb_(base::Bind(&base::TimeTicks::Now)), 51 state_(kUninitialized), 52 sink_playing_(false), 53 pending_read_(false), 54 received_end_of_stream_(false), 55 rendered_end_of_stream_(false), 56 audio_time_buffered_(kNoTimestamp()), 57 current_time_(kNoTimestamp()), 58 underflow_disabled_(false), 59 preroll_aborted_(false) { 60 } 61 62 AudioRendererImpl::~AudioRendererImpl() { 63 // Stop() should have been called and |algorithm_| should have been destroyed. 64 DCHECK(state_ == kUninitialized || state_ == kStopped); 65 DCHECK(!algorithm_.get()); 66 } 67 68 void AudioRendererImpl::Play(const base::Closure& callback) { 69 DCHECK(message_loop_->BelongsToCurrentThread()); 70 71 base::AutoLock auto_lock(lock_); 72 DCHECK_EQ(state_, kPaused); 73 ChangeState_Locked(kPlaying); 74 callback.Run(); 75 earliest_end_time_ = now_cb_.Run(); 76 77 if (algorithm_->playback_rate() != 0) 78 DoPlay_Locked(); 79 else 80 DCHECK(!sink_playing_); 81 } 82 83 void AudioRendererImpl::DoPlay_Locked() { 84 DCHECK(message_loop_->BelongsToCurrentThread()); 85 lock_.AssertAcquired(); 86 earliest_end_time_ = now_cb_.Run(); 87 88 if ((state_ == kPlaying || state_ == kRebuffering || state_ == kUnderflow) && 89 !sink_playing_) { 90 { 91 base::AutoUnlock auto_unlock(lock_); 92 sink_->Play(); 93 } 94 95 sink_playing_ = true; 96 } 97 } 98 99 void AudioRendererImpl::Pause(const base::Closure& callback) { 100 DCHECK(message_loop_->BelongsToCurrentThread()); 101 102 base::AutoLock auto_lock(lock_); 103 DCHECK(state_ == kPlaying || state_ == kUnderflow || 104 state_ == kRebuffering) << "state_ == " << state_; 105 ChangeState_Locked(kPaused); 106 107 DoPause_Locked(); 108 109 callback.Run(); 110 } 111 112 void AudioRendererImpl::DoPause_Locked() { 113 DCHECK(message_loop_->BelongsToCurrentThread()); 114 lock_.AssertAcquired(); 115 116 if (sink_playing_) { 117 { 118 base::AutoUnlock auto_unlock(lock_); 119 sink_->Pause(); 120 } 121 sink_playing_ = false; 122 } 123 } 124 125 void AudioRendererImpl::Flush(const base::Closure& callback) { 126 DCHECK(message_loop_->BelongsToCurrentThread()); 127 128 base::AutoLock auto_lock(lock_); 129 DCHECK_EQ(state_, kPaused); 130 DCHECK(flush_cb_.is_null()); 131 132 flush_cb_ = callback; 133 134 if (pending_read_) { 135 ChangeState_Locked(kFlushing); 136 return; 137 } 138 139 DoFlush_Locked(); 140 } 141 142 void AudioRendererImpl::DoFlush_Locked() { 143 DCHECK(message_loop_->BelongsToCurrentThread()); 144 lock_.AssertAcquired(); 145 146 DCHECK(!pending_read_); 147 DCHECK_EQ(state_, kPaused); 148 149 if (decrypting_demuxer_stream_) { 150 decrypting_demuxer_stream_->Reset(BindToCurrentLoop( 151 base::Bind(&AudioRendererImpl::ResetDecoder, weak_this_))); 152 return; 153 } 154 155 ResetDecoder(); 156 } 157 158 void AudioRendererImpl::ResetDecoder() { 159 DCHECK(message_loop_->BelongsToCurrentThread()); 160 decoder_->Reset(BindToCurrentLoop( 161 base::Bind(&AudioRendererImpl::ResetDecoderDone, weak_this_))); 162 } 163 164 void AudioRendererImpl::ResetDecoderDone() { 165 base::AutoLock auto_lock(lock_); 166 if (state_ == kStopped) 167 return; 168 169 DCHECK_EQ(state_, kPaused); 170 DCHECK(!flush_cb_.is_null()); 171 172 audio_time_buffered_ = kNoTimestamp(); 173 current_time_ = kNoTimestamp(); 174 received_end_of_stream_ = false; 175 rendered_end_of_stream_ = false; 176 preroll_aborted_ = false; 177 178 earliest_end_time_ = now_cb_.Run(); 179 splicer_->Reset(); 180 algorithm_->FlushBuffers(); 181 182 base::ResetAndReturn(&flush_cb_).Run(); 183 } 184 185 void AudioRendererImpl::Stop(const base::Closure& callback) { 186 DCHECK(message_loop_->BelongsToCurrentThread()); 187 DCHECK(!callback.is_null()); 188 189 // TODO(scherkus): Consider invalidating |weak_factory_| and replacing 190 // task-running guards that check |state_| with DCHECK(). 191 192 if (sink_) { 193 sink_->Stop(); 194 sink_ = NULL; 195 } 196 197 { 198 base::AutoLock auto_lock(lock_); 199 ChangeState_Locked(kStopped); 200 algorithm_.reset(NULL); 201 init_cb_.Reset(); 202 underflow_cb_.Reset(); 203 time_cb_.Reset(); 204 flush_cb_.Reset(); 205 } 206 207 callback.Run(); 208 } 209 210 void AudioRendererImpl::Preroll(base::TimeDelta time, 211 const PipelineStatusCB& cb) { 212 DCHECK(message_loop_->BelongsToCurrentThread()); 213 214 base::AutoLock auto_lock(lock_); 215 DCHECK(!sink_playing_); 216 DCHECK_EQ(state_, kPaused); 217 DCHECK(!pending_read_) << "Pending read must complete before seeking"; 218 DCHECK(preroll_cb_.is_null()); 219 220 ChangeState_Locked(kPrerolling); 221 preroll_cb_ = cb; 222 preroll_timestamp_ = time; 223 224 AttemptRead_Locked(); 225 } 226 227 void AudioRendererImpl::Initialize(DemuxerStream* stream, 228 const PipelineStatusCB& init_cb, 229 const StatisticsCB& statistics_cb, 230 const base::Closure& underflow_cb, 231 const TimeCB& time_cb, 232 const base::Closure& ended_cb, 233 const base::Closure& disabled_cb, 234 const PipelineStatusCB& error_cb) { 235 DCHECK(message_loop_->BelongsToCurrentThread()); 236 DCHECK(stream); 237 DCHECK_EQ(stream->type(), DemuxerStream::AUDIO); 238 DCHECK(!init_cb.is_null()); 239 DCHECK(!statistics_cb.is_null()); 240 DCHECK(!underflow_cb.is_null()); 241 DCHECK(!time_cb.is_null()); 242 DCHECK(!ended_cb.is_null()); 243 DCHECK(!disabled_cb.is_null()); 244 DCHECK(!error_cb.is_null()); 245 DCHECK_EQ(kUninitialized, state_); 246 DCHECK(sink_); 247 248 weak_this_ = weak_factory_.GetWeakPtr(); 249 init_cb_ = init_cb; 250 statistics_cb_ = statistics_cb; 251 underflow_cb_ = underflow_cb; 252 time_cb_ = time_cb; 253 ended_cb_ = ended_cb; 254 disabled_cb_ = disabled_cb; 255 error_cb_ = error_cb; 256 257 decoder_selector_->SelectAudioDecoder( 258 stream, 259 statistics_cb, 260 base::Bind(&AudioRendererImpl::OnDecoderSelected, weak_this_)); 261 } 262 263 void AudioRendererImpl::OnDecoderSelected( 264 scoped_ptr<AudioDecoder> decoder, 265 scoped_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream) { 266 DCHECK(message_loop_->BelongsToCurrentThread()); 267 268 base::AutoLock auto_lock(lock_); 269 scoped_ptr<AudioDecoderSelector> deleter(decoder_selector_.Pass()); 270 271 if (state_ == kStopped) { 272 DCHECK(!sink_); 273 return; 274 } 275 276 if (!decoder) { 277 base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED); 278 return; 279 } 280 281 decoder_ = decoder.Pass(); 282 decrypting_demuxer_stream_ = decrypting_demuxer_stream.Pass(); 283 284 int sample_rate = decoder_->samples_per_second(); 285 286 // The actual buffer size is controlled via the size of the AudioBus provided 287 // to Render(), so just choose something reasonable here for looks. 288 int buffer_size = decoder_->samples_per_second() / 100; 289 audio_parameters_ = AudioParameters( 290 AudioParameters::AUDIO_PCM_LOW_LATENCY, decoder_->channel_layout(), 291 sample_rate, decoder_->bits_per_channel(), buffer_size); 292 if (!audio_parameters_.IsValid()) { 293 base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED); 294 return; 295 } 296 297 splicer_.reset(new AudioSplicer(sample_rate)); 298 299 // We're all good! Continue initializing the rest of the audio renderer based 300 // on the decoder format. 301 algorithm_.reset(new AudioRendererAlgorithm()); 302 algorithm_->Initialize(0, audio_parameters_); 303 304 ChangeState_Locked(kPaused); 305 306 HistogramRendererEvent(INITIALIZED); 307 308 { 309 base::AutoUnlock auto_unlock(lock_); 310 sink_->Initialize(audio_parameters_, weak_this_.get()); 311 sink_->Start(); 312 313 // Some sinks play on start... 314 sink_->Pause(); 315 } 316 317 DCHECK(!sink_playing_); 318 319 base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); 320 } 321 322 void AudioRendererImpl::ResumeAfterUnderflow() { 323 DCHECK(message_loop_->BelongsToCurrentThread()); 324 base::AutoLock auto_lock(lock_); 325 if (state_ == kUnderflow) { 326 // The "!preroll_aborted_" is a hack. If preroll is aborted, then we 327 // shouldn't even reach the kUnderflow state to begin with. But for now 328 // we're just making sure that the audio buffer capacity (i.e. the 329 // number of bytes that need to be buffered for preroll to complete) 330 // does not increase due to an aborted preroll. 331 // TODO(vrk): Fix this bug correctly! (crbug.com/151352) 332 if (!preroll_aborted_) 333 algorithm_->IncreaseQueueCapacity(); 334 335 ChangeState_Locked(kRebuffering); 336 } 337 } 338 339 void AudioRendererImpl::SetVolume(float volume) { 340 DCHECK(message_loop_->BelongsToCurrentThread()); 341 DCHECK(sink_); 342 sink_->SetVolume(volume); 343 } 344 345 void AudioRendererImpl::DecodedAudioReady( 346 AudioDecoder::Status status, 347 const scoped_refptr<AudioBuffer>& buffer) { 348 DVLOG(1) << __FUNCTION__ << "(" << status << ")"; 349 DCHECK(message_loop_->BelongsToCurrentThread()); 350 351 base::AutoLock auto_lock(lock_); 352 DCHECK(state_ != kUninitialized); 353 354 CHECK(pending_read_); 355 pending_read_ = false; 356 357 if (status == AudioDecoder::kAborted) { 358 HandleAbortedReadOrDecodeError(false); 359 return; 360 } 361 362 if (status == AudioDecoder::kDecodeError) { 363 HandleAbortedReadOrDecodeError(true); 364 return; 365 } 366 367 DCHECK_EQ(status, AudioDecoder::kOk); 368 DCHECK(buffer.get()); 369 370 if (state_ == kFlushing) { 371 ChangeState_Locked(kPaused); 372 DoFlush_Locked(); 373 return; 374 } 375 376 if (!splicer_->AddInput(buffer)) { 377 HandleAbortedReadOrDecodeError(true); 378 return; 379 } 380 381 if (!splicer_->HasNextBuffer()) { 382 AttemptRead_Locked(); 383 return; 384 } 385 386 bool need_another_buffer = false; 387 while (splicer_->HasNextBuffer()) 388 need_another_buffer = HandleSplicerBuffer(splicer_->GetNextBuffer()); 389 390 if (!need_another_buffer && !CanRead_Locked()) 391 return; 392 393 AttemptRead_Locked(); 394 } 395 396 bool AudioRendererImpl::HandleSplicerBuffer( 397 const scoped_refptr<AudioBuffer>& buffer) { 398 if (buffer->end_of_stream()) { 399 received_end_of_stream_ = true; 400 401 // Transition to kPlaying if we are currently handling an underflow since 402 // no more data will be arriving. 403 if (state_ == kUnderflow || state_ == kRebuffering) 404 ChangeState_Locked(kPlaying); 405 } else { 406 if (state_ == kPrerolling) { 407 if (IsBeforePrerollTime(buffer)) 408 return true; 409 410 // Trim off any additional time before the preroll timestamp. 411 const base::TimeDelta trim_time = 412 preroll_timestamp_ - buffer->timestamp(); 413 if (trim_time > base::TimeDelta()) { 414 buffer->TrimStart(buffer->frame_count() * 415 (static_cast<double>(trim_time.InMicroseconds()) / 416 buffer->duration().InMicroseconds())); 417 } 418 // If the entire buffer was trimmed, request a new one. 419 if (!buffer->frame_count()) 420 return true; 421 } 422 423 if (state_ != kUninitialized && state_ != kStopped) 424 algorithm_->EnqueueBuffer(buffer); 425 } 426 427 switch (state_) { 428 case kUninitialized: 429 case kFlushing: 430 NOTREACHED(); 431 return false; 432 433 case kPaused: 434 DCHECK(!pending_read_); 435 return false; 436 437 case kPrerolling: 438 if (!buffer->end_of_stream() && !algorithm_->IsQueueFull()) 439 return true; 440 ChangeState_Locked(kPaused); 441 base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK); 442 return false; 443 444 case kPlaying: 445 case kUnderflow: 446 return false; 447 448 case kRebuffering: 449 if (!algorithm_->IsQueueFull()) 450 return true; 451 ChangeState_Locked(kPlaying); 452 return false; 453 454 case kStopped: 455 return false; 456 } 457 return false; 458 } 459 460 void AudioRendererImpl::AttemptRead() { 461 base::AutoLock auto_lock(lock_); 462 AttemptRead_Locked(); 463 } 464 465 void AudioRendererImpl::AttemptRead_Locked() { 466 DCHECK(message_loop_->BelongsToCurrentThread()); 467 lock_.AssertAcquired(); 468 469 if (!CanRead_Locked()) 470 return; 471 472 pending_read_ = true; 473 decoder_->Read(base::Bind(&AudioRendererImpl::DecodedAudioReady, weak_this_)); 474 } 475 476 bool AudioRendererImpl::CanRead_Locked() { 477 lock_.AssertAcquired(); 478 479 switch (state_) { 480 case kUninitialized: 481 case kPaused: 482 case kFlushing: 483 case kStopped: 484 return false; 485 486 case kPrerolling: 487 case kPlaying: 488 case kUnderflow: 489 case kRebuffering: 490 break; 491 } 492 493 return !pending_read_ && !received_end_of_stream_ && 494 !algorithm_->IsQueueFull(); 495 } 496 497 void AudioRendererImpl::SetPlaybackRate(float playback_rate) { 498 DVLOG(1) << __FUNCTION__ << "(" << playback_rate << ")"; 499 DCHECK(message_loop_->BelongsToCurrentThread()); 500 DCHECK_GE(playback_rate, 0); 501 DCHECK(sink_); 502 503 base::AutoLock auto_lock(lock_); 504 505 // We have two cases here: 506 // Play: current_playback_rate == 0 && playback_rate != 0 507 // Pause: current_playback_rate != 0 && playback_rate == 0 508 float current_playback_rate = algorithm_->playback_rate(); 509 if (current_playback_rate == 0 && playback_rate != 0) 510 DoPlay_Locked(); 511 else if (current_playback_rate != 0 && playback_rate == 0) 512 DoPause_Locked(); 513 514 algorithm_->SetPlaybackRate(playback_rate); 515 } 516 517 bool AudioRendererImpl::IsBeforePrerollTime( 518 const scoped_refptr<AudioBuffer>& buffer) { 519 DCHECK_EQ(state_, kPrerolling); 520 return buffer && !buffer->end_of_stream() && 521 (buffer->timestamp() + buffer->duration()) < preroll_timestamp_; 522 } 523 524 int AudioRendererImpl::Render(AudioBus* audio_bus, 525 int audio_delay_milliseconds) { 526 const int requested_frames = audio_bus->frames(); 527 base::TimeDelta current_time = kNoTimestamp(); 528 base::TimeDelta max_time = kNoTimestamp(); 529 base::TimeDelta playback_delay = base::TimeDelta::FromMilliseconds( 530 audio_delay_milliseconds); 531 532 int frames_written = 0; 533 base::Closure underflow_cb; 534 { 535 base::AutoLock auto_lock(lock_); 536 537 // Ensure Stop() hasn't destroyed our |algorithm_| on the pipeline thread. 538 if (!algorithm_) 539 return 0; 540 541 float playback_rate = algorithm_->playback_rate(); 542 if (playback_rate == 0) 543 return 0; 544 545 // Mute audio by returning 0 when not playing. 546 if (state_ != kPlaying) 547 return 0; 548 549 // We use the following conditions to determine end of playback: 550 // 1) Algorithm can not fill the audio callback buffer 551 // 2) We received an end of stream buffer 552 // 3) We haven't already signalled that we've ended 553 // 4) Our estimated earliest end time has expired 554 // 555 // TODO(enal): we should replace (4) with a check that the browser has no 556 // more audio data or at least use a delayed callback. 557 // 558 // We use the following conditions to determine underflow: 559 // 1) Algorithm can not fill the audio callback buffer 560 // 2) We have NOT received an end of stream buffer 561 // 3) We are in the kPlaying state 562 // 563 // Otherwise the buffer has data we can send to the device. 564 frames_written = algorithm_->FillBuffer(audio_bus, requested_frames); 565 if (frames_written == 0) { 566 const base::TimeTicks now = now_cb_.Run(); 567 568 if (received_end_of_stream_ && !rendered_end_of_stream_ && 569 now >= earliest_end_time_) { 570 rendered_end_of_stream_ = true; 571 ended_cb_.Run(); 572 } else if (!received_end_of_stream_ && state_ == kPlaying && 573 !underflow_disabled_) { 574 ChangeState_Locked(kUnderflow); 575 underflow_cb = underflow_cb_; 576 } else { 577 // We can't write any data this cycle. For example, we may have 578 // sent all available data to the audio device while not reaching 579 // |earliest_end_time_|. 580 } 581 } 582 583 if (CanRead_Locked()) { 584 message_loop_->PostTask(FROM_HERE, base::Bind( 585 &AudioRendererImpl::AttemptRead, weak_this_)); 586 } 587 588 // The |audio_time_buffered_| is the ending timestamp of the last frame 589 // buffered at the audio device. |playback_delay| is the amount of time 590 // buffered at the audio device. The current time can be computed by their 591 // difference. 592 if (audio_time_buffered_ != kNoTimestamp()) { 593 // Adjust the delay according to playback rate. 594 base::TimeDelta adjusted_playback_delay = 595 base::TimeDelta::FromMicroseconds(ceil( 596 playback_delay.InMicroseconds() * playback_rate)); 597 598 base::TimeDelta previous_time = current_time_; 599 current_time_ = audio_time_buffered_ - adjusted_playback_delay; 600 601 // Time can change in one of two ways: 602 // 1) The time of the audio data at the audio device changed, or 603 // 2) The playback delay value has changed 604 // 605 // We only want to set |current_time| (and thus execute |time_cb_|) if 606 // time has progressed and we haven't signaled end of stream yet. 607 // 608 // Why? The current latency of the system results in getting the last call 609 // to FillBuffer() later than we'd like, which delays firing the 'ended' 610 // event, which delays the looping/trigging performance of short sound 611 // effects. 612 // 613 // TODO(scherkus): revisit this and switch back to relying on playback 614 // delay after we've revamped our audio IPC subsystem. 615 if (current_time_ > previous_time && !rendered_end_of_stream_) { 616 current_time = current_time_; 617 } 618 } 619 620 // The call to FillBuffer() on |algorithm_| has increased the amount of 621 // buffered audio data. Update the new amount of time buffered. 622 max_time = algorithm_->GetTime(); 623 audio_time_buffered_ = max_time; 624 625 if (frames_written > 0) { 626 UpdateEarliestEndTime_Locked( 627 frames_written, playback_delay, now_cb_.Run()); 628 } 629 } 630 631 if (current_time != kNoTimestamp() && max_time != kNoTimestamp()) 632 time_cb_.Run(current_time, max_time); 633 634 if (!underflow_cb.is_null()) 635 underflow_cb.Run(); 636 637 DCHECK_LE(frames_written, requested_frames); 638 return frames_written; 639 } 640 641 void AudioRendererImpl::UpdateEarliestEndTime_Locked( 642 int frames_filled, const base::TimeDelta& playback_delay, 643 const base::TimeTicks& time_now) { 644 DCHECK_GT(frames_filled, 0); 645 646 base::TimeDelta predicted_play_time = base::TimeDelta::FromMicroseconds( 647 static_cast<float>(frames_filled) * base::Time::kMicrosecondsPerSecond / 648 audio_parameters_.sample_rate()); 649 650 lock_.AssertAcquired(); 651 earliest_end_time_ = std::max( 652 earliest_end_time_, time_now + playback_delay + predicted_play_time); 653 } 654 655 void AudioRendererImpl::OnRenderError() { 656 HistogramRendererEvent(RENDER_ERROR); 657 disabled_cb_.Run(); 658 } 659 660 void AudioRendererImpl::DisableUnderflowForTesting() { 661 underflow_disabled_ = true; 662 } 663 664 void AudioRendererImpl::HandleAbortedReadOrDecodeError(bool is_decode_error) { 665 lock_.AssertAcquired(); 666 667 PipelineStatus status = is_decode_error ? PIPELINE_ERROR_DECODE : PIPELINE_OK; 668 switch (state_) { 669 case kUninitialized: 670 NOTREACHED(); 671 return; 672 case kPaused: 673 if (status != PIPELINE_OK) 674 error_cb_.Run(status); 675 return; 676 case kFlushing: 677 ChangeState_Locked(kPaused); 678 679 if (status == PIPELINE_OK) { 680 DoFlush_Locked(); 681 return; 682 } 683 684 error_cb_.Run(status); 685 base::ResetAndReturn(&flush_cb_).Run(); 686 return; 687 case kPrerolling: 688 // This is a signal for abort if it's not an error. 689 preroll_aborted_ = !is_decode_error; 690 ChangeState_Locked(kPaused); 691 base::ResetAndReturn(&preroll_cb_).Run(status); 692 return; 693 case kPlaying: 694 case kUnderflow: 695 case kRebuffering: 696 case kStopped: 697 if (status != PIPELINE_OK) 698 error_cb_.Run(status); 699 return; 700 } 701 } 702 703 void AudioRendererImpl::ChangeState_Locked(State new_state) { 704 DVLOG(1) << __FUNCTION__ << " : " << state_ << " -> " << new_state; 705 lock_.AssertAcquired(); 706 state_ = new_state; 707 } 708 709 } // namespace media 710