// 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 "base/command_line.h" #include "base/environment.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/nix/xdg_util.h" #include "base/stl_util.h" #if defined(USE_ALSA) #include "media/audio/alsa/audio_manager_alsa.h" #endif #include "media/audio/audio_parameters.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/channel_layout.h" #if defined(DLOPEN_PULSEAUDIO) #include "media/audio/pulse/pulse_stubs.h" using media_audio_pulse::kModulePulse; using media_audio_pulse::InitializeStubs; using media_audio_pulse::StubPathMap; #endif // defined(DLOPEN_PULSEAUDIO) 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; static const base::FilePath::CharType kPulseLib[] = FILE_PATH_LITERAL("libpulse.so.0"); // static AudioManager* AudioManagerPulse::Create(AudioLogFactory* audio_log_factory) { scoped_ptr<AudioManagerPulse> ret(new AudioManagerPulse(audio_log_factory)); if (ret->Init()) return ret.release(); DVLOG(1) << "PulseAudio is not available on the OS"; return NULL; } AudioManagerPulse::AudioManagerPulse(AudioLogFactory* audio_log_factory) : AudioManagerBase(audio_log_factory), input_mainloop_(NULL), input_context_(NULL), devices_(NULL), native_input_sample_rate_(0) { SetMaxOutputStreamsAllowed(kMaxOutputStreams); } AudioManagerPulse::~AudioManagerPulse() { Shutdown(); // The Pulse objects are the last things to be destroyed since Shutdown() // needs them. DestroyPulse(); } // Implementation of AudioManager. bool AudioManagerPulse::HasAudioOutputDevices() { AudioDeviceNames devices; GetAudioOutputDeviceNames(&devices); return !devices.empty(); } bool AudioManagerPulse::HasAudioInputDevices() { AudioDeviceNames devices; GetAudioInputDeviceNames(&devices); return !devices.empty(); } void AudioManagerPulse::ShowAudioInputSettings() { #if defined(USE_ALSA) AudioManagerAlsa::ShowLinuxAudioInputSettings(); #endif } 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(AudioManagerBase::kDefaultDeviceName, AudioManagerBase::kDefaultDeviceId)); } } 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. return AudioParameters( AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO, GetNativeSampleRate(), 16, buffer_size); } AudioOutputStream* AudioManagerPulse::MakeLinearOutputStream( const AudioParameters& params) { DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); return MakeOutputStream(params, AudioManagerBase::kDefaultDeviceId); } AudioOutputStream* AudioManagerPulse::MakeLowLatencyOutputStream( const AudioParameters& params, const std::string& device_id) { DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); return MakeOutputStream( params, device_id.empty() ? AudioManagerBase::kDefaultDeviceId : device_id); } AudioInputStream* AudioManagerPulse::MakeLinearInputStream( const AudioParameters& params, const std::string& device_id) { DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); return MakeInputStream(params, device_id); } AudioInputStream* AudioManagerPulse::MakeLowLatencyInputStream( const AudioParameters& params, const std::string& device_id) { DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); return MakeInputStream(params, device_id); } 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!"; ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO; int buffer_size = kMinimumOutputBufferSize; int bits_per_sample = 16; int input_channels = 0; int sample_rate; if (input_params.IsValid()) { bits_per_sample = input_params.bits_per_sample(); channel_layout = input_params.channel_layout(); input_channels = input_params.input_channels(); buffer_size = std::min(kMaximumOutputBufferSize, std::max(buffer_size, input_params.frames_per_buffer())); sample_rate = input_params.sample_rate(); } else { sample_rate = GetNativeSampleRate(); } int user_buffer_size = GetUserBufferSize(); if (user_buffer_size) buffer_size = user_buffer_size; return AudioParameters( AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, input_channels, sample_rate, bits_per_sample, buffer_size, AudioParameters::NO_EFFECTS); } 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_); } int AudioManagerPulse::GetNativeSampleRate() { DCHECK(input_mainloop_); DCHECK(input_context_); AutoPulseLock auto_lock(input_mainloop_); pa_operation* operation = pa_context_get_server_info( input_context_, SampleRateInfoCallback, this); WaitForOperationCompletion(input_mainloop_, operation); return native_input_sample_rate_; } bool AudioManagerPulse::Init() { DCHECK(!input_mainloop_); #if defined(DLOPEN_PULSEAUDIO) StubPathMap paths; // Check if the pulse library is avialbale. paths[kModulePulse].push_back(kPulseLib); if (!InitializeStubs(paths)) { VLOG(1) << "Failed on loading the Pulse library and symbols"; return false; } #endif // defined(DLOPEN_PULSEAUDIO) // Create a mainloop API and connect to the default server. // The mainloop is the internal asynchronous API event loop. input_mainloop_ = pa_threaded_mainloop_new(); if (!input_mainloop_) return false; // Start the threaded mainloop. if (pa_threaded_mainloop_start(input_mainloop_)) return false; // Lock the event loop object, effectively blocking the event loop thread // from processing events. This is necessary. AutoPulseLock auto_lock(input_mainloop_); pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(input_mainloop_); input_context_ = pa_context_new(pa_mainloop_api, "Chrome input"); if (!input_context_) return false; pa_context_set_state_callback(input_context_, &pulse::ContextStateCallback, input_mainloop_); if (pa_context_connect(input_context_, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL)) { VLOG(0) << "Failed to connect to the context. Error: " << pa_strerror(pa_context_errno(input_context_)); return false; } // Wait until |input_context_| is ready. pa_threaded_mainloop_wait() must be // called after pa_context_get_state() in case the context is already ready, // otherwise pa_threaded_mainloop_wait() will hang indefinitely. while (true) { pa_context_state_t context_state = pa_context_get_state(input_context_); if (!PA_CONTEXT_IS_GOOD(context_state)) return false; if (context_state == PA_CONTEXT_READY) break; pa_threaded_mainloop_wait(input_mainloop_); } return true; } void AudioManagerPulse::DestroyPulse() { if (!input_mainloop_) { DCHECK(!input_context_); return; } { AutoPulseLock auto_lock(input_mainloop_); if (input_context_) { // Clear our state callback. pa_context_set_state_callback(input_context_, NULL, NULL); pa_context_disconnect(input_context_); pa_context_unref(input_context_); input_context_ = NULL; } } pa_threaded_mainloop_stop(input_mainloop_); pa_threaded_mainloop_free(input_mainloop_); input_mainloop_ = NULL; } 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 the output devices. if (info->monitor_of_sink == PA_INVALID_INDEX) { 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::SampleRateInfoCallback(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; pa_threaded_mainloop_signal(manager->input_mainloop_, 0); } } // namespace media