diff options
Diffstat (limited to 'media/audio/alsa/alsa_input.cc')
-rw-r--r-- | media/audio/alsa/alsa_input.cc | 340 |
1 files changed, 340 insertions, 0 deletions
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<float>(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<uint32>(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<double>(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<long>(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<snd_mixer_selem_channel_id_t>(0), + ¤t_volume); + if (error < 0) { + DLOG(WARNING) << "Unable to get volume for " << device_name_; + return 0.0; + } + + return static_cast<double>(current_volume); +} + +void AlsaPcmInputStream::HandleError(const char* method, int error) { + LOG(WARNING) << method << ": " << wrapper_->StrError(error); + callback_->OnError(this); +} + +} // namespace media |