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..68145bdfdf385f0d1ce432287c1d073af688231e 100644 |
--- a/media/audio/audio_output_controller.cc |
+++ b/media/audio/audio_output_controller.cc |
@@ -24,19 +24,76 @@ namespace media { |
const int AudioOutputController::kPollNumAttempts = 3; |
const int AudioOutputController::kPollPauseInMilliseconds = 3; |
+// An AudioSourceCallback that allows passing around the underlying SyncReader |
+// data source between consumers of the data. It does this in a "pass or |
+// delayed pass" scheme that ensures synchronization between timing-sensitive |
+// threads is effectively non-blocking. In addition, SourceSharingCallback |
+// ensures that only one consumer is pulling data from the SyncReader at a time. |
+class AudioOutputController::SourceSharingCallback |
+ : public AudioOutputStream::AudioSourceCallback, |
+ public base::RefCountedThreadSafe<SourceSharingCallback> { |
+ public: |
+ SourceSharingCallback(AudioOutputController* controller, SyncReader* source); |
+ |
+ // Pass the SyncReader to another instance as soon as possible. |
+ void PassSoon(const scoped_refptr<SourceSharingCallback>& receiver); |
+ |
+ // AudioSourceCallback implementation. |
+ virtual int OnMoreData(AudioBus* dest_bus, |
+ AudioBuffersState buffers_state) OVERRIDE; |
+ virtual int OnMoreIOData(AudioBus* source_bus, |
+ AudioBus* dest_bus, |
+ AudioBuffersState buffers_state) OVERRIDE; |
+ virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE; |
+ virtual void WaitTillDataReady() OVERRIDE; |
+ |
+ private: |
+ friend class base::RefCountedThreadSafe<SourceSharingCallback>; |
+ virtual ~SourceSharingCallback(); |
+ |
+ // Take over reading from |source|. |
+ void ReceiveSource(SyncReader* source); |
+ |
+ // Executes a delayed pass (i.e., a pass that was delayed because source_ was |
+ // in-use at the time PassSoon() was called). |
+ void DoDelayedPassWithLockHeld( |
+ const scoped_refptr<SourceSharingCallback>& to); |
+ |
+ // Access to parent/owner instance. |
+ AudioOutputController* const controller_; |
+ |
+ // Guards passing of SyncReader to another SourceSharingCallback instance. |
+ // The implementation should acquire lock_ for only very short operations |
+ // since audio threads are timing-sensitive. |
+ base::Lock lock_; |
+ |
+ // Audio data source, or NULL if none yet. |
+ SyncReader* source_; |
+ |
+ // True while source_ is in-use without lock_ held. |
+ bool is_reading_from_source_; |
+ |
+ // When !delayed_pass_.is_null(), source_ is scheduled to be passed as soon as |
+ // possible. |
+ base::Closure delayed_pass_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(SourceSharingCallback); |
+}; |
+ |
AudioOutputController::AudioOutputController(AudioManager* audio_manager, |
EventHandler* handler, |
const AudioParameters& params, |
SyncReader* sync_reader) |
: audio_manager_(audio_manager), |
+ params_(params), |
handler_(handler), |
stream_(NULL), |
+ callback_for_stream_(new SourceSharingCallback(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 +127,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 +192,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 +269,7 @@ void AudioOutputController::StartStream() { |
state_ = kPlaying; |
// We start the AudioOutputStream lazily. |
- stream_->Start(this); |
+ stream_->Start(callback_for_stream_); |
// Tell the event handler that we are now playing. |
handler_->OnPlaying(this); |
@@ -262,6 +316,7 @@ void AudioOutputController::DoClose() { |
DCHECK(message_loop_->BelongsToCurrentThread()); |
if (state_ != kClosed) { |
+ DCHECK(!callback_for_divert_); |
DoStopCloseAndClearStream(NULL); |
sync_reader_->Close(); |
state_ = kClosed; |
@@ -294,32 +349,108 @@ void AudioOutputController::DoReportError(int code) { |
handler_->OnError(this, code); |
} |
-int AudioOutputController::OnMoreData(AudioBus* dest, |
- AudioBuffersState buffers_state) { |
- return OnMoreIOData(NULL, dest, buffers_state); |
+AudioOutputController::SourceSharingCallback::SourceSharingCallback( |
+ AudioOutputController* controller, SyncReader* source) |
+ : controller_(controller), |
+ source_(source), |
+ is_reading_from_source_(false) { |
+} |
+ |
+AudioOutputController::SourceSharingCallback::~SourceSharingCallback() { |
+} |
+ |
+void AudioOutputController::SourceSharingCallback::PassSoon( |
+ const scoped_refptr<SourceSharingCallback>& to) { |
+ 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( |
+ &SourceSharingCallback::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::SourceSharingCallback::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::SourceSharingCallback::DoDelayedPassWithLockHeld( |
+ const scoped_refptr<SourceSharingCallback>& to) { |
+ lock_.AssertAcquired(); |
+ |
+ // Pass the SyncReader to another SourceSharingCallback instance, but do not |
+ // allow the current thread to block on this action. |
+ controller_->message_loop_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&SourceSharingCallback::ReceiveSource, to, source_)); |
+ source_ = NULL; |
+} |
+ |
+int AudioOutputController::SourceSharingCallback::OnMoreData( |
+ AudioBus* dest_bus, AudioBuffersState buffers_state) { |
+ return OnMoreIOData(NULL, dest_bus, buffers_state); |
+} |
+ |
+int AudioOutputController::SourceSharingCallback::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::SourceSharingCallback::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 +458,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 +470,11 @@ void AudioOutputController::WaitTillDataReady() { |
#endif |
} |
-void AudioOutputController::OnError(AudioOutputStream* stream, int code) { |
+void AudioOutputController::SourceSharingCallback::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 +487,6 @@ void AudioOutputController::DoStopCloseAndClearStream(WaitableEvent* done) { |
stream_ = NULL; |
audio_manager_->RemoveOutputDeviceChangeListener(this); |
- audio_manager_ = NULL; |
weak_this_.InvalidateWeakPtrs(); |
} |
@@ -369,13 +502,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 +516,7 @@ void AudioOutputController::OnDeviceChange() { |
switch (original_state) { |
case kStarting: |
case kPlaying: |
+ DoSetVolume(volume_); |
DoPlay(); |
return; |
case kCreated: |
@@ -397,4 +529,18 @@ void AudioOutputController::OnDeviceChange() { |
} |
} |
+AudioOutputStream::AudioSourceCallback* AudioOutputController::Divert() { |
+ DCHECK(!callback_for_divert_) << "BUG: Already diverted!"; |
+ callback_for_divert_ = new SourceSharingCallback(this, NULL); |
+ callback_for_stream_->PassSoon(callback_for_divert_); |
+ return callback_for_divert_; |
+} |
+ |
+void AudioOutputController::Revert( |
+ AudioOutputStream::AudioSourceCallback* asc) { |
+ DCHECK_EQ(callback_for_divert_, asc); |
+ callback_for_divert_->PassSoon(callback_for_stream_); |
+ callback_for_divert_ = NULL; |
+} |
+ |
} // namespace media |