diff options
author | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-21 19:25:01 +0000 |
---|---|---|
committer | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-21 19:25:01 +0000 |
commit | 66e3905287330abff21e82db183ef87f63808f0d (patch) | |
tree | d0636f5eb315414a07403aa043de1fd4c10f203f /media/audio | |
parent | ee01e6641130a8d8693fab9f6c876a8e7589ad42 (diff) | |
download | chromium_src-66e3905287330abff21e82db183ef87f63808f0d.zip chromium_src-66e3905287330abff21e82db183ef87f63808f0d.tar.gz chromium_src-66e3905287330abff21e82db183ef87f63808f0d.tar.bz2 |
Third attempt at http://codereview.chromium.org/115276
Review URL: http://codereview.chromium.org/115645
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16639 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r-- | media/audio/audio_output.h | 9 | ||||
-rw-r--r-- | media/audio/linux/alsa_output.cc | 521 | ||||
-rw-r--r-- | media/audio/linux/alsa_output.h | 165 | ||||
-rw-r--r-- | media/audio/linux/audio_manager_linux.cc | 67 | ||||
-rw-r--r-- | media/audio/linux/audio_manager_linux.h | 32 |
5 files changed, 790 insertions, 4 deletions
diff --git a/media/audio/audio_output.h b/media/audio/audio_output.h index 289b2af..4793e71 100644 --- a/media/audio/audio_output.h +++ b/media/audio/audio_output.h @@ -43,9 +43,11 @@ class AudioOutputStream { public: enum State { STATE_CREATED = 0, // The output stream is created. + STATE_OPENED, // The output stream is opened. STATE_STARTED, // The output stream is started. STATE_PAUSED, // The output stream is paused. STATE_STOPPED, // The output stream is stopped. + STATE_CLOSING, // The output stream is being closed. STATE_CLOSED, // The output stream is closed. STATE_ERROR, // The output stream is in error state. }; @@ -82,6 +84,9 @@ class AudioOutputStream { // two buffers of |packet_size| size are created, one will be locked for // playback and one will be ready to be filled in the call to // AudioSourceCallback::OnMoreData(). + // + // TODO(ajwong): Streams are not reusable, so try to move packet_size into the + // constructor. virtual bool Open(size_t packet_size) = 0; // Starts playing audio and generating AudioSourceCallback::OnMoreData(). @@ -89,9 +94,11 @@ class AudioOutputStream { // after calling this method initial buffers are fetched. User of this // object should prepare |AudioOutputStream::GetNumBuffers()| before calling // AudioOutputStream::Start(). + // + // The output stream does not take ownership of this callback. virtual void Start(AudioSourceCallback* callback) = 0; - // Stops playing audio. Effect might no be instantaneous as the hardware + // Stops playing audio. Effect might not be instantaneous as the hardware // might have locked audio data that is processing. virtual void Stop() = 0; 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_ |