| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | /*************************************************************************/ | 
					
						
							| 
									
										
										
										
											2016-06-18 14:46:12 +02:00
										 |  |  | /*  audio_driver_pulseaudio.cpp                                          */ | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | /*************************************************************************/ | 
					
						
							|  |  |  | /*                       This file is part of:                           */ | 
					
						
							|  |  |  | /*                           GODOT ENGINE                                */ | 
					
						
							| 
									
										
										
										
											2017-08-27 14:16:55 +02:00
										 |  |  | /*                      https://godotengine.org                          */ | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | /*************************************************************************/ | 
					
						
							| 
									
										
										
										
											2019-01-01 12:53:14 +01:00
										 |  |  | /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */ | 
					
						
							|  |  |  | /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */ | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | /*                                                                       */ | 
					
						
							|  |  |  | /* Permission is hereby granted, free of charge, to any person obtaining */ | 
					
						
							|  |  |  | /* a copy of this software and associated documentation files (the       */ | 
					
						
							|  |  |  | /* "Software"), to deal in the Software without restriction, including   */ | 
					
						
							|  |  |  | /* without limitation the rights to use, copy, modify, merge, publish,   */ | 
					
						
							|  |  |  | /* distribute, sublicense, and/or sell copies of the Software, and to    */ | 
					
						
							|  |  |  | /* permit persons to whom the Software is furnished to do so, subject to */ | 
					
						
							|  |  |  | /* the following conditions:                                             */ | 
					
						
							|  |  |  | /*                                                                       */ | 
					
						
							|  |  |  | /* The above copyright notice and this permission notice shall be        */ | 
					
						
							|  |  |  | /* included in all copies or substantial portions of the Software.       */ | 
					
						
							|  |  |  | /*                                                                       */ | 
					
						
							|  |  |  | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ | 
					
						
							|  |  |  | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ | 
					
						
							|  |  |  | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ | 
					
						
							|  |  |  | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ | 
					
						
							|  |  |  | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ | 
					
						
							|  |  |  | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ | 
					
						
							|  |  |  | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ | 
					
						
							|  |  |  | /*************************************************************************/ | 
					
						
							| 
									
										
										
										
											2018-01-05 00:50:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | #include "audio_driver_pulseaudio.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef PULSEAUDIO_ENABLED
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-11 18:13:45 +02:00
										 |  |  | #include "core/os/os.h"
 | 
					
						
							|  |  |  | #include "core/project_settings.h"
 | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | void AudioDriverPulseAudio::pa_state_cb(pa_context *c, void *userdata) { | 
					
						
							|  |  |  | 	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	switch (pa_context_get_state(c)) { | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 		case PA_CONTEXT_TERMINATED: | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 		case PA_CONTEXT_FAILED: | 
					
						
							|  |  |  | 			ad->pa_ready = -1; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 			break; | 
					
						
							|  |  |  | 		case PA_CONTEXT_READY: | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 			ad->pa_ready = 1; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 			break; | 
					
						
							| 
									
										
										
										
											2018-09-26 13:13:56 +02:00
										 |  |  | 		default: | 
					
						
							|  |  |  | 			// TODO: Check if we want to handle some of the other
 | 
					
						
							|  |  |  | 			// PA context states like PA_CONTEXT_UNCONNECTED.
 | 
					
						
							|  |  |  | 			break; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | void AudioDriverPulseAudio::pa_sink_info_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { | 
					
						
							|  |  |  | 	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// If eol is set to a positive number, you're at the end of the list
 | 
					
						
							|  |  |  | 	if (eol > 0) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-12 12:01:23 -03:00
										 |  |  | 	ad->pa_map = l->channel_map; | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	ad->pa_status++; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | void AudioDriverPulseAudio::pa_source_info_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) { | 
					
						
							|  |  |  | 	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If eol is set to a positive number, you're at the end of the list
 | 
					
						
							|  |  |  | 	if (eol > 0) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ad->pa_rec_map = l->channel_map; | 
					
						
							|  |  |  | 	ad->pa_status++; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | void AudioDriverPulseAudio::pa_server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) { | 
					
						
							|  |  |  | 	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 	ad->capture_default_device = i->default_source_name; | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	ad->default_device = i->default_sink_name; | 
					
						
							|  |  |  | 	ad->pa_status++; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | void AudioDriverPulseAudio::detect_channels(bool capture) { | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 	pa_channel_map_init_stereo(capture ? &pa_rec_map : &pa_map); | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 	String device = capture ? capture_device_name : device_name; | 
					
						
							|  |  |  | 	if (device == "Default") { | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 		// Get the default output device name
 | 
					
						
							|  |  |  | 		pa_status = 0; | 
					
						
							|  |  |  | 		pa_operation *pa_op = pa_context_get_server_info(pa_ctx, &AudioDriverPulseAudio::pa_server_info_cb, (void *)this); | 
					
						
							|  |  |  | 		if (pa_op) { | 
					
						
							|  |  |  | 			while (pa_status == 0) { | 
					
						
							|  |  |  | 				int ret = pa_mainloop_iterate(pa_ml, 1, NULL); | 
					
						
							|  |  |  | 				if (ret < 0) { | 
					
						
							|  |  |  | 					ERR_PRINT("pa_mainloop_iterate error"); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 			pa_operation_unref(pa_op); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			ERR_PRINT("pa_context_get_server_info error"); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 	char dev[1024]; | 
					
						
							|  |  |  | 	if (device == "Default") { | 
					
						
							|  |  |  | 		strcpy(dev, capture ? capture_default_device.utf8().get_data() : default_device.utf8().get_data()); | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 		strcpy(dev, device.utf8().get_data()); | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	// Now using the device name get the amount of channels
 | 
					
						
							|  |  |  | 	pa_status = 0; | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 	pa_operation *pa_op; | 
					
						
							|  |  |  | 	if (capture) { | 
					
						
							|  |  |  | 		pa_op = pa_context_get_source_info_by_name(pa_ctx, dev, &AudioDriverPulseAudio::pa_source_info_cb, (void *)this); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		pa_op = pa_context_get_sink_info_by_name(pa_ctx, dev, &AudioDriverPulseAudio::pa_sink_info_cb, (void *)this); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 	if (pa_op) { | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 		while (pa_status == 0) { | 
					
						
							|  |  |  | 			int ret = pa_mainloop_iterate(pa_ml, 1, NULL); | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 			if (ret < 0) { | 
					
						
							|  |  |  | 				ERR_PRINT("pa_mainloop_iterate error"); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		pa_operation_unref(pa_op); | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 		if (capture) { | 
					
						
							|  |  |  | 			ERR_PRINT("pa_context_get_source_info_by_name error"); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			ERR_PRINT("pa_context_get_sink_info_by_name error"); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | Error AudioDriverPulseAudio::init_device() { | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	// If there is a specified device check that it is really present
 | 
					
						
							|  |  |  | 	if (device_name != "Default") { | 
					
						
							|  |  |  | 		Array list = get_device_list(); | 
					
						
							|  |  |  | 		if (list.find(device_name) == -1) { | 
					
						
							|  |  |  | 			device_name = "Default"; | 
					
						
							|  |  |  | 			new_device = "Default"; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-28 22:37:42 -03:00
										 |  |  | 	// Detect the amount of channels PulseAudio is using
 | 
					
						
							| 
									
										
										
										
											2018-04-12 12:01:23 -03:00
										 |  |  | 	// Note: If using an even amount of channels (2, 4, etc) channels and pa_map.channels will be equal,
 | 
					
						
							|  |  |  | 	// if not then pa_map.channels will have the real amount of channels PulseAudio is using and channels
 | 
					
						
							|  |  |  | 	// will have the amount of channels Godot is using (in this case it's pa_map.channels + 1)
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	detect_channels(); | 
					
						
							| 
									
										
										
										
											2018-04-12 12:01:23 -03:00
										 |  |  | 	switch (pa_map.channels) { | 
					
						
							| 
									
										
										
										
											2018-02-28 22:37:42 -03:00
										 |  |  | 		case 1: // Mono
 | 
					
						
							|  |  |  | 		case 3: // Surround 2.1
 | 
					
						
							|  |  |  | 		case 5: // Surround 5.0
 | 
					
						
							|  |  |  | 		case 7: // Surround 7.0
 | 
					
						
							| 
									
										
										
										
											2018-04-12 12:01:23 -03:00
										 |  |  | 			channels = pa_map.channels + 1; | 
					
						
							| 
									
										
										
										
											2018-02-28 22:37:42 -03:00
										 |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 		case 2: // Stereo
 | 
					
						
							| 
									
										
										
										
											2018-02-28 22:37:42 -03:00
										 |  |  | 		case 4: // Surround 4.0
 | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 		case 6: // Surround 5.1
 | 
					
						
							|  |  |  | 		case 8: // Surround 7.1
 | 
					
						
							| 
									
										
										
										
											2018-04-12 12:01:23 -03:00
										 |  |  | 			channels = pa_map.channels; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		default: | 
					
						
							| 
									
										
										
										
											2018-04-12 12:01:23 -03:00
										 |  |  | 			WARN_PRINTS("PulseAudio: Unsupported number of channels: " + itos(pa_map.channels)); | 
					
						
							|  |  |  | 			pa_channel_map_init_stereo(&pa_map); | 
					
						
							| 
									
										
										
										
											2018-04-11 12:04:23 -03:00
										 |  |  | 			channels = 2; | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 			break; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-19 18:58:15 -03:00
										 |  |  | 	int latency = GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY); | 
					
						
							| 
									
										
										
										
											2017-08-29 16:47:44 -03:00
										 |  |  | 	buffer_frames = closest_power_of_2(latency * mix_rate / 1000); | 
					
						
							| 
									
										
										
										
											2018-04-12 12:01:23 -03:00
										 |  |  | 	pa_buffer_size = buffer_frames * pa_map.channels; | 
					
						
							| 
									
										
										
										
											2017-08-29 16:47:44 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-24 08:47:34 +02:00
										 |  |  | 	print_verbose("PulseAudio: detected " + itos(pa_map.channels) + " channels"); | 
					
						
							|  |  |  | 	print_verbose("PulseAudio: audio buffer frames: " + itos(buffer_frames) + " calculated latency: " + itos(buffer_frames * 1000 / mix_rate) + "ms"); | 
					
						
							| 
									
										
										
										
											2017-01-03 19:00:31 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-12 12:01:23 -03:00
										 |  |  | 	pa_sample_spec spec; | 
					
						
							|  |  |  | 	spec.format = PA_SAMPLE_S16LE; | 
					
						
							|  |  |  | 	spec.channels = pa_map.channels; | 
					
						
							|  |  |  | 	spec.rate = mix_rate; | 
					
						
							| 
									
										
										
										
											2019-07-01 22:03:02 +02:00
										 |  |  | 	pa_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; | 
					
						
							|  |  |  | 	pa_map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; | 
					
						
							|  |  |  | 	pa_map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; | 
					
						
							|  |  |  | 	pa_map.map[3] = PA_CHANNEL_POSITION_LFE; | 
					
						
							|  |  |  | 	pa_map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; | 
					
						
							|  |  |  | 	pa_map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; | 
					
						
							|  |  |  | 	pa_map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; | 
					
						
							|  |  |  | 	pa_map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; | 
					
						
							| 
									
										
										
										
											2018-04-12 12:01:23 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	pa_str = pa_stream_new(pa_ctx, "Sound", &spec, &pa_map); | 
					
						
							|  |  |  | 	if (pa_str == NULL) { | 
					
						
							|  |  |  | 		ERR_PRINTS("PulseAudio: pa_stream_new error: " + String(pa_strerror(pa_context_errno(pa_ctx)))); | 
					
						
							|  |  |  | 		ERR_FAIL_V(ERR_CANT_OPEN); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-03 19:00:31 +01:00
										 |  |  | 	pa_buffer_attr attr; | 
					
						
							| 
									
										
										
										
											2017-08-29 16:47:44 -03:00
										 |  |  | 	// set to appropriate buffer length (in bytes) from global settings
 | 
					
						
							| 
									
										
										
										
											2018-04-15 11:29:16 -03:00
										 |  |  | 	// Note: PulseAudio defaults to 4 fragments, which means that the actual
 | 
					
						
							|  |  |  | 	// latency is tlength / fragments. It seems that the PulseAudio has no way
 | 
					
						
							|  |  |  | 	// to get the fragments number so we're hardcoding this to the default of 4
 | 
					
						
							|  |  |  | 	const int fragments = 4; | 
					
						
							|  |  |  | 	attr.tlength = pa_buffer_size * sizeof(int16_t) * fragments; | 
					
						
							| 
									
										
										
										
											2017-01-03 19:00:31 +01:00
										 |  |  | 	// set them to be automatically chosen
 | 
					
						
							|  |  |  | 	attr.prebuf = (uint32_t)-1; | 
					
						
							|  |  |  | 	attr.maxlength = (uint32_t)-1; | 
					
						
							|  |  |  | 	attr.minreq = (uint32_t)-1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	const char *dev = device_name == "Default" ? NULL : device_name.utf8().get_data(); | 
					
						
							|  |  |  | 	pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); | 
					
						
							|  |  |  | 	int error_code = pa_stream_connect_playback(pa_str, dev, &attr, flags, NULL, NULL); | 
					
						
							|  |  |  | 	ERR_FAIL_COND_V(error_code < 0, ERR_CANT_OPEN); | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-28 22:37:42 -03:00
										 |  |  | 	samples_in.resize(buffer_frames * channels); | 
					
						
							|  |  |  | 	samples_out.resize(pa_buffer_size); | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 	// Reset audio input to keep synchronisation.
 | 
					
						
							|  |  |  | 	input_position = 0; | 
					
						
							|  |  |  | 	input_size = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	return OK; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Error AudioDriverPulseAudio::init() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	active = false; | 
					
						
							|  |  |  | 	thread_exited = false; | 
					
						
							|  |  |  | 	exit_thread = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-19 18:58:15 -03:00
										 |  |  | 	mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	pa_ml = pa_mainloop_new(); | 
					
						
							|  |  |  | 	ERR_FAIL_COND_V(pa_ml == NULL, ERR_CANT_OPEN); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pa_ctx = pa_context_new(pa_mainloop_get_api(pa_ml), "Godot"); | 
					
						
							|  |  |  | 	ERR_FAIL_COND_V(pa_ctx == NULL, ERR_CANT_OPEN); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pa_ready = 0; | 
					
						
							|  |  |  | 	pa_context_set_state_callback(pa_ctx, pa_state_cb, (void *)this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	int ret = pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL); | 
					
						
							|  |  |  | 	if (ret < 0) { | 
					
						
							|  |  |  | 		if (pa_ctx) { | 
					
						
							|  |  |  | 			pa_context_unref(pa_ctx); | 
					
						
							|  |  |  | 			pa_ctx = NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (pa_ml) { | 
					
						
							|  |  |  | 			pa_mainloop_free(pa_ml); | 
					
						
							|  |  |  | 			pa_ml = NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return ERR_CANT_OPEN; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	while (pa_ready == 0) { | 
					
						
							|  |  |  | 		pa_mainloop_iterate(pa_ml, 1, NULL); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pa_ready < 0) { | 
					
						
							|  |  |  | 		if (pa_ctx) { | 
					
						
							|  |  |  | 			pa_context_disconnect(pa_ctx); | 
					
						
							|  |  |  | 			pa_context_unref(pa_ctx); | 
					
						
							|  |  |  | 			pa_ctx = NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (pa_ml) { | 
					
						
							|  |  |  | 			pa_mainloop_free(pa_ml); | 
					
						
							|  |  |  | 			pa_ml = NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return ERR_CANT_OPEN; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Error err = init_device(); | 
					
						
							|  |  |  | 	if (err == OK) { | 
					
						
							|  |  |  | 		mutex = Mutex::create(); | 
					
						
							|  |  |  | 		thread = Thread::create(AudioDriverPulseAudio::thread_func, this); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return OK; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-09-26 14:50:42 -03:00
										 |  |  | float AudioDriverPulseAudio::get_latency() { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-05 16:44:50 +01:00
										 |  |  | 	if (latency == 0) { //only do this once since it's approximate anyway
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 		lock(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		pa_usec_t palat = 0; | 
					
						
							|  |  |  | 		if (pa_stream_get_state(pa_str) == PA_STREAM_READY) { | 
					
						
							|  |  |  | 			int negative = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (pa_stream_get_latency(pa_str, &palat, &negative) >= 0) { | 
					
						
							|  |  |  | 				if (negative) { | 
					
						
							|  |  |  | 					palat = 0; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (palat > 0) { | 
					
						
							|  |  |  | 			latency = double(palat) / 1000000.0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		unlock(); | 
					
						
							| 
									
										
										
										
											2015-09-26 14:50:42 -03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return latency; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-05 16:44:50 +01:00
										 |  |  | void AudioDriverPulseAudio::thread_func(void *p_udata) { | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-05 16:44:50 +01:00
										 |  |  | 	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)p_udata; | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	unsigned int write_ofs = 0; | 
					
						
							|  |  |  | 	size_t avail_bytes = 0; | 
					
						
							| 
									
										
										
										
											2018-12-12 11:17:46 -03:00
										 |  |  | 	uint32_t default_device_msec = OS::get_singleton()->get_ticks_msec(); | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	while (!ad->exit_thread) { | 
					
						
							| 
									
										
										
										
											2018-06-20 21:02:02 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 		size_t read_bytes = 0; | 
					
						
							|  |  |  | 		size_t written_bytes = 0; | 
					
						
							| 
									
										
										
										
											2018-06-20 21:02:02 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 		if (avail_bytes == 0) { | 
					
						
							|  |  |  | 			ad->lock(); | 
					
						
							|  |  |  | 			ad->start_counting_ticks(); | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 			if (!ad->active) { | 
					
						
							| 
									
										
										
										
											2018-02-28 22:37:42 -03:00
										 |  |  | 				for (unsigned int i = 0; i < ad->pa_buffer_size; i++) { | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 					ad->samples_out.write[i] = 0; | 
					
						
							| 
									
										
										
										
											2018-02-28 22:37:42 -03:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 				ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptrw()); | 
					
						
							| 
									
										
										
										
											2018-02-28 22:37:42 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 				if (ad->channels == ad->pa_map.channels) { | 
					
						
							|  |  |  | 					for (unsigned int i = 0; i < ad->pa_buffer_size; i++) { | 
					
						
							|  |  |  | 						ad->samples_out.write[i] = ad->samples_in[i] >> 16; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					// Uneven amount of channels
 | 
					
						
							|  |  |  | 					unsigned int in_idx = 0; | 
					
						
							|  |  |  | 					unsigned int out_idx = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					for (unsigned int i = 0; i < ad->buffer_frames; i++) { | 
					
						
							| 
									
										
										
										
											2018-09-26 17:38:02 +02:00
										 |  |  | 						for (int j = 0; j < ad->pa_map.channels - 1; j++) { | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 							ad->samples_out.write[out_idx++] = ad->samples_in[in_idx++] >> 16; | 
					
						
							|  |  |  | 						} | 
					
						
							| 
									
										
										
										
											2018-11-17 21:16:06 -03:00
										 |  |  | 						uint32_t l = ad->samples_in[in_idx++] >> 16; | 
					
						
							|  |  |  | 						uint32_t r = ad->samples_in[in_idx++] >> 16; | 
					
						
							|  |  |  | 						ad->samples_out.write[out_idx++] = (l + r) / 2; | 
					
						
							| 
									
										
										
										
											2018-02-28 22:37:42 -03:00
										 |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			avail_bytes = ad->pa_buffer_size * sizeof(int16_t); | 
					
						
							|  |  |  | 			write_ofs = 0; | 
					
						
							|  |  |  | 			ad->stop_counting_ticks(); | 
					
						
							|  |  |  | 			ad->unlock(); | 
					
						
							| 
									
										
										
										
											2017-01-21 19:00:25 -03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 		ad->lock(); | 
					
						
							|  |  |  | 		ad->start_counting_ticks(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 		int ret; | 
					
						
							|  |  |  | 		do { | 
					
						
							|  |  |  | 			ret = pa_mainloop_iterate(ad->pa_ml, 0, NULL); | 
					
						
							|  |  |  | 		} while (ret > 0); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 		if (avail_bytes > 0 && pa_stream_get_state(ad->pa_str) == PA_STREAM_READY) { | 
					
						
							|  |  |  | 			size_t bytes = pa_stream_writable_size(ad->pa_str); | 
					
						
							|  |  |  | 			if (bytes > 0) { | 
					
						
							|  |  |  | 				size_t bytes_to_write = MIN(bytes, avail_bytes); | 
					
						
							|  |  |  | 				const void *ptr = ad->samples_out.ptr(); | 
					
						
							| 
									
										
										
										
											2018-08-11 21:27:59 -03:00
										 |  |  | 				ret = pa_stream_write(ad->pa_str, (char *)ptr + write_ofs, bytes_to_write, NULL, 0LL, PA_SEEK_RELATIVE); | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 				if (ret != 0) { | 
					
						
							| 
									
										
										
										
											2018-11-25 14:57:31 -03:00
										 |  |  | 					ERR_PRINTS("PulseAudio: pa_stream_write error: " + String(pa_strerror(ret))); | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 					avail_bytes -= bytes_to_write; | 
					
						
							|  |  |  | 					write_ofs += bytes_to_write; | 
					
						
							|  |  |  | 					written_bytes += bytes_to_write; | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-01-03 19:00:31 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// User selected a new device, finish the current one so we'll init the new device
 | 
					
						
							|  |  |  | 		if (ad->device_name != ad->new_device) { | 
					
						
							|  |  |  | 			ad->device_name = ad->new_device; | 
					
						
							|  |  |  | 			ad->finish_device(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Error err = ad->init_device(); | 
					
						
							|  |  |  | 			if (err != OK) { | 
					
						
							|  |  |  | 				ERR_PRINT("PulseAudio: init_device error"); | 
					
						
							|  |  |  | 				ad->device_name = "Default"; | 
					
						
							|  |  |  | 				ad->new_device = "Default"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				err = ad->init_device(); | 
					
						
							|  |  |  | 				if (err != OK) { | 
					
						
							|  |  |  | 					ad->active = false; | 
					
						
							|  |  |  | 					ad->exit_thread = true; | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-11-25 14:57:31 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			avail_bytes = 0; | 
					
						
							|  |  |  | 			write_ofs = 0; | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-12 11:17:46 -03:00
										 |  |  | 		// If we're using the default device check that the current device is still the default
 | 
					
						
							|  |  |  | 		if (ad->device_name == "Default") { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			uint32_t msec = OS::get_singleton()->get_ticks_msec(); | 
					
						
							|  |  |  | 			if (msec > (default_device_msec + 1000)) { | 
					
						
							|  |  |  | 				String old_default_device = ad->default_device; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				default_device_msec = msec; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				ad->pa_status = 0; | 
					
						
							|  |  |  | 				pa_operation *pa_op = pa_context_get_server_info(ad->pa_ctx, &AudioDriverPulseAudio::pa_server_info_cb, (void *)ad); | 
					
						
							|  |  |  | 				if (pa_op) { | 
					
						
							|  |  |  | 					while (ad->pa_status == 0) { | 
					
						
							| 
									
										
										
										
											2019-02-12 21:10:08 +01:00
										 |  |  | 						ret = pa_mainloop_iterate(ad->pa_ml, 1, NULL); | 
					
						
							| 
									
										
										
										
											2018-12-12 11:17:46 -03:00
										 |  |  | 						if (ret < 0) { | 
					
						
							|  |  |  | 							ERR_PRINT("pa_mainloop_iterate error"); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					pa_operation_unref(pa_op); | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					ERR_PRINT("pa_context_get_server_info error"); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if (old_default_device != ad->default_device) { | 
					
						
							|  |  |  | 					ad->finish_device(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					Error err = ad->init_device(); | 
					
						
							|  |  |  | 					if (err != OK) { | 
					
						
							|  |  |  | 						ERR_PRINT("PulseAudio: init_device error"); | 
					
						
							|  |  |  | 						ad->active = false; | 
					
						
							|  |  |  | 						ad->exit_thread = true; | 
					
						
							|  |  |  | 						break; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					avail_bytes = 0; | 
					
						
							|  |  |  | 					write_ofs = 0; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 		if (ad->pa_rec_str && pa_stream_get_state(ad->pa_rec_str) == PA_STREAM_READY) { | 
					
						
							|  |  |  | 			size_t bytes = pa_stream_readable_size(ad->pa_rec_str); | 
					
						
							|  |  |  | 			if (bytes > 0) { | 
					
						
							|  |  |  | 				const void *ptr = NULL; | 
					
						
							| 
									
										
										
										
											2018-07-27 15:09:03 -03:00
										 |  |  | 				size_t maxbytes = ad->input_buffer.size() * sizeof(int16_t); | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				bytes = MIN(bytes, maxbytes); | 
					
						
							|  |  |  | 				ret = pa_stream_peek(ad->pa_rec_str, &ptr, &bytes); | 
					
						
							|  |  |  | 				if (ret != 0) { | 
					
						
							|  |  |  | 					ERR_PRINT("pa_stream_peek error"); | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					int16_t *srcptr = (int16_t *)ptr; | 
					
						
							|  |  |  | 					for (size_t i = bytes >> 1; i > 0; i--) { | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 						int32_t sample = int32_t(*srcptr++) << 16; | 
					
						
							|  |  |  | 						ad->input_buffer_write(sample); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						if (ad->pa_rec_map.channels == 1) { | 
					
						
							|  |  |  | 							// In case input device is single channel convert it to Stereo
 | 
					
						
							|  |  |  | 							ad->input_buffer_write(sample); | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					read_bytes += bytes; | 
					
						
							|  |  |  | 					ret = pa_stream_drop(ad->pa_rec_str); | 
					
						
							|  |  |  | 					if (ret != 0) { | 
					
						
							|  |  |  | 						ERR_PRINT("pa_stream_drop error"); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// User selected a new device, finish the current one so we'll init the new device
 | 
					
						
							|  |  |  | 			if (ad->capture_device_name != ad->capture_new_device) { | 
					
						
							|  |  |  | 				ad->capture_device_name = ad->capture_new_device; | 
					
						
							|  |  |  | 				ad->capture_finish_device(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				Error err = ad->capture_init_device(); | 
					
						
							|  |  |  | 				if (err != OK) { | 
					
						
							|  |  |  | 					ERR_PRINT("PulseAudio: capture_init_device error"); | 
					
						
							|  |  |  | 					ad->capture_device_name = "Default"; | 
					
						
							|  |  |  | 					ad->capture_new_device = "Default"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					err = ad->capture_init_device(); | 
					
						
							|  |  |  | 					if (err != OK) { | 
					
						
							|  |  |  | 						ad->active = false; | 
					
						
							|  |  |  | 						ad->exit_thread = true; | 
					
						
							|  |  |  | 						break; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-20 21:02:02 -03:00
										 |  |  | 		ad->stop_counting_ticks(); | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 		ad->unlock(); | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Let the thread rest a while if we haven't read or write anything
 | 
					
						
							|  |  |  | 		if (written_bytes == 0 && read_bytes == 0) { | 
					
						
							|  |  |  | 			OS::get_singleton()->delay_usec(1000); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-01-21 19:00:25 -03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-03 19:00:31 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ad->thread_exited = true; | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AudioDriverPulseAudio::start() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	active = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int AudioDriverPulseAudio::get_mix_rate() const { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return mix_rate; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-15 16:06:14 -03:00
										 |  |  | AudioDriver::SpeakerMode AudioDriverPulseAudio::get_speaker_mode() const { | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 	return get_speaker_mode_by_total_channels(channels); | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | void AudioDriverPulseAudio::pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { | 
					
						
							|  |  |  | 	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If eol is set to a positive number, you're at the end of the list
 | 
					
						
							|  |  |  | 	if (eol > 0) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ad->pa_devices.push_back(l->name); | 
					
						
							|  |  |  | 	ad->pa_status++; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Array AudioDriverPulseAudio::get_device_list() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pa_devices.clear(); | 
					
						
							|  |  |  | 	pa_devices.push_back("Default"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pa_ctx == NULL) { | 
					
						
							|  |  |  | 		return pa_devices; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lock(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get the device list
 | 
					
						
							|  |  |  | 	pa_status = 0; | 
					
						
							|  |  |  | 	pa_operation *pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, (void *)this); | 
					
						
							|  |  |  | 	if (pa_op) { | 
					
						
							|  |  |  | 		while (pa_status == 0) { | 
					
						
							|  |  |  | 			int ret = pa_mainloop_iterate(pa_ml, 1, NULL); | 
					
						
							|  |  |  | 			if (ret < 0) { | 
					
						
							|  |  |  | 				ERR_PRINT("pa_mainloop_iterate error"); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		pa_operation_unref(pa_op); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ERR_PRINT("pa_context_get_server_info error"); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	unlock(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return pa_devices; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | String AudioDriverPulseAudio::get_device() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return device_name; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AudioDriverPulseAudio::set_device(String device) { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-20 21:02:02 -03:00
										 |  |  | 	lock(); | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	new_device = device; | 
					
						
							| 
									
										
										
										
											2018-06-20 21:02:02 -03:00
										 |  |  | 	unlock(); | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | void AudioDriverPulseAudio::lock() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!thread || !mutex) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	mutex->lock(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AudioDriverPulseAudio::unlock() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!thread || !mutex) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	mutex->unlock(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | void AudioDriverPulseAudio::finish_device() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pa_str) { | 
					
						
							|  |  |  | 		pa_stream_disconnect(pa_str); | 
					
						
							|  |  |  | 		pa_stream_unref(pa_str); | 
					
						
							|  |  |  | 		pa_str = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | void AudioDriverPulseAudio::finish() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!thread) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	exit_thread = true; | 
					
						
							|  |  |  | 	Thread::wait_to_finish(thread); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 00:43:51 -03:00
										 |  |  | 	finish_device(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pa_ctx) { | 
					
						
							|  |  |  | 		pa_context_disconnect(pa_ctx); | 
					
						
							|  |  |  | 		pa_context_unref(pa_ctx); | 
					
						
							|  |  |  | 		pa_ctx = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pa_ml) { | 
					
						
							|  |  |  | 		pa_mainloop_free(pa_ml); | 
					
						
							|  |  |  | 		pa_ml = NULL; | 
					
						
							| 
									
										
										
										
											2017-08-29 16:47:44 -03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	memdelete(thread); | 
					
						
							| 
									
										
										
										
											2017-01-03 19:00:31 +01:00
										 |  |  | 	if (mutex) { | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 		memdelete(mutex); | 
					
						
							| 
									
										
										
										
											2017-01-03 19:00:31 +01:00
										 |  |  | 		mutex = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	thread = NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | Error AudioDriverPulseAudio::capture_init_device() { | 
					
						
							| 
									
										
										
										
											2018-02-27 07:54:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | 	// If there is a specified device check that it is really present
 | 
					
						
							|  |  |  | 	if (capture_device_name != "Default") { | 
					
						
							|  |  |  | 		Array list = capture_get_device_list(); | 
					
						
							|  |  |  | 		if (list.find(capture_device_name) == -1) { | 
					
						
							|  |  |  | 			capture_device_name = "Default"; | 
					
						
							|  |  |  | 			capture_new_device = "Default"; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-27 07:54:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 	detect_channels(true); | 
					
						
							|  |  |  | 	switch (pa_rec_map.channels) { | 
					
						
							|  |  |  | 		case 1: // Mono
 | 
					
						
							|  |  |  | 		case 2: // Stereo
 | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			WARN_PRINTS("PulseAudio: Unsupported number of input channels: " + itos(pa_rec_map.channels)); | 
					
						
							|  |  |  | 			pa_channel_map_init_stereo(&pa_rec_map); | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	pa_sample_spec spec; | 
					
						
							| 
									
										
										
										
											2018-02-27 07:54:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	spec.format = PA_SAMPLE_S16LE; | 
					
						
							| 
									
										
										
										
											2018-07-27 17:28:41 -03:00
										 |  |  | 	spec.channels = pa_rec_map.channels; | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	spec.rate = mix_rate; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-19 16:32:03 -03:00
										 |  |  | 	int input_latency = 30; | 
					
						
							|  |  |  | 	int input_buffer_frames = closest_power_of_2(input_latency * mix_rate / 1000); | 
					
						
							|  |  |  | 	int input_buffer_size = input_buffer_frames * spec.channels; | 
					
						
							| 
									
										
										
										
											2018-02-27 07:54:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	pa_buffer_attr attr; | 
					
						
							| 
									
										
										
										
											2018-10-19 16:32:03 -03:00
										 |  |  | 	attr.fragsize = input_buffer_size * sizeof(int16_t); | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	pa_rec_str = pa_stream_new(pa_ctx, "Record", &spec, &pa_rec_map); | 
					
						
							|  |  |  | 	if (pa_rec_str == NULL) { | 
					
						
							|  |  |  | 		ERR_PRINTS("PulseAudio: pa_stream_new error: " + String(pa_strerror(pa_context_errno(pa_ctx)))); | 
					
						
							|  |  |  | 		ERR_FAIL_V(ERR_CANT_OPEN); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | 	const char *dev = capture_device_name == "Default" ? NULL : capture_device_name.utf8().get_data(); | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | 	int error_code = pa_stream_connect_record(pa_rec_str, dev, &attr, flags); | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	if (error_code < 0) { | 
					
						
							|  |  |  | 		ERR_PRINTS("PulseAudio: pa_stream_connect_record error: " + String(pa_strerror(error_code))); | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | 		ERR_FAIL_V(ERR_CANT_OPEN); | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-19 16:32:03 -03:00
										 |  |  | 	input_buffer_init(input_buffer_frames); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	print_verbose("PulseAudio: detected " + itos(pa_rec_map.channels) + " input channels"); | 
					
						
							|  |  |  | 	print_verbose("PulseAudio: input buffer frames: " + itos(input_buffer_frames) + " calculated latency: " + itos(input_buffer_frames * 1000 / mix_rate) + "ms"); | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | 	return OK; | 
					
						
							| 
									
										
										
										
											2018-02-27 07:54:56 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | void AudioDriverPulseAudio::capture_finish_device() { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	if (pa_rec_str) { | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | 		int ret = pa_stream_disconnect(pa_rec_str); | 
					
						
							|  |  |  | 		if (ret != 0) { | 
					
						
							|  |  |  | 			ERR_PRINTS("PulseAudio: pa_stream_disconnect error: " + String(pa_strerror(ret))); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 		pa_stream_unref(pa_rec_str); | 
					
						
							|  |  |  | 		pa_rec_str = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Error AudioDriverPulseAudio::capture_start() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lock(); | 
					
						
							|  |  |  | 	Error err = capture_init_device(); | 
					
						
							|  |  |  | 	unlock(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return err; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Error AudioDriverPulseAudio::capture_stop() { | 
					
						
							|  |  |  | 	lock(); | 
					
						
							|  |  |  | 	capture_finish_device(); | 
					
						
							|  |  |  | 	unlock(); | 
					
						
							| 
									
										
										
										
											2018-02-27 07:54:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-03 22:08:43 -03:00
										 |  |  | 	return OK; | 
					
						
							| 
									
										
										
										
											2018-02-27 07:54:56 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 16:15:24 +01:00
										 |  |  | void AudioDriverPulseAudio::capture_set_device(const String &p_name) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lock(); | 
					
						
							|  |  |  | 	capture_new_device = p_name; | 
					
						
							|  |  |  | 	unlock(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AudioDriverPulseAudio::pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) { | 
					
						
							|  |  |  | 	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If eol is set to a positive number, you're at the end of the list
 | 
					
						
							|  |  |  | 	if (eol > 0) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (l->monitor_of_sink == PA_INVALID_INDEX) { | 
					
						
							|  |  |  | 		ad->pa_rec_devices.push_back(l->name); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ad->pa_status++; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Array AudioDriverPulseAudio::capture_get_device_list() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pa_rec_devices.clear(); | 
					
						
							|  |  |  | 	pa_rec_devices.push_back("Default"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pa_ctx == NULL) { | 
					
						
							|  |  |  | 		return pa_rec_devices; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lock(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get the device list
 | 
					
						
							|  |  |  | 	pa_status = 0; | 
					
						
							|  |  |  | 	pa_operation *pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, (void *)this); | 
					
						
							|  |  |  | 	if (pa_op) { | 
					
						
							|  |  |  | 		while (pa_status == 0) { | 
					
						
							|  |  |  | 			int ret = pa_mainloop_iterate(pa_ml, 1, NULL); | 
					
						
							|  |  |  | 			if (ret < 0) { | 
					
						
							|  |  |  | 				ERR_PRINT("pa_mainloop_iterate error"); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		pa_operation_unref(pa_op); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ERR_PRINT("pa_context_get_server_info error"); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	unlock(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return pa_rec_devices; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | String AudioDriverPulseAudio::capture_get_device() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lock(); | 
					
						
							|  |  |  | 	String name = capture_device_name; | 
					
						
							|  |  |  | 	unlock(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return name; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-08 15:07:33 -05:00
										 |  |  | AudioDriverPulseAudio::AudioDriverPulseAudio() : | 
					
						
							|  |  |  | 		thread(NULL), | 
					
						
							|  |  |  | 		mutex(NULL), | 
					
						
							|  |  |  | 		pa_ml(NULL), | 
					
						
							|  |  |  | 		pa_ctx(NULL), | 
					
						
							|  |  |  | 		pa_str(NULL), | 
					
						
							|  |  |  | 		pa_rec_str(NULL), | 
					
						
							|  |  |  | 		device_name("Default"), | 
					
						
							|  |  |  | 		new_device("Default"), | 
					
						
							|  |  |  | 		default_device(""), | 
					
						
							|  |  |  | 		mix_rate(0), | 
					
						
							|  |  |  | 		buffer_frames(0), | 
					
						
							|  |  |  | 		pa_buffer_size(0), | 
					
						
							|  |  |  | 		channels(0), | 
					
						
							|  |  |  | 		pa_ready(0), | 
					
						
							|  |  |  | 		pa_status(0), | 
					
						
							|  |  |  | 		active(false), | 
					
						
							|  |  |  | 		thread_exited(false), | 
					
						
							|  |  |  | 		exit_thread(false), | 
					
						
							|  |  |  | 		latency(0) { | 
					
						
							| 
									
										
										
										
											2017-08-22 18:27:17 -03:00
										 |  |  | 	samples_in.clear(); | 
					
						
							|  |  |  | 	samples_out.clear(); | 
					
						
							| 
									
										
										
										
											2014-12-19 13:44:34 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | AudioDriverPulseAudio::~AudioDriverPulseAudio() { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif
 |