| Index: media/audio/linux/cras_input.cc
|
| diff --git a/media/audio/linux/cras_input.cc b/media/audio/linux/cras_input.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6de405ef7f21bf5bf22ccf54b16d7b2d1ebf7147
|
| --- /dev/null
|
| +++ b/media/audio/linux/cras_input.cc
|
| @@ -0,0 +1,278 @@
|
| +// 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/cras_input.h"
|
| +
|
| +#include <math.h>
|
| +
|
| +#include "base/basictypes.h"
|
| +#include "base/bind.h"
|
| +#include "base/logging.h"
|
| +#include "base/time.h"
|
| +#include "media/audio/audio_manager.h"
|
| +#include "media/audio/linux/alsa_util.h"
|
| +#include "media/audio/linux/audio_manager_linux.h"
|
| +
|
| +namespace media {
|
| +
|
| +CrasInputStream::CrasInputStream(const AudioParameters& params,
|
| + AudioManagerLinux* manager)
|
| + : audio_manager_(manager),
|
| + bytes_per_frame_(0),
|
| + callback_(NULL),
|
| + client_(NULL),
|
| + params_(params),
|
| + started_(false),
|
| + stream_id_(0) {
|
| + DCHECK(audio_manager_);
|
| +}
|
| +
|
| +CrasInputStream::~CrasInputStream() {
|
| + DCHECK(!client_);
|
| +}
|
| +
|
| +bool CrasInputStream::Open() {
|
| + if (client_) {
|
| + NOTREACHED() << "CrasInputStream already open";
|
| + return false; // Already open.
|
| + }
|
| +
|
| + // Sanity check input values.
|
| + if (params_.sample_rate() <= 0) {
|
| + DLOG(WARNING) << "Unsupported audio frequency.";
|
| + return false;
|
| + }
|
| +
|
| + if (AudioParameters::AUDIO_PCM_LINEAR != params_.format() &&
|
| + AudioParameters::AUDIO_PCM_LOW_LATENCY != params_.format()) {
|
| + DLOG(WARNING) << "Unsupported audio format.";
|
| + return false;
|
| + }
|
| +
|
| + snd_pcm_format_t pcm_format =
|
| + alsa_util::BitsToFormat(params_.bits_per_sample());
|
| + if (pcm_format == SND_PCM_FORMAT_UNKNOWN) {
|
| + DLOG(WARNING) << "Unsupported bits/sample: " << params_.bits_per_sample();
|
| + return false;
|
| + }
|
| +
|
| + // Create the client and connect to the CRAS server.
|
| + if (cras_client_create(&client_) < 0) {
|
| + DLOG(WARNING) << "Couldn't create CRAS client.\n";
|
| + client_ = NULL;
|
| + return false;
|
| + }
|
| +
|
| + if (cras_client_connect(client_)) {
|
| + DLOG(WARNING) << "Couldn't connect CRAS client.\n";
|
| + cras_client_destroy(client_);
|
| + client_ = NULL;
|
| + return false;
|
| + }
|
| +
|
| + // Then start running the client.
|
| + if (cras_client_run_thread(client_)) {
|
| + DLOG(WARNING) << "Couldn't run CRAS client.\n";
|
| + cras_client_destroy(client_);
|
| + client_ = NULL;
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void CrasInputStream::Close() {
|
| + if (client_) {
|
| + cras_client_stop(client_);
|
| + cras_client_destroy(client_);
|
| + client_ = NULL;
|
| + }
|
| +
|
| + if (callback_) {
|
| + callback_->OnClose(this);
|
| + callback_ = NULL;
|
| + }
|
| +
|
| + // Signal to the manager that we're closed and can be removed.
|
| + // Should be last call in the method as it deletes "this".
|
| + audio_manager_->ReleaseInputStream(this);
|
| +}
|
| +
|
| +void CrasInputStream::Start(AudioInputCallback* callback) {
|
| + DCHECK(client_);
|
| + DCHECK(callback);
|
| +
|
| + // If already playing, stop before re-starting.
|
| + if (started_)
|
| + return;
|
| +
|
| + callback_ = callback;
|
| +
|
| + // Prepare |audio_format| and |stream_params| for the stream we
|
| + // will create.
|
| + cras_audio_format* audio_format = cras_audio_format_create(
|
| + alsa_util::BitsToFormat(params_.bits_per_sample()),
|
| + params_.sample_rate(),
|
| + params_.channels());
|
| + if (!audio_format) {
|
| + DLOG(WARNING) << "Error setting up audio parameters.";
|
| + callback_->OnError(this, -ENOMEM);
|
| + callback_ = NULL;
|
| + return;
|
| + }
|
| +
|
| + unsigned int frames_per_packet = params_.frames_per_buffer();
|
| + cras_stream_params* stream_params = cras_client_stream_params_create(
|
| + CRAS_STREAM_INPUT,
|
| + frames_per_packet, // Total latency.
|
| + frames_per_packet, // Call back when this many ready.
|
| + frames_per_packet, // Minimum Callback level ignored for capture streams.
|
| + CRAS_STREAM_TYPE_DEFAULT,
|
| + 0, // Unused flags.
|
| + this,
|
| + CrasInputStream::SamplesReady,
|
| + CrasInputStream::StreamError,
|
| + audio_format);
|
| + if (!stream_params) {
|
| + DLOG(WARNING) << "Error setting up stream parameters.";
|
| + callback_->OnError(this, -ENOMEM);
|
| + callback_ = NULL;
|
| + cras_audio_format_destroy(audio_format);
|
| + return;
|
| + }
|
| +
|
| + // Before starting the stream, save the number of bytes in a frame for use in
|
| + // the callback.
|
| + bytes_per_frame_ = cras_client_format_bytes_per_frame(audio_format);
|
| +
|
| + // Adding the stream will start the audio callbacks.
|
| + if (cras_client_add_stream(client_, &stream_id_, stream_params) == 0) {
|
| + audio_manager_->IncreaseActiveInputStreamCount();
|
| + } else {
|
| + DLOG(WARNING) << "Failed to add the stream.";
|
| + callback_->OnError(this, -EIO);
|
| + callback_ = NULL;
|
| + }
|
| +
|
| + // Done with config params.
|
| + cras_audio_format_destroy(audio_format);
|
| + cras_client_stream_params_destroy(stream_params);
|
| +
|
| + started_ = true;
|
| +}
|
| +
|
| +void CrasInputStream::Stop() {
|
| + DCHECK(client_);
|
| +
|
| + if (!callback_ || !started_)
|
| + return;
|
| +
|
| + // Removing the stream from the client stops audio.
|
| + cras_client_rm_stream(client_, stream_id_);
|
| +
|
| + audio_manager_->DecreaseActiveInputStreamCount();
|
| +
|
| + started_ = false;
|
| +}
|
| +
|
| +// Static callback asking for samples. Run on high priority thread.
|
| +int CrasInputStream::SamplesReady(cras_client* client,
|
| + cras_stream_id_t stream_id,
|
| + uint8* samples,
|
| + size_t frames,
|
| + const timespec* sample_ts,
|
| + void* arg) {
|
| + CrasInputStream* me = static_cast<CrasInputStream*>(arg);
|
| + me->ReadAudio(frames, samples, sample_ts);
|
| + return frames;
|
| +}
|
| +
|
| +// Static callback for stream errors.
|
| +int CrasInputStream::StreamError(cras_client* client,
|
| + cras_stream_id_t stream_id,
|
| + int err,
|
| + void* arg) {
|
| + CrasInputStream* me = static_cast<CrasInputStream*>(arg);
|
| + me->NotifyStreamError(err);
|
| + return 0;
|
| +}
|
| +
|
| +void CrasInputStream::ReadAudio(size_t frames,
|
| + uint8* buffer,
|
| + const timespec* sample_ts) {
|
| + DCHECK(callback_);
|
| +
|
| + timespec latency_ts = {0, 0};
|
| +
|
| + // Determine latency and pass that on to the sink. sample_ts is the wall time
|
| + // indicating when the first sample in the buffer was captured. Convert that
|
| + // to latency in bytes.
|
| + cras_client_calc_capture_latency(sample_ts, &latency_ts);
|
| + double latency_usec =
|
| + latency_ts.tv_sec * base::Time::kMicrosecondsPerSecond +
|
| + latency_ts.tv_nsec / base::Time::kNanosecondsPerMicrosecond;
|
| + double frames_latency =
|
| + latency_usec * params_.sample_rate() / base::Time::kMicrosecondsPerSecond;
|
| + unsigned int bytes_latency =
|
| + static_cast<unsigned int>(frames_latency * bytes_per_frame_);
|
| +
|
| + // 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.
|
| + double normalized_volume = 0.0;
|
| + QueryAgcVolume(&normalized_volume);
|
| +
|
| + callback_->OnData(this,
|
| + buffer,
|
| + frames * bytes_per_frame_,
|
| + bytes_latency,
|
| + normalized_volume);
|
| +}
|
| +
|
| +void CrasInputStream::NotifyStreamError(int err) {
|
| + if (callback_)
|
| + callback_->OnError(this, err);
|
| +}
|
| +
|
| +double CrasInputStream::GetMaxVolume() {
|
| + DCHECK(client_);
|
| +
|
| + // Capture gain is returned as dB * 100 (150 => 1.5dBFS). Convert the dB
|
| + // value to a ratio before returning.
|
| + double dB = cras_client_get_system_max_capture_gain(client_) / 100.0;
|
| + return GetVolumeRatioFromDecibels(dB);
|
| +}
|
| +
|
| +void CrasInputStream::SetVolume(double volume) {
|
| + DCHECK(client_);
|
| +
|
| + // Convert from the passed volume ratio, to dB * 100.
|
| + double dB = GetDecibelsFromVolumeRatio(volume);
|
| + cras_client_set_system_capture_gain(client_, static_cast<long>(dB * 100.0));
|
| +
|
| + // 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 CrasInputStream::GetVolume() {
|
| + if (!client_)
|
| + return 0.0;
|
| +
|
| + long dB = cras_client_get_system_capture_gain(client_) / 100.0;
|
| + return GetVolumeRatioFromDecibels(dB);
|
| +}
|
| +
|
| +double CrasInputStream::GetVolumeRatioFromDecibels(double dB) const {
|
| + return pow(10, dB / 20.0);
|
| +}
|
| +
|
| +double CrasInputStream::GetDecibelsFromVolumeRatio(double volume_ratio) const {
|
| + return 20 * log10(volume_ratio);
|
| +}
|
| +
|
| +} // namespace media
|
|
|