From c7de3220767690b1ecff4a655bd06f9334d1cc84 Mon Sep 17 00:00:00 2001 From: Norihiro Kamae Date: Fri, 1 Apr 2022 01:19:21 +0900 Subject: [PATCH 1/8] libobs/media-io: Add compensation to audio-resampler An API `audio_resampler_compensate` is added that activates resampling compensation by calling `swr_set_compensation`. --- docs/sphinx/reference-libobs-media-io.rst | 17 ++++++ libobs/media-io/audio-resampler-ffmpeg.c | 46 +++++++++++++++++ libobs/media-io/audio-resampler.h | 4 ++ libobs/media-io/lag_lead_filter.h | 63 +++++++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 libobs/media-io/lag_lead_filter.h diff --git a/docs/sphinx/reference-libobs-media-io.rst b/docs/sphinx/reference-libobs-media-io.rst index 6d7e46f3efb1a9..f5af7c249fdadc 100644 --- a/docs/sphinx/reference-libobs-media-io.rst +++ b/docs/sphinx/reference-libobs-media-io.rst @@ -514,6 +514,23 @@ FFmpeg wrapper to resample audio. --------------------- +.. function:: void audio_resampler_set_compensation_error(audio_resampler_t *resampler, int error_ns) + + Activate resampling compensation and set current error. + + :param resampler: Audio resampler object + :param error_ns: Current error in nanosecond + +--------------------- + +.. function:: void audio_resampler_disable_compensation(audio_resampler_t *resampler) + + Deactivate resampling compensation. + + :param resampler: Audio resampler object + +--------------------- + .. function:: bool audio_resampler_resample(audio_resampler_t *resampler, uint8_t *output[], uint32_t *out_frames, uint64_t *ts_offset, const uint8_t *const input[], uint32_t in_frames) Resamples audio frames. diff --git a/libobs/media-io/audio-resampler-ffmpeg.c b/libobs/media-io/audio-resampler-ffmpeg.c index eb5be4f7435e2e..ad6c574c02418c 100644 --- a/libobs/media-io/audio-resampler-ffmpeg.c +++ b/libobs/media-io/audio-resampler-ffmpeg.c @@ -21,6 +21,9 @@ #include #include #include +#include "lag_lead_filter.h" + +// #define DEBUG_COMPENSATION struct audio_resampler { struct SwrContext *context; @@ -41,6 +44,9 @@ struct audio_resampler { AVChannelLayout input_ch_layout; AVChannelLayout output_ch_layout; #endif + + struct lag_lead_filter compensation_filter; + bool compensation_filter_configured; }; static inline enum AVSampleFormat convert_audio_format(enum audio_format format) @@ -178,6 +184,27 @@ void audio_resampler_destroy(audio_resampler_t *rs) } } +void audio_resampler_set_compensation_error(audio_resampler_t *rs, int error_ns) +{ + if (!rs->compensation_filter_configured) { + // Parameters are determined experimentally by SPICE simulation + // to have these characteristics. + // - Unity gain frequency: 1/180 Hz (inverse of 3 minutes) + // - Phase margin: 60 degrees + lag_lead_filter_set_parameters(&rs->compensation_filter, 3.756e-2, 8.250, 107.1); + lag_lead_filter_reset(&rs->compensation_filter); + rs->compensation_filter_configured = true; + } + + lag_lead_filter_set_error_ns(&rs->compensation_filter, error_ns); +} + +void audio_resampler_disable_compensation(audio_resampler_t *rs) +{ + rs->compensation_filter_configured = false; + swr_set_compensation(rs->context, 0, 0); +} + bool audio_resampler_resample(audio_resampler_t *rs, uint8_t *output[], uint32_t *out_frames, uint64_t *ts_offset, const uint8_t *const input[], uint32_t in_frames) { @@ -187,6 +214,17 @@ bool audio_resampler_resample(audio_resampler_t *rs, uint8_t *output[], uint32_t struct SwrContext *context = rs->context; int ret; + if (rs->compensation_filter_configured) { + lag_lead_filter_tick(&rs->compensation_filter, rs->input_freq, in_frames); + double drift = lag_lead_filter_get_drift(&rs->compensation_filter); + + ret = swr_set_compensation(rs->context, -(int)(drift * 65536 / rs->input_freq), 65536); + if (ret < 0) { + blog(LOG_ERROR, "swr_set_compensation failed: %d", ret); + return false; + } + } + int64_t delay = swr_get_delay(context, rs->input_freq); int estimated = (int)av_rescale_rnd(delay + (int64_t)in_frames, (int64_t)rs->output_freq, (int64_t)rs->input_freq, AV_ROUND_UP); @@ -210,6 +248,14 @@ bool audio_resampler_resample(audio_resampler_t *rs, uint8_t *output[], uint32_t return false; } +#ifdef DEBUG_COMPENSATION + if (rs->compensation_filter_configured) + blog(LOG_INFO, + "async-compensation in_frames=%d out_frames=%d error_ns=%" PRIi64 " internal-condition=(%f %f)", + in_frames, ret, rs->compensation_filter.error_ns, rs->compensation_filter.vc1, + rs->compensation_filter.vc2); +#endif + for (uint32_t i = 0; i < rs->output_planes; i++) output[i] = rs->output_buffer[i]; diff --git a/libobs/media-io/audio-resampler.h b/libobs/media-io/audio-resampler.h index ca1e7b08d58000..6addfaf32e7812 100644 --- a/libobs/media-io/audio-resampler.h +++ b/libobs/media-io/audio-resampler.h @@ -36,6 +36,10 @@ struct resample_info { EXPORT audio_resampler_t *audio_resampler_create(const struct resample_info *dst, const struct resample_info *src); EXPORT void audio_resampler_destroy(audio_resampler_t *resampler); +EXPORT void audio_resampler_set_compensation_error(audio_resampler_t *resampler, int delta_ns_per_s); + +EXPORT void audio_resampler_disable_compensation(audio_resampler_t *resampler); + EXPORT bool audio_resampler_resample(audio_resampler_t *resampler, uint8_t *output[], uint32_t *out_frames, uint64_t *ts_offset, const uint8_t *const input[], uint32_t in_frames); diff --git a/libobs/media-io/lag_lead_filter.h b/libobs/media-io/lag_lead_filter.h new file mode 100644 index 00000000000000..d673d743b9cbed --- /dev/null +++ b/libobs/media-io/lag_lead_filter.h @@ -0,0 +1,63 @@ +/* + * Lag lead filter for asynchronous sample rate converter + * Copyright (C) 2022-2026 Norihiro Kamae + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +struct lag_lead_filter { + int64_t error_ns; + double vc1; + double vc2; + + double gain; + double c1_inv; + double c2_inv; +}; + +static void lag_lead_filter_reset(struct lag_lead_filter *f) +{ + f->vc1 = 0.0; + f->vc2 = 0.0; +} + +static void lag_lead_filter_set_parameters(struct lag_lead_filter *f, double gain, double c1, double c2) +{ + f->gain = gain; + f->c1_inv = 1.0 / c1; + f->c2_inv = 1.0 / c2; +} + +static inline void lag_lead_filter_set_error_ns(struct lag_lead_filter *f, int32_t error_ns) +{ + f->error_ns = error_ns; +} + +static void lag_lead_filter_tick(struct lag_lead_filter *f, uint32_t fs, uint32_t frames) +{ + const double icp = f->gain * f->error_ns * 1e-9; + const double ic2 = (f->vc1 - f->vc2) / fs; + const double ic1 = icp - ic2; + + f->vc1 += ic1 * f->c1_inv * frames; + f->vc2 += ic2 * f->c2_inv * frames; +} + +static inline double lag_lead_filter_get_drift(const struct lag_lead_filter *f) +{ + return f->vc1; +} From c81368c8eceae4cf75948627557475eee03d7e4c Mon Sep 17 00:00:00 2001 From: Norihiro Kamae Date: Wed, 28 Sep 2022 00:57:17 +0900 Subject: [PATCH 2/8] libobs: Implement async compensation in source This commit implements asynchronous sample-rate conversion for audio source. --- libobs/obs-internal.h | 2 ++ libobs/obs-source.c | 34 ++++++++++++++++++++++++++++++++-- libobs/obs.h | 3 +++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index d88093eec1cfea..e68b708a7a070c 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -861,6 +861,8 @@ struct obs_source { bool audio_active; bool user_muted; bool muted; + volatile bool async_compensation; + bool last_async_compensation; struct obs_source *next_audio_source; struct obs_source **prev_next_audio_source; uint64_t audio_ts; diff --git a/libobs/obs-source.c b/libobs/obs-source.c index e5ef1304fdb7f4..901a81f1d02648 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -1635,6 +1635,16 @@ static void source_output_audio_data(obs_source_t *source, const struct audio_da } } + if (os_atomic_load_bool(&source->async_compensation) && source->resampler) { + uint64_t ts_buffered = in.timestamp; + uint64_t ts_data = data->timestamp - source->resample_offset; + int64_t error = (int64_t)(ts_buffered - ts_data); + if (push_back && INT32_MIN <= error && error <= INT32_MAX) + audio_resampler_set_compensation_error(source->resampler, (int)error); + else + audio_resampler_set_compensation_error(source->resampler, 0); + } + sync_offset = source->sync_offset; in.timestamp += sync_offset; in.timestamp -= source->resample_offset; @@ -3894,16 +3904,20 @@ static inline void reset_resampler(obs_source_t *source, const struct obs_source output_info.samples_per_sec = obs_info->samples_per_sec; output_info.speakers = obs_info->speakers; + bool async_compensation = os_atomic_load_bool(&source->async_compensation); + source->sample_info.format = audio->format; source->sample_info.samples_per_sec = audio->samples_per_sec; source->sample_info.speakers = audio->speakers; + source->last_async_compensation = async_compensation; audio_resampler_destroy(source->resampler); source->resampler = NULL; source->resample_offset = 0; if (source->sample_info.samples_per_sec == obs_info->samples_per_sec && - source->sample_info.format == obs_info->format && source->sample_info.speakers == obs_info->speakers) { + source->sample_info.format == obs_info->format && source->sample_info.speakers == obs_info->speakers && + !async_compensation) { source->audio_failed = false; return; } @@ -3996,7 +4010,8 @@ static void process_audio(obs_source_t *source, const struct obs_source_audio *a bool mono_output; if (source->sample_info.samples_per_sec != audio->samples_per_sec || - source->sample_info.format != audio->format || source->sample_info.speakers != audio->speakers) + source->sample_info.format != audio->format || source->sample_info.speakers != audio->speakers || + source->last_async_compensation != os_atomic_load_bool(&source->async_compensation)) reset_resampler(source, audio); if (source->audio_failed) @@ -5608,6 +5623,21 @@ bool obs_source_async_unbuffered(const obs_source_t *source) return obs_source_valid(source, "obs_source_async_unbuffered") ? source->async_unbuffered : false; } +void obs_source_set_async_compensation(obs_source_t *source, bool compensate) +{ + if (!obs_source_valid(source, "obs_source_set_async_compensation")) + return; + + os_atomic_store_bool(&source->async_compensation, compensate); +} + +bool obs_source_async_compensation(const obs_source_t *source) +{ + return obs_source_valid(source, "obs_source_async_compensation") + ? os_atomic_load_bool(&source->async_compensation) + : false; +} + obs_data_t *obs_source_get_private_settings(obs_source_t *source) { if (!obs_ptr_valid(source, "obs_source_get_private_settings")) diff --git a/libobs/obs.h b/libobs/obs.h index 57844b05694cec..8f875e76ce0b47 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -1544,6 +1544,9 @@ EXPORT void obs_source_get_audio_mix(const obs_source_t *source, struct obs_sour EXPORT void obs_source_set_async_unbuffered(obs_source_t *source, bool unbuffered); EXPORT bool obs_source_async_unbuffered(const obs_source_t *source); +EXPORT void obs_source_set_async_compensation(obs_source_t *source, bool compensate); +EXPORT bool obs_source_async_compensation(const obs_source_t *source); + /** Used to decouple audio from video so that audio doesn't attempt to sync up * with video. I.E. Audio acts independently. Only works when in unbuffered * mode. */ From a976a76c8de254b260867e5e183ee94fde28cd6b Mon Sep 17 00:00:00 2001 From: Norihiro Kamae Date: Wed, 27 Apr 2022 16:56:05 +0900 Subject: [PATCH 3/8] libobs/media-io: Report estimated frequency of sample rate compensation --- libobs/media-io/audio-resampler-ffmpeg.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libobs/media-io/audio-resampler-ffmpeg.c b/libobs/media-io/audio-resampler-ffmpeg.c index ad6c574c02418c..619f3dda3b2b3a 100644 --- a/libobs/media-io/audio-resampler-ffmpeg.c +++ b/libobs/media-io/audio-resampler-ffmpeg.c @@ -47,6 +47,9 @@ struct audio_resampler { struct lag_lead_filter compensation_filter; bool compensation_filter_configured; + + uint64_t total_input_samples; + uint64_t total_output_samples; }; static inline enum AVSampleFormat convert_audio_format(enum audio_format format) @@ -175,6 +178,14 @@ audio_resampler_t *audio_resampler_create(const struct resample_info *dst, const void audio_resampler_destroy(audio_resampler_t *rs) { if (rs) { + uint64_t total_output_samples = rs->total_output_samples + swr_get_delay(rs->context, rs->output_freq); + if (rs->compensation_filter_configured) + blog(LOG_INFO, + "audio_resampler (asynchronous compensation): input %" PRIu64 " samples, " + "output %" PRIu64 " samples, estimated input frequency %f Hz", + rs->total_input_samples, rs->total_output_samples, + (double)rs->total_input_samples / total_output_samples * rs->output_freq); + if (rs->context) swr_free(&rs->context); if (rs->output_buffer[0]) @@ -259,6 +270,9 @@ bool audio_resampler_resample(audio_resampler_t *rs, uint8_t *output[], uint32_t for (uint32_t i = 0; i < rs->output_planes; i++) output[i] = rs->output_buffer[i]; + rs->total_input_samples += in_frames; + rs->total_output_samples += ret; + *out_frames = (uint32_t)ret; return true; } From 98fa88dee395cc80dc655e1543529f2c4bd0a596 Mon Sep 17 00:00:00 2001 From: Norihiro Kamae Date: Mon, 18 Apr 2022 23:59:04 +0900 Subject: [PATCH 4/8] linux-pulseaudio: Add option to activate async compensation Since the audio from most sound devices are asynchronous, it requires to be compensated so that the audio source provides exactly necessary amount of samples. The option is enabled by default. Also update linux-alsa. --- plugins/linux-alsa/alsa-input.c | 12 ++++++++++++ plugins/linux-alsa/data/locale/en-US.ini | 1 + plugins/linux-pulseaudio/data/locale/en-US.ini | 1 + plugins/linux-pulseaudio/pulse-input.c | 6 ++++++ 4 files changed, 20 insertions(+) diff --git a/plugins/linux-alsa/alsa-input.c b/plugins/linux-alsa/alsa-input.c index cb6d79dce9920d..0ca865f9dc8854 100644 --- a/plugins/linux-alsa/alsa-input.c +++ b/plugins/linux-alsa/alsa-input.c @@ -110,6 +110,7 @@ static enum speaker_layout _alsa_channels_to_obs_speakers(unsigned int); void *alsa_create(obs_data_t *settings, obs_source_t *source) { struct alsa_data *data = bzalloc(sizeof(struct alsa_data)); + bool async_compensation; data->source = source; #if SHUTDOWN_ON_DEACTIVATE @@ -140,6 +141,10 @@ void *alsa_create(obs_data_t *settings, obs_source_t *source) #if !SHUTDOWN_ON_DEACTIVATE _alsa_try_open(data); #endif + + async_compensation = obs_data_get_bool(settings, "async_compensation"); + obs_source_set_async_compensation(data->source, async_compensation); + return data; cleanup: @@ -186,6 +191,7 @@ void alsa_update(void *vptr, obs_data_t *settings) struct alsa_data *data = vptr; const char *device; unsigned int rate; + bool async_compensation; bool reset = false; device = obs_data_get_string(settings, "device_id"); @@ -218,6 +224,9 @@ void alsa_update(void *vptr, obs_data_t *settings) _alsa_try_open(data); } #endif + + async_compensation = obs_data_get_bool(settings, "async_compensation"); + obs_source_set_async_compensation(data->source, async_compensation); } const char *alsa_get_name(void *unused) @@ -231,6 +240,7 @@ void alsa_get_defaults(obs_data_t *settings) obs_data_set_default_string(settings, "device_id", "default"); obs_data_set_default_string(settings, "custom_pcm", "default"); obs_data_set_default_int(settings, "rate", 44100); + obs_data_set_default_bool(settings, "async_compensation", true); } static bool alsa_devices_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings) @@ -333,6 +343,8 @@ obs_properties_t *alsa_get_properties(void *unused) snd_device_name_free_hint(hints); + obs_properties_add_bool(props, "async_compensation", obs_module_text("AsyncCompensation")); + return props; } diff --git a/plugins/linux-alsa/data/locale/en-US.ini b/plugins/linux-alsa/data/locale/en-US.ini index 244797d9fc0a47..fc3d496004f67a 100644 --- a/plugins/linux-alsa/data/locale/en-US.ini +++ b/plugins/linux-alsa/data/locale/en-US.ini @@ -4,3 +4,4 @@ Custom="Custom" Device="Device" PCM="PCM" Rate="Rate" +AsyncCompensation="Enable Asynchronous Compensation" diff --git a/plugins/linux-pulseaudio/data/locale/en-US.ini b/plugins/linux-pulseaudio/data/locale/en-US.ini index 43718c05748b3b..4689dfbae4abc2 100644 --- a/plugins/linux-pulseaudio/data/locale/en-US.ini +++ b/plugins/linux-pulseaudio/data/locale/en-US.ini @@ -2,3 +2,4 @@ PulseInput="Audio Input Capture (PulseAudio)" PulseOutput="Audio Output Capture (PulseAudio)" Device="Device" Default="Default" +AsyncCompensation="Enable Asynchronous Compensation" diff --git a/plugins/linux-pulseaudio/pulse-input.c b/plugins/linux-pulseaudio/pulse-input.c index 591446fe0f4dc7..d389fad904e40f 100644 --- a/plugins/linux-pulseaudio/pulse-input.c +++ b/plugins/linux-pulseaudio/pulse-input.c @@ -455,6 +455,8 @@ static obs_properties_t *pulse_properties(bool input) if (count > 0) obs_property_list_insert_string(devices, 0, obs_module_text("Default"), "default"); + obs_properties_add_bool(props, "async_compensation", obs_module_text("AsyncCompensation")); + return props; } @@ -478,6 +480,7 @@ static obs_properties_t *pulse_output_properties(void *unused) static void pulse_defaults(obs_data_t *settings) { obs_data_set_default_string(settings, "device_id", "default"); + obs_data_set_default_bool(settings, "async_compensation", true); } /** @@ -540,6 +543,9 @@ static void pulse_update(void *vptr, obs_data_t *settings) restart = true; } + bool async_compensation = obs_data_get_bool(settings, "async_compensation"); + obs_source_set_async_compensation(data->source, async_compensation); + if (!restart) return; From cc8e6426c21d8c486457cf6a0c8157aa59eb3243 Mon Sep 17 00:00:00 2001 From: Norihiro Kamae Date: Tue, 26 Jul 2022 09:02:42 +0900 Subject: [PATCH 5/8] win-wasapi: Add option to activate async compensation When `use_device_timing` is not set, the audio from the device is asynchronous. It requires to be compensated to avoid audio glitch. The option is masked if `use_device_timing` is set. The option is enabled by default for wasapi_input_capture. --- plugins/win-wasapi/data/locale/en-US.ini | 1 + plugins/win-wasapi/win-wasapi.cpp | 32 ++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/plugins/win-wasapi/data/locale/en-US.ini b/plugins/win-wasapi/data/locale/en-US.ini index 58531fc864eb88..bf0cda594a65d0 100644 --- a/plugins/win-wasapi/data/locale/en-US.ini +++ b/plugins/win-wasapi/data/locale/en-US.ini @@ -9,3 +9,4 @@ Priority="Window Match Priority" Priority.Title="Window title must match" Priority.Class="Match title, otherwise find window of same type" Priority.Exe="Match title, otherwise find window of same executable" +AsyncCompensation="Enable Asynchronous Compensation" diff --git a/plugins/win-wasapi/win-wasapi.cpp b/plugins/win-wasapi/win-wasapi.cpp index 0cea4758559b2f..679caca2b35ec0 100644 --- a/plugins/win-wasapi/win-wasapi.cpp +++ b/plugins/win-wasapi/win-wasapi.cpp @@ -28,6 +28,7 @@ using namespace std; #define OPT_USE_DEVICE_TIMING "use_device_timing" #define OPT_WINDOW "window" #define OPT_PRIORITY "priority" +#define OPT_ASYNC_COMPENSATION "async_compensation" WASAPINotify *GetNotify(); static void GetWASAPIDefaults(obs_data_t *settings); @@ -255,6 +256,7 @@ class WASAPISource { string device_id; bool useDeviceTiming; bool isDefaultDevice; + bool async_compensation; window_priority priority; string window_class; string title; @@ -477,6 +479,7 @@ WASAPISource::UpdateParams WASAPISource::BuildUpdateParams(obs_data_t *settings) params.device_id = obs_data_get_string(settings, OPT_DEVICE_ID); params.useDeviceTiming = obs_data_get_bool(settings, OPT_USE_DEVICE_TIMING); params.isDefaultDevice = _strcmpi(params.device_id.c_str(), "default") == 0; + params.async_compensation = obs_data_get_bool(settings, OPT_ASYNC_COMPENSATION); params.priority = (window_priority)obs_data_get_int(settings, "priority"); params.window_class.clear(); params.title.clear(); @@ -517,6 +520,8 @@ void WASAPISource::UpdateSettings(UpdateParams &¶ms) window_class = std::move(params.window_class); title = std::move(params.title); executable = std::move(params.executable); + + obs_source_set_async_compensation(source, params.async_compensation & !params.useDeviceTiming); } void WASAPISource::LogSettings() @@ -1331,6 +1336,7 @@ static void GetWASAPIDefaultsInput(obs_data_t *settings) { obs_data_set_default_string(settings, OPT_DEVICE_ID, "default"); obs_data_set_default_bool(settings, OPT_USE_DEVICE_TIMING, false); + obs_data_set_default_bool(settings, OPT_ASYNC_COMPENSATION, true); } static void GetWASAPIDefaultsDeviceOutput(obs_data_t *settings) @@ -1461,6 +1467,16 @@ static bool UpdateWASAPIMethod(obs_properties_t *props, obs_property_t *, obs_da return true; } +static bool device_timing_changed(obs_properties_t *props, obs_property_t *property, obs_data_t *settings) +{ + bool useDeviceTiming = obs_data_get_bool(settings, OPT_USE_DEVICE_TIMING); + + obs_property_t *prop = obs_properties_get(props, OPT_ASYNC_COMPENSATION); + obs_property_set_enabled(prop, !useDeviceTiming); + + return true; +} + static obs_properties_t *GetWASAPIPropertiesInput(void *) { obs_properties_t *props = obs_properties_create(); @@ -1479,7 +1495,12 @@ static obs_properties_t *GetWASAPIPropertiesInput(void *) obs_property_list_add_string(device_prop, device.name.c_str(), device.id.c_str()); } - obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING, obs_module_text("UseDeviceTiming")); + obs_property_t *device_timing_prop = + obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING, obs_module_text("UseDeviceTiming")); + + obs_property_set_modified_callback(device_timing_prop, device_timing_changed); + + obs_properties_add_bool(props, OPT_ASYNC_COMPENSATION, obs_module_text("AsyncCompensation")); return props; } @@ -1502,7 +1523,12 @@ static obs_properties_t *GetWASAPIPropertiesDeviceOutput(void *) obs_property_list_add_string(device_prop, device.name.c_str(), device.id.c_str()); } - obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING, obs_module_text("UseDeviceTiming")); + obs_property_t *device_timing_prop = + obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING, obs_module_text("UseDeviceTiming")); + + obs_property_set_modified_callback(device_timing_prop, device_timing_changed); + + obs_properties_add_bool(props, OPT_ASYNC_COMPENSATION, obs_module_text("AsyncCompensation")); return props; } @@ -1535,6 +1561,8 @@ static obs_properties_t *GetWASAPIPropertiesProcessOutput(void *data) obs_property_list_add_int(priority_prop, obs_module_text("Priority.Class"), WINDOW_PRIORITY_CLASS); obs_property_list_add_int(priority_prop, obs_module_text("Priority.Exe"), WINDOW_PRIORITY_EXE); + obs_properties_add_bool(props, OPT_ASYNC_COMPENSATION, obs_module_text("AsyncCompensation")); + return props; } From c88f145535d114c1a525f533e488d76e3a72b19e Mon Sep 17 00:00:00 2001 From: Norihiro Kamae Date: Tue, 26 Jul 2022 09:03:30 +0900 Subject: [PATCH 6/8] mac-capture: Add option to activate async compensation Since the audio from most sound devices are asynchronous, it requires to be compensated so that the audio source provides exactly necessary amount of samples. The option is enabled by default. --- plugins/mac-capture/data/locale/en-US.ini | 1 + plugins/mac-capture/mac-audio.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/plugins/mac-capture/data/locale/en-US.ini b/plugins/mac-capture/data/locale/en-US.ini index 40c62a4e3604fc..9efafa8798efd5 100644 --- a/plugins/mac-capture/data/locale/en-US.ini +++ b/plugins/mac-capture/data/locale/en-US.ini @@ -37,3 +37,4 @@ SCK.AudioUnavailable="Audio capture requires macOS 13 or newer." SCK.CaptureTypeUnavailable="Selected capture type requires macOS 13 or newer." SCK.Method="Method" SCK.Restart="Restart capture" +AsyncCompensation="Enable Asynchronous Compensation" diff --git a/plugins/mac-capture/mac-audio.c b/plugins/mac-capture/mac-audio.c index 8d9dca9972fbe7..1fa9c3f00efdf9 100644 --- a/plugins/mac-capture/mac-audio.c +++ b/plugins/mac-capture/mac-audio.c @@ -837,6 +837,9 @@ static void coreaudio_update(void *data, obs_data_t *settings) coreaudio_set_channels(ca, settings); } + bool async_compensation = obs_data_get_bool(settings, "async_compensation"); + obs_source_set_async_compensation(ca->source, async_compensation); + coreaudio_try_init(ca); } @@ -844,6 +847,7 @@ static void coreaudio_defaults(obs_data_t *settings) { obs_data_set_default_string(settings, "device_id", "default"); obs_data_set_default_bool(settings, "enable_downmix", true); + obs_data_set_default_bool(settings, "async_compensation", true); } static void *coreaudio_create(obs_data_t *settings, obs_source_t *source, bool input) @@ -1027,6 +1031,8 @@ static obs_properties_t *coreaudio_properties(bool input, void *data) } device_list_free(&devices); + + obs_properties_add_bool(props, "async_compensation", obs_module_text("AsyncCompensation")); return props; } From 1646bb7ddb11bab73eeea15a07071b69b5a8658e Mon Sep 17 00:00:00 2001 From: Norihiro Kamae Date: Tue, 26 Jul 2022 09:05:20 +0900 Subject: [PATCH 7/8] aja: Add option to activate async compensation Since the audio from the device is asynchronous, it requires to be compensated so that the audio source provides exactly necessary amount of samples. The option is enabled by default. --- plugins/aja/aja-source.cpp | 5 +++++ plugins/aja/data/locale/en-US.ini | 1 + 2 files changed, 6 insertions(+) diff --git a/plugins/aja/aja-source.cpp b/plugins/aja/aja-source.cpp index 1aabc3b3620879..38af253256269e 100644 --- a/plugins/aja/aja-source.cpp +++ b/plugins/aja/aja-source.cpp @@ -860,6 +860,8 @@ static void aja_source_update(void *data, obs_data_t *settings) ajaSource->Deactivate(); } + obs_source_set_async_compensation(ajaSource->GetOBSSource(), obs_data_get_bool(settings, "async_compensation")); + auto &cardManager = aja::CardManager::Instance(); cardManager.EnumerateCards(); auto cardEntry = cardManager.GetCardEntry(wantCardID); @@ -1015,6 +1017,8 @@ static obs_properties_t *aja_source_get_properties(void *data) obs_property_set_modified_callback2(device_list, aja_source_device_changed, data); obs_property_set_modified_callback2(io_select_list, aja_io_selection_changed, data); + obs_properties_add_bool(props, "async_compensation", obs_module_text("AsyncCompensation")); + return props; } @@ -1029,6 +1033,7 @@ void aja_source_get_defaults(obs_data_t *settings) obs_data_set_default_int(settings, kUIPropChannelFormat.id, kDefaultAudioCaptureChannels); obs_data_set_default_bool(settings, kUIPropChannelSwap_FC_LFE.id, false); obs_data_set_default_bool(settings, kUIPropDeactivateWhenNotShowing.id, false); + obs_data_set_default_bool(settings, "async_compensation", true); } static void aja_source_get_defaults_v1(obs_data_t *settings) diff --git a/plugins/aja/data/locale/en-US.ini b/plugins/aja/data/locale/en-US.ini index 88c4430c46e12c..a26ce5ba566692 100644 --- a/plugins/aja/data/locale/en-US.ini +++ b/plugins/aja/data/locale/en-US.ini @@ -25,3 +25,4 @@ ChannelFormat.5_1ch="5.1ch" ChannelFormat.7_1ch="7.1ch" SwapFC-LFE="Swap FC and LFE" SwapFC-LFE.Tooltip="Swap Front Center Channel and LFE Channel" +AsyncCompensation="Enable Asynchronous Compensation" From c88796e2683bdad31ffac8b7516b94fd3801a1c5 Mon Sep 17 00:00:00 2001 From: Norihiro Kamae Date: Thu, 21 Apr 2022 01:04:18 +0900 Subject: [PATCH 8/8] decklink: Add option to activate async compensation Since the audio from the device is asynchronous, it requires to be compensated so that the audio source provides exactly necessary amount of samples. The option is enabled by default. --- plugins/decklink/const.h | 2 ++ plugins/decklink/data/locale/en-US.ini | 1 + plugins/decklink/decklink-source.cpp | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/plugins/decklink/const.h b/plugins/decklink/const.h index 9a2d094865f6d3..cfb53be69bfacd 100644 --- a/plugins/decklink/const.h +++ b/plugins/decklink/const.h @@ -15,6 +15,7 @@ #define KEYER "keyer" #define SWAP "swap" #define ALLOW_10_BIT "allow_10_bit" +#define ASYNC_COMPENSATION "async_compensation" #define TEXT_DEVICE obs_module_text("Device") #define TEXT_VIDEO_CONNECTION obs_module_text("VideoConnection") @@ -43,3 +44,4 @@ #define TEXT_SWAP obs_module_text("SwapFC-LFE") #define TEXT_SWAP_TOOLTIP obs_module_text("SwapFC-LFE.Tooltip") #define TEXT_ALLOW_10_BIT obs_module_text("Allow10Bit") +#define TEXT_ASYNC_COMPENSATION obs_module_text("AsyncCompensation") diff --git a/plugins/decklink/data/locale/en-US.ini b/plugins/decklink/data/locale/en-US.ini index d12fc8cb2156bd..22d39daef30e3a 100644 --- a/plugins/decklink/data/locale/en-US.ini +++ b/plugins/decklink/data/locale/en-US.ini @@ -25,3 +25,4 @@ SwapFC-LFE.Tooltip="Swap Front Center Channel and LFE Channel" VideoConnection="Video Connection" AudioConnection="Audio Connection" Allow10Bit="Allow 10 Bit (Required for SDI captions, may cause performance overhead)" +AsyncCompensation="Enable Asynchronous Compensation" diff --git a/plugins/decklink/decklink-source.cpp b/plugins/decklink/decklink-source.cpp index 6dcf6a7b5f1e66..ff7b01f7eadc68 100644 --- a/plugins/decklink/decklink-source.cpp +++ b/plugins/decklink/decklink-source.cpp @@ -71,6 +71,8 @@ static void decklink_update(void *data, obs_data_t *settings) decklink->swap = obs_data_get_bool(settings, SWAP); decklink->allow10Bit = obs_data_get_bool(settings, ALLOW_10_BIT); decklink->Activate(device, id, videoConnection, audioConnection); + + obs_source_set_async_compensation(decklink->GetSource(), obs_data_get_bool(settings, ASYNC_COMPENSATION)); } static void decklink_show(void *data) @@ -99,6 +101,7 @@ static void decklink_get_defaults(obs_data_t *settings) obs_data_set_default_int(settings, COLOR_RANGE, VIDEO_RANGE_DEFAULT); obs_data_set_default_int(settings, CHANNEL_FORMAT, SPEAKERS_STEREO); obs_data_set_default_bool(settings, SWAP, false); + obs_data_set_default_bool(settings, ASYNC_COMPENSATION, true); } static const char *decklink_get_name(void *) @@ -261,6 +264,8 @@ static obs_properties_t *decklink_get_properties(void *data) obs_properties_add_bool(props, ALLOW_10_BIT, TEXT_ALLOW_10_BIT); + obs_properties_add_bool(props, ASYNC_COMPENSATION, TEXT_ASYNC_COMPENSATION); + UNUSED_PARAMETER(data); return props; }