| // Copyright 2013 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/pulse/audio_manager_pulse.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/environment.h" |
| #include "base/logging.h" |
| #include "base/nix/xdg_util.h" |
| #include "base/stl_util.h" |
| #include "media/audio/audio_device_description.h" |
| #include "media/audio/pulse/pulse_input.h" |
| #include "media/audio/pulse/pulse_output.h" |
| #include "media/audio/pulse/pulse_util.h" |
| #include "media/base/audio_parameters.h" |
| #include "media/base/channel_layout.h" |
| |
| namespace media { |
| |
| using pulse::AutoPulseLock; |
| using pulse::WaitForOperationCompletion; |
| |
| // Maximum number of output streams that can be open simultaneously. |
| static const int kMaxOutputStreams = 50; |
| |
| // Define bounds for the output buffer size. |
| static const int kMinimumOutputBufferSize = 512; |
| static const int kMaximumOutputBufferSize = 8192; |
| |
| // Default input buffer size. |
| static const int kDefaultInputBufferSize = 1024; |
| |
| AudioManagerPulse::AudioManagerPulse(std::unique_ptr<AudioThread> audio_thread, |
| AudioLogFactory* audio_log_factory, |
| pa_threaded_mainloop* pa_mainloop, |
| pa_context* pa_context) |
| : AudioManagerBase(std::move(audio_thread), audio_log_factory), |
| input_mainloop_(pa_mainloop), |
| input_context_(pa_context), |
| devices_(NULL), |
| native_input_sample_rate_(0), |
| native_channel_count_(0), |
| default_source_is_monitor_(false) { |
| DCHECK(input_mainloop_); |
| DCHECK(input_context_); |
| SetMaxOutputStreamsAllowed(kMaxOutputStreams); |
| } |
| |
| AudioManagerPulse::~AudioManagerPulse() = default; |
| |
| void AudioManagerPulse::ShutdownOnAudioThread() { |
| AudioManagerBase::ShutdownOnAudioThread(); |
| // The Pulse objects are the last things to be destroyed since |
| // AudioManagerBase::ShutdownOnAudioThread() needs them. |
| pulse::DestroyPulse(input_mainloop_, input_context_); |
| } |
| |
| bool AudioManagerPulse::HasAudioOutputDevices() { |
| AudioDeviceNames devices; |
| GetAudioOutputDeviceNames(&devices); |
| return !devices.empty(); |
| } |
| |
| bool AudioManagerPulse::HasAudioInputDevices() { |
| AudioDeviceNames devices; |
| GetAudioInputDeviceNames(&devices); |
| return !devices.empty(); |
| } |
| |
| void AudioManagerPulse::GetAudioDeviceNames( |
| bool input, media::AudioDeviceNames* device_names) { |
| DCHECK(device_names->empty()); |
| DCHECK(input_mainloop_); |
| DCHECK(input_context_); |
| AutoPulseLock auto_lock(input_mainloop_); |
| devices_ = device_names; |
| pa_operation* operation = NULL; |
| if (input) { |
| operation = pa_context_get_source_info_list( |
| input_context_, InputDevicesInfoCallback, this); |
| } else { |
| operation = pa_context_get_sink_info_list( |
| input_context_, OutputDevicesInfoCallback, this); |
| } |
| WaitForOperationCompletion(input_mainloop_, operation); |
| |
| // Prepend the default device if the list is not empty. |
| if (!device_names->empty()) |
| device_names->push_front(AudioDeviceName::CreateDefault()); |
| } |
| |
| void AudioManagerPulse::GetAudioInputDeviceNames( |
| AudioDeviceNames* device_names) { |
| GetAudioDeviceNames(true, device_names); |
| } |
| |
| void AudioManagerPulse::GetAudioOutputDeviceNames( |
| AudioDeviceNames* device_names) { |
| GetAudioDeviceNames(false, device_names); |
| } |
| |
| AudioParameters AudioManagerPulse::GetInputStreamParameters( |
| const std::string& device_id) { |
| int user_buffer_size = GetUserBufferSize(); |
| int buffer_size = user_buffer_size ? |
| user_buffer_size : kDefaultInputBufferSize; |
| |
| // TODO(xians): add support for querying native channel layout for pulse. |
| UpdateNativeAudioHardwareInfo(); |
| // We don't want to accidentally open a monitor device, so return invalid |
| // parameters for those. |
| if (device_id == AudioDeviceDescription::kDefaultDeviceId && |
| default_source_is_monitor_) { |
| return AudioParameters(); |
| } |
| return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| CHANNEL_LAYOUT_STEREO, native_input_sample_rate_, |
| buffer_size); |
| } |
| |
| const char* AudioManagerPulse::GetName() { |
| return "PulseAudio"; |
| } |
| |
| AudioOutputStream* AudioManagerPulse::MakeLinearOutputStream( |
| const AudioParameters& params, |
| const LogCallback& log_callback) { |
| DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); |
| return MakeOutputStream(params, AudioDeviceDescription::kDefaultDeviceId); |
| } |
| |
| AudioOutputStream* AudioManagerPulse::MakeLowLatencyOutputStream( |
| const AudioParameters& params, |
| const std::string& device_id, |
| const LogCallback& log_callback) { |
| DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); |
| return MakeOutputStream(params, device_id.empty() |
| ? AudioDeviceDescription::kDefaultDeviceId |
| : device_id); |
| } |
| |
| AudioInputStream* AudioManagerPulse::MakeLinearInputStream( |
| const AudioParameters& params, |
| const std::string& device_id, |
| const LogCallback& log_callback) { |
| DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); |
| return MakeInputStream(params, device_id); |
| } |
| |
| AudioInputStream* AudioManagerPulse::MakeLowLatencyInputStream( |
| const AudioParameters& params, |
| const std::string& device_id, |
| const LogCallback& log_callback) { |
| DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); |
| return MakeInputStream(params, device_id); |
| } |
| |
| std::string AudioManagerPulse::GetDefaultInputDeviceID() { |
| // Do not use the real default input device since it is a fallback |
| // device rather than a default device. Using the default input device |
| // reported by Pulse Audio prevents, for example, input redirection |
| // using the PULSE_SOURCE environment variable. |
| return AudioManagerBase::GetDefaultInputDeviceID(); |
| } |
| |
| std::string AudioManagerPulse::GetDefaultOutputDeviceID() { |
| // Do not use the real default output device since it is a fallback |
| // device rather than a default device. Using the default output device |
| // reported by Pulse Audio prevents, for example, output redirection |
| // using the PULSE_SINK environment variable. |
| return AudioManagerBase::GetDefaultOutputDeviceID(); |
| } |
| |
| std::string AudioManagerPulse::GetAssociatedOutputDeviceID( |
| const std::string& input_device_id) { |
| #if defined(OS_CHROMEOS) |
| return AudioManagerBase::GetAssociatedOutputDeviceID(input_device_id); |
| #else |
| DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread()); |
| DCHECK(input_mainloop_); |
| DCHECK(input_context_); |
| |
| if (input_device_id == AudioDeviceDescription::kDefaultDeviceId) |
| return std::string(); |
| |
| std::string input_bus = |
| pulse::GetBusOfInput(input_mainloop_, input_context_, input_device_id); |
| return input_bus.empty() ? std::string() |
| : pulse::GetOutputCorrespondingTo( |
| input_mainloop_, input_context_, input_bus); |
| #endif |
| } |
| |
| AudioParameters AudioManagerPulse::GetPreferredOutputStreamParameters( |
| const std::string& output_device_id, |
| const AudioParameters& input_params) { |
| // TODO(tommi): Support |output_device_id|. |
| VLOG_IF(0, !output_device_id.empty()) << "Not implemented!"; |
| |
| int buffer_size = kMinimumOutputBufferSize; |
| |
| // Query native parameters where applicable; Pulse does not require these to |
| // be respected though, so prefer the input parameters for channel count. |
| UpdateNativeAudioHardwareInfo(); |
| int sample_rate = native_input_sample_rate_; |
| ChannelLayout channel_layout = GuessChannelLayout(native_channel_count_); |
| |
| if (input_params.IsValid()) { |
| // Use the system's output channel count for the DISCRETE layout. This is to |
| // avoid a crash due to the lack of support on the multi-channel beyond 8 in |
| // the PulseAudio layer. |
| if (input_params.channel_layout() != CHANNEL_LAYOUT_DISCRETE) |
| channel_layout = input_params.channel_layout(); |
| |
| buffer_size = |
| std::min(kMaximumOutputBufferSize, |
| std::max(buffer_size, input_params.frames_per_buffer())); |
| } |
| |
| int user_buffer_size = GetUserBufferSize(); |
| if (user_buffer_size) |
| buffer_size = user_buffer_size; |
| |
| return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, |
| sample_rate, buffer_size); |
| } |
| |
| AudioOutputStream* AudioManagerPulse::MakeOutputStream( |
| const AudioParameters& params, |
| const std::string& device_id) { |
| DCHECK(!device_id.empty()); |
| return new PulseAudioOutputStream(params, device_id, this); |
| } |
| |
| AudioInputStream* AudioManagerPulse::MakeInputStream( |
| const AudioParameters& params, const std::string& device_id) { |
| return new PulseAudioInputStream(this, device_id, params, |
| input_mainloop_, input_context_); |
| } |
| |
| void AudioManagerPulse::UpdateNativeAudioHardwareInfo() { |
| DCHECK(input_mainloop_); |
| DCHECK(input_context_); |
| AutoPulseLock auto_lock(input_mainloop_); |
| pa_operation* operation = pa_context_get_server_info( |
| input_context_, AudioHardwareInfoCallback, this); |
| WaitForOperationCompletion(input_mainloop_, operation); |
| operation = pa_context_get_source_info_by_name( |
| input_context_, default_source_name_.c_str(), DefaultSourceInfoCallback, |
| this); |
| WaitForOperationCompletion(input_mainloop_, operation); |
| } |
| |
| void AudioManagerPulse::InputDevicesInfoCallback(pa_context* context, |
| const pa_source_info* info, |
| int error, void *user_data) { |
| AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data); |
| |
| if (error) { |
| // Signal the pulse object that it is done. |
| pa_threaded_mainloop_signal(manager->input_mainloop_, 0); |
| return; |
| } |
| |
| // Exclude output monitor (i.e. loopback) devices. |
| if (info->monitor_of_sink != PA_INVALID_INDEX) |
| return; |
| |
| // If the device has ports, but none of them are available, skip it. |
| if (info->n_ports > 0) { |
| uint32_t port = 0; |
| for (; port != info->n_ports; ++port) { |
| if (info->ports[port]->available != PA_PORT_AVAILABLE_NO) |
| break; |
| } |
| if (port == info->n_ports) |
| return; |
| } |
| |
| manager->devices_->push_back(AudioDeviceName(info->description, info->name)); |
| } |
| |
| void AudioManagerPulse::OutputDevicesInfoCallback(pa_context* context, |
| const pa_sink_info* info, |
| int error, void *user_data) { |
| AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data); |
| |
| if (error) { |
| // Signal the pulse object that it is done. |
| pa_threaded_mainloop_signal(manager->input_mainloop_, 0); |
| return; |
| } |
| |
| manager->devices_->push_back(AudioDeviceName(info->description, |
| info->name)); |
| } |
| |
| void AudioManagerPulse::AudioHardwareInfoCallback(pa_context* context, |
| const pa_server_info* info, |
| void* user_data) { |
| AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data); |
| |
| manager->native_input_sample_rate_ = info->sample_spec.rate; |
| manager->native_channel_count_ = info->sample_spec.channels; |
| if (info->default_source_name) |
| manager->default_source_name_ = info->default_source_name; |
| pa_threaded_mainloop_signal(manager->input_mainloop_, 0); |
| } |
| |
| void AudioManagerPulse::DefaultSourceInfoCallback(pa_context* context, |
| const pa_source_info* info, |
| int eol, |
| void* user_data) { |
| AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data); |
| if (eol) { |
| // Signal the pulse object that it is done. |
| pa_threaded_mainloop_signal(manager->input_mainloop_, 0); |
| return; |
| } |
| |
| DCHECK(info); |
| manager->default_source_is_monitor_ = |
| info->monitor_of_sink != PA_INVALID_INDEX; |
| } |
| |
| } // namespace media |