diff --git a/daemon/.gitignore b/daemon/.gitignore index 4f4037c3d..fb20bc43d 100644 --- a/daemon/.gitignore +++ b/daemon/.gitignore @@ -22,3 +22,4 @@ dtmf_rx_fillin.h spandsp_logging.h mvr2s_x64_avx512.S mvr2s_x64_avx2.S +mix_buffer.c diff --git a/daemon/Makefile b/daemon/Makefile index 7e570ce64..ffaa13a6d 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -83,7 +83,7 @@ SRCS= main.c kernel.c poller.c aux.c control_tcp.c call.c control_udp.c redis.c media_socket.c homer.c recording.c statistics.c cdr.c ssrc.c iptables.c tcp_listener.c \ codec.c load.c dtmf.c timerthread.c media_player.c jitter_buffer.c t38.c websocket.c \ mqtt.c janus.strhash.c -LIBSRCS= loglib.c auxlib.c rtplib.c str.c socket.c streambuf.c ssllib.c dtmflib.c +LIBSRCS= loglib.c auxlib.c rtplib.c str.c socket.c streambuf.c ssllib.c dtmflib.c mix_buffer.c ifeq ($(with_transcoding),yes) LIBSRCS+= codeclib.strhash.c resample.c LIBASM= mvr2s_x64_avx2.S mvr2s_x64_avx512.S diff --git a/lib/mix_buffer.c b/lib/mix_buffer.c new file mode 100644 index 000000000..6845624d8 --- /dev/null +++ b/lib/mix_buffer.c @@ -0,0 +1,277 @@ +#include "mix_buffer.h" +#include +#include +#include +#include +#include "ssrc.h" + + +typedef void (*mix_in_fn_t)(void *restrict dst, const void *restrict src, unsigned int num); + + +struct mix_buffer_impl { + unsigned int sample_size; + mix_in_fn_t mix_in; +}; + +struct mix_buffer_ssrc_source { + struct ssrc_entry h; // must be first + unsigned int write_pos; + unsigned int loops; +}; + + +static void s16_mix_in_c(void *restrict dst, const void *restrict src, unsigned int samples) { + int16_t *d = dst; + const int16_t *s = src; + + for (unsigned int i = 0; i < samples; i++) { + int16_t orig = d[i]; + d[i] += s[i]; + // saturate/clamp + if (d[i] < orig && s[i] > 0) + d[i] = 32767; + else if (d[i] > orig && s[i] < 0) + d[i] = -32768; + } +} + + +const struct mix_buffer_impl impl_s16_c = { + .sample_size = sizeof(int16_t), + .mix_in = s16_mix_in_c, +}; +// TODO: SIMD-accelerated implementations + + +// must be locked already +static void fill_up_to(struct mix_buffer *mb, unsigned int up_to) { + if (mb->fill >= up_to) + return; + unsigned int needed = up_to - mb->fill; + assert(up_to <= mb->size); + + // tail end + unsigned int tail_room = mb->size - mb->head_write_pos; + tail_room = MIN(tail_room, needed); + memset(mb->buf.c + mb->head_write_pos * mb->sample_size_channels, 0, tail_room * mb->sample_size_channels); + + needed -= tail_room; + mb->head_write_pos += tail_room; + mb->fill += tail_room; + + if (needed) { + // ran against the end of the buffer. fill up from beginning + memset(mb->buf.c, 0, needed * mb->sample_size_channels); + mb->head_write_pos = needed; + mb->fill += needed; + mb->loops++; + } +} + + +void *mix_buffer_read_fast(struct mix_buffer *mb, unsigned int samples, unsigned int *size) { + LOCK(&mb->lock); + + if (samples > mb->size) { + *size = 0; // error + return NULL; + } + + fill_up_to(mb, samples); + + *size = samples * mb->sample_size_channels; + + // shortcut extraction possible? + int end_read_pos = mb->read_pos + samples; + if (end_read_pos > mb->size) + return NULL; // nope, must use temporary buffer + + void *ret = mb->buf.c + mb->read_pos * mb->sample_size_channels; + mb->read_pos = end_read_pos == mb->size ? 0 : end_read_pos; + mb->fill -= samples; + return ret; +} + + +// must be called after mix_buffer_read_fast returned NULL, with a buffer the size of *size bytes +void mix_buffer_read_slow(struct mix_buffer *mb, void *outbuf, unsigned int samples) { + LOCK(&mb->lock); + + unsigned int tail_part = mb->size - mb->read_pos; + memcpy(outbuf, mb->buf.c + mb->read_pos * mb->sample_size_channels, tail_part * mb->sample_size_channels); + mb->fill -= samples; + samples -= tail_part; + memcpy(outbuf + tail_part * mb->sample_size_channels, mb->buf.c, samples * mb->sample_size_channels); + + mb->read_pos = samples; +} + + +static void mix_ssrc_put(struct mix_buffer_ssrc_source **s) { + if (*s) + obj_put(&(*s)->h); +} + + +// write at the write-head, direct copy without mixing +// must be locked already +static bool mix_buffer_write_fast(struct mix_buffer *mb, struct mix_buffer_ssrc_source *src, + const void *buf, unsigned int samples) +{ + // check for buffer overflow + if (mb->fill + samples > mb->size) + return false; + + // will there be a buffer wrap-around? + if (mb->head_write_pos + samples >= mb->size) { + // copy in to end of buffer + unsigned int tail_part = mb->size - mb->head_write_pos; + memcpy(mb->buf.c + mb->head_write_pos * mb->sample_size_channels, buf, + tail_part * mb->sample_size_channels); + mb->fill += tail_part; + samples -= tail_part; + buf = ((const char *) buf) + tail_part * mb->sample_size_channels; + mb->head_write_pos = 0; + // src->write_pos is updated below + mb->loops++; + src->loops = mb->loops; + } + + // copy in remainder, if any + memcpy(mb->buf.c + mb->head_write_pos * mb->sample_size_channels, buf, + samples * mb->sample_size_channels); + mb->head_write_pos += samples; + src->write_pos = mb->head_write_pos; + mb->fill += samples; + + return true; +} + + +// write before the write-head with mixing-in +// must be locked already +static bool mix_buffer_write_slow(struct mix_buffer *mb, struct mix_buffer_ssrc_source *src, + const void *buf, unsigned int samples) +{ + // mix-in up to the current write-head, or end of buffer in case of wrap-around + + if (mb->head_write_pos < src->write_pos) { + // wrap-arund: mix-in to end of buffer + unsigned int tail_part = mb->size - src->write_pos; + if (tail_part > samples) + tail_part = samples; + mb->impl->mix_in(mb->buf.c + src->write_pos * mb->sample_size_channels, buf, + tail_part * mb->channels); + samples -= tail_part; + buf = ((const char *) buf) + tail_part * mb->sample_size_channels; + src->write_pos += tail_part; + if (src->write_pos == mb->size) { + src->write_pos = 0; + src->loops++; + } + + if (samples == 0) + return true; + } + + // mix-in to current write-head + unsigned int mix_part = mb->head_write_pos - src->write_pos; + if (mix_part > samples) + mix_part = samples; + mb->impl->mix_in(mb->buf.c + src->write_pos * mb->sample_size_channels, buf, mix_part * mb->channels); + samples -= mix_part; + src->write_pos += mix_part; + buf = ((const char *) buf) + mix_part * mb->sample_size_channels; + + // anything that's left, just copy-in + return mix_buffer_write_fast(mb, src, buf, samples); +} + + +static void mix_buffer_src_init_pos(struct mix_buffer *mb, struct mix_buffer_ssrc_source *src) { + src->write_pos = mb->read_pos; + src->loops = mb->loops; + if (mb->head_write_pos < src->write_pos) + src->loops--; +} + + +bool mix_buffer_write(struct mix_buffer *mb, uint32_t ssrc, const void *buf, unsigned int samples) { + LOCK(&mb->lock); + + AUTO_CLEANUP(struct mix_buffer_ssrc_source *src, mix_ssrc_put) = get_ssrc(ssrc, mb->ssrc_hash); + if (!src) + return false; + + // loop twice at the most to re-run logic after a reset + while (true) { + // shortcut if we're at the write head + if (src->write_pos == mb->head_write_pos && src->loops == mb->loops) + return mix_buffer_write_fast(mb, src, buf, samples); + + // not at the write head... did we fall behind what has been read already? + if (mb->head_write_pos >= mb->read_pos) { + // |--------------|###################|------------| + // R W + // ^- slow mix-in + if (src->write_pos >= mb->read_pos && src->write_pos < mb->head_write_pos + && src->loops == mb->loops) + return mix_buffer_write_slow(mb, src, buf, samples); + } + else { + // |#########|-----------------------------|#######| + // W R + // ^--- slow mix-in ------^ + if ((src->write_pos < mb->head_write_pos && src->loops == mb->loops) + || (src->write_pos >= mb->read_pos && src->loops + 1 == mb->loops)) + return mix_buffer_write_slow(mb, src, buf, samples); + } + + // we fell behind. reset write position to current read pos and try again + mix_buffer_src_init_pos(mb, src); + // TODO: add offset to correct for jitter/delay + } +} + + +static struct ssrc_entry *mix_buffer_ssrc_new(void *p) { + struct mix_buffer *mb = p; + struct mix_buffer_ssrc_source *src = obj_alloc0("mix_buffer_ssrc", sizeof(*src), NULL); + mix_buffer_src_init_pos(mb, src); + return &src->h; +} + + +// struct must be zeroed already +bool mix_buffer_init(struct mix_buffer *mb, enum AVSampleFormat fmt, unsigned int clockrate, + unsigned int channels, unsigned int size_ms) +{ + switch (fmt) { + case AV_SAMPLE_FMT_S16: + mb->impl = &impl_s16_c; + break; + default: + return false; + } + + unsigned int size = clockrate * size_ms / 1000; // in samples + + mutex_init(&mb->lock); + mb->sample_size_channels = channels * mb->impl->sample_size; + mb->buf.v = g_malloc(mb->sample_size_channels * size); + mb->size = size; + mb->clockrate = clockrate; + mb->channels = channels; + + mb->ssrc_hash = create_ssrc_hash_full(mix_buffer_ssrc_new, mb); + + return true; +} + + +void mix_buffer_destroy(struct mix_buffer *mb) { + g_free(mb->buf.v); + free_ssrc_hash(&mb->ssrc_hash); + mutex_destroy(&mb->lock); +} diff --git a/lib/mix_buffer.h b/lib/mix_buffer.h new file mode 100644 index 000000000..c576b6f69 --- /dev/null +++ b/lib/mix_buffer.h @@ -0,0 +1,68 @@ +#ifndef _MIX_BUFFER_H_ +#define _MIX_BUFFER_H_ + +#include +#include +#include "aux.h" + + +enum AVSampleFormat; +struct mix_buffer_impl; +struct ssrc_hash; + + +/* + * A simple circular audio buffer that allows mixing multiple sources of + * audio. Sources are tracked by SSRC and all sources are expected to + * provide audio in the same format (same clock rate, channels, sample + * format). + + * Only one consumer per buffer is supported, which is expected to retrieve + * buffered audio at regular intervals (ptime) and so continuously empty + * the buffer. + + * The first audio source to write into the buffer at the leading edge of + * the circular buffer has its audio simply copied into the buffer, with + * the leading edge advanced, while other later sources writing into the + * buffer mixed into the existing buffered audio at their respective write + * positions. + */ +struct mix_buffer { + mutex_t lock; + + union { + // generic pointers + void *v; + char *c; + + // implementation-specific pointers + int16_t *s16; + } buf; + + unsigned int channels; + unsigned int clockrate; + + // all sizes and positions in samples + unsigned int size; // total size + unsigned int read_pos; // current read (output) position + unsigned int head_write_pos; // furthest ahead write (input) position + unsigned int fill; // difference between read and write position + unsigned int loops; // how many times the write pos has circled around + + // implementation details + const struct mix_buffer_impl *impl; + unsigned int sample_size_channels; // = sample_size * channels + struct ssrc_hash *ssrc_hash; +}; + + +bool mix_buffer_init(struct mix_buffer *, enum AVSampleFormat, unsigned int clockrate, + unsigned int channels, unsigned int size_ms); +void mix_buffer_destroy(struct mix_buffer *); + +void *mix_buffer_read_fast(struct mix_buffer *, unsigned int samples, unsigned int *size); +void mix_buffer_read_slow(struct mix_buffer *, void *outbuf, unsigned int samples); +bool mix_buffer_write(struct mix_buffer *, uint32_t ssrc, const void *buf, unsigned int samples); + + +#endif diff --git a/recording-daemon/mix.c b/recording-daemon/mix.c index a101ab331..c7742bc48 100644 --- a/recording-daemon/mix.c +++ b/recording-daemon/mix.c @@ -21,6 +21,7 @@ struct mix_s { format_t in_format, out_format; + // TODO: use mix_buffer AVFilterGraph *graph; AVFilterContext *src_ctxs[MIX_MAX_INPUTS]; uint64_t pts_offs[MIX_MAX_INPUTS]; // initialized at first input seen diff --git a/t/.gitignore b/t/.gitignore index f7c40101d..d0ff95e3d 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -76,3 +76,5 @@ ssllib.c time-fudge-preload.so mvr2s_x64_avx2.S mvr2s_x64_avx512.S +test-mix-buffer +mix_buffer.c diff --git a/t/Makefile b/t/Makefile index 23ddf41e8..1d8bfa56f 100644 --- a/t/Makefile +++ b/t/Makefile @@ -62,8 +62,8 @@ LDLIBS+= -lhiredis LDLIBS+= $(shell mysql_config --libs) endif -SRCS= test-bitstr.c aes-crypt.c aead-aes-crypt.c test-const_str_hash.strhash.c -LIBSRCS= loglib.c auxlib.c str.c rtplib.c ssllib.c +SRCS= test-bitstr.c aes-crypt.c aead-aes-crypt.c test-const_str_hash.strhash.c test-mix-buffer.c +LIBSRCS= loglib.c auxlib.c str.c rtplib.c ssllib.c mix_buffer.c DAEMONSRCS= crypto.c ssrc.c aux.c rtp.c HASHSRCS= @@ -94,7 +94,7 @@ include ../lib/common.Makefile daemon-tests-intfs daemon-tests-stats daemon-tests-delay-buffer daemon-tests-delay-timing \ daemon-tests-evs daemon-tests-player-cache daemon-tests-redis -TESTS= test-bitstr aes-crypt aead-aes-crypt test-const_str_hash.strhash +TESTS= test-bitstr aes-crypt aead-aes-crypt test-const_str_hash.strhash test-mix-buffer ifeq ($(with_transcoding),yes) TESTS+= test-transcode test-dtmf-detect test-payload-tracker test-resample test-stats ifeq ($(with_amr_tests),yes) @@ -239,6 +239,8 @@ daemon-tests-redis: daemon-test-deps test-bitstr: test-bitstr.o +test-mix-buffer: test-mix-buffer.o $(COMMONOBJS) mix_buffer.o ssrc.o rtp.o crypto.o + spandsp_send_fax_pcm: spandsp_send_fax_pcm.o spandsp_recv_fax_pcm: spandsp_recv_fax_pcm.o diff --git a/t/test-mix-buffer.c b/t/test-mix-buffer.c new file mode 100644 index 000000000..fd17d8d06 --- /dev/null +++ b/t/test-mix-buffer.c @@ -0,0 +1,421 @@ +#include "mix_buffer.h" +#include +#include +#include +#include "statistics.h" + + +struct rtpengine_config rtpe_config; +struct global_stats_gauge rtpe_stats_gauge; +struct global_gauge_min_max rtpe_gauge_min_max; +struct global_stats_counter rtpe_stats; +struct global_stats_counter rtpe_stats_rate; +struct global_stats_sampled rtpe_stats_sampled; +struct global_sampled_min_max rtpe_sampled_min_max; +struct global_sampled_min_max rtpe_sampled_graphite_min_max; +struct global_sampled_min_max rtpe_sampled_graphite_min_max_sampled; + +int get_local_log_level(unsigned int u) { + return -1; +} + + +int main(void) { + struct mix_buffer mb; + + memset(&mb, 0, sizeof(mb)); + bool ret = mix_buffer_init(&mb, AV_SAMPLE_FMT_S16, 500, 1, 100); + assert(ret == true); + + // pre-fill with zeroes + + unsigned int size = 0; + void *p = mix_buffer_read_fast(&mb, 20, &size); + assert(p != NULL); + assert(size == 40); + assert(memcmp(p, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + + p = mix_buffer_read_fast(&mb, 25, &size); + assert(p != NULL); + assert(size == 50); + assert(memcmp(p, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + + // slow-path read around boundary + + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p == NULL); + assert(size == 40); + char buf[size]; + mix_buffer_read_slow(&mb, buf, 20); + assert(memcmp(buf, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + + // write-in and read-out + + ret = mix_buffer_write(&mb, 0x1234, "1122334455", 5); + assert(ret == true); + + p = mix_buffer_read_fast(&mb, 5, &size); + assert(p != NULL); + assert(size == 10); + assert(memcmp(p, "1122334455", size) == 0); + + // subsequent read with pre-fill + + p = mix_buffer_read_fast(&mb, 25, &size); + assert(p != NULL); + assert(size == 50); + assert(memcmp(p, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + + // write-in around boundary + + ret = mix_buffer_write(&mb, 0x1234, "qqwweerrttyyuuiiooppaassddffgg", 15); + assert(ret == true); + + // read-out around boundary past end with pre-fill + + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p == NULL); + assert(size == 40); + mix_buffer_read_slow(&mb, buf, 20); + assert(memcmp(buf, "qqwweerrttyyuuiiooppaassddffgg\0\0\0\0\0\0\0\0\0\0", size) == 0); + + // write-in and partial read-out + + ret = mix_buffer_write(&mb, 0x1234, "qqwweerrttyyuuiiooppaassddffgg", 15); + assert(ret == true); + + p = mix_buffer_read_fast(&mb, 5, &size); + assert(p != NULL); + assert(size == 10); + assert(memcmp(p, "qqwweerrtt", size) == 0); + // read-pos = 20, write-pos = 30 + + // another write-in and partial read-out + + ret = mix_buffer_write(&mb, 0x1234, "mmnnbbvvccxxzzllkkjjhhggffddss", 15); + assert(ret == true); + + p = mix_buffer_read_fast(&mb, 5, &size); + assert(p != NULL); + assert(size == 10); + assert(memcmp(p, "yyuuiioopp", size) == 0); + // read-pos = 25, write-pos = 45 + + // write-in around boundary + + ret = mix_buffer_write(&mb, 0x1234, "00112233445566778899", 10); + assert(ret == true); + // read-pos = 25, write-pos = 5 + + // partial read-out + + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p != NULL); + assert(size == 40); + assert(memcmp(p, "aassddffggmmnnbbvvccxxzzllkkjjhhggffddss", size) == 0); + // read-pos = 45, write-pos = 5 + + // read-out across boundary plus pre-fill + p = mix_buffer_read_fast(&mb, 15, &size); + assert(p == NULL); + assert(size == 30); + mix_buffer_read_slow(&mb, buf, 15); + assert(memcmp(buf, "00112233445566778899\0\0\0\0\0\0\0\0\0\0", size) == 0); + // read-pos = 10, write-pos = 10 + + // write and read to end of buffer + + ret = mix_buffer_write(&mb, 0x1234, "llkkccgg449900dd22bbqqddffpp[[rr..//5500kkxxmmnnffggoorrpp00ss9933[[ss==]]xx..ss", 40); + assert(ret == true); + + p = mix_buffer_read_fast(&mb, 40, &size); + assert(p != NULL); + assert(size == 80); + assert(memcmp(p, "llkkccgg449900dd22bbqqddffpp[[rr..//5500kkxxmmnnffggoorrpp00ss9933[[ss==]]xx..ss", size) == 0); + // read-pos = 0, write-pos = 0 + + // mix-in + + // write from source 1 + ret = mix_buffer_write(&mb, 0x1234, "\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2", 10); + assert(ret == true); + // write from source 2 + ret = mix_buffer_write(&mb, 0x6543, "\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5", 10); + assert(ret == true); + + // read mixed output + p = mix_buffer_read_fast(&mb, 10, &size); + assert(p != NULL); + assert(size == 20); + assert(memcmp(p, "\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7", size) == 0); + // read-pos = 10, write-pos = 10 + + // write with only partial mix-in + ret = mix_buffer_write(&mb, 0x1234, "!!##$$%%&&''(())**++aabbccddee", 15); + assert(ret == true); + ret = mix_buffer_write(&mb, 0x6543, "AABBCCDDEEFFGGHHIIJJ", 10); + assert(ret == true); + + // read partially mixed output + p = mix_buffer_read_fast(&mb, 15, &size); + assert(p != NULL); + assert(size == 30); + assert(memcmp(p, "bbeeggiikkmmooqqssuuaabbccddee", size) == 0); + // read-pos = 25, write-pos = 25 + + // partial write followed by larger mix-in + ret = mix_buffer_write(&mb, 0x6543, "AABBCCDDEEFFGGHHIIJJ", 10); + assert(ret == true); + ret = mix_buffer_write(&mb, 0x1234, "!!##$$%%&&''(())**++aabbccddee", 15); + assert(ret == true); + + // read partially mixed output plus fill-in + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p != NULL); + assert(size == 40); + assert(memcmp(p, "bbeeggiikkmmooqqssuuaabbccddee\0\0\0\0\0\0\0\0\0\0", size) == 0); + // read-pos = 45, write-pos = 45 + + // mix-in across boundary + ret = mix_buffer_write(&mb, 0x6543, "//..--,,++**))((''&&%%$$##\"\"!!", 15); + assert(ret == true); + ret = mix_buffer_write(&mb, 0x1234, "NNLLKKJJIIHHGGFFEEDDCCBBAA@@??", 15); + assert(ret == true); + // read-pos = 45, write-pos = 10 + + // continue mix-in before reading + ret = mix_buffer_write(&mb, 0x1234, "AABBCCDDEEFFGGHHIIJJKKLLMMNNOO", 15); + assert(ret == true); + ret = mix_buffer_write(&mb, 0x6543, "00112233445566778899::;;<<==>>", 15); + assert(ret == true); + // read-pos = 45, write-pos = 25 + + // read some mixed data, slow path across boundary + p = mix_buffer_read_fast(&mb, 10, &size); + assert(p == NULL); + assert(size == 20); + mix_buffer_read_slow(&mb, buf, 10); + assert(memcmp(buf, "}}zzxxvvttrrppnnlljj", size) == 0); + // read-pos = 5, write-pos = 25 + + // read some more, fast path + p = mix_buffer_read_fast(&mb, 10, &size); + assert(p != NULL); + assert(size == 20); + assert(memcmp(p, "hhffddbb``qqssuuwwyy{{", size) == 0); + // read-pos = 15, write-pos = 25 + + // read remainder + p = mix_buffer_read_fast(&mb, 10, &size); + assert(p != NULL); + assert(size == 20); + assert(memcmp(p, "{{}}\177\177\377\177\377\177\377\177\377\177\377\177\377\177\377\177", size) == 0); + // read-pos = 25, write-pos = 25 + + // write across boundary + ret = mix_buffer_write(&mb, 0x1234, "000000000000000000000000000000000000000000000000000000000000", 30); + assert(ret == true); + // read-pos = 25, write-pos = 5 + + // mix-in small piece + ret = mix_buffer_write(&mb, 0x6543, "11111111111111111111", 10); + assert(ret == true); + + // read partially mixed + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p != NULL); + assert(size == 40); + assert(memcmp(p, "aaaaaaaaaaaaaaaaaaaa00000000000000000000", size) == 0); + // read-pos = 15, write-pos = 25 + + mix_buffer_destroy(&mb); + + + + // 2-channel + + memset(&mb, 0, sizeof(mb)); + ret = mix_buffer_init(&mb, AV_SAMPLE_FMT_S16, 500, 2, 100); + assert(ret == true); + + // pre-fill with zeroes + + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p != NULL); + assert(size == 80); + assert(memcmp(p, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + + p = mix_buffer_read_fast(&mb, 25, &size); + assert(p != NULL); + assert(size == 100); + assert(memcmp(p, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + + // slow-path read around boundary + + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p == NULL); + assert(size == 80); + char sbuf[size]; + mix_buffer_read_slow(&mb, sbuf, 20); + assert(memcmp(sbuf, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + + // write-in and read-out + + ret = mix_buffer_write(&mb, 0x1234, "11223344551122334455", 5); + assert(ret == true); + + p = mix_buffer_read_fast(&mb, 5, &size); + assert(p != NULL); + assert(size == 20); + assert(memcmp(p, "11223344551122334455", size) == 0); + + // subsequent read with pre-fill + + p = mix_buffer_read_fast(&mb, 25, &size); + assert(p != NULL); + assert(size == 100); + assert(memcmp(p, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + + // write-in around boundary + + ret = mix_buffer_write(&mb, 0x1234, "qqwweerrttyyuuiiooppaassddffggqqwweerrttyyuuiiooppaassddffgg", 15); + assert(ret == true); + + // read-out around boundary past end with pre-fill + + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p == NULL); + assert(size == 80); + mix_buffer_read_slow(&mb, sbuf, 20); + assert(memcmp(sbuf, "qqwweerrttyyuuiiooppaassddffggqqwweerrttyyuuiiooppaassddffgg\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + + // write-in and partial read-out + + ret = mix_buffer_write(&mb, 0x1234, "qqwweerrttyyuuiiooppaassddffggqqwweerrttyyuuiiooppaassddffgg", 15); + assert(ret == true); + + p = mix_buffer_read_fast(&mb, 5, &size); + assert(p != NULL); + assert(size == 20); + assert(memcmp(p, "qqwweerrttyyuuiioopp", size) == 0); + // read-pos = 20, write-pos = 30 + + // another write-in and partial read-out + + ret = mix_buffer_write(&mb, 0x1234, "mmnnbbvvccxxzzllkkjjhhggffddssmmnnbbvvccxxzzllkkjjhhggffddss", 15); + assert(ret == true); + + p = mix_buffer_read_fast(&mb, 5, &size); + assert(p != NULL); + assert(size == 20); + assert(memcmp(p, "aassddffggqqwweerrtt", size) == 0); + // read-pos = 25, write-pos = 45 + + // write-in around boundary + + ret = mix_buffer_write(&mb, 0x1234, "0011223344556677889900112233445566778899", 10); + assert(ret == true); + // read-pos = 25, write-pos = 5 + + // partial read-out + + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p != NULL); + assert(size == 80); + assert(memcmp(p, "yyuuiiooppaassddffggmmnnbbvvccxxzzllkkjjhhggffddssmmnnbbvvccxxzzllkkjjhhggffddss", size) == 0); + // read-pos = 45, write-pos = 5 + + // read-out across boundary plus pre-fill + p = mix_buffer_read_fast(&mb, 15, &size); + assert(p == NULL); + assert(size == 60); + mix_buffer_read_slow(&mb, sbuf, 15); + assert(memcmp(sbuf, "0011223344556677889900112233445566778899\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + // read-pos = 10, write-pos = 10 + + // write and read to end of buffer + + ret = mix_buffer_write(&mb, 0x1234, "llkkccgg449900dd22bbqqddffpp[[rr..//5500kkxxmmnnffggoorrpp00ss9933[[ss==]]xx..ssllkkccgg449900dd22bbqqddffpp[[rr..//5500kkxxmmnnffggoorrpp00ss9933[[ss==]]xx..ss", 40); + assert(ret == true); + + p = mix_buffer_read_fast(&mb, 40, &size); + assert(p != NULL); + assert(size == 160); + assert(memcmp(p, "llkkccgg449900dd22bbqqddffpp[[rr..//5500kkxxmmnnffggoorrpp00ss9933[[ss==]]xx..ssllkkccgg449900dd22bbqqddffpp[[rr..//5500kkxxmmnnffggoorrpp00ss9933[[ss==]]xx..ss", size) == 0); + // read-pos = 0, write-pos = 0 + + // mix-in + + // write from source 1 + ret = mix_buffer_write(&mb, 0x1234, "\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2\1\2", 10); + assert(ret == true); + // write from source 2 + ret = mix_buffer_write(&mb, 0x6543, "\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5", 10); + assert(ret == true); + + // read mixed output + p = mix_buffer_read_fast(&mb, 10, &size); + assert(p != NULL); + assert(size == 40); + assert(memcmp(p, "\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7\4\7", size) == 0); + // read-pos = 10, write-pos = 10 + + // write with only partial mix-in + ret = mix_buffer_write(&mb, 0x1234, "!!##$$%%&&''(())**++aabbccddee!!##$$%%&&''(())**++aabbccddee", 15); + assert(ret == true); + ret = mix_buffer_write(&mb, 0x6543, "AABBCCDDEEFFGGHHIIJJAABBCCDDEEFFGGHHIIJJ", 10); + assert(ret == true); + + // read partially mixed output + p = mix_buffer_read_fast(&mb, 15, &size); + assert(p != NULL); + assert(size == 60); + assert(memcmp(p, "bbeeggiikkmmooqqssuu\377\177\377\177\377\177\377\177\377\177ggjjllnnpp''(())**++aabbccddee", size) == 0); + // read-pos = 25, write-pos = 25 + + // partial write followed by larger mix-in + ret = mix_buffer_write(&mb, 0x6543, "AABBCCDDEEFFGGHHIIJJAABBCCDDEEFFGGHHIIJJ", 10); + assert(ret == true); + ret = mix_buffer_write(&mb, 0x1234, "!!##$$%%&&''(())**++aabbccddee!!##$$%%&&''(())**++aabbccddee", 15); + assert(ret == true); + + // read partially mixed output plus fill-in + p = mix_buffer_read_fast(&mb, 20, &size); + assert(p != NULL); + assert(size == 80); + assert(memcmp(p, "bbeeggiikkmmooqqssuu\377\177\377\177\377\177\377\177\377\177ggjjllnnpp''(())**++aabbccddee\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", size) == 0); + // read-pos = 45, write-pos = 45 + + // mix-in across boundary + ret = mix_buffer_write(&mb, 0x6543, "//..--,,++**))((''&&%%$$##\"\"!!//..--,,++**))((''&&%%$$##\"\"!!", 15); + assert(ret == true); + ret = mix_buffer_write(&mb, 0x1234, "NNLLKKJJIIHHGGFFEEDDCCBBAA@@??NNLLKKJJIIHHGGFFEEDDCCBBAA@@??", 15); + assert(ret == true); + // read-pos = 45, write-pos = 10 + + // continue mix-in before reading + ret = mix_buffer_write(&mb, 0x1234, "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOAABBCCDDEEFFGGHHIIJJKKLLMMNNOO", 15); + assert(ret == true); + ret = mix_buffer_write(&mb, 0x6543, "00112233445566778899::;;<<==>>00112233445566778899::;;<<==>>", 15); + assert(ret == true); + // read-pos = 45, write-pos = 25 + + // read some mixed data, slow path across boundary + p = mix_buffer_read_fast(&mb, 10, &size); + assert(p == NULL); + assert(size == 40); + mix_buffer_read_slow(&mb, sbuf, 10); + assert(memcmp(sbuf, "}}zzxxvvttrrppnnlljjhhffddbb``}}zzxxvvtt", size) == 0); + // read-pos = 5, write-pos = 25 + + // read some more, fast path + p = mix_buffer_read_fast(&mb, 10, &size); + assert(p != NULL); + assert(size == 40); + assert(memcmp(p, "rrppnnlljjhhffddbb``qqssuuwwyy{{}}\177\177\377\177\377\177", size) == 0); + // read-pos = 15, write-pos = 25 + + mix_buffer_destroy(&mb); + + return 0; +}