Index: services/media/audio/audio_output.cc |
diff --git a/services/media/audio/audio_output.cc b/services/media/audio/audio_output.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..be2b89556d7ffb242000b18660b01bf3c97c15b3 |
--- /dev/null |
+++ b/services/media/audio/audio_output.cc |
@@ -0,0 +1,215 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "base/bind.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+ |
+#include "services/media/audio/audio_output.h" |
+#include "services/media/audio/audio_output_manager.h" |
+#include "services/media/audio/audio_track_to_output_link.h" |
+ |
+namespace mojo { |
+namespace media { |
+namespace audio { |
+ |
+// Function used in a base::Closure to defer the final part of a shutdown task |
+// to the main event loop. |
+static void FinishShutdownSelf(AudioOutputManager* manager, |
+ AudioOutputWeakPtr weak_output) { |
+ auto output = weak_output.lock(); |
+ if (output) { |
+ manager->ShutdownOutput(output); |
+ } |
+} |
+ |
+AudioOutput::AudioOutput(AudioOutputManager* manager) |
+ : manager_(manager) { |
+ DCHECK(manager_); |
+} |
+ |
+AudioOutput::~AudioOutput() { |
+ DCHECK(!task_runner_ && shutting_down_); |
+} |
+ |
+MediaResult AudioOutput::AddTrackLink(AudioTrackToOutputLinkPtr link) { |
+ MediaResult res = InitializeLink(link); |
+ |
+ if (res == MediaResult::OK) { |
+ base::AutoLock lock(processing_lock_); |
+ |
+ // Assert that we are the output in this link. |
+ DCHECK(this == link->GetOutput().get()); |
jeffbrown
2015/11/04 23:43:32
Not sure I understand why this has to happen insid
johngro
2015/11/06 02:20:24
Link initialization happens outside of the scope o
|
+ |
+ if (shutting_down_) { |
+ return MediaResult::SHUTTING_DOWN; |
+ } |
+ |
+ auto insert_result = links_.emplace(link); |
+ DCHECK(insert_result.second); |
+ } else { |
+ // TODO(johngro): Output didn't like this track for some reason... Should |
+ // probably log something about this. |
+ } |
+ |
+ return res; |
+} |
+ |
+MediaResult AudioOutput::RemoveTrackLink( |
+ const AudioTrackToOutputLinkPtr& link) { |
+ base::AutoLock lock(processing_lock_); |
+ |
+ if (shutting_down_) { |
+ return MediaResult::SHUTTING_DOWN; |
+ } |
+ |
+ auto iter = links_.find(link); |
+ if (iter == links_.end()) { |
+ return MediaResult::NOT_FOUND; |
+ } |
+ |
+ links_.erase(iter); |
+ return MediaResult::OK; |
+} |
+ |
+MediaResult AudioOutput::Init() { |
+ return MediaResult::OK; |
+} |
+ |
+void AudioOutput::Cleanup() { |
+} |
+ |
+MediaResult AudioOutput::InitializeLink(const AudioTrackToOutputLinkPtr& link) { |
+ DCHECK(link); |
+ return MediaResult::OK; |
+} |
+ |
+void AudioOutput::ScheduleCallback(LocalTime when) { |
+ base::AutoLock lock(shutdown_lock_); |
+ |
+ // If we are in the process of shutting down, then we are no longer permitted |
+ // to schedule callbacks. |
jeffbrown
2015/11/04 23:43:32
There's a race condition here given that shutdown
johngro
2015/11/06 02:20:24
No, I do not think that there is. I took care to
|
+ if (shutting_down_) { |
+ DCHECK(!task_runner_); |
+ return; |
+ } |
+ DCHECK(task_runner_); |
+ |
+ // TODO(johngro): Someday, if there is ever a way to schedule delayed tasks |
+ // with absolute time, or with resolution better than microseconds, do so. |
+ // Until then figure out the relative time for scheduling the task and do so. |
+ LocalTime now = LocalClock::now(); |
+ base::TimeDelta sched_time = (now > when) |
+ ? base::TimeDelta::FromMicroseconds(0) |
+ : base::TimeDelta::FromMicroseconds( |
+ local_time::to_usec<int64_t>(when - now)); |
+ |
+ task_runner_->PostNonNestableDelayedTask( |
+ FROM_HERE, |
+ base::Bind(&ProcessThunk, weak_self_), |
+ sched_time); |
+} |
+ |
+void AudioOutput::ShutdownSelf() { |
+ // If we are not already in the process of shutting down, send a message to |
+ // the main message loop telling it to complete the shutdown process. |
+ if (!BeginShutdown()) { |
+ DCHECK(manager_); |
+ manager_->ScheduleMessageLoopTask( |
+ FROM_HERE, |
+ base::Bind(&FinishShutdownSelf, manager_, weak_self_)); |
+ } |
+} |
+ |
+void AudioOutput::ProcessThunk(AudioOutputWeakPtr weak_output) { |
+ // If we are still around by the time this callback fires, enter the procesing |
+ // lock and dispatch to our derived class's implementation. |
+ auto output = weak_output.lock(); |
+ if (output) { |
+ base::AutoLock lock(output->processing_lock_); |
+ output->Process(); |
+ } |
+} |
+ |
+MediaResult AudioOutput::Init( |
+ const AudioOutputPtr& self, |
+ scoped_refptr<base::SequencedTaskRunner> task_runner) { |
+ DCHECK(this == self.get()); |
+ DCHECK(task_runner); |
+ |
+ // If our derived class failed to initialize, don't bother to hold onto the |
+ // state we will need drive our callback engine. Begin the process of |
+ // shutting ourselves down, the output manager will eventually finish the job |
+ // for us. |
+ MediaResult res = Init(); |
+ if (res != MediaResult::OK) { |
+ ShutdownSelf(); |
+ return res; |
+ } |
+ |
+ // Stash our callback state and schedule an immediate callback to get things |
+ // running. |
+ task_runner_ = task_runner; |
+ weak_self_ = self; |
+ task_runner_->PostNonNestableTask(FROM_HERE, |
+ base::Bind(&ProcessThunk, weak_self_)); |
+ |
+ return MediaResult::OK; |
+} |
+ |
+bool AudioOutput::BeginShutdown() { |
+ // Start the process of shutting down if we have not already. This method may |
+ // be called from either a processing context, or from the audio output |
+ // manager. After it finishes, any pending processing callbacks will have |
+ // been nerfed, although there may still be callbacks in flight. |
+ base::AutoLock lock(shutdown_lock_); |
+ |
+ if (shutting_down_) { return true; } |
+ |
+ shutting_down_ = true; |
+ task_runner_ = nullptr; |
+ |
+ return false; |
+} |
+ |
+void AudioOutput::Shutdown() { |
+ if (shut_down_) { return; } |
+ |
+ // TODO(johngro): Assert that we are running on the audio server's main |
+ // message loop thread. |
+ |
+ // Make sure no new callbacks can be generated, and that pending callbacks |
+ // have been nerfed. |
+ BeginShutdown(); |
+ |
+ // Synchronize with any callbacks in flight. By acquiring and releasing the |
+ // processing lock, we are guaranteed that we have no callbacks which are in |
+ // the middle of processing, and that any pending callbacks will be nerfed. |
+ // It is safe to destroy this audio output at any point in time after this. |
+ processing_lock_.Acquire(); |
+ processing_lock_.Release(); |
jeffbrown
2015/11/04 23:43:32
Obviously this blocks the main thread but I have t
johngro
2015/11/06 02:20:24
I believe that I have already address this in resp
|
+ |
+ // Unlink ourselves from all of our tracks. Then go ahead and clear the track |
+ // set. |
+ for (const auto& link : links_) { |
+ DCHECK(link); |
+ AudioTrackImplPtr track = link->GetTrack(); |
+ if (track) { |
+ track->RemoveOutput(link); |
+ } |
+ } |
+ links_.clear(); |
+ |
+ // Give our derived class a chance to clean up its resources. |
+ Cleanup(); |
+ |
+ // We are now completely shut down. The only reason we have this flag is to |
+ // make sure that Shutdown is idempotent. |
+ shut_down_ = true; |
+} |
+ |
+} // namespace audio |
+} // namespace media |
+} // namespace mojo |
+ |