noise-suppress-filter.c 8.3 KB
Newer Older
1 2 3
#include <stdint.h>
#include <inttypes.h>

4
#include <util/circlebuf.h>
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
#include <obs-module.h>
#include <speex/speex_preprocess.h>

/* -------------------------------------------------------- */

#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;
36 37
	int suppress_level;

38 39
	uint64_t last_timestamp;

40 41 42 43 44 45
	size_t frames;
	size_t channels;

	struct circlebuf info_buffer;
	struct circlebuf input_buffers[MAX_PREPROC_CHANNELS];
	struct circlebuf output_buffers[MAX_PREPROC_CHANNELS];
46 47 48 49 50

	/* Speex preprocessor state */
	SpeexPreprocessState *states[MAX_PREPROC_CHANNELS];

	/* 16 bit PCM buffers */
51
	float *copy_buffers[MAX_PREPROC_CHANNELS];
52 53
	spx_int16_t *segment_buffers[MAX_PREPROC_CHANNELS];

54 55 56
	/* output data */
	struct obs_audio_data output_audio;
	DARRAY(float) output_data;
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
};

/* -------------------------------------------------------- */

#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;

79
	for (size_t i = 0; i < ng->channels; i++) {
80
		speex_preprocess_state_destroy(ng->states[i]);
81 82
		circlebuf_free(&ng->input_buffers[i]);
		circlebuf_free(&ng->output_buffers[i]);
83 84
	}

85 86 87 88
	bfree(ng->segment_buffers[0]);
	bfree(ng->copy_buffers[0]);
	circlebuf_free(&ng->info_buffer);
	da_free(ng->output_data);
89 90 91 92
	bfree(ng);
}

static inline void alloc_channel(struct noise_suppress_data *ng,
93
		uint32_t sample_rate, size_t channel, size_t frames)
94
{
95 96
	ng->states[channel] = speex_preprocess_state_init((int)frames,
			sample_rate);
97

98 99
	circlebuf_reserve(&ng->input_buffers[channel],  frames * sizeof(float));
	circlebuf_reserve(&ng->output_buffers[channel], frames * sizeof(float));
100 101 102 103 104 105 106 107
}

static void noise_suppress_update(void *data, obs_data_t *s)
{
	struct noise_suppress_data *ng = data;

	uint32_t sample_rate = audio_output_get_sample_rate(obs_get_audio());
	size_t channels = audio_output_get_channels(obs_get_audio());
108 109 110
	size_t frames = (size_t)sample_rate / 100;

	ng->suppress_level = (int)obs_data_get_int(s, S_SUPPRESS_LEVEL);
111

112 113 114
	/* Process 10 millisecond segments to keep latency low */
	ng->frames = frames;
	ng->channels = channels;
115

116 117 118
	/* Ignore if already allocated */
	if (ng->states[0])
		return;
119 120

	/* One speex state for each channel (limit 2) */
121 122 123 124 125 126 127 128
	ng->copy_buffers[0] = bmalloc(frames * channels * sizeof(float));
	ng->segment_buffers[0] = bmalloc(frames * channels * sizeof(spx_int16_t));

	if (channels == 2) {
		ng->copy_buffers[1] = ng->copy_buffers[0] + frames;
		ng->segment_buffers[1] = ng->segment_buffers[0] + frames;
	}

129
	for (size_t i = 0; i < channels; i++)
130
		alloc_channel(ng, sample_rate, i, frames);
131 132 133 134 135 136 137 138 139 140 141 142
}

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;
}

143
static inline void process(struct noise_suppress_data *ng)
144
{
145 146 147 148 149
	/* Pop from input circlebuf */
	for (size_t i = 0; i < ng->channels; i++)
		circlebuf_pop_front(&ng->input_buffers[i], ng->copy_buffers[i],
				ng->frames * sizeof(float));

150
	/* Set args */
151 152 153 154
	for (size_t i = 0; i < ng->channels; i++)
		speex_preprocess_ctl(ng->states[i],
				SPEEX_PREPROCESS_SET_NOISE_SUPPRESS,
				&ng->suppress_level);
155 156

	/* Convert to 16bit */
157 158 159 160
	for (size_t i = 0; i < ng->channels; i++)
		for (size_t j = 0; j < ng->frames; j++)
			ng->segment_buffers[i][j] = (spx_int16_t)
				(ng->copy_buffers[i][j] * c_32_to_16);
161 162

	/* Execute */
163 164
	for (size_t i = 0; i < ng->channels; i++)
		speex_preprocess_run(ng->states[i], ng->segment_buffers[i]);
165 166

	/* Convert back to 32bit */
167 168 169 170 171 172 173 174 175
	for (size_t i = 0; i < ng->channels; i++)
		for (size_t j = 0; j < ng->frames; j++)
			ng->copy_buffers[i][j] =
				(float)ng->segment_buffers[i][j] / c_16_to_32;

	/* Push to output circlebuf */
	for (size_t i = 0; i < ng->channels; i++)
		circlebuf_push_back(&ng->output_buffers[i], ng->copy_buffers[i],
				ng->frames * sizeof(float));
176 177
}

178 179 180 181 182
struct ng_audio_info {
	uint32_t frames;
	uint64_t timestamp;
};

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
static inline void clear_circlebuf(struct circlebuf *buf)
{
	circlebuf_pop_front(buf, NULL, buf->size);
}

static void reset_data(struct noise_suppress_data *ng)
{
	for (size_t i = 0; i < ng->channels; i++) {
		clear_circlebuf(&ng->input_buffers[i]);
		clear_circlebuf(&ng->output_buffers[i]);
	}

	clear_circlebuf(&ng->info_buffer);
}

198 199 200 201
static struct obs_audio_data *noise_suppress_filter_audio(void *data,
	struct obs_audio_data *audio)
{
	struct noise_suppress_data *ng = data;
202 203 204 205 206 207 208
	struct ng_audio_info info;
	size_t segment_size = ng->frames * sizeof(float);
	size_t out_size;

	if (!ng->states[0])
		return audio;

209 210 211 212
	/* -----------------------------------------------
	 * if timestamp has dramatically changed, consider it a new stream of
	 * audio data.  clear all circular buffers to prevent old audio data
	 * from being processed as part of the new data. */
J
jp9000 已提交
213
	if (ng->last_timestamp) {
214 215 216 217 218 219 220
		int64_t diff = llabs((int64_t)ng->last_timestamp -
				(int64_t)audio->timestamp);

		if (diff > 1000000000LL)
			reset_data(ng);
	}

221 222 223 224
	ng->last_timestamp = audio->timestamp;

	/* -----------------------------------------------
	 * push audio packet info (timestamp/frame count) to info circlebuf */
225 226 227 228
	info.frames = audio->frames;
	info.timestamp = audio->timestamp;
	circlebuf_push_back(&ng->info_buffer, &info, sizeof(info));

229 230
	/* -----------------------------------------------
	 * push back current audio data to input circlebuf */
231 232 233 234
	for (size_t i = 0; i < ng->channels; i++)
		circlebuf_push_back(&ng->input_buffers[i], audio->data[i],
				audio->frames * sizeof(float));

235 236
	/* -----------------------------------------------
	 * pop/process each 10ms segments, push back to output circlebuf */
237 238 239
	while (ng->input_buffers[0].size >= segment_size)
		process(ng);

240 241 242
	/* -----------------------------------------------
	 * peek front of info circlebuf, check to see if we have enough to
	 * pop the expected packet size, if not, return null */
243 244 245 246 247 248 249
	memset(&info, 0, sizeof(info));
	circlebuf_peek_front(&ng->info_buffer, &info, sizeof(info));
	out_size = info.frames * sizeof(float);

	if (ng->output_buffers[0].size < out_size)
		return NULL;

250 251 252
	/* -----------------------------------------------
	 * if there's enough audio data buffered in the output circlebuf,
	 * pop and return a packet */
253 254 255 256 257 258
	circlebuf_pop_front(&ng->info_buffer, NULL, sizeof(info));
	da_resize(ng->output_data, out_size * ng->channels);

	for (size_t i = 0; i < ng->channels; i++) {
		ng->output_audio.data[i] =
			(uint8_t*)&ng->output_data.array[i * out_size];
259

260 261 262
		circlebuf_pop_front(&ng->output_buffers[i],
				ng->output_audio.data[i],
				out_size);
263 264
	}

265 266 267
	ng->output_audio.frames = info.frames;
	ng->output_audio.timestamp = info.timestamp;
	return &ng->output_audio;
268 269 270 271 272 273 274 275 276 277 278 279
}

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,
280
			TEXT_SUPPRESS_LEVEL, SUP_MIN, SUP_MAX, 1);
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297

	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,
};