summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorenal@chromium.org <enal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-08 18:56:55 +0000
committerenal@chromium.org <enal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-08 18:56:55 +0000
commit9a30e9ca9ce478772657375ffaae50f0d4894716 (patch)
tree3c6dcc5f619dc2b8480f2f0b3b000ca94879f573 /media
parent90d78ed45ea51db81431ad1db5db93933a762095 (diff)
downloadchromium_src-9a30e9ca9ce478772657375ffaae50f0d4894716.zip
chromium_src-9a30e9ca9ce478772657375ffaae50f0d4894716.tar.gz
chromium_src-9a30e9ca9ce478772657375ffaae50f0d4894716.tar.bz2
Simplify AlsaPcmOutputStream and AudioManagerLinux. Code was thread-safe, but now
client and audio threads are the same, so we don't need locks/ref counts/etc. http://codereview.chromium.org/7117001 BUG=62588 Review URL: http://codereview.chromium.org/7117001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@88369 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/audio/linux/alsa_output.cc222
-rw-r--r--media/audio/linux/alsa_output.h101
-rw-r--r--media/audio/linux/alsa_output_unittest.cc90
-rw-r--r--media/audio/linux/audio_manager_linux.cc11
-rw-r--r--media/audio/linux/audio_manager_linux.h8
5 files changed, 159 insertions, 273 deletions
diff --git a/media/audio/linux/alsa_output.cc b/media/audio/linux/alsa_output.cc
index 31470fc..3802462 100644
--- a/media/audio/linux/alsa_output.cc
+++ b/media/audio/linux/alsa_output.cc
@@ -4,65 +4,29 @@
//
// THREAD SAFETY
//
-// The AlsaPcmOutputStream object's internal state is accessed by two threads:
-//
+// AlsaPcmOutputStream object is *not* thread-safe -- we assume that
// client thread - creates the object and calls the public APIs.
// message loop thread - executes all the internal tasks including querying
// the data source for more data, writing to the alsa device, and closing
-// the alsa device. It does *not* handle opening the device though.
-//
-// The class is designed so that most operations that read/modify the object's
-// internal state are done on the message loop thread. The exception is data
-// conatined in the |shared_data_| structure. Data in this structure needs to
-// be accessed by both threads, and should only be accessed when the
-// |shared_data_.lock_| is held.
-//
-// All member variables that are not in |shared_data_| are created/destroyed on
-// the |message_loop_|. This allows safe access to them from any task posted to
-// |message_loop_|. The values in |shared_data_| are considered to be read-only
-// signals by tasks posted to |message_loop_| (see the SEMANTICS of
-// |shared_data_| section below). Because of these two constraints, tasks can,
-// and must, be coded to be safe in the face of a changing |shared_data_|.
-//
-//
-// SEMANTICS OF |shared_data_|
-//
-// Though |shared_data_| is accessable by both threads, the code has been
-// structured so that all mutations to |shared_data_| are only done in the
-// client thread. The message loop thread only ever reads the shared data.
-//
-// 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, 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 of state changes are delegated to the
-// |shared_data_| object so they can executed while holding
-// |shared_data_.lock_|.
+// the alsa device.
+// is actually the same thread.
//
//
// SEMANTICS OF CloseTask()
//
// The CloseTask() is responsible for cleaning up any resources that were
-// acquired after a successful Open(). After a CloseTask() has executed,
-// scheduling of reads should stop. Currently scheduled tasks may run, but
-// they should not attempt to access any of the internal structures beyond
-// querying the |stop_stream_| flag and no-oping themselves. This will
-// guarantee that eventually no more tasks will be posted into the message
-// loop, and the AlsaPcmOutputStream will be able to delete itself.
+// acquired after a successful Open(). CloseTask() would revoke any
+// scheduled outstanding runnable methods.
//
//
// SEMANTICS OF ERROR STATES
//
-// The object has two distinct error states: |shared_data_.state_| == kInError
-// and |stop_stream_|. The |shared_data_.state_| state is only settable
-// by the client thread, and thus cannot be used to signal when the ALSA device
-// fails due to a hardware (or other low-level) event. The |stop_stream_|
-// variable is only accessed by the message loop thread; it is used to indicate
+// The object has two distinct error states: |state_| == kInError
+// and |stop_stream_|. The |stop_stream_| variable is used to indicate
// that the playback_handle should no longer be used either because of a
-// hardware/low-level event, or because the CloseTask() has been run.
+// hardware/low-level event.
//
-// When |shared_data_.state_| == kInError, all public API functions will fail
+// When |state_| == kInError, all public API functions will fail
// with an error (Start() will call the OnError() function on the callback
// immediately), or no-op themselves with the exception of Close(). Even if an
// error state has been entered, if Open() has previously returned successfully,
@@ -218,8 +182,7 @@ AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name,
AlsaWrapper* wrapper,
AudioManagerLinux* manager,
MessageLoop* message_loop)
- : shared_data_(MessageLoop::current()),
- requested_device_name_(device_name),
+ : requested_device_name_(device_name),
pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample)),
channels_(params.channels),
sample_rate_(params.sample_rate),
@@ -238,30 +201,36 @@ AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name,
manager_(manager),
playback_handle_(NULL),
frames_per_packet_(packet_size_ / bytes_per_frame_),
- client_thread_loop_(MessageLoop::current()),
- message_loop_(message_loop) {
+ message_loop_(message_loop),
+ ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
+ state_(kCreated),
+ volume_(1.0f),
+ source_callback_(NULL) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
// Sanity check input values.
if ((params.sample_rate > kAlsaMaxSampleRate) || (params.sample_rate <= 0)) {
LOG(WARNING) << "Unsupported audio frequency.";
- shared_data_.TransitionTo(kInError);
+ TransitionTo(kInError);
}
if (AudioParameters::AUDIO_PCM_LINEAR != params.format &&
AudioParameters::AUDIO_PCM_LOW_LATENCY != params.format) {
LOG(WARNING) << "Unsupported audio format";
- shared_data_.TransitionTo(kInError);
+ TransitionTo(kInError);
}
if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) {
LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample;
- shared_data_.TransitionTo(kInError);
+ TransitionTo(kInError);
}
}
AlsaPcmOutputStream::~AlsaPcmOutputStream() {
- InternalState state = shared_data_.state();
- DCHECK(state == kCreated || state == kIsClosed || state == kInError);
+ InternalState current_state = state();
+ DCHECK(current_state == kCreated ||
+ current_state == kIsClosed ||
+ current_state == kInError);
// TODO(ajwong): Ensure that CloseTask has been called and the
// playback handle released by DCHECKing that playback_handle_ is NULL.
@@ -270,14 +239,14 @@ AlsaPcmOutputStream::~AlsaPcmOutputStream() {
}
bool AlsaPcmOutputStream::Open() {
- DCHECK_EQ(MessageLoop::current(), client_thread_loop_);
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
- if (shared_data_.state() == kInError) {
+ if (state() == kInError) {
return false;
}
- if (!shared_data_.CanTransitionTo(kIsOpened)) {
- NOTREACHED() << "Invalid state: " << shared_data_.state();
+ if (!CanTransitionTo(kIsOpened)) {
+ NOTREACHED() << "Invalid state: " << state();
return false;
}
@@ -285,67 +254,62 @@ bool AlsaPcmOutputStream::Open() {
// CanTransitionTo() was checked above, and it is assumed that this
// object's public API is only called on one thread so the state cannot
// transition out from under us.
- shared_data_.TransitionTo(kIsOpened);
+ TransitionTo(kIsOpened);
message_loop_->PostTask(
FROM_HERE,
- NewRunnableMethod(this, &AlsaPcmOutputStream::OpenTask));
+ method_factory_.NewRunnableMethod(&AlsaPcmOutputStream::OpenTask));
return true;
}
void AlsaPcmOutputStream::Close() {
- DCHECK_EQ(MessageLoop::current(), client_thread_loop_);
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
// 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) {
+ if (TransitionTo(kIsClosed) != kIsClosed) {
NOTREACHED() << "Unable to transition Closed.";
}
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()->ReleaseOutputStream(this);
+ method_factory_.NewRunnableMethod(&AlsaPcmOutputStream::CloseTask));
}
void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) {
- DCHECK_EQ(MessageLoop::current(), client_thread_loop_);
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
CHECK(callback);
- shared_data_.set_source_callback(callback);
+ set_source_callback(callback);
// Only post the task if we can enter the playing state.
- if (shared_data_.TransitionTo(kIsPlaying) == kIsPlaying) {
+ if (TransitionTo(kIsPlaying) == kIsPlaying) {
message_loop_->PostTask(
FROM_HERE,
- NewRunnableMethod(this, &AlsaPcmOutputStream::StartTask));
+ method_factory_.NewRunnableMethod(&AlsaPcmOutputStream::StartTask));
}
}
void AlsaPcmOutputStream::Stop() {
- DCHECK_EQ(MessageLoop::current(), client_thread_loop_);
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
// Reset the callback, so that it is not called anymore.
- shared_data_.set_source_callback(NULL);
+ set_source_callback(NULL);
- shared_data_.TransitionTo(kIsStopped);
+ TransitionTo(kIsStopped);
}
void AlsaPcmOutputStream::SetVolume(double volume) {
- DCHECK_EQ(MessageLoop::current(), client_thread_loop_);
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
- shared_data_.set_volume(static_cast<float>(volume));
+ volume_ = static_cast<float>(volume);
}
void AlsaPcmOutputStream::GetVolume(double* volume) {
- DCHECK_EQ(MessageLoop::current(), client_thread_loop_);
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
- *volume = shared_data_.volume();
+ *volume = volume_;
}
void AlsaPcmOutputStream::OpenTask() {
@@ -396,7 +360,7 @@ void AlsaPcmOutputStream::StartTask() {
return;
}
- if (shared_data_.state() != kIsPlaying) {
+ if (state() != kIsPlaying) {
return;
}
@@ -428,8 +392,6 @@ void AlsaPcmOutputStream::StartTask() {
}
void AlsaPcmOutputStream::CloseTask() {
- // NOTE: Keep this function idempotent to handle errors that might cause
- // multiple CloseTasks to be posted.
DCHECK_EQ(message_loop_, MessageLoop::current());
// Shutdown the audio device.
@@ -443,7 +405,13 @@ void AlsaPcmOutputStream::CloseTask() {
buffer_.reset();
// Signal anything that might already be scheduled to stop.
- stop_stream_ = true;
+ stop_stream_ = true; // Not necessary in production, but unit tests
+ // uses the flag to verify that stream was closed.
+ method_factory_.RevokeAll();
+
+ // Signal to the manager that we're closed and can be removed.
+ // Should be last call in the method as it deletes "this".
+ manager_->ReleaseOutputStream(this);
}
void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) {
@@ -470,10 +438,10 @@ void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) {
scoped_refptr<media::DataBuffer> packet =
new media::DataBuffer(packet_size_);
- size_t packet_size =
- shared_data_.OnMoreData(
- this, packet->GetWritableData(), packet->GetBufferSize(),
- AudioBuffersState(buffer_delay, hardware_delay));
+ size_t packet_size = RunDataCallback(packet->GetWritableData(),
+ packet->GetBufferSize(),
+ AudioBuffersState(buffer_delay,
+ hardware_delay));
CHECK(packet_size <= packet->GetBufferSize()) <<
"Data source overran buffer.";
@@ -489,7 +457,7 @@ void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) {
packet_size,
channels_,
bytes_per_sample_,
- shared_data_.volume())) {
+ volume_)) {
// Adjust packet size for downmix.
packet_size =
packet_size / bytes_per_frame_ * bytes_per_output_frame_;
@@ -525,7 +493,7 @@ void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) {
packet_size,
channels_,
bytes_per_sample_,
- shared_data_.volume());
+ volume_);
}
if (packet_size > 0) {
@@ -547,7 +515,7 @@ void AlsaPcmOutputStream::WritePacket() {
return;
}
- if (shared_data_.state() == kIsStopped) {
+ if (state() == kIsStopped) {
return;
}
@@ -579,7 +547,7 @@ void AlsaPcmOutputStream::WritePacket() {
if (frames_written != -EAGAIN) {
LOG(ERROR) << "Failed to write to pcm device: "
<< wrapper_->StrError(frames_written);
- shared_data_.OnError(this, frames_written);
+ RunErrorCallback(frames_written);
stop_stream_ = true;
}
} else {
@@ -610,7 +578,7 @@ void AlsaPcmOutputStream::WriteTask() {
return;
}
- if (shared_data_.state() == kIsStopped) {
+ if (state() == kIsStopped) {
return;
}
@@ -655,17 +623,17 @@ void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) {
}
// Only schedule more reads/writes if we are still in the playing state.
- if (shared_data_.state() == kIsPlaying) {
+ if (state() == kIsPlaying) {
if (next_fill_time_ms == 0) {
message_loop_->PostTask(
FROM_HERE,
- NewRunnableMethod(this, &AlsaPcmOutputStream::WriteTask));
+ method_factory_.NewRunnableMethod(&AlsaPcmOutputStream::WriteTask));
} else {
// TODO(ajwong): Measure the reliability of the delay interval. Use
// base/metrics/histogram.h.
message_loop_->PostDelayedTask(
FROM_HERE,
- NewRunnableMethod(this, &AlsaPcmOutputStream::WriteTask),
+ method_factory_.NewRunnableMethod(&AlsaPcmOutputStream::WriteTask),
next_fill_time_ms);
}
}
@@ -842,28 +810,7 @@ snd_pcm_t* AlsaPcmOutputStream::AutoSelectDevice(unsigned int latency) {
return NULL;
}
-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) {
-}
-
-bool AlsaPcmOutputStream::SharedData::CanTransitionTo(InternalState to) {
- base::AutoLock l(lock_);
- return CanTransitionTo_Locked(to);
-}
-
-bool AlsaPcmOutputStream::SharedData::CanTransitionTo_Locked(
- InternalState to) {
- lock_.AssertAcquired();
-
+bool AlsaPcmOutputStream::CanTransitionTo(InternalState to) {
switch (state_) {
case kCreated:
return to == kIsOpened || to == kIsClosed || to == kInError;
@@ -890,11 +837,10 @@ bool AlsaPcmOutputStream::SharedData::CanTransitionTo_Locked(
}
AlsaPcmOutputStream::InternalState
-AlsaPcmOutputStream::SharedData::TransitionTo(InternalState to) {
- DCHECK_EQ(MessageLoop::current(), state_transition_loop_);
+AlsaPcmOutputStream::TransitionTo(InternalState to) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
- base::AutoLock l(lock_);
- if (!CanTransitionTo_Locked(to)) {
+ if (!CanTransitionTo(to)) {
NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to;
state_ = kInError;
} else {
@@ -903,45 +849,29 @@ AlsaPcmOutputStream::SharedData::TransitionTo(InternalState to) {
return state_;
}
-AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::SharedData::state() {
- base::AutoLock l(lock_);
+AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() {
return state_;
}
-float AlsaPcmOutputStream::SharedData::volume() {
- base::AutoLock l(lock_);
- return volume_;
-}
-
-void AlsaPcmOutputStream::SharedData::set_volume(float v) {
- base::AutoLock l(lock_);
- volume_ = v;
-}
-
-uint32 AlsaPcmOutputStream::SharedData::OnMoreData(
- AudioOutputStream* stream, uint8* dest, uint32 max_size,
- AudioBuffersState buffers_state) {
- base::AutoLock l(lock_);
+uint32 AlsaPcmOutputStream::RunDataCallback(uint8* dest,
+ uint32 max_size,
+ AudioBuffersState buffers_state) {
if (source_callback_) {
- return source_callback_->OnMoreData(stream, dest, max_size, buffers_state);
+ return source_callback_->OnMoreData(this, dest, max_size, buffers_state);
}
return 0;
}
-void AlsaPcmOutputStream::SharedData::OnError(AudioOutputStream* stream,
- int code) {
- base::AutoLock l(lock_);
+void AlsaPcmOutputStream::RunErrorCallback(int code) {
if (source_callback_) {
- source_callback_->OnError(stream, code);
+ source_callback_->OnError(this, 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_);
- base::AutoLock l(lock_);
+void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
source_callback_ = callback;
}
diff --git a/media/audio/linux/alsa_output.h b/media/audio/linux/alsa_output.h
index 6c629c2..48482b1 100644
--- a/media/audio/linux/alsa_output.h
+++ b/media/audio/linux/alsa_output.h
@@ -24,13 +24,6 @@
// threading assumptions at the top of the implementation file to avoid
// introducing race conditions between tasks posted to the internal
// message_loop, and the thread calling the public APIs.
-//
-// TODO(sergeyu): AlsaPcmOutputStream is always created and used from the
-// audio thread (i.e. |client_thread_loop_| and |message_loop_| always point
-// to the same thread), so it doesn't need to be thread-safe anymore.
-//
-// TODO(sergeyu): Remove refcounter from AlsaPcmOutputStream and use
-// ScopedRunnableMethodFactory to create tasks.
#ifndef MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_
#define MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_
@@ -40,9 +33,8 @@
#include <string>
#include "base/gtest_prod_util.h"
-#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
-#include "base/synchronization/lock.h"
+#include "base/task.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_parameters.h"
@@ -54,9 +46,7 @@ class AlsaWrapper;
class AudioManagerLinux;
class MessageLoop;
-class AlsaPcmOutputStream :
- public AudioOutputStream,
- public base::RefCountedThreadSafe<AlsaPcmOutputStream> {
+class AlsaPcmOutputStream : public AudioOutputStream {
public:
// String for the generic "default" ALSA device that has the highest
// compatibility and chance of working.
@@ -85,6 +75,8 @@ class AlsaPcmOutputStream :
AudioManagerLinux* manager,
MessageLoop* message_loop);
+ virtual ~AlsaPcmOutputStream();
+
// Implementation of AudioOutputStream.
virtual bool Open();
virtual void Close();
@@ -94,7 +86,6 @@ class AlsaPcmOutputStream :
virtual void GetVolume(double* volume);
private:
- friend class base::RefCountedThreadSafe<AlsaPcmOutputStream>;
friend class AlsaPcmOutputStreamTest;
FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
AutoSelectDevice_DeviceSelect);
@@ -120,8 +111,6 @@ class AlsaPcmOutputStream :
FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_StopStream);
FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_WriteFails);
- virtual ~AlsaPcmOutputStream();
-
// Flags indicating the state of the stream.
enum InternalState {
kInError = 0,
@@ -156,54 +145,28 @@ class AlsaPcmOutputStream :
// of channels. This function will set |device_name_| and |should_downmix_|.
snd_pcm_t* AutoSelectDevice(uint32 latency);
- // Thread-asserting accessors for member variables.
- AudioManagerLinux* manager();
-
- // Struct holding all mutable the data that must be shared by the
- // message_loop() and the thread that created the object.
- class SharedData {
- public:
- explicit SharedData(MessageLoop* state_transition_loop);
-
- // Functions to safeguard state transitions and ensure that transitions are
- // only allowed occuring on the thread that created the object. All
- // changes to the object state should go through these functions.
- bool CanTransitionTo(InternalState to);
- bool CanTransitionTo_Locked(InternalState to);
- InternalState TransitionTo(InternalState to);
- InternalState state();
-
- float volume();
- void set_volume(float v);
-
- // API for Proxying calls to the AudioSourceCallback provided during
- // Start(). These APIs are threadsafe.
- //
- // TODO(ajwong): This is necessary because the ownership semantics for the
- // |source_callback_| object are incorrect in AudioRenderHost. The callback
- // is passed into the output stream, but ownership is not transfered which
- // requires a synchronization on access of the |source_callback_| to avoid
- // using a deleted callback.
- uint32 OnMoreData(AudioOutputStream* stream, uint8* dest,
- uint32 max_size, AudioBuffersState buffers_state);
- void OnError(AudioOutputStream* stream, int code);
+ // Functions to safeguard state transitions. All changes to the object state
+ // should go through these functions.
+ bool CanTransitionTo(InternalState to);
+ InternalState TransitionTo(InternalState to);
+ InternalState state();
- // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to
- // release ownership of the currently registered callback.
- void set_source_callback(AudioSourceCallback* callback);
-
- private:
- base::Lock lock_;
-
- InternalState state_;
- float volume_; // Volume level from 0.0 to 1.0.
-
- AudioSourceCallback* source_callback_;
-
- MessageLoop* const state_transition_loop_;
-
- DISALLOW_COPY_AND_ASSIGN(SharedData);
- } shared_data_;
+ // API for Proxying calls to the AudioSourceCallback provided during
+ // Start().
+ //
+ // TODO(ajwong): This is necessary because the ownership semantics for the
+ // |source_callback_| object are incorrect in AudioRenderHost. The callback
+ // is passed into the output stream, but ownership is not transfered which
+ // requires a synchronization on access of the |source_callback_| to avoid
+ // using a deleted callback.
+ uint32 RunDataCallback(uint8* dest,
+ uint32 max_size,
+ AudioBuffersState buffers_state);
+ void RunErrorCallback(int code);
+
+ // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to
+ // release ownership of the currently registered callback.
+ void set_source_callback(AudioSourceCallback* callback);
// Configuration constants from the constructor. Referenceable by all threads
// since they are constants.
@@ -232,8 +195,6 @@ class AlsaPcmOutputStream :
AlsaWrapper* wrapper_;
// Audio manager that created us. Used to report that we've been closed.
- // This should only be used on the |client_thread_loop_|. Access via
- // the manager() function.
AudioManagerLinux* manager_;
// Handle to the actual PCM playback device.
@@ -242,13 +203,19 @@ class AlsaPcmOutputStream :
scoped_ptr<media::SeekableBuffer> buffer_;
uint32 frames_per_packet_;
- // Used to check which message loop is allowed to call the public APIs.
- MessageLoop* client_thread_loop_;
-
// The message loop responsible for querying the data source, and writing to
// the output device.
MessageLoop* message_loop_;
+ // Allows us to run tasks on the AlsaPcmOutputStream instance which are
+ // bound by its lifetime.
+ ScopedRunnableMethodFactory<AlsaPcmOutputStream> method_factory_;
+
+ InternalState state_;
+ float volume_; // Volume level from 0.0 to 1.0.
+
+ AudioSourceCallback* source_callback_;
+
DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStream);
};
diff --git a/media/audio/linux/alsa_output_unittest.cc b/media/audio/linux/alsa_output_unittest.cc
index 3608fcc..eea8321 100644
--- a/media/audio/linux/alsa_output_unittest.cc
+++ b/media/audio/linux/alsa_output_unittest.cc
@@ -91,11 +91,11 @@ class MockAudioManagerLinux : public AudioManagerLinux {
class AlsaPcmOutputStreamTest : public testing::Test {
protected:
AlsaPcmOutputStreamTest() {
- test_stream_ = CreateStream(kTestChannelLayout);
+ test_stream_.reset(CreateStream(kTestChannelLayout));
}
virtual ~AlsaPcmOutputStreamTest() {
- test_stream_ = NULL;
+ test_stream_.reset(NULL);
}
AlsaPcmOutputStream* CreateStream(ChannelLayout layout) {
@@ -156,7 +156,7 @@ class AlsaPcmOutputStreamTest : public testing::Test {
StrictMock<MockAlsaWrapper> mock_alsa_wrapper_;
StrictMock<MockAudioManagerLinux> mock_manager_;
MessageLoop message_loop_;
- scoped_refptr<AlsaPcmOutputStream> test_stream_;
+ scoped_ptr<AlsaPcmOutputStream> test_stream_;
scoped_refptr<media::DataBuffer> packet_;
private:
@@ -194,42 +194,37 @@ void* AlsaPcmOutputStreamTest::kFakeHints[] = {
kSurround70, kSurround71, NULL };
TEST_F(AlsaPcmOutputStreamTest, ConstructedState) {
- EXPECT_EQ(AlsaPcmOutputStream::kCreated,
- test_stream_->shared_data_.state());
+ EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream_->state());
// Should support mono.
- test_stream_ = CreateStream(CHANNEL_LAYOUT_MONO);
- EXPECT_EQ(AlsaPcmOutputStream::kCreated,
- test_stream_->shared_data_.state());
+ test_stream_.reset(CreateStream(CHANNEL_LAYOUT_MONO));
+ EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream_->state());
// Should support multi-channel.
- test_stream_ = CreateStream(CHANNEL_LAYOUT_SURROUND);
- EXPECT_EQ(AlsaPcmOutputStream::kCreated,
- test_stream_->shared_data_.state());
+ test_stream_.reset(CreateStream(CHANNEL_LAYOUT_SURROUND));
+ EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream_->state());
// Bad bits per sample.
AudioParameters bad_bps_params(kTestFormat, kTestChannelLayout,
kTestSampleRate, kTestBitsPerSample - 1,
kTestFramesPerPacket);
- test_stream_ = new AlsaPcmOutputStream(kTestDeviceName,
- bad_bps_params,
- &mock_alsa_wrapper_,
- &mock_manager_,
- &message_loop_);
- EXPECT_EQ(AlsaPcmOutputStream::kInError,
- test_stream_->shared_data_.state());
+ test_stream_.reset(new AlsaPcmOutputStream(kTestDeviceName,
+ bad_bps_params,
+ &mock_alsa_wrapper_,
+ &mock_manager_,
+ &message_loop_));
+ EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream_->state());
// Bad format.
AudioParameters bad_format_params(
AudioParameters::AUDIO_LAST_FORMAT, kTestChannelLayout, kTestSampleRate,
kTestBitsPerSample, kTestFramesPerPacket);
- test_stream_ = new AlsaPcmOutputStream(kTestDeviceName,
- bad_format_params,
- &mock_alsa_wrapper_,
- &mock_manager_,
- &message_loop_);
- EXPECT_EQ(AlsaPcmOutputStream::kInError,
- test_stream_->shared_data_.state());
+ test_stream_.reset(new AlsaPcmOutputStream(kTestDeviceName,
+ bad_format_params,
+ &mock_alsa_wrapper_,
+ &mock_manager_,
+ &message_loop_));
+ EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream_->state());
}
TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
@@ -253,7 +248,8 @@ TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
SetArgumentPointee<2>(kTestFramesPerPacket / 2),
Return(0)));
- test_stream_ = CreateStream(kTestChannelLayout, kPacketFramesInMinLatency);
+ test_stream_.reset(CreateStream(kTestChannelLayout,
+ kPacketFramesInMinLatency));
ASSERT_TRUE(test_stream_->Open());
message_loop_.RunAllPending();
@@ -283,7 +279,8 @@ TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
SetArgumentPointee<2>(kTestFramesPerPacket / 2),
Return(0)));
- test_stream_ = CreateStream(kTestChannelLayout, kOverMinLatencyPacketSize);
+ test_stream_.reset(CreateStream(kTestChannelLayout,
+ kOverMinLatencyPacketSize));
ASSERT_TRUE(test_stream_->Open());
message_loop_.RunAllPending();
@@ -331,8 +328,7 @@ TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
ASSERT_TRUE(test_stream_->Open());
message_loop_.RunAllPending();
- EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
- test_stream_->shared_data_.state());
+ EXPECT_EQ(AlsaPcmOutputStream::kIsOpened, test_stream_->state());
EXPECT_EQ(kFakeHandle, test_stream_->playback_handle_);
EXPECT_EQ(kTestFramesPerPacket, test_stream_->frames_per_packet_);
EXPECT_TRUE(test_stream_->buffer_.get());
@@ -360,14 +356,12 @@ TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) {
// Open still succeeds since PcmOpen is delegated to another thread.
ASSERT_TRUE(test_stream_->Open());
- ASSERT_EQ(AlsaPcmOutputStream::kIsOpened,
- test_stream_->shared_data_.state());
+ ASSERT_EQ(AlsaPcmOutputStream::kIsOpened, test_stream_->state());
ASSERT_FALSE(test_stream_->stop_stream_);
message_loop_.RunAllPending();
// Ensure internal state is set for a no-op stream if PcmOpen() failes.
- EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
- test_stream_->shared_data_.state());
+ EXPECT_EQ(AlsaPcmOutputStream::kIsOpened, test_stream_->state());
EXPECT_TRUE(test_stream_->stop_stream_);
EXPECT_TRUE(test_stream_->playback_handle_ == NULL);
EXPECT_FALSE(test_stream_->buffer_.get());
@@ -394,14 +388,12 @@ TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) {
// If open fails, the stream stays in kCreated because it has effectively had
// no changes.
ASSERT_TRUE(test_stream_->Open());
- EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
- test_stream_->shared_data_.state());
+ EXPECT_EQ(AlsaPcmOutputStream::kIsOpened, test_stream_->state());
ASSERT_FALSE(test_stream_->stop_stream_);
message_loop_.RunAllPending();
// Ensure internal state is set for a no-op stream if PcmSetParams() failes.
- EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
- test_stream_->shared_data_.state());
+ EXPECT_EQ(AlsaPcmOutputStream::kIsOpened, test_stream_->state());
EXPECT_TRUE(test_stream_->stop_stream_);
EXPECT_TRUE(test_stream_->playback_handle_ == NULL);
EXPECT_FALSE(test_stream_->buffer_.get());
@@ -561,7 +553,7 @@ TEST_F(AlsaPcmOutputStreamTest, BufferPacket) {
.WillOnce(Return(10));
bool source_exhausted;
- test_stream_->shared_data_.set_source_callback(&mock_callback);
+ test_stream_->set_source_callback(&mock_callback);
test_stream_->packet_size_ = kTestPacketSize;
test_stream_->BufferPacket(&source_exhausted);
@@ -586,7 +578,7 @@ TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Negative) {
.WillOnce(Return(10));
bool source_exhausted;
- test_stream_->shared_data_.set_source_callback(&mock_callback);
+ test_stream_->set_source_callback(&mock_callback);
test_stream_->packet_size_ = kTestPacketSize;
test_stream_->BufferPacket(&source_exhausted);
@@ -611,7 +603,7 @@ TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Underrun) {
.WillOnce(Return(10));
bool source_exhausted;
- test_stream_->shared_data_.set_source_callback(&mock_callback);
+ test_stream_->set_source_callback(&mock_callback);
test_stream_->packet_size_ = kTestPacketSize;
test_stream_->BufferPacket(&source_exhausted);
@@ -688,7 +680,7 @@ TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect) {
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
.WillRepeatedly(Invoke(EchoHint));
- test_stream_ = CreateStream(kExpectedLayouts[i]);
+ test_stream_.reset(CreateStream(kExpectedLayouts[i]));
EXPECT_TRUE(test_stream_->AutoSelectDevice(i));
EXPECT_EQ(kExpectedDownmix[i], test_stream_->should_downmix_);
@@ -738,7 +730,7 @@ TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_FallbackDevices) {
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(fourth_try.c_str()), _, _))
.WillOnce(Return(kTestFailedErrno));
- test_stream_ = CreateStream(CHANNEL_LAYOUT_5POINT0);
+ test_stream_.reset(CreateStream(CHANNEL_LAYOUT_5POINT0));
EXPECT_FALSE(test_stream_->AutoSelectDevice(5));
}
@@ -756,7 +748,7 @@ TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail) {
EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
.WillOnce(Return(kDummyMessage));
- test_stream_ = CreateStream(CHANNEL_LAYOUT_5POINT0);
+ test_stream_.reset(CreateStream(CHANNEL_LAYOUT_5POINT0));
EXPECT_TRUE(test_stream_->AutoSelectDevice(5));
EXPECT_TRUE(test_stream_->should_downmix_);
}
@@ -771,8 +763,8 @@ TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) {
}
TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) {
- test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsOpened);
- test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsPlaying);
+ test_stream_->TransitionTo(AlsaPcmOutputStream::kIsOpened);
+ test_stream_->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
InitBuffer();
@@ -789,12 +781,12 @@ TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) {
test_stream_->stop_stream_ = true;
message_loop_.RunAllPending();
- test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsClosed);
+ test_stream_->TransitionTo(AlsaPcmOutputStream::kIsClosed);
}
TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) {
- test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsOpened);
- test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsPlaying);
+ test_stream_->TransitionTo(AlsaPcmOutputStream::kIsOpened);
+ test_stream_->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
InitBuffer();
@@ -805,5 +797,5 @@ TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) {
// posted so we can verify that the Alsa code will indeed break the task
// posting loop.
- test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsClosed);
+ test_stream_->TransitionTo(AlsaPcmOutputStream::kIsClosed);
}
diff --git a/media/audio/linux/audio_manager_linux.cc b/media/audio/linux/audio_manager_linux.cc
index 58b4c0e..c21d050 100644
--- a/media/audio/linux/audio_manager_linux.cc
+++ b/media/audio/linux/audio_manager_linux.cc
@@ -9,6 +9,7 @@
#include "base/logging.h"
#include "base/nix/xdg_util.h"
#include "base/process_util.h"
+#include "base/stl_util-inl.h"
#include "media/audio/audio_output_dispatcher.h"
#include "media/audio/fake_audio_input_stream.h"
#include "media/audio/fake_audio_output_stream.h"
@@ -107,9 +108,7 @@ AudioOutputStream* AudioManagerLinux::MakeAudioOutputStream(
AlsaPcmOutputStream* stream =
new AlsaPcmOutputStream(device_name, params, wrapper_.get(), this,
GetMessageLoop());
-
- base::AutoLock l(lock_);
- active_streams_[stream] = scoped_refptr<AlsaPcmOutputStream>(stream);
+ active_streams_.insert(stream);
return stream;
}
@@ -153,7 +152,9 @@ AudioManagerLinux::~AudioManagerLinux() {
// Free output dispatchers, closing all remaining open streams.
output_dispatchers_.clear();
- active_streams_.clear();
+ // Delete all the streams. Have to do it manually, we don't have ScopedSet<>,
+ // and we are not using ScopedVector<> because search there is slow.
+ STLDeleteElements(&active_streams_);
}
void AudioManagerLinux::Init() {
@@ -171,8 +172,8 @@ void AudioManagerLinux::UnMuteAll() {
void AudioManagerLinux::ReleaseOutputStream(AlsaPcmOutputStream* stream) {
if (stream) {
- base::AutoLock l(lock_);
active_streams_.erase(stream);
+ delete stream;
}
}
diff --git a/media/audio/linux/audio_manager_linux.h b/media/audio/linux/audio_manager_linux.h
index 6a5d287..52d2940 100644
--- a/media/audio/linux/audio_manager_linux.h
+++ b/media/audio/linux/audio_manager_linux.h
@@ -5,11 +5,9 @@
#ifndef MEDIA_AUDIO_LINUX_AUDIO_MANAGER_LINUX_H_
#define MEDIA_AUDIO_LINUX_AUDIO_MANAGER_LINUX_H_
-#include <map>
+#include <set>
#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
#include "media/audio/audio_manager_base.h"
@@ -46,9 +44,7 @@ class AudioManagerLinux : public AudioManagerBase {
scoped_ptr<AlsaWrapper> wrapper_;
- base::Lock lock_;
- std::map<AlsaPcmOutputStream*, scoped_refptr<AlsaPcmOutputStream> >
- active_streams_;
+ std::set<AlsaPcmOutputStream*> active_streams_;
DISALLOW_COPY_AND_ASSIGN(AudioManagerLinux);
};