diff --git a/plugins/obs-filters/CMakeLists.txt b/plugins/obs-filters/CMakeLists.txt index 936f3b7b8..253be972a 100644 --- a/plugins/obs-filters/CMakeLists.txt +++ b/plugins/obs-filters/CMakeLists.txt @@ -1,5 +1,23 @@ project(obs-filters) +find_package(Libspeexdsp QUIET) +if(LIBSPEEXDSP_FOUND) + set(obs-filters_LIBSPEEXDSP_SOURCES + noise-suppress-filter.c) + set(obs-filters_LIBSPEEXDSP_LIBRARIES + ${LIBSPEEXDSP_LIBRARIES}) +else() + message(STATUS "Speexdsp library not found, speexdsp filters disabled") +endif() + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/obs-filters-config.h.in" + "${CMAKE_BINARY_DIR}/plugins/obs-filters/config/obs-filters-config.h") + +set(obs-filters_config_HEADERS + "${CMAKE_BINARY_DIR}/plugins/obs-filters/config/obs-filters-config.h") +include_directories(${LIBSPEEXDSP_INCLUDE_DIRS} + "${CMAKE_BINARY_DIR}/plugins/obs-filters/config") + set(obs-filters_SOURCES obs-filters.c color-filter.c @@ -15,8 +33,11 @@ set(obs-filters_SOURCES mask-filter.c) add_library(obs-filters MODULE - ${obs-filters_SOURCES}) + ${obs-filters_SOURCES} + ${obs-filters_config_HEADERS} + ${obs-filters_LIBSPEEXDSP_SOURCES}) target_link_libraries(obs-filters - libobs) + libobs + ${obs-filters_LIBSPEEXDSP_LIBRARIES}) install_obs_plugin_with_data(obs-filters data) diff --git a/plugins/obs-filters/data/locale/en-US.ini b/plugins/obs-filters/data/locale/en-US.ini index 5e766c5f9..95b8c4330 100644 --- a/plugins/obs-filters/data/locale/en-US.ini +++ b/plugins/obs-filters/data/locale/en-US.ini @@ -8,6 +8,7 @@ ColorKeyFilter="Color Key" SharpnessFilter="Sharpen" ScaleFilter="Scaling/Aspect Ratio" NoiseGate="Noise Gate" +NoiseSuppress="Noise Suppression" Gain="Gain" DelayMs="Delay (milliseconds)" Type="Type" @@ -59,3 +60,4 @@ ScaleFiltering.Point="Point" ScaleFiltering.Bilinear="Bilinear" ScaleFiltering.Bicubic="Bicubic" ScaleFiltering.Lanczos="Lanczos" +NoiseSuppress.SuppressLevel="Suppression Level (dB)" diff --git a/plugins/obs-filters/noise-suppress-filter.c b/plugins/obs-filters/noise-suppress-filter.c new file mode 100644 index 000000000..a2f45478a --- /dev/null +++ b/plugins/obs-filters/noise-suppress-filter.c @@ -0,0 +1,186 @@ +#include +#include + +#include +#include + +/* -------------------------------------------------------- */ + +#define do_log(level, format, ...) \ + blog(level, "[noise suppress: '%s'] " format, \ + obs_source_get_name(ng->context), ##__VA_ARGS__) + +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) +#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) + +#ifdef _DEBUG +#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) +#else +#define debug(format, ...) +#endif + +/* -------------------------------------------------------- */ + +#define S_SUPPRESS_LEVEL "suppress_level" + +#define MT_ obs_module_text +#define TEXT_SUPPRESS_LEVEL MT_("NoiseSuppress.SuppressLevel") + +#define MAX_PREPROC_CHANNELS 2 + +/* -------------------------------------------------------- */ + +struct noise_suppress_data { + obs_source_t *context; + + /* Speex preprocessor state */ + SpeexPreprocessState *states[MAX_PREPROC_CHANNELS]; + + /* 16 bit PCM buffers */ + spx_int16_t *segment_buffers[MAX_PREPROC_CHANNELS]; + + int suppress_level; +}; + +/* -------------------------------------------------------- */ + +#define SUP_MIN -60 +#define SUP_MAX 0 + +static const float c_32_to_16 = (float)INT16_MAX; +static const float c_16_to_32 = ((float)INT16_MAX + 1.0f); + +/* -------------------------------------------------------- */ + +static const char *noise_suppress_name(void *unused) +{ + UNUSED_PARAMETER(unused); + return obs_module_text("NoiseSuppress"); +} + +static void noise_suppress_destroy(void *data) +{ + struct noise_suppress_data *ng = data; + + for (size_t i = 0; i < MAX_PREPROC_CHANNELS; i++) { + speex_preprocess_state_destroy(ng->states[i]); + bfree(ng->segment_buffers[i]); + } + + bfree(ng); +} + +static inline void alloc_channel(struct noise_suppress_data *ng, + uint32_t sample_rate, size_t channel, uint32_t size) +{ + if (ng->states[channel]) + return; + + debug("Create channel %d speex state", (int)channel); + ng->states[channel] = speex_preprocess_state_init(size, sample_rate); + + if (!ng->segment_buffers[channel]) { + ng->segment_buffers[channel] = + bzalloc(size * sizeof(spx_int16_t)); + debug("Create channel %d speex buffer: size = %"PRIu32, + (int)channel, size); + } +} + +static void noise_suppress_update(void *data, obs_data_t *s) +{ + struct noise_suppress_data *ng = data; + + int suppress_level = (int)obs_data_get_int(s, S_SUPPRESS_LEVEL); + uint32_t sample_rate = audio_output_get_sample_rate(obs_get_audio()); + uint32_t segment_size = sample_rate / 100; + size_t channels = audio_output_get_channels(obs_get_audio()); + + ng->suppress_level = suppress_level; + + debug("channels = %d", (int)channels); + debug("sample_rate = %"PRIu32, sample_rate); + debug("segment_size = %u"PRIu32, segment_size); + debug("block size = %d", + (int)audio_output_get_block_size(obs_get_audio())); + + /* One speex state for each channel (limit 2) */ + for (size_t i = 0; i < channels; i++) + alloc_channel(ng, sample_rate, i, segment_size); +} + +static void *noise_suppress_create(obs_data_t *settings, obs_source_t *filter) +{ + struct noise_suppress_data *ng = + bzalloc(sizeof(struct noise_suppress_data)); + + ng->context = filter; + noise_suppress_update(ng, settings); + return ng; +} + +static inline void process_channel(struct noise_suppress_data *ng, + size_t channel, size_t frames, float *adata[2]) +{ + /* Set args */ + speex_preprocess_ctl(ng->states[channel], + SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, + &ng->suppress_level); + + /* Convert to 16bit */ + for (size_t i = 0; i < frames; i++) + ng->segment_buffers[channel][i] = + (spx_int16_t)(adata[channel][i] * c_32_to_16); + + /* Execute */ + speex_preprocess_run(ng->states[channel], ng->segment_buffers[channel]); + + /* Convert back to 32bit */ + for (size_t i = 0; i < frames; i++) + adata[channel][i] = + (float)ng->segment_buffers[channel][i] / c_16_to_32; +} + +static struct obs_audio_data *noise_suppress_filter_audio(void *data, + struct obs_audio_data *audio) +{ + struct noise_suppress_data *ng = data; + float *adata[2] = {(float*)audio->data[0], (float*)audio->data[1]}; + + /* Execute for each available channel */ + for (size_t i = 0; i < MAX_PREPROC_CHANNELS; i++) { + if (ng->states[i]) + process_channel(ng, i, audio->frames, adata); + } + + return audio; +} + +static void noise_suppress_defaults(obs_data_t *s) +{ + obs_data_set_default_int(s, S_SUPPRESS_LEVEL, -30); +} + +static obs_properties_t *noise_suppress_properties(void *data) +{ + obs_properties_t *ppts = obs_properties_create(); + + obs_properties_add_int_slider(ppts, S_SUPPRESS_LEVEL, + TEXT_SUPPRESS_LEVEL, SUP_MIN, SUP_MAX, 0); + + UNUSED_PARAMETER(data); + return ppts; +} + +struct obs_source_info noise_suppress_filter = { + .id = "noise_suppress_filter", + .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_AUDIO, + .get_name = noise_suppress_name, + .create = noise_suppress_create, + .destroy = noise_suppress_destroy, + .update = noise_suppress_update, + .filter_audio = noise_suppress_filter_audio, + .get_defaults = noise_suppress_defaults, + .get_properties = noise_suppress_properties, +}; diff --git a/plugins/obs-filters/obs-filters-config.h.in b/plugins/obs-filters/obs-filters-config.h.in new file mode 100644 index 000000000..8aeb0bda8 --- /dev/null +++ b/plugins/obs-filters/obs-filters-config.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define SPEEXDSP_ENABLED @LIBSPEEXDSP_FOUND@ diff --git a/plugins/obs-filters/obs-filters.c b/plugins/obs-filters/obs-filters.c index 6d349d34e..d86a91de2 100644 --- a/plugins/obs-filters/obs-filters.c +++ b/plugins/obs-filters/obs-filters.c @@ -1,4 +1,5 @@ #include +#include "obs-filters-config.h" OBS_DECLARE_MODULE() @@ -14,6 +15,9 @@ extern struct obs_source_info color_key_filter; extern struct obs_source_info sharpness_filter; extern struct obs_source_info chroma_key_filter; extern struct obs_source_info async_delay_filter; +#ifdef SPEEXDSP_ENABLED +extern struct obs_source_info noise_suppress_filter; +#endif extern struct obs_source_info noise_gate_filter; bool obs_module_load(void) @@ -28,6 +32,9 @@ bool obs_module_load(void) obs_register_source(&sharpness_filter); obs_register_source(&chroma_key_filter); obs_register_source(&async_delay_filter); +#ifdef SPEEXDSP_ENABLED + obs_register_source(&noise_suppress_filter); +#endif obs_register_source(&noise_gate_filter); return true; }