| // 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 |