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 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 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. 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 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 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 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 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 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 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