mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-21 10:40:21 +00:00
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:
@@ -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 $@
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(¶ms);
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
10
src/host.cpp
10
src/host.cpp
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
168
src/utils/audio_buffer.c
Normal 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
58
src/utils/audio_buffer.h
Normal 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_ */
|
||||
|
||||
Reference in New Issue
Block a user