summaryrefslogtreecommitdiffstats
path: root/media/audio/linux/alsa_input.cc
diff options
context:
space:
mode:
Diffstat (limited to 'media/audio/linux/alsa_input.cc')
-rw-r--r--media/audio/linux/alsa_input.cc214
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);
+}
+