Chromium Code Reviews| 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..cd150a385a1bb4e2728dd5e1e2a7befa27d1f7b2 |
| --- /dev/null |
| +++ b/media/audio/linux/cras_input.cc |
| @@ -0,0 +1,253 @@ |
| +// 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 "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), |
| + sample_rate_(params.sample_rate()), |
| + num_channels_(params.channels()), |
|
scherkus (not reviewing)
2012/06/21 21:39:46
if you save params_ do you need to save sample_rat
dgreid
2012/06/22 05:36:32
Nice, I don't really need these two or pcm_format_
|
| + params_(params), |
| + pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())), |
| + 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; |
| + } |
| + |
| + 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); |
| + |
| + // 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_ && callback); |
|
scherkus (not reviewing)
2012/06/21 21:39:46
split these DCHECKs into two so if they fail we kn
dgreid
2012/06/22 05:36:32
Done.
|
| + callback_ = callback; |
| + |
| + // Prepare |audio_format| and |stream_params| for the stream we |
| + // will create. |
| + cras_audio_format* audio_format = cras_audio_format_create( |
| + pcm_format_, |
| + sample_rate_, |
| + num_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); |
| +} |
| + |
| +void CrasInputStream::Stop() { |
| + if (!client_ || !callback_) |
|
scherkus (not reviewing)
2012/06/21 21:39:46
is this a DCHECK/programmer error?
dgreid
2012/06/22 05:36:32
Half and half, changed client_ to DCHECK. I left
|
| + return; |
| + |
| + // Removing the stream from the client stops audio. |
| + cras_client_rm_stream(client_, stream_id_); |
| + |
| + audio_manager_->DecreaseActiveInputStreamCount(); |
| +} |
| + |
| +// 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}; |
|
scherkus (not reviewing)
2012/06/21 21:39:46
nit: remove extra space before =
dgreid
2012/06/22 05:36:32
Done.
|
| + |
| + // 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 * 1e6) + latency_ts.tv_nsec * 1e-3; |
|
scherkus (not reviewing)
2012/06/21 21:39:46
magic numbers!
you can use base/time.h for kMicro
dgreid
2012/06/22 05:36:32
Done.
|
| + double frames_latency = latency_usec * sample_rate_ * 1e-6; |
| + 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_); |
| + |
| + // Return the max volume as a double to match what the alsa input does. |
| + return static_cast<double>(cras_client_get_system_max_capture_gain(client_)); |
|
scherkus (not reviewing)
2012/06/21 21:39:46
ditto here -- see my concerns about casting below
dgreid
2012/06/22 05:36:32
it's weird, see below.
|
| +} |
| + |
| +void CrasInputStream::SetVolume(double volume) { |
| + DCHECK(client_); |
| + |
| + cras_client_set_system_capture_gain(client_, static_cast<long>(volume)); |
| + |
| + // 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_) |
|
scherkus (not reviewing)
2012/06/21 21:39:46
SetVolume() when client_ == NULL is DCHECK() but g
dgreid
2012/06/22 05:36:32
Not that I can find. The only path to get here th
|
| + return 0.0; |
| + |
| + long current_volume = cras_client_get_system_capture_gain(client_); |
| + |
| + // Only want the gain, limit reported level to zero. |
| + if (current_volume < 0) |
| + return 0.0; |
| + |
| + return static_cast<double>(current_volume); |
|
scherkus (not reviewing)
2012/06/21 21:39:46
what's the range of value for current_volume?
doe
dgreid
2012/06/22 05:36:32
I thought this was really strange too, but went wi
|
| +} |
| + |
| +} // namespace media |