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