1 // Copyright (c) 2012 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.
5 #include "media/audio/pulse/pulse_util.h"
7 #include "base/logging.h"
8 #include "base/time/time.h"
9 #include "media/audio/audio_manager_base.h"
10 #include "media/audio/audio_parameters.h"
18 #if defined(GOOGLE_CHROME_BUILD)
19 static const char kBrowserDisplayName
[] = "google-chrome";
21 static const char kBrowserDisplayName
[] = "chromium-browser";
24 pa_channel_position
ChromiumToPAChannelPosition(Channels channel
) {
26 // PulseAudio does not differentiate between left/right and
27 // stereo-left/stereo-right, both translate to front-left/front-right.
29 return PA_CHANNEL_POSITION_FRONT_LEFT
;
31 return PA_CHANNEL_POSITION_FRONT_RIGHT
;
33 return PA_CHANNEL_POSITION_FRONT_CENTER
;
35 return PA_CHANNEL_POSITION_LFE
;
37 return PA_CHANNEL_POSITION_REAR_LEFT
;
39 return PA_CHANNEL_POSITION_REAR_RIGHT
;
41 return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER
;
43 return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER
;
45 return PA_CHANNEL_POSITION_REAR_CENTER
;
47 return PA_CHANNEL_POSITION_SIDE_LEFT
;
49 return PA_CHANNEL_POSITION_SIDE_RIGHT
;
51 NOTREACHED() << "Invalid channel: " << channel
;
52 return PA_CHANNEL_POSITION_INVALID
;
56 class ScopedPropertyList
{
58 ScopedPropertyList() : property_list_(pa_proplist_new()) {}
59 ~ScopedPropertyList() { pa_proplist_free(property_list_
); }
61 pa_proplist
* get() const { return property_list_
; }
64 pa_proplist
* property_list_
;
65 DISALLOW_COPY_AND_ASSIGN(ScopedPropertyList
);
70 // static, pa_stream_success_cb_t
71 void StreamSuccessCallback(pa_stream
* s
, int error
, void* mainloop
) {
72 pa_threaded_mainloop
* pa_mainloop
=
73 static_cast<pa_threaded_mainloop
*>(mainloop
);
74 pa_threaded_mainloop_signal(pa_mainloop
, 0);
77 // |pa_context| and |pa_stream| state changed cb.
78 void ContextStateCallback(pa_context
* context
, void* mainloop
) {
79 pa_threaded_mainloop
* pa_mainloop
=
80 static_cast<pa_threaded_mainloop
*>(mainloop
);
81 pa_threaded_mainloop_signal(pa_mainloop
, 0);
84 pa_sample_format_t
BitsToPASampleFormat(int bits_per_sample
) {
85 switch (bits_per_sample
) {
89 return PA_SAMPLE_S16LE
;
91 return PA_SAMPLE_S24LE
;
93 return PA_SAMPLE_S32LE
;
95 NOTREACHED() << "Invalid bits per sample: " << bits_per_sample
;
96 return PA_SAMPLE_INVALID
;
100 pa_channel_map
ChannelLayoutToPAChannelMap(ChannelLayout channel_layout
) {
101 pa_channel_map channel_map
;
102 if (channel_layout
== CHANNEL_LAYOUT_MONO
) {
103 // CHANNEL_LAYOUT_MONO only specifies audio on the C channel, but we
104 // want PulseAudio to play single-channel audio on more than just that.
105 pa_channel_map_init_mono(&channel_map
);
107 pa_channel_map_init(&channel_map
);
109 channel_map
.channels
= ChannelLayoutToChannelCount(channel_layout
);
110 for (Channels ch
= LEFT
; ch
<= CHANNELS_MAX
;
111 ch
= static_cast<Channels
>(ch
+ 1)) {
112 int channel_index
= ChannelOrder(channel_layout
, ch
);
113 if (channel_index
< 0)
116 channel_map
.map
[channel_index
] = ChromiumToPAChannelPosition(ch
);
123 void WaitForOperationCompletion(pa_threaded_mainloop
* pa_mainloop
,
124 pa_operation
* operation
) {
126 DLOG(WARNING
) << "Operation is NULL";
130 while (pa_operation_get_state(operation
) == PA_OPERATION_RUNNING
)
131 pa_threaded_mainloop_wait(pa_mainloop
);
133 pa_operation_unref(operation
);
136 int GetHardwareLatencyInBytes(pa_stream
* stream
,
138 int bytes_per_frame
) {
141 pa_usec_t latency_micros
= 0;
142 if (pa_stream_get_latency(stream
, &latency_micros
, &negative
) != 0)
148 return latency_micros
* sample_rate
* bytes_per_frame
/
149 base::Time::kMicrosecondsPerSecond
;
152 // Helper macro for CreateInput/OutputStream() to avoid code spam and
154 #define RETURN_ON_FAILURE(expression, message) do { \
155 if (!(expression)) { \
156 DLOG(ERROR) << message; \
161 bool CreateInputStream(pa_threaded_mainloop
* mainloop
,
164 const AudioParameters
& params
,
165 const std::string
& device_id
,
166 pa_stream_notify_cb_t stream_callback
,
171 // Set sample specifications.
172 pa_sample_spec sample_specifications
;
173 sample_specifications
.format
= BitsToPASampleFormat(
174 params
.bits_per_sample());
175 sample_specifications
.rate
= params
.sample_rate();
176 sample_specifications
.channels
= params
.channels();
178 // Get channel mapping and open recording stream.
179 pa_channel_map source_channel_map
= ChannelLayoutToPAChannelMap(
180 params
.channel_layout());
181 pa_channel_map
* map
= (source_channel_map
.channels
!= 0) ?
182 &source_channel_map
: NULL
;
184 // Create a new recording stream and
185 // tells PulseAudio what the stream icon should be.
186 ScopedPropertyList property_list
;
187 pa_proplist_sets(property_list
.get(), PA_PROP_APPLICATION_ICON_NAME
,
188 kBrowserDisplayName
);
189 *stream
= pa_stream_new_with_proplist(context
, "RecordStream",
190 &sample_specifications
, map
,
191 property_list
.get());
192 RETURN_ON_FAILURE(*stream
, "failed to create PA recording stream");
194 pa_stream_set_state_callback(*stream
, stream_callback
, user_data
);
196 // Set server-side capture buffer metrics. Detailed documentation on what
197 // values should be chosen can be found at
198 // freedesktop.org/software/pulseaudio/doxygen/structpa__buffer__attr.html.
199 pa_buffer_attr buffer_attributes
;
200 const unsigned int buffer_size
= params
.GetBytesPerBuffer();
201 buffer_attributes
.maxlength
= static_cast<uint32_t>(-1);
202 buffer_attributes
.tlength
= buffer_size
;
203 buffer_attributes
.minreq
= buffer_size
;
204 buffer_attributes
.prebuf
= static_cast<uint32_t>(-1);
205 buffer_attributes
.fragsize
= buffer_size
;
206 int flags
= PA_STREAM_AUTO_TIMING_UPDATE
|
207 PA_STREAM_INTERPOLATE_TIMING
|
208 PA_STREAM_ADJUST_LATENCY
|
209 PA_STREAM_START_CORKED
;
211 pa_stream_connect_record(
213 device_id
== AudioManagerBase::kDefaultDeviceId
?
214 NULL
: device_id
.c_str(),
216 static_cast<pa_stream_flags_t
>(flags
)) == 0,
217 "pa_stream_connect_record FAILED ");
219 // Wait for the stream to be ready.
221 pa_stream_state_t stream_state
= pa_stream_get_state(*stream
);
223 PA_STREAM_IS_GOOD(stream_state
), "Invalid PulseAudio stream state");
224 if (stream_state
== PA_STREAM_READY
)
226 pa_threaded_mainloop_wait(mainloop
);
232 bool CreateOutputStream(pa_threaded_mainloop
** mainloop
,
233 pa_context
** context
,
235 const AudioParameters
& params
,
236 const std::string
& device_id
,
237 const std::string
& app_name
,
238 pa_stream_notify_cb_t stream_callback
,
239 pa_stream_request_cb_t write_callback
,
244 *mainloop
= pa_threaded_mainloop_new();
245 RETURN_ON_FAILURE(*mainloop
, "Failed to create PulseAudio main loop.");
247 pa_mainloop_api
* pa_mainloop_api
= pa_threaded_mainloop_get_api(*mainloop
);
248 *context
= pa_context_new(pa_mainloop_api
,
249 app_name
.empty() ? "Chromium" : app_name
.c_str());
250 RETURN_ON_FAILURE(*context
, "Failed to create PulseAudio context.");
252 // A state callback must be set before calling pa_threaded_mainloop_lock() or
253 // pa_threaded_mainloop_wait() calls may lead to dead lock.
254 pa_context_set_state_callback(*context
, &ContextStateCallback
, *mainloop
);
256 // Lock the main loop while setting up the context. Failure to do so may lead
257 // to crashes as the PulseAudio thread tries to run before things are ready.
258 AutoPulseLock
auto_lock(*mainloop
);
260 RETURN_ON_FAILURE(pa_threaded_mainloop_start(*mainloop
) == 0,
261 "Failed to start PulseAudio main loop.");
263 pa_context_connect(*context
, NULL
, PA_CONTEXT_NOAUTOSPAWN
, NULL
) == 0,
264 "Failed to connect PulseAudio context.");
266 // Wait until |pa_context_| is ready. pa_threaded_mainloop_wait() must be
267 // called after pa_context_get_state() in case the context is already ready,
268 // otherwise pa_threaded_mainloop_wait() will hang indefinitely.
270 pa_context_state_t context_state
= pa_context_get_state(*context
);
272 PA_CONTEXT_IS_GOOD(context_state
), "Invalid PulseAudio context state.");
273 if (context_state
== PA_CONTEXT_READY
)
275 pa_threaded_mainloop_wait(*mainloop
);
278 // Set sample specifications.
279 pa_sample_spec sample_specifications
;
280 sample_specifications
.format
= BitsToPASampleFormat(
281 params
.bits_per_sample());
282 sample_specifications
.rate
= params
.sample_rate();
283 sample_specifications
.channels
= params
.channels();
285 // Get channel mapping.
286 pa_channel_map
* map
= NULL
;
287 pa_channel_map source_channel_map
= ChannelLayoutToPAChannelMap(
288 params
.channel_layout());
289 if (source_channel_map
.channels
!= 0) {
290 // The source data uses a supported channel map so we will use it rather
291 // than the default channel map (NULL).
292 map
= &source_channel_map
;
295 // Open playback stream and
296 // tell PulseAudio what the stream icon should be.
297 ScopedPropertyList property_list
;
298 pa_proplist_sets(property_list
.get(), PA_PROP_APPLICATION_ICON_NAME
,
299 kBrowserDisplayName
);
300 *stream
= pa_stream_new_with_proplist(
301 *context
, "Playback", &sample_specifications
, map
, property_list
.get());
302 RETURN_ON_FAILURE(*stream
, "failed to create PA playback stream");
304 pa_stream_set_state_callback(*stream
, stream_callback
, user_data
);
306 // Even though we start the stream corked above, PulseAudio will issue one
307 // stream request after setup. write_callback() must fulfill the write.
308 pa_stream_set_write_callback(*stream
, write_callback
, user_data
);
310 // Pulse is very finicky with the small buffer sizes used by Chrome. The
311 // settings below are mostly found through trial and error. Essentially we
312 // want Pulse to auto size its internal buffers, but call us back nearly every
313 // |minreq| bytes. |tlength| should be a multiple of |minreq|; too low and
314 // Pulse will issue callbacks way too fast, too high and we don't get
315 // callbacks frequently enough.
317 // Setting |minreq| to the exact buffer size leads to more callbacks than
318 // necessary, so we've clipped it to half the buffer size. Regardless of the
319 // requested amount, we'll always fill |params.GetBytesPerBuffer()| though.
320 pa_buffer_attr pa_buffer_attributes
;
321 pa_buffer_attributes
.maxlength
= static_cast<uint32_t>(-1);
322 pa_buffer_attributes
.minreq
= params
.GetBytesPerBuffer() / 2;
323 pa_buffer_attributes
.prebuf
= static_cast<uint32_t>(-1);
324 pa_buffer_attributes
.tlength
= params
.GetBytesPerBuffer() * 3;
325 pa_buffer_attributes
.fragsize
= static_cast<uint32_t>(-1);
327 // Connect playback stream. Like pa_buffer_attr, the pa_stream_flags have a
328 // huge impact on the performance of the stream and were chosen through trial
331 pa_stream_connect_playback(
333 device_id
== AudioManagerBase::kDefaultDeviceId
?
334 NULL
: device_id
.c_str(),
335 &pa_buffer_attributes
,
336 static_cast<pa_stream_flags_t
>(
337 PA_STREAM_INTERPOLATE_TIMING
| PA_STREAM_ADJUST_LATENCY
|
338 PA_STREAM_AUTO_TIMING_UPDATE
| PA_STREAM_NOT_MONOTONIC
|
339 PA_STREAM_START_CORKED
),
342 "pa_stream_connect_playback FAILED ");
344 // Wait for the stream to be ready.
346 pa_stream_state_t stream_state
= pa_stream_get_state(*stream
);
348 PA_STREAM_IS_GOOD(stream_state
), "Invalid PulseAudio stream state");
349 if (stream_state
== PA_STREAM_READY
)
351 pa_threaded_mainloop_wait(*mainloop
);
357 #undef RETURN_ON_FAILURE