Index: media/audio/pulse/pulse_util.cc |
diff --git a/media/audio/pulse/pulse_util.cc b/media/audio/pulse/pulse_util.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..963905865e748a340caaeccb3ed55d780d7ee194 |
--- /dev/null |
+++ b/media/audio/pulse/pulse_util.cc |
@@ -0,0 +1,248 @@ |
+// 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/pulse/pulse_util.h" |
+ |
+#include "base/logging.h" |
+#include "media/audio/audio_manager_base.h" |
+ |
+namespace media { |
+ |
+namespace { |
+ |
+pa_channel_position ChromiumToPAChannelPosition(Channels channel) { |
+ switch (channel) { |
+ // PulseAudio does not differentiate between left/right and |
+ // stereo-left/stereo-right, both translate to front-left/front-right. |
+ case LEFT: |
+ return PA_CHANNEL_POSITION_FRONT_LEFT; |
+ case RIGHT: |
+ return PA_CHANNEL_POSITION_FRONT_RIGHT; |
+ case CENTER: |
+ return PA_CHANNEL_POSITION_FRONT_CENTER; |
+ case LFE: |
+ return PA_CHANNEL_POSITION_LFE; |
+ case BACK_LEFT: |
+ return PA_CHANNEL_POSITION_REAR_LEFT; |
+ case BACK_RIGHT: |
+ return PA_CHANNEL_POSITION_REAR_RIGHT; |
+ case LEFT_OF_CENTER: |
+ return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; |
+ case RIGHT_OF_CENTER: |
+ return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; |
+ case BACK_CENTER: |
+ return PA_CHANNEL_POSITION_REAR_CENTER; |
+ case SIDE_LEFT: |
+ return PA_CHANNEL_POSITION_SIDE_LEFT; |
+ case SIDE_RIGHT: |
+ return PA_CHANNEL_POSITION_SIDE_RIGHT; |
+ case CHANNELS_MAX: |
+ return PA_CHANNEL_POSITION_INVALID; |
+ default: |
+ NOTREACHED() << "Invalid channel: " << channel; |
+ return PA_CHANNEL_POSITION_INVALID; |
+ } |
+} |
+ |
+} // namespace |
+ |
+PulseInputObject::PulseInputObject() |
+ : pa_mainloop_(NULL), |
+ pa_context_(NULL), |
+ devices(NULL), |
+ sample_rate_(0), |
+ initialized_(false) { |
+ Initialize(); |
+} |
+ |
+PulseInputObject::~PulseInputObject() { |
+ Terminate(); |
+} |
+ |
+void PulseInputObject::GetInputDevices(AudioDeviceNames* device_names) { |
+ DCHECK(pa_mainloop_); |
+ DCHECK(pa_context_); |
+ devices = device_names; |
+ AutoPulseLock auto_lock(pa_mainloop_); |
+ pa_operation* operation = pa_context_get_source_info_list( |
+ pa_context_, DevicesInfoCallback, this); |
+ WaitForOperationCompletion(pa_mainloop_, operation); |
+ |
+ if (!device_names->empty()) { |
+ device_names->push_front( |
+ AudioDeviceName(AudioManagerBase::kDefaultDeviceName, |
+ AudioManagerBase::kDefaultDeviceId)); |
+ } |
+} |
+ |
+int PulseInputObject::GetNativeSampleRate() { |
+ DCHECK(pa_mainloop_); |
+ DCHECK(pa_context_); |
+ AutoPulseLock auto_lock(pa_mainloop_); |
+ pa_operation* operation = pa_context_get_server_info( |
+ pa_context_, SamplerateInfoCallback, this); |
+ WaitForOperationCompletion(pa_mainloop_, operation); |
+ |
+ return sample_rate_; |
+} |
+ |
+void PulseInputObject::Initialize() { |
+ DCHECK(!initialized_); |
+ DCHECK(!pa_mainloop_); |
+ |
+ // Create a mainloop API and connect to the default server. |
+ // The mainloop is the internal asynchronous API event loop. |
+ pa_mainloop_ = pa_threaded_mainloop_new(); |
+ if (!pa_mainloop_) |
+ return; |
+ |
+ // Start the threaded mainloop. |
+ if (pa_threaded_mainloop_start(pa_mainloop_)) { |
+ Terminate(); |
+ return; |
+ } |
+ |
+ // Lock the event loop object, effectively blocking the event loop thread |
+ // from processing events. This is necessary. |
+ AutoPulseLock auto_lock(pa_mainloop_); |
+ |
+ pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(pa_mainloop_); |
+ pa_context_ = pa_context_new(pa_mainloop_api, "Chrome input"); |
+ DCHECK(pa_context_) << "Failed to create PA context"; |
+ if (!pa_context_) { |
+ return; |
+ } |
+ |
+ pa_context_set_state_callback(pa_context_, &ContextStateCallback, this); |
+ if (pa_context_connect(pa_context_, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL)) { |
+ DLOG(ERROR) << "Failed to connect to the context"; |
+ return; |
+ } |
+ |
+ // Wait until |pa_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(pa_context_); |
+ if (!PA_CONTEXT_IS_GOOD(context_state)) { |
+ return; |
+ } |
+ if (context_state == PA_CONTEXT_READY) |
+ break; |
+ pa_threaded_mainloop_wait(pa_mainloop_); |
+ } |
+ |
+ initialized_ = true; |
+} |
+ |
+void PulseInputObject::Terminate() { |
+ if (!pa_mainloop_) { |
+ DCHECK(!pa_context_); |
+ return; |
+ } |
+ |
+ { |
+ AutoPulseLock auto_lock(pa_mainloop_); |
+ if (pa_context_) { |
+ // Clear our state callback. |
+ pa_context_set_state_callback(pa_context_, NULL, NULL); |
+ pa_context_disconnect(pa_context_); |
+ pa_context_unref(pa_context_); |
+ pa_context_ = NULL; |
+ } |
+ } |
+ |
+ pa_threaded_mainloop_stop(pa_mainloop_); |
+ pa_threaded_mainloop_free(pa_mainloop_); |
+ pa_mainloop_ = NULL; |
+ initialized_ = false; |
+} |
+ |
+void PulseInputObject::ContextStateCallback(pa_context* context, |
+ void* user_data) { |
+ PulseInputObject* pulse = reinterpret_cast<PulseInputObject*>(user_data); |
+ pa_threaded_mainloop_signal(pulse->mainloop(), 0); |
+} |
+ |
+void PulseInputObject::DevicesInfoCallback(pa_context* context, |
+ const pa_source_info* info, |
+ int error, void *user_data) { |
+ PulseInputObject* pulse = reinterpret_cast<PulseInputObject*>(user_data); |
+ |
+ if (error) { |
+ // Signal the pulse object that it is done. |
+ pa_threaded_mainloop_signal(pulse->mainloop(), 0); |
+ return; |
+ } |
+ |
+ // Exclude the output devices. |
+ if (info->monitor_of_sink == PA_INVALID_INDEX) { |
+ pulse->devices->push_back(media::AudioDeviceName(info->description, |
+ info->name)); |
+ } |
+} |
+ |
+void PulseInputObject::SamplerateInfoCallback(pa_context* context, |
+ const pa_server_info* info, |
+ void* user_data) { |
+ PulseInputObject* pulse = reinterpret_cast<PulseInputObject*>(user_data); |
+ |
+ pulse->sample_rate_ = info->sample_spec.rate; |
+ pa_threaded_mainloop_signal(pulse->mainloop(), 0); |
+} |
+ |
+pa_sample_format_t BitsToPASampleFormat(int bits_per_sample) { |
+ switch (bits_per_sample) { |
+ case 8: |
+ return PA_SAMPLE_U8; |
+ case 16: |
+ return PA_SAMPLE_S16LE; |
+ case 24: |
+ return PA_SAMPLE_S24LE; |
+ case 32: |
+ return PA_SAMPLE_S32LE; |
+ default: |
+ NOTREACHED() << "Invalid bits per sample: " << bits_per_sample; |
+ return PA_SAMPLE_INVALID; |
+ } |
+} |
+ |
+// pa_stream_success_cb_t |
+void StreamSuccessCallback(pa_stream* s, int error, void* mainloop) { |
+ pa_threaded_mainloop* pa_mainloop = |
+ static_cast<pa_threaded_mainloop*>(mainloop); |
+ pa_threaded_mainloop_signal(pa_mainloop, 0); |
+} |
+ |
+pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout) { |
+ pa_channel_map channel_map; |
+ pa_channel_map_init(&channel_map); |
+ |
+ channel_map.channels = ChannelLayoutToChannelCount(channel_layout); |
+ for (Channels ch = LEFT; ch < CHANNELS_MAX; |
+ ch = static_cast<Channels>(ch + 1)) { |
+ int channel_index = ChannelOrder(channel_layout, ch); |
+ if (channel_index < 0) |
+ continue; |
+ |
+ channel_map.map[channel_index] = ChromiumToPAChannelPosition(ch); |
+ } |
+ |
+ return channel_map; |
+} |
+ |
+void WaitForOperationCompletion(pa_threaded_mainloop* pa_mainloop, |
+ pa_operation* operation) { |
+ if (!operation) { |
+ DLOG(WARNING) << "Operation is NULL"; |
+ return; |
+ } |
+ |
+ while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) |
+ pa_threaded_mainloop_wait(pa_mainloop); |
+ |
+ pa_operation_unref(operation); |
+} |
+ |
+} // namespace media |