MT#56471 add mix_buffer implementation

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.

Change-Id: I0f6642b036944508f2a33420359de488ef8b991c
pull/1627/head
Richard Fuchs 2 years ago
parent 088c4d9201
commit 1a30947ea1

1
daemon/.gitignore vendored

@ -22,3 +22,4 @@ dtmf_rx_fillin.h
spandsp_logging.h
mvr2s_x64_avx512.S
mvr2s_x64_avx2.S
mix_buffer.c

@ -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

@ -0,0 +1,277 @@
#include "mix_buffer.h"
#include <libavutil/samplefmt.h>
#include <stdlib.h>
#include <assert.h>
#include <glib.h>
#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);
}

@ -0,0 +1,68 @@
#ifndef _MIX_BUFFER_H_
#define _MIX_BUFFER_H_
#include <stdint.h>
#include <stdbool.h>
#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

@ -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

2
t/.gitignore vendored

@ -76,3 +76,5 @@ ssllib.c
time-fudge-preload.so
mvr2s_x64_avx2.S
mvr2s_x64_avx512.S
test-mix-buffer
mix_buffer.c

@ -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

@ -0,0 +1,421 @@
#include "mix_buffer.h"
#include <assert.h>
#include <libavutil/samplefmt.h>
#include <string.h>
#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;
}
Loading…
Cancel
Save