summaryrefslogtreecommitdiffstats
path: root/media/audio/linux
diff options
context:
space:
mode:
authorajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-21 05:42:08 +0000
committerajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-21 05:42:08 +0000
commitfe24ccff145ec31f31b3504419440f1639581862 (patch)
treeaa0e8cc602324d4760949f00f3ac684b6680d551 /media/audio/linux
parent03269cf96713c7b44cf7fe1f8241d0b2e33a7ab6 (diff)
downloadchromium_src-fe24ccff145ec31f31b3504419440f1639581862.zip
chromium_src-fe24ccff145ec31f31b3504419440f1639581862.tar.gz
chromium_src-fe24ccff145ec31f31b3504419440f1639581862.tar.bz2
This iteration creates one message loop per audio output stream that is responsible for buffering data from the data source, and writing the buffers to the output device. This design blocks while waiting for data to be retrieved from the data source, which can lead to unnecessary buffer underruns in the audio device. This will be solved later after the non-blocking AudioSource interface is written.
Review URL: http://codereview.chromium.org/115276 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16583 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio/linux')
-rw-r--r--media/audio/linux/alsa_output.cc521
-rw-r--r--media/audio/linux/alsa_output.h165
-rw-r--r--media/audio/linux/audio_manager_linux.cc67
-rw-r--r--media/audio/linux/audio_manager_linux.h32
4 files changed, 782 insertions, 3 deletions
diff --git a/media/audio/linux/alsa_output.cc b/media/audio/linux/alsa_output.cc
new file mode 100644
index 0000000..0dc9ca9
--- /dev/null
+++ b/media/audio/linux/alsa_output.cc
@@ -0,0 +1,521 @@
+// Copyright (c) 2009 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.
+//
+// The audio stream implementation is made difficult because different methods
+// are available for calling depending on what state the stream is. Here is the
+// state transition table for the stream.
+//
+// STATE_CREATED -> Open() -> STATE_OPENED
+// STATE_OPENED -> Start() -> STATE_STARTED
+// STATE_OPENED -> Close() -> STATE_CLOSED
+// STATE_STARTED -> Stop() -> STATE_STOPPED
+// STATE_STARTED -> Close() -> STATE_CLOSING | STATE_CLOSED
+// STATE_STOPPED -> Close() -> STATE_CLOSING | STATE_CLOSED
+// STATE_CLOSING -> [automatic] -> STATE_CLOSED
+//
+// Error states and resource management:
+//
+// Entrance into STATE_STOPPED signals schedules a call to ReleaseResources().
+//
+// Any state may transition to STATE_ERROR. On transitioning into STATE_ERROR,
+// the function doing the transition is reponsible for scheduling a call to
+// ReleaseResources() or otherwise ensuring resources are cleaned (eg., as is
+// done in Open()). This should be done while holding the lock to avoid a
+// destruction race condition where the stream is deleted via ref-count before
+// the ReleaseResources() task is scheduled. In particular, be careful of
+// resource management in a transtiion from STATE_STOPPED -> STATE_ERROR if
+// that becomes necessary in the future.
+//
+// STATE_ERROR may transition to STATE_CLOSED. In this situation, no further
+// resource management is done because it is assumed that the resource
+// reclaimation was executed at the point of the state transition into
+// STATE_ERROR.
+//
+// Entrance into STATE_CLOSED implies a transition through STATE_STOPPED, which
+// triggers the resource management code.
+//
+// The destructor is not responsible for ultimate cleanup of resources.
+// Instead, it only checks that the stream is in a state where all resources
+// have been cleaned up. These states are STATE_CREATED, STATE_CLOSED,
+// STATE_ERROR.
+//
+// TODO(ajwong): This incorrectly handles the period size for filling of the
+// ALSA device buffer. Currently the period size is hardcoded, and not
+// reported to the sound device. Also, there are options that let the device
+// wait until the buffer is minimally filled before playing. Those should be
+// explored. Also consider doing the device interactions either outside of the
+// class lock, or under a different lock to avoid unecessarily blocking other
+// threads.
+
+#include "media/audio/linux/alsa_output.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util-inl.h"
+#include "base/time.h"
+
+// Require 10ms latency from the audio device. Taken from ALSA documentation
+// example.
+// TODO(ajwong): Figure out what this parameter actually does, and what a good
+// value would be.
+static const unsigned int kTargetLatencyMicroseconds = 10000;
+
+// Minimal amount of time to sleep. If any future event is expected to
+// execute within this timeframe, treat it as if it should execute immediately.
+//
+// TODO(ajwong): Determine if there is a sensible minimum sleep resolution and
+// adjust accordingly.
+static const int64 kMinSleepMilliseconds = 10L;
+
+const char* AlsaPCMOutputStream::kDefaultDevice = "plug:default";
+
+AlsaPCMOutputStream::AlsaPCMOutputStream(const std::string& device_name,
+ int min_buffer_ms,
+ AudioManager::Format format,
+ int channels,
+ int sample_rate,
+ char bits_per_sample)
+ : state_(STATE_CREATED),
+ device_name_(device_name),
+ playback_handle_(NULL),
+ source_callback_(NULL),
+ playback_thread_("PlaybackThread"),
+ channels_(channels),
+ sample_rate_(sample_rate),
+ bits_per_sample_(bits_per_sample),
+ min_buffer_frames_((min_buffer_ms * sample_rate_) /
+ base::Time::kMillisecondsPerSecond),
+ packet_size_(0),
+ device_write_suspended_(true), // Start suspended.
+ resources_released_(false) {
+ CHECK(channels_ == 2) << "Only 2-channel audio is supported right now.";
+ CHECK(AudioManager::AUDIO_PCM_LINEAR == format)
+ << "Only linear PCM supported.";
+ CHECK(bits_per_sample % 8 == 0) << "Only allow byte-aligned samples";
+
+ // Reference self to avoid accidental deletion before the message loop is
+ // done.
+ AddRef();
+
+ switch (bits_per_sample) {
+ case 8:
+ pcm_format_ = SND_PCM_FORMAT_S8;
+ break;
+
+ case 16:
+ pcm_format_ = SND_PCM_FORMAT_S16;
+ break;
+
+ case 24:
+ pcm_format_ = SND_PCM_FORMAT_S24;
+ break;
+
+ case 32:
+ pcm_format_ = SND_PCM_FORMAT_S32;
+ break;
+
+ default:
+ LOG(DFATAL) << "Unsupported bits per sample: " << bits_per_sample_;
+ state_ = STATE_ERROR;
+ break;
+ }
+
+ // Interleaved audio is expected, so each frame has one sample per channel.
+ bytes_per_frame_ = channels_ * (bits_per_sample_ / 8);
+}
+
+AlsaPCMOutputStream::~AlsaPCMOutputStream() {
+ AutoLock l(lock_);
+ // In STATE_CREATED, STATE_CLOSED, and STATE_ERROR, resources are guaranteed
+ // to be released.
+ CHECK(state_ == STATE_CREATED ||
+ state_ == STATE_CLOSED ||
+ state_ == STATE_ERROR);
+}
+
+bool AlsaPCMOutputStream::Open(size_t packet_size) {
+ AutoLock l(lock_);
+
+ // Check that stream is coming from the correct state and early out if not.
+ if (state_ == STATE_ERROR) {
+ return false;
+ }
+ if (state_ != STATE_CREATED) {
+ NOTREACHED() << "Stream must be in STATE_CREATED on Open. Instead in: "
+ << state_;
+ return false;
+ }
+
+ // Open the device and set the parameters.
+ // TODO(ajwong): Can device open block? Probably. If yes, we need to move
+ // the open call into a different thread.
+ int error = snd_pcm_open(&playback_handle_, device_name_.c_str(),
+ SND_PCM_STREAM_PLAYBACK, 0);
+ if (error < 0) {
+ LOG(ERROR) << "Cannot open audio device (" << device_name_ << "): "
+ << snd_strerror(error);
+ EnterStateError_Locked();
+ return false;
+ }
+ if ((error = snd_pcm_set_params(playback_handle_,
+ pcm_format_,
+ SND_PCM_ACCESS_RW_INTERLEAVED,
+ channels_,
+ sample_rate_,
+ 1, // soft_resample -- let ALSA resample
+ kTargetLatencyMicroseconds)) < 0) {
+ LOG(ERROR) << "Unable to set PCM parameters: " << snd_strerror(error);
+ if (!CloseDevice_Locked()) {
+ LOG(WARNING) << "Unable to close audio device. Leaking handle.";
+ }
+ playback_handle_ = NULL;
+ EnterStateError_Locked();
+ return false;
+ }
+
+ // Configure the buffering.
+ packet_size_ = packet_size;
+ DCHECK_EQ(0U, packet_size_ % bytes_per_frame_)
+ << "Buffers should end on a frame boundary. Frame size: "
+ << bytes_per_frame_;
+
+ // Everything is okay. Stream is officially STATE_OPENED for business.
+ state_ = STATE_OPENED;
+
+ return true;
+}
+
+void AlsaPCMOutputStream::Start(AudioSourceCallback* callback) {
+ AutoLock l(lock_);
+
+ // Check that stream is coming from the correct state and early out if not.
+ if (state_ == STATE_ERROR) {
+ return;
+ }
+ if (state_ != STATE_OPENED) {
+ NOTREACHED() << "Can only be started from STATE_OPEN. Current state: "
+ << state_;
+ return;
+ }
+
+ source_callback_ = callback;
+
+ playback_thread_.Start();
+ playback_thread_.message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &AlsaPCMOutputStream::BufferPackets));
+
+ state_ = STATE_STARTED;
+}
+
+void AlsaPCMOutputStream::Stop() {
+ AutoLock l(lock_);
+ // If the stream is in STATE_ERROR, it is effectively stopped already.
+ if (state_ == STATE_ERROR) {
+ return;
+ }
+ StopInternal_Locked();
+}
+
+void AlsaPCMOutputStream::StopInternal_Locked() {
+ // Check the lock is held in a debug build.
+ DCHECK((lock_.AssertAcquired(), true));
+
+ if (state_ != STATE_STARTED) {
+ NOTREACHED() << "Stream must be in STATE_STARTED to Stop. Instead in: "
+ << state_;
+ return;
+ }
+
+ // Move immediately to STATE_STOPPED to signal that all functions should cease
+ // working at this point. Then post a task to the playback thread to release
+ // resources.
+ state_ = STATE_STOPPED;
+
+ playback_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AlsaPCMOutputStream::ReleaseResources));
+}
+
+void AlsaPCMOutputStream::EnterStateError_Locked() {
+ // Check the lock is held in a debug build.
+ DCHECK((lock_.AssertAcquired(), true));
+
+ state_ = STATE_ERROR;
+ resources_released_ = true;
+
+ // TODO(ajwong): Call OnError() on source_callback_.
+}
+
+void AlsaPCMOutputStream::Close() {
+ AutoLock l(lock_);
+
+ // If in STATE_ERROR, all asynchronous resource reclaimation is finished, so
+ // just change states and release this instance to delete ourself.
+ if (state_ == STATE_ERROR) {
+ Release();
+ state_ = STATE_CLOSED;
+ return;
+ }
+
+ // Otherwise, cleanup as necessary.
+ if (state_ == STATE_CLOSED || state_ == STATE_CLOSING) {
+ NOTREACHED() << "Attempting to close twice.";
+ return;
+ }
+
+ // If the stream is still running, stop it.
+ if (state_ == STATE_STARTED) {
+ StopInternal_Locked();
+ }
+
+ // If it is stopped (we may have just transitioned here in the previous if
+ // block), check if the resources have been released. If they have,
+ // transition immediately to STATE_CLOSED. Otherwise, move to
+ // STATE_CLOSING, and the ReleaseResources() task will move to STATE_CLOSED
+ // for us.
+ //
+ // If the stream has been stopped, close.
+ if (state_ == STATE_STOPPED) {
+ if (resources_released_) {
+ state_ = STATE_CLOSED;
+ } else {
+ state_ = STATE_CLOSING;
+ }
+ } else {
+ // TODO(ajwong): Can we safely handle state_ == STATE_CREATED?
+ NOTREACHED() << "Invalid state on close: " << state_;
+ // In release, just move to STATE_ERROR, and hope for the best.
+ EnterStateError_Locked();
+ }
+}
+
+bool AlsaPCMOutputStream::CloseDevice_Locked() {
+ // Check the lock is held in a debug build.
+ DCHECK((lock_.AssertAcquired(), true));
+
+ int error = snd_pcm_close(playback_handle_);
+ if (error < 0) {
+ LOG(ERROR) << "Cannot close audio device (" << device_name_ << "): "
+ << snd_strerror(error);
+ return false;
+ }
+
+ return true;
+}
+
+void AlsaPCMOutputStream::ReleaseResources() {
+ AutoLock l(lock_);
+
+ // Shutdown the audio device.
+ if (!CloseDevice_Locked()) {
+ LOG(WARNING) << "Unable to close audio device. Leaking handle.";
+ playback_handle_ = NULL;
+ }
+
+ // Delete all the buffers.
+ STLDeleteElements(&buffered_packets_);
+
+ // Release the source callback.
+ source_callback_->OnClose(this);
+
+ // Shutdown the thread.
+ DCHECK_EQ(PlatformThread::CurrentId(), playback_thread_.thread_id());
+ playback_thread_.message_loop()->Quit();
+
+ // TODO(ajwong): Do we need to join the playback thread?
+
+ // If the stream is closing, then this function has just completed the last
+ // bit needed before closing. Transition to STATE_CLOSED.
+ if (state_ == STATE_CLOSING) {
+ state_ = STATE_CLOSED;
+ }
+
+ // TODO(ajwong): Currently, the stream is leaked after the |playback_thread_|
+ // is stopped. Find a way to schedule its deletion on another thread, maybe
+ // using a DestructionObserver.
+}
+
+snd_pcm_sframes_t AlsaPCMOutputStream::GetFramesOfDelay_Locked() {
+ // Check the lock is held in a debug build.
+ DCHECK((lock_.AssertAcquired(), true));
+
+ // Find the number of frames queued in the sound device.
+ snd_pcm_sframes_t delay_frames = 0;
+ int error = snd_pcm_delay(playback_handle_, &delay_frames);
+ if (error < 0) {
+ error = snd_pcm_recover(playback_handle_,
+ error /* Original error. */,
+ 0 /* Silenty recover. */);
+ }
+ if (error < 0) {
+ LOG(ERROR) << "Could not query sound device for delay. Assuming 0: "
+ << snd_strerror(error);
+ }
+
+ for (std::deque<Packet*>::const_iterator it = buffered_packets_.begin();
+ it != buffered_packets_.end();
+ ++it) {
+ delay_frames += ((*it)->size - (*it)->used) / bytes_per_frame_;
+ }
+
+ return delay_frames;
+}
+
+void AlsaPCMOutputStream::BufferPackets() {
+ AutoLock l(lock_);
+
+ // Handle early outs for errored, stopped, or closing streams.
+ if (state_ == STATE_ERROR ||
+ state_ == STATE_STOPPED ||
+ state_ == STATE_CLOSING) {
+ return;
+ }
+ if (state_ != STATE_STARTED) {
+ NOTREACHED() << "Invalid stream state while buffering. "
+ << "Expected STATE_STARTED. Current state: " << state_;
+ return;
+ }
+
+ // Early out if the buffer is already full.
+ snd_pcm_sframes_t delay_frames = GetFramesOfDelay_Locked();
+ if (delay_frames < min_buffer_frames_) {
+ // Grab one packet. Drop the lock for the synchronous call. This will
+ // still stall the playback thread, but at least it will not block any
+ // other threads.
+ //
+ // TODO(ajwong): Move to cpu@'s non-blocking audio source.
+ scoped_ptr<Packet> packet;
+ size_t capacity = packet_size_; // Snag it for non-locked usage.
+ {
+ AutoUnlock synchronous_data_fetch(lock_);
+ packet.reset(new Packet(capacity));
+ size_t used = source_callback_->OnMoreData(this, packet->buffer.get(),
+ packet->capacity);
+ CHECK(used <= capacity) << "Data source overran buffer. Aborting.";
+ packet->size = used;
+ // TODO(ajwong): Do more buffer validation here, like checking that the
+ // packet is correctly aligned to frames, etc.
+ }
+ // After reacquiring the lock, recheck state to make sure it is still
+ // STATE_STARTED.
+ if (state_ != STATE_STARTED) {
+ return;
+ }
+ buffered_packets_.push_back(packet.release());
+
+ // Recalculate delay frames.
+ delay_frames = GetFramesOfDelay_Locked();
+ }
+
+ // Since the current implementation of OnMoreData() blocks, only try to grab
+ // one packet per task. If the buffer is still too low, post another
+ // BufferPackets() task immediately. Otherwise, calculate when the buffer is
+ // likely to need filling and schedule a poll for the future.
+ int next_fill_time_ms = (delay_frames - min_buffer_frames_) / sample_rate_;
+ if (next_fill_time_ms <= kMinSleepMilliseconds) {
+ playback_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AlsaPCMOutputStream::BufferPackets));
+ } else {
+ // TODO(ajwong): Measure the reliability of the delay interval. Use
+ // base/histogram.h.
+ playback_thread_.message_loop()->PostDelayedTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AlsaPCMOutputStream::BufferPackets),
+ next_fill_time_ms);
+ }
+
+ // If the |device_write_suspended_|, the audio device write tasks have
+ // stopped scheduling themselves due to an underrun of the in-memory buffer.
+ // Post a new task to restart it since we now have data.
+ if (device_write_suspended_) {
+ device_write_suspended_ = false;
+ playback_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AlsaPCMOutputStream::FillAlsaDeviceBuffer));
+ }
+}
+
+void AlsaPCMOutputStream::FillAlsaDeviceBuffer() {
+ // TODO(ajwong): Try to move some of this code out from underneath the lock.
+ AutoLock l(lock_);
+
+ // Find the number of frames that the device can accept right now.
+ snd_pcm_sframes_t device_buffer_frames_avail =
+ snd_pcm_avail_update(playback_handle_);
+
+ // Write up to |device_buffer_frames_avail| frames to the ALSA device.
+ while (device_buffer_frames_avail > 0) {
+ if (buffered_packets_.empty()) {
+ device_write_suspended_ = true;
+ break;
+ }
+
+ Packet* current_packet = buffered_packets_.front();
+
+ // Only process non 0-lengthed packets.
+ if (current_packet->used < current_packet->size) {
+ // Calculate the number of frames we have to write.
+ char* buffer_pos = current_packet->buffer.get() + current_packet->used;
+ snd_pcm_sframes_t buffer_frames =
+ (current_packet->size - current_packet->used) /
+ bytes_per_frame_;
+ snd_pcm_sframes_t frames_to_write =
+ std::min(buffer_frames, device_buffer_frames_avail);
+
+ // Check that device_buffer_frames_avail isn't < 0.
+ DCHECK_GT(frames_to_write, 0);
+
+ // Write it to the device.
+ int frames_written =
+ snd_pcm_writei(playback_handle_, buffer_pos, frames_to_write);
+ if (frames_written < 0) {
+ // Recover from EINTR, EPIPE (overrun/underrun), ESTRPIPE (stream
+ // suspended).
+ //
+ // TODO(ajwong): Check that we do not need to loop on recover, here and
+ // anywhere else we use recover.
+ frames_written = snd_pcm_recover(playback_handle_,
+ frames_written /* Original error. */,
+ 0 /* Silenty recover. */);
+ }
+ if (frames_written < 0) {
+ LOG(ERROR) << "Failed to write to pcm device: "
+ << snd_strerror(frames_written);
+ ReleaseResources();
+ EnterStateError_Locked();
+ break;
+ } else {
+ current_packet->used += frames_written * bytes_per_frame_;
+ DCHECK_LE(current_packet->used, current_packet->size);
+ }
+ }
+
+ if (current_packet->used >= current_packet->size) {
+ delete current_packet;
+ buffered_packets_.pop_front();
+ }
+ }
+
+ // If the memory buffer was not underrun, schedule another fill in the future.
+ if (!device_write_suspended_) {
+ playback_thread_.message_loop()->PostDelayedTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AlsaPCMOutputStream::FillAlsaDeviceBuffer),
+ kTargetLatencyMicroseconds / base::Time::kMicrosecondsPerMillisecond);
+ }
+}
+
+void AlsaPCMOutputStream::SetVolume(double left_level, double right_level) {
+ NOTIMPLEMENTED();
+}
+
+void AlsaPCMOutputStream::GetVolume(double* left_level, double* right_level) {
+ NOTIMPLEMENTED();
+}
+
+size_t AlsaPCMOutputStream::GetNumBuffers() {
+ return 0;
+}
diff --git a/media/audio/linux/alsa_output.h b/media/audio/linux/alsa_output.h
new file mode 100644
index 0000000..b6d405b
--- /dev/null
+++ b/media/audio/linux/alsa_output.h
@@ -0,0 +1,165 @@
+// Copyright (c) 2009 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.
+//
+// Creates an output stream based on the ALSA PCM interface. The current
+// implementation creates one thread per ALSA playback handle that is
+// responsible for synchronously pulling data from the audio data source.
+//
+// This output stream buffers in two places:
+// (1) In the ALSA device
+// (2) In an in-memory buffer.
+//
+// The ALSA device buffer is kept as full as possible. The in-memory buffer
+// attempts to keep enough extra data so that |min_buffer_ms| worth of data
+// is available between the in-memory buffer and the device buffer. Requests
+// to the audio data source are made if the total amount buffered falls below
+// |min_buffer_ms|.
+//
+// On device write failure, the stream will move into an invalid state. No
+// more data will be pulled from the data source, and the playback thread will
+// be stopped.
+//
+// If the stream is successfully opened, Close() must be called before the
+// stream is deleted.
+
+#ifndef MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_
+#define MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_
+
+#include <alsa/asoundlib.h>
+
+#include <deque>
+#include <string>
+
+#include "base/lock.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/thread.h"
+#include "media/audio/audio_output.h"
+
+class Thread;
+
+class AlsaPCMOutputStream :
+ public AudioOutputStream,
+ public base::RefCountedThreadSafe<AlsaPCMOutputStream> {
+ public:
+ // Set to "plug:default" which should allow ALSA to do whatever is
+ // necessary to simulate an audio device capable of handling our PCMs.
+ static const char* kDefaultDevice;
+
+ // Create a PCM Output stream for the ALSA device identified by
+ // |device_name|. If unsure of hte device_name, use kDefaultDevice.
+ AlsaPCMOutputStream(const std::string& device_name,
+ int min_buffer_ms,
+ AudioManager::Format format,
+ int channels,
+ int sample_rate,
+ char bits_per_sample);
+ virtual ~AlsaPCMOutputStream();
+
+ // Implementation of AudioOutputStream.
+ virtual bool Open(size_t packet_size);
+ virtual void Close();
+ virtual void Start(AudioSourceCallback* callback);
+ virtual void Stop();
+ virtual void SetVolume(double left_level, double right_level);
+ virtual void GetVolume(double* left_level, double* right_level);
+ virtual size_t GetNumBuffers();
+
+ private:
+ // Closes the playback handle, reporting errors if any occur. Returns true
+ // if the device was successfully closed.
+ bool CloseDevice_Locked();
+
+ // Stops playback, ignoring state checks.
+ void StopInternal_Locked();
+
+ // Moves the stream into the error state, setting the correct internal flags.
+ // Ensure that all resources are cleaned up before executing this function.
+ void EnterStateError_Locked();
+
+ // Releases all the resources in the audio stream. This method will also
+ // terminate the playback thread itself.
+ //
+ // This method must be run in the |playback_thead_|.
+ void ReleaseResources();
+
+ // Retrieve the total number of frames buffered in both memory and in the
+ // audio device. Use this to determine if more data should be requested from
+ // the audio source.
+ snd_pcm_sframes_t GetFramesOfDelay_Locked();
+
+ // Buffer more packets from data source if necessary.
+ //
+ // This function must be run in the |playback_thread_|.
+ void BufferPackets();
+
+ // Returns true if our buffer is underfull.
+ bool ShouldBufferMore_NoLock();
+
+ // Write as many buffered packets into the device as there is room in the
+ // device buffer.
+ //
+ // This function must be run in the |playback_thread_|.
+ void FillAlsaDeviceBuffer();
+
+ // State of the stream.
+ State state_;
+
+ // The ALSA device name to use.
+ std::string device_name_;
+
+ // Handle to the actual PCM playback device.
+ snd_pcm_t* playback_handle_;
+
+ // Period size for ALSA ring-buffer. Basically, how long to wait between
+ // writes.
+ snd_pcm_sframes_t period_size_;
+
+ // Callback used to request more data from the data source.
+ AudioSourceCallback* source_callback_;
+
+ // Playback thread.
+ base::Thread playback_thread_;
+
+ // Lock for field access to this object.
+ Lock lock_;
+
+ // Sample format configuration.
+ snd_pcm_format_t pcm_format_;
+ const int channels_;
+ const int sample_rate_;
+ const char bits_per_sample_;
+ char bytes_per_frame_;
+
+ // In-memory buffer to hold sound samples before pushing to the sound device.
+ // TODO(ajwong): There are now about 3 buffer queue implementations. Factor
+ // them out.
+ struct Packet {
+ explicit Packet(int new_capacity)
+ : capacity(new_capacity),
+ size(0),
+ used(0),
+ buffer(new char[capacity]) {
+ }
+ size_t capacity;
+ size_t size;
+ size_t used;
+ scoped_array<char> buffer;
+ };
+ int min_buffer_frames_;
+ std::deque<Packet*> buffered_packets_;
+ size_t packet_size_;
+
+ // Flag indiciating the device write tasks have stopped scheduling
+ // themselves. This should only be modified by the BufferPackets() and
+ // FillAlsaDeviceBuffer() methods.
+ bool device_write_suspended_;
+
+ // Flag indicating that the resources are already cleaned.
+ bool resources_released_;
+
+ DISALLOW_COPY_AND_ASSIGN(AlsaPCMOutputStream);
+};
+
+#endif // MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_
diff --git a/media/audio/linux/audio_manager_linux.cc b/media/audio/linux/audio_manager_linux.cc
index 15a13b2..afe1c3c 100644
--- a/media/audio/linux/audio_manager_linux.cc
+++ b/media/audio/linux/audio_manager_linux.cc
@@ -2,9 +2,70 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/audio/audio_output.h"
+#include "media/audio/linux/audio_manager_linux.h"
-// TODO(hclam): Do something more useful than returning NULL.
-AudioManager* AudioManager::GetAudioManager() {
+#include "base/at_exit.h"
+#include "base/logging.h"
+#include "media/audio/linux/alsa_output.h"
+
+namespace {
+AudioManagerLinux* g_audio_manager = NULL;
+} // namespace
+
+// Implementation of AudioManager.
+bool AudioManagerLinux::HasAudioDevices() {
+ // TODO(ajwong): Make this actually query audio devices.
+ return true;
+}
+
+AudioOutputStream* AudioManagerLinux::MakeAudioStream(Format format,
+ int channels,
+ int sample_rate,
+ char bits_per_sample) {
+ // TODO(ajwong): Do we want to be able to configure the device? plug:default
+ // should work correctly for all mono/stereo, but not surround, which needs
+ // surround40, surround51, etc.
+ //
+ // http://0pointer.de/blog/projects/guide-to-sound-apis.html
+ AlsaPCMOutputStream* stream =
+ new AlsaPCMOutputStream(AlsaPCMOutputStream::kDefaultDevice,
+ 100 /* 100ms minimal buffer */,
+ format, channels, sample_rate, bits_per_sample);
+ return stream;
+}
+
+AudioManagerLinux::AudioManagerLinux() {
+}
+
+AudioManagerLinux::~AudioManagerLinux() {
+}
+
+void AudioManagerLinux::MuteAll() {
+ // TODO(ajwong): Implement.
+ NOTIMPLEMENTED();
+}
+
+void AudioManagerLinux::UnMuteAll() {
+ // TODO(ajwong): Implement.
+ NOTIMPLEMENTED();
+}
+
+const void* AudioManagerLinux::GetLastMockBuffer() {
+ // TODO(ajwong): Implement.
+ NOTIMPLEMENTED();
return NULL;
}
+
+// TODO(ajwong): Collapse this with the windows version.
+void DestroyAudioManagerLinux(void* not_used) {
+ delete g_audio_manager;
+ g_audio_manager = NULL;
+}
+
+AudioManager* AudioManager::GetAudioManager() {
+ if (!g_audio_manager) {
+ g_audio_manager = new AudioManagerLinux();
+ base::AtExitManager::RegisterCallback(&DestroyAudioManagerLinux, NULL);
+ }
+ return g_audio_manager;
+}
diff --git a/media/audio/linux/audio_manager_linux.h b/media/audio/linux/audio_manager_linux.h
new file mode 100644
index 0000000..5b369a1
--- /dev/null
+++ b/media/audio/linux/audio_manager_linux.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2009 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 MEDIA_AUDIO_LINUX_AUDIO_MANAGER_LINUX_H_
+#define MEDIA_AUDIO_LINUX_AUDIO_MANAGER_LINUX_H_
+
+#include "base/thread.h"
+#include "media/audio/audio_output.h"
+
+class AudioManagerLinux : public AudioManager {
+ public:
+ AudioManagerLinux();
+
+ // Implementation of AudioManager.
+ virtual bool HasAudioDevices();
+ virtual AudioOutputStream* MakeAudioStream(Format format, int channels,
+ int sample_rate,
+ char bits_per_sample);
+ virtual void MuteAll();
+ virtual void UnMuteAll();
+ virtual const void* GetLastMockBuffer();
+
+ private:
+ // Friend function for invoking the private destructor at exit.
+ friend void DestroyAudioManagerLinux(void*);
+ virtual ~AudioManagerLinux();
+
+ DISALLOW_COPY_AND_ASSIGN(AudioManagerLinux);
+};
+
+#endif // MEDIA_AUDIO_LINUX_AUDIO_MANAGER_LINUX_H_