summaryrefslogtreecommitdiffstats
path: root/media/audio/linux/alsa_output.cc
diff options
context:
space:
mode:
Diffstat (limited to 'media/audio/linux/alsa_output.cc')
-rw-r--r--media/audio/linux/alsa_output.cc521
1 files changed, 0 insertions, 521 deletions
diff --git a/media/audio/linux/alsa_output.cc b/media/audio/linux/alsa_output.cc
deleted file mode 100644
index 0dc9ca9..0000000
--- a/media/audio/linux/alsa_output.cc
+++ /dev/null
@@ -1,521 +0,0 @@
-// 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;
-}