Chromium Code Reviews| Index: media/audio/audio_output_controller.cc |
| diff --git a/media/audio/audio_output_controller.cc b/media/audio/audio_output_controller.cc |
| index 50850c9265cdd2b0f503e97994a122b70167047d..0120d0e5e37c276d20bf0c345dff876dd5f218ce 100644 |
| --- a/media/audio/audio_output_controller.cc |
| +++ b/media/audio/audio_output_controller.cc |
| @@ -29,14 +29,15 @@ AudioOutputController::AudioOutputController(AudioManager* audio_manager, |
| const AudioParameters& params, |
| SyncReader* sync_reader) |
| : audio_manager_(audio_manager), |
| + params_(params), |
| handler_(handler), |
| stream_(NULL), |
| + stream_glue_(new Glue(this, sync_reader)), |
| volume_(1.0), |
| state_(kEmpty), |
| sync_reader_(sync_reader), |
| message_loop_(audio_manager->GetMessageLoop()), |
| number_polling_attempts_left_(0), |
| - params_(params), |
| ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)) { |
| } |
| @@ -70,13 +71,10 @@ scoped_refptr<AudioOutputController> AudioOutputController::Create( |
| if (!params.IsValid() || !audio_manager) |
| return NULL; |
| - // Starts the audio controller thread. |
| scoped_refptr<AudioOutputController> controller(new AudioOutputController( |
| audio_manager, event_handler, params, sync_reader)); |
| - |
| controller->message_loop_->PostTask(FROM_HERE, base::Bind( |
| &AudioOutputController::DoCreate, controller)); |
| - |
| return controller; |
| } |
| @@ -138,10 +136,10 @@ void AudioOutputController::DoCreate() { |
| return; |
| } |
| - // Everything started okay, so register for state change callbacks if we have |
| - // not already done so. |
| - if (state_ != kRecreating) |
| - audio_manager_->AddOutputDeviceChangeListener(this); |
| + // Everything started okay, so re-register for state change callbacks. Note: |
| + // The call to DoStopCloseAndClearStream() above called |
| + // RemoveOutputDeviceChangeListener(). |
| + audio_manager_->AddOutputDeviceChangeListener(this); |
| // We have successfully opened the stream. Set the initial volume. |
| stream_->SetVolume(volume_); |
| @@ -215,7 +213,7 @@ void AudioOutputController::StartStream() { |
| state_ = kPlaying; |
| // We start the AudioOutputStream lazily. |
| - stream_->Start(this); |
| + stream_->Start(stream_glue_); |
| // Tell the event handler that we are now playing. |
| handler_->OnPlaying(this); |
| @@ -262,6 +260,7 @@ void AudioOutputController::DoClose() { |
| DCHECK(message_loop_->BelongsToCurrentThread()); |
| if (state_ != kClosed) { |
| + DCHECK(!divert_glue_); |
| DoStopCloseAndClearStream(NULL); |
| sync_reader_->Close(); |
| state_ = kClosed; |
| @@ -294,32 +293,105 @@ void AudioOutputController::DoReportError(int code) { |
| handler_->OnError(this, code); |
| } |
| -int AudioOutputController::OnMoreData(AudioBus* dest, |
| - AudioBuffersState buffers_state) { |
| - return OnMoreIOData(NULL, dest, buffers_state); |
| +AudioOutputController::Glue::Glue(AudioOutputController* controller, |
| + SyncReader* source) |
| + : controller_(controller), |
| + source_(source), |
| + is_reading_from_source_(false) { |
| +} |
| + |
| +AudioOutputController::Glue::~Glue() { |
| +} |
| + |
| +void AudioOutputController::Glue::PassSoon(const scoped_refptr<Glue>& to) { |
|
DaleCurtis
2012/12/12 01:29:15
This is all really complicated and I don't quite f
miu
2012/12/12 03:13:39
Main reason: Per our hallway conversation, "Though
DaleCurtis
2012/12/12 20:38:38
How come you don't do something similar to OnDevic
miu
2012/12/13 01:22:51
Done.
Good call, Dale! :-) Per hallway conversa
|
| + DCHECK_NE(this, to); |
| + |
| + SyncReader* pass_me; |
| + { |
| + base::AutoLock auto_lock(lock_); |
| + |
| + // Two reasons to delay a pass: 1) another thread is currently reading from |
| + // source_; 2) this instance is hasn't received source_ yet. |
| + if (is_reading_from_source_ || !source_) { |
| + DCHECK(delayed_pass_.is_null()) << "BUG: Attempt to pass twice."; |
| + delayed_pass_ = base::Bind(&Glue::DoDelayedPassWithLockHeld, this, to); |
| + return; |
| + } |
| + |
| + pass_me = source_; |
| + source_ = NULL; |
| + } |
| + |
| + // Normal path: Pass source_ immediately. |
| + to->ReceiveSource(pass_me); |
| } |
| -int AudioOutputController::OnMoreIOData(AudioBus* source, |
| - AudioBus* dest, |
| - AudioBuffersState buffers_state) { |
| +void AudioOutputController::Glue::ReceiveSource(SyncReader* source) { |
| + base::AutoLock auto_lock(lock_); |
| + |
| + // Accept the audio data source. |
| + DCHECK(!source_) |
| + << "BUG: Attempt to receive while already holding a SyncReader."; |
| + source_ = source; |
| + |
| + // Execute a delayed pass if PassSoon() scheduled one. |
| + if (!delayed_pass_.is_null()) { |
| + delayed_pass_.Run(); |
| + delayed_pass_.Reset(); |
| + } |
| +} |
| + |
| +void AudioOutputController::Glue::DoDelayedPassWithLockHeld( |
| + const scoped_refptr<Glue>& to) { |
| + lock_.AssertAcquired(); |
| + |
| + // Pass the SyncReader to another Glue instance, but do not allow the current |
| + // thread to block on this action. |
| + controller_->message_loop_->PostTask( |
| + FROM_HERE, base::Bind(&Glue::ReceiveSource, to, source_)); |
| + source_ = NULL; |
| +} |
| + |
| +int AudioOutputController::Glue::OnMoreData(AudioBus* dest_bus, |
| + AudioBuffersState buffers_state) { |
| + return OnMoreIOData(NULL, dest_bus, buffers_state); |
| +} |
| + |
| +int AudioOutputController::Glue::OnMoreIOData(AudioBus* source_bus, |
| + AudioBus* dest_bus, |
| + AudioBuffersState buffers_state) { |
| TRACE_EVENT0("audio", "AudioOutputController::OnMoreIOData"); |
| + // Attempt to read from the SyncReader. While reading, do not hold lock_. |
| + // After reading, acquire the lock again; and execute a delayed pass, if |
| + // scheduled. |
| { |
| - // Check state and do nothing if we are not playing. |
| - // We are on the hardware audio thread, so lock is needed. |
| base::AutoLock auto_lock(lock_); |
| - if (state_ != kPlaying) { |
| - return 0; |
| + if (source_ && controller_->state_ == kPlaying) { |
| + is_reading_from_source_ = true; |
| + int frames_read; |
| + { |
| + base::AutoUnlock unlock_while_reading(lock_); |
| + frames_read = source_->Read(source_bus, dest_bus); |
| + source_->UpdatePendingBytes( |
| + buffers_state.total_bytes() + |
| + frames_read * controller_->params_.GetBytesPerFrame()); |
| + } |
| + is_reading_from_source_ = false; |
| + if (!delayed_pass_.is_null()) { |
| + delayed_pass_.Run(); |
| + delayed_pass_.Reset(); |
| + } |
| + return frames_read; |
| } |
| } |
| - int frames = sync_reader_->Read(source, dest); |
| - sync_reader_->UpdatePendingBytes( |
| - buffers_state.total_bytes() + frames * params_.GetBytesPerFrame()); |
| - return frames; |
| + // We haven't received the SyncReader yet, so fill the destination with zeros. |
| + dest_bus->Zero(); |
| + return dest_bus->frames(); |
| } |
| -void AudioOutputController::WaitTillDataReady() { |
| +void AudioOutputController::Glue::WaitTillDataReady() { |
| #if defined(OS_WIN) || defined(OS_MACOSX) |
| base::Time start = base::Time::Now(); |
| // Wait for up to 1.5 seconds for DataReady(). 1.5 seconds was chosen because |
| @@ -327,8 +399,10 @@ void AudioOutputController::WaitTillDataReady() { |
| // minimum supported sample rate: 4096 / 3000 = ~1.4 seconds. Even a client |
| // expecting real time playout should be able to fill in this time. |
| const base::TimeDelta max_wait = base::TimeDelta::FromMilliseconds(1500); |
| - while (!sync_reader_->DataReady() && |
| + base::AutoLock auto_lock(lock_); |
| + while (source_ && !source_->DataReady() && |
| ((base::Time::Now() - start) < max_wait)) { |
| + base::AutoUnlock unlock_while_yielding(lock_); |
| base::PlatformThread::YieldCurrentThread(); |
| } |
| #else |
| @@ -337,10 +411,10 @@ void AudioOutputController::WaitTillDataReady() { |
| #endif |
| } |
| -void AudioOutputController::OnError(AudioOutputStream* stream, int code) { |
| +void AudioOutputController::Glue::OnError(AudioOutputStream* stream, int code) { |
| // Handle error on the audio controller thread. |
| - message_loop_->PostTask(FROM_HERE, base::Bind( |
| - &AudioOutputController::DoReportError, this, code)); |
| + controller_->message_loop_->PostTask(FROM_HERE, base::Bind( |
| + &AudioOutputController::DoReportError, controller_, code)); |
| } |
| void AudioOutputController::DoStopCloseAndClearStream(WaitableEvent* done) { |
| @@ -353,7 +427,6 @@ void AudioOutputController::DoStopCloseAndClearStream(WaitableEvent* done) { |
| stream_ = NULL; |
| audio_manager_->RemoveOutputDeviceChangeListener(this); |
| - audio_manager_ = NULL; |
| weak_this_.InvalidateWeakPtrs(); |
| } |
| @@ -369,13 +442,11 @@ void AudioOutputController::OnDeviceChange() { |
| // We should always have a stream by this point. |
| CHECK(stream_); |
| - // Preserve the original state and shutdown the stream. |
| - State original_state = state_; |
| - stream_->Stop(); |
| - stream_->Close(); |
| - stream_ = NULL; |
| + // Preserve the original state. |
| + const State original_state = state_; |
| - // Recreate the stream, exit if we ran into an error. |
| + // Recreate the stream (DoCreate() will first shut down an existing stream). |
| + // Exit if we ran into an error. |
| state_ = kRecreating; |
| DoCreate(); |
| if (!stream_ || state_ == kError) |
| @@ -385,6 +456,7 @@ void AudioOutputController::OnDeviceChange() { |
| switch (original_state) { |
| case kStarting: |
| case kPlaying: |
| + DoSetVolume(volume_); |
| DoPlay(); |
| return; |
| case kCreated: |
| @@ -397,4 +469,18 @@ void AudioOutputController::OnDeviceChange() { |
| } |
| } |
| +AudioOutputStream::AudioSourceCallback* AudioOutputController::Divert() { |
| + DCHECK(!divert_glue_) << "BUG: Already diverted!"; |
| + divert_glue_ = new Glue(this, NULL); |
| + stream_glue_->PassSoon(divert_glue_); |
| + return divert_glue_; |
| +} |
| + |
| +void AudioOutputController::Revert( |
| + AudioOutputStream::AudioSourceCallback* asc) { |
| + DCHECK_EQ(divert_glue_, asc); |
| + divert_glue_->PassSoon(stream_glue_); |
| + divert_glue_ = NULL; |
| +} |
| + |
| } // namespace media |