Add an initial revision of an audio server.
Add a functional skeleton for a Motown audio server. Currently, the server
allows clients to create and configure AudioTracks, and push packets of audio
information to the tracks. The mixer will mix for multiple outputs, and there
is a many to many relationship between tracks and outputs. Right now, there is
only a single, generic, audio output implementation whose job in life is to
proved backpressure for audio track pipelines when no other outputs are present.
R=jeffbrown@google.com, jamesr@chromium.org
BUG=
Review URL: https://codereview.chromium.org/1424933002 .
diff --git a/services/media/BUILD.gn b/services/media/BUILD.gn
index 718d8db..042c359 100644
--- a/services/media/BUILD.gn
+++ b/services/media/BUILD.gn
@@ -4,6 +4,7 @@
group("media") {
deps = [
+ "//services/media/audio",
"//services/media/common",
]
}
diff --git a/services/media/audio/BUILD.gn b/services/media/audio/BUILD.gn
new file mode 100644
index 0000000..7cddf1f
--- /dev/null
+++ b/services/media/audio/BUILD.gn
@@ -0,0 +1,40 @@
+# 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.
+
+import("//mojo/public/mojo_application.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+group("audio") {
+ deps = [
+ ":audio_server",
+ ]
+}
+
+mojo_native_application("audio_server") {
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/services/media/audio/interfaces",
+ "//mojo/services/media/common/cpp",
+ "//mojo/services/media/common/interfaces",
+ "//services/media/common",
+ ]
+
+ sources = [
+ "audio_output.cc",
+ "audio_output_manager.cc",
+ "audio_pipe.cc",
+ "audio_server_app.cc",
+ "audio_server_impl.cc",
+ "audio_track_impl.cc",
+ "audio_track_to_output_link.cc",
+ "platform/generic/mixer.cc",
+ "platform/generic/mixers/no_op.cc",
+ "platform/generic/mixers/point_sampler.cc",
+ "platform/generic/standard_output_base.cc",
+ "platform/generic/throttle_output.cc",
+ ]
+
+ libs = []
+}
diff --git a/services/media/audio/audio_output.cc b/services/media/audio/audio_output.cc
new file mode 100644
index 0000000..be2b895
--- /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());
+
+ 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_);
+ 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
+
diff --git a/services/media/audio/audio_output.h b/services/media/audio/audio_output.h
new file mode 100644
index 0000000..0a0cce8
--- /dev/null
+++ b/services/media/audio/audio_output.h
@@ -0,0 +1,174 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_AUDIO_OUTPUT_H_
+#define SERVICES_MEDIA_AUDIO_AUDIO_OUTPUT_H_
+
+#include <deque>
+#include <memory>
+#include <set>
+
+#include "base/callback.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "mojo/services/media/common/cpp/local_time.h"
+#include "services/media/audio/audio_pipe.h"
+#include "services/media/audio/audio_track_impl.h"
+#include "services/media/audio/fwd_decls.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class AudioOutput {
+ public:
+ virtual ~AudioOutput();
+
+ // AddTrack/RemoveTrack
+ //
+ // Adds or removes a track to/from the set of current set of tracks serviced
+ // by this output. Called only from the main message loop. Obtains the
+ // processing_lock and may block for the time it takes the derived class to
+ // run its processing task if the task is in progress when the method was
+ // called.
+ MediaResult AddTrackLink(AudioTrackToOutputLinkPtr link);
+ MediaResult RemoveTrackLink(const AudioTrackToOutputLinkPtr& link);
+
+ protected:
+ explicit AudioOutput(AudioOutputManager* manager);
+
+ //////////////////////////////////////////////////////////////////////////////
+ //
+ // Methods which may be implemented by derived classes to customize behavior.
+ //
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Init
+ //
+ // Called during startup on the AudioServer's main message loop thread. No
+ // locks are being held at this point. Derived classes should allocate their
+ // hardware resources and initialize any internal state. Return
+ // MediaResult::OK if everything is good and the output is ready to do work.
+ virtual MediaResult Init();
+
+ // Cleanup
+ //
+ // Called at shutdown on the AudioServer's main message loop thread to allow
+ // derived classes to clean up any allocated resources. All pending
+ // processing callbacks have either been nerfed or run till completion. All
+ // AudioTrack tracks have been disconnected. No locks are being held.
+ virtual void Cleanup();
+
+ // Process
+ //
+ // Called from within the context of the processing lock any time a scheduled
+ // processing callback fires. One callback will be automatically scheduled at
+ // the end of initialization. After that, derived classes are responsible for
+ // scheduling all subsequent callbacks to keep the engine running.
+ //
+ // Note: Process callbacks execute on one of the threads from the
+ // AudioOutputManager's base::SequencedWorkerPool. While successive callbacks
+ // may not execute on the same thread, they are guaranteed to execute in a
+ // serialized fashion.
+ virtual void Process() = 0;
+
+ // InitializeLink
+ //
+ // Called on the AudioServer's main message loop any time a track is being
+ // added to this output. Outputs should allocate and initialize any
+ // bookkeeping they will need to perform mixing on behalf of the newly added
+ // track.
+ //
+ // @return MediaResult::OK if initialization succeeded, or an appropriate
+ // error code otherwise.
+ virtual MediaResult InitializeLink(const AudioTrackToOutputLinkPtr& link);
+
+ //////////////////////////////////////////////////////////////////////////////
+ //
+ // Methods which may used by derived classes from within the context of a
+ // processing callback. Note; since these methods are intended to be called
+ // from the within a process callback, the processing_lock will always be held
+ // when they are called.
+ //
+
+ // ScheduleCallback
+ //
+ // Schedule a processing callback at the specified absolute time on the local
+ // clock.
+ void ScheduleCallback(LocalTime when);
+
+ // ShutdownSelf
+ //
+ // Kick off the process of shooting ourselves in the head. Note, after this
+ // method has been called, no new callbacks may be scheduled. As soon as the
+ // main message loop finds out about our shutdown request, it will complete
+ // the process of shutting us down, unlinking us from our tracks and calling
+ // the Cleanup method.
+ void ShutdownSelf();
+
+ // shutting_down
+ //
+ // Check the shutting down flag. Only the base class may modify the flag, but
+ // derived classes are free to check it at any time.
+ inline bool shutting_down() const { return shutting_down_; }
+
+ // TODO(johngro): Order this by priority. Figure out how we are going to be
+ // able to quickly find a track with a specific priority in order to optimize
+ // changes of priority. Perhaps uniquify the priorities by assigning a
+ // sequence number to the lower bits (avoiding collisions when assigning new
+ // priorities will be the trick).
+ //
+ // Right now, we have no priorities, so this is just a set of track/output
+ // links.
+ AudioTrackToOutputLinkSet links_;
+ AudioOutputManager* manager_;
+
+ private:
+ // It's always nice when you manager is also your friend. Seriously though,
+ // the AudioOutputManager gets to call Init and Shutown, no one else
+ // (including derived classes) should be able to.
+ friend class AudioOutputManager;
+
+ // Thunk used to schedule delayed processing tasks on our task_runner.
+ static void ProcessThunk(AudioOutputWeakPtr weak_output);
+
+ // Called from the AudioOutputManager after an output has been created.
+ // Gives derived classes a chance to set up hardware, then sets up the
+ // machinery needed for scheduling processing tasks and schedules the first
+ // processing callback immediately in order to get the process running.
+ MediaResult Init(const AudioOutputPtr& self,
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+ // Called from Shutdown (main message loop) and ShutdowSelf (processing
+ // context). Starts the process of shutdown, preventing new processing tasks
+ // from being scheduled, and nerfing any tasks in flight.
+ //
+ // @return true if this call just kicked off the process of shutting down,
+ // false otherwise.
+ bool BeginShutdown();
+
+ // Called from the AudioOutputManager on the main message loop
+ // thread. Makes certain that the process of shutdown has started,
+ // synchronizes with any processing tasks which were executing at the time,
+ // then finishes the shutdown process by unlinking from all tracks and
+ // cleaning up all resources.
+ void Shutdown();
+
+ base::Lock processing_lock_;
+ base::Lock shutdown_lock_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ AudioOutputWeakPtr weak_self_;
+
+ // TODO(johngro): Eliminate the shutting down flag and just use the
+ // task_runner_'s nullness for this test?
+ volatile bool shutting_down_ = false;
+ volatile bool shut_down_ = false;
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_AUDIO_OUTPUT_H_
+
diff --git a/services/media/audio/audio_output_manager.cc b/services/media/audio/audio_output_manager.cc
new file mode 100644
index 0000000..59e4ff3
--- /dev/null
+++ b/services/media/audio/audio_output_manager.cc
@@ -0,0 +1,150 @@
+// 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 <string>
+
+#include "services/media/audio/audio_output.h"
+#include "services/media/audio/audio_output_manager.h"
+#include "services/media/audio/audio_server_impl.h"
+#include "services/media/audio/audio_track_to_output_link.h"
+#include "services/media/audio/platform/generic/throttle_output.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+static constexpr size_t THREAD_POOL_SZ = 2;
+static const std::string THREAD_PREFIX("AudioMixer");
+
+AudioOutputManager::AudioOutputManager(AudioServerImpl* server)
+ : server_(server) {
+}
+
+AudioOutputManager::~AudioOutputManager() {
+ Shutdown();
+ DCHECK_EQ(outputs_.size(), 0u);
+ DCHECK(!thread_pool_);
+}
+
+MediaResult AudioOutputManager::Init() {
+ // Step #1: Initialize the mixing thread pool.
+ //
+ // TODO(johngro): make the thread pool size proportional to the maximum
+ // number of cores available in the system.
+ //
+ // TODO(johngro): make sure that the threads are executed at an elevated
+ // priority, not the default priority.
+ thread_pool_ = new base::SequencedWorkerPool(THREAD_POOL_SZ, THREAD_PREFIX);
+
+ // Step #2: Instantiate all of the built-in audio output devices.
+ //
+ // TODO(johngro): Come up with a better way of doing this based on our
+ // platform. Right now, we just create some hardcoded default outputs and
+ // leave it at that.
+ outputs_.emplace(audio::ThrottleOutput::New(this));
+
+ // Step #3: Being monitoring for plug/unplug events for pluggable audio
+ // output devices.
+ //
+ // TODO(johngro): Implement step #3. Right now, the details are behind
+ // hot-plug monitoring are TBD, so the feature is not implemented.
+
+ // Step #4: Attempt to initialize each of the audio outputs we have created,
+ // then kick off the callback engine for each of them.
+ for (auto iter = outputs_.begin(); iter != outputs_.end(); ) {
+ const AudioOutputPtr& output = *iter;
+ auto tmp = iter++;
+ DCHECK(output);
+
+ // Create a sequenced task runner for this output. It will be used by the
+ // output to schedule jobs (such as mixing) on the thread pool.
+ scoped_refptr<base::SequencedTaskRunner> task_runner =
+ thread_pool_->GetSequencedTaskRunnerWithShutdownBehavior(
+ thread_pool_->GetSequenceToken(),
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
+
+ MediaResult res = output->Init(output, task_runner);
+ if (res != MediaResult::OK) {
+ // TODO(johngro): Probably should log something about this, assuming that
+ // the output has not already.
+ outputs_.erase(tmp);
+ }
+ }
+
+ return MediaResult::OK;
+}
+
+void AudioOutputManager::Shutdown() {
+ // Are we already shutdown (or were we never successfully initialized?)
+ if (thread_pool_ == nullptr) {
+ DCHECK_EQ(outputs_.size(), 0u);
+ return;
+ }
+
+ // Step #1: Stop monitoringing plug/unplug events. We are shutting down and
+ // no longer care about outputs coming and going.
+ //
+ // TODO(johngro): Implement step #1. Right now, the details are behind
+ // hot-plug monitoring are TBD, so the feature is not implemented.
+
+ // Step #2: Shut down each currently active output in the system. It is
+ // possible for this to take a bit of time as outputs release their hardware,
+ // but it should not take long.
+ for (const auto& output_ptr : outputs_) {
+ output_ptr->Shutdown();
+ }
+ outputs_.clear();
+
+ // Step #3: Shutdown and release our thread pool. Since we have shut down all
+ // of our outputs, any pending tasks left in the task runner are now no-ops,
+ // so it does not matter that the task runner is going to cancel them all
+ // (instead of blocking) when we shut it down.
+ thread_pool_->Shutdown();
+ thread_pool_ = nullptr;
+}
+
+void AudioOutputManager::ShutdownOutput(AudioOutputPtr output) {
+ // No one should be calling this method if we have been shut down (or never
+ // successfully started).
+ DCHECK(thread_pool_);
+
+ auto iter = outputs_.find(output);
+ if (iter != outputs_.end()) {
+ output->Shutdown();
+ outputs_.erase(iter);
+ }
+}
+
+void AudioOutputManager::SelectOutputsForTrack(AudioTrackImplPtr track) {
+ // TODO(johngro): Someday, base this on policy. For now, every track gets
+ // assigned to every output in the system.
+ DCHECK(track);
+
+ // TODO(johngro): Add some way to assert that we are executing on the main
+ // message loop thread.
+
+ for (auto output : outputs_) {
+ auto link = AudioTrackToOutputLink::New(track, output);
+ DCHECK(output);
+ DCHECK(link);
+
+ // If we cannot add this link to the output, it's because the output is in
+ // the process of shutting down (we didn't want to hang out with that guy
+ // anyway)
+ if (output->AddTrackLink(link) == MediaResult::OK) {
+ track->AddOutput(link);
+ }
+ }
+}
+
+void AudioOutputManager::ScheduleMessageLoopTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ DCHECK(server_);
+ server_->ScheduleMessageLoopTask(from_here, task);
+}
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
diff --git a/services/media/audio/audio_output_manager.h b/services/media/audio/audio_output_manager.h
new file mode 100644
index 0000000..2599ee5
--- /dev/null
+++ b/services/media/audio/audio_output_manager.h
@@ -0,0 +1,109 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_AUDIO_OUTPUT_MANAGER_H_
+#define SERVICES_MEDIA_AUDIO_AUDIO_OUTPUT_MANAGER_H_
+
+#include <set>
+
+#include "base/synchronization/lock.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "mojo/services/media/common/interfaces/media_common.mojom.h"
+#include "mojo/services/media/common/interfaces/media_pipe.mojom.h"
+#include "services/media/audio/audio_output.h"
+#include "services/media/audio/fwd_decls.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class AudioOutputManager {
+ public:
+ explicit AudioOutputManager(AudioServerImpl* server);
+ ~AudioOutputManager();
+
+ // Initialize the output manager. Called from the service implementation,
+ // once, at startup time. Should...
+ //
+ // 1) Initialize the mixing thread pool.
+ // 2) Instantiate all of the built-in audio output devices.
+ // 3) Being monitoring for plug/unplug events for pluggable audio output
+ // devices.
+ MediaResult Init();
+
+ // Blocking call. Called by the service, once, when it is time to shutdown
+ // the service implementation. While this function is blocking, it must never
+ // block for long. Our process is going away; this is our last chance to
+ // perform a clean shutdown. If an unclean shutdown must be performed in
+ // order to implode in a timely fashion, so be it.
+ //
+ // Shutdown must be idempotent, and safe to call from the output manager's
+ // destructor, although it should never be necessary to do so. If the
+ // shutdown called from the destructor has to do real work, something has gone
+ // Very Seriously Wrong.
+ void Shutdown();
+
+ // Select the initial set of outputs for a track which has just been
+ // configured.
+ void SelectOutputsForTrack(AudioTrackImplPtr track);
+
+ // Schedule a closure to run on our encapsulating server's main message loop.
+ void ScheduleMessageLoopTask(const tracked_objects::Location& from_here,
+ const base::Closure& task);
+
+ // Shutdown the specified audio output and remove it from the set of active
+ // outputs.
+ void ShutdownOutput(AudioOutputPtr output);
+
+ private:
+ // TODO(johngro): A SequencedWorkerPool currently seems to be as close to what
+ // we want which we can currently get using the chrome/mojo framework. Things
+ // which are missing and will eventually need to be addressed include...
+ //
+ // 1) Threads are created on the fly, as needed. We really want to be able to
+ // spin up the proper number of threads at pool creation time. Audio
+ // mixing is very timing sensitive... If we are in a situation where we
+ // have not hit the max number of threads in the pool, and we need to spin
+ // up a thread in order to mix, we *really* do not want to have to wait to
+ // create the thread at the OS level before we can mix some audio. The
+ // thread needs to already be ready to go.
+ // 2) Threads in the pool are created with default priority. Audio mixing
+ // threads will need to be created with elevated priority.
+ // 3) It is currently unclear if explicitly scheduling tasks with delays will
+ // be sufficient for the audio mixer. We really would like to be able to
+ // have tasks fire when some handle abstraction becomes signalled. This
+ // will let us implement mixing not only with open loop timing, but also
+ // with events which can come from device drivers. Note: this seems to be
+ // an issue with the TaskRunner architecture in general, not the
+ // SequencedWorkerPool in specific.
+ // 4) The resolution of posted delayed tasks may hinder the low latency goals
+ // of the system. Being able to know the underlying achievable resolution
+ // of dispatching delayed tasks is a minimum requirement. From that, we
+ // can compute our worst case overhead which can be communicated to the
+ // user and will have an effect on overall latency. Hitting something on
+ // the order of 10s of microseconds is what we really should be shooting
+ // for here. Single milliseconds is probably too coarse.
+ // 5) Not a requirement, but a sure-would-be-nice-to-have... Scheduling
+ // delayed tasks using absolute times. Having to schedule using delta
+ // times from now means that we need to take the time it takes to schedule
+ // the task (along with its jitter) into account when scheduling. This can
+ // lead to additional, undesirable, latency.
+ scoped_refptr<base::SequencedWorkerPool> thread_pool_;
+
+ // A pointer to the server which encapsulates us. It is not possible for this
+ // pointer to be bad while we still exist.
+ AudioServerImpl* server_;
+
+ // Our set of currently active audio output instances.
+ //
+ // Contents of the output set must only be manipulated on the main message
+ // loop thread, so no synchronization should be needed.
+ AudioOutputSet outputs_;
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_AUDIO_OUTPUT_MANAGER_H_
diff --git a/services/media/audio/audio_pipe.cc b/services/media/audio/audio_pipe.cc
new file mode 100644
index 0000000..9c3702c
--- /dev/null
+++ b/services/media/audio/audio_pipe.cc
@@ -0,0 +1,131 @@
+// 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 <limits>
+#include <vector>
+
+#include "services/media/audio/audio_pipe.h"
+#include "services/media/audio/audio_server_impl.h"
+#include "services/media/audio/audio_track_impl.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+AudioPipe::AudioPacketRef::AudioPacketRef(
+ MediaPacketStatePtr state,
+ AudioServerImpl* server,
+ std::vector<Region>&& regions, // NOLINT(build/c++11)
+ int64_t start_pts,
+ int64_t end_pts)
+ : state_(std::move(state)),
+ server_(server),
+ regions_(std::move(regions)),
+ start_pts_(start_pts),
+ end_pts_(end_pts) {
+ DCHECK(state_);
+ DCHECK(server_);
+}
+
+AudioPipe::AudioPacketRef::~AudioPacketRef() {
+ DCHECK(server_);
+ server_->SchedulePacketCleanup(std::move(state_));
+}
+
+AudioPipe::AudioPipe(AudioTrackImpl* owner,
+ AudioServerImpl* server)
+ : owner_(owner),
+ server_(server) {
+ DCHECK(owner_);
+ DCHECK(server_);
+}
+
+AudioPipe::~AudioPipe() {}
+
+void AudioPipe::OnPacketReceived(MediaPacketStatePtr state) {
+ const MediaPacketPtr& packet = state->GetPacket();
+ DCHECK(packet);
+ DCHECK(packet->payload);
+ DCHECK(owner_);
+
+ // Start by making sure that we are regions we are receiving are made from an
+ // integral number of audio frames. Count the total number of frames in the
+ // process.
+ //
+ // TODO(johngro): Someday, automatically enforce this using
+ // alignment/allocation restrictions at the MediaPipe level of things.
+ uint32_t frame_count = 0;
+ uint32_t frame_size = owner_->BytesPerFrame();
+ const MediaPacketRegionPtr* region = &packet->payload;
+ size_t ndx = 0;
+ std::vector<AudioPacketRef::Region> regions;
+
+ regions.reserve(packet->extra_payload.size() + 1);
+
+ DCHECK(frame_size);
+ while (true) {
+ if ((frame_size > 1) && ((*region)->length % frame_size)) {
+ state->SetResult(MediaResult::INVALID_ARGUMENT);
+ return;
+ }
+
+ frame_count += ((*region)->length / frame_size);
+ if (frame_count > (std::numeric_limits<uint32_t>::max() >>
+ AudioTrackImpl::PTS_FRACTIONAL_BITS)) {
+ state->SetResult(MediaResult::INVALID_ARGUMENT);
+ return;
+ }
+
+ regions.emplace_back(
+ static_cast<const uint8_t*>(buffer()) + (*region)->offset,
+ frame_count << AudioTrackImpl::PTS_FRACTIONAL_BITS);
+
+ if (ndx >= packet->extra_payload.size()) {
+ break;
+ }
+
+ region = &(packet->extra_payload[ndx]);
+ ndx++;
+ }
+
+ // Figure out the starting PTS.
+ int64_t start_pts;
+ if (packet->pts != MediaPacket::kNoTimestamp) {
+ // The user provided an explicit PTS for this audio. Transform it into
+ // units of fractional frames.
+ LinearTransform tmp(0, owner_->FractionalFrameToMediaTimeRatio(), 0);
+ if (!tmp.DoForwardTransform(packet->pts, &start_pts)) {
+ state->SetResult(MediaResult::INTERNAL_ERROR);
+ return;
+ }
+ } else {
+ // No PTS was provided. Use the end time of the last audio packet, if
+ // known. Otherwise, just assume a media time of 0.
+ start_pts = next_pts_known_ ? next_pts_ : 0;
+ }
+
+ // The end pts is the value we will use for the next packet's start PTS, if
+ // the user does not provide an explicit PTS.
+ int64_t pts_delta = (static_cast<int64_t>(frame_count)
+ << AudioTrackImpl::PTS_FRACTIONAL_BITS);
+ next_pts_ = start_pts + pts_delta;
+ next_pts_known_ = true;
+
+ owner_->OnPacketReceived(AudioPacketRefPtr(
+ new AudioPacketRef(std::move(state),
+ server_,
+ std::move(regions),
+ start_pts,
+ next_pts_)));
+}
+
+void AudioPipe::OnFlushRequested(const FlushCallback& cbk) {
+ DCHECK(owner_);
+ owner_->OnFlushRequested(cbk);
+ next_pts_known_ = false;
+}
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
diff --git a/services/media/audio/audio_pipe.h b/services/media/audio/audio_pipe.h
new file mode 100644
index 0000000..9c0a57c
--- /dev/null
+++ b/services/media/audio/audio_pipe.h
@@ -0,0 +1,101 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_AUDIO_PIPE_H_
+#define SERVICES_MEDIA_AUDIO_AUDIO_PIPE_H_
+
+#include <memory>
+#include <vector>
+
+#include "mojo/services/media/common/cpp/linear_transform.h"
+#include "services/media/audio/fwd_decls.h"
+#include "services/media/common/media_pipe_base.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class AudioPipe : public MediaPipeBase {
+ public:
+ class AudioPacketRef;
+ using AudioPacketRefPtr = std::shared_ptr<AudioPacketRef>;
+ using MediaPacketStatePtr = MediaPipeBase::MediaPacketStatePtr;
+
+ class AudioPacketRef {
+ public:
+ struct Region {
+ Region(const void* b, uint32_t ffl) : base(b), frac_frame_len(ffl) {}
+ const void* base;
+ uint32_t frac_frame_len;
+ };
+
+ ~AudioPacketRef();
+
+ const MediaPacketPtr& GetPacket() const { return state_->GetPacket(); }
+ void SetResult(MediaResult result) { return state_->SetResult(result); }
+
+ // Accessors for starting and ending presentation time stamps expressed in
+ // units of audio frames (note, not media time), as signed 51.12 fixed point
+ // integers (see AudioTrackImpl:PTS_FRACTIONAL_BITS). At 192KHz, this
+ // allows for ~372.7 years of usable range when starting from a media time
+ // of 0.
+ //
+ // AudioPackets consumed by the AudioServer are all expected to have
+ // explicit presentation time stamps. If packets sent by the user are
+ // missing timestamps, appropriate timestamps will be synthesized at this
+ // point in the pipeline.
+ //
+ // Note, the start pts is the time at which the first frame of audio in the
+ // packet should be presented. The end_pts is the time at which the frame
+ // after the final frame in the packet would be presented.
+ //
+ // TODO(johngro): Reconsider this. It may be best to keep things expressed
+ // simply in media time instead of converting to fractional units of track
+ // frames. If/when outputs move away from a single fixed step size for
+ // output sampling, it will probably be best to just convert this back to
+ // media time.
+ const int64_t& start_pts() const { return start_pts_; }
+ const int64_t& end_pts() const { return end_pts_; }
+
+ // Accessor for the regions in the shared buffer which contain the actual
+ // sample data.
+ const std::vector<Region>& regions() const { return regions_; }
+
+ private:
+ friend class AudioPipe;
+ AudioPacketRef(MediaPacketStatePtr state,
+ AudioServerImpl* server,
+ std::vector<Region>&& regions, // NOLINT(build/c++11)
+ int64_t start_pts,
+ int64_t end_pts);
+
+ MediaPacketStatePtr state_;
+ AudioServerImpl* server_;
+
+ std::vector<Region> regions_;
+ int64_t start_pts_;
+ int64_t end_pts_;
+ };
+
+ AudioPipe(AudioTrackImpl* owner, AudioServerImpl* server);
+ ~AudioPipe() override;
+
+ protected:
+ void OnPacketReceived(MediaPacketStatePtr state) override;
+ void OnFlushRequested(const FlushCallback& cbk) override;
+
+ private:
+ AudioTrackImpl* owner_;
+ AudioServerImpl* server_;
+
+ // State used for timestamp interpolation
+ bool next_pts_known_ = 0;
+ int64_t next_pts_;
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_AUDIO_PIPE_H_
diff --git a/services/media/audio/audio_server_app.cc b/services/media/audio/audio_server_app.cc
new file mode 100644
index 0000000..6a2a279
--- /dev/null
+++ b/services/media/audio/audio_server_app.cc
@@ -0,0 +1,47 @@
+// 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/logging.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "services/media/audio/audio_server_app.h"
+
+#define VERBOSE 0
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+AudioServerApp::AudioServerApp() {}
+AudioServerApp::~AudioServerApp() {}
+
+void AudioServerApp::Initialize(ApplicationImpl* app) {
+ server_impl_.Initialize();
+}
+
+bool AudioServerApp::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService(this);
+ return true;
+}
+
+void AudioServerApp::Quit() {
+}
+
+void AudioServerApp::Create(ApplicationConnection* connection,
+ InterfaceRequest<AudioServer> request) {
+ bindings_.AddBinding(&server_impl_, request.Pass());
+}
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle app_request) {
+ mojo::ApplicationRunnerChromium runner(
+ new mojo::media::audio::AudioServerApp);
+ return runner.Run(app_request);
+}
diff --git a/services/media/audio/audio_server_app.h b/services/media/audio/audio_server_app.h
new file mode 100644
index 0000000..b67262a
--- /dev/null
+++ b/services/media/audio/audio_server_app.h
@@ -0,0 +1,43 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_AUDIO_SERVER_APP_H_
+#define SERVICES_MEDIA_AUDIO_AUDIO_SERVER_APP_H_
+
+#include "mojo/common/binding_set.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/media/audio/interfaces/audio_server.mojom.h"
+#include "services/media/audio/audio_server_impl.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class AudioServerApp : public ApplicationDelegate
+ , public InterfaceFactory<AudioServer> {
+ public:
+ // Constructor/Destructor
+ AudioServerApp();
+ ~AudioServerApp() override;
+
+ // ApplicationDelegate
+ void Initialize(ApplicationImpl* app) override;
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override;
+ void Quit() override;
+
+ // InterfaceFactory<AudioServer>
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<AudioServer> request) override;
+
+ private:
+ AudioServerImpl server_impl_;
+ BindingSet<AudioServer> bindings_;
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_AUDIO_SERVER_APP_H_
diff --git a/services/media/audio/audio_server_impl.cc b/services/media/audio/audio_server_impl.cc
new file mode 100644
index 0000000..cb644a5
--- /dev/null
+++ b/services/media/audio/audio_server_impl.cc
@@ -0,0 +1,103 @@
+// 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/callback.h"
+#include "base/message_loop/message_loop.h"
+#include "base/task_runner.h"
+
+#include "services/media/audio/audio_output_manager.h"
+#include "services/media/audio/audio_server_impl.h"
+#include "services/media/audio/audio_track_impl.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+AudioServerImpl::AudioServerImpl()
+ : output_manager_(this),
+ cleanup_queue_(new CleanupQueue) {
+ cleanup_closure_ = base::Bind(
+ &AudioServerImpl::DoPacketCleanup,
+ base::Unretained(this));
+}
+
+AudioServerImpl::~AudioServerImpl() {
+ Shutdown();
+ DCHECK(cleanup_queue_);
+ DCHECK_EQ(cleanup_queue_->size(), 0u);
+}
+
+void AudioServerImpl::Initialize() {
+ // Stash a pointer to our task runner.
+ DCHECK(base::MessageLoop::current());
+ task_runner_ = base::MessageLoop::current()->task_runner();
+ DCHECK(task_runner_);
+
+ // Set up our output manager.
+ MediaResult res = output_manager_.Init();
+ // TODO(johngro): Do better at error handling than this weak check.
+ DCHECK(res == MediaResult::OK);
+}
+
+void AudioServerImpl::Shutdown() {
+ shutting_down_ = true;
+ output_manager_.Shutdown();
+ DoPacketCleanup();
+}
+
+void AudioServerImpl::CreateTrack(InterfaceRequest<AudioTrack> track) {
+ tracks_.insert(AudioTrackImpl::Create(track.Pass(), this));
+}
+
+void AudioServerImpl::DoPacketCleanup() {
+ // In order to minimize the time we spend in the lock, we allocate a new
+ // queue, then lock, swap and clear the sched flag, and finally clean out the
+ // queue (which has the side effect of triggering all of the send packet
+ // callbacks).
+ //
+ // Note: this is only safe because we know that we are executing on a single
+ // threaded task runner. Without this guarantee, it might be possible call
+ // the send packet callbacks for a media pipe in a different order than the
+ // packets were sent in the first place. If the task_runner for the audio
+ // server ever loses this serialization guarantee (because it becomes
+ // multi-threaded, for example) we will need to introduce another lock
+ // (different from the cleanup lock) in order to keep the cleanup tasks
+ // properly ordered while guaranteeing minimal contention of the cleanup lock
+ // (which is being acquired by the high priority mixing threads).
+ std::unique_ptr<CleanupQueue> tmp_queue(new CleanupQueue());
+
+ {
+ base::AutoLock lock(cleanup_queue_lock_);
+ cleanup_queue_.swap(tmp_queue);
+ cleanup_scheduled_ = false;
+ }
+
+ // The clear method of standard containers do not guarantee any ordering of
+ // destruction of the objects they hold. In order to guarantee proper
+ // sequencing of the callbacks, go over the container front-to-back, nulling
+ // out the std::unique_ptrs they hold as we go (which will trigger the
+ // callbacks). Afterwards, just let tmp_queue go out of scope and clear()
+ // itself automatically.
+ for (auto iter = tmp_queue->begin(); iter != tmp_queue->end(); ++iter) {
+ (*iter) = nullptr;
+ }
+}
+
+void AudioServerImpl::SchedulePacketCleanup(
+ MediaPipeBase::MediaPacketStatePtr state) {
+ base::AutoLock lock(cleanup_queue_lock_);
+
+ cleanup_queue_->emplace_back(std::move(state));
+
+ if (!cleanup_scheduled_ && !shutting_down_) {
+ DCHECK(task_runner_);
+ cleanup_scheduled_ = task_runner_->PostTask(FROM_HERE, cleanup_closure_);
+ DCHECK(cleanup_scheduled_);
+ }
+}
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
diff --git a/services/media/audio/audio_server_impl.h b/services/media/audio/audio_server_impl.h
new file mode 100644
index 0000000..2057b74
--- /dev/null
+++ b/services/media/audio/audio_server_impl.h
@@ -0,0 +1,88 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_AUDIO_SERVER_IMPL_H_
+#define SERVICES_MEDIA_AUDIO_AUDIO_SERVER_IMPL_H_
+
+#include <list>
+#include <set>
+
+#include "base/callback.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "mojo/services/media/audio/interfaces/audio_server.mojom.h"
+#include "mojo/services/media/audio/interfaces/audio_track.mojom.h"
+#include "services/media/audio/audio_output_manager.h"
+#include "services/media/audio/fwd_decls.h"
+#include "services/media/common/media_pipe_base.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class AudioServerImpl : public AudioServer {
+ public:
+ AudioServerImpl();
+ ~AudioServerImpl() override;
+
+ void Initialize();
+
+ // AudioServer
+ void CreateTrack(mojo::InterfaceRequest<AudioTrack> track) override;
+
+ // Called (indirectly) by AudioOutputs to schedule the callback for a
+ // MediaPacked which was queued to an AudioTrack via. a media pipe.
+ //
+ // TODO(johngro): This bouncing through thread contexts is inefficient and
+ // will increase the latency requirements for clients (its going to take them
+ // some extra time to discover that their media has been completely consumed).
+ // When mojo exposes a way to safely invoke interface method callbacks from
+ // threads other than the thread which executed the method itself, we will
+ // want to switch to creating the callback message directly, instead of
+ // indirecting through the server.
+ void SchedulePacketCleanup(MediaPipeBase::MediaPacketStatePtr state);
+
+ // Schedule a closure to run on the server's main message loop.
+ void ScheduleMessageLoopTask(const tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ DCHECK(task_runner_);
+ bool success = task_runner_->PostTask(from_here, task);
+ DCHECK(success);
+ }
+
+
+ // Accessor for our encapsulated output manager.
+ AudioOutputManager& GetOutputManager() { return output_manager_; }
+
+ private:
+ using CleanupQueue = std::list<MediaPipeBase::MediaPacketStatePtr>;
+
+ void Shutdown();
+ void DoPacketCleanup();
+
+ // A reference to our message loop's task runner. Allows us to post events to
+ // be handled by our main application thread from things like the output
+ // manager's thread pool.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // State for dealing with outputs.
+ AudioOutputManager output_manager_;
+
+ // State for dealing with tracks.
+ base::Lock track_lock_;
+ std::set<AudioTrackImplPtr> tracks_;
+
+ // State for dealing with cleanup tasks.
+ base::Lock cleanup_queue_lock_;
+ std::unique_ptr<CleanupQueue> cleanup_queue_;
+ bool cleanup_scheduled_ = false;
+ bool shutting_down_ = false;
+ base::Closure cleanup_closure_;
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_AUDIO_SERVER_IMPL_H_
diff --git a/services/media/audio/audio_track_impl.cc b/services/media/audio/audio_track_impl.cc
new file mode 100644
index 0000000..f0a2263
--- /dev/null
+++ b/services/media/audio/audio_track_impl.cc
@@ -0,0 +1,274 @@
+// 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 <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "mojo/services/media/common/cpp/linear_transform.h"
+#include "services/media/audio/audio_output_manager.h"
+#include "services/media/audio/audio_server_impl.h"
+#include "services/media/audio/audio_track_impl.h"
+#include "services/media/audio/audio_track_to_output_link.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+constexpr size_t AudioTrackImpl::PTS_FRACTIONAL_BITS;
+
+// TODO(johngro): If there is ever a better way to do this type of static-table
+// initialization using mojom generated structs, we should switch to it.
+static const struct {
+ LpcmSampleFormat sample_format;
+ uint8_t min_samples_per_frame;
+ uint8_t max_samples_per_frame;
+ uint32_t min_frames_per_second;
+ uint32_t max_frames_per_second;
+} kSupportedLpcmTypeSets[] = {
+ {
+ .sample_format = LpcmSampleFormat::UNSIGNED_8,
+ .min_samples_per_frame = 1,
+ .max_samples_per_frame = 2,
+ .min_frames_per_second = 1000,
+ .max_frames_per_second = 48000,
+ },
+ {
+ .sample_format = LpcmSampleFormat::SIGNED_16,
+ .min_samples_per_frame = 1,
+ .max_samples_per_frame = 2,
+ .min_frames_per_second = 1000,
+ .max_frames_per_second = 48000,
+ },
+};
+
+AudioTrackImpl::AudioTrackImpl(InterfaceRequest<AudioTrack> iface,
+ AudioServerImpl* owner)
+ : owner_(owner),
+ binding_(this),
+ pipe_(this, owner) {
+ CHECK(nullptr != owner_);
+ binding_.Bind(iface.Pass());
+}
+
+AudioTrackImpl::~AudioTrackImpl() {
+}
+
+AudioTrackImplPtr AudioTrackImpl::Create(InterfaceRequest<AudioTrack> iface,
+ AudioServerImpl* owner) {
+ AudioTrackImplPtr ret(new AudioTrackImpl(iface.Pass(), owner));
+ ret->weak_this_ = ret;
+ return ret;
+}
+
+void AudioTrackImpl::Describe(const DescribeCallback& cbk) {
+ // Build a minimal descriptor
+ //
+ // TODO(johngro): one day, we need to make this description much more rich and
+ // fully describe our capabilities, based on things like what outputs are
+ // available, the class of hardware we are on, and what options we were
+ // compiled with.
+ //
+ // For now, it would be nice to just be able to have a static const tree of
+ // capabilities in this translational unit which we could use to construct our
+ // message, but the nature of the structures generated by the C++ bindings
+ // make this difficult. For now, we just create a trivial descriptor entierly
+ // by hand.
+ AudioTrackDescriptorPtr desc(AudioTrackDescriptor::New());
+
+ desc->supported_media_types =
+ Array<MediaTypeSetPtr>::New(arraysize(kSupportedLpcmTypeSets));
+
+ for (size_t i = 0; i < desc->supported_media_types.size(); ++i) {
+ const MediaTypeSetPtr& mts =
+ (desc->supported_media_types[i] = MediaTypeSet::New());
+
+ mts->scheme = MediaTypeScheme::LPCM;
+ mts->details = MediaTypeSetDetails::New();
+
+ const auto& s = kSupportedLpcmTypeSets[i];
+ LpcmMediaTypeSetDetailsPtr lpcm_detail = LpcmMediaTypeSetDetails::New();
+
+ lpcm_detail->sample_format = s.sample_format;
+ lpcm_detail->min_samples_per_frame = s.min_samples_per_frame;
+ lpcm_detail->max_samples_per_frame = s.max_samples_per_frame;
+ lpcm_detail->min_frames_per_second = s.min_frames_per_second;
+ lpcm_detail->max_frames_per_second = s.max_frames_per_second;
+ mts->details->set_lpcm(lpcm_detail.Pass());
+ }
+
+ cbk.Run(desc.Pass());
+}
+
+void AudioTrackImpl::Configure(AudioTrackConfigurationPtr configuration,
+ InterfaceRequest<MediaPipe> req,
+ const ConfigureCallback& cbk) {
+ // Are we already configured?
+ if (pipe_.IsInitialized()) {
+ cbk.Run(MediaResult::BAD_STATE);
+ return;
+ }
+
+ // Check the requested configuration.
+ if ((configuration->media_type->scheme != MediaTypeScheme::LPCM) ||
+ (!configuration->media_type->details->is_lpcm())) {
+ cbk.Run(MediaResult::UNSUPPORTED_CONFIG);
+ return;
+ }
+
+ // Search our supported configuration sets to find one compatible with this
+ // request.
+ auto& cfg = configuration->media_type->details->get_lpcm();
+ size_t i;
+ for (i = 0; i < arraysize(kSupportedLpcmTypeSets); ++i) {
+ const auto& cfg_set = kSupportedLpcmTypeSets[i];
+
+ if ((cfg->sample_format == cfg_set.sample_format) &&
+ (cfg->samples_per_frame >= cfg_set.min_samples_per_frame) &&
+ (cfg->samples_per_frame <= cfg_set.max_samples_per_frame) &&
+ (cfg->frames_per_second >= cfg_set.min_frames_per_second) &&
+ (cfg->frames_per_second <= cfg_set.max_frames_per_second)) {
+ break;
+ }
+ }
+
+ if (i >= arraysize(kSupportedLpcmTypeSets)) {
+ cbk.Run(MediaResult::UNSUPPORTED_CONFIG);
+ return;
+ }
+
+ // Sanity check the ratio which relates audio frames to media time.
+ int32_t numerator = static_cast<int32_t>(configuration->audio_frame_ratio);
+ uint32_t denominator = static_cast<int32_t>(configuration->media_time_ratio);
+ if ((numerator < 1) || (denominator < 1)) {
+ cbk.Run(MediaResult::INVALID_ARGUMENT);
+ return;
+ }
+
+
+ // Figure out the rate we need to scale by in order to produce our fixed
+ // point timestamps.
+ LinearTransform::Ratio frac_scale(1 << PTS_FRACTIONAL_BITS, 1);
+ LinearTransform::Ratio frame_scale(LinearTransform::Ratio(numerator,
+ denominator));
+ bool no_loss = LinearTransform::Ratio::Compose(frac_scale,
+ frame_scale,
+ &frame_to_media_ratio_);
+ if (!no_loss) {
+ cbk.Run(MediaResult::INVALID_ARGUMENT);
+ return;
+ }
+
+ // Figure out how many bytes we need to hold the requested number of nSec of
+ // audio.
+ switch (cfg->sample_format) {
+ case LpcmSampleFormat::UNSIGNED_8:
+ bytes_per_frame_ = 1;
+ break;
+
+ case LpcmSampleFormat::SIGNED_16:
+ bytes_per_frame_ = 2;
+ break;
+
+ case LpcmSampleFormat::SIGNED_24_IN_32:
+ bytes_per_frame_ = 4;
+ break;
+
+ default:
+ DCHECK(false);
+ bytes_per_frame_ = 2;
+ break;
+ }
+ bytes_per_frame_ *= cfg->samples_per_frame;
+
+ // Overflow trying to convert from frames to bytes?
+ uint64_t requested_frames = configuration->max_frames;
+ if (requested_frames >
+ (std::numeric_limits<size_t>::max() / bytes_per_frame_)) {
+ cbk.Run(MediaResult::INSUFFICIENT_RESOURCES);
+ return;
+ }
+
+ size_t requested_bytes = (requested_frames * bytes_per_frame_);
+
+ // Attempt to initialize our shared buffer and bind it to our interface
+ // request.
+ if (pipe_.Init(req.Pass(), requested_bytes) != MOJO_RESULT_OK) {
+ cbk.Run(MediaResult::INSUFFICIENT_RESOURCES);
+ return;
+ }
+
+ // Stash our configuration.
+ format_ = cfg.Pass();
+
+ // Have the audio output manager initialize our set of outputs. Note; there
+ // is currently no need for a lock here. Methods called from our user-facing
+ // interfaces are seriailzed by nature of the mojo framework, and none of the
+ // output manager's threads should ever need to manipulate the set. Cleanup
+ // of outputs which have gone away is currently handled in a lazy fashion when
+ // the track fails to promote its weak reference during an operation involving
+ // its outputs.
+ //
+ // TODO(johngro): someday, we will need to deal with recalculating properties
+ // which depend on a track's current set of outputs (for example, the minimum
+ // latency). This will probably be done using a dirty flag in the track
+ // implementations, and scheduling a job to recalculate the properties for the
+ // dirty tracks and notify the users as appropriate.
+
+ // If we cannot promote our own weak pointer, something is seriously wrong.
+ AudioTrackImplPtr strong_this(weak_this_.lock());
+ DCHECK(strong_this);
+ DCHECK(owner_);
+ owner_->GetOutputManager().SelectOutputsForTrack(strong_this);
+
+ // Done
+ cbk.Run(MediaResult::OK);
+}
+
+void AudioTrackImpl::GetRateControl(InterfaceRequest<RateControl> req,
+ const GetRateControlCallback& cbk) {
+ cbk.Run(rate_control_.Bind(req.Pass()));
+}
+
+void AudioTrackImpl::AddOutput(AudioTrackToOutputLinkPtr link) {
+ // TODO(johngro): assert that we are on the main message loop thread.
+ DCHECK(link);
+ auto res = outputs_.emplace(link);
+ DCHECK(res.second);
+}
+
+void AudioTrackImpl::RemoveOutput(AudioTrackToOutputLinkPtr link) {
+ // TODO(johngro): assert that we are on the main message loop thread.
+ DCHECK(link);
+
+ auto iter = outputs_.find(link);
+ if (iter != outputs_.end()) {
+ outputs_.erase(iter);
+ } else {
+ // TODO(johngro): that's odd. I can't think of a reason why we we should
+ // not be able to find this link in our set of outputs... should we log
+ // something about this?
+ DCHECK(false);
+ }
+}
+
+void AudioTrackImpl::OnPacketReceived(AudioPipe::AudioPacketRefPtr packet) {
+ DCHECK(packet);
+ for (const auto& output : outputs_) {
+ DCHECK(output);
+ output->PushToPendingQueue(packet);
+ }
+}
+
+void AudioTrackImpl::OnFlushRequested(const MediaPipe::FlushCallback& cbk) {
+ for (const auto& output : outputs_) {
+ DCHECK(output);
+ output->FlushPendingQueue();
+ }
+ cbk.Run(MediaResult::OK);
+}
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
diff --git a/services/media/audio/audio_track_impl.h b/services/media/audio/audio_track_impl.h
new file mode 100644
index 0000000..9dad504
--- /dev/null
+++ b/services/media/audio/audio_track_impl.h
@@ -0,0 +1,90 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_AUDIO_TRACK_IMPL_H_
+#define SERVICES_MEDIA_AUDIO_AUDIO_TRACK_IMPL_H_
+
+#include <deque>
+#include <set>
+
+#include "base/synchronization/lock.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/callback.h"
+#include "mojo/services/media/audio/interfaces/audio_track.mojom.h"
+#include "mojo/services/media/common/cpp/linear_transform.h"
+#include "services/media/audio/audio_pipe.h"
+#include "services/media/audio/fwd_decls.h"
+#include "services/media/common/rate_control_base.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class AudioTrackImpl : public AudioTrack {
+ public:
+ // TODO(johngro): Find a better place for this constant. It affects the
+ // behavior of more than just the Audio Track implementation.
+ static constexpr size_t PTS_FRACTIONAL_BITS = 12;
+
+ ~AudioTrackImpl() override;
+ static AudioTrackImplPtr Create(InterfaceRequest<AudioTrack> iface,
+ AudioServerImpl* owner);
+
+ // Methods used by the output manager to link this track to different outputs.
+ void AddOutput(AudioTrackToOutputLinkPtr link);
+ void RemoveOutput(AudioTrackToOutputLinkPtr link);
+
+ // Accessors used by AudioOutputs during mixing to access parameters which are
+ // important for the mixing process.
+ void SnapshotRateTrans(LinearTransform* out, uint32_t* generation = nullptr) {
+ rate_control_.SnapshotCurrentTransform(out, generation);
+ }
+
+ const LinearTransform::Ratio& FractionalFrameToMediaTimeRatio() const {
+ return frame_to_media_ratio_;
+ }
+
+ uint32_t BytesPerFrame() const { return bytes_per_frame_; }
+ const LpcmMediaTypeDetailsPtr& Format() const { return format_; }
+
+ private:
+ friend class AudioPipe;
+
+ AudioTrackImpl(InterfaceRequest<AudioTrack> track,
+ AudioServerImpl* owner);
+
+ // Implementation of AudioTrack interface.
+ void Describe(const DescribeCallback& cbk) override;
+ void Configure(AudioTrackConfigurationPtr configuration,
+ InterfaceRequest<MediaPipe> req,
+ const ConfigureCallback& cbk) override;
+ void GetRateControl(InterfaceRequest<RateControl> req,
+ const GetRateControlCallback& cbk) override;
+
+ // Methods called by our AudioPipe.
+ //
+ // TODO(johngro): MI is banned by style, but multiple interface inheritance
+ // (inheriting for one or more base classes consisting only of pure virtual
+ // methods) is allowed. Consider defining an interface for AudioPipe
+ // encapsulation so that AudioPipe does not have to know that we are an
+ // AudioTrackImpl (just that we implement its interface).
+ void OnPacketReceived(AudioPipe::AudioPacketRefPtr packet);
+ void OnFlushRequested(const MediaPipe::FlushCallback& cbk);
+
+ AudioTrackImplWeakPtr weak_this_;
+ AudioServerImpl* owner_;
+ Binding<AudioTrack> binding_;
+ AudioPipe pipe_;
+ RateControlBase rate_control_;
+ LinearTransform::Ratio frame_to_media_ratio_;
+ uint32_t bytes_per_frame_ = 1;
+ LpcmMediaTypeDetailsPtr format_;
+ AudioTrackToOutputLinkSet outputs_;
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_AUDIO_TRACK_IMPL_H_
diff --git a/services/media/audio/audio_track_to_output_link.cc b/services/media/audio/audio_track_to_output_link.cc
new file mode 100644
index 0000000..9d40042
--- /dev/null
+++ b/services/media/audio/audio_track_to_output_link.cc
@@ -0,0 +1,144 @@
+// 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/logging.h"
+#include "services/media/audio/audio_track_to_output_link.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+AudioTrackToOutputLink::Bookkeeping::~Bookkeeping() {}
+
+AudioTrackToOutputLink::AudioTrackToOutputLink(AudioTrackImplWeakPtr track,
+ AudioOutputWeakPtr output)
+ : track_(track),
+ output_(output),
+ pending_queue_(new PacketQueue) {
+#if !(defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON))
+ flush_lock_held_ = false;
+#endif
+}
+
+AudioTrackToOutputLink::~AudioTrackToOutputLink() {
+ ReleaseQueue(pending_queue_);
+}
+
+AudioTrackToOutputLinkPtr AudioTrackToOutputLink::New(
+ AudioTrackImplWeakPtr track,
+ AudioOutputWeakPtr output) {
+ return AudioTrackToOutputLinkPtr(new AudioTrackToOutputLink(track, output));
+}
+
+void AudioTrackToOutputLink::PushToPendingQueue(
+ const AudioPipe::AudioPacketRefPtr& pkt) {
+ base::AutoLock lock(pending_queue_lock_);
+ pending_queue_->emplace_back(pkt);
+}
+
+void AudioTrackToOutputLink::FlushPendingQueue() {
+ // Create a new (empty) queue before obtaining any locks. This will allow us
+ // to quickly swap the empty queue for the current queue and get out of all
+ // the locks, and then release the packets at our leisure instead of
+ // potentially holding off a high priority mixing thread while releasing
+ // packets.
+ //
+ // Note: the safety of this technique depends on Flush only ever being called
+ // from the AudioTrack, and the AudioTrack's actions being serialized on the
+ // AudioServer's message loop thread. If multiple flushes are allowed to be
+ // invoked simultaniously, or if a packet is permitted to be added to the
+ // queue while a flush operation is in progress, it is possible to return
+ // packets to the user in an order different than the one that they were
+ // queued in.
+ PacketQueuePtr new_queue(new PacketQueue);
+
+ {
+ base::AutoLock lock(flush_lock_);
+ {
+ // TODO(johngro): Assuming that it is impossible to push a new packet
+ // while a flush is in progress, it's pretty easy to show that this lock
+ // can never be contended. Because of this, we could consider removing
+ // this lock operation (although, flush is a relatively rare operation, so
+ // the extra overhead is pretty insignificant.
+ base::AutoLock lock(pending_queue_lock_);
+ pending_queue_.swap(new_queue);
+ }
+ flushed_ = true;
+ }
+
+ ReleaseQueue(new_queue);
+}
+
+AudioPipe::AudioPacketRefPtr AudioTrackToOutputLink::LockPendingQueueFront(
+ bool* was_flushed) {
+#if !(defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON))
+ // No one had better be holding the flush lock right now. If someone is, then
+ // we either have multiple threads hitting this operation at the same time
+ // (should be impossible), or an audio output forgot to unlock the front of
+ // the queue before attempting to lock it again.
+ bool was_held = false;
+ flush_lock_held_.compare_exchange_strong(was_held, true);
+ DCHECK(!was_held);
+#endif
+ flush_lock_.Acquire();
+
+ DCHECK(was_flushed);
+ *was_flushed = flushed_;
+ flushed_ = false;
+
+ {
+ base::AutoLock lock(pending_queue_lock_);
+ if (pending_queue_->size()) {
+ return pending_queue_->front();
+ } else {
+ return nullptr;
+ }
+ }
+}
+
+void AudioTrackToOutputLink::UnlockPendingQueueFront(
+ AudioPipe::AudioPacketRefPtr* pkt,
+ bool release_packet) {
+ {
+ base::AutoLock lock(pending_queue_lock_);
+
+ // Assert that the user either got no packet when they locked the queue
+ // (because the queue was empty), or that they got the front of the queue
+ // and that the front of the queue has not changed.
+ DCHECK(pkt);
+ DCHECK((*pkt == nullptr) ||
+ (pending_queue_->size() && (*pkt == pending_queue_->front())));
+
+ if (*pkt) {
+ *pkt = nullptr;
+ if (release_packet) {
+ pending_queue_->pop_front();
+ }
+ }
+ }
+
+ flush_lock_.Release();
+#if !(defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON))
+ bool was_held = true;
+ flush_lock_held_.compare_exchange_strong(was_held, false);
+ DCHECK(was_held);
+#endif
+}
+
+void AudioTrackToOutputLink::ReleaseQueue(const PacketQueuePtr& queue) {
+ if (!queue) {
+ return;
+ }
+
+ for (auto iter = queue->begin(); iter != queue->end(); ++iter) {
+ (*iter).reset();
+ }
+
+ queue->clear();
+}
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
diff --git a/services/media/audio/audio_track_to_output_link.h b/services/media/audio/audio_track_to_output_link.h
new file mode 100644
index 0000000..f9d31ec
--- /dev/null
+++ b/services/media/audio/audio_track_to_output_link.h
@@ -0,0 +1,128 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_AUDIO_TRACK_TO_OUTPUT_LINK_H_
+#define SERVICES_MEDIA_AUDIO_AUDIO_TRACK_TO_OUTPUT_LINK_H_
+
+#include <deque>
+#include <memory>
+
+#include "base/synchronization/lock.h"
+#include "services/media/audio/audio_pipe.h"
+#include "services/media/audio/fwd_decls.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+// AudioTrackToOutputLink is a small class which tracks the relationship between
+// an audio track and an audio output. Tracks and outputs are expected to hold
+// strong pointers to the state in the collections they use to track their
+// peers.
+//
+// When either a track or an output ceases to exist, its collection will clear
+// releasing the reference to the shared state. When the other half of the
+// relationship realizes that its peer has gone away (typically by failing to
+// promote the weak reference to its peer held in the share state object), it
+// can purge the state object strong pointer from its collection triggering the
+// final cleanup of the shared state.
+//
+// Because the final cleanup of the shared state can be triggered either from an
+// output manager mixer thread, or from the audio service's main message loop,
+// it must be safe to destruct all of the shared state from any thread in the
+// system. No assumptions may be made about threading when destructing.
+//
+// The AudioTrackToOutputLink object holds a queue of pending audio packet
+// references sourced from the AudioTrack to be rendered on the audio output.
+// The references are safe to release either from an output manager thread, or
+// from the audio service's main message loop thread (which drives track
+// behavior).
+//
+// Finally, both the Output may have a pointer to a Bookkeeping object in order
+// to manage bookkeeping tasks specific to the Track/Output relationship. The
+// following rules must be obeyed at all times...
+//
+// + Derrived classes of the Bookkeeping object created by the Output must be
+// safe to destroy either thread. During destruction, no potentially blocking
+// operations may be performed. No heavy operations (such as logging) should
+// be performed.
+// + Only the output is permitted to access the output bookkeeping. The track
+// must make no attempts to modify the bookkeeping or its pointer.
+// + Outputs must hold a strong reference to the shared link object object
+// whenever they are accessing their bookkeeping object. The link object is
+// considered to be the owner of the Bookkeeping, users must never hold a
+// naked pointer to their bookkeeping if the link could possibly destruct.
+//
+class AudioTrackToOutputLink {
+ public:
+ struct Bookkeeping {
+ virtual ~Bookkeeping();
+ };
+
+ using BookkeepingPtr = std::unique_ptr<Bookkeeping>;
+ using PacketQueue = std::deque<AudioPipe::AudioPacketRefPtr>;
+ using PacketQueuePtr = std::unique_ptr<PacketQueue>;
+
+ static AudioTrackToOutputLinkPtr New(AudioTrackImplWeakPtr track,
+ AudioOutputWeakPtr output);
+ virtual ~AudioTrackToOutputLink();
+
+ // Accessors for the track and output pointers. Automatically attempts to
+ // promote the weak pointer to a strong pointer.
+ //
+ // TODO(johngro): Given the way outputs are currently shut down, there is
+ // actually no need for the link to hold a weak pointer to output. By the
+ // time it destructs, All references to it are guaranteed to have been removed
+ // from all tracks in the context of the main event loop. Consider converting
+ // this from a weak pointer to a strong pointer.
+ AudioTrackImplPtr GetTrack() { return track_.lock(); }
+ AudioOutputPtr GetOutput() { return output_.lock(); }
+
+ // AudioTrack PendingQueue operations. Never call these from the AudioOutput.
+ void PushToPendingQueue(const AudioPipe::AudioPacketRefPtr& pkt);
+ void FlushPendingQueue();
+
+ // AudioOutput PendingQueue operations. Never call these from the AudioTrack.
+ // When consuming audio, AudioOutputs must always pair their calls to
+ // LockPendingQueueFront and UnlockPendingQueueFront, passing the pointer to
+ // the reference to the front of the queue they obtained in the process (even
+ // if the front of the queue was nullptr).
+ //
+ // Doing so ensures that AudioTracks which are attempting to flush the pending
+ // queue are forced to wait if the front of the queue is involved in a mixing
+ // operation. This, in turn, guarantees that audio packets are always
+ // returned to the user in the order which they were queued in without forcing
+ // AudioTracks to wait to queue new data if a mix operation is in progress.
+ AudioPipe::AudioPacketRefPtr LockPendingQueueFront(bool* was_flushed);
+ void UnlockPendingQueueFront(AudioPipe::AudioPacketRefPtr* pkt,
+ bool release_packet);
+
+ // Bookkeeping access.
+ //
+ BookkeepingPtr& output_bookkeeping() { return output_bookkeeping_; }
+
+ private:
+ void ReleaseQueue(const PacketQueuePtr& queue);
+
+ AudioTrackToOutputLink(AudioTrackImplWeakPtr track,
+ AudioOutputWeakPtr output);
+
+ AudioTrackImplWeakPtr track_;
+ AudioOutputWeakPtr output_;
+ BookkeepingPtr output_bookkeeping_;
+
+ base::Lock flush_lock_;
+ base::Lock pending_queue_lock_;
+ PacketQueuePtr pending_queue_;
+ bool flushed_ = true;
+#if !(defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON))
+ std::atomic<bool> flush_lock_held_;
+#endif
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_AUDIO_TRACK_TO_OUTPUT_LINK_H_
diff --git a/services/media/audio/fwd_decls.h b/services/media/audio/fwd_decls.h
new file mode 100644
index 0000000..c4f2c9e
--- /dev/null
+++ b/services/media/audio/fwd_decls.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_FWD_DECLS_H__
+#define SERVICES_MEDIA_AUDIO_FWD_DECLS_H__
+
+#include <memory>
+#include <set>
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class AudioOutput;
+class AudioOutputManager;
+class AudioServerImpl;
+class AudioTrackImpl;
+class AudioTrackToOutputLink;
+
+using AudioOutputPtr = std::shared_ptr<AudioOutput>;
+using AudioOutputSet = std::set<AudioOutputPtr,
+ std::owner_less<AudioOutputPtr>>;
+using AudioOutputWeakPtr = std::weak_ptr<AudioOutput>;
+using AudioOutputWeakSet = std::set<AudioOutputWeakPtr,
+ std::owner_less<AudioOutputWeakPtr>>;
+
+using AudioTrackImplPtr = std::shared_ptr<AudioTrackImpl>;
+using AudioTrackImplSet = std::set<AudioTrackImplPtr,
+ std::owner_less<AudioTrackImplPtr>>;
+using AudioTrackImplWeakPtr = std::weak_ptr<AudioTrackImpl>;
+using AudioTrackImplWeakSet = std::set<AudioTrackImplWeakPtr,
+ std::owner_less<AudioTrackImplWeakPtr>>;
+
+using AudioTrackToOutputLinkPtr = std::shared_ptr<AudioTrackToOutputLink>;
+using AudioTrackToOutputLinkSet =
+ std::set<AudioTrackToOutputLinkPtr,
+ std::owner_less<AudioTrackToOutputLinkPtr>>;
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_FWD_DECLS_H__
diff --git a/services/media/audio/platform/generic/mixer.cc b/services/media/audio/platform/generic/mixer.cc
new file mode 100644
index 0000000..47b55f9
--- /dev/null
+++ b/services/media/audio/platform/generic/mixer.cc
@@ -0,0 +1,31 @@
+// 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/logging.h"
+#include "services/media/audio/platform/generic/mixer.h"
+#include "services/media/audio/platform/generic/mixers/no_op.h"
+#include "services/media/audio/platform/generic/mixers/point_sampler.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+Mixer::Mixer() {}
+Mixer::~Mixer() {}
+
+MixerPtr Mixer::Select(const LpcmMediaTypeDetailsPtr& src_format,
+ const LpcmMediaTypeDetailsPtr& dst_format) {
+ // We should always have a source format.
+ DCHECK(src_format);
+
+ // If we don't have a destination format, just stick with no-op. This is
+ // probably the ThrottleOutput we are picking a mixer for.
+ if (!dst_format) { return MixerPtr(new mixers::NoOp()); }
+
+ return mixers::PointSampler::Select(src_format, dst_format);
+}
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
diff --git a/services/media/audio/platform/generic/mixer.h b/services/media/audio/platform/generic/mixer.h
new file mode 100644
index 0000000..323e75a
--- /dev/null
+++ b/services/media/audio/platform/generic/mixer.h
@@ -0,0 +1,110 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXER_H_
+#define SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXER_H_
+
+#include <memory>
+
+#include "mojo/services/media/common/interfaces/media_types.mojom.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class Mixer;
+using MixerPtr = std::unique_ptr<Mixer>;
+
+class Mixer {
+ public:
+ virtual ~Mixer();
+
+ // Select
+ //
+ // Select an appropriate instance of a mixer based on the properties of the
+ // source and destination formats.
+ //
+ // TODO(johngro): Come back here and add a way to indicate user preference
+ // where appropriate. For example, where we might chose a linear
+ // interpolation sampler, the user may actually prefer cubic interpolation, or
+ // perhaps just a point sampler.
+ static MixerPtr Select(const LpcmMediaTypeDetailsPtr& src_format,
+ const LpcmMediaTypeDetailsPtr& dst_format);
+
+ // Mix
+ //
+ // Perform a mixing operation from the source buffer into the destination
+ // buffer.
+ //
+ // @param dst
+ // The pointer to the destination buffer into which frames will be mixed.
+ //
+ // @param dst_frames
+ // The total number of frames of audio which comprise the destination buffer.
+ //
+ // @param dst_offset
+ // The pointer to the offset (in destination frames) at which we should start
+ // to mix destination frames. When Mix has finished, dst_offset will be
+ // updated to indicate the offset into the destination buffer of the next
+ // frame to be mixed.
+ //
+ // @param src
+ // The pointer the the source buffer containing the frames to be mixed into
+ // the destination buffer.
+ //
+ // @param frac_src_frames
+ // The total number of fractional track frames contained by the source buffer.
+ //
+ // @param frac_src_offset
+ // A pointer to the offset (expressed in fractional track frames) at which the
+ // first frame to be mixed with the destination buffer should be sampled.
+ // When Mix has finished, frac_src_offset will be updated to indicate the
+ // offset of the sampling position of the next frame to be mixed with the
+ // output buffer.
+ //
+ // @param frac_step_size
+ // How much to increment the fractional sampling position for each output
+ // frame produced.
+ //
+ // TODO(johngro): Right now, this number may have some amount of rounding
+ // error which will accumulate as sampling position error as we produce more
+ // output samples for a single call to Mix. This error will reset when we
+ // swtich to the next source buffer, but could (in theory) be the source of
+ // distortion. If this becomes a problem, we should consider switching to
+ // some form of (N,M) stepping system where we count by frac_step_size for N
+ // output samples, then frac_step_size+1 for M samples, etc...
+ //
+ // @param accumulate
+ // When true, the mixer will accumulate into the destination buffer (read,
+ // sum, clip, write-back). When false, the mixer will simply replace the
+ // destination buffer with its output.
+ //
+ // @return True if the mixer is finished with this source data and will not
+ // need it in the future. False if the mixer has not consumed the entire
+ // source buffer and will need more of it in the future.
+ virtual bool Mix(void* dst,
+ uint32_t dst_frames,
+ uint32_t* dst_offset,
+ const void* src,
+ uint32_t frac_src_frames,
+ uint32_t* frac_src_offset,
+ uint32_t frac_step_size,
+ bool accumulate) = 0;
+
+ // Reset
+ //
+ // Reset the internal state of the mixer. Will be called every time there is
+ // a discontinuity in the source stream. Mixer implementations should reset
+ // anything related to their internal filter state.
+ virtual void Reset() {}
+
+ protected:
+ Mixer();
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXER_H_
diff --git a/services/media/audio/platform/generic/mixers/mixer_utils.h b/services/media/audio/platform/generic/mixers/mixer_utils.h
new file mode 100644
index 0000000..6efeaad
--- /dev/null
+++ b/services/media/audio/platform/generic/mixers/mixer_utils.h
@@ -0,0 +1,158 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXERS_MIXER_UTILS_H_
+#define SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXERS_MIXER_UTILS_H_
+
+#include <limits>
+#include <type_traits>
+
+namespace mojo {
+namespace media {
+namespace audio {
+namespace mixers {
+namespace utils {
+
+// mixer_utils.h is a collection of inline templated utility functions meant to
+// be used by mixer implementations and expanded/optimized at compile time in
+// order to produce efficient inner mixing loops for all of the different
+// variations of source/destination sample type/channel counts.
+
+// Template to read samples and normalize them into signed 32 bit integers.
+template <typename SType, typename Enable = void> class SampleNormalizer;
+
+template <typename SType>
+class SampleNormalizer<
+ SType,
+ typename std::enable_if<
+ std::is_same<SType, uint8_t>::value,
+ void>::type> {
+ public:
+ static inline int32_t Read(const SType* src) {
+ register SType tmp = *src;
+ return (static_cast<int32_t>(tmp) << 8) - 0x8000;
+ }
+};
+
+template <typename SType>
+class SampleNormalizer<
+ SType,
+ typename std::enable_if<
+ std::is_same<SType, int16_t>::value,
+ void>::type> {
+ public:
+ static inline int32_t Read(const SType* src) {
+ return static_cast<int32_t>(*src);
+ }
+};
+
+// Template to read normalized source samples, and combine channels if required.
+template <typename SType,
+ size_t SChCount,
+ size_t DChCount,
+ typename Enable = void>
+class SrcReader;
+
+template <typename SType,
+ size_t SChCount,
+ size_t DChCount>
+class SrcReader<SType, SChCount, DChCount,
+ typename std::enable_if<
+ (SChCount == DChCount) ||
+ ((SChCount == 1) && (DChCount == 2)),
+ void>::type> {
+ public:
+ static constexpr size_t DstPerSrc = DChCount / SChCount;
+ static inline int32_t Read(const SType* src) {
+ return SampleNormalizer<SType>::Read(src);
+ }
+};
+
+template <typename SType,
+ size_t SChCount,
+ size_t DChCount>
+class SrcReader<SType, SChCount, DChCount,
+ typename std::enable_if<
+ (SChCount == 2) && (DChCount == 1),
+ void>::type> {
+ public:
+ static constexpr size_t DstPerSrc = 1;
+ static inline int32_t Read(const SType* src) {
+ return (SampleNormalizer<SType>::Read(src + 0) +
+ SampleNormalizer<SType>::Read(src + 1)) >> 1;
+ }
+};
+
+// Template to produce destination samples from normalized samples.
+template <typename DType, typename Enable = void> class DstConverter;
+
+template <typename DType>
+class DstConverter<DType,
+ typename std::enable_if<
+ std::is_same<DType, int16_t>::value,
+ void>::type> {
+ public:
+ static inline DType Convert(int32_t sample) {
+ return static_cast<DType>(sample);
+ }
+};
+
+template <typename DType>
+class DstConverter<DType,
+ typename std::enable_if<
+ std::is_same<DType, uint8_t>::value,
+ void>::type> {
+ public:
+ static inline DType Convert(int32_t sample) {
+ return static_cast<DType>((sample >> 8) + 0x80);
+ }
+};
+
+// Template to mix destination samples with normalized source samples based on
+// accumulation policy.
+template <typename DType,
+ bool DoAccumulate,
+ typename Enable = void>
+class DstMixer;
+
+template <typename DType,
+ bool DoAccumulate>
+class DstMixer<DType, DoAccumulate,
+ typename std::enable_if<
+ DoAccumulate == false,
+ void>::type> {
+ public:
+ static inline int32_t Mix(const DType* dst, uint32_t sample) {
+ return DstConverter<DType>::Convert(sample);
+ }
+};
+
+template <typename DType,
+ bool DoAccumulate>
+class DstMixer<DType, DoAccumulate,
+ typename std::enable_if<
+ DoAccumulate == true,
+ void>::type> {
+ public:
+ static inline int32_t Mix(const DType* dst, int32_t sample) {
+ sample += SampleNormalizer<DType>::Read(dst);
+
+ if (sample > std::numeric_limits<int16_t>::max()) {
+ return DstConverter<DType>::Convert(std::numeric_limits<int16_t>::max());
+ } else if (sample < std::numeric_limits<int16_t>::min()) {
+ return DstConverter<DType>::Convert(std::numeric_limits<int16_t>::min());
+ } else {
+ return DstConverter<DType>::Convert(sample);
+ }
+ }
+};
+
+
+} // namespace utils
+} // namespace mixers
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXERS_MIXER_UTILS_H_
diff --git a/services/media/audio/platform/generic/mixers/no_op.cc b/services/media/audio/platform/generic/mixers/no_op.cc
new file mode 100644
index 0000000..ded8df5
--- /dev/null
+++ b/services/media/audio/platform/generic/mixers/no_op.cc
@@ -0,0 +1,40 @@
+// 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/logging.h"
+#include "services/media/audio/platform/generic/mixers/no_op.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+namespace mixers {
+
+bool NoOp::Mix(void* dst,
+ uint32_t dst_frames,
+ uint32_t* dst_offset,
+ const void* src,
+ uint32_t frac_src_frames,
+ uint32_t* frac_src_offset,
+ uint32_t frac_step_size,
+ bool accumulate) {
+ DCHECK_LT(*dst_offset, dst_frames);
+ DCHECK_LT(*frac_src_offset, frac_src_frames);
+
+ uint32_t frames_produced = ((frac_src_frames - *frac_src_offset)
+ + frac_step_size - 1) / frac_step_size;
+
+ if (frames_produced > (dst_frames - *dst_offset)) {
+ frames_produced = (dst_frames - *dst_offset);
+ }
+
+ *dst_offset += frames_produced;
+ *frac_src_offset += frames_produced * frac_step_size;
+
+ return (*frac_src_offset >= frac_src_frames);
+}
+
+} // namespace mixers
+} // namespace audio
+} // namespace media
+} // namespace mojo
diff --git a/services/media/audio/platform/generic/mixers/no_op.h b/services/media/audio/platform/generic/mixers/no_op.h
new file mode 100644
index 0000000..03fa901
--- /dev/null
+++ b/services/media/audio/platform/generic/mixers/no_op.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXERS_NO_OP_H_
+#define SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXERS_NO_OP_H_
+
+#include "services/media/audio/platform/generic/mixer.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+namespace mixers {
+
+class NoOp : public Mixer {
+ public:
+ bool Mix(void* dst,
+ uint32_t dst_frames,
+ uint32_t* dst_offset,
+ const void* src,
+ uint32_t frac_src_frames,
+ uint32_t* frac_src_offset,
+ uint32_t frac_step_size,
+ bool accumulate) override;
+};
+
+} // namespace mixers
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXERS_NO_OP_H_
diff --git a/services/media/audio/platform/generic/mixers/point_sampler.cc b/services/media/audio/platform/generic/mixers/point_sampler.cc
new file mode 100644
index 0000000..b2d6f28
--- /dev/null
+++ b/services/media/audio/platform/generic/mixers/point_sampler.cc
@@ -0,0 +1,179 @@
+// 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/logging.h"
+#include "services/media/audio/audio_track_impl.h"
+#include "services/media/audio/platform/generic/mixers/mixer_utils.h"
+#include "services/media/audio/platform/generic/mixers/point_sampler.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+namespace mixers {
+
+// Point Sample Mixer implementation.
+template <typename DType,
+ size_t DChCount,
+ typename SType,
+ size_t SChCount>
+class PointSamplerImpl : public PointSampler {
+ public:
+ PointSamplerImpl() {}
+
+ bool Mix(void* dst,
+ uint32_t dst_frames,
+ uint32_t* dst_offset,
+ const void* src,
+ uint32_t frac_src_frames,
+ uint32_t* frac_src_offset,
+ uint32_t frac_step_size,
+ bool accumulate) override;
+
+ private:
+ template <bool DoAccumulate>
+ static inline bool Mix(void* dst,
+ uint32_t dst_frames,
+ uint32_t* dst_offset,
+ const void* src,
+ uint32_t frac_src_frames,
+ uint32_t* frac_src_offset,
+ uint32_t frac_step_size);
+};
+
+template <typename DType,
+ size_t DChCount,
+ typename SType,
+ size_t SChCount>
+template <bool DoAccumulate>
+inline bool PointSamplerImpl<DType, DChCount, SType, SChCount>::Mix(
+ void* dst_void,
+ uint32_t dst_frames,
+ uint32_t* dst_offset,
+ const void* src_void,
+ uint32_t frac_src_frames,
+ uint32_t* frac_src_offset,
+ uint32_t frac_step_size) {
+ using SR = utils::SrcReader<SType, SChCount, DChCount>;
+ using DM = utils::DstMixer<DType, DoAccumulate>;
+
+ const SType* src = static_cast<const SType*>(src_void);
+ DType* dst = static_cast<DType*>(dst_void);
+ uint32_t doff = *dst_offset;
+ uint32_t soff = *frac_src_offset;
+
+ DCHECK_LT(doff, dst_frames);
+ DCHECK_LT(soff, frac_src_frames);
+
+ while ((doff < dst_frames) && (soff < frac_src_frames)) {
+ uint32_t src_iter;
+ DType* out;
+
+ src_iter = (soff >> AudioTrackImpl::PTS_FRACTIONAL_BITS) * SChCount;
+ out = dst + (doff * DChCount);
+
+ for (size_t dst_iter = 0; dst_iter < DChCount; ++dst_iter) {
+ int32_t sample = SR::Read(src + src_iter + (dst_iter / SR::DstPerSrc));
+ out[dst_iter] = DM::Mix(out + dst_iter, sample);
+ }
+
+ doff += 1;
+ soff += frac_step_size;
+ }
+
+ *dst_offset = doff;
+ *frac_src_offset = soff;
+
+ return (soff >= frac_src_frames);
+}
+
+template <typename DType,
+ size_t DChCount,
+ typename SType,
+ size_t SChCount>
+bool PointSamplerImpl<DType, DChCount, SType, SChCount>::Mix(
+ void* dst,
+ uint32_t dst_frames,
+ uint32_t* dst_offset,
+ const void* src,
+ uint32_t frac_src_frames,
+ uint32_t* frac_src_offset,
+ uint32_t frac_step_size,
+ bool accumulate) {
+ return accumulate ? Mix<true>(dst, dst_frames, dst_offset,
+ src, frac_src_frames, frac_src_offset,
+ frac_step_size)
+ : Mix<false>(dst, dst_frames, dst_offset,
+ src, frac_src_frames, frac_src_offset,
+ frac_step_size);
+}
+
+// Templates used to expand all of the different combinations of the possible
+// Point Sampler Mixer configurations.
+template <typename DType,
+ size_t DChCount,
+ typename SType,
+ size_t SChCount>
+static inline MixerPtr SelectPSM(const LpcmMediaTypeDetailsPtr& src_format,
+ const LpcmMediaTypeDetailsPtr& dst_format) {
+ return MixerPtr(new PointSamplerImpl<DType, DChCount, SType, SChCount>());
+}
+
+template <typename DType,
+ size_t DChCount,
+ typename SType>
+static inline MixerPtr SelectPSM(const LpcmMediaTypeDetailsPtr& src_format,
+ const LpcmMediaTypeDetailsPtr& dst_format) {
+ switch (src_format->samples_per_frame) {
+ case 1:
+ return SelectPSM<DType, DChCount, SType, 1>(src_format, dst_format);
+ case 2:
+ return SelectPSM<DType, DChCount, SType, 2>(src_format, dst_format);
+ default:
+ return nullptr;
+ }
+}
+
+template <typename DType,
+ size_t DChCount>
+static inline MixerPtr SelectPSM(const LpcmMediaTypeDetailsPtr& src_format,
+ const LpcmMediaTypeDetailsPtr& dst_format) {
+ switch (src_format->sample_format) {
+ case LpcmSampleFormat::UNSIGNED_8:
+ return SelectPSM<DType, DChCount, uint8_t>(src_format, dst_format);
+ case LpcmSampleFormat::SIGNED_16:
+ return SelectPSM<DType, DChCount, int16_t>(src_format, dst_format);
+ default:
+ return nullptr;
+ }
+}
+
+template <typename DType>
+static inline MixerPtr SelectPSM(const LpcmMediaTypeDetailsPtr& src_format,
+ const LpcmMediaTypeDetailsPtr& dst_format) {
+ switch (dst_format->samples_per_frame) {
+ case 1:
+ return SelectPSM<DType, 1>(src_format, dst_format);
+ case 2:
+ return SelectPSM<DType, 2>(src_format, dst_format);
+ default:
+ return nullptr;
+ }
+}
+
+MixerPtr PointSampler::Select(const LpcmMediaTypeDetailsPtr& src_format,
+ const LpcmMediaTypeDetailsPtr& dst_format) {
+ switch (dst_format->sample_format) {
+ case LpcmSampleFormat::UNSIGNED_8:
+ return SelectPSM<uint8_t>(src_format, dst_format);
+ case LpcmSampleFormat::SIGNED_16:
+ return SelectPSM<int16_t>(src_format, dst_format);
+ default:
+ return nullptr;
+ }
+}
+
+} // namespace mixers
+} // namespace audio
+} // namespace media
+} // namespace mojo
diff --git a/services/media/audio/platform/generic/mixers/point_sampler.h b/services/media/audio/platform/generic/mixers/point_sampler.h
new file mode 100644
index 0000000..01f1190
--- /dev/null
+++ b/services/media/audio/platform/generic/mixers/point_sampler.h
@@ -0,0 +1,27 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXERS_POINT_SAMPLER_H_
+#define SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXERS_POINT_SAMPLER_H_
+
+#include "mojo/services/media/common/interfaces/media_types.mojom.h"
+#include "services/media/audio/platform/generic/mixer.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+namespace mixers {
+
+class PointSampler : public Mixer {
+ public:
+ static MixerPtr Select(const LpcmMediaTypeDetailsPtr& src_format,
+ const LpcmMediaTypeDetailsPtr& dst_format);
+};
+
+} // namespace mixers
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_MIXERS_POINT_SAMPLER_H_
diff --git a/services/media/audio/platform/generic/standard_output_base.cc b/services/media/audio/platform/generic/standard_output_base.cc
new file mode 100644
index 0000000..eb7c0f3
--- /dev/null
+++ b/services/media/audio/platform/generic/standard_output_base.cc
@@ -0,0 +1,491 @@
+// 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 <limits>
+
+#include "base/logging.h"
+#include "services/media/audio/audio_track_impl.h"
+#include "services/media/audio/audio_track_to_output_link.h"
+#include "services/media/audio/platform/generic/mixer.h"
+#include "services/media/audio/platform/generic/standard_output_base.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+static constexpr LocalDuration MAX_TRIM_PERIOD = local_time::from_msec(10);
+constexpr uint32_t StandardOutputBase::MixJob::INVALID_GENERATION;
+
+StandardOutputBase::TrackBookkeeping::TrackBookkeeping() {}
+StandardOutputBase::TrackBookkeeping::~TrackBookkeeping() {}
+
+StandardOutputBase::StandardOutputBase(AudioOutputManager* manager)
+ : AudioOutput(manager) {
+ setup_mix_ =
+ [this] (const AudioTrackImplPtr& track, TrackBookkeeping* info) -> bool {
+ return SetupMix(track, info);
+ };
+
+ process_mix_ =
+ [this] (const AudioTrackImplPtr& track,
+ TrackBookkeeping* info,
+ const AudioPipe::AudioPacketRefPtr& pkt_ref) -> bool {
+ return ProcessMix(track, info, pkt_ref);
+ };
+
+ setup_trim_ =
+ [this] (const AudioTrackImplPtr& track, TrackBookkeeping* info) -> bool {
+ return SetupTrim(track, info);
+ };
+
+ process_trim_ =
+ [this] (const AudioTrackImplPtr& track,
+ TrackBookkeeping* info,
+ const AudioPipe::AudioPacketRefPtr& pkt_ref) -> bool {
+ return ProcessTrim(track, info, pkt_ref);
+ };
+
+ next_sched_time_ = LocalClock::now();
+ next_sched_time_known_ = true;
+}
+
+StandardOutputBase::~StandardOutputBase() {}
+
+void StandardOutputBase::Process() {
+ bool mixed = false;
+ LocalTime now = LocalClock::now();
+
+ // At this point, we should always know when our implementation would like to
+ // be called to do some mixing work next. If we do not know, then we should
+ // have already shut down.
+ //
+ // If the next sched time has not arrived yet, don't attempt to mix anything.
+ // Just trim the queues and move on.
+ DCHECK(next_sched_time_known_);
+ if (now >= next_sched_time_) {
+ // Clear the flag, if the implementation does not set this flag by calling
+ // SetNextSchedTime during the cycle, we consider it to be an error and shut
+ // down.
+ next_sched_time_known_ = false;
+
+ // As long as our implementation wants to mix more and has not run into a
+ // problem trying to finish the mix job, mix some more.
+ do {
+ ::memset(&cur_mix_job_, 0, sizeof(cur_mix_job_));
+
+ if (!StartMixJob(&cur_mix_job_, now)) {
+ break;
+ }
+
+ ForeachTrack(setup_mix_, process_mix_);
+ mixed = true;
+ } while (FinishMixJob(cur_mix_job_));
+ }
+
+ if (!next_sched_time_known_) {
+ // TODO(johngro): log this as an error.
+ ShutdownSelf();
+ return;
+ }
+
+ // If we mixed nothing this time, make sure that we trim all of our track
+ // queues. No matter what is going on with the output hardware, we are not
+ // allowed to hold onto the queued data past its presentation time.
+ if (!mixed) {
+ ForeachTrack(setup_trim_, process_trim_);
+ }
+
+ // Figure out when we should wake up to do more work again. No matter how
+ // long our implementation wants to wait, we need to make sure to wake up and
+ // periodically trim our input queues.
+ LocalTime max_sched_time = now + MAX_TRIM_PERIOD;
+ ScheduleCallback((next_sched_time_ > max_sched_time)
+ ? max_sched_time
+ : next_sched_time_);
+}
+
+MediaResult StandardOutputBase::InitializeLink(
+ const AudioTrackToOutputLinkPtr& link) {
+ TrackBookkeeping* bk = AllocBookkeeping();
+ AudioTrackToOutputLink::BookkeepingPtr ref(bk);
+
+ // We should never fail to allocate our bookkeeping. The only way this can
+ // happen is if we have a badly behaved implementation.
+ if (!bk) { return MediaResult::INTERNAL_ERROR; }
+
+ // We cannot proceed if our track has somehow managed to go away already.
+ AudioTrackImplPtr track = link->GetTrack();
+ if (!track) { return MediaResult::INVALID_ARGUMENT; }
+
+ // Pick a mixer based on the input and output formats.
+ bk->mixer = Mixer::Select(track->Format(), output_format_);
+ if (bk->mixer == nullptr) { return MediaResult::UNSUPPORTED_CONFIG; }
+
+ // Looks like things went well. Stash a reference to our bookkeeping and get
+ // out.
+ link->output_bookkeeping() = std::move(ref);
+ return MediaResult::OK;
+}
+
+StandardOutputBase::TrackBookkeeping* StandardOutputBase::AllocBookkeeping() {
+ return new TrackBookkeeping();
+}
+
+void StandardOutputBase::ForeachTrack(const TrackSetupTask& setup,
+ const TrackProcessTask& process) {
+ for (auto iter = links_.begin(); iter != links_.end(); ) {
+ if (shutting_down()) { return; }
+
+ // Is the track still around? If so, process it. Otherwise, remove the
+ // track entry and move on.
+ const AudioTrackToOutputLinkPtr& link = *iter;
+ AudioTrackImplPtr track(link->GetTrack());
+
+ auto tmp_iter = iter++;
+ if (!track) {
+ links_.erase(tmp_iter);
+ continue;
+ }
+
+ // It would be nice to be able to use a dynamic cast for this, but currently
+ // we are building with no-rtti
+ TrackBookkeeping* info =
+ static_cast<TrackBookkeeping*>(link->output_bookkeeping().get());
+ DCHECK(info);
+
+ // Make sure that the mapping between the track's frame time domain and
+ // local time is up to date.
+ info->UpdateTrackTrans(track);
+
+ bool setup_done = false;
+ AudioPipe::AudioPacketRefPtr pkt_ref;
+ while (true) {
+ // Try to grab the front of the packet queue. If it has been flushed
+ // since the last time we grabbed it, be sure to reset our mixer's
+ // internal filter state.
+ bool was_flushed;
+ pkt_ref = link->LockPendingQueueFront(&was_flushed);
+ if (was_flushed) {
+ info->mixer->Reset();
+ }
+
+ // If the queue is empty, then we are done.
+ if (!pkt_ref) { break; }
+
+ // If we have not set up for this track yet, do so. If the setup fails
+ // for any reason, stop processing packets for this track.
+ if (!setup_done) {
+ setup_done = setup(track, info);
+ if (!setup_done) { break; }
+ }
+
+ // Now process the packet which is at the front of the track's queue. If
+ // the packet has been entirely consumed, pop it off the front and proceed
+ // to the next one. Otherwise, we are finished.
+ if (!process(track, info, pkt_ref)) { break; }
+ link->UnlockPendingQueueFront(&pkt_ref, true);
+ }
+
+ // Unlock the queue and proceed to the next track.
+ link->UnlockPendingQueueFront(&pkt_ref, false);
+
+ // Note: there is no point in doing this for the trim task, but it dosn't
+ // hurt anything, and its easier then introducing another function to the
+ // ForeachTrack arguments to run after each track is processed just for the
+ // purpose of setting this flag.
+ cur_mix_job_.accumulate = true;
+ }
+}
+
+bool StandardOutputBase::SetupMix(const AudioTrackImplPtr& track,
+ TrackBookkeeping* info) {
+ // If we need to recompose our transformation from output frame space to input
+ // fractional frames, do so now.
+ DCHECK(info);
+ info->UpdateOutputTrans(cur_mix_job_);
+ cur_mix_job_.frames_produced = 0;
+
+ return true;
+}
+
+bool StandardOutputBase::ProcessMix(
+ const AudioTrackImplPtr& track,
+ TrackBookkeeping* info,
+ const AudioPipe::AudioPacketRefPtr& pkt_ref) {
+ // Sanity check our parameters.
+ DCHECK(info);
+ DCHECK(pkt_ref);
+
+ // We had better have a valid job, or why are we here?
+ DCHECK(cur_mix_job_.buf);
+ DCHECK(cur_mix_job_.buf_frames);
+ DCHECK(cur_mix_job_.frames_produced <= cur_mix_job_.buf_frames);
+
+ // Have we produced all that we are supposed to? If so, hold the current
+ // packet and move on to the next track.
+ if (cur_mix_job_.frames_produced >= cur_mix_job_.buf_frames) {
+ return false;
+ }
+
+ uint32_t frames_left = cur_mix_job_.buf_frames - cur_mix_job_.frames_produced;
+ void* buf = static_cast<uint8_t*>(cur_mix_job_.buf)
+ + (cur_mix_job_.frames_produced * output_bytes_per_frame_);
+
+ // Figure out where this job starts, expressed in fractional input frames.
+ int64_t start_pts_ftf;
+ bool good = info->out_frames_to_track_frames.DoForwardTransform(
+ cur_mix_job_.start_pts_of + cur_mix_job_.frames_produced,
+ &start_pts_ftf);
+ DCHECK(good);
+
+ // If the start of this mix job is past the end of this packet presentation,
+ // do no mixing. Let the ForeachTrack loop know that we are done with the
+ // packet and it can be released.
+ if (start_pts_ftf >= pkt_ref->end_pts()) {
+ return true;
+ }
+
+ // If this track is currently paused (or being sampled extremely slowly), our
+ // step size will be zero. We know that this packet will be relevant at some
+ // point in the future, but right now it contributes nothing. Tell the
+ // ForeachTrack loop that we are done and to hold onto this packet for now.
+ if (!info->step_size) {
+ return false;
+ }
+
+ // Figure out how many output samples into the current job this packet starts.
+ int64_t delta;
+ int64_t output_offset_64;
+ if (pkt_ref->start_pts() > start_pts_ftf) {
+ delta = pkt_ref->start_pts() - start_pts_ftf;
+ output_offset_64 = delta + info->step_size - 1;
+ output_offset_64 /= info->step_size;
+ } else {
+ output_offset_64 = 0;
+ }
+ DCHECK_GE(output_offset_64, 0);
+
+ // If this packet starts after the end of this job (entirely in the future),
+ // then we are done for now.
+ if (output_offset_64 >= frames_left) {
+ return false;
+ }
+
+ // Figure out the offset (in fractional frames) into this packet where we want
+ // to start sampling.
+ int64_t input_offset_64;
+ if (output_offset_64) {
+ input_offset_64 = output_offset_64 * info->step_size;
+ input_offset_64 -= delta;
+ DCHECK_LT(input_offset_64, info->step_size);
+ } else {
+ input_offset_64 = start_pts_ftf - pkt_ref->start_pts();
+ }
+ DCHECK_GE(input_offset_64, 0);
+ DCHECK_LE(input_offset_64, std::numeric_limits<int32_t>::max());
+ DCHECK_LT(input_offset_64, pkt_ref->end_pts() - pkt_ref->start_pts());
+
+ uint32_t input_offset = static_cast<uint32_t>(input_offset_64);
+ uint32_t output_offset = static_cast<uint32_t>(output_offset_64);
+ const auto& regions = pkt_ref->regions();
+ DCHECK(info->mixer != nullptr);
+
+ for (size_t i = 0;
+ (i < regions.size()) && (output_offset < frames_left);
+ ++i) {
+ const auto& region = regions[i];
+
+ if (input_offset >= region.frac_frame_len) {
+ input_offset -= region.frac_frame_len;
+ continue;
+ }
+
+ bool consumed_source = info->mixer->Mix(buf,
+ frames_left,
+ &output_offset,
+ region.base,
+ region.frac_frame_len,
+ &input_offset,
+ info->step_size,
+ cur_mix_job_.accumulate);
+ DCHECK_LE(output_offset, frames_left);
+
+ if (!consumed_source) {
+ // Looks like we didn't consume all of this region. Assert that we have
+ // produced all of our frames and we are done.
+ DCHECK(output_offset == frames_left);
+ return false;
+ }
+
+ input_offset -= region.frac_frame_len;
+ }
+
+ cur_mix_job_.frames_produced += output_offset;
+ DCHECK(cur_mix_job_.frames_produced <= cur_mix_job_.buf_frames);
+ return true;
+}
+
+bool StandardOutputBase::SetupTrim(const AudioTrackImplPtr& track,
+ TrackBookkeeping* info) {
+ // Compute the cutoff time we will use to decide wether or not to trim
+ // packets. ForeachTracks has already updated our transformation, no need
+ // for us to do so here.
+ DCHECK(info);
+
+ int64_t local_now_ticks = LocalClock::now().time_since_epoch().count();
+
+ // The behavior of the RateControlBase implementation guarantees that the
+ // transformation into the media timeline is never singular. If the
+ // forward transformation fails it can only be because of an overflow,
+ // which should be impossible unless the user has defined a playback rate
+ // where the ratio between media time ticks and local time ticks is
+ // greater than one.
+ //
+ // IOW - this should never happen. If it does, we just stop processing
+ // payloads.
+ //
+ // TODO(johngro): Log an error? Communicate this to the user somehow?
+ if (!info->lt_to_track_frames.DoForwardTransform(local_now_ticks,
+ &trim_threshold_)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool StandardOutputBase::ProcessTrim(
+ const AudioTrackImplPtr& track,
+ TrackBookkeeping* info,
+ const AudioPipe::AudioPacketRefPtr& pkt_ref) {
+ DCHECK(pkt_ref);
+
+ // If the presentation end of this packet is in the future, stop trimming.
+ if (pkt_ref->end_pts() > trim_threshold_) {
+ return false;
+ }
+
+ return true;
+}
+
+void StandardOutputBase::TrackBookkeeping::UpdateTrackTrans(
+ const AudioTrackImplPtr& track) {
+ LinearTransform tmp;
+ uint32_t gen;
+
+ DCHECK(track);
+ track->SnapshotRateTrans(&tmp, &gen);
+
+ // If the local time -> media time transformation has not changed since the
+ // last time we examines it, just get out now.
+ if (lt_to_track_frames_gen == gen) { return; }
+
+ // The transformation has changed, re-compute the local time -> track frame
+ // transformation.
+ LinearTransform scale(0, track->FractionalFrameToMediaTimeRatio(), 0);
+ bool good;
+
+ lt_to_track_frames.a_zero = tmp.a_zero;
+ good = scale.DoReverseTransform(tmp.b_zero, <_to_track_frames.b_zero);
+ DCHECK(good);
+ good = LinearTransform::Ratio::Compose(scale.scale,
+ tmp.scale,
+ <_to_track_frames.scale);
+ DCHECK(good);
+
+ // Update the generation, and invalidate the output to track generation.
+ lt_to_track_frames_gen = gen;
+ out_frames_to_track_frames_gen = MixJob::INVALID_GENERATION;
+}
+
+void StandardOutputBase::TrackBookkeeping::UpdateOutputTrans(
+ const MixJob& job) {
+ // We should not be here unless we have a valid mix job. From our point of
+ // view, this means that we have a job which supplies a valid transformation
+ // from local time to output frames.
+ DCHECK(job.local_to_output);
+ DCHECK(job.local_to_output_gen != MixJob::INVALID_GENERATION);
+
+ // If our generations match, we don't need to re-compute anything. Just use
+ // what we have already.
+ if (out_frames_to_track_frames_gen == job.local_to_output_gen) { return; }
+
+ // Assert that we have a good mapping from local time to fractional track
+ // frames.
+ //
+ // TODO(johngro): Don't assume that 0 means invalid. Make it a proper
+ // constant defined somewhere.
+ DCHECK(lt_to_track_frames_gen);
+
+ // Compose the job supplied transformation from local to output with the
+ // track supplied mapping from local to fraction input frames to produce a
+ // transformation which maps from output frames to fractional input frames.
+ //
+ // TODO(johngro): Make this composition operation part of the LinearTransform
+ // class instead of doing it by hand here. Its a more complicated task that
+ // one might initially think, because of the need to deal with the
+ // intermediate offset term, and distributing it to either side of the end of
+ // the transformation with a minimum amt of loss, while avoiding overflow.
+ //
+ // For now, we punt, do it by hand and just assume that everything went well.
+ LinearTransform& dst = out_frames_to_track_frames;
+
+ // Distribute the intermediate offset entirely to the fractional frame domain
+ // for now. We can do better by extracting portions of the intermedate
+ // offset that can be scaled by the ratios on either side of with without
+ // loss, but for now this should be close enough.
+ int64_t intermediate = job.local_to_output->a_zero
+ - lt_to_track_frames.a_zero;
+ int64_t track_frame_offset;
+
+ // TODO(johngro): add routines to LinearTransform::Ratio which allow us to
+ // scale using just a ratio without needing to create a linear transform with
+ // empty offsets.
+ LinearTransform tmp(0, lt_to_track_frames.scale, 0);
+ bool good = tmp.DoForwardTransform(intermediate, &track_frame_offset);
+ DCHECK(good);
+
+ dst.a_zero = job.local_to_output->b_zero;
+ dst.b_zero = lt_to_track_frames.b_zero + track_frame_offset;
+
+ // TODO(johngro): Add options to allow us to invert one or both of the ratios
+ // during composition instead of needing to make a temporary ratio to
+ // acomplish the task.
+ LinearTransform::Ratio tmp_ratio(job.local_to_output->scale.denominator,
+ job.local_to_output->scale.numerator);
+ good = LinearTransform::Ratio::Compose(tmp_ratio,
+ lt_to_track_frames.scale,
+ &dst.scale);;
+ DCHECK(good);
+
+ // Finally, compute the step size in fractional frames. IOW, every time we
+ // move forward one output frame, how many fractional frames of input do we
+ // consume. Don't bother doing the multiplication if we already know that the
+ // numerator is zero.
+ //
+ // TODO(johngro): same complaint as before... Do this without a temp. The
+ // special casing should be handled in the routine added to
+ // LinearTransform::Ratio.
+ DCHECK(dst.scale.denominator);
+ if (!dst.scale.numerator) {
+ step_size = 0;
+ } else {
+ LinearTransform tmp(0, dst.scale, 0);
+ int64_t tmp_step_size;
+
+ good = tmp.DoForwardTransform(1, &tmp_step_size);
+
+ DCHECK(good);
+ DCHECK_GE(tmp_step_size, 0);
+ DCHECK_LE(tmp_step_size, std::numeric_limits<uint32_t>::max());
+
+ step_size = static_cast<uint32_t>(tmp_step_size);
+ }
+
+ // Done, update our generation.
+ out_frames_to_track_frames_gen = job.local_to_output_gen;
+}
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
diff --git a/services/media/audio/platform/generic/standard_output_base.h b/services/media/audio/platform/generic/standard_output_base.h
new file mode 100644
index 0000000..fe3205a
--- /dev/null
+++ b/services/media/audio/platform/generic/standard_output_base.h
@@ -0,0 +1,117 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_STANDARD_OUTPUT_BASE_H_
+#define SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_STANDARD_OUTPUT_BASE_H_
+
+#include "base/callback.h"
+#include "mojo/services/media/common/cpp/linear_transform.h"
+#include "mojo/services/media/common/cpp/local_time.h"
+#include "mojo/services/media/common/interfaces/media_common.mojom.h"
+#include "mojo/services/media/common/interfaces/media_types.mojom.h"
+#include "services/media/audio/audio_output.h"
+#include "services/media/audio/audio_track_to_output_link.h"
+#include "services/media/audio/platform/generic/mixer.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class StandardOutputBase : public AudioOutput {
+ public:
+ ~StandardOutputBase() override;
+
+ protected:
+ struct MixJob {
+ static constexpr uint32_t INVALID_GENERATION = 0;
+
+ // State for the job set up once by the output implementation and then used
+ // by all tracks.
+ void* buf;
+ uint32_t buf_frames;
+ int64_t start_pts_of; // start PTS, expressed in output frames.
+ uint32_t local_to_output_gen;
+ bool accumulate;
+ const LinearTransform* local_to_output;
+
+ // State for the job which is set up for each track during SetupMix
+ uint32_t frames_produced;
+ };
+
+ struct TrackBookkeeping : public AudioTrackToOutputLink::Bookkeeping {
+ TrackBookkeeping();
+ ~TrackBookkeeping() override;
+
+ LinearTransform lt_to_track_frames;
+ LinearTransform out_frames_to_track_frames;
+ uint32_t lt_to_track_frames_gen = 0;
+ uint32_t out_frames_to_track_frames_gen = MixJob::INVALID_GENERATION;
+ uint32_t step_size;
+ MixerPtr mixer;
+
+ void UpdateTrackTrans(const AudioTrackImplPtr& track);
+ void UpdateOutputTrans(const MixJob& job);
+ };
+
+ explicit StandardOutputBase(AudioOutputManager* manager);
+
+ void Process() final;
+ MediaResult InitializeLink(const AudioTrackToOutputLinkPtr& link) final;
+
+ void SetNextSchedTime(const LocalTime& next_sched_time) {
+ next_sched_time_ = next_sched_time;
+ next_sched_time_known_ = true;
+ }
+
+ void SetNextSchedDelay(const LocalDuration& next_sched_delay) {
+ SetNextSchedTime(LocalClock::now() + next_sched_delay);
+ }
+
+ virtual bool StartMixJob(MixJob* job, const LocalTime& process_start) = 0;
+ virtual bool FinishMixJob(const MixJob& job) = 0;
+ virtual TrackBookkeeping* AllocBookkeeping();
+
+ LpcmMediaTypeDetailsPtr output_format_;
+ uint32_t output_bytes_per_frame_;
+
+ private:
+ using TrackSetupTask = std::function<bool(const AudioTrackImplPtr& track,
+ TrackBookkeeping* info)>;
+ using TrackProcessTask =
+ std::function<bool(const AudioTrackImplPtr& track,
+ TrackBookkeeping* info,
+ const AudioPipe::AudioPacketRefPtr& pkt_ref)>;
+
+ void ForeachTrack(const TrackSetupTask& setup,
+ const TrackProcessTask& process);
+
+ bool SetupMix(const AudioTrackImplPtr& track, TrackBookkeeping* info);
+ bool ProcessMix(const AudioTrackImplPtr& track,
+ TrackBookkeeping* info,
+ const AudioPipe::AudioPacketRefPtr& pkt_ref);
+
+ bool SetupTrim(const AudioTrackImplPtr& track, TrackBookkeeping* info);
+ bool ProcessTrim(const AudioTrackImplPtr& track,
+ TrackBookkeeping* info,
+ const AudioPipe::AudioPacketRefPtr& pkt_ref);
+
+ LocalTime next_sched_time_;
+ bool next_sched_time_known_;
+
+ // State used by the mix task.
+ TrackSetupTask setup_mix_;
+ TrackProcessTask process_mix_;
+ MixJob cur_mix_job_;
+
+ // State used by the trim task.
+ TrackSetupTask setup_trim_;
+ TrackProcessTask process_trim_;
+ int64_t trim_threshold_;
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_STANDARD_OUTPUT_BASE_H_
diff --git a/services/media/audio/platform/generic/throttle_output.cc b/services/media/audio/platform/generic/throttle_output.cc
new file mode 100644
index 0000000..47489a0
--- /dev/null
+++ b/services/media/audio/platform/generic/throttle_output.cc
@@ -0,0 +1,74 @@
+// 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/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "mojo/services/media/common/cpp/local_time.h"
+#include "services/media/audio/audio_output_manager.h"
+#include "services/media/audio/platform/generic/throttle_output.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+static constexpr LocalDuration TRIM_PERIOD = local_time::from_msec(10);
+
+ThrottleOutput::ThrottleOutput(AudioOutputManager* manager)
+ : StandardOutputBase(manager) {}
+
+ThrottleOutput::~ThrottleOutput() {}
+
+AudioOutputPtr ThrottleOutput::New(AudioOutputManager* manager) {
+ return AudioOutputPtr(new ThrottleOutput(manager));
+}
+
+MediaResult ThrottleOutput::Init() {
+ last_sched_time_ = LocalClock::now();
+ return MediaResult::OK;
+}
+
+bool ThrottleOutput::StartMixJob(MixJob* job,
+ const LocalTime& process_start) {
+ // Compute our next callback time, and check to see if we are falling behind
+ // in the process.
+ last_sched_time_ += TRIM_PERIOD;
+ if (process_start > last_sched_time_) {
+ // TODO(johngro): We are falling behind on our trimming. We should
+ // probably tell someone.
+ last_sched_time_ = process_start + TRIM_PERIOD;;
+ }
+
+ // TODO(johngro): We could optimize this trim operation by scheduling our
+ // callback to the time at which the first pending packet in our queue will
+ // end, instead of using this polling style. This would have the addition
+ // benefit of tighten the timing on returning packets (currently, we could
+ // hold a packet for up to TRIM_PERIOD - episilon past its end pts before
+ // releasing it)
+ //
+ // In order to do this, however, we would to wake up and recompute whenever
+ // the rate transformations for one of our client tracks changes. For now, we
+ // just poll because its simpler.
+ SetNextSchedTime(last_sched_time_);
+
+ // The throttle output never actually mixes anything, it just provides
+ // backpressure to the pipeline by holding references to AudioPackets until
+ // after their presentation should be finished. All we need to do here is
+ // schedule our next callback to keep things running, and let the base class
+ // implementation handle trimming the output.
+ return false;
+}
+
+bool ThrottleOutput::FinishMixJob(const MixJob& job) {
+ // Since we never start any jobs, this should never be called.
+ DCHECK(false);
+ return false;
+}
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
diff --git a/services/media/audio/platform/generic/throttle_output.h b/services/media/audio/platform/generic/throttle_output.h
new file mode 100644
index 0000000..5889620
--- /dev/null
+++ b/services/media/audio/platform/generic/throttle_output.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_THROTTLE_OUTPUT_H_
+#define SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_THROTTLE_OUTPUT_H_
+
+#include "base/callback.h"
+#include "mojo/services/media/common/cpp/local_time.h"
+#include "services/media/audio/platform/generic/standard_output_base.h"
+
+namespace mojo {
+namespace media {
+namespace audio {
+
+class ThrottleOutput : public StandardOutputBase {
+ public:
+ static AudioOutputPtr New(AudioOutputManager* manager);
+ ~ThrottleOutput() override;
+
+ protected:
+ explicit ThrottleOutput(AudioOutputManager* manager);
+
+ // AudioOutput Implementation
+ MediaResult Init() override;
+
+ // StandardOutputBase Implementation
+ bool StartMixJob(MixJob* job, const LocalTime& process_start) override;
+ bool FinishMixJob(const MixJob& job) override;
+
+ private:
+ LocalTime last_sched_time_;
+};
+
+} // namespace audio
+} // namespace media
+} // namespace mojo
+
+#endif // SERVICES_MEDIA_AUDIO_PLATFORM_GENERIC_THROTTLE_OUTPUT_H_
+