| Index: media/audio/linux/alsa_input.cc
|
| diff --git a/media/audio/linux/alsa_input.cc b/media/audio/linux/alsa_input.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d2be907e82916598a35da0147291741d87b0707c
|
| --- /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);
|
| +}
|
| +
|
|
|