Chromium Code Reviews| Index: media/audio/linux/alsa_output.cc |
| =================================================================== |
| --- media/audio/linux/alsa_output.cc (revision 87982) |
| +++ media/audio/linux/alsa_output.cc (working copy) |
| @@ -4,43 +4,14 @@ |
| // |
| // 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 alsa device. |
| +// is actually the same thread. |
| // |
| -// 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_|. |
| -// |
| -// |
| // SEMANTICS OF CloseTask() |
| // |
| // The CloseTask() is responsible for cleaning up any resources that were |
| @@ -49,20 +20,17 @@ |
| // 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. |
| +// loop. |
| // |
| // |
| // 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. |
| // |
| -// 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 +186,7 @@ |
| 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 +205,36 @@ |
| 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 +243,14 @@ |
| } |
| 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,26 +258,26 @@ |
| // 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)); |
| + method_factory_.NewRunnableMethod(&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 |
| @@ -313,39 +286,39 @@ |
| } |
| 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)); |
| + set_volume(static_cast<float>(volume)); |
| } |
| -void AlsaPcmOutputStream::GetVolume(double* volume) { |
| - DCHECK_EQ(MessageLoop::current(), client_thread_loop_); |
| +void AlsaPcmOutputStream::GetVolume(double* pointer_to_volume) { |
| + DCHECK_EQ(MessageLoop::current(), message_loop_); |
| - *volume = shared_data_.volume(); |
| + *pointer_to_volume = volume(); |
| } |
| void AlsaPcmOutputStream::OpenTask() { |
| @@ -396,7 +369,7 @@ |
| return; |
| } |
| - if (shared_data_.state() != kIsPlaying) { |
| + if (state() != kIsPlaying) { |
| return; |
| } |
| @@ -471,9 +444,8 @@ |
| 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)); |
| + OnMoreData(this, packet->GetWritableData(), packet->GetBufferSize(), |
| + AudioBuffersState(buffer_delay, hardware_delay)); |
| CHECK(packet_size <= packet->GetBufferSize()) << |
| "Data source overran buffer."; |
| @@ -489,7 +461,7 @@ |
| 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 +497,7 @@ |
| packet_size, |
| channels_, |
| bytes_per_sample_, |
| - shared_data_.volume()); |
| + volume()); |
| } |
| if (packet_size > 0) { |
| @@ -547,7 +519,7 @@ |
| return; |
| } |
| - if (shared_data_.state() == kIsStopped) { |
| + if (state() == kIsStopped) { |
| return; |
| } |
| @@ -579,7 +551,7 @@ |
| if (frames_written != -EAGAIN) { |
| LOG(ERROR) << "Failed to write to pcm device: " |
| << wrapper_->StrError(frames_written); |
| - shared_data_.OnError(this, frames_written); |
| + OnError(this, frames_written); |
| stop_stream_ = true; |
| } |
| } else { |
| @@ -610,7 +582,7 @@ |
| return; |
| } |
| - if (shared_data_.state() == kIsStopped) { |
| + if (state() == kIsStopped) { |
| return; |
| } |
| @@ -655,17 +627,17 @@ |
| } |
| // 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); |
| } |
| } |
| @@ -843,27 +815,11 @@ |
| } |
| AudioManagerLinux* AlsaPcmOutputStream::manager() { |
|
scherkus (not reviewing)
2011/06/07 00:19:01
inline this method as it no longer needs to be spl
enal
2011/06/07 17:29:00
Done.
|
| - DCHECK_EQ(MessageLoop::current(), client_thread_loop_); |
| + CHECK_EQ(MessageLoop::current(), message_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 +846,10 @@ |
| } |
| 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,25 +858,22 @@ |
| return state_; |
| } |
| -AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::SharedData::state() { |
| - base::AutoLock l(lock_); |
| +AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() { |
|
scherkus (not reviewing)
2011/06/07 00:19:01
I'd drop this method as it's private
enal
2011/06/07 17:29:00
It is used in the unit tests, I'd prefer tests to
|
| return state_; |
| } |
| -float AlsaPcmOutputStream::SharedData::volume() { |
| - base::AutoLock l(lock_); |
| +float AlsaPcmOutputStream::volume() { |
|
scherkus (not reviewing)
2011/06/07 00:19:01
I'd drop this method as it's private
enal
2011/06/07 17:29:00
Done.
|
| return volume_; |
| } |
| -void AlsaPcmOutputStream::SharedData::set_volume(float v) { |
| - base::AutoLock l(lock_); |
| +void AlsaPcmOutputStream::set_volume(float v) { |
|
scherkus (not reviewing)
2011/06/07 00:19:01
I'd drop this method as it's private
enal
2011/06/07 17:29:00
Done.
|
| volume_ = v; |
| } |
| -uint32 AlsaPcmOutputStream::SharedData::OnMoreData( |
| - AudioOutputStream* stream, uint8* dest, uint32 max_size, |
| - AudioBuffersState buffers_state) { |
| - base::AutoLock l(lock_); |
| +uint32 AlsaPcmOutputStream::OnMoreData(AudioOutputStream* stream, |
|
scherkus (not reviewing)
2011/06/07 00:19:01
it looks like we pass "this" for the stream parame
enal
2011/06/07 17:29:00
Name and arguments are defined in the parent class
scherkus (not reviewing)
2011/06/07 23:01:59
the abstract virtual function is actually from Aud
|
| + uint8* dest, |
| + uint32 max_size, |
| + AudioBuffersState buffers_state) { |
| if (source_callback_) { |
| return source_callback_->OnMoreData(stream, dest, max_size, buffers_state); |
| } |
| @@ -929,9 +881,7 @@ |
| return 0; |
| } |
| -void AlsaPcmOutputStream::SharedData::OnError(AudioOutputStream* stream, |
| - int code) { |
| - base::AutoLock l(lock_); |
| +void AlsaPcmOutputStream::OnError(AudioOutputStream* stream, int code) { |
|
scherkus (not reviewing)
2011/06/07 00:19:01
ditto, maybe RunErrorCallback()
enal
2011/06/07 17:29:00
Name and arguments are defined in the parent class
scherkus (not reviewing)
2011/06/07 23:01:59
Ditto
|
| if (source_callback_) { |
| source_callback_->OnError(stream, code); |
| } |
| @@ -939,9 +889,7 @@ |
| // 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) { |
|
scherkus (not reviewing)
2011/06/07 00:19:01
I'd drop this method as it's private
enal
2011/06/07 17:29:00
Called in the unit tests.
|
| + DCHECK_EQ(MessageLoop::current(), message_loop_); |
| source_callback_ = callback; |
| } |