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