| 1 | /**************************************************************************/ |
| 2 | /* audio_driver_pulseaudio.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "audio_driver_pulseaudio.h" |
| 32 | |
| 33 | #ifdef PULSEAUDIO_ENABLED |
| 34 | |
| 35 | #include "core/config/project_settings.h" |
| 36 | #include "core/os/os.h" |
| 37 | #include "core/version.h" |
| 38 | |
| 39 | #ifdef ALSAMIDI_ENABLED |
| 40 | #ifdef SOWRAP_ENABLED |
| 41 | #include "drivers/alsa/asound-so_wrap.h" |
| 42 | #else |
| 43 | #include <alsa/asoundlib.h> |
| 44 | #endif |
| 45 | #endif |
| 46 | |
| 47 | void AudioDriverPulseAudio::pa_state_cb(pa_context *c, void *userdata) { |
| 48 | AudioDriverPulseAudio *ad = static_cast<AudioDriverPulseAudio *>(userdata); |
| 49 | |
| 50 | switch (pa_context_get_state(c)) { |
| 51 | case PA_CONTEXT_TERMINATED: |
| 52 | print_verbose("PulseAudio: context terminated" ); |
| 53 | ad->pa_ready = -1; |
| 54 | break; |
| 55 | case PA_CONTEXT_FAILED: |
| 56 | print_verbose("PulseAudio: context failed" ); |
| 57 | ad->pa_ready = -1; |
| 58 | break; |
| 59 | case PA_CONTEXT_READY: |
| 60 | print_verbose("PulseAudio: context ready" ); |
| 61 | ad->pa_ready = 1; |
| 62 | break; |
| 63 | default: |
| 64 | print_verbose("PulseAudio: context other" ); |
| 65 | // TODO: Check if we want to handle some of the other |
| 66 | // PA context states like PA_CONTEXT_UNCONNECTED. |
| 67 | break; |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | void AudioDriverPulseAudio::pa_sink_info_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { |
| 72 | AudioDriverPulseAudio *ad = static_cast<AudioDriverPulseAudio *>(userdata); |
| 73 | |
| 74 | // If eol is set to a positive number, you're at the end of the list |
| 75 | if (eol > 0) { |
| 76 | return; |
| 77 | } |
| 78 | |
| 79 | // If eol is set to a negative number there's an error. |
| 80 | if (eol < 0) { |
| 81 | ERR_PRINT("PulseAudio: sink info error: " + String(pa_strerror(pa_context_errno(c)))); |
| 82 | ad->pa_status--; |
| 83 | return; |
| 84 | } |
| 85 | |
| 86 | ad->pa_map = l->channel_map; |
| 87 | ad->pa_status++; |
| 88 | } |
| 89 | |
| 90 | void AudioDriverPulseAudio::pa_source_info_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) { |
| 91 | AudioDriverPulseAudio *ad = static_cast<AudioDriverPulseAudio *>(userdata); |
| 92 | |
| 93 | // If eol is set to a positive number, you're at the end of the list |
| 94 | if (eol > 0) { |
| 95 | return; |
| 96 | } |
| 97 | |
| 98 | // If eol is set to a negative number there's an error. |
| 99 | if (eol < 0) { |
| 100 | ERR_PRINT("PulseAudio: sink info error: " + String(pa_strerror(pa_context_errno(c)))); |
| 101 | ad->pa_status--; |
| 102 | return; |
| 103 | } |
| 104 | |
| 105 | ad->pa_rec_map = l->channel_map; |
| 106 | ad->pa_status++; |
| 107 | } |
| 108 | |
| 109 | void AudioDriverPulseAudio::pa_server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) { |
| 110 | ERR_FAIL_NULL_MSG(i, "PulseAudio server info is null." ); |
| 111 | AudioDriverPulseAudio *ad = static_cast<AudioDriverPulseAudio *>(userdata); |
| 112 | |
| 113 | ad->default_input_device = i->default_source_name; |
| 114 | ad->default_output_device = i->default_sink_name; |
| 115 | ad->pa_status++; |
| 116 | } |
| 117 | |
| 118 | Error AudioDriverPulseAudio::detect_channels(bool input) { |
| 119 | pa_channel_map_init_stereo(input ? &pa_rec_map : &pa_map); |
| 120 | |
| 121 | String device = input ? input_device_name : output_device_name; |
| 122 | if (device == "Default" ) { |
| 123 | // Get the default output device name |
| 124 | pa_status = 0; |
| 125 | pa_operation *pa_op = pa_context_get_server_info(pa_ctx, &AudioDriverPulseAudio::pa_server_info_cb, (void *)this); |
| 126 | if (pa_op) { |
| 127 | while (pa_status == 0) { |
| 128 | int ret = pa_mainloop_iterate(pa_ml, 1, nullptr); |
| 129 | if (ret < 0) { |
| 130 | ERR_PRINT("pa_mainloop_iterate error" ); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | pa_operation_unref(pa_op); |
| 135 | } else { |
| 136 | ERR_PRINT("pa_context_get_server_info error: " + String(pa_strerror(pa_context_errno(pa_ctx)))); |
| 137 | return FAILED; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | char dev[1024]; |
| 142 | if (device == "Default" ) { |
| 143 | strcpy(dev, input ? default_input_device.utf8().get_data() : default_output_device.utf8().get_data()); |
| 144 | } else { |
| 145 | strcpy(dev, device.utf8().get_data()); |
| 146 | } |
| 147 | print_verbose("PulseAudio: Detecting channels for device: " + String(dev)); |
| 148 | |
| 149 | // Now using the device name get the amount of channels |
| 150 | pa_status = 0; |
| 151 | pa_operation *pa_op; |
| 152 | if (input) { |
| 153 | pa_op = pa_context_get_source_info_by_name(pa_ctx, dev, &AudioDriverPulseAudio::pa_source_info_cb, (void *)this); |
| 154 | } else { |
| 155 | pa_op = pa_context_get_sink_info_by_name(pa_ctx, dev, &AudioDriverPulseAudio::pa_sink_info_cb, (void *)this); |
| 156 | } |
| 157 | |
| 158 | if (pa_op) { |
| 159 | while (pa_status == 0) { |
| 160 | int ret = pa_mainloop_iterate(pa_ml, 1, nullptr); |
| 161 | if (ret < 0) { |
| 162 | ERR_PRINT("pa_mainloop_iterate error" ); |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | pa_operation_unref(pa_op); |
| 167 | |
| 168 | if (pa_status == -1) { |
| 169 | return FAILED; |
| 170 | } |
| 171 | } else { |
| 172 | if (input) { |
| 173 | ERR_PRINT("pa_context_get_source_info_by_name error" ); |
| 174 | } else { |
| 175 | ERR_PRINT("pa_context_get_sink_info_by_name error" ); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | return OK; |
| 180 | } |
| 181 | |
| 182 | Error AudioDriverPulseAudio::init_output_device() { |
| 183 | // If there is a specified output device, check that it is really present |
| 184 | if (output_device_name != "Default" ) { |
| 185 | PackedStringArray list = get_output_device_list(); |
| 186 | if (list.find(output_device_name) == -1) { |
| 187 | output_device_name = "Default" ; |
| 188 | new_output_device = "Default" ; |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | // Detect the amount of channels PulseAudio is using |
| 193 | // Note: If using an even amount of channels (2, 4, etc) channels and pa_map.channels will be equal, |
| 194 | // if not then pa_map.channels will have the real amount of channels PulseAudio is using and channels |
| 195 | // will have the amount of channels Godot is using (in this case it's pa_map.channels + 1) |
| 196 | Error err = detect_channels(); |
| 197 | if (err != OK) { |
| 198 | // This most likely means there are no sinks. |
| 199 | ERR_PRINT("PulseAudio: init_output_device failed to detect number of output channels" ); |
| 200 | return err; |
| 201 | } |
| 202 | |
| 203 | switch (pa_map.channels) { |
| 204 | case 1: // Mono |
| 205 | case 3: // Surround 2.1 |
| 206 | case 5: // Surround 5.0 |
| 207 | case 7: // Surround 7.0 |
| 208 | channels = pa_map.channels + 1; |
| 209 | break; |
| 210 | |
| 211 | case 2: // Stereo |
| 212 | case 4: // Surround 4.0 |
| 213 | case 6: // Surround 5.1 |
| 214 | case 8: // Surround 7.1 |
| 215 | channels = pa_map.channels; |
| 216 | break; |
| 217 | |
| 218 | default: |
| 219 | WARN_PRINT("PulseAudio: Unsupported number of output channels: " + itos(pa_map.channels)); |
| 220 | pa_channel_map_init_stereo(&pa_map); |
| 221 | channels = 2; |
| 222 | break; |
| 223 | } |
| 224 | |
| 225 | int tmp_latency = Engine::get_singleton()->get_audio_output_latency(); |
| 226 | buffer_frames = closest_power_of_2(tmp_latency * mix_rate / 1000); |
| 227 | pa_buffer_size = buffer_frames * pa_map.channels; |
| 228 | |
| 229 | print_verbose("PulseAudio: detected " + itos(pa_map.channels) + " output channels" ); |
| 230 | print_verbose("PulseAudio: audio buffer frames: " + itos(buffer_frames) + " calculated output latency: " + itos(buffer_frames * 1000 / mix_rate) + "ms" ); |
| 231 | |
| 232 | pa_sample_spec spec; |
| 233 | spec.format = PA_SAMPLE_S16LE; |
| 234 | spec.channels = pa_map.channels; |
| 235 | spec.rate = mix_rate; |
| 236 | pa_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; |
| 237 | pa_map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; |
| 238 | pa_map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; |
| 239 | pa_map.map[3] = PA_CHANNEL_POSITION_LFE; |
| 240 | pa_map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; |
| 241 | pa_map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; |
| 242 | pa_map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; |
| 243 | pa_map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; |
| 244 | |
| 245 | pa_str = pa_stream_new(pa_ctx, "Sound" , &spec, &pa_map); |
| 246 | if (pa_str == nullptr) { |
| 247 | ERR_PRINT("PulseAudio: pa_stream_new error: " + String(pa_strerror(pa_context_errno(pa_ctx)))); |
| 248 | ERR_FAIL_V(ERR_CANT_OPEN); |
| 249 | } |
| 250 | |
| 251 | pa_buffer_attr attr; |
| 252 | // set to appropriate buffer length (in bytes) from global settings |
| 253 | // Note: PulseAudio defaults to 4 fragments, which means that the actual |
| 254 | // latency is tlength / fragments. It seems that the PulseAudio has no way |
| 255 | // to get the fragments number so we're hardcoding this to the default of 4 |
| 256 | const int fragments = 4; |
| 257 | attr.tlength = pa_buffer_size * sizeof(int16_t) * fragments; |
| 258 | // set them to be automatically chosen |
| 259 | attr.prebuf = (uint32_t)-1; |
| 260 | attr.maxlength = (uint32_t)-1; |
| 261 | attr.minreq = (uint32_t)-1; |
| 262 | |
| 263 | const char *dev = output_device_name == "Default" ? nullptr : output_device_name.utf8().get_data(); |
| 264 | pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); |
| 265 | int error_code = pa_stream_connect_playback(pa_str, dev, &attr, flags, nullptr, nullptr); |
| 266 | ERR_FAIL_COND_V(error_code < 0, ERR_CANT_OPEN); |
| 267 | |
| 268 | samples_in.resize(buffer_frames * channels); |
| 269 | samples_out.resize(pa_buffer_size); |
| 270 | |
| 271 | // Reset audio input to keep synchronization. |
| 272 | input_position = 0; |
| 273 | input_size = 0; |
| 274 | |
| 275 | return OK; |
| 276 | } |
| 277 | |
| 278 | Error AudioDriverPulseAudio::init() { |
| 279 | #ifdef SOWRAP_ENABLED |
| 280 | #ifdef DEBUG_ENABLED |
| 281 | int dylibloader_verbose = 1; |
| 282 | #else |
| 283 | int dylibloader_verbose = 0; |
| 284 | #endif |
| 285 | #ifdef ALSAMIDI_ENABLED |
| 286 | // If using PulseAudio with ALSA MIDI, we need to initialize ALSA as well |
| 287 | initialize_asound(dylibloader_verbose); |
| 288 | #endif |
| 289 | if (initialize_pulse(dylibloader_verbose)) { |
| 290 | return ERR_CANT_OPEN; |
| 291 | } |
| 292 | #endif |
| 293 | bool ver_ok = false; |
| 294 | String version = String::utf8(pa_get_library_version()); |
| 295 | Vector<String> ver_parts = version.split("." ); |
| 296 | if (ver_parts.size() >= 2) { |
| 297 | ver_ok = (ver_parts[0].to_int() >= 8); // 8.0.0 |
| 298 | } |
| 299 | print_verbose(vformat("PulseAudio %s detected." , version)); |
| 300 | if (!ver_ok) { |
| 301 | print_verbose("Unsupported PulseAudio library version!" ); |
| 302 | return ERR_CANT_OPEN; |
| 303 | } |
| 304 | |
| 305 | active.clear(); |
| 306 | exit_thread.clear(); |
| 307 | |
| 308 | mix_rate = _get_configured_mix_rate(); |
| 309 | |
| 310 | pa_ml = pa_mainloop_new(); |
| 311 | ERR_FAIL_NULL_V(pa_ml, ERR_CANT_OPEN); |
| 312 | |
| 313 | String context_name; |
| 314 | if (Engine::get_singleton()->is_editor_hint()) { |
| 315 | context_name = VERSION_NAME " Editor" ; |
| 316 | } else { |
| 317 | context_name = GLOBAL_GET("application/config/name" ); |
| 318 | if (context_name.is_empty()) { |
| 319 | context_name = VERSION_NAME " Project" ; |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | pa_ctx = pa_context_new(pa_mainloop_get_api(pa_ml), context_name.utf8().ptr()); |
| 324 | ERR_FAIL_NULL_V(pa_ctx, ERR_CANT_OPEN); |
| 325 | |
| 326 | pa_ready = 0; |
| 327 | pa_context_set_state_callback(pa_ctx, pa_state_cb, (void *)this); |
| 328 | |
| 329 | int ret = pa_context_connect(pa_ctx, nullptr, PA_CONTEXT_NOFLAGS, nullptr); |
| 330 | if (ret < 0) { |
| 331 | if (pa_ctx) { |
| 332 | pa_context_unref(pa_ctx); |
| 333 | pa_ctx = nullptr; |
| 334 | } |
| 335 | |
| 336 | if (pa_ml) { |
| 337 | pa_mainloop_free(pa_ml); |
| 338 | pa_ml = nullptr; |
| 339 | } |
| 340 | |
| 341 | return ERR_CANT_OPEN; |
| 342 | } |
| 343 | |
| 344 | while (pa_ready == 0) { |
| 345 | ret = pa_mainloop_iterate(pa_ml, 1, nullptr); |
| 346 | if (ret < 0) { |
| 347 | ERR_PRINT("pa_mainloop_iterate error" ); |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | if (pa_ready < 0) { |
| 352 | if (pa_ctx) { |
| 353 | pa_context_disconnect(pa_ctx); |
| 354 | pa_context_unref(pa_ctx); |
| 355 | pa_ctx = nullptr; |
| 356 | } |
| 357 | |
| 358 | if (pa_ml) { |
| 359 | pa_mainloop_free(pa_ml); |
| 360 | pa_ml = nullptr; |
| 361 | } |
| 362 | |
| 363 | return ERR_CANT_OPEN; |
| 364 | } |
| 365 | |
| 366 | init_output_device(); |
| 367 | thread.start(AudioDriverPulseAudio::thread_func, this); |
| 368 | |
| 369 | return OK; |
| 370 | } |
| 371 | |
| 372 | float AudioDriverPulseAudio::get_latency() { |
| 373 | lock(); |
| 374 | |
| 375 | pa_usec_t pa_lat = 0; |
| 376 | if (pa_stream_get_state(pa_str) == PA_STREAM_READY) { |
| 377 | int negative = 0; |
| 378 | |
| 379 | if (pa_stream_get_latency(pa_str, &pa_lat, &negative) >= 0) { |
| 380 | if (negative) { |
| 381 | pa_lat = 0; |
| 382 | } |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | if (pa_lat > 0) { |
| 387 | latency = double(pa_lat) / 1000000.0; |
| 388 | } |
| 389 | |
| 390 | unlock(); |
| 391 | return latency; |
| 392 | } |
| 393 | |
| 394 | void AudioDriverPulseAudio::thread_func(void *p_udata) { |
| 395 | AudioDriverPulseAudio *ad = static_cast<AudioDriverPulseAudio *>(p_udata); |
| 396 | unsigned int write_ofs = 0; |
| 397 | size_t avail_bytes = 0; |
| 398 | uint64_t default_device_msec = OS::get_singleton()->get_ticks_msec(); |
| 399 | |
| 400 | while (!ad->exit_thread.is_set()) { |
| 401 | size_t read_bytes = 0; |
| 402 | size_t written_bytes = 0; |
| 403 | |
| 404 | if (avail_bytes == 0) { |
| 405 | ad->lock(); |
| 406 | ad->start_counting_ticks(); |
| 407 | |
| 408 | if (!ad->active.is_set()) { |
| 409 | ad->samples_out.fill(0); |
| 410 | } else { |
| 411 | ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptrw()); |
| 412 | |
| 413 | int16_t *out_ptr = ad->samples_out.ptrw(); |
| 414 | |
| 415 | if (ad->channels == ad->pa_map.channels) { |
| 416 | for (unsigned int i = 0; i < ad->pa_buffer_size; i++) { |
| 417 | out_ptr[i] = ad->samples_in[i] >> 16; |
| 418 | } |
| 419 | } else { |
| 420 | // Uneven amount of channels |
| 421 | unsigned int in_idx = 0; |
| 422 | unsigned int out_idx = 0; |
| 423 | |
| 424 | for (unsigned int i = 0; i < ad->buffer_frames; i++) { |
| 425 | for (int j = 0; j < ad->pa_map.channels - 1; j++) { |
| 426 | out_ptr[out_idx++] = ad->samples_in[in_idx++] >> 16; |
| 427 | } |
| 428 | uint32_t l = ad->samples_in[in_idx++] >> 16; |
| 429 | uint32_t r = ad->samples_in[in_idx++] >> 16; |
| 430 | out_ptr[out_idx++] = (l + r) / 2; |
| 431 | } |
| 432 | } |
| 433 | } |
| 434 | |
| 435 | avail_bytes = ad->pa_buffer_size * sizeof(int16_t); |
| 436 | write_ofs = 0; |
| 437 | ad->stop_counting_ticks(); |
| 438 | ad->unlock(); |
| 439 | } |
| 440 | |
| 441 | ad->lock(); |
| 442 | ad->start_counting_ticks(); |
| 443 | |
| 444 | int ret; |
| 445 | do { |
| 446 | ret = pa_mainloop_iterate(ad->pa_ml, 0, nullptr); |
| 447 | } while (ret > 0); |
| 448 | |
| 449 | if (avail_bytes > 0 && pa_stream_get_state(ad->pa_str) == PA_STREAM_READY) { |
| 450 | size_t bytes = pa_stream_writable_size(ad->pa_str); |
| 451 | if (bytes > 0) { |
| 452 | size_t bytes_to_write = MIN(bytes, avail_bytes); |
| 453 | const void *ptr = ad->samples_out.ptr(); |
| 454 | ret = pa_stream_write(ad->pa_str, (char *)ptr + write_ofs, bytes_to_write, nullptr, 0LL, PA_SEEK_RELATIVE); |
| 455 | if (ret != 0) { |
| 456 | ERR_PRINT("PulseAudio: pa_stream_write error: " + String(pa_strerror(ret))); |
| 457 | } else { |
| 458 | avail_bytes -= bytes_to_write; |
| 459 | write_ofs += bytes_to_write; |
| 460 | written_bytes += bytes_to_write; |
| 461 | } |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | // User selected a new output device, finish the current one so we'll init the new output device |
| 466 | if (ad->output_device_name != ad->new_output_device) { |
| 467 | ad->output_device_name = ad->new_output_device; |
| 468 | ad->finish_output_device(); |
| 469 | |
| 470 | Error err = ad->init_output_device(); |
| 471 | if (err != OK) { |
| 472 | ERR_PRINT("PulseAudio: init_output_device error" ); |
| 473 | ad->output_device_name = "Default" ; |
| 474 | ad->new_output_device = "Default" ; |
| 475 | |
| 476 | err = ad->init_output_device(); |
| 477 | if (err != OK) { |
| 478 | ad->active.clear(); |
| 479 | ad->exit_thread.set(); |
| 480 | break; |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | avail_bytes = 0; |
| 485 | write_ofs = 0; |
| 486 | } |
| 487 | |
| 488 | // If we're using the default output device, check that the current output device is still the default |
| 489 | if (ad->output_device_name == "Default" ) { |
| 490 | uint64_t msec = OS::get_singleton()->get_ticks_msec(); |
| 491 | if (msec > (default_device_msec + 1000)) { |
| 492 | String old_default_device = ad->default_output_device; |
| 493 | |
| 494 | default_device_msec = msec; |
| 495 | |
| 496 | ad->pa_status = 0; |
| 497 | pa_operation *pa_op = pa_context_get_server_info(ad->pa_ctx, &AudioDriverPulseAudio::pa_server_info_cb, (void *)ad); |
| 498 | if (pa_op) { |
| 499 | while (ad->pa_status == 0) { |
| 500 | ret = pa_mainloop_iterate(ad->pa_ml, 1, nullptr); |
| 501 | if (ret < 0) { |
| 502 | ERR_PRINT("pa_mainloop_iterate error" ); |
| 503 | } |
| 504 | } |
| 505 | |
| 506 | pa_operation_unref(pa_op); |
| 507 | } else { |
| 508 | ERR_PRINT("pa_context_get_server_info error: " + String(pa_strerror(pa_context_errno(ad->pa_ctx)))); |
| 509 | } |
| 510 | |
| 511 | if (old_default_device != ad->default_output_device) { |
| 512 | ad->finish_output_device(); |
| 513 | |
| 514 | Error err = ad->init_output_device(); |
| 515 | if (err != OK) { |
| 516 | ERR_PRINT("PulseAudio: init_output_device error" ); |
| 517 | ad->active.clear(); |
| 518 | ad->exit_thread.set(); |
| 519 | break; |
| 520 | } |
| 521 | |
| 522 | avail_bytes = 0; |
| 523 | write_ofs = 0; |
| 524 | } |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | if (ad->pa_rec_str && pa_stream_get_state(ad->pa_rec_str) == PA_STREAM_READY) { |
| 529 | size_t bytes = pa_stream_readable_size(ad->pa_rec_str); |
| 530 | if (bytes > 0) { |
| 531 | const void *ptr = nullptr; |
| 532 | size_t maxbytes = ad->input_buffer.size() * sizeof(int16_t); |
| 533 | |
| 534 | bytes = MIN(bytes, maxbytes); |
| 535 | ret = pa_stream_peek(ad->pa_rec_str, &ptr, &bytes); |
| 536 | if (ret != 0) { |
| 537 | ERR_PRINT("pa_stream_peek error" ); |
| 538 | } else { |
| 539 | int16_t *srcptr = (int16_t *)ptr; |
| 540 | for (size_t i = bytes >> 1; i > 0; i--) { |
| 541 | int32_t sample = int32_t(*srcptr++) << 16; |
| 542 | ad->input_buffer_write(sample); |
| 543 | |
| 544 | if (ad->pa_rec_map.channels == 1) { |
| 545 | // In case input device is single channel convert it to Stereo |
| 546 | ad->input_buffer_write(sample); |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | read_bytes += bytes; |
| 551 | ret = pa_stream_drop(ad->pa_rec_str); |
| 552 | if (ret != 0) { |
| 553 | ERR_PRINT("pa_stream_drop error" ); |
| 554 | } |
| 555 | } |
| 556 | } |
| 557 | |
| 558 | // User selected a new input device, finish the current one so we'll init the new input device |
| 559 | if (ad->input_device_name != ad->new_input_device) { |
| 560 | ad->input_device_name = ad->new_input_device; |
| 561 | ad->finish_input_device(); |
| 562 | |
| 563 | Error err = ad->init_input_device(); |
| 564 | if (err != OK) { |
| 565 | ERR_PRINT("PulseAudio: init_input_device error" ); |
| 566 | ad->input_device_name = "Default" ; |
| 567 | ad->new_input_device = "Default" ; |
| 568 | |
| 569 | err = ad->init_input_device(); |
| 570 | if (err != OK) { |
| 571 | ad->active.clear(); |
| 572 | ad->exit_thread.set(); |
| 573 | break; |
| 574 | } |
| 575 | } |
| 576 | } |
| 577 | } |
| 578 | |
| 579 | ad->stop_counting_ticks(); |
| 580 | ad->unlock(); |
| 581 | |
| 582 | // Let the thread rest a while if we haven't read or write anything |
| 583 | if (written_bytes == 0 && read_bytes == 0) { |
| 584 | OS::get_singleton()->delay_usec(1000); |
| 585 | } |
| 586 | } |
| 587 | } |
| 588 | |
| 589 | void AudioDriverPulseAudio::start() { |
| 590 | active.set(); |
| 591 | } |
| 592 | |
| 593 | int AudioDriverPulseAudio::get_mix_rate() const { |
| 594 | return mix_rate; |
| 595 | } |
| 596 | |
| 597 | AudioDriver::SpeakerMode AudioDriverPulseAudio::get_speaker_mode() const { |
| 598 | return get_speaker_mode_by_total_channels(channels); |
| 599 | } |
| 600 | |
| 601 | void AudioDriverPulseAudio::pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { |
| 602 | AudioDriverPulseAudio *ad = static_cast<AudioDriverPulseAudio *>(userdata); |
| 603 | |
| 604 | // If eol is set to a positive number, you're at the end of the list |
| 605 | if (eol > 0) { |
| 606 | return; |
| 607 | } |
| 608 | |
| 609 | ad->pa_devices.push_back(l->name); |
| 610 | ad->pa_status++; |
| 611 | } |
| 612 | |
| 613 | PackedStringArray AudioDriverPulseAudio::get_output_device_list() { |
| 614 | pa_devices.clear(); |
| 615 | pa_devices.push_back("Default" ); |
| 616 | |
| 617 | if (pa_ctx == nullptr) { |
| 618 | return pa_devices; |
| 619 | } |
| 620 | |
| 621 | lock(); |
| 622 | |
| 623 | // Get the output device list |
| 624 | pa_status = 0; |
| 625 | pa_operation *pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, (void *)this); |
| 626 | if (pa_op) { |
| 627 | while (pa_status == 0) { |
| 628 | int ret = pa_mainloop_iterate(pa_ml, 1, nullptr); |
| 629 | if (ret < 0) { |
| 630 | ERR_PRINT("pa_mainloop_iterate error" ); |
| 631 | } |
| 632 | } |
| 633 | |
| 634 | pa_operation_unref(pa_op); |
| 635 | } else { |
| 636 | ERR_PRINT("pa_context_get_server_info error" ); |
| 637 | } |
| 638 | |
| 639 | unlock(); |
| 640 | |
| 641 | return pa_devices; |
| 642 | } |
| 643 | |
| 644 | String AudioDriverPulseAudio::get_output_device() { |
| 645 | return output_device_name; |
| 646 | } |
| 647 | |
| 648 | void AudioDriverPulseAudio::set_output_device(const String &p_name) { |
| 649 | lock(); |
| 650 | new_output_device = p_name; |
| 651 | unlock(); |
| 652 | } |
| 653 | |
| 654 | void AudioDriverPulseAudio::lock() { |
| 655 | mutex.lock(); |
| 656 | } |
| 657 | |
| 658 | void AudioDriverPulseAudio::unlock() { |
| 659 | mutex.unlock(); |
| 660 | } |
| 661 | |
| 662 | void AudioDriverPulseAudio::finish_output_device() { |
| 663 | if (pa_str) { |
| 664 | pa_stream_disconnect(pa_str); |
| 665 | pa_stream_unref(pa_str); |
| 666 | pa_str = nullptr; |
| 667 | } |
| 668 | } |
| 669 | |
| 670 | void AudioDriverPulseAudio::finish() { |
| 671 | if (!thread.is_started()) { |
| 672 | return; |
| 673 | } |
| 674 | |
| 675 | exit_thread.set(); |
| 676 | if (thread.is_started()) { |
| 677 | thread.wait_to_finish(); |
| 678 | } |
| 679 | |
| 680 | finish_output_device(); |
| 681 | |
| 682 | if (pa_ctx) { |
| 683 | pa_context_disconnect(pa_ctx); |
| 684 | pa_context_unref(pa_ctx); |
| 685 | pa_ctx = nullptr; |
| 686 | } |
| 687 | |
| 688 | if (pa_ml) { |
| 689 | pa_mainloop_free(pa_ml); |
| 690 | pa_ml = nullptr; |
| 691 | } |
| 692 | } |
| 693 | |
| 694 | Error AudioDriverPulseAudio::init_input_device() { |
| 695 | // If there is a specified input device, check that it is really present |
| 696 | if (input_device_name != "Default" ) { |
| 697 | PackedStringArray list = get_input_device_list(); |
| 698 | if (list.find(input_device_name) == -1) { |
| 699 | input_device_name = "Default" ; |
| 700 | new_input_device = "Default" ; |
| 701 | } |
| 702 | } |
| 703 | |
| 704 | detect_channels(true); |
| 705 | switch (pa_rec_map.channels) { |
| 706 | case 1: // Mono |
| 707 | case 2: // Stereo |
| 708 | break; |
| 709 | |
| 710 | default: |
| 711 | WARN_PRINT("PulseAudio: Unsupported number of input channels: " + itos(pa_rec_map.channels)); |
| 712 | pa_channel_map_init_stereo(&pa_rec_map); |
| 713 | break; |
| 714 | } |
| 715 | |
| 716 | print_verbose("PulseAudio: detected " + itos(pa_rec_map.channels) + " input channels" ); |
| 717 | |
| 718 | pa_sample_spec spec; |
| 719 | |
| 720 | spec.format = PA_SAMPLE_S16LE; |
| 721 | spec.channels = pa_rec_map.channels; |
| 722 | spec.rate = mix_rate; |
| 723 | |
| 724 | int input_latency = 30; |
| 725 | int input_buffer_frames = closest_power_of_2(input_latency * mix_rate / 1000); |
| 726 | int input_buffer_size = input_buffer_frames * spec.channels; |
| 727 | |
| 728 | pa_buffer_attr attr; |
| 729 | attr.fragsize = input_buffer_size * sizeof(int16_t); |
| 730 | |
| 731 | pa_rec_str = pa_stream_new(pa_ctx, "Record" , &spec, &pa_rec_map); |
| 732 | if (pa_rec_str == nullptr) { |
| 733 | ERR_PRINT("PulseAudio: pa_stream_new error: " + String(pa_strerror(pa_context_errno(pa_ctx)))); |
| 734 | ERR_FAIL_V(ERR_CANT_OPEN); |
| 735 | } |
| 736 | |
| 737 | const char *dev = input_device_name == "Default" ? nullptr : input_device_name.utf8().get_data(); |
| 738 | pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); |
| 739 | int error_code = pa_stream_connect_record(pa_rec_str, dev, &attr, flags); |
| 740 | if (error_code < 0) { |
| 741 | ERR_PRINT("PulseAudio: pa_stream_connect_record error: " + String(pa_strerror(error_code))); |
| 742 | ERR_FAIL_V(ERR_CANT_OPEN); |
| 743 | } |
| 744 | |
| 745 | input_buffer_init(input_buffer_frames); |
| 746 | |
| 747 | print_verbose("PulseAudio: detected " + itos(pa_rec_map.channels) + " input channels" ); |
| 748 | print_verbose("PulseAudio: input buffer frames: " + itos(input_buffer_frames) + " calculated latency: " + itos(input_buffer_frames * 1000 / mix_rate) + "ms" ); |
| 749 | |
| 750 | return OK; |
| 751 | } |
| 752 | |
| 753 | void AudioDriverPulseAudio::finish_input_device() { |
| 754 | if (pa_rec_str) { |
| 755 | int ret = pa_stream_disconnect(pa_rec_str); |
| 756 | if (ret != 0) { |
| 757 | ERR_PRINT("PulseAudio: pa_stream_disconnect error: " + String(pa_strerror(ret))); |
| 758 | } |
| 759 | pa_stream_unref(pa_rec_str); |
| 760 | pa_rec_str = nullptr; |
| 761 | } |
| 762 | } |
| 763 | |
| 764 | Error AudioDriverPulseAudio::input_start() { |
| 765 | lock(); |
| 766 | Error err = init_input_device(); |
| 767 | unlock(); |
| 768 | |
| 769 | return err; |
| 770 | } |
| 771 | |
| 772 | Error AudioDriverPulseAudio::input_stop() { |
| 773 | lock(); |
| 774 | finish_input_device(); |
| 775 | unlock(); |
| 776 | |
| 777 | return OK; |
| 778 | } |
| 779 | |
| 780 | void AudioDriverPulseAudio::pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) { |
| 781 | AudioDriverPulseAudio *ad = static_cast<AudioDriverPulseAudio *>(userdata); |
| 782 | |
| 783 | // If eol is set to a positive number, you're at the end of the list |
| 784 | if (eol > 0) { |
| 785 | return; |
| 786 | } |
| 787 | |
| 788 | if (l->monitor_of_sink == PA_INVALID_INDEX) { |
| 789 | ad->pa_rec_devices.push_back(l->name); |
| 790 | } |
| 791 | |
| 792 | ad->pa_status++; |
| 793 | } |
| 794 | |
| 795 | PackedStringArray AudioDriverPulseAudio::get_input_device_list() { |
| 796 | pa_rec_devices.clear(); |
| 797 | pa_rec_devices.push_back("Default" ); |
| 798 | |
| 799 | if (pa_ctx == nullptr) { |
| 800 | return pa_rec_devices; |
| 801 | } |
| 802 | |
| 803 | lock(); |
| 804 | |
| 805 | // Get the device list |
| 806 | pa_status = 0; |
| 807 | pa_operation *pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, (void *)this); |
| 808 | if (pa_op) { |
| 809 | while (pa_status == 0) { |
| 810 | int ret = pa_mainloop_iterate(pa_ml, 1, nullptr); |
| 811 | if (ret < 0) { |
| 812 | ERR_PRINT("pa_mainloop_iterate error" ); |
| 813 | } |
| 814 | } |
| 815 | |
| 816 | pa_operation_unref(pa_op); |
| 817 | } else { |
| 818 | ERR_PRINT("pa_context_get_server_info error" ); |
| 819 | } |
| 820 | |
| 821 | unlock(); |
| 822 | |
| 823 | return pa_rec_devices; |
| 824 | } |
| 825 | |
| 826 | String AudioDriverPulseAudio::get_input_device() { |
| 827 | lock(); |
| 828 | String name = input_device_name; |
| 829 | unlock(); |
| 830 | |
| 831 | return name; |
| 832 | } |
| 833 | |
| 834 | void AudioDriverPulseAudio::set_input_device(const String &p_name) { |
| 835 | lock(); |
| 836 | new_input_device = p_name; |
| 837 | unlock(); |
| 838 | } |
| 839 | |
| 840 | AudioDriverPulseAudio::AudioDriverPulseAudio() { |
| 841 | samples_in.clear(); |
| 842 | samples_out.clear(); |
| 843 | } |
| 844 | |
| 845 | #endif // PULSEAUDIO_ENABLED |
| 846 | |