1 /// Translated from C to D 2 module soundio.pulseaudio; 3 4 version(SOUNDIO_HAVE_PULSEAUDIO): 5 @nogc nothrow: 6 extern(C): __gshared: 7 8 9 import soundio.api; 10 import soundio.atomics; 11 import soundio.util; 12 import soundio.soundio_private; 13 import soundio.headers.pulseheader; 14 import core.stdc.string; 15 import core.stdc.stdio; 16 import core.stdc.stdlib: free; 17 18 private: 19 20 package struct SoundIoDevicePulseAudio { int make_the_struct_not_empty; } 21 22 package struct SoundIoPulseAudio { 23 int device_query_err; 24 int connection_err; 25 bool emitted_shutdown_cb; 26 27 pa_context* pulse_context; 28 bool device_scan_queued; 29 30 // the one that we're working on building 31 SoundIoDevicesInfo* current_devices_info; 32 char* default_sink_name; 33 char* default_source_name; 34 35 // this one is ready to be read with flush_events. protected by mutex 36 SoundIoDevicesInfo* ready_devices_info; 37 38 bool ready_flag; 39 40 pa_threaded_mainloop* main_loop; 41 pa_proplist* props; 42 } 43 44 package struct SoundIoOutStreamPulseAudio { 45 pa_stream* stream; 46 SoundIoAtomicBool stream_ready; 47 pa_buffer_attr buffer_attr; 48 char* write_ptr; 49 size_t write_byte_count; 50 SoundIoAtomicFlag clear_buffer_flag; 51 SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas; 52 } 53 54 package struct SoundIoInStreamPulseAudio { 55 pa_stream* stream; 56 SoundIoAtomicBool stream_ready; 57 pa_buffer_attr buffer_attr; 58 char* peek_buf; 59 size_t peek_buf_index; 60 size_t peek_buf_size; 61 int peek_buf_frames_left; 62 int read_frame_count; 63 SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas; 64 } 65 66 static void subscribe_callback(pa_context* context, pa_subscription_event_type_t event_bits, uint index, void* userdata) { 67 SoundIoPrivate* si = cast(SoundIoPrivate*)userdata; 68 SoundIo* soundio = &si.pub; 69 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 70 sipa.device_scan_queued = true; 71 pa_threaded_mainloop_signal(sipa.main_loop, 0); 72 soundio.on_events_signal(soundio); 73 } 74 75 static int subscribe_to_events(SoundIoPrivate* si) { 76 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 77 pa_subscription_mask_t events = cast(pa_subscription_mask_t)( 78 PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SERVER 79 ); 80 pa_operation* subscribe_op = pa_context_subscribe(sipa.pulse_context, events, null, si); 81 if (!subscribe_op) 82 return SoundIoError.NoMem; 83 pa_operation_unref(subscribe_op); 84 return 0; 85 } 86 87 static void context_state_callback(pa_context* context, void* userdata) { 88 SoundIoPrivate* si = cast(SoundIoPrivate*)userdata; 89 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 90 SoundIo* soundio = &si.pub; 91 92 switch (pa_context_get_state(context)) { 93 case PA_CONTEXT_UNCONNECTED: // The context hasn't been connected yet. 94 return; 95 case PA_CONTEXT_CONNECTING: // A connection is being established. 96 return; 97 case PA_CONTEXT_AUTHORIZING: // The client is authorizing itself to the daemon. 98 return; 99 case PA_CONTEXT_SETTING_NAME: // The client is passing its application name to the daemon. 100 return; 101 case PA_CONTEXT_READY: // The connection is established, the context is ready to execute operations. 102 sipa.ready_flag = true; 103 pa_threaded_mainloop_signal(sipa.main_loop, 0); 104 return; 105 case PA_CONTEXT_TERMINATED: // The connection was terminated cleanly. 106 pa_threaded_mainloop_signal(sipa.main_loop, 0); 107 return; 108 case PA_CONTEXT_FAILED: // The connection failed or was disconnected. 109 if (sipa.ready_flag) { 110 sipa.connection_err = SoundIoError.BackendDisconnected; 111 } else { 112 sipa.connection_err = SoundIoError.InitAudioBackend; 113 sipa.ready_flag = true; 114 } 115 pa_threaded_mainloop_signal(sipa.main_loop, 0); 116 soundio.on_events_signal(soundio); 117 return; 118 default: break; 119 } 120 } 121 122 static void destroy_pa(SoundIoPrivate* si) { 123 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 124 125 if (sipa.main_loop) 126 pa_threaded_mainloop_stop(sipa.main_loop); 127 128 pa_context_disconnect(sipa.pulse_context); 129 pa_context_unref(sipa.pulse_context); 130 131 soundio_destroy_devices_info(sipa.current_devices_info); 132 soundio_destroy_devices_info(sipa.ready_devices_info); 133 134 if (sipa.main_loop) 135 pa_threaded_mainloop_free(sipa.main_loop); 136 137 if (sipa.props) 138 pa_proplist_free(sipa.props); 139 140 free(sipa.default_sink_name); 141 free(sipa.default_source_name); 142 } 143 144 static SoundIoFormat from_pulseaudio_format(pa_sample_spec sample_spec) { 145 switch (sample_spec.format) { 146 case PA_SAMPLE_U8: return SoundIoFormat.U8; 147 case PA_SAMPLE_S16LE: return SoundIoFormat.S16LE; 148 case PA_SAMPLE_S16BE: return SoundIoFormat.S16BE; 149 case PA_SAMPLE_FLOAT32LE: return SoundIoFormat.Float32LE; 150 case PA_SAMPLE_FLOAT32BE: return SoundIoFormat.Float32BE; 151 case PA_SAMPLE_S32LE: return SoundIoFormat.S32LE; 152 case PA_SAMPLE_S32BE: return SoundIoFormat.S32BE; 153 case PA_SAMPLE_S24_32LE: return SoundIoFormat.S24LE; 154 case PA_SAMPLE_S24_32BE: return SoundIoFormat.S24BE; 155 156 case PA_SAMPLE_MAX: 157 case PA_SAMPLE_INVALID: 158 case PA_SAMPLE_ALAW: 159 case PA_SAMPLE_ULAW: 160 case PA_SAMPLE_S24LE: 161 case PA_SAMPLE_S24BE: 162 return SoundIoFormat.Invalid; 163 default: break; 164 } 165 return SoundIoFormat.Invalid; 166 } 167 168 static SoundIoChannelId from_pulseaudio_channel_pos(pa_channel_position_t pos) { 169 switch (pos) { 170 case PA_CHANNEL_POSITION_MONO: return SoundIoChannelId.FrontCenter; 171 case PA_CHANNEL_POSITION_FRONT_LEFT: return SoundIoChannelId.FrontLeft; 172 case PA_CHANNEL_POSITION_FRONT_RIGHT: return SoundIoChannelId.FrontRight; 173 case PA_CHANNEL_POSITION_FRONT_CENTER: return SoundIoChannelId.FrontCenter; 174 case PA_CHANNEL_POSITION_REAR_CENTER: return SoundIoChannelId.BackCenter; 175 case PA_CHANNEL_POSITION_REAR_LEFT: return SoundIoChannelId.BackLeft; 176 case PA_CHANNEL_POSITION_REAR_RIGHT: return SoundIoChannelId.BackRight; 177 case PA_CHANNEL_POSITION_LFE: return SoundIoChannelId.Lfe; 178 case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return SoundIoChannelId.FrontLeftCenter; 179 case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return SoundIoChannelId.FrontRightCenter; 180 case PA_CHANNEL_POSITION_SIDE_LEFT: return SoundIoChannelId.SideLeft; 181 case PA_CHANNEL_POSITION_SIDE_RIGHT: return SoundIoChannelId.SideRight; 182 case PA_CHANNEL_POSITION_TOP_CENTER: return SoundIoChannelId.TopCenter; 183 case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return SoundIoChannelId.TopFrontLeft; 184 case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return SoundIoChannelId.TopFrontRight; 185 case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return SoundIoChannelId.TopFrontCenter; 186 case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return SoundIoChannelId.TopBackLeft; 187 case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return SoundIoChannelId.TopBackRight; 188 case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return SoundIoChannelId.TopBackCenter; 189 190 case PA_CHANNEL_POSITION_AUX0: return SoundIoChannelId.Aux0; 191 case PA_CHANNEL_POSITION_AUX1: return SoundIoChannelId.Aux1; 192 case PA_CHANNEL_POSITION_AUX2: return SoundIoChannelId.Aux2; 193 case PA_CHANNEL_POSITION_AUX3: return SoundIoChannelId.Aux3; 194 case PA_CHANNEL_POSITION_AUX4: return SoundIoChannelId.Aux4; 195 case PA_CHANNEL_POSITION_AUX5: return SoundIoChannelId.Aux5; 196 case PA_CHANNEL_POSITION_AUX6: return SoundIoChannelId.Aux6; 197 case PA_CHANNEL_POSITION_AUX7: return SoundIoChannelId.Aux7; 198 case PA_CHANNEL_POSITION_AUX8: return SoundIoChannelId.Aux8; 199 case PA_CHANNEL_POSITION_AUX9: return SoundIoChannelId.Aux9; 200 case PA_CHANNEL_POSITION_AUX10: return SoundIoChannelId.Aux10; 201 case PA_CHANNEL_POSITION_AUX11: return SoundIoChannelId.Aux11; 202 case PA_CHANNEL_POSITION_AUX12: return SoundIoChannelId.Aux12; 203 case PA_CHANNEL_POSITION_AUX13: return SoundIoChannelId.Aux13; 204 case PA_CHANNEL_POSITION_AUX14: return SoundIoChannelId.Aux14; 205 case PA_CHANNEL_POSITION_AUX15: return SoundIoChannelId.Aux15; 206 207 default: return SoundIoChannelId.Invalid; 208 } 209 } 210 211 static void set_from_pulseaudio_channel_map(pa_channel_map channel_map, SoundIoChannelLayout* channel_layout) { 212 channel_layout.channel_count = channel_map.channels; 213 for (int i = 0; i < channel_map.channels; i += 1) { 214 channel_layout.channels[i] = from_pulseaudio_channel_pos(channel_map.map[i]); 215 } 216 channel_layout.name = null; 217 int builtin_layout_count = soundio_channel_layout_builtin_count(); 218 for (int i = 0; i < builtin_layout_count; i += 1) { 219 const(SoundIoChannelLayout)* builtin_layout = soundio_channel_layout_get_builtin(i); 220 if (soundio_channel_layout_equal(builtin_layout, channel_layout)) { 221 channel_layout.name = builtin_layout.name; 222 break; 223 } 224 } 225 } 226 227 extern(D) int set_all_device_channel_layouts(SoundIoDevice* device) { 228 device.layout_count = soundio_channel_layout_builtin_count(); 229 device.layouts = ALLOCATE!SoundIoChannelLayout(device.layout_count); 230 if (!device.layouts) 231 return SoundIoError.NoMem; 232 for (int i = 0; i < device.layout_count; i += 1) 233 device.layouts[i] = *soundio_channel_layout_get_builtin(i); 234 return 0; 235 } 236 237 extern(D) int set_all_device_formats(SoundIoDevice* device) { 238 device.format_count = 9; 239 device.formats = ALLOCATE!SoundIoFormat(device.format_count); 240 if (!device.formats) 241 return SoundIoError.NoMem; 242 device.formats[0] = SoundIoFormat.U8; 243 device.formats[1] = SoundIoFormat.S16LE; 244 device.formats[2] = SoundIoFormat.S16BE; 245 device.formats[3] = SoundIoFormat.Float32LE; 246 device.formats[4] = SoundIoFormat.Float32BE; 247 device.formats[5] = SoundIoFormat.S32LE; 248 device.formats[6] = SoundIoFormat.S32BE; 249 device.formats[7] = SoundIoFormat.S24LE; 250 device.formats[8] = SoundIoFormat.S24BE; 251 return 0; 252 } 253 254 extern(D) int perform_operation(SoundIoPrivate* si, pa_operation* op) { 255 if (!op) 256 return SoundIoError.NoMem; 257 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 258 for (;;) { 259 switch (pa_operation_get_state(op)) { 260 case PA_OPERATION_RUNNING: 261 pa_threaded_mainloop_wait(sipa.main_loop); 262 continue; 263 case PA_OPERATION_DONE: 264 pa_operation_unref(op); 265 return 0; 266 case PA_OPERATION_CANCELLED: 267 pa_operation_unref(op); 268 return SoundIoError.Interrupted; 269 default: break; 270 } 271 } 272 } 273 274 void sink_info_callback(pa_context* pulse_context, const(pa_sink_info)* info, int eol, void* userdata) { 275 SoundIoPrivate* si = cast(SoundIoPrivate*)userdata; 276 SoundIo* soundio = &si.pub; 277 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 278 if (eol) { 279 pa_threaded_mainloop_signal(sipa.main_loop, 0); 280 return; 281 } 282 if (sipa.device_query_err) 283 return; 284 285 SoundIoDevicePrivate* dev = ALLOCATE!SoundIoDevicePrivate(1); 286 if (!dev) { 287 sipa.device_query_err = SoundIoError.NoMem; 288 return; 289 } 290 SoundIoDevice* device = &dev.pub; 291 292 device.ref_count = 1; 293 device.soundio = soundio; 294 device.id = strdup(info.name); 295 device.name = strdup(info.description); 296 if (!device.id || !device.name) { 297 soundio_device_unref(device); 298 sipa.device_query_err = SoundIoError.NoMem; 299 return; 300 } 301 302 device.sample_rate_current = info.sample_spec.rate; 303 // PulseAudio performs resampling, so any value is valid. Let's pick 304 // some reasonable min and max values. 305 device.sample_rate_count = 1; 306 device.sample_rates = &dev.prealloc_sample_rate_range; 307 device.sample_rates[0].min = soundio_int_min(SOUNDIO_MIN_SAMPLE_RATE, device.sample_rate_current); 308 device.sample_rates[0].max = soundio_int_max(SOUNDIO_MAX_SAMPLE_RATE, device.sample_rate_current); 309 310 device.current_format = from_pulseaudio_format(info.sample_spec); 311 // PulseAudio performs sample format conversion, so any PulseAudio 312 // value is valid. 313 if (auto err = set_all_device_formats(device)) { 314 soundio_device_unref(device); 315 sipa.device_query_err = SoundIoError.NoMem; 316 return; 317 } 318 319 set_from_pulseaudio_channel_map(info.channel_map, &device.current_layout); 320 // PulseAudio does channel layout remapping, so any channel layout is valid. 321 if (auto err = set_all_device_channel_layouts(device)) { 322 soundio_device_unref(device); 323 sipa.device_query_err = SoundIoError.NoMem; 324 return; 325 } 326 327 device.aim = SoundIoDeviceAim.Output; 328 329 if (sipa.current_devices_info.output_devices.append(device)) { 330 soundio_device_unref(device); 331 sipa.device_query_err = SoundIoError.NoMem; 332 return; 333 } 334 } 335 336 void source_info_callback(pa_context* pulse_context, const(pa_source_info)* info, int eol, void* userdata) { 337 SoundIoPrivate* si = cast(SoundIoPrivate*)userdata; 338 SoundIo* soundio = &si.pub; 339 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 340 341 if (eol) { 342 pa_threaded_mainloop_signal(sipa.main_loop, 0); 343 return; 344 } 345 if (sipa.device_query_err) 346 return; 347 348 SoundIoDevicePrivate* dev = ALLOCATE!SoundIoDevicePrivate(1); 349 if (!dev) { 350 sipa.device_query_err = SoundIoError.NoMem; 351 return; 352 } 353 SoundIoDevice* device = &dev.pub; 354 355 device.ref_count = 1; 356 device.soundio = soundio; 357 device.id = strdup(info.name); 358 device.name = strdup(info.description); 359 if (!device.id || !device.name) { 360 soundio_device_unref(device); 361 sipa.device_query_err = SoundIoError.NoMem; 362 return; 363 } 364 365 device.sample_rate_current = info.sample_spec.rate; 366 // PulseAudio performs resampling, so any value is valid. Let's pick 367 // some reasonable min and max values. 368 device.sample_rate_count = 1; 369 device.sample_rates = &dev.prealloc_sample_rate_range; 370 device.sample_rates[0].min = soundio_int_min(SOUNDIO_MIN_SAMPLE_RATE, device.sample_rate_current); 371 device.sample_rates[0].max = soundio_int_max(SOUNDIO_MAX_SAMPLE_RATE, device.sample_rate_current); 372 373 device.current_format = from_pulseaudio_format(info.sample_spec); 374 // PulseAudio performs sample format conversion, so any PulseAudio 375 // value is valid. 376 if (auto err = set_all_device_formats(device)) { 377 soundio_device_unref(device); 378 sipa.device_query_err = SoundIoError.NoMem; 379 return; 380 } 381 382 set_from_pulseaudio_channel_map(info.channel_map, &device.current_layout); 383 // PulseAudio does channel layout remapping, so any channel layout is valid. 384 if (auto err = set_all_device_channel_layouts(device)) { 385 soundio_device_unref(device); 386 sipa.device_query_err = SoundIoError.NoMem; 387 return; 388 } 389 390 device.aim = SoundIoDeviceAim.Input; 391 392 if (sipa.current_devices_info.input_devices.append(device)) { 393 soundio_device_unref(device); 394 sipa.device_query_err = SoundIoError.NoMem; 395 return; 396 } 397 } 398 399 static void server_info_callback(pa_context* pulse_context, const(pa_server_info)* info, void* userdata) { 400 SoundIoPrivate* si = cast(SoundIoPrivate*)userdata; 401 assert(si); 402 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 403 404 assert(!sipa.default_sink_name); 405 assert(!sipa.default_source_name); 406 407 sipa.default_sink_name = strdup(info.default_sink_name); 408 sipa.default_source_name = strdup(info.default_source_name); 409 410 if (!sipa.default_sink_name || !sipa.default_source_name) 411 sipa.device_query_err = SoundIoError.NoMem; 412 413 pa_threaded_mainloop_signal(sipa.main_loop, 0); 414 } 415 416 // always called even when refresh_devices succeeds 417 extern(D) void cleanup_refresh_devices(SoundIoPrivate* si) { 418 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 419 420 soundio_destroy_devices_info(sipa.current_devices_info); 421 sipa.current_devices_info = null; 422 423 free(sipa.default_sink_name); 424 sipa.default_sink_name = null; 425 426 free(sipa.default_source_name); 427 sipa.default_source_name = null; 428 } 429 430 // call this while holding the main loop lock 431 extern(D) int refresh_devices(SoundIoPrivate* si) { 432 SoundIo* soundio = &si.pub; 433 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 434 435 assert(!sipa.current_devices_info); 436 sipa.current_devices_info = ALLOCATE!SoundIoDevicesInfo(1); 437 if (!sipa.current_devices_info) 438 return SoundIoError.NoMem; 439 440 pa_operation* list_sink_op = pa_context_get_sink_info_list(sipa.pulse_context, &sink_info_callback, si); 441 pa_operation* list_source_op = pa_context_get_source_info_list(sipa.pulse_context, &source_info_callback, si); 442 pa_operation* server_info_op = pa_context_get_server_info(sipa.pulse_context, &server_info_callback, si); 443 444 if (auto err = perform_operation(si, list_sink_op)) { 445 return err; 446 } 447 if (auto err = perform_operation(si, list_source_op)) { 448 return err; 449 } 450 if (auto err = perform_operation(si, server_info_op)) { 451 return err; 452 } 453 454 if (sipa.device_query_err) { 455 return sipa.device_query_err; 456 } 457 458 // based on the default sink name, figure out the default output index 459 // if the name doesn't match just pick the first one. if there are no 460 // devices then we need to set it to -1. 461 sipa.current_devices_info.default_output_index = -1; 462 sipa.current_devices_info.default_input_index = -1; 463 464 if (sipa.current_devices_info.input_devices.length > 0) { 465 sipa.current_devices_info.default_input_index = 0; 466 for (int i = 0; i < sipa.current_devices_info.input_devices.length; i += 1) { 467 SoundIoDevice* device = sipa.current_devices_info.input_devices.val_at(i); 468 469 assert(device.aim == SoundIoDeviceAim.Input); 470 if (strcmp(device.id, sipa.default_source_name) == 0) { 471 sipa.current_devices_info.default_input_index = i; 472 } 473 } 474 } 475 476 if (sipa.current_devices_info.output_devices.length > 0) { 477 sipa.current_devices_info.default_output_index = 0; 478 for (int i = 0; i < sipa.current_devices_info.output_devices.length; i += 1) { 479 SoundIoDevice* device = sipa.current_devices_info.output_devices.val_at(i); 480 481 assert(device.aim == SoundIoDeviceAim.Output); 482 if (strcmp(device.id, sipa.default_sink_name) == 0) { 483 sipa.current_devices_info.default_output_index = i; 484 } 485 } 486 } 487 488 soundio_destroy_devices_info(sipa.ready_devices_info); 489 sipa.ready_devices_info = sipa.current_devices_info; 490 sipa.current_devices_info = null; 491 pa_threaded_mainloop_signal(sipa.main_loop, 0); 492 soundio.on_events_signal(soundio); 493 494 return 0; 495 } 496 497 extern(D) void my_flush_events(SoundIoPrivate* si, bool wait) { 498 SoundIo* soundio = &si.pub; 499 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 500 501 bool change = false; 502 bool cb_shutdown = false; 503 SoundIoDevicesInfo* old_devices_info = null; 504 505 pa_threaded_mainloop_lock(sipa.main_loop); 506 507 if (wait) 508 pa_threaded_mainloop_wait(sipa.main_loop); 509 510 if (sipa.device_scan_queued && !sipa.connection_err) { 511 sipa.device_scan_queued = false; 512 sipa.connection_err = refresh_devices(si); 513 cleanup_refresh_devices(si); 514 } 515 516 if (sipa.connection_err && !sipa.emitted_shutdown_cb) { 517 sipa.emitted_shutdown_cb = true; 518 cb_shutdown = true; 519 } else if (sipa.ready_devices_info) { 520 old_devices_info = si.safe_devices_info; 521 si.safe_devices_info = sipa.ready_devices_info; 522 sipa.ready_devices_info = null; 523 change = true; 524 } 525 526 pa_threaded_mainloop_unlock(sipa.main_loop); 527 528 if (cb_shutdown) 529 soundio.on_backend_disconnect(soundio, sipa.connection_err); 530 else if (change) 531 soundio.on_devices_change(soundio); 532 533 soundio_destroy_devices_info(old_devices_info); 534 } 535 536 static void flush_events_pa(SoundIoPrivate* si) { 537 my_flush_events(si, false); 538 } 539 540 static void wait_events_pa(SoundIoPrivate* si) { 541 my_flush_events(si, false); 542 my_flush_events(si, true); 543 } 544 545 static void wakeup_pa(SoundIoPrivate* si) { 546 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 547 pa_threaded_mainloop_lock(sipa.main_loop); 548 pa_threaded_mainloop_signal(sipa.main_loop, 0); 549 pa_threaded_mainloop_unlock(sipa.main_loop); 550 } 551 552 static void force_device_scan_pa(SoundIoPrivate* si) { 553 SoundIo* soundio = &si.pub; 554 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 555 pa_threaded_mainloop_lock(sipa.main_loop); 556 sipa.device_scan_queued = true; 557 pa_threaded_mainloop_signal(sipa.main_loop, 0); 558 soundio.on_events_signal(soundio); 559 pa_threaded_mainloop_unlock(sipa.main_loop); 560 } 561 562 static pa_sample_format_t to_pulseaudio_format(SoundIoFormat format) { 563 switch (format) { 564 case SoundIoFormat.U8: return PA_SAMPLE_U8; 565 case SoundIoFormat.S16LE: return PA_SAMPLE_S16LE; 566 case SoundIoFormat.S16BE: return PA_SAMPLE_S16BE; 567 case SoundIoFormat.S24LE: return PA_SAMPLE_S24_32LE; 568 case SoundIoFormat.S24BE: return PA_SAMPLE_S24_32BE; 569 case SoundIoFormat.S32LE: return PA_SAMPLE_S32LE; 570 case SoundIoFormat.S32BE: return PA_SAMPLE_S32BE; 571 case SoundIoFormat.Float32LE: return PA_SAMPLE_FLOAT32LE; 572 case SoundIoFormat.Float32BE: return PA_SAMPLE_FLOAT32BE; 573 574 case SoundIoFormat.Invalid: 575 case SoundIoFormat.S8: 576 case SoundIoFormat.U16LE: 577 case SoundIoFormat.U16BE: 578 case SoundIoFormat.U24LE: 579 case SoundIoFormat.U24BE: 580 case SoundIoFormat.U32LE: 581 case SoundIoFormat.U32BE: 582 case SoundIoFormat.Float64LE: 583 case SoundIoFormat.Float64BE: 584 return PA_SAMPLE_INVALID; 585 default: break; 586 } 587 return PA_SAMPLE_INVALID; 588 } 589 590 static pa_channel_position_t to_pulseaudio_channel_pos(SoundIoChannelId channel_id) { 591 switch (channel_id) { 592 case SoundIoChannelId.FrontLeft: return PA_CHANNEL_POSITION_FRONT_LEFT; 593 case SoundIoChannelId.FrontRight: return PA_CHANNEL_POSITION_FRONT_RIGHT; 594 case SoundIoChannelId.FrontCenter: return PA_CHANNEL_POSITION_FRONT_CENTER; 595 case SoundIoChannelId.Lfe: return PA_CHANNEL_POSITION_LFE; 596 case SoundIoChannelId.BackLeft: return PA_CHANNEL_POSITION_REAR_LEFT; 597 case SoundIoChannelId.BackRight: return PA_CHANNEL_POSITION_REAR_RIGHT; 598 case SoundIoChannelId.FrontLeftCenter: return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; 599 case SoundIoChannelId.FrontRightCenter: return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; 600 case SoundIoChannelId.BackCenter: return PA_CHANNEL_POSITION_REAR_CENTER; 601 case SoundIoChannelId.SideLeft: return PA_CHANNEL_POSITION_SIDE_LEFT; 602 case SoundIoChannelId.SideRight: return PA_CHANNEL_POSITION_SIDE_RIGHT; 603 case SoundIoChannelId.TopCenter: return PA_CHANNEL_POSITION_TOP_CENTER; 604 case SoundIoChannelId.TopFrontLeft: return PA_CHANNEL_POSITION_TOP_FRONT_LEFT; 605 case SoundIoChannelId.TopFrontCenter: return PA_CHANNEL_POSITION_TOP_FRONT_CENTER; 606 case SoundIoChannelId.TopFrontRight: return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; 607 case SoundIoChannelId.TopBackLeft: return PA_CHANNEL_POSITION_TOP_REAR_LEFT; 608 case SoundIoChannelId.TopBackCenter: return PA_CHANNEL_POSITION_TOP_REAR_CENTER; 609 case SoundIoChannelId.TopBackRight: return PA_CHANNEL_POSITION_TOP_REAR_RIGHT; 610 611 case SoundIoChannelId.Aux0: return PA_CHANNEL_POSITION_AUX0; 612 case SoundIoChannelId.Aux1: return PA_CHANNEL_POSITION_AUX1; 613 case SoundIoChannelId.Aux2: return PA_CHANNEL_POSITION_AUX2; 614 case SoundIoChannelId.Aux3: return PA_CHANNEL_POSITION_AUX3; 615 case SoundIoChannelId.Aux4: return PA_CHANNEL_POSITION_AUX4; 616 case SoundIoChannelId.Aux5: return PA_CHANNEL_POSITION_AUX5; 617 case SoundIoChannelId.Aux6: return PA_CHANNEL_POSITION_AUX6; 618 case SoundIoChannelId.Aux7: return PA_CHANNEL_POSITION_AUX7; 619 case SoundIoChannelId.Aux8: return PA_CHANNEL_POSITION_AUX8; 620 case SoundIoChannelId.Aux9: return PA_CHANNEL_POSITION_AUX9; 621 case SoundIoChannelId.Aux10: return PA_CHANNEL_POSITION_AUX10; 622 case SoundIoChannelId.Aux11: return PA_CHANNEL_POSITION_AUX11; 623 case SoundIoChannelId.Aux12: return PA_CHANNEL_POSITION_AUX12; 624 case SoundIoChannelId.Aux13: return PA_CHANNEL_POSITION_AUX13; 625 case SoundIoChannelId.Aux14: return PA_CHANNEL_POSITION_AUX14; 626 case SoundIoChannelId.Aux15: return PA_CHANNEL_POSITION_AUX15; 627 628 default: 629 return PA_CHANNEL_POSITION_INVALID; 630 } 631 } 632 633 static pa_channel_map to_pulseaudio_channel_map(const(SoundIoChannelLayout)* channel_layout) { 634 pa_channel_map channel_map; 635 channel_map.channels = cast(ubyte) channel_layout.channel_count; 636 637 assert(cast()channel_layout.channel_count <= PA_CHANNELS_MAX); 638 639 for (int i = 0; i < channel_layout.channel_count; i += 1) 640 channel_map.map[i] = to_pulseaudio_channel_pos(channel_layout.channels[i]); 641 642 return channel_map; 643 } 644 645 static void playback_stream_state_callback(pa_stream* stream, void* userdata) { 646 SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*) userdata; 647 SoundIoOutStream* outstream = &os.pub; 648 SoundIo* soundio = outstream.device.soundio; 649 SoundIoPrivate* si = cast(SoundIoPrivate*)soundio; 650 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 651 SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio; 652 switch (pa_stream_get_state(stream)) { 653 case PA_STREAM_UNCONNECTED: 654 case PA_STREAM_CREATING: 655 case PA_STREAM_TERMINATED: 656 break; 657 case PA_STREAM_READY: 658 SOUNDIO_ATOMIC_STORE(ospa.stream_ready, true); 659 pa_threaded_mainloop_signal(sipa.main_loop, 0); 660 break; 661 case PA_STREAM_FAILED: 662 outstream.error_callback(outstream, SoundIoError.Streaming); 663 break; 664 default: break; 665 } 666 } 667 668 static void playback_stream_underflow_callback(pa_stream* stream, void* userdata) { 669 SoundIoOutStream* outstream = cast(SoundIoOutStream*)userdata; 670 outstream.underflow_callback(outstream); 671 } 672 673 static void playback_stream_write_callback(pa_stream* stream, size_t nbytes, void* userdata) { 674 SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)(userdata); 675 SoundIoOutStream* outstream = &os.pub; 676 int frame_count = cast(int) (nbytes / outstream.bytes_per_frame); 677 outstream.write_callback(outstream, 0, frame_count); 678 } 679 680 static void outstream_destroy_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 681 SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio; 682 683 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 684 pa_stream* stream = ospa.stream; 685 if (stream) { 686 pa_threaded_mainloop_lock(sipa.main_loop); 687 688 pa_stream_set_write_callback(stream, null, null); 689 pa_stream_set_state_callback(stream, null, null); 690 pa_stream_set_underflow_callback(stream, null, null); 691 pa_stream_set_overflow_callback(stream, null, null); 692 pa_stream_disconnect(stream); 693 694 pa_stream_unref(stream); 695 696 pa_threaded_mainloop_unlock(sipa.main_loop); 697 698 ospa.stream = null; 699 } 700 } 701 702 static void timing_update_callback(pa_stream* stream, int success, void* userdata) { 703 SoundIoPrivate* si = cast(SoundIoPrivate*)userdata; 704 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 705 pa_threaded_mainloop_signal(sipa.main_loop, 0); 706 } 707 708 static int outstream_open_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 709 SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio; 710 SoundIoOutStream* outstream = &os.pub; 711 712 if (cast()outstream.layout.channel_count > PA_CHANNELS_MAX) 713 return SoundIoError.IncompatibleBackend; 714 715 if (!outstream.name) 716 outstream.name = "SoundIoOutStream"; 717 718 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 719 SOUNDIO_ATOMIC_STORE(ospa.stream_ready, false); 720 SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(ospa.clear_buffer_flag); 721 722 assert(sipa.pulse_context); 723 724 pa_threaded_mainloop_lock(sipa.main_loop); 725 726 pa_sample_spec sample_spec; 727 sample_spec.format = to_pulseaudio_format(outstream.format); 728 sample_spec.rate = outstream.sample_rate; 729 730 sample_spec.channels = cast(ubyte) outstream.layout.channel_count; 731 pa_channel_map channel_map = to_pulseaudio_channel_map(&outstream.layout); 732 733 ospa.stream = pa_stream_new(sipa.pulse_context, outstream.name, &sample_spec, &channel_map); 734 if (!ospa.stream) { 735 pa_threaded_mainloop_unlock(sipa.main_loop); 736 outstream_destroy_pa(si, os); 737 return SoundIoError.NoMem; 738 } 739 pa_stream_set_state_callback(ospa.stream, &playback_stream_state_callback, os); 740 741 ospa.buffer_attr.maxlength = uint.max; 742 ospa.buffer_attr.tlength = uint.max; 743 ospa.buffer_attr.prebuf = 0; 744 ospa.buffer_attr.minreq = uint.max; 745 ospa.buffer_attr.fragsize = uint.max; 746 747 int bytes_per_second = outstream.bytes_per_frame * outstream.sample_rate; 748 if (outstream.software_latency > 0.0) { 749 int buffer_length = outstream.bytes_per_frame * 750 ceil_dbl_to_int(outstream.software_latency * bytes_per_second / cast(double)outstream.bytes_per_frame); 751 752 ospa.buffer_attr.maxlength = buffer_length; 753 ospa.buffer_attr.tlength = buffer_length; 754 } 755 756 pa_stream_flags_t flags = cast(pa_stream_flags_t)( 757 PA_STREAM_START_CORKED | PA_STREAM_AUTO_TIMING_UPDATE | 758 PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY 759 ); 760 761 if (auto err = pa_stream_connect_playback(ospa.stream,outstream.device.id, &ospa.buffer_attr,flags, null, null)) { 762 pa_threaded_mainloop_unlock(sipa.main_loop); 763 return SoundIoError.OpeningDevice; 764 } 765 766 while (!SOUNDIO_ATOMIC_LOAD(ospa.stream_ready)) 767 pa_threaded_mainloop_wait(sipa.main_loop); 768 769 pa_operation* update_timing_info_op = pa_stream_update_timing_info(ospa.stream, &timing_update_callback, si); 770 if (auto err = perform_operation(si, update_timing_info_op)) { 771 pa_threaded_mainloop_unlock(sipa.main_loop); 772 return err; 773 } 774 775 size_t writable_size = pa_stream_writable_size(ospa.stream); 776 outstream.software_latency = (cast(double)writable_size) / cast(double)bytes_per_second; 777 778 pa_threaded_mainloop_unlock(sipa.main_loop); 779 780 return 0; 781 } 782 783 static int outstream_start_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 784 SoundIoOutStream* outstream = &os.pub; 785 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 786 SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio; 787 788 pa_threaded_mainloop_lock(sipa.main_loop); 789 790 ospa.write_byte_count = pa_stream_writable_size(ospa.stream); 791 int frame_count = cast(int) (ospa.write_byte_count / outstream.bytes_per_frame); 792 outstream.write_callback(outstream, 0, frame_count); 793 794 pa_operation* op = pa_stream_cork(ospa.stream, false, null, null); 795 if (!op) { 796 pa_threaded_mainloop_unlock(sipa.main_loop); 797 return SoundIoError.Streaming; 798 } 799 pa_operation_unref(op); 800 pa_stream_set_write_callback(ospa.stream, &playback_stream_write_callback, os); 801 pa_stream_set_underflow_callback(ospa.stream, &playback_stream_underflow_callback, outstream); 802 pa_stream_set_overflow_callback(ospa.stream, &playback_stream_underflow_callback, outstream); 803 804 pa_threaded_mainloop_unlock(sipa.main_loop); 805 806 return 0; 807 } 808 809 static int outstream_begin_write_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, SoundIoChannelArea** out_areas, int* frame_count) { 810 SoundIoOutStream* outstream = &os.pub; 811 SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio; 812 pa_stream* stream = ospa.stream; 813 814 ospa.write_byte_count = *frame_count * outstream.bytes_per_frame; 815 if (pa_stream_begin_write(stream, cast(void**)&ospa.write_ptr, &ospa.write_byte_count)) 816 return SoundIoError.Streaming; 817 818 for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) { 819 ospa.areas[ch].ptr = ospa.write_ptr + outstream.bytes_per_sample * ch; 820 ospa.areas[ch].step = outstream.bytes_per_frame; 821 } 822 823 *frame_count = cast(int) (ospa.write_byte_count / outstream.bytes_per_frame); 824 *out_areas = ospa.areas.ptr; 825 826 return 0; 827 } 828 829 static int outstream_end_write_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 830 SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio; 831 pa_stream* stream = ospa.stream; 832 833 pa_seek_mode_t seek_mode = SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(ospa.clear_buffer_flag) ? PA_SEEK_RELATIVE : PA_SEEK_RELATIVE_ON_READ; 834 if (pa_stream_write(stream, ospa.write_ptr, ospa.write_byte_count, null, 0, seek_mode)) 835 return SoundIoError.Streaming; 836 837 return 0; 838 } 839 840 static int outstream_clear_buffer_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 841 SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio; 842 SOUNDIO_ATOMIC_FLAG_CLEAR(ospa.clear_buffer_flag); 843 return 0; 844 } 845 846 static int outstream_pause_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, bool pause) { 847 SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio; 848 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 849 850 if (!pa_threaded_mainloop_in_thread(sipa.main_loop)) { 851 pa_threaded_mainloop_lock(sipa.main_loop); 852 } 853 854 if (pause != pa_stream_is_corked(ospa.stream)) { 855 pa_operation* op = pa_stream_cork(ospa.stream, pause, null, null); 856 if (!op) { 857 pa_threaded_mainloop_unlock(sipa.main_loop); 858 return SoundIoError.Streaming; 859 } 860 pa_operation_unref(op); 861 } 862 863 if (!pa_threaded_mainloop_in_thread(sipa.main_loop)) { 864 pa_threaded_mainloop_unlock(sipa.main_loop); 865 } 866 867 return 0; 868 } 869 870 static int outstream_get_latency_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, double* out_latency) { 871 SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio; 872 873 pa_usec_t r_usec; 874 int negative; 875 if (auto err = pa_stream_get_latency(ospa.stream, &r_usec, &negative)) { 876 return SoundIoError.Streaming; 877 } 878 *out_latency = r_usec / 1000000.0; 879 return 0; 880 } 881 882 static void recording_stream_state_callback(pa_stream* stream, void* userdata) { 883 SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)userdata; 884 SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio; 885 SoundIoInStream* instream = &is_.pub; 886 SoundIo* soundio = instream.device.soundio; 887 SoundIoPrivate* si = cast(SoundIoPrivate*)soundio; 888 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 889 switch (pa_stream_get_state(stream)) { 890 case PA_STREAM_UNCONNECTED: 891 case PA_STREAM_CREATING: 892 case PA_STREAM_TERMINATED: 893 break; 894 case PA_STREAM_READY: 895 SOUNDIO_ATOMIC_STORE(ispa.stream_ready, true); 896 pa_threaded_mainloop_signal(sipa.main_loop, 0); 897 break; 898 case PA_STREAM_FAILED: 899 instream.error_callback(instream, SoundIoError.Streaming); 900 break; 901 default: break; 902 } 903 } 904 905 static void recording_stream_read_callback(pa_stream* stream, size_t nbytes, void* userdata) { 906 SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)userdata; 907 SoundIoInStream* instream = &is_.pub; 908 assert(nbytes % instream.bytes_per_frame == 0); 909 assert(nbytes > 0); 910 int available_frame_count = cast(int) (nbytes / instream.bytes_per_frame); 911 instream.read_callback(instream, 0, available_frame_count); 912 } 913 914 static void instream_destroy_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 915 SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio; 916 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 917 pa_stream* stream = ispa.stream; 918 if (stream) { 919 pa_threaded_mainloop_lock(sipa.main_loop); 920 921 pa_stream_set_state_callback(stream, null, null); 922 pa_stream_set_read_callback(stream, null, null); 923 pa_stream_disconnect(stream); 924 pa_stream_unref(stream); 925 926 pa_threaded_mainloop_unlock(sipa.main_loop); 927 928 ispa.stream = null; 929 } 930 } 931 932 static int instream_open_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 933 SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio; 934 SoundIoInStream* instream = &is_.pub; 935 936 if (cast()instream.layout.channel_count > PA_CHANNELS_MAX) 937 return SoundIoError.IncompatibleBackend; 938 if (!instream.name) 939 instream.name = "SoundIoInStream"; 940 941 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 942 SOUNDIO_ATOMIC_STORE(ispa.stream_ready, false); 943 944 pa_threaded_mainloop_lock(sipa.main_loop); 945 946 pa_sample_spec sample_spec; 947 sample_spec.format = to_pulseaudio_format(instream.format); 948 sample_spec.rate = instream.sample_rate; 949 sample_spec.channels = cast(ubyte) instream.layout.channel_count; 950 951 pa_channel_map channel_map = to_pulseaudio_channel_map(&instream.layout); 952 953 ispa.stream = pa_stream_new(sipa.pulse_context, instream.name, &sample_spec, &channel_map); 954 if (!ispa.stream) { 955 pa_threaded_mainloop_unlock(sipa.main_loop); 956 instream_destroy_pa(si, is_); 957 return SoundIoError.NoMem; 958 } 959 960 pa_stream* stream = ispa.stream; 961 962 pa_stream_set_state_callback(stream, &recording_stream_state_callback, is_); 963 pa_stream_set_read_callback(stream, &recording_stream_read_callback, is_); 964 965 ispa.buffer_attr.maxlength = uint.max; 966 ispa.buffer_attr.tlength = uint.max; 967 ispa.buffer_attr.prebuf = 0; 968 ispa.buffer_attr.minreq = uint.max; 969 ispa.buffer_attr.fragsize = uint.max; 970 971 if (instream.software_latency > 0.0) { 972 int bytes_per_second = instream.bytes_per_frame * instream.sample_rate; 973 int buffer_length = instream.bytes_per_frame * 974 ceil_dbl_to_int(instream.software_latency * bytes_per_second / cast(double)instream.bytes_per_frame); 975 ispa.buffer_attr.fragsize = buffer_length; 976 } 977 978 pa_threaded_mainloop_unlock(sipa.main_loop); 979 980 return 0; 981 } 982 983 static int instream_start_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 984 SoundIoInStream* instream = &is_.pub; 985 SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio; 986 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 987 pa_threaded_mainloop_lock(sipa.main_loop); 988 989 pa_stream_flags_t flags = cast(pa_stream_flags_t)( 990 PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY 991 ); 992 993 if (auto err = pa_stream_connect_record(ispa.stream, instream.device.id, &ispa.buffer_attr, flags)) { 994 pa_threaded_mainloop_unlock(sipa.main_loop); 995 return SoundIoError.OpeningDevice; 996 } 997 998 while (!SOUNDIO_ATOMIC_LOAD(ispa.stream_ready)) 999 pa_threaded_mainloop_wait(sipa.main_loop); 1000 1001 pa_operation* update_timing_info_op = pa_stream_update_timing_info(ispa.stream, &timing_update_callback, si); 1002 if (auto err = perform_operation(si, update_timing_info_op)) { 1003 pa_threaded_mainloop_unlock(sipa.main_loop); 1004 return err; 1005 } 1006 1007 1008 pa_threaded_mainloop_unlock(sipa.main_loop); 1009 return 0; 1010 } 1011 1012 static int instream_begin_read_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, SoundIoChannelArea** out_areas, int* frame_count) { 1013 SoundIoInStream* instream = &is_.pub; 1014 SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio; 1015 pa_stream* stream = ispa.stream; 1016 1017 assert(SOUNDIO_ATOMIC_LOAD(ispa.stream_ready)); 1018 1019 if (!ispa.peek_buf) { 1020 if (pa_stream_peek(stream, cast(const(void)**)&ispa.peek_buf, &ispa.peek_buf_size)) 1021 return SoundIoError.Streaming; 1022 1023 ispa.peek_buf_frames_left = cast(int) (ispa.peek_buf_size / instream.bytes_per_frame); 1024 ispa.peek_buf_index = 0; 1025 1026 // hole 1027 if (!ispa.peek_buf) { 1028 *frame_count = ispa.peek_buf_frames_left; 1029 *out_areas = null; 1030 return 0; 1031 } 1032 } 1033 1034 ispa.read_frame_count = soundio_int_min(*frame_count, ispa.peek_buf_frames_left); 1035 *frame_count = ispa.read_frame_count; 1036 for (int ch = 0; ch < instream.layout.channel_count; ch += 1) { 1037 ispa.areas[ch].ptr = ispa.peek_buf + ispa.peek_buf_index + instream.bytes_per_sample * ch; 1038 ispa.areas[ch].step = instream.bytes_per_frame; 1039 } 1040 1041 *out_areas = ispa.areas.ptr; 1042 1043 return 0; 1044 } 1045 1046 static int instream_end_read_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 1047 SoundIoInStream* instream = &is_.pub; 1048 SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio; 1049 pa_stream* stream = ispa.stream; 1050 1051 // hole 1052 if (!ispa.peek_buf) { 1053 if (pa_stream_drop(stream)) 1054 return SoundIoError.Streaming; 1055 return 0; 1056 } 1057 1058 size_t advance_bytes = ispa.read_frame_count * instream.bytes_per_frame; 1059 ispa.peek_buf_index += advance_bytes; 1060 ispa.peek_buf_frames_left -= ispa.read_frame_count; 1061 1062 if (ispa.peek_buf_index >= ispa.peek_buf_size) { 1063 if (pa_stream_drop(stream)) 1064 return SoundIoError.Streaming; 1065 ispa.peek_buf = null; 1066 } 1067 1068 return 0; 1069 } 1070 1071 static int instream_pause_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, bool pause) { 1072 SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio; 1073 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 1074 1075 if (!pa_threaded_mainloop_in_thread(sipa.main_loop)) { 1076 pa_threaded_mainloop_lock(sipa.main_loop); 1077 } 1078 1079 if (pause != pa_stream_is_corked(ispa.stream)) { 1080 pa_operation* op = pa_stream_cork(ispa.stream, pause, null, null); 1081 if (!op) 1082 return SoundIoError.Streaming; 1083 pa_operation_unref(op); 1084 } 1085 1086 if (!pa_threaded_mainloop_in_thread(sipa.main_loop)) { 1087 pa_threaded_mainloop_unlock(sipa.main_loop); 1088 } 1089 1090 return 0; 1091 } 1092 1093 static int instream_get_latency_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, double* out_latency) { 1094 SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio; 1095 1096 pa_usec_t r_usec; 1097 int negative; 1098 if (auto err = pa_stream_get_latency(ispa.stream, &r_usec, &negative)) { 1099 return SoundIoError.Streaming; 1100 } 1101 *out_latency = r_usec / 1000000.0; 1102 return 0; 1103 } 1104 1105 package int soundio_pulseaudio_init(SoundIoPrivate* si) { 1106 SoundIo* soundio = &si.pub; 1107 SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio; 1108 1109 sipa.device_scan_queued = true; 1110 1111 sipa.main_loop = pa_threaded_mainloop_new(); 1112 if (!sipa.main_loop) { 1113 destroy_pa(si); 1114 return SoundIoError.NoMem; 1115 } 1116 1117 pa_mainloop_api* main_loop_api = pa_threaded_mainloop_get_api(sipa.main_loop); 1118 1119 sipa.props = pa_proplist_new(); 1120 if (!sipa.props) { 1121 destroy_pa(si); 1122 return SoundIoError.NoMem; 1123 } 1124 1125 sipa.pulse_context = pa_context_new_with_proplist(main_loop_api, soundio.app_name, sipa.props); 1126 if (!sipa.pulse_context) { 1127 destroy_pa(si); 1128 return SoundIoError.NoMem; 1129 } 1130 1131 pa_context_set_subscribe_callback(sipa.pulse_context, &subscribe_callback, si); 1132 pa_context_set_state_callback(sipa.pulse_context, &context_state_callback, si); 1133 1134 if (auto err = pa_context_connect(sipa.pulse_context, null, cast(pa_context_flags_t)0, null)) { 1135 destroy_pa(si); 1136 return SoundIoError.InitAudioBackend; 1137 } 1138 1139 if (pa_threaded_mainloop_start(sipa.main_loop)) { 1140 destroy_pa(si); 1141 return SoundIoError.NoMem; 1142 } 1143 1144 pa_threaded_mainloop_lock(sipa.main_loop); 1145 1146 // block until ready 1147 while (!sipa.ready_flag) 1148 pa_threaded_mainloop_wait(sipa.main_loop); 1149 1150 if (sipa.connection_err) { 1151 pa_threaded_mainloop_unlock(sipa.main_loop); 1152 destroy_pa(si); 1153 return sipa.connection_err; 1154 } 1155 1156 if (auto err = subscribe_to_events(si)) { 1157 pa_threaded_mainloop_unlock(sipa.main_loop); 1158 destroy_pa(si); 1159 return err; 1160 } 1161 1162 pa_threaded_mainloop_unlock(sipa.main_loop); 1163 1164 si.destroy = &destroy_pa; 1165 si.flush_events = &flush_events_pa; 1166 si.wait_events = &wait_events_pa; 1167 si.wakeup = &wakeup_pa; 1168 si.force_device_scan = &force_device_scan_pa; 1169 1170 si.outstream_open = &outstream_open_pa; 1171 si.outstream_destroy = &outstream_destroy_pa; 1172 si.outstream_start = &outstream_start_pa; 1173 si.outstream_begin_write = &outstream_begin_write_pa; 1174 si.outstream_end_write = &outstream_end_write_pa; 1175 si.outstream_clear_buffer = &outstream_clear_buffer_pa; 1176 si.outstream_pause = &outstream_pause_pa; 1177 si.outstream_get_latency = &outstream_get_latency_pa; 1178 1179 si.instream_open = &instream_open_pa; 1180 si.instream_destroy = &instream_destroy_pa; 1181 si.instream_start = &instream_start_pa; 1182 si.instream_begin_read = &instream_begin_read_pa; 1183 si.instream_end_read = &instream_end_read_pa; 1184 si.instream_pause = &instream_pause_pa; 1185 si.instream_get_latency = &instream_get_latency_pa; 1186 1187 return 0; 1188 }