NULL
};
-static void pa_adrian_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map)
+static void pa_adrian_ec_fixate_spec(pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map)
{
- source_ss->format = PA_SAMPLE_S16NE;
- source_ss->channels = 1;
- pa_channel_map_init_mono(source_map);
-
- *sink_ss = *source_ss;
- *sink_map = *source_map;
+ out_ss->format = PA_SAMPLE_S16NE;
+ out_ss->channels = 1;
+ pa_channel_map_init_mono(out_map);
+
+ *play_ss = *out_ss;
+ *play_map = *out_map;
+ *rec_ss = *out_ss;
+ *rec_map = *out_map;
}
pa_bool_t pa_adrian_ec_init(pa_core *c, pa_echo_canceller *ec,
- pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map,
- uint32_t *nframes, const char *args)
+ pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map,
+ uint32_t *nframes, const char *args)
{
int rate, have_vector = 0;
uint32_t frame_size_ms;
goto fail;
}
- pa_adrian_ec_fixate_spec(source_ss, source_map, sink_ss, sink_map);
+ pa_adrian_ec_fixate_spec(rec_ss, rec_map, play_ss, play_map, out_ss, out_map);
- rate = source_ss->rate;
+ rate = out_ss->rate;
*nframes = (rate * frame_size_ms) / 1000;
- ec->params.priv.adrian.blocksize = (*nframes) * pa_frame_size(source_ss);
+ ec->params.priv.adrian.blocksize = (*nframes) * pa_frame_size(out_ss);
- pa_log_debug ("Using nframes %d, blocksize %u, channels %d, rate %d", *nframes, ec->params.priv.adrian.blocksize, source_ss->channels, source_ss->rate);
+ pa_log_debug ("Using nframes %d, blocksize %u, channels %d, rate %d", *nframes, ec->params.priv.adrian.blocksize, out_ss->channels, out_ss->rate);
/* For now we only support SSE */
if (c->cpu_info.cpu_type == PA_CPU_X86 && (c->cpu_info.flags.x86 & PA_CPU_X86_SSE))
struct pa_echo_canceller_params {
union {
struct {
- pa_sample_spec source_ss;
+ pa_sample_spec out_ss;
} null;
#ifdef HAVE_SPEEX
struct {
/* Initialise canceller engine. */
pa_bool_t (*init) (pa_core *c,
pa_echo_canceller *ec,
- pa_sample_spec *source_ss,
- pa_channel_map *source_map,
- pa_sample_spec *sink_ss,
- pa_channel_map *sink_map,
+ pa_sample_spec *rec_ss,
+ pa_channel_map *rec_map,
+ pa_sample_spec *play_ss,
+ pa_channel_map *play_map,
+ pa_sample_spec *out_ss,
+ pa_channel_map *out_map,
uint32_t *nframes,
const char *args);
/* Null canceller functions */
pa_bool_t pa_null_ec_init(pa_core *c, pa_echo_canceller *ec,
- pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map,
- uint32_t *nframes, const char *args);
+ pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map,
+ uint32_t *nframes, const char *args);
void pa_null_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out);
void pa_null_ec_done(pa_echo_canceller *ec);
#ifdef HAVE_SPEEX
/* Speex canceller functions */
pa_bool_t pa_speex_ec_init(pa_core *c, pa_echo_canceller *ec,
- pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map,
+ pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map,
uint32_t *nframes, const char *args);
void pa_speex_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out);
void pa_speex_ec_done(pa_echo_canceller *ec);
#ifdef HAVE_ADRIAN_EC
/* Adrian Andre's echo canceller */
pa_bool_t pa_adrian_ec_init(pa_core *c, pa_echo_canceller *ec,
- pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map,
- uint32_t *nframes, const char *args);
+ pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map,
+ uint32_t *nframes, const char *args);
void pa_adrian_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out);
void pa_adrian_ec_done(pa_echo_canceller *ec);
#endif
/* WebRTC canceller functions */
PA_C_DECL_BEGIN
pa_bool_t pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec,
- pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map,
+ pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map,
uint32_t *nframes, const char *args);
void pa_webrtc_ec_play(pa_echo_canceller *ec, const uint8_t *play);
void pa_webrtc_ec_record(pa_echo_canceller *ec, const uint8_t *rec, uint8_t *out);
pa_bool_t save_aec;
pa_echo_canceller *ec;
+ uint32_t source_output_blocksize;
uint32_t source_blocksize;
uint32_t sink_blocksize;
/* Add the latency internal to our source output on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec) +
/* and the buffering we do on the source */
- pa_bytes_to_usec(u->source_blocksize, &u->source_output->source->sample_spec);
+ pa_bytes_to_usec(u->source_output_blocksize, &u->source_output->source->sample_spec);
return 0;
*/
drift = ((float)(plen - u->sink_rem) - (rlen - u->source_rem)) / ((float)(rlen - u->source_rem));
u->sink_rem = plen % u->sink_blocksize;
- u->source_rem = rlen % u->source_blocksize;
+ u->source_rem = rlen % u->source_output_blocksize;
/* Now let the canceller work its drift compensation magic */
u->ec->set_drift(u->ec, drift);
}
/* And now the capture samples */
- while (rlen >= u->source_blocksize) {
- pa_memblockq_peek_fixed_size(u->source_memblockq, u->source_blocksize, &rchunk);
+ while (rlen >= u->source_output_blocksize) {
+ pa_memblockq_peek_fixed_size(u->source_memblockq, u->source_output_blocksize, &rchunk);
rdata = pa_memblock_acquire(rchunk.memblock);
rdata += rchunk.index;
cchunk.index = 0;
- cchunk.length = u->source_blocksize;
+ cchunk.length = u->source_output_blocksize;
cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
cdata = pa_memblock_acquire(cchunk.memblock);
if (u->save_aec) {
if (u->drift_file)
- fprintf(u->drift_file, "c %d\n", u->source_blocksize);
+ fprintf(u->drift_file, "c %d\n", u->source_output_blocksize);
if (u->captured_file)
- unused = fwrite(rdata, 1, u->source_blocksize, u->captured_file);
+ unused = fwrite(rdata, 1, u->source_output_blocksize, u->captured_file);
if (u->canceled_file)
- unused = fwrite(cdata, 1, u->source_blocksize, u->canceled_file);
+ unused = fwrite(cdata, 1, u->source_output_blocksize, u->canceled_file);
}
pa_memblock_release(cchunk.memblock);
pa_source_post(u->source, &cchunk);
pa_memblock_unref(cchunk.memblock);
- pa_memblockq_drop(u->source_memblockq, u->source_blocksize);
- rlen -= u->source_blocksize;
+ pa_memblockq_drop(u->source_memblockq, u->source_output_blocksize);
+ rlen -= u->source_output_blocksize;
}
}
rlen = pa_memblockq_get_length(u->source_memblockq);
plen = pa_memblockq_get_length(u->sink_memblockq);
- while (rlen >= u->source_blocksize) {
+ while (rlen >= u->source_output_blocksize) {
/* take fixed blocks from recorded and played samples */
- pa_memblockq_peek_fixed_size(u->source_memblockq, u->source_blocksize, &rchunk);
+ pa_memblockq_peek_fixed_size(u->source_memblockq, u->source_output_blocksize, &rchunk);
pa_memblockq_peek_fixed_size(u->sink_memblockq, u->sink_blocksize, &pchunk);
/* we ran out of played data and pchunk has been filled with silence bytes */
if (u->save_aec) {
if (u->captured_file)
- unused = fwrite(rdata, 1, u->source_blocksize, u->captured_file);
+ unused = fwrite(rdata, 1, u->source_output_blocksize, u->captured_file);
if (u->played_file)
unused = fwrite(pdata, 1, u->sink_blocksize, u->played_file);
}
pa_memblock_release(rchunk.memblock);
/* drop consumed source samples */
- pa_memblockq_drop(u->source_memblockq, u->source_blocksize);
+ pa_memblockq_drop(u->source_memblockq, u->source_output_blocksize);
pa_memblock_unref(rchunk.memblock);
- rlen -= u->source_blocksize;
+ rlen -= u->source_output_blocksize;
/* drop consumed sink samples */
pa_memblockq_drop(u->sink_memblockq, u->sink_blocksize);
plen = pa_memblockq_get_length(u->sink_memblockq);
/* Let's not do anything else till we have enough data to process */
- if (rlen < u->source_blocksize)
+ if (rlen < u->source_output_blocksize)
return;
/* See if we need to drop samples in order to sync */
* means the only way to try to catch up is drop sink samples and let
* the canceller cope up with this. */
to_skip = rlen >= u->source_skip ? u->source_skip : rlen;
- to_skip -= to_skip % u->source_blocksize;
+ to_skip -= to_skip % u->source_output_blocksize;
if (to_skip) {
pa_memblockq_peek_fixed_size(u->source_memblockq, to_skip, &rchunk);
u->source_skip -= to_skip;
}
- if (rlen && u->source_skip % u->source_blocksize) {
- u->sink_skip += (uint64_t) (u->source_blocksize - (u->source_skip % u->source_blocksize)) * u->sink_blocksize / u->source_blocksize;
- u->source_skip -= (u->source_skip % u->source_blocksize);
+ if (rlen && u->source_skip % u->source_output_blocksize) {
+ u->sink_skip += (uint64_t) (u->source_output_blocksize - (u->source_skip % u->source_output_blocksize)) * u->sink_blocksize / u->source_output_blocksize;
+ u->source_skip -= (u->source_skip % u->source_output_blocksize);
}
}
/* Called from main context. */
int pa__init(pa_module*m) {
struct userdata *u;
- pa_sample_spec source_ss, sink_ss;
- pa_channel_map source_map, sink_map;
+ pa_sample_spec source_output_ss, source_ss, sink_ss;
+ pa_channel_map source_output_map, source_map, sink_map;
pa_modargs *ma;
pa_source *source_master=NULL;
pa_sink *sink_master=NULL;
u->asyncmsgq = pa_asyncmsgq_new(0);
u->need_realign = TRUE;
+ source_output_ss = source_ss;
+ source_output_map = source_map;
+
+ if (sink_ss.rate != source_ss.rate) {
+ pa_log_info("Sample rates of play and out stream differ. Adjusting rate of play stream.");
+ sink_ss.rate = source_ss.rate;
+ }
+
pa_assert(u->ec->init);
- if (!u->ec->init(u->core, u->ec, &source_ss, &source_map, &sink_ss, &sink_map, &nframes, pa_modargs_get_value(ma, "aec_args", NULL))) {
+ if (!u->ec->init(u->core, u->ec, &source_output_ss, &source_output_map, &sink_ss, &sink_map, &source_ss, &source_map, &nframes, pa_modargs_get_value(ma, "aec_args", NULL))) {
pa_log("Failed to init AEC engine");
goto fail;
}
+ pa_assert(source_output_ss.rate == source_ss.rate);
+ pa_assert(sink_ss.rate == source_ss.rate);
+
+ u->source_output_blocksize = nframes * pa_frame_size(&source_output_ss);
u->source_blocksize = nframes * pa_frame_size(&source_ss);
u->sink_blocksize = nframes * pa_frame_size(&sink_ss);
pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Source Stream");
pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
- pa_source_output_new_data_set_sample_spec(&source_output_data, &source_ss);
- pa_source_output_new_data_set_channel_map(&source_output_data, &source_map);
+ pa_source_output_new_data_set_sample_spec(&source_output_data, &source_output_ss);
+ pa_source_output_new_data_set_channel_map(&source_output_data, &source_output_map);
pa_source_output_new(&u->source_output, m->core, &source_output_data);
pa_source_output_new_data_done(&source_output_data);
pa_sink_input_get_silence(u->sink_input, &silence);
u->source_memblockq = pa_memblockq_new("module-echo-cancel source_memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0,
- &source_ss, 1, 1, 0, &silence);
+ &source_output_ss, 1, 1, 0, &silence);
u->sink_memblockq = pa_memblockq_new("module-echo-cancel sink_memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0,
&sink_ss, 0, 1, 0, &silence);
*/
int main(int argc, char* argv[]) {
struct userdata u;
- pa_sample_spec source_ss, sink_ss;
- pa_channel_map source_map, sink_map;
+ pa_sample_spec source_output_ss, source_ss, sink_ss;
+ pa_channel_map source_output_map, source_map, sink_map;
pa_modargs *ma = NULL;
uint8_t *rdata = NULL, *pdata = NULL, *cdata = NULL;
int unused PA_GCC_UNUSED;
if (init_common(ma, &u, &source_ss, &source_map) < 0)
goto fail;
- if (!u.ec->init(u.core, u.ec, &source_ss, &source_map, &sink_ss, &sink_map, &nframes,
+ source_output_ss = source_ss;
+ source_output_map = source_map;
+
+ if (!u.ec->init(u.core, u.ec, &source_output_ss, &source_output_map, &sink_ss, &sink_map, &source_ss, &source_map, &nframes,
pa_modargs_get_value(ma, "aec_args", NULL))) {
pa_log("Failed to init AEC engine");
goto fail;
}
+ u.source_output_blocksize = nframes * pa_frame_size(&source_output_ss);
u.source_blocksize = nframes * pa_frame_size(&source_ss);
u.sink_blocksize = nframes * pa_frame_size(&sink_ss);
}
}
- rdata = pa_xmalloc(u.source_blocksize);
+ rdata = pa_xmalloc(u.source_output_blocksize);
pdata = pa_xmalloc(u.sink_blocksize);
cdata = pa_xmalloc(u.source_blocksize);
if (!u.ec->params.drift_compensation) {
- while (fread(rdata, u.source_blocksize, 1, u.captured_file) > 0) {
+ while (fread(rdata, u.source_output_blocksize, 1, u.captured_file) > 0) {
if (fread(pdata, u.sink_blocksize, 1, u.played_file) == 0) {
perror("Played file ended before captured file");
goto fail;
PA_C_DECL_END
pa_bool_t pa_null_ec_init(pa_core *c, pa_echo_canceller *ec,
- pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map,
- uint32_t *nframes, const char *args) {
+ pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map,
+ uint32_t *nframes, const char *args) {
char strss_source[PA_SAMPLE_SPEC_SNPRINT_MAX];
char strss_sink[PA_SAMPLE_SPEC_SNPRINT_MAX];
*nframes = 256;
- ec->params.priv.null.source_ss = *source_ss;
+ ec->params.priv.null.out_ss = *out_ss;
+
+ *rec_ss = *out_ss;
+ *rec_map = *out_map;
pa_log_debug("null AEC: nframes=%u, sample spec source=%s, sample spec sink=%s", *nframes,
- pa_sample_spec_snprint(strss_source, sizeof(strss_source), source_ss),
- pa_sample_spec_snprint(strss_sink, sizeof(strss_sink), sink_ss));
+ pa_sample_spec_snprint(strss_source, sizeof(strss_source), out_ss),
+ pa_sample_spec_snprint(strss_sink, sizeof(strss_sink), play_ss));
return TRUE;
}
void pa_null_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) {
/* The null implementation simply copies the recorded buffer to the output
buffer and ignores the play buffer. */
- memcpy(out, rec, 256 * pa_frame_size(&ec->params.priv.null.source_ss));
+ memcpy(out, rec, 256 * pa_frame_size(&ec->params.priv.null.out_ss));
}
void pa_null_ec_done(pa_echo_canceller *ec) {
NULL
};
-static void pa_speex_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map)
+static void pa_speex_ec_fixate_spec(pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map)
{
- source_ss->format = PA_SAMPLE_S16NE;
+ out_ss->format = PA_SAMPLE_S16NE;
- *sink_ss = *source_ss;
- *sink_map = *source_map;
+ *play_ss = *out_ss;
+ *play_map = *out_map;
+ *rec_ss = *out_ss;
+ *rec_map = *out_map;
}
-static pa_bool_t pa_speex_ec_preprocessor_init(pa_echo_canceller *ec, pa_sample_spec *source_ss, uint32_t nframes, pa_modargs *ma) {
+static pa_bool_t pa_speex_ec_preprocessor_init(pa_echo_canceller *ec, pa_sample_spec *out_ss, uint32_t nframes, pa_modargs *ma) {
pa_bool_t agc;
pa_bool_t denoise;
pa_bool_t echo_suppress;
if (agc || denoise || echo_suppress) {
spx_int32_t tmp;
- if (source_ss->channels != 1) {
+ if (out_ss->channels != 1) {
pa_log("AGC, denoising and echo suppression only work with channels=1");
goto fail;
}
- ec->params.priv.speex.pp_state = speex_preprocess_state_init(nframes, source_ss->rate);
+ ec->params.priv.speex.pp_state = speex_preprocess_state_init(nframes, out_ss->rate);
tmp = agc;
speex_preprocess_ctl(ec->params.priv.speex.pp_state, SPEEX_PREPROCESS_SET_AGC, &tmp);
pa_bool_t pa_speex_ec_init(pa_core *c, pa_echo_canceller *ec,
- pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map,
+ pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map,
uint32_t *nframes, const char *args)
{
int rate;
goto fail;
}
- pa_speex_ec_fixate_spec(source_ss, source_map, sink_ss, sink_map);
+ pa_speex_ec_fixate_spec(rec_ss, rec_map, play_ss, play_map, out_ss, out_map);
- rate = source_ss->rate;
+ rate = out_ss->rate;
*nframes = pa_echo_canceller_blocksize_power2(rate, frame_size_ms);
- pa_log_debug ("Using nframes %d, channels %d, rate %d", *nframes, source_ss->channels, source_ss->rate);
- ec->params.priv.speex.state = speex_echo_state_init_mc(*nframes, (rate * filter_size_ms) / 1000, source_ss->channels, source_ss->channels);
+ pa_log_debug ("Using nframes %d, channels %d, rate %d", *nframes, out_ss->channels, out_ss->rate);
+ ec->params.priv.speex.state = speex_echo_state_init_mc(*nframes, (rate * filter_size_ms) / 1000, out_ss->channels, out_ss->channels);
if (!ec->params.priv.speex.state)
goto fail;
speex_echo_ctl(ec->params.priv.speex.state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
- if (!pa_speex_ec_preprocessor_init(ec, source_ss, *nframes, ma))
+ if (!pa_speex_ec_preprocessor_init(ec, out_ss, *nframes, ma))
goto fail;
pa_modargs_free(ma);
}
pa_bool_t pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec,
- pa_sample_spec *source_ss, pa_channel_map *source_map,
- pa_sample_spec *sink_ss, pa_channel_map *sink_map,
+ pa_sample_spec *rec_ss, pa_channel_map *rec_map,
+ pa_sample_spec *play_ss, pa_channel_map *play_map,
+ pa_sample_spec *out_ss, pa_channel_map *out_map,
uint32_t *nframes, const char *args)
{
webrtc::AudioProcessing *apm = NULL;
apm = webrtc::AudioProcessing::Create(0);
- source_ss->format = PA_SAMPLE_S16NE;
- *sink_ss = *source_ss;
+ out_ss->format = PA_SAMPLE_S16NE;
+ *play_ss = *out_ss;
/* FIXME: the implementation actually allows a different number of
* source/sink channels. Do we want to support that? */
- *sink_map = *source_map;
+ *play_map = *out_map;
+ *rec_ss = *out_ss;
+ *rec_map = *out_map;
- apm->set_sample_rate_hz(source_ss->rate);
+ apm->set_sample_rate_hz(out_ss->rate);
- apm->set_num_channels(source_ss->channels, source_ss->channels);
- apm->set_num_reverse_channels(sink_ss->channels);
+ apm->set_num_channels(out_ss->channels, out_ss->channels);
+ apm->set_num_reverse_channels(play_ss->channels);
if (hpf)
apm->high_pass_filter()->Enable(true);
if (!mobile) {
if (ec->params.drift_compensation) {
- apm->echo_cancellation()->set_device_sample_rate_hz(source_ss->rate);
+ apm->echo_cancellation()->set_device_sample_rate_hz(out_ss->rate);
apm->echo_cancellation()->enable_drift_compensation(true);
} else {
apm->echo_cancellation()->enable_drift_compensation(false);
apm->voice_detection()->Enable(true);
ec->params.priv.webrtc.apm = apm;
- ec->params.priv.webrtc.sample_spec = *source_ss;
- ec->params.priv.webrtc.blocksize = (uint64_t)pa_bytes_per_second(source_ss) * BLOCK_SIZE_US / PA_USEC_PER_SEC;
- *nframes = ec->params.priv.webrtc.blocksize / pa_frame_size(source_ss);
+ ec->params.priv.webrtc.sample_spec = *out_ss;
+ ec->params.priv.webrtc.blocksize = (uint64_t)pa_bytes_per_second(out_ss) * BLOCK_SIZE_US / PA_USEC_PER_SEC;
+ *nframes = ec->params.priv.webrtc.blocksize / pa_frame_size(out_ss);
pa_modargs_free(ma);
return TRUE;