diff options
Diffstat (limited to 'media/audio/linux/alsa_input.cc')
-rw-r--r-- | media/audio/linux/alsa_input.cc | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/media/audio/linux/alsa_input.cc b/media/audio/linux/alsa_input.cc new file mode 100644 index 0000000..d2be907 --- /dev/null +++ b/media/audio/linux/alsa_input.cc @@ -0,0 +1,214 @@ +// Copyright (c) 2010 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/logging.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "media/audio/linux/alsa_util.h" +#include "media/audio/linux/alsa_wrapper.h" + +namespace { + +const int kNumPacketsInRingBuffer = 3; + +// If a read failed with no audio data, try again after this duration. +const int kNoAudioReadAgainTimeoutMs = 20; + +const char kDefaultDevice1[] = "default"; +const char kDefaultDevice2[] = "plug:default"; + +} // namespace + +const char* AlsaPcmInputStream::kAutoSelectDevice = ""; + +AlsaPcmInputStream::AlsaPcmInputStream(const std::string& device_name, + const AudioParameters& params, + int samples_per_packet, + AlsaWrapper* wrapper) + : device_name_(device_name), + params_(params), + samples_per_packet_(samples_per_packet), + bytes_per_packet_(samples_per_packet_ * + (params.channels * params.bits_per_sample) / 8), + wrapper_(wrapper), + packet_duration_ms_( + (samples_per_packet_ * base::Time::kMillisecondsPerSecond) / + params.sample_rate), + callback_(NULL), + device_handle_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)) { +} + +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; + } + + int latency_us = packet_duration_ms_ * kNumPacketsInRingBuffer * + base::Time::kMicrosecondsPerMillisecond; + if (device_name_ == kAutoSelectDevice) { + device_handle_ = alsa_util::OpenCaptureDevice(wrapper_, kDefaultDevice1, + params_.channels, + params_.sample_rate, + pcm_format, latency_us); + if (!device_handle_) { + device_handle_ = alsa_util::OpenCaptureDevice(wrapper_, kDefaultDevice2, + params_.channels, + params_.sample_rate, + pcm_format, latency_us); + } + } else { + device_handle_ = alsa_util::OpenCaptureDevice(wrapper_, + device_name_.c_str(), + params_.channels, + params_.sample_rate, + pcm_format, latency_us); + } + + if (device_handle_) + audio_packet_.reset(new uint8[bytes_per_packet_]); + + return device_handle_ != NULL; +} + +void AlsaPcmInputStream::Start(AudioInputCallback* callback) { + DCHECK(!callback_ && callback); + callback_ = callback; + 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 a little later than when the packet might have got + // filled, to accommodate some delays in the audio driver. This could + // also give us a smooth read sequence going forward. + int64 delay_ms = packet_duration_ms_ + kNoAudioReadAgainTimeoutMs; + next_read_time_ = base::Time::Now() + base::TimeDelta::FromMilliseconds( + delay_ms); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + task_factory_.NewRunnableMethod(&AlsaPcmInputStream::ReadAudio), + delay_ms); + } +} + +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; +} + +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 < samples_per_packet_) { + // Not enough data yet or error happened. In both cases wait for a very + // small duration before checking again. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + task_factory_.NewRunnableMethod(&AlsaPcmInputStream::ReadAudio), + kNoAudioReadAgainTimeoutMs); + return; + } + + int num_packets = frames / samples_per_packet_; + while (num_packets--) { + int frames_read = wrapper_->PcmReadi(device_handle_, audio_packet_.get(), + samples_per_packet_); + if (frames_read == samples_per_packet_) { + callback_->OnData(this, audio_packet_.get(), bytes_per_packet_); + } else { + LOG(WARNING) << "PcmReadi returning less than expected frames: " + << frames_read << " vs. " << samples_per_packet_ + << ". Dropping this packet."; + } + } + + next_read_time_ += base::TimeDelta::FromMilliseconds(packet_duration_ms_); + int64 delay_ms = (next_read_time_ - base::Time::Now()).InMilliseconds(); + if (delay_ms < 0) { + LOG(WARNING) << "Audio read callback behind schedule by " + << (packet_duration_ms_ - delay_ms) << " (ms)."; + delay_ms = 0; + } + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + task_factory_.NewRunnableMethod(&AlsaPcmInputStream::ReadAudio), + delay_ms); +} + +void AlsaPcmInputStream::Stop() { + if (!device_handle_ || !callback_) + return; + + task_factory_.RevokeAll(); // Cancel the next scheduled read. + int error = wrapper_->PcmDrop(device_handle_); + if (error < 0) + HandleError("PcmDrop", error); +} + +void AlsaPcmInputStream::Close() { + // Check in case we were already closed or not initialized yet. + if (!device_handle_ || !callback_) + return; + + task_factory_.RevokeAll(); // Cancel the next scheduled read. + int error = alsa_util::CloseDevice(wrapper_, device_handle_); + if (error < 0) + HandleError("PcmClose", error); + + audio_packet_.reset(); + device_handle_ = NULL; + callback_->OnClose(this); +} + +void AlsaPcmInputStream::HandleError(const char* method, int error) { + LOG(WARNING) << method << ": " << wrapper_->StrError(error); + callback_->OnError(this, error); +} + |