pulse_util.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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.h"
9#include "media/audio/audio_manager_base.h"
10#include "media/audio/audio_parameters.h"
11
12namespace media {
13
14namespace pulse {
15
16namespace {
17
18pa_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
55void 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.
62void 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
68pa_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
84pa_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
101void 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
114int 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
139bool 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
204bool 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_FIX_RATE | PA_STREAM_INTERPOLATE_TIMING |
293              PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE |
294              PA_STREAM_NOT_MONOTONIC | 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