diff --git a/Makefile.in b/Makefile.in index b07e562f7..a33b91604 100644 --- a/Makefile.in +++ b/Makefile.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 $@ diff --git a/configure.ac b/configure.ac index b27669af5..d8aa7f268 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 65834b608..56511ba31 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -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); } diff --git a/src/audio/playback/alsa.c b/src/audio/playback/alsa.c index fa2ddd6e2..2e662554c 100644 --- a/src/audio/playback/alsa.c +++ b/src/audio/playback/alsa.c @@ -47,8 +47,6 @@ #include "config_unix.h" #endif -#ifdef HAVE_ALSA - #include #include #include @@ -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 +#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:\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=][:alsa-playback-bufmax=] # 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 */ diff --git a/src/audio/types.cpp b/src/audio/types.cpp index 6731c05ce..bcf3e5518 100644 --- a/src/audio/types.cpp +++ b/src/audio/types.cpp @@ -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) { diff --git a/src/audio/types.h b/src/audio/types.h index ec15a4a34..17ff1f703 100644 --- a/src/audio/types.h +++ b/src/audio/types.h @@ -53,6 +53,10 @@ typedef enum { AC_FLAC, } audio_codec_t; +#ifdef __cplusplus +#include +#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 }; diff --git a/src/host.cpp b/src/host.cpp index 35d75ca81..1e17a7e98 100644 --- a/src/host.cpp +++ b/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; + } +} + diff --git a/src/host.h b/src/host.h index 4de088087..ebe67a805 100644 --- a/src/host.h +++ b/src/host.h @@ -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 diff --git a/src/utils/audio_buffer.c b/src/utils/audio_buffer.c new file mode 100644 index 000000000..384917f9f --- /dev/null +++ b/src/utils/audio_buffer.c @@ -0,0 +1,168 @@ +/** + * @file utils/audio_buffer.c + * @author Martin Pulec + */ +/* + * 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<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); +} + diff --git a/src/utils/audio_buffer.h b/src/utils/audio_buffer.h new file mode 100644 index 000000000..70fea520b --- /dev/null +++ b/src/utils/audio_buffer.h @@ -0,0 +1,58 @@ +/** + * @file utils/audio_buffer.h + * @author Martin Pulec + */ +/* + * 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_ */ +