mirror of https://github.com/sipwise/rtpengine.git
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: I0f6642b036944508f2a33420359de488ef8b991cpull/1627/head
parent
088c4d9201
commit
1a30947ea1
@ -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
|
@ -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…
Reference in new issue