mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-20 12:40:05 +00:00
187 lines
6.9 KiB
C
187 lines
6.9 KiB
C
/**
|
|
* @file utils/audio_buffer.c
|
|
* @author Martin Pulec <pulec@cesnet.cz>
|
|
*/
|
|
/*
|
|
* Copyright (c) 2016-2021 CESNET, z. s. p. o.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, is permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of CESNET nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
|
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#include "config_unix.h"
|
|
#include "config_win32.h"
|
|
#endif
|
|
|
|
#include "audio/types.h"
|
|
#include "debug.h"
|
|
#include "host.h"
|
|
#include "utils/audio_buffer.h"
|
|
#include "utils/ring_buffer.h"
|
|
|
|
#define WINDOW 50
|
|
|
|
#undef max
|
|
#undef min
|
|
#define max(a, b) (((a) > (b))? (a): (b))
|
|
#define min(a, b) (((a) < (b))? (a): (b))
|
|
|
|
#define BUF_LAST_UNDERRUN_MAX 1000000000
|
|
#define BUF_LAST_UNDERRUN_THRESHOLD 10000
|
|
#define BUF_LAST_OVERRUN_THRESHOLD 10000
|
|
#define AGGRESSIVITY_MAX 4
|
|
#define AGGRESSIVITY_STEP 100
|
|
|
|
static const int occupacy_windows[] = { 50, 200 };
|
|
|
|
struct audio_buffer {
|
|
struct audio_desc desc;
|
|
ring_buffer_t *ring;
|
|
int suggested_latency_ms;
|
|
|
|
// moving averages
|
|
int in_pkt_size;
|
|
int out_pkt_size;
|
|
int avg_occupancy[2]; // at read time
|
|
int last_underrun; // last underrun n output frames ago
|
|
int last_overrun; // last overrun n output frames ago
|
|
int aggressivity;
|
|
int last_aggressivity_change;
|
|
};
|
|
|
|
struct audio_buffer *audio_buffer_init(int sample_rate, int bps, int ch_count, int suggested_latency_ms)
|
|
{
|
|
struct audio_buffer *buf = calloc(1, sizeof(struct audio_buffer));
|
|
buf->desc.sample_rate = sample_rate;
|
|
buf->desc.bps = bps;
|
|
buf->desc.ch_count = ch_count;
|
|
|
|
buf->last_underrun = BUF_LAST_UNDERRUN_MAX;
|
|
|
|
buf->ring = ring_buffer_init(sample_rate * bps * ch_count);
|
|
|
|
buf->suggested_latency_ms = suggested_latency_ms;
|
|
|
|
buf->aggressivity = 1;
|
|
buf->last_aggressivity_change = AGGRESSIVITY_STEP;
|
|
|
|
return buf;
|
|
}
|
|
|
|
void audio_buffer_destroy(struct audio_buffer *buf)
|
|
{
|
|
if (!buf) {
|
|
return;
|
|
}
|
|
ring_buffer_destroy(buf->ring);
|
|
free(buf);
|
|
}
|
|
|
|
int audio_buffer_read(struct audio_buffer *buf, char *out, int max_len)
|
|
{
|
|
if (buf->out_pkt_size > 0) {
|
|
buf->out_pkt_size = (max_len + (buf->out_pkt_size * (WINDOW-1))) / WINDOW;
|
|
} else {
|
|
buf->out_pkt_size = max_len;
|
|
}
|
|
|
|
int ring_size = ring_get_current_size(buf->ring);
|
|
|
|
for (unsigned int i = 0; i < sizeof buf->avg_occupancy / sizeof buf->avg_occupancy[0]; ++i) {
|
|
if (buf->avg_occupancy[i] > 0) {
|
|
buf->avg_occupancy[i] = (ring_size + (buf->avg_occupancy[i] * (occupacy_windows[i] -1))) / occupacy_windows[i];
|
|
} else {
|
|
buf->avg_occupancy[i] = ring_size;
|
|
}
|
|
}
|
|
|
|
// handle underruns
|
|
if (ring_size < max_len) {
|
|
buf->last_underrun = 0;
|
|
} else {
|
|
if (buf->last_underrun < BUF_LAST_UNDERRUN_MAX) {
|
|
buf->last_underrun += 1;
|
|
}
|
|
}
|
|
|
|
int suggested_latency_bytes = buf->suggested_latency_ms * buf->desc.bps * buf->desc.ch_count * buf->desc.sample_rate / 1000;
|
|
int requested_latency_bytes = max(suggested_latency_bytes, 2*max(buf->in_pkt_size, buf->out_pkt_size));
|
|
|
|
int ret = ring_buffer_read(buf->ring, out, max_len);
|
|
|
|
// fiddle aggressivity
|
|
if (buf->last_aggressivity_change >= AGGRESSIVITY_STEP) {
|
|
buf->last_aggressivity_change = 0;
|
|
if ((buf->avg_occupancy[0] > buf->avg_occupancy[1] && buf->last_underrun > BUF_LAST_UNDERRUN_THRESHOLD / 10) && buf->last_overrun <= BUF_LAST_OVERRUN_THRESHOLD) {
|
|
buf->aggressivity = min(buf->aggressivity + 1, AGGRESSIVITY_MAX);
|
|
} else if (buf->avg_occupancy[0] < buf->avg_occupancy[1] || buf->last_underrun < BUF_LAST_UNDERRUN_THRESHOLD / 100 || buf->last_overrun > BUF_LAST_OVERRUN_THRESHOLD) {
|
|
buf->aggressivity = max(buf->aggressivity - 1, 1);
|
|
}
|
|
} else {
|
|
buf->last_aggressivity_change += 1;
|
|
}
|
|
|
|
// handle overruns
|
|
int remaining_bytes = ring_size - ret;
|
|
if (requested_latency_bytes < remaining_bytes) {
|
|
int len_drop = (1<<buf->aggressivity) * buf->desc.bps * buf->desc.ch_count * 128;
|
|
len_drop = min(len_drop, remaining_bytes / 2);
|
|
|
|
char *tmp = alloca(len_drop);
|
|
ring_buffer_read(buf->ring, tmp, len_drop);
|
|
buf->last_overrun = 0;
|
|
log_msg(LOG_LEVEL_VERBOSE, "Dropped audio samples: req latency %d remaining %d dropped %d!\n", requested_latency_bytes, remaining_bytes, len_drop);
|
|
} else {
|
|
buf->last_overrun += 1;
|
|
}
|
|
|
|
log_msg(LOG_LEVEL_DEBUG, "buf - in a. %d, out a. %d, occ. a. [%d,%d] last under/overrun %d, %d aggressivity %d\n", buf->in_pkt_size, buf->out_pkt_size, buf->avg_occupancy[0],buf->avg_occupancy[1], buf->last_underrun, buf->last_overrun, buf->aggressivity);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void audio_buffer_write(struct audio_buffer *buf, const char *in, int len)
|
|
{
|
|
if (buf->in_pkt_size > 0) {
|
|
buf->in_pkt_size = (len + (buf->in_pkt_size * (WINDOW-1))) / WINDOW;
|
|
} else {
|
|
buf->in_pkt_size = len;
|
|
}
|
|
ring_buffer_write(buf->ring, in, len);
|
|
}
|
|
|
|
struct audio_buffer_api audio_buffer_fns = {
|
|
(void (*)(void *)) audio_buffer_destroy,
|
|
(int (*)(void *, char *, int)) audio_buffer_read,
|
|
(void (*)(void *, const char *, int)) audio_buffer_write,
|
|
};
|
|
|