Chromium Code Reviews| 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 |
| + |