| // Copyright 2013 The Chromium Authors |
| // 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 "build/chromeos_buildflags.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. |
| constexpr int kMaxOutputStreams = 50; |
| |
| constexpr int kMinimumOutputBufferSize = 512; |
| constexpr int kMaximumOutputBufferSize = 8192; |
| constexpr int kDefaultInputBufferSize = 1024; |
| constexpr int kDefaultSampleRate = 48000; |
| constexpr int kDefaultChannelCount = 2; |
| |
| 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_(nullptr), |
| native_input_sample_rate_(kDefaultSampleRate), |
| native_channel_count_(kDefaultChannelCount), |
| 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_.ExtractAsDangling(), |
| input_context_.ExtractAsDangling()); |
| } |
| |
| 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, input_context_); |
| |
| // 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) { |
| UpdateNativeAudioHardwareInfo(); |
| |
| { |
| AutoPulseLock auto_lock(input_mainloop_); |
| auto* operation = pa_context_get_source_info_by_name( |
| input_context_, default_source_name_.c_str(), DefaultSourceInfoCallback, |
| this); |
| WaitForOperationCompletion(input_mainloop_, operation, input_context_); |
| } |
| |
| // We don't want to accidentally open a monitor device, so return invalid |
| // parameters for those. Note: The value of |default_source_is_monitor_| |
| // depends on the the call to pa_context_get_source_info_by_name() above. |
| if (device_id == AudioDeviceDescription::kDefaultDeviceId && |
| default_source_is_monitor_) { |
| return AudioParameters(); |
| } |
| |
| const int user_buffer_size = GetUserBufferSize(); |
| const int buffer_size = |
| user_buffer_size ? user_buffer_size : kDefaultInputBufferSize; |
| return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| ChannelLayoutConfig::Stereo(), |
| native_input_sample_rate_ ? native_input_sample_rate_ |
| : kDefaultSampleRate, |
| 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, |
| log_callback); |
| } |
| |
| 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, |
| log_callback); |
| } |
| |
| 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, log_callback); |
| } |
| |
| 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, log_callback); |
| } |
| |
| 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 BUILDFLAG(IS_CHROMEOS_ASH) |
| 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_ ? native_input_sample_rate_ |
| : kDefaultSampleRate; |
| ChannelLayoutConfig channel_layout_config = ChannelLayoutConfig::Guess( |
| native_channel_count_ ? native_channel_count_ : 2); |
| |
| 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_config = input_params.channel_layout_config(); |
| |
| 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_config, sample_rate, buffer_size); |
| } |
| |
| AudioOutputStream* AudioManagerPulse::MakeOutputStream( |
| const AudioParameters& params, |
| const std::string& device_id, |
| LogCallback log_callback) { |
| DCHECK(!device_id.empty()); |
| return new PulseAudioOutputStream(params, device_id, this, |
| std::move(log_callback)); |
| } |
| |
| AudioInputStream* AudioManagerPulse::MakeInputStream( |
| const AudioParameters& params, |
| const std::string& device_id, |
| LogCallback log_callback) { |
| return new PulseAudioInputStream(this, device_id, params, input_mainloop_, |
| input_context_, std::move(log_callback)); |
| } |
| |
| 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, input_context_); |
| |
| // Be careful about adding OS calls to this method. |
| // GetPreferredOutputStreamParameters() calls this method on a critical path. |
| // If the OS calls hang they will hang all device authorizations. |
| } |
| |
| void AudioManagerPulse::InputDevicesInfoCallback(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; |
| } |
| |
| // 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 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; |
| } |
| |
| 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 |