ALSA play.: write from thread

This is a proposed change (not yet default) to the ALSA behaviour. There
is a thread that writes to device all the time, avoiding underruns. On
the other hand there is a need to handle latency for which new buffer
was created. Alternatively, speex jitter buffer could be used.
This commit is contained in:
Martin Pulec
2016-05-03 17:24:24 +02:00
parent e0bfedbd14
commit 880a19dd4c
10 changed files with 546 additions and 44 deletions

View File

@@ -121,11 +121,12 @@ OBJS = @OBJS@ \
src/utils/list.o \
src/lib_common.o \
src/ug_runtime_error.o \
src/utils/synchronized_queue.o \
src/utils/audio_buffer.o \
src/utils/misc.o \
src/utils/packet_counter.o \
src/utils/resource_manager.o \
src/utils/ring_buffer.o \
src/utils/synchronized_queue.o \
src/utils/vf_split.o \
src/utils/wait_obj.o \
src/utils/worker.o \
@@ -245,6 +246,9 @@ src/audio/smallft.o:
src/audio/mdf.o:
$(CC) $(CFLAGS) $(SPEEX_FLAGS) $(INC) -DEXPORT="" -DRANDOM_PREFIX=speex -DFLOATING_POINT -DOUTSIDE_SPEEX -I. -I $(srcdir)/speex-1.2rc1/include/speex -Iinclude -fvisibility=hidden -c $(srcdir)/speex-1.2rc1/libspeex/mdf.c -fPIC -DPIC -o $@
src/audio/jitter.o:
$(CC) $(CFLAGS) $(SPEEX_FLAGS) $(INC) -DEXPORT="" -DRANDOM_PREFIX=speex -DFLOATING_POINT -DOUTSIDE_SPEEX -I. -I $(srcdir)/speex-1.2rc1/include/speex -Iinclude -fvisibility=hidden -c $(srcdir)/speex-1.2rc1/libspeex/jitter.c -fPIC -DPIC -o $@
src/audio/capture/coreaudio.o: src/audio/capture/coreaudio.c $(ALL_INCLUDES)
$(CC) $(CFLAGS) -Wno-error=deprecated-declarations $(INC) -c $< -o $@

View File

@@ -2356,7 +2356,7 @@ AC_SUBST(PORTAUDIO_PLAY_OBJ)
speex=yes
SPEEX_PATH=speex-1.2rc1
SPEEX_INC=-I$srcdir/${SPEEX_PATH}/include
SPEEX_OBJ="src/audio/resample.o src/audio/preprocess.o src/audio/filterbank.o src/audio/fftwrap.o src/audio/smallft.o src/audio/mdf.o"
SPEEX_OBJ="src/audio/resample.o src/audio/preprocess.o src/audio/filterbank.o src/audio/fftwrap.o src/audio/smallft.o src/audio/mdf.o src/audio/jitter.o"
SPEEX_OBJ="$SPEEX_OBJ src/audio/echo.o"
SPEEX_LIB=
AC_DEFINE([HAVE_SPEEX], [1], [Build with SPEEX support])

View File

@@ -496,6 +496,7 @@ static struct rtp *initialize_audio_network(struct audio_network_parameters *par
if (r != NULL) {
pdb_add(params->participants, rtp_my_ssrc(r));
rtp_set_option(r, RTP_OPT_WEAK_VALIDATION, TRUE);
rtp_set_option(r, RTP_OPT_PROMISC, TRUE);
rtp_set_sdes(r, rtp_my_ssrc(r), RTCP_SDES_TOOL,
PACKAGE_STRING, strlen(PACKAGE_VERSION));
rtp_set_recv_buf(r, 256*1024);
@@ -645,18 +646,23 @@ echo_play(s->echo_state, &pbuf_data.buffer);
curr_desc = audio_desc_from_audio_frame(&pbuf_data.buffer);
if(!audio_desc_eq(device_desc, curr_desc)) {
int log_l;
string msg;
if (audio_playback_reconfigure(s->audio_playback_device, curr_desc.bps * 8,
curr_desc.ch_count,
curr_desc.sample_rate) != TRUE) {
log_msg(LOG_LEVEL_ERROR, "Audio reconfiguration failed!");
log_l = LOG_LEVEL_ERROR;
msg = "Audio reconfiguration failed";
failed = true;
}
else {
log_msg(LOG_LEVEL_INFO, "Audio reconfiguration succeeded.");
log_l = LOG_LEVEL_INFO;
msg = "Audio reconfiguration succeeded";
device_desc = curr_desc;
rtp_flush_recv_buf(s->audio_network_device);
}
std::cerr << " (" << curr_desc << ")\n";
LOG(log_l) << msg << " (" << curr_desc << ")" << (log_l < LOG_LEVEL_WARNING ? "!" : ".") << "\n";
}
if(!failed)
@@ -686,18 +692,22 @@ echo_play(s->echo_state, &pbuf_data.buffer);
curr_desc = audio_desc_from_audio_frame(&pbuf_data.buffer);
if(!audio_desc_eq(device_desc, curr_desc)) {
int log_l;
string msg;
if (audio_playback_reconfigure(s->audio_playback_device, curr_desc.bps * 8,
curr_desc.ch_count,
curr_desc.sample_rate) != TRUE) {
log_msg(LOG_LEVEL_ERROR, "Audio reconfiguration failed!");
log_l = LOG_LEVEL_ERROR;
msg = "Audio reconfiguration failed";
failed = true;
}
else {
log_msg(LOG_LEVEL_INFO, "Audio reconfiguration succeeded.");
log_l = LOG_LEVEL_INFO;
msg = "Audio reconfiguration succeeded";
device_desc = curr_desc;
rtp_flush_recv_buf(s->audio_network_device);
}
std::cerr << " (" << curr_desc << ")\n";
LOG(log_l) << msg << " (" << curr_desc << ")" << (log_l < LOG_LEVEL_WARNING ? "!" : ".") << "\n";
rtp_flush_recv_buf(s->audio_network_device);
}

View File

@@ -47,8 +47,6 @@
#include "config_unix.h"
#endif
#ifdef HAVE_ALSA
#include <alsa/asoundlib.h>
#include <stdlib.h>
#include <string.h>
@@ -65,6 +63,18 @@
#define BUFFER_MAX 200
#define MOD_NAME "[ALSA play.] "
/**
* Speex jitter buffer use is currently not stable and not ready for production use.
* Moreover, it is unclear whether is suitable for our use case.
*/
//#define USE_SPEEX_JITTER_BUFFER 1
#ifdef USE_SPEEX_JITTER_BUFFER
#include <speex/speex_jitter.h>
#else
#include "utils/audio_buffer.h"
#endif
struct state_alsa_playback {
snd_pcm_t *handle;
struct audio_desc desc;
@@ -78,8 +88,99 @@ struct state_alsa_playback {
snd_config_t * local_config;
bool non_interleaved;
bool new_api;
// following variables are used only if new_api == true
pthread_t thread_id;
#ifdef USE_SPEEX_JITTER_BUFFER
JitterBuffer *buf;
#else
audio_buffer_t *buf;
#endif
pthread_mutex_t lock;
bool should_exit_thread;
bool thread_started;
uint32_t timestamp;
struct timeval last_audio_read;
};
static void audio_play_alsa_write_frame(void *state, struct audio_frame *frame);
static void *worker(void *args) {
struct state_alsa_playback *s = args;
struct audio_frame f = { .bps = s->desc.bps,
.sample_rate = s->desc.sample_rate,
.ch_count = s->desc.ch_count };
size_t len = 256;
size_t len_100ms = f.bps * f.sample_rate * f.ch_count / 10;
char *silence = alloca(len_100ms);
memset(silence, 0, len_100ms);
#ifdef USE_SPEEX_JITTER_BUFFER
const int pkt_max_len = s->desc.sample_rate / 10;
JitterBufferPacket pkt;
pkt.len = pkt_max_len;
pkt.data = alloca(pkt_max_len);
#else
char *data = alloca(len);
#endif
while (1) {
pthread_mutex_lock(&s->lock);
if (s->should_exit_thread) {
pthread_mutex_unlock(&s->lock);
return NULL;
}
#ifdef USE_SPEEX_JITTER_BUFFER
int start_offset;
int err = jitter_buffer_get(s->buf, &pkt, len, &start_offset);
jitter_buffer_tick(s->buf);
#else
int ret = audio_buffer_read(s->buf, data, len);
#endif
pthread_mutex_unlock(&s->lock);
#ifdef USE_SPEEX_JITTER_BUFFER
if (err == JITTER_BUFFER_OK) {
f.data_len = pkt.len;
f.data = pkt.data;
} else {
log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "underrun occurred\n");
if (err < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Jitter buffer: %s\n",
JITTER_BUFFER_INTERNAL_ERROR ? "internal error" :
"invalid argument\n");
}
f.data_len = len;
f.data = silence;
}
pkt.len = pkt_max_len;
#else
struct timeval now;
gettimeofday(&now, NULL);
if (ret > 0) {
f.data_len = ret;
f.data = data;
s->last_audio_read = now;
} else {
f.data = silence;
if (tv_diff(now, s->last_audio_read) < 2.0) {
log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "underrun occurred\n");
f.data_len = len;
} else {
f.data_len = len_100ms;
}
}
#endif
audio_play_alsa_write_frame(s, &f);
}
}
static struct audio_desc audio_play_alsa_query_format(void *state, struct audio_desc desc)
{
struct state_alsa_playback *s = (struct state_alsa_playback *) state;
@@ -209,10 +310,20 @@ static int audio_play_alsa_reconfigure(void *state, struct audio_desc desc)
int rc;
unsigned int frames;
if (s->new_api && s->thread_started) {
pthread_mutex_lock(&s->lock);
s->should_exit_thread = true;
pthread_mutex_unlock(&s->lock);
pthread_join(s->thread_id, NULL);
s->should_exit_thread = false;
s->thread_started = false;
}
s->desc.bps = desc.bps;
s->desc.ch_count = desc.ch_count;
s->desc.sample_rate = desc.sample_rate;
snd_pcm_drop(s->handle); // stop the stream
/* Allocate a hardware parameters object. */
snd_pcm_hw_params_alloca(&params);
@@ -304,22 +415,46 @@ static int audio_play_alsa_reconfigure(void *state, struct audio_desc desc)
snd_strerror(rc));
}
val = BUFFER_MIN * 1000;
dir = 1;
rc = snd_pcm_hw_params_set_buffer_time_min(s->handle, params,
&val, &dir);
if (rc < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning - unable to set minimal buffer size: %s\n",
snd_strerror(rc));
}
if (s->new_api) {
int mindir = -1, maxdir = 1;
unsigned int minval = 0;
unsigned int maxval = 15000;
val = BUFFER_MAX * 1000;
dir = -1;
rc = snd_pcm_hw_params_set_buffer_time_max(s->handle, params,
&val, &dir);
if (rc < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning - unable to set maximal buffer size: %s\n",
snd_strerror(rc));
const char *minval_str = get_commandline_param("alsa-playback-bufmin");
if (minval_str) {
minval = atoi(minval_str);
}
const char *maxval_str = get_commandline_param("alsa-playback-bufmax");
if (maxval_str) {
maxval = atoi(maxval_str);
}
rc = snd_pcm_hw_params_set_buffer_time_minmax(s->handle, params, &minval, &mindir,
&maxval, &maxdir);
if (rc < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning - unable to set buffer to its size: %s\n",
snd_strerror(rc));
} else {
log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Buffer size: %d-%d ns\n", minval, maxval);
}
} else {
val = BUFFER_MIN * 1000;
dir = 1;
rc = snd_pcm_hw_params_set_buffer_time_min(s->handle, params,
&val, &dir);
if (rc < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning - unable to set minimal buffer size: %s\n",
snd_strerror(rc));
}
val = BUFFER_MAX * 1000;
dir = -1;
rc = snd_pcm_hw_params_set_buffer_time_max(s->handle, params,
&val, &dir);
if (rc < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning - unable to set maximal buffer size: %s\n",
snd_strerror(rc));
}
}
/* Write the parameters to the driver */
@@ -330,6 +465,18 @@ static int audio_play_alsa_reconfigure(void *state, struct audio_desc desc)
return FALSE;
}
if (s->new_api) {
#ifdef USE_SPEEX_JITTER_BUFFER
jitter_buffer_reset(s->buf);
#else
audio_buffer_destroy(s->buf);
s->buf = audio_buffer_init(s->desc.sample_rate, s->desc.bps, s->desc.ch_count, 20);
#endif
s->timestamp = 0;
pthread_create(&s->thread_id, NULL, worker, s);
s->thread_started = true;
}
return TRUE;
}
@@ -462,10 +609,33 @@ static void * audio_play_alsa_init(const char *cfg)
s = calloc(1, sizeof(struct state_alsa_playback));
const char *new_api;
new_api = get_commandline_param("alsa-playback-api");
if (new_api) {
if (strcmp(new_api, "new") == 0) {
s->new_api = true;
} else if (strcmp(new_api, "old") == 0) {
s->new_api = false;
} else {
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unknown API string \"%s\"!\n", new_api);
free(s);
return NULL;
}
} else {
log_msg(LOG_LEVEL_NOTICE, MOD_NAME "You may try to use \"--param alsa-playback-api=new\" to use a new playback API which may become default in future versions.\n");
}
gettimeofday(&s->start_time, NULL);
if(cfg && strlen(cfg) > 0) {
if(strcmp(cfg, "help") == 0) {
printf("Usage\n");
printf("\t-s alsa\n");
printf("\t-s alsa:<device>\n");
printf("\t-s alsa --param alsa-playback-api={new|old} # uses new experimental API\n");
printf("\t-s alsa --param alsa-playback-api=new[:alsa-playback-bufmin=<us>][:alsa-playback-bufmax=<us>] # new api only\n");
printf("\n");
printf("Available ALSA playback devices:\n");
audio_play_alsa_help(NULL);
free(s);
@@ -481,10 +651,14 @@ static void * audio_play_alsa_init(const char *cfg)
}
char device[1024] = "pcm.";
strncat(device, name, sizeof(device) - strlen(device) - 1);
s->local_config = init_local_config_with_workaround(device);
if (s->local_config) {
strncat(device, name, sizeof(device) - strlen(device) - 1);
if (!s->new_api) {
s->local_config = init_local_config_with_workaround(device);
}
if (!s->new_api && s->local_config) {
rc = snd_pcm_open_lconf(&s->handle, name,
SND_PCM_STREAM_PLAYBACK, 0, s->local_config);
} else {
@@ -498,9 +672,16 @@ static void * audio_play_alsa_init(const char *cfg)
goto error;
}
rc = snd_pcm_nonblock(s->handle, 1);
if(rc < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning: Unable to set nonblock mode.\n");
if (s->new_api) {
#ifdef USE_SPEEX_JITTER_BUFFER
s->buf = jitter_buffer_init(1);
#endif
pthread_mutex_init(&s->lock, NULL);
} else {
rc = snd_pcm_nonblock(s->handle, 1);
if(rc < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning: Unable to set nonblock mode.\n");
}
}
/* CC3000e playback needs to be initialized prior to capture
@@ -517,21 +698,39 @@ error:
return NULL;
}
static int write_samples(snd_pcm_t *handle, const struct audio_frame *frame, int frames, bool noninterleaved) {
static int write_samples(snd_pcm_t *handle, const struct audio_frame *frame, int frames, bool noninterleaved, bool new_api) {
char *write_ptr[frame->ch_count]; // for non-interleaved
char *tmp_data = (char *) alloca(frame->data_len);
if (noninterleaved) {
char *write_ptr[frame->ch_count];
char *tmp_data = (char *) alloca(frame->data_len);
interleaved2noninterleaved(tmp_data, frame->data, frame->bps, frame->data_len, frame->ch_count);
for (int i = 0; i < frame->ch_count; ++i) {
write_ptr[i] = tmp_data + frame->data_len / frame->ch_count * i;
}
return snd_pcm_writen(handle, (void **) &write_ptr, frames);
} else {
return snd_pcm_writei(handle, frame->data, frames);
}
char *data = frame->data; // for interleaved
int written = 0;
while (written < frames) {
int rc;
if (noninterleaved) {
for (int i = 0; i < frame->ch_count; ++i) {
write_ptr[i] = tmp_data + frame->data_len / frame->ch_count * i + written * frame->bps;
}
rc = snd_pcm_writen(handle, (void **) &write_ptr, frames);
} else {
rc = snd_pcm_writei(handle, data, frames - written);
}
if (rc < 0) {
return rc;
}
if (!new_api) { // overrun (in non-blocking mode) or signal
return rc;
}
written += rc;
data += written * frame->bps * frame->ch_count;
}
return written;
}
static void audio_play_alsa_put_frame(void *state, struct audio_frame *frame)
static void audio_play_alsa_write_frame(void *state, struct audio_frame *frame)
{
struct state_alsa_playback *s = (struct state_alsa_playback *) state;
int rc;
@@ -549,7 +748,7 @@ static void audio_play_alsa_put_frame(void *state, struct audio_frame *frame)
s->played_samples += frame->data_len / frame->bps / frame->ch_count;
int frames = frame->data_len / (frame->bps * frame->ch_count);
rc = write_samples(s->handle, frame, frames, s->non_interleaved);
rc = write_samples(s->handle, frame, frames, s->non_interleaved, s->new_api);
if (rc == -EPIPE) {
/* EPIPE means underrun */
log_msg(LOG_LEVEL_WARNING, MOD_NAME "underrun occurred\n");
@@ -560,7 +759,7 @@ static void audio_play_alsa_put_frame(void *state, struct audio_frame *frame)
if(sec + (double) frames/frame->sample_rate > BUFFER_MAX / 1000.0) {
frames_to_write = (BUFFER_MAX / 1000.0 - sec) * frame->sample_rate;
}
int rc = write_samples(s->handle, frame, frames_to_write, s->non_interleaved);
int rc = write_samples(s->handle, frame, frames_to_write, s->non_interleaved, s->new_api);
if(rc < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "error from writei: %s\n",
snd_strerror(rc));
@@ -570,17 +769,48 @@ static void audio_play_alsa_put_frame(void *state, struct audio_frame *frame)
} else if (rc < 0) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "error from writei: %s\n",
snd_strerror(rc));
snd_pcm_recover(s->handle, rc, 0);
} else if (rc != (int)frames) {
log_msg(LOG_LEVEL_WARNING, MOD_NAME "short write, written %d frames (overrun)\n", rc);
}
}
static void audio_play_alsa_put_frame(void *state, struct audio_frame *frame)
{
struct state_alsa_playback *s = state;
if (s->new_api) {
pthread_mutex_lock(&s->lock);
#ifdef USE_SPEEX_JITTER_BUFFER
JitterBufferPacket pkt;
pkt.data = frame->data;
pkt.len = frame->data_len;
pkt.timestamp = s->timestamp;
pkt.span = frame->data_len / s->desc.bps / s->desc.ch_count;
s->timestamp += pkt.span;
jitter_buffer_put(s->buf, &pkt);
#else
audio_buffer_write(s->buf, frame->data, frame->data_len);
#endif
pthread_mutex_unlock(&s->lock);
} else {
audio_play_alsa_write_frame(state, frame);
}
}
static void audio_play_alsa_done(void *state)
{
struct state_alsa_playback *s = (struct state_alsa_playback *) state;
struct timeval t;
if (s->new_api && s->thread_started) {
pthread_mutex_lock(&s->lock);
s->should_exit_thread = true;
pthread_mutex_unlock(&s->lock);
pthread_join(s->thread_id, NULL);
}
gettimeofday(&t, NULL);
log_msg(LOG_LEVEL_INFO, MOD_NAME "Played %lld samples in %f seconds (%f samples per second).\n",
s->played_samples, tv_diff(t, s->start_time),
@@ -591,6 +821,14 @@ static void audio_play_alsa_done(void *state)
if (s->local_config) {
snd_config_delete(s->local_config);
}
if (s->new_api) {
#ifdef USE_SPEEX_JITTER_BUFFER
jitter_buffer_destroy(s->buf);
#else
audio_buffer_destroy(s->buf);
#endif
pthread_mutex_destroy(&s->lock);
}
free(s);
}
@@ -606,4 +844,3 @@ static const struct audio_playback_info aplay_alsa_info = {
REGISTER_MODULE(alsa, &aplay_alsa_info, LIBRARY_CLASS_AUDIO_PLAYBACK, AUDIO_PLAYBACK_ABI_VERSION);
#endif /* HAVE_ALSA */

View File

@@ -56,6 +56,14 @@ bool audio_desc::operator!() const
return codec == AC_NONE;
}
audio_desc::operator string() const
{
ostringstream oss;
oss << *this;
return oss.str();
}
audio_frame2_resampler::audio_frame2_resampler() : resampler(nullptr), resample_from(0),
resample_ch_count(0), resample_to(0)
{

View File

@@ -53,6 +53,10 @@ typedef enum {
AC_FLAC,
} audio_codec_t;
#ifdef __cplusplus
#include <string>
#endif
struct audio_desc {
int bps; /* bytes per sample */
int sample_rate;
@@ -60,6 +64,7 @@ struct audio_desc {
audio_codec_t codec;
#ifdef __cplusplus
bool operator!() const;
operator std::string() const;
#endif
};

View File

@@ -207,3 +207,13 @@ void print_version()
printf(AUTOCONF_RESULT);
}
const char *get_commandline_param(const char *key)
{
auto it = commandline_params.find(key);
if (it != commandline_params.end()) {
return it->second.c_str();
} else {
return NULL;
}
}

View File

@@ -132,6 +132,8 @@ void print_capabilities(struct module *root, bool use_vidcap);
void print_version(void);
const char *get_commandline_param(const char *key);
#ifdef __cplusplus
}
#endif

168
src/utils/audio_buffer.c Normal file
View File

@@ -0,0 +1,168 @@
/**
* @file utils/audio_buffer.c
* @author Martin Pulec <pulec@cesnet.cz>
*/
/*
* Copyright (c) 2016 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
#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 AGGRESIVITY_MAX 4
#define AGGRESIVITY_STEP 100
static const int occupacy_windows[3] = { 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 aggresivity;
int last_aggresivity_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->aggresivity = 1;
buf->last_aggresivity_change = AGGRESIVITY_STEP;
return buf;
}
void audio_buffer_destroy(struct audio_buffer *buf)
{
if (buf) {
ring_buffer_destroy(buf->ring);
}
}
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; ++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;
}
}
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);
int remaining_bytes = ring_size - ret;
if (buf->last_aggresivity_change >= AGGRESIVITY_STEP) {
buf->last_aggresivity_change = 0;
if ((buf->avg_occupancy[0] > buf->avg_occupancy[1] && buf->last_underrun > BUF_LAST_UNDERRUN_THRESHOLD / 10)) {
buf->aggresivity = min(buf->aggresivity + 1, AGGRESIVITY_MAX);
} else if (buf->avg_occupancy[0] < buf->avg_occupancy[1] || buf->last_underrun < BUF_LAST_UNDERRUN_THRESHOLD / 100) {
buf->aggresivity = max(buf->aggresivity - 1, 1);
}
} else {
buf->last_aggresivity_change += 1;
}
if (requested_latency_bytes < remaining_bytes) {
int len_drop = (1<<buf->aggresivity) * buf->desc.bps * buf->desc.ch_count;
len_drop = min(len_drop, remaining_bytes / 2);
char *tmp = alloca(len_drop);
ring_buffer_read(buf->ring, tmp, len_drop);
}
log_msg(LOG_LEVEL_DEBUG, "buf - in avg %d, out avg %d, occupancy avg %d, last underrun %d, aggresivity %d\n", buf->in_pkt_size, buf->out_pkt_size, buf->avg_occupancy[0], buf->last_underrun, buf->aggresivity);
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);
}

58
src/utils/audio_buffer.h Normal file
View File

@@ -0,0 +1,58 @@
/**
* @file utils/audio_buffer.h
* @author Martin Pulec <pulec@cesnet.cz>
*/
/*
* Copyright (c) 2016 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.
*/
#ifndef AUDIO_BUFFER_H_
#define AUDIO_BUFFER_H_
#ifdef __cplusplus
extern "C" {
#endif
struct audio_buffer;
typedef struct audio_buffer audio_buffer_t;
struct audio_buffer *audio_buffer_init(int sample_rate, int bps, int ch_count, int suggested_latency_ms);
void audio_buffer_destroy(struct audio_buffer *buf);
int audio_buffer_read(struct audio_buffer *buf, char *out, int max_len);
void audio_buffer_write(struct audio_buffer *buf, const char *in, int len);
#ifdef __cplusplus
}
#endif
#endif /* AUDIO_BUFFER_H_ */