pa_bool_t save_aec;
pa_echo_canceller *ec;
- uint32_t blocksize;
+ uint32_t source_output_blocksize;
+ uint32_t source_blocksize;
+ uint32_t sink_blocksize;
pa_bool_t need_realign;
/* 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->blocksize, &u->source_output->source->sample_spec);
+ pa_bytes_to_usec(u->source_output_blocksize, &u->source_output->source->sample_spec);
return 0;
* those remainder samples.
*/
drift = ((float)(plen - u->sink_rem) - (rlen - u->source_rem)) / ((float)(rlen - u->source_rem));
- u->sink_rem = plen % u->blocksize;
- u->source_rem = rlen % u->blocksize;
+ u->sink_rem = plen % u->sink_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);
}
/* Send in the playback samples first */
- while (plen >= u->blocksize) {
- pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
+ while (plen >= u->sink_blocksize) {
+ pa_memblockq_peek_fixed_size(u->sink_memblockq, u->sink_blocksize, &pchunk);
pdata = pa_memblock_acquire(pchunk.memblock);
pdata += pchunk.index;
if (u->save_aec) {
if (u->drift_file)
- fprintf(u->drift_file, "p %d\n", u->blocksize);
+ fprintf(u->drift_file, "p %d\n", u->sink_blocksize);
if (u->played_file)
- unused = fwrite(pdata, 1, u->blocksize, u->played_file);
+ unused = fwrite(pdata, 1, u->sink_blocksize, u->played_file);
}
pa_memblock_release(pchunk.memblock);
- pa_memblockq_drop(u->sink_memblockq, u->blocksize);
+ pa_memblockq_drop(u->sink_memblockq, u->sink_blocksize);
pa_memblock_unref(pchunk.memblock);
- plen -= u->blocksize;
+ plen -= u->sink_blocksize;
}
/* And now the capture samples */
- while (rlen >= u->blocksize) {
- pa_memblockq_peek_fixed_size(u->source_memblockq, u->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->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->blocksize);
+ fprintf(u->drift_file, "c %d\n", u->source_output_blocksize);
if (u->captured_file)
- unused = fwrite(rdata, 1, u->blocksize, u->captured_file);
+ unused = fwrite(rdata, 1, u->source_output_blocksize, u->captured_file);
if (u->canceled_file)
- unused = fwrite(cdata, 1, u->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->blocksize);
- rlen -= u->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->blocksize) {
- /* take fixed block from recorded samples */
- pa_memblockq_peek_fixed_size(u->source_memblockq, u->blocksize, &rchunk);
-
- if (plen >= u->blocksize) {
- /* take fixed block from played samples */
- pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
-
- rdata = pa_memblock_acquire(rchunk.memblock);
- rdata += rchunk.index;
- pdata = pa_memblock_acquire(pchunk.memblock);
- pdata += pchunk.index;
-
- cchunk.index = 0;
- cchunk.length = u->blocksize;
- cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
- cdata = pa_memblock_acquire(cchunk.memblock);
-
- if (u->save_aec) {
- if (u->captured_file)
- unused = fwrite(rdata, 1, u->blocksize, u->captured_file);
- if (u->played_file)
- unused = fwrite(pdata, 1, u->blocksize, u->played_file);
- }
+ while (rlen >= u->source_output_blocksize) {
- /* perform echo cancellation */
- u->ec->run(u->ec, rdata, pdata, cdata);
+ /* take fixed blocks from recorded and played samples */
+ 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);
- if (u->save_aec) {
- if (u->canceled_file)
- unused = fwrite(cdata, 1, u->blocksize, u->canceled_file);
- }
+ /* we ran out of played data and pchunk has been filled with silence bytes */
+ if (plen < u->sink_blocksize)
+ pa_memblockq_seek(u->sink_memblockq, u->sink_blocksize - plen, PA_SEEK_RELATIVE, true);
- pa_memblock_release(cchunk.memblock);
- pa_memblock_release(pchunk.memblock);
- pa_memblock_release(rchunk.memblock);
+ rdata = pa_memblock_acquire(rchunk.memblock);
+ rdata += rchunk.index;
+ pdata = pa_memblock_acquire(pchunk.memblock);
+ pdata += pchunk.index;
- /* drop consumed sink samples */
- pa_memblockq_drop(u->sink_memblockq, u->blocksize);
- pa_memblock_unref(pchunk.memblock);
+ cchunk.index = 0;
+ cchunk.length = u->source_blocksize;
+ cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
+ cdata = pa_memblock_acquire(cchunk.memblock);
- pa_memblock_unref(rchunk.memblock);
- /* the filtered samples now become the samples from our
- * source */
- rchunk = cchunk;
+ if (u->save_aec) {
+ if (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);
+ }
+
+ /* perform echo cancellation */
+ u->ec->run(u->ec, rdata, pdata, cdata);
- plen -= u->blocksize;
+ if (u->save_aec) {
+ if (u->canceled_file)
+ unused = fwrite(cdata, 1, u->source_blocksize, u->canceled_file);
}
- /* forward the (echo-canceled) data to the virtual source */
- pa_source_post(u->source, &rchunk);
+ pa_memblock_release(cchunk.memblock);
+ pa_memblock_release(pchunk.memblock);
+ pa_memblock_release(rchunk.memblock);
+
+ /* drop consumed source samples */
+ pa_memblockq_drop(u->source_memblockq, u->source_output_blocksize);
pa_memblock_unref(rchunk.memblock);
+ rlen -= u->source_output_blocksize;
- pa_memblockq_drop(u->source_memblockq, u->blocksize);
- rlen -= u->blocksize;
+ /* drop consumed sink samples */
+ pa_memblockq_drop(u->sink_memblockq, u->sink_blocksize);
+ pa_memblock_unref(pchunk.memblock);
+
+ if (plen >= u->sink_blocksize)
+ plen -= u->sink_blocksize;
+ else
+ plen = 0;
+
+ /* forward the (echo-canceled) data to the virtual source */
+ pa_source_post(u->source, &cchunk);
+ pa_memblock_unref(cchunk.memblock);
}
}
plen = pa_memblockq_get_length(u->sink_memblockq);
/* Let's not do anything else till we have enough data to process */
- if (rlen < u->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->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->blocksize) {
- u->sink_skip += u->blocksize - (u->source_skip % u->blocksize);
- u->source_skip -= (u->source_skip % u->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);
}
}
}
}
+uint32_t pa_echo_canceller_blocksize_power2(unsigned rate, unsigned ms) {
+ unsigned nframes = (rate * ms) / 1000;
+ uint32_t y = 1 << ((8 * sizeof(uint32_t)) - 2);
+
+ assert(rate >= 4000);
+ assert(ms >= 1);
+
+ /* nframes should be a power of 2, round down to nearest power of two */
+ while (y > nframes)
+ y >>= 1;
+
+ assert(y >= 1);
+ return y;
+}
+
static pa_echo_canceller_method_t get_ec_method_from_string(const char *method) {
if (pa_streq(method, "null"))
return PA_ECHO_CANCELLER_NULL;
ec_string = pa_modargs_get_value(ma, "aec_method", DEFAULT_ECHO_CANCELLER);
if ((ec_method = get_ec_method_from_string(ec_string)) < 0) {
- pa_log("Invalid echo canceller implementation");
+ pa_log("Invalid echo canceller implementation '%s'", ec_string);
goto fail;
}
/* 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;
pa_sink_new_data sink_data;
pa_memchunk silence;
uint32_t temp;
+ uint32_t nframes = 0;
pa_assert(m);
u->asyncmsgq = pa_asyncmsgq_new(0);
u->need_realign = TRUE;
- if (u->ec->init) {
- if (!u->ec->init(u->core, u->ec, &source_ss, &source_map, &sink_ss, &sink_map, &u->blocksize, pa_modargs_get_value(ma, "aec_args", NULL))) {
- pa_log("Failed to init AEC engine");
- goto fail;
- }
+ 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_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);
+
if (u->ec->params.drift_compensation)
pa_assert(u->ec->set_drift);
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, 1, 1, 0, &silence);
+ &sink_ss, 0, 1, 0, &silence);
pa_memblock_unref(silence.memblock);
*/
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;
int ret = 0, i;
char c;
float drift;
+ uint32_t nframes;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
pa_memzero(&u, sizeof(u));
u.captured_file = fopen(argv[2], "rb");
if (u.captured_file == NULL) {
- perror ("fopen failed");
+ perror ("Could not open capture file");
goto fail;
}
u.played_file = fopen(argv[1], "rb");
if (u.played_file == NULL) {
- perror ("fopen failed");
+ perror ("Could not open play file");
goto fail;
}
u.canceled_file = fopen(argv[3], "wb");
if (u.canceled_file == NULL) {
- perror ("fopen failed");
+ perror ("Could not open canceled file");
goto fail;
}
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, &u.blocksize,
- (argc > 5) ? argv[5] : NULL )) {
+ 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);
if (u.ec->params.drift_compensation) {
- if (argc < 7) {
+ if (argc < 6) {
pa_log("Drift compensation enabled but drift file not specified");
goto fail;
}
- u.drift_file = fopen(argv[6], "rt");
+ u.drift_file = fopen(argv[5], "rt");
if (u.drift_file == NULL) {
- perror ("fopen failed");
+ perror ("Could not open drift file");
goto fail;
}
}
- rdata = pa_xmalloc(u.blocksize);
- pdata = pa_xmalloc(u.blocksize);
- cdata = pa_xmalloc(u.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.blocksize, 1, u.captured_file) > 0) {
- if (fread(pdata, u.blocksize, 1, u.played_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;
}
u.ec->run(u.ec, rdata, pdata, cdata);
- unused = fwrite(cdata, u.blocksize, 1, u.canceled_file);
+ unused = fwrite(cdata, u.source_blocksize, 1, u.canceled_file);
}
} else {
while (fscanf(u.drift_file, "%c", &c) > 0) {
return ret;
usage:
- pa_log("Usage: %s play_file rec_file out_file [module args] [aec_args] [drift_file]", argv[0]);
+ pa_log("Usage: %s play_file rec_file out_file [module args] [drift_file]", argv[0]);
fail:
ret = -1;