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.cc107
1 files changed, 80 insertions, 27 deletions
diff --git a/media/audio/linux/alsa_output.cc b/media/audio/linux/alsa_output.cc
index d0fda06..522b8c4 100644
--- a/media/audio/linux/alsa_output.cc
+++ b/media/audio/linux/alsa_output.cc
@@ -34,10 +34,11 @@
// This reduces the need for critical sections because the public API code can
// assume that no mutations occur to the |shared_data_| between queries.
//
-// On the message loop side, most tasks have been coded such that they can
+// On the message loop side, tasks have been coded such that they can
// operate safely regardless of when state changes happen to |shared_data_|.
-// Code that is sensitive to the timing holds the |shared_data_.lock_|
-// explicitly for the duration of the critical section.
+// Code that is sensitive to the timing of state changes are delegated to the
+// |shared_data_| object so they can executed while holding
+// |shared_data_.lock_|.
//
//
// SEMANTICS OF CloseTask()
@@ -81,6 +82,7 @@
#include "base/time.h"
#include "media/audio/audio_util.h"
#include "media/audio/linux/alsa_wrapper.h"
+#include "media/audio/linux/audio_manager_linux.h"
// Amount of time to wait if we've exhausted the data source. This is to avoid
// busy looping.
@@ -150,6 +152,7 @@ AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name,
int sample_rate,
int bits_per_sample,
AlsaWrapper* wrapper,
+ AudioManagerLinux* manager,
MessageLoop* message_loop)
: shared_data_(MessageLoop::current()),
device_name_(device_name),
@@ -160,8 +163,8 @@ AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name,
bytes_per_frame_(channels_ * bits_per_sample / 8),
stop_stream_(false),
wrapper_(wrapper),
+ manager_(manager),
playback_handle_(NULL),
- source_callback_(NULL),
frames_per_packet_(0),
client_thread_loop_(MessageLoop::current()),
message_loop_(message_loop) {
@@ -205,6 +208,10 @@ bool AlsaPcmOutputStream::Open(size_t packet_size) {
<< "Buffers should end on a frame boundary. Frame size: "
<< bytes_per_frame_;
+ if (shared_data_.state() == kInError) {
+ return false;
+ }
+
if (!shared_data_.CanTransitionTo(kIsOpened)) {
NOTREACHED() << "Invalid state: " << shared_data_.state();
return false;
@@ -256,11 +263,24 @@ bool AlsaPcmOutputStream::Open(size_t packet_size) {
void AlsaPcmOutputStream::Close() {
DCHECK_EQ(MessageLoop::current(), client_thread_loop_);
- if (shared_data_.TransitionTo(kIsClosed) == kIsClosed) {
- message_loop_->PostTask(
- FROM_HERE,
- NewRunnableMethod(this, &AlsaPcmOutputStream::CloseTask));
+ // Sanity check that the transition occurs correctly. It is safe to
+ // continue anyways because all operations for closing are idempotent.
+ if (shared_data_.TransitionTo(kIsClosed) != kIsClosed) {
+ NOTREACHED() << "Unable to transition Closed.";
}
+
+ // Signal our successful close, and disassociate the source callback.
+ shared_data_.OnClose(this);
+ shared_data_.set_source_callback(NULL);
+
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AlsaPcmOutputStream::CloseTask));
+
+ // Signal to the manager that we're closed and can be removed. Since
+ // we just posted a CloseTask to the message loop, we won't be deleted
+ // immediately, but it will happen soon afterwards.
+ manager()->ReleaseStream(this);
}
void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) {
@@ -268,11 +288,13 @@ void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) {
CHECK(callback);
+ shared_data_.set_source_callback(callback);
+
// Only post the task if we can enter the playing state.
if (shared_data_.TransitionTo(kIsPlaying) == kIsPlaying) {
message_loop_->PostTask(
FROM_HERE,
- NewRunnableMethod(this, &AlsaPcmOutputStream::StartTask, callback));
+ NewRunnableMethod(this, &AlsaPcmOutputStream::StartTask));
}
}
@@ -303,11 +325,9 @@ void AlsaPcmOutputStream::FinishOpen(snd_pcm_t* playback_handle,
frames_per_packet_ = packet_size / bytes_per_frame_;
}
-void AlsaPcmOutputStream::StartTask(AudioSourceCallback* callback) {
+void AlsaPcmOutputStream::StartTask() {
DCHECK_EQ(MessageLoop::current(), message_loop_);
- source_callback_ = callback;
-
// When starting again, drop all packets in the device and prepare it again
// incase we are restarting from a pause state and need to flush old data.
int error = wrapper_->PcmDrop(playback_handle_);
@@ -341,6 +361,8 @@ void AlsaPcmOutputStream::StartTask(AudioSourceCallback* callback) {
}
void AlsaPcmOutputStream::CloseTask() {
+ // NOTE: Keep this function idempotent to handle errors that might cause
+ // multiple CloseTasks to be posted.
DCHECK_EQ(MessageLoop::current(), message_loop_);
// Shutdown the audio device.
@@ -352,15 +374,6 @@ void AlsaPcmOutputStream::CloseTask() {
// Release the buffer.
packet_.reset();
- // The |source_callback_| may be NULL if the stream is being closed before it
- // was ever started.
- if (source_callback_) {
- // TODO(ajwong): We need to call source_callback_->OnClose(), but the
- // ownerships of the callback is broken right now, so we'd crash. Instead,
- // just leak. Bug 18217.
- source_callback_ = NULL;
- }
-
// Signal anything that might already be scheduled to stop.
stop_stream_ = true;
}
@@ -377,8 +390,8 @@ void AlsaPcmOutputStream::BufferPacket(Packet* packet) {
// Request more data if we don't have any cached.
if (packet->used >= packet->size) {
packet->used = 0;
- packet->size = source_callback_->OnMoreData(this, packet->buffer.get(),
- packet->capacity);
+ packet->size = shared_data_.OnMoreData(this, packet->buffer.get(),
+ packet->capacity);
CHECK(packet->size <= packet->capacity) << "Data source overran buffer.";
// This should not happen, but incase it does, drop any trailing bytes
@@ -431,9 +444,7 @@ void AlsaPcmOutputStream::WritePacket(Packet* packet) {
if (frames_written != -EAGAIN) {
LOG(ERROR) << "Failed to write to pcm device: "
<< wrapper_->StrError(frames_written);
- // TODO(ajwong): We need to call source_callback_->OnError(), but the
- // ownerships of the callback is broken right now, so we'd crash.
- // Instead, just leak. Bug 18217.
+ shared_data_.OnError(this, frames_written);
stop_stream_ = true;
}
} else {
@@ -540,10 +551,16 @@ snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() {
return available_frames;
}
+AudioManagerLinux* AlsaPcmOutputStream::manager() {
+ DCHECK_EQ(MessageLoop::current(), client_thread_loop_);
+ return manager_;
+}
+
AlsaPcmOutputStream::SharedData::SharedData(
MessageLoop* state_transition_loop)
: state_(kCreated),
volume_(1.0f),
+ source_callback_(NULL),
state_transition_loop_(state_transition_loop) {
}
@@ -565,7 +582,8 @@ bool AlsaPcmOutputStream::SharedData::CanTransitionTo_Locked(
to == kIsClosed || to == kInError;
case kIsPlaying:
- return to == kIsStopped || to == kIsClosed || to == kInError;
+ return to == kIsPlaying || to == kIsStopped ||
+ to == kIsClosed || to == kInError;
case kIsStopped:
return to == kIsPlaying || to == kIsStopped ||
@@ -608,3 +626,38 @@ void AlsaPcmOutputStream::SharedData::set_volume(float v) {
AutoLock l(lock_);
volume_ = v;
}
+
+size_t AlsaPcmOutputStream::SharedData::OnMoreData(AudioOutputStream* stream,
+ void* dest,
+ size_t max_size) {
+ AutoLock l(lock_);
+ if (source_callback_) {
+ return source_callback_->OnMoreData(stream, dest, max_size);
+ }
+
+ return 0;
+}
+
+void AlsaPcmOutputStream::SharedData::OnClose(AudioOutputStream* stream) {
+ AutoLock l(lock_);
+ if (source_callback_) {
+ source_callback_->OnClose(stream);
+ }
+}
+
+void AlsaPcmOutputStream::SharedData::OnError(AudioOutputStream* stream,
+ int code) {
+ AutoLock l(lock_);
+ if (source_callback_) {
+ source_callback_->OnError(stream, code);
+ }
+}
+
+// Changes the AudioSourceCallback to proxy calls to. Pass in NULL to
+// release ownership of the currently registered callback.
+void AlsaPcmOutputStream::SharedData::set_source_callback(
+ AudioSourceCallback* callback) {
+ DCHECK_EQ(MessageLoop::current(), state_transition_loop_);
+ AutoLock l(lock_);
+ source_callback_ = callback;
+}