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.
4
5 #include "media/audio/pulse/pulse_util.h"
6
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"
11
12 namespace media {
13
14 namespace pulse {
15
16 namespace {
17
ChromiumToPAChannelPosition(Channels channel)18 pa_channel_position ChromiumToPAChannelPosition(Channels channel) {
19 switch (channel) {
20 // PulseAudio does not differentiate between left/right and
21 // stereo-left/stereo-right, both translate to front-left/front-right.
22 case LEFT:
23 return PA_CHANNEL_POSITION_FRONT_LEFT;
24 case RIGHT:
25 return PA_CHANNEL_POSITION_FRONT_RIGHT;
26 case CENTER:
27 return PA_CHANNEL_POSITION_FRONT_CENTER;
28 case LFE:
29 return PA_CHANNEL_POSITION_LFE;
30 case BACK_LEFT:
31 return PA_CHANNEL_POSITION_REAR_LEFT;
32 case BACK_RIGHT:
33 return PA_CHANNEL_POSITION_REAR_RIGHT;
34 case LEFT_OF_CENTER:
35 return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
36 case RIGHT_OF_CENTER:
37 return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
38 case BACK_CENTER:
39 return PA_CHANNEL_POSITION_REAR_CENTER;
40 case SIDE_LEFT:
41 return PA_CHANNEL_POSITION_SIDE_LEFT;
42 case SIDE_RIGHT:
43 return PA_CHANNEL_POSITION_SIDE_RIGHT;
44 case CHANNELS_MAX:
45 return PA_CHANNEL_POSITION_INVALID;
46 default:
47 NOTREACHED() << "Invalid channel: " << channel;
48 return PA_CHANNEL_POSITION_INVALID;
49 }
50 }
51
52 } // namespace
53
54 // static, pa_stream_success_cb_t
StreamSuccessCallback(pa_stream * s,int error,void * mainloop)55 void StreamSuccessCallback(pa_stream* s, int error, void* mainloop) {
56 pa_threaded_mainloop* pa_mainloop =
57 static_cast<pa_threaded_mainloop*>(mainloop);
58 pa_threaded_mainloop_signal(pa_mainloop, 0);
59 }
60
61 // |pa_context| and |pa_stream| state changed cb.
ContextStateCallback(pa_context * context,void * mainloop)62 void ContextStateCallback(pa_context* context, void* mainloop) {
63 pa_threaded_mainloop* pa_mainloop =
64 static_cast<pa_threaded_mainloop*>(mainloop);
65 pa_threaded_mainloop_signal(pa_mainloop, 0);
66 }
67
BitsToPASampleFormat(int bits_per_sample)68 pa_sample_format_t BitsToPASampleFormat(int bits_per_sample) {
69 switch (bits_per_sample) {
70 case 8:
71 return PA_SAMPLE_U8;
72 case 16:
73 return PA_SAMPLE_S16LE;
74 case 24:
75 return PA_SAMPLE_S24LE;
76 case 32:
77 return PA_SAMPLE_S32LE;
78 default:
79 NOTREACHED() << "Invalid bits per sample: " << bits_per_sample;
80 return PA_SAMPLE_INVALID;
81 }
82 }
83
ChannelLayoutToPAChannelMap(ChannelLayout channel_layout)84 pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout) {
85 pa_channel_map channel_map;
86 pa_channel_map_init(&channel_map);
87
88 channel_map.channels = ChannelLayoutToChannelCount(channel_layout);
89 for (Channels ch = LEFT; ch < CHANNELS_MAX;
90 ch = static_cast<Channels>(ch + 1)) {
91 int channel_index = ChannelOrder(channel_layout, ch);
92 if (channel_index < 0)
93 continue;
94
95 channel_map.map[channel_index] = ChromiumToPAChannelPosition(ch);
96 }
97
98 return channel_map;
99 }
100
WaitForOperationCompletion(pa_threaded_mainloop * pa_mainloop,pa_operation * operation)101 void WaitForOperationCompletion(pa_threaded_mainloop* pa_mainloop,
102 pa_operation* operation) {
103 if (!operation) {
104 DLOG(WARNING) << "Operation is NULL";
105 return;
106 }
107
108 while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
109 pa_threaded_mainloop_wait(pa_mainloop);
110
111 pa_operation_unref(operation);
112 }
113
GetHardwareLatencyInBytes(pa_stream * stream,int sample_rate,int bytes_per_frame)114 int GetHardwareLatencyInBytes(pa_stream* stream,
115 int sample_rate,
116 int bytes_per_frame) {
117 DCHECK(stream);
118 int negative = 0;
119 pa_usec_t latency_micros = 0;
120 if (pa_stream_get_latency(stream, &latency_micros, &negative) != 0)
121 return 0;
122
123 if (negative)
124 return 0;
125
126 return latency_micros * sample_rate * bytes_per_frame /
127 base::Time::kMicrosecondsPerSecond;
128 }
129
130 // Helper macro for CreateInput/OutputStream() to avoid code spam and
131 // string bloat.
132 #define RETURN_ON_FAILURE(expression, message) do { \
133 if (!(expression)) { \
134 DLOG(ERROR) << message; \
135 return false; \
136 } \
137 } while(0)
138
CreateInputStream(pa_threaded_mainloop * mainloop,pa_context * context,pa_stream ** stream,const AudioParameters & params,const std::string & device_id,pa_stream_notify_cb_t stream_callback,void * user_data)139 bool CreateInputStream(pa_threaded_mainloop* mainloop,
140 pa_context* context,
141 pa_stream** stream,
142 const AudioParameters& params,
143 const std::string& device_id,
144 pa_stream_notify_cb_t stream_callback,
145 void* user_data) {
146 DCHECK(mainloop);
147 DCHECK(context);
148
149 // Set sample specifications.
150 pa_sample_spec sample_specifications;
151 sample_specifications.format = BitsToPASampleFormat(
152 params.bits_per_sample());
153 sample_specifications.rate = params.sample_rate();
154 sample_specifications.channels = params.channels();
155
156 // Get channel mapping and open recording stream.
157 pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
158 params.channel_layout());
159 pa_channel_map* map = (source_channel_map.channels != 0) ?
160 &source_channel_map : NULL;
161
162 // Create a new recording stream.
163 *stream = pa_stream_new(context, "RecordStream", &sample_specifications, map);
164 RETURN_ON_FAILURE(*stream, "failed to create PA recording stream");
165
166 pa_stream_set_state_callback(*stream, stream_callback, user_data);
167
168 // Set server-side capture buffer metrics. Detailed documentation on what
169 // values should be chosen can be found at
170 // freedesktop.org/software/pulseaudio/doxygen/structpa__buffer__attr.html.
171 pa_buffer_attr buffer_attributes;
172 const unsigned int buffer_size = params.GetBytesPerBuffer();
173 buffer_attributes.maxlength = static_cast<uint32_t>(-1);
174 buffer_attributes.tlength = buffer_size;
175 buffer_attributes.minreq = buffer_size;
176 buffer_attributes.prebuf = static_cast<uint32_t>(-1);
177 buffer_attributes.fragsize = buffer_size;
178 int flags = PA_STREAM_AUTO_TIMING_UPDATE |
179 PA_STREAM_INTERPOLATE_TIMING |
180 PA_STREAM_ADJUST_LATENCY |
181 PA_STREAM_START_CORKED;
182 RETURN_ON_FAILURE(
183 pa_stream_connect_record(
184 *stream,
185 device_id == AudioManagerBase::kDefaultDeviceId ?
186 NULL : device_id.c_str(),
187 &buffer_attributes,
188 static_cast<pa_stream_flags_t>(flags)) == 0,
189 "pa_stream_connect_record FAILED ");
190
191 // Wait for the stream to be ready.
192 while (true) {
193 pa_stream_state_t stream_state = pa_stream_get_state(*stream);
194 RETURN_ON_FAILURE(
195 PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state");
196 if (stream_state == PA_STREAM_READY)
197 break;
198 pa_threaded_mainloop_wait(mainloop);
199 }
200
201 return true;
202 }
203
CreateOutputStream(pa_threaded_mainloop ** mainloop,pa_context ** context,pa_stream ** stream,const AudioParameters & params,pa_stream_notify_cb_t stream_callback,pa_stream_request_cb_t write_callback,void * user_data)204 bool CreateOutputStream(pa_threaded_mainloop** mainloop,
205 pa_context** context,
206 pa_stream** stream,
207 const AudioParameters& params,
208 pa_stream_notify_cb_t stream_callback,
209 pa_stream_request_cb_t write_callback,
210 void* user_data) {
211 DCHECK(!*mainloop);
212 DCHECK(!*context);
213
214 *mainloop = pa_threaded_mainloop_new();
215 RETURN_ON_FAILURE(*mainloop, "Failed to create PulseAudio main loop.");
216
217 pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(*mainloop);
218 *context = pa_context_new(pa_mainloop_api, "Chromium");
219 RETURN_ON_FAILURE(*context, "Failed to create PulseAudio context.");
220
221 // A state callback must be set before calling pa_threaded_mainloop_lock() or
222 // pa_threaded_mainloop_wait() calls may lead to dead lock.
223 pa_context_set_state_callback(*context, &ContextStateCallback, *mainloop);
224
225 // Lock the main loop while setting up the context. Failure to do so may lead
226 // to crashes as the PulseAudio thread tries to run before things are ready.
227 AutoPulseLock auto_lock(*mainloop);
228
229 RETURN_ON_FAILURE(pa_threaded_mainloop_start(*mainloop) == 0,
230 "Failed to start PulseAudio main loop.");
231 RETURN_ON_FAILURE(
232 pa_context_connect(*context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) == 0,
233 "Failed to connect PulseAudio context.");
234
235 // Wait until |pa_context_| is ready. pa_threaded_mainloop_wait() must be
236 // called after pa_context_get_state() in case the context is already ready,
237 // otherwise pa_threaded_mainloop_wait() will hang indefinitely.
238 while (true) {
239 pa_context_state_t context_state = pa_context_get_state(*context);
240 RETURN_ON_FAILURE(
241 PA_CONTEXT_IS_GOOD(context_state), "Invalid PulseAudio context state.");
242 if (context_state == PA_CONTEXT_READY)
243 break;
244 pa_threaded_mainloop_wait(*mainloop);
245 }
246
247 // Set sample specifications.
248 pa_sample_spec sample_specifications;
249 sample_specifications.format = BitsToPASampleFormat(
250 params.bits_per_sample());
251 sample_specifications.rate = params.sample_rate();
252 sample_specifications.channels = params.channels();
253
254 // Get channel mapping and open playback stream.
255 pa_channel_map* map = NULL;
256 pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
257 params.channel_layout());
258 if (source_channel_map.channels != 0) {
259 // The source data uses a supported channel map so we will use it rather
260 // than the default channel map (NULL).
261 map = &source_channel_map;
262 }
263 *stream = pa_stream_new(*context, "Playback", &sample_specifications, map);
264 RETURN_ON_FAILURE(*stream, "failed to create PA playback stream");
265
266 pa_stream_set_state_callback(*stream, stream_callback, user_data);
267
268 // Even though we start the stream corked above, PulseAudio will issue one
269 // stream request after setup. write_callback() must fulfill the write.
270 pa_stream_set_write_callback(*stream, write_callback, user_data);
271
272 // Pulse is very finicky with the small buffer sizes used by Chrome. The
273 // settings below are mostly found through trial and error. Essentially we
274 // want Pulse to auto size its internal buffers, but call us back nearly every
275 // |minreq| bytes. |tlength| should be a multiple of |minreq|; too low and
276 // Pulse will issue callbacks way too fast, too high and we don't get
277 // callbacks frequently enough.
278 pa_buffer_attr pa_buffer_attributes;
279 pa_buffer_attributes.maxlength = static_cast<uint32_t>(-1);
280 pa_buffer_attributes.minreq = params.GetBytesPerBuffer();
281 pa_buffer_attributes.prebuf = static_cast<uint32_t>(-1);
282 pa_buffer_attributes.tlength = params.GetBytesPerBuffer() * 3;
283 pa_buffer_attributes.fragsize = static_cast<uint32_t>(-1);
284
285 // Connect playback stream. Like pa_buffer_attr, the pa_stream_flags have a
286 // huge impact on the performance of the stream and were chosen through trial
287 // and error.
288 RETURN_ON_FAILURE(
289 pa_stream_connect_playback(
290 *stream, NULL, &pa_buffer_attributes,
291 static_cast<pa_stream_flags_t>(
292 PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY |
293 PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_NOT_MONOTONIC |
294 PA_STREAM_START_CORKED),
295 NULL, NULL) == 0,
296 "pa_stream_connect_playback FAILED ");
297
298 // Wait for the stream to be ready.
299 while (true) {
300 pa_stream_state_t stream_state = pa_stream_get_state(*stream);
301 RETURN_ON_FAILURE(
302 PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state");
303 if (stream_state == PA_STREAM_READY)
304 break;
305 pa_threaded_mainloop_wait(*mainloop);
306 }
307
308 return true;
309 }
310
311 #undef RETURN_ON_FAILURE
312
313 } // namespace pulse
314
315 } // namespace media
316