OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "media/audio/pulse/audio_manager_pulse.h" | |
6 | |
7 #include "base/command_line.h" | |
8 #include "base/environment.h" | |
9 #include "base/file_path.h" | |
10 #include "base/logging.h" | |
11 #include "base/nix/xdg_util.h" | |
12 #include "base/process_util.h" | |
13 #include "base/stl_util.h" | |
14 #include "media/audio/audio_util.h" | |
15 #include "media/audio/pulse/pulse_input.h" | |
16 #include "media/audio/pulse/pulse_output.h" | |
17 #include "media/audio/pulse/pulse_stubs.h" | |
18 #include "media/audio/pulse/pulse_util.h" | |
19 | |
20 using media_audio_pulse::kModulePulse; | |
21 using media_audio_pulse::InitializeStubs; | |
22 using media_audio_pulse::StubPathMap; | |
23 | |
24 namespace media { | |
25 | |
26 namespace { | |
DaleCurtis
2013/02/20 00:17:38
No need for namespace {}. Media style is static co
no longer working on chromium
2013/02/20 14:43:38
Done.
| |
27 | |
28 // Maximum number of output streams that can be open simultaneously. | |
29 static const int kMaxOutputStreams = 50; | |
30 | |
31 static const base::FilePath::CharType kPulseLib[] = | |
32 FILE_PATH_LITERAL("libpulse.so.0"); | |
33 | |
DaleCurtis
2013/02/20 00:17:38
Extra line.
no longer working on chromium
2013/02/20 14:43:38
Done.
| |
34 } // namespace | |
35 | |
36 // static | |
37 AudioManager* AudioManagerPulse::Create() { | |
38 scoped_ptr<AudioManagerPulse> ret(new AudioManagerPulse()); | |
39 if (ret->Init()) | |
40 return ret.release(); | |
41 | |
42 DVLOG(1) << "PulseAudio is not available on the OS"; | |
43 return NULL; | |
44 } | |
45 | |
46 AudioManagerPulse::AudioManagerPulse() | |
47 : input_mainloop_(NULL), | |
48 input_context_(NULL), | |
49 devices_(NULL), | |
50 native_input_sample_rate_(0) { | |
51 SetMaxOutputStreamsAllowed(kMaxOutputStreams); | |
52 } | |
53 | |
54 AudioManagerPulse::~AudioManagerPulse() { | |
55 Terminate(); | |
DaleCurtis
2013/02/20 00:17:38
How about a more descriptive name than Terminate()
no longer working on chromium
2013/02/20 14:43:38
How about DestroyPulse()?
| |
56 Shutdown(); | |
57 } | |
58 | |
59 // Implementation of AudioManager. | |
60 bool AudioManagerPulse::HasAudioOutputDevices() { | |
61 // TODO(xians): implement this function. | |
62 return true; | |
63 } | |
64 | |
65 bool AudioManagerPulse::HasAudioInputDevices() { | |
66 // TODO(xians): implement this function. | |
67 return true; | |
68 } | |
69 | |
70 bool AudioManagerPulse::CanShowAudioInputSettings() { | |
DaleCurtis
2013/02/20 00:17:38
Delete.
no longer working on chromium
2013/02/20 14:43:38
Done.
| |
71 scoped_ptr<base::Environment> env(base::Environment::Create()); | |
72 | |
73 switch (base::nix::GetDesktopEnvironment(env.get())) { | |
74 case base::nix::DESKTOP_ENVIRONMENT_GNOME: | |
75 case base::nix::DESKTOP_ENVIRONMENT_KDE3: | |
76 case base::nix::DESKTOP_ENVIRONMENT_KDE4: | |
77 case base::nix::DESKTOP_ENVIRONMENT_UNITY: | |
78 return true; | |
79 case base::nix::DESKTOP_ENVIRONMENT_OTHER: | |
80 case base::nix::DESKTOP_ENVIRONMENT_XFCE: | |
81 return false; | |
82 } | |
83 // Unless GetDesktopEnvironment() badly misbehaves, this should never happen. | |
84 NOTREACHED(); | |
85 return false; | |
86 } | |
87 | |
88 void AudioManagerPulse::ShowAudioInputSettings() { | |
DaleCurtis
2013/02/20 00:17:38
Same as AudioManagerLinux, just make it a static t
no longer working on chromium
2013/02/20 14:43:38
Done.
| |
89 scoped_ptr<base::Environment> env(base::Environment::Create()); | |
90 CommandLine command_line(CommandLine::NO_PROGRAM); | |
91 switch (base::nix::GetDesktopEnvironment(env.get())) { | |
92 case base::nix::DESKTOP_ENVIRONMENT_GNOME: | |
93 command_line.SetProgram(base::FilePath("gnome-volume-control")); | |
94 break; | |
95 case base::nix::DESKTOP_ENVIRONMENT_KDE3: | |
96 case base::nix::DESKTOP_ENVIRONMENT_KDE4: | |
97 command_line.SetProgram(base::FilePath("kmix")); | |
98 break; | |
99 case base::nix::DESKTOP_ENVIRONMENT_UNITY: | |
100 command_line.SetProgram(base::FilePath("gnome-control-center")); | |
101 command_line.AppendArg("sound"); | |
102 command_line.AppendArg("input"); | |
103 break; | |
104 default: | |
105 LOG(ERROR) << "Failed to show audio input settings: we don't know " | |
106 << "what command to use for your desktop environment."; | |
107 return; | |
108 } | |
109 base::LaunchProcess(command_line, base::LaunchOptions(), NULL); | |
110 } | |
111 | |
112 void AudioManagerPulse::GetAudioInputDeviceNames( | |
113 media::AudioDeviceNames* device_names) { | |
114 DCHECK(device_names->empty()); | |
115 DCHECK(input_mainloop_); | |
116 DCHECK(input_context_); | |
117 devices_ = device_names; | |
118 AutoPulseLock auto_lock(input_mainloop_); | |
119 pa_operation* operation = pa_context_get_source_info_list( | |
120 input_context_, DevicesInfoCallback, this); | |
121 WaitForOperationCompletion(input_mainloop_, operation); | |
122 | |
123 if (!device_names->empty()) { | |
DaleCurtis
2013/02/20 00:17:38
Should this be "if (device_name->empty())" ? Othe
no longer working on chromium
2013/02/20 14:43:38
No, we append the default device on top of the lis
| |
124 device_names->push_front( | |
125 AudioDeviceName(AudioManagerBase::kDefaultDeviceName, | |
126 AudioManagerBase::kDefaultDeviceId)); | |
127 } | |
128 } | |
129 | |
130 AudioOutputStream* AudioManagerPulse::MakeLinearOutputStream( | |
131 const AudioParameters& params) { | |
132 DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); | |
133 return MakeOutputStream(params); | |
134 } | |
135 | |
136 AudioOutputStream* AudioManagerPulse::MakeLowLatencyOutputStream( | |
137 const AudioParameters& params) { | |
138 DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); | |
139 return MakeOutputStream(params); | |
140 } | |
141 | |
142 AudioInputStream* AudioManagerPulse::MakeLinearInputStream( | |
143 const AudioParameters& params, const std::string& device_id) { | |
144 DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); | |
145 return MakeInputStream(params, device_id); | |
146 } | |
147 | |
148 AudioInputStream* AudioManagerPulse::MakeLowLatencyInputStream( | |
149 const AudioParameters& params, const std::string& device_id) { | |
150 DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); | |
151 return MakeInputStream(params, device_id); | |
152 } | |
153 | |
154 AudioOutputStream* AudioManagerPulse::MakeOutputStream( | |
155 const AudioParameters& params) { | |
156 return new PulseAudioOutputStream(params, this); | |
157 } | |
158 | |
159 AudioInputStream* AudioManagerPulse::MakeInputStream( | |
160 const AudioParameters& params, const std::string& device_id) { | |
161 return new PulseAudioInputStream(this, device_id, params, | |
162 input_mainloop_, input_context_); | |
163 } | |
164 | |
165 AudioParameters AudioManagerPulse::GetPreferredLowLatencyOutputStreamParameters( | |
166 const AudioParameters& input_params) { | |
167 // TODO(xians): figure out the optimized buffer size for the Pulse IO. | |
168 int buffer_size = GetAudioHardwareBufferSize(); | |
169 if (input_params.frames_per_buffer() < buffer_size) | |
170 buffer_size = input_params.frames_per_buffer(); | |
171 | |
172 // TODO(dalecurtis): This should include bits per channel and channel layout | |
173 // eventually. | |
174 return AudioParameters( | |
175 AudioParameters::AUDIO_PCM_LOW_LATENCY, input_params.channel_layout(), | |
176 input_params.sample_rate(), 16, buffer_size); | |
177 } | |
178 | |
179 int AudioManagerPulse::GetNativeSampleRate() { | |
180 DCHECK(input_mainloop_); | |
181 DCHECK(input_context_); | |
182 AutoPulseLock auto_lock(input_mainloop_); | |
183 pa_operation* operation = pa_context_get_server_info( | |
184 input_context_, SamplerateInfoCallback, this); | |
185 WaitForOperationCompletion(input_mainloop_, operation); | |
186 | |
187 return native_input_sample_rate_; | |
188 } | |
189 | |
190 bool AudioManagerPulse::Init() { | |
191 DCHECK(!input_mainloop_); | |
192 | |
193 StubPathMap paths; | |
194 | |
195 // Check if the pulse library is avialbale. | |
196 paths[kModulePulse].push_back(kPulseLib); | |
197 if (!InitializeStubs(paths)) { | |
198 DLOG(WARNING) << "Failed on loading the Pulse library and symbols"; | |
199 return false; | |
200 } | |
201 | |
202 // Create a mainloop API and connect to the default server. | |
203 // The mainloop is the internal asynchronous API event loop. | |
204 input_mainloop_ = pa_threaded_mainloop_new(); | |
205 if (!input_mainloop_) | |
206 return false; | |
207 | |
208 // Start the threaded mainloop. | |
209 if (pa_threaded_mainloop_start(input_mainloop_)) { | |
210 Terminate(); | |
211 return false; | |
212 } | |
213 | |
214 // Lock the event loop object, effectively blocking the event loop thread | |
215 // from processing events. This is necessary. | |
216 AutoPulseLock auto_lock(input_mainloop_); | |
217 | |
218 pa_mainloop_api* pa_mainloop_api = | |
219 pa_threaded_mainloop_get_api(input_mainloop_); | |
220 input_context_ = pa_context_new(pa_mainloop_api, "Chrome input"); | |
221 DCHECK(input_context_) << "Failed to create PA context"; | |
222 if (!input_context_) { | |
223 return false; | |
224 } | |
225 | |
226 pa_context_set_state_callback(input_context_, &ContextStateCallback, | |
227 input_mainloop_); | |
228 if (pa_context_connect(input_context_, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL)) { | |
229 DLOG(ERROR) << "Failed to connect to the context"; | |
230 return false; | |
231 } | |
232 | |
233 // Wait until |input_context_| is ready. pa_threaded_mainloop_wait() must be | |
234 // called after pa_context_get_state() in case the context is already ready, | |
235 // otherwise pa_threaded_mainloop_wait() will hang indefinitely. | |
236 while (true) { | |
237 pa_context_state_t context_state = pa_context_get_state(input_context_); | |
238 if (!PA_CONTEXT_IS_GOOD(context_state)) { | |
239 return false; | |
240 } | |
241 if (context_state == PA_CONTEXT_READY) | |
242 break; | |
243 pa_threaded_mainloop_wait(input_mainloop_); | |
244 } | |
245 | |
246 return true; | |
247 } | |
248 | |
249 void AudioManagerPulse::Terminate() { | |
250 if (!input_mainloop_) { | |
251 DCHECK(!input_context_); | |
252 return; | |
253 } | |
254 | |
255 { | |
256 AutoPulseLock auto_lock(input_mainloop_); | |
257 if (input_context_) { | |
258 // Clear our state callback. | |
259 pa_context_set_state_callback(input_context_, NULL, NULL); | |
260 pa_context_disconnect(input_context_); | |
261 pa_context_unref(input_context_); | |
262 input_context_ = NULL; | |
263 } | |
264 } | |
265 | |
266 pa_threaded_mainloop_stop(input_mainloop_); | |
267 pa_threaded_mainloop_free(input_mainloop_); | |
268 input_mainloop_ = NULL; | |
269 } | |
270 | |
271 void AudioManagerPulse::DevicesInfoCallback(pa_context* context, | |
272 const pa_source_info* info, | |
273 int error, void *user_data) { | |
274 AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data); | |
275 | |
276 if (error) { | |
277 // Signal the pulse object that it is done. | |
278 pa_threaded_mainloop_signal(manager->input_mainloop_, 0); | |
279 return; | |
DaleCurtis
2013/02/20 00:17:38
indent is off.
no longer working on chromium
2013/02/20 14:43:38
Done.
| |
280 } | |
281 | |
282 // Exclude the output devices. | |
283 if (info->monitor_of_sink == PA_INVALID_INDEX) { | |
284 manager->devices_->push_back(media::AudioDeviceName(info->description, | |
285 info->name)); | |
DaleCurtis
2013/02/20 00:17:38
Indent is off.
no longer working on chromium
2013/02/20 14:43:38
Done.
| |
286 } | |
287 } | |
288 | |
289 void AudioManagerPulse::SamplerateInfoCallback(pa_context* context, | |
290 const pa_server_info* info, | |
291 void* user_data) { | |
292 AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data); | |
293 | |
294 manager->native_input_sample_rate_ = info->sample_spec.rate; | |
295 pa_threaded_mainloop_signal(manager->input_mainloop_, 0); | |
296 } | |
297 | |
298 } // namespace media | |
OLD | NEW |