// 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/cras/cras_input.h" #include #include "base/logging.h" #include "base/macros.h" #include "base/time/time.h" #include "media/audio/audio_manager.h" #include "media/audio/cras/audio_manager_cras.h" namespace media { CrasInputStream::CrasInputStream(const AudioParameters& params, AudioManagerCras* manager, const std::string& device_id) : audio_manager_(manager), bytes_per_frame_(0), callback_(NULL), client_(NULL), params_(params), started_(false), stream_id_(0), stream_direction_(CRAS_STREAM_INPUT), pin_device_(NO_DEVICE), is_loopback_(device_id == AudioManagerBase::kLoopbackInputDeviceId) { DCHECK(audio_manager_); audio_bus_ = AudioBus::Create(params_); } 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 = AudioManagerCras::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; } if (is_loopback_) { if (cras_client_connected_wait(client_) < 0) { DLOG(WARNING) << "Couldn't synchronize data."; // TODO(chinyue): Add a DestroyClientOnError method to de-duplicate the // cleanup code. cras_client_destroy(client_); client_ = NULL; return false; } pin_device_ = cras_client_get_first_dev_type_idx(client_, CRAS_NODE_TYPE_POST_MIX_PRE_DSP, CRAS_STREAM_INPUT); if (pin_device_ < 0) { DLOG(WARNING) << "Couldn't find CRAS loopback device."; cras_client_destroy(client_); client_ = NULL; return false; } } return true; } void CrasInputStream::Close() { Stop(); if (client_) { cras_client_stop(client_); cras_client_destroy(client_); client_ = 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); // Channel map to CRAS_CHANNEL, values in the same order of // corresponding source in Chromium defined Channels. static const int kChannelMap[] = { CRAS_CH_FL, CRAS_CH_FR, CRAS_CH_FC, CRAS_CH_LFE, CRAS_CH_RL, CRAS_CH_RR, CRAS_CH_FLC, CRAS_CH_FRC, CRAS_CH_RC, CRAS_CH_SL, CRAS_CH_SR }; static_assert(arraysize(kChannelMap) == CHANNELS_MAX + 1, "kChannelMap array size should match"); // If already playing, stop before re-starting. if (started_) return; StartAgc(); callback_ = callback; // Prepare |audio_format| and |stream_params| for the stream we // will create. cras_audio_format* audio_format = cras_audio_format_create( AudioManagerCras::BitsToFormat(params_.bits_per_sample()), params_.sample_rate(), params_.channels()); if (!audio_format) { DLOG(WARNING) << "Error setting up audio parameters."; callback_->OnError(this); callback_ = NULL; return; } // Initialize channel layout to all -1 to indicate that none of // the channels is set in the layout. int8_t layout[CRAS_CH_MAX]; for (size_t i = 0; i < arraysize(layout); ++i) layout[i] = -1; // Converts to CRAS defined channels. ChannelOrder will return -1 // for channels that are not present in params_.channel_layout(). for (size_t i = 0; i < arraysize(kChannelMap); ++i) { layout[kChannelMap[i]] = ChannelOrder(params_.channel_layout(), static_cast(i)); } if (cras_audio_format_set_channel_layout(audio_format, layout) != 0) { DLOG(WARNING) << "Error setting channel layout."; callback->OnError(this); return; } uint32_t flags = 0; if (params_.effects() & AudioParameters::PlatformEffectsMask::HOTWORD) flags = HOTWORD_STREAM; unsigned int frames_per_packet = params_.frames_per_buffer(); cras_stream_params* stream_params = cras_client_stream_params_create( stream_direction_, 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, flags, this, CrasInputStream::SamplesReady, CrasInputStream::StreamError, audio_format); if (!stream_params) { DLOG(WARNING) << "Error setting up stream parameters."; callback_->OnError(this); 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_pinned_stream(client_, pin_device_, &stream_id_, stream_params)) { DLOG(WARNING) << "Failed to add the stream."; callback_->OnError(this); 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; StopAgc(); // Removing the stream from the client stops audio. cras_client_rm_stream(client_, stream_id_); started_ = false; callback_ = NULL; } // Static callback asking for samples. Run on high priority thread. int CrasInputStream::SamplesReady(cras_client* client, cras_stream_id_t stream_id, uint8_t* samples, size_t frames, const timespec* sample_ts, void* arg) { CrasInputStream* me = static_cast(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(arg); me->NotifyStreamError(err); return 0; } void CrasInputStream::ReadAudio(size_t frames, uint8_t* 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(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; GetAgcVolume(&normalized_volume); audio_bus_->FromInterleaved( buffer, audio_bus_->frames(), params_.bits_per_sample() / 8); callback_->OnData(this, audio_bus_.get(), bytes_latency, normalized_volume); } void CrasInputStream::NotifyStreamError(int err) { if (callback_) callback_->OnError(this); } 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(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); } bool CrasInputStream::IsMuted() { return false; } 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