From 8117a12e805a3243141cf3c7cb0f2cdd0538b47f Mon Sep 17 00:00:00 2001 From: "spang@chromium.org" Date: Thu, 28 Nov 2013 06:00:33 +0000 Subject: Support use_alsa==0 on Linux This moves the ALSA audio code from media/audio/linux to media/audio/alsa and splits Linux's CreateAudioManager() into a new file. This function chooses which AudioManager subclass to use on Linux. When use_alsa==0 on Linux, the fallback audio manager is changed from ALSA to a new fake implementation that always has an empty set of devices and will only return fake streams. This allows us to compile with no audio support on Linux. In particular, we can compile for targets that don't have the ALSA libraries available. Obviously, it's not (yet) possible to play audio in this configuration. BUG=318315, 318413 Review URL: https://codereview.chromium.org/89793003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237696 0039d316-1c4b-4281-b951-d872f2087c98 --- .../media/media_stream_manager_unittest.cc | 14 +- media/audio/alsa/alsa_input.cc | 340 ++++++++ media/audio/alsa/alsa_input.h | 92 +++ media/audio/alsa/alsa_output.cc | 765 ++++++++++++++++++ media/audio/alsa/alsa_output.h | 228 ++++++ media/audio/alsa/alsa_output_unittest.cc | 870 +++++++++++++++++++++ media/audio/alsa/alsa_util.cc | 200 +++++ media/audio/alsa/alsa_util.h | 47 ++ media/audio/alsa/alsa_wrapper.cc | 173 ++++ media/audio/alsa/alsa_wrapper.h | 86 ++ media/audio/alsa/audio_manager_alsa.cc | 361 +++++++++ media/audio/alsa/audio_manager_alsa.h | 94 +++ .../audio_low_latency_input_output_unittest.cc | 12 +- media/audio/audio_manager_unittest.cc | 14 +- media/audio/cras/cras_input.cc | 2 +- media/audio/cras/cras_unified.cc | 2 +- media/audio/fake_audio_manager.cc | 68 ++ media/audio/fake_audio_manager.h | 51 ++ media/audio/linux/alsa_input.cc | 340 -------- media/audio/linux/alsa_input.h | 92 --- media/audio/linux/alsa_output.cc | 765 ------------------ media/audio/linux/alsa_output.h | 228 ------ media/audio/linux/alsa_output_unittest.cc | 870 --------------------- media/audio/linux/alsa_util.cc | 200 ----- media/audio/linux/alsa_util.h | 47 -- media/audio/linux/alsa_wrapper.cc | 173 ---- media/audio/linux/alsa_wrapper.h | 81 -- media/audio/linux/audio_manager_linux.cc | 355 +-------- media/audio/linux/audio_manager_linux.h | 94 --- media/audio/pulse/audio_manager_pulse.cc | 4 +- media/media.gyp | 32 +- tools/valgrind/tsan/suppressions.txt | 2 +- 32 files changed, 3433 insertions(+), 3269 deletions(-) create mode 100644 media/audio/alsa/alsa_input.cc create mode 100644 media/audio/alsa/alsa_input.h create mode 100644 media/audio/alsa/alsa_output.cc create mode 100644 media/audio/alsa/alsa_output.h create mode 100644 media/audio/alsa/alsa_output_unittest.cc create mode 100644 media/audio/alsa/alsa_util.cc create mode 100644 media/audio/alsa/alsa_util.h create mode 100644 media/audio/alsa/alsa_wrapper.cc create mode 100644 media/audio/alsa/alsa_wrapper.h create mode 100644 media/audio/alsa/audio_manager_alsa.cc create mode 100644 media/audio/alsa/audio_manager_alsa.h create mode 100644 media/audio/fake_audio_manager.cc create mode 100644 media/audio/fake_audio_manager.h delete mode 100644 media/audio/linux/alsa_input.cc delete mode 100644 media/audio/linux/alsa_input.h delete mode 100644 media/audio/linux/alsa_output.cc delete mode 100644 media/audio/linux/alsa_output.h delete mode 100644 media/audio/linux/alsa_output_unittest.cc delete mode 100644 media/audio/linux/alsa_util.cc delete mode 100644 media/audio/linux/alsa_util.h delete mode 100644 media/audio/linux/alsa_wrapper.cc delete mode 100644 media/audio/linux/alsa_wrapper.h delete mode 100644 media/audio/linux/audio_manager_linux.h diff --git a/content/browser/renderer_host/media/media_stream_manager_unittest.cc b/content/browser/renderer_host/media/media_stream_manager_unittest.cc index a67237a..03e95e0 100644 --- a/content/browser/renderer_host/media/media_stream_manager_unittest.cc +++ b/content/browser/renderer_host/media/media_stream_manager_unittest.cc @@ -13,14 +13,16 @@ #include "content/common/media/media_stream_options.h" #include "content/public/test/test_browser_thread_bundle.h" #include "media/audio/audio_manager_base.h" -#if defined(OS_ANDROID) +#if defined(USE_ALSA) +#include "media/audio/alsa/audio_manager_alsa.h" +#elif defined(OS_ANDROID) #include "media/audio/android/audio_manager_android.h" -#elif defined(OS_LINUX) || defined(OS_OPENBSD) -#include "media/audio/linux/audio_manager_linux.h" #elif defined(OS_MACOSX) #include "media/audio/mac/audio_manager_mac.h" #elif defined(OS_WIN) #include "media/audio/win/audio_manager_win.h" +#else +#include "media/audio/fake_audio_manager.h" #endif #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -29,14 +31,16 @@ using testing::_; namespace content { -#if defined(OS_LINUX) || defined(OS_OPENBSD) -typedef media::AudioManagerLinux AudioManagerPlatform; +#if defined(USE_ALSA) +typedef media::AudioManagerAlsa AudioManagerPlatform; #elif defined(OS_MACOSX) typedef media::AudioManagerMac AudioManagerPlatform; #elif defined(OS_WIN) typedef media::AudioManagerWin AudioManagerPlatform; #elif defined(OS_ANDROID) typedef media::AudioManagerAndroid AudioManagerPlatform; +#else +typedef media::FakeAudioManager AudioManagerPlatform; #endif diff --git a/media/audio/alsa/alsa_input.cc b/media/audio/alsa/alsa_input.cc new file mode 100644 index 0000000..9dcbf2b --- /dev/null +++ b/media/audio/alsa/alsa_input.cc @@ -0,0 +1,340 @@ +// Copyright 2013 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. + +#include "media/audio/alsa/alsa_input.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/time/time.h" +#include "media/audio/alsa/alsa_output.h" +#include "media/audio/alsa/alsa_util.h" +#include "media/audio/alsa/alsa_wrapper.h" +#include "media/audio/alsa/audio_manager_alsa.h" +#include "media/audio/audio_manager.h" + +namespace media { + +static const int kNumPacketsInRingBuffer = 3; + +static const char kDefaultDevice1[] = "default"; +static const char kDefaultDevice2[] = "plug:default"; + +const char AlsaPcmInputStream::kAutoSelectDevice[] = ""; + +AlsaPcmInputStream::AlsaPcmInputStream(AudioManagerBase* audio_manager, + const std::string& device_name, + const AudioParameters& params, + AlsaWrapper* wrapper) + : audio_manager_(audio_manager), + device_name_(device_name), + params_(params), + bytes_per_buffer_(params.frames_per_buffer() * + (params.channels() * params.bits_per_sample()) / 8), + wrapper_(wrapper), + buffer_duration_(base::TimeDelta::FromMicroseconds( + params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond / + static_cast(params.sample_rate()))), + callback_(NULL), + device_handle_(NULL), + mixer_handle_(NULL), + mixer_element_handle_(NULL), + weak_factory_(this), + read_callback_behind_schedule_(false) { +} + +AlsaPcmInputStream::~AlsaPcmInputStream() {} + +bool AlsaPcmInputStream::Open() { + if (device_handle_) + return false; // Already open. + + snd_pcm_format_t pcm_format = alsa_util::BitsToFormat( + params_.bits_per_sample()); + if (pcm_format == SND_PCM_FORMAT_UNKNOWN) { + LOG(WARNING) << "Unsupported bits per sample: " + << params_.bits_per_sample(); + return false; + } + + uint32 latency_us = + buffer_duration_.InMicroseconds() * kNumPacketsInRingBuffer; + + // Use the same minimum required latency as output. + latency_us = std::max(latency_us, AlsaPcmOutputStream::kMinLatencyMicros); + + if (device_name_ == kAutoSelectDevice) { + const char* device_names[] = { kDefaultDevice1, kDefaultDevice2 }; + for (size_t i = 0; i < arraysize(device_names); ++i) { + device_handle_ = alsa_util::OpenCaptureDevice( + wrapper_, device_names[i], params_.channels(), + params_.sample_rate(), pcm_format, latency_us); + + if (device_handle_) { + device_name_ = device_names[i]; + break; + } + } + } else { + device_handle_ = alsa_util::OpenCaptureDevice(wrapper_, + device_name_.c_str(), + params_.channels(), + params_.sample_rate(), + pcm_format, latency_us); + } + + if (device_handle_) { + audio_buffer_.reset(new uint8[bytes_per_buffer_]); + + // Open the microphone mixer. + mixer_handle_ = alsa_util::OpenMixer(wrapper_, device_name_); + if (mixer_handle_) { + mixer_element_handle_ = alsa_util::LoadCaptureMixerElement( + wrapper_, mixer_handle_); + } + } + + return device_handle_ != NULL; +} + +void AlsaPcmInputStream::Start(AudioInputCallback* callback) { + DCHECK(!callback_ && callback); + callback_ = callback; + StartAgc(); + int error = wrapper_->PcmPrepare(device_handle_); + if (error < 0) { + HandleError("PcmPrepare", error); + } else { + error = wrapper_->PcmStart(device_handle_); + if (error < 0) + HandleError("PcmStart", error); + } + + if (error < 0) { + callback_ = NULL; + } else { + // We start reading data half |buffer_duration_| later than when the + // buffer might have got filled, to accommodate some delays in the audio + // driver. This could also give us a smooth read sequence going forward. + base::TimeDelta delay = buffer_duration_ + buffer_duration_ / 2; + next_read_time_ = base::TimeTicks::Now() + delay; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&AlsaPcmInputStream::ReadAudio, weak_factory_.GetWeakPtr()), + delay); + } +} + +bool AlsaPcmInputStream::Recover(int original_error) { + int error = wrapper_->PcmRecover(device_handle_, original_error, 1); + if (error < 0) { + // Docs say snd_pcm_recover returns the original error if it is not one + // of the recoverable ones, so this log message will probably contain the + // same error twice. + LOG(WARNING) << "Unable to recover from \"" + << wrapper_->StrError(original_error) << "\": " + << wrapper_->StrError(error); + return false; + } + + if (original_error == -EPIPE) { // Buffer underrun/overrun. + // For capture streams we have to repeat the explicit start() to get + // data flowing again. + error = wrapper_->PcmStart(device_handle_); + if (error < 0) { + HandleError("PcmStart", error); + return false; + } + } + + return true; +} + +snd_pcm_sframes_t AlsaPcmInputStream::GetCurrentDelay() { + snd_pcm_sframes_t delay = -1; + + int error = wrapper_->PcmDelay(device_handle_, &delay); + if (error < 0) + Recover(error); + + // snd_pcm_delay() may not work in the beginning of the stream. In this case + // return delay of data we know currently is in the ALSA's buffer. + if (delay < 0) + delay = wrapper_->PcmAvailUpdate(device_handle_); + + return delay; +} + +void AlsaPcmInputStream::ReadAudio() { + DCHECK(callback_); + + snd_pcm_sframes_t frames = wrapper_->PcmAvailUpdate(device_handle_); + if (frames < 0) { // Potentially recoverable error? + LOG(WARNING) << "PcmAvailUpdate(): " << wrapper_->StrError(frames); + Recover(frames); + } + + if (frames < params_.frames_per_buffer()) { + // Not enough data yet or error happened. In both cases wait for a very + // small duration before checking again. + // Even Though read callback was behind schedule, there is no data, so + // reset the next_read_time_. + if (read_callback_behind_schedule_) { + next_read_time_ = base::TimeTicks::Now(); + read_callback_behind_schedule_ = false; + } + + base::TimeDelta next_check_time = buffer_duration_ / 2; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&AlsaPcmInputStream::ReadAudio, weak_factory_.GetWeakPtr()), + next_check_time); + return; + } + + int num_buffers = frames / params_.frames_per_buffer(); + uint32 hardware_delay_bytes = + static_cast(GetCurrentDelay() * params_.GetBytesPerFrame()); + double normalized_volume = 0.0; + + // Update the AGC volume level once every second. Note that, |volume| is + // also updated each time SetVolume() is called through IPC by the + // render-side AGC. + GetAgcVolume(&normalized_volume); + + while (num_buffers--) { + int frames_read = wrapper_->PcmReadi(device_handle_, audio_buffer_.get(), + params_.frames_per_buffer()); + if (frames_read == params_.frames_per_buffer()) { + callback_->OnData(this, audio_buffer_.get(), bytes_per_buffer_, + hardware_delay_bytes, normalized_volume); + } else { + LOG(WARNING) << "PcmReadi returning less than expected frames: " + << frames_read << " vs. " << params_.frames_per_buffer() + << ". Dropping this buffer."; + } + } + + next_read_time_ += buffer_duration_; + base::TimeDelta delay = next_read_time_ - base::TimeTicks::Now(); + if (delay < base::TimeDelta()) { + DVLOG(1) << "Audio read callback behind schedule by " + << (buffer_duration_ - delay).InMicroseconds() + << " (us)."; + // Read callback is behind schedule. Assuming there is data pending in + // the soundcard, invoke the read callback immediate in order to catch up. + read_callback_behind_schedule_ = true; + delay = base::TimeDelta(); + } + + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&AlsaPcmInputStream::ReadAudio, weak_factory_.GetWeakPtr()), + delay); +} + +void AlsaPcmInputStream::Stop() { + if (!device_handle_ || !callback_) + return; + + StopAgc(); + + weak_factory_.InvalidateWeakPtrs(); // Cancel the next scheduled read. + int error = wrapper_->PcmDrop(device_handle_); + if (error < 0) + HandleError("PcmDrop", error); +} + +void AlsaPcmInputStream::Close() { + if (device_handle_) { + weak_factory_.InvalidateWeakPtrs(); // Cancel the next scheduled read. + int error = alsa_util::CloseDevice(wrapper_, device_handle_); + if (error < 0) + HandleError("PcmClose", error); + + if (mixer_handle_) + alsa_util::CloseMixer(wrapper_, mixer_handle_, device_name_); + + audio_buffer_.reset(); + device_handle_ = NULL; + mixer_handle_ = NULL; + mixer_element_handle_ = NULL; + + if (callback_) + callback_->OnClose(this); + } + + audio_manager_->ReleaseInputStream(this); +} + +double AlsaPcmInputStream::GetMaxVolume() { + if (!mixer_handle_ || !mixer_element_handle_) { + DLOG(WARNING) << "GetMaxVolume is not supported for " << device_name_; + return 0.0; + } + + if (!wrapper_->MixerSelemHasCaptureVolume(mixer_element_handle_)) { + DLOG(WARNING) << "Unsupported microphone volume for " << device_name_; + return 0.0; + } + + long min = 0; + long max = 0; + if (wrapper_->MixerSelemGetCaptureVolumeRange(mixer_element_handle_, + &min, + &max)) { + DLOG(WARNING) << "Unsupported max microphone volume for " << device_name_; + return 0.0; + } + DCHECK(min == 0); + DCHECK(max > 0); + + return static_cast(max); +} + +void AlsaPcmInputStream::SetVolume(double volume) { + if (!mixer_handle_ || !mixer_element_handle_) { + DLOG(WARNING) << "SetVolume is not supported for " << device_name_; + return; + } + + int error = wrapper_->MixerSelemSetCaptureVolumeAll( + mixer_element_handle_, static_cast(volume)); + if (error < 0) { + DLOG(WARNING) << "Unable to set volume for " << device_name_; + } + + // Update the AGC volume level based on the last setting above. Note that, + // the volume-level resolution is not infinite and it is therefore not + // possible to assume that the volume provided as input parameter can be + // used directly. Instead, a new query to the audio hardware is required. + // This method does nothing if AGC is disabled. + UpdateAgcVolume(); +} + +double AlsaPcmInputStream::GetVolume() { + if (!mixer_handle_ || !mixer_element_handle_) { + DLOG(WARNING) << "GetVolume is not supported for " << device_name_; + return 0.0; + } + + long current_volume = 0; + int error = wrapper_->MixerSelemGetCaptureVolume( + mixer_element_handle_, static_cast(0), + ¤t_volume); + if (error < 0) { + DLOG(WARNING) << "Unable to get volume for " << device_name_; + return 0.0; + } + + return static_cast(current_volume); +} + +void AlsaPcmInputStream::HandleError(const char* method, int error) { + LOG(WARNING) << method << ": " << wrapper_->StrError(error); + callback_->OnError(this); +} + +} // namespace media diff --git a/media/audio/alsa/alsa_input.h b/media/audio/alsa/alsa_input.h new file mode 100644 index 0000000..6e9aad90 --- /dev/null +++ b/media/audio/alsa/alsa_input.h @@ -0,0 +1,92 @@ +// Copyright 2013 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_ALSA_ALSA_INPUT_H_ +#define MEDIA_AUDIO_ALSA_ALSA_INPUT_H_ + +#include + +#include + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "media/audio/agc_audio_stream.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_parameters.h" + +namespace media { + +class AlsaWrapper; +class AudioManagerBase; + +// Provides an input stream for audio capture based on the ALSA PCM interface. +// This object is not thread safe and all methods should be invoked in the +// thread that created the object. +class AlsaPcmInputStream : public AgcAudioStream { + public: + // Pass this to the constructor if you want to attempt auto-selection + // of the audio recording device. + static const char kAutoSelectDevice[]; + + // Create a PCM Output stream for the ALSA device identified by + // |device_name|. If unsure of what to use for |device_name|, use + // |kAutoSelectDevice|. + AlsaPcmInputStream(AudioManagerBase* audio_manager, + const std::string& device_name, + const AudioParameters& params, + AlsaWrapper* wrapper); + + virtual ~AlsaPcmInputStream(); + + // Implementation of AudioInputStream. + virtual bool Open() OVERRIDE; + virtual void Start(AudioInputCallback* callback) OVERRIDE; + virtual void Stop() OVERRIDE; + virtual void Close() OVERRIDE; + virtual double GetMaxVolume() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual double GetVolume() OVERRIDE; + + private: + // Logs the error and invokes any registered callbacks. + void HandleError(const char* method, int error); + + // Reads one or more buffers of audio from the device, passes on to the + // registered callback and schedules the next read. + void ReadAudio(); + + // Recovers from any device errors if possible. + bool Recover(int error); + + // Utility function for talking with the ALSA API. + snd_pcm_sframes_t GetCurrentDelay(); + + // Non-refcounted pointer back to the audio manager. + // The AudioManager indirectly holds on to stream objects, so we don't + // want circular references. Additionally, stream objects live on the audio + // thread, which is owned by the audio manager and we don't want to addref + // the manager from that thread. + AudioManagerBase* audio_manager_; + std::string device_name_; + AudioParameters params_; + int bytes_per_buffer_; + AlsaWrapper* wrapper_; + base::TimeDelta buffer_duration_; // Length of each recorded buffer. + AudioInputCallback* callback_; // Valid during a recording session. + base::TimeTicks next_read_time_; // Scheduled time for next read callback. + snd_pcm_t* device_handle_; // Handle to the ALSA PCM recording device. + snd_mixer_t* mixer_handle_; // Handle to the ALSA microphone mixer. + snd_mixer_elem_t* mixer_element_handle_; // Handle to the capture element. + base::WeakPtrFactory weak_factory_; + scoped_ptr audio_buffer_; // Buffer used for reading audio data. + bool read_callback_behind_schedule_; + + DISALLOW_COPY_AND_ASSIGN(AlsaPcmInputStream); +}; + +} // namespace media + +#endif // MEDIA_AUDIO_ALSA_ALSA_INPUT_H_ diff --git a/media/audio/alsa/alsa_output.cc b/media/audio/alsa/alsa_output.cc new file mode 100644 index 0000000..eccf8ee --- /dev/null +++ b/media/audio/alsa/alsa_output.cc @@ -0,0 +1,765 @@ +// Copyright 2013 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. +// +// THREAD SAFETY +// +// AlsaPcmOutputStream object is *not* thread-safe and should only be used +// from the audio thread. We DCHECK on this assumption whenever we can. +// +// SEMANTICS OF Close() +// +// Close() is responsible for cleaning up any resources that were acquired after +// a successful Open(). Close() will nullify any scheduled outstanding runnable +// methods. +// +// +// SEMANTICS OF ERROR STATES +// +// 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. +// +// 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, Close() must be +// called to cleanup the ALSA devices and release resources. +// +// When |stop_stream_| is set, no more commands will be made against the +// ALSA device, and playback will effectively stop. From the client's point of +// view, it will seem that the device has just clogged and stopped requesting +// data. + +#include "media/audio/alsa/alsa_output.h" + +#include + +#include "base/bind.h" +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/time/time.h" +#include "media/audio/alsa/alsa_util.h" +#include "media/audio/alsa/alsa_wrapper.h" +#include "media/audio/alsa/audio_manager_alsa.h" +#include "media/base/channel_mixer.h" +#include "media/base/data_buffer.h" +#include "media/base/seekable_buffer.h" + +namespace media { + +// Set to 0 during debugging if you want error messages due to underrun +// events or other recoverable errors. +#if defined(NDEBUG) +static const int kPcmRecoverIsSilent = 1; +#else +static const int kPcmRecoverIsSilent = 0; +#endif + +// While the "default" device may support multi-channel audio, in Alsa, only +// the device names surround40, surround41, surround50, etc, have a defined +// channel mapping according to Lennart: +// +// http://0pointer.de/blog/projects/guide-to-sound-apis.html +// +// This function makes a best guess at the specific > 2 channel device name +// based on the number of channels requested. NULL is returned if no device +// can be found to match the channel numbers. In this case, using +// kDefaultDevice is probably the best bet. +// +// A five channel source is assumed to be surround50 instead of surround41 +// (which is also 5 channels). +// +// TODO(ajwong): The source data should have enough info to tell us if we want +// surround41 versus surround51, etc., instead of needing us to guess based on +// channel number. Fix API to pass that data down. +static const char* GuessSpecificDeviceName(uint32 channels) { + switch (channels) { + case 8: + return "surround71"; + + case 7: + return "surround70"; + + case 6: + return "surround51"; + + case 5: + return "surround50"; + + case 4: + return "surround40"; + + default: + return NULL; + } +} + +std::ostream& operator<<(std::ostream& os, + AlsaPcmOutputStream::InternalState state) { + switch (state) { + case AlsaPcmOutputStream::kInError: + os << "kInError"; + break; + case AlsaPcmOutputStream::kCreated: + os << "kCreated"; + break; + case AlsaPcmOutputStream::kIsOpened: + os << "kIsOpened"; + break; + case AlsaPcmOutputStream::kIsPlaying: + os << "kIsPlaying"; + break; + case AlsaPcmOutputStream::kIsStopped: + os << "kIsStopped"; + break; + case AlsaPcmOutputStream::kIsClosed: + os << "kIsClosed"; + break; + }; + return os; +} + +const char AlsaPcmOutputStream::kDefaultDevice[] = "default"; +const char AlsaPcmOutputStream::kAutoSelectDevice[] = ""; +const char AlsaPcmOutputStream::kPlugPrefix[] = "plug:"; + +// We use 40ms as our minimum required latency. If it is needed, we may be able +// to get it down to 20ms. +const uint32 AlsaPcmOutputStream::kMinLatencyMicros = 40 * 1000; + +AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name, + const AudioParameters& params, + AlsaWrapper* wrapper, + AudioManagerBase* manager) + : requested_device_name_(device_name), + pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())), + channels_(params.channels()), + channel_layout_(params.channel_layout()), + sample_rate_(params.sample_rate()), + bytes_per_sample_(params.bits_per_sample() / 8), + bytes_per_frame_(params.GetBytesPerFrame()), + packet_size_(params.GetBytesPerBuffer()), + latency_(std::max( + base::TimeDelta::FromMicroseconds(kMinLatencyMicros), + FramesToTimeDelta(params.frames_per_buffer() * 2, sample_rate_))), + bytes_per_output_frame_(bytes_per_frame_), + alsa_buffer_frames_(0), + stop_stream_(false), + wrapper_(wrapper), + manager_(manager), + message_loop_(base::MessageLoop::current()), + playback_handle_(NULL), + frames_per_packet_(packet_size_ / bytes_per_frame_), + weak_factory_(this), + state_(kCreated), + volume_(1.0f), + source_callback_(NULL), + audio_bus_(AudioBus::Create(params)) { + DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); + DCHECK_EQ(audio_bus_->frames() * bytes_per_frame_, packet_size_); + + // Sanity check input values. + if (!params.IsValid()) { + LOG(WARNING) << "Unsupported audio parameters."; + TransitionTo(kInError); + } + + if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { + LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample(); + TransitionTo(kInError); + } +} + +AlsaPcmOutputStream::~AlsaPcmOutputStream() { + InternalState current_state = state(); + DCHECK(current_state == kCreated || + current_state == kIsClosed || + current_state == kInError); + DCHECK(!playback_handle_); +} + +bool AlsaPcmOutputStream::Open() { + DCHECK(IsOnAudioThread()); + + if (state() == kInError) + return false; + + if (!CanTransitionTo(kIsOpened)) { + NOTREACHED() << "Invalid state: " << state(); + return false; + } + + // We do not need to check if the transition was successful because + // 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. + TransitionTo(kIsOpened); + + // Try to open the device. + if (requested_device_name_ == kAutoSelectDevice) { + playback_handle_ = AutoSelectDevice(latency_.InMicroseconds()); + if (playback_handle_) + DVLOG(1) << "Auto-selected device: " << device_name_; + } else { + device_name_ = requested_device_name_; + playback_handle_ = alsa_util::OpenPlaybackDevice( + wrapper_, device_name_.c_str(), channels_, sample_rate_, + pcm_format_, latency_.InMicroseconds()); + } + + // Finish initializing the stream if the device was opened successfully. + if (playback_handle_ == NULL) { + stop_stream_ = true; + TransitionTo(kInError); + return false; + } else { + bytes_per_output_frame_ = channel_mixer_ ? + mixed_audio_bus_->channels() * bytes_per_sample_ : bytes_per_frame_; + uint32 output_packet_size = frames_per_packet_ * bytes_per_output_frame_; + buffer_.reset(new media::SeekableBuffer(0, output_packet_size)); + + // Get alsa buffer size. + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; + int error = wrapper_->PcmGetParams(playback_handle_, &buffer_size, + &period_size); + if (error < 0) { + LOG(ERROR) << "Failed to get playback buffer size from ALSA: " + << wrapper_->StrError(error); + // Buffer size is at least twice of packet size. + alsa_buffer_frames_ = frames_per_packet_ * 2; + } else { + alsa_buffer_frames_ = buffer_size; + } + } + + return true; +} + +void AlsaPcmOutputStream::Close() { + DCHECK(IsOnAudioThread()); + + if (state() != kIsClosed) + TransitionTo(kIsClosed); + + // Shutdown the audio device. + if (playback_handle_) { + if (alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) { + LOG(WARNING) << "Unable to close audio device. Leaking handle."; + } + playback_handle_ = NULL; + + // Release the buffer. + buffer_.reset(); + + // Signal anything that might already be scheduled to stop. + stop_stream_ = true; // Not necessary in production, but unit tests + // uses the flag to verify that stream was closed. + } + + weak_factory_.InvalidateWeakPtrs(); + + // 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::Start(AudioSourceCallback* callback) { + DCHECK(IsOnAudioThread()); + + CHECK(callback); + + if (stop_stream_) + return; + + // Only post the task if we can enter the playing state. + if (TransitionTo(kIsPlaying) != kIsPlaying) + return; + + // Before starting, the buffer might have audio from previous user of this + // device. + buffer_->Clear(); + + // When starting again, drop all packets in the device and prepare it again + // in case we are restarting from a pause state and need to flush old data. + int error = wrapper_->PcmDrop(playback_handle_); + if (error < 0 && error != -EAGAIN) { + LOG(ERROR) << "Failure clearing playback device (" + << wrapper_->PcmName(playback_handle_) << "): " + << wrapper_->StrError(error); + stop_stream_ = true; + return; + } + + error = wrapper_->PcmPrepare(playback_handle_); + if (error < 0 && error != -EAGAIN) { + LOG(ERROR) << "Failure preparing stream (" + << wrapper_->PcmName(playback_handle_) << "): " + << wrapper_->StrError(error); + stop_stream_ = true; + return; + } + + // Ensure the first buffer is silence to avoid startup glitches. + int buffer_size = GetAvailableFrames() * bytes_per_output_frame_; + scoped_refptr silent_packet = new DataBuffer(buffer_size); + silent_packet->set_data_size(buffer_size); + memset(silent_packet->writable_data(), 0, silent_packet->data_size()); + buffer_->Append(silent_packet); + WritePacket(); + + // Start the callback chain. + set_source_callback(callback); + WriteTask(); +} + +void AlsaPcmOutputStream::Stop() { + DCHECK(IsOnAudioThread()); + + // Reset the callback, so that it is not called anymore. + set_source_callback(NULL); + weak_factory_.InvalidateWeakPtrs(); + + TransitionTo(kIsStopped); +} + +void AlsaPcmOutputStream::SetVolume(double volume) { + DCHECK(IsOnAudioThread()); + + volume_ = static_cast(volume); +} + +void AlsaPcmOutputStream::GetVolume(double* volume) { + DCHECK(IsOnAudioThread()); + + *volume = volume_; +} + +void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) { + DCHECK(IsOnAudioThread()); + + // If stopped, simulate a 0-length packet. + if (stop_stream_) { + buffer_->Clear(); + *source_exhausted = true; + return; + } + + *source_exhausted = false; + + // Request more data only when we run out of data in the buffer, because + // WritePacket() comsumes only the current chunk of data. + if (!buffer_->forward_bytes()) { + // Before making a request to source for data we need to determine the + // delay (in bytes) for the requested data to be played. + const uint32 hardware_delay = GetCurrentDelay() * bytes_per_frame_; + + scoped_refptr packet = + new media::DataBuffer(packet_size_); + int frames_filled = RunDataCallback( + audio_bus_.get(), AudioBuffersState(0, hardware_delay)); + + size_t packet_size = frames_filled * bytes_per_frame_; + DCHECK_LE(packet_size, packet_size_); + + // TODO(dalecurtis): Channel downmixing, upmixing, should be done in mixer; + // volume adjust should use SSE optimized vector_fmul() prior to interleave. + AudioBus* output_bus = audio_bus_.get(); + if (channel_mixer_) { + output_bus = mixed_audio_bus_.get(); + channel_mixer_->Transform(audio_bus_.get(), output_bus); + // Adjust packet size for downmix. + packet_size = packet_size / bytes_per_frame_ * bytes_per_output_frame_; + } + + // Note: If this ever changes to output raw float the data must be clipped + // and sanitized since it may come from an untrusted source such as NaCl. + output_bus->Scale(volume_); + output_bus->ToInterleaved( + frames_filled, bytes_per_sample_, packet->writable_data()); + + if (packet_size > 0) { + packet->set_data_size(packet_size); + // Add the packet to the buffer. + buffer_->Append(packet); + } else { + *source_exhausted = true; + } + } +} + +void AlsaPcmOutputStream::WritePacket() { + DCHECK(IsOnAudioThread()); + + // If the device is in error, just eat the bytes. + if (stop_stream_) { + buffer_->Clear(); + return; + } + + if (state() != kIsPlaying) + return; + + CHECK_EQ(buffer_->forward_bytes() % bytes_per_output_frame_, 0u); + + const uint8* buffer_data; + int buffer_size; + if (buffer_->GetCurrentChunk(&buffer_data, &buffer_size)) { + buffer_size = buffer_size - (buffer_size % bytes_per_output_frame_); + snd_pcm_sframes_t frames = std::min( + static_cast(buffer_size / bytes_per_output_frame_), + GetAvailableFrames()); + + if (!frames) + return; + + snd_pcm_sframes_t frames_written = + wrapper_->PcmWritei(playback_handle_, buffer_data, frames); + if (frames_written < 0) { + // Attempt once to immediately recover from EINTR, + // EPIPE (overrun/underrun), ESTRPIPE (stream suspended). WritePacket + // will eventually be called again, so eventual recovery will happen if + // muliple retries are required. + frames_written = wrapper_->PcmRecover(playback_handle_, + frames_written, + kPcmRecoverIsSilent); + if (frames_written < 0) { + if (frames_written != -EAGAIN) { + LOG(ERROR) << "Failed to write to pcm device: " + << wrapper_->StrError(frames_written); + RunErrorCallback(frames_written); + stop_stream_ = true; + } + } + } else { + DCHECK_EQ(frames_written, frames); + + // Seek forward in the buffer after we've written some data to ALSA. + buffer_->Seek(frames_written * bytes_per_output_frame_); + } + } else { + // If nothing left to write and playback hasn't started yet, start it now. + // This ensures that shorter sounds will still play. + if (playback_handle_ && + (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) && + GetCurrentDelay() > 0) { + wrapper_->PcmStart(playback_handle_); + } + } +} + +void AlsaPcmOutputStream::WriteTask() { + DCHECK(IsOnAudioThread()); + + if (stop_stream_) + return; + + if (state() == kIsStopped) + return; + + bool source_exhausted; + BufferPacket(&source_exhausted); + WritePacket(); + + ScheduleNextWrite(source_exhausted); +} + +void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) { + DCHECK(IsOnAudioThread()); + + if (stop_stream_ || state() != kIsPlaying) + return; + + const uint32 kTargetFramesAvailable = alsa_buffer_frames_ / 2; + uint32 available_frames = GetAvailableFrames(); + + base::TimeDelta next_fill_time; + if (buffer_->forward_bytes() && available_frames) { + // If we've got data available and ALSA has room, deliver it immediately. + next_fill_time = base::TimeDelta(); + } else if (buffer_->forward_bytes()) { + // If we've got data available and no room, poll until room is available. + // Polling in this manner allows us to ensure a more consistent callback + // schedule. In testing this yields a variance of +/- 5ms versus the non- + // polling strategy which is around +/- 30ms and bimodal. + next_fill_time = base::TimeDelta::FromMilliseconds(5); + } else if (available_frames < kTargetFramesAvailable) { + // Schedule the next write for the moment when the available buffer of the + // sound card hits |kTargetFramesAvailable|. + next_fill_time = FramesToTimeDelta( + kTargetFramesAvailable - available_frames, sample_rate_); + } else if (!source_exhausted) { + // The sound card has |kTargetFramesAvailable| or more frames available. + // Invoke the next write immediately to avoid underrun. + next_fill_time = base::TimeDelta(); + } else { + // The sound card has frames available, but our source is exhausted, so + // avoid busy looping by delaying a bit. + next_fill_time = base::TimeDelta::FromMilliseconds(10); + } + + message_loop_->PostDelayedTask(FROM_HERE, base::Bind( + &AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr()), + next_fill_time); +} + +// static +base::TimeDelta AlsaPcmOutputStream::FramesToTimeDelta(int frames, + double sample_rate) { + return base::TimeDelta::FromMicroseconds( + frames * base::Time::kMicrosecondsPerSecond / sample_rate); +} + +std::string AlsaPcmOutputStream::FindDeviceForChannels(uint32 channels) { + // Constants specified by the ALSA API for device hints. + static const int kGetAllDevices = -1; + static const char kPcmInterfaceName[] = "pcm"; + static const char kIoHintName[] = "IOID"; + static const char kNameHintName[] = "NAME"; + + const char* wanted_device = GuessSpecificDeviceName(channels); + if (!wanted_device) + return std::string(); + + std::string guessed_device; + void** hints = NULL; + int error = wrapper_->DeviceNameHint(kGetAllDevices, + kPcmInterfaceName, + &hints); + if (error == 0) { + // NOTE: Do not early return from inside this if statement. The + // hints above need to be freed. + for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) { + // Only examine devices that are output capable.. Valid values are + // "Input", "Output", and NULL which means both input and output. + scoped_ptr_malloc io( + wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName)); + if (io != NULL && strcmp(io.get(), "Input") == 0) + continue; + + // Attempt to select the closest device for number of channels. + scoped_ptr_malloc name( + wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName)); + if (strncmp(wanted_device, name.get(), strlen(wanted_device)) == 0) { + guessed_device = name.get(); + break; + } + } + + // Destroy the hint now that we're done with it. + wrapper_->DeviceNameFreeHint(hints); + hints = NULL; + } else { + LOG(ERROR) << "Unable to get hints for devices: " + << wrapper_->StrError(error); + } + + return guessed_device; +} + +snd_pcm_sframes_t AlsaPcmOutputStream::GetCurrentDelay() { + snd_pcm_sframes_t delay = -1; + // Don't query ALSA's delay if we have underrun since it'll be jammed at some + // non-zero value and potentially even negative! + // + // Also, if we're in the prepared state, don't query because that seems to + // cause an I/O error when we do query the delay. + snd_pcm_state_t pcm_state = wrapper_->PcmState(playback_handle_); + if (pcm_state != SND_PCM_STATE_XRUN && + pcm_state != SND_PCM_STATE_PREPARED) { + int error = wrapper_->PcmDelay(playback_handle_, &delay); + if (error < 0) { + // Assume a delay of zero and attempt to recover the device. + delay = -1; + error = wrapper_->PcmRecover(playback_handle_, + error, + kPcmRecoverIsSilent); + if (error < 0) { + LOG(ERROR) << "Failed querying delay: " << wrapper_->StrError(error); + } + } + } + + // snd_pcm_delay() sometimes returns crazy values. In this case return delay + // of data we know currently is in ALSA's buffer. Note: When the underlying + // driver is PulseAudio based, certain configuration settings (e.g., tsched=1) + // will generate much larger delay values than |alsa_buffer_frames_|, so only + // clip if delay is truly crazy (> 10x expected). + if (static_cast(delay) > alsa_buffer_frames_ * 10) { + delay = alsa_buffer_frames_ - GetAvailableFrames(); + } + + if (delay < 0) { + delay = 0; + } + + return delay; +} + +snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { + DCHECK(IsOnAudioThread()); + + if (stop_stream_) + return 0; + + // Find the number of frames queued in the sound device. + snd_pcm_sframes_t available_frames = + wrapper_->PcmAvailUpdate(playback_handle_); + if (available_frames < 0) { + available_frames = wrapper_->PcmRecover(playback_handle_, + available_frames, + kPcmRecoverIsSilent); + } + if (available_frames < 0) { + LOG(ERROR) << "Failed querying available frames. Assuming 0: " + << wrapper_->StrError(available_frames); + return 0; + } + if (static_cast(available_frames) > alsa_buffer_frames_ * 2) { + LOG(ERROR) << "ALSA returned " << available_frames << " of " + << alsa_buffer_frames_ << " frames available."; + return alsa_buffer_frames_; + } + + return available_frames; +} + +snd_pcm_t* AlsaPcmOutputStream::AutoSelectDevice(unsigned int latency) { + // For auto-selection: + // 1) Attempt to open a device that best matches the number of channels + // requested. + // 2) If that fails, attempt the "plug:" version of it in case ALSA can + // remap do some software conversion to make it work. + // 3) Fallback to kDefaultDevice. + // 4) If that fails too, try the "plug:" version of kDefaultDevice. + // 5) Give up. + snd_pcm_t* handle = NULL; + device_name_ = FindDeviceForChannels(channels_); + + // Step 1. + if (!device_name_.empty()) { + if ((handle = alsa_util::OpenPlaybackDevice(wrapper_, device_name_.c_str(), + channels_, sample_rate_, + pcm_format_, + latency)) != NULL) { + return handle; + } + + // Step 2. + device_name_ = kPlugPrefix + device_name_; + if ((handle = alsa_util::OpenPlaybackDevice(wrapper_, device_name_.c_str(), + channels_, sample_rate_, + pcm_format_, + latency)) != NULL) { + return handle; + } + } + + // For the kDefaultDevice device, we can only reliably depend on 2-channel + // output to have the correct ordering according to Lennart. For the channel + // formats that we know how to downmix from (3 channel to 8 channel), setup + // downmixing. + uint32 default_channels = channels_; + if (default_channels > 2) { + channel_mixer_.reset(new ChannelMixer( + channel_layout_, CHANNEL_LAYOUT_STEREO)); + default_channels = 2; + mixed_audio_bus_ = AudioBus::Create( + default_channels, audio_bus_->frames()); + } + + // Step 3. + device_name_ = kDefaultDevice; + if ((handle = alsa_util::OpenPlaybackDevice( + wrapper_, device_name_.c_str(), default_channels, sample_rate_, + pcm_format_, latency)) != NULL) { + return handle; + } + + // Step 4. + device_name_ = kPlugPrefix + device_name_; + if ((handle = alsa_util::OpenPlaybackDevice( + wrapper_, device_name_.c_str(), default_channels, sample_rate_, + pcm_format_, latency)) != NULL) { + return handle; + } + + // Unable to open any device. + device_name_.clear(); + return NULL; +} + +bool AlsaPcmOutputStream::CanTransitionTo(InternalState to) { + switch (state_) { + case kCreated: + return to == kIsOpened || to == kIsClosed || to == kInError; + + case kIsOpened: + return to == kIsPlaying || to == kIsStopped || + to == kIsClosed || to == kInError; + + case kIsPlaying: + return to == kIsPlaying || to == kIsStopped || + to == kIsClosed || to == kInError; + + case kIsStopped: + return to == kIsPlaying || to == kIsStopped || + to == kIsClosed || to == kInError; + + case kInError: + return to == kIsClosed || to == kInError; + + case kIsClosed: + default: + return false; + } +} + +AlsaPcmOutputStream::InternalState +AlsaPcmOutputStream::TransitionTo(InternalState to) { + DCHECK(IsOnAudioThread()); + + if (!CanTransitionTo(to)) { + NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to; + state_ = kInError; + } else { + state_ = to; + } + return state_; +} + +AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() { + return state_; +} + +bool AlsaPcmOutputStream::IsOnAudioThread() const { + return message_loop_ && message_loop_ == base::MessageLoop::current(); +} + +int AlsaPcmOutputStream::RunDataCallback(AudioBus* audio_bus, + AudioBuffersState buffers_state) { + TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback"); + + if (source_callback_) + return source_callback_->OnMoreData(audio_bus, buffers_state); + + return 0; +} + +void AlsaPcmOutputStream::RunErrorCallback(int code) { + if (source_callback_) + source_callback_->OnError(this); +} + +// Changes the AudioSourceCallback to proxy calls to. Pass in NULL to +// release ownership of the currently registered callback. +void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) { + DCHECK(IsOnAudioThread()); + source_callback_ = callback; +} + +} // namespace media diff --git a/media/audio/alsa/alsa_output.h b/media/audio/alsa/alsa_output.h new file mode 100644 index 0000000..65a23f7 --- /dev/null +++ b/media/audio/alsa/alsa_output.h @@ -0,0 +1,228 @@ +// Copyright 2013 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. +// +// On device write failure, the stream will move itself to an invalid state. +// No more data will be pulled from the data source, or written to the device. +// All calls to public API functions will either no-op themselves, or return an +// error if possible. Specifically, If the stream is in an error state, Open() +// will return false, and Start() will call OnError() immediately on the +// provided callback. +// +// If the stream is successfully opened, Close() must be called. After Close +// has been called, the object should be regarded as deleted and not touched. +// +// AlsaPcmOutputStream is a single threaded class that should only be used from +// the audio thread. When modifying the code in this class, please read the +// threading assumptions at the top of the implementation. + +#ifndef MEDIA_AUDIO_ALSA_ALSA_OUTPUT_H_ +#define MEDIA_AUDIO_ALSA_ALSA_OUTPUT_H_ + +#include + +#include + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_parameters.h" + +namespace base { +class MessageLoop; +} + +namespace media { + +class AlsaWrapper; +class AudioManagerBase; +class ChannelMixer; +class SeekableBuffer; + +class MEDIA_EXPORT AlsaPcmOutputStream : public AudioOutputStream { + public: + // String for the generic "default" ALSA device that has the highest + // compatibility and chance of working. + static const char kDefaultDevice[]; + + // Pass this to the AlsaPcmOutputStream if you want to attempt auto-selection + // of the audio device. + static const char kAutoSelectDevice[]; + + // Prefix for device names to enable ALSA library resampling. + static const char kPlugPrefix[]; + + // The minimum latency that is accepted by the device. + static const uint32 kMinLatencyMicros; + + // Create a PCM Output stream for the ALSA device identified by + // |device_name|. The AlsaPcmOutputStream uses |wrapper| to communicate with + // the alsa libraries, allowing for dependency injection during testing. All + // requesting of data, and writing to the alsa device will be done on + // |message_loop|. + // + // If unsure of what to use for |device_name|, use |kAutoSelectDevice|. + AlsaPcmOutputStream(const std::string& device_name, + const AudioParameters& params, + AlsaWrapper* wrapper, + AudioManagerBase* manager); + + virtual ~AlsaPcmOutputStream(); + + // Implementation of AudioOutputStream. + virtual bool Open() OVERRIDE; + virtual void Close() OVERRIDE; + virtual void Start(AudioSourceCallback* callback) OVERRIDE; + virtual void Stop() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual void GetVolume(double* volume) OVERRIDE; + + private: + friend class AlsaPcmOutputStreamTest; + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, + AutoSelectDevice_DeviceSelect); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, + AutoSelectDevice_FallbackDevices); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Negative); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_StopStream); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Underrun); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ConstructedState); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, LatencyFloor); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, OpenClose); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmOpenFailed); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmSetParamsFailed); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ScheduleNextWrite); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, + ScheduleNextWrite_StopStream); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, StartStop); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_NormalPacket); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_StopStream); + FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_WriteFails); + + // Flags indicating the state of the stream. + enum InternalState { + kInError = 0, + kCreated, + kIsOpened, + kIsPlaying, + kIsStopped, + kIsClosed + }; + friend std::ostream& operator<<(std::ostream& os, InternalState); + + // Functions to get another packet from the data source and write it into the + // ALSA device. + void BufferPacket(bool* source_exhausted); + void WritePacket(); + void WriteTask(); + void ScheduleNextWrite(bool source_exhausted); + + // Utility functions for talking with the ALSA API. + static base::TimeDelta FramesToTimeDelta(int frames, double sample_rate); + std::string FindDeviceForChannels(uint32 channels); + snd_pcm_sframes_t GetAvailableFrames(); + snd_pcm_sframes_t GetCurrentDelay(); + + // Attempts to find the best matching linux audio device for the given number + // of channels. This function will set |device_name_| and |channel_mixer_|. + snd_pcm_t* AutoSelectDevice(uint32 latency); + + // 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(); + + // Returns true when we're on the audio thread or if the audio thread's + // message loop is NULL (which will happen during shutdown). + bool IsOnAudioThread() const; + + // 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. + int RunDataCallback(AudioBus* audio_bus, 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. + const std::string requested_device_name_; + const snd_pcm_format_t pcm_format_; + const uint32 channels_; + const ChannelLayout channel_layout_; + const uint32 sample_rate_; + const uint32 bytes_per_sample_; + const uint32 bytes_per_frame_; + + // Device configuration data. Populated after OpenTask() completes. + std::string device_name_; + uint32 packet_size_; + base::TimeDelta latency_; + uint32 bytes_per_output_frame_; + uint32 alsa_buffer_frames_; + + // Flag indicating the code should stop reading from the data source or + // writing to the ALSA device. This is set because the device has entered + // an unrecoverable error state, or the ClosedTask() has executed. + bool stop_stream_; + + // Wrapper class to invoke all the ALSA functions. + AlsaWrapper* wrapper_; + + // Audio manager that created us. Used to report that we've been closed. + AudioManagerBase* manager_; + + // Message loop to use for polling. The object is owned by the AudioManager. + // We hold a reference to the audio thread message loop since + // AudioManagerBase::ShutDown() can invalidate the message loop pointer + // before the stream gets deleted. + base::MessageLoop* message_loop_; + + // Handle to the actual PCM playback device. + snd_pcm_t* playback_handle_; + + scoped_ptr buffer_; + uint32 frames_per_packet_; + + // Allows us to run tasks on the AlsaPcmOutputStream instance which are + // bound by its lifetime. + base::WeakPtrFactory weak_factory_; + + InternalState state_; + float volume_; // Volume level from 0.0 to 1.0. + + AudioSourceCallback* source_callback_; + + // Container for retrieving data from AudioSourceCallback::OnMoreData(). + scoped_ptr audio_bus_; + + // Channel mixer and temporary bus for the final mixed channel data. + scoped_ptr channel_mixer_; + scoped_ptr mixed_audio_bus_; + + DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStream); +}; + +MEDIA_EXPORT std::ostream& operator<<(std::ostream& os, + AlsaPcmOutputStream::InternalState); + +}; // namespace media + +#endif // MEDIA_AUDIO_ALSA_ALSA_OUTPUT_H_ diff --git a/media/audio/alsa/alsa_output_unittest.cc b/media/audio/alsa/alsa_output_unittest.cc new file mode 100644 index 0000000..9d83b56 --- /dev/null +++ b/media/audio/alsa/alsa_output_unittest.cc @@ -0,0 +1,870 @@ +// Copyright 2013 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. + +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "media/audio/alsa/alsa_output.h" +#include "media/audio/alsa/alsa_wrapper.h" +#include "media/audio/alsa/audio_manager_alsa.h" +#include "media/base/data_buffer.h" +#include "media/base/seekable_buffer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::AllOf; +using testing::AtLeast; +using testing::DoAll; +using testing::Field; +using testing::InSequence; +using testing::Invoke; +using testing::InvokeWithoutArgs; +using testing::Mock; +using testing::MockFunction; +using testing::Return; +using testing::SetArgumentPointee; +using testing::StrictMock; +using testing::StrEq; +using testing::Unused; + +namespace media { + +class MockAlsaWrapper : public AlsaWrapper { + public: + MOCK_METHOD3(DeviceNameHint, int(int card, + const char* iface, + void*** hints)); + MOCK_METHOD2(DeviceNameGetHint, char*(const void* hint, const char* id)); + MOCK_METHOD1(DeviceNameFreeHint, int(void** hints)); + + MOCK_METHOD4(PcmOpen, int(snd_pcm_t** handle, const char* name, + snd_pcm_stream_t stream, int mode)); + MOCK_METHOD1(PcmClose, int(snd_pcm_t* handle)); + MOCK_METHOD1(PcmPrepare, int(snd_pcm_t* handle)); + MOCK_METHOD1(PcmDrop, int(snd_pcm_t* handle)); + MOCK_METHOD2(PcmDelay, int(snd_pcm_t* handle, snd_pcm_sframes_t* delay)); + MOCK_METHOD3(PcmWritei, snd_pcm_sframes_t(snd_pcm_t* handle, + const void* buffer, + snd_pcm_uframes_t size)); + MOCK_METHOD3(PcmReadi, snd_pcm_sframes_t(snd_pcm_t* handle, + void* buffer, + snd_pcm_uframes_t size)); + MOCK_METHOD3(PcmRecover, int(snd_pcm_t* handle, int err, int silent)); + MOCK_METHOD7(PcmSetParams, int(snd_pcm_t* handle, snd_pcm_format_t format, + snd_pcm_access_t access, unsigned int channels, + unsigned int rate, int soft_resample, + unsigned int latency)); + MOCK_METHOD3(PcmGetParams, int(snd_pcm_t* handle, + snd_pcm_uframes_t* buffer_size, + snd_pcm_uframes_t* period_size)); + MOCK_METHOD1(PcmName, const char*(snd_pcm_t* handle)); + MOCK_METHOD1(PcmAvailUpdate, snd_pcm_sframes_t(snd_pcm_t* handle)); + MOCK_METHOD1(PcmState, snd_pcm_state_t(snd_pcm_t* handle)); + MOCK_METHOD1(PcmStart, int(snd_pcm_t* handle)); + + MOCK_METHOD1(StrError, const char*(int errnum)); +}; + +class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback { + public: + MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus, + AudioBuffersState buffers_state)); + MOCK_METHOD3(OnMoreIOData, int(AudioBus* source, + AudioBus* dest, + AudioBuffersState buffers_state)); + MOCK_METHOD1(OnError, void(AudioOutputStream* stream)); +}; + +class MockAudioManagerAlsa : public AudioManagerAlsa { + public: + MOCK_METHOD0(Init, void()); + MOCK_METHOD0(HasAudioOutputDevices, bool()); + MOCK_METHOD0(HasAudioInputDevices, bool()); + MOCK_METHOD1(MakeLinearOutputStream, AudioOutputStream*( + const AudioParameters& params)); + MOCK_METHOD3(MakeLowLatencyOutputStream, AudioOutputStream*( + const AudioParameters& params, + const std::string& device_id, + const std::string& input_device_id)); + MOCK_METHOD2(MakeLowLatencyInputStream, AudioInputStream*( + const AudioParameters& params, const std::string& device_id)); + + // We need to override this function in order to skip the checking the number + // of active output streams. It is because the number of active streams + // is managed inside MakeAudioOutputStream, and we don't use + // MakeAudioOutputStream to create the stream in the tests. + virtual void ReleaseOutputStream(AudioOutputStream* stream) OVERRIDE { + DCHECK(stream); + delete stream; + } + + // We don't mock this method since all tests will do the same thing + // and use the current message loop. + virtual scoped_refptr GetMessageLoop() OVERRIDE { + return base::MessageLoop::current()->message_loop_proxy(); + } +}; + +class AlsaPcmOutputStreamTest : public testing::Test { + protected: + AlsaPcmOutputStreamTest() { + mock_manager_.reset(new StrictMock()); + } + + virtual ~AlsaPcmOutputStreamTest() { + } + + AlsaPcmOutputStream* CreateStream(ChannelLayout layout) { + return CreateStream(layout, kTestFramesPerPacket); + } + + AlsaPcmOutputStream* CreateStream(ChannelLayout layout, + int32 samples_per_packet) { + AudioParameters params(kTestFormat, layout, kTestSampleRate, + kTestBitsPerSample, samples_per_packet); + return new AlsaPcmOutputStream(kTestDeviceName, + params, + &mock_alsa_wrapper_, + mock_manager_.get()); + } + + // Helper function to malloc the string returned by DeviceNameHint for NAME. + static char* EchoHint(const void* name, Unused) { + return strdup(static_cast(name)); + } + + // Helper function to malloc the string returned by DeviceNameHint for IOID. + static char* OutputHint(Unused, Unused) { + return strdup("Output"); + } + + // Helper function to initialize |test_stream->buffer_|. Must be called + // in all tests that use buffer_ without opening the stream. + void InitBuffer(AlsaPcmOutputStream* test_stream) { + DCHECK(test_stream); + packet_ = new media::DataBuffer(kTestPacketSize); + packet_->set_data_size(kTestPacketSize); + test_stream->buffer_.reset(new media::SeekableBuffer(0, kTestPacketSize)); + test_stream->buffer_->Append(packet_.get()); + } + + static const ChannelLayout kTestChannelLayout; + static const int kTestSampleRate; + static const int kTestBitsPerSample; + static const int kTestBytesPerFrame; + static const AudioParameters::Format kTestFormat; + static const char kTestDeviceName[]; + static const char kDummyMessage[]; + static const uint32 kTestFramesPerPacket; + static const int kTestPacketSize; + static const int kTestFailedErrno; + static snd_pcm_t* const kFakeHandle; + + // Used to simulate DeviceNameHint. + static char kSurround40[]; + static char kSurround41[]; + static char kSurround50[]; + static char kSurround51[]; + static char kSurround70[]; + static char kSurround71[]; + static void* kFakeHints[]; + + StrictMock mock_alsa_wrapper_; + scoped_ptr > mock_manager_; + base::MessageLoop message_loop_; + scoped_refptr packet_; + + private: + DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStreamTest); +}; + +const ChannelLayout AlsaPcmOutputStreamTest::kTestChannelLayout = + CHANNEL_LAYOUT_STEREO; +const int AlsaPcmOutputStreamTest::kTestSampleRate = + AudioParameters::kAudioCDSampleRate; +const int AlsaPcmOutputStreamTest::kTestBitsPerSample = 8; +const int AlsaPcmOutputStreamTest::kTestBytesPerFrame = + AlsaPcmOutputStreamTest::kTestBitsPerSample / 8 * + ChannelLayoutToChannelCount(AlsaPcmOutputStreamTest::kTestChannelLayout); +const AudioParameters::Format AlsaPcmOutputStreamTest::kTestFormat = + AudioParameters::AUDIO_PCM_LINEAR; +const char AlsaPcmOutputStreamTest::kTestDeviceName[] = "TestDevice"; +const char AlsaPcmOutputStreamTest::kDummyMessage[] = "dummy"; +const uint32 AlsaPcmOutputStreamTest::kTestFramesPerPacket = 1000; +const int AlsaPcmOutputStreamTest::kTestPacketSize = + AlsaPcmOutputStreamTest::kTestFramesPerPacket * + AlsaPcmOutputStreamTest::kTestBytesPerFrame; +const int AlsaPcmOutputStreamTest::kTestFailedErrno = -EACCES; +snd_pcm_t* const AlsaPcmOutputStreamTest::kFakeHandle = + reinterpret_cast(1); + +char AlsaPcmOutputStreamTest::kSurround40[] = "surround40:CARD=foo,DEV=0"; +char AlsaPcmOutputStreamTest::kSurround41[] = "surround41:CARD=foo,DEV=0"; +char AlsaPcmOutputStreamTest::kSurround50[] = "surround50:CARD=foo,DEV=0"; +char AlsaPcmOutputStreamTest::kSurround51[] = "surround51:CARD=foo,DEV=0"; +char AlsaPcmOutputStreamTest::kSurround70[] = "surround70:CARD=foo,DEV=0"; +char AlsaPcmOutputStreamTest::kSurround71[] = "surround71:CARD=foo,DEV=0"; +void* AlsaPcmOutputStreamTest::kFakeHints[] = { + kSurround40, kSurround41, kSurround50, kSurround51, + kSurround70, kSurround71, NULL }; + +// Custom action to clear a memory buffer. +ACTION(ClearBuffer) { + arg0->Zero(); +} + +TEST_F(AlsaPcmOutputStreamTest, ConstructedState) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state()); + test_stream->Close(); + + // Should support mono. + test_stream = CreateStream(CHANNEL_LAYOUT_MONO); + EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state()); + test_stream->Close(); + + // Should support multi-channel. + test_stream = CreateStream(CHANNEL_LAYOUT_SURROUND); + EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state()); + test_stream->Close(); + + // 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_.get()); + EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state()); + test_stream->Close(); + + // 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_.get()); + EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state()); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) { + const double kMicrosPerFrame = + static_cast(1000000) / kTestSampleRate; + const double kPacketFramesInMinLatency = + AlsaPcmOutputStream::kMinLatencyMicros / kMicrosPerFrame / 2.0; + + // Test that packets which would cause a latency under less than + // AlsaPcmOutputStream::kMinLatencyMicros will get clipped to + // AlsaPcmOutputStream::kMinLatencyMicros, + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), + Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, + PcmSetParams(_, _, _, _, _, _, + AlsaPcmOutputStream::kMinLatencyMicros)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), + SetArgumentPointee<2>(kTestFramesPerPacket / 2), + Return(0))); + + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout, + kPacketFramesInMinLatency); + ASSERT_TRUE(test_stream->Open()); + + // Now close it and test that everything was released. + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)).WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) + .WillOnce(Return(kTestDeviceName)); + test_stream->Close(); + + Mock::VerifyAndClear(&mock_alsa_wrapper_); + Mock::VerifyAndClear(mock_manager_.get()); + + // Test that having more packets ends up with a latency based on packet size. + const int kOverMinLatencyPacketSize = kPacketFramesInMinLatency + 1; + int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta( + kOverMinLatencyPacketSize * 2, kTestSampleRate).InMicroseconds(); + + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, + PcmSetParams(_, _, _, _, _, _, expected_micros)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), + SetArgumentPointee<2>(kTestFramesPerPacket / 2), + Return(0))); + + test_stream = CreateStream(kTestChannelLayout, + kOverMinLatencyPacketSize); + ASSERT_TRUE(test_stream->Open()); + + // Now close it and test that everything was released. + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) + .WillOnce(Return(kTestDeviceName)); + test_stream->Close(); + + Mock::VerifyAndClear(&mock_alsa_wrapper_); + Mock::VerifyAndClear(mock_manager_.get()); +} + +TEST_F(AlsaPcmOutputStreamTest, OpenClose) { + int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta( + 2 * kTestFramesPerPacket, kTestSampleRate).InMicroseconds(); + + // Open() call opens the playback device, sets the parameters, posts a task + // with the resulting configuration data, and transitions the object state to + // kIsOpened. + EXPECT_CALL(mock_alsa_wrapper_, + PcmOpen(_, StrEq(kTestDeviceName), + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), + Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, + PcmSetParams(kFakeHandle, + SND_PCM_FORMAT_U8, + SND_PCM_ACCESS_RW_INTERLEAVED, + ChannelLayoutToChannelCount(kTestChannelLayout), + kTestSampleRate, + 1, + expected_micros)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(kFakeHandle, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), + SetArgumentPointee<2>(kTestFramesPerPacket / 2), + Return(0))); + + // Open the stream. + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + ASSERT_TRUE(test_stream->Open()); + + 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()); + EXPECT_FALSE(test_stream->stop_stream_); + + // Now close it and test that everything was released. + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) + .WillOnce(Return(kTestDeviceName)); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) { + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) + .WillOnce(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) + .WillOnce(Return(kDummyMessage)); + + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + ASSERT_FALSE(test_stream->Open()); + ASSERT_EQ(AlsaPcmOutputStream::kInError, test_stream->state()); + + // Ensure internal state is set for a no-op stream if PcmOpen() failes. + EXPECT_TRUE(test_stream->stop_stream_); + EXPECT_TRUE(test_stream->playback_handle_ == NULL); + EXPECT_FALSE(test_stream->buffer_.get()); + + // Close the stream since we opened it to make destruction happy. + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) { + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), + Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _)) + .WillOnce(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) + .WillOnce(Return(kTestDeviceName)); + EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) + .WillOnce(Return(kDummyMessage)); + + // If open fails, the stream stays in kCreated because it has effectively had + // no changes. + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + ASSERT_FALSE(test_stream->Open()); + EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state()); + + // Ensure internal state is set for a no-op stream if PcmSetParams() failes. + EXPECT_TRUE(test_stream->stop_stream_); + EXPECT_TRUE(test_stream->playback_handle_ == NULL); + EXPECT_FALSE(test_stream->buffer_.get()); + + // Close the stream since we opened it to make destruction happy. + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, StartStop) { + // Open() call opens the playback device, sets the parameters, posts a task + // with the resulting configuration data, and transitions the object state to + // kIsOpened. + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), + Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), + SetArgumentPointee<2>(kTestFramesPerPacket / 2), + Return(0))); + + // Open the stream. + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + ASSERT_TRUE(test_stream->Open()); + + // Expect Device setup. + EXPECT_CALL(mock_alsa_wrapper_, PcmDrop(kFakeHandle)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmPrepare(kFakeHandle)) + .WillOnce(Return(0)); + + // Expect the pre-roll. + MockAudioSourceCallback mock_callback; + EXPECT_CALL(mock_alsa_wrapper_, PcmState(kFakeHandle)) + .WillRepeatedly(Return(SND_PCM_STATE_RUNNING)); + EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(kFakeHandle, _)) + .WillRepeatedly(DoAll(SetArgumentPointee<1>(0), Return(0))); + EXPECT_CALL(mock_callback, OnMoreData(_, _)) + .WillRepeatedly(DoAll(ClearBuffer(), Return(kTestFramesPerPacket))); + EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _)) + .WillRepeatedly(Return(kTestFramesPerPacket)); + + // Expect scheduling. + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) + .Times(AtLeast(2)) + .WillRepeatedly(Return(kTestFramesPerPacket)); + + test_stream->Start(&mock_callback); + // Start() will issue a WriteTask() directly and then schedule the next one, + // call Stop() immediately after to ensure we don't run the message loop + // forever. + test_stream->Stop(); + message_loop_.RunUntilIdle(); + + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) + .WillOnce(Return(kTestDeviceName)); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + InitBuffer(test_stream); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); + + // Nothing should happen. Don't set any expectations and Our strict mocks + // should verify most of this. + + // Test empty buffer. + test_stream->buffer_->Clear(); + test_stream->WritePacket(); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, WritePacket_NormalPacket) { + // We need to open the stream before writing data to ALSA. + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), + Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), + SetArgumentPointee<2>(kTestFramesPerPacket / 2), + Return(0))); + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + ASSERT_TRUE(test_stream->Open()); + InitBuffer(test_stream); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); + + // Write a little less than half the data. + int written = packet_->data_size() / kTestBytesPerFrame / 2 - 1; + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) + .WillOnce(Return(written)); + EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, packet_->data(), _)) + .WillOnce(Return(written)); + + test_stream->WritePacket(); + + ASSERT_EQ(test_stream->buffer_->forward_bytes(), + packet_->data_size() - written * kTestBytesPerFrame); + + // Write the rest. + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) + .WillOnce(Return(kTestFramesPerPacket - written)); + EXPECT_CALL(mock_alsa_wrapper_, + PcmWritei(kFakeHandle, + packet_->data() + written * kTestBytesPerFrame, + _)) + .WillOnce(Return(packet_->data_size() / kTestBytesPerFrame - written)); + test_stream->WritePacket(); + EXPECT_EQ(0, test_stream->buffer_->forward_bytes()); + + // Now close it and test that everything was released. + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) + .WillOnce(Return(kTestDeviceName)); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, WritePacket_WriteFails) { + // We need to open the stream before writing data to ALSA. + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), + Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), + SetArgumentPointee<2>(kTestFramesPerPacket / 2), + Return(0))); + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + ASSERT_TRUE(test_stream->Open()); + InitBuffer(test_stream); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); + + // Fail due to a recoverable error and see that PcmRecover code path + // continues normally. + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) + .WillOnce(Return(kTestFramesPerPacket)); + EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _)) + .WillOnce(Return(-EINTR)); + EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _)) + .WillOnce(Return(0)); + + test_stream->WritePacket(); + + ASSERT_EQ(test_stream->buffer_->forward_bytes(), packet_->data_size()); + + // Fail the next write, and see that stop_stream_ is set. + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) + .WillOnce(Return(kTestFramesPerPacket)); + EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _)) + .WillOnce(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _)) + .WillOnce(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) + .WillOnce(Return(kDummyMessage)); + test_stream->WritePacket(); + EXPECT_EQ(test_stream->buffer_->forward_bytes(), packet_->data_size()); + EXPECT_TRUE(test_stream->stop_stream_); + + // Now close it and test that everything was released. + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) + .WillOnce(Return(kTestDeviceName)); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, WritePacket_StopStream) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + InitBuffer(test_stream); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); + + // No expectations set on the strict mock because nothing should be called. + test_stream->stop_stream_ = true; + test_stream->WritePacket(); + EXPECT_EQ(0, test_stream->buffer_->forward_bytes()); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, BufferPacket) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + InitBuffer(test_stream); + test_stream->buffer_->Clear(); + + MockAudioSourceCallback mock_callback; + EXPECT_CALL(mock_alsa_wrapper_, PcmState(_)) + .WillOnce(Return(SND_PCM_STATE_RUNNING)); + EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) + .WillRepeatedly(Return(0)); // Buffer is full. + + // Return a partially filled packet. + EXPECT_CALL(mock_callback, OnMoreData(_, _)) + .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2))); + + bool source_exhausted; + test_stream->set_source_callback(&mock_callback); + test_stream->packet_size_ = kTestPacketSize; + test_stream->BufferPacket(&source_exhausted); + + EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes()); + EXPECT_FALSE(source_exhausted); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Negative) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + InitBuffer(test_stream); + test_stream->buffer_->Clear(); + + // Simulate where the underrun has occurred right after checking the delay. + MockAudioSourceCallback mock_callback; + EXPECT_CALL(mock_alsa_wrapper_, PcmState(_)) + .WillOnce(Return(SND_PCM_STATE_RUNNING)); + EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(-1), Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) + .WillRepeatedly(Return(0)); // Buffer is full. + EXPECT_CALL(mock_callback, OnMoreData(_, _)) + .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2))); + + bool source_exhausted; + test_stream->set_source_callback(&mock_callback); + test_stream->packet_size_ = kTestPacketSize; + test_stream->BufferPacket(&source_exhausted); + + EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes()); + EXPECT_FALSE(source_exhausted); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Underrun) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + InitBuffer(test_stream); + test_stream->buffer_->Clear(); + + // If ALSA has underrun then we should assume a delay of zero. + MockAudioSourceCallback mock_callback; + EXPECT_CALL(mock_alsa_wrapper_, PcmState(_)) + .WillOnce(Return(SND_PCM_STATE_XRUN)); + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) + .WillRepeatedly(Return(0)); // Buffer is full. + EXPECT_CALL(mock_callback, + OnMoreData(_, AllOf( + Field(&AudioBuffersState::pending_bytes, 0), + Field(&AudioBuffersState::hardware_delay_bytes, 0)))) + .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2))); + + bool source_exhausted; + test_stream->set_source_callback(&mock_callback); + test_stream->packet_size_ = kTestPacketSize; + test_stream->BufferPacket(&source_exhausted); + + EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes()); + EXPECT_FALSE(source_exhausted); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + InitBuffer(test_stream); + // No expectations set on the strict mock because nothing should be called. + bool source_exhausted; + test_stream->packet_size_ = kTestPacketSize; + test_stream->BufferPacket(&source_exhausted); + EXPECT_EQ(kTestPacketSize, test_stream->buffer_->forward_bytes()); + EXPECT_FALSE(source_exhausted); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect) { + // Try channels from 1 -> 9. and see that we get the more specific surroundXX + // device opened for channels 4-8. For all other channels, the device should + // default to |AlsaPcmOutputStream::kDefaultDevice|. We should also not + // downmix any channel in this case because downmixing is only defined for + // channels 4-8, which we are guaranteeing to work. + // + // Note that the loop starts at "1", so the first parameter is ignored in + // these arrays. + const char* kExpectedDeviceName[] = { NULL, + AlsaPcmOutputStream::kDefaultDevice, + AlsaPcmOutputStream::kDefaultDevice, + AlsaPcmOutputStream::kDefaultDevice, + kSurround40, kSurround50, kSurround51, + kSurround70, kSurround71, + AlsaPcmOutputStream::kDefaultDevice }; + bool kExpectedDownmix[] = { false, false, false, false, false, true, + false, false, false, false }; + ChannelLayout kExpectedLayouts[] = { CHANNEL_LAYOUT_NONE, + CHANNEL_LAYOUT_MONO, + CHANNEL_LAYOUT_STEREO, + CHANNEL_LAYOUT_SURROUND, + CHANNEL_LAYOUT_4_0, + CHANNEL_LAYOUT_5_0, + CHANNEL_LAYOUT_5_1, + CHANNEL_LAYOUT_7_0, + CHANNEL_LAYOUT_7_1 }; + + + for (int i = 1; i < 9; ++i) { + if (i == 3 || i == 4 || i == 5) // invalid number of channels + continue; + SCOPED_TRACE(base::StringPrintf("Attempting %d Channel", i)); + + // Hints will only be grabbed for channel numbers that have non-default + // devices associated with them. + if (kExpectedDeviceName[i] != AlsaPcmOutputStream::kDefaultDevice) { + // The DeviceNameHint and DeviceNameFreeHint need to be paired to avoid a + // memory leak. + EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0])) + .Times(1); + } + + EXPECT_CALL(mock_alsa_wrapper_, + PcmOpen(_, StrEq(kExpectedDeviceName[i]), _, _)) + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, + PcmSetParams(kFakeHandle, _, _, i, _, _, _)) + .WillOnce(Return(0)); + + // The parameters are specified by ALSA documentation, and are in constants + // in the implementation files. + EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID"))) + .WillRepeatedly(Invoke(OutputHint)); + EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME"))) + .WillRepeatedly(Invoke(EchoHint)); + + AlsaPcmOutputStream* test_stream = CreateStream(kExpectedLayouts[i]); + EXPECT_TRUE(test_stream->AutoSelectDevice(i)); + EXPECT_EQ(kExpectedDownmix[i], + static_cast(test_stream->channel_mixer_)); + + Mock::VerifyAndClearExpectations(&mock_alsa_wrapper_); + Mock::VerifyAndClearExpectations(mock_manager_.get()); + test_stream->Close(); + } +} + +TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_FallbackDevices) { + using std::string; + + // If there are problems opening a multi-channel device, it the fallbacks + // operations should be as follows. Assume the multi-channel device name is + // surround50: + // + // 1) Try open "surround50" + // 2) Try open "plug:surround50". + // 3) Try open "default". + // 4) Try open "plug:default". + // 5) Give up trying to open. + // + const string first_try = kSurround50; + const string second_try = string(AlsaPcmOutputStream::kPlugPrefix) + + kSurround50; + const string third_try = AlsaPcmOutputStream::kDefaultDevice; + const string fourth_try = string(AlsaPcmOutputStream::kPlugPrefix) + + AlsaPcmOutputStream::kDefaultDevice; + + EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0])) + .Times(1); + EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID"))) + .WillRepeatedly(Invoke(OutputHint)); + EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME"))) + .WillRepeatedly(Invoke(EchoHint)); + EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) + .WillRepeatedly(Return(kDummyMessage)); + + InSequence s; + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(first_try.c_str()), _, _)) + .WillOnce(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(second_try.c_str()), _, _)) + .WillOnce(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(third_try.c_str()), _, _)) + .WillOnce(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(fourth_try.c_str()), _, _)) + .WillOnce(Return(kTestFailedErrno)); + + AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0); + EXPECT_FALSE(test_stream->AutoSelectDevice(5)); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail) { + // Should get |kDefaultDevice|, and force a 2-channel downmix on a failure to + // enumerate devices. + EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _)) + .WillRepeatedly(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, + PcmOpen(_, StrEq(AlsaPcmOutputStream::kDefaultDevice), _, _)) + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0))); + EXPECT_CALL(mock_alsa_wrapper_, + PcmSetParams(kFakeHandle, _, _, 2, _, _, _)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) + .WillOnce(Return(kDummyMessage)); + + AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0); + EXPECT_TRUE(test_stream->AutoSelectDevice(5)); + EXPECT_TRUE(test_stream->channel_mixer_); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + InitBuffer(test_stream); + test_stream->stop_stream_ = true; + bool source_exhausted; + test_stream->BufferPacket(&source_exhausted); + EXPECT_EQ(0, test_stream->buffer_->forward_bytes()); + EXPECT_TRUE(source_exhausted); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); + InitBuffer(test_stream); + DVLOG(1) << test_stream->state(); + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) + .WillOnce(Return(10)); + test_stream->ScheduleNextWrite(false); + DVLOG(1) << test_stream->state(); + // TODO(sergeyu): Figure out how to check that the task has been added to the + // message loop. + + // Cleanup the message queue. Currently ~MessageQueue() doesn't free pending + // tasks unless running on valgrind. The code below is needed to keep + // heapcheck happy. + + test_stream->stop_stream_ = true; + DVLOG(1) << test_stream->state(); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed); + DVLOG(1) << test_stream->state(); + test_stream->Close(); +} + +TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) { + AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened); + test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); + + InitBuffer(test_stream); + + test_stream->stop_stream_ = true; + test_stream->ScheduleNextWrite(true); + + // TODO(ajwong): Find a way to test whether or not another task has been + // posted so we can verify that the Alsa code will indeed break the task + // posting loop. + + test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed); + test_stream->Close(); +} + +} // namespace media diff --git a/media/audio/alsa/alsa_util.cc b/media/audio/alsa/alsa_util.cc new file mode 100644 index 0000000..f26cbd3 --- /dev/null +++ b/media/audio/alsa/alsa_util.cc @@ -0,0 +1,200 @@ +// Copyright 2013 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. + +#include "media/audio/alsa/alsa_util.h" + +#include + +#include "base/logging.h" +#include "media/audio/alsa/alsa_wrapper.h" + +namespace alsa_util { + +static snd_pcm_t* OpenDevice(media::AlsaWrapper* wrapper, + const char* device_name, + snd_pcm_stream_t type, + int channels, + int sample_rate, + snd_pcm_format_t pcm_format, + int latency_us) { + snd_pcm_t* handle = NULL; + int error = wrapper->PcmOpen(&handle, device_name, type, SND_PCM_NONBLOCK); + if (error < 0) { + LOG(WARNING) << "PcmOpen: " << device_name << "," + << wrapper->StrError(error); + return NULL; + } + + error = wrapper->PcmSetParams(handle, pcm_format, + SND_PCM_ACCESS_RW_INTERLEAVED, channels, + sample_rate, 1, latency_us); + if (error < 0) { + LOG(WARNING) << "PcmSetParams: " << device_name << ", " + << wrapper->StrError(error) << " - Format: " << pcm_format + << " Channels: " << channels << " Latency: " << latency_us; + if (alsa_util::CloseDevice(wrapper, handle) < 0) { + // TODO(ajwong): Retry on certain errors? + LOG(WARNING) << "Unable to close audio device. Leaking handle."; + } + return NULL; + } + + return handle; +} + +static std::string DeviceNameToControlName(const std::string& device_name) { + const char kMixerPrefix[] = "hw"; + std::string control_name; + size_t pos1 = device_name.find(':'); + if (pos1 == std::string::npos) { + control_name = device_name; + } else { + // Examples: + // deviceName: "front:CARD=Intel,DEV=0", controlName: "hw:CARD=Intel". + // deviceName: "default:CARD=Intel", controlName: "CARD=Intel". + size_t pos2 = device_name.find(','); + control_name = (pos2 == std::string::npos) ? + device_name.substr(pos1) : + kMixerPrefix + device_name.substr(pos1, pos2 - pos1); + } + + return control_name; +} + +snd_pcm_format_t BitsToFormat(int bits_per_sample) { + switch (bits_per_sample) { + case 8: + return SND_PCM_FORMAT_U8; + + case 16: + return SND_PCM_FORMAT_S16; + + case 24: + return SND_PCM_FORMAT_S24; + + case 32: + return SND_PCM_FORMAT_S32; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle) { + std::string device_name = wrapper->PcmName(handle); + int error = wrapper->PcmClose(handle); + if (error < 0) { + LOG(ERROR) << "PcmClose: " << device_name << ", " + << wrapper->StrError(error); + } + + return error; +} + +snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper, + const char* device_name, + int channels, + int sample_rate, + snd_pcm_format_t pcm_format, + int latency_us) { + return OpenDevice(wrapper, device_name, SND_PCM_STREAM_CAPTURE, channels, + sample_rate, pcm_format, latency_us); +} + +snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper, + const char* device_name, + int channels, + int sample_rate, + snd_pcm_format_t pcm_format, + int latency_us) { + return OpenDevice(wrapper, device_name, SND_PCM_STREAM_PLAYBACK, channels, + sample_rate, pcm_format, latency_us); +} + +snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper, + const std::string& device_name) { + snd_mixer_t* mixer = NULL; + + int error = wrapper->MixerOpen(&mixer, 0); + if (error < 0) { + LOG(ERROR) << "MixerOpen: " << device_name << ", " + << wrapper->StrError(error); + return NULL; + } + + std::string control_name = DeviceNameToControlName(device_name); + error = wrapper->MixerAttach(mixer, control_name.c_str()); + if (error < 0) { + LOG(ERROR) << "MixerAttach, " << control_name << ", " + << wrapper->StrError(error); + alsa_util::CloseMixer(wrapper, mixer, device_name); + return NULL; + } + + error = wrapper->MixerElementRegister(mixer, NULL, NULL); + if (error < 0) { + LOG(ERROR) << "MixerElementRegister: " << control_name << ", " + << wrapper->StrError(error); + alsa_util::CloseMixer(wrapper, mixer, device_name); + return NULL; + } + + return mixer; +} + +void CloseMixer(media::AlsaWrapper* wrapper, snd_mixer_t* mixer, + const std::string& device_name) { + if (!mixer) + return; + + wrapper->MixerFree(mixer); + + int error = 0; + if (!device_name.empty()) { + std::string control_name = DeviceNameToControlName(device_name); + error = wrapper->MixerDetach(mixer, control_name.c_str()); + if (error < 0) { + LOG(WARNING) << "MixerDetach: " << control_name << ", " + << wrapper->StrError(error); + } + } + + error = wrapper->MixerClose(mixer); + if (error < 0) { + LOG(WARNING) << "MixerClose: " << wrapper->StrError(error); + } +} + +snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper, + snd_mixer_t* mixer) { + if (!mixer) + return NULL; + + int error = wrapper->MixerLoad(mixer); + if (error < 0) { + LOG(ERROR) << "MixerLoad: " << wrapper->StrError(error); + return NULL; + } + + snd_mixer_elem_t* elem = NULL; + snd_mixer_elem_t* mic_elem = NULL; + const char kCaptureElemName[] = "Capture"; + const char kMicElemName[] = "Mic"; + for (elem = wrapper->MixerFirstElem(mixer); + elem; + elem = wrapper->MixerNextElem(elem)) { + if (wrapper->MixerSelemIsActive(elem)) { + const char* elem_name = wrapper->MixerSelemName(elem); + if (strcmp(elem_name, kCaptureElemName) == 0) + return elem; + else if (strcmp(elem_name, kMicElemName) == 0) + mic_elem = elem; + } + } + + // Did not find any Capture handle, use the Mic handle. + return mic_elem; +} + +} // namespace alsa_util diff --git a/media/audio/alsa/alsa_util.h b/media/audio/alsa/alsa_util.h new file mode 100644 index 0000000..a23ab31 --- /dev/null +++ b/media/audio/alsa/alsa_util.h @@ -0,0 +1,47 @@ +// Copyright 2013 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_ALSA_ALSA_UTIL_H_ +#define MEDIA_AUDIO_ALSA_ALSA_UTIL_H_ + +#include +#include + +namespace media { +class AlsaWrapper; +} + +namespace alsa_util { + +snd_pcm_format_t BitsToFormat(int bits_per_sample); + +snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper, + const char* device_name, + int channels, + int sample_rate, + snd_pcm_format_t pcm_format, + int latency_us); + +snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper, + const char* device_name, + int channels, + int sample_rate, + snd_pcm_format_t pcm_format, + int latency_us); + +int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle); + +snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper, + const std::string& device_name); + +void CloseMixer(media::AlsaWrapper* wrapper, + snd_mixer_t* mixer, + const std::string& device_name); + +snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper, + snd_mixer_t* mixer); + +} // namespace alsa_util + +#endif // MEDIA_AUDIO_ALSA_ALSA_UTIL_H_ diff --git a/media/audio/alsa/alsa_wrapper.cc b/media/audio/alsa/alsa_wrapper.cc new file mode 100644 index 0000000..969f3c4 --- /dev/null +++ b/media/audio/alsa/alsa_wrapper.cc @@ -0,0 +1,173 @@ +// Copyright 2013 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. + +#include "media/audio/alsa/alsa_wrapper.h" + +#include + +namespace media { + +AlsaWrapper::AlsaWrapper() { +} + +AlsaWrapper::~AlsaWrapper() { +} + +int AlsaWrapper::PcmOpen(snd_pcm_t** handle, const char* name, + snd_pcm_stream_t stream, int mode) { + return snd_pcm_open(handle, name, stream, mode); +} + +int AlsaWrapper::DeviceNameHint(int card, const char* iface, void*** hints) { + return snd_device_name_hint(card, iface, hints); +} + +char* AlsaWrapper::DeviceNameGetHint(const void* hint, const char* id) { + return snd_device_name_get_hint(hint, id); +} + +int AlsaWrapper::DeviceNameFreeHint(void** hints) { + return snd_device_name_free_hint(hints); +} + +int AlsaWrapper::CardNext(int* rcard) { + return snd_card_next(rcard); +} + +int AlsaWrapper::PcmClose(snd_pcm_t* handle) { + return snd_pcm_close(handle); +} + +int AlsaWrapper::PcmPrepare(snd_pcm_t* handle) { + return snd_pcm_prepare(handle); +} + +int AlsaWrapper::PcmDrop(snd_pcm_t* handle) { + return snd_pcm_drop(handle); +} + +int AlsaWrapper::PcmDelay(snd_pcm_t* handle, snd_pcm_sframes_t* delay) { + return snd_pcm_delay(handle, delay); +} + +snd_pcm_sframes_t AlsaWrapper::PcmWritei(snd_pcm_t* handle, + const void* buffer, + snd_pcm_uframes_t size) { + return snd_pcm_writei(handle, buffer, size); +} + +snd_pcm_sframes_t AlsaWrapper::PcmReadi(snd_pcm_t* handle, + void* buffer, + snd_pcm_uframes_t size) { + return snd_pcm_readi(handle, buffer, size); +} + +int AlsaWrapper::PcmRecover(snd_pcm_t* handle, int err, int silent) { + return snd_pcm_recover(handle, err, silent); +} + +const char* AlsaWrapper::PcmName(snd_pcm_t* handle) { + return snd_pcm_name(handle); +} + +int AlsaWrapper::PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format, + snd_pcm_access_t access, unsigned int channels, + unsigned int rate, int soft_resample, + unsigned int latency) { + return snd_pcm_set_params(handle, + format, + access, + channels, + rate, + soft_resample, + latency); +} + +int AlsaWrapper::PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size, + snd_pcm_uframes_t* period_size) { + return snd_pcm_get_params(handle, buffer_size, period_size); +} + +snd_pcm_sframes_t AlsaWrapper::PcmAvailUpdate(snd_pcm_t* handle) { + return snd_pcm_avail_update(handle); +} + +snd_pcm_state_t AlsaWrapper::PcmState(snd_pcm_t* handle) { + return snd_pcm_state(handle); +} + +const char* AlsaWrapper::StrError(int errnum) { + return snd_strerror(errnum); +} + +int AlsaWrapper::PcmStart(snd_pcm_t* handle) { + return snd_pcm_start(handle); +} + +int AlsaWrapper::MixerOpen(snd_mixer_t** mixer, int mode) { + return snd_mixer_open(mixer, mode); +} + +int AlsaWrapper::MixerAttach(snd_mixer_t* mixer, const char* name) { + return snd_mixer_attach(mixer, name); +} + +int AlsaWrapper::MixerElementRegister(snd_mixer_t* mixer, + struct snd_mixer_selem_regopt* options, + snd_mixer_class_t** classp) { + return snd_mixer_selem_register(mixer, options, classp); +} + +void AlsaWrapper::MixerFree(snd_mixer_t* mixer) { + snd_mixer_free(mixer); +} + +int AlsaWrapper::MixerDetach(snd_mixer_t* mixer, const char* name) { + return snd_mixer_detach(mixer, name); +} + +int AlsaWrapper::MixerClose(snd_mixer_t* mixer) { + return snd_mixer_close(mixer); +} + +int AlsaWrapper::MixerLoad(snd_mixer_t* mixer) { + return snd_mixer_load(mixer); +} + +snd_mixer_elem_t* AlsaWrapper::MixerFirstElem(snd_mixer_t* mixer) { + return snd_mixer_first_elem(mixer); +} + +snd_mixer_elem_t* AlsaWrapper::MixerNextElem(snd_mixer_elem_t* elem) { + return snd_mixer_elem_next(elem); +} + +int AlsaWrapper::MixerSelemIsActive(snd_mixer_elem_t* elem) { + return snd_mixer_selem_is_active(elem); +} + +const char* AlsaWrapper::MixerSelemName(snd_mixer_elem_t* elem) { + return snd_mixer_selem_get_name(elem); +} + +int AlsaWrapper::MixerSelemSetCaptureVolumeAll( + snd_mixer_elem_t* elem, long value) { + return snd_mixer_selem_set_capture_volume_all(elem, value); +} + +int AlsaWrapper::MixerSelemGetCaptureVolume( + snd_mixer_elem_t* elem, snd_mixer_selem_channel_id_t channel, long* value) { + return snd_mixer_selem_get_capture_volume(elem, channel, value); +} + +int AlsaWrapper::MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem) { + return snd_mixer_selem_has_capture_volume(elem); +} + +int AlsaWrapper::MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem, + long* min, long* max) { + return snd_mixer_selem_get_capture_volume_range(elem, min, max); +} + +} // namespace media diff --git a/media/audio/alsa/alsa_wrapper.h b/media/audio/alsa/alsa_wrapper.h new file mode 100644 index 0000000..4b3c295 --- /dev/null +++ b/media/audio/alsa/alsa_wrapper.h @@ -0,0 +1,86 @@ +// Copyright 2013 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. +// +// AlsaWrapper is a simple stateless class that wraps the alsa library commands +// we want to use. It's purpose is to allow injection of a mock so that the +// higher level code is testable. + +#ifndef MEDIA_AUDIO_ALSA_ALSA_WRAPPER_H_ +#define MEDIA_AUDIO_ALSA_ALSA_WRAPPER_H_ + +#include + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace media { + +class MEDIA_EXPORT AlsaWrapper { + public: + AlsaWrapper(); + virtual ~AlsaWrapper(); + + virtual int DeviceNameHint(int card, const char* iface, void*** hints); + virtual char* DeviceNameGetHint(const void* hint, const char* id); + virtual int DeviceNameFreeHint(void** hints); + virtual int CardNext(int* rcard); + + virtual int PcmOpen(snd_pcm_t** handle, const char* name, + snd_pcm_stream_t stream, int mode); + virtual int PcmClose(snd_pcm_t* handle); + virtual int PcmPrepare(snd_pcm_t* handle); + virtual int PcmDrop(snd_pcm_t* handle); + virtual int PcmDelay(snd_pcm_t* handle, snd_pcm_sframes_t* delay); + virtual snd_pcm_sframes_t PcmWritei(snd_pcm_t* handle, + const void* buffer, + snd_pcm_uframes_t size); + virtual snd_pcm_sframes_t PcmReadi(snd_pcm_t* handle, + void* buffer, + snd_pcm_uframes_t size); + virtual int PcmRecover(snd_pcm_t* handle, int err, int silent); + virtual int PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format, + snd_pcm_access_t access, unsigned int channels, + unsigned int rate, int soft_resample, + unsigned int latency); + virtual int PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size, + snd_pcm_uframes_t* period_size); + virtual const char* PcmName(snd_pcm_t* handle); + virtual snd_pcm_sframes_t PcmAvailUpdate(snd_pcm_t* handle); + virtual snd_pcm_state_t PcmState(snd_pcm_t* handle); + virtual int PcmStart(snd_pcm_t* handle); + + virtual int MixerOpen(snd_mixer_t** mixer, int mode); + virtual int MixerAttach(snd_mixer_t* mixer, const char* name); + virtual int MixerElementRegister(snd_mixer_t* mixer, + struct snd_mixer_selem_regopt* options, + snd_mixer_class_t** classp); + virtual void MixerFree(snd_mixer_t* mixer); + virtual int MixerDetach(snd_mixer_t* mixer, const char* name); + virtual int MixerClose(snd_mixer_t* mixer); + virtual int MixerLoad(snd_mixer_t* mixer); + virtual snd_mixer_elem_t* MixerFirstElem(snd_mixer_t* mixer); + virtual snd_mixer_elem_t* MixerNextElem(snd_mixer_elem_t* elem); + virtual int MixerSelemIsActive(snd_mixer_elem_t* elem); + virtual const char* MixerSelemName(snd_mixer_elem_t* elem); + virtual int MixerSelemSetCaptureVolumeAll(snd_mixer_elem_t* elem, long value); + virtual int MixerSelemGetCaptureVolume(snd_mixer_elem_t* elem, + snd_mixer_selem_channel_id_t channel, + long* value); + virtual int MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem); + virtual int MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem, + long* min, long* max); + + virtual const char* StrError(int errnum); + + private: + int ConfigureHwParams(snd_pcm_t* handle, snd_pcm_hw_params_t* hw_params, + snd_pcm_format_t format, snd_pcm_access_t access, + unsigned int channels, unsigned int rate, + int soft_resample, unsigned int latency); + DISALLOW_COPY_AND_ASSIGN(AlsaWrapper); +}; + +} // namespace media + +#endif // MEDIA_AUDIO_ALSA_ALSA_WRAPPER_H_ diff --git a/media/audio/alsa/audio_manager_alsa.cc b/media/audio/alsa/audio_manager_alsa.cc new file mode 100644 index 0000000..6c9696a --- /dev/null +++ b/media/audio/alsa/audio_manager_alsa.cc @@ -0,0 +1,361 @@ +// Copyright 2013 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. + +#include "media/audio/alsa/audio_manager_alsa.h" + +#include "base/command_line.h" +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/nix/xdg_util.h" +#include "base/process/launch.h" +#include "base/stl_util.h" +#include "media/audio/audio_output_dispatcher.h" +#include "media/audio/audio_parameters.h" +#if defined(USE_CRAS) +#include "media/audio/cras/audio_manager_cras.h" +#endif +#include "media/audio/alsa/alsa_input.h" +#include "media/audio/alsa/alsa_output.h" +#include "media/audio/alsa/alsa_wrapper.h" +#if defined(USE_PULSEAUDIO) +#include "media/audio/pulse/audio_manager_pulse.h" +#endif +#include "media/base/channel_layout.h" +#include "media/base/limits.h" +#include "media/base/media_switches.h" + +namespace media { + +// Maximum number of output streams that can be open simultaneously. +static const int kMaxOutputStreams = 50; + +// Default sample rate for input and output streams. +static const int kDefaultSampleRate = 48000; + +// Since "default", "pulse" and "dmix" devices are virtual devices mapped to +// real devices, we remove them from the list to avoiding duplicate counting. +// In addition, note that we support no more than 2 channels for recording, +// hence surround devices are not stored in the list. +static const char* kInvalidAudioInputDevices[] = { + "default", + "dmix", + "null", + "pulse", + "surround", +}; + +// static +void AudioManagerAlsa::ShowLinuxAudioInputSettings() { + scoped_ptr env(base::Environment::Create()); + CommandLine command_line(CommandLine::NO_PROGRAM); + switch (base::nix::GetDesktopEnvironment(env.get())) { + case base::nix::DESKTOP_ENVIRONMENT_GNOME: + command_line.SetProgram(base::FilePath("gnome-volume-control")); + break; + case base::nix::DESKTOP_ENVIRONMENT_KDE3: + case base::nix::DESKTOP_ENVIRONMENT_KDE4: + command_line.SetProgram(base::FilePath("kmix")); + break; + case base::nix::DESKTOP_ENVIRONMENT_UNITY: + command_line.SetProgram(base::FilePath("gnome-control-center")); + command_line.AppendArg("sound"); + command_line.AppendArg("input"); + break; + default: + LOG(ERROR) << "Failed to show audio input settings: we don't know " + << "what command to use for your desktop environment."; + return; + } + base::LaunchProcess(command_line, base::LaunchOptions(), NULL); +} + +// Implementation of AudioManager. +bool AudioManagerAlsa::HasAudioOutputDevices() { + return HasAnyAlsaAudioDevice(kStreamPlayback); +} + +bool AudioManagerAlsa::HasAudioInputDevices() { + return HasAnyAlsaAudioDevice(kStreamCapture); +} + +AudioManagerAlsa::AudioManagerAlsa() + : wrapper_(new AlsaWrapper()) { + SetMaxOutputStreamsAllowed(kMaxOutputStreams); +} + +AudioManagerAlsa::~AudioManagerAlsa() { + Shutdown(); +} + +void AudioManagerAlsa::ShowAudioInputSettings() { + ShowLinuxAudioInputSettings(); +} + +void AudioManagerAlsa::GetAudioInputDeviceNames( + AudioDeviceNames* device_names) { + DCHECK(device_names->empty()); + GetAlsaAudioDevices(kStreamCapture, device_names); +} + +void AudioManagerAlsa::GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) { + DCHECK(device_names->empty()); + GetAlsaAudioDevices(kStreamPlayback, device_names); +} + +AudioParameters AudioManagerAlsa::GetInputStreamParameters( + const std::string& device_id) { + static const int kDefaultInputBufferSize = 1024; + + return AudioParameters( + AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO, + kDefaultSampleRate, 16, kDefaultInputBufferSize); +} + +void AudioManagerAlsa::GetAlsaAudioDevices( + StreamType type, + media::AudioDeviceNames* device_names) { + // Constants specified by the ALSA API for device hints. + static const char kPcmInterfaceName[] = "pcm"; + int card = -1; + + // Loop through the sound cards to get ALSA device hints. + while (!wrapper_->CardNext(&card) && card >= 0) { + void** hints = NULL; + int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints); + if (!error) { + GetAlsaDevicesInfo(type, hints, device_names); + + // Destroy the hints now that we're done with it. + wrapper_->DeviceNameFreeHint(hints); + } else { + DLOG(WARNING) << "GetAlsaAudioDevices: unable to get device hints: " + << wrapper_->StrError(error); + } + } +} + +void AudioManagerAlsa::GetAlsaDevicesInfo( + AudioManagerAlsa::StreamType type, + void** hints, + media::AudioDeviceNames* device_names) { + static const char kIoHintName[] = "IOID"; + static const char kNameHintName[] = "NAME"; + static const char kDescriptionHintName[] = "DESC"; + + const char* unwanted_device_type = UnwantedDeviceTypeWhenEnumerating(type); + + for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) { + // Only examine devices of the right type. Valid values are + // "Input", "Output", and NULL which means both input and output. + scoped_ptr_malloc io(wrapper_->DeviceNameGetHint(*hint_iter, + kIoHintName)); + if (io != NULL && strcmp(unwanted_device_type, io.get()) == 0) + continue; + + // Found a device, prepend the default device since we always want + // it to be on the top of the list for all platforms. And there is + // no duplicate counting here since it is only done if the list is + // still empty. Note, pulse has exclusively opened the default + // device, so we must open the device via the "default" moniker. + if (device_names->empty()) { + device_names->push_front(media::AudioDeviceName( + AudioManagerBase::kDefaultDeviceName, + AudioManagerBase::kDefaultDeviceId)); + } + + // Get the unique device name for the device. + scoped_ptr_malloc unique_device_name( + wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName)); + + // Find out if the device is available. + if (IsAlsaDeviceAvailable(type, unique_device_name.get())) { + // Get the description for the device. + scoped_ptr_malloc desc(wrapper_->DeviceNameGetHint( + *hint_iter, kDescriptionHintName)); + + media::AudioDeviceName name; + name.unique_id = unique_device_name.get(); + if (desc) { + // Use the more user friendly description as name. + // Replace '\n' with '-'. + char* pret = strchr(desc.get(), '\n'); + if (pret) + *pret = '-'; + name.device_name = desc.get(); + } else { + // Virtual devices don't necessarily have descriptions. + // Use their names instead. + name.device_name = unique_device_name.get(); + } + + // Store the device information. + device_names->push_back(name); + } + } +} + +// static +bool AudioManagerAlsa::IsAlsaDeviceAvailable( + AudioManagerAlsa::StreamType type, + const char* device_name) { + if (!device_name) + return false; + + // We do prefix matches on the device name to see whether to include + // it or not. + if (type == kStreamCapture) { + // Check if the device is in the list of invalid devices. + for (size_t i = 0; i < arraysize(kInvalidAudioInputDevices); ++i) { + if (strncmp(kInvalidAudioInputDevices[i], device_name, + strlen(kInvalidAudioInputDevices[i])) == 0) + return false; + } + return true; + } else { + DCHECK_EQ(kStreamPlayback, type); + // We prefer the device type that maps straight to hardware but + // goes through software conversion if needed (e.g. incompatible + // sample rate). + // TODO(joi): Should we prefer "hw" instead? + static const char kDeviceTypeDesired[] = "plughw"; + return strncmp(kDeviceTypeDesired, + device_name, + arraysize(kDeviceTypeDesired) - 1) == 0; + } +} + +// static +const char* AudioManagerAlsa::UnwantedDeviceTypeWhenEnumerating( + AudioManagerAlsa::StreamType wanted_type) { + return wanted_type == kStreamPlayback ? "Input" : "Output"; +} + +bool AudioManagerAlsa::HasAnyAlsaAudioDevice( + AudioManagerAlsa::StreamType stream) { + static const char kPcmInterfaceName[] = "pcm"; + static const char kIoHintName[] = "IOID"; + void** hints = NULL; + bool has_device = false; + int card = -1; + + // Loop through the sound cards. + // Don't use snd_device_name_hint(-1,..) since there is a access violation + // inside this ALSA API with libasound.so.2.0.0. + while (!wrapper_->CardNext(&card) && (card >= 0) && !has_device) { + int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints); + if (!error) { + for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) { + // Only examine devices that are |stream| capable. Valid values are + // "Input", "Output", and NULL which means both input and output. + scoped_ptr_malloc io(wrapper_->DeviceNameGetHint(*hint_iter, + kIoHintName)); + const char* unwanted_type = UnwantedDeviceTypeWhenEnumerating(stream); + if (io != NULL && strcmp(unwanted_type, io.get()) == 0) + continue; // Wrong type, skip the device. + + // Found an input device. + has_device = true; + break; + } + + // Destroy the hints now that we're done with it. + wrapper_->DeviceNameFreeHint(hints); + hints = NULL; + } else { + DLOG(WARNING) << "HasAnyAudioDevice: unable to get device hints: " + << wrapper_->StrError(error); + } + } + + return has_device; +} + +AudioOutputStream* AudioManagerAlsa::MakeLinearOutputStream( + const AudioParameters& params) { + DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); + return MakeOutputStream(params); +} + +AudioOutputStream* AudioManagerAlsa::MakeLowLatencyOutputStream( + const AudioParameters& params, + const std::string& device_id, + const std::string& input_device_id) { + DLOG_IF(ERROR, !device_id.empty()) << "Not implemented!"; + DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); + // TODO(xians): Use input_device_id for unified IO. + return MakeOutputStream(params); +} + +AudioInputStream* AudioManagerAlsa::MakeLinearInputStream( + const AudioParameters& params, const std::string& device_id) { + DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); + return MakeInputStream(params, device_id); +} + +AudioInputStream* AudioManagerAlsa::MakeLowLatencyInputStream( + const AudioParameters& params, const std::string& device_id) { + DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); + return MakeInputStream(params, device_id); +} + +AudioParameters AudioManagerAlsa::GetPreferredOutputStreamParameters( + const std::string& output_device_id, + const AudioParameters& input_params) { + // TODO(tommi): Support |output_device_id|. + DLOG_IF(ERROR, !output_device_id.empty()) << "Not implemented!"; + static const int kDefaultOutputBufferSize = 2048; + ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO; + int sample_rate = kDefaultSampleRate; + int buffer_size = kDefaultOutputBufferSize; + int bits_per_sample = 16; + int input_channels = 0; + if (input_params.IsValid()) { + // Some clients, such as WebRTC, have a more limited use case and work + // acceptably with a smaller buffer size. The check below allows clients + // which want to try a smaller buffer size on Linux to do so. + // TODO(dalecurtis): This should include bits per channel and channel layout + // eventually. + sample_rate = input_params.sample_rate(); + bits_per_sample = input_params.bits_per_sample(); + channel_layout = input_params.channel_layout(); + input_channels = input_params.input_channels(); + buffer_size = std::min(input_params.frames_per_buffer(), buffer_size); + } + + int user_buffer_size = GetUserBufferSize(); + if (user_buffer_size) + buffer_size = user_buffer_size; + + return AudioParameters( + AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, input_channels, + sample_rate, bits_per_sample, buffer_size); +} + +AudioOutputStream* AudioManagerAlsa::MakeOutputStream( + const AudioParameters& params) { + std::string device_name = AlsaPcmOutputStream::kAutoSelectDevice; + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAlsaOutputDevice)) { + device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kAlsaOutputDevice); + } + return new AlsaPcmOutputStream(device_name, params, wrapper_.get(), this); +} + +AudioInputStream* AudioManagerAlsa::MakeInputStream( + const AudioParameters& params, const std::string& device_id) { + std::string device_name = (device_id == AudioManagerBase::kDefaultDeviceId) ? + AlsaPcmInputStream::kAutoSelectDevice : device_id; + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAlsaInputDevice)) { + device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kAlsaInputDevice); + } + + return new AlsaPcmInputStream(this, device_name, params, wrapper_.get()); +} + +} // namespace media diff --git a/media/audio/alsa/audio_manager_alsa.h b/media/audio/alsa/audio_manager_alsa.h new file mode 100644 index 0000000..c8ed7c1 --- /dev/null +++ b/media/audio/alsa/audio_manager_alsa.h @@ -0,0 +1,94 @@ +// Copyright 2013 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_ALSA_AUDIO_MANAGER_ALSA_H_ +#define MEDIA_AUDIO_ALSA_AUDIO_MANAGER_ALSA_H_ + +#include +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread.h" +#include "media/audio/audio_manager_base.h" + +namespace media { + +class AlsaWrapper; + +class MEDIA_EXPORT AudioManagerAlsa : public AudioManagerBase { + public: + AudioManagerAlsa(); + + static void ShowLinuxAudioInputSettings(); + + // Implementation of AudioManager. + virtual bool HasAudioOutputDevices() OVERRIDE; + virtual bool HasAudioInputDevices() OVERRIDE; + virtual void ShowAudioInputSettings() OVERRIDE; + virtual void GetAudioInputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; + virtual void GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; + virtual AudioParameters GetInputStreamParameters( + const std::string& device_id) OVERRIDE; + + // Implementation of AudioManagerBase. + virtual AudioOutputStream* MakeLinearOutputStream( + const AudioParameters& params) OVERRIDE; + virtual AudioOutputStream* MakeLowLatencyOutputStream( + const AudioParameters& params, + const std::string& device_id, + const std::string& input_device_id) OVERRIDE; + virtual AudioInputStream* MakeLinearInputStream( + const AudioParameters& params, const std::string& device_id) OVERRIDE; + virtual AudioInputStream* MakeLowLatencyInputStream( + const AudioParameters& params, const std::string& device_id) OVERRIDE; + + protected: + virtual ~AudioManagerAlsa(); + + virtual AudioParameters GetPreferredOutputStreamParameters( + const std::string& output_device_id, + const AudioParameters& input_params) OVERRIDE; + + private: + enum StreamType { + kStreamPlayback = 0, + kStreamCapture, + }; + + // Gets a list of available ALSA devices. + void GetAlsaAudioDevices(StreamType type, + media::AudioDeviceNames* device_names); + + // Gets the ALSA devices' names and ids that support streams of the + // given type. + void GetAlsaDevicesInfo(StreamType type, + void** hint, + media::AudioDeviceNames* device_names); + + // Checks if the specific ALSA device is available. + static bool IsAlsaDeviceAvailable(StreamType type, + const char* device_name); + + static const char* UnwantedDeviceTypeWhenEnumerating( + StreamType wanted_type); + + // Returns true if a device is present for the given stream type. + bool HasAnyAlsaAudioDevice(StreamType stream); + + // Called by MakeLinearOutputStream and MakeLowLatencyOutputStream. + AudioOutputStream* MakeOutputStream(const AudioParameters& params); + + // Called by MakeLinearInputStream and MakeLowLatencyInputStream. + AudioInputStream* MakeInputStream(const AudioParameters& params, + const std::string& device_id); + + scoped_ptr wrapper_; + + DISALLOW_COPY_AND_ASSIGN(AudioManagerAlsa); +}; + +} // namespace media + +#endif // MEDIA_AUDIO_ALSA_AUDIO_MANAGER_ALSA_H_ diff --git a/media/audio/audio_low_latency_input_output_unittest.cc b/media/audio/audio_low_latency_input_output_unittest.cc index 994353e..a187e3b 100644 --- a/media/audio/audio_low_latency_input_output_unittest.cc +++ b/media/audio/audio_low_latency_input_output_unittest.cc @@ -18,8 +18,8 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -#if defined(OS_LINUX) || defined(OS_OPENBSD) -#include "media/audio/linux/audio_manager_linux.h" +#if defined(USE_ALSA) +#include "media/audio/alsa/audio_manager_alsa.h" #elif defined(OS_MACOSX) #include "media/audio/mac/audio_manager_mac.h" #elif defined(OS_WIN) @@ -27,18 +27,22 @@ #include "media/audio/win/core_audio_util_win.h" #elif defined(OS_ANDROID) #include "media/audio/android/audio_manager_android.h" +#else +#include "media/audio/fake_audio_manager.h" #endif namespace media { -#if defined(OS_LINUX) || defined(OS_OPENBSD) -typedef AudioManagerLinux AudioManagerAnyPlatform; +#if defined(USE_ALSA) +typedef AudioManagerAlsa AudioManagerAnyPlatform; #elif defined(OS_MACOSX) typedef AudioManagerMac AudioManagerAnyPlatform; #elif defined(OS_WIN) typedef AudioManagerWin AudioManagerAnyPlatform; #elif defined(OS_ANDROID) typedef AudioManagerAndroid AudioManagerAnyPlatform; +#else +typedef FakeAudioManager AudioManagerAnyPlatform; #endif // Limits the number of delay measurements we can store in an array and diff --git a/media/audio/audio_manager_unittest.cc b/media/audio/audio_manager_unittest.cc index 4747c2e..e96cef1 100644 --- a/media/audio/audio_manager_unittest.cc +++ b/media/audio/audio_manager_unittest.cc @@ -9,9 +9,9 @@ #include "media/audio/audio_manager_base.h" #include "testing/gtest/include/gtest/gtest.h" -#if defined(OS_LINUX) -#include "media/audio/linux/audio_manager_linux.h" -#endif // defined(OS_LINUX) +#if defined(USE_ALSA) +#include "media/audio/alsa/audio_manager_alsa.h" +#endif // defined(USE_ALSA) #if defined(OS_WIN) #include "base/win/scoped_com_initializer.h" @@ -288,8 +288,8 @@ TEST_F(AudioManagerTest, EnumerateInputDevicesAlsa) { if (!CanRunInputTest()) return; - VLOG(2) << "Testing AudioManagerLinux."; - audio_manager_.reset(new AudioManagerLinux()); + VLOG(2) << "Testing AudioManagerAlsa."; + audio_manager_.reset(new AudioManagerAlsa()); AudioDeviceNames device_names; audio_manager_->GetAudioInputDeviceNames(&device_names); CheckDeviceNames(device_names); @@ -299,8 +299,8 @@ TEST_F(AudioManagerTest, EnumerateOutputDevicesAlsa) { if (!CanRunOutputTest()) return; - VLOG(2) << "Testing AudioManagerLinux."; - audio_manager_.reset(new AudioManagerLinux()); + VLOG(2) << "Testing AudioManagerAlsa."; + audio_manager_.reset(new AudioManagerAlsa()); AudioDeviceNames device_names; audio_manager_->GetAudioOutputDeviceNames(&device_names); CheckDeviceNames(device_names); diff --git a/media/audio/cras/cras_input.cc b/media/audio/cras/cras_input.cc index fd574dc..c41f364 100644 --- a/media/audio/cras/cras_input.cc +++ b/media/audio/cras/cras_input.cc @@ -10,9 +10,9 @@ #include "base/bind.h" #include "base/logging.h" #include "base/time/time.h" +#include "media/audio/alsa/alsa_util.h" #include "media/audio/audio_manager.h" #include "media/audio/cras/audio_manager_cras.h" -#include "media/audio/linux/alsa_util.h" namespace media { diff --git a/media/audio/cras/cras_unified.cc b/media/audio/cras/cras_unified.cc index 906f209..c85cf59 100644 --- a/media/audio/cras/cras_unified.cc +++ b/media/audio/cras/cras_unified.cc @@ -8,8 +8,8 @@ #include "base/command_line.h" #include "base/logging.h" +#include "media/audio/alsa/alsa_util.h" #include "media/audio/cras/audio_manager_cras.h" -#include "media/audio/linux/alsa_util.h" namespace media { diff --git a/media/audio/fake_audio_manager.cc b/media/audio/fake_audio_manager.cc new file mode 100644 index 0000000..c346289 --- /dev/null +++ b/media/audio/fake_audio_manager.cc @@ -0,0 +1,68 @@ +// Copyright 2013 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. + +#include "media/audio/fake_audio_manager.h" + +namespace media { + +FakeAudioManager::FakeAudioManager() {} + +FakeAudioManager::~FakeAudioManager() { + Shutdown(); +} + +// Implementation of AudioManager. +bool FakeAudioManager::HasAudioOutputDevices() { return false; } + +bool FakeAudioManager::HasAudioInputDevices() { return false; } + +// Implementation of AudioManagerBase. +AudioOutputStream* FakeAudioManager::MakeLinearOutputStream( + const AudioParameters& params) { + return FakeAudioOutputStream::MakeFakeStream(this, params); +} + +AudioOutputStream* FakeAudioManager::MakeLowLatencyOutputStream( + const AudioParameters& params, + const std::string& device_id, + const std::string& input_device_id) { + return FakeAudioOutputStream::MakeFakeStream(this, params); +} + +AudioInputStream* FakeAudioManager::MakeLinearInputStream( + const AudioParameters& params, + const std::string& device_id) { + return FakeAudioInputStream::MakeFakeStream(this, params); +} + +AudioInputStream* FakeAudioManager::MakeLowLatencyInputStream( + const AudioParameters& params, + const std::string& device_id) { + return FakeAudioInputStream::MakeFakeStream(this, params); +} + +AudioParameters FakeAudioManager::GetPreferredOutputStreamParameters( + const std::string& output_device_id, + const AudioParameters& input_params) { + static const int kDefaultOutputBufferSize = 2048; + static const int kDefaultSampleRate = 48000; + ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO; + int sample_rate = kDefaultSampleRate; + int buffer_size = kDefaultOutputBufferSize; + int bits_per_sample = 16; + int input_channels = 0; + if (input_params.IsValid()) { + sample_rate = input_params.sample_rate(); + bits_per_sample = input_params.bits_per_sample(); + channel_layout = input_params.channel_layout(); + input_channels = input_params.input_channels(); + buffer_size = std::min(input_params.frames_per_buffer(), buffer_size); + } + + return AudioParameters( + AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, input_channels, + sample_rate, bits_per_sample, buffer_size); +} + +} // namespace media diff --git a/media/audio/fake_audio_manager.h b/media/audio/fake_audio_manager.h new file mode 100644 index 0000000..d230739 --- /dev/null +++ b/media/audio/fake_audio_manager.h @@ -0,0 +1,51 @@ +// Copyright 2013 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_FAKE_AUDIO_MANAGER_H_ +#define MEDIA_AUDIO_FAKE_AUDIO_MANAGER_H_ + +#include +#include "base/compiler_specific.h" +#include "media/audio/audio_manager_base.h" +#include "media/audio/fake_audio_input_stream.h" +#include "media/audio/fake_audio_output_stream.h" + +namespace media { + +class MEDIA_EXPORT FakeAudioManager : public AudioManagerBase { + public: + FakeAudioManager(); + + // Implementation of AudioManager. + virtual bool HasAudioOutputDevices() OVERRIDE; + virtual bool HasAudioInputDevices() OVERRIDE; + + // Implementation of AudioManagerBase. + virtual AudioOutputStream* MakeLinearOutputStream( + const AudioParameters& params) OVERRIDE; + virtual AudioOutputStream* MakeLowLatencyOutputStream( + const AudioParameters& params, + const std::string& device_id, + const std::string& input_device_id) OVERRIDE; + virtual AudioInputStream* MakeLinearInputStream(const AudioParameters& params, + const std::string& device_id) + OVERRIDE; + virtual AudioInputStream* MakeLowLatencyInputStream( + const AudioParameters& params, + const std::string& device_id) OVERRIDE; + + protected: + virtual ~FakeAudioManager(); + + virtual AudioParameters GetPreferredOutputStreamParameters( + const std::string& output_device_id, + const AudioParameters& input_params) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(FakeAudioManager); +}; + +} // namespace media + +#endif // MEDIA_AUDIO_FAKE_AUDIO_MANAGER_H_ diff --git a/media/audio/linux/alsa_input.cc b/media/audio/linux/alsa_input.cc deleted file mode 100644 index 929cbe7..0000000 --- a/media/audio/linux/alsa_input.cc +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (c) 2012 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. - -#include "media/audio/linux/alsa_input.h" - -#include "base/basictypes.h" -#include "base/bind.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "base/time/time.h" -#include "media/audio/audio_manager.h" -#include "media/audio/linux/alsa_output.h" -#include "media/audio/linux/alsa_util.h" -#include "media/audio/linux/alsa_wrapper.h" -#include "media/audio/linux/audio_manager_linux.h" - -namespace media { - -static const int kNumPacketsInRingBuffer = 3; - -static const char kDefaultDevice1[] = "default"; -static const char kDefaultDevice2[] = "plug:default"; - -const char AlsaPcmInputStream::kAutoSelectDevice[] = ""; - -AlsaPcmInputStream::AlsaPcmInputStream(AudioManagerLinux* audio_manager, - const std::string& device_name, - const AudioParameters& params, - AlsaWrapper* wrapper) - : audio_manager_(audio_manager), - device_name_(device_name), - params_(params), - bytes_per_buffer_(params.frames_per_buffer() * - (params.channels() * params.bits_per_sample()) / 8), - wrapper_(wrapper), - buffer_duration_(base::TimeDelta::FromMicroseconds( - params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond / - static_cast(params.sample_rate()))), - callback_(NULL), - device_handle_(NULL), - mixer_handle_(NULL), - mixer_element_handle_(NULL), - weak_factory_(this), - read_callback_behind_schedule_(false) { -} - -AlsaPcmInputStream::~AlsaPcmInputStream() {} - -bool AlsaPcmInputStream::Open() { - if (device_handle_) - return false; // Already open. - - snd_pcm_format_t pcm_format = alsa_util::BitsToFormat( - params_.bits_per_sample()); - if (pcm_format == SND_PCM_FORMAT_UNKNOWN) { - LOG(WARNING) << "Unsupported bits per sample: " - << params_.bits_per_sample(); - return false; - } - - uint32 latency_us = - buffer_duration_.InMicroseconds() * kNumPacketsInRingBuffer; - - // Use the same minimum required latency as output. - latency_us = std::max(latency_us, AlsaPcmOutputStream::kMinLatencyMicros); - - if (device_name_ == kAutoSelectDevice) { - const char* device_names[] = { kDefaultDevice1, kDefaultDevice2 }; - for (size_t i = 0; i < arraysize(device_names); ++i) { - device_handle_ = alsa_util::OpenCaptureDevice( - wrapper_, device_names[i], params_.channels(), - params_.sample_rate(), pcm_format, latency_us); - - if (device_handle_) { - device_name_ = device_names[i]; - break; - } - } - } else { - device_handle_ = alsa_util::OpenCaptureDevice(wrapper_, - device_name_.c_str(), - params_.channels(), - params_.sample_rate(), - pcm_format, latency_us); - } - - if (device_handle_) { - audio_buffer_.reset(new uint8[bytes_per_buffer_]); - - // Open the microphone mixer. - mixer_handle_ = alsa_util::OpenMixer(wrapper_, device_name_); - if (mixer_handle_) { - mixer_element_handle_ = alsa_util::LoadCaptureMixerElement( - wrapper_, mixer_handle_); - } - } - - return device_handle_ != NULL; -} - -void AlsaPcmInputStream::Start(AudioInputCallback* callback) { - DCHECK(!callback_ && callback); - callback_ = callback; - StartAgc(); - int error = wrapper_->PcmPrepare(device_handle_); - if (error < 0) { - HandleError("PcmPrepare", error); - } else { - error = wrapper_->PcmStart(device_handle_); - if (error < 0) - HandleError("PcmStart", error); - } - - if (error < 0) { - callback_ = NULL; - } else { - // We start reading data half |buffer_duration_| later than when the - // buffer might have got filled, to accommodate some delays in the audio - // driver. This could also give us a smooth read sequence going forward. - base::TimeDelta delay = buffer_duration_ + buffer_duration_ / 2; - next_read_time_ = base::TimeTicks::Now() + delay; - base::MessageLoop::current()->PostDelayedTask( - FROM_HERE, - base::Bind(&AlsaPcmInputStream::ReadAudio, weak_factory_.GetWeakPtr()), - delay); - } -} - -bool AlsaPcmInputStream::Recover(int original_error) { - int error = wrapper_->PcmRecover(device_handle_, original_error, 1); - if (error < 0) { - // Docs say snd_pcm_recover returns the original error if it is not one - // of the recoverable ones, so this log message will probably contain the - // same error twice. - LOG(WARNING) << "Unable to recover from \"" - << wrapper_->StrError(original_error) << "\": " - << wrapper_->StrError(error); - return false; - } - - if (original_error == -EPIPE) { // Buffer underrun/overrun. - // For capture streams we have to repeat the explicit start() to get - // data flowing again. - error = wrapper_->PcmStart(device_handle_); - if (error < 0) { - HandleError("PcmStart", error); - return false; - } - } - - return true; -} - -snd_pcm_sframes_t AlsaPcmInputStream::GetCurrentDelay() { - snd_pcm_sframes_t delay = -1; - - int error = wrapper_->PcmDelay(device_handle_, &delay); - if (error < 0) - Recover(error); - - // snd_pcm_delay() may not work in the beginning of the stream. In this case - // return delay of data we know currently is in the ALSA's buffer. - if (delay < 0) - delay = wrapper_->PcmAvailUpdate(device_handle_); - - return delay; -} - -void AlsaPcmInputStream::ReadAudio() { - DCHECK(callback_); - - snd_pcm_sframes_t frames = wrapper_->PcmAvailUpdate(device_handle_); - if (frames < 0) { // Potentially recoverable error? - LOG(WARNING) << "PcmAvailUpdate(): " << wrapper_->StrError(frames); - Recover(frames); - } - - if (frames < params_.frames_per_buffer()) { - // Not enough data yet or error happened. In both cases wait for a very - // small duration before checking again. - // Even Though read callback was behind schedule, there is no data, so - // reset the next_read_time_. - if (read_callback_behind_schedule_) { - next_read_time_ = base::TimeTicks::Now(); - read_callback_behind_schedule_ = false; - } - - base::TimeDelta next_check_time = buffer_duration_ / 2; - base::MessageLoop::current()->PostDelayedTask( - FROM_HERE, - base::Bind(&AlsaPcmInputStream::ReadAudio, weak_factory_.GetWeakPtr()), - next_check_time); - return; - } - - int num_buffers = frames / params_.frames_per_buffer(); - uint32 hardware_delay_bytes = - static_cast(GetCurrentDelay() * params_.GetBytesPerFrame()); - double normalized_volume = 0.0; - - // Update the AGC volume level once every second. Note that, |volume| is - // also updated each time SetVolume() is called through IPC by the - // render-side AGC. - GetAgcVolume(&normalized_volume); - - while (num_buffers--) { - int frames_read = wrapper_->PcmReadi(device_handle_, audio_buffer_.get(), - params_.frames_per_buffer()); - if (frames_read == params_.frames_per_buffer()) { - callback_->OnData(this, audio_buffer_.get(), bytes_per_buffer_, - hardware_delay_bytes, normalized_volume); - } else { - LOG(WARNING) << "PcmReadi returning less than expected frames: " - << frames_read << " vs. " << params_.frames_per_buffer() - << ". Dropping this buffer."; - } - } - - next_read_time_ += buffer_duration_; - base::TimeDelta delay = next_read_time_ - base::TimeTicks::Now(); - if (delay < base::TimeDelta()) { - DVLOG(1) << "Audio read callback behind schedule by " - << (buffer_duration_ - delay).InMicroseconds() - << " (us)."; - // Read callback is behind schedule. Assuming there is data pending in - // the soundcard, invoke the read callback immediate in order to catch up. - read_callback_behind_schedule_ = true; - delay = base::TimeDelta(); - } - - base::MessageLoop::current()->PostDelayedTask( - FROM_HERE, - base::Bind(&AlsaPcmInputStream::ReadAudio, weak_factory_.GetWeakPtr()), - delay); -} - -void AlsaPcmInputStream::Stop() { - if (!device_handle_ || !callback_) - return; - - StopAgc(); - - weak_factory_.InvalidateWeakPtrs(); // Cancel the next scheduled read. - int error = wrapper_->PcmDrop(device_handle_); - if (error < 0) - HandleError("PcmDrop", error); -} - -void AlsaPcmInputStream::Close() { - if (device_handle_) { - weak_factory_.InvalidateWeakPtrs(); // Cancel the next scheduled read. - int error = alsa_util::CloseDevice(wrapper_, device_handle_); - if (error < 0) - HandleError("PcmClose", error); - - if (mixer_handle_) - alsa_util::CloseMixer(wrapper_, mixer_handle_, device_name_); - - audio_buffer_.reset(); - device_handle_ = NULL; - mixer_handle_ = NULL; - mixer_element_handle_ = NULL; - - if (callback_) - callback_->OnClose(this); - } - - audio_manager_->ReleaseInputStream(this); -} - -double AlsaPcmInputStream::GetMaxVolume() { - if (!mixer_handle_ || !mixer_element_handle_) { - DLOG(WARNING) << "GetMaxVolume is not supported for " << device_name_; - return 0.0; - } - - if (!wrapper_->MixerSelemHasCaptureVolume(mixer_element_handle_)) { - DLOG(WARNING) << "Unsupported microphone volume for " << device_name_; - return 0.0; - } - - long min = 0; - long max = 0; - if (wrapper_->MixerSelemGetCaptureVolumeRange(mixer_element_handle_, - &min, - &max)) { - DLOG(WARNING) << "Unsupported max microphone volume for " << device_name_; - return 0.0; - } - DCHECK(min == 0); - DCHECK(max > 0); - - return static_cast(max); -} - -void AlsaPcmInputStream::SetVolume(double volume) { - if (!mixer_handle_ || !mixer_element_handle_) { - DLOG(WARNING) << "SetVolume is not supported for " << device_name_; - return; - } - - int error = wrapper_->MixerSelemSetCaptureVolumeAll( - mixer_element_handle_, static_cast(volume)); - if (error < 0) { - DLOG(WARNING) << "Unable to set volume for " << device_name_; - } - - // Update the AGC volume level based on the last setting above. Note that, - // the volume-level resolution is not infinite and it is therefore not - // possible to assume that the volume provided as input parameter can be - // used directly. Instead, a new query to the audio hardware is required. - // This method does nothing if AGC is disabled. - UpdateAgcVolume(); -} - -double AlsaPcmInputStream::GetVolume() { - if (!mixer_handle_ || !mixer_element_handle_) { - DLOG(WARNING) << "GetVolume is not supported for " << device_name_; - return 0.0; - } - - long current_volume = 0; - int error = wrapper_->MixerSelemGetCaptureVolume( - mixer_element_handle_, static_cast(0), - ¤t_volume); - if (error < 0) { - DLOG(WARNING) << "Unable to get volume for " << device_name_; - return 0.0; - } - - return static_cast(current_volume); -} - -void AlsaPcmInputStream::HandleError(const char* method, int error) { - LOG(WARNING) << method << ": " << wrapper_->StrError(error); - callback_->OnError(this); -} - -} // namespace media diff --git a/media/audio/linux/alsa_input.h b/media/audio/linux/alsa_input.h deleted file mode 100644 index 888e478..0000000 --- a/media/audio/linux/alsa_input.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2012 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_ALSA_INPUT_H_ -#define MEDIA_AUDIO_LINUX_ALSA_INPUT_H_ - -#include - -#include - -#include "base/compiler_specific.h" -#include "base/memory/scoped_ptr.h" -#include "base/memory/weak_ptr.h" -#include "base/time/time.h" -#include "media/audio/agc_audio_stream.h" -#include "media/audio/audio_io.h" -#include "media/audio/audio_parameters.h" - -namespace media { - -class AlsaWrapper; -class AudioManagerLinux; - -// Provides an input stream for audio capture based on the ALSA PCM interface. -// This object is not thread safe and all methods should be invoked in the -// thread that created the object. -class AlsaPcmInputStream : public AgcAudioStream { - public: - // Pass this to the constructor if you want to attempt auto-selection - // of the audio recording device. - static const char kAutoSelectDevice[]; - - // Create a PCM Output stream for the ALSA device identified by - // |device_name|. If unsure of what to use for |device_name|, use - // |kAutoSelectDevice|. - AlsaPcmInputStream(AudioManagerLinux* audio_manager, - const std::string& device_name, - const AudioParameters& params, - AlsaWrapper* wrapper); - - virtual ~AlsaPcmInputStream(); - - // Implementation of AudioInputStream. - virtual bool Open() OVERRIDE; - virtual void Start(AudioInputCallback* callback) OVERRIDE; - virtual void Stop() OVERRIDE; - virtual void Close() OVERRIDE; - virtual double GetMaxVolume() OVERRIDE; - virtual void SetVolume(double volume) OVERRIDE; - virtual double GetVolume() OVERRIDE; - - private: - // Logs the error and invokes any registered callbacks. - void HandleError(const char* method, int error); - - // Reads one or more buffers of audio from the device, passes on to the - // registered callback and schedules the next read. - void ReadAudio(); - - // Recovers from any device errors if possible. - bool Recover(int error); - - // Utility function for talking with the ALSA API. - snd_pcm_sframes_t GetCurrentDelay(); - - // Non-refcounted pointer back to the audio manager. - // The AudioManager indirectly holds on to stream objects, so we don't - // want circular references. Additionally, stream objects live on the audio - // thread, which is owned by the audio manager and we don't want to addref - // the manager from that thread. - AudioManagerLinux* audio_manager_; - std::string device_name_; - AudioParameters params_; - int bytes_per_buffer_; - AlsaWrapper* wrapper_; - base::TimeDelta buffer_duration_; // Length of each recorded buffer. - AudioInputCallback* callback_; // Valid during a recording session. - base::TimeTicks next_read_time_; // Scheduled time for next read callback. - snd_pcm_t* device_handle_; // Handle to the ALSA PCM recording device. - snd_mixer_t* mixer_handle_; // Handle to the ALSA microphone mixer. - snd_mixer_elem_t* mixer_element_handle_; // Handle to the capture element. - base::WeakPtrFactory weak_factory_; - scoped_ptr audio_buffer_; // Buffer used for reading audio data. - bool read_callback_behind_schedule_; - - DISALLOW_COPY_AND_ASSIGN(AlsaPcmInputStream); -}; - -} // namespace media - -#endif // MEDIA_AUDIO_LINUX_ALSA_INPUT_H_ diff --git a/media/audio/linux/alsa_output.cc b/media/audio/linux/alsa_output.cc deleted file mode 100644 index fa83835..0000000 --- a/media/audio/linux/alsa_output.cc +++ /dev/null @@ -1,765 +0,0 @@ -// Copyright (c) 2012 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. -// -// THREAD SAFETY -// -// AlsaPcmOutputStream object is *not* thread-safe and should only be used -// from the audio thread. We DCHECK on this assumption whenever we can. -// -// SEMANTICS OF Close() -// -// Close() is responsible for cleaning up any resources that were acquired after -// a successful Open(). Close() will nullify any scheduled outstanding runnable -// methods. -// -// -// SEMANTICS OF ERROR STATES -// -// 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. -// -// 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, Close() must be -// called to cleanup the ALSA devices and release resources. -// -// When |stop_stream_| is set, no more commands will be made against the -// ALSA device, and playback will effectively stop. From the client's point of -// view, it will seem that the device has just clogged and stopped requesting -// data. - -#include "media/audio/linux/alsa_output.h" - -#include - -#include "base/bind.h" -#include "base/debug/trace_event.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "base/stl_util.h" -#include "base/time/time.h" -#include "media/audio/linux/alsa_util.h" -#include "media/audio/linux/alsa_wrapper.h" -#include "media/audio/linux/audio_manager_linux.h" -#include "media/base/channel_mixer.h" -#include "media/base/data_buffer.h" -#include "media/base/seekable_buffer.h" - -namespace media { - -// Set to 0 during debugging if you want error messages due to underrun -// events or other recoverable errors. -#if defined(NDEBUG) -static const int kPcmRecoverIsSilent = 1; -#else -static const int kPcmRecoverIsSilent = 0; -#endif - -// While the "default" device may support multi-channel audio, in Alsa, only -// the device names surround40, surround41, surround50, etc, have a defined -// channel mapping according to Lennart: -// -// http://0pointer.de/blog/projects/guide-to-sound-apis.html -// -// This function makes a best guess at the specific > 2 channel device name -// based on the number of channels requested. NULL is returned if no device -// can be found to match the channel numbers. In this case, using -// kDefaultDevice is probably the best bet. -// -// A five channel source is assumed to be surround50 instead of surround41 -// (which is also 5 channels). -// -// TODO(ajwong): The source data should have enough info to tell us if we want -// surround41 versus surround51, etc., instead of needing us to guess based on -// channel number. Fix API to pass that data down. -static const char* GuessSpecificDeviceName(uint32 channels) { - switch (channels) { - case 8: - return "surround71"; - - case 7: - return "surround70"; - - case 6: - return "surround51"; - - case 5: - return "surround50"; - - case 4: - return "surround40"; - - default: - return NULL; - } -} - -std::ostream& operator<<(std::ostream& os, - AlsaPcmOutputStream::InternalState state) { - switch (state) { - case AlsaPcmOutputStream::kInError: - os << "kInError"; - break; - case AlsaPcmOutputStream::kCreated: - os << "kCreated"; - break; - case AlsaPcmOutputStream::kIsOpened: - os << "kIsOpened"; - break; - case AlsaPcmOutputStream::kIsPlaying: - os << "kIsPlaying"; - break; - case AlsaPcmOutputStream::kIsStopped: - os << "kIsStopped"; - break; - case AlsaPcmOutputStream::kIsClosed: - os << "kIsClosed"; - break; - }; - return os; -} - -const char AlsaPcmOutputStream::kDefaultDevice[] = "default"; -const char AlsaPcmOutputStream::kAutoSelectDevice[] = ""; -const char AlsaPcmOutputStream::kPlugPrefix[] = "plug:"; - -// We use 40ms as our minimum required latency. If it is needed, we may be able -// to get it down to 20ms. -const uint32 AlsaPcmOutputStream::kMinLatencyMicros = 40 * 1000; - -AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name, - const AudioParameters& params, - AlsaWrapper* wrapper, - AudioManagerLinux* manager) - : requested_device_name_(device_name), - pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())), - channels_(params.channels()), - channel_layout_(params.channel_layout()), - sample_rate_(params.sample_rate()), - bytes_per_sample_(params.bits_per_sample() / 8), - bytes_per_frame_(params.GetBytesPerFrame()), - packet_size_(params.GetBytesPerBuffer()), - latency_(std::max( - base::TimeDelta::FromMicroseconds(kMinLatencyMicros), - FramesToTimeDelta(params.frames_per_buffer() * 2, sample_rate_))), - bytes_per_output_frame_(bytes_per_frame_), - alsa_buffer_frames_(0), - stop_stream_(false), - wrapper_(wrapper), - manager_(manager), - message_loop_(base::MessageLoop::current()), - playback_handle_(NULL), - frames_per_packet_(packet_size_ / bytes_per_frame_), - weak_factory_(this), - state_(kCreated), - volume_(1.0f), - source_callback_(NULL), - audio_bus_(AudioBus::Create(params)) { - DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread()); - DCHECK_EQ(audio_bus_->frames() * bytes_per_frame_, packet_size_); - - // Sanity check input values. - if (!params.IsValid()) { - LOG(WARNING) << "Unsupported audio parameters."; - TransitionTo(kInError); - } - - if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { - LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample(); - TransitionTo(kInError); - } -} - -AlsaPcmOutputStream::~AlsaPcmOutputStream() { - InternalState current_state = state(); - DCHECK(current_state == kCreated || - current_state == kIsClosed || - current_state == kInError); - DCHECK(!playback_handle_); -} - -bool AlsaPcmOutputStream::Open() { - DCHECK(IsOnAudioThread()); - - if (state() == kInError) - return false; - - if (!CanTransitionTo(kIsOpened)) { - NOTREACHED() << "Invalid state: " << state(); - return false; - } - - // We do not need to check if the transition was successful because - // 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. - TransitionTo(kIsOpened); - - // Try to open the device. - if (requested_device_name_ == kAutoSelectDevice) { - playback_handle_ = AutoSelectDevice(latency_.InMicroseconds()); - if (playback_handle_) - DVLOG(1) << "Auto-selected device: " << device_name_; - } else { - device_name_ = requested_device_name_; - playback_handle_ = alsa_util::OpenPlaybackDevice( - wrapper_, device_name_.c_str(), channels_, sample_rate_, - pcm_format_, latency_.InMicroseconds()); - } - - // Finish initializing the stream if the device was opened successfully. - if (playback_handle_ == NULL) { - stop_stream_ = true; - TransitionTo(kInError); - return false; - } else { - bytes_per_output_frame_ = channel_mixer_ ? - mixed_audio_bus_->channels() * bytes_per_sample_ : bytes_per_frame_; - uint32 output_packet_size = frames_per_packet_ * bytes_per_output_frame_; - buffer_.reset(new media::SeekableBuffer(0, output_packet_size)); - - // Get alsa buffer size. - snd_pcm_uframes_t buffer_size; - snd_pcm_uframes_t period_size; - int error = wrapper_->PcmGetParams(playback_handle_, &buffer_size, - &period_size); - if (error < 0) { - LOG(ERROR) << "Failed to get playback buffer size from ALSA: " - << wrapper_->StrError(error); - // Buffer size is at least twice of packet size. - alsa_buffer_frames_ = frames_per_packet_ * 2; - } else { - alsa_buffer_frames_ = buffer_size; - } - } - - return true; -} - -void AlsaPcmOutputStream::Close() { - DCHECK(IsOnAudioThread()); - - if (state() != kIsClosed) - TransitionTo(kIsClosed); - - // Shutdown the audio device. - if (playback_handle_) { - if (alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) { - LOG(WARNING) << "Unable to close audio device. Leaking handle."; - } - playback_handle_ = NULL; - - // Release the buffer. - buffer_.reset(); - - // Signal anything that might already be scheduled to stop. - stop_stream_ = true; // Not necessary in production, but unit tests - // uses the flag to verify that stream was closed. - } - - weak_factory_.InvalidateWeakPtrs(); - - // 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::Start(AudioSourceCallback* callback) { - DCHECK(IsOnAudioThread()); - - CHECK(callback); - - if (stop_stream_) - return; - - // Only post the task if we can enter the playing state. - if (TransitionTo(kIsPlaying) != kIsPlaying) - return; - - // Before starting, the buffer might have audio from previous user of this - // device. - buffer_->Clear(); - - // When starting again, drop all packets in the device and prepare it again - // in case we are restarting from a pause state and need to flush old data. - int error = wrapper_->PcmDrop(playback_handle_); - if (error < 0 && error != -EAGAIN) { - LOG(ERROR) << "Failure clearing playback device (" - << wrapper_->PcmName(playback_handle_) << "): " - << wrapper_->StrError(error); - stop_stream_ = true; - return; - } - - error = wrapper_->PcmPrepare(playback_handle_); - if (error < 0 && error != -EAGAIN) { - LOG(ERROR) << "Failure preparing stream (" - << wrapper_->PcmName(playback_handle_) << "): " - << wrapper_->StrError(error); - stop_stream_ = true; - return; - } - - // Ensure the first buffer is silence to avoid startup glitches. - int buffer_size = GetAvailableFrames() * bytes_per_output_frame_; - scoped_refptr silent_packet = new DataBuffer(buffer_size); - silent_packet->set_data_size(buffer_size); - memset(silent_packet->writable_data(), 0, silent_packet->data_size()); - buffer_->Append(silent_packet); - WritePacket(); - - // Start the callback chain. - set_source_callback(callback); - WriteTask(); -} - -void AlsaPcmOutputStream::Stop() { - DCHECK(IsOnAudioThread()); - - // Reset the callback, so that it is not called anymore. - set_source_callback(NULL); - weak_factory_.InvalidateWeakPtrs(); - - TransitionTo(kIsStopped); -} - -void AlsaPcmOutputStream::SetVolume(double volume) { - DCHECK(IsOnAudioThread()); - - volume_ = static_cast(volume); -} - -void AlsaPcmOutputStream::GetVolume(double* volume) { - DCHECK(IsOnAudioThread()); - - *volume = volume_; -} - -void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) { - DCHECK(IsOnAudioThread()); - - // If stopped, simulate a 0-length packet. - if (stop_stream_) { - buffer_->Clear(); - *source_exhausted = true; - return; - } - - *source_exhausted = false; - - // Request more data only when we run out of data in the buffer, because - // WritePacket() comsumes only the current chunk of data. - if (!buffer_->forward_bytes()) { - // Before making a request to source for data we need to determine the - // delay (in bytes) for the requested data to be played. - const uint32 hardware_delay = GetCurrentDelay() * bytes_per_frame_; - - scoped_refptr packet = - new media::DataBuffer(packet_size_); - int frames_filled = RunDataCallback( - audio_bus_.get(), AudioBuffersState(0, hardware_delay)); - - size_t packet_size = frames_filled * bytes_per_frame_; - DCHECK_LE(packet_size, packet_size_); - - // TODO(dalecurtis): Channel downmixing, upmixing, should be done in mixer; - // volume adjust should use SSE optimized vector_fmul() prior to interleave. - AudioBus* output_bus = audio_bus_.get(); - if (channel_mixer_) { - output_bus = mixed_audio_bus_.get(); - channel_mixer_->Transform(audio_bus_.get(), output_bus); - // Adjust packet size for downmix. - packet_size = packet_size / bytes_per_frame_ * bytes_per_output_frame_; - } - - // Note: If this ever changes to output raw float the data must be clipped - // and sanitized since it may come from an untrusted source such as NaCl. - output_bus->Scale(volume_); - output_bus->ToInterleaved( - frames_filled, bytes_per_sample_, packet->writable_data()); - - if (packet_size > 0) { - packet->set_data_size(packet_size); - // Add the packet to the buffer. - buffer_->Append(packet); - } else { - *source_exhausted = true; - } - } -} - -void AlsaPcmOutputStream::WritePacket() { - DCHECK(IsOnAudioThread()); - - // If the device is in error, just eat the bytes. - if (stop_stream_) { - buffer_->Clear(); - return; - } - - if (state() != kIsPlaying) - return; - - CHECK_EQ(buffer_->forward_bytes() % bytes_per_output_frame_, 0u); - - const uint8* buffer_data; - int buffer_size; - if (buffer_->GetCurrentChunk(&buffer_data, &buffer_size)) { - buffer_size = buffer_size - (buffer_size % bytes_per_output_frame_); - snd_pcm_sframes_t frames = std::min( - static_cast(buffer_size / bytes_per_output_frame_), - GetAvailableFrames()); - - if (!frames) - return; - - snd_pcm_sframes_t frames_written = - wrapper_->PcmWritei(playback_handle_, buffer_data, frames); - if (frames_written < 0) { - // Attempt once to immediately recover from EINTR, - // EPIPE (overrun/underrun), ESTRPIPE (stream suspended). WritePacket - // will eventually be called again, so eventual recovery will happen if - // muliple retries are required. - frames_written = wrapper_->PcmRecover(playback_handle_, - frames_written, - kPcmRecoverIsSilent); - if (frames_written < 0) { - if (frames_written != -EAGAIN) { - LOG(ERROR) << "Failed to write to pcm device: " - << wrapper_->StrError(frames_written); - RunErrorCallback(frames_written); - stop_stream_ = true; - } - } - } else { - DCHECK_EQ(frames_written, frames); - - // Seek forward in the buffer after we've written some data to ALSA. - buffer_->Seek(frames_written * bytes_per_output_frame_); - } - } else { - // If nothing left to write and playback hasn't started yet, start it now. - // This ensures that shorter sounds will still play. - if (playback_handle_ && - (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) && - GetCurrentDelay() > 0) { - wrapper_->PcmStart(playback_handle_); - } - } -} - -void AlsaPcmOutputStream::WriteTask() { - DCHECK(IsOnAudioThread()); - - if (stop_stream_) - return; - - if (state() == kIsStopped) - return; - - bool source_exhausted; - BufferPacket(&source_exhausted); - WritePacket(); - - ScheduleNextWrite(source_exhausted); -} - -void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) { - DCHECK(IsOnAudioThread()); - - if (stop_stream_ || state() != kIsPlaying) - return; - - const uint32 kTargetFramesAvailable = alsa_buffer_frames_ / 2; - uint32 available_frames = GetAvailableFrames(); - - base::TimeDelta next_fill_time; - if (buffer_->forward_bytes() && available_frames) { - // If we've got data available and ALSA has room, deliver it immediately. - next_fill_time = base::TimeDelta(); - } else if (buffer_->forward_bytes()) { - // If we've got data available and no room, poll until room is available. - // Polling in this manner allows us to ensure a more consistent callback - // schedule. In testing this yields a variance of +/- 5ms versus the non- - // polling strategy which is around +/- 30ms and bimodal. - next_fill_time = base::TimeDelta::FromMilliseconds(5); - } else if (available_frames < kTargetFramesAvailable) { - // Schedule the next write for the moment when the available buffer of the - // sound card hits |kTargetFramesAvailable|. - next_fill_time = FramesToTimeDelta( - kTargetFramesAvailable - available_frames, sample_rate_); - } else if (!source_exhausted) { - // The sound card has |kTargetFramesAvailable| or more frames available. - // Invoke the next write immediately to avoid underrun. - next_fill_time = base::TimeDelta(); - } else { - // The sound card has frames available, but our source is exhausted, so - // avoid busy looping by delaying a bit. - next_fill_time = base::TimeDelta::FromMilliseconds(10); - } - - message_loop_->PostDelayedTask(FROM_HERE, base::Bind( - &AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr()), - next_fill_time); -} - -// static -base::TimeDelta AlsaPcmOutputStream::FramesToTimeDelta(int frames, - double sample_rate) { - return base::TimeDelta::FromMicroseconds( - frames * base::Time::kMicrosecondsPerSecond / sample_rate); -} - -std::string AlsaPcmOutputStream::FindDeviceForChannels(uint32 channels) { - // Constants specified by the ALSA API for device hints. - static const int kGetAllDevices = -1; - static const char kPcmInterfaceName[] = "pcm"; - static const char kIoHintName[] = "IOID"; - static const char kNameHintName[] = "NAME"; - - const char* wanted_device = GuessSpecificDeviceName(channels); - if (!wanted_device) - return std::string(); - - std::string guessed_device; - void** hints = NULL; - int error = wrapper_->DeviceNameHint(kGetAllDevices, - kPcmInterfaceName, - &hints); - if (error == 0) { - // NOTE: Do not early return from inside this if statement. The - // hints above need to be freed. - for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) { - // Only examine devices that are output capable.. Valid values are - // "Input", "Output", and NULL which means both input and output. - scoped_ptr_malloc io( - wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName)); - if (io != NULL && strcmp(io.get(), "Input") == 0) - continue; - - // Attempt to select the closest device for number of channels. - scoped_ptr_malloc name( - wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName)); - if (strncmp(wanted_device, name.get(), strlen(wanted_device)) == 0) { - guessed_device = name.get(); - break; - } - } - - // Destroy the hint now that we're done with it. - wrapper_->DeviceNameFreeHint(hints); - hints = NULL; - } else { - LOG(ERROR) << "Unable to get hints for devices: " - << wrapper_->StrError(error); - } - - return guessed_device; -} - -snd_pcm_sframes_t AlsaPcmOutputStream::GetCurrentDelay() { - snd_pcm_sframes_t delay = -1; - // Don't query ALSA's delay if we have underrun since it'll be jammed at some - // non-zero value and potentially even negative! - // - // Also, if we're in the prepared state, don't query because that seems to - // cause an I/O error when we do query the delay. - snd_pcm_state_t pcm_state = wrapper_->PcmState(playback_handle_); - if (pcm_state != SND_PCM_STATE_XRUN && - pcm_state != SND_PCM_STATE_PREPARED) { - int error = wrapper_->PcmDelay(playback_handle_, &delay); - if (error < 0) { - // Assume a delay of zero and attempt to recover the device. - delay = -1; - error = wrapper_->PcmRecover(playback_handle_, - error, - kPcmRecoverIsSilent); - if (error < 0) { - LOG(ERROR) << "Failed querying delay: " << wrapper_->StrError(error); - } - } - } - - // snd_pcm_delay() sometimes returns crazy values. In this case return delay - // of data we know currently is in ALSA's buffer. Note: When the underlying - // driver is PulseAudio based, certain configuration settings (e.g., tsched=1) - // will generate much larger delay values than |alsa_buffer_frames_|, so only - // clip if delay is truly crazy (> 10x expected). - if (static_cast(delay) > alsa_buffer_frames_ * 10) { - delay = alsa_buffer_frames_ - GetAvailableFrames(); - } - - if (delay < 0) { - delay = 0; - } - - return delay; -} - -snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { - DCHECK(IsOnAudioThread()); - - if (stop_stream_) - return 0; - - // Find the number of frames queued in the sound device. - snd_pcm_sframes_t available_frames = - wrapper_->PcmAvailUpdate(playback_handle_); - if (available_frames < 0) { - available_frames = wrapper_->PcmRecover(playback_handle_, - available_frames, - kPcmRecoverIsSilent); - } - if (available_frames < 0) { - LOG(ERROR) << "Failed querying available frames. Assuming 0: " - << wrapper_->StrError(available_frames); - return 0; - } - if (static_cast(available_frames) > alsa_buffer_frames_ * 2) { - LOG(ERROR) << "ALSA returned " << available_frames << " of " - << alsa_buffer_frames_ << " frames available."; - return alsa_buffer_frames_; - } - - return available_frames; -} - -snd_pcm_t* AlsaPcmOutputStream::AutoSelectDevice(unsigned int latency) { - // For auto-selection: - // 1) Attempt to open a device that best matches the number of channels - // requested. - // 2) If that fails, attempt the "plug:" version of it in case ALSA can - // remap do some software conversion to make it work. - // 3) Fallback to kDefaultDevice. - // 4) If that fails too, try the "plug:" version of kDefaultDevice. - // 5) Give up. - snd_pcm_t* handle = NULL; - device_name_ = FindDeviceForChannels(channels_); - - // Step 1. - if (!device_name_.empty()) { - if ((handle = alsa_util::OpenPlaybackDevice(wrapper_, device_name_.c_str(), - channels_, sample_rate_, - pcm_format_, - latency)) != NULL) { - return handle; - } - - // Step 2. - device_name_ = kPlugPrefix + device_name_; - if ((handle = alsa_util::OpenPlaybackDevice(wrapper_, device_name_.c_str(), - channels_, sample_rate_, - pcm_format_, - latency)) != NULL) { - return handle; - } - } - - // For the kDefaultDevice device, we can only reliably depend on 2-channel - // output to have the correct ordering according to Lennart. For the channel - // formats that we know how to downmix from (3 channel to 8 channel), setup - // downmixing. - uint32 default_channels = channels_; - if (default_channels > 2) { - channel_mixer_.reset(new ChannelMixer( - channel_layout_, CHANNEL_LAYOUT_STEREO)); - default_channels = 2; - mixed_audio_bus_ = AudioBus::Create( - default_channels, audio_bus_->frames()); - } - - // Step 3. - device_name_ = kDefaultDevice; - if ((handle = alsa_util::OpenPlaybackDevice( - wrapper_, device_name_.c_str(), default_channels, sample_rate_, - pcm_format_, latency)) != NULL) { - return handle; - } - - // Step 4. - device_name_ = kPlugPrefix + device_name_; - if ((handle = alsa_util::OpenPlaybackDevice( - wrapper_, device_name_.c_str(), default_channels, sample_rate_, - pcm_format_, latency)) != NULL) { - return handle; - } - - // Unable to open any device. - device_name_.clear(); - return NULL; -} - -bool AlsaPcmOutputStream::CanTransitionTo(InternalState to) { - switch (state_) { - case kCreated: - return to == kIsOpened || to == kIsClosed || to == kInError; - - case kIsOpened: - return to == kIsPlaying || to == kIsStopped || - to == kIsClosed || to == kInError; - - case kIsPlaying: - return to == kIsPlaying || to == kIsStopped || - to == kIsClosed || to == kInError; - - case kIsStopped: - return to == kIsPlaying || to == kIsStopped || - to == kIsClosed || to == kInError; - - case kInError: - return to == kIsClosed || to == kInError; - - case kIsClosed: - default: - return false; - } -} - -AlsaPcmOutputStream::InternalState -AlsaPcmOutputStream::TransitionTo(InternalState to) { - DCHECK(IsOnAudioThread()); - - if (!CanTransitionTo(to)) { - NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to; - state_ = kInError; - } else { - state_ = to; - } - return state_; -} - -AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() { - return state_; -} - -bool AlsaPcmOutputStream::IsOnAudioThread() const { - return message_loop_ && message_loop_ == base::MessageLoop::current(); -} - -int AlsaPcmOutputStream::RunDataCallback(AudioBus* audio_bus, - AudioBuffersState buffers_state) { - TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback"); - - if (source_callback_) - return source_callback_->OnMoreData(audio_bus, buffers_state); - - return 0; -} - -void AlsaPcmOutputStream::RunErrorCallback(int code) { - if (source_callback_) - source_callback_->OnError(this); -} - -// Changes the AudioSourceCallback to proxy calls to. Pass in NULL to -// release ownership of the currently registered callback. -void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) { - DCHECK(IsOnAudioThread()); - source_callback_ = callback; -} - -} // namespace media diff --git a/media/audio/linux/alsa_output.h b/media/audio/linux/alsa_output.h deleted file mode 100644 index 841615d..0000000 --- a/media/audio/linux/alsa_output.h +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) 2012 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. -// -// On device write failure, the stream will move itself to an invalid state. -// No more data will be pulled from the data source, or written to the device. -// All calls to public API functions will either no-op themselves, or return an -// error if possible. Specifically, If the stream is in an error state, Open() -// will return false, and Start() will call OnError() immediately on the -// provided callback. -// -// If the stream is successfully opened, Close() must be called. After Close -// has been called, the object should be regarded as deleted and not touched. -// -// AlsaPcmOutputStream is a single threaded class that should only be used from -// the audio thread. When modifying the code in this class, please read the -// threading assumptions at the top of the implementation. - -#ifndef MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_ -#define MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_ - -#include - -#include - -#include "base/compiler_specific.h" -#include "base/gtest_prod_util.h" -#include "base/memory/scoped_ptr.h" -#include "base/memory/weak_ptr.h" -#include "base/time/time.h" -#include "media/audio/audio_io.h" -#include "media/audio/audio_parameters.h" - -namespace base { -class MessageLoop; -} - -namespace media { - -class AlsaWrapper; -class AudioManagerLinux; -class ChannelMixer; -class SeekableBuffer; - -class MEDIA_EXPORT AlsaPcmOutputStream : public AudioOutputStream { - public: - // String for the generic "default" ALSA device that has the highest - // compatibility and chance of working. - static const char kDefaultDevice[]; - - // Pass this to the AlsaPcmOutputStream if you want to attempt auto-selection - // of the audio device. - static const char kAutoSelectDevice[]; - - // Prefix for device names to enable ALSA library resampling. - static const char kPlugPrefix[]; - - // The minimum latency that is accepted by the device. - static const uint32 kMinLatencyMicros; - - // Create a PCM Output stream for the ALSA device identified by - // |device_name|. The AlsaPcmOutputStream uses |wrapper| to communicate with - // the alsa libraries, allowing for dependency injection during testing. All - // requesting of data, and writing to the alsa device will be done on - // |message_loop|. - // - // If unsure of what to use for |device_name|, use |kAutoSelectDevice|. - AlsaPcmOutputStream(const std::string& device_name, - const AudioParameters& params, - AlsaWrapper* wrapper, - AudioManagerLinux* manager); - - virtual ~AlsaPcmOutputStream(); - - // Implementation of AudioOutputStream. - virtual bool Open() OVERRIDE; - virtual void Close() OVERRIDE; - virtual void Start(AudioSourceCallback* callback) OVERRIDE; - virtual void Stop() OVERRIDE; - virtual void SetVolume(double volume) OVERRIDE; - virtual void GetVolume(double* volume) OVERRIDE; - - private: - friend class AlsaPcmOutputStreamTest; - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, - AutoSelectDevice_DeviceSelect); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, - AutoSelectDevice_FallbackDevices); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Negative); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_StopStream); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Underrun); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ConstructedState); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, LatencyFloor); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, OpenClose); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmOpenFailed); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmSetParamsFailed); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ScheduleNextWrite); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, - ScheduleNextWrite_StopStream); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, StartStop); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_NormalPacket); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_StopStream); - FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_WriteFails); - - // Flags indicating the state of the stream. - enum InternalState { - kInError = 0, - kCreated, - kIsOpened, - kIsPlaying, - kIsStopped, - kIsClosed - }; - friend std::ostream& operator<<(std::ostream& os, InternalState); - - // Functions to get another packet from the data source and write it into the - // ALSA device. - void BufferPacket(bool* source_exhausted); - void WritePacket(); - void WriteTask(); - void ScheduleNextWrite(bool source_exhausted); - - // Utility functions for talking with the ALSA API. - static base::TimeDelta FramesToTimeDelta(int frames, double sample_rate); - std::string FindDeviceForChannels(uint32 channels); - snd_pcm_sframes_t GetAvailableFrames(); - snd_pcm_sframes_t GetCurrentDelay(); - - // Attempts to find the best matching linux audio device for the given number - // of channels. This function will set |device_name_| and |channel_mixer_|. - snd_pcm_t* AutoSelectDevice(uint32 latency); - - // 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(); - - // Returns true when we're on the audio thread or if the audio thread's - // message loop is NULL (which will happen during shutdown). - bool IsOnAudioThread() const; - - // 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. - int RunDataCallback(AudioBus* audio_bus, 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. - const std::string requested_device_name_; - const snd_pcm_format_t pcm_format_; - const uint32 channels_; - const ChannelLayout channel_layout_; - const uint32 sample_rate_; - const uint32 bytes_per_sample_; - const uint32 bytes_per_frame_; - - // Device configuration data. Populated after OpenTask() completes. - std::string device_name_; - uint32 packet_size_; - base::TimeDelta latency_; - uint32 bytes_per_output_frame_; - uint32 alsa_buffer_frames_; - - // Flag indicating the code should stop reading from the data source or - // writing to the ALSA device. This is set because the device has entered - // an unrecoverable error state, or the ClosedTask() has executed. - bool stop_stream_; - - // Wrapper class to invoke all the ALSA functions. - AlsaWrapper* wrapper_; - - // Audio manager that created us. Used to report that we've been closed. - AudioManagerLinux* manager_; - - // Message loop to use for polling. The object is owned by the AudioManager. - // We hold a reference to the audio thread message loop since - // AudioManagerBase::ShutDown() can invalidate the message loop pointer - // before the stream gets deleted. - base::MessageLoop* message_loop_; - - // Handle to the actual PCM playback device. - snd_pcm_t* playback_handle_; - - scoped_ptr buffer_; - uint32 frames_per_packet_; - - // Allows us to run tasks on the AlsaPcmOutputStream instance which are - // bound by its lifetime. - base::WeakPtrFactory weak_factory_; - - InternalState state_; - float volume_; // Volume level from 0.0 to 1.0. - - AudioSourceCallback* source_callback_; - - // Container for retrieving data from AudioSourceCallback::OnMoreData(). - scoped_ptr audio_bus_; - - // Channel mixer and temporary bus for the final mixed channel data. - scoped_ptr channel_mixer_; - scoped_ptr mixed_audio_bus_; - - DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStream); -}; - -MEDIA_EXPORT std::ostream& operator<<(std::ostream& os, - AlsaPcmOutputStream::InternalState); - -}; // namespace media - -#endif // MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_ diff --git a/media/audio/linux/alsa_output_unittest.cc b/media/audio/linux/alsa_output_unittest.cc deleted file mode 100644 index 82fbab9..0000000 --- a/media/audio/linux/alsa_output_unittest.cc +++ /dev/null @@ -1,870 +0,0 @@ -// Copyright (c) 2012 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. - -#include "base/message_loop/message_loop.h" -#include "base/strings/stringprintf.h" -#include "media/audio/linux/alsa_output.h" -#include "media/audio/linux/alsa_wrapper.h" -#include "media/audio/linux/audio_manager_linux.h" -#include "media/base/data_buffer.h" -#include "media/base/seekable_buffer.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -using testing::_; -using testing::AllOf; -using testing::AtLeast; -using testing::DoAll; -using testing::Field; -using testing::InSequence; -using testing::Invoke; -using testing::InvokeWithoutArgs; -using testing::Mock; -using testing::MockFunction; -using testing::Return; -using testing::SetArgumentPointee; -using testing::StrictMock; -using testing::StrEq; -using testing::Unused; - -namespace media { - -class MockAlsaWrapper : public AlsaWrapper { - public: - MOCK_METHOD3(DeviceNameHint, int(int card, - const char* iface, - void*** hints)); - MOCK_METHOD2(DeviceNameGetHint, char*(const void* hint, const char* id)); - MOCK_METHOD1(DeviceNameFreeHint, int(void** hints)); - - MOCK_METHOD4(PcmOpen, int(snd_pcm_t** handle, const char* name, - snd_pcm_stream_t stream, int mode)); - MOCK_METHOD1(PcmClose, int(snd_pcm_t* handle)); - MOCK_METHOD1(PcmPrepare, int(snd_pcm_t* handle)); - MOCK_METHOD1(PcmDrop, int(snd_pcm_t* handle)); - MOCK_METHOD2(PcmDelay, int(snd_pcm_t* handle, snd_pcm_sframes_t* delay)); - MOCK_METHOD3(PcmWritei, snd_pcm_sframes_t(snd_pcm_t* handle, - const void* buffer, - snd_pcm_uframes_t size)); - MOCK_METHOD3(PcmReadi, snd_pcm_sframes_t(snd_pcm_t* handle, - void* buffer, - snd_pcm_uframes_t size)); - MOCK_METHOD3(PcmRecover, int(snd_pcm_t* handle, int err, int silent)); - MOCK_METHOD7(PcmSetParams, int(snd_pcm_t* handle, snd_pcm_format_t format, - snd_pcm_access_t access, unsigned int channels, - unsigned int rate, int soft_resample, - unsigned int latency)); - MOCK_METHOD3(PcmGetParams, int(snd_pcm_t* handle, - snd_pcm_uframes_t* buffer_size, - snd_pcm_uframes_t* period_size)); - MOCK_METHOD1(PcmName, const char*(snd_pcm_t* handle)); - MOCK_METHOD1(PcmAvailUpdate, snd_pcm_sframes_t(snd_pcm_t* handle)); - MOCK_METHOD1(PcmState, snd_pcm_state_t(snd_pcm_t* handle)); - MOCK_METHOD1(PcmStart, int(snd_pcm_t* handle)); - - MOCK_METHOD1(StrError, const char*(int errnum)); -}; - -class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback { - public: - MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus, - AudioBuffersState buffers_state)); - MOCK_METHOD3(OnMoreIOData, int(AudioBus* source, - AudioBus* dest, - AudioBuffersState buffers_state)); - MOCK_METHOD1(OnError, void(AudioOutputStream* stream)); -}; - -class MockAudioManagerLinux : public AudioManagerLinux { - public: - MOCK_METHOD0(Init, void()); - MOCK_METHOD0(HasAudioOutputDevices, bool()); - MOCK_METHOD0(HasAudioInputDevices, bool()); - MOCK_METHOD1(MakeLinearOutputStream, AudioOutputStream*( - const AudioParameters& params)); - MOCK_METHOD3(MakeLowLatencyOutputStream, AudioOutputStream*( - const AudioParameters& params, - const std::string& device_id, - const std::string& input_device_id)); - MOCK_METHOD2(MakeLowLatencyInputStream, AudioInputStream*( - const AudioParameters& params, const std::string& device_id)); - - // We need to override this function in order to skip the checking the number - // of active output streams. It is because the number of active streams - // is managed inside MakeAudioOutputStream, and we don't use - // MakeAudioOutputStream to create the stream in the tests. - virtual void ReleaseOutputStream(AudioOutputStream* stream) OVERRIDE { - DCHECK(stream); - delete stream; - } - - // We don't mock this method since all tests will do the same thing - // and use the current message loop. - virtual scoped_refptr GetMessageLoop() OVERRIDE { - return base::MessageLoop::current()->message_loop_proxy(); - } -}; - -class AlsaPcmOutputStreamTest : public testing::Test { - protected: - AlsaPcmOutputStreamTest() { - mock_manager_.reset(new StrictMock()); - } - - virtual ~AlsaPcmOutputStreamTest() { - } - - AlsaPcmOutputStream* CreateStream(ChannelLayout layout) { - return CreateStream(layout, kTestFramesPerPacket); - } - - AlsaPcmOutputStream* CreateStream(ChannelLayout layout, - int32 samples_per_packet) { - AudioParameters params(kTestFormat, layout, kTestSampleRate, - kTestBitsPerSample, samples_per_packet); - return new AlsaPcmOutputStream(kTestDeviceName, - params, - &mock_alsa_wrapper_, - mock_manager_.get()); - } - - // Helper function to malloc the string returned by DeviceNameHint for NAME. - static char* EchoHint(const void* name, Unused) { - return strdup(static_cast(name)); - } - - // Helper function to malloc the string returned by DeviceNameHint for IOID. - static char* OutputHint(Unused, Unused) { - return strdup("Output"); - } - - // Helper function to initialize |test_stream->buffer_|. Must be called - // in all tests that use buffer_ without opening the stream. - void InitBuffer(AlsaPcmOutputStream* test_stream) { - DCHECK(test_stream); - packet_ = new media::DataBuffer(kTestPacketSize); - packet_->set_data_size(kTestPacketSize); - test_stream->buffer_.reset(new media::SeekableBuffer(0, kTestPacketSize)); - test_stream->buffer_->Append(packet_.get()); - } - - static const ChannelLayout kTestChannelLayout; - static const int kTestSampleRate; - static const int kTestBitsPerSample; - static const int kTestBytesPerFrame; - static const AudioParameters::Format kTestFormat; - static const char kTestDeviceName[]; - static const char kDummyMessage[]; - static const uint32 kTestFramesPerPacket; - static const int kTestPacketSize; - static const int kTestFailedErrno; - static snd_pcm_t* const kFakeHandle; - - // Used to simulate DeviceNameHint. - static char kSurround40[]; - static char kSurround41[]; - static char kSurround50[]; - static char kSurround51[]; - static char kSurround70[]; - static char kSurround71[]; - static void* kFakeHints[]; - - StrictMock mock_alsa_wrapper_; - scoped_ptr > mock_manager_; - base::MessageLoop message_loop_; - scoped_refptr packet_; - - private: - DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStreamTest); -}; - -const ChannelLayout AlsaPcmOutputStreamTest::kTestChannelLayout = - CHANNEL_LAYOUT_STEREO; -const int AlsaPcmOutputStreamTest::kTestSampleRate = - AudioParameters::kAudioCDSampleRate; -const int AlsaPcmOutputStreamTest::kTestBitsPerSample = 8; -const int AlsaPcmOutputStreamTest::kTestBytesPerFrame = - AlsaPcmOutputStreamTest::kTestBitsPerSample / 8 * - ChannelLayoutToChannelCount(AlsaPcmOutputStreamTest::kTestChannelLayout); -const AudioParameters::Format AlsaPcmOutputStreamTest::kTestFormat = - AudioParameters::AUDIO_PCM_LINEAR; -const char AlsaPcmOutputStreamTest::kTestDeviceName[] = "TestDevice"; -const char AlsaPcmOutputStreamTest::kDummyMessage[] = "dummy"; -const uint32 AlsaPcmOutputStreamTest::kTestFramesPerPacket = 1000; -const int AlsaPcmOutputStreamTest::kTestPacketSize = - AlsaPcmOutputStreamTest::kTestFramesPerPacket * - AlsaPcmOutputStreamTest::kTestBytesPerFrame; -const int AlsaPcmOutputStreamTest::kTestFailedErrno = -EACCES; -snd_pcm_t* const AlsaPcmOutputStreamTest::kFakeHandle = - reinterpret_cast(1); - -char AlsaPcmOutputStreamTest::kSurround40[] = "surround40:CARD=foo,DEV=0"; -char AlsaPcmOutputStreamTest::kSurround41[] = "surround41:CARD=foo,DEV=0"; -char AlsaPcmOutputStreamTest::kSurround50[] = "surround50:CARD=foo,DEV=0"; -char AlsaPcmOutputStreamTest::kSurround51[] = "surround51:CARD=foo,DEV=0"; -char AlsaPcmOutputStreamTest::kSurround70[] = "surround70:CARD=foo,DEV=0"; -char AlsaPcmOutputStreamTest::kSurround71[] = "surround71:CARD=foo,DEV=0"; -void* AlsaPcmOutputStreamTest::kFakeHints[] = { - kSurround40, kSurround41, kSurround50, kSurround51, - kSurround70, kSurround71, NULL }; - -// Custom action to clear a memory buffer. -ACTION(ClearBuffer) { - arg0->Zero(); -} - -TEST_F(AlsaPcmOutputStreamTest, ConstructedState) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state()); - test_stream->Close(); - - // Should support mono. - test_stream = CreateStream(CHANNEL_LAYOUT_MONO); - EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state()); - test_stream->Close(); - - // Should support multi-channel. - test_stream = CreateStream(CHANNEL_LAYOUT_SURROUND); - EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state()); - test_stream->Close(); - - // 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_.get()); - EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state()); - test_stream->Close(); - - // 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_.get()); - EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state()); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) { - const double kMicrosPerFrame = - static_cast(1000000) / kTestSampleRate; - const double kPacketFramesInMinLatency = - AlsaPcmOutputStream::kMinLatencyMicros / kMicrosPerFrame / 2.0; - - // Test that packets which would cause a latency under less than - // AlsaPcmOutputStream::kMinLatencyMicros will get clipped to - // AlsaPcmOutputStream::kMinLatencyMicros, - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) - .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), - Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, - PcmSetParams(_, _, _, _, _, _, - AlsaPcmOutputStream::kMinLatencyMicros)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) - .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), - SetArgumentPointee<2>(kTestFramesPerPacket / 2), - Return(0))); - - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout, - kPacketFramesInMinLatency); - ASSERT_TRUE(test_stream->Open()); - - // Now close it and test that everything was released. - EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)).WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) - .WillOnce(Return(kTestDeviceName)); - test_stream->Close(); - - Mock::VerifyAndClear(&mock_alsa_wrapper_); - Mock::VerifyAndClear(mock_manager_.get()); - - // Test that having more packets ends up with a latency based on packet size. - const int kOverMinLatencyPacketSize = kPacketFramesInMinLatency + 1; - int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta( - kOverMinLatencyPacketSize * 2, kTestSampleRate).InMicroseconds(); - - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) - .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, - PcmSetParams(_, _, _, _, _, _, expected_micros)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) - .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), - SetArgumentPointee<2>(kTestFramesPerPacket / 2), - Return(0))); - - test_stream = CreateStream(kTestChannelLayout, - kOverMinLatencyPacketSize); - ASSERT_TRUE(test_stream->Open()); - - // Now close it and test that everything was released. - EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) - .WillOnce(Return(kTestDeviceName)); - test_stream->Close(); - - Mock::VerifyAndClear(&mock_alsa_wrapper_); - Mock::VerifyAndClear(mock_manager_.get()); -} - -TEST_F(AlsaPcmOutputStreamTest, OpenClose) { - int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta( - 2 * kTestFramesPerPacket, kTestSampleRate).InMicroseconds(); - - // Open() call opens the playback device, sets the parameters, posts a task - // with the resulting configuration data, and transitions the object state to - // kIsOpened. - EXPECT_CALL(mock_alsa_wrapper_, - PcmOpen(_, StrEq(kTestDeviceName), - SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) - .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), - Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, - PcmSetParams(kFakeHandle, - SND_PCM_FORMAT_U8, - SND_PCM_ACCESS_RW_INTERLEAVED, - ChannelLayoutToChannelCount(kTestChannelLayout), - kTestSampleRate, - 1, - expected_micros)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(kFakeHandle, _, _)) - .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), - SetArgumentPointee<2>(kTestFramesPerPacket / 2), - Return(0))); - - // Open the stream. - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - ASSERT_TRUE(test_stream->Open()); - - 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()); - EXPECT_FALSE(test_stream->stop_stream_); - - // Now close it and test that everything was released. - EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) - .WillOnce(Return(kTestDeviceName)); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) { - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) - .WillOnce(Return(kTestFailedErrno)); - EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) - .WillOnce(Return(kDummyMessage)); - - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - ASSERT_FALSE(test_stream->Open()); - ASSERT_EQ(AlsaPcmOutputStream::kInError, test_stream->state()); - - // Ensure internal state is set for a no-op stream if PcmOpen() failes. - EXPECT_TRUE(test_stream->stop_stream_); - EXPECT_TRUE(test_stream->playback_handle_ == NULL); - EXPECT_FALSE(test_stream->buffer_.get()); - - // Close the stream since we opened it to make destruction happy. - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) { - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) - .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), - Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _)) - .WillOnce(Return(kTestFailedErrno)); - EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) - .WillOnce(Return(kTestDeviceName)); - EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) - .WillOnce(Return(kDummyMessage)); - - // If open fails, the stream stays in kCreated because it has effectively had - // no changes. - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - ASSERT_FALSE(test_stream->Open()); - EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state()); - - // Ensure internal state is set for a no-op stream if PcmSetParams() failes. - EXPECT_TRUE(test_stream->stop_stream_); - EXPECT_TRUE(test_stream->playback_handle_ == NULL); - EXPECT_FALSE(test_stream->buffer_.get()); - - // Close the stream since we opened it to make destruction happy. - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, StartStop) { - // Open() call opens the playback device, sets the parameters, posts a task - // with the resulting configuration data, and transitions the object state to - // kIsOpened. - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) - .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), - Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) - .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), - SetArgumentPointee<2>(kTestFramesPerPacket / 2), - Return(0))); - - // Open the stream. - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - ASSERT_TRUE(test_stream->Open()); - - // Expect Device setup. - EXPECT_CALL(mock_alsa_wrapper_, PcmDrop(kFakeHandle)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmPrepare(kFakeHandle)) - .WillOnce(Return(0)); - - // Expect the pre-roll. - MockAudioSourceCallback mock_callback; - EXPECT_CALL(mock_alsa_wrapper_, PcmState(kFakeHandle)) - .WillRepeatedly(Return(SND_PCM_STATE_RUNNING)); - EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(kFakeHandle, _)) - .WillRepeatedly(DoAll(SetArgumentPointee<1>(0), Return(0))); - EXPECT_CALL(mock_callback, OnMoreData(_, _)) - .WillRepeatedly(DoAll(ClearBuffer(), Return(kTestFramesPerPacket))); - EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _)) - .WillRepeatedly(Return(kTestFramesPerPacket)); - - // Expect scheduling. - EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) - .Times(AtLeast(2)) - .WillRepeatedly(Return(kTestFramesPerPacket)); - - test_stream->Start(&mock_callback); - // Start() will issue a WriteTask() directly and then schedule the next one, - // call Stop() immediately after to ensure we don't run the message loop - // forever. - test_stream->Stop(); - message_loop_.RunUntilIdle(); - - EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) - .WillOnce(Return(kTestDeviceName)); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - InitBuffer(test_stream); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); - - // Nothing should happen. Don't set any expectations and Our strict mocks - // should verify most of this. - - // Test empty buffer. - test_stream->buffer_->Clear(); - test_stream->WritePacket(); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, WritePacket_NormalPacket) { - // We need to open the stream before writing data to ALSA. - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) - .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), - Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) - .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), - SetArgumentPointee<2>(kTestFramesPerPacket / 2), - Return(0))); - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - ASSERT_TRUE(test_stream->Open()); - InitBuffer(test_stream); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); - - // Write a little less than half the data. - int written = packet_->data_size() / kTestBytesPerFrame / 2 - 1; - EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) - .WillOnce(Return(written)); - EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, packet_->data(), _)) - .WillOnce(Return(written)); - - test_stream->WritePacket(); - - ASSERT_EQ(test_stream->buffer_->forward_bytes(), - packet_->data_size() - written * kTestBytesPerFrame); - - // Write the rest. - EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) - .WillOnce(Return(kTestFramesPerPacket - written)); - EXPECT_CALL(mock_alsa_wrapper_, - PcmWritei(kFakeHandle, - packet_->data() + written * kTestBytesPerFrame, - _)) - .WillOnce(Return(packet_->data_size() / kTestBytesPerFrame - written)); - test_stream->WritePacket(); - EXPECT_EQ(0, test_stream->buffer_->forward_bytes()); - - // Now close it and test that everything was released. - EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) - .WillOnce(Return(kTestDeviceName)); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, WritePacket_WriteFails) { - // We need to open the stream before writing data to ALSA. - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) - .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), - Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) - .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket), - SetArgumentPointee<2>(kTestFramesPerPacket / 2), - Return(0))); - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - ASSERT_TRUE(test_stream->Open()); - InitBuffer(test_stream); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); - - // Fail due to a recoverable error and see that PcmRecover code path - // continues normally. - EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) - .WillOnce(Return(kTestFramesPerPacket)); - EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _)) - .WillOnce(Return(-EINTR)); - EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _)) - .WillOnce(Return(0)); - - test_stream->WritePacket(); - - ASSERT_EQ(test_stream->buffer_->forward_bytes(), packet_->data_size()); - - // Fail the next write, and see that stop_stream_ is set. - EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) - .WillOnce(Return(kTestFramesPerPacket)); - EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _)) - .WillOnce(Return(kTestFailedErrno)); - EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _)) - .WillOnce(Return(kTestFailedErrno)); - EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) - .WillOnce(Return(kDummyMessage)); - test_stream->WritePacket(); - EXPECT_EQ(test_stream->buffer_->forward_bytes(), packet_->data_size()); - EXPECT_TRUE(test_stream->stop_stream_); - - // Now close it and test that everything was released. - EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle)) - .WillOnce(Return(kTestDeviceName)); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, WritePacket_StopStream) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - InitBuffer(test_stream); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); - - // No expectations set on the strict mock because nothing should be called. - test_stream->stop_stream_ = true; - test_stream->WritePacket(); - EXPECT_EQ(0, test_stream->buffer_->forward_bytes()); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, BufferPacket) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - InitBuffer(test_stream); - test_stream->buffer_->Clear(); - - MockAudioSourceCallback mock_callback; - EXPECT_CALL(mock_alsa_wrapper_, PcmState(_)) - .WillOnce(Return(SND_PCM_STATE_RUNNING)); - EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _)) - .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) - .WillRepeatedly(Return(0)); // Buffer is full. - - // Return a partially filled packet. - EXPECT_CALL(mock_callback, OnMoreData(_, _)) - .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2))); - - bool source_exhausted; - test_stream->set_source_callback(&mock_callback); - test_stream->packet_size_ = kTestPacketSize; - test_stream->BufferPacket(&source_exhausted); - - EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes()); - EXPECT_FALSE(source_exhausted); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Negative) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - InitBuffer(test_stream); - test_stream->buffer_->Clear(); - - // Simulate where the underrun has occurred right after checking the delay. - MockAudioSourceCallback mock_callback; - EXPECT_CALL(mock_alsa_wrapper_, PcmState(_)) - .WillOnce(Return(SND_PCM_STATE_RUNNING)); - EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _)) - .WillOnce(DoAll(SetArgumentPointee<1>(-1), Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) - .WillRepeatedly(Return(0)); // Buffer is full. - EXPECT_CALL(mock_callback, OnMoreData(_, _)) - .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2))); - - bool source_exhausted; - test_stream->set_source_callback(&mock_callback); - test_stream->packet_size_ = kTestPacketSize; - test_stream->BufferPacket(&source_exhausted); - - EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes()); - EXPECT_FALSE(source_exhausted); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Underrun) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - InitBuffer(test_stream); - test_stream->buffer_->Clear(); - - // If ALSA has underrun then we should assume a delay of zero. - MockAudioSourceCallback mock_callback; - EXPECT_CALL(mock_alsa_wrapper_, PcmState(_)) - .WillOnce(Return(SND_PCM_STATE_XRUN)); - EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) - .WillRepeatedly(Return(0)); // Buffer is full. - EXPECT_CALL(mock_callback, - OnMoreData(_, AllOf( - Field(&AudioBuffersState::pending_bytes, 0), - Field(&AudioBuffersState::hardware_delay_bytes, 0)))) - .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2))); - - bool source_exhausted; - test_stream->set_source_callback(&mock_callback); - test_stream->packet_size_ = kTestPacketSize; - test_stream->BufferPacket(&source_exhausted); - - EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes()); - EXPECT_FALSE(source_exhausted); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - InitBuffer(test_stream); - // No expectations set on the strict mock because nothing should be called. - bool source_exhausted; - test_stream->packet_size_ = kTestPacketSize; - test_stream->BufferPacket(&source_exhausted); - EXPECT_EQ(kTestPacketSize, test_stream->buffer_->forward_bytes()); - EXPECT_FALSE(source_exhausted); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect) { - // Try channels from 1 -> 9. and see that we get the more specific surroundXX - // device opened for channels 4-8. For all other channels, the device should - // default to |AlsaPcmOutputStream::kDefaultDevice|. We should also not - // downmix any channel in this case because downmixing is only defined for - // channels 4-8, which we are guaranteeing to work. - // - // Note that the loop starts at "1", so the first parameter is ignored in - // these arrays. - const char* kExpectedDeviceName[] = { NULL, - AlsaPcmOutputStream::kDefaultDevice, - AlsaPcmOutputStream::kDefaultDevice, - AlsaPcmOutputStream::kDefaultDevice, - kSurround40, kSurround50, kSurround51, - kSurround70, kSurround71, - AlsaPcmOutputStream::kDefaultDevice }; - bool kExpectedDownmix[] = { false, false, false, false, false, true, - false, false, false, false }; - ChannelLayout kExpectedLayouts[] = { CHANNEL_LAYOUT_NONE, - CHANNEL_LAYOUT_MONO, - CHANNEL_LAYOUT_STEREO, - CHANNEL_LAYOUT_SURROUND, - CHANNEL_LAYOUT_4_0, - CHANNEL_LAYOUT_5_0, - CHANNEL_LAYOUT_5_1, - CHANNEL_LAYOUT_7_0, - CHANNEL_LAYOUT_7_1 }; - - - for (int i = 1; i < 9; ++i) { - if (i == 3 || i == 4 || i == 5) // invalid number of channels - continue; - SCOPED_TRACE(base::StringPrintf("Attempting %d Channel", i)); - - // Hints will only be grabbed for channel numbers that have non-default - // devices associated with them. - if (kExpectedDeviceName[i] != AlsaPcmOutputStream::kDefaultDevice) { - // The DeviceNameHint and DeviceNameFreeHint need to be paired to avoid a - // memory leak. - EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _)) - .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0])) - .Times(1); - } - - EXPECT_CALL(mock_alsa_wrapper_, - PcmOpen(_, StrEq(kExpectedDeviceName[i]), _, _)) - .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, - PcmSetParams(kFakeHandle, _, _, i, _, _, _)) - .WillOnce(Return(0)); - - // The parameters are specified by ALSA documentation, and are in constants - // in the implementation files. - EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID"))) - .WillRepeatedly(Invoke(OutputHint)); - EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME"))) - .WillRepeatedly(Invoke(EchoHint)); - - AlsaPcmOutputStream* test_stream = CreateStream(kExpectedLayouts[i]); - EXPECT_TRUE(test_stream->AutoSelectDevice(i)); - EXPECT_EQ(kExpectedDownmix[i], - static_cast(test_stream->channel_mixer_)); - - Mock::VerifyAndClearExpectations(&mock_alsa_wrapper_); - Mock::VerifyAndClearExpectations(mock_manager_.get()); - test_stream->Close(); - } -} - -TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_FallbackDevices) { - using std::string; - - // If there are problems opening a multi-channel device, it the fallbacks - // operations should be as follows. Assume the multi-channel device name is - // surround50: - // - // 1) Try open "surround50" - // 2) Try open "plug:surround50". - // 3) Try open "default". - // 4) Try open "plug:default". - // 5) Give up trying to open. - // - const string first_try = kSurround50; - const string second_try = string(AlsaPcmOutputStream::kPlugPrefix) + - kSurround50; - const string third_try = AlsaPcmOutputStream::kDefaultDevice; - const string fourth_try = string(AlsaPcmOutputStream::kPlugPrefix) + - AlsaPcmOutputStream::kDefaultDevice; - - EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _)) - .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0])) - .Times(1); - EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID"))) - .WillRepeatedly(Invoke(OutputHint)); - EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME"))) - .WillRepeatedly(Invoke(EchoHint)); - EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) - .WillRepeatedly(Return(kDummyMessage)); - - InSequence s; - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(first_try.c_str()), _, _)) - .WillOnce(Return(kTestFailedErrno)); - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(second_try.c_str()), _, _)) - .WillOnce(Return(kTestFailedErrno)); - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(third_try.c_str()), _, _)) - .WillOnce(Return(kTestFailedErrno)); - EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(fourth_try.c_str()), _, _)) - .WillOnce(Return(kTestFailedErrno)); - - AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0); - EXPECT_FALSE(test_stream->AutoSelectDevice(5)); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail) { - // Should get |kDefaultDevice|, and force a 2-channel downmix on a failure to - // enumerate devices. - EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _)) - .WillRepeatedly(Return(kTestFailedErrno)); - EXPECT_CALL(mock_alsa_wrapper_, - PcmOpen(_, StrEq(AlsaPcmOutputStream::kDefaultDevice), _, _)) - .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0))); - EXPECT_CALL(mock_alsa_wrapper_, - PcmSetParams(kFakeHandle, _, _, 2, _, _, _)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) - .WillOnce(Return(kDummyMessage)); - - AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0); - EXPECT_TRUE(test_stream->AutoSelectDevice(5)); - EXPECT_TRUE(test_stream->channel_mixer_); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - InitBuffer(test_stream); - test_stream->stop_stream_ = true; - bool source_exhausted; - test_stream->BufferPacket(&source_exhausted); - EXPECT_EQ(0, test_stream->buffer_->forward_bytes()); - EXPECT_TRUE(source_exhausted); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); - InitBuffer(test_stream); - DVLOG(1) << test_stream->state(); - EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) - .WillOnce(Return(10)); - test_stream->ScheduleNextWrite(false); - DVLOG(1) << test_stream->state(); - // TODO(sergeyu): Figure out how to check that the task has been added to the - // message loop. - - // Cleanup the message queue. Currently ~MessageQueue() doesn't free pending - // tasks unless running on valgrind. The code below is needed to keep - // heapcheck happy. - - test_stream->stop_stream_ = true; - DVLOG(1) << test_stream->state(); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed); - DVLOG(1) << test_stream->state(); - test_stream->Close(); -} - -TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) { - AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened); - test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying); - - InitBuffer(test_stream); - - test_stream->stop_stream_ = true; - test_stream->ScheduleNextWrite(true); - - // TODO(ajwong): Find a way to test whether or not another task has been - // posted so we can verify that the Alsa code will indeed break the task - // posting loop. - - test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed); - test_stream->Close(); -} - -} // namespace media diff --git a/media/audio/linux/alsa_util.cc b/media/audio/linux/alsa_util.cc deleted file mode 100644 index 176ef69..0000000 --- a/media/audio/linux/alsa_util.cc +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) 2012 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. - -#include "media/audio/linux/alsa_util.h" - -#include - -#include "base/logging.h" -#include "media/audio/linux/alsa_wrapper.h" - -namespace alsa_util { - -static snd_pcm_t* OpenDevice(media::AlsaWrapper* wrapper, - const char* device_name, - snd_pcm_stream_t type, - int channels, - int sample_rate, - snd_pcm_format_t pcm_format, - int latency_us) { - snd_pcm_t* handle = NULL; - int error = wrapper->PcmOpen(&handle, device_name, type, SND_PCM_NONBLOCK); - if (error < 0) { - LOG(WARNING) << "PcmOpen: " << device_name << "," - << wrapper->StrError(error); - return NULL; - } - - error = wrapper->PcmSetParams(handle, pcm_format, - SND_PCM_ACCESS_RW_INTERLEAVED, channels, - sample_rate, 1, latency_us); - if (error < 0) { - LOG(WARNING) << "PcmSetParams: " << device_name << ", " - << wrapper->StrError(error) << " - Format: " << pcm_format - << " Channels: " << channels << " Latency: " << latency_us; - if (alsa_util::CloseDevice(wrapper, handle) < 0) { - // TODO(ajwong): Retry on certain errors? - LOG(WARNING) << "Unable to close audio device. Leaking handle."; - } - return NULL; - } - - return handle; -} - -static std::string DeviceNameToControlName(const std::string& device_name) { - const char kMixerPrefix[] = "hw"; - std::string control_name; - size_t pos1 = device_name.find(':'); - if (pos1 == std::string::npos) { - control_name = device_name; - } else { - // Examples: - // deviceName: "front:CARD=Intel,DEV=0", controlName: "hw:CARD=Intel". - // deviceName: "default:CARD=Intel", controlName: "CARD=Intel". - size_t pos2 = device_name.find(','); - control_name = (pos2 == std::string::npos) ? - device_name.substr(pos1) : - kMixerPrefix + device_name.substr(pos1, pos2 - pos1); - } - - return control_name; -} - -snd_pcm_format_t BitsToFormat(int bits_per_sample) { - switch (bits_per_sample) { - case 8: - return SND_PCM_FORMAT_U8; - - case 16: - return SND_PCM_FORMAT_S16; - - case 24: - return SND_PCM_FORMAT_S24; - - case 32: - return SND_PCM_FORMAT_S32; - - default: - return SND_PCM_FORMAT_UNKNOWN; - } -} - -int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle) { - std::string device_name = wrapper->PcmName(handle); - int error = wrapper->PcmClose(handle); - if (error < 0) { - LOG(ERROR) << "PcmClose: " << device_name << ", " - << wrapper->StrError(error); - } - - return error; -} - -snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper, - const char* device_name, - int channels, - int sample_rate, - snd_pcm_format_t pcm_format, - int latency_us) { - return OpenDevice(wrapper, device_name, SND_PCM_STREAM_CAPTURE, channels, - sample_rate, pcm_format, latency_us); -} - -snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper, - const char* device_name, - int channels, - int sample_rate, - snd_pcm_format_t pcm_format, - int latency_us) { - return OpenDevice(wrapper, device_name, SND_PCM_STREAM_PLAYBACK, channels, - sample_rate, pcm_format, latency_us); -} - -snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper, - const std::string& device_name) { - snd_mixer_t* mixer = NULL; - - int error = wrapper->MixerOpen(&mixer, 0); - if (error < 0) { - LOG(ERROR) << "MixerOpen: " << device_name << ", " - << wrapper->StrError(error); - return NULL; - } - - std::string control_name = DeviceNameToControlName(device_name); - error = wrapper->MixerAttach(mixer, control_name.c_str()); - if (error < 0) { - LOG(ERROR) << "MixerAttach, " << control_name << ", " - << wrapper->StrError(error); - alsa_util::CloseMixer(wrapper, mixer, device_name); - return NULL; - } - - error = wrapper->MixerElementRegister(mixer, NULL, NULL); - if (error < 0) { - LOG(ERROR) << "MixerElementRegister: " << control_name << ", " - << wrapper->StrError(error); - alsa_util::CloseMixer(wrapper, mixer, device_name); - return NULL; - } - - return mixer; -} - -void CloseMixer(media::AlsaWrapper* wrapper, snd_mixer_t* mixer, - const std::string& device_name) { - if (!mixer) - return; - - wrapper->MixerFree(mixer); - - int error = 0; - if (!device_name.empty()) { - std::string control_name = DeviceNameToControlName(device_name); - error = wrapper->MixerDetach(mixer, control_name.c_str()); - if (error < 0) { - LOG(WARNING) << "MixerDetach: " << control_name << ", " - << wrapper->StrError(error); - } - } - - error = wrapper->MixerClose(mixer); - if (error < 0) { - LOG(WARNING) << "MixerClose: " << wrapper->StrError(error); - } -} - -snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper, - snd_mixer_t* mixer) { - if (!mixer) - return NULL; - - int error = wrapper->MixerLoad(mixer); - if (error < 0) { - LOG(ERROR) << "MixerLoad: " << wrapper->StrError(error); - return NULL; - } - - snd_mixer_elem_t* elem = NULL; - snd_mixer_elem_t* mic_elem = NULL; - const char kCaptureElemName[] = "Capture"; - const char kMicElemName[] = "Mic"; - for (elem = wrapper->MixerFirstElem(mixer); - elem; - elem = wrapper->MixerNextElem(elem)) { - if (wrapper->MixerSelemIsActive(elem)) { - const char* elem_name = wrapper->MixerSelemName(elem); - if (strcmp(elem_name, kCaptureElemName) == 0) - return elem; - else if (strcmp(elem_name, kMicElemName) == 0) - mic_elem = elem; - } - } - - // Did not find any Capture handle, use the Mic handle. - return mic_elem; -} - -} // namespace alsa_util diff --git a/media/audio/linux/alsa_util.h b/media/audio/linux/alsa_util.h deleted file mode 100644 index 53cf80a..0000000 --- a/media/audio/linux/alsa_util.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2012 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_ALSA_UTIL_H_ -#define MEDIA_AUDIO_LINUX_ALSA_UTIL_H_ - -#include -#include - -namespace media { -class AlsaWrapper; -} - -namespace alsa_util { - -snd_pcm_format_t BitsToFormat(int bits_per_sample); - -snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper, - const char* device_name, - int channels, - int sample_rate, - snd_pcm_format_t pcm_format, - int latency_us); - -snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper, - const char* device_name, - int channels, - int sample_rate, - snd_pcm_format_t pcm_format, - int latency_us); - -int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle); - -snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper, - const std::string& device_name); - -void CloseMixer(media::AlsaWrapper* wrapper, - snd_mixer_t* mixer, - const std::string& device_name); - -snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper, - snd_mixer_t* mixer); - -} // namespace alsa_util - -#endif // MEDIA_AUDIO_LINUX_ALSA_UTIL_H_ diff --git a/media/audio/linux/alsa_wrapper.cc b/media/audio/linux/alsa_wrapper.cc deleted file mode 100644 index c1ce359..0000000 --- a/media/audio/linux/alsa_wrapper.cc +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) 2012 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. - -#include "media/audio/linux/alsa_wrapper.h" - -#include - -namespace media { - -AlsaWrapper::AlsaWrapper() { -} - -AlsaWrapper::~AlsaWrapper() { -} - -int AlsaWrapper::PcmOpen(snd_pcm_t** handle, const char* name, - snd_pcm_stream_t stream, int mode) { - return snd_pcm_open(handle, name, stream, mode); -} - -int AlsaWrapper::DeviceNameHint(int card, const char* iface, void*** hints) { - return snd_device_name_hint(card, iface, hints); -} - -char* AlsaWrapper::DeviceNameGetHint(const void* hint, const char* id) { - return snd_device_name_get_hint(hint, id); -} - -int AlsaWrapper::DeviceNameFreeHint(void** hints) { - return snd_device_name_free_hint(hints); -} - -int AlsaWrapper::CardNext(int* rcard) { - return snd_card_next(rcard); -} - -int AlsaWrapper::PcmClose(snd_pcm_t* handle) { - return snd_pcm_close(handle); -} - -int AlsaWrapper::PcmPrepare(snd_pcm_t* handle) { - return snd_pcm_prepare(handle); -} - -int AlsaWrapper::PcmDrop(snd_pcm_t* handle) { - return snd_pcm_drop(handle); -} - -int AlsaWrapper::PcmDelay(snd_pcm_t* handle, snd_pcm_sframes_t* delay) { - return snd_pcm_delay(handle, delay); -} - -snd_pcm_sframes_t AlsaWrapper::PcmWritei(snd_pcm_t* handle, - const void* buffer, - snd_pcm_uframes_t size) { - return snd_pcm_writei(handle, buffer, size); -} - -snd_pcm_sframes_t AlsaWrapper::PcmReadi(snd_pcm_t* handle, - void* buffer, - snd_pcm_uframes_t size) { - return snd_pcm_readi(handle, buffer, size); -} - -int AlsaWrapper::PcmRecover(snd_pcm_t* handle, int err, int silent) { - return snd_pcm_recover(handle, err, silent); -} - -const char* AlsaWrapper::PcmName(snd_pcm_t* handle) { - return snd_pcm_name(handle); -} - -int AlsaWrapper::PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format, - snd_pcm_access_t access, unsigned int channels, - unsigned int rate, int soft_resample, - unsigned int latency) { - return snd_pcm_set_params(handle, - format, - access, - channels, - rate, - soft_resample, - latency); -} - -int AlsaWrapper::PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size, - snd_pcm_uframes_t* period_size) { - return snd_pcm_get_params(handle, buffer_size, period_size); -} - -snd_pcm_sframes_t AlsaWrapper::PcmAvailUpdate(snd_pcm_t* handle) { - return snd_pcm_avail_update(handle); -} - -snd_pcm_state_t AlsaWrapper::PcmState(snd_pcm_t* handle) { - return snd_pcm_state(handle); -} - -const char* AlsaWrapper::StrError(int errnum) { - return snd_strerror(errnum); -} - -int AlsaWrapper::PcmStart(snd_pcm_t* handle) { - return snd_pcm_start(handle); -} - -int AlsaWrapper::MixerOpen(snd_mixer_t** mixer, int mode) { - return snd_mixer_open(mixer, mode); -} - -int AlsaWrapper::MixerAttach(snd_mixer_t* mixer, const char* name) { - return snd_mixer_attach(mixer, name); -} - -int AlsaWrapper::MixerElementRegister(snd_mixer_t* mixer, - struct snd_mixer_selem_regopt* options, - snd_mixer_class_t** classp) { - return snd_mixer_selem_register(mixer, options, classp); -} - -void AlsaWrapper::MixerFree(snd_mixer_t* mixer) { - snd_mixer_free(mixer); -} - -int AlsaWrapper::MixerDetach(snd_mixer_t* mixer, const char* name) { - return snd_mixer_detach(mixer, name); -} - -int AlsaWrapper::MixerClose(snd_mixer_t* mixer) { - return snd_mixer_close(mixer); -} - -int AlsaWrapper::MixerLoad(snd_mixer_t* mixer) { - return snd_mixer_load(mixer); -} - -snd_mixer_elem_t* AlsaWrapper::MixerFirstElem(snd_mixer_t* mixer) { - return snd_mixer_first_elem(mixer); -} - -snd_mixer_elem_t* AlsaWrapper::MixerNextElem(snd_mixer_elem_t* elem) { - return snd_mixer_elem_next(elem); -} - -int AlsaWrapper::MixerSelemIsActive(snd_mixer_elem_t* elem) { - return snd_mixer_selem_is_active(elem); -} - -const char* AlsaWrapper::MixerSelemName(snd_mixer_elem_t* elem) { - return snd_mixer_selem_get_name(elem); -} - -int AlsaWrapper::MixerSelemSetCaptureVolumeAll( - snd_mixer_elem_t* elem, long value) { - return snd_mixer_selem_set_capture_volume_all(elem, value); -} - -int AlsaWrapper::MixerSelemGetCaptureVolume( - snd_mixer_elem_t* elem, snd_mixer_selem_channel_id_t channel, long* value) { - return snd_mixer_selem_get_capture_volume(elem, channel, value); -} - -int AlsaWrapper::MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem) { - return snd_mixer_selem_has_capture_volume(elem); -} - -int AlsaWrapper::MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem, - long* min, long* max) { - return snd_mixer_selem_get_capture_volume_range(elem, min, max); -} - -} // namespace media diff --git a/media/audio/linux/alsa_wrapper.h b/media/audio/linux/alsa_wrapper.h deleted file mode 100644 index 30d9463..0000000 --- a/media/audio/linux/alsa_wrapper.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2012 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. -// -// AlsaWrapper is a simple stateless class that wraps the alsa library commands -// we want to use. It's purpose is to allow injection of a mock so that the -// higher level code is testable. - -#include - -#include "base/basictypes.h" -#include "media/base/media_export.h" - -namespace media { - -class MEDIA_EXPORT AlsaWrapper { - public: - AlsaWrapper(); - virtual ~AlsaWrapper(); - - virtual int DeviceNameHint(int card, const char* iface, void*** hints); - virtual char* DeviceNameGetHint(const void* hint, const char* id); - virtual int DeviceNameFreeHint(void** hints); - virtual int CardNext(int* rcard); - - virtual int PcmOpen(snd_pcm_t** handle, const char* name, - snd_pcm_stream_t stream, int mode); - virtual int PcmClose(snd_pcm_t* handle); - virtual int PcmPrepare(snd_pcm_t* handle); - virtual int PcmDrop(snd_pcm_t* handle); - virtual int PcmDelay(snd_pcm_t* handle, snd_pcm_sframes_t* delay); - virtual snd_pcm_sframes_t PcmWritei(snd_pcm_t* handle, - const void* buffer, - snd_pcm_uframes_t size); - virtual snd_pcm_sframes_t PcmReadi(snd_pcm_t* handle, - void* buffer, - snd_pcm_uframes_t size); - virtual int PcmRecover(snd_pcm_t* handle, int err, int silent); - virtual int PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format, - snd_pcm_access_t access, unsigned int channels, - unsigned int rate, int soft_resample, - unsigned int latency); - virtual int PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size, - snd_pcm_uframes_t* period_size); - virtual const char* PcmName(snd_pcm_t* handle); - virtual snd_pcm_sframes_t PcmAvailUpdate(snd_pcm_t* handle); - virtual snd_pcm_state_t PcmState(snd_pcm_t* handle); - virtual int PcmStart(snd_pcm_t* handle); - - virtual int MixerOpen(snd_mixer_t** mixer, int mode); - virtual int MixerAttach(snd_mixer_t* mixer, const char* name); - virtual int MixerElementRegister(snd_mixer_t* mixer, - struct snd_mixer_selem_regopt* options, - snd_mixer_class_t** classp); - virtual void MixerFree(snd_mixer_t* mixer); - virtual int MixerDetach(snd_mixer_t* mixer, const char* name); - virtual int MixerClose(snd_mixer_t* mixer); - virtual int MixerLoad(snd_mixer_t* mixer); - virtual snd_mixer_elem_t* MixerFirstElem(snd_mixer_t* mixer); - virtual snd_mixer_elem_t* MixerNextElem(snd_mixer_elem_t* elem); - virtual int MixerSelemIsActive(snd_mixer_elem_t* elem); - virtual const char* MixerSelemName(snd_mixer_elem_t* elem); - virtual int MixerSelemSetCaptureVolumeAll(snd_mixer_elem_t* elem, long value); - virtual int MixerSelemGetCaptureVolume(snd_mixer_elem_t* elem, - snd_mixer_selem_channel_id_t channel, - long* value); - virtual int MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem); - virtual int MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem, - long* min, long* max); - - virtual const char* StrError(int errnum); - - private: - int ConfigureHwParams(snd_pcm_t* handle, snd_pcm_hw_params_t* hw_params, - snd_pcm_format_t format, snd_pcm_access_t access, - unsigned int channels, unsigned int rate, - int soft_resample, unsigned int latency); - DISALLOW_COPY_AND_ASSIGN(AlsaWrapper); -}; - -} // namespace media diff --git a/media/audio/linux/audio_manager_linux.cc b/media/audio/linux/audio_manager_linux.cc index 0d0f104..350cd64 100644 --- a/media/audio/linux/audio_manager_linux.cc +++ b/media/audio/linux/audio_manager_linux.cc @@ -2,51 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "media/audio/linux/audio_manager_linux.h" - #include "base/command_line.h" -#include "base/environment.h" -#include "base/files/file_path.h" -#include "base/logging.h" #include "base/metrics/histogram.h" -#include "base/nix/xdg_util.h" -#include "base/process/launch.h" -#include "base/stl_util.h" -#include "media/audio/audio_output_dispatcher.h" -#include "media/audio/audio_parameters.h" +#if defined(USE_ALSA) +#include "media/audio/alsa/audio_manager_alsa.h" +#else +#include "media/audio/fake_audio_manager.h" +#endif #if defined(USE_CRAS) #include "media/audio/cras/audio_manager_cras.h" #endif -#include "media/audio/linux/alsa_input.h" -#include "media/audio/linux/alsa_output.h" -#include "media/audio/linux/alsa_wrapper.h" #if defined(USE_PULSEAUDIO) #include "media/audio/pulse/audio_manager_pulse.h" #endif -#include "media/base/channel_layout.h" -#include "media/base/limits.h" #include "media/base/media_switches.h" namespace media { -// Maximum number of output streams that can be open simultaneously. -static const int kMaxOutputStreams = 50; - -// Default sample rate for input and output streams. -static const int kDefaultSampleRate = 48000; - -// Since "default", "pulse" and "dmix" devices are virtual devices mapped to -// real devices, we remove them from the list to avoiding duplicate counting. -// In addition, note that we support no more than 2 channels for recording, -// hence surround devices are not stored in the list. -static const char* kInvalidAudioInputDevices[] = { - "default", - "dmix", - "null", - "pulse", - "surround", -}; - enum LinuxAudioIO { kPulse, kAlsa, @@ -54,317 +26,6 @@ enum LinuxAudioIO { kAudioIOMax // Must always be last! }; -// static -void AudioManagerLinux::ShowLinuxAudioInputSettings() { - scoped_ptr env(base::Environment::Create()); - CommandLine command_line(CommandLine::NO_PROGRAM); - switch (base::nix::GetDesktopEnvironment(env.get())) { - case base::nix::DESKTOP_ENVIRONMENT_GNOME: - command_line.SetProgram(base::FilePath("gnome-volume-control")); - break; - case base::nix::DESKTOP_ENVIRONMENT_KDE3: - case base::nix::DESKTOP_ENVIRONMENT_KDE4: - command_line.SetProgram(base::FilePath("kmix")); - break; - case base::nix::DESKTOP_ENVIRONMENT_UNITY: - command_line.SetProgram(base::FilePath("gnome-control-center")); - command_line.AppendArg("sound"); - command_line.AppendArg("input"); - break; - default: - LOG(ERROR) << "Failed to show audio input settings: we don't know " - << "what command to use for your desktop environment."; - return; - } - base::LaunchProcess(command_line, base::LaunchOptions(), NULL); -} - -// Implementation of AudioManager. -bool AudioManagerLinux::HasAudioOutputDevices() { - return HasAnyAlsaAudioDevice(kStreamPlayback); -} - -bool AudioManagerLinux::HasAudioInputDevices() { - return HasAnyAlsaAudioDevice(kStreamCapture); -} - -AudioManagerLinux::AudioManagerLinux() - : wrapper_(new AlsaWrapper()) { - SetMaxOutputStreamsAllowed(kMaxOutputStreams); -} - -AudioManagerLinux::~AudioManagerLinux() { - Shutdown(); -} - -void AudioManagerLinux::ShowAudioInputSettings() { - ShowLinuxAudioInputSettings(); -} - -void AudioManagerLinux::GetAudioInputDeviceNames( - AudioDeviceNames* device_names) { - DCHECK(device_names->empty()); - GetAlsaAudioDevices(kStreamCapture, device_names); -} - -void AudioManagerLinux::GetAudioOutputDeviceNames( - AudioDeviceNames* device_names) { - DCHECK(device_names->empty()); - GetAlsaAudioDevices(kStreamPlayback, device_names); -} - -AudioParameters AudioManagerLinux::GetInputStreamParameters( - const std::string& device_id) { - static const int kDefaultInputBufferSize = 1024; - - return AudioParameters( - AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO, - kDefaultSampleRate, 16, kDefaultInputBufferSize); -} - -void AudioManagerLinux::GetAlsaAudioDevices( - StreamType type, - media::AudioDeviceNames* device_names) { - // Constants specified by the ALSA API for device hints. - static const char kPcmInterfaceName[] = "pcm"; - int card = -1; - - // Loop through the sound cards to get ALSA device hints. - while (!wrapper_->CardNext(&card) && card >= 0) { - void** hints = NULL; - int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints); - if (!error) { - GetAlsaDevicesInfo(type, hints, device_names); - - // Destroy the hints now that we're done with it. - wrapper_->DeviceNameFreeHint(hints); - } else { - DLOG(WARNING) << "GetAlsaAudioDevices: unable to get device hints: " - << wrapper_->StrError(error); - } - } -} - -void AudioManagerLinux::GetAlsaDevicesInfo( - AudioManagerLinux::StreamType type, - void** hints, - media::AudioDeviceNames* device_names) { - static const char kIoHintName[] = "IOID"; - static const char kNameHintName[] = "NAME"; - static const char kDescriptionHintName[] = "DESC"; - - const char* unwanted_device_type = UnwantedDeviceTypeWhenEnumerating(type); - - for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) { - // Only examine devices of the right type. Valid values are - // "Input", "Output", and NULL which means both input and output. - scoped_ptr_malloc io(wrapper_->DeviceNameGetHint(*hint_iter, - kIoHintName)); - if (io != NULL && strcmp(unwanted_device_type, io.get()) == 0) - continue; - - // Found a device, prepend the default device since we always want - // it to be on the top of the list for all platforms. And there is - // no duplicate counting here since it is only done if the list is - // still empty. Note, pulse has exclusively opened the default - // device, so we must open the device via the "default" moniker. - if (device_names->empty()) { - device_names->push_front(media::AudioDeviceName( - AudioManagerBase::kDefaultDeviceName, - AudioManagerBase::kDefaultDeviceId)); - } - - // Get the unique device name for the device. - scoped_ptr_malloc unique_device_name( - wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName)); - - // Find out if the device is available. - if (IsAlsaDeviceAvailable(type, unique_device_name.get())) { - // Get the description for the device. - scoped_ptr_malloc desc(wrapper_->DeviceNameGetHint( - *hint_iter, kDescriptionHintName)); - - media::AudioDeviceName name; - name.unique_id = unique_device_name.get(); - if (desc) { - // Use the more user friendly description as name. - // Replace '\n' with '-'. - char* pret = strchr(desc.get(), '\n'); - if (pret) - *pret = '-'; - name.device_name = desc.get(); - } else { - // Virtual devices don't necessarily have descriptions. - // Use their names instead. - name.device_name = unique_device_name.get(); - } - - // Store the device information. - device_names->push_back(name); - } - } -} - -// static -bool AudioManagerLinux::IsAlsaDeviceAvailable( - AudioManagerLinux::StreamType type, - const char* device_name) { - if (!device_name) - return false; - - // We do prefix matches on the device name to see whether to include - // it or not. - if (type == kStreamCapture) { - // Check if the device is in the list of invalid devices. - for (size_t i = 0; i < arraysize(kInvalidAudioInputDevices); ++i) { - if (strncmp(kInvalidAudioInputDevices[i], device_name, - strlen(kInvalidAudioInputDevices[i])) == 0) - return false; - } - return true; - } else { - DCHECK_EQ(kStreamPlayback, type); - // We prefer the device type that maps straight to hardware but - // goes through software conversion if needed (e.g. incompatible - // sample rate). - // TODO(joi): Should we prefer "hw" instead? - static const char kDeviceTypeDesired[] = "plughw"; - return strncmp(kDeviceTypeDesired, - device_name, - arraysize(kDeviceTypeDesired) - 1) == 0; - } -} - -// static -const char* AudioManagerLinux::UnwantedDeviceTypeWhenEnumerating( - AudioManagerLinux::StreamType wanted_type) { - return wanted_type == kStreamPlayback ? "Input" : "Output"; -} - -bool AudioManagerLinux::HasAnyAlsaAudioDevice( - AudioManagerLinux::StreamType stream) { - static const char kPcmInterfaceName[] = "pcm"; - static const char kIoHintName[] = "IOID"; - void** hints = NULL; - bool has_device = false; - int card = -1; - - // Loop through the sound cards. - // Don't use snd_device_name_hint(-1,..) since there is a access violation - // inside this ALSA API with libasound.so.2.0.0. - while (!wrapper_->CardNext(&card) && (card >= 0) && !has_device) { - int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints); - if (!error) { - for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) { - // Only examine devices that are |stream| capable. Valid values are - // "Input", "Output", and NULL which means both input and output. - scoped_ptr_malloc io(wrapper_->DeviceNameGetHint(*hint_iter, - kIoHintName)); - const char* unwanted_type = UnwantedDeviceTypeWhenEnumerating(stream); - if (io != NULL && strcmp(unwanted_type, io.get()) == 0) - continue; // Wrong type, skip the device. - - // Found an input device. - has_device = true; - break; - } - - // Destroy the hints now that we're done with it. - wrapper_->DeviceNameFreeHint(hints); - hints = NULL; - } else { - DLOG(WARNING) << "HasAnyAudioDevice: unable to get device hints: " - << wrapper_->StrError(error); - } - } - - return has_device; -} - -AudioOutputStream* AudioManagerLinux::MakeLinearOutputStream( - const AudioParameters& params) { - DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); - return MakeOutputStream(params); -} - -AudioOutputStream* AudioManagerLinux::MakeLowLatencyOutputStream( - const AudioParameters& params, - const std::string& device_id, - const std::string& input_device_id) { - DLOG_IF(ERROR, !device_id.empty()) << "Not implemented!"; - DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); - // TODO(xians): Use input_device_id for unified IO. - return MakeOutputStream(params); -} - -AudioInputStream* AudioManagerLinux::MakeLinearInputStream( - const AudioParameters& params, const std::string& device_id) { - DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); - return MakeInputStream(params, device_id); -} - -AudioInputStream* AudioManagerLinux::MakeLowLatencyInputStream( - const AudioParameters& params, const std::string& device_id) { - DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); - return MakeInputStream(params, device_id); -} - -AudioParameters AudioManagerLinux::GetPreferredOutputStreamParameters( - const std::string& output_device_id, - const AudioParameters& input_params) { - // TODO(tommi): Support |output_device_id|. - DLOG_IF(ERROR, !output_device_id.empty()) << "Not implemented!"; - static const int kDefaultOutputBufferSize = 2048; - ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO; - int sample_rate = kDefaultSampleRate; - int buffer_size = kDefaultOutputBufferSize; - int bits_per_sample = 16; - int input_channels = 0; - if (input_params.IsValid()) { - // Some clients, such as WebRTC, have a more limited use case and work - // acceptably with a smaller buffer size. The check below allows clients - // which want to try a smaller buffer size on Linux to do so. - // TODO(dalecurtis): This should include bits per channel and channel layout - // eventually. - sample_rate = input_params.sample_rate(); - bits_per_sample = input_params.bits_per_sample(); - channel_layout = input_params.channel_layout(); - input_channels = input_params.input_channels(); - buffer_size = std::min(input_params.frames_per_buffer(), buffer_size); - } - - int user_buffer_size = GetUserBufferSize(); - if (user_buffer_size) - buffer_size = user_buffer_size; - - return AudioParameters( - AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, input_channels, - sample_rate, bits_per_sample, buffer_size); -} - -AudioOutputStream* AudioManagerLinux::MakeOutputStream( - const AudioParameters& params) { - std::string device_name = AlsaPcmOutputStream::kAutoSelectDevice; - if (CommandLine::ForCurrentProcess()->HasSwitch( - switches::kAlsaOutputDevice)) { - device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - switches::kAlsaOutputDevice); - } - return new AlsaPcmOutputStream(device_name, params, wrapper_.get(), this); -} - -AudioInputStream* AudioManagerLinux::MakeInputStream( - const AudioParameters& params, const std::string& device_id) { - std::string device_name = (device_id == AudioManagerBase::kDefaultDeviceId) ? - AlsaPcmInputStream::kAutoSelectDevice : device_id; - if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAlsaInputDevice)) { - device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - switches::kAlsaInputDevice); - } - - return new AlsaPcmInputStream(this, device_name, params, wrapper_.get()); -} - AudioManager* CreateAudioManager() { #if defined(USE_CRAS) if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUseCras)) { @@ -381,8 +42,12 @@ AudioManager* CreateAudioManager() { } #endif +#if defined(USE_ALSA) UMA_HISTOGRAM_ENUMERATION("Media.LinuxAudioIO", kAlsa, kAudioIOMax); - return new AudioManagerLinux(); + return new AudioManagerAlsa(); +#else + return new FakeAudioManager(); +#endif } } // namespace media diff --git a/media/audio/linux/audio_manager_linux.h b/media/audio/linux/audio_manager_linux.h deleted file mode 100644 index ab284df..0000000 --- a/media/audio/linux/audio_manager_linux.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2012 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 -#include "base/compiler_specific.h" -#include "base/memory/ref_counted.h" -#include "base/threading/thread.h" -#include "media/audio/audio_manager_base.h" - -namespace media { - -class AlsaWrapper; - -class MEDIA_EXPORT AudioManagerLinux : public AudioManagerBase { - public: - AudioManagerLinux(); - - static void ShowLinuxAudioInputSettings(); - - // Implementation of AudioManager. - virtual bool HasAudioOutputDevices() OVERRIDE; - virtual bool HasAudioInputDevices() OVERRIDE; - virtual void ShowAudioInputSettings() OVERRIDE; - virtual void GetAudioInputDeviceNames( - AudioDeviceNames* device_names) OVERRIDE; - virtual void GetAudioOutputDeviceNames( - AudioDeviceNames* device_names) OVERRIDE; - virtual AudioParameters GetInputStreamParameters( - const std::string& device_id) OVERRIDE; - - // Implementation of AudioManagerBase. - virtual AudioOutputStream* MakeLinearOutputStream( - const AudioParameters& params) OVERRIDE; - virtual AudioOutputStream* MakeLowLatencyOutputStream( - const AudioParameters& params, - const std::string& device_id, - const std::string& input_device_id) OVERRIDE; - virtual AudioInputStream* MakeLinearInputStream( - const AudioParameters& params, const std::string& device_id) OVERRIDE; - virtual AudioInputStream* MakeLowLatencyInputStream( - const AudioParameters& params, const std::string& device_id) OVERRIDE; - - protected: - virtual ~AudioManagerLinux(); - - virtual AudioParameters GetPreferredOutputStreamParameters( - const std::string& output_device_id, - const AudioParameters& input_params) OVERRIDE; - - private: - enum StreamType { - kStreamPlayback = 0, - kStreamCapture, - }; - - // Gets a list of available ALSA devices. - void GetAlsaAudioDevices(StreamType type, - media::AudioDeviceNames* device_names); - - // Gets the ALSA devices' names and ids that support streams of the - // given type. - void GetAlsaDevicesInfo(StreamType type, - void** hint, - media::AudioDeviceNames* device_names); - - // Checks if the specific ALSA device is available. - static bool IsAlsaDeviceAvailable(StreamType type, - const char* device_name); - - static const char* UnwantedDeviceTypeWhenEnumerating( - StreamType wanted_type); - - // Returns true if a device is present for the given stream type. - bool HasAnyAlsaAudioDevice(StreamType stream); - - // Called by MakeLinearOutputStream and MakeLowLatencyOutputStream. - AudioOutputStream* MakeOutputStream(const AudioParameters& params); - - // Called by MakeLinearInputStream and MakeLowLatencyInputStream. - AudioInputStream* MakeInputStream(const AudioParameters& params, - const std::string& device_id); - - scoped_ptr wrapper_; - - DISALLOW_COPY_AND_ASSIGN(AudioManagerLinux); -}; - -} // namespace media - -#endif // MEDIA_AUDIO_LINUX_AUDIO_MANAGER_LINUX_H_ diff --git a/media/audio/pulse/audio_manager_pulse.cc b/media/audio/pulse/audio_manager_pulse.cc index 4aac9c5..c5b78d7 100644 --- a/media/audio/pulse/audio_manager_pulse.cc +++ b/media/audio/pulse/audio_manager_pulse.cc @@ -10,8 +10,8 @@ #include "base/logging.h" #include "base/nix/xdg_util.h" #include "base/stl_util.h" +#include "media/audio/alsa/audio_manager_alsa.h" #include "media/audio/audio_parameters.h" -#include "media/audio/linux/audio_manager_linux.h" #include "media/audio/pulse/pulse_input.h" #include "media/audio/pulse/pulse_output.h" #include "media/audio/pulse/pulse_unified.h" @@ -77,7 +77,7 @@ bool AudioManagerPulse::HasAudioInputDevices() { } void AudioManagerPulse::ShowAudioInputSettings() { - AudioManagerLinux::ShowLinuxAudioInputSettings(); + AudioManagerAlsa::ShowLinuxAudioInputSettings(); } void AudioManagerPulse::GetAudioDeviceNames( diff --git a/media/media.gyp b/media/media.gyp index d1e638a..8c3a2af 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -22,7 +22,7 @@ 'media_use_libvpx%': 1, }], # ALSA usage. - ['OS=="linux" or OS=="freebsd" or OS=="solaris"', { + ['(OS=="linux" or OS=="freebsd" or OS=="solaris") and embedded!=1', { 'use_alsa%': 1, }, { 'use_alsa%': 0, @@ -62,6 +62,16 @@ ], 'sources': [ 'audio/agc_audio_stream.h', + 'audio/alsa/alsa_input.cc', + 'audio/alsa/alsa_input.h', + 'audio/alsa/alsa_output.cc', + 'audio/alsa/alsa_output.h', + 'audio/alsa/alsa_util.cc', + 'audio/alsa/alsa_util.h', + 'audio/alsa/alsa_wrapper.cc', + 'audio/alsa/alsa_wrapper.h', + 'audio/alsa/audio_manager_alsa.cc', + 'audio/alsa/audio_manager_alsa.h', 'audio/android/audio_manager_android.cc', 'audio/android/audio_manager_android.h', 'audio/android/opensles_input.cc', @@ -115,16 +125,10 @@ 'audio/fake_audio_consumer.h', 'audio/fake_audio_input_stream.cc', 'audio/fake_audio_input_stream.h', + 'audio/fake_audio_manager.cc', + 'audio/fake_audio_manager.h', 'audio/fake_audio_output_stream.cc', 'audio/fake_audio_output_stream.h', - 'audio/linux/alsa_input.cc', - 'audio/linux/alsa_input.h', - 'audio/linux/alsa_output.cc', - 'audio/linux/alsa_output.h', - 'audio/linux/alsa_util.cc', - 'audio/linux/alsa_util.h', - 'audio/linux/alsa_wrapper.cc', - 'audio/linux/alsa_wrapper.h', 'audio/linux/audio_manager_linux.cc', 'audio/linux/audio_manager_linux.h', 'audio/mac/aggregate_device_manager.cc', @@ -573,9 +577,11 @@ '-lasound', ], }, + 'defines': [ + 'USE_ALSA', + ], }, { # use_alsa==0 - 'sources/': [ ['exclude', '/alsa_'], - ['exclude', '/audio_manager_linux'] ], + 'sources/': [ ['exclude', '(^|/)alsa/'], ], }], ['OS!="openbsd"', { 'sources!': [ @@ -875,6 +881,7 @@ ], 'sources': [ 'audio/android/audio_android_unittest.cc', + 'audio/alsa/alsa_output_unittest.cc', 'audio/audio_input_controller_unittest.cc', 'audio/audio_input_unittest.cc', 'audio/audio_input_volume_unittest.cc', @@ -886,7 +893,6 @@ 'audio/audio_parameters_unittest.cc', 'audio/audio_power_monitor_unittest.cc', 'audio/fake_audio_consumer_unittest.cc', - 'audio/linux/alsa_output_unittest.cc', 'audio/mac/audio_auhal_mac_unittest.cc', 'audio/mac/audio_device_listener_mac_unittest.cc', 'audio/mac/audio_low_latency_input_mac_unittest.cc', @@ -1064,7 +1070,7 @@ }], ['use_alsa==0', { 'sources!': [ - 'audio/linux/alsa_output_unittest.cc', + 'audio/alsa/alsa_output_unittest.cc', 'audio/audio_low_latency_input_output_unittest.cc', ], }], diff --git a/tools/valgrind/tsan/suppressions.txt b/tools/valgrind/tsan/suppressions.txt index 7e25d0b..6497f29 100644 --- a/tools/valgrind/tsan/suppressions.txt +++ b/tools/valgrind/tsan/suppressions.txt @@ -1069,7 +1069,7 @@ { bug_256792 ThreadSanitizer:Race - fun:media::AudioManagerLinux::~AudioManagerLinux + fun:media::AudioManagerAlsa::~AudioManagerAlsa fun:content::MockAudioManager::~MockAudioManager fun:content::MockAudioManager::~MockAudioManager fun:base::DefaultDeleter* -- cgit v1.1