blob: 98fbc4453ccf3c951cdc0419af748dabcd014408 [file] [log] [blame]
// 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());
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.
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_);
// Make sure that we are not in the process of cleaning up before we start
// processing.
if (!output->shutting_down_) {
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();
// 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