#if HAVE_CRT
#define _CRTDBG_MAP_ALLOC 
#include <stdlib.h> 
#include <crtdbg.h>
#endif //HAVE_CRT
/*
* Copyright (C) 2020, University of the Basque Country (UPV/EHU)
* Contact for licensing options: <licensing-mcpttclient(at)mcopenplatform(dot)com>
*
* The original file was part of Open Source Doubango Framework
* Copyright (C) 2010-2011 Mamadou Diop.
* Copyright (C) 2012 Doubango Telecom <http://doubango.org>
*
* This file is part of Open Source Doubango Framework.
*
* DOUBANGO 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 3 of the License, or
* (at your option) any later version.
*
* DOUBANGO 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 DOUBANGO.
*
*/


/**@file tdav_webrtc_denoise.c
* @brief Google WebRTC Denoiser (Noise suppression, AGC, AEC) Plugin
*/
#include "tinydav/audio/tdav_webrtc_denoise.h"

#if HAVE_WEBRTC && (!defined(HAVE_WEBRTC_DENOISE) || HAVE_WEBRTC_DENOISE)

#include "tsk_string.h"
#include "tsk_memory.h"
#include "tsk_debug.h"

#include "tinymedia/tmedia_defaults.h"
#include "tinymedia/tmedia_resampler.h"

#include <string.h>

#if !defined(WEBRTC_AEC_AGGRESSIVE)
#	define WEBRTC_AEC_AGGRESSIVE		0
#endif
#if !defined(WEBRTC_MAX_ECHO_TAIL)
#	define WEBRTC_MAX_ECHO_TAIL		500
#endif
#if !defined(WEBRTC_MIN_ECHO_TAIL)
#	define WEBRTC_MIN_ECHO_TAIL		20 // 0 will cause random crashes
#endif

#if TDAV_UNDER_MOBILE || 1 // FIXME
typedef int16_t sample_t;
#else
typedef float sample_t;
#endif

typedef struct tdav_webrtc_pin_xs
{
	uint32_t n_duration;
	uint32_t n_rate;
	uint32_t n_channels;
	uint32_t n_sample_size;
}
tdav_webrtc_pin_xt;

typedef struct tdav_webrtc_resampler_s
{
	TSK_DECLARE_OBJECT;

	tmedia_resampler_t* p_resampler;
	void* p_bufftmp_ptr; // used to convert float <->int16
	tsk_size_t n_bufftmp_size_in_bytes;

	struct {
		tdav_webrtc_pin_xt x_pin;
		tsk_size_t n_buff_size_in_bytes;
		tsk_size_t n_buff_size_in_samples;
	} in;
	struct {
		tdav_webrtc_pin_xt x_pin;
		void* p_buff_ptr;
		tsk_size_t n_buff_size_in_bytes;
		tsk_size_t n_buff_size_in_samples;
	} out;
}
tdav_webrtc_resampler_t;

static int _tdav_webrtc_resampler_create(const tdav_webrtc_pin_xt* p_pin_in, const tdav_webrtc_pin_xt* p_pin_out, tdav_webrtc_resampler_t **pp_resampler);
static int _tdav_webrtc_resampler_process(tdav_webrtc_resampler_t* p_self, const void* p_buff_ptr, tsk_size_t n_buff_size_in_bytes);

/** WebRTC denoiser (AEC, NS, AGC...) */
typedef struct tdav_webrtc_denoise_s
{
	TMEDIA_DECLARE_DENOISE;

	void *AEC_inst;
#if HAVE_SPEEX_DSP && PREFER_SPEEX_DENOISER
	SpeexPreprocessState *SpeexDenoiser_proc;
#else
	TDAV_NsHandle *NS_inst;
#endif

	uint32_t echo_tail;
	uint32_t echo_skew;

	struct {
		tdav_webrtc_resampler_t* p_rpl_in2den; // input -> denoiser
		tdav_webrtc_resampler_t* p_rpl_den2in; // denoiser -> input
	} record;
	struct {
		tdav_webrtc_resampler_t* p_rpl_in2den; // input -> denoiser
		tdav_webrtc_resampler_t* p_rpl_den2in; // denoiser -> input
	} playback;

	struct {
		uint32_t nb_samples_per_process;
		uint32_t sampling_rate;
		uint32_t channels; // always "1"
	} neg;

	TSK_DECLARE_SAFEOBJ;
}
tdav_webrtc_denoise_t;

static int tdav_webrtc_denoise_set(tmedia_denoise_t* _self, const tmedia_param_t* param)
{
	tdav_webrtc_denoise_t *self = (tdav_webrtc_denoise_t *)_self;
	if (!self || !param) {
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	if (param->value_type == tmedia_pvt_int32) {
		if (tsk_striequals(param->key, "echo-tail")) {
			int32_t echo_tail = *((int32_t*)param->value);
			self->echo_tail = TSK_CLAMP(WEBRTC_MIN_ECHO_TAIL, echo_tail, WEBRTC_MAX_ECHO_TAIL);
			TSK_DEBUG_INFO("set_echo_tail (%d->%d)", echo_tail, self->echo_tail);
			return 0;
		}
	}
	return -1;
}

static int tdav_webrtc_denoise_open(tmedia_denoise_t* self, uint32_t record_frame_size_samples, uint32_t record_sampling_rate, uint32_t record_channels, uint32_t playback_frame_size_samples, uint32_t playback_sampling_rate, uint32_t playback_channels)
{
	tdav_webrtc_denoise_t *denoiser = (tdav_webrtc_denoise_t *)self;
	int ret;
	tdav_webrtc_pin_xt pin_record_in = { 0 }, pin_record_den = { 0 }, pin_playback_in = { 0 }, pin_playback_den = { 0 };

	if (!denoiser) {
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	if (denoiser->AEC_inst ||
#if HAVE_SPEEX_DSP && PREFER_SPEEX_DENOISER
		denoiser->SpeexDenoiser_proc
#else
		denoiser->NS_inst
#endif
		){
		TSK_DEBUG_ERROR("Denoiser already initialized");
		return -2;
	}

	denoiser->echo_tail = TSK_CLAMP(WEBRTC_MIN_ECHO_TAIL, TMEDIA_DENOISE(denoiser)->echo_tail, WEBRTC_MAX_ECHO_TAIL);
	denoiser->echo_skew = TMEDIA_DENOISE(denoiser)->echo_skew;
	TSK_DEBUG_INFO("echo_tail=%d, echo_skew=%d, echo_supp_enabled=%d, noise_supp_enabled=%d", denoiser->echo_tail, denoiser->echo_skew, self->echo_supp_enabled, self->noise_supp_enabled);

	//
	//	DENOISER
	//
#if TDAV_UNDER_MOBILE // AECM= [8-16]k, AEC=[8-32]k
	denoiser->neg.sampling_rate = TSK_MIN(TSK_MAX(record_sampling_rate, playback_sampling_rate), 16000);
#else
	denoiser->neg.sampling_rate = TSK_MIN(TSK_MAX(record_sampling_rate, playback_sampling_rate), 16000); // FIXME: 32000 accepted by echo_process fails
#endif
	denoiser->neg.nb_samples_per_process = /*TSK_CLAMP(80,*/ ((denoiser->neg.sampling_rate * 10) / 1000)/*, 160)*/; // Supported by the module: "80"(10ms) and "160"(20ms)
	denoiser->neg.channels = 1;

	//
	//	RECORD
	//
	TSK_OBJECT_SAFE_FREE(denoiser->record.p_rpl_den2in);
	TSK_OBJECT_SAFE_FREE(denoiser->record.p_rpl_in2den);
	pin_record_in.n_sample_size = sizeof(int16_t);
	pin_record_in.n_rate = record_sampling_rate;
	pin_record_in.n_channels = record_channels;
	pin_record_in.n_duration = (((record_frame_size_samples * 1000) / record_sampling_rate)) / record_channels;
	pin_record_den.n_sample_size = sizeof(sample_t);
	pin_record_den.n_rate = denoiser->neg.sampling_rate;

	pin_record_den.n_channels = 1;
	pin_record_den.n_duration = pin_record_in.n_duration;
	if (pin_record_in.n_sample_size != pin_record_den.n_sample_size || pin_record_in.n_rate != pin_record_den.n_rate || pin_record_in.n_channels != pin_record_den.n_channels) {
		if ((ret = _tdav_webrtc_resampler_create(&pin_record_in, &pin_record_den, &denoiser->record.p_rpl_in2den))) {
			return ret;
		}
		if ((ret = _tdav_webrtc_resampler_create(&pin_record_den, &pin_record_in, &denoiser->record.p_rpl_den2in))) {
			return ret;
		}
	}
	//
	//	PLAYBACK
	//
	TSK_OBJECT_SAFE_FREE(denoiser->playback.p_rpl_den2in);
	TSK_OBJECT_SAFE_FREE(denoiser->playback.p_rpl_in2den);
	pin_playback_in.n_sample_size = sizeof(int16_t);
	pin_playback_in.n_rate = playback_sampling_rate;
	pin_playback_in.n_channels = playback_channels;
	pin_playback_in.n_duration = (((playback_frame_size_samples * 1000) / playback_sampling_rate)) / playback_channels;
	pin_playback_den.n_sample_size = sizeof(sample_t);
	pin_playback_den.n_rate = denoiser->neg.sampling_rate;
	pin_playback_den.n_channels = 1;
	pin_playback_den.n_duration = pin_playback_in.n_duration;
	if (pin_playback_in.n_sample_size != pin_playback_den.n_sample_size || pin_playback_in.n_rate != pin_playback_den.n_rate || pin_playback_in.n_channels != pin_playback_den.n_channels) {
		if ((ret = _tdav_webrtc_resampler_create(&pin_playback_in, &pin_playback_den, &denoiser->playback.p_rpl_in2den))) {
			return ret;
		}
		if ((ret = _tdav_webrtc_resampler_create(&pin_playback_den, &pin_playback_in, &denoiser->playback.p_rpl_den2in))) {
			return ret;
		}
	}

	//
	//	AEC instance
	//
	if ((ret = TDAV_WebRtcAec_Create(&denoiser->AEC_inst))) {
		TSK_DEBUG_ERROR("WebRtcAec_Create failed with error code = %d", ret);
		return ret;
	}
	if ((ret = TDAV_WebRtcAec_Init(denoiser->AEC_inst, denoiser->neg.sampling_rate, denoiser->neg.sampling_rate))) {
		TSK_DEBUG_ERROR("WebRtcAec_Init failed with error code = %d", ret);
		return ret;
	}

#if TDAV_UNDER_MOBILE
#else
	{
		AecConfig aecConfig;
#if WEBRTC_AEC_AGGRESSIVE
		aecConfig.nlpMode = kAecNlpAggressive;
#else
		aecConfig.nlpMode = kAecNlpModerate;
#endif
		aecConfig.skewMode = kAecFalse;
		aecConfig.metricsMode = kAecTrue;
		aecConfig.delay_logging = kAecFalse;
		if ((ret = WebRtcAec_set_config(denoiser->AEC_inst, aecConfig))) {
			TSK_DEBUG_ERROR("WebRtcAec_set_config failed with error code = %d", ret);
		}
	}
#endif


	//
	//	Noise Suppression instance
	//
	if (TMEDIA_DENOISE(denoiser)->noise_supp_enabled) {
#if HAVE_SPEEX_DSP && PREFER_SPEEX_DENOISER
		if ((denoiser->SpeexDenoiser_proc = speex_preprocess_state_init((pin_record_den.n_rate / 1000) * pin_record_den.n_duration, pin_record_den.n_rate))) {
			int i = 1;
			speex_preprocess_ctl(denoiser->SpeexDenoiser_proc, SPEEX_PREPROCESS_SET_DENOISE, &i);
			i = TMEDIA_DENOISE(denoiser)->noise_supp_level;
			speex_preprocess_ctl(denoiser->SpeexDenoiser_proc, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &i);
		}
#else
		if ((ret = TDAV_WebRtcNs_Create(&denoiser->NS_inst))) {
			TSK_DEBUG_ERROR("WebRtcNs_Create failed with error code = %d", ret);
			return ret;
		}
		if ((ret = TDAV_WebRtcNs_Init(denoiser->NS_inst, 80))) {
			TSK_DEBUG_ERROR("WebRtcNs_Init failed with error code = %d", ret);
			return ret;
		}
#endif
	}

	TSK_DEBUG_INFO("WebRTC denoiser opened: record:%uHz,%uchannels // playback:%uHz,%uchannels // neg:%uHz,%uchannels",
		record_sampling_rate, record_channels,
		playback_sampling_rate, playback_channels,
		denoiser->neg.sampling_rate, denoiser->neg.channels);

	return ret;
}

static int tdav_webrtc_denoise_echo_playback(tmedia_denoise_t* self, const void* echo_frame, uint32_t echo_frame_size_bytes)
{
	tdav_webrtc_denoise_t *p_self = (tdav_webrtc_denoise_t *)self;
	int ret = 0;
	
	tsk_safeobj_lock(p_self);
	if (p_self->AEC_inst && echo_frame && echo_frame_size_bytes) {
		const sample_t* _echo_frame = (const sample_t*)echo_frame;
		tsk_size_t _echo_frame_size_bytes = echo_frame_size_bytes;
		tsk_size_t _echo_frame_size_samples = (_echo_frame_size_bytes / sizeof(int16_t));
		// IN -> DEN
		if (p_self->playback.p_rpl_in2den) {
			if ((ret = _tdav_webrtc_resampler_process(p_self->playback.p_rpl_in2den, _echo_frame, _echo_frame_size_bytes))) {
				goto bail;
			}
			_echo_frame = p_self->playback.p_rpl_in2den->out.p_buff_ptr;
			_echo_frame_size_bytes = p_self->playback.p_rpl_in2den->out.n_buff_size_in_bytes;
			_echo_frame_size_samples = p_self->playback.p_rpl_in2den->out.n_buff_size_in_samples;
		}
		// PROCESS
		if (_echo_frame_size_samples && _echo_frame) {
			uint32_t _samples;
			for (_samples = 0; _samples < _echo_frame_size_samples; _samples += p_self->neg.nb_samples_per_process) {
				if ((ret = TDAV_WebRtcAec_BufferFarend(p_self->AEC_inst, &_echo_frame[_samples], p_self->neg.nb_samples_per_process))){
					TSK_DEBUG_ERROR("WebRtcAec_BufferFarend failed with error code = %d, nb_samples_per_process=%u", ret, p_self->neg.nb_samples_per_process);
					goto bail;
				}
			}
		}
	}
bail:
	tsk_safeobj_unlock(p_self);
	return ret;
}

static int tdav_webrtc_denoise_process_record(tmedia_denoise_t* self, void* audio_frame, uint32_t audio_frame_size_bytes, tsk_bool_t* silence_or_noise)
{
	tdav_webrtc_denoise_t *p_self = (tdav_webrtc_denoise_t *)self;
	int ret = 0;

	*silence_or_noise = tsk_false;

	tsk_safeobj_lock(p_self);

	if (p_self->AEC_inst && audio_frame && audio_frame_size_bytes) {
		tsk_size_t _samples;
		const sample_t* _audio_frame = (const sample_t*)audio_frame;
		tsk_size_t _audio_frame_size_bytes = audio_frame_size_bytes;
		tsk_size_t _audio_frame_size_samples = (_audio_frame_size_bytes / sizeof(int16_t));
		// IN -> DEN
		if (p_self->record.p_rpl_in2den) {
			if ((ret = _tdav_webrtc_resampler_process(p_self->record.p_rpl_in2den, _audio_frame, _audio_frame_size_bytes))) {
				goto bail;
			}
			_audio_frame = p_self->record.p_rpl_in2den->out.p_buff_ptr;
			_audio_frame_size_bytes = p_self->record.p_rpl_in2den->out.n_buff_size_in_bytes;
			_audio_frame_size_samples = p_self->record.p_rpl_in2den->out.n_buff_size_in_samples;
		}
		// NOISE SUPPRESSION
#if HAVE_SPEEX_DSP && PREFER_SPEEX_DENOISER
		if (p_self->SpeexDenoiser_proc) {
			speex_preprocess_run(p_self->SpeexDenoiser_proc, (spx_int16_t*)_audio_frame);
		}
#else
		// WebRTC NoiseSupp only accept 10ms frames
		// Our encoder will always output 20ms frames ==> execute 2x noise_supp
		if (p_self->NS_inst) {
			for (_samples = 0; _samples < _audio_frame_size_samples; _samples+= p_self->neg.nb_samples_per_process) {
				if ((ret = TDAV_WebRtcNs_Process(p_self->NS_inst, &_audio_frame[_samples], tsk_null, _audio_frame, tsk_null))) {
					TSK_DEBUG_ERROR("WebRtcNs_Process with error code = %d", ret);
					goto bail;
				}
			}
		}
#endif
		// PROCESS
		if (_audio_frame_size_samples && _audio_frame) {
			for (_samples = 0; _samples < _audio_frame_size_samples; _samples += p_self->neg.nb_samples_per_process) {
				if ((ret = TDAV_WebRtcAec_Process(p_self->AEC_inst, &_audio_frame[_samples], tsk_null, (sample_t*)&_audio_frame[_samples], tsk_null, p_self->neg.nb_samples_per_process, p_self->echo_tail, p_self->echo_skew))){
					TSK_DEBUG_ERROR("WebRtcAec_Process with error code = %d, nb_samples_per_process=%u", ret, p_self->neg.nb_samples_per_process);
					goto bail;
				}
			}
		}
		// DEN -> IN
		if (p_self->record.p_rpl_den2in) {
			if ((ret = _tdav_webrtc_resampler_process(p_self->record.p_rpl_den2in, _audio_frame, _audio_frame_size_bytes))) {
				goto bail;
			}
			_audio_frame = p_self->record.p_rpl_den2in->out.p_buff_ptr;
			_audio_frame_size_bytes = p_self->record.p_rpl_den2in->out.n_buff_size_in_bytes;
			_audio_frame_size_samples = p_self->record.p_rpl_den2in->out.n_buff_size_in_samples;
		}
		// Sanity check
		if (_audio_frame_size_bytes != audio_frame_size_bytes) {
			TSK_DEBUG_ERROR("Size mismatch: %u <> %u", _audio_frame_size_bytes, audio_frame_size_bytes);
			ret = -3;
			goto bail;
		}
		if (audio_frame != (const void*)_audio_frame) {
			memcpy(audio_frame, _audio_frame, _audio_frame_size_bytes);
		}
	}

bail:
	tsk_safeobj_unlock(p_self);
	return ret;
}

static int tdav_webrtc_denoise_process_playback(tmedia_denoise_t* self, void* audio_frame, uint32_t audio_frame_size_bytes)
{
	tdav_webrtc_denoise_t *denoiser = (tdav_webrtc_denoise_t *)self;

	(void)(denoiser);

	// Not mandatory to denoise audio before playback.
	// All Doubango clients support noise suppression.
	return 0;
}

static int tdav_webrtc_denoise_close(tmedia_denoise_t* self)
{
	tdav_webrtc_denoise_t *denoiser = (tdav_webrtc_denoise_t *)self;

	tsk_safeobj_lock(denoiser);
	if (denoiser->AEC_inst) {
		TDAV_WebRtcAec_Free(denoiser->AEC_inst);
		denoiser->AEC_inst = tsk_null;
	}
#if HAVE_SPEEX_DSP && PREFER_SPEEX_DENOISER
	if (denoiser->SpeexDenoiser_proc) {
		speex_preprocess_state_destroy(denoiser->SpeexDenoiser_proc);
		denoiser->SpeexDenoiser_proc = tsk_null;
	}
#else
	if (denoiser->NS_inst) {
		TDAV_WebRtcNs_Free(denoiser->NS_inst);
		denoiser->NS_inst = tsk_null;
	}
#endif
	tsk_safeobj_unlock(denoiser);

	return 0;
}

static int _tdav_webrtc_resampler_create(const tdav_webrtc_pin_xt* p_pin_in, const tdav_webrtc_pin_xt* p_pin_out, tdav_webrtc_resampler_t **pp_resampler)
{
	extern const tsk_object_def_t *tdav_webrtc_resampler_def_t;
	int ret = 0;
	if (!p_pin_in || !p_pin_out || !pp_resampler || *pp_resampler) {
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	if (!(*pp_resampler = tsk_object_new(tdav_webrtc_resampler_def_t))) {
		TSK_DEBUG_ERROR("Failed to create resampler object");
		ret = -3;
		goto bail;
	}
	if (!((*pp_resampler)->p_resampler = tmedia_resampler_create())) {
		ret = -3;
		goto bail;
	}
	ret = tmedia_resampler_open((*pp_resampler)->p_resampler,
		p_pin_in->n_rate, p_pin_out->n_rate,
		p_pin_in->n_duration,
		p_pin_in->n_channels, p_pin_out->n_channels,
		TMEDIA_RESAMPLER_QUALITY,
		(p_pin_out->n_sample_size << 3));
	if (ret) {
		TSK_DEBUG_ERROR("Failed to open resampler: in_rate=%u,in_duration=%u,in_channels=%u /// out_rate=%u,out_duration=%u,out_channels=%u",
			p_pin_in->n_rate, p_pin_in->n_duration, p_pin_in->n_channels,
			p_pin_out->n_rate, p_pin_out->n_duration, p_pin_out->n_channels);
		goto bail;
	}

	(*pp_resampler)->out.n_buff_size_in_bytes = ((((p_pin_out->n_rate * p_pin_out->n_duration) / 1000)) * p_pin_out->n_channels) * p_pin_out->n_sample_size;
	#if HAVE_CRT //Debug memory
		(*pp_resampler)->out.p_buff_ptr = malloc((*pp_resampler)->out.n_buff_size_in_bytes);
	#else
		(*pp_resampler)->out.p_buff_ptr = tsk_malloc((*pp_resampler)->out.n_buff_size_in_bytes);
	#endif //HAVE_CRT
	if (!(*pp_resampler)->out.p_buff_ptr) {
		TSK_DEBUG_ERROR("Failed to allocate buffer with size=%u", (*pp_resampler)->out.n_buff_size_in_bytes);
		ret = -3;
		goto bail;
	}
	(*pp_resampler)->out.n_buff_size_in_samples = (*pp_resampler)->out.n_buff_size_in_bytes / p_pin_out->n_sample_size;
	(*pp_resampler)->in.n_buff_size_in_bytes = ((((p_pin_in->n_rate * p_pin_in->n_duration) / 1000)) * p_pin_in->n_channels) * p_pin_in->n_sample_size;
	(*pp_resampler)->in.n_buff_size_in_samples = (*pp_resampler)->in.n_buff_size_in_bytes / p_pin_in->n_sample_size;

	(*pp_resampler)->n_bufftmp_size_in_bytes = (((48000 * TSK_MAX(p_pin_in->n_duration, p_pin_out->n_duration)) / 1000) * 2/*channels*/) * sizeof(float); // Max
	#if HAVE_CRT //Debug memory
		(*pp_resampler)->p_bufftmp_ptr = malloc((*pp_resampler)->n_bufftmp_size_in_bytes);
	#else
		(*pp_resampler)->p_bufftmp_ptr = tsk_malloc((*pp_resampler)->n_bufftmp_size_in_bytes);
	#endif //HAVE_CRT
	
	if (!(*pp_resampler)->p_bufftmp_ptr) {
		TSK_DEBUG_ERROR("Failed to allocate buffer with size:%u", (*pp_resampler)->n_bufftmp_size_in_bytes);
		ret = -3;
		goto bail;
	}

	memcpy(&(*pp_resampler)->in.x_pin, p_pin_in, sizeof(tdav_webrtc_pin_xt));
	memcpy(&(*pp_resampler)->out.x_pin, p_pin_out, sizeof(tdav_webrtc_pin_xt));
bail:
	if (ret) {
		TSK_OBJECT_SAFE_FREE((*pp_resampler));
	}
	return ret;
}

static int _tdav_webrtc_resampler_process(tdav_webrtc_resampler_t *p_self, const void* p_buff_ptr, tsk_size_t n_buff_size_in_bytes)
{
	tsk_size_t n_out_size;
	const void* _p_buff_ptr = p_buff_ptr;
	tsk_size_t _n_buff_size_in_bytes = n_buff_size_in_bytes;
	tsk_size_t _n_buff_size_in_samples;

	if (!p_self || !p_buff_ptr || !n_buff_size_in_bytes) {
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	if (p_self->in.n_buff_size_in_bytes != n_buff_size_in_bytes) {
		TSK_DEBUG_ERROR("Invalid input size: %u <> %u", p_self->in.n_buff_size_in_bytes, n_buff_size_in_bytes);
		return -2;
	}
	_n_buff_size_in_samples = p_self->in.n_buff_size_in_samples;
	if (p_self->in.x_pin.n_sample_size != p_self->out.x_pin.n_sample_size) {
		tsk_size_t index;
		if (p_self->in.x_pin.n_sample_size == sizeof(int16_t)) {
			// int16_t -> float
			const int16_t* p_src = (const int16_t*)p_buff_ptr;
			float* p_dst = (float*)p_self->p_bufftmp_ptr;
			for (index = 0; index < _n_buff_size_in_samples; ++index) {
				p_dst[index] = (float)p_src[index];
			}
		}
		else {
			// float -> int16_t
			const float* p_src = (const float*)p_buff_ptr;
			int16_t* p_dst = (int16_t*)p_self->p_bufftmp_ptr;
			for (index = 0; index < _n_buff_size_in_samples; ++index) {
				p_dst[index] = (int16_t)p_src[index];
			}
		}
		_p_buff_ptr = p_self->p_bufftmp_ptr;
		_n_buff_size_in_bytes = p_self->in.n_buff_size_in_bytes;
	}
	n_out_size = tmedia_resampler_process(p_self->p_resampler, _p_buff_ptr, _n_buff_size_in_samples, (int16_t*)p_self->out.p_buff_ptr, p_self->out.n_buff_size_in_samples);
	if (n_out_size != p_self->out.n_buff_size_in_samples) {
		TSK_DEBUG_ERROR("Invalid output size: %u <> %u", n_out_size, p_self->out.n_buff_size_in_bytes);
		return -4;
	}
	return 0;
}

//
//	WEBRTC resampler object definition
//
static tsk_object_t* tdav_webrtc_resampler_ctor(tsk_object_t * self, va_list * app)
{
	tdav_webrtc_resampler_t *p_resampler = (tdav_webrtc_resampler_t*)self;
	if (p_resampler) {

	}
	return self;
}
static tsk_object_t* tdav_webrtc_resampler_dtor(tsk_object_t * self)
{
	tdav_webrtc_resampler_t *p_resampler = (tdav_webrtc_resampler_t*)self;
	if (p_resampler) {
		TSK_OBJECT_SAFE_FREE(p_resampler->p_resampler);
		TSK_FREE(p_resampler->out.p_buff_ptr);
		TSK_FREE(p_resampler->p_bufftmp_ptr);
	}
	return self;
}
static const tsk_object_def_t tdav_webrtc_resampler_def_s =
{
	sizeof(tdav_webrtc_resampler_t),
	tdav_webrtc_resampler_ctor,
	tdav_webrtc_resampler_dtor,
	tsk_object_cmp,
};
const tsk_object_def_t *tdav_webrtc_resampler_def_t = &tdav_webrtc_resampler_def_s;


//
//	WEBRTC denoiser Plugin definition
//

/* constructor */
static tsk_object_t* tdav_webrtc_denoise_ctor(tsk_object_t * _self, va_list * app)
{
	tdav_webrtc_denoise_t *self = _self;
	if (self){
		/* init base */
		tmedia_denoise_init(TMEDIA_DENOISE(self));
		/* init self */
		tsk_safeobj_init(self);
		self->neg.channels = 1;

		TSK_DEBUG_INFO("Create WebRTC denoiser");
	}
	return self;
}
/* destructor */
static tsk_object_t* tdav_webrtc_denoise_dtor(tsk_object_t * _self)
{
	tdav_webrtc_denoise_t *self = _self;
	if (self){
		/* deinit base (will close the denoise if not done yet) */
		tmedia_denoise_deinit(TMEDIA_DENOISE(self));
		/* deinit self */
		tdav_webrtc_denoise_close(TMEDIA_DENOISE(self));
		TSK_OBJECT_SAFE_FREE(self->record.p_rpl_in2den);
		TSK_OBJECT_SAFE_FREE(self->record.p_rpl_den2in);
		TSK_OBJECT_SAFE_FREE(self->playback.p_rpl_in2den);
		TSK_OBJECT_SAFE_FREE(self->playback.p_rpl_den2in);
		tsk_safeobj_deinit(self);

		TSK_DEBUG_INFO("*** Destroy WebRTC denoiser ***");
	}

	return self;
}
/* object definition */
static const tsk_object_def_t tdav_webrtc_denoise_def_s =
{
	sizeof(tdav_webrtc_denoise_t),
	tdav_webrtc_denoise_ctor,
	tdav_webrtc_denoise_dtor,
	tsk_null,
};
/* plugin definition*/
static const tmedia_denoise_plugin_def_t tdav_webrtc_denoise_plugin_def_s =
{
	&tdav_webrtc_denoise_def_s,

	"Audio Denoiser based on Google WebRTC",

	tdav_webrtc_denoise_set,
	tdav_webrtc_denoise_open,
	tdav_webrtc_denoise_echo_playback,
	tdav_webrtc_denoise_process_record,
	tdav_webrtc_denoise_process_playback,
	tdav_webrtc_denoise_close,
};
const tmedia_denoise_plugin_def_t *tdav_webrtc_denoise_plugin_def_t = &tdav_webrtc_denoise_plugin_def_s;


#endif /* HAVE_WEBRTC */