+/* 1. Calculate drift at this point, pass to canceller
+ * 2. Push out playback samples in blocksize chunks
+ * 3. Push out capture samples in blocksize chunks
+ * 4. ???
+ * 5. Profit
+ *
+ * Called from source I/O thread context.
+ */
+static void do_push_drift_comp(struct userdata *u) {
+ size_t rlen, plen;
+ pa_memchunk rchunk, pchunk, cchunk;
+ uint8_t *rdata, *pdata, *cdata;
+ float drift;
+ int unused PA_GCC_UNUSED;
+
+ rlen = pa_memblockq_get_length(u->source_memblockq);
+ plen = pa_memblockq_get_length(u->sink_memblockq);
+
+ /* Estimate snapshot drift as follows:
+ * pd: amount of data consumed since last time
+ * rd: amount of data consumed since last time
+ *
+ * drift = (pd - rd) / rd;
+ *
+ * We calculate pd and rd as the memblockq length less the number of
+ * samples left from the last iteration (to avoid double counting
+ * those remainder samples.
+ */
+ 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_output_blocksize;
+
+ /* Now let the canceller work its drift compensation magic */
+ u->ec->set_drift(u->ec, drift);
+
+ if (u->save_aec) {
+ if (u->drift_file)
+ fprintf(u->drift_file, "d %a\n", drift);
+ }
+
+ /* Send in the playback samples first */
+ 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;
+
+ u->ec->play(u->ec, pdata);
+
+ if (u->save_aec) {
+ if (u->drift_file)
+ fprintf(u->drift_file, "p %d\n", u->sink_blocksize);
+ if (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->sink_blocksize);
+ pa_memblock_unref(pchunk.memblock);
+
+ plen -= u->sink_blocksize;
+ }
+
+ /* And now the capture samples */
+ 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_output_blocksize;
+ cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
+ cdata = pa_memblock_acquire(cchunk.memblock);
+
+ u->ec->record(u->ec, rdata, cdata);
+
+ if (u->save_aec) {
+ if (u->drift_file)
+ fprintf(u->drift_file, "c %d\n", u->source_output_blocksize);
+ if (u->captured_file)
+ unused = fwrite(rdata, 1, u->source_output_blocksize, u->captured_file);
+ if (u->canceled_file)
+ unused = fwrite(cdata, 1, u->source_output_blocksize, u->canceled_file);
+ }
+
+ pa_memblock_release(cchunk.memblock);
+ pa_memblock_release(rchunk.memblock);
+
+ pa_memblock_unref(rchunk.memblock);
+
+ pa_source_post(u->source, &cchunk);
+ pa_memblock_unref(cchunk.memblock);
+
+ pa_memblockq_drop(u->source_memblockq, u->source_output_blocksize);
+ rlen -= u->source_output_blocksize;
+ }
+}
+
+/* This one's simpler than the drift compensation case -- we just iterate over
+ * the capture buffer, and pass the canceller blocksize bytes of playback and
+ * capture data.
+ *
+ * Called from source I/O thread context. */
+static void do_push(struct userdata *u) {
+ size_t rlen, plen;
+ pa_memchunk rchunk, pchunk, cchunk;
+ uint8_t *rdata, *pdata, *cdata;
+ int unused PA_GCC_UNUSED;
+
+ rlen = pa_memblockq_get_length(u->source_memblockq);
+ plen = pa_memblockq_get_length(u->sink_memblockq);
+
+ while (rlen >= u->source_output_blocksize) {
+
+ /* 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);
+
+ /* 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);
+
+ rdata = pa_memblock_acquire(rchunk.memblock);
+ rdata += rchunk.index;
+ pdata = pa_memblock_acquire(pchunk.memblock);
+ pdata += pchunk.index;
+
+ 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);
+
+ 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);
+
+ if (u->save_aec) {
+ if (u->canceled_file)
+ unused = fwrite(cdata, 1, u->source_blocksize, u->canceled_file);
+ }
+
+ 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;
+
+ /* 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);
+ }
+}
+
+/* Called from source I/O thread context. */