pulse_util.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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 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 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 53void 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. 60void 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 66pa_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 82pa_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 99void 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 112int 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 137bool 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 202bool 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 pa_buffer_attr pa_buffer_attributes; 278 pa_buffer_attributes.maxlength = static_cast<uint32_t>(-1); 279 pa_buffer_attributes.minreq = params.GetBytesPerBuffer(); 280 pa_buffer_attributes.prebuf = static_cast<uint32_t>(-1); 281 pa_buffer_attributes.tlength = params.GetBytesPerBuffer() * 3; 282 pa_buffer_attributes.fragsize = static_cast<uint32_t>(-1); 283 284 // Connect playback stream. Like pa_buffer_attr, the pa_stream_flags have a 285 // huge impact on the performance of the stream and were chosen through trial 286 // and error. 287 RETURN_ON_FAILURE( 288 pa_stream_connect_playback( 289 *stream, 290 device_id == AudioManagerBase::kDefaultDeviceId ? 291 NULL : device_id.c_str(), 292 &pa_buffer_attributes, 293 static_cast<pa_stream_flags_t>( 294 PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | 295 PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_NOT_MONOTONIC | 296 PA_STREAM_START_CORKED), 297 NULL, 298 NULL) == 0, 299 "pa_stream_connect_playback FAILED "); 300 301 // Wait for the stream to be ready. 302 while (true) { 303 pa_stream_state_t stream_state = pa_stream_get_state(*stream); 304 RETURN_ON_FAILURE( 305 PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state"); 306 if (stream_state == PA_STREAM_READY) 307 break; 308 pa_threaded_mainloop_wait(*mainloop); 309 } 310 311 return true; 312} 313 314#undef RETURN_ON_FAILURE 315 316} // namespace pulse 317 318} // namespace media 319