diff --git a/plugins/linux-pulseaudio/pulse-input.c b/plugins/linux-pulseaudio/pulse-input.c index 8c920ead1b44c394c7c903778ebc25bc3ed2bffa..eaf6d900a5711fddce02f129fc3dff306b19c285 100644 --- a/plugins/linux-pulseaudio/pulse-input.c +++ b/plugins/linux-pulseaudio/pulse-input.c @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#include #include #include @@ -34,9 +35,13 @@ struct pulse_data { uint_fast32_t bytes_per_frame; pa_stream *stream; + + bool ostime; }; -/* +static void pulse_stop_recording(struct pulse_data *data); + +/** * get obs from pulse audio format */ static enum audio_format pulse_to_obs_audio_format( @@ -58,8 +63,8 @@ static enum audio_format pulse_to_obs_audio_format( return AUDIO_FORMAT_UNKNOWN; } -/* - * get the buffer size needed for length msec with current settings +/** + * Get the buffer size needed for length msec with current settings */ static uint_fast32_t get_buffer_size(struct pulse_data *data, uint_fast32_t length) @@ -67,7 +72,7 @@ static uint_fast32_t get_buffer_size(struct pulse_data *data, return (length * data->samples_per_sec * data->bytes_per_frame) / 1000; } -/* +/** * Get latency for a pulse audio stream */ static int pulse_get_stream_latency(pa_stream *stream, int64_t *latency) @@ -81,8 +86,10 @@ static int pulse_get_stream_latency(pa_stream *stream, int64_t *latency) return ret; } -/* +/** * Callback for pulse which gets executed when new audio data is available + * + * @warning The function may be called even after disconnecting the stream */ static void pulse_stream_read(pa_stream *p, size_t nbytes, void *userdata) { @@ -95,6 +102,9 @@ static void pulse_stream_read(pa_stream *p, size_t nbytes, void *userdata) uint64_t pa_time; int64_t pa_latency; + if (!data->stream) + goto exit; + pa_stream_peek(data->stream, &frames, &bytes); // check if we got data @@ -115,6 +125,7 @@ static void pulse_stream_read(pa_stream *p, size_t nbytes, void *userdata) pa_stream_drop(data->stream); goto exit; } + pa_time = (!data->ostime) ? pa_time * 1000 : os_gettime_ns(); pulse_get_stream_latency(data->stream, &pa_latency); @@ -124,7 +135,7 @@ static void pulse_stream_read(pa_stream *p, size_t nbytes, void *userdata) out.format = pulse_to_obs_audio_format(data->format); out.data[0] = (uint8_t *) frames; out.frames = bytes / data->bytes_per_frame; - out.timestamp = (pa_time - pa_latency) * 1000; + out.timestamp = pa_time - (pa_latency * 1000); obs_source_output_audio(data->source, &out); pa_stream_drop(data->stream); @@ -133,7 +144,7 @@ exit: pulse_signal(0); } -/* +/** * Server info callback */ static void pulse_server_info(pa_context *c, const pa_server_info *i, @@ -154,7 +165,7 @@ static void pulse_server_info(pa_context *c, const pa_server_info *i, pulse_signal(0); } -/* +/** * start recording */ static int_fast32_t pulse_start_recording(struct pulse_data *data) @@ -207,6 +218,7 @@ static int_fast32_t pulse_start_recording(struct pulse_data *data) &attr, flags); pulse_unlock(); if (ret < 0) { + pulse_stop_recording(data); blog(LOG_ERROR, "pulse-input: Unable to connect to stream"); return -1; } @@ -215,7 +227,7 @@ static int_fast32_t pulse_start_recording(struct pulse_data *data) return 0; } -/* +/** * stop recording */ static void pulse_stop_recording(struct pulse_data *data) @@ -224,11 +236,12 @@ static void pulse_stop_recording(struct pulse_data *data) pulse_lock(); pa_stream_disconnect(data->stream); pa_stream_unref(data->stream); + data->stream = NULL; pulse_unlock(); } } -/* +/** * input info callback */ static void pulse_input_info(pa_context *c, const pa_source_info *i, int eol, @@ -262,8 +275,8 @@ skip: pulse_signal(0); } -/* - * get plugin properties +/** + * Get plugin properties */ static obs_properties_t pulse_properties(const char *locale, bool input) { @@ -276,6 +289,8 @@ static obs_properties_t pulse_properties(const char *locale, bool input) pulse_get_source_info_list(cb, (void *) devices); pulse_unref(); + obs_properties_add_bool(props, "ostime", "Use OS timestamps"); + return props; } @@ -289,15 +304,67 @@ static obs_properties_t pulse_output_properties(const char *locale) return pulse_properties(locale, false); } -/* - * get plugin defaults +/** + * Server info callback */ -static void pulse_defaults(obs_data_t settings) +static void pulse_input_device(pa_context *c, const pa_server_info *i, + void *userdata) { - obs_data_set_default_string(settings, "device_id", "default"); + UNUSED_PARAMETER(c); + obs_data_t settings = (obs_data_t) userdata; + + obs_data_set_default_string(settings, "device_id", + i->default_source_name); + blog(LOG_DEBUG, "pulse-input: Default input device: '%s'", + i->default_source_name); + + pulse_signal(0); } -/* +static void pulse_output_device(pa_context *c, const pa_server_info *i, + void *userdata) +{ + UNUSED_PARAMETER(c); + obs_data_t settings = (obs_data_t) userdata; + + char *monitor = bzalloc(strlen(i->default_sink_name) + 9); + strcat(monitor, i->default_sink_name); + strcat(monitor, ".monitor"); + + obs_data_set_default_string(settings, "device_id", monitor); + blog(LOG_DEBUG, "pulse-input: Default output device: '%s'", monitor); + bfree(monitor); + + pulse_signal(0); +} + +/** + * Get plugin defaults + */ +static void pulse_defaults(obs_data_t settings, bool input) +{ + pulse_init(); + + pa_server_info_cb_t cb = (input) + ? pulse_input_device : pulse_output_device; + pulse_get_server_info(cb, (void *) settings); + + pulse_unref(); + + obs_data_set_default_bool(settings, "ostime", false); +} + +static void pulse_input_defaults(obs_data_t settings) +{ + return pulse_defaults(settings, true); +} + +static void pulse_output_defaults(obs_data_t settings) +{ + return pulse_defaults(settings, false); +} + +/** * Returns the name of the plugin */ static const char *pulse_input_getname(const char *locale) @@ -312,7 +379,7 @@ static const char *pulse_output_getname(const char *locale) return "Pulse Audio Output Capture"; } -/* +/** * Destroy the plugin object and free all memory */ static void pulse_destroy(void *vptr) @@ -322,7 +389,8 @@ static void pulse_destroy(void *vptr) if (!data) return; - pulse_stop_recording(data); + if (data->stream) + pulse_stop_recording(data); pulse_unref(); if (data->device) @@ -332,7 +400,37 @@ static void pulse_destroy(void *vptr) blog(LOG_DEBUG, "pulse-input: Input destroyed"); } -/* +/** + * Update the input settings + */ +static void pulse_update(void *vptr, obs_data_t settings) +{ + PULSE_DATA(vptr); + bool restart = false; + char *new_device; + + new_device = bstrdup(obs_data_getstring(settings, "device_id")); + if (!data->device || strcmp(data->device, new_device) != 0) { + if (data->device) + bfree(data->device); + data->device = new_device; + restart = true; + } + + if (data->ostime != obs_data_getbool(settings, "ostime")) { + data->ostime = obs_data_getbool(settings, "ostime"); + restart = true; + } + + if (!restart) + return; + + if (data->stream) + pulse_stop_recording(data); + pulse_start_recording(data); +} + +/** * Create the plugin object */ static void *pulse_create(obs_data_t settings, obs_source_t source) @@ -341,24 +439,17 @@ static void *pulse_create(obs_data_t settings, obs_source_t source) data->source = source; data->speakers = SPEAKERS_STEREO; - data->device = bstrdup(obs_data_getstring(settings, "device_id")); pulse_init(); - if (pulse_start_recording(data) < 0) - goto fail; + pulse_update(data, settings); + + if (data->stream) + return data; - return data; -fail: pulse_destroy(data); return NULL; } -static void pulse_update(void *vptr, obs_data_t settings) -{ - UNUSED_PARAMETER(vptr); - UNUSED_PARAMETER(settings); -} - struct obs_source_info pulse_input_capture = { .id = "pulse_input_capture", .type = OBS_SOURCE_TYPE_INPUT, @@ -367,7 +458,7 @@ struct obs_source_info pulse_input_capture = { .create = pulse_create, .destroy = pulse_destroy, .update = pulse_update, - .defaults = pulse_defaults, + .defaults = pulse_input_defaults, .properties = pulse_input_properties }; @@ -379,6 +470,6 @@ struct obs_source_info pulse_output_capture = { .create = pulse_create, .destroy = pulse_destroy, .update = pulse_update, - .defaults = pulse_defaults, + .defaults = pulse_output_defaults, .properties = pulse_output_properties }; diff --git a/plugins/linux-pulseaudio/pulse-wrapper.c b/plugins/linux-pulseaudio/pulse-wrapper.c index a491c1879453f52301d4c95ee3d1a943f8846369..2f33709f7bd866bbcf382856e7815d7661561497 100644 --- a/plugins/linux-pulseaudio/pulse-wrapper.c +++ b/plugins/linux-pulseaudio/pulse-wrapper.c @@ -86,7 +86,7 @@ static void pulse_init_context() static int_fast32_t pulse_context_ready() { pulse_lock(); - + if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(pulse_context))) { pulse_unlock(); return -1; diff --git a/plugins/linux-pulseaudio/pulse-wrapper.h b/plugins/linux-pulseaudio/pulse-wrapper.h index e9db5a5da4e8a7353ea2347559eedc68f206e3fc..493fc3de8e567bd92769a030b41dc6cf4bb1db8b 100644 --- a/plugins/linux-pulseaudio/pulse-wrapper.h +++ b/plugins/linux-pulseaudio/pulse-wrapper.h @@ -83,7 +83,7 @@ void pulse_accept(); * Request source information * * The function will block until the operation was executed and the mainloop - * called the provided callback functions. + * called the provided callback function. * * @return negative on error * @@ -97,7 +97,7 @@ int_fast32_t pulse_get_source_info_list(pa_source_info_cb_t cb, void *userdata); * Request server information * * The function will block until the operation was executed and the mainloop - * called the provided callback functions + * called the provided callback function. * * @return negative on error *