From 01e5128507341ac2434e19b3542dbf2029d5e30c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Aug 2016 12:51:15 +0100 Subject: [PATCH 001/135] Add ability to set output format and output rate --- audio.h | 2 +- audio_alsa.c | 54 +++++++++++++++++++++++++++++++++++++++++--------- audio_ao.c | 6 ++++-- audio_dummy.c | 2 +- audio_pipe.c | 2 +- audio_pulse.c | 6 ++++-- audio_sndio.c | 2 ++ audio_stdout.c | 2 +- common.h | 2 ++ player.c | 2 +- shairport.c | 2 ++ 11 files changed, 64 insertions(+), 18 deletions(-) diff --git a/audio.h b/audio.h index 4c86d7db..b93e48b5 100644 --- a/audio.h +++ b/audio.h @@ -19,7 +19,7 @@ typedef struct { // at end of program void (*deinit)(void); - void (*start)(int sample_rate); + void (*start)(int sample_rate, int sample_format); // block of samples void (*play)(short buf[], int samples); diff --git a/audio_alsa.c b/audio_alsa.c index ad5b0c74..db864f65 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -38,7 +38,7 @@ static void help(void); static int init(int argc, char **argv); static void deinit(void); -static void start(int sample_rate); +static void start(int i_sample_rate, int i_sample_format); static void play(short buf[], int samples); static void stop(void); static void flush(void); @@ -68,6 +68,7 @@ audio_output audio_alsa = { static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; static unsigned int desired_sample_rate; +static snd_pcm_format_t sample_format; static snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; @@ -205,6 +206,35 @@ static int init(int argc, char **argv) { die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\""); } + /* Get the output format, using the same names as aplay does -- S16_LE, S32_LE to begin with*/ + if (config_lookup_string(config.cfg, "alsa.output_format", &str)) { + if (strcasecmp(str, "S16_LE") == 0) + config.output_format = SND_PCM_FORMAT_S16_LE; + else if (strcasecmp(str, "S24_LE") == 0) + config.output_format = SND_PCM_FORMAT_S24_LE; + else if (strcasecmp(str, "S32_LE") == 0) + config.output_format = SND_PCM_FORMAT_S32_LE; + else + die("Invalid output format \"%s\". It should be \"S16_LE\", \"S24_LE\" or \"S32_LE\"",str); + } + + /* Get the output rate, which must be a multiple of 44,100*/ + if (config_lookup_int(config.cfg, "alsa.output_rate", + &value)) { + debug(1,"Value read for output rate is %d.",value); + switch(value) { + case 44100: + case 88200: + case 176400: + case 352800: + config.output_rate = value; + break; + default : + die("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800",value); + } + } + + /* Get the use_mmap_if_available setting. */ if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) { if (strcasecmp(str, "no") == 0) @@ -424,11 +454,10 @@ int open_alsa_device(void) { alsa_out_dev, snd_strerror(ret)); } - ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, - SND_PCM_FORMAT_S16); + ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sample_format); if (ret < 0) { - die("audio_alsa: Sample format not available for device \"%s\": %s", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Sample format %d not available for device \"%s\": %s", + sample_format, alsa_out_dev, snd_strerror(ret)); } ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2); @@ -640,11 +669,18 @@ int open_alsa_device(void) { return (0); } -static void start(int sample_rate) { +static void start(int i_sample_rate, int i_sample_format) { // debug(2,"audio_alsa start called."); - if (sample_rate != 44100) - die("Unexpected sample rate %d -- only 44,100 supported!", sample_rate); - desired_sample_rate = sample_rate; // must be a variable + if (i_sample_rate==0) + desired_sample_rate = 44100; // default + else + desired_sample_rate = i_sample_rate; // must be a variable + + if (i_sample_format==0) + sample_format = SND_PCM_FORMAT_S16_LE; // default + else + sample_format = i_sample_format; + } int delay(long* the_delay) { diff --git a/audio_ao.c b/audio_ao.c index 41b823b0..b6a09a07 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -134,9 +134,11 @@ static void deinit(void) { ao_shutdown(); } -static void start(int sample_rate) { - if (sample_rate != 44100) +static void start(int sample_rate, int sample_format) { + if (sample_rate != 0) die("unexpected sample rate!"); + if (sample_format != 0) + die("unexpected sample format!"); } static void play(short buf[], int samples) { ao_play(dev, (char *)buf, samples * 4); } diff --git a/audio_dummy.c b/audio_dummy.c index 659a7202..3f24dadd 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -41,7 +41,7 @@ static int init(int argc, char **argv) { return 0; } static void deinit(void) {} -static void start(int sample_rate) { +static void start(int sample_rate, int sample_format) { Fs = sample_rate; starttime = 0; samples_played = 0; diff --git a/audio_pipe.c b/audio_pipe.c index 17bf3103..dfde2a92 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -43,7 +43,7 @@ static int fd = -1; char *pipename = NULL; -static void start(int sample_rate) { +static void start(int sample_rate, int sample_format) { // this will leave fd as -1 if a reader hasn't been attached fd = open(pipename, O_WRONLY | O_NONBLOCK); } diff --git a/audio_pulse.c b/audio_pulse.c index 6fa94e97..e4118259 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -110,9 +110,11 @@ static void deinit(void) { pa_dev = NULL; } -static void start(int sample_rate) { - if (sample_rate != 44100) +static void start(int sample_rate, int sample_format) { + if (sample_rate != 0) die("unexpected sample rate!"); + if (sample_format != 0) + die("unexpected sample format!"); } static void play(short buf[], int samples) { diff --git a/audio_sndio.c b/audio_sndio.c index 7093261e..1d2e1532 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -56,6 +56,8 @@ static void deinit(void) { sio_close(sio); } static void start(int sample_rate) { if (sample_rate != par.rate) die("unexpected sample rate!"); + if (sample_format != 0) + die("unexpected sample format!"); sio_start(sio); } diff --git a/audio_stdout.c b/audio_stdout.c index b8b0aa53..61b8e6b1 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -38,7 +38,7 @@ static int fd = -1; -static void start(int sample_rate) { +static void start(int sample_rate int sample_format) { fd = STDOUT_FILENO; } diff --git a/common.h b/common.h index 32ee301d..625108b0 100644 --- a/common.h +++ b/common.h @@ -100,6 +100,8 @@ typedef struct { // audio backend buffer -- the DAC buffer for ALSA long audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency there might be in the audio path uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's native range. + int output_format; + int output_rate; } shairport_cfg; // true if Shairport Sync is supposed to be sending output to the output device, false otherwise diff --git a/player.c b/player.c index 97eccfb2..3b86a373 100644 --- a/player.c +++ b/player.c @@ -1716,7 +1716,7 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { #endif if (rc) debug(1, "Error initialising condition variable."); - config.output->start(sampling_rate); + config.output->start(config.output_rate,config.output_format); size_t size = (PTHREAD_STACK_MIN + 256 * 1024); pthread_attr_t tattr; pthread_attr_init(&tattr); diff --git a/shairport.c b/shairport.c index f725d607..1b97099b 100644 --- a/shairport.c +++ b/shairport.c @@ -1016,6 +1016,8 @@ int main(int argc, char **argv) { debug(1, "playback_mode is %d (0-stereo, 1-mono).", config.playback_mode); debug(1, "disable_synchronization is %d.", config.no_sync); debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); + debug(1, "output_rate is %d (0 means 44,100).", config.output_rate); + debug(1, "output_format is %d (0 means S16_LE).", config.output_format); debug(1, "audio backend desired buffer length is %d.", config.audio_backend_buffer_desired_length); debug(1, "audio backend latency offset is %d.", config.audio_backend_latency_offset); From 63461d43836c0412b2a2dd696079867f326b96f0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Aug 2016 16:46:27 +0100 Subject: [PATCH 002/135] fix tiny syntax error --- audio_stdout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_stdout.c b/audio_stdout.c index 61b8e6b1..cb11a99a 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -38,7 +38,7 @@ static int fd = -1; -static void start(int sample_rate int sample_format) { +static void start(int sample_rate, int sample_format) { fd = STDOUT_FILENO; } From 0c72f73d774a47c5e3b9fabc2b193b3458136f39 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Aug 2016 16:46:47 +0100 Subject: [PATCH 003/135] Great things happening... --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 629ce995..1f542178 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.8.4.7], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.4.8], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) @@ -194,7 +194,7 @@ AC_ARG_WITH(ao, [ --with-ao = choose AO (Audio Output?) API support. N.B. no sy AC_MSG_RESULT(>>Including an AO back end. N.B. no synchronisation -- so underflow or overflow is inevitable!) HAS_AO=1 AC_DEFINE([CONFIG_AO], 1, [Needed by the compiler.]) - AC_CHECK_LIB([ao], [ao_initialize], , AC_MSG_ERROR(AO support requires the ao library!))], ) + AC_CHECK_LIB([ao], [ao_initialize], , AC_MSG_ERROR(AO support requires the ao library -- libao-dev suggested))], ) AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"]) # Look for pulseaudio flag From 7bd82dad0eb7ad8272c29d2e64af2dce4c770507 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Aug 2016 18:25:59 +0100 Subject: [PATCH 004/135] Begin to add in calculations for expanded back end --- player.c | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/player.c b/player.c index 3b86a373..d601fe80 100644 --- a/player.c +++ b/player.c @@ -66,6 +66,7 @@ #include "rtsp.h" #include "alac.h" +#include // parameters from the source static unsigned char *aesiv; @@ -75,9 +76,11 @@ static AES_KEY aes; static int sampling_rate, frame_size; static int bytes_per_audio_frame = 4; +static int bytes_per_output_audio_frame; + // The maximum frame size change there can be is +/- 1; -static int max_frame_size_change = 1; +static int max_frame_size_change; // #define FRAME_BYTES(frame_size) (4 * frame_size) // maximal resampling shift - conservative //#define OUTFRAME_BYTES(frame_size) (4 * (frame_size + 3)) @@ -994,8 +997,28 @@ static void *player_thread_func(void *arg) { pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)&itr); pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)&itr); - session_corrections = 0; - play_segment_reference_frame = 0; // zero signals that we are not in a play segment + session_corrections = 0; + play_segment_reference_frame = 0; // zero signals that we are not in a play segment + + int output_sample_ratio = 1; + if (config.output_rate!=0) + output_sample_ratio = config.output_rate/44100; + + max_frame_size_change = 1*sample_ratio; + bytes_per_output_audio_frame = 4; + + + + switch (config.output_format) { + case SND_PCM_FORMAT_S24_LE: + bytes_per_output_audio_frame=6; + break; + case SND_PCM_FORMAT_S32_LE: + bytes_per_output_audio_frame=8; + break; + } + + // check that there are enough buffers to accommodate the desired latency and the latency offset int maximum_latency = config.latency+config.audio_backend_latency_offset; @@ -1034,13 +1057,16 @@ static void *player_thread_func(void *arg) { initstate(time(NULL), rnstate, 256); signed short *inbuf, *outbuf, *silence; - outbuf = malloc(bytes_per_audio_frame*(frame_size+max_frame_size_change)); + + // We might need an output buffer and a buffer of silence. + // The size of these dependents on the number of frames, the size of each frame and the maximum size change + outbuf = malloc(bytes_per_audio_frame*(frame_size*output_sample_ratio+max_frame_size_change)); if (outbuf==NULL) debug(1,"Failed to allocate memory for an output buffer."); silence = malloc(bytes_per_audio_frame*frame_size); if (silence==NULL) debug(1,"Failed to allocate memory for a silence buffer."); - memset(silence, 0, bytes_per_audio_frame*frame_size); + memset(silence, 0, bytes_per_audio_frame*frame_size*output_sample_ratio); late_packet_message_sent = 0; first_packet_timestamp = 0; missing_packets = late_packets = too_late_packets = resend_requests = 0; @@ -1242,7 +1268,7 @@ static void *player_thread_func(void *arg) { if (config.no_sync!=0) amount_to_stuff = 0 ; // no stuffing if it's been disabled - if ((amount_to_stuff == 0) && (fix_volume == 0x10000)) { + if ((amount_to_stuff == 0) && (fix_volume == 0x10000) && ((config.output_rate==0) || (config.output_rate==44100)) && ((config.output_format==0) || (config.output_format==SND_PCM_FORMAT_S16_LE))) { // if no stuffing needed and no volume adjustment, then // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the // output device... @@ -1255,6 +1281,8 @@ static void *player_thread_func(void *arg) { config.output->play(inframe->data, inframe->length); } } else { + + #ifdef HAVE_LIBSOXR switch (config.packet_stuffing) { case ST_basic: From ae036720b47bd50775b3e09f74243df3c351fa3d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 22 Aug 2016 09:09:17 +0100 Subject: [PATCH 005/135] Fix a comment typo. --- player.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/player.c b/player.c index d601fe80..2083f005 100644 --- a/player.c +++ b/player.c @@ -507,6 +507,7 @@ static abuf_t *buffer_get_frame(void) { shutdown_requested = 1; } } + int rco = get_requested_connection_state_to_output(); if (connection_state_to_output != rco) { @@ -530,6 +531,7 @@ static abuf_t *buffer_get_frame(void) { flush_requested = 0; } pthread_mutex_unlock(&flush_mutex); + uint32_t flush_limit = 0; if (ab_synced) { do { @@ -575,7 +577,7 @@ static abuf_t *buffer_get_frame(void) { if (curframe->ready) { notified_buffer_empty=0; // at least one buffer now -- diagnostic only. if (ab_buffering) { // if we are getting packets but not yet forwarding them to the player - int have_sent_prefiller_silence; // set true when we have send some silent frames to the DAC + int have_sent_prefiller_silence; // set true when we have sent some silent frames to the DAC uint32_t reference_timestamp; uint64_t reference_timestamp_time,remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); From 716d7501363795bafa6cd199b2e496bd1c3bcbbf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 24 Aug 2016 16:29:39 +0100 Subject: [PATCH 006/135] Start the great trek --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 1f542178..5ba84ebc 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.8.4.8], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0PB1], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From a1519f2abe209a456ff12f2afc9a99d5ce6357c0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 24 Aug 2016 16:30:02 +0100 Subject: [PATCH 007/135] Make it compile -- untested --- common.h | 23 ++++++++++++++++++++++- player.c | 14 +++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/common.h b/common.h index 625108b0..d67100e7 100644 --- a/common.h +++ b/common.h @@ -48,6 +48,27 @@ enum playback_mode_type { ST_mono, } playback_mode_type; + +// the following enum must _exactly match the snd_pcm_format_t definition in the alsa pcm.h file. + +enum sps_format_t { + SPS_FORMAT_UNKNOWN = -1, SPS_FORMAT_S8 = 0, SPS_FORMAT_U8, SPS_FORMAT_S16_LE, + SPS_FORMAT_S16_BE, SPS_FORMAT_U16_LE, SPS_FORMAT_U16_BE, SPS_FORMAT_S24_LE, + SPS_FORMAT_S24_BE, SPS_FORMAT_U24_LE, SPS_FORMAT_U24_BE, SPS_FORMAT_S32_LE, + SPS_FORMAT_S32_BE, SPS_FORMAT_U32_LE, SPS_FORMAT_U32_BE, SPS_FORMAT_FLOAT_LE, + SPS_FORMAT_FLOAT_BE, SPS_FORMAT_FLOAT64_LE, SPS_FORMAT_FLOAT64_BE, SPS_FORMAT_IEC958_SUBFRAME_LE, + SPS_FORMAT_IEC958_SUBFRAME_BE, SPS_FORMAT_MU_LAW, SPS_FORMAT_A_LAW, SPS_FORMAT_IMA_ADPCM, + SPS_FORMAT_MPEG, SPS_FORMAT_GSM, SPS_FORMAT_SPECIAL = 31, SPS_FORMAT_S24_3LE = 32, + SPS_FORMAT_S24_3BE, SPS_FORMAT_U24_3LE, SPS_FORMAT_U24_3BE, SPS_FORMAT_S20_3LE, + SPS_FORMAT_S20_3BE, SPS_FORMAT_U20_3LE, SPS_FORMAT_U20_3BE, SPS_FORMAT_S18_3LE, + SPS_FORMAT_S18_3BE, SPS_FORMAT_U18_3LE, SPS_FORMAT_U18_3BE, SPS_FORMAT_G723_24, + SPS_FORMAT_G723_24_1B, SPS_FORMAT_G723_40, SPS_FORMAT_G723_40_1B, SPS_FORMAT_DSD_U8, + SPS_FORMAT_DSD_U16_LE, SPS_FORMAT_DSD_U32_LE, SPS_FORMAT_DSD_U16_BE, SPS_FORMAT_DSD_U32_BE, + SPS_FORMAT_LAST = SPS_FORMAT_DSD_U32_BE, SPS_FORMAT_S16 = SPS_FORMAT_S16_LE, SPS_FORMAT_U16 = SPS_FORMAT_U16_LE, SPS_FORMAT_S24 = SPS_FORMAT_S24_LE, + SPS_FORMAT_U24 = SPS_FORMAT_U24_LE, SPS_FORMAT_S32 = SPS_FORMAT_S32_LE, SPS_FORMAT_U32 = SPS_FORMAT_U32_LE, SPS_FORMAT_FLOAT = SPS_FORMAT_FLOAT_LE, + SPS_FORMAT_FLOAT64 = SPS_FORMAT_FLOAT64_LE, SPS_FORMAT_IEC958_SUBFRAME = SPS_FORMAT_IEC958_SUBFRAME_LE +} sps_format_t; + typedef struct { config_t *cfg; char *password; @@ -100,7 +121,7 @@ typedef struct { // audio backend buffer -- the DAC buffer for ALSA long audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency there might be in the audio path uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's native range. - int output_format; + enum sps_format_t output_format; int output_rate; } shairport_cfg; diff --git a/player.c b/player.c index 2083f005..da1a312c 100644 --- a/player.c +++ b/player.c @@ -66,7 +66,6 @@ #include "rtsp.h" #include "alac.h" -#include // parameters from the source static unsigned char *aesiv; @@ -1006,21 +1005,18 @@ static void *player_thread_func(void *arg) { if (config.output_rate!=0) output_sample_ratio = config.output_rate/44100; - max_frame_size_change = 1*sample_ratio; + max_frame_size_change = 1*output_sample_ratio; // we add or subtract one frame at the nominal rate, multiply it by the frame ratio. bytes_per_output_audio_frame = 4; - - switch (config.output_format) { - case SND_PCM_FORMAT_S24_LE: + case SPS_FORMAT_S24_LE: bytes_per_output_audio_frame=6; break; - case SND_PCM_FORMAT_S32_LE: + case SPS_FORMAT_S32_LE: bytes_per_output_audio_frame=8; break; } - - + // check that there are enough buffers to accommodate the desired latency and the latency offset int maximum_latency = config.latency+config.audio_backend_latency_offset; @@ -1270,7 +1266,7 @@ static void *player_thread_func(void *arg) { if (config.no_sync!=0) amount_to_stuff = 0 ; // no stuffing if it's been disabled - if ((amount_to_stuff == 0) && (fix_volume == 0x10000) && ((config.output_rate==0) || (config.output_rate==44100)) && ((config.output_format==0) || (config.output_format==SND_PCM_FORMAT_S16_LE))) { + if ((amount_to_stuff == 0) && (fix_volume == 0x10000) && ((config.output_rate==0) || (config.output_rate==44100)) && ((config.output_format==0) || (config.output_format==SPS_FORMAT_S16_LE))) { // if no stuffing needed and no volume adjustment, then // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the // output device... From 945483d9c34eaa39b5911fa1501a5741583dee11 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Aug 2016 21:38:24 +0100 Subject: [PATCH 008/135] Merge support of apple's ALAC decoder --- Makefile.am | 5 +++ apple_alac.cpp | 58 +++++++++++++++++++++++++++ apple_alac.h | 21 ++++++++++ common.c | 1 - common.h | 9 +++++ configure.ac | 19 ++++++++- player.c | 78 +++++++++++++++++++++++++++++-------- rtsp.c | 2 +- scripts/shairport-sync.conf | 1 + shairport.c | 19 +++++++++ 10 files changed, 194 insertions(+), 19 deletions(-) create mode 100644 apple_alac.cpp create mode 100644 apple_alac.h diff --git a/Makefile.am b/Makefile.am index 3ad62609..ff677f80 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,6 +4,11 @@ bin_PROGRAMS = shairport-sync shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c AM_CFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" +AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" + +if USE_APPLE_ALAC + shairport_sync_SOURCES += apple_alac.cpp +endif if USE_CUSTOMPIDDIR AM_CFLAGS+= \ diff --git a/apple_alac.cpp b/apple_alac.cpp new file mode 100644 index 00000000..5c633fb9 --- /dev/null +++ b/apple_alac.cpp @@ -0,0 +1,58 @@ +#include + +// these are headers for the ALAC decoder, utilities and endian utilities +#include +#include +#include + +#include "config.h" +#include "apple_alac.h" + +typedef struct magicCookie { + ALACSpecificConfig config; + ALACAudioChannelLayout channelLayoutInfo; // seems to be unused +} magicCookie; + +magicCookie cookie; +ALACDecoder * theDecoder; + +extern "C" int apple_alac_init(int frame_size,int sample_size,int sample_rate) { + + memset(&cookie,0,sizeof(magicCookie)); + + #define CHANNELS_PER_FRAME 2 + + //create a magic cookie for the decoder + + cookie.config.frameLength = Swap32NtoB(frame_size); + cookie.config.compatibleVersion = 0; + cookie.config.bitDepth = sample_size; + cookie.config.pb = 40; + cookie.config.mb = 10; + cookie.config.kb = 14; + cookie.config.numChannels = CHANNELS_PER_FRAME; + cookie.config.maxRun = Swap16NtoB(255); + cookie.config.maxFrameBytes = 0; + cookie.config.avgBitRate = 0; + cookie.config.sampleRate = Swap32NtoB(sample_rate); + + theDecoder = new ALACDecoder; + theDecoder->Init(&cookie, sizeof(magicCookie)); + + return 0; +} + +extern "C" int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength, unsigned char *dest, int *outsize) +{ + uint32_t numFrames = 0; + BitBuffer theInputBuffer; + BitBufferInit(&theInputBuffer, sampleBuffer, bufferLength); + theDecoder->Decode(&theInputBuffer, dest, Swap32BtoN(cookie.config.frameLength), CHANNELS_PER_FRAME, &numFrames); + *outsize = numFrames; + return 0; +} + +extern "C" int apple_alac_terminate() { + delete(theDecoder); +} + diff --git a/apple_alac.h b/apple_alac.h new file mode 100644 index 00000000..61eb685c --- /dev/null +++ b/apple_alac.h @@ -0,0 +1,21 @@ +#ifndef __APPLE_ALAC_H +#define __APPLE_ALAC_H + +#include +#include "config.h" + +#ifdef __cplusplus + #define EXTERNC extern "C" +#else + #define EXTERNC +#endif + + +EXTERNC int apple_alac_init(int frame_size,int sample_size,int sample_rate); +EXTERNC int apple_alac_terminate(); +EXTERNC int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength, unsigned char *dest, int *outsize); + +#undef EXTERNC + +#endif /* __APPLE_ALAC_H */ + diff --git a/common.c b/common.c index bf721273..39a936f5 100644 --- a/common.c +++ b/common.c @@ -67,7 +67,6 @@ #endif #endif -#include "common.h" #include // true if Shairport Sync is supposed to be sending output to the output device, false otherwise diff --git a/common.h b/common.h index d67100e7..e2ab47b9 100644 --- a/common.h +++ b/common.h @@ -46,8 +46,15 @@ enum stuffing_type { enum playback_mode_type { ST_stereo = 0, ST_mono, + ST_reverse_stereo, + ST_left_only, + ST_right_only, } playback_mode_type; +enum decoders_supported_type { + decoder_hammerton = 0, + decoder_apple_alac, +} decoders_supported_type; // the following enum must _exactly match the snd_pcm_format_t definition in the alsa pcm.h file. @@ -112,6 +119,8 @@ typedef struct { int cmd_blocking; int tolerance; // allow this much drift before attempting to correct it enum stuffing_type packet_stuffing; + int decoders_supported; + int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by David Hammerton char *pidfile; // char *logfile; // char *errfile; diff --git a/configure.ac b/configure.ac index 5ba84ebc..51fa97d8 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0PB1], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0PB2], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) @@ -20,6 +20,7 @@ with_os_type=`echo ${with_os_type} | tr '[[:upper:]]' '[[:lower:]]' ` # Checks for programs. AC_PROG_CC +AC_PROG_CXX AC_PROG_INSTALL PKG_PROG_PKG_CONFIG([0.9.0]) @@ -93,6 +94,22 @@ AC_ARG_WITH([configfiles], [ --with-configfiles = install configuration files during a make install ], ,[with_configfiles=yes]) AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "xyes"]) +# Look for Apple ALAC flag +AC_ARG_WITH(apple-alac, [ --with-apple-alac = include support for the Apple ALAC decoder], + [AC_MSG_RESULT(>>Including the Apple ALAC Decoder) + HAS_APPLE_ALAC=1 + AM_CONDITIONAL([USE_APPLE_ALAC], [test 0]) + AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Needed by the compiler.]) + if test "x${with_pkg_config}" = xyes ; then + PKG_CHECK_MODULES( + [ALAC], [alac], + [LIBS="${ALAC_LIBS} ${LIBS}" + AC_DEFINE([HAVE_APPLE_ALAC],[1],[Define to 1 if you are using the Apple ALAC Decoder])]) + else + AC_CHECK_LIB([alac], [BitBufferInit], , AC_MSG_ERROR(Apple ALAC Decoder support requires the alac library!)) + AC_DEFINE([HAVE_APPLE_ALAC],[1],[Define to 1 if you have the Apple ALAC library]) + fi ]) +AM_CONDITIONAL([USE_APPLE_ALAC], [test "x$HAS_APPLE_ALAC" = "x1"]) # Look for piddir flag AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [ diff --git a/player.c b/player.c index da1a312c..7572b899 100644 --- a/player.c +++ b/player.c @@ -67,6 +67,8 @@ #include "alac.h" +#include "apple_alac.h" + // parameters from the source static unsigned char *aesiv; #ifdef HAVE_LIBSSL @@ -100,6 +102,7 @@ static alac_file *decoder_info; static int late_packet_message_sent; static uint64_t packet_count = 0; static int32_t last_seqno_read; +static int decoder_in_use = 0; // interthread variables static int fix_volume = 0x10000; @@ -240,7 +243,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { } unsigned char packet[MAX_PACKET]; unsigned char packetp[MAX_PACKET]; - // assert(len <= MAX_PACKET); + assert(len <= MAX_PACKET); int reply = 0; //everything okay int outsize=bytes_per_audio_frame*(*destlen); // the size the output should be, in bytes int toutsize = outsize; @@ -256,15 +259,49 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { AES_cbc_encrypt(buf, packet, aeslen, &aes, iv, AES_DECRYPT); #endif memcpy(packet + aeslen, buf + aeslen, len - aeslen); - alac_decode_frame(decoder_info, packet, dest, &outsize); +#ifdef HAVE_APPLE_ALAC + if (config.use_apple_decoder) { + if (decoder_in_use!=1<toutsize) { debug(2,"Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- truncated, but buffer overflow possible! Encrypted = %d.",outsize, toutsize, encrypted); reply = -1; // output packet is the wrong size - } + } + *destlen = outsize / bytes_per_audio_frame; if ((outsize % bytes_per_audio_frame)!=0) debug(1,"Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) and the audio frame size (%d).",*destlen,outsize,bytes_per_audio_frame); @@ -298,10 +335,20 @@ static int init_decoder(int32_t fmtp[12]) { alac->setinfo_86 = fmtp[10]; alac->setinfo_8a_rate = fmtp[11]; alac_allocate_buffers(alac); + +#ifdef HAVE_APPLE_ALAC + apple_alac_init(frame_size,sample_size,sampling_rate); +#endif + return 0; } -static void free_decoder(void) { alac_free(decoder_info); } +static void terminate_decoders(void) { + alac_free(decoder_info); +#ifdef HAVE_APPLE_ALAC + apple_alac_terminate(); +#endif +} static void init_buffer(void) { int i; @@ -506,7 +553,6 @@ static abuf_t *buffer_get_frame(void) { shutdown_requested = 1; } } - int rco = get_requested_connection_state_to_output(); if (connection_state_to_output != rco) { @@ -530,7 +576,6 @@ static abuf_t *buffer_get_frame(void) { flush_requested = 0; } pthread_mutex_unlock(&flush_mutex); - uint32_t flush_limit = 0; if (ab_synced) { do { @@ -1000,7 +1045,7 @@ static void *player_thread_func(void *arg) { session_corrections = 0; play_segment_reference_frame = 0; // zero signals that we are not in a play segment - + int output_sample_ratio = 1; if (config.output_rate!=0) output_sample_ratio = config.output_rate/44100; @@ -1112,20 +1157,21 @@ static void *player_thread_func(void *arg) { while (!please_stop) { abuf_t *inframe = buffer_get_frame(); if (inframe) { - if (inframe->data) { + inbuf = inframe->data; + if (inbuf) { play_number++; // if it's a supplied silent frame, let us know... if (inframe->timestamp == 0) { // debug(1,"Player has a supplied silent frame."); last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); // manage the packet out of sequence minder - if (inframe->data==NULL) - debug(1,"NULL inframe->data to play -- skipping it."); + if (inbuf==NULL) + debug(1,"NULL inbuf to play -- skipping it."); else { if (inframe->length==0) - debug(1,"empty frame to play -- skipping it."); + debug(1,"empty frame to play -- skipping it (1)."); else - config.output->play(inframe->data, inframe->length); + config.output->play(inbuf, inframe->length); } } else { // We have a frame of data. We need to see if we want to add or remove a frame from it to @@ -1279,8 +1325,8 @@ static void *player_thread_func(void *arg) { config.output->play(inframe->data, inframe->length); } } else { - - + + #ifdef HAVE_LIBSOXR switch (config.packet_stuffing) { case ST_basic: @@ -1766,7 +1812,7 @@ void player_stop(pthread_t *player_thread) { #endif command_stop(); free_buffer(); - free_decoder(); + terminate_decoders(); int rc = pthread_cond_destroy(&flowcontrol); if (rc) debug(1, "Error destroying condition variable."); diff --git a/rtsp.c b/rtsp.c index d2c71497..3f6d9646 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1882,7 +1882,7 @@ void rtsp_listen_loop(void) { freeaddrinfo(info); if (!nsock) - die("Could not establish a service on port %d -- program terminating. Is another Shairport Sync running on this device?",config.port); + die("Could not establish a service on port %d -- program terminating. Is another instance of Shairport Sync running on this device?",config.port); int maxfd = -1; fd_set fds; diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index bb737ee8..3d7918d0 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -62,6 +62,7 @@ alsa = // audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. // disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". // period_size = ; // Use this optional advanced setting to set the alsa period size near to this value +// buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value // use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" }; diff --git a/shairport.c b/shairport.c index 1b97099b..2ccaa9fb 100644 --- a/shairport.c +++ b/shairport.c @@ -441,6 +441,19 @@ int parse_options(int argc, char **argv) { config.volume_range_db = value; } + /* Get the use_apple_decoder setting. */ + if (config_lookup_string(config.cfg, "general.use_apple_decoder", &str)) { + if (strcasecmp(str, "no") == 0) + config.use_apple_decoder = 0; + else if (strcasecmp(str, "yes") == 0) { + if ((config.decoders_supported & 1<= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { @@ -1023,6 +1040,8 @@ int main(int argc, char **argv) { debug(1, "audio backend latency offset is %d.", config.audio_backend_latency_offset); debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", config.volume_range_db); debug(1, "zeroconf regtype is \"%s\".", config.regtype); + debug(1, "decoders_supported field is %d.", config.decoders_supported); + debug(1, "use_apple_decoder is %d.", config.use_apple_decoder); char *realConfigPath = realpath(config.configfile,NULL); if (realConfigPath) { From 72f1c3b5aed6bab85245eab0c874711a7a56ba5e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 30 Aug 2016 22:01:05 +0100 Subject: [PATCH 009/135] Make the decoder choice a bit more general, highlight names of input format parameters --- player.c | 111 ++++++++++++++++++++++++++---------- scripts/shairport-sync.conf | 2 + shairport.c | 12 ++-- 3 files changed, 88 insertions(+), 37 deletions(-) diff --git a/player.c b/player.c index 7572b899..9cfda6d1 100644 --- a/player.c +++ b/player.c @@ -74,17 +74,17 @@ static unsigned char *aesiv; #ifdef HAVE_LIBSSL static AES_KEY aes; #endif -static int sampling_rate, frame_size; +static int input_sample_rate, input_bit_depth, input_num_channels, max_frames_per_packet; -static int bytes_per_audio_frame = 4; -static int bytes_per_output_audio_frame; +static int input_bytes_per_frame = 4; +static int output_bytes_per_frame; // The maximum frame size change there can be is +/- 1; static int max_frame_size_change; -// #define FRAME_BYTES(frame_size) (4 * frame_size) +// #define FRAME_BYTES(max_frames_per_packet) (4 * max_frames_per_packet) // maximal resampling shift - conservative -//#define OUTFRAME_BYTES(frame_size) (4 * (frame_size + 3)) +//#define OUTFRAME_BYTES(max_frames_per_packet) (4 * (max_frames_per_packet + 3)) #ifdef HAVE_LIBPOLARSSL static aes_context dctx; @@ -245,7 +245,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { unsigned char packetp[MAX_PACKET]; assert(len <= MAX_PACKET); int reply = 0; //everything okay - int outsize=bytes_per_audio_frame*(*destlen); // the size the output should be, in bytes + int outsize=input_bytes_per_frame*(*destlen); // the size the output should be, in bytes int toutsize = outsize; if (encrypted) { @@ -302,30 +302,79 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { reply = -1; // output packet is the wrong size } - *destlen = outsize / bytes_per_audio_frame; - if ((outsize % bytes_per_audio_frame)!=0) - debug(1,"Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) and the audio frame size (%d).",*destlen,outsize,bytes_per_audio_frame); + *destlen = outsize / input_bytes_per_frame; + if ((outsize % input_bytes_per_frame)!=0) + debug(1,"Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) and the audio frame size (%d).",*destlen,outsize,input_bytes_per_frame); return reply; } static int init_decoder(int32_t fmtp[12]) { + +// This is a guess, but the format of the fmtp looks identical to the format of an ALACSpecificCOnfig +// which is detailed in the file ALACMagicCookieDescription.txt in the Apple ALAC sample implementation +// Here it is: + +/* + struct ALACSpecificConfig (defined in ALACAudioTypes.h) + abstract This struct is used to describe codec provided information about the encoded Apple Lossless bitstream. + It must accompany the encoded stream in the containing audio file and be provided to the decoder. + + field frameLength uint32_t indicating the frames per packet when no explicit frames per packet setting is + present in the packet header. The encoder frames per packet can be explicitly set + but for maximum compatibility, the default encoder setting of 4096 should be used. + + field compatibleVersion uint8_t indicating compatible version, + value must be set to 0 + + field bitDepth uint8_t describes the bit depth of the source PCM data (maximum value = 32) + + field pb uint8_t currently unused tuning parameter. + value should be set to 40 + + field mb uint8_t currently unused tuning parameter. + value should be set to 14 + + field kb uint8_t currently unused tuning parameter. + value should be set to 10 + + field numChannels uint8_t describes the channel count (1 = mono, 2 = stereo, etc...) + when channel layout info is not provided in the 'magic cookie', a channel count > 2 + describes a set of discreet channels with no specific ordering + + field maxRun uint16_t currently unused. + value should be set to 255 + + field maxFrameBytes uint32_t the maximum size of an Apple Lossless packet within the encoded stream. + value of 0 indicates unknown + + field avgBitRate uint32_t the average bit rate in bits per second of the Apple Lossless stream. + value of 0 indicates unknown + + field sampleRate uint32_t sample rate of the encoded stream + */ + +// We are going to go on that basis + + + alac_file *alac; - frame_size = fmtp[1]; // stereo samples - sampling_rate = fmtp[11]; + max_frames_per_packet = fmtp[1]; // number of audio frames per packet. - int sample_size = fmtp[3]; - if (sample_size != 16) - die("only 16-bit samples supported!"); + input_sample_rate = fmtp[11]; + input_num_channels = fmtp[7]; + input_bit_depth = fmtp[3]; + + input_bytes_per_frame = input_num_channels*(input_bit_depth+7)/8; - alac = alac_create(sample_size, 2); + alac = alac_create(input_bit_depth, input_num_channels); if (!alac) return 1; decoder_info = alac; - alac->setinfo_max_samples_per_frame = frame_size; + alac->setinfo_max_samples_per_frame = max_frames_per_packet; alac->setinfo_7a = fmtp[2]; - alac->setinfo_sample_size = sample_size; + alac->setinfo_sample_size = input_bit_depth; alac->setinfo_rice_historymult = fmtp[4]; alac->setinfo_rice_initialhistory = fmtp[5]; alac->setinfo_rice_kmodifier = fmtp[6]; @@ -337,7 +386,7 @@ static int init_decoder(int32_t fmtp[12]) { alac_allocate_buffers(alac); #ifdef HAVE_APPLE_ALAC - apple_alac_init(frame_size,sample_size,sampling_rate); + apple_alac_init(max_frames_per_packet,input_bit_depth,input_sample_rate); #endif return 0; @@ -353,7 +402,7 @@ static void terminate_decoders(void) { static void init_buffer(void) { int i; for (i = 0; i < BUFFER_FRAMES; i++) - audio_buffer[i].data = malloc(bytes_per_audio_frame*(frame_size+max_frame_size_change)); + audio_buffer[i].data = malloc(input_bytes_per_frame*(max_frames_per_packet+max_frame_size_change)); ab_resync(); } @@ -438,7 +487,7 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len) // pthread_mutex_unlock(&ab_mutex); if (abuf) { - int datalen = frame_size; + int datalen = max_frames_per_packet; if (alac_decode(abuf->data, &datalen, data, len)==0) { abuf->ready = 1; abuf->length = datalen; @@ -449,7 +498,7 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len) signed short *v = abuf->data; int i; int both; - for (i=frame_size;i;i--) { + for (i=max_frames_per_packet;i;i--) { int both = *v + *(v+1); if (both > INT16_MAX) { both = INT16_MAX; @@ -738,7 +787,7 @@ static abuf_t *buffer_get_frame(void) { debug(2,"frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",max_dac_delay, dac_delay); fs=0; } - if ((exact_frame_gap <= fs) || (exact_frame_gap <= frame_size * 2)) { + if ((exact_frame_gap <= fs) || (exact_frame_gap <= max_frames_per_packet * 2)) { fs = exact_frame_gap; // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, // with %d packets, ab_read is %04x, ab_write is @@ -751,11 +800,11 @@ static abuf_t *buffer_get_frame(void) { // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and dac_delay of %lld.",gross_frame_gap,dac_delay); // the fs (number of frames of silence to play) can be zero in the DAC doesn't start ouotputting frames for a while -- it could get loaded up but not start responding for many milliseconds. if (fs!=0) { - silence = malloc(bytes_per_audio_frame*fs); + silence = malloc(input_bytes_per_frame*fs); if (silence==NULL) debug(1,"Failed to allocate %d byte silence buffer.",fs); else { - memset(silence, 0, bytes_per_audio_frame*fs); + memset(silence, 0, input_bytes_per_frame*fs); // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, // with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write)); config.output->play(silence, fs); @@ -887,7 +936,7 @@ static abuf_t *buffer_get_frame(void) { if (!curframe->ready) { // debug(1, "Supplying a silent frame for frame %u", read); missing_packets++; - memset(curframe->data, 0, bytes_per_audio_frame*frame_size); + memset(curframe->data, 0, input_bytes_per_frame*max_frames_per_packet); curframe->timestamp = 0; } curframe->ready = 0; @@ -1051,14 +1100,14 @@ static void *player_thread_func(void *arg) { output_sample_ratio = config.output_rate/44100; max_frame_size_change = 1*output_sample_ratio; // we add or subtract one frame at the nominal rate, multiply it by the frame ratio. - bytes_per_output_audio_frame = 4; + output_bytes_per_frame = 4; switch (config.output_format) { case SPS_FORMAT_S24_LE: - bytes_per_output_audio_frame=6; + output_bytes_per_frame=6; break; case SPS_FORMAT_S32_LE: - bytes_per_output_audio_frame=8; + output_bytes_per_frame=8; break; } @@ -1103,13 +1152,13 @@ static void *player_thread_func(void *arg) { // We might need an output buffer and a buffer of silence. // The size of these dependents on the number of frames, the size of each frame and the maximum size change - outbuf = malloc(bytes_per_audio_frame*(frame_size*output_sample_ratio+max_frame_size_change)); + outbuf = malloc(input_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); if (outbuf==NULL) debug(1,"Failed to allocate memory for an output buffer."); - silence = malloc(bytes_per_audio_frame*frame_size); + silence = malloc(input_bytes_per_frame*max_frames_per_packet); if (silence==NULL) debug(1,"Failed to allocate memory for a silence buffer."); - memset(silence, 0, bytes_per_audio_frame*frame_size*output_sample_ratio); + memset(silence, 0, input_bytes_per_frame*max_frames_per_packet*output_sample_ratio); late_packet_message_sent = 0; first_packet_timestamp = 0; missing_packets = late_packets = too_late_packets = resend_requests = 0; diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 3d7918d0..0502720f 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -26,6 +26,8 @@ general = // volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". // playback_mode = "stereo"; // This can be "stereo" or "mono". Default is "stereo". +// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose +// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. }; // How to deal with metadata, including artwork diff --git a/shairport.c b/shairport.c index 2ccaa9fb..219f95e1 100644 --- a/shairport.c +++ b/shairport.c @@ -441,17 +441,17 @@ int parse_options(int argc, char **argv) { config.volume_range_db = value; } - /* Get the use_apple_decoder setting. */ - if (config_lookup_string(config.cfg, "general.use_apple_decoder", &str)) { - if (strcasecmp(str, "no") == 0) + /* Get the alac_decoder setting. */ + if (config_lookup_string(config.cfg, "alac_decoder", &str)) { + if (strcasecmp(str, "hammerton") == 0) config.use_apple_decoder = 0; - else if (strcasecmp(str, "yes") == 0) { + else if (strcasecmp(str, "apple") == 0) { if ((config.decoders_supported & 1< Date: Tue, 30 Aug 2016 22:09:01 +0100 Subject: [PATCH 010/135] Small improvements --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 51fa97d8..f3bf540c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0PB2], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0 (B3)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 0479994c4cea0a917468f79713583d58892d17d9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 30 Aug 2016 23:33:08 +0100 Subject: [PATCH 011/135] Apple ALAC decoder not working -- magic cookie contents are faulty. --- apple_alac.cpp | 30 ++++++++++++++---------------- apple_alac.h | 2 +- player.c | 4 ++-- shairport.c | 2 +- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/apple_alac.cpp b/apple_alac.cpp index 5c633fb9..3996cfe3 100644 --- a/apple_alac.cpp +++ b/apple_alac.cpp @@ -16,25 +16,23 @@ typedef struct magicCookie { magicCookie cookie; ALACDecoder * theDecoder; -extern "C" int apple_alac_init(int frame_size,int sample_size,int sample_rate) { +extern "C" int apple_alac_init(int32_t fmtp[12]) { memset(&cookie,0,sizeof(magicCookie)); - #define CHANNELS_PER_FRAME 2 - - //create a magic cookie for the decoder + //create a magic cookie for the decoder from the fmtp information. It seems to be in the same format as a simple magic cookie - cookie.config.frameLength = Swap32NtoB(frame_size); - cookie.config.compatibleVersion = 0; - cookie.config.bitDepth = sample_size; - cookie.config.pb = 40; - cookie.config.mb = 10; - cookie.config.kb = 14; - cookie.config.numChannels = CHANNELS_PER_FRAME; - cookie.config.maxRun = Swap16NtoB(255); - cookie.config.maxFrameBytes = 0; - cookie.config.avgBitRate = 0; - cookie.config.sampleRate = Swap32NtoB(sample_rate); + cookie.config.frameLength = Swap32NtoB(352); + cookie.config.compatibleVersion = fmtp[2]; // should be zero, uint8_t + cookie.config.bitDepth = fmtp[3]; // uint8_t expected to be 16 + cookie.config.pb = fmtp[4]; // uint8_t should be 40; + cookie.config.mb = fmtp[5]; // uint8_t should be 10; + cookie.config.kb = fmtp[6]; // uint8_t should be 14; + cookie.config.numChannels = fmtp[7]; // uint8_t expected to be 2 + cookie.config.maxRun = Swap16NtoB(fmtp[8]); // uint16_t expected to be 255 + cookie.config.maxFrameBytes = Swap32NtoB(fmtp[9]); // uint32_t should be 0; + cookie.config.avgBitRate = Swap32NtoB(fmtp[10]); // uint32_t should be 0;; + cookie.config.sampleRate = Swap32NtoB(fmtp[11]); // uint32_t expected to be 44100; theDecoder = new ALACDecoder; theDecoder->Init(&cookie, sizeof(magicCookie)); @@ -47,7 +45,7 @@ extern "C" int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t buf uint32_t numFrames = 0; BitBuffer theInputBuffer; BitBufferInit(&theInputBuffer, sampleBuffer, bufferLength); - theDecoder->Decode(&theInputBuffer, dest, Swap32BtoN(cookie.config.frameLength), CHANNELS_PER_FRAME, &numFrames); + theDecoder->Decode(&theInputBuffer, dest, Swap32BtoN(cookie.config.frameLength), cookie.config.numChannels, &numFrames); *outsize = numFrames; return 0; } diff --git a/apple_alac.h b/apple_alac.h index 61eb685c..11ed4ded 100644 --- a/apple_alac.h +++ b/apple_alac.h @@ -11,7 +11,7 @@ #endif -EXTERNC int apple_alac_init(int frame_size,int sample_size,int sample_rate); +EXTERNC int apple_alac_init(int32_t fmtp[12]); EXTERNC int apple_alac_terminate(); EXTERNC int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength, unsigned char *dest, int *outsize); diff --git a/player.c b/player.c index 9cfda6d1..933ee1bc 100644 --- a/player.c +++ b/player.c @@ -365,7 +365,7 @@ static int init_decoder(int32_t fmtp[12]) { input_num_channels = fmtp[7]; input_bit_depth = fmtp[3]; - input_bytes_per_frame = input_num_channels*(input_bit_depth+7)/8; + input_bytes_per_frame = input_num_channels*((input_bit_depth+7)/8); alac = alac_create(input_bit_depth, input_num_channels); if (!alac) @@ -386,7 +386,7 @@ static int init_decoder(int32_t fmtp[12]) { alac_allocate_buffers(alac); #ifdef HAVE_APPLE_ALAC - apple_alac_init(max_frames_per_packet,input_bit_depth,input_sample_rate); + apple_alac_init(fmtp); #endif return 0; diff --git a/shairport.c b/shairport.c index 219f95e1..afcccac3 100644 --- a/shairport.c +++ b/shairport.c @@ -442,7 +442,7 @@ int parse_options(int argc, char **argv) { } /* Get the alac_decoder setting. */ - if (config_lookup_string(config.cfg, "alac_decoder", &str)) { + if (config_lookup_string(config.cfg, "general.alac_decoder", &str)) { if (strcasecmp(str, "hammerton") == 0) config.use_apple_decoder = 0; else if (strcasecmp(str, "apple") == 0) { From 7f6f4f4478b142328b5fa9c542ecf2c72d1a033b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 2 Sep 2016 12:22:38 +0100 Subject: [PATCH 012/135] Begin to add support for monotonic 64-bit timestamps and frame sequence numbers --- player.c | 20 +++++++++++++++++++- player.h | 6 ++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/player.c b/player.c index 933ee1bc..f570ca5a 100644 --- a/player.c +++ b/player.c @@ -76,6 +76,9 @@ static AES_KEY aes; #endif static int input_sample_rate, input_bit_depth, input_num_channels, max_frames_per_packet; +static uint32_t timestamp_epoch; // zero means not initialised, could start at 2 or 1. + + static int input_bytes_per_frame = 4; static int output_bytes_per_frame; @@ -147,6 +150,19 @@ static audio_parameters audio_information; // stats static uint64_t missing_packets, late_packets, too_late_packets, resend_requests; +// make timestamps and seqnos definitely monotonic + +// add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33 frames and continue up to 2^64 frames +// which is about 4*10^8 * 1,000 seconds at 384,000 frames per second -- about 4 trillion seconds or over 100,000 years. +// also, it won't reach zero until then, if ever, so we can safely say that a null monotonic timestamp can mean something special +uint64_t monotonic_timestamp(uint32_t timestamp) { + +} + +// add an epoch to the seq_no. Uses the accompanying timstamp to detemine the correct epoch +uint64_t monotonic_seqno(uint16_t seq_no) { +} + static void ab_resync(void) { int i; for (i = 0; i < BUFFER_FRAMES; i++) { @@ -1086,6 +1102,7 @@ typedef struct stats { // statistics for running averages static void *player_thread_func(void *arg) { struct inter_threads_record itr; itr.please_stop = 0; + timestamp_epoch = 0; // indicate that the next timestamp will be the first one. // create and start the timing, control and audio receiver threads pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&itr); @@ -1816,7 +1833,7 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { #endif aesiv = stream->aesiv; } - init_decoder(stream->fmtp); + init_decoder(stream->fmtp); // this sets up incoming rate, bit depth, channels // must be after decoder init init_buffer(); please_stop = 0; @@ -1844,6 +1861,7 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { rc = pthread_attr_setstacksize(&tattr, size); if (rc) debug(1, "Error setting stack size for player_thread: %s", strerror(errno)); + timestamp_epoch = 0; pthread_create(player_thread, &tattr, player_thread_func, NULL); pthread_attr_destroy(&tattr); return 0; diff --git a/player.h b/player.h index 9012b0cb..d6a16e5a 100644 --- a/player.h +++ b/player.h @@ -22,4 +22,10 @@ void player_flush(uint32_t timestamp); void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len); +uint64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 2^33 frames and continue up to 2^64 frames +// which is about 4*10^8 * 1,000 seconds at 384,000 frames per second -- about 4 trillion seconds. +// assumes, without checking, that successive timestamps in a series always span an interval of less than one minute. + +uint64_t monotonic_seqno(uint16_t seq_no); // add an epoch to the seq_no. Uses the accompanying timstamp to detemine the correct epoch + #endif //_PLAYER_H From 1185a2aaa959060a3691d9f1fb9d8668ceb8aea3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 2 Sep 2016 13:48:39 +0100 Subject: [PATCH 013/135] Add more support for monotonic 64-bit timestamps and frame sequence numbers --- player.c | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/player.c b/player.c index f570ca5a..8f35c6d8 100644 --- a/player.c +++ b/player.c @@ -76,7 +76,8 @@ static AES_KEY aes; #endif static int input_sample_rate, input_bit_depth, input_num_channels, max_frames_per_packet; -static uint32_t timestamp_epoch; // zero means not initialised, could start at 2 or 1. +static uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval;// timestamp_epoch of zero means not initialised, could start at 2 or 1. + static int input_bytes_per_frame = 4; @@ -156,10 +157,47 @@ static uint64_t missing_packets, late_packets, too_late_packets, resend_requests // which is about 4*10^8 * 1,000 seconds at 384,000 frames per second -- about 4 trillion seconds or over 100,000 years. // also, it won't reach zero until then, if ever, so we can safely say that a null monotonic timestamp can mean something special uint64_t monotonic_timestamp(uint32_t timestamp) { - + uint64_t previous_value; + uint64_t return_value; + if (timestamp_epoch==0) { + if (timestamp>maximum_timestamp_interval) + timestamp_epoch=1; + else + timestamp_epoch=2; + previous_value = timestamp_epoch; + previous_value<<=32; + previous_value+=timestamp; + } else { + previous_value = timestamp_epoch; + previous_value<<=32; + previous_value+=last_timestamp; + if (timestampmaximum_timestamp_interval) + timestamp_epoch++; + } else { + // the incoming timestamp is greater than the last one. + // if the difference is more than a minute, assume it's really from the previous epoch + if ((timestamp-last_timestamp)>maximum_timestamp_interval) + timestamp_epoch--; + } + } + last_timestamp=timestamp; + return_value = timestamp_epoch; + return_value<<=32; + return_value+=timestamp; + if (previous_value>return_value) { + if ((previous_value-return_value)>maximum_timestamp_interval) + debug(1,"interval between successive rtptimes greater than allowed!"); + } else { + if ((return_value-previous_value)>maximum_timestamp_interval) + debug(1,"interval between successive rtptimes greater than allowed!"); + } + return return_value; } -// add an epoch to the seq_no. Uses the accompanying timstamp to detemine the correct epoch +// add an epoch to the seq_no. Uses the accompanying timestamp to determine the correct epoch uint64_t monotonic_seqno(uint16_t seq_no) { } @@ -1103,6 +1141,7 @@ static void *player_thread_func(void *arg) { struct inter_threads_record itr; itr.please_stop = 0; timestamp_epoch = 0; // indicate that the next timestamp will be the first one. + maximum_timestamp_interval = input_sample_rate * 60; // actually there shouldn't be more than about 13v seconds of a gap between successive rtptimes, at worst // create and start the timing, control and audio receiver threads pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&itr); @@ -1861,7 +1900,6 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { rc = pthread_attr_setstacksize(&tattr, size); if (rc) debug(1, "Error setting stack size for player_thread: %s", strerror(errno)); - timestamp_epoch = 0; pthread_create(player_thread, &tattr, player_thread_func, NULL); pthread_attr_destroy(&tattr); return 0; From ef851a927bd948cd61dfae2ea3ca7c5101274098 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 2 Sep 2016 16:53:52 +0100 Subject: [PATCH 014/135] Enable 64-bit monotonic rtptime calculations --- common.h | 12 +++++------ player.c | 64 +++++++++++++++++++++++++++----------------------------- player.h | 8 +++---- rtp.c | 18 ++++++++-------- rtp.h | 2 +- 5 files changed, 51 insertions(+), 53 deletions(-) diff --git a/common.h b/common.h index e2ab47b9..c273fd19 100644 --- a/common.h +++ b/common.h @@ -107,11 +107,11 @@ typedef struct { char *mdns_name; mdns_backend *mdns; int buffer_start_fill; - int32_t latency; - int32_t userSuppliedLatency; // overrides all other latencies -- use with caution - int32_t iTunesLatency; // supplied with --iTunesLatency option - int32_t AirPlayLatency; // supplied with --AirPlayLatency option - int32_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option + int64_t latency; + int64_t userSuppliedLatency; // overrides all other latencies -- use with caution + int64_t iTunesLatency; // supplied with --iTunesLatency option + int64_t AirPlayLatency; // supplied with --AirPlayLatency option + int64_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option int daemonise; int statistics_requested,use_negotiated_latencies; enum playback_mode_type playback_mode; @@ -176,7 +176,7 @@ config_t config_file_stuff; int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative int64_t session_corrections; -uint32_t play_segment_reference_frame; +int64_t play_segment_reference_frame; uint64_t play_segment_reference_frame_remote_time; void command_start(void); diff --git a/player.c b/player.c index 8f35c6d8..19892c82 100644 --- a/player.c +++ b/player.c @@ -122,7 +122,7 @@ static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; typedef struct audio_buffer_entry { // decoded audio packets int ready; - uint32_t timestamp; + int64_t timestamp; seq_t sequence_number; signed short *data; int length; // the length of the decoded data @@ -133,9 +133,9 @@ static abuf_t audio_buffer[BUFFER_FRAMES]; // mutex-protected variables static seq_t ab_read, ab_write; static int ab_buffering = 1, ab_synced = 0; -static uint32_t first_packet_timestamp = 0; +static int64_t first_packet_timestamp = 0; static int flush_requested = 0; -static uint32_t flush_rtp_timestamp; +static int64_t flush_rtp_timestamp; static uint64_t time_of_last_audio_packet; static int shutdown_requested; @@ -153,12 +153,13 @@ static uint64_t missing_packets, late_packets, too_late_packets, resend_requests // make timestamps and seqnos definitely monotonic -// add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33 frames and continue up to 2^64 frames -// which is about 4*10^8 * 1,000 seconds at 384,000 frames per second -- about 4 trillion seconds or over 100,000 years. +// add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33 frames and continue up to 2^63-1 frames +// if should never get into the negative range +// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds or over 50,000 years. // also, it won't reach zero until then, if ever, so we can safely say that a null monotonic timestamp can mean something special -uint64_t monotonic_timestamp(uint32_t timestamp) { - uint64_t previous_value; - uint64_t return_value; +int64_t monotonic_timestamp(uint32_t timestamp) { + int64_t previous_value; + int64_t return_value; if (timestamp_epoch==0) { if (timestamp>maximum_timestamp_interval) timestamp_epoch=1; @@ -194,6 +195,8 @@ uint64_t monotonic_timestamp(uint32_t timestamp) { if ((return_value-previous_value)>maximum_timestamp_interval) debug(1,"interval between successive rtptimes greater than allowed!"); } + if (return_value<0) + debug(1,"monotonic rtptime is negative!"); return return_value; } @@ -466,7 +469,7 @@ static void free_buffer(void) { free(audio_buffer[i].data); } -void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len) { +void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { // ignore a request to flush that has been made before the first packet... if (packet_count==0) { @@ -485,14 +488,13 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len) // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); if ((flush_rtp_timestamp != 0) && - ((timestamp == flush_rtp_timestamp) || seq32_order(timestamp, flush_rtp_timestamp))) { - debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %u, flushing to " - "timestamp: %u.", + (timestamp <= flush_rtp_timestamp)) { + debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " + "timestamp: %lld.", seqno, timestamp, flush_rtp_timestamp); } else { if ((flush_rtp_timestamp != 0x0) && - (!seq32_order(timestamp, - flush_rtp_timestamp))) // if we have gone past the flush boundary time + (timestamp>flush_rtp_timestamp)) // if we have gone past the flush boundary time flush_rtp_timestamp = 0x0; abuf_t *abuf = 0; @@ -699,17 +701,14 @@ static abuf_t *buffer_get_frame(void) { } if ((flush_rtp_timestamp != 0) && - ((curframe->timestamp == flush_rtp_timestamp) || - seq32_order(curframe->timestamp, flush_rtp_timestamp))) { - debug(1, "Dropping flushed packet seqno %u, timestamp %u", curframe->sequence_number, + (curframe->timestamp <= flush_rtp_timestamp)) { + debug(1, "Dropping flushed packet seqno %u, timestamp %lld", curframe->sequence_number, curframe->timestamp); curframe->ready = 0; flush_limit++; ab_read = SUCCESSOR(ab_read); } - if ((flush_rtp_timestamp != 0) && - (!seq32_order(curframe->timestamp, - flush_rtp_timestamp))) // if we have gone past the flush boundary time + if (curframe->timestamp>flush_rtp_timestamp) flush_rtp_timestamp = 0; } } while ((flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); @@ -725,7 +724,7 @@ static abuf_t *buffer_get_frame(void) { notified_buffer_empty=0; // at least one buffer now -- diagnostic only. if (ab_buffering) { // if we are getting packets but not yet forwarding them to the player int have_sent_prefiller_silence; // set true when we have sent some silent frames to the DAC - uint32_t reference_timestamp; + int64_t reference_timestamp; uint64_t reference_timestamp_time,remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); if (first_packet_timestamp == 0) { // if this is the very first packet @@ -760,14 +759,14 @@ static abuf_t *buffer_get_frame(void) { // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e. // -4410 frames. - int64_t delta = ((int64_t)first_packet_timestamp - (int64_t)reference_timestamp)+config.latency+config.audio_backend_latency_offset; // uint32_t to int64_t is okay and int32t to int64t promotion is okay. + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset; if (delta>=0) { - uint64_t delta_fp_sec = (delta << 32) / 44100; // int64_t which is positive + int64_t delta_fp_sec = (delta << 32) / 44100; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; } else { int64_t abs_delta = -delta; - uint64_t delta_fp_sec = (abs_delta << 32) / 44100; // int64_t which is positive + int64_t delta_fp_sec = (abs_delta << 32) / 44100; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; } @@ -782,14 +781,14 @@ static abuf_t *buffer_get_frame(void) { if (first_packet_time_to_play != 0) { // recalculate first_packet_time_to_play -- the latency might change - int64_t delta = ((int64_t)first_packet_timestamp - (int64_t)reference_timestamp)+config.latency+config.audio_backend_latency_offset; // uint32_t to int64_t is okay and int32t to int64t promotion is okay. + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset; if (delta>=0) { - uint64_t delta_fp_sec = (delta << 32) / 44100; // int64_t which is positive + int64_t delta_fp_sec = (delta << 32) / 44100; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; } else { int64_t abs_delta = -delta; - uint64_t delta_fp_sec = (abs_delta << 32) / 44100; // int64_t which is positive + int64_t delta_fp_sec = (abs_delta << 32) / 44100; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; } @@ -896,12 +895,12 @@ static abuf_t *buffer_get_frame(void) { int do_wait = 0; // don't wait unless we can really prove we must if ((ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { do_wait = 1; // if the current frame exists and is ready, then wait unless it's time to let it go... - uint32_t reference_timestamp; + int64_t reference_timestamp; uint64_t reference_timestamp_time,remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); // all types okay if (reference_timestamp) { // if we have a reference time - uint32_t packet_timestamp = curframe->timestamp; // types okay - int64_t delta = (int64_t)packet_timestamp - (int64_t)reference_timestamp; // uint32_t to int64_t is okay. + int64_t packet_timestamp = curframe->timestamp; // types okay + int64_t delta = packet_timestamp - reference_timestamp; int64_t offset = config.latency + config.audio_backend_latency_offset - config.audio_backend_buffer_desired_length; // all arguments are int32_t, so expression promotion okay int64_t net_offset = delta + offset; // okay @@ -954,7 +953,6 @@ static abuf_t *buffer_get_frame(void) { #endif #ifdef COMPILE_FOR_OSX uint64_t sec = time_to_wait_for_wakeup_fp >> 32; - ; uint64_t nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32; struct timespec time_to_wait; time_to_wait.tv_sec = sec; @@ -1288,7 +1286,7 @@ static void *player_thread_func(void *arg) { at_least_one_frame_seen = 1; - uint32_t reference_timestamp; + int64_t reference_timestamp; uint64_t reference_timestamp_time,remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); // types okay @@ -1840,7 +1838,7 @@ void player_volume(double airplay_volume) { #endif } -void player_flush(uint32_t timestamp) { +void player_flush(int64_t timestamp) { debug(3,"Flush requested up to %u. It seems as if 0 is special.",timestamp); pthread_mutex_lock(&flush_mutex); flush_requested = 1; diff --git a/player.h b/player.h index d6a16e5a..bd14acd2 100644 --- a/player.h +++ b/player.h @@ -18,12 +18,12 @@ int player_play(stream_cfg *cfg, pthread_t *thread); void player_stop(pthread_t *thread); void player_volume(double f); -void player_flush(uint32_t timestamp); +void player_flush(int64_t timestamp); -void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len); +void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len); -uint64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 2^33 frames and continue up to 2^64 frames -// which is about 4*10^8 * 1,000 seconds at 384,000 frames per second -- about 4 trillion seconds. +int64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 2^33 frames and continue up to 2^64 frames +// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds. // assumes, without checking, that successive timestamps in a series always span an interval of less than one minute. uint64_t monotonic_seqno(uint16_t seq_no); // add an epoch to the seq_no. Uses the accompanying timstamp to detemine the correct epoch diff --git a/rtp.c b/rtp.c index 0f8dae93..cbef773d 100644 --- a/rtp.c +++ b/rtp.c @@ -71,7 +71,7 @@ static int control_socket; // our local [server] control socket static int timing_socket; // local timing socket //static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; -static uint32_t reference_timestamp; +static int64_t reference_timestamp; static uint64_t reference_timestamp_time; static uint64_t remote_reference_timestamp_time; @@ -159,7 +159,7 @@ void *rtp_audio_receiver(void *arg) { // debug(3, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno); last_seqno = seqno; // reset warning... } - uint32_t timestamp = ntohl(*(unsigned long *)(pktp + 4)); + int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4))); // if (packet[1]&0x10) // debug(1,"Audio packet Extension bit set."); @@ -198,7 +198,7 @@ void *rtp_control_receiver(void *arg) { uint8_t packet[2048], *pktp; struct timespec tn; uint64_t remote_time_of_sync, local_time_now, remote_time_now; - uint32_t sync_rtp_timestamp, rtp_timestamp_less_latency; + int64_t sync_rtp_timestamp, rtp_timestamp_less_latency; ssize_t nread; while (itr->please_stop==0) { nread = recv(control_socket, packet, sizeof(packet), 0); @@ -229,11 +229,11 @@ void *rtp_control_receiver(void *arg) { // debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync); - rtp_timestamp_less_latency = ntohl(*((uint32_t *)&packet[4])); - sync_rtp_timestamp = ntohl(*((uint32_t *)&packet[16])); + rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4]))); + sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16]))); if (config.use_negotiated_latencies) { - uint32_t la = sync_rtp_timestamp-rtp_timestamp_less_latency+11025; + int64_t la = sync_rtp_timestamp-rtp_timestamp_less_latency+11025; if (la!=config.latency) { config.latency = la; // debug(1,"Using negotiated latency of %u frames.",config.latency); @@ -269,7 +269,7 @@ void *rtp_control_receiver(void *arg) { plen -= 4; seq_t seqno = ntohs(*(unsigned short *)(pktp + 2)); - uint32_t timestamp = ntohl(*(unsigned long *)(pktp + 4)); + int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4))); pktp += 12; plen -= 12; @@ -494,7 +494,7 @@ void *rtp_timing_receiver(void *arg) { int64_t source_drift_usec; if (play_segment_reference_frame!=0) { - uint32_t reference_timestamp; + int64_t reference_timestamp; uint64_t reference_timestamp_time,remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); uint64_t frame_difference = 0; @@ -708,7 +708,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t request_sent = 0; } -void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time) { +void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time) { // types okay pthread_mutex_lock(&reference_time_mutex); *timestamp = reference_timestamp; diff --git a/rtp.h b/rtp.h index 446142d0..195f6738 100644 --- a/rtp.h +++ b/rtp.h @@ -19,7 +19,7 @@ void rtp_shutdown(void); void rtp_request_resend(seq_t first, uint32_t count); void rtp_request_client_pause(void); // ask the client to pause -void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time); +void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time); void clear_reference_timestamp(void); uint64_t static local_to_remote_time_jitters; From 68721f64e0bb537b12b3d4c0b9f5ea917ebff2c0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 2 Sep 2016 17:29:24 +0100 Subject: [PATCH 015/135] Fix a few little bugs associated with outputing at a higher rate --- configure.ac | 2 +- player.c | 39 ++++++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/configure.ac b/configure.ac index f3bf540c..96029098 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0 (B3)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0 (B4)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/player.c b/player.c index 19892c82..4d657077 100644 --- a/player.c +++ b/player.c @@ -82,6 +82,7 @@ static uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval;// t static int input_bytes_per_frame = 4; static int output_bytes_per_frame; +static int output_sample_ratio; // The maximum frame size change there can be is +/- 1; @@ -197,7 +198,7 @@ int64_t monotonic_timestamp(uint32_t timestamp) { } if (return_value<0) debug(1,"monotonic rtptime is negative!"); - return return_value; + return return_value*output_sample_ratio; } // add an epoch to the seq_no. Uses the accompanying timestamp to determine the correct epoch @@ -1140,6 +1141,25 @@ static void *player_thread_func(void *arg) { itr.please_stop = 0; timestamp_epoch = 0; // indicate that the next timestamp will be the first one. maximum_timestamp_interval = input_sample_rate * 60; // actually there shouldn't be more than about 13v seconds of a gap between successive rtptimes, at worst + + output_sample_ratio = 1; + if (config.output_rate!=0) + output_sample_ratio = config.output_rate/44100; + + debug(1,"Output sample ratio is %d.",output_sample_ratio); + + max_frame_size_change = 1*output_sample_ratio; // we add or subtract one frame at the nominal rate, multiply it by the frame ratio. + + output_bytes_per_frame = 4; + switch (config.output_format) { + case SPS_FORMAT_S24_LE: + output_bytes_per_frame=6; + break; + case SPS_FORMAT_S32_LE: + output_bytes_per_frame=8; + break; + } + // create and start the timing, control and audio receiver threads pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&itr); @@ -1149,21 +1169,6 @@ static void *player_thread_func(void *arg) { session_corrections = 0; play_segment_reference_frame = 0; // zero signals that we are not in a play segment - int output_sample_ratio = 1; - if (config.output_rate!=0) - output_sample_ratio = config.output_rate/44100; - - max_frame_size_change = 1*output_sample_ratio; // we add or subtract one frame at the nominal rate, multiply it by the frame ratio. - output_bytes_per_frame = 4; - - switch (config.output_format) { - case SPS_FORMAT_S24_LE: - output_bytes_per_frame=6; - break; - case SPS_FORMAT_S32_LE: - output_bytes_per_frame=8; - break; - } // check that there are enough buffers to accommodate the desired latency and the latency offset @@ -1209,7 +1214,7 @@ static void *player_thread_func(void *arg) { outbuf = malloc(input_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); if (outbuf==NULL) debug(1,"Failed to allocate memory for an output buffer."); - silence = malloc(input_bytes_per_frame*max_frames_per_packet); + silence = malloc(input_bytes_per_frame*max_frames_per_packet*output_sample_ratio); if (silence==NULL) debug(1,"Failed to allocate memory for a silence buffer."); memset(silence, 0, input_bytes_per_frame*max_frames_per_packet*output_sample_ratio); From 30a016867c1c8771eac5d51c258ae7e3a75eaf99 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 4 Sep 2016 13:29:11 +0100 Subject: [PATCH 016/135] Add code to change format from 16 to 24 and 32 bit and to change sample rates --- player.c | 141 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 27 deletions(-) diff --git a/player.c b/player.c index 4d657077..a81b4d04 100644 --- a/player.c +++ b/player.c @@ -83,6 +83,7 @@ static uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval;// t static int input_bytes_per_frame = 4; static int output_bytes_per_frame; static int output_sample_ratio; +static int output_sample_rate; // The maximum frame size change there can be is +/- 1; @@ -550,24 +551,6 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { abuf->length = datalen; abuf->timestamp = timestamp; abuf->sequence_number = seqno; - - if (config.playback_mode==ST_mono) { - signed short *v = abuf->data; - int i; - int both; - for (i=max_frames_per_packet;i;i--) { - int both = *v + *(v+1); - if (both > INT16_MAX) { - both = INT16_MAX; - } else if (both < INT16_MIN) { - both = INT16_MIN; - } - short sboth = (short)both; - *v++ = sboth; - *v++ = sboth; - } - } - } else { debug(1,"Bad audio packet detected and discarded."); abuf->ready = 0; @@ -1142,9 +1125,12 @@ static void *player_thread_func(void *arg) { timestamp_epoch = 0; // indicate that the next timestamp will be the first one. maximum_timestamp_interval = input_sample_rate * 60; // actually there shouldn't be more than about 13v seconds of a gap between successive rtptimes, at worst - output_sample_ratio = 1; + output_sample_rate = input_sample_rate; + if (config.output_rate!=0) - output_sample_ratio = config.output_rate/44100; + output_sample_rate = config.output_rate; + + output_sample_ratio = output_sample_rate/input_sample_rate; debug(1,"Output sample ratio is %d.",output_sample_ratio); @@ -1153,7 +1139,7 @@ static void *player_thread_func(void *arg) { output_bytes_per_frame = 4; switch (config.output_format) { case SPS_FORMAT_S24_LE: - output_bytes_per_frame=6; + output_bytes_per_frame=8; break; case SPS_FORMAT_S32_LE: output_bytes_per_frame=8; @@ -1207,17 +1193,44 @@ static void *player_thread_func(void *arg) { char rnstate[256]; initstate(time(NULL), rnstate, 256); - signed short *inbuf, *outbuf, *silence; + signed short *inbuf, *outbuf, *tbuf, *silence; + int inbuflength; + + int output_bit_depth = 16; // default; + + switch (config.output_format) { + case SPS_FORMAT_S16_LE: + output_bit_depth = 16; + break; + case SPS_FORMAT_S24_LE: + output_bit_depth = 24; + break; + case SPS_FORMAT_S32_LE: + output_bit_depth = 32; + break; + } + + + // if we are changing any of the parameters of the input, like sample rate or sample depth, then we + // need an intermediate "transition" buffer + +// if ((config.output_rate!=0 && (input_sample_rate!=config.output_rate)) || (input_bit_depth!=output_bit_depth)) { + tbuf = malloc(output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); + if (tbuf==NULL) + debug(1,"Failed to allocate memory for the transition buffer."); +// } else { +// tbuf = 0; +// } // We might need an output buffer and a buffer of silence. // The size of these dependents on the number of frames, the size of each frame and the maximum size change - outbuf = malloc(input_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); + outbuf = malloc(output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); if (outbuf==NULL) debug(1,"Failed to allocate memory for an output buffer."); - silence = malloc(input_bytes_per_frame*max_frames_per_packet*output_sample_ratio); + silence = malloc(output_bytes_per_frame*max_frames_per_packet*output_sample_ratio); if (silence==NULL) debug(1,"Failed to allocate memory for a silence buffer."); - memset(silence, 0, input_bytes_per_frame*max_frames_per_packet*output_sample_ratio); + memset(silence, 0, output_bytes_per_frame*max_frames_per_packet*output_sample_ratio); late_packet_message_sent = 0; first_packet_timestamp = 0; missing_packets = late_packets = too_late_packets = resend_requests = 0; @@ -1266,6 +1279,7 @@ static void *player_thread_func(void *arg) { abuf_t *inframe = buffer_get_frame(); if (inframe) { inbuf = inframe->data; + inbuflength = inframe->length; if (inbuf) { play_number++; // if it's a supplied silent frame, let us know... @@ -1276,12 +1290,85 @@ static void *player_thread_func(void *arg) { if (inbuf==NULL) debug(1,"NULL inbuf to play -- skipping it."); else { - if (inframe->length==0) + if (inbuflength==0) debug(1,"empty frame to play -- skipping it (1)."); else - config.output->play(inbuf, inframe->length); + config.output->play(inbuf, inbuflength); } } else { + // here, let's transform the frame of data, if necessary + + if (tbuf!=NULL) { //this will be null if no changes are needed + switch (input_bit_depth) { + case 16: { + int i,j; + int16_t ls,rs; + int16_t *inps=inbuf; + int16_t *outps=tbuf; + int32_t *outpl=(int32_t*)tbuf; + for (i=0;i INT16_MAX) { + both = INT16_MAX; + } else if (both < INT16_MIN) { + both = INT16_MIN; + } + uint16_t sboth = (uint16_t)both; + ls = sboth; + rs = sboth; + } break; + case ST_reverse_stereo: { + uint16_t t = ls; + ls = rs; + rs = t; + } break; + case ST_left_only: + rs = ls; + break; + case ST_right_only: + ls = rs; + break; + } + + // here, replicate the samples if you're upsampling + for (j=0;j Date: Sun, 4 Sep 2016 13:30:12 +0100 Subject: [PATCH 017/135] Adding rate and format transformation stuff --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 96029098..20f3768d 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0 (B4)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0 (B5)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From eca364691e1303fdc65d49a9982b30a3275f84f3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 4 Sep 2016 16:34:56 +0100 Subject: [PATCH 018/135] Working reliably with all 16-bit speeds --- player.c | 90 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/player.c b/player.c index a81b4d04..532252a3 100644 --- a/player.c +++ b/player.c @@ -387,7 +387,7 @@ static int init_decoder(int32_t fmtp[12]) { field bitDepth uint8_t describes the bit depth of the source PCM data (maximum value = 32) - field pb uint8_t currently unused tuning parameter. + field pb uint8_t currently unused tuning parametetbugr. value should be set to 40 field mb uint8_t currently unused tuning parameter. @@ -746,11 +746,11 @@ static abuf_t *buffer_get_frame(void) { int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset; if (delta>=0) { - int64_t delta_fp_sec = (delta << 32) / 44100; // int64_t which is positive + int64_t delta_fp_sec = (delta << 32) / output_sample_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; } else { int64_t abs_delta = -delta; - int64_t delta_fp_sec = (abs_delta << 32) / 44100; // int64_t which is positive + int64_t delta_fp_sec = (abs_delta << 32) / output_sample_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; } @@ -768,22 +768,22 @@ static abuf_t *buffer_get_frame(void) { int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset; if (delta>=0) { - int64_t delta_fp_sec = (delta << 32) / 44100; // int64_t which is positive + int64_t delta_fp_sec = (delta << 32) / output_sample_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; } else { int64_t abs_delta = -delta; - int64_t delta_fp_sec = (abs_delta << 32) / 44100; // int64_t which is positive + int64_t delta_fp_sec = (abs_delta << 32) / output_sample_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; } - int64_t max_dac_delay = 4410; + int64_t max_dac_delay = output_sample_rate/10; int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC if (local_time_now >= first_packet_time_to_play) { // we've gone past the time... // debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp // of %llx and dac_delay of %d and %d packets; - // flush.",(((tn-first_packet_time_to_play)*44100)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, + // flush.",(((tn-first_packet_time_to_play)*output_sample_rate)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, // ab_write)); if (config.output->flush) @@ -803,7 +803,7 @@ static abuf_t *buffer_get_frame(void) { } else dac_delay = 0; int64_t gross_frame_gap = - ((first_packet_time_to_play - local_time_now) * 44100) >> 32; + ((first_packet_time_to_play - local_time_now) * output_sample_rate) >> 32; int64_t exact_frame_gap = gross_frame_gap - dac_delay; if (exact_frame_gap < 0) { // we've gone past the time... @@ -890,12 +890,12 @@ static abuf_t *buffer_get_frame(void) { int64_t net_offset = delta + offset; // okay uint64_t time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { - uint64_t net_offset_fp_sec = (net_offset << 32) / 44100; // int64_t which is positive + uint64_t net_offset_fp_sec = (net_offset << 32) / output_sample_rate; // int64_t which is positive time_to_play += net_offset_fp_sec; // using the latency requested... // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); } else { int64_t abs_net_offset = -net_offset; - uint64_t net_offset_fp_sec = (abs_net_offset << 32) / 44100; // int64_t which is positive + uint64_t net_offset_fp_sec = (abs_net_offset << 32) / output_sample_rate; // int64_t which is positive time_to_play -= net_offset_fp_sec; // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); } @@ -917,7 +917,7 @@ static abuf_t *buffer_get_frame(void) { if (wait) { uint64_t time_to_wait_for_wakeup_fp = - ((uint64_t)1 << 32) / 44100; // this is time period of one frame + ((uint64_t)1 << 32) / input_sample_rate; // this is time period of one frame time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time @@ -993,13 +993,14 @@ static inline short shortmean(short a, short b) { // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff) { + int tstuff = stuff; if ((stuff > 1) || (stuff < -1) || (length <100)) { // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); - return length; + tstuff = 0; // if any of these conditions hold, don't stuff anything/ } int i; int stuffsamp = length; - if (stuff) + if (tstuff) // stuffsamp = rand() % (length - 1); stuffsamp = (rand() % (length - 2)) + 1; // ensure there's always a sample before and after the item @@ -1009,8 +1010,8 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff *outptr++ = dithered_vol(*inptr++); *outptr++ = dithered_vol(*inptr++); }; - if (stuff) { - if (stuff == 1) { + if (tstuff) { + if (tstuff == 1) { // debug(3, "+++++++++"); // interpolate one sample //*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1); @@ -1025,8 +1026,8 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the rest. int remainder = length; - if (stuff<0) - remainder = remainder+stuff; // don't run over the correct end of the output buffer + if (tstuff<0) + remainder = remainder+tstuff; // don't run over the correct end of the output buffer for (i = stuffsamp; i < remainder; i++) { *outptr++ = dithered_vol(*inptr++); @@ -1035,22 +1036,23 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff } pthread_mutex_unlock(&vol_mutex); - return length + stuff; + return length + tstuff; } #ifdef HAVE_LIBSOXR // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) { - if ((stuff > 1) || (stuff < -1) || (length < 100)) { - // debug(1, "Stuff argument to sox_stuff_buffer must be from -1 to +1 and length must be > 100."); - return length; + int tstuff = stuff; + if ((stuff > 1) || (stuff < -1) || (length <100)) { + // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); + tstuff = 0; // if any of these conditions hold, don't stuff anything/ } int i; short *ip, *op; ip = inptr; op = outptr; - if (stuff) { + if (tstuff) { // debug(1,"Stuff %d.",stuff); soxr_io_spec_t io_spec; io_spec.itype = SOXR_INT16_I; @@ -1061,9 +1063,9 @@ static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) size_t odone; - soxr_error_t error = soxr_oneshot(length, length + stuff, 2, /* Rates and # of chans. */ + soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, /* Rates and # of chans. */ inptr, length, NULL, /* Input. */ - outptr, length + stuff, &odone, /* Output. */ + outptr, length + tstuff, &odone, /* Output. */ &io_spec, /* Input, output and transfer spec. */ NULL, NULL); /* Default configuration.*/ @@ -1082,7 +1084,7 @@ static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) } // keep the last (dpm) samples, to mitigate the Gibbs phenomenon - op = outptr + (length + stuff - gpm) * sizeof(short); + op = outptr + (length + tstuff - gpm) * sizeof(short); ip = inptr + (length - gpm) * sizeof(short); for (i = 0; i < gpm; i++) { *op++ = *ip++; @@ -1093,7 +1095,7 @@ static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) if (fix_volume != 65536.0) { // pthread_mutex_lock(&vol_mutex); op = outptr; - for (i = 0; i < length + stuff; i++) { + for (i = 0; i < length + tstuff; i++) { *op = dithered_vol(*op); op++; *op = dithered_vol(*op); @@ -1111,7 +1113,7 @@ static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) }; // pthread_mutex_unlock(&vol_mutex); } - return length + stuff; + return length + tstuff; } #endif @@ -1366,7 +1368,9 @@ static void *player_thread_func(void *arg) { default: die("Shairport Sync only supports 16 bit input"); } + inbuf = tbuf; + inbuflength*=output_sample_ratio; } // We have a frame of data. We need to see if we want to add or remove a frame from it to @@ -1397,10 +1401,10 @@ static void *player_thread_func(void *arg) { if (local_time_now >= reference_timestamp_time) { // debug(1,"td is %lld.",td); td = local_time_now - reference_timestamp_time; // this is the positive value. Conversion is positive uint64_t to int64_t, thus okay - td_in_frames = (td * 44100) >> 32; + td_in_frames = (td * output_sample_rate) >> 32; } else { td = reference_timestamp_time - local_time_now; // this is the absolute value, which should be negated. Conversion is positive uint64_t to int64_t, thus okay. - td_in_frames = (td * 44100) >> 32; //use the absolute td value for the present. Types okay + td_in_frames = (td * output_sample_rate) >> 32; //use the absolute td value for the present. Types okay td_in_frames = -td_in_frames; td = -td; // should be okay, as the range of values should be very small w.r.t 64 bits } @@ -1511,13 +1515,13 @@ static void *player_thread_func(void *arg) { // if no stuffing needed and no volume adjustment, then // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the // output device... - if (inframe->data==NULL) + if (inbuf==NULL) debug(1,"NULL inframe->data to play -- skipping it."); else { - if (inframe->length==0) + if (inbuflength==0) debug(1,"empty frame to play -- skipping it (2)."); else - config.output->play(inframe->data, inframe->length); + config.output->play(inbuf, inbuflength); } } else { @@ -1526,16 +1530,16 @@ static void *player_thread_func(void *arg) { switch (config.packet_stuffing) { case ST_basic: // if (amount_to_stuff) debug(1,"Basic stuff..."); - play_samples = stuff_buffer_basic(inframe->data, inframe->length, outbuf, amount_to_stuff); + play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, amount_to_stuff); break; case ST_soxr: // if (amount_to_stuff) debug(1,"Soxr stuff..."); - play_samples = stuff_buffer_soxr(inframe->data, inframe->length, outbuf, amount_to_stuff); + play_samples = stuff_buffer_soxr(inbuf, inbuflength, outbuf, amount_to_stuff); break; } #else // if (amount_to_stuff) debug(1,"Standard stuff..."); - play_samples = stuff_buffer_basic(inframe->data, inframe->length, outbuf, amount_to_stuff); + play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, amount_to_stuff); #endif /* @@ -1593,21 +1597,21 @@ static void *player_thread_func(void *arg) { if (fix_volume == 0x10000) - if (inframe->data==NULL) - debug(1,"NULL inframe->data to play -- skipping it."); + if (inbuf==NULL) + debug(1,"NULL inbuf to play -- skipping it."); else { - if (inframe->length==0) + if (inbuflength==0) debug(1,"empty frame to play -- skipping it (3)."); else - config.output->play(inframe->data, inframe->length); + config.output->play(inbuf, inbuflength); } else { - play_samples = stuff_buffer_basic(inframe->data, inframe->length, outbuf, 0); // no stuffing, but volume adjustment + play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, 0); // no stuffing, but volume adjustment if (outbuf==NULL) debug(1,"NULL outbuf to play -- skipping it."); else { - if (inframe->length==0) + if (inbuf==0) debug(1,"empty frame to play -- skipping it (4)."); else config.output->play(outbuf, play_samples); @@ -1692,8 +1696,8 @@ static void *player_thread_func(void *arg) { "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ 10, moving_average_sync_error, - 10, moving_average_correction * 1000000 / 352, - 10, moving_average_insertions_plus_deletions * 1000000 / 352, + 10, moving_average_correction * 1000000 / (352*output_sample_ratio), + 10, moving_average_insertions_plus_deletions * 1000000 / (352*output_sample_ratio), 12, play_number, 7, missing_packets, 7, late_packets, From 0db88e9029259c2121daaab688e9ca202c1127d7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 4 Sep 2016 16:35:39 +0100 Subject: [PATCH 019/135] Working reliably with all 16-bit speeds --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 20f3768d..1d5191c8 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0 (B5)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0 (B6)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From ae84366e8d6f5f6aaa9ab60821f8e73e74f4c85b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 4 Sep 2016 19:37:22 +0100 Subject: [PATCH 020/135] Change over to times as opposed to frames for setting timing options --- audio_alsa.c | 34 +++++++++++++++++----------------- audio_ao.c | 31 +++++++++++++++++-------------- audio_pipe.c | 34 ++++++++++++++++++++-------------- audio_stdout.c | 31 +++++++++++++++++++------------ common.h | 12 ++++++------ player.c | 37 +++++++++++++++++++------------------ scripts/shairport-sync.conf | 26 ++++++++++++++------------ shairport.c | 31 ++++++++++++++++--------------- 8 files changed, 128 insertions(+), 108 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index db864f65..c736e205 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -136,40 +136,40 @@ static int init(int argc, char **argv) { // debug(2,"audio_alsa init called."); const char *str; int value; + double dvalue; set_period_size_request = 0; set_buffer_size_request = 0; - config.audio_backend_latency_offset = 0; // this is the default for ALSA - config.audio_backend_buffer_desired_length = - 6615; // default for alsa with a software mixer + config.audio_backend_latency_offset = 0; + config.audio_backend_buffer_desired_length = 0.15; // get settings from settings file first, allow them to be overridden by // command line options if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_int(config.cfg, - "alsa.audio_backend_buffer_desired_length", &value)) { - if ((value < 0) || (value > 66150)) - die("Invalid alsa audio backend buffer desired length \"%d\". It " + if (config_lookup_float(config.cfg, + "alsa.audio_backend_buffer_desired_length", &dvalue)) { + if ((dvalue < 0) || (value > 1.5)) + die("Invalid alsa audio backend buffer desired length \"%f\". It " "should be between 0 and " - "66150, default is 6615", - value); + "1.5, default is 0.15 seconds", + dvalue); else { - config.audio_backend_buffer_desired_length = value; + config.audio_backend_buffer_desired_length = dvalue; } } /* Get the latency offset. */ - if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", - &value)) { - if ((value < -66150) || (value > 66150)) - die("Invalid alsa audio backend buffer latency offset \"%d\". It " - "should be between -66150 and +66150, default is 0", - value); + if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset", + &dvalue)) { + if ((dvalue < -1.0) || (value > 1.5)) + die("Invalid alsa audio backend buffer latency offset \"%f\". It " + "should be between -1.0 and +1.5, default is 0 seconds", + dvalue); else - config.audio_backend_latency_offset = value; + config.audio_backend_latency_offset = dvalue; } /* Get the Output Device Name. */ diff --git a/audio_ao.c b/audio_ao.c index b6a09a07..51cf3337 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -47,34 +47,37 @@ static int init(int argc, char **argv) { int driver = ao_default_driver_id(); ao_option *ao_opts = NULL; - config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; // get settings from settings file first, allow them to be overridden by command line options if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_int(config.cfg, "ao.audio_backend_buffer_desired_length", &value)) { - if ((value < 0) || (value > 66150)) - die("Invalid a0 audio backend buffer desired length \"%d\". It should be between 0 and " - "66150, default is 44100", - value); + if (config_lookup_float(config.cfg, + "ao.audio_backend_buffer_desired_length", &dvalue)) { + if ((dvalue < 0) || (value > 1.5)) + die("Invalid ao audio backend buffer desired length \"%f\". It " + "should be between 0 and " + "1.5, default is 1.0 second", + dvalue); else { - config.audio_backend_buffer_desired_length = value; + config.audio_backend_buffer_desired_length = dvalue; } } - + /* Get the latency offset. */ - if (config_lookup_int(config.cfg, "ao.audio_backend_latency_offset", &value)) { - if ((value < -66150) || (value > 66150)) - die("Invalid ao audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", - value); + if (config_lookup_float(config.cfg, "ao.audio_backend_latency_offset", + &dvalue)) { + if ((dvalue < -1.0) || (value > 1.5)) + die("Invalid ao audio backend buffer latency offset \"%f\". It " + "should be between -1.0 and +1.5, default is 0 seconds", + dvalue); else - config.audio_backend_latency_offset = value; + config.audio_backend_latency_offset = dvalue; } } - optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour argv--; // so we shift the arguments to satisfy getopt() argc++; diff --git a/audio_pipe.c b/audio_pipe.c index dfde2a92..155e9b07 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -69,8 +69,9 @@ static int init(int argc, char **argv) { debug(1, "pipe init"); const char *str; int value; - - config.audio_backend_buffer_desired_length = 44100; // one second. + double dvalue; + + config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; if (config.cfg != NULL) { @@ -84,24 +85,29 @@ static int init(int argc, char **argv) { die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead."); /* Get the desired buffer size setting. */ - if (config_lookup_int(config.cfg, "pipe.audio_backend_buffer_desired_length", &value)) { - if ((value < 0) || (value > 132300)) - die("Invalid pipe audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100", - value); - else - config.audio_backend_buffer_desired_length = value; + if (config_lookup_float(config.cfg, + "pipe.audio_backend_buffer_desired_length", &dvalue)) { + if ((dvalue < 0) || (value > 1.5)) + die("Invalid pipe audio backend buffer desired length \"%f\". It " + "should be between 0 and " + "1.5, default is 1.0 second", + dvalue); + else { + config.audio_backend_buffer_desired_length = dvalue; + } } /* Get the latency offset. */ - if (config_lookup_int(config.cfg, "pipe.audio_backend_latency_offset", &value)) { - if ((value < -66150) || (value > 66150)) - die("Invalid pipe audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", - value); + if (config_lookup_float(config.cfg, "pipe.audio_backend_latency_offset", + &dvalue)) { + if ((dvalue < -1.0) || (value > 1.5)) + die("Invalid pipe audio backend buffer latency offset \"%f\". It " + "should be between -1.0 and +1.5, default is 0 seconds", + dvalue); else - config.audio_backend_latency_offset = value; + config.audio_backend_latency_offset = dvalue; } } - if ((pipename == NULL) && (argc != 1)) die("bad or missing argument(s) to pipe"); diff --git a/audio_stdout.c b/audio_stdout.c index cb11a99a..b24e0da1 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -53,27 +53,34 @@ static void stop(void) { static int init(int argc, char **argv) { const char *str; int value; + double dvalue; - config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_int(config.cfg, "stdout.audio_backend_buffer_desired_length", &value)) { - if ((value < 0) || (value > 132300)) - die("Invalid stdout audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100", - value); - else - config.audio_backend_buffer_desired_length = value; + if (config_lookup_float(config.cfg, + "stdout.audio_backend_buffer_desired_length", &dvalue)) { + if ((dvalue < 0) || (dvalue > 1.5)) + die("Invalid stdout audio backend buffer desired length \"%f\". It " + "should be between 0 and " + "1.5, default is 1.0 second", + dvalue); + else { + config.audio_backend_buffer_desired_length = dvalue; + } } /* Get the latency offset. */ - if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", &value)) { - if ((value < -66150) || (value > 66150)) - die("Invalid stdout audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", - value); + if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset", + &dvalue)) { + if ((dvalue < -1.0) || (value > 1.5)) + die("Invalid stdout audio backend buffer latency offset \"%f\". It " + "should be between -1.0 and +1.5, default is 0 seconds", + dvalue); else - config.audio_backend_latency_offset = value; + config.audio_backend_latency_offset = dvalue; } } return 0; diff --git a/common.h b/common.h index c273fd19..4e6b1141 100644 --- a/common.h +++ b/common.h @@ -95,8 +95,8 @@ typedef struct { int ignore_volume_control; int no_sync; // disable synchronisation, even if it's available int no_mmap; // disable use of mmap-based output, even if it's available - int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never - // resync. + double resyncthreshold; // if it get's out of whack my more than this number of seconds, resync. Zero means never + // resync. int allow_session_interruption; int timeout; // while in play mode, exit if no packets of audio come in for more than this number // of seconds . Zero means never exit. @@ -117,7 +117,7 @@ typedef struct { enum playback_mode_type playback_mode; char *cmd_start, *cmd_stop; int cmd_blocking; - int tolerance; // allow this much drift before attempting to correct it + double tolerance; // allow this much drift before attempting to correct it enum stuffing_type packet_stuffing; int decoders_supported; int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by David Hammerton @@ -126,9 +126,9 @@ typedef struct { // char *errfile; char *configfile; char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by default “_raop._tcp.”. - long audio_backend_buffer_desired_length; // this will be the desired number of frames in the - // audio backend buffer -- the DAC buffer for ALSA - long audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency there might be in the audio path + double audio_backend_buffer_desired_length; // this will be the length in seconds of the + // audio backend buffer -- the DAC buffer for ALSA + double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any fixed latency there might be in the audio path uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's native range. enum sps_format_t output_format; int output_rate; diff --git a/player.c b/player.c index 532252a3..7edc2b3b 100644 --- a/player.c +++ b/player.c @@ -743,7 +743,7 @@ static abuf_t *buffer_get_frame(void) { // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e. // -4410 frames. - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset; + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset*output_sample_rate; if (delta>=0) { int64_t delta_fp_sec = (delta << 32) / output_sample_rate; // int64_t which is positive @@ -765,7 +765,7 @@ static abuf_t *buffer_get_frame(void) { if (first_packet_time_to_play != 0) { // recalculate first_packet_time_to_play -- the latency might change - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset; + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset*output_sample_rate; if (delta>=0) { int64_t delta_fp_sec = (delta << 32) / output_sample_rate; // int64_t which is positive @@ -885,8 +885,8 @@ static abuf_t *buffer_get_frame(void) { if (reference_timestamp) { // if we have a reference time int64_t packet_timestamp = curframe->timestamp; // types okay int64_t delta = packet_timestamp - reference_timestamp; - int64_t offset = config.latency + config.audio_backend_latency_offset - - config.audio_backend_buffer_desired_length; // all arguments are int32_t, so expression promotion okay + int64_t offset = config.latency + config.audio_backend_latency_offset*output_sample_rate - + config.audio_backend_buffer_desired_length*output_sample_rate; // all arguments are int32_t, so expression promotion okay int64_t net_offset = delta + offset; // okay uint64_t time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { @@ -1160,7 +1160,7 @@ static void *player_thread_func(void *arg) { // check that there are enough buffers to accommodate the desired latency and the latency offset - int maximum_latency = config.latency+config.audio_backend_latency_offset; + int maximum_latency = config.latency+config.audio_backend_latency_offset*output_sample_rate; if ((maximum_latency+(352-1))/352 + 10 > BUFFER_FRAMES) die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame packets may be accommodated.",maximum_latency,BUFFER_FRAMES); connection_state_to_output = get_requested_connection_state_to_output(); @@ -1243,7 +1243,7 @@ static void *player_thread_func(void *arg) { if (config.statistics_requested) { if ((config.output->delay)) { if (config.no_sync==0) { - inform("sync error in frames, " + inform("sync error in milliseconds, " "net correction in ppm, " "corrections in ppm, " "total packets, " @@ -1255,7 +1255,7 @@ static void *player_thread_func(void *arg) { "min buffer occupancy, " "max buffer occupancy"); } else { - inform("sync error in frames, " + inform("sync error in milliseconds, " "total packets, " "missing packets, " "late packets, " @@ -1266,7 +1266,8 @@ static void *player_thread_func(void *arg) { "max buffer occupancy"); } } else { - inform("total packets, " + inform("sync error in milliseconds, " + "total packets, " "missing packets, " "late packets, " "too late packets, " @@ -1477,10 +1478,10 @@ static void *player_thread_func(void *arg) { // before we finally commit to this frame, check its sequencing and timing // require a certain error before bothering to fix it... - if (sync_error > config.tolerance) { // int64_t > int, okay + if (sync_error > config.tolerance*output_sample_rate) { // int64_t > int, okay amount_to_stuff = -1; } - if (sync_error < -config.tolerance) { + if (sync_error < -config.tolerance*output_sample_rate) { amount_to_stuff = 1; } @@ -1576,8 +1577,8 @@ static void *player_thread_func(void *arg) { if (abs_sync_error<0) abs_sync_error = -abs_sync_error; - if ((config.no_sync==0) && (inframe->timestamp != 0) && (!please_stop) && (config.resyncthreshold != 0) && - (abs_sync_error > config.resyncthreshold)) { + if ((config.no_sync==0) && (inframe->timestamp != 0) && (!please_stop) && (config.resyncthreshold > 0.0) && + (abs_sync_error > config.resyncthreshold*output_sample_rate)) { sync_error_out_of_bounds++; // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld; // timestamp: %llx, time now @@ -1684,7 +1685,7 @@ static void *player_thread_func(void *arg) { if (at_least_one_frame_seen) { if ((config.output->delay)) { if (config.no_sync==0) { - inform("%*.1f," /* Sync error inf frames */ + inform("%*.1f," /* Sync error in milliseconds */ "%*.1f," /* net correction in ppm */ "%*.1f," /* corrections in ppm */ "%*d," /* total packets */ @@ -1695,7 +1696,7 @@ static void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, moving_average_sync_error, + 10, 1000*moving_average_sync_error/output_sample_rate, 10, moving_average_correction * 1000000 / (352*output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / (352*output_sample_ratio), 12, play_number, @@ -1707,7 +1708,7 @@ static void *player_thread_func(void *arg) { 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { - inform("%*.1f," /* Sync error inf frames */ + inform("%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -1716,7 +1717,7 @@ static void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, moving_average_sync_error, + 10, 1000*moving_average_sync_error/output_sample_rate, 12, play_number, 7, missing_packets, 7, late_packets, @@ -1727,7 +1728,7 @@ static void *player_thread_func(void *arg) { 5, maximum_buffer_occupancy); } } else { - inform("%*.1f," /* Sync error inf frames */ + inform("%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -1735,7 +1736,7 @@ static void *player_thread_func(void *arg) { "%*llu," /* resend requests */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, moving_average_sync_error, + 10, 1000*moving_average_sync_error/output_sample_rate, 12, play_number, 7, missing_packets, 7, late_packets, diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 0502720f..50d0f797 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -8,8 +8,8 @@ general = // The default is "" -- the first letter is capitalised (ASCII only.) // You can use %h for the hostname, // %H for the Hostname (i.e. with first letter capitalised (ASCII only)), -// %v for the version number, e.g. 2.8.4 and -// %V for the full version string, e.g. 2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata +// %v for the version number, e.g. 3.2.1 and +// %V for the full version string, e.g. 3.2.1-OpenSSL-Avahi-ALSA-soxr-metadata // Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H". // password = "secret"; // leave this commented out if you don't want to require a password // interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor. @@ -19,8 +19,8 @@ general = // udp_port_base = 6001; // start allocating UDP ports from this port number when needed // udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base (only three are needed). // statistics = "no"; // set to "yes" to print statistics in the log -// drift = 88; // allow this number of frames of drift away from exact synchronisation before attempting to correct it -// resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it +// drift = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it +// resync_threshold = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. // volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. @@ -60,8 +60,10 @@ alsa = // output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc. // mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. // mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. -// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +// output_rate = 44100; // can be 44100, 88200, 176400, 352800, but the device must be capable of it +// output_format = "S16_LE"; // can be "S16_LE", "S24_LE" or "S32_LE", but the device must be capable of it +// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. +// audio_backend_buffer_desired_length = 0.15; // Given in seconds. If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. // disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". // period_size = ; // Use this optional advanced setting to set the alsa period size near to this value // buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value @@ -72,22 +74,22 @@ alsa = pipe = { // name = "/path/to/pipe"; // there is no default pipe name for the output -// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 44100; // Having started to send audio at the right time, send all subsequent audio this many frames ahead of time, creating a buffer this size. +// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. +// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length. }; // These are parameters for the "stdout" audio back end, a back end that directs raw CD-style audio output to stdout. No interpolation is done. stdout = { -// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 44100; // Having started to send audio at the right time, send all subsequent audio this many frames ahead of time, creating a buffer this size. +// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. +// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length. }; // These are parameters for the "ao" audio back end. No interpolation is done. ao = { -// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 44100; // Having started to send audio at the right time, send all subsequent audio this many frames ahead of time, creating a buffer this size. +// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. +// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length. }; // Static latency settings are deprecated and the settings have been removed. diff --git a/shairport.c b/shairport.c index afcccac3..1b746922 100644 --- a/shairport.c +++ b/shairport.c @@ -214,14 +214,14 @@ void usage(char *progname) { printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service.\n"); printf(" if no mdns provider is specified,\n"); printf(" shairport tries them all until one works.\n"); - printf(" -r, --resync=THRESHOLD resync if error exceeds this number of frames. Set to 0 to " + printf(" -r, --resync=THRESHOLD resync if timing error exceeds this number of seconds. Set to 0 to " "stop resyncing.\n"); printf(" -t, --timeout=SECONDS go back to idle mode from play mode after a break in " "communications of this many seconds (default 120). Set to 0 never to exit play mode.\n"); printf(" --statistics print some interesting statistics -- output to the logfile " "if running as a daemon.\n"); - printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE frames (default " - "88) before trying to correct it.\n"); + printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE seconds (default " + "0.002) before trying to correct it.\n"); printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a " "password.\n"); #ifdef CONFIG_METADATA @@ -264,10 +264,10 @@ int parse_options(int argc, char **argv) { {"iTunesLatency", 'i', POPT_ARG_INT, &config.iTunesLatency, 0, NULL}, {"forkedDaapdLatency", 0, POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL}, {"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL}, - {"resync", 'r', POPT_ARG_INT, &config.resyncthreshold, 0, NULL}, + {"resync", 'r', POPT_ARG_DOUBLE, &config.resyncthreshold, 0, NULL}, {"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL}, {"password", 0, POPT_ARG_STRING, &config.password, 0, NULL}, - {"tolerance", 0, POPT_ARG_INT, &config.tolerance, 0, NULL}, + {"tolerance", 0, POPT_ARG_DOUBLE, &config.tolerance, 0, NULL}, #ifdef CONFIG_METADATA {"metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL}, {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL}, @@ -302,6 +302,7 @@ int parse_options(int argc, char **argv) { config_setting_t *setting; const char *str=0; int value=0; + double dvalue = 0.0; debug(1,"Looking for the configuration file \"%s\".",config.configfile); @@ -390,12 +391,12 @@ int parse_options(int argc, char **argv) { } /* Get the drift tolerance setting. */ - if (config_lookup_int(config.cfg, "general.drift", &value)) - config.tolerance = value; + if (config_lookup_float(config.cfg, "general.drift", &dvalue)) + config.tolerance = dvalue; /* Get the resync setting. */ - if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) - config.resyncthreshold = value; + if (config_lookup_float(config.cfg, "general.resync_threshold", &dvalue)) + config.resyncthreshold = dvalue; /* Get the verbosity setting. */ if (config_lookup_int(config.cfg, "general.log_verbosity", &value)) { @@ -759,9 +760,9 @@ int main(int argc, char **argv) { config.AirPlayLatency = -1; // -1 means not set. 88200 seems to work well for AirPlay -- Syncs sound and // vision on AppleTV, but also used for iPhone/iPod/iPad sources config.ForkedDaapdLatency = -1; // -1 means not set. 99400 seems to be right - config.resyncthreshold = 441 * 5; // this number of frames is 50 ms + config.resyncthreshold = 0.05; // 50 ms config.timeout = 120; // this number of seconds to wait for [more] audio before switching to idle. - config.tolerance = 88; // this number of frames of error before attempting to correct it. + config.tolerance = 0.002; // this number of seconds of timing error before attempting to correct it. config.buffer_start_fill = 220; config.port = 5000; config.packet_stuffing = ST_basic; // simple interpolation or deletion @@ -1024,10 +1025,10 @@ int main(int argc, char **argv) { debug(2, "iTunesLatency is %d.", config.iTunesLatency); debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency); debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing); - debug(1, "resync time is %d.", config.resyncthreshold); + debug(1, "resync time is %f seconds.", config.resyncthreshold); debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption); debug(1, "busy timeout time is %d.", config.timeout); - debug(1, "drift tolerance is %d frames.", config.tolerance); + debug(1, "drift tolerance is %f seconds.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); debug(1, "playback_mode is %d (0-stereo, 1-mono).", config.playback_mode); @@ -1035,9 +1036,9 @@ int main(int argc, char **argv) { debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); debug(1, "output_rate is %d (0 means 44,100).", config.output_rate); debug(1, "output_format is %d (0 means S16_LE).", config.output_format); - debug(1, "audio backend desired buffer length is %d.", + debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); - debug(1, "audio backend latency offset is %d.", config.audio_backend_latency_offset); + debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", config.volume_range_db); debug(1, "zeroconf regtype is \"%s\".", config.regtype); debug(1, "decoders_supported field is %d.", config.decoders_supported); From 06216a0e5f1a7e808b7b92a8445a182d01c0e722 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 4 Sep 2016 19:52:58 +0100 Subject: [PATCH 021/135] Change over to times as opposed to frames for setting timing options --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 1d5191c8..8dc9bd6c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0 (B6)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0 (B7)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 4b5dba7381629cfb3617122b08b2c446be12a326 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 5 Sep 2016 15:23:31 +0100 Subject: [PATCH 022/135] Fix small bug introduced moving over to time-based settings --- audio_ao.c | 1 + 1 file changed, 1 insertion(+) diff --git a/audio_ao.c b/audio_ao.c index 51cf3337..62c0729f 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -43,6 +43,7 @@ static void help(void) { static int init(int argc, char **argv) { const char *str; int value; + double dvalue; ao_initialize(); int driver = ao_default_driver_id(); ao_option *ao_opts = NULL; From e6e1acd91e3b27df8f196c617ac16b9b0108fa57 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 5 Sep 2016 15:24:59 +0100 Subject: [PATCH 023/135] Stop multiplying monotonic timestamps by the output to input sampling ratio --- player.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/player.c b/player.c index 7edc2b3b..96c152f0 100644 --- a/player.c +++ b/player.c @@ -199,7 +199,7 @@ int64_t monotonic_timestamp(uint32_t timestamp) { } if (return_value<0) debug(1,"monotonic rtptime is negative!"); - return return_value*output_sample_ratio; + return return_value; } // add an epoch to the seq_no. Uses the accompanying timestamp to determine the correct epoch @@ -472,6 +472,10 @@ static void free_buffer(void) { } void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { + + // all timestamps are done at the output rate + + int64_t ltimestamp = timestamp*output_sample_ratio; // ignore a request to flush that has been made before the first packet... if (packet_count==0) { @@ -490,13 +494,13 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); if ((flush_rtp_timestamp != 0) && - (timestamp <= flush_rtp_timestamp)) { + (ltimestamp <= flush_rtp_timestamp)) { debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " "timestamp: %lld.", - seqno, timestamp, flush_rtp_timestamp); + seqno, ltimestamp, flush_rtp_timestamp); } else { if ((flush_rtp_timestamp != 0x0) && - (timestamp>flush_rtp_timestamp)) // if we have gone past the flush boundary time + (ltimestamp>flush_rtp_timestamp)) // if we have gone past the flush boundary time flush_rtp_timestamp = 0x0; abuf_t *abuf = 0; @@ -549,7 +553,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { if (alac_decode(abuf->data, &datalen, data, len)==0) { abuf->ready = 1; abuf->length = datalen; - abuf->timestamp = timestamp; + abuf->timestamp = ltimestamp; abuf->sequence_number = seqno; } else { debug(1,"Bad audio packet detected and discarded."); @@ -665,6 +669,7 @@ static abuf_t *buffer_get_frame(void) { flush_requested = 0; } pthread_mutex_unlock(&flush_mutex); + uint32_t flush_limit = 0; if (ab_synced) { do { @@ -711,6 +716,7 @@ static abuf_t *buffer_get_frame(void) { int64_t reference_timestamp; uint64_t reference_timestamp_time,remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); + reference_timestamp*=output_sample_ratio; if (first_packet_timestamp == 0) { // if this is the very first packet // debug(1,"First frame seen, time %u, with %d // frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); @@ -743,7 +749,9 @@ static abuf_t *buffer_get_frame(void) { // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e. // -4410 frames. - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset*output_sample_rate; +debug(1,"Output sample ratio is %d",output_sample_ratio); + + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*output_sample_rate; if (delta>=0) { int64_t delta_fp_sec = (delta << 32) / output_sample_rate; // int64_t which is positive @@ -758,14 +766,14 @@ static abuf_t *buffer_get_frame(void) { debug( 1, "First packet is late! It should have played before now. Flushing 0.1 seconds"); - player_flush(first_packet_timestamp + 4410); + player_flush(first_packet_timestamp + 4410*output_sample_ratio); } } } if (first_packet_time_to_play != 0) { // recalculate first_packet_time_to_play -- the latency might change - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency+config.audio_backend_latency_offset*output_sample_rate; + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*output_sample_rate; if (delta>=0) { int64_t delta_fp_sec = (delta << 32) / output_sample_rate; // int64_t which is positive @@ -856,6 +864,7 @@ static abuf_t *buffer_get_frame(void) { // not the time of the playing of the first frame uint64_t reference_timestamp_time; // don't need this... get_reference_timestamp_stuff(&play_segment_reference_frame, &reference_timestamp_time, &play_segment_reference_frame_remote_time); + play_segment_reference_frame*=output_sample_ratio; #ifdef CONFIG_METADATA send_ssnc_metadata('prsm', NULL, 0, 0); // "resume", but don't wait if the queue is locked #endif @@ -882,10 +891,11 @@ static abuf_t *buffer_get_frame(void) { int64_t reference_timestamp; uint64_t reference_timestamp_time,remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); // all types okay + reference_timestamp*=output_sample_ratio; if (reference_timestamp) { // if we have a reference time int64_t packet_timestamp = curframe->timestamp; // types okay int64_t delta = packet_timestamp - reference_timestamp; - int64_t offset = config.latency + config.audio_backend_latency_offset*output_sample_rate - + int64_t offset = config.latency*output_sample_ratio + config.audio_backend_latency_offset*output_sample_rate - config.audio_backend_buffer_desired_length*output_sample_rate; // all arguments are int32_t, so expression promotion okay int64_t net_offset = delta + offset; // okay uint64_t time_to_play = reference_timestamp_time; // type okay @@ -1160,7 +1170,7 @@ static void *player_thread_func(void *arg) { // check that there are enough buffers to accommodate the desired latency and the latency offset - int maximum_latency = config.latency+config.audio_backend_latency_offset*output_sample_rate; + int maximum_latency = config.latency+config.audio_backend_latency_offset*input_sample_rate; if ((maximum_latency+(352-1))/352 + 10 > BUFFER_FRAMES) die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame packets may be accommodated.",maximum_latency,BUFFER_FRAMES); connection_state_to_output = get_requested_connection_state_to_output(); @@ -1386,7 +1396,7 @@ static void *player_thread_func(void *arg) { int64_t reference_timestamp; uint64_t reference_timestamp_time,remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); // types okay - + reference_timestamp*=output_sample_ratio; int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t nt = inframe->timestamp; // uint32_t to int64_t @@ -1470,7 +1480,7 @@ static void *player_thread_func(void *arg) { // This is the timing error for the next audio frame in the DAC. - sync_error = delay - config.latency; // int64_t from int64_t - int32_t, so okay + sync_error = delay - config.latency*output_sample_ratio; // int64_t from int64_t - int32_t, so okay // if (llabs(sync_error)>352*512) // debug(1,"Very large sync error: %lld",sync_error); From cb7c12d6362ba03342279c6678e1d5bc05e25783 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 5 Sep 2016 17:11:30 +0100 Subject: [PATCH 024/135] Fix silence bug and hack code to avoid the stuff procedures --- player.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/player.c b/player.c index 96c152f0..89a6adb6 100644 --- a/player.c +++ b/player.c @@ -845,11 +845,11 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and dac_delay of %lld.",gross_frame_gap,dac_delay); // the fs (number of frames of silence to play) can be zero in the DAC doesn't start ouotputting frames for a while -- it could get loaded up but not start responding for many milliseconds. if (fs!=0) { - silence = malloc(input_bytes_per_frame*fs); + silence = malloc(output_bytes_per_frame*fs); if (silence==NULL) debug(1,"Failed to allocate %d byte silence buffer.",fs); else { - memset(silence, 0, input_bytes_per_frame*fs); + memset(silence, 0, output_bytes_per_frame*fs); // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, // with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write)); config.output->play(silence, fs); @@ -1158,6 +1158,8 @@ static void *player_thread_func(void *arg) { break; } + debug(1,"Output frame bytes is %d.",output_bytes_per_frame); + // create and start the timing, control and audio receiver threads pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&itr); @@ -1223,6 +1225,8 @@ static void *player_thread_func(void *arg) { } + debug(1,"Output bit depth is %d.",output_bit_depth); + // if we are changing any of the parameters of the input, like sample rate or sample depth, then we // need an intermediate "transition" buffer @@ -1334,12 +1338,12 @@ static void *player_thread_func(void *arg) { } else if (both < INT16_MIN) { both = INT16_MIN; } - uint16_t sboth = (uint16_t)both; + int16_t sboth = (int16_t)both; ls = sboth; rs = sboth; } break; case ST_reverse_stereo: { - uint16_t t = ls; + int16_t t = ls; ls = rs; rs = t; } break; @@ -1359,15 +1363,15 @@ static void *player_thread_func(void *arg) { *outps++=rs; break; case 24: { - uint32_t t = ls<<8; + int32_t t = ls<<8; *outpl++=t; - uint32_t u = rs<<8; + int32_t u = rs<<8; *outpl++=u; } break; case 32: { - uint32_t t = ls<<16; + int32_t t = ls<<16; *outpl++=t; - uint32_t u = rs<<16; + int32_t u = rs<<16; *outpl++=u; } break; } @@ -1522,7 +1526,8 @@ static void *player_thread_func(void *arg) { if (config.no_sync!=0) amount_to_stuff = 0 ; // no stuffing if it's been disabled - if ((amount_to_stuff == 0) && (fix_volume == 0x10000) && ((config.output_rate==0) || (config.output_rate==44100)) && ((config.output_format==0) || (config.output_format==SPS_FORMAT_S16_LE))) { +// if ((amount_to_stuff == 0) && (fix_volume == 0x10000) && ((config.output_rate==0) || (config.output_rate==44100)) && ((config.output_format==0) || (config.output_format==SPS_FORMAT_S16_LE))) { +if (1) { // if no stuffing needed and no volume adjustment, then // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the // output device... From de178d250528bf1835267e1a81854e83e14d518b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 5 Sep 2016 17:11:46 +0100 Subject: [PATCH 025/135] Fix silence bug and hack code to avoid the stuff procedures --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8dc9bd6c..066e5db2 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0 (B7)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0 (B8)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From c7c794c72b7d9b42ab577452c408a42b3637c079 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 5 Sep 2016 22:35:44 +0100 Subject: [PATCH 026/135] Preliminary support for 32-bit stuffing --- player.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 117 insertions(+), 6 deletions(-) diff --git a/player.c b/player.c index 89a6adb6..3aafa459 100644 --- a/player.c +++ b/player.c @@ -582,6 +582,45 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { return sp >> 32; } +int64_t rand_in_range_64(int64_t exclusive_range_limit) { + static uint64_t lcg_prev = 12345; + // returns a pseudo random integer in the range 0 to (exclusive_range_limit-1) inclusive + int64_t sp = lcg_prev; + int64_t rl = exclusive_range_limit; + lcg_prev = lcg_prev * 69069 + 3; // crappy psrg + sp = sp*rl; // 64 bit calculation. INtersting part if above the 32 rightmost bits; + return sp; +} + +static inline int32_t dithered_vol_32(int32_t sample, int output_precision) { + if ((fix_volume==0x1000) && (output_precision==32)) { + return sample; + } else { + int64_t s = sample; // 64 bit signed + int64_t v = fix_volume & 0x1FFFF; // 17 bit unsigned made a 33 bit unsigned + v<<=16; + s = s*v; // 32 bit multiplication + /* + // here, do the dithering + int64_t tpdf = rand_in_range_64((1<<(64-output_precision))+1) - rand_in_range_64((1<<(64-output_precision))+1); + // Check there's no clipping -- if there is, + if (tpdf>=0) { + if (INT64_MAX-tpdf>=s) + s += tpdf; + else + s = INT64_MAX; + } else { + if (INT64_MIN-tpdf<=s) + s += tpdf; + else + s = INT64_MIN; + } + */ + s>>=(64-output_precision); + return s; + } +} + static inline short dithered_vol(short sample) { long out; @@ -1001,7 +1040,64 @@ static inline short shortmean(short a, short b) { return r; } +static inline int32_t mean_32(int32_t a, int32_t b) { + int64_t al = a; + int64_t bl = b; + int64_t mean = (al + bl) / 2; + int32_t r = (int32_t)mean; + if (r != mean) + debug(1, "Error calculating average of two int32_ts"); + return r; +} + // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 +static int stuff_buffer_basic_32(int32_t *inptr, int length, int32_t *outptr, int stuff, int l_output_bit_depth) { + int tstuff = stuff; + if ((stuff > 1) || (stuff < -1) || (length <100)) { + // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); + tstuff = 0; // if any of these conditions hold, don't stuff anything/ + } + int i; + int stuffsamp = length; + if (tstuff) + // stuffsamp = rand() % (length - 1); + stuffsamp = + (rand() % (length - 2)) + 1; // ensure there's always a sample before and after the item + + pthread_mutex_lock(&vol_mutex); + for (i = 0; i < stuffsamp; i++) { // the whole frame, if no stuffing + *outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); + *outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); + }; + if (tstuff) { + if (tstuff == 1) { + // debug(3, "+++++++++"); + // interpolate one sample + //*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1); + //*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1); + *outptr++ = dithered_vol_32(mean_32(inptr[-2], inptr[0]),l_output_bit_depth); + *outptr++ = dithered_vol_32(mean_32(inptr[-1], inptr[1]),l_output_bit_depth); + } else if (stuff == -1) { + // debug(3, "---------"); + inptr++; + inptr++; + } + + // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the rest. + int remainder = length; + if (tstuff<0) + remainder = remainder+tstuff; // don't run over the correct end of the output buffer + + for (i = stuffsamp; i < remainder; i++) { + *outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); + *outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); + } + } + pthread_mutex_unlock(&vol_mutex); + + return length + tstuff; +} + static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff) { int tstuff = stuff; if ((stuff > 1) || (stuff < -1) || (length <100)) { @@ -1050,6 +1146,9 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff } #ifdef HAVE_LIBSOXR +static int stuff_buffer_soxr_32(int32_t *inptr, int length, int32_t *outptr, int stuff, int l_output_bit_depth) { + return length; +} // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) { int tstuff = stuff; @@ -1526,8 +1625,8 @@ static void *player_thread_func(void *arg) { if (config.no_sync!=0) amount_to_stuff = 0 ; // no stuffing if it's been disabled -// if ((amount_to_stuff == 0) && (fix_volume == 0x10000) && ((config.output_rate==0) || (config.output_rate==44100)) && ((config.output_format==0) || (config.output_format==SPS_FORMAT_S16_LE))) { -if (1) { + if ((amount_to_stuff == 0) && (fix_volume == 0x10000) && ((config.output_rate==0) || (config.output_rate==44100)) && ((config.output_format==0) || (config.output_format==SPS_FORMAT_S16_LE))) { + // if no stuffing needed and no volume adjustment, then // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the // output device... @@ -1546,16 +1645,25 @@ if (1) { switch (config.packet_stuffing) { case ST_basic: // if (amount_to_stuff) debug(1,"Basic stuff..."); - play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, amount_to_stuff); + if (tbuf) + play_samples = stuff_buffer_basic_32((int32_t*)inbuf, inbuflength, (int32_t*)outbuf, amount_to_stuff,output_bit_depth); + else + play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, amount_to_stuff); break; case ST_soxr: // if (amount_to_stuff) debug(1,"Soxr stuff..."); - play_samples = stuff_buffer_soxr(inbuf, inbuflength, outbuf, amount_to_stuff); + if (tbuf) + play_samples = stuff_buffer_soxr_32((int32_t*)inbuf, inbuflength, (int32_t*)outbuf, amount_to_stuff,output_bit_depth); + else + play_samples = stuff_buffer_soxr(inbuf, inbuflength, outbuf, amount_to_stuff); break; } #else // if (amount_to_stuff) debug(1,"Standard stuff..."); - play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, amount_to_stuff); + if (tbuf) + play_samples = stuff_buffer_basic_32((int32_t*)inbuf, inbuflength, (int32_t*)outbuf, amount_to_stuff,output_bit_depth); + else + play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, amount_to_stuff); #endif /* @@ -1622,7 +1730,10 @@ if (1) { config.output->play(inbuf, inbuflength); } else { - play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, 0); // no stuffing, but volume adjustment + if (tbuf) + play_samples = stuff_buffer_basic_32((int32_t*)inbuf, inbuflength, (int32_t*)outbuf, 0, output_bit_depth); // no stuffing, but volume adjustment + else + play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, 0); // no stuffing, but volume adjustment if (outbuf==NULL) debug(1,"NULL outbuf to play -- skipping it."); From 06fce76ce44148a74c668623c447e7a7799f5442 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 5 Sep 2016 22:35:53 +0100 Subject: [PATCH 027/135] Preliminary support for 32-bit stuffing --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 066e5db2..b20350cf 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0 (B8)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0 (B9)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 4e4edf9909067e041a483389346c403e70221472 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 7 Sep 2016 09:05:43 +0100 Subject: [PATCH 028/135] Add a fancy 64-bit pseudo random number generator -- thanks to its developers --- Makefile.am | 2 +- mt19937-64.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ mt64.h | 81 +++++++++++++++++++++++++ player.c | 14 ++--- shairport.c | 9 +++ 5 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 mt19937-64.c create mode 100644 mt64.h diff --git a/Makefile.am b/Makefile.am index ff677f80..332f3255 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = man bin_PROGRAMS = shairport-sync -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c mt19937-64.c AM_CFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" diff --git a/mt19937-64.c b/mt19937-64.c new file mode 100644 index 00000000..5c742422 --- /dev/null +++ b/mt19937-64.c @@ -0,0 +1,168 @@ +/* + A C-program for MT19937-64 (2014/2/23 version). + Coded by Takuji Nishimura and Makoto Matsumoto. + + This is a 64-bit version of Mersenne Twister pseudorandom number + generator. + + Before using, initialize the state by using init_genrand64(seed) + or init_by_array64(init_key, key_length). + + Copyright (C) 2004, 2014, Makoto Matsumoto and Takuji Nishimura, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are 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. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER 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. + + References: + T. Nishimura, ``Tables of 64-bit Mersenne Twisters'' + ACM Transactions on Modeling and + Computer Simulation 10. (2000) 348--357. + M. Matsumoto and T. Nishimura, + ``Mersenne Twister: a 623-dimensionally equidistributed + uniform pseudorandom number generator'' + ACM Transactions on Modeling and + Computer Simulation 8. (Jan. 1998) 3--30. + + Any feedback is very welcome. + http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces) +*/ + + +#include +#include "mt64.h" + +#define NN 312 +#define MM 156 +#define MATRIX_A UINT64_C(0xB5026F5AA96619E9) +#define UM UINT64_C(0xFFFFFFFF80000000) /* Most significant 33 bits */ +#define LM UINT64_C(0x7FFFFFFF) /* Least significant 31 bits */ + + +/* The array for the state vector */ +static uint64_t mt[NN]; +/* mti==NN+1 means mt[NN] is not initialized */ +static int mti=NN+1; + +/* initializes mt[NN] with a seed */ +void init_genrand64(uint64_t seed) +{ + mt[0] = seed; + for (mti=1; mti> 62)) + mti); +} + +/* initialize by an array with array-length */ +/* init_key is the array for initializing keys */ +/* key_length is its length */ +void init_by_array64(uint64_t init_key[], + uint64_t key_length) +{ + unsigned int i, j; + uint64_t k; + init_genrand64(UINT64_C(19650218)); + i=1; j=0; + k = (NN>key_length ? NN : key_length); + for (; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 62)) * UINT64_C(3935559000370003845))) + + init_key[j] + j; /* non linear */ + i++; j++; + if (i>=NN) { mt[0] = mt[NN-1]; i=1; } + if (j>=key_length) j=0; + } + for (k=NN-1; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 62)) * UINT64_C(2862933555777941757))) + - i; /* non linear */ + i++; + if (i>=NN) { mt[0] = mt[NN-1]; i=1; } + } + + mt[0] = UINT64_C(1) << 63; /* MSB is 1; assuring non-zero initial array */ +} + +/* generates a random number on [0, 2^64-1]-interval */ +uint64_t genrand64_int64(void) +{ + int i; + uint64_t x; + static uint64_t mag01[2]={UINT64_C(0), MATRIX_A}; + + if (mti >= NN) { /* generate NN words at one time */ + + /* if init_genrand64() has not been called, */ + /* a default initial seed is used */ + if (mti == NN+1) + init_genrand64(UINT64_C(5489)); + + for (i=0;i>1) ^ mag01[(int)(x&UINT64_C(1))]; + } + for (;i>1) ^ mag01[(int)(x&UINT64_C(1))]; + } + x = (mt[NN-1]&UM)|(mt[0]&LM); + mt[NN-1] = mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&UINT64_C(1))]; + + mti = 0; + } + + x = mt[mti++]; + + x ^= (x >> 29) & UINT64_C(0x5555555555555555); + x ^= (x << 17) & UINT64_C(0x71D67FFFEDA60000); + x ^= (x << 37) & UINT64_C(0xFFF7EEE000000000); + x ^= (x >> 43); + + return x; +} + +/* generates a random number on [0, 2^63-1]-interval */ +int64_t genrand64_int63(void) +{ + return (int64_t)(genrand64_int64() >> 1); +} + +/* generates a random number on [0,1]-real-interval */ +double genrand64_real1(void) +{ + return (genrand64_int64() >> 11) * (1.0/9007199254740991.0); +} + +/* generates a random number on [0,1)-real-interval */ +double genrand64_real2(void) +{ + return (genrand64_int64() >> 11) * (1.0/9007199254740992.0); +} + +/* generates a random number on (0,1)-real-interval */ +double genrand64_real3(void) +{ + return ((genrand64_int64() >> 12) + 0.5) * (1.0/4503599627370496.0); +} diff --git a/mt64.h b/mt64.h new file mode 100644 index 00000000..5d0a4152 --- /dev/null +++ b/mt64.h @@ -0,0 +1,81 @@ +/* + A C-program for MT19937-64 (2014/2/23 version). + Coded by Takuji Nishimura and Makoto Matsumoto. + + This is a 64-bit version of Mersenne Twister pseudorandom number + generator. + + Before using, initialize the state by using init_genrand64(seed) + or init_by_array64(init_key, key_length). + + Copyright (C) 2004, 2014, Makoto Matsumoto and Takuji Nishimura, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are 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. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER 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. + + References: + T. Nishimura, ``Tables of 64-bit Mersenne Twisters'' + ACM Transactions on Modeling and + Computer Simulation 10. (2000) 348--357. + M. Matsumoto and T. Nishimura, + ``Mersenne Twister: a 623-dimensionally equidistributed + uniform pseudorandom number generator'' + ACM Transactions on Modeling and + Computer Simulation 8. (Jan. 1998) 3--30. + + Any feedback is very welcome. + http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces) +*/ + +#include + +/* initializes mt[NN] with a seed */ +void init_genrand64(uint64_t seed); + +/* initialize by an array with array-length */ +/* init_key is the array for initializing keys */ +/* key_length is its length */ +void init_by_array64(uint64_t init_key[], + uint64_t key_length); + +/* generates a random number on [0, 2^64-1]-interval */ +uint64_t genrand64_int64(void); + + +/* generates a random number on [0, 2^63-1]-interval */ +int64_t genrand64_int63(void); + +/* generates a random number on [0,1]-real-interval */ +double genrand64_real1(void); + +/* generates a random number on [0,1)-real-interval */ +double genrand64_real2(void); + +/* generates a random number on (0,1)-real-interval */ +double genrand64_real3(void); diff --git a/player.c b/player.c index 3aafa459..9299ab55 100644 --- a/player.c +++ b/player.c @@ -68,6 +68,7 @@ #include "alac.h" #include "apple_alac.h" +#include "mt64.h" // parameters from the source static unsigned char *aesiv; @@ -583,13 +584,12 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { } int64_t rand_in_range_64(int64_t exclusive_range_limit) { - static uint64_t lcg_prev = 12345; - // returns a pseudo random integer in the range 0 to (exclusive_range_limit-1) inclusive - int64_t sp = lcg_prev; - int64_t rl = exclusive_range_limit; - lcg_prev = lcg_prev * 69069 + 3; // crappy psrg - sp = sp*rl; // 64 bit calculation. INtersting part if above the 32 rightmost bits; - return sp; + // this is a call to a 64-bit pseudo-random number generator + // from the archive downloaded http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/mt19937-64stdint.tgz + // from http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt64.html + + + return genrand64_int63() % exclusive_range_limit; } static inline int32_t dithered_vol_32(int32_t sample, int output_precision) { diff --git a/shairport.c b/shairport.c index 1b746922..050338d8 100644 --- a/shairport.c +++ b/shairport.c @@ -54,6 +54,7 @@ #include "rtsp.h" #include "rtp.h" #include "mdns.h" +#include "mt64.h" // 64-bit prng #include #include @@ -778,6 +779,14 @@ int main(int argc, char **argv) { #ifdef HAVE_APPLE_ALAC config.decoders_supported += 1<= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { From 62714370660ac69235919e5dd0424b40c57c06c4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 7 Sep 2016 09:07:09 +0100 Subject: [PATCH 029/135] Add a fancy 64-bit pseudo random number generator -- thanks to its developers --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index b20350cf..d910085e 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0 (B9)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0build10)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From e5eb441b31f3d7223ec29ba8ceb1dbd3628a62dd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 7 Sep 2016 09:14:35 +0100 Subject: [PATCH 030/135] Remove prng initialisation code -- it's not needed --- shairport.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/shairport.c b/shairport.c index 050338d8..1b746922 100644 --- a/shairport.c +++ b/shairport.c @@ -54,7 +54,6 @@ #include "rtsp.h" #include "rtp.h" #include "mdns.h" -#include "mt64.h" // 64-bit prng #include #include @@ -779,14 +778,6 @@ int main(int argc, char **argv) { #ifdef HAVE_APPLE_ALAC config.decoders_supported += 1<= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { From 4849c1d429b085e973e7246bfae01d6607dddf71 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 7 Sep 2016 16:55:14 +0100 Subject: [PATCH 031/135] Remove mersenne generator -- too slow, try another one --- Makefile.am | 2 +- common.c | 37 ++++++++++++ common.h | 8 +++ mt19937-64.c | 168 --------------------------------------------------- mt64.h | 81 ------------------------- player.c | 26 ++++---- shairport.c | 4 ++ 7 files changed, 61 insertions(+), 265 deletions(-) delete mode 100644 mt19937-64.c delete mode 100644 mt64.h diff --git a/Makefile.am b/Makefile.am index 332f3255..1be55de5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = man bin_PROGRAMS = shairport-sync -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c mt19937-64.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c AM_CFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" diff --git a/common.c b/common.c index 39a936f5..92abaa15 100644 --- a/common.c +++ b/common.c @@ -575,3 +575,40 @@ char *str_replace ( const char *string, const char *substr, const char *replacem } return newstr; } + +/* from http://burtleburtle.net/bob/rand/smallprng.html */ + +typedef uint64_t u8; +typedef struct ranctx { uint64_t a; uint64_t b; uint64_t c; uint64_t d; } ranctx; + +static struct ranctx rx; + +#define rot(x,k) (((x)<<(k))|((x)>>(64-(k)))) +inline uint64_t ranval( ranctx *x ) { + uint64_t e = x->a - rot(x->b, 7); + x->a = x->b ^ rot(x->c, 13); + x->b = x->c + rot(x->d, 37); + x->c = x->d + e; + x->d = e + x->a; + return x->d; +} + +void raninit( ranctx *x, uint64_t seed ) { + uint64_t i; + x->a = 0xf1ea5eed, x->b = x->c = x->d = seed; + for (i=0; i<20; ++i) { + (void)ranval(x); + } +} + +void r64init(uint64_t seed) { + raninit(&rx,seed); +} + +inline uint64_t r64u() { + return(ranval(&rx)); +} + +inline int64_t r64i() { + return(ranval(&rx)>>1); +} \ No newline at end of file diff --git a/common.h b/common.h index 4e6b1141..93c2ce88 100644 --- a/common.h +++ b/common.h @@ -145,6 +145,14 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a /* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */ char *str_replace ( const char *string, const char *substr, const char *replacement ); + +// based on http://burtleburtle.net/bob/rand/smallprng.html + +void r64init(uint64_t seed); +inline uint64_t r64u(); +inline int64_t r64i(); + + int debuglev; void die(char *format, ...); void warn(char *format, ...); diff --git a/mt19937-64.c b/mt19937-64.c deleted file mode 100644 index 5c742422..00000000 --- a/mt19937-64.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - A C-program for MT19937-64 (2014/2/23 version). - Coded by Takuji Nishimura and Makoto Matsumoto. - - This is a 64-bit version of Mersenne Twister pseudorandom number - generator. - - Before using, initialize the state by using init_genrand64(seed) - or init_by_array64(init_key, key_length). - - Copyright (C) 2004, 2014, Makoto Matsumoto and Takuji Nishimura, - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are 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. The names of its contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER 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. - - References: - T. Nishimura, ``Tables of 64-bit Mersenne Twisters'' - ACM Transactions on Modeling and - Computer Simulation 10. (2000) 348--357. - M. Matsumoto and T. Nishimura, - ``Mersenne Twister: a 623-dimensionally equidistributed - uniform pseudorandom number generator'' - ACM Transactions on Modeling and - Computer Simulation 8. (Jan. 1998) 3--30. - - Any feedback is very welcome. - http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html - email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces) -*/ - - -#include -#include "mt64.h" - -#define NN 312 -#define MM 156 -#define MATRIX_A UINT64_C(0xB5026F5AA96619E9) -#define UM UINT64_C(0xFFFFFFFF80000000) /* Most significant 33 bits */ -#define LM UINT64_C(0x7FFFFFFF) /* Least significant 31 bits */ - - -/* The array for the state vector */ -static uint64_t mt[NN]; -/* mti==NN+1 means mt[NN] is not initialized */ -static int mti=NN+1; - -/* initializes mt[NN] with a seed */ -void init_genrand64(uint64_t seed) -{ - mt[0] = seed; - for (mti=1; mti> 62)) + mti); -} - -/* initialize by an array with array-length */ -/* init_key is the array for initializing keys */ -/* key_length is its length */ -void init_by_array64(uint64_t init_key[], - uint64_t key_length) -{ - unsigned int i, j; - uint64_t k; - init_genrand64(UINT64_C(19650218)); - i=1; j=0; - k = (NN>key_length ? NN : key_length); - for (; k; k--) { - mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 62)) * UINT64_C(3935559000370003845))) - + init_key[j] + j; /* non linear */ - i++; j++; - if (i>=NN) { mt[0] = mt[NN-1]; i=1; } - if (j>=key_length) j=0; - } - for (k=NN-1; k; k--) { - mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 62)) * UINT64_C(2862933555777941757))) - - i; /* non linear */ - i++; - if (i>=NN) { mt[0] = mt[NN-1]; i=1; } - } - - mt[0] = UINT64_C(1) << 63; /* MSB is 1; assuring non-zero initial array */ -} - -/* generates a random number on [0, 2^64-1]-interval */ -uint64_t genrand64_int64(void) -{ - int i; - uint64_t x; - static uint64_t mag01[2]={UINT64_C(0), MATRIX_A}; - - if (mti >= NN) { /* generate NN words at one time */ - - /* if init_genrand64() has not been called, */ - /* a default initial seed is used */ - if (mti == NN+1) - init_genrand64(UINT64_C(5489)); - - for (i=0;i>1) ^ mag01[(int)(x&UINT64_C(1))]; - } - for (;i>1) ^ mag01[(int)(x&UINT64_C(1))]; - } - x = (mt[NN-1]&UM)|(mt[0]&LM); - mt[NN-1] = mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&UINT64_C(1))]; - - mti = 0; - } - - x = mt[mti++]; - - x ^= (x >> 29) & UINT64_C(0x5555555555555555); - x ^= (x << 17) & UINT64_C(0x71D67FFFEDA60000); - x ^= (x << 37) & UINT64_C(0xFFF7EEE000000000); - x ^= (x >> 43); - - return x; -} - -/* generates a random number on [0, 2^63-1]-interval */ -int64_t genrand64_int63(void) -{ - return (int64_t)(genrand64_int64() >> 1); -} - -/* generates a random number on [0,1]-real-interval */ -double genrand64_real1(void) -{ - return (genrand64_int64() >> 11) * (1.0/9007199254740991.0); -} - -/* generates a random number on [0,1)-real-interval */ -double genrand64_real2(void) -{ - return (genrand64_int64() >> 11) * (1.0/9007199254740992.0); -} - -/* generates a random number on (0,1)-real-interval */ -double genrand64_real3(void) -{ - return ((genrand64_int64() >> 12) + 0.5) * (1.0/4503599627370496.0); -} diff --git a/mt64.h b/mt64.h deleted file mode 100644 index 5d0a4152..00000000 --- a/mt64.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - A C-program for MT19937-64 (2014/2/23 version). - Coded by Takuji Nishimura and Makoto Matsumoto. - - This is a 64-bit version of Mersenne Twister pseudorandom number - generator. - - Before using, initialize the state by using init_genrand64(seed) - or init_by_array64(init_key, key_length). - - Copyright (C) 2004, 2014, Makoto Matsumoto and Takuji Nishimura, - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are 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. The names of its contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER 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. - - References: - T. Nishimura, ``Tables of 64-bit Mersenne Twisters'' - ACM Transactions on Modeling and - Computer Simulation 10. (2000) 348--357. - M. Matsumoto and T. Nishimura, - ``Mersenne Twister: a 623-dimensionally equidistributed - uniform pseudorandom number generator'' - ACM Transactions on Modeling and - Computer Simulation 8. (Jan. 1998) 3--30. - - Any feedback is very welcome. - http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html - email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces) -*/ - -#include - -/* initializes mt[NN] with a seed */ -void init_genrand64(uint64_t seed); - -/* initialize by an array with array-length */ -/* init_key is the array for initializing keys */ -/* key_length is its length */ -void init_by_array64(uint64_t init_key[], - uint64_t key_length); - -/* generates a random number on [0, 2^64-1]-interval */ -uint64_t genrand64_int64(void); - - -/* generates a random number on [0, 2^63-1]-interval */ -int64_t genrand64_int63(void); - -/* generates a random number on [0,1]-real-interval */ -double genrand64_real1(void); - -/* generates a random number on [0,1)-real-interval */ -double genrand64_real2(void); - -/* generates a random number on (0,1)-real-interval */ -double genrand64_real3(void); diff --git a/player.c b/player.c index 9299ab55..a47b985c 100644 --- a/player.c +++ b/player.c @@ -68,7 +68,6 @@ #include "alac.h" #include "apple_alac.h" -#include "mt64.h" // parameters from the source static unsigned char *aesiv; @@ -85,6 +84,7 @@ static int input_bytes_per_frame = 4; static int output_bytes_per_frame; static int output_sample_ratio; static int output_sample_rate; +static int64_t previous_random_number = 0; // The maximum frame size change there can be is +/- 1; @@ -583,15 +583,6 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { return sp >> 32; } -int64_t rand_in_range_64(int64_t exclusive_range_limit) { - // this is a call to a 64-bit pseudo-random number generator - // from the archive downloaded http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/mt19937-64stdint.tgz - // from http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt64.html - - - return genrand64_int63() % exclusive_range_limit; -} - static inline int32_t dithered_vol_32(int32_t sample, int output_precision) { if ((fix_volume==0x1000) && (output_precision==32)) { return sample; @@ -599,10 +590,15 @@ static inline int32_t dithered_vol_32(int32_t sample, int output_precision) { int64_t s = sample; // 64 bit signed int64_t v = fix_volume & 0x1FFFF; // 17 bit unsigned made a 33 bit unsigned v<<=16; - s = s*v; // 32 bit multiplication - /* - // here, do the dithering - int64_t tpdf = rand_in_range_64((1<<(64-output_precision))+1) - rand_in_range_64((1<<(64-output_precision))+1); + s = s*v; // 32 bit multiplication -- we need to dither it down to its target resolution + + int64_t r = r64i(); + int64_t mask = 1<<(64+1-output_precision); + //mask -=1; + int64_t tpdf = (r&mask); //-(previous_random_number&((1<<(64+1-output_precision))-1)); + debug(1,"output precision is %d, m is %llx, r is %llx, tpdf is %llx",output_precision,mask,r,tpdf); + previous_random_number=r; + // Check there's no clipping -- if there is, if (tpdf>=0) { if (INT64_MAX-tpdf>=s) @@ -615,7 +611,7 @@ static inline int32_t dithered_vol_32(int32_t sample, int output_precision) { else s = INT64_MIN; } - */ + s>>=(64-output_precision); return s; } diff --git a/shairport.c b/shairport.c index 1b746922..be8100a3 100644 --- a/shairport.c +++ b/shairport.c @@ -778,6 +778,10 @@ int main(int argc, char **argv) { #ifdef HAVE_APPLE_ALAC config.decoders_supported += 1<= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { From 2fad92e7a7b2d2c4a232f5f00f0aa9beb87eaf2f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 8 Sep 2016 07:57:28 +0100 Subject: [PATCH 032/135] Get dither mask working right --- player.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/player.c b/player.c index a47b985c..5990c45d 100644 --- a/player.c +++ b/player.c @@ -593,10 +593,10 @@ static inline int32_t dithered_vol_32(int32_t sample, int output_precision) { s = s*v; // 32 bit multiplication -- we need to dither it down to its target resolution int64_t r = r64i(); - int64_t mask = 1<<(64+1-output_precision); - //mask -=1; + int64_t mask = (int64_t)1<<(64+1-output_precision); + mask -=1; int64_t tpdf = (r&mask); //-(previous_random_number&((1<<(64+1-output_precision))-1)); - debug(1,"output precision is %d, m is %llx, r is %llx, tpdf is %llx",output_precision,mask,r,tpdf); +// debug(1,"output precision is %d, m is %llx, r is %llx, tpdf is %llx",output_precision,mask,r,tpdf); previous_random_number=r; // Check there's no clipping -- if there is, From 8d008fa2ec6bb3688289fe9e7a59fcd0e3589fbe Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 8 Sep 2016 15:56:22 +0100 Subject: [PATCH 033/135] Begin to add full-scale post processing. Does not compile. --- common.c | 8 ++--- common.h | 4 +-- player.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 98 insertions(+), 14 deletions(-) diff --git a/common.c b/common.c index 92abaa15..c1eb58e2 100644 --- a/common.c +++ b/common.c @@ -584,7 +584,7 @@ typedef struct ranctx { uint64_t a; uint64_t b; uint64_t c; uint64_t d; } ranctx static struct ranctx rx; #define rot(x,k) (((x)<<(k))|((x)>>(64-(k)))) -inline uint64_t ranval( ranctx *x ) { +uint64_t ranval( ranctx *x ) { uint64_t e = x->a - rot(x->b, 7); x->a = x->b ^ rot(x->c, 13); x->b = x->c + rot(x->d, 37); @@ -605,10 +605,10 @@ void r64init(uint64_t seed) { raninit(&rx,seed); } -inline uint64_t r64u() { +uint64_t r64u() { return(ranval(&rx)); } -inline int64_t r64i() { +int64_t r64i() { return(ranval(&rx)>>1); -} \ No newline at end of file +} diff --git a/common.h b/common.h index 93c2ce88..d146ab3a 100644 --- a/common.h +++ b/common.h @@ -149,8 +149,8 @@ char *str_replace ( const char *string, const char *substr, const char *replacem // based on http://burtleburtle.net/bob/rand/smallprng.html void r64init(uint64_t seed); -inline uint64_t r64u(); -inline int64_t r64i(); +uint64_t r64u(); +int64_t r64i(); int debuglev; diff --git a/player.c b/player.c index 5990c45d..4be02b2f 100644 --- a/player.c +++ b/player.c @@ -583,6 +583,78 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { return sp >> 32; } +inline void process_sample(int32_t sample,char**outp,enum sps_format_t format,int volume,int dither) { + int64_t hyper_sample = sample; + int result; + // first, modify volume, if necessary + if (volume==0x10000) + hyper_sample<<32; + else + hyper_sample*=(volume<<16); + + // next, do dither, if necessary + if (dither) { + int64_t dither_mask; + switch (format) { + case SPS_FORMAT_S32_LE: + dither_mask = (int64_t)1<<(64+1-32); + break; + case SPS_FORMAT_S24_LE: + dither_mask = (int64_t)1<<(64+1-24); + break; + case SPS_FORMAT_S16_LE: + dither_mask = (int64_t)1<<(64+1-16); + break; + case SPS_FORMAT_S8: + dither_mask = (int64_t)1<<(64+1-24); + break; + } + dither_mask-=1; + int64_t r = r64i(); + int64_t tpdf = (r&dither_mask)-(previous_random_number&dither_mask); + previous_random_number=r; + // add dither, allowing for clipping + if (tpdf>=0) { + if (INT64_MAX-tpdf>=hyper_sample) + hyper_sample += tpdf; + else + hyper_sample = INT64_MAX; + } else { + if (INT64_MIN-tpdf<=hyper_sample) + hyper_sample += tpdf; + else + hyper_sample = INT64_MIN; + } + // dither is complete here + } + // move the result to the desired position in the int64_t + char *op = *outp; + switch (format) { + case SPS_FORMAT_S32_LE: + hyper_sample>>64-32; + *op++ = (int32_t)hyper_sample; + result=4; + break; + case SPS_FORMAT_S24_LE: + hyper_sample>>64-24; + *op = (int32_t)hyper_sample; + result=4; + break; + case SPS_FORMAT_S16_LE: + hyper_sample>>64-16; + *op = (int16_t)hyper_sample; + result=2; + break; + case SPS_FORMAT_S8: + hyper_sample>>64-8; + *op = (int8_t)hyper_sample; + result=1; + break; + } + *outp+=result; +} + + static inline int32_t dithered_vol_32(int32_t sample, int output_precision) { if ((fix_volume==0x1000) && (output_precision==32)) { return sample; @@ -1046,13 +1118,21 @@ static inline int32_t mean_32(int32_t a, int32_t b) { return r; } +// this takes an array of signed 32-bit integers and (a) removes or inserts a frame as specified in stuff, +// (b) multiplies each sample by the fixedvolume (a 16-bit quantity) +// (c) dithers the result to the output size 32/24/16 bits +// (d) outputs the result in the approprate format +// formats accepted so far include S16_LE, S24_LE and S32_LE on a little endinan machine. + // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 -static int stuff_buffer_basic_32(int32_t *inptr, int length, int32_t *outptr, int stuff, int l_output_bit_depth) { +static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, char *outptr, int* outlength, int stuff, int dither) { int tstuff = stuff; + char *l_outptr = outptr; if ((stuff > 1) || (stuff < -1) || (length <100)) { // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); tstuff = 0; // if any of these conditions hold, don't stuff anything/ } + int i; int stuffsamp = length; if (tstuff) @@ -1062,8 +1142,10 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, int32_t *outptr, in pthread_mutex_lock(&vol_mutex); for (i = 0; i < stuffsamp; i++) { // the whole frame, if no stuffing - *outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); - *outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); + process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); + process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); + //*outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); + //*outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); }; if (tstuff) { if (tstuff == 1) { @@ -1071,8 +1153,10 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, int32_t *outptr, in // interpolate one sample //*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1); //*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1); - *outptr++ = dithered_vol_32(mean_32(inptr[-2], inptr[0]),l_output_bit_depth); - *outptr++ = dithered_vol_32(mean_32(inptr[-1], inptr[1]),l_output_bit_depth); + process_sample(mean_32(inptr[-2], inptr[0]),&l_outptr,l_output_format,fix_volume,dither); + process_sample(mean_32(inptr[-1], inptr[1]),&l_outptr,l_output_format,fix_volume,dither); + //*outptr++ = dithered_vol_32(mean_32(inptr[-2], inptr[0]),l_output_bit_depth); + //*outptr++ = dithered_vol_32(mean_32(inptr[-1], inptr[1]),l_output_bit_depth); } else if (stuff == -1) { // debug(3, "---------"); inptr++; @@ -1085,12 +1169,12 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, int32_t *outptr, in remainder = remainder+tstuff; // don't run over the correct end of the output buffer for (i = stuffsamp; i < remainder; i++) { - *outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); - *outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); + process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); + process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); } } pthread_mutex_unlock(&vol_mutex); - + *outlength = l_outptr-outptr; // in bytes return length + tstuff; } From 3469a2ab026baca66b7ae08c01e4e91259cdc241 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 8 Sep 2016 21:53:19 +0100 Subject: [PATCH 034/135] New backend for converting 32-bit "native" samples to output format ready for testing --- player.c | 188 ++++++++++++++++++++++++---------------------------- shairport.c | 4 +- 2 files changed, 89 insertions(+), 103 deletions(-) diff --git a/player.c b/player.c index 4be02b2f..eccc19d5 100644 --- a/player.c +++ b/player.c @@ -74,7 +74,7 @@ static unsigned char *aesiv; #ifdef HAVE_LIBSSL static AES_KEY aes; #endif -static int input_sample_rate, input_bit_depth, input_num_channels, max_frames_per_packet; +static int input_rate, input_bit_depth, input_num_channels, max_frames_per_packet; static uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval;// timestamp_epoch of zero means not initialised, could start at 2 or 1. @@ -83,7 +83,7 @@ static uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval;// t static int input_bytes_per_frame = 4; static int output_bytes_per_frame; static int output_sample_ratio; -static int output_sample_rate; +static int output_rate; static int64_t previous_random_number = 0; @@ -421,7 +421,7 @@ static int init_decoder(int32_t fmtp[12]) { max_frames_per_packet = fmtp[1]; // number of audio frames per packet. - input_sample_rate = fmtp[11]; + input_rate = fmtp[11]; input_num_channels = fmtp[7]; input_bit_depth = fmtp[3]; @@ -583,7 +583,7 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { return sp >> 32; } -inline void process_sample(int32_t sample,char**outp,enum sps_format_t format,int volume,int dither) { +static inline void process_sample(int32_t sample,char**outp,enum sps_format_t format,int volume,int dither) { int64_t hyper_sample = sample; int result; // first, modify volume, if necessary @@ -858,14 +858,14 @@ static abuf_t *buffer_get_frame(void) { debug(1,"Output sample ratio is %d",output_sample_ratio); - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*output_sample_rate; + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*output_rate; if (delta>=0) { - int64_t delta_fp_sec = (delta << 32) / output_sample_rate; // int64_t which is positive + int64_t delta_fp_sec = (delta << 32) / output_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; } else { int64_t abs_delta = -delta; - int64_t delta_fp_sec = (abs_delta << 32) / output_sample_rate; // int64_t which is positive + int64_t delta_fp_sec = (abs_delta << 32) / output_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; } @@ -880,25 +880,25 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); if (first_packet_time_to_play != 0) { // recalculate first_packet_time_to_play -- the latency might change - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*output_sample_rate; + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*output_rate; if (delta>=0) { - int64_t delta_fp_sec = (delta << 32) / output_sample_rate; // int64_t which is positive + int64_t delta_fp_sec = (delta << 32) / output_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; } else { int64_t abs_delta = -delta; - int64_t delta_fp_sec = (abs_delta << 32) / output_sample_rate; // int64_t which is positive + int64_t delta_fp_sec = (abs_delta << 32) / output_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; } - int64_t max_dac_delay = output_sample_rate/10; + int64_t max_dac_delay = output_rate/10; int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC if (local_time_now >= first_packet_time_to_play) { // we've gone past the time... // debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp // of %llx and dac_delay of %d and %d packets; - // flush.",(((tn-first_packet_time_to_play)*output_sample_rate)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, + // flush.",(((tn-first_packet_time_to_play)*output_rate)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, // ab_write)); if (config.output->flush) @@ -918,7 +918,7 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); } else dac_delay = 0; int64_t gross_frame_gap = - ((first_packet_time_to_play - local_time_now) * output_sample_rate) >> 32; + ((first_packet_time_to_play - local_time_now) * output_rate) >> 32; int64_t exact_frame_gap = gross_frame_gap - dac_delay; if (exact_frame_gap < 0) { // we've gone past the time... @@ -1002,17 +1002,17 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); if (reference_timestamp) { // if we have a reference time int64_t packet_timestamp = curframe->timestamp; // types okay int64_t delta = packet_timestamp - reference_timestamp; - int64_t offset = config.latency*output_sample_ratio + config.audio_backend_latency_offset*output_sample_rate - - config.audio_backend_buffer_desired_length*output_sample_rate; // all arguments are int32_t, so expression promotion okay + int64_t offset = config.latency*output_sample_ratio + config.audio_backend_latency_offset*output_rate - + config.audio_backend_buffer_desired_length*output_rate; // all arguments are int32_t, so expression promotion okay int64_t net_offset = delta + offset; // okay uint64_t time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { - uint64_t net_offset_fp_sec = (net_offset << 32) / output_sample_rate; // int64_t which is positive + uint64_t net_offset_fp_sec = (net_offset << 32) / output_rate; // int64_t which is positive time_to_play += net_offset_fp_sec; // using the latency requested... // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); } else { int64_t abs_net_offset = -net_offset; - uint64_t net_offset_fp_sec = (abs_net_offset << 32) / output_sample_rate; // int64_t which is positive + uint64_t net_offset_fp_sec = (abs_net_offset << 32) / output_rate; // int64_t which is positive time_to_play -= net_offset_fp_sec; // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); } @@ -1034,7 +1034,7 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); if (wait) { uint64_t time_to_wait_for_wakeup_fp = - ((uint64_t)1 << 32) / input_sample_rate; // this is time period of one frame + ((uint64_t)1 << 32) / input_rate; // this is time period of one frame time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time @@ -1125,7 +1125,7 @@ static inline int32_t mean_32(int32_t a, int32_t b) { // formats accepted so far include S16_LE, S24_LE and S32_LE on a little endinan machine. // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 -static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, char *outptr, int* outlength, int stuff, int dither) { +static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, char *outptr, int stuff, int dither) { int tstuff = stuff; char *l_outptr = outptr; if ((stuff > 1) || (stuff < -1) || (length <100)) { @@ -1174,7 +1174,6 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l } } pthread_mutex_unlock(&vol_mutex); - *outlength = l_outptr-outptr; // in bytes return length + tstuff; } @@ -1314,14 +1313,9 @@ static void *player_thread_func(void *arg) { struct inter_threads_record itr; itr.please_stop = 0; timestamp_epoch = 0; // indicate that the next timestamp will be the first one. - maximum_timestamp_interval = input_sample_rate * 60; // actually there shouldn't be more than about 13v seconds of a gap between successive rtptimes, at worst - - output_sample_rate = input_sample_rate; - - if (config.output_rate!=0) - output_sample_rate = config.output_rate; - - output_sample_ratio = output_sample_rate/input_sample_rate; + maximum_timestamp_interval = input_rate * 60; // actually there shouldn't be more than about 13v seconds of a gap between successive rtptimes, at worst + + output_sample_ratio = config.output_rate/input_rate; debug(1,"Output sample ratio is %d.",output_sample_ratio); @@ -1351,7 +1345,7 @@ static void *player_thread_func(void *arg) { // check that there are enough buffers to accommodate the desired latency and the latency offset - int maximum_latency = config.latency+config.audio_backend_latency_offset*input_sample_rate; + int maximum_latency = config.latency+config.audio_backend_latency_offset*input_rate; if ((maximum_latency+(352-1))/352 + 10 > BUFFER_FRAMES) die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame packets may be accommodated.",maximum_latency,BUFFER_FRAMES); connection_state_to_output = get_requested_connection_state_to_output(); @@ -1386,7 +1380,10 @@ static void *player_thread_func(void *arg) { char rnstate[256]; initstate(time(NULL), rnstate, 256); - signed short *inbuf, *outbuf, *tbuf, *silence; + signed short *inbuf, *tbuf, *silence; + + char *outbuf; + int inbuflength; int output_bit_depth = 16; // default; @@ -1409,7 +1406,7 @@ static void *player_thread_func(void *arg) { // if we are changing any of the parameters of the input, like sample rate or sample depth, then we // need an intermediate "transition" buffer -// if ((config.output_rate!=0 && (input_sample_rate!=config.output_rate)) || (input_bit_depth!=output_bit_depth)) { +// if ((config.output_rate!=0 && (input_rate!=config.output_rate)) || (input_bit_depth!=output_bit_depth)) { tbuf = malloc(output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); if (tbuf==NULL) debug(1,"Failed to allocate memory for the transition buffer."); @@ -1492,6 +1489,9 @@ static void *player_thread_func(void *arg) { config.output->play(inbuf, inbuflength); } } else { + + int enable_dither = !((fix_volume==0x10000)&&(input_bit_depth==output_bit_depth)); // avoid dither on audio being sent through without alteration + // here, let's transform the frame of data, if necessary if (tbuf!=NULL) { //this will be null if no changes are needed @@ -1595,10 +1595,10 @@ static void *player_thread_func(void *arg) { if (local_time_now >= reference_timestamp_time) { // debug(1,"td is %lld.",td); td = local_time_now - reference_timestamp_time; // this is the positive value. Conversion is positive uint64_t to int64_t, thus okay - td_in_frames = (td * output_sample_rate) >> 32; + td_in_frames = (td * config.output_rate) >> 32; } else { td = reference_timestamp_time - local_time_now; // this is the absolute value, which should be negated. Conversion is positive uint64_t to int64_t, thus okay. - td_in_frames = (td * output_sample_rate) >> 32; //use the absolute td value for the present. Types okay + td_in_frames = (td * config.output_rate) >> 32; //use the absolute td value for the present. Types okay td_in_frames = -td_in_frames; td = -td; // should be okay, as the range of values should be very small w.r.t 64 bits } @@ -1671,10 +1671,10 @@ static void *player_thread_func(void *arg) { // before we finally commit to this frame, check its sequencing and timing // require a certain error before bothering to fix it... - if (sync_error > config.tolerance*output_sample_rate) { // int64_t > int, okay + if (sync_error > config.tolerance*config.output_rate) { // int64_t > int, okay amount_to_stuff = -1; } - if (sync_error < -config.tolerance*output_sample_rate) { + if (sync_error < -config.tolerance*config.output_rate) { amount_to_stuff = 1; } @@ -1704,74 +1704,58 @@ static void *player_thread_func(void *arg) { if (config.no_sync!=0) amount_to_stuff = 0 ; // no stuffing if it's been disabled - - if ((amount_to_stuff == 0) && (fix_volume == 0x10000) && ((config.output_rate==0) || (config.output_rate==44100)) && ((config.output_format==0) || (config.output_format==SPS_FORMAT_S16_LE))) { - - // if no stuffing needed and no volume adjustment, then - // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the - // output device... - if (inbuf==NULL) - debug(1,"NULL inframe->data to play -- skipping it."); - else { - if (inbuflength==0) - debug(1,"empty frame to play -- skipping it (2)."); - else - config.output->play(inbuf, inbuflength); - } - } else { - + #ifdef HAVE_LIBSOXR - switch (config.packet_stuffing) { - case ST_basic: - // if (amount_to_stuff) debug(1,"Basic stuff..."); - if (tbuf) - play_samples = stuff_buffer_basic_32((int32_t*)inbuf, inbuflength, (int32_t*)outbuf, amount_to_stuff,output_bit_depth); - else - play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, amount_to_stuff); - break; - case ST_soxr: - // if (amount_to_stuff) debug(1,"Soxr stuff..."); - if (tbuf) - play_samples = stuff_buffer_soxr_32((int32_t*)inbuf, inbuflength, (int32_t*)outbuf, amount_to_stuff,output_bit_depth); - else - play_samples = stuff_buffer_soxr(inbuf, inbuflength, outbuf, amount_to_stuff); - break; - } -#else - // if (amount_to_stuff) debug(1,"Standard stuff..."); + switch (config.packet_stuffing) { + case ST_basic: + // if (amount_to_stuff) debug(1,"Basic stuff..."); if (tbuf) - play_samples = stuff_buffer_basic_32((int32_t*)inbuf, inbuflength, (int32_t*)outbuf, amount_to_stuff,output_bit_depth); + play_samples = stuff_buffer_basic_32((int32_t*)inbuf, inbuflength, config.output_format, outbuf, amount_to_stuff, enable_dither); else - play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, amount_to_stuff); + play_samples = stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); + break; + case ST_soxr: + // if (amount_to_stuff) debug(1,"Soxr stuff..."); + if (tbuf) + play_samples = stuff_buffer_soxr_32((int32_t*)inbuf, inbuflength, (int32_t*)outbuf, amount_to_stuff,output_bit_depth); + else + play_samples = stuff_buffer_soxr(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); + break; + } +#else + // if (amount_to_stuff) debug(1,"Standard stuff..."); + if (tbuf) + play_samples = stuff_buffer_basic_32((int32_t*)inbuf, inbuflength, config.output_format, outbuf, amount_to_stuff, enable_dither); + else + play_samples = stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); #endif - /* - { - int co; - int is_silent=1; - short *p = outbuf; - for (co=0;coplay(outbuf, play_samples); + /* + { + int co; + int is_silent=1; + short *p = outbuf; + for (co=0;coplay((short*)outbuf, play_samples); // remove the (short*)! + } + // check for loss of sync // timestamp of zero means an inserted silent frame in place of a missing frame @@ -1781,7 +1765,7 @@ static void *player_thread_func(void *arg) { abs_sync_error = -abs_sync_error; if ((config.no_sync==0) && (inframe->timestamp != 0) && (!please_stop) && (config.resyncthreshold > 0.0) && - (abs_sync_error > config.resyncthreshold*output_sample_rate)) { + (abs_sync_error > config.resyncthreshold*config.output_rate)) { sync_error_out_of_bounds++; // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld; // timestamp: %llx, time now @@ -1811,9 +1795,9 @@ static void *player_thread_func(void *arg) { } else { if (tbuf) - play_samples = stuff_buffer_basic_32((int32_t*)inbuf, inbuflength, (int32_t*)outbuf, 0, output_bit_depth); // no stuffing, but volume adjustment + play_samples = stuff_buffer_basic_32((int32_t*)inbuf, inbuflength, config.output_format, outbuf, 0, enable_dither); else - play_samples = stuff_buffer_basic(inbuf, inbuflength, outbuf, 0); // no stuffing, but volume adjustment + play_samples = stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, 0); // no stuffing, but volume adjustment if (outbuf==NULL) debug(1,"NULL outbuf to play -- skipping it."); @@ -1821,7 +1805,7 @@ static void *player_thread_func(void *arg) { if (inbuf==0) debug(1,"empty frame to play -- skipping it (4)."); else - config.output->play(outbuf, play_samples); + config.output->play((short*)outbuf, play_samples); // remove the (short*)! } } } @@ -1902,7 +1886,7 @@ static void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, 1000*moving_average_sync_error/output_sample_rate, + 10, 1000*moving_average_sync_error/config.output_rate, 10, moving_average_correction * 1000000 / (352*output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / (352*output_sample_ratio), 12, play_number, @@ -1923,7 +1907,7 @@ static void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, 1000*moving_average_sync_error/output_sample_rate, + 10, 1000*moving_average_sync_error/config.output_rate, 12, play_number, 7, missing_packets, 7, late_packets, @@ -1942,7 +1926,7 @@ static void *player_thread_func(void *arg) { "%*llu," /* resend requests */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, 1000*moving_average_sync_error/output_sample_rate, + 10, 1000*moving_average_sync_error/config.output_rate, 12, play_number, 7, missing_packets, 7, late_packets, diff --git a/shairport.c b/shairport.c index be8100a3..538f418e 100644 --- a/shairport.c +++ b/shairport.c @@ -774,6 +774,8 @@ int main(int argc, char **argv) { config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. config.udp_port_base = 6001; config.udp_port_range = 100; + config.output_format = SPS_FORMAT_S16_LE; // default + config.output_rate = 44100; // default config.decoders_supported = 1< Date: Thu, 8 Sep 2016 21:53:48 +0100 Subject: [PATCH 035/135] New backend for converting 32-bit "native" samples to output format ready for testing --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d910085e..c482fa0d 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0build10)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0build11)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From d1c6478035d5956ce2171e7e4f3487a894ae7a87 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 9 Sep 2016 12:34:21 +0100 Subject: [PATCH 036/135] Some bug fixes and hacks. Not working. --- player.c | 53 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/player.c b/player.c index eccc19d5..14aeb097 100644 --- a/player.c +++ b/player.c @@ -587,10 +587,10 @@ static inline void process_sample(int32_t sample,char**outp,enum sps_format_t fo int64_t hyper_sample = sample; int result; // first, modify volume, if necessary - if (volume==0x10000) + //if (volume==0x10000) hyper_sample<<32; - else - hyper_sample*=(volume<<16); + //else + // hyper_sample*=(volume<<16); // next, do dither, if necessary if (dither) { @@ -629,24 +629,25 @@ static inline void process_sample(int32_t sample,char**outp,enum sps_format_t fo } // move the result to the desired position in the int64_t char *op = *outp; + short ss; switch (format) { case SPS_FORMAT_S32_LE: - hyper_sample>>64-32; + hyper_sample>>=(64-32); *op++ = (int32_t)hyper_sample; result=4; break; case SPS_FORMAT_S24_LE: - hyper_sample>>64-24; + hyper_sample>>=(64-24); *op = (int32_t)hyper_sample; result=4; break; case SPS_FORMAT_S16_LE: - hyper_sample>>64-16; + hyper_sample>>=(64-16); *op = (int16_t)hyper_sample; result=2; break; case SPS_FORMAT_S8: - hyper_sample>>64-8; + hyper_sample>>=(64-8); *op = (int8_t)hyper_sample; result=1; break; @@ -858,14 +859,14 @@ static abuf_t *buffer_get_frame(void) { debug(1,"Output sample ratio is %d",output_sample_ratio); - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*output_rate; + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*config.output_rate; if (delta>=0) { - int64_t delta_fp_sec = (delta << 32) / output_rate; // int64_t which is positive + int64_t delta_fp_sec = (delta << 32) / config.output_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; } else { int64_t abs_delta = -delta; - int64_t delta_fp_sec = (abs_delta << 32) / output_rate; // int64_t which is positive + int64_t delta_fp_sec = (abs_delta << 32) / config.output_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; } @@ -880,25 +881,25 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); if (first_packet_time_to_play != 0) { // recalculate first_packet_time_to_play -- the latency might change - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*output_rate; + int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*config.output_rate; if (delta>=0) { - int64_t delta_fp_sec = (delta << 32) / output_rate; // int64_t which is positive + int64_t delta_fp_sec = (delta << 32) / config.output_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; } else { int64_t abs_delta = -delta; - int64_t delta_fp_sec = (abs_delta << 32) / output_rate; // int64_t which is positive + int64_t delta_fp_sec = (abs_delta << 32) / config.output_rate; // int64_t which is positive first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; } - int64_t max_dac_delay = output_rate/10; + int64_t max_dac_delay = config.output_rate/10; int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC if (local_time_now >= first_packet_time_to_play) { // we've gone past the time... // debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp // of %llx and dac_delay of %d and %d packets; - // flush.",(((tn-first_packet_time_to_play)*output_rate)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, + // flush.",(((tn-first_packet_time_to_play)*config.output_rate)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, // ab_write)); if (config.output->flush) @@ -918,7 +919,7 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); } else dac_delay = 0; int64_t gross_frame_gap = - ((first_packet_time_to_play - local_time_now) * output_rate) >> 32; + ((first_packet_time_to_play - local_time_now) * config.output_rate) >> 32; int64_t exact_frame_gap = gross_frame_gap - dac_delay; if (exact_frame_gap < 0) { // we've gone past the time... @@ -1002,17 +1003,17 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); if (reference_timestamp) { // if we have a reference time int64_t packet_timestamp = curframe->timestamp; // types okay int64_t delta = packet_timestamp - reference_timestamp; - int64_t offset = config.latency*output_sample_ratio + config.audio_backend_latency_offset*output_rate - - config.audio_backend_buffer_desired_length*output_rate; // all arguments are int32_t, so expression promotion okay + int64_t offset = config.latency*output_sample_ratio + config.audio_backend_latency_offset*config.output_rate - + config.audio_backend_buffer_desired_length*config.output_rate; // all arguments are int32_t, so expression promotion okay int64_t net_offset = delta + offset; // okay uint64_t time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { - uint64_t net_offset_fp_sec = (net_offset << 32) / output_rate; // int64_t which is positive + uint64_t net_offset_fp_sec = (net_offset << 32) / config.output_rate; // int64_t which is positive time_to_play += net_offset_fp_sec; // using the latency requested... // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); } else { int64_t abs_net_offset = -net_offset; - uint64_t net_offset_fp_sec = (abs_net_offset << 32) / output_rate; // int64_t which is positive + uint64_t net_offset_fp_sec = (abs_net_offset << 32) / config.output_rate; // int64_t which is positive time_to_play -= net_offset_fp_sec; // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); } @@ -1490,7 +1491,8 @@ static void *player_thread_func(void *arg) { } } else { - int enable_dither = !((fix_volume==0x10000)&&(input_bit_depth==output_bit_depth)); // avoid dither on audio being sent through without alteration + int enable_dither = 0; + // int enable_dither = !((fix_volume==0x10000)&&(input_bit_depth==output_bit_depth)); // avoid dither on audio being sent through without alteration // here, let's transform the frame of data, if necessary @@ -1535,7 +1537,15 @@ static void *player_thread_func(void *arg) { } // here, replicate the samples if you're upsampling + for (j=0;j Date: Sat, 10 Sep 2016 18:59:53 +0100 Subject: [PATCH 037/135] Add initial support for U8 and S8 formats --- audio_alsa.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index c736e205..348a0d1f 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -206,7 +206,7 @@ static int init(int argc, char **argv) { die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\""); } - /* Get the output format, using the same names as aplay does -- S16_LE, S32_LE to begin with*/ + /* Get the output format, using the same names as aplay does*/ if (config_lookup_string(config.cfg, "alsa.output_format", &str)) { if (strcasecmp(str, "S16_LE") == 0) config.output_format = SND_PCM_FORMAT_S16_LE; @@ -214,8 +214,12 @@ static int init(int argc, char **argv) { config.output_format = SND_PCM_FORMAT_S24_LE; else if (strcasecmp(str, "S32_LE") == 0) config.output_format = SND_PCM_FORMAT_S32_LE; + else if (strcasecmp(str, "U8") == 0) + config.output_format = SPS_FORMAT_U8; + else if (strcasecmp(str, "S8") == 0) + config.output_format = SPS_FORMAT_S8; else - die("Invalid output format \"%s\". It should be \"S16_LE\", \"S24_LE\" or \"S32_LE\"",str); + die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16_LE\", \"S24_LE\" or \"S32_LE\"",str); } /* Get the output rate, which must be a multiple of 44,100*/ From 6bf0a1d4834389b14021eeaf7d078f9b4bc8e032 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 10 Sep 2016 19:00:57 +0100 Subject: [PATCH 038/135] Beginning to work, also added S8 and U8 support as VMWare Fusion and the Pi both offer them --- player.c | 72 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/player.c b/player.c index 14aeb097..96d3ac27 100644 --- a/player.c +++ b/player.c @@ -587,10 +587,10 @@ static inline void process_sample(int32_t sample,char**outp,enum sps_format_t fo int64_t hyper_sample = sample; int result; // first, modify volume, if necessary - //if (volume==0x10000) - hyper_sample<<32; - //else - // hyper_sample*=(volume<<16); + if (volume==0x10000) + hyper_sample<<=32; + else + hyper_sample*=(volume<<16); // next, do dither, if necessary if (dither) { @@ -606,6 +606,7 @@ static inline void process_sample(int32_t sample,char**outp,enum sps_format_t fo dither_mask = (int64_t)1<<(64+1-16); break; case SPS_FORMAT_S8: + case SPS_FORMAT_U8: dither_mask = (int64_t)1<<(64+1-24); break; } @@ -627,31 +628,38 @@ static inline void process_sample(int32_t sample,char**outp,enum sps_format_t fo } // dither is complete here } + // move the result to the desired position in the int64_t char *op = *outp; - short ss; switch (format) { case SPS_FORMAT_S32_LE: hyper_sample>>=(64-32); - *op++ = (int32_t)hyper_sample; + *(int32_t*)op = hyper_sample; result=4; break; case SPS_FORMAT_S24_LE: hyper_sample>>=(64-24); - *op = (int32_t)hyper_sample; + *(int32_t*)op = hyper_sample; result=4; break; case SPS_FORMAT_S16_LE: hyper_sample>>=(64-16); - *op = (int16_t)hyper_sample; + *(int16_t*)op = hyper_sample; result=2; break; case SPS_FORMAT_S8: hyper_sample>>=(64-8); - *op = (int8_t)hyper_sample; + *op = hyper_sample; result=1; break; - } + case SPS_FORMAT_U8: + hyper_sample>>=(64-8); + hyper_sample+=128; // this is just a guess! + *op = hyper_sample; + result=1; + break; + } + *outp+=result; } @@ -1123,7 +1131,7 @@ static inline int32_t mean_32(int32_t a, int32_t b) { // (b) multiplies each sample by the fixedvolume (a 16-bit quantity) // (c) dithers the result to the output size 32/24/16 bits // (d) outputs the result in the approprate format -// formats accepted so far include S16_LE, S24_LE and S32_LE on a little endinan machine. +// formats accepted so far include U8, S8, S16_LE, S24_LE and S32_LE on a little endinan machine. // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, char *outptr, int stuff, int dither) { @@ -1145,19 +1153,13 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l for (i = 0; i < stuffsamp; i++) { // the whole frame, if no stuffing process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); - //*outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); - //*outptr++ = dithered_vol_32(*inptr++,l_output_bit_depth); }; if (tstuff) { if (tstuff == 1) { // debug(3, "+++++++++"); // interpolate one sample - //*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1); - //*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1); process_sample(mean_32(inptr[-2], inptr[0]),&l_outptr,l_output_format,fix_volume,dither); process_sample(mean_32(inptr[-1], inptr[1]),&l_outptr,l_output_format,fix_volume,dither); - //*outptr++ = dithered_vol_32(mean_32(inptr[-2], inptr[0]),l_output_bit_depth); - //*outptr++ = dithered_vol_32(mean_32(inptr[-1], inptr[1]),l_output_bit_depth); } else if (stuff == -1) { // debug(3, "---------"); inptr++; @@ -1175,6 +1177,7 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l } } pthread_mutex_unlock(&vol_mutex); + return length + tstuff; } @@ -1407,13 +1410,15 @@ static void *player_thread_func(void *arg) { // if we are changing any of the parameters of the input, like sample rate or sample depth, then we // need an intermediate "transition" buffer -// if ((config.output_rate!=0 && (input_rate!=config.output_rate)) || (input_bit_depth!=output_bit_depth)) { - tbuf = malloc(output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); + if (1) { + //if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { + // debug(1,"Define tbuf of length %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); + tbuf = malloc(sizeof(int32_t)*2*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); if (tbuf==NULL) debug(1,"Failed to allocate memory for the transition buffer."); -// } else { -// tbuf = 0; -// } + } else { + tbuf = 0; + } // We might need an output buffer and a buffer of silence. // The size of these dependents on the number of frames, the size of each frame and the maximum size change @@ -1491,8 +1496,8 @@ static void *player_thread_func(void *arg) { } } else { - int enable_dither = 0; - // int enable_dither = !((fix_volume==0x10000)&&(input_bit_depth==output_bit_depth)); // avoid dither on audio being sent through without alteration +// int enable_dither = 0; + int enable_dither = !((fix_volume==0x10000)&&(input_bit_depth==output_bit_depth)); // avoid dither on audio being sent through without alteration // here, let's transform the frame of data, if necessary @@ -1507,7 +1512,7 @@ static void *player_thread_func(void *arg) { for (i=0;i Date: Sun, 11 Sep 2016 10:35:53 +0100 Subject: [PATCH 039/135] Tidy up with a clang-format --- alac.c | 1658 ++++++++++++++++++++------------------------ alac.h | 61 +- apple_alac.cpp | 49 +- apple_alac.h | 11 +- audio.c | 6 +- audio.h | 8 +- audio_alsa.c | 547 ++++++++------- audio_ao.c | 16 +- audio_dummy.c | 8 +- audio_pipe.c | 37 +- audio_pulse.c | 12 +- audio_sndio.c | 6 +- audio_stdout.c | 50 +- common.c | 156 +++-- common.h | 115 ++- mdns.c | 14 +- mdns.h | 17 +- mdns_avahi.c | 307 ++++---- mdns_dns_sd.c | 8 +- mdns_external.c | 22 +- mdns_tinysvcmdns.c | 33 +- player.c | 1544 ++++++++++++++++++++++------------------- player.h | 10 +- rtp.c | 305 ++++---- rtp.h | 12 +- rtsp.c | 325 ++++----- rtsp.h | 1 - shairport.c | 424 +++++------ tinysvcmdns.c | 30 +- 29 files changed, 2907 insertions(+), 2885 deletions(-) diff --git a/alac.c b/alac.c index cf72781a..c814ee3f 100644 --- a/alac.c +++ b/alac.c @@ -35,59 +35,60 @@ static const int host_bigendian = 0; #include #include #ifdef _WIN32 - #include "stdint_win.h" +#include "stdint_win.h" #else - #include +#include #endif #include "alac.h" -#define _Swap32(v) do { \ - v = (((v) & 0x000000FF) << 0x18) | \ - (((v) & 0x0000FF00) << 0x08) | \ - (((v) & 0x00FF0000) >> 0x08) | \ - (((v) & 0xFF000000) >> 0x18); } while(0) +#define _Swap32(v) \ + do { \ + v = (((v)&0x000000FF) << 0x18) | (((v)&0x0000FF00) << 0x08) | (((v)&0x00FF0000) >> 0x08) | \ + (((v)&0xFF000000) >> 0x18); \ + } while (0) -#define _Swap16(v) do { \ - v = (((v) & 0x00FF) << 0x08) | \ - (((v) & 0xFF00) >> 0x08); } while (0) +#define _Swap16(v) \ + do { \ + v = (((v)&0x00FF) << 0x08) | (((v)&0xFF00) >> 0x08); \ + } while (0) -struct {signed int x:24;} se_struct_24; +struct { + signed int x : 24; +} se_struct_24; #define SignExtend24(val) (se_struct_24.x = val) void alac_free(alac_file *alac) { - if (alac->predicterror_buffer_a) - free(alac->predicterror_buffer_a); - if (alac->predicterror_buffer_b) - free(alac->predicterror_buffer_b); + if (alac->predicterror_buffer_a) + free(alac->predicterror_buffer_a); + if (alac->predicterror_buffer_b) + free(alac->predicterror_buffer_b); - if (alac->outputsamples_buffer_a) - free(alac->outputsamples_buffer_a); - if (alac->outputsamples_buffer_b) - free(alac->outputsamples_buffer_b); + if (alac->outputsamples_buffer_a) + free(alac->outputsamples_buffer_a); + if (alac->outputsamples_buffer_b) + free(alac->outputsamples_buffer_b); - if (alac->uncompressed_bytes_buffer_a) - free(alac->uncompressed_bytes_buffer_a); - if (alac->uncompressed_bytes_buffer_b) - free(alac->uncompressed_bytes_buffer_b); + if (alac->uncompressed_bytes_buffer_a) + free(alac->uncompressed_bytes_buffer_a); + if (alac->uncompressed_bytes_buffer_b) + free(alac->uncompressed_bytes_buffer_b); - free(alac); + free(alac); } -void alac_allocate_buffers(alac_file *alac) -{ - alac->predicterror_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); - alac->predicterror_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); +void alac_allocate_buffers(alac_file *alac) { + alac->predicterror_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); + alac->predicterror_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); - alac->outputsamples_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); - alac->outputsamples_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); + alac->outputsamples_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); + alac->outputsamples_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); - alac->uncompressed_bytes_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); - alac->uncompressed_bytes_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); + alac->uncompressed_bytes_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); + alac->uncompressed_bytes_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); } -void alac_set_info(alac_file *alac, char *inputbuffer) -{ +void alac_set_info(alac_file *alac, char *inputbuffer) { char *ptr = inputbuffer; ptr += 4; /* size */ ptr += 4; /* frma */ @@ -97,122 +98,114 @@ void alac_set_info(alac_file *alac, char *inputbuffer) ptr += 4; /* 0 ? */ - alac->setinfo_max_samples_per_frame = *(uint32_t*)ptr; /* buffer size / 2 ? */ + alac->setinfo_max_samples_per_frame = *(uint32_t *)ptr; /* buffer size / 2 ? */ if (!host_bigendian) - _Swap32(alac->setinfo_max_samples_per_frame); + _Swap32(alac->setinfo_max_samples_per_frame); ptr += 4; - alac->setinfo_7a = *(uint8_t*)ptr; + alac->setinfo_7a = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_sample_size = *(uint8_t*)ptr; + alac->setinfo_sample_size = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_rice_historymult = *(uint8_t*)ptr; + alac->setinfo_rice_historymult = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_rice_initialhistory = *(uint8_t*)ptr; + alac->setinfo_rice_initialhistory = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_rice_kmodifier = *(uint8_t*)ptr; + alac->setinfo_rice_kmodifier = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_7f = *(uint8_t*)ptr; + alac->setinfo_7f = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_80 = *(uint16_t*)ptr; + alac->setinfo_80 = *(uint16_t *)ptr; if (!host_bigendian) - _Swap16(alac->setinfo_80); + _Swap16(alac->setinfo_80); ptr += 2; - alac->setinfo_82 = *(uint32_t*)ptr; + alac->setinfo_82 = *(uint32_t *)ptr; if (!host_bigendian) - _Swap32(alac->setinfo_82); + _Swap32(alac->setinfo_82); ptr += 4; - alac->setinfo_86 = *(uint32_t*)ptr; + alac->setinfo_86 = *(uint32_t *)ptr; if (!host_bigendian) - _Swap32(alac->setinfo_86); + _Swap32(alac->setinfo_86); ptr += 4; - alac->setinfo_8a_rate = *(uint32_t*)ptr; + alac->setinfo_8a_rate = *(uint32_t *)ptr; if (!host_bigendian) - _Swap32(alac->setinfo_8a_rate); + _Swap32(alac->setinfo_8a_rate); alac_allocate_buffers(alac); - } /* stream reading */ /* supports reading 1 to 16 bits, in big endian format */ -static uint32_t readbits_16(alac_file *alac, int bits) -{ - uint32_t result; - int new_accumulator; +static uint32_t readbits_16(alac_file *alac, int bits) { + uint32_t result; + int new_accumulator; - result = (alac->input_buffer[0] << 16) | - (alac->input_buffer[1] << 8) | - (alac->input_buffer[2]); + result = (alac->input_buffer[0] << 16) | (alac->input_buffer[1] << 8) | (alac->input_buffer[2]); - /* shift left by the number of bits we've already read, - * so that the top 'n' bits of the 24 bits we read will - * be the return bits */ - result = result << alac->input_buffer_bitaccumulator; + /* shift left by the number of bits we've already read, + * so that the top 'n' bits of the 24 bits we read will + * be the return bits */ + result = result << alac->input_buffer_bitaccumulator; - result = result & 0x00ffffff; + result = result & 0x00ffffff; - /* and then only want the top 'n' bits from that, where - * n is 'bits' */ - result = result >> (24 - bits); + /* and then only want the top 'n' bits from that, where + * n is 'bits' */ + result = result >> (24 - bits); - new_accumulator = (alac->input_buffer_bitaccumulator + bits); + new_accumulator = (alac->input_buffer_bitaccumulator + bits); - /* increase the buffer pointer if we've read over n bytes. */ - alac->input_buffer += (new_accumulator >> 3); + /* increase the buffer pointer if we've read over n bytes. */ + alac->input_buffer += (new_accumulator >> 3); - /* and the remainder goes back into the bit accumulator */ - alac->input_buffer_bitaccumulator = (new_accumulator & 7); + /* and the remainder goes back into the bit accumulator */ + alac->input_buffer_bitaccumulator = (new_accumulator & 7); - return result; + return result; } /* supports reading 1 to 32 bits, in big endian format */ -static uint32_t readbits(alac_file *alac, int bits) -{ - int32_t result = 0; +static uint32_t readbits(alac_file *alac, int bits) { + int32_t result = 0; - if (bits > 16) - { - bits -= 16; - result = readbits_16(alac, 16) << bits; - } + if (bits > 16) { + bits -= 16; + result = readbits_16(alac, 16) << bits; + } - result |= readbits_16(alac, bits); + result |= readbits_16(alac, bits); - return result; + return result; } /* reads a single bit */ -static int readbit(alac_file *alac) -{ - int result; - int new_accumulator; +static int readbit(alac_file *alac) { + int result; + int new_accumulator; - result = alac->input_buffer[0]; + result = alac->input_buffer[0]; - result = result << alac->input_buffer_bitaccumulator; + result = result << alac->input_buffer_bitaccumulator; - result = result >> 7 & 1; + result = result >> 7 & 1; - new_accumulator = (alac->input_buffer_bitaccumulator + 1); + new_accumulator = (alac->input_buffer_bitaccumulator + 1); - alac->input_buffer += (new_accumulator / 8); + alac->input_buffer += (new_accumulator / 8); - alac->input_buffer_bitaccumulator = (new_accumulator % 8); + alac->input_buffer_bitaccumulator = (new_accumulator % 8); - return result; + return result; } -static void unreadbits(alac_file *alac, int bits) -{ - int new_accumulator = (alac->input_buffer_bitaccumulator - bits); +static void unreadbits(alac_file *alac, int bits) { + int new_accumulator = (alac->input_buffer_bitaccumulator - bits); - alac->input_buffer += (new_accumulator >> 3); + alac->input_buffer += (new_accumulator >> 3); - alac->input_buffer_bitaccumulator = (new_accumulator & 7); - if (alac->input_buffer_bitaccumulator < 0) - alac->input_buffer_bitaccumulator *= -1; + alac->input_buffer_bitaccumulator = (new_accumulator & 7); + if (alac->input_buffer_bitaccumulator < 0) + alac->input_buffer_bitaccumulator *= -1; } /* various implementations of count_leading_zero: @@ -240,247 +233,220 @@ static int count_leading_zeros(int32_t input) /* for some reason the unrolled version (below) is * actually faster than this. yay intel! */ -static int count_leading_zeros(int input) -{ - return __builtin_clz(input); -} +static int count_leading_zeros(int input) { return __builtin_clz(input); } #elif defined(_MSC_VER) && defined(_M_IX86) -static int count_leading_zeros(int input) -{ - int output = 0; - if (!input) return 32; - __asm - { +static int count_leading_zeros(int input) { + int output = 0; + if (!input) + return 32; + __asm + { mov eax, input; mov edx, 0x1f; bsr ecx, eax; sub edx, ecx; mov output, edx; - } - return output; + } + return output; } #else #warning using generic count leading zeroes. You may wish to write one for your CPU / compiler -static int count_leading_zeros(int input) -{ - int output = 0; - int curbyte = 0; +static int count_leading_zeros(int input) { + int output = 0; + int curbyte = 0; - curbyte = input >> 24; - if (curbyte) goto found; - output += 8; + curbyte = input >> 24; + if (curbyte) + goto found; + output += 8; - curbyte = input >> 16; - if (curbyte & 0xff) goto found; - output += 8; + curbyte = input >> 16; + if (curbyte & 0xff) + goto found; + output += 8; - curbyte = input >> 8; - if (curbyte & 0xff) goto found; - output += 8; + curbyte = input >> 8; + if (curbyte & 0xff) + goto found; + output += 8; - curbyte = input; - if (curbyte & 0xff) goto found; - output += 8; + curbyte = input; + if (curbyte & 0xff) + goto found; + output += 8; - return output; + return output; found: - if (!(curbyte & 0xf0)) - { - output += 4; - } - else - curbyte >>= 4; + if (!(curbyte & 0xf0)) { + output += 4; + } else + curbyte >>= 4; - if (curbyte & 0x8) - return output; - if (curbyte & 0x4) - return output + 1; - if (curbyte & 0x2) - return output + 2; - if (curbyte & 0x1) - return output + 3; + if (curbyte & 0x8) + return output; + if (curbyte & 0x4) + return output + 1; + if (curbyte & 0x2) + return output + 2; + if (curbyte & 0x1) + return output + 3; - /* shouldn't get here: */ - return output + 4; + /* shouldn't get here: */ + return output + 4; } #endif #define RICE_THRESHOLD 8 // maximum number of bits for a rice prefix. -static int32_t entropy_decode_value(alac_file* alac, - int readSampleSize, - int k, - int rice_kmodifier_mask) -{ - int32_t x = 0; // decoded value +static int32_t entropy_decode_value(alac_file *alac, int readSampleSize, int k, + int rice_kmodifier_mask) { + int32_t x = 0; // decoded value - // read x, number of 1s before 0 represent the rice value. - while (x <= RICE_THRESHOLD && readbit(alac)) - { - x++; + // read x, number of 1s before 0 represent the rice value. + while (x <= RICE_THRESHOLD && readbit(alac)) { + x++; + } + + if (x > RICE_THRESHOLD) { + // read the number from the bit stream (raw value) + int32_t value; + + value = readbits(alac, readSampleSize); + + // mask value + value &= (((uint32_t)0xffffffff) >> (32 - readSampleSize)); + + x = value; + } else { + if (k != 1) { + int extraBits = readbits(alac, k); + + // x = x * (2^k - 1) + x *= (((1 << k) - 1) & rice_kmodifier_mask); + + if (extraBits > 1) + x += extraBits - 1; + else + unreadbits(alac, 1); } + } - if (x > RICE_THRESHOLD) - { - // read the number from the bit stream (raw value) - int32_t value; - - value = readbits(alac, readSampleSize); - - // mask value - value &= (((uint32_t)0xffffffff) >> (32 - readSampleSize)); - - x = value; - } - else - { - if (k != 1) - { - int extraBits = readbits(alac, k); - - // x = x * (2^k - 1) - x *= (((1 << k) - 1) & rice_kmodifier_mask); - - if (extraBits > 1) - x += extraBits - 1; - else - unreadbits(alac, 1); - } - } - - return x; + return x; } -static void entropy_rice_decode(alac_file* alac, - int32_t* outputBuffer, - int outputSize, - int readSampleSize, - int rice_initialhistory, - int rice_kmodifier, - int rice_historymult, - int rice_kmodifier_mask) -{ - int outputCount; - int history = rice_initialhistory; - int signModifier = 0; +static void entropy_rice_decode(alac_file *alac, int32_t *outputBuffer, int outputSize, + int readSampleSize, int rice_initialhistory, int rice_kmodifier, + int rice_historymult, int rice_kmodifier_mask) { + int outputCount; + int history = rice_initialhistory; + int signModifier = 0; - for (outputCount = 0; outputCount < outputSize; outputCount++) - { - int32_t decodedValue; - int32_t finalValue; - int32_t k; + for (outputCount = 0; outputCount < outputSize; outputCount++) { + int32_t decodedValue; + int32_t finalValue; + int32_t k; - k = 31 - rice_kmodifier - count_leading_zeros((history >> 9) + 3); + k = 31 - rice_kmodifier - count_leading_zeros((history >> 9) + 3); - if (k < 0) k += rice_kmodifier; - else k = rice_kmodifier; + if (k < 0) + k += rice_kmodifier; + else + k = rice_kmodifier; - // note: don't use rice_kmodifier_mask here (set mask to 0xFFFFFFFF) - decodedValue = entropy_decode_value(alac, readSampleSize, k, 0xFFFFFFFF); + // note: don't use rice_kmodifier_mask here (set mask to 0xFFFFFFFF) + decodedValue = entropy_decode_value(alac, readSampleSize, k, 0xFFFFFFFF); - decodedValue += signModifier; - finalValue = (decodedValue + 1) / 2; // inc by 1 and shift out sign bit - if (decodedValue & 1) // the sign is stored in the low bit - finalValue *= -1; + decodedValue += signModifier; + finalValue = (decodedValue + 1) / 2; // inc by 1 and shift out sign bit + if (decodedValue & 1) // the sign is stored in the low bit + finalValue *= -1; - outputBuffer[outputCount] = finalValue; + outputBuffer[outputCount] = finalValue; + signModifier = 0; + + // update history + history += (decodedValue * rice_historymult) - ((history * rice_historymult) >> 9); + + if (decodedValue > 0xFFFF) + history = 0xFFFF; + + // special case, for compressed blocks of 0 + if ((history < 128) && (outputCount + 1 < outputSize)) { + int32_t blockSize; + + signModifier = 1; + + k = count_leading_zeros(history) + ((history + 16) / 64) - 24; + + // note: blockSize is always 16bit + blockSize = entropy_decode_value(alac, 16, k, rice_kmodifier_mask); + + // got blockSize 0s + if (blockSize > 0) { + memset(&outputBuffer[outputCount + 1], 0, blockSize * sizeof(*outputBuffer)); + outputCount += blockSize; + } + + if (blockSize > 0xFFFF) signModifier = 0; - // update history - history += (decodedValue * rice_historymult) - - ((history * rice_historymult) >> 9); - - if (decodedValue > 0xFFFF) - history = 0xFFFF; - - // special case, for compressed blocks of 0 - if ((history < 128) && (outputCount + 1 < outputSize)) - { - int32_t blockSize; - - signModifier = 1; - - k = count_leading_zeros(history) + ((history + 16) / 64) - 24; - - // note: blockSize is always 16bit - blockSize = entropy_decode_value(alac, 16, k, rice_kmodifier_mask); - - // got blockSize 0s - if (blockSize > 0) - { - memset(&outputBuffer[outputCount + 1], 0, blockSize * sizeof(*outputBuffer)); - outputCount += blockSize; - } - - if (blockSize > 0xFFFF) - signModifier = 0; - - history = 0; - } + history = 0; } + } } #define SIGN_EXTENDED32(val, bits) ((val << (32 - bits)) >> (32 - bits)) -#define SIGN_ONLY(v) \ - ((v < 0) ? (-1) : \ - ((v > 0) ? (1) : \ - (0))) +#define SIGN_ONLY(v) ((v < 0) ? (-1) : ((v > 0) ? (1) : (0))) -static void predictor_decompress_fir_adapt(int32_t *error_buffer, - int32_t *buffer_out, - int output_size, - int readsamplesize, - int16_t *predictor_coef_table, - int predictor_coef_num, - int predictor_quantitization) -{ +static void predictor_decompress_fir_adapt(int32_t *error_buffer, int32_t *buffer_out, + int output_size, int readsamplesize, + int16_t *predictor_coef_table, int predictor_coef_num, + int predictor_quantitization) { + int i; + + /* first sample always copies */ + *buffer_out = *error_buffer; + + if (!predictor_coef_num) { + if (output_size <= 1) + return; + memcpy(buffer_out + 1, error_buffer + 1, (output_size - 1) * 4); + return; + } + + if (predictor_coef_num == 0x1f) /* 11111 - max value of predictor_coef_num */ + { /* second-best case scenario for fir decompression, + * error describes a small difference from the previous sample only + */ + if (output_size <= 1) + return; + for (i = 0; i < output_size - 1; i++) { + int32_t prev_value; + int32_t error_value; + + prev_value = buffer_out[i]; + error_value = error_buffer[i + 1]; + buffer_out[i + 1] = SIGN_EXTENDED32((prev_value + error_value), readsamplesize); + } + return; + } + + /* read warm-up samples */ + if (predictor_coef_num > 0) { int i; + for (i = 0; i < predictor_coef_num; i++) { + int32_t val; - /* first sample always copies */ - *buffer_out = *error_buffer; + val = buffer_out[i] + error_buffer[i + 1]; - if (!predictor_coef_num) - { - if (output_size <= 1) return; - memcpy(buffer_out+1, error_buffer+1, (output_size-1) * 4); - return; - } + val = SIGN_EXTENDED32(val, readsamplesize); - if (predictor_coef_num == 0x1f) /* 11111 - max value of predictor_coef_num */ - { /* second-best case scenario for fir decompression, - * error describes a small difference from the previous sample only - */ - if (output_size <= 1) return; - for (i = 0; i < output_size - 1; i++) - { - int32_t prev_value; - int32_t error_value; - - prev_value = buffer_out[i]; - error_value = error_buffer[i+1]; - buffer_out[i+1] = SIGN_EXTENDED32((prev_value + error_value), readsamplesize); - } - return; - } - - /* read warm-up samples */ - if (predictor_coef_num > 0) - { - int i; - for (i = 0; i < predictor_coef_num; i++) - { - int32_t val; - - val = buffer_out[i] + error_buffer[i+1]; - - val = SIGN_EXTENDED32(val, readsamplesize); - - buffer_out[i+1] = val; - } + buffer_out[i + 1] = val; } + } #if 0 /* 4 and 8 are very common cases (the only ones i've seen). these @@ -499,657 +465,545 @@ static void predictor_decompress_fir_adapt(int32_t *error_buffer, } #endif + /* general case */ + if (predictor_coef_num > 0) { + for (i = predictor_coef_num + 1; i < output_size; i++) { + int j; + int sum = 0; + int outval; + int error_val = error_buffer[i]; - /* general case */ - if (predictor_coef_num > 0) - { - for (i = predictor_coef_num + 1; - i < output_size; - i++) - { - int j; - int sum = 0; - int outval; - int error_val = error_buffer[i]; + for (j = 0; j < predictor_coef_num; j++) { + sum += (buffer_out[predictor_coef_num - j] - buffer_out[0]) * predictor_coef_table[j]; + } - for (j = 0; j < predictor_coef_num; j++) - { - sum += (buffer_out[predictor_coef_num-j] - buffer_out[0]) * - predictor_coef_table[j]; - } + outval = (1 << (predictor_quantitization - 1)) + sum; + outval = outval >> predictor_quantitization; + outval = outval + buffer_out[0] + error_val; + outval = SIGN_EXTENDED32(outval, readsamplesize); - outval = (1 << (predictor_quantitization-1)) + sum; - outval = outval >> predictor_quantitization; - outval = outval + buffer_out[0] + error_val; - outval = SIGN_EXTENDED32(outval, readsamplesize); + buffer_out[predictor_coef_num + 1] = outval; - buffer_out[predictor_coef_num+1] = outval; + if (error_val > 0) { + int predictor_num = predictor_coef_num - 1; - if (error_val > 0) - { - int predictor_num = predictor_coef_num - 1; + while (predictor_num >= 0 && error_val > 0) { + int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num]; + int sign = SIGN_ONLY(val); - while (predictor_num >= 0 && error_val > 0) - { - int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num]; - int sign = SIGN_ONLY(val); + predictor_coef_table[predictor_num] -= sign; - predictor_coef_table[predictor_num] -= sign; + val *= sign; /* absolute value */ - val *= sign; /* absolute value */ + error_val -= ((val >> predictor_quantitization) * (predictor_coef_num - predictor_num)); - error_val -= ((val >> predictor_quantitization) * - (predictor_coef_num - predictor_num)); - - predictor_num--; - } - } - else if (error_val < 0) - { - int predictor_num = predictor_coef_num - 1; - - while (predictor_num >= 0 && error_val < 0) - { - int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num]; - int sign = - SIGN_ONLY(val); - - predictor_coef_table[predictor_num] -= sign; - - val *= sign; /* neg value */ - - error_val -= ((val >> predictor_quantitization) * - (predictor_coef_num - predictor_num)); - - predictor_num--; - } - } - - buffer_out++; + predictor_num--; } + } else if (error_val < 0) { + int predictor_num = predictor_coef_num - 1; + + while (predictor_num >= 0 && error_val < 0) { + int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num]; + int sign = -SIGN_ONLY(val); + + predictor_coef_table[predictor_num] -= sign; + + val *= sign; /* neg value */ + + error_val -= ((val >> predictor_quantitization) * (predictor_coef_num - predictor_num)); + + predictor_num--; + } + } + + buffer_out++; } + } } -static void deinterlace_16(int32_t *buffer_a, int32_t *buffer_b, - int16_t *buffer_out, - int numchannels, int numsamples, - uint8_t interlacing_shift, - uint8_t interlacing_leftweight) -{ - int i; - if (numsamples <= 0) return; +static void deinterlace_16(int32_t *buffer_a, int32_t *buffer_b, int16_t *buffer_out, + int numchannels, int numsamples, uint8_t interlacing_shift, + uint8_t interlacing_leftweight) { + int i; + if (numsamples <= 0) + return; - /* weighted interlacing */ - if (interlacing_leftweight) - { - for (i = 0; i < numsamples; i++) - { - int32_t difference, midright; - int16_t left; - int16_t right; + /* weighted interlacing */ + if (interlacing_leftweight) { + for (i = 0; i < numsamples; i++) { + int32_t difference, midright; + int16_t left; + int16_t right; - midright = buffer_a[i]; - difference = buffer_b[i]; + midright = buffer_a[i]; + difference = buffer_b[i]; + right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); + left = right + difference; - right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); - left = right + difference; + /* output is always little endian */ + if (host_bigendian) { + _Swap16(left); + _Swap16(right); + } - /* output is always little endian */ - if (host_bigendian) - { - _Swap16(left); - _Swap16(right); - } - - buffer_out[i*numchannels] = left; - buffer_out[i*numchannels + 1] = right; - } - - return; + buffer_out[i * numchannels] = left; + buffer_out[i * numchannels + 1] = right; } - /* otherwise basic interlacing took place */ - for (i = 0; i < numsamples; i++) - { - int16_t left, right; + return; + } - left = buffer_a[i]; - right = buffer_b[i]; + /* otherwise basic interlacing took place */ + for (i = 0; i < numsamples; i++) { + int16_t left, right; - /* output is always little endian */ + left = buffer_a[i]; + right = buffer_b[i]; + + /* output is always little endian */ + if (host_bigendian) { + _Swap16(left); + _Swap16(right); + } + + buffer_out[i * numchannels] = left; + buffer_out[i * numchannels + 1] = right; + } +} + +static void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b, int uncompressed_bytes, + int32_t *uncompressed_bytes_buffer_a, + int32_t *uncompressed_bytes_buffer_b, void *buffer_out, int numchannels, + int numsamples, uint8_t interlacing_shift, + uint8_t interlacing_leftweight) { + int i; + if (numsamples <= 0) + return; + + /* weighted interlacing */ + if (interlacing_leftweight) { + for (i = 0; i < numsamples; i++) { + int32_t difference, midright; + int32_t left; + int32_t right; + + midright = buffer_a[i]; + difference = buffer_b[i]; + + right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); + left = right + difference; + + if (uncompressed_bytes) { + uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); + left <<= (uncompressed_bytes * 8); + right <<= (uncompressed_bytes * 8); + + left |= uncompressed_bytes_buffer_a[i] & mask; + right |= uncompressed_bytes_buffer_b[i] & mask; + } + + ((uint8_t *)buffer_out)[i * numchannels * 3] = (left)&0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; + + ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right)&0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; + } + + return; + } + + /* otherwise basic interlacing took place */ + for (i = 0; i < numsamples; i++) { + int32_t left, right; + + left = buffer_a[i]; + right = buffer_b[i]; + + if (uncompressed_bytes) { + uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); + left <<= (uncompressed_bytes * 8); + right <<= (uncompressed_bytes * 8); + + left |= uncompressed_bytes_buffer_a[i] & mask; + right |= uncompressed_bytes_buffer_b[i] & mask; + } + + ((uint8_t *)buffer_out)[i * numchannels * 3] = (left)&0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; + + ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right)&0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; + } +} + +void alac_decode_frame(alac_file *alac, unsigned char *inbuffer, void *outbuffer, int *outputsize) { + int outbuffer_allocation_size = *outputsize; // initial value + int channels; + int32_t outputsamples = alac->setinfo_max_samples_per_frame; + + /* setup the stream */ + alac->input_buffer = inbuffer; + alac->input_buffer_bitaccumulator = 0; + + channels = readbits(alac, 3); + + *outputsize = outputsamples * alac->bytespersample; + if (*outputsize > outbuffer_allocation_size) { + fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E1.\n"); + *outputsize = 0; + return; + } + + switch (channels) { + case 0: /* 1 channel */ + { + int hassize; + int isnotcompressed; + int readsamplesize; + + int uncompressed_bytes; + int ricemodifier; + + /* 2^result = something to do with output waiting. + * perhaps matters if we read > 1 frame in a pass? + */ + readbits(alac, 4); + + readbits(alac, 12); /* unknown, skip 12 bits */ + + hassize = readbits(alac, 1); /* the output sample size is stored soon */ + + uncompressed_bytes = + readbits(alac, 2); /* number of bytes in the (compressed) stream that are not compressed */ + + isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */ + + if (hassize) { + /* now read the number of samples, + * as a 32bit integer */ + outputsamples = readbits(alac, 32); + *outputsize = outputsamples * alac->bytespersample; + if (*outputsize > outbuffer_allocation_size) { + fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E2.\n"); + *outputsize = 0; + return; + } + } + + readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8); + + if (!isnotcompressed) { /* so it is compressed */ + int16_t predictor_coef_table[32]; + int predictor_coef_num; + int prediction_type; + int prediction_quantitization; + int i; + + /* skip 16 bits, not sure what they are. seem to be used in + * two channel case */ + readbits(alac, 8); + readbits(alac, 8); + + prediction_type = readbits(alac, 4); + prediction_quantitization = readbits(alac, 4); + + ricemodifier = readbits(alac, 3); + predictor_coef_num = readbits(alac, 5); + + /* read the predictor table */ + for (i = 0; i < predictor_coef_num; i++) { + predictor_coef_table[i] = (int16_t)readbits(alac, 16); + } + + if (uncompressed_bytes) { + int i; + for (i = 0; i < outputsamples; i++) { + alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8); + } + } + + entropy_rice_decode(alac, alac->predicterror_buffer_a, outputsamples, readsamplesize, + alac->setinfo_rice_initialhistory, alac->setinfo_rice_kmodifier, + ricemodifier * alac->setinfo_rice_historymult / 4, + (1 << alac->setinfo_rice_kmodifier) - 1); + + if (prediction_type == 0) { /* adaptive fir */ + predictor_decompress_fir_adapt(alac->predicterror_buffer_a, alac->outputsamples_buffer_a, + outputsamples, readsamplesize, predictor_coef_table, + predictor_coef_num, prediction_quantitization); + } else { + fprintf(stderr, "FIXME: unhandled prediction type for compressed case: %i\n", + prediction_type); + /* i think the only other prediction type (or perhaps this is just a + * boolean?) runs adaptive fir twice.. like: + * predictor_decompress_fir_adapt(predictor_error, tempout, ...) + * predictor_decompress_fir_adapt(predictor_error, outputsamples ...) + * little strange.. + */ + } + + } else { /* not compressed, easy case */ + if (alac->setinfo_sample_size <= 16) { + int i; + for (i = 0; i < outputsamples; i++) { + int32_t audiobits = readbits(alac, alac->setinfo_sample_size); + + audiobits = SIGN_EXTENDED32(audiobits, alac->setinfo_sample_size); + + alac->outputsamples_buffer_a[i] = audiobits; + } + } else { + int i; + for (i = 0; i < outputsamples; i++) { + int32_t audiobits; + + audiobits = readbits(alac, 16); + /* special case of sign extension.. + * as we'll be ORing the low 16bits into this */ + audiobits = audiobits << (alac->setinfo_sample_size - 16); + audiobits |= readbits(alac, alac->setinfo_sample_size - 16); + audiobits = SignExtend24(audiobits); + + alac->outputsamples_buffer_a[i] = audiobits; + } + } + uncompressed_bytes = 0; // always 0 for uncompressed + } + + switch (alac->setinfo_sample_size) { + case 16: { + int i; + for (i = 0; i < outputsamples; i++) { + int16_t sample = alac->outputsamples_buffer_a[i]; if (host_bigendian) - { - _Swap16(left); - _Swap16(right); - } - - buffer_out[i*numchannels] = left; - buffer_out[i*numchannels + 1] = right; + _Swap16(sample); + ((int16_t *)outbuffer)[i * alac->numchannels] = sample; + } + break; } -} + case 24: { + int i; + for (i = 0; i < outputsamples; i++) { + int32_t sample = alac->outputsamples_buffer_a[i]; -static void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b, - int uncompressed_bytes, - int32_t *uncompressed_bytes_buffer_a, int32_t *uncompressed_bytes_buffer_b, - void *buffer_out, - int numchannels, int numsamples, - uint8_t interlacing_shift, - uint8_t interlacing_leftweight) -{ - int i; - if (numsamples <= 0) return; - - /* weighted interlacing */ - if (interlacing_leftweight) - { - for (i = 0; i < numsamples; i++) - { - int32_t difference, midright; - int32_t left; - int32_t right; - - midright = buffer_a[i]; - difference = buffer_b[i]; - - right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); - left = right + difference; - - if (uncompressed_bytes) - { - uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); - left <<= (uncompressed_bytes * 8); - right <<= (uncompressed_bytes * 8); - - left |= uncompressed_bytes_buffer_a[i] & mask; - right |= uncompressed_bytes_buffer_b[i] & mask; - } - - ((uint8_t*)buffer_out)[i * numchannels * 3] = (left) & 0xFF; - ((uint8_t*)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; - ((uint8_t*)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; - - ((uint8_t*)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF; - ((uint8_t*)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; - ((uint8_t*)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; + if (uncompressed_bytes) { + uint32_t mask; + sample = sample << (uncompressed_bytes * 8); + mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); + sample |= alac->uncompressed_bytes_buffer_a[i] & mask; } + ((uint8_t *)outbuffer)[i * alac->numchannels * 3] = (sample)&0xFF; + ((uint8_t *)outbuffer)[i * alac->numchannels * 3 + 1] = (sample >> 8) & 0xFF; + ((uint8_t *)outbuffer)[i * alac->numchannels * 3 + 2] = (sample >> 16) & 0xFF; + } + break; + } + case 20: + case 32: + fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size); + break; + default: + break; + } + break; + } + case 1: /* 2 channels */ + { + int hassize; + int isnotcompressed; + int readsamplesize; + + int uncompressed_bytes; + + uint8_t interlacing_shift; + uint8_t interlacing_leftweight; + + /* 2^result = something to do with output waiting. + * perhaps matters if we read > 1 frame in a pass? + */ + readbits(alac, 4); + + readbits(alac, 12); /* unknown, skip 12 bits */ + + hassize = readbits(alac, 1); /* the output sample size is stored soon */ + + uncompressed_bytes = readbits( + alac, 2); /* the number of bytes in the (compressed) stream that are not compressed */ + + isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */ + + if (hassize) { + /* now read the number of samples, + * as a 32bit integer */ + outputsamples = readbits(alac, 32); + *outputsize = outputsamples * alac->bytespersample; + if (*outputsize > outbuffer_allocation_size) { + fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E3.\n"); + *outputsize = 0; return; + } } - /* otherwise basic interlacing took place */ - for (i = 0; i < numsamples; i++) - { - int32_t left, right; + readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8) + 1; - left = buffer_a[i]; - right = buffer_b[i]; + if (!isnotcompressed) { /* compressed */ + int16_t predictor_coef_table_a[32]; + int predictor_coef_num_a; + int prediction_type_a; + int prediction_quantitization_a; + int ricemodifier_a; - if (uncompressed_bytes) - { - uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); - left <<= (uncompressed_bytes * 8); - right <<= (uncompressed_bytes * 8); + int16_t predictor_coef_table_b[32]; + int predictor_coef_num_b; + int prediction_type_b; + int prediction_quantitization_b; + int ricemodifier_b; - left |= uncompressed_bytes_buffer_a[i] & mask; - right |= uncompressed_bytes_buffer_b[i] & mask; + int i; + + interlacing_shift = readbits(alac, 8); + interlacing_leftweight = readbits(alac, 8); + + /******** channel 1 ***********/ + prediction_type_a = readbits(alac, 4); + prediction_quantitization_a = readbits(alac, 4); + + ricemodifier_a = readbits(alac, 3); + predictor_coef_num_a = readbits(alac, 5); + + /* read the predictor table */ + for (i = 0; i < predictor_coef_num_a; i++) { + predictor_coef_table_a[i] = (int16_t)readbits(alac, 16); + } + + /******** channel 2 *********/ + prediction_type_b = readbits(alac, 4); + prediction_quantitization_b = readbits(alac, 4); + + ricemodifier_b = readbits(alac, 3); + predictor_coef_num_b = readbits(alac, 5); + + /* read the predictor table */ + for (i = 0; i < predictor_coef_num_b; i++) { + predictor_coef_table_b[i] = (int16_t)readbits(alac, 16); + } + + /*********************/ + if (uncompressed_bytes) { /* see mono case */ + int i; + for (i = 0; i < outputsamples; i++) { + alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8); + alac->uncompressed_bytes_buffer_b[i] = readbits(alac, uncompressed_bytes * 8); } + } - ((uint8_t*)buffer_out)[i * numchannels * 3] = (left) & 0xFF; - ((uint8_t*)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; - ((uint8_t*)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; + /* channel 1 */ + entropy_rice_decode(alac, alac->predicterror_buffer_a, outputsamples, readsamplesize, + alac->setinfo_rice_initialhistory, alac->setinfo_rice_kmodifier, + ricemodifier_a * alac->setinfo_rice_historymult / 4, + (1 << alac->setinfo_rice_kmodifier) - 1); - ((uint8_t*)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF; - ((uint8_t*)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; - ((uint8_t*)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; + if (prediction_type_a == 0) { /* adaptive fir */ + predictor_decompress_fir_adapt(alac->predicterror_buffer_a, alac->outputsamples_buffer_a, + outputsamples, readsamplesize, predictor_coef_table_a, + predictor_coef_num_a, prediction_quantitization_a); + } else { /* see mono case */ + fprintf(stderr, "FIXME: unhandled prediction type on channel 1: %i\n", prediction_type_a); + } + /* channel 2 */ + entropy_rice_decode(alac, alac->predicterror_buffer_b, outputsamples, readsamplesize, + alac->setinfo_rice_initialhistory, alac->setinfo_rice_kmodifier, + ricemodifier_b * alac->setinfo_rice_historymult / 4, + (1 << alac->setinfo_rice_kmodifier) - 1); + + if (prediction_type_b == 0) { /* adaptive fir */ + predictor_decompress_fir_adapt(alac->predicterror_buffer_b, alac->outputsamples_buffer_b, + outputsamples, readsamplesize, predictor_coef_table_b, + predictor_coef_num_b, prediction_quantitization_b); + } else { + fprintf(stderr, "FIXME: unhandled prediction type on channel 2: %i\n", prediction_type_b); + } + } else { /* not compressed, easy case */ + if (alac->setinfo_sample_size <= 16) { + int i; + for (i = 0; i < outputsamples; i++) { + int32_t audiobits_a, audiobits_b; + + audiobits_a = readbits(alac, alac->setinfo_sample_size); + audiobits_b = readbits(alac, alac->setinfo_sample_size); + + audiobits_a = SIGN_EXTENDED32(audiobits_a, alac->setinfo_sample_size); + audiobits_b = SIGN_EXTENDED32(audiobits_b, alac->setinfo_sample_size); + + alac->outputsamples_buffer_a[i] = audiobits_a; + alac->outputsamples_buffer_b[i] = audiobits_b; + } + } else { + int i; + for (i = 0; i < outputsamples; i++) { + int32_t audiobits_a, audiobits_b; + + audiobits_a = readbits(alac, 16); + audiobits_a = audiobits_a << (alac->setinfo_sample_size - 16); + audiobits_a |= readbits(alac, alac->setinfo_sample_size - 16); + audiobits_a = SignExtend24(audiobits_a); + + audiobits_b = readbits(alac, 16); + audiobits_b = audiobits_b << (alac->setinfo_sample_size - 16); + audiobits_b |= readbits(alac, alac->setinfo_sample_size - 16); + audiobits_b = SignExtend24(audiobits_b); + + alac->outputsamples_buffer_a[i] = audiobits_a; + alac->outputsamples_buffer_b[i] = audiobits_b; + } + } + uncompressed_bytes = 0; // always 0 for uncompressed + interlacing_shift = 0; + interlacing_leftweight = 0; } + switch (alac->setinfo_sample_size) { + case 16: { + deinterlace_16(alac->outputsamples_buffer_a, alac->outputsamples_buffer_b, + (int16_t *)outbuffer, alac->numchannels, outputsamples, interlacing_shift, + interlacing_leftweight); + break; + } + case 24: { + deinterlace_24(alac->outputsamples_buffer_a, alac->outputsamples_buffer_b, uncompressed_bytes, + alac->uncompressed_bytes_buffer_a, alac->uncompressed_bytes_buffer_b, + (int16_t *)outbuffer, alac->numchannels, outputsamples, interlacing_shift, + interlacing_leftweight); + break; + } + case 20: + case 32: + fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size); + break; + default: + break; + } + + break; + } + } } -void alac_decode_frame(alac_file *alac, - unsigned char *inbuffer, - void *outbuffer, int *outputsize) -{ - int outbuffer_allocation_size = *outputsize; // initial value - int channels; - int32_t outputsamples = alac->setinfo_max_samples_per_frame; +alac_file *alac_create(int samplesize, int numchannels) { + alac_file *newfile = malloc(sizeof(alac_file)); - /* setup the stream */ - alac->input_buffer = inbuffer; - alac->input_buffer_bitaccumulator = 0; + memset(newfile, 0, sizeof(alac_file)); - channels = readbits(alac, 3); + newfile->samplesize = samplesize; + newfile->numchannels = numchannels; + newfile->bytespersample = (samplesize / 8) * numchannels; - *outputsize = outputsamples * alac->bytespersample; - if (*outputsize>outbuffer_allocation_size) { - fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E1.\n"); - *outputsize = 0; - return; - } - - switch(channels) - { - case 0: /* 1 channel */ - { - int hassize; - int isnotcompressed; - int readsamplesize; - - int uncompressed_bytes; - int ricemodifier; - - /* 2^result = something to do with output waiting. - * perhaps matters if we read > 1 frame in a pass? - */ - readbits(alac, 4); - - readbits(alac, 12); /* unknown, skip 12 bits */ - - hassize = readbits(alac, 1); /* the output sample size is stored soon */ - - uncompressed_bytes = readbits(alac, 2); /* number of bytes in the (compressed) stream that are not compressed */ - - isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */ - - if (hassize) - { - /* now read the number of samples, - * as a 32bit integer */ - outputsamples = readbits(alac, 32); - *outputsize = outputsamples * alac->bytespersample; - if (*outputsize>outbuffer_allocation_size) { - fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E2.\n"); - *outputsize = 0; - return; - } - - } - - readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8); - - if (!isnotcompressed) - { /* so it is compressed */ - int16_t predictor_coef_table[32]; - int predictor_coef_num; - int prediction_type; - int prediction_quantitization; - int i; - - /* skip 16 bits, not sure what they are. seem to be used in - * two channel case */ - readbits(alac, 8); - readbits(alac, 8); - - prediction_type = readbits(alac, 4); - prediction_quantitization = readbits(alac, 4); - - ricemodifier = readbits(alac, 3); - predictor_coef_num = readbits(alac, 5); - - /* read the predictor table */ - for (i = 0; i < predictor_coef_num; i++) - { - predictor_coef_table[i] = (int16_t)readbits(alac, 16); - } - - if (uncompressed_bytes) - { - int i; - for (i = 0; i < outputsamples; i++) - { - alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8); - } - } - - entropy_rice_decode(alac, - alac->predicterror_buffer_a, - outputsamples, - readsamplesize, - alac->setinfo_rice_initialhistory, - alac->setinfo_rice_kmodifier, - ricemodifier * alac->setinfo_rice_historymult / 4, - (1 << alac->setinfo_rice_kmodifier) - 1); - - if (prediction_type == 0) - { /* adaptive fir */ - predictor_decompress_fir_adapt(alac->predicterror_buffer_a, - alac->outputsamples_buffer_a, - outputsamples, - readsamplesize, - predictor_coef_table, - predictor_coef_num, - prediction_quantitization); - } - else - { - fprintf(stderr, "FIXME: unhandled prediction type for compressed case: %i\n", prediction_type); - /* i think the only other prediction type (or perhaps this is just a - * boolean?) runs adaptive fir twice.. like: - * predictor_decompress_fir_adapt(predictor_error, tempout, ...) - * predictor_decompress_fir_adapt(predictor_error, outputsamples ...) - * little strange.. - */ - } - - } - else - { /* not compressed, easy case */ - if (alac->setinfo_sample_size <= 16) - { - int i; - for (i = 0; i < outputsamples; i++) - { - int32_t audiobits = readbits(alac, alac->setinfo_sample_size); - - audiobits = SIGN_EXTENDED32(audiobits, alac->setinfo_sample_size); - - alac->outputsamples_buffer_a[i] = audiobits; - } - } - else - { - int i; - for (i = 0; i < outputsamples; i++) - { - int32_t audiobits; - - audiobits = readbits(alac, 16); - /* special case of sign extension.. - * as we'll be ORing the low 16bits into this */ - audiobits = audiobits << (alac->setinfo_sample_size - 16); - audiobits |= readbits(alac, alac->setinfo_sample_size - 16); - audiobits = SignExtend24(audiobits); - - alac->outputsamples_buffer_a[i] = audiobits; - } - } - uncompressed_bytes = 0; // always 0 for uncompressed - } - - switch(alac->setinfo_sample_size) - { - case 16: - { - int i; - for (i = 0; i < outputsamples; i++) - { - int16_t sample = alac->outputsamples_buffer_a[i]; - if (host_bigendian) - _Swap16(sample); - ((int16_t*)outbuffer)[i * alac->numchannels] = sample; - } - break; - } - case 24: - { - int i; - for (i = 0; i < outputsamples; i++) - { - int32_t sample = alac->outputsamples_buffer_a[i]; - - if (uncompressed_bytes) - { - uint32_t mask; - sample = sample << (uncompressed_bytes * 8); - mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); - sample |= alac->uncompressed_bytes_buffer_a[i] & mask; - } - - ((uint8_t*)outbuffer)[i * alac->numchannels * 3] = (sample) & 0xFF; - ((uint8_t*)outbuffer)[i * alac->numchannels * 3 + 1] = (sample >> 8) & 0xFF; - ((uint8_t*)outbuffer)[i * alac->numchannels * 3 + 2] = (sample >> 16) & 0xFF; - } - break; - } - case 20: - case 32: - fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size); - break; - default: - break; - } - break; - } - case 1: /* 2 channels */ - { - int hassize; - int isnotcompressed; - int readsamplesize; - - int uncompressed_bytes; - - uint8_t interlacing_shift; - uint8_t interlacing_leftweight; - - /* 2^result = something to do with output waiting. - * perhaps matters if we read > 1 frame in a pass? - */ - readbits(alac, 4); - - readbits(alac, 12); /* unknown, skip 12 bits */ - - hassize = readbits(alac, 1); /* the output sample size is stored soon */ - - uncompressed_bytes = readbits(alac, 2); /* the number of bytes in the (compressed) stream that are not compressed */ - - isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */ - - if (hassize) - { - /* now read the number of samples, - * as a 32bit integer */ - outputsamples = readbits(alac, 32); - *outputsize = outputsamples * alac->bytespersample; - if (*outputsize>outbuffer_allocation_size) { - fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E3.\n"); - *outputsize = 0; - return; - } - } - - readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8) + 1; - - if (!isnotcompressed) - { /* compressed */ - int16_t predictor_coef_table_a[32]; - int predictor_coef_num_a; - int prediction_type_a; - int prediction_quantitization_a; - int ricemodifier_a; - - int16_t predictor_coef_table_b[32]; - int predictor_coef_num_b; - int prediction_type_b; - int prediction_quantitization_b; - int ricemodifier_b; - - int i; - - interlacing_shift = readbits(alac, 8); - interlacing_leftweight = readbits(alac, 8); - - /******** channel 1 ***********/ - prediction_type_a = readbits(alac, 4); - prediction_quantitization_a = readbits(alac, 4); - - ricemodifier_a = readbits(alac, 3); - predictor_coef_num_a = readbits(alac, 5); - - /* read the predictor table */ - for (i = 0; i < predictor_coef_num_a; i++) - { - predictor_coef_table_a[i] = (int16_t)readbits(alac, 16); - } - - /******** channel 2 *********/ - prediction_type_b = readbits(alac, 4); - prediction_quantitization_b = readbits(alac, 4); - - ricemodifier_b = readbits(alac, 3); - predictor_coef_num_b = readbits(alac, 5); - - /* read the predictor table */ - for (i = 0; i < predictor_coef_num_b; i++) - { - predictor_coef_table_b[i] = (int16_t)readbits(alac, 16); - } - - /*********************/ - if (uncompressed_bytes) - { /* see mono case */ - int i; - for (i = 0; i < outputsamples; i++) - { - alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8); - alac->uncompressed_bytes_buffer_b[i] = readbits(alac, uncompressed_bytes * 8); - } - } - - /* channel 1 */ - entropy_rice_decode(alac, - alac->predicterror_buffer_a, - outputsamples, - readsamplesize, - alac->setinfo_rice_initialhistory, - alac->setinfo_rice_kmodifier, - ricemodifier_a * alac->setinfo_rice_historymult / 4, - (1 << alac->setinfo_rice_kmodifier) - 1); - - if (prediction_type_a == 0) - { /* adaptive fir */ - predictor_decompress_fir_adapt(alac->predicterror_buffer_a, - alac->outputsamples_buffer_a, - outputsamples, - readsamplesize, - predictor_coef_table_a, - predictor_coef_num_a, - prediction_quantitization_a); - } - else - { /* see mono case */ - fprintf(stderr, "FIXME: unhandled prediction type on channel 1: %i\n", prediction_type_a); - } - - /* channel 2 */ - entropy_rice_decode(alac, - alac->predicterror_buffer_b, - outputsamples, - readsamplesize, - alac->setinfo_rice_initialhistory, - alac->setinfo_rice_kmodifier, - ricemodifier_b * alac->setinfo_rice_historymult / 4, - (1 << alac->setinfo_rice_kmodifier) - 1); - - if (prediction_type_b == 0) - { /* adaptive fir */ - predictor_decompress_fir_adapt(alac->predicterror_buffer_b, - alac->outputsamples_buffer_b, - outputsamples, - readsamplesize, - predictor_coef_table_b, - predictor_coef_num_b, - prediction_quantitization_b); - } - else - { - fprintf(stderr, "FIXME: unhandled prediction type on channel 2: %i\n", prediction_type_b); - } - } - else - { /* not compressed, easy case */ - if (alac->setinfo_sample_size <= 16) - { - int i; - for (i = 0; i < outputsamples; i++) - { - int32_t audiobits_a, audiobits_b; - - audiobits_a = readbits(alac, alac->setinfo_sample_size); - audiobits_b = readbits(alac, alac->setinfo_sample_size); - - audiobits_a = SIGN_EXTENDED32(audiobits_a, alac->setinfo_sample_size); - audiobits_b = SIGN_EXTENDED32(audiobits_b, alac->setinfo_sample_size); - - alac->outputsamples_buffer_a[i] = audiobits_a; - alac->outputsamples_buffer_b[i] = audiobits_b; - } - } - else - { - int i; - for (i = 0; i < outputsamples; i++) - { - int32_t audiobits_a, audiobits_b; - - audiobits_a = readbits(alac, 16); - audiobits_a = audiobits_a << (alac->setinfo_sample_size - 16); - audiobits_a |= readbits(alac, alac->setinfo_sample_size - 16); - audiobits_a = SignExtend24(audiobits_a); - - audiobits_b = readbits(alac, 16); - audiobits_b = audiobits_b << (alac->setinfo_sample_size - 16); - audiobits_b |= readbits(alac, alac->setinfo_sample_size - 16); - audiobits_b = SignExtend24(audiobits_b); - - alac->outputsamples_buffer_a[i] = audiobits_a; - alac->outputsamples_buffer_b[i] = audiobits_b; - } - } - uncompressed_bytes = 0; // always 0 for uncompressed - interlacing_shift = 0; - interlacing_leftweight = 0; - } - - switch(alac->setinfo_sample_size) - { - case 16: - { - deinterlace_16(alac->outputsamples_buffer_a, - alac->outputsamples_buffer_b, - (int16_t*)outbuffer, - alac->numchannels, - outputsamples, - interlacing_shift, - interlacing_leftweight); - break; - } - case 24: - { - deinterlace_24(alac->outputsamples_buffer_a, - alac->outputsamples_buffer_b, - uncompressed_bytes, - alac->uncompressed_bytes_buffer_a, - alac->uncompressed_bytes_buffer_b, - (int16_t*)outbuffer, - alac->numchannels, - outputsamples, - interlacing_shift, - interlacing_leftweight); - break; - } - case 20: - case 32: - fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size); - break; - default: - break; - } - - break; - } - } + return newfile; } - -alac_file *alac_create(int samplesize, int numchannels) -{ - alac_file *newfile = malloc(sizeof(alac_file)); - - memset(newfile, 0, sizeof(alac_file)); - - newfile->samplesize = samplesize; - newfile->numchannels = numchannels; - newfile->bytespersample = (samplesize / 8) * numchannels; - - return newfile; -} - diff --git a/alac.h b/alac.h index 09666a1d..a56c29f1 100644 --- a/alac.h +++ b/alac.h @@ -6,52 +6,43 @@ typedef struct alac_file alac_file; alac_file *alac_create(int samplesize, int numchannels); -void alac_decode_frame(alac_file *alac, - unsigned char *inbuffer, - void *outbuffer, int *outputsize); +void alac_decode_frame(alac_file *alac, unsigned char *inbuffer, void *outbuffer, int *outputsize); void alac_set_info(alac_file *alac, char *inputbuffer); void alac_allocate_buffers(alac_file *alac); void alac_free(alac_file *alac); -struct alac_file -{ - unsigned char *input_buffer; - int input_buffer_bitaccumulator; /* used so we can do arbitary - bit reads */ +struct alac_file { + unsigned char *input_buffer; + int input_buffer_bitaccumulator; /* used so we can do arbitary + bit reads */ - int samplesize; - int numchannels; - int bytespersample; + int samplesize; + int numchannels; + int bytespersample; + /* buffers */ + int32_t *predicterror_buffer_a; + int32_t *predicterror_buffer_b; - /* buffers */ - int32_t *predicterror_buffer_a; - int32_t *predicterror_buffer_b; - - int32_t *outputsamples_buffer_a; - int32_t *outputsamples_buffer_b; - - int32_t *uncompressed_bytes_buffer_a; - int32_t *uncompressed_bytes_buffer_b; - + int32_t *outputsamples_buffer_a; + int32_t *outputsamples_buffer_b; + int32_t *uncompressed_bytes_buffer_a; + int32_t *uncompressed_bytes_buffer_b; /* stuff from setinfo */ - uint32_t setinfo_max_samples_per_frame; /* 0x1000 = 4096 */ /* max samples per frame? */ - uint8_t setinfo_7a; /* 0x00 */ - uint8_t setinfo_sample_size; /* 0x10 */ - uint8_t setinfo_rice_historymult; /* 0x28 */ - uint8_t setinfo_rice_initialhistory; /* 0x0a */ - uint8_t setinfo_rice_kmodifier; /* 0x0e */ - uint8_t setinfo_7f; /* 0x02 */ - uint16_t setinfo_80; /* 0x00ff */ - uint32_t setinfo_82; /* 0x000020e7 */ /* max sample size?? */ - uint32_t setinfo_86; /* 0x00069fe4 */ /* bit rate (avarge)?? */ - uint32_t setinfo_8a_rate; /* 0x0000ac44 */ + uint32_t setinfo_max_samples_per_frame; /* 0x1000 = 4096 */ /* max samples per frame? */ + uint8_t setinfo_7a; /* 0x00 */ + uint8_t setinfo_sample_size; /* 0x10 */ + uint8_t setinfo_rice_historymult; /* 0x28 */ + uint8_t setinfo_rice_initialhistory; /* 0x0a */ + uint8_t setinfo_rice_kmodifier; /* 0x0e */ + uint8_t setinfo_7f; /* 0x02 */ + uint16_t setinfo_80; /* 0x00ff */ + uint32_t setinfo_82; /* 0x000020e7 */ /* max sample size?? */ + uint32_t setinfo_86; /* 0x00069fe4 */ /* bit rate (avarge)?? */ + uint32_t setinfo_8a_rate; /* 0x0000ac44 */ /* end setinfo stuff */ - }; - #endif /* __ALAC__DECOMP_H */ - diff --git a/apple_alac.cpp b/apple_alac.cpp index 3996cfe3..0ee30290 100644 --- a/apple_alac.cpp +++ b/apple_alac.cpp @@ -1,12 +1,12 @@ #include // these are headers for the ALAC decoder, utilities and endian utilities -#include #include +#include #include -#include "config.h" #include "apple_alac.h" +#include "config.h" typedef struct magicCookie { ALACSpecificConfig config; @@ -14,43 +14,42 @@ typedef struct magicCookie { } magicCookie; magicCookie cookie; -ALACDecoder * theDecoder; +ALACDecoder *theDecoder; extern "C" int apple_alac_init(int32_t fmtp[12]) { - memset(&cookie,0,sizeof(magicCookie)); - - //create a magic cookie for the decoder from the fmtp information. It seems to be in the same format as a simple magic cookie - + memset(&cookie, 0, sizeof(magicCookie)); + + // create a magic cookie for the decoder from the fmtp information. It seems to be in the same + // format as a simple magic cookie + cookie.config.frameLength = Swap32NtoB(352); - cookie.config.compatibleVersion = fmtp[2]; // should be zero, uint8_t - cookie.config.bitDepth = fmtp[3]; // uint8_t expected to be 16 - cookie.config.pb = fmtp[4]; // uint8_t should be 40; - cookie.config.mb = fmtp[5]; // uint8_t should be 10; - cookie.config.kb = fmtp[6]; // uint8_t should be 14; - cookie.config.numChannels = fmtp[7]; // uint8_t expected to be 2 - cookie.config.maxRun = Swap16NtoB(fmtp[8]); // uint16_t expected to be 255 + cookie.config.compatibleVersion = fmtp[2]; // should be zero, uint8_t + cookie.config.bitDepth = fmtp[3]; // uint8_t expected to be 16 + cookie.config.pb = fmtp[4]; // uint8_t should be 40; + cookie.config.mb = fmtp[5]; // uint8_t should be 10; + cookie.config.kb = fmtp[6]; // uint8_t should be 14; + cookie.config.numChannels = fmtp[7]; // uint8_t expected to be 2 + cookie.config.maxRun = Swap16NtoB(fmtp[8]); // uint16_t expected to be 255 cookie.config.maxFrameBytes = Swap32NtoB(fmtp[9]); // uint32_t should be 0; - cookie.config.avgBitRate = Swap32NtoB(fmtp[10]); // uint32_t should be 0;; - cookie.config.sampleRate = Swap32NtoB(fmtp[11]); // uint32_t expected to be 44100; - + cookie.config.avgBitRate = Swap32NtoB(fmtp[10]); // uint32_t should be 0;; + cookie.config.sampleRate = Swap32NtoB(fmtp[11]); // uint32_t expected to be 44100; + theDecoder = new ALACDecoder; - theDecoder->Init(&cookie, sizeof(magicCookie)); + theDecoder->Init(&cookie, sizeof(magicCookie)); return 0; } -extern "C" int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength, unsigned char *dest, int *outsize) -{ +extern "C" int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength, + unsigned char *dest, int *outsize) { uint32_t numFrames = 0; BitBuffer theInputBuffer; BitBufferInit(&theInputBuffer, sampleBuffer, bufferLength); - theDecoder->Decode(&theInputBuffer, dest, Swap32BtoN(cookie.config.frameLength), cookie.config.numChannels, &numFrames); + theDecoder->Decode(&theInputBuffer, dest, Swap32BtoN(cookie.config.frameLength), + cookie.config.numChannels, &numFrames); *outsize = numFrames; return 0; } -extern "C" int apple_alac_terminate() { - delete(theDecoder); -} - +extern "C" int apple_alac_terminate() { delete (theDecoder); } diff --git a/apple_alac.h b/apple_alac.h index 11ed4ded..eb8bcf6c 100644 --- a/apple_alac.h +++ b/apple_alac.h @@ -1,21 +1,20 @@ #ifndef __APPLE_ALAC_H #define __APPLE_ALAC_H -#include #include "config.h" +#include #ifdef __cplusplus - #define EXTERNC extern "C" +#define EXTERNC extern "C" #else - #define EXTERNC +#define EXTERNC #endif - EXTERNC int apple_alac_init(int32_t fmtp[12]); EXTERNC int apple_alac_terminate(); -EXTERNC int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength, unsigned char *dest, int *outsize); +EXTERNC int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength, + unsigned char *dest, int *outsize); #undef EXTERNC #endif /* __APPLE_ALAC_H */ - diff --git a/audio.c b/audio.c index 6c2c6fce..e40006c4 100644 --- a/audio.c +++ b/audio.c @@ -24,10 +24,10 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include #include "audio.h" #include "config.h" +#include +#include #ifdef CONFIG_SNDIO extern audio_output audio_sndio; @@ -67,7 +67,7 @@ static audio_output *outputs[] = { #ifdef CONFIG_DUMMY &audio_dummy, #endif -#ifdef CONFIG_PIPE +#ifdef CONFIG_PIPE &audio_pipe, #endif #ifdef CONFIG_STDOUT diff --git a/audio.h b/audio.h index b93e48b5..31fa1069 100644 --- a/audio.h +++ b/audio.h @@ -1,8 +1,8 @@ #ifndef _AUDIO_H #define _AUDIO_H -#include #include +#include typedef struct { double current_volume_dB; @@ -32,17 +32,17 @@ typedef struct { // almost certainly wrong if the buffer is empty, so put silent buffers into it to make it busy. // will change dynamically, so keep watching it. Implemented in ALSA only. // returns a negative error code if there's a problem - int (*delay)(long* the_delay); // snd_pcm_sframes_t is a signed long + int (*delay)(long *the_delay); // snd_pcm_sframes_t is a signed long // may be NULL, in which case soft volume is applied void (*volume)(double vol); // may be NULL, in which case soft volume parameters are used void (*parameters)(audio_parameters *info); - + // may be NULL, in which case software muting is used. void (*mute)(int do_mute); - + } audio_output; audio_output *audio_get_output(char *name); diff --git a/audio_alsa.c b/audio_alsa.c index 348a0d1f..3319a7cf 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -26,14 +26,14 @@ #define ALSA_PCM_NEW_HW_PARAMS_API +#include "audio.h" +#include "common.h" +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include "common.h" -#include "audio.h" static void help(void); static int init(int argc, char **argv); @@ -42,7 +42,7 @@ static void start(int i_sample_rate, int i_sample_format); static void play(short buf[], int samples); static void stop(void); static void flush(void); -int delay(long* the_delay); +int delay(long *the_delay); static void volume(double vol); static void linear_volume(double vol); static void parameters(audio_parameters *info); @@ -50,20 +50,18 @@ static void mute(int do_mute); static double set_volume; static int output_method_signalled = 0; -audio_output audio_alsa = { - .name = "alsa", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = &flush, - .delay = &delay, - .play = &play, - .mute = &mute, - .volume = &volume, - .parameters = ¶meters -}; +audio_output audio_alsa = {.name = "alsa", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = &flush, + .delay = &delay, + .play = &play, + .mute = &mute, + .volume = &volume, + .parameters = ¶meters}; static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -87,15 +85,15 @@ static int alsa_mix_index = 0; static int hardware_mixer = 0; static int has_softvol = 0; -static snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *, snd_pcm_uframes_t) = snd_pcm_writei; +static snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *, + snd_pcm_uframes_t) = snd_pcm_writei; static int play_number; static int64_t accumulated_delay, accumulated_da_delay; int alsa_characteristics_already_listed = 0; -static snd_pcm_uframes_t period_size_requested,buffer_size_requested; -static int set_period_size_request,set_buffer_size_request; - +static snd_pcm_uframes_t period_size_requested, buffer_size_requested; +static int set_period_size_request, set_buffer_size_request; static void help(void) { printf(" -d output-device set the output device [default*|...]\n" @@ -138,9 +136,9 @@ static int init(int argc, char **argv) { int value; double dvalue; - set_period_size_request = 0; - set_buffer_size_request = 0; - + set_period_size_request = 0; + set_buffer_size_request = 0; + config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; @@ -149,8 +147,7 @@ static int init(int argc, char **argv) { if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_float(config.cfg, - "alsa.audio_backend_buffer_desired_length", &dvalue)) { + if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length", &dvalue)) { if ((dvalue < 0) || (value > 1.5)) die("Invalid alsa audio backend buffer desired length \"%f\". It " "should be between 0 and " @@ -162,8 +159,7 @@ static int init(int argc, char **argv) { } /* Get the latency offset. */ - if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset", - &dvalue)) { + if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset", &dvalue)) { if ((dvalue < -1.0) || (value > 1.5)) die("Invalid alsa audio backend buffer latency offset \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", @@ -195,7 +191,7 @@ static int init(int argc, char **argv) { alsa_mix_ctrl = (char *)str; hardware_mixer = 1; } - + /* Get the disable_synchronization setting. */ if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) { if (strcasecmp(str, "no") == 0) @@ -205,7 +201,7 @@ static int init(int argc, char **argv) { else die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\""); } - + /* Get the output format, using the same names as aplay does*/ if (config_lookup_string(config.cfg, "alsa.output_format", &str)) { if (strcasecmp(str, "S16_LE") == 0) @@ -219,26 +215,26 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "S8") == 0) config.output_format = SPS_FORMAT_S8; else - die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16_LE\", \"S24_LE\" or \"S32_LE\"",str); + die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16_LE\", \"S24_LE\" or " + "\"S32_LE\"", + str); } /* Get the output rate, which must be a multiple of 44,100*/ - if (config_lookup_int(config.cfg, "alsa.output_rate", - &value)) { - debug(1,"Value read for output rate is %d.",value); - switch(value) { - case 44100: - case 88200: - case 176400: - case 352800: - config.output_rate = value; - break; - default : - die("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800",value); - } + if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) { + debug(1, "Value read for output rate is %d.", value); + switch (value) { + case 44100: + case 88200: + case 176400: + case 352800: + config.output_rate = value; + break; + default: + die("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800", value); + } } - /* Get the use_mmap_if_available setting. */ if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) { if (strcasecmp(str, "no") == 0) @@ -249,10 +245,9 @@ static int init(int argc, char **argv) { die("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\""); } /* Get the optional period size value */ - if (config_lookup_int(config.cfg, "alsa.period_size", - &value)) { + if (config_lookup_int(config.cfg, "alsa.period_size", &value)) { set_period_size_request = 1; - debug(1,"Value read for period size is %d.",value); + debug(1, "Value read for period size is %d.", value); if (value < 0) die("Invalid alsa period size setting \"%d\". It " "must be greater than 0.", @@ -262,10 +257,9 @@ static int init(int argc, char **argv) { } /* Get the optional buffer size value */ - if (config_lookup_int(config.cfg, "alsa.buffer_size", - &value)) { + if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) { set_buffer_size_request = 1; - debug(1,"Value read for buffer size is %d.",value); + debug(1, "Value read for buffer size is %d.", value); if (value < 0) die("Invalid alsa buffer size setting \"%d\". It " "must be greater than 0.", @@ -274,8 +268,6 @@ static int init(int argc, char **argv) { buffer_size_requested = value; } } - - optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour argv--; // so we shift the arguments to satisfy getopt() @@ -316,7 +308,7 @@ static int init(int argc, char **argv) { debug(1, "Output device name is \"%s\".", alsa_out_dev); if (hardware_mixer) { - + if (alsa_mix_dev == NULL) alsa_mix_dev = alsa_out_dev; @@ -324,25 +316,24 @@ static int init(int argc, char **argv) { open_mixer(); - if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, - &alsa_mix_maxv) < 0) + if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < + 0) debug(1, "Can't read mixer's [linear] min and max volumes."); else { - if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, - &alsa_mix_maxdb) == 0) { + if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) == + 0) { - audio_alsa.volume = - &volume; // insert the volume function now we know it can do dB stuff + audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff audio_alsa.parameters = ¶meters; // likewise the parameters stuff if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { // Raspberry Pi does this debug(1, "Lowest dB value is a mute -- try minimum volume +1"); - if (snd_mixer_selem_ask_playback_vol_dB( - alsa_mix_elem, alsa_mix_minv + 1, &alsa_mix_mindb) != 0) + if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, + &alsa_mix_mindb) != 0) debug(1, "Can't get dB value corresponding to a minimum volume + 1."); } - debug(1, "Hardware mixer has dB volume from %f to %f.", - (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); + debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, + (1.0 * alsa_mix_maxdb) / 100.0); } else { // use the linear scale and do the db conversion ourselves debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have " @@ -357,16 +348,12 @@ static int init(int argc, char **argv) { snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); - if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, - &alsa_mix_maxdb) == 0) { - debug(1, "Volume control \"%s\" has dB volume from %f to %f.", - alsa_mix_ctrl, - (1.0 * alsa_mix_mindb) / 100.0, - (1.0 * alsa_mix_maxdb) / 100.0); + if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { + debug(1, "Volume control \"%s\" has dB volume from %f to %f.", alsa_mix_ctrl, + (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); has_softvol = 1; } else { - debug(1, "Cannot get the dB range from the volume control \"%s\"", - alsa_mix_ctrl); + debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); } /* @@ -386,14 +373,13 @@ static int init(int argc, char **argv) { } } if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) { - audio_alsa.mute = - &mute; // insert the mute function now we know it can do muting stuff + audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff debug(1, "Has mute ability."); } snd_mixer_close(alsa_mix_handle); } - + alsa_mix_handle = NULL; pthread_mutex_unlock(&alsa_mutex); return 0; @@ -408,7 +394,7 @@ static void deinit(void) { } int open_alsa_device(void) { - + const snd_pcm_uframes_t minimal_buffer_headroom = 352 * 2; // we accept this much headroom in the hardware buffer, but we'll // accept less @@ -436,77 +422,83 @@ int open_alsa_device(void) { alsa_out_dev); } - if ((config.no_mmap == 0) && (snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) >= 0)) { - if (output_method_signalled==0) { - debug(1,"Output written using MMAP"); - output_method_signalled=1; - } + if ((config.no_mmap == 0) && + (snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) >= + 0)) { + if (output_method_signalled == 0) { + debug(1, "Output written using MMAP"); + output_method_signalled = 1; + } access = SND_PCM_ACCESS_MMAP_INTERLEAVED; alsa_pcm_write = snd_pcm_mmap_writei; } else { - if (output_method_signalled==0) { - debug(1,"Output written with RW"); - output_method_signalled=1; - } + if (output_method_signalled == 0) { + debug(1, "Output written with RW"); + output_method_signalled = 1; + } access = SND_PCM_ACCESS_RW_INTERLEAVED; alsa_pcm_write = snd_pcm_writei; } ret = snd_pcm_hw_params_set_access(alsa_handle, alsa_params, access); if (ret < 0) { - die("audio_alsa: Access type not available for device \"%s\": %s", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev, + snd_strerror(ret)); } ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sample_format); if (ret < 0) { - die("audio_alsa: Sample format %d not available for device \"%s\": %s", - sample_format, alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, + alsa_out_dev, snd_strerror(ret)); } ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2); if (ret < 0) { - die("audio_alsa: Channels count (2) not available for device \"%s\": %s", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev, + snd_strerror(ret)); } - ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, - &my_sample_rate, &dir); + ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir); if (ret < 0) { - die("audio_alsa: Rate %iHz not available for playback: %s", - desired_sample_rate, snd_strerror(ret)); + die("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate, + snd_strerror(ret)); } - if (set_period_size_request!=0) { - debug(1,"Attempting to set the period size"); - ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested, &dir); - if (ret < 0) { - die("audio_alsa: cannot set period size of %lu: %s", - period_size_requested, snd_strerror(ret)); - snd_pcm_uframes_t actual_period_size; - snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); - if (actual_period_size!=period_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual setting: %lu",period_size_requested,actual_period_size); - } + if (set_period_size_request != 0) { + debug(1, "Attempting to set the period size"); + ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested, + &dir); + if (ret < 0) { + die("audio_alsa: cannot set period size of %lu: %s", period_size_requested, + snd_strerror(ret)); + snd_pcm_uframes_t actual_period_size; + snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); + if (actual_period_size != period_size_requested) + inform("Actual period size set to a different value than requested. Requested: %lu, actual " + "setting: %lu", + period_size_requested, actual_period_size); + } } - if (set_buffer_size_request!=0) { - debug(1,"Attempting to set the buffer size to %lu",buffer_size_requested); - ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested); - if (ret < 0) { - die("audio_alsa: cannot set buffer size of %lu: %s", - buffer_size_requested, snd_strerror(ret)); - snd_pcm_uframes_t actual_buffer_size; - snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); - if (actual_buffer_size!=buffer_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual setting: %lu",buffer_size,actual_buffer_size); - } + if (set_buffer_size_request != 0) { + debug(1, "Attempting to set the buffer size to %lu", buffer_size_requested); + ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested); + if (ret < 0) { + die("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested, + snd_strerror(ret)); + snd_pcm_uframes_t actual_buffer_size; + snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); + if (actual_buffer_size != buffer_size_requested) + inform("Actual period size set to a different value than requested. Requested: %lu, actual " + "setting: %lu", + buffer_size, actual_buffer_size); + } } ret = snd_pcm_hw_params(alsa_handle, alsa_params); if (ret < 0) { - die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev, + snd_strerror(ret)); } if (my_sample_rate != desired_sample_rate) { @@ -515,12 +507,11 @@ int open_alsa_device(void) { ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length); if (ret < 0) { - die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev, + snd_strerror(ret)); } - if (actual_buffer_length < - config.audio_backend_buffer_desired_length + minimal_buffer_headroom) { + if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) { /* // the dac buffer is too small, so let's try to set it buffer_size = @@ -543,153 +534,158 @@ int open_alsa_device(void) { buffer_size); } */ - debug(1,"The alsa buffer is to small (%lu bytes) to accommodate the desired backend buffer length (%ld) you have chosen.",actual_buffer_length,config.audio_backend_buffer_desired_length); + debug(1, "The alsa buffer is to small (%lu bytes) to accommodate the desired backend buffer " + "length (%ld) you have chosen.", + actual_buffer_length, config.audio_backend_buffer_desired_length); } - - if (alsa_characteristics_already_listed==0) { - alsa_characteristics_already_listed=1; - int log_level = 2; // the level at which debug information should be output - int rc; - snd_pcm_access_t access_type; - snd_pcm_format_t format_type; - snd_pcm_subformat_t subformat_type; - unsigned int val, val2; - unsigned int uval, uval2; - int sval; - int dir; - snd_pcm_uframes_t frames; - debug(log_level,"PCM handle name = '%s'", - snd_pcm_name(alsa_handle)); - -// ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); -// if (ret < 0) { -// die("audio_alsa: Cannpot get configuration for device \"%s\": no configurations " -// "available", -// alsa_out_dev); -// } + if (alsa_characteristics_already_listed == 0) { + alsa_characteristics_already_listed = 1; + int log_level = 2; // the level at which debug information should be output + int rc; + snd_pcm_access_t access_type; + snd_pcm_format_t format_type; + snd_pcm_subformat_t subformat_type; + unsigned int val, val2; + unsigned int uval, uval2; + int sval; + int dir; + snd_pcm_uframes_t frames; - debug(log_level,"alsa device parameters:"); + debug(log_level, "PCM handle name = '%s'", snd_pcm_name(alsa_handle)); - snd_pcm_hw_params_get_access(alsa_params,&access_type); - debug(log_level," access type = %s", snd_pcm_access_name(access_type)); + // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); + // if (ret < 0) { + // die("audio_alsa: Cannpot get configuration for device \"%s\": no + // configurations + //" + // "available", + // alsa_out_dev); + // } - snd_pcm_hw_params_get_format(alsa_params,&format_type); - debug(log_level," format = '%s' (%s)",snd_pcm_format_name(format_type),snd_pcm_format_description(format_type)); + debug(log_level, "alsa device parameters:"); - snd_pcm_hw_params_get_subformat(alsa_params,&subformat_type); - debug(log_level," subformat = '%s' (%s)",snd_pcm_subformat_name(subformat_type),snd_pcm_subformat_description(subformat_type)); + snd_pcm_hw_params_get_access(alsa_params, &access_type); + debug(log_level, " access type = %s", snd_pcm_access_name(access_type)); - snd_pcm_hw_params_get_channels(alsa_params, &uval); - debug(log_level," number of channels = %u", uval); + snd_pcm_hw_params_get_format(alsa_params, &format_type); + debug(log_level, " format = '%s' (%s)", snd_pcm_format_name(format_type), + snd_pcm_format_description(format_type)); - sval = snd_pcm_hw_params_get_sbits(alsa_params); - debug(log_level," number of significant bits = %d", sval); - - snd_pcm_hw_params_get_rate(alsa_params, &uval, &dir); - switch (dir) { - case -1: - debug(log_level," rate = %u frames per second (<).", uval); - break; - case 0: - debug(log_level," rate = %u frames per second (precisely).", uval); - break; - case 1: - debug(log_level," rate = %u frames per second (>).", uval); - break; - } + snd_pcm_hw_params_get_subformat(alsa_params, &subformat_type); + debug(log_level, " subformat = '%s' (%s)", snd_pcm_subformat_name(subformat_type), + snd_pcm_subformat_description(subformat_type)); - if (snd_pcm_hw_params_get_rate_numden(alsa_params,&uval, &uval2)==0) - debug(log_level," precise (rational) rate = %.3f frames per second (i.e. %u/%u).", uval, uval2, ((double)uval)/uval2); - else - debug(log_level," precise (rational) rate information unavailable."); + snd_pcm_hw_params_get_channels(alsa_params, &uval); + debug(log_level, " number of channels = %u", uval); - snd_pcm_hw_params_get_period_time(alsa_params,&uval, &dir); - switch (dir) { - case -1: - debug(log_level," period_time = %u us (<).", uval); - break; - case 0: - debug(log_level," period_time = %u us (precisely).", uval); - break; - case 1: - debug(log_level," period_time = %u us (>).", uval); - break; - } + sval = snd_pcm_hw_params_get_sbits(alsa_params); + debug(log_level, " number of significant bits = %d", sval); - snd_pcm_hw_params_get_period_size(alsa_params,&frames, &dir); - switch (dir) { - case -1: - debug(log_level," period_size = %lu frames (<).", frames); - break; - case 0: - debug(log_level," period_size = %lu frames (precisely).", frames); - break; - case 1: - debug(log_level," period_size = %lu frames (>).", frames); - break; - } + snd_pcm_hw_params_get_rate(alsa_params, &uval, &dir); + switch (dir) { + case -1: + debug(log_level, " rate = %u frames per second (<).", uval); + break; + case 0: + debug(log_level, " rate = %u frames per second (precisely).", uval); + break; + case 1: + debug(log_level, " rate = %u frames per second (>).", uval); + break; + } - snd_pcm_hw_params_get_buffer_time(alsa_params,&uval, &dir); - switch (dir) { - case -1: - debug(log_level," buffer_time = %u us (<).", uval); - break; - case 0: - debug(log_level," buffer_time = %u us (precisely).", uval); - break; - case 1: - debug(log_level," buffer_time = %u us (>).", uval); - break; - } + if (snd_pcm_hw_params_get_rate_numden(alsa_params, &uval, &uval2) == 0) + debug(log_level, " precise (rational) rate = %.3f frames per second (i.e. %u/%u).", uval, + uval2, ((double)uval) / uval2); + else + debug(log_level, " precise (rational) rate information unavailable."); + + snd_pcm_hw_params_get_period_time(alsa_params, &uval, &dir); + switch (dir) { + case -1: + debug(log_level, " period_time = %u us (<).", uval); + break; + case 0: + debug(log_level, " period_time = %u us (precisely).", uval); + break; + case 1: + debug(log_level, " period_time = %u us (>).", uval); + break; + } + + snd_pcm_hw_params_get_period_size(alsa_params, &frames, &dir); + switch (dir) { + case -1: + debug(log_level, " period_size = %lu frames (<).", frames); + break; + case 0: + debug(log_level, " period_size = %lu frames (precisely).", frames); + break; + case 1: + debug(log_level, " period_size = %lu frames (>).", frames); + break; + } + + snd_pcm_hw_params_get_buffer_time(alsa_params, &uval, &dir); + switch (dir) { + case -1: + debug(log_level, " buffer_time = %u us (<).", uval); + break; + case 0: + debug(log_level, " buffer_time = %u us (precisely).", uval); + break; + case 1: + debug(log_level, " buffer_time = %u us (>).", uval); + break; + } + + snd_pcm_hw_params_get_buffer_size(alsa_params, &frames); + switch (dir) { + case -1: + debug(log_level, " buffer_size = %lu frames (<).", frames); + break; + case 0: + debug(log_level, " buffer_size = %lu frames (precisely).", frames); + break; + case 1: + debug(log_level, " buffer_size = %lu frames (>).", frames); + break; + } + + snd_pcm_hw_params_get_periods(alsa_params, &uval, &dir); + switch (dir) { + case -1: + debug(log_level, " periods_per_buffer = %u (<).", uval); + break; + case 0: + debug(log_level, " periods_per_buffer = %u (precisely).", uval); + break; + case 1: + debug(log_level, " periods_per_buffer = %u (>).", uval); + break; + } + } - snd_pcm_hw_params_get_buffer_size(alsa_params,&frames); - switch (dir) { - case -1: - debug(log_level," buffer_size = %lu frames (<).", frames); - break; - case 0: - debug(log_level," buffer_size = %lu frames (precisely).", frames); - break; - case 1: - debug(log_level," buffer_size = %lu frames (>).", frames); - break; - } - - snd_pcm_hw_params_get_periods(alsa_params, &uval, &dir); - switch (dir) { - case -1: - debug(log_level," periods_per_buffer = %u (<).", uval); - break; - case 0: - debug(log_level," periods_per_buffer = %u (precisely).", uval); - break; - case 1: - debug(log_level," periods_per_buffer = %u (>).", uval); - break; - } - } - return (0); } static void start(int i_sample_rate, int i_sample_format) { // debug(2,"audio_alsa start called."); - if (i_sample_rate==0) + if (i_sample_rate == 0) desired_sample_rate = 44100; // default else desired_sample_rate = i_sample_rate; // must be a variable - - if (i_sample_format==0) - sample_format = SND_PCM_FORMAT_S16_LE; // default + + if (i_sample_format == 0) + sample_format = SND_PCM_FORMAT_S16_LE; // default else sample_format = i_sample_format; - } -int delay(long* the_delay) { - //snd_pcm_sframes_t is a signed long -- hence the return of a "long" - int reply; +int delay(long *the_delay) { + // snd_pcm_sframes_t is a signed long -- hence the return of a "long" + int reply; // debug(3,"audio_alsa delay called."); if (alsa_handle == NULL) { return -ENODEV; @@ -709,12 +705,11 @@ int delay(long* the_delay) { reply = 0; // no error } else { if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { - *the_delay = 0; - reply = 0; // no error + *the_delay = 0; + reply = 0; // no error } else { reply = -EIO; - debug(1, "Error -- ALSA delay(): bad state: %d.", - snd_pcm_state(alsa_handle)); + debug(1, "Error -- ALSA delay(): bad state: %d.", snd_pcm_state(alsa_handle)); } if ((derr = snd_pcm_prepare(alsa_handle))) { ignore = snd_pcm_recover(alsa_handle, derr, 1); @@ -723,8 +718,8 @@ int delay(long* the_delay) { } pthread_mutex_unlock(&alsa_mutex); // here, occasionally pretend there's a problem with pcm_get_delay() - //if ((random() % 100000) < 3) // keep it pretty rare - // reply = -EPERM; // pretend something bad has happened + // if ((random() % 100000) < 3) // keep it pretty rare + // reply = -EPERM; // pretend something bad has happened return reply; } } @@ -736,7 +731,7 @@ static void play(short buf[], int samples) { pthread_mutex_lock(&alsa_mutex); ret = open_alsa_device(); if (hardware_mixer) - open_mixer(); + open_mixer(); pthread_mutex_unlock(&alsa_mutex); if ((hardware_mixer) && (ret == 0) && (audio_alsa.volume)) audio_alsa.volume(set_volume); @@ -747,17 +742,17 @@ static void play(short buf[], int samples) { int err, ignore; if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) { - if (buf==NULL) - debug(1,"NULL buffer passed to pcm_writei -- skipping it"); - if (samples==0) - debug(1,"empty buffer being passed to pcm_writei -- skipping it"); - if ((samples!=0) && (buf!=NULL)) { - err = alsa_pcm_write(alsa_handle, (char *)buf, samples); - if (err < 0) { - debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, - snd_strerror(err)); - ignore = snd_pcm_recover(alsa_handle, err, 1); - } + if (buf == NULL) + debug(1, "NULL buffer passed to pcm_writei -- skipping it"); + if (samples == 0) + debug(1, "empty buffer being passed to pcm_writei -- skipping it"); + if ((samples != 0) && (buf != NULL)) { + err = alsa_pcm_write(alsa_handle, (char *)buf, samples); + if (err < 0) { + debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, + snd_strerror(err)); + ignore = snd_pcm_recover(alsa_handle, err, 1); + } } } else { debug(1, "Error -- ALSA device in incorrect state (%d) for play.", @@ -795,8 +790,7 @@ static void flush(void) { */ if (!((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING))) - debug(1, "Flush returning unexpected state -- %d.", - snd_pcm_state(alsa_handle)); + debug(1, "Flush returning unexpected state -- %d.", snd_pcm_state(alsa_handle)); // flush also closes the device snd_pcm_close(alsa_handle); @@ -822,7 +816,7 @@ static void parameters(audio_parameters *info) { static void volume(double vol) { pthread_mutex_lock(&alsa_mutex); - debug(2, "Setting volume db to %f.", vol); + debug(2, "Setting volume db to %f.", vol); set_volume = vol; if (hardware_mixer && alsa_mix_handle) { if (has_softvol) { @@ -830,17 +824,17 @@ static void volume(double vol) { snd_ctl_elem_value_t *value; long raw; - if (snd_ctl_convert_from_dB(ctl, elem_id, (long) vol, &raw, 0) < 0) - debug(1, "Failed converting dB gain to raw volume value for the " - "software volume control."); + if (snd_ctl_convert_from_dB(ctl, elem_id, (long)vol, &raw, 0) < 0) + debug(1, "Failed converting dB gain to raw volume value for the " + "software volume control."); snd_ctl_elem_value_alloca(&value); snd_ctl_elem_value_set_id(value, elem_id); snd_ctl_elem_value_set_integer(value, 0, raw); snd_ctl_elem_value_set_integer(value, 1, raw); if (snd_ctl_elem_write(ctl, value) < 0) - debug(1, "Failed to set playback dB volume for the software volume " - "control."); + debug(1, "Failed to set playback dB volume for the software volume " + "control."); } } else { if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 0) != 0) { @@ -860,8 +854,7 @@ static void linear_volume(double vol) { if (hardware_mixer && alsa_mix_handle) { double linear_volume = pow(10, vol); // debug(1,"Linear volume is %f.",linear_volume); - long int_vol = - alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume; + long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume; // debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol); if (alsa_mix_handle) { if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0) diff --git a/audio_ao.c b/audio_ao.c index 62c0729f..69867caa 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -24,12 +24,12 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include "audio.h" +#include "common.h" +#include +#include #include #include -#include -#include -#include "common.h" -#include "audio.h" ao_device *dev = NULL; @@ -50,13 +50,12 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; - + // get settings from settings file first, allow them to be overridden by command line options if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_float(config.cfg, - "ao.audio_backend_buffer_desired_length", &dvalue)) { + if (config_lookup_float(config.cfg, "ao.audio_backend_buffer_desired_length", &dvalue)) { if ((dvalue < 0) || (value > 1.5)) die("Invalid ao audio backend buffer desired length \"%f\". It " "should be between 0 and " @@ -68,8 +67,7 @@ static int init(int argc, char **argv) { } /* Get the latency offset. */ - if (config_lookup_float(config.cfg, "ao.audio_backend_latency_offset", - &dvalue)) { + if (config_lookup_float(config.cfg, "ao.audio_backend_latency_offset", &dvalue)) { if ((dvalue < -1.0) || (value > 1.5)) die("Invalid ao audio backend buffer latency offset \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", diff --git a/audio_dummy.c b/audio_dummy.c index 3f24dadd..98d14efb 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -28,11 +28,11 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include "common.h" #include "audio.h" +#include "common.h" +#include +#include +#include int Fs; long long starttime, samples_played; diff --git a/audio_pipe.c b/audio_pipe.c index 155e9b07..550893dd 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -28,16 +28,16 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include +#include "audio.h" +#include "common.h" +#include #include #include +#include #include -#include -#include #include -#include "common.h" -#include "audio.h" +#include +#include static int fd = -1; @@ -51,7 +51,7 @@ static void start(int sample_rate, int sample_format) { static void play(short buf[], int samples) { // if the file is not open, try to open it. if (fd == -1) { - fd = open(pipename, O_WRONLY | O_NONBLOCK); + fd = open(pipename, O_WRONLY | O_NONBLOCK); } // if it's got a reader, write to it. if (fd != -1) { @@ -60,9 +60,9 @@ static void play(short buf[], int samples) { } static void stop(void) { -// Don't close the pipe just because a play session has stopped. -// if (fd > 0) -// close(fd); + // Don't close the pipe just because a play session has stopped. + // if (fd > 0) + // close(fd); } static int init(int argc, char **argv) { @@ -80,13 +80,12 @@ static int init(int argc, char **argv) { if (config_lookup_string(config.cfg, "pipe.name", &str)) { pipename = (char *)str; } - + if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0)) die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead."); /* Get the desired buffer size setting. */ - if (config_lookup_float(config.cfg, - "pipe.audio_backend_buffer_desired_length", &dvalue)) { + if (config_lookup_float(config.cfg, "pipe.audio_backend_buffer_desired_length", &dvalue)) { if ((dvalue < 0) || (value > 1.5)) die("Invalid pipe audio backend buffer desired length \"%f\". It " "should be between 0 and " @@ -98,8 +97,7 @@ static int init(int argc, char **argv) { } /* Get the latency offset. */ - if (config_lookup_float(config.cfg, "pipe.audio_backend_latency_offset", - &dvalue)) { + if (config_lookup_float(config.cfg, "pipe.audio_backend_latency_offset", &dvalue)) { if ((dvalue < -1.0) || (value > 1.5)) die("Invalid pipe audio backend buffer latency offset \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", @@ -113,8 +111,7 @@ static int init(int argc, char **argv) { if (argc == 1) pipename = strdup(argv[0]); - - + // here, create the pipe if (mkfifo(pipename, 0644) && errno != EEXIST) die("Could not create output pipe \"%s\"", pipename); @@ -125,13 +122,11 @@ static int init(int argc, char **argv) { } static void deinit(void) { - if (fd > 0) + if (fd > 0) close(fd); } -static void help(void) { - printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); -} +static void help(void) { printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); } audio_output audio_pipe = {.name = "pipe", .help = &help, diff --git a/audio_pulse.c b/audio_pulse.c index e4118259..083f863b 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -28,14 +28,14 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include "audio.h" +#include "common.h" +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include "common.h" -#include "audio.h" static pa_simple *pa_dev = NULL; diff --git a/audio_sndio.c b/audio_sndio.c index 1d2e1532..357e405b 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -19,10 +19,10 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "audio.h" +#include #include #include -#include -#include "audio.h" static struct sio_hdl *sio; static struct sio_par par; @@ -88,4 +88,4 @@ audio_output audio_sndio = {.name = "sndio", .play = &play, .volume = &volume, .parameters = NULL, - .mute= NULL}; + .mute = NULL}; diff --git a/audio_stdout.c b/audio_stdout.c index b24e0da1..fff9c654 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -27,24 +27,20 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include +#include "audio.h" +#include "common.h" +#include #include #include +#include #include -#include -#include "common.h" -#include "audio.h" +#include static int fd = -1; -static void start(int sample_rate, int sample_format) { - fd = STDOUT_FILENO; -} +static void start(int sample_rate, int sample_format) { fd = STDOUT_FILENO; } -static void play(short buf[], int samples) { - int ignore = write(fd, buf, samples * 4); -} +static void play(short buf[], int samples) { int ignore = write(fd, buf, samples * 4); } static void stop(void) { // don't close stdout @@ -60,8 +56,7 @@ static int init(int argc, char **argv) { if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_float(config.cfg, - "stdout.audio_backend_buffer_desired_length", &dvalue)) { + if (config_lookup_float(config.cfg, "stdout.audio_backend_buffer_desired_length", &dvalue)) { if ((dvalue < 0) || (dvalue > 1.5)) die("Invalid stdout audio backend buffer desired length \"%f\". It " "should be between 0 and " @@ -73,8 +68,7 @@ static int init(int argc, char **argv) { } /* Get the latency offset. */ - if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset", - &dvalue)) { + if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset", &dvalue)) { if ((dvalue < -1.0) || (value > 1.5)) die("Invalid stdout audio backend buffer latency offset \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", @@ -90,19 +84,17 @@ static void deinit(void) { // don't close stdout } -static void help(void) { - printf(" stdout takes no arguments\n"); -} +static void help(void) { printf(" stdout takes no arguments\n"); } audio_output audio_stdout = {.name = "stdout", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = NULL, - .delay = NULL, - .play = &play, - .volume = NULL, - .parameters = NULL, - .mute = NULL}; + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = NULL, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = NULL, + .mute = NULL}; diff --git a/common.c b/common.c index c1eb58e2..3389e5f9 100644 --- a/common.c +++ b/common.c @@ -25,20 +25,20 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include #include -#include -#include -#include +#include #include +#include +#include +#include +#include #include #include +#include +#include -#include #include "common.h" +#include #ifdef COMPILE_FOR_OSX #include @@ -47,20 +47,20 @@ #endif #ifdef HAVE_LIBSSL -#include -#include -#include #include #include +#include +#include +#include #endif #ifdef HAVE_LIBPOLARSSL -#include -#include -#include -#include -#include "polarssl/entropy.h" #include "polarssl/ctr_drbg.h" +#include "polarssl/entropy.h" +#include +#include +#include +#include #if POLARSSL_VERSION_NUMBER >= 0x01030000 #include "polarssl/compat-1.2.h" @@ -478,7 +478,8 @@ uint64_t get_absolute_time_in_fp() { struct timespec tn; // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt clock_gettime(CLOCK_MONOTONIC, &tn); - time_now_fp = ((uint64_t)tn.tv_sec << 32) + ((uint64_t)tn.tv_nsec << 32) / 1000000000; // types okay + time_now_fp = + ((uint64_t)tn.tv_sec << 32) + ((uint64_t)tn.tv_nsec << 32) / 1000000000; // types okay #endif #ifdef COMPILE_FOR_OSX uint64_t time_now_mach; @@ -513,13 +514,13 @@ uint64_t get_absolute_time_in_fp() { } ssize_t non_blocking_write(int fd, const void *buf, size_t count) { - void *ibuf = (void *)buf; - size_t bytes_remaining = count; - int rc = 0; + void *ibuf = (void *)buf; + size_t bytes_remaining = count; + int rc = 0; struct pollfd ufds[1]; - while ((bytes_remaining>0) && (rc==0)) { - // check that we can do some writing - ufds[0].fd = fd; + while ((bytes_remaining > 0) && (rc == 0)) { + // check that we can do some writing + ufds[0].fd = fd; ufds[0].events = POLLOUT; rc = poll(ufds, 1, 5000); if (rc < 0) { @@ -527,51 +528,55 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { } else if (rc == 0) { // warn("non-blocking write timeout waiting for pipe to become ready for writing"); rc = -2; - } else { //rc > 0, implying it might be ready - size_t bytes_written = write(fd,ibuf,bytes_remaining); - if (bytes_written==-1) { - // debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno)); - rc = -1; - } else { - ibuf += bytes_written; - bytes_remaining -= bytes_written; - } + } else { // rc > 0, implying it might be ready + size_t bytes_written = write(fd, ibuf, bytes_remaining); + if (bytes_written == -1) { + // debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno)); + rc = -1; + } else { + ibuf += bytes_written; + bytes_remaining -= bytes_written; + } } - } - if (rc==0) - return count-bytes_remaining; // this is just to mimic a normal write/3. - else - return rc; + } + if (rc == 0) + return count - bytes_remaining; // this is just to mimic a normal write/3. + else + return rc; // return write(fd,buf,count); } -/* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */ +/* from + * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 + */ -char *str_replace ( const char *string, const char *substr, const char *replacement ){ +char *str_replace(const char *string, const char *substr, const char *replacement) { char *tok = NULL; char *newstr = NULL; char *oldstr = NULL; char *head = NULL; - + /* if either substr or replacement is NULL, duplicate string a let caller handle it */ - if ( substr == NULL || replacement == NULL ) return strdup (string); - newstr = strdup (string); + if (substr == NULL || replacement == NULL) + return strdup(string); + newstr = strdup(string); head = newstr; - while ( (tok = strstr ( head, substr ))){ + while ((tok = strstr(head, substr))) { oldstr = newstr; - newstr = malloc ( strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) + 1 ); + newstr = malloc(strlen(oldstr) - strlen(substr) + strlen(replacement) + 1); /*failed to alloc mem, free old string and return NULL */ - if ( newstr == NULL ){ - free (oldstr); + if (newstr == NULL) { + free(oldstr); return NULL; } - memcpy ( newstr, oldstr, tok - oldstr ); - memcpy ( newstr + (tok - oldstr), replacement, strlen ( replacement ) ); - memcpy ( newstr + (tok - oldstr) + strlen( replacement ), tok + strlen ( substr ), strlen ( oldstr ) - strlen ( substr ) - ( tok - oldstr ) ); - memset ( newstr + strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) , 0, 1 ); + memcpy(newstr, oldstr, tok - oldstr); + memcpy(newstr + (tok - oldstr), replacement, strlen(replacement)); + memcpy(newstr + (tok - oldstr) + strlen(replacement), tok + strlen(substr), + strlen(oldstr) - strlen(substr) - (tok - oldstr)); + memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replacement), 0, 1); /* move back head right after the last replacement */ - head = newstr + (tok - oldstr) + strlen( replacement ); - free (oldstr); + head = newstr + (tok - oldstr) + strlen(replacement); + free(oldstr); } return newstr; } @@ -579,36 +584,35 @@ char *str_replace ( const char *string, const char *substr, const char *replacem /* from http://burtleburtle.net/bob/rand/smallprng.html */ typedef uint64_t u8; -typedef struct ranctx { uint64_t a; uint64_t b; uint64_t c; uint64_t d; } ranctx; +typedef struct ranctx { + uint64_t a; + uint64_t b; + uint64_t c; + uint64_t d; +} ranctx; static struct ranctx rx; -#define rot(x,k) (((x)<<(k))|((x)>>(64-(k)))) -uint64_t ranval( ranctx *x ) { - uint64_t e = x->a - rot(x->b, 7); - x->a = x->b ^ rot(x->c, 13); - x->b = x->c + rot(x->d, 37); - x->c = x->d + e; - x->d = e + x->a; - return x->d; +#define rot(x, k) (((x) << (k)) | ((x) >> (64 - (k)))) +uint64_t ranval(ranctx *x) { + uint64_t e = x->a - rot(x->b, 7); + x->a = x->b ^ rot(x->c, 13); + x->b = x->c + rot(x->d, 37); + x->c = x->d + e; + x->d = e + x->a; + return x->d; } -void raninit( ranctx *x, uint64_t seed ) { - uint64_t i; - x->a = 0xf1ea5eed, x->b = x->c = x->d = seed; - for (i=0; i<20; ++i) { - (void)ranval(x); - } +void raninit(ranctx *x, uint64_t seed) { + uint64_t i; + x->a = 0xf1ea5eed, x->b = x->c = x->d = seed; + for (i = 0; i < 20; ++i) { + (void)ranval(x); + } } -void r64init(uint64_t seed) { - raninit(&rx,seed); -} +void r64init(uint64_t seed) { raninit(&rx, seed); } -uint64_t r64u() { - return(ranval(&rx)); -} +uint64_t r64u() { return (ranval(&rx)); } -int64_t r64i() { - return(ranval(&rx)>>1); -} +int64_t r64i() { return (ranval(&rx) >> 1); } diff --git a/common.h b/common.h index d146ab3a..64b9b44f 100644 --- a/common.h +++ b/common.h @@ -1,12 +1,12 @@ #ifndef _COMMON_H #define _COMMON_H +#include #include #include -#include -#include "config.h" #include "audio.h" +#include "config.h" #include "mdns.h" #if defined(__APPLE__) && defined(__MACH__) @@ -58,28 +58,72 @@ enum decoders_supported_type { // the following enum must _exactly match the snd_pcm_format_t definition in the alsa pcm.h file. -enum sps_format_t { - SPS_FORMAT_UNKNOWN = -1, SPS_FORMAT_S8 = 0, SPS_FORMAT_U8, SPS_FORMAT_S16_LE, - SPS_FORMAT_S16_BE, SPS_FORMAT_U16_LE, SPS_FORMAT_U16_BE, SPS_FORMAT_S24_LE, - SPS_FORMAT_S24_BE, SPS_FORMAT_U24_LE, SPS_FORMAT_U24_BE, SPS_FORMAT_S32_LE, - SPS_FORMAT_S32_BE, SPS_FORMAT_U32_LE, SPS_FORMAT_U32_BE, SPS_FORMAT_FLOAT_LE, - SPS_FORMAT_FLOAT_BE, SPS_FORMAT_FLOAT64_LE, SPS_FORMAT_FLOAT64_BE, SPS_FORMAT_IEC958_SUBFRAME_LE, - SPS_FORMAT_IEC958_SUBFRAME_BE, SPS_FORMAT_MU_LAW, SPS_FORMAT_A_LAW, SPS_FORMAT_IMA_ADPCM, - SPS_FORMAT_MPEG, SPS_FORMAT_GSM, SPS_FORMAT_SPECIAL = 31, SPS_FORMAT_S24_3LE = 32, - SPS_FORMAT_S24_3BE, SPS_FORMAT_U24_3LE, SPS_FORMAT_U24_3BE, SPS_FORMAT_S20_3LE, - SPS_FORMAT_S20_3BE, SPS_FORMAT_U20_3LE, SPS_FORMAT_U20_3BE, SPS_FORMAT_S18_3LE, - SPS_FORMAT_S18_3BE, SPS_FORMAT_U18_3LE, SPS_FORMAT_U18_3BE, SPS_FORMAT_G723_24, - SPS_FORMAT_G723_24_1B, SPS_FORMAT_G723_40, SPS_FORMAT_G723_40_1B, SPS_FORMAT_DSD_U8, - SPS_FORMAT_DSD_U16_LE, SPS_FORMAT_DSD_U32_LE, SPS_FORMAT_DSD_U16_BE, SPS_FORMAT_DSD_U32_BE, - SPS_FORMAT_LAST = SPS_FORMAT_DSD_U32_BE, SPS_FORMAT_S16 = SPS_FORMAT_S16_LE, SPS_FORMAT_U16 = SPS_FORMAT_U16_LE, SPS_FORMAT_S24 = SPS_FORMAT_S24_LE, - SPS_FORMAT_U24 = SPS_FORMAT_U24_LE, SPS_FORMAT_S32 = SPS_FORMAT_S32_LE, SPS_FORMAT_U32 = SPS_FORMAT_U32_LE, SPS_FORMAT_FLOAT = SPS_FORMAT_FLOAT_LE, - SPS_FORMAT_FLOAT64 = SPS_FORMAT_FLOAT64_LE, SPS_FORMAT_IEC958_SUBFRAME = SPS_FORMAT_IEC958_SUBFRAME_LE +enum sps_format_t { + SPS_FORMAT_UNKNOWN = -1, + SPS_FORMAT_S8 = 0, + SPS_FORMAT_U8, + SPS_FORMAT_S16_LE, + SPS_FORMAT_S16_BE, + SPS_FORMAT_U16_LE, + SPS_FORMAT_U16_BE, + SPS_FORMAT_S24_LE, + SPS_FORMAT_S24_BE, + SPS_FORMAT_U24_LE, + SPS_FORMAT_U24_BE, + SPS_FORMAT_S32_LE, + SPS_FORMAT_S32_BE, + SPS_FORMAT_U32_LE, + SPS_FORMAT_U32_BE, + SPS_FORMAT_FLOAT_LE, + SPS_FORMAT_FLOAT_BE, + SPS_FORMAT_FLOAT64_LE, + SPS_FORMAT_FLOAT64_BE, + SPS_FORMAT_IEC958_SUBFRAME_LE, + SPS_FORMAT_IEC958_SUBFRAME_BE, + SPS_FORMAT_MU_LAW, + SPS_FORMAT_A_LAW, + SPS_FORMAT_IMA_ADPCM, + SPS_FORMAT_MPEG, + SPS_FORMAT_GSM, + SPS_FORMAT_SPECIAL = 31, + SPS_FORMAT_S24_3LE = 32, + SPS_FORMAT_S24_3BE, + SPS_FORMAT_U24_3LE, + SPS_FORMAT_U24_3BE, + SPS_FORMAT_S20_3LE, + SPS_FORMAT_S20_3BE, + SPS_FORMAT_U20_3LE, + SPS_FORMAT_U20_3BE, + SPS_FORMAT_S18_3LE, + SPS_FORMAT_S18_3BE, + SPS_FORMAT_U18_3LE, + SPS_FORMAT_U18_3BE, + SPS_FORMAT_G723_24, + SPS_FORMAT_G723_24_1B, + SPS_FORMAT_G723_40, + SPS_FORMAT_G723_40_1B, + SPS_FORMAT_DSD_U8, + SPS_FORMAT_DSD_U16_LE, + SPS_FORMAT_DSD_U32_LE, + SPS_FORMAT_DSD_U16_BE, + SPS_FORMAT_DSD_U32_BE, + SPS_FORMAT_LAST = SPS_FORMAT_DSD_U32_BE, + SPS_FORMAT_S16 = SPS_FORMAT_S16_LE, + SPS_FORMAT_U16 = SPS_FORMAT_U16_LE, + SPS_FORMAT_S24 = SPS_FORMAT_S24_LE, + SPS_FORMAT_U24 = SPS_FORMAT_U24_LE, + SPS_FORMAT_S32 = SPS_FORMAT_S32_LE, + SPS_FORMAT_U32 = SPS_FORMAT_U32_LE, + SPS_FORMAT_FLOAT = SPS_FORMAT_FLOAT_LE, + SPS_FORMAT_FLOAT64 = SPS_FORMAT_FLOAT64_LE, + SPS_FORMAT_IEC958_SUBFRAME = SPS_FORMAT_IEC958_SUBFRAME_LE } sps_format_t; typedef struct { config_t *cfg; char *password; - char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running on host %h" + char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running + // on host %h" #ifdef CONFIG_METADATA int metadata_enabled; char *metadata_pipename; @@ -93,9 +137,10 @@ typedef struct { int udp_port_base; int udp_port_range; int ignore_volume_control; - int no_sync; // disable synchronisation, even if it's available - int no_mmap; // disable use of mmap-based output, even if it's available - double resyncthreshold; // if it get's out of whack my more than this number of seconds, resync. Zero means never + int no_sync; // disable synchronisation, even if it's available + int no_mmap; // disable use of mmap-based output, even if it's available + double resyncthreshold; // if it get's out of whack my more than this number of seconds, resync. + // Zero means never // resync. int allow_session_interruption; int timeout; // while in play mode, exit if no packets of audio come in for more than this number @@ -110,26 +155,30 @@ typedef struct { int64_t latency; int64_t userSuppliedLatency; // overrides all other latencies -- use with caution int64_t iTunesLatency; // supplied with --iTunesLatency option - int64_t AirPlayLatency; // supplied with --AirPlayLatency option - int64_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option + int64_t AirPlayLatency; // supplied with --AirPlayLatency option + int64_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option int daemonise; - int statistics_requested,use_negotiated_latencies; + int statistics_requested, use_negotiated_latencies; enum playback_mode_type playback_mode; char *cmd_start, *cmd_stop; int cmd_blocking; double tolerance; // allow this much drift before attempting to correct it enum stuffing_type packet_stuffing; int decoders_supported; - int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by David Hammerton + int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by + // David Hammerton char *pidfile; // char *logfile; // char *errfile; char *configfile; - char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by default “_raop._tcp.”. + char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by + // default “_raop._tcp.”. double audio_backend_buffer_desired_length; // this will be the length in seconds of the // audio backend buffer -- the DAC buffer for ALSA - double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any fixed latency there might be in the audio path - uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's native range. + double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any + // fixed latency there might be in the audio path + uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's + // native range. enum sps_format_t output_format; int output_rate; } shairport_cfg; @@ -142,9 +191,10 @@ void set_requested_connection_state_to_output(int v); ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places -/* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */ -char *str_replace ( const char *string, const char *substr, const char *replacement ); - +/* from + * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 + */ +char *str_replace(const char *string, const char *substr, const char *replacement); // based on http://burtleburtle.net/bob/rand/smallprng.html @@ -152,7 +202,6 @@ void r64init(uint64_t seed); uint64_t r64u(); int64_t r64i(); - int debuglev; void die(char *format, ...); void warn(char *format, ...); diff --git a/mdns.c b/mdns.c index fa5bfb91..ad8d8775 100644 --- a/mdns.c +++ b/mdns.c @@ -24,13 +24,13 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include "mdns.h" +#include "common.h" +#include "config.h" #include -#include #include #include -#include "config.h" -#include "common.h" -#include "mdns.h" +#include #ifdef CONFIG_AVAHI extern mdns_backend mdns_avahi; @@ -46,10 +46,12 @@ extern mdns_backend mdns_tinysvcmdns; static mdns_backend *mdns_backends[] = { #ifdef CONFIG_AVAHI - &mdns_avahi, &mdns_external_avahi, + &mdns_avahi, + &mdns_external_avahi, #endif #ifdef CONFIG_HAVE_DNS_SD_H - &mdns_dns_sd, &mdns_external_dns_sd, + &mdns_dns_sd, + &mdns_external_dns_sd, #endif #ifdef CONFIG_TINYSVCMDNS &mdns_tinysvcmdns, diff --git a/mdns.h b/mdns.h index 4c1cf247..c4fe4bc6 100644 --- a/mdns.h +++ b/mdns.h @@ -23,9 +23,10 @@ typedef struct { // "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", METADATA_EXPRESSION, "ss=16", \ // "sr=44100", "vn=3", "txtvers=1", config.password ? "pw=true" : "pw=false" -#define MDNS_RECORD_WITH_METADATA \ - "sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ - METADATA_EXPRESSION, "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false" +#define MDNS_RECORD_WITH_METADATA \ + "sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ + METADATA_EXPRESSION, "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", \ + "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false" #endif @@ -33,11 +34,9 @@ typedef struct { // "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", METADATA_EXPRESSION, "ss=16", "sr=44100", "vn=3", \ // "txtvers=1", config.password ? "pw=true" : "pw=false" -#define MDNS_RECORD_WITHOUT_METADATA \ - "sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ - "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false" +#define MDNS_RECORD_WITHOUT_METADATA \ + "sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", "ss=16", \ + "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", \ + config.password ? "pw=true" : "pw=false" #endif // _MDNS_H - - - diff --git a/mdns_avahi.c b/mdns_avahi.c index 065322d7..12df0505 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -27,15 +27,15 @@ #include -#include #include "common.h" #include "mdns.h" +#include #include #include -#include -#include #include +#include +#include #include #include @@ -48,157 +48,148 @@ static AvahiThreadedPoll *tpoll = NULL; static char *name = NULL; static int port = 0; -static void resolve_callback( - AvahiServiceResolver *r, - AVAHI_GCC_UNUSED AvahiIfIndex interface, - AVAHI_GCC_UNUSED AvahiProtocol protocol, - AvahiResolverEvent event, - const char *name, - const char *type, - const char *domain, - const char *host_name, - const AvahiAddress *address, - uint16_t port, - AvahiStringList *txt, - AvahiLookupResultFlags flags, - AVAHI_GCC_UNUSED void* userdata) { +static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface, + AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event, + const char *name, const char *type, const char *domain, + const char *host_name, const AvahiAddress *address, uint16_t port, + AvahiStringList *txt, AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void *userdata) { - assert(r); + assert(r); - /* Called whenever a service has been resolved successfully or timed out */ + /* Called whenever a service has been resolved successfully or timed out */ - switch (event) { - case AVAHI_RESOLVER_FAILURE: - debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); - break; + switch (event) { + case AVAHI_RESOLVER_FAILURE: + debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, + type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + break; - case AVAHI_RESOLVER_FOUND: { - if (flags & AVAHI_LOOKUP_RESULT_OUR_OWN) { - char a[AVAHI_ADDRESS_STR_MAX], *t; + case AVAHI_RESOLVER_FOUND: { + if (flags & AVAHI_LOOKUP_RESULT_OUR_OWN) { + char a[AVAHI_ADDRESS_STR_MAX], *t; -// debug(1, "avahi: service '%s' of type '%s' in domain '%s' added.", name, type, domain); - avahi_address_snprint(a, sizeof(a), address); - debug(1,"avahi: address advertised is: \"%s\".",a); - /* - t = avahi_string_list_to_string(txt); - debug(1, - "\t%s:%u (%s)\n" - "\tTXT=%s\n" - "\tcookie is %u\n" - "\tis_local: %i\n" - "\tour_own: %i\n" - "\twide_area: %i\n" - "\tmulticast: %i\n" - "\tcached: %i\n", - host_name, port, a, - t, - avahi_string_list_get_service_cookie(txt), - !!(flags & AVAHI_LOOKUP_RESULT_LOCAL), - !!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN), - !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA), - !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST), - !!(flags & AVAHI_LOOKUP_RESULT_CACHED)); + // debug(1, "avahi: service '%s' of type '%s' in domain '%s' added.", name, type, + // domain); + avahi_address_snprint(a, sizeof(a), address); + debug(1, "avahi: address advertised is: \"%s\".", a); + /* + t = avahi_string_list_to_string(txt); + debug(1, + "\t%s:%u (%s)\n" + "\tTXT=%s\n" + "\tcookie is %u\n" + "\tis_local: %i\n" + "\tour_own: %i\n" + "\twide_area: %i\n" + "\tmulticast: %i\n" + "\tcached: %i\n", + host_name, port, a, + t, + avahi_string_list_get_service_cookie(txt), + !!(flags & AVAHI_LOOKUP_RESULT_LOCAL), + !!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN), + !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA), + !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST), + !!(flags & AVAHI_LOOKUP_RESULT_CACHED)); - avahi_free(t); - */ - } - } + avahi_free(t); + */ } + } + } - avahi_service_resolver_free(r); + avahi_service_resolver_free(r); } -static void browse_callback( - AvahiServiceBrowser *b, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiBrowserEvent event, - const char *name, - const char *type, - const char *domain, - AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, - void* userdata) { +static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiBrowserEvent event, const char *name, const char *type, + const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + void *userdata) { - AvahiClient *c = userdata; - assert(b); + AvahiClient *c = userdata; + assert(b); - /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ + /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ - switch (event) { - case AVAHI_BROWSER_FAILURE: + switch (event) { + case AVAHI_BROWSER_FAILURE: -// debug(1, "(Browser) %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); - return; + // debug(1, "(Browser) %s\n", + // avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); + return; - case AVAHI_BROWSER_NEW: -// debug(1, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain); + case AVAHI_BROWSER_NEW: + // debug(1, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, + // domain); - /* We ignore the returned resolver object. In the callback - function we free it. If the server is terminated before - the callback function is called the server will free - the resolver for us. */ + /* We ignore the returned resolver object. In the callback + function we free it. If the server is terminated before + the callback function is called the server will free + the resolver for us. */ - if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, c))) - debug(1, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c))); + if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, + 0, resolve_callback, c))) + debug(1, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c))); - break; + break; - case AVAHI_BROWSER_REMOVE: -// debug(1, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain); - break; + case AVAHI_BROWSER_REMOVE: + // debug(1, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, + // type, domain); + break; - case AVAHI_BROWSER_ALL_FOR_NOW: - case AVAHI_BROWSER_CACHE_EXHAUSTED: -// debug(1, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); - break; - } + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + // debug(1, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? + // "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); + break; + } } - -static void register_service( AvahiClient *c ); +static void register_service(AvahiClient *c); static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { - switch ( state ) - { - case AVAHI_ENTRY_GROUP_ESTABLISHED: - /* The entry group has been established successfully */ - debug(1,"avahi: service '%s' successfully added.", name ); - break; + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully */ + debug(1, "avahi: service '%s' successfully added.", name); + break; - case AVAHI_ENTRY_GROUP_COLLISION: - { - char *n; + case AVAHI_ENTRY_GROUP_COLLISION: { + char *n; - /* A service name collision with a remote service - * happened. Let's pick a new name */ - n = avahi_alternative_service_name(name); - avahi_free(name); - name = n; + /* A service name collision with a remote service + * happened. Let's pick a new name */ + n = avahi_alternative_service_name(name); + avahi_free(name); + name = n; - debug(2,"avahi: service name collision, renaming service to '%s'", name ); + debug(2, "avahi: service name collision, renaming service to '%s'", name); - /* And recreate the services */ - register_service( avahi_entry_group_get_client( g ) ); - break; - } + /* And recreate the services */ + register_service(avahi_entry_group_get_client(g)); + break; + } - case AVAHI_ENTRY_GROUP_FAILURE: - debug(1,"avahi: entry group failure: %s", avahi_strerror( avahi_client_errno( avahi_entry_group_get_client( g ) ) ) ); - break; + case AVAHI_ENTRY_GROUP_FAILURE: + debug(1, "avahi: entry group failure: %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + break; - case AVAHI_ENTRY_GROUP_UNCOMMITED: - debug(2,"avahi: service '%s' group is not yet commited.", name ); - break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + debug(2, "avahi: service '%s' group is not yet commited.", name); + break; - case AVAHI_ENTRY_GROUP_REGISTERING: - debug(2,"avahi: service '%s' group is registering.", name ); - break; + case AVAHI_ENTRY_GROUP_REGISTERING: + debug(2, "avahi: service '%s' group is registering.", name); + break; - default: - debug(1,"avahi: unhandled egroup state: %d", state ); - break; - } + default: + debug(1, "avahi: unhandled egroup state: %d", state); + break; + } } static void register_service(AvahiClient *c) { @@ -213,30 +204,30 @@ static void register_service(AvahiClient *c) { return; int ret; - #ifdef CONFIG_METADATA +#ifdef CONFIG_METADATA if (config.metadata_enabled) { ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, - config.regtype, NULL, NULL, port, MDNS_RECORD_WITH_METADATA, - NULL); - if (ret==0) - debug(1, "avahi: request to add \"%s\" service with metadata",config.regtype); + config.regtype, NULL, NULL, port, + MDNS_RECORD_WITH_METADATA, NULL); + if (ret == 0) + debug(1, "avahi: request to add \"%s\" service with metadata", config.regtype); } else { - #endif +#endif ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, config.regtype, NULL, NULL, port, MDNS_RECORD_WITHOUT_METADATA, NULL); - if (ret==0) - debug(1, "avahi: request to add \"%s\" service without metadata",config.regtype); - #ifdef CONFIG_METADATA + if (ret == 0) + debug(1, "avahi: request to add \"%s\" service without metadata", config.regtype); +#ifdef CONFIG_METADATA } - #endif +#endif if (ret < 0) - debug(1,"avahi: avahi_entry_group_add_service failed"); + debug(1, "avahi: avahi_entry_group_add_service failed"); else { ret = avahi_entry_group_commit(group); if (ret < 0) - debug(1,"avahi: avahi_entry_group_commit failed"); + debug(1, "avahi: avahi_entry_group_commit failed"); } } } @@ -244,30 +235,30 @@ static void register_service(AvahiClient *c) { static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata) { switch (state) { - case AVAHI_CLIENT_S_REGISTERING: - if (group) - avahi_entry_group_reset(group); - break; + case AVAHI_CLIENT_S_REGISTERING: + if (group) + avahi_entry_group_reset(group); + break; - case AVAHI_CLIENT_S_RUNNING: - register_service(c); - break; + case AVAHI_CLIENT_S_RUNNING: + register_service(c); + break; - case AVAHI_CLIENT_FAILURE: - debug(1,"avahi: client failure"); - break; + case AVAHI_CLIENT_FAILURE: + debug(1, "avahi: client failure"); + break; - case AVAHI_CLIENT_S_COLLISION: - debug(2, "avahi: state is AVAHI_CLIENT_S_COLLISION...needs a rename: %s", name ); - break; + case AVAHI_CLIENT_S_COLLISION: + debug(2, "avahi: state is AVAHI_CLIENT_S_COLLISION...needs a rename: %s", name); + break; - case AVAHI_CLIENT_CONNECTING: - debug(2, "avahi: received AVAHI_CLIENT_CONNECTING" ); - break; + case AVAHI_CLIENT_CONNECTING: + debug(2, "avahi: received AVAHI_CLIENT_CONNECTING"); + break; - default: - debug(1,"avahi: unexpected and unhandled avahi client state: %d", state ); - break; + default: + debug(1, "avahi: unexpected and unhandled avahi client state: %d", state); + break; } } @@ -286,13 +277,13 @@ static int avahi_register(char *srvname, int srvport) { warn("couldn't create avahi client: %s!", avahi_strerror(err)); return -1; } - - // we need this to detect the IPv6 number we're advertising... - // if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, config.regtype, NULL, 0, browse_callback, client))) { - // warn("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client))); - // return -1; - // } + // we need this to detect the IPv6 number we're advertising... + // if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + // config.regtype, NULL, 0, browse_callback, client))) { + // warn("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client))); + // return -1; + // } if (avahi_threaded_poll_start(tpoll) < 0) { warn("couldn't start avahi tpoll thread"); diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c index fb9424a2..b4b7e4ac 100644 --- a/mdns_dns_sd.c +++ b/mdns_dns_sd.c @@ -24,12 +24,12 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include #include "mdns.h" #include "common.h" +#include +#include +#include +#include static DNSServiceRef service; diff --git a/mdns_external.c b/mdns_external.c index c3b5dc1b..fcd3a323 100644 --- a/mdns_external.c +++ b/mdns_external.c @@ -24,14 +24,14 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include -#include -#include -#include "common.h" #include "mdns.h" +#include "common.h" +#include +#include +#include +#include +#include +#include int mdns_pid = 0; @@ -87,8 +87,8 @@ static int mdns_external_avahi_register(char *apname, int port) { char mdns_port[6]; sprintf(mdns_port, "%d", config.port); - char *argvwithoutmetadata[] = {NULL, apname, config.regtype, mdns_port, - MDNS_RECORD_WITHOUT_METADATA, NULL}; + char *argvwithoutmetadata[] = { + NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL}; #ifdef CONFIG_METADATA char *argvwithmetadata[] = {NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITH_METADATA, NULL}; @@ -126,8 +126,8 @@ static int mdns_external_dns_sd_register(char *apname, int port) { char mdns_port[6]; sprintf(mdns_port, "%d", config.port); - char *argvwithoutmetadata[] = {NULL, apname, config.regtype, mdns_port, - MDNS_RECORD_WITHOUT_METADATA, NULL}; + char *argvwithoutmetadata[] = { + NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL}; #ifdef CONFIG_METADATA char *argvwithmetadata[] = {NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITH_METADATA, diff --git a/mdns_tinysvcmdns.c b/mdns_tinysvcmdns.c index 049737b4..8d51d4bf 100644 --- a/mdns_tinysvcmdns.c +++ b/mdns_tinysvcmdns.c @@ -24,15 +24,15 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include -#include -#include -#include -#include "common.h" #include "mdns.h" +#include "common.h" +#include +#include +#include +#include +#include +#include +#include #include "tinysvcmdns.h" @@ -124,22 +124,23 @@ static int mdns_tinysvcmdns_register(char *apname, int port) { #endif txt = txtwithoutmetadata; - + if (config.regtype == NULL) die("tinysvcmdns: regtype is null"); - char* extendedregtype = malloc(strlen(config.regtype)+strlen(".local")+1); + char *extendedregtype = malloc(strlen(config.regtype) + strlen(".local") + 1); - if (extendedregtype==NULL) + if (extendedregtype == NULL) die("tinysvcmdns: could not allocated memory to request a Zeroconf service"); - - strcpy(extendedregtype,config.regtype); - strcat(extendedregtype,".local"); - struct mdns_service *svc = mdnsd_register_svc(svr, apname, extendedregtype, port, NULL, + strcpy(extendedregtype, config.regtype); + strcat(extendedregtype, ".local"); + + struct mdns_service *svc = + mdnsd_register_svc(svr, apname, extendedregtype, port, NULL, (const char **)txt); // TTL should be 75 minutes, i.e. 4500 seconds mdns_service_destroy(svc); - + free(extendedregtype); return 0; diff --git a/player.c b/player.c index 96d3ac27..0e226c1c 100644 --- a/player.c +++ b/player.c @@ -28,22 +28,22 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include +#include +#include +#include +#include +#include #include "config.h" @@ -76,9 +76,9 @@ static AES_KEY aes; #endif static int input_rate, input_bit_depth, input_num_channels, max_frames_per_packet; -static uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval;// timestamp_epoch of zero means not initialised, could start at 2 or 1. - - +static uint32_t timestamp_epoch, last_timestamp, + maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 + // or 1. static int input_bytes_per_frame = 4; static int output_bytes_per_frame; @@ -86,7 +86,6 @@ static int output_sample_ratio; static int output_rate; static int64_t previous_random_number = 0; - // The maximum frame size change there can be is +/- 1; static int max_frame_size_change; // #define FRAME_BYTES(max_frames_per_packet) (4 * max_frames_per_packet) @@ -97,11 +96,12 @@ static int max_frame_size_change; static aes_context dctx; #endif -//static pthread_t player_thread = NULL; +// static pthread_t player_thread = NULL; static int please_stop; static int encrypted; // Normally the audio is encrypted, but it may not be -static int connection_state_to_output; // if true, then play incoming stuff; if false drop everything +static int + connection_state_to_output; // if true, then play incoming stuff; if false drop everything static alac_file *decoder_info; @@ -156,56 +156,58 @@ static uint64_t missing_packets, late_packets, too_late_packets, resend_requests // make timestamps and seqnos definitely monotonic -// add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33 frames and continue up to 2^63-1 frames +// add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33 +// frames and continue up to 2^63-1 frames // if should never get into the negative range -// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds or over 50,000 years. -// also, it won't reach zero until then, if ever, so we can safely say that a null monotonic timestamp can mean something special +// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds or +// over 50,000 years. +// also, it won't reach zero until then, if ever, so we can safely say that a null monotonic +// timestamp can mean something special int64_t monotonic_timestamp(uint32_t timestamp) { int64_t previous_value; int64_t return_value; - if (timestamp_epoch==0) { - if (timestamp>maximum_timestamp_interval) - timestamp_epoch=1; + if (timestamp_epoch == 0) { + if (timestamp > maximum_timestamp_interval) + timestamp_epoch = 1; else - timestamp_epoch=2; + timestamp_epoch = 2; previous_value = timestamp_epoch; - previous_value<<=32; - previous_value+=timestamp; + previous_value <<= 32; + previous_value += timestamp; } else { previous_value = timestamp_epoch; - previous_value<<=32; - previous_value+=last_timestamp; - if (timestampmaximum_timestamp_interval) - timestamp_epoch++; + if ((last_timestamp - timestamp) > maximum_timestamp_interval) + timestamp_epoch++; } else { // the incoming timestamp is greater than the last one. // if the difference is more than a minute, assume it's really from the previous epoch - if ((timestamp-last_timestamp)>maximum_timestamp_interval) + if ((timestamp - last_timestamp) > maximum_timestamp_interval) timestamp_epoch--; } } - last_timestamp=timestamp; + last_timestamp = timestamp; return_value = timestamp_epoch; - return_value<<=32; - return_value+=timestamp; - if (previous_value>return_value) { - if ((previous_value-return_value)>maximum_timestamp_interval) - debug(1,"interval between successive rtptimes greater than allowed!"); + return_value <<= 32; + return_value += timestamp; + if (previous_value > return_value) { + if ((previous_value - return_value) > maximum_timestamp_interval) + debug(1, "interval between successive rtptimes greater than allowed!"); } else { - if ((return_value-previous_value)>maximum_timestamp_interval) - debug(1,"interval between successive rtptimes greater than allowed!"); + if ((return_value - previous_value) > maximum_timestamp_interval) + debug(1, "interval between successive rtptimes greater than allowed!"); } - if (return_value<0) - debug(1,"monotonic rtptime is negative!"); + if (return_value < 0) + debug(1, "monotonic rtptime is negative!"); return return_value; } // add an epoch to the seq_no. Uses the accompanying timestamp to determine the correct epoch -uint64_t monotonic_seqno(uint16_t seq_no) { -} +uint64_t monotonic_seqno(uint16_t seq_no) {} static void ab_resync(void) { int i; @@ -239,7 +241,7 @@ static inline seq_t PREDECESSOR(seq_t x) { // anything with ORDINATE in it must be proctected by the ab_mutex static inline int32_t ORDINATE(seq_t x) { - int32_t p = x; // int32_t from seq_t, i.e. uint16_t, so okay + int32_t p = x; // int32_t from seq_t, i.e. uint16_t, so okay int32_t q = ab_read; // int32_t from seq_t, i.e. uint16_t, so okay int32_t t = (p + 0x10000 - q) & 0xffff; // we definitely will get a positive number in t at this point, but it might be a @@ -296,16 +298,17 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { // parameters: where the decoded stuff goes, its length in samples, // the incoming packet, the length of the incoming packet in bytes // destlen should contain the allowed max number of samples on entry - - if (len>MAX_PACKET) { - warn("Incoming audio packet size is too large at %d; it should not exceed %d.",len,MAX_PACKET); + + if (len > MAX_PACKET) { + warn("Incoming audio packet size is too large at %d; it should not exceed %d.", len, + MAX_PACKET); return -1; } unsigned char packet[MAX_PACKET]; unsigned char packetp[MAX_PACKET]; assert(len <= MAX_PACKET); - int reply = 0; //everything okay - int outsize=input_bytes_per_frame*(*destlen); // the size the output should be, in bytes + int reply = 0; // everything okay + int outsize = input_bytes_per_frame * (*destlen); // the size the output should be, in bytes int toutsize = outsize; if (encrypted) { @@ -321,101 +324,132 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { memcpy(packet + aeslen, buf + aeslen, len - aeslen); #ifdef HAVE_APPLE_ALAC if (config.use_apple_decoder) { - if (decoder_in_use!=1<toutsize) { - debug(2,"Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- truncated, but buffer overflow possible! Encrypted = %d.",outsize, toutsize, encrypted); + if (outsize > toutsize) { + debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " + "truncated, but buffer overflow possible! Encrypted = %d.", + outsize, toutsize, encrypted); reply = -1; // output packet is the wrong size } *destlen = outsize / input_bytes_per_frame; - if ((outsize % input_bytes_per_frame)!=0) - debug(1,"Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) and the audio frame size (%d).",*destlen,outsize,input_bytes_per_frame); + if ((outsize % input_bytes_per_frame) != 0) + debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " + "and the audio frame size (%d).", + *destlen, outsize, input_bytes_per_frame); return reply; } static int init_decoder(int32_t fmtp[12]) { -// This is a guess, but the format of the fmtp looks identical to the format of an ALACSpecificCOnfig -// which is detailed in the file ALACMagicCookieDescription.txt in the Apple ALAC sample implementation -// Here it is: + // This is a guess, but the format of the fmtp looks identical to the format of an + // ALACSpecificCOnfig + // which is detailed in the file ALACMagicCookieDescription.txt in the Apple ALAC sample + // implementation + // Here it is: -/* - struct ALACSpecificConfig (defined in ALACAudioTypes.h) - abstract This struct is used to describe codec provided information about the encoded Apple Lossless bitstream. - It must accompany the encoded stream in the containing audio file and be provided to the decoder. + /* + struct ALACSpecificConfig (defined in ALACAudioTypes.h) + abstract This struct is used to describe codec provided information about the encoded + Apple Lossless bitstream. + It must accompany the encoded stream in the containing audio file and be provided + to the decoder. - field frameLength uint32_t indicating the frames per packet when no explicit frames per packet setting is - present in the packet header. The encoder frames per packet can be explicitly set - but for maximum compatibility, the default encoder setting of 4096 should be used. + field frameLength uint32_t indicating the frames per packet + when + no + explicit + frames per packet setting is + present in the packet header. The + encoder frames per packet can be explicitly set + but for maximum compatibility, the + default encoder setting of 4096 should be used. - field compatibleVersion uint8_t indicating compatible version, - value must be set to 0 + field compatibleVersion uint8_t indicating compatible version, + value must be set to 0 - field bitDepth uint8_t describes the bit depth of the source PCM data (maximum value = 32) + field bitDepth uint8_t describes the bit depth of the source + PCM + data + (maximum + value = 32) - field pb uint8_t currently unused tuning parametetbugr. - value should be set to 40 + field pb uint8_t currently unused tuning + parametetbugr. + value should be set to 40 - field mb uint8_t currently unused tuning parameter. - value should be set to 14 + field mb uint8_t currently unused tuning parameter. + value should be set to 14 - field kb uint8_t currently unused tuning parameter. - value should be set to 10 + field kb uint8_t currently unused tuning parameter. + value should be set to 10 - field numChannels uint8_t describes the channel count (1 = mono, 2 = stereo, etc...) - when channel layout info is not provided in the 'magic cookie', a channel count > 2 - describes a set of discreet channels with no specific ordering + field numChannels uint8_t describes the channel count (1 = mono, + 2 + = + stereo, + etc...) + when channel layout info is not provided + in the 'magic cookie', a channel count > 2 + describes a set of discreet channels + with no specific ordering - field maxRun uint16_t currently unused. - value should be set to 255 + field maxRun uint16_t currently unused. + value should be set to 255 - field maxFrameBytes uint32_t the maximum size of an Apple Lossless packet within the encoded stream. - value of 0 indicates unknown + field maxFrameBytes uint32_t the maximum size of an Apple + Lossless + packet + within + the encoded stream. + value of 0 indicates unknown - field avgBitRate uint32_t the average bit rate in bits per second of the Apple Lossless stream. - value of 0 indicates unknown - - field sampleRate uint32_t sample rate of the encoded stream - */ - -// We are going to go on that basis + field avgBitRate uint32_t the average bit rate in bits per second + of + the + Apple + Lossless stream. + value of 0 indicates unknown + field sampleRate uint32_t sample rate of the encoded stream + */ + // We are going to go on that basis alac_file *alac; @@ -424,8 +458,8 @@ static int init_decoder(int32_t fmtp[12]) { input_rate = fmtp[11]; input_num_channels = fmtp[7]; input_bit_depth = fmtp[3]; - - input_bytes_per_frame = input_num_channels*((input_bit_depth+7)/8); + + input_bytes_per_frame = input_num_channels * ((input_bit_depth + 7) / 8); alac = alac_create(input_bit_depth, input_num_channels); if (!alac) @@ -462,7 +496,8 @@ static void terminate_decoders(void) { static void init_buffer(void) { int i; for (i = 0; i < BUFFER_FRAMES; i++) - audio_buffer[i].data = malloc(input_bytes_per_frame*(max_frames_per_packet+max_frame_size_change)); + audio_buffer[i].data = + malloc(input_bytes_per_frame * (max_frames_per_packet + max_frame_size_change)); ab_resync(); } @@ -473,35 +508,35 @@ static void free_buffer(void) { } void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { - - // all timestamps are done at the output rate - - int64_t ltimestamp = timestamp*output_sample_ratio; + + // all timestamps are done at the output rate + + int64_t ltimestamp = timestamp * output_sample_ratio; // ignore a request to flush that has been made before the first packet... - if (packet_count==0) { + if (packet_count == 0) { pthread_mutex_lock(&flush_mutex); flush_requested = 0; flush_rtp_timestamp = 0; pthread_mutex_unlock(&flush_mutex); } - + pthread_mutex_lock(&ab_mutex); packet_count++; time_of_last_audio_packet = get_absolute_time_in_fp(); if (connection_state_to_output) { // if we are supposed to be processing these packets -// if (flush_rtp_timestamp != 0) -// debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); + // if (flush_rtp_timestamp != 0) + // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); - if ((flush_rtp_timestamp != 0) && - (ltimestamp <= flush_rtp_timestamp)) { - debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " - "timestamp: %lld.", + if ((flush_rtp_timestamp != 0) && (ltimestamp <= flush_rtp_timestamp)) { + debug(3, + "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " + "timestamp: %lld.", seqno, ltimestamp, flush_rtp_timestamp); } else { if ((flush_rtp_timestamp != 0x0) && - (ltimestamp>flush_rtp_timestamp)) // if we have gone past the flush boundary time + (ltimestamp > flush_rtp_timestamp)) // if we have gone past the flush boundary time flush_rtp_timestamp = 0x0; abuf_t *abuf = 0; @@ -531,8 +566,8 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { } // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write); abuf = audio_buffer + BUFIDX(seqno); -// rtp_request_resend(ab_write, gap); -// resend_requests++; + // rtp_request_resend(ab_write, gap); + // resend_requests++; ab_write = SUCCESSOR(seqno); } else if (seq_order(ab_read, seqno)) { // late but not yet played late_packets++; @@ -551,16 +586,16 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { if (abuf) { int datalen = max_frames_per_packet; - if (alac_decode(abuf->data, &datalen, data, len)==0) { - abuf->ready = 1; - abuf->length = datalen; - abuf->timestamp = ltimestamp; - abuf->sequence_number = seqno; + if (alac_decode(abuf->data, &datalen, data, len) == 0) { + abuf->ready = 1; + abuf->length = datalen; + abuf->timestamp = ltimestamp; + abuf->sequence_number = seqno; } else { - debug(1,"Bad audio packet detected and discarded."); - abuf->ready = 0; - abuf->timestamp = 0; - abuf->sequence_number = 0; + debug(1, "Bad audio packet detected and discarded."); + abuf->ready = 0; + abuf->timestamp = 0; + abuf->sequence_number = 0; } } @@ -575,125 +610,154 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { int32_t rand_in_range(int32_t exclusive_range_limit) { static uint32_t lcg_prev = 12345; - // returns a pseudo random integer in the range 0 to (exclusive_range_limit-1) inclusive - int64_t sp = lcg_prev; - int64_t rl = exclusive_range_limit; - lcg_prev = lcg_prev * 69069 + 3; // crappy psrg - sp = sp*rl; // 64 bit calculation. INtersting part if above the 32 rightmost bits; - return sp >> 32; + // returns a pseudo random integer in the range 0 to (exclusive_range_limit-1) inclusive + int64_t sp = lcg_prev; + int64_t rl = exclusive_range_limit; + lcg_prev = lcg_prev * 69069 + 3; // crappy psrg + sp = sp * rl; // 64 bit calculation. INtersting part if above the 32 rightmost bits; + return sp >> 32; } -static inline void process_sample(int32_t sample,char**outp,enum sps_format_t format,int volume,int dither) { +static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume, + int dither) { int64_t hyper_sample = sample; int result; // first, modify volume, if necessary - if (volume==0x10000) - hyper_sample<<=32; + if (volume == 0x10000) + hyper_sample <<= 32; else - hyper_sample*=(volume<<16); - + hyper_sample *= (volume << 16); // this is 32*32 -> 64 bit bit multiplication -- we may need to + // dither it down to its + // target resolution + // next, do dither, if necessary if (dither) { + + // add a TPDF dither -- see + // http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf + // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25 + + // I think, for a 32 --> 16 bits, the range of + // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from + // -32768 to +32767 + + // See the original paper at + // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf + // by Lipshitz, Wannamaker and Vanderkooy, 1992. + int64_t dither_mask; switch (format) { - case SPS_FORMAT_S32_LE: - dither_mask = (int64_t)1<<(64+1-32); - break; - case SPS_FORMAT_S24_LE: - dither_mask = (int64_t)1<<(64+1-24); - break; - case SPS_FORMAT_S16_LE: - dither_mask = (int64_t)1<<(64+1-16); - break; - case SPS_FORMAT_S8: - case SPS_FORMAT_U8: - dither_mask = (int64_t)1<<(64+1-24); - break; + case SPS_FORMAT_S32_LE: + dither_mask = (int64_t)1 << (64 + 1 - 32); + break; + case SPS_FORMAT_S24_LE: + dither_mask = (int64_t)1 << (64 + 1 - 24); + break; + case SPS_FORMAT_S16_LE: + dither_mask = (int64_t)1 << (64 + 1 - 16); + break; + case SPS_FORMAT_S8: + case SPS_FORMAT_U8: + dither_mask = (int64_t)1 << (64 + 1 - 24); + break; } - dither_mask-=1; + dither_mask -= 1; int64_t r = r64i(); - int64_t tpdf = (r&dither_mask)-(previous_random_number&dither_mask); - previous_random_number=r; + int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask); + previous_random_number = r; // add dither, allowing for clipping - if (tpdf>=0) { - if (INT64_MAX-tpdf>=hyper_sample) + if (tpdf >= 0) { + if (INT64_MAX - tpdf >= hyper_sample) hyper_sample += tpdf; else hyper_sample = INT64_MAX; } else { - if (INT64_MIN-tpdf<=hyper_sample) + if (INT64_MIN - tpdf <= hyper_sample) hyper_sample += tpdf; else hyper_sample = INT64_MIN; } // dither is complete here } - + // move the result to the desired position in the int64_t char *op = *outp; switch (format) { - case SPS_FORMAT_S32_LE: - hyper_sample>>=(64-32); - *(int32_t*)op = hyper_sample; - result=4; - break; - case SPS_FORMAT_S24_LE: - hyper_sample>>=(64-24); - *(int32_t*)op = hyper_sample; - result=4; - break; - case SPS_FORMAT_S16_LE: - hyper_sample>>=(64-16); - *(int16_t*)op = hyper_sample; - result=2; - break; - case SPS_FORMAT_S8: - hyper_sample>>=(64-8); - *op = hyper_sample; - result=1; - break; - case SPS_FORMAT_U8: - hyper_sample>>=(64-8); - hyper_sample+=128; // this is just a guess! - *op = hyper_sample; - result=1; - break; + case SPS_FORMAT_S32_LE: + hyper_sample >>= (64 - 32); + *(int32_t *)op = hyper_sample; + result = 4; + break; + case SPS_FORMAT_S24_LE: + hyper_sample >>= (64 - 24); + *(int32_t *)op = hyper_sample; + result = 4; + break; + case SPS_FORMAT_S16_LE: + hyper_sample >>= (64 - 16); + *(int16_t *)op = hyper_sample; + result = 2; + break; + case SPS_FORMAT_S8: + hyper_sample >>= (64 - 8); + *op = hyper_sample; + result = 1; + break; + case SPS_FORMAT_U8: + hyper_sample >>= (64 - 8); + hyper_sample += 128; // this is just a guess! + *op = hyper_sample; + result = 1; + break; } - - *outp+=result; + + *outp += result; } - static inline int32_t dithered_vol_32(int32_t sample, int output_precision) { - if ((fix_volume==0x1000) && (output_precision==32)) { + if ((fix_volume == 0x1000) && (output_precision == 32)) { return sample; } else { - int64_t s = sample; // 64 bit signed + int64_t s = sample; // 64 bit signed int64_t v = fix_volume & 0x1FFFF; // 17 bit unsigned made a 33 bit unsigned - v<<=16; - s = s*v; // 32 bit multiplication -- we need to dither it down to its target resolution + v <<= 16; + s = s * v; // this is 32*32 -> 64 bit bit multiplication -- we may need to dither it down to its + // target resolution + + // add a TPDF dither -- see + // http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf + // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25 + + // I think, for a 32 --> 16 bits, the range of + // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from + // -32768 to +32767 + + // See the original paper at + // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf + // by Lipshitz, Wannamaker and Vanderkooy, 1992. int64_t r = r64i(); - int64_t mask = (int64_t)1<<(64+1-output_precision); - mask -=1; - int64_t tpdf = (r&mask); //-(previous_random_number&((1<<(64+1-output_precision))-1)); -// debug(1,"output precision is %d, m is %llx, r is %llx, tpdf is %llx",output_precision,mask,r,tpdf); - previous_random_number=r; - - // Check there's no clipping -- if there is, - if (tpdf>=0) { - if (INT64_MAX-tpdf>=s) + int64_t mask = (int64_t)1 << (64 + 1 - output_precision); + mask -= 1; + int64_t tpdf = (r & mask) - (previous_random_number & mask); + + previous_random_number = r; + + // Check there's no clipping -- if there is, use INT64_MAX or INT64_MIN + + if (tpdf >= 0) { + if (INT64_MAX - tpdf >= s) s += tpdf; else s = INT64_MAX; } else { - if (INT64_MIN-tpdf<=s) + if (INT64_MIN - tpdf <= s) s += tpdf; else s = INT64_MIN; } - s>>=(64-output_precision); + s >>= (64 - output_precision); return s; } } @@ -703,28 +767,31 @@ static inline short dithered_vol(short sample) { out = (long)sample * fix_volume; if (fix_volume < 0x10000) { - - // add a TPDF dither -- see http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf - // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25 - - // I think, for a 32 --> 16 bits, the range of - // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from -32768 to +32767 - - // See the original paper at http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf - // by Lipshitz, Wannamaker and Vanderkooy, 1992. - - long tpdf = rand_in_range(65536+1) - rand_in_range(65536+1); - // Check there's no clipping -- if there is, - if (tpdf>=0) { - if (LONG_MAX-tpdf>=out) - out += tpdf; - else - out = LONG_MAX; + + // add a TPDF dither -- see + // http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf + // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25 + + // I think, for a 32 --> 16 bits, the range of + // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from + // -32768 to +32767 + + // See the original paper at + // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf + // by Lipshitz, Wannamaker and Vanderkooy, 1992. + + long tpdf = rand_in_range(65536 + 1) - rand_in_range(65536 + 1); + // Check there's no clipping -- if there is, + if (tpdf >= 0) { + if (LONG_MAX - tpdf >= out) + out += tpdf; + else + out = LONG_MAX; } else { - if (LONG_MIN-tpdf<=out) - out += tpdf; - else - out = LONG_MIN; + if (LONG_MIN - tpdf <= out) + out += tpdf; + else + out = LONG_MIN; } } return out >> 16; @@ -742,7 +809,7 @@ static abuf_t *buffer_get_frame(void) { pthread_mutex_lock(&ab_mutex); int wait; - long dac_delay = 0; // long because alsa returns a long + long dac_delay = 0; // long because alsa returns a long do { // get the time local_time_now = get_absolute_time_in_fp(); // type okay @@ -785,12 +852,14 @@ static abuf_t *buffer_get_frame(void) { flush_requested = 0; } pthread_mutex_unlock(&flush_mutex); - + uint32_t flush_limit = 0; if (ab_synced) { do { curframe = audio_buffer + BUFIDX(ab_read); - if ((ab_read!=ab_write) && (curframe->ready)) { // it could be synced and empty, under exceptional circumstances, with the frame unused, thus apparently ready + if ((ab_read != ab_write) && (curframe->ready)) { // it could be synced and empty, under + // exceptional circumstances, with the + // frame unused, thus apparently ready if (curframe->sequence_number != ab_read) { // some kind of sync problem has occurred. @@ -805,15 +874,14 @@ static abuf_t *buffer_get_frame(void) { } } - if ((flush_rtp_timestamp != 0) && - (curframe->timestamp <= flush_rtp_timestamp)) { + if ((flush_rtp_timestamp != 0) && (curframe->timestamp <= flush_rtp_timestamp)) { debug(1, "Dropping flushed packet seqno %u, timestamp %lld", curframe->sequence_number, curframe->timestamp); curframe->ready = 0; flush_limit++; ab_read = SUCCESSOR(ab_read); } - if (curframe->timestamp>flush_rtp_timestamp) + if (curframe->timestamp > flush_rtp_timestamp) flush_rtp_timestamp = 0; } } while ((flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); @@ -826,13 +894,15 @@ static abuf_t *buffer_get_frame(void) { curframe = audio_buffer + BUFIDX(ab_read); if (curframe->ready) { - notified_buffer_empty=0; // at least one buffer now -- diagnostic only. + notified_buffer_empty = 0; // at least one buffer now -- diagnostic only. if (ab_buffering) { // if we are getting packets but not yet forwarding them to the player - int have_sent_prefiller_silence; // set true when we have sent some silent frames to the DAC + int have_sent_prefiller_silence; // set true when we have sent some silent frames to the + // DAC int64_t reference_timestamp; - uint64_t reference_timestamp_time,remote_reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); - reference_timestamp*=output_sample_ratio; + uint64_t reference_timestamp_time, remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, + &remote_reference_timestamp_time); + reference_timestamp *= output_sample_ratio; if (first_packet_timestamp == 0) { // if this is the very first packet // debug(1,"First frame seen, time %u, with %d // frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); @@ -865,42 +935,50 @@ static abuf_t *buffer_get_frame(void) { // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e. // -4410 frames. -debug(1,"Output sample ratio is %d",output_sample_ratio); + debug(1, "Output sample ratio is %d", output_sample_ratio); - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*config.output_rate; - - if (delta>=0) { - int64_t delta_fp_sec = (delta << 32) / config.output_rate; // int64_t which is positive - first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; + int64_t delta = (first_packet_timestamp - reference_timestamp) + + config.latency * output_sample_ratio + + config.audio_backend_latency_offset * config.output_rate; + + if (delta >= 0) { + int64_t delta_fp_sec = + (delta << 32) / config.output_rate; // int64_t which is positive + first_packet_time_to_play = reference_timestamp_time + delta_fp_sec; } else { int64_t abs_delta = -delta; - int64_t delta_fp_sec = (abs_delta << 32) / config.output_rate; // int64_t which is positive - first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; + int64_t delta_fp_sec = + (abs_delta << 32) / config.output_rate; // int64_t which is positive + first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } if (local_time_now >= first_packet_time_to_play) { debug( 1, "First packet is late! It should have played before now. Flushing 0.1 seconds"); - player_flush(first_packet_timestamp + 4410*output_sample_ratio); + player_flush(first_packet_timestamp + 4410 * output_sample_ratio); } } } if (first_packet_time_to_play != 0) { // recalculate first_packet_time_to_play -- the latency might change - int64_t delta = (first_packet_timestamp - reference_timestamp)+config.latency*output_sample_ratio+config.audio_backend_latency_offset*config.output_rate; - - if (delta>=0) { - int64_t delta_fp_sec = (delta << 32) / config.output_rate; // int64_t which is positive - first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; + int64_t delta = (first_packet_timestamp - reference_timestamp) + + config.latency * output_sample_ratio + + config.audio_backend_latency_offset * config.output_rate; + + if (delta >= 0) { + int64_t delta_fp_sec = + (delta << 32) / config.output_rate; // int64_t which is positive + first_packet_time_to_play = reference_timestamp_time + delta_fp_sec; } else { int64_t abs_delta = -delta; - int64_t delta_fp_sec = (abs_delta << 32) / config.output_rate; // int64_t which is positive - first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; + int64_t delta_fp_sec = + (abs_delta << 32) / config.output_rate; // int64_t which is positive + first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } - int64_t max_dac_delay = config.output_rate/10; + int64_t max_dac_delay = config.output_rate / 10; int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC if (local_time_now >= first_packet_time_to_play) { @@ -919,11 +997,11 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); } else { // first_packet_time_to_play is definitely later than local_time_now if ((config.output->delay) && (have_sent_prefiller_silence != 0)) { - int resp = config.output->delay(&dac_delay); + int resp = config.output->delay(&dac_delay); if (resp != 0) { - debug(1, "Error %d getting dac_delay in buffer_get_frame.",resp); + debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp); dac_delay = 0; - } + } } else dac_delay = 0; int64_t gross_frame_gap = @@ -944,9 +1022,10 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); int64_t fs = filler_size; if (fs > (max_dac_delay - dac_delay)) fs = max_dac_delay - dac_delay; - if (fs<0) { - debug(2,"frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",max_dac_delay, dac_delay); - fs=0; + if (fs < 0) { + debug(2, "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld", + max_dac_delay, dac_delay); + fs = 0; } if ((exact_frame_gap <= fs) || (exact_frame_gap <= max_frames_per_packet * 2)) { fs = exact_frame_gap; @@ -957,15 +1036,18 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); ab_buffering = 0; } signed short *silence; - //if (fs==0) - // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and dac_delay of %lld.",gross_frame_gap,dac_delay); - // the fs (number of frames of silence to play) can be zero in the DAC doesn't start ouotputting frames for a while -- it could get loaded up but not start responding for many milliseconds. - if (fs!=0) { - silence = malloc(output_bytes_per_frame*fs); - if (silence==NULL) - debug(1,"Failed to allocate %d byte silence buffer.",fs); + // if (fs==0) + // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and + // dac_delay of %lld.",gross_frame_gap,dac_delay); + // the fs (number of frames of silence to play) can be zero in the DAC doesn't start + // ouotputting frames for a while -- it could get loaded up but not start responding + // for many milliseconds. + if (fs != 0) { + silence = malloc(output_bytes_per_frame * fs); + if (silence == NULL) + debug(1, "Failed to allocate %d byte silence buffer.", fs); else { - memset(silence, 0, output_bytes_per_frame*fs); + memset(silence, 0, output_bytes_per_frame * fs); // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, // with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write)); config.output->play(silence, fs); @@ -979,12 +1061,14 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); if (ab_buffering == 0) { // not the time of the playing of the first frame uint64_t reference_timestamp_time; // don't need this... - get_reference_timestamp_stuff(&play_segment_reference_frame, &reference_timestamp_time, &play_segment_reference_frame_remote_time); - play_segment_reference_frame*=output_sample_ratio; + get_reference_timestamp_stuff(&play_segment_reference_frame, &reference_timestamp_time, + &play_segment_reference_frame_remote_time); + play_segment_reference_frame *= output_sample_ratio; #ifdef CONFIG_METADATA - send_ssnc_metadata('prsm', NULL, 0, 0); // "resume", but don't wait if the queue is locked + send_ssnc_metadata('prsm', NULL, 0, + 0); // "resume", but don't wait if the queue is locked #endif - } + } } } } @@ -1003,25 +1087,32 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); int do_wait = 0; // don't wait unless we can really prove we must if ((ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { - do_wait = 1; // if the current frame exists and is ready, then wait unless it's time to let it go... + do_wait = + 1; // if the current frame exists and is ready, then wait unless it's time to let it go... int64_t reference_timestamp; - uint64_t reference_timestamp_time,remote_reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); // all types okay - reference_timestamp*=output_sample_ratio; - if (reference_timestamp) { // if we have a reference time + uint64_t reference_timestamp_time, remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, + &remote_reference_timestamp_time); // all types okay + reference_timestamp *= output_sample_ratio; + if (reference_timestamp) { // if we have a reference time int64_t packet_timestamp = curframe->timestamp; // types okay int64_t delta = packet_timestamp - reference_timestamp; - int64_t offset = config.latency*output_sample_ratio + config.audio_backend_latency_offset*config.output_rate - - config.audio_backend_buffer_desired_length*config.output_rate; // all arguments are int32_t, so expression promotion okay - int64_t net_offset = delta + offset; // okay + int64_t offset = + config.latency * output_sample_ratio + + config.audio_backend_latency_offset * config.output_rate - + config.audio_backend_buffer_desired_length * + config.output_rate; // all arguments are int32_t, so expression promotion okay + int64_t net_offset = delta + offset; // okay uint64_t time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { - uint64_t net_offset_fp_sec = (net_offset << 32) / config.output_rate; // int64_t which is positive - time_to_play += net_offset_fp_sec; // using the latency requested... + uint64_t net_offset_fp_sec = + (net_offset << 32) / config.output_rate; // int64_t which is positive + time_to_play += net_offset_fp_sec; // using the latency requested... // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); } else { int64_t abs_net_offset = -net_offset; - uint64_t net_offset_fp_sec = (abs_net_offset << 32) / config.output_rate; // int64_t which is positive + uint64_t net_offset_fp_sec = + (abs_net_offset << 32) / config.output_rate; // int64_t which is positive time_to_play -= net_offset_fp_sec; // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); } @@ -1031,21 +1122,21 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); } } } - if (do_wait==0) - if ((ab_synced!=0) && (ab_read==ab_write)) { // the buffer is empty! - if (notified_buffer_empty==0) { - debug(1,"Buffers exhausted."); - notified_buffer_empty=1; + if (do_wait == 0) + if ((ab_synced != 0) && (ab_read == ab_write)) { // the buffer is empty! + if (notified_buffer_empty == 0) { + debug(1, "Buffers exhausted."); + notified_buffer_empty = 1; } - do_wait=1; + do_wait = 1; } wait = (ab_buffering || (do_wait != 0) || (!ab_synced)) && (!please_stop); if (wait) { uint64_t time_to_wait_for_wakeup_fp = - ((uint64_t)1 << 32) / input_rate; // this is time period of one frame + ((uint64_t)1 << 32) / input_rate; // this is time period of one frame time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets - time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time + time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; @@ -1094,11 +1185,10 @@ debug(1,"Output sample ratio is %d",output_sample_ratio); } } - if (!curframe->ready) { // debug(1, "Supplying a silent frame for frame %u", read); missing_packets++; - memset(curframe->data, 0, input_bytes_per_frame*max_frames_per_packet); + memset(curframe->data, 0, input_bytes_per_frame * max_frames_per_packet); curframe->timestamp = 0; } curframe->ready = 0; @@ -1127,21 +1217,23 @@ static inline int32_t mean_32(int32_t a, int32_t b) { return r; } -// this takes an array of signed 32-bit integers and (a) removes or inserts a frame as specified in stuff, +// this takes an array of signed 32-bit integers and (a) removes or inserts a frame as specified in +// stuff, // (b) multiplies each sample by the fixedvolume (a 16-bit quantity) // (c) dithers the result to the output size 32/24/16 bits // (d) outputs the result in the approprate format // formats accepted so far include U8, S8, S16_LE, S24_LE and S32_LE on a little endinan machine. // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 -static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, char *outptr, int stuff, int dither) { +static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, + char *outptr, int stuff, int dither) { int tstuff = stuff; char *l_outptr = outptr; - if ((stuff > 1) || (stuff < -1) || (length <100)) { + if ((stuff > 1) || (stuff < -1) || (length < 100)) { // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); tstuff = 0; // if any of these conditions hold, don't stuff anything/ } - + int i; int stuffsamp = length; if (tstuff) @@ -1151,39 +1243,40 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l pthread_mutex_lock(&vol_mutex); for (i = 0; i < stuffsamp; i++) { // the whole frame, if no stuffing - process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); - process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither); }; if (tstuff) { if (tstuff == 1) { // debug(3, "+++++++++"); // interpolate one sample - process_sample(mean_32(inptr[-2], inptr[0]),&l_outptr,l_output_format,fix_volume,dither); - process_sample(mean_32(inptr[-1], inptr[1]),&l_outptr,l_output_format,fix_volume,dither); + process_sample(mean_32(inptr[-2], inptr[0]), &l_outptr, l_output_format, fix_volume, dither); + process_sample(mean_32(inptr[-1], inptr[1]), &l_outptr, l_output_format, fix_volume, dither); } else if (stuff == -1) { // debug(3, "---------"); inptr++; inptr++; } - - // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the rest. + + // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the + // rest. int remainder = length; - if (tstuff<0) - remainder = remainder+tstuff; // don't run over the correct end of the output buffer + if (tstuff < 0) + remainder = remainder + tstuff; // don't run over the correct end of the output buffer for (i = stuffsamp; i < remainder; i++) { - process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); - process_sample(*inptr++,&l_outptr,l_output_format,fix_volume,dither); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither); } } pthread_mutex_unlock(&vol_mutex); - + return length + tstuff; } static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff) { int tstuff = stuff; - if ((stuff > 1) || (stuff < -1) || (length <100)) { + if ((stuff > 1) || (stuff < -1) || (length < 100)) { // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); tstuff = 0; // if any of these conditions hold, don't stuff anything/ } @@ -1212,11 +1305,12 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff inptr++; inptr++; } - - // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the rest. + + // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the + // rest. int remainder = length; - if (tstuff<0) - remainder = remainder+tstuff; // don't run over the correct end of the output buffer + if (tstuff < 0) + remainder = remainder + tstuff; // don't run over the correct end of the output buffer for (i = stuffsamp; i < remainder; i++) { *outptr++ = dithered_vol(*inptr++); @@ -1229,13 +1323,14 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff } #ifdef HAVE_LIBSOXR -static int stuff_buffer_soxr_32(int32_t *inptr, int length, int32_t *outptr, int stuff, int l_output_bit_depth) { +static int stuff_buffer_soxr_32(int32_t *inptr, int length, int32_t *outptr, int stuff, + int l_output_bit_depth) { return length; } // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) { int tstuff = stuff; - if ((stuff > 1) || (stuff < -1) || (length <100)) { + if ((stuff > 1) || (stuff < -1) || (length < 100)) { // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); tstuff = 0; // if any of these conditions hold, don't stuff anything/ } @@ -1255,8 +1350,8 @@ static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) size_t odone; - soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, /* Rates and # of chans. */ - inptr, length, NULL, /* Input. */ + soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, /* Rates and # of chans. */ + inptr, length, NULL, /* Input. */ outptr, length + tstuff, &odone, /* Output. */ &io_spec, /* Input, output and transfer spec. */ NULL, NULL); /* Default configuration.*/ @@ -1314,44 +1409,48 @@ typedef struct stats { // statistics for running averages } stats_t; static void *player_thread_func(void *arg) { - struct inter_threads_record itr; - itr.please_stop = 0; - timestamp_epoch = 0; // indicate that the next timestamp will be the first one. - maximum_timestamp_interval = input_rate * 60; // actually there shouldn't be more than about 13v seconds of a gap between successive rtptimes, at worst - - output_sample_ratio = config.output_rate/input_rate; + struct inter_threads_record itr; + itr.please_stop = 0; + timestamp_epoch = 0; // indicate that the next timestamp will be the first one. + maximum_timestamp_interval = input_rate * 60; // actually there shouldn't be more than about 13v + // seconds of a gap between successive rtptimes, at + // worst - debug(1,"Output sample ratio is %d.",output_sample_ratio); - - max_frame_size_change = 1*output_sample_ratio; // we add or subtract one frame at the nominal rate, multiply it by the frame ratio. + output_sample_ratio = config.output_rate / input_rate; - output_bytes_per_frame = 4; - switch (config.output_format) { - case SPS_FORMAT_S24_LE: - output_bytes_per_frame=8; - break; - case SPS_FORMAT_S32_LE: - output_bytes_per_frame=8; - break; - } + debug(1, "Output sample ratio is %d.", output_sample_ratio); - debug(1,"Output frame bytes is %d.",output_bytes_per_frame); + max_frame_size_change = 1 * output_sample_ratio; // we add or subtract one frame at the nominal + // rate, multiply it by the frame ratio. - // create and start the timing, control and audio receiver threads - pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; - pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&itr); + output_bytes_per_frame = 4; + switch (config.output_format) { + case SPS_FORMAT_S24_LE: + output_bytes_per_frame = 8; + break; + case SPS_FORMAT_S32_LE: + output_bytes_per_frame = 8; + break; + } + + debug(1, "Output frame bytes is %d.", output_bytes_per_frame); + + // create and start the timing, control and audio receiver threads + pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; + pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&itr); pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)&itr); pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)&itr); - session_corrections = 0; - play_segment_reference_frame = 0; // zero signals that we are not in a play segment + session_corrections = 0; + play_segment_reference_frame = 0; // zero signals that we are not in a play segment + // check that there are enough buffers to accommodate the desired latency and the latency offset - // check that there are enough buffers to accommodate the desired latency and the latency offset - - int maximum_latency = config.latency+config.audio_backend_latency_offset*input_rate; - if ((maximum_latency+(352-1))/352 + 10 > BUFFER_FRAMES) - die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame packets may be accommodated.",maximum_latency,BUFFER_FRAMES); + int maximum_latency = config.latency + config.audio_backend_latency_offset * input_rate; + if ((maximum_latency + (352 - 1)) / 352 + 10 > BUFFER_FRAMES) + die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame " + "packets may be accommodated.", + maximum_latency, BUFFER_FRAMES); connection_state_to_output = get_requested_connection_state_to_output(); // this is about half a minute #define trend_interval 3758 @@ -1387,58 +1486,63 @@ static void *player_thread_func(void *arg) { signed short *inbuf, *tbuf, *silence; char *outbuf; - - int inbuflength; - - int output_bit_depth = 16; // default; - - switch (config.output_format) { - case SPS_FORMAT_S16_LE: - output_bit_depth = 16; - break; - case SPS_FORMAT_S24_LE: - output_bit_depth = 24; - break; - case SPS_FORMAT_S32_LE: - output_bit_depth = 32; - break; - } - - - debug(1,"Output bit depth is %d.",output_bit_depth); - // if we are changing any of the parameters of the input, like sample rate or sample depth, then we + int inbuflength; + + int output_bit_depth = 16; // default; + + switch (config.output_format) { + case SPS_FORMAT_S16_LE: + output_bit_depth = 16; + break; + case SPS_FORMAT_S24_LE: + output_bit_depth = 24; + break; + case SPS_FORMAT_S32_LE: + output_bit_depth = 32; + break; + } + + debug(1, "Output bit depth is %d.", output_bit_depth); + + // if we are changing any of the parameters of the input, like sample rate or sample depth, then + // we // need an intermediate "transition" buffer - + if (1) { - //if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { - // debug(1,"Define tbuf of length %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - tbuf = malloc(sizeof(int32_t)*2*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - if (tbuf==NULL) - debug(1,"Failed to allocate memory for the transition buffer."); + // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { + // debug(1,"Define tbuf of length + // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); + tbuf = malloc(sizeof(int32_t) * 2 * + (max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + if (tbuf == NULL) + debug(1, "Failed to allocate memory for the transition buffer."); } else { tbuf = 0; - } - + } + // We might need an output buffer and a buffer of silence. - // The size of these dependents on the number of frames, the size of each frame and the maximum size change - outbuf = malloc(output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - if (outbuf==NULL) - debug(1,"Failed to allocate memory for an output buffer."); - silence = malloc(output_bytes_per_frame*max_frames_per_packet*output_sample_ratio); - if (silence==NULL) - debug(1,"Failed to allocate memory for a silence buffer."); - memset(silence, 0, output_bytes_per_frame*max_frames_per_packet*output_sample_ratio); + // The size of these dependents on the number of frames, the size of each frame and the maximum + // size change + outbuf = malloc(output_bytes_per_frame * + (max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + if (outbuf == NULL) + debug(1, "Failed to allocate memory for an output buffer."); + silence = malloc(output_bytes_per_frame * max_frames_per_packet * output_sample_ratio); + if (silence == NULL) + debug(1, "Failed to allocate memory for a silence buffer."); + memset(silence, 0, output_bytes_per_frame * max_frames_per_packet * output_sample_ratio); late_packet_message_sent = 0; first_packet_timestamp = 0; missing_packets = late_packets = too_late_packets = resend_requests = 0; flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to be used // as a null operand, so we'll use it like that too - int sync_error_out_of_bounds = 0; // number of times in a row that there's been a serious sync error + int sync_error_out_of_bounds = + 0; // number of times in a row that there's been a serious sync error if (config.statistics_requested) { if ((config.output->delay)) { - if (config.no_sync==0) { + if (config.no_sync == 0) { inform("sync error in milliseconds, " "net correction in ppm, " "corrections in ppm, " @@ -1486,103 +1590,105 @@ static void *player_thread_func(void *arg) { // debug(1,"Player has a supplied silent frame."); last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); // manage the packet out of sequence minder - if (inbuf==NULL) - debug(1,"NULL inbuf to play -- skipping it."); + if (inbuf == NULL) + debug(1, "NULL inbuf to play -- skipping it."); else { - if (inbuflength==0) - debug(1,"empty frame to play -- skipping it (1)."); + if (inbuflength == 0) + debug(1, "empty frame to play -- skipping it (1)."); else config.output->play(inbuf, inbuflength); } } else { - -// int enable_dither = 0; - int enable_dither = !((fix_volume==0x10000)&&(input_bit_depth==output_bit_depth)); // avoid dither on audio being sent through without alteration - + + // int enable_dither = 0; + int enable_dither = + !((fix_volume == 0x10000) && + (input_bit_depth == + output_bit_depth)); // avoid dither on audio being sent through without alteration + // here, let's transform the frame of data, if necessary - - if (tbuf!=NULL) { //this will be null if no changes are needed + + if (tbuf != NULL) { // this will be null if no changes are needed switch (input_bit_depth) { - case 16: { - int i,j; - int16_t ls,rs; - int16_t *inps=inbuf; - int16_t *outps=tbuf; - int32_t *outpl=(int32_t*)tbuf; - for (i=0;i INT16_MAX) { - both = INT16_MAX; - } else if (both < INT16_MIN) { - both = INT16_MIN; - } - int16_t sboth = (int16_t)both; - ls = sboth; - rs = sboth; - } break; - case ST_reverse_stereo: { - int16_t t = ls; - ls = rs; - rs = t; - } break; - case ST_left_only: - rs = ls; - break; - case ST_right_only: - ls = rs; - break; - } - - // here, replicate the samples if you're upsampling - - for (j=0;j INT16_MAX) { + both = INT16_MAX; + } else if (both < INT16_MIN) { + both = INT16_MIN; } - + int16_t sboth = (int16_t)both; + ls = sboth; + rs = sboth; + } break; + case ST_reverse_stereo: { + int16_t t = ls; + ls = rs; + rs = t; + } break; + case ST_left_only: + rs = ls; + break; + case ST_right_only: + ls = rs; + break; } - break; - default: - die("Shairport Sync only supports 16 bit input"); + + // here, replicate the samples if you're upsampling + + for (j = 0; j < output_sample_ratio; j++) { + // raise the 16-bit sample to 32 bits. + int32_t t = ls << 16; + *outpl++ = t; + int32_t u = rs << 16; + *outpl++ = u; + + /* + switch (output_bit_depth) { + case 16: + *outps++=ls; + *outps++=rs; + break; + case 24: { + int32_t t = ls<<8; + *outpl++=t; + int32_t u = rs<<8; + *outpl++=u; + } break; + case 32: { + int32_t t = ls<<16; + *outpl++=t; + int32_t u = rs<<16; + *outpl++=u; + } break; + } + */ + } + } + + } break; + default: + die("Shairport Sync only supports 16 bit input"); } - + // inbuf = tbuf; - inbuflength*=output_sample_ratio; + inbuflength *= output_sample_ratio; } - + // We have a frame of data. We need to see if we want to add or remove a frame from it to // keep in sync. // So we calculate the timing error for the first frame in the DAC. @@ -1593,12 +1699,13 @@ static void *player_thread_func(void *arg) { at_least_one_frame_seen = 1; int64_t reference_timestamp; - uint64_t reference_timestamp_time,remote_reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); // types okay - reference_timestamp*=output_sample_ratio; + uint64_t reference_timestamp_time, remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, + &remote_reference_timestamp_time); // types okay + reference_timestamp *= output_sample_ratio; int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t - nt = inframe->timestamp; // uint32_t to int64_t + nt = inframe->timestamp; // uint32_t to int64_t uint64_t local_time_now = get_absolute_time_in_fp(); // types okay // struct timespec tn; @@ -1609,12 +1716,18 @@ static void *player_thread_func(void *arg) { int64_t td = 0; int64_t td_in_frames = 0; if (local_time_now >= reference_timestamp_time) { - // debug(1,"td is %lld.",td); - td = local_time_now - reference_timestamp_time; // this is the positive value. Conversion is positive uint64_t to int64_t, thus okay + // debug(1,"td is %lld.",td); + td = local_time_now - reference_timestamp_time; // this is the positive value. + // Conversion is positive uint64_t to + // int64_t, thus okay td_in_frames = (td * config.output_rate) >> 32; } else { - td = reference_timestamp_time - local_time_now; // this is the absolute value, which should be negated. Conversion is positive uint64_t to int64_t, thus okay. - td_in_frames = (td * config.output_rate) >> 32; //use the absolute td value for the present. Types okay + td = reference_timestamp_time - local_time_now; // this is the absolute value, which + // should be negated. Conversion is + // positive uint64_t to int64_t, thus + // okay. + td_in_frames = (td * config.output_rate) >> + 32; // use the absolute td value for the present. Types okay td_in_frames = -td_in_frames; td = -td; // should be okay, as the range of values should be very small w.r.t 64 bits } @@ -1626,60 +1739,64 @@ static void *player_thread_func(void *arg) { // check sequencing if (last_seqno_read == -1) - last_seqno_read = inframe->sequence_number; //int32_t from seq_t, i.e. uint16_t, so okay. + last_seqno_read = + inframe->sequence_number; // int32_t from seq_t, i.e. uint16_t, so okay. else { - last_seqno_read = SUCCESSOR(last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. - if (inframe->sequence_number != last_seqno_read) { //seq_t, ei.e. uint16_t and int32_t, so okay - debug( - 1, - "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u and ab_write: %u.", - last_seqno_read, inframe->sequence_number,ab_read,ab_write); + last_seqno_read = + SUCCESSOR(last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. + if (inframe->sequence_number != + last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay + debug(1, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " + "and ab_write: %u.", + last_seqno_read, inframe->sequence_number, ab_read, ab_write); last_seqno_read = inframe->sequence_number; // reset warning... } } - buffer_occupancy = seq_diff(ab_read, ab_write); // int32_t from int32 + buffer_occupancy = seq_diff(ab_read, ab_write); // int32_t from int32 - if (buffer_occupancy < minimum_buffer_occupancy) - minimum_buffer_occupancy = buffer_occupancy; + if (buffer_occupancy < minimum_buffer_occupancy) + minimum_buffer_occupancy = buffer_occupancy; - if (buffer_occupancy > maximum_buffer_occupancy) - maximum_buffer_occupancy = buffer_occupancy; + if (buffer_occupancy > maximum_buffer_occupancy) + maximum_buffer_occupancy = buffer_occupancy; // here, we want to check (a) if we are meant to do synchronisation, // (b) if we have a delay procedure, (c) if we can get the delay. // If any of these are false, we don't do any synchronisation stuff - int resp = -1; // use this as a flag -- if negative, we can't rely on a real known delay + int resp = -1; // use this as a flag -- if negative, we can't rely on a real known delay current_delay = -1; // use this as a failure flag - - if (config.output->delay) { - long l_delay; - resp = config.output->delay(&l_delay); - current_delay = l_delay; - if (resp==0) { // no error - if (current_delay<0) { - debug(1,"Underrun of %d frames reported, but ignored.",current_delay); - current_delay=0; // could get a negative value if there was underrun, but ignore it. - } - if (current_delay < minimum_dac_queue_size) { - minimum_dac_queue_size = current_delay; // update for display later - } - } else { - debug(2, "Delay error %d when checking running latency.",resp); - } - } + + if (config.output->delay) { + long l_delay; + resp = config.output->delay(&l_delay); + current_delay = l_delay; + if (resp == 0) { // no error + if (current_delay < 0) { + debug(1, "Underrun of %d frames reported, but ignored.", current_delay); + current_delay = + 0; // could get a negative value if there was underrun, but ignore it. + } + if (current_delay < minimum_dac_queue_size) { + minimum_dac_queue_size = current_delay; // update for display later + } + } else { + debug(2, "Delay error %d when checking running latency.", resp); + } + } if (resp >= 0) { // this is the actual delay, including the latency we actually want, which will // fluctuate a good bit about a potentially rising or falling trend. int64_t delay = td_in_frames + rt - (nt - current_delay); // all int64_t - // This is the timing error for the next audio frame in the DAC. - sync_error = delay - config.latency*output_sample_ratio; // int64_t from int64_t - int32_t, so okay + sync_error = + delay - + config.latency * output_sample_ratio; // int64_t from int64_t - int32_t, so okay // if (llabs(sync_error)>352*512) // debug(1,"Very large sync error: %lld",sync_error); @@ -1687,10 +1804,10 @@ static void *player_thread_func(void *arg) { // before we finally commit to this frame, check its sequencing and timing // require a certain error before bothering to fix it... - if (sync_error > config.tolerance*config.output_rate) { // int64_t > int, okay + if (sync_error > config.tolerance * config.output_rate) { // int64_t > int, okay amount_to_stuff = -1; } - if (sync_error < -config.tolerance*config.output_rate) { + if (sync_error < -config.tolerance * config.output_rate) { amount_to_stuff = 1; } @@ -1701,51 +1818,60 @@ static void *player_thread_func(void *arg) { } // try to keep the corrections definitely below 1 in 1000 audio frames - - // calculate the time elapsed since the play session started. - - if (amount_to_stuff) { - if ((local_time_now) && (first_packet_time_to_play) && (local_time_now >= first_packet_time_to_play)) { - int64_t tp = (local_time_now - first_packet_time_to_play)>>32; // seconds int64_t from uint64_t which is always positive, so ok - - if (tp<5) + // calculate the time elapsed since the play session started. + + if (amount_to_stuff) { + if ((local_time_now) && (first_packet_time_to_play) && + (local_time_now >= first_packet_time_to_play)) { + + int64_t tp = (local_time_now - first_packet_time_to_play) >> + 32; // seconds int64_t from uint64_t which is always positive, so ok + + if (tp < 5) amount_to_stuff = 0; // wait at least five seconds - else if (tp<30) { - if ((random() % 1000) > 352) // keep it to about 1:1000 for the first thirty seconds + else if (tp < 30) { + if ((random() % 1000) > + 352) // keep it to about 1:1000 for the first thirty seconds amount_to_stuff = 0; } } } - - if (config.no_sync!=0) - amount_to_stuff = 0 ; // no stuffing if it's been disabled - + + if (config.no_sync != 0) + amount_to_stuff = 0; // no stuffing if it's been disabled #ifdef HAVE_LIBSOXR switch (config.packet_stuffing) { case ST_basic: // if (amount_to_stuff) debug(1,"Basic stuff..."); if (tbuf) { - play_samples = stuff_buffer_basic_32((int32_t*)tbuf, inbuflength, config.output_format, outbuf, amount_to_stuff, enable_dither); - } - else - play_samples = stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); + play_samples = + stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, + outbuf, amount_to_stuff, enable_dither); + } else + play_samples = + stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); break; case ST_soxr: // if (amount_to_stuff) debug(1,"Soxr stuff..."); if (tbuf) - play_samples = stuff_buffer_soxr_32((int32_t*)tbuf, inbuflength, (int32_t*)outbuf, amount_to_stuff,output_bit_depth); + play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, inbuflength, (int32_t *)outbuf, + amount_to_stuff, output_bit_depth); else - play_samples = stuff_buffer_soxr(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); + play_samples = + stuff_buffer_soxr(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); break; } #else // if (amount_to_stuff) debug(1,"Standard stuff..."); if (tbuf) - play_samples = stuff_buffer_basic_32((int32_t*)tbuf, inbuflength, config.output_format, outbuf, amount_to_stuff, enable_dither); + play_samples = + stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, outbuf, + amount_to_stuff, enable_dither); else - play_samples = stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); + play_samples = + stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); #endif /* @@ -1762,27 +1888,27 @@ static void *player_thread_func(void *arg) { debug(1,"Silence!"); } */ - - - if (outbuf==NULL) - debug(1,"NULL outbuf to play -- skipping it."); + + if (outbuf == NULL) + debug(1, "NULL outbuf to play -- skipping it."); else { - if (play_samples==0) - debug(1,"play_samples==0 skipping it (1)."); + if (play_samples == 0) + debug(1, "play_samples==0 skipping it (1)."); else - config.output->play((short*)outbuf, play_samples); // remove the (short*)! + config.output->play((short *)outbuf, play_samples); // remove the (short*)! } - + // check for loss of sync // timestamp of zero means an inserted silent frame in place of a missing frame - + // not too sure if abs() is implemented for int64_t, so we'll do it manually int64_t abs_sync_error = sync_error; - if (abs_sync_error<0) + if (abs_sync_error < 0) abs_sync_error = -abs_sync_error; - - if ((config.no_sync==0) && (inframe->timestamp != 0) && (!please_stop) && (config.resyncthreshold > 0.0) && - (abs_sync_error > config.resyncthreshold*config.output_rate)) { + + if ((config.no_sync == 0) && (inframe->timestamp != 0) && (!please_stop) && + (config.resyncthreshold > 0.0) && + (abs_sync_error > config.resyncthreshold * config.output_rate)) { sync_error_out_of_bounds++; // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld; // timestamp: %llx, time now @@ -1798,31 +1924,34 @@ static void *player_thread_func(void *arg) { sync_error_out_of_bounds = 0; } } else { - // if there is no delay procedure, or it's not working or not allowed, there can be no synchronising - + // if there is no delay procedure, or it's not working or not allowed, there can be no + // synchronising + if (fix_volume == 0x10000) - - if (inbuf==NULL) - debug(1,"NULL inbuf to play -- skipping it."); + + if (inbuf == NULL) + debug(1, "NULL inbuf to play -- skipping it."); else { - if (inbuflength==0) - debug(1,"empty frame to play -- skipping it (3)."); + if (inbuflength == 0) + debug(1, "empty frame to play -- skipping it (3)."); else config.output->play(inbuf, inbuflength); } else { if (tbuf) - play_samples = stuff_buffer_basic_32((int32_t*)tbuf, inbuflength, config.output_format, outbuf, 0, enable_dither); + play_samples = stuff_buffer_basic_32( + (int32_t *)tbuf, inbuflength, config.output_format, outbuf, 0, enable_dither); else - play_samples = stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, 0); // no stuffing, but volume adjustment + play_samples = stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, + 0); // no stuffing, but volume adjustment - if (outbuf==NULL) - debug(1,"NULL outbuf to play -- skipping it."); + if (outbuf == NULL) + debug(1, "NULL outbuf to play -- skipping it."); else { - if (inbuf==0) - debug(1,"empty frame to play -- skipping it (4)."); + if (inbuf == 0) + debug(1, "empty frame to play -- skipping it (4)."); else - config.output->play((short*)outbuf, play_samples); // remove the (short*)! + config.output->play((short *)outbuf, play_samples); // remove the (short*)! } } } @@ -1835,10 +1964,12 @@ static void *player_thread_func(void *arg) { // new stats calculation. We want a running average of sync error, drift, adjustment, // number of additions+subtractions - - // this is a misleading hack -- the statistics should include some data on the number of valid samples and the number of times sync wasn't checked due to non availability of a delay figure. + + // this is a misleading hack -- the statistics should include some data on the number of + // valid samples and the number of times sync wasn't checked due to non availability of a + // delay figure. // for the present, stats are only updated when sync has been checked - if (sync_error!=-1) { + if (sync_error != -1) { if (number_of_statistics == trend_interval) { // here we remove the oldest statistical data and take it from the summaries as well tsum_of_sync_errors -= statistics[oldest_statistic].sync_error; @@ -1874,10 +2005,9 @@ static void *player_thread_func(void *arg) { tsum_of_corrections += amount_to_stuff; session_corrections += amount_to_stuff; - newest_statistic = (newest_statistic + 1) % trend_interval; number_of_statistics++; - } + } } if (play_number % print_interval == 0) { // we can now calculate running averages for sync error (frames), corrections (ppm), @@ -1890,67 +2020,56 @@ static void *player_thread_func(void *arg) { // if ((play_number/print_interval)%20==0) if (config.statistics_requested) { if (at_least_one_frame_seen) { - if ((config.output->delay)) { - if (config.no_sync==0) { - inform("%*.1f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, 1000*moving_average_sync_error/config.output_rate, - 10, moving_average_correction * 1000000 / (352*output_sample_ratio), - 10, moving_average_insertions_plus_deletions * 1000000 / (352*output_sample_ratio), - 12, play_number, - 7, missing_packets, - 7, late_packets, - 7, too_late_packets, - 7, resend_requests, - 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, + if ((config.output->delay)) { + if (config.no_sync == 0) { + inform("%*.1f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * output_sample_ratio), 10, + moving_average_insertions_plus_deletions * 1000000 / + (352 * output_sample_ratio), + 12, play_number, 7, missing_packets, 7, late_packets, 7, too_late_packets, + 7, resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { - inform("%*.1f," /* Sync error in milliseconds */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, 1000*moving_average_sync_error/config.output_rate, - 12, play_number, - 7, missing_packets, - 7, late_packets, - 7, too_late_packets, - 7, resend_requests, - 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, - 5, maximum_buffer_occupancy); - } + inform("%*.1f," /* Sync error in milliseconds */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + missing_packets, 7, late_packets, 7, too_late_packets, 7, resend_requests, + 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy); + } } else { - inform("%*.1f," /* Sync error in milliseconds */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, 1000*moving_average_sync_error/config.output_rate, - 12, play_number, - 7, missing_packets, - 7, late_packets, - 7, too_late_packets, - 7, resend_requests, - 5, minimum_buffer_occupancy, - 5, maximum_buffer_occupancy); + inform("%*.1f," /* Sync error in milliseconds */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + missing_packets, 7, late_packets, 7, too_late_packets, 7, resend_requests, 5, + minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } } else { inform("No frames received in the last sampling interval."); @@ -1966,32 +2085,34 @@ static void *player_thread_func(void *arg) { } if (config.statistics_requested) { - int rawSeconds = (int) difftime( time( NULL ), playstart ); - int elapsedHours = rawSeconds / 3600; - int elapsedMin = (rawSeconds / 60) % 60; - int elapsedSec = rawSeconds % 60; - inform( "Playback Stopped. Total playing time %02d:%02d:%02d\n", elapsedHours, elapsedMin, elapsedSec ); + int rawSeconds = (int)difftime(time(NULL), playstart); + int elapsedHours = rawSeconds / 3600; + int elapsedMin = (rawSeconds / 60) % 60; + int elapsedSec = rawSeconds % 60; + inform("Playback Stopped. Total playing time %02d:%02d:%02d\n", elapsedHours, elapsedMin, + elapsedSec); } if (config.output->stop) - config.output->stop(); - usleep(100000); // allow this time to (?) allow the alsa subsystem to finish cleaning up after itself. 50 ms seems too short + config.output->stop(); + usleep(100000); // allow this time to (?) allow the alsa subsystem to finish cleaning up after + // itself. 50 ms seems too short free(outbuf); free(silence); if (tbuf) free(tbuf); - debug(1,"Shut down audio, control and timing threads"); + debug(1, "Shut down audio, control and timing threads"); itr.please_stop = 1; pthread_kill(rtp_audio_thread, SIGUSR1); pthread_kill(rtp_control_thread, SIGUSR1); pthread_kill(rtp_timing_thread, SIGUSR1); pthread_join(rtp_timing_thread, NULL); - debug(1,"timing thread joined"); + debug(1, "timing thread joined"); pthread_join(rtp_audio_thread, NULL); - debug(1,"audio thread joined"); + debug(1, "audio thread joined"); pthread_join(rtp_control_thread, NULL); - debug(1,"control thread joined"); - debug(1,"Player thread exit"); + debug(1, "control thread joined"); + debug(1, "Player thread exit"); return 0; } @@ -2003,72 +2124,81 @@ void player_volume(double airplay_volume) { // By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30 // equal increments. Since the human ear's response is roughly logarithmic, we imagine these to // be power dB, i.e. from -30dB to 0dB. - + // We may have a hardware mixer, and if so, we will give it priority. // If a desired volume range is given, then we will try to accommodate it from // the top of the hardware mixer's range downwards. - - // If no desired volume range is given, we will use the native resolution of the hardware mixer, if any, - // or failing that, the software mixer. The software mixer has a range of from -96.3 dB up to 0 dB, + + // If no desired volume range is given, we will use the native resolution of the hardware mixer, + // if any, + // or failing that, the software mixer. The software mixer has a range of from -96.3 dB up to 0 + // dB, // corresponding to a multiplier of 1 to 65535. - - // Otherwise, we will accommodate the desired volume range in the combination of the software and hardware mixer + + // Otherwise, we will accommodate the desired volume range in the combination of the software and + // hardware mixer // Intuitively (!), it seems best to give the hardware mixer as big a role as possible, so // we will use its full range and then accommodate the rest of the attenuation in software. // A problem is that we don't know whether the lowest hardware volume actually mutes the output - // so we must assume that it does, and for this reason, the volume control goes at the "bottom" of the adjustment range - + // so we must assume that it does, and for this reason, the volume control goes at the "bottom" of + // the adjustment range + // The dB range of a value from 1 to 65536 is about 96.3 dB (log10 of 65536 is 4.8164). // Since the levels correspond with amplitude, they correspond to voltage, hence voltage dB, // or 20 times the log of the ratio. Then multiplied by 100 for convenience. // Thus, we ask our vol2attn function for an appropriate dB between -96.3 and 0 dB and translate // it back to a number. - - int32_t hw_min_db, hw_max_db, hw_range_db, range_to_use, min_db, max_db; // hw_range_db is a flag; if 0 means no mixer - + + int32_t hw_min_db, hw_max_db, hw_range_db, range_to_use, min_db, + max_db; // hw_range_db is a flag; if 0 means no mixer + int32_t sw_min_db = -9630; int32_t sw_max_db = 0; int32_t sw_range_db = sw_max_db - sw_min_db; int32_t desired_range_db; // this is used as a flag; if 0 means no desired range - + if (config.volume_range_db) - desired_range_db = (int32_t)trunc(config.volume_range_db*100); + desired_range_db = (int32_t)trunc(config.volume_range_db * 100); else desired_range_db = 0; - + if (config.output->parameters) { // have a hardware mixer config.output->parameters(&audio_information); hw_max_db = audio_information.maximum_volume_dB; hw_min_db = audio_information.minimum_volume_dB; - hw_range_db = hw_max_db-hw_min_db; + hw_range_db = hw_max_db - hw_min_db; } else { // don't have a hardware mixer hw_max_db = hw_min_db = hw_range_db = 0; } - + if (desired_range_db) { // debug(1,"An attenuation range of %d is requested.",desired_range_db); // we have a desired volume range. if (hw_range_db) { - // we have a hardware mixer - if (hw_range_db>=desired_range_db) { + // we have a hardware mixer + if (hw_range_db >= desired_range_db) { // the hardware mixer can accommodate the desired range max_db = hw_max_db; min_db = max_db - desired_range_db; } else { - if ((hw_range_db+sw_range_db)mute) - config.output->mute(1); // use real mute if it's there - + hardware_attenuation = hw_min_db; + software_attenuation = sw_min_db; + if (config.output->mute) + config.output->mute(1); // use real mute if it's there + } else { - if (config.output->mute) - config.output->mute(0); // unmute mute if it's there + if (config.output->mute) + config.output->mute(0); // unmute mute if it's there scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); if (hw_range_db) { // if there is a hardware mixer - if (scaled_attenuation<=hw_max_db) { + if (scaled_attenuation <= hw_max_db) { // the attenuation is so low that's it's in the hardware mixer's range // debug(1,"Attenuation all taken care of by the hardware mixer."); hardware_attenuation = scaled_attenuation; - software_attenuation = sw_max_db - (max_db-hw_max_db); // e.g. if the hw_max_db is +4 and the max is +40, this will be -36 (all by 100, of course) + software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and + // the max is +40, this will be -36 + // (all by 100, of course) } else { // debug(1,"Attenuation taken care of by hardware and software mixer."); hardware_attenuation = hw_max_db; // the hardware mixer is turned up full - software_attenuation = sw_max_db - (max_db-scaled_attenuation); + software_attenuation = sw_max_db - (max_db - scaled_attenuation); } } else { // if there is no hardware mixer, the scaled_volume is the software volume @@ -2119,14 +2251,16 @@ void player_volume(double airplay_volume) { software_attenuation = scaled_attenuation; } } - + if ((config.output->volume) && (hw_range_db)) { - config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value - //debug(1,"Hardware attenuation set to %f for airplay volume of %f.",hardware_attenuation,airplay_volume); + config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value + // debug(1,"Hardware attenuation set to %f for airplay volume of + // %f.",hardware_attenuation,airplay_volume); } double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); - // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of %f",software_attenuation,temp_fix_volume,airplay_volume); - + // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of + // %f",software_attenuation,temp_fix_volume,airplay_volume); + pthread_mutex_lock(&vol_mutex); fix_volume = temp_fix_volume; pthread_mutex_unlock(&vol_mutex); @@ -2135,17 +2269,15 @@ void player_volume(double airplay_volume) { char *dv = malloc(128); // will be freed in the metadata thread if (dv) { memset(dv, 0, 128); - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, - scaled_attenuation / 100.0, - min_db / 100.0, - max_db / 100.0); + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0, + min_db / 100.0, max_db / 100.0); send_ssnc_metadata('pvol', dv, strlen(dv), 1); } #endif } void player_flush(int64_t timestamp) { - debug(3,"Flush requested up to %u. It seems as if 0 is special.",timestamp); + debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp); pthread_mutex_lock(&flush_mutex); flush_requested = 1; // if (timestamp!=0) @@ -2158,8 +2290,8 @@ void player_flush(int64_t timestamp) { } int player_play(stream_cfg *stream, pthread_t *player_thread) { - //if (*player_thread!=NULL) - // die("Trying to create a second player thread for this RTSP session"); + // if (*player_thread!=NULL) + // die("Trying to create a second player thread for this RTSP session"); packet_count = 0; encrypted = stream->encrypted; if (config.buffer_start_fill > BUFFER_FRAMES) @@ -2197,7 +2329,7 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { #endif if (rc) debug(1, "Error initialising condition variable."); - config.output->start(config.output_rate,config.output_format); + config.output->start(config.output_rate, config.output_format); size_t size = (PTHREAD_STACK_MIN + 256 * 1024); pthread_attr_t tattr; pthread_attr_init(&tattr); @@ -2210,20 +2342,20 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { } void player_stop(pthread_t *player_thread) { - //if (*thread==NULL) - // debug(1,"Trying to stop a non-existent player thread"); - // else { - please_stop = 1; - pthread_cond_signal(&flowcontrol); // tell it to give up - pthread_join(*player_thread, NULL); - #ifdef CONFIG_METADATA - send_ssnc_metadata('pend', NULL, 0, 1); - #endif - command_stop(); - free_buffer(); - terminate_decoders(); - int rc = pthread_cond_destroy(&flowcontrol); - if (rc) - debug(1, "Error destroying condition variable."); - // } + // if (*thread==NULL) + // debug(1,"Trying to stop a non-existent player thread"); + // else { + please_stop = 1; + pthread_cond_signal(&flowcontrol); // tell it to give up + pthread_join(*player_thread, NULL); +#ifdef CONFIG_METADATA + send_ssnc_metadata('pend', NULL, 0, 1); +#endif + command_stop(); + free_buffer(); + terminate_decoders(); + int rc = pthread_cond_destroy(&flowcontrol); + if (rc) + debug(1, "Error destroying condition variable."); + // } } diff --git a/player.h b/player.h index bd14acd2..eba97e8e 100644 --- a/player.h +++ b/player.h @@ -22,10 +22,14 @@ void player_flush(int64_t timestamp); void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len); -int64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 2^33 frames and continue up to 2^64 frames +int64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic + // timestamp guaranteed to start between 2^32 2^33 + // frames and continue up to 2^64 frames // which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds. -// assumes, without checking, that successive timestamps in a series always span an interval of less than one minute. +// assumes, without checking, that successive timestamps in a series always span an interval of less +// than one minute. -uint64_t monotonic_seqno(uint16_t seq_no); // add an epoch to the seq_no. Uses the accompanying timstamp to detemine the correct epoch +uint64_t monotonic_seqno(uint16_t seq_no); // add an epoch to the seq_no. Uses the accompanying + // timstamp to detemine the correct epoch #endif //_PLAYER_H diff --git a/rtp.c b/rtp.c index cbef773d..aad447fb 100644 --- a/rtp.c +++ b/rtp.c @@ -24,19 +24,19 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include #include "common.h" #include "player.h" @@ -59,17 +59,18 @@ typedef struct time_ping_record { static int running = 0; static char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client -static char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it could be one of many, so we need to know it -static uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope -static short connection_ip_family; // AF_INET / AF_INET6 -static uint32_t client_active_remote; // used when you want to control the client... +static char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it + // could be one of many, so we need to know it +static uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope +static short connection_ip_family; // AF_INET / AF_INET6 +static uint32_t client_active_remote; // used when you want to control the client... static SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client static SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client static int audio_socket; // our local [server] audio socket static int control_socket; // our local [server] control socket static int timing_socket; // local timing socket -//static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; +// static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; static int64_t reference_timestamp; static uint64_t reference_timestamp_time; @@ -104,29 +105,33 @@ void *rtp_audio_receiver(void *arg) { uint64_t time_of_previous_packet_fp = 0; float longest_packet_time_interval_us = 0.0; - - // mean and variance calculations from "online_variance" algorithm at https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm - + + // mean and variance calculations from "online_variance" algorithm at + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm + int32_t stat_n = 0; float stat_mean = 0.0; float stat_M2 = 0.0; - + ssize_t nread; - while (itr->please_stop==0) { + while (itr->please_stop == 0) { nread = recv(audio_socket, packet, sizeof(packet), 0); - + uint64_t local_time_now_fp = get_absolute_time_in_fp(); if (time_of_previous_packet_fp) { - float time_interval_us = (((local_time_now_fp - time_of_previous_packet_fp)*1000000)>>32)*1.0; + float time_interval_us = + (((local_time_now_fp - time_of_previous_packet_fp) * 1000000) >> 32) * 1.0; time_of_previous_packet_fp = local_time_now_fp; - if (time_interval_us>longest_packet_time_interval_us) - longest_packet_time_interval_us=time_interval_us; - stat_n+=1; + if (time_interval_us > longest_packet_time_interval_us) + longest_packet_time_interval_us = time_interval_us; + stat_n += 1; float stat_delta = time_interval_us - stat_mean; - stat_mean += stat_delta/stat_n; - stat_M2 += stat_delta*(time_interval_us - stat_mean); + stat_mean += stat_delta / stat_n; + stat_M2 += stat_delta * (time_interval_us - stat_mean); if (stat_n % 2500 == 0) { - debug(2,"Packet reception interval stats: mean, standard deviation and max for the last 2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",stat_mean, sqrtf(stat_M2 / (stat_n-1)),longest_packet_time_interval_us); + debug(2, "Packet reception interval stats: mean, standard deviation and max for the last " + "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.", + stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us); stat_n = 0; stat_mean = 0.0; stat_M2 = 0.0; @@ -155,7 +160,7 @@ void *rtp_audio_receiver(void *arg) { last_seqno = seqno; else { last_seqno = (last_seqno + 1) & 0xffff; - //if (seqno != last_seqno) + // if (seqno != last_seqno) // debug(3, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno); last_seqno = seqno; // reset warning... } @@ -200,7 +205,7 @@ void *rtp_control_receiver(void *arg) { uint64_t remote_time_of_sync, local_time_now, remote_time_now; int64_t sync_rtp_timestamp, rtp_timestamp_less_latency; ssize_t nread; - while (itr->please_stop==0) { + while (itr->please_stop == 0) { nread = recv(control_socket, packet, sizeof(packet), 0); local_time_now = get_absolute_time_in_fp(); // clock_gettime(CLOCK_MONOTONIC,&tn); @@ -231,15 +236,15 @@ void *rtp_control_receiver(void *arg) { rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4]))); sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16]))); - + if (config.use_negotiated_latencies) { - int64_t la = sync_rtp_timestamp-rtp_timestamp_less_latency+11025; - if (la!=config.latency) { + int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency + 11025; + if (la != config.latency) { config.latency = la; // debug(1,"Using negotiated latency of %u frames.",config.latency); } } - + if (packet[0] & 0x10) { // if it's a packet right after a flush or resume sync_rtp_timestamp += 352; // add frame_size -- can't see a reference to this anywhere, @@ -265,7 +270,7 @@ void *rtp_control_receiver(void *arg) { } } else if (packet[1] == 0xd6) { // resent audio data in the control path -- whaale only? // debug(1, "Control Port -- Retransmitted Audio Data Packet received."); - pktp = packet+4; + pktp = packet + 4; plen -= 4; seq_t seqno = ntohs(*(unsigned short *)(pktp + 2)); @@ -293,7 +298,7 @@ void *rtp_control_receiver(void *arg) { void *rtp_timing_sender(void *arg) { debug(2, "Timing sender thread starting."); - int *stop = arg; // the parameter points to this request to stop thing + int *stop = arg; // the parameter points to this request to stop thing struct timing_request { char leader; char type; @@ -314,8 +319,8 @@ void *rtp_timing_sender(void *arg) { time_ping_count = 0; // we inherit the signal mask (SIGUSR1) - while (*stop==0) { - // debug(1,"Send a timing request"); + while (*stop == 0) { + // debug(1,"Send a timing request"); if (!running) die("rtp_timing_sender called without active stream!"); @@ -351,7 +356,7 @@ void *rtp_timing_receiver(void *arg) { debug(2, "Timing receiver -- Server RTP thread starting."); // we inherit the signal mask (SIGUSR1) - struct inter_threads_record *itr = arg; + struct inter_threads_record *itr = arg; uint8_t packet[2048], *pktp; ssize_t nread; @@ -365,11 +370,11 @@ void *rtp_timing_receiver(void *arg) { local_to_remote_time_jitters_count = 0; uint64_t first_remote_time = 0; uint64_t first_local_time = 0; - + uint64_t first_local_to_remote_time_difference = 0; uint64_t first_local_to_remote_time_difference_time; uint64_t l2rtd = 0; - while (itr->please_stop==0) { + while (itr->please_stop == 0) { nread = recv(timing_socket, packet, sizeof(packet), 0); arrival_time = get_absolute_time_in_fp(); // clock_gettime(CLOCK_MONOTONIC,&att); @@ -428,13 +433,14 @@ void *rtp_timing_receiver(void *arg) { // these are for diagnostics only -- not used time_pings[0].local_time = arrival_time; time_pings[0].remote_time = distant_transmit_time; - + time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time; time_pings[0].dispersion = return_time; if (time_ping_count < time_ping_history) time_ping_count++; - - uint64_t local_time_chosen = arrival_time;; + + uint64_t local_time_chosen = arrival_time; + ; uint64_t remote_time_chosen = distant_transmit_time; // now pick the timestamp with the lowest dispersion uint64_t l2rtd = time_pings[0].local_to_remote_difference; @@ -465,69 +471,81 @@ void *rtp_timing_receiver(void *arg) { // with dispersion of %lld us with delta of %lld us",rtus,ji); local_to_remote_time_difference = l2rtd; - if (first_local_to_remote_time_difference==0) { + if (first_local_to_remote_time_difference == 0) { first_local_to_remote_time_difference = local_to_remote_time_difference; first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); } - + int64_t clock_drift, clock_drift_in_usec; - if (first_local_time==0) { + if (first_local_time == 0) { first_local_time = local_time_chosen; first_remote_time = remote_time_chosen; uint64_t clock_drift = 0; } else { uint64_t local_time_change = local_time_chosen - first_local_time; uint64_t remote_time_change = remote_time_chosen - first_remote_time; - if (remote_time_change >= local_time_change) clock_drift = remote_time_change - local_time_change; else clock_drift = -(local_time_change - remote_time_change); } - if (clock_drift>=0) - clock_drift_in_usec = (clock_drift * 1000000)>>32; + if (clock_drift >= 0) + clock_drift_in_usec = (clock_drift * 1000000) >> 32; else - clock_drift_in_usec = -(((-clock_drift) * 1000000)>>32); - - - - int64_t source_drift_usec; - if (play_segment_reference_frame!=0) { - int64_t reference_timestamp; - uint64_t reference_timestamp_time,remote_reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); - uint64_t frame_difference = 0; - if (reference_timestamp>=play_segment_reference_frame) - frame_difference = (uint64_t)reference_timestamp-(uint64_t)play_segment_reference_frame; - else // rollover - frame_difference = (uint64_t)reference_timestamp+0x100000000-(uint64_t)play_segment_reference_frame; - uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference<<32)/44100); - uint64_t frame_time_difference_actual = remote_reference_timestamp_time-play_segment_reference_frame_remote_time; // this is all done by reference to the sources' system clock - // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, (frame_time_difference_actual*1000000)>>32); - if (frame_time_difference_calculated>=frame_time_difference_actual) // i.e. if the time it should have taken to send the packets is greater than the actual time difference measured on the source clock - // then the source DAC's clock is running fast relative to the source system clock - source_drift_usec = frame_time_difference_calculated-frame_time_difference_actual; - else - // otherwise the source DAC's clock is running slow relative to the source system clock - source_drift_usec = -(frame_time_difference_actual-frame_time_difference_calculated); - } else - source_drift_usec = 0; - source_drift_usec = (source_drift_usec*1000000)>>32; // turn it to microseconds - - //long current_delay = 0; - //if (config.output->delay) { - // config.output->delay(¤t_delay); - //} - // Useful for troubleshooting: - // clock_drift between source and local clock -- +ve means source is faster - // session_corrections -- the amount of correction done, in microseconds. +ve means frames added - // current_delay = delay in DAC buffer in frames - // source_drift_usec = how much faster (+ve) or slower the source DAC is running relative to the source clock - // buffer_occupancy = the number of buffers occupied. Crude, but should show no long term trend if source and device are in sync. - // return_time = the time from soliciting a timing packet to getting it back. It should be short ( < 5 ms) and pretty consistent. - // debug(1, "%lld\t%lld\t%ld\t%lld\t%u\t%llu", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32); - + clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32); + + int64_t source_drift_usec; + if (play_segment_reference_frame != 0) { + int64_t reference_timestamp; + uint64_t reference_timestamp_time, remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, + &remote_reference_timestamp_time); + uint64_t frame_difference = 0; + if (reference_timestamp >= play_segment_reference_frame) + frame_difference = (uint64_t)reference_timestamp - (uint64_t)play_segment_reference_frame; + else // rollover + frame_difference = + (uint64_t)reference_timestamp + 0x100000000 - (uint64_t)play_segment_reference_frame; + uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference << 32) / 44100); + uint64_t frame_time_difference_actual = + remote_reference_timestamp_time - + play_segment_reference_frame_remote_time; // this is all done by reference to the + // sources' system clock + // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec + // actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, + // (frame_time_difference_actual*1000000)>>32); + if (frame_time_difference_calculated >= + frame_time_difference_actual) // i.e. if the time it should have taken to send the + // packets is greater than the actual time difference + // measured on the source clock + // then the source DAC's clock is running fast relative to the source system clock + source_drift_usec = frame_time_difference_calculated - frame_time_difference_actual; + else + // otherwise the source DAC's clock is running slow relative to the source system clock + source_drift_usec = -(frame_time_difference_actual - frame_time_difference_calculated); + } else + source_drift_usec = 0; + source_drift_usec = (source_drift_usec * 1000000) >> 32; // turn it to microseconds + + // long current_delay = 0; + // if (config.output->delay) { + // config.output->delay(¤t_delay); + //} + // Useful for troubleshooting: + // clock_drift between source and local clock -- +ve means source is faster + // session_corrections -- the amount of correction done, in microseconds. +ve means frames + // added + // current_delay = delay in DAC buffer in frames + // source_drift_usec = how much faster (+ve) or slower the source DAC is running relative + // to the source clock + // buffer_occupancy = the number of buffers occupied. Crude, but should show no long term + // trend if source and device are in sync. + // return_time = the time from soliciting a timing packet to getting it back. It should be + // short ( < 5 ms) and pretty consistent. + // debug(1, "%lld\t%lld\t%ld\t%lld\t%u\t%llu", + // clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32); + } else { debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); } @@ -545,44 +563,45 @@ void *rtp_timing_receiver(void *arg) { return NULL; } -static int bind_port(int ip_family,const char *self_ip_address,uint32_t scope_id,int *sock) { +static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id, int *sock) { // look for a port in the range, if any was specified. int desired_port = config.udp_port_base; int ret; - + int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP); - if (local_socket== -1) + if (local_socket == -1) die("Could not allocate a socket."); SOCKADDR myaddr; do { - memset(&myaddr,0,sizeof(myaddr)); - if (ip_family==AF_INET) { + memset(&myaddr, 0, sizeof(myaddr)); + if (ip_family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr; sa->sin_family = AF_INET; sa->sin_port = ntohs(desired_port); - inet_pton(AF_INET,self_ip_address,&(sa->sin_addr)); - ret = bind(local_socket,(struct sockaddr*)sa, sizeof(struct sockaddr_in)); + inet_pton(AF_INET, self_ip_address, &(sa->sin_addr)); + ret = bind(local_socket, (struct sockaddr *)sa, sizeof(struct sockaddr_in)); } #ifdef AF_INET6 - if (ip_family==AF_INET6) { + if (ip_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&myaddr; sa6->sin6_family = AF_INET6; sa6->sin6_port = ntohs(desired_port); - inet_pton(AF_INET6,self_ip_address,&(sa6->sin6_addr)); - sa6->sin6_scope_id=scope_id; - ret = bind(local_socket,(struct sockaddr*)sa6, sizeof(struct sockaddr_in6)); + inet_pton(AF_INET6, self_ip_address, &(sa6->sin6_addr)); + sa6->sin6_scope_id = scope_id; + ret = bind(local_socket, (struct sockaddr *)sa6, sizeof(struct sockaddr_in6)); } -#endif - - } while ((ret<0) && (errno==EADDRINUSE) && (desired_port!=0) && (desired_port++ < config.udp_port_base+config.udp_port_range)); - +#endif + + } while ((ret < 0) && (errno == EADDRINUSE) && (desired_port != 0) && + (desired_port++ < config.udp_port_base + config.udp_port_range)); + // debug(1,"UDP port chosen: %d.",desired_port); - + if (ret < 0) { - close(local_socket); - die("error: could not bind a UDP port!"); + close(local_socket); + die("error: could not bind a UDP port!"); } - + int sport; SOCKADDR local; socklen_t local_len = sizeof(local); @@ -597,18 +616,18 @@ static int bind_port(int ip_family,const char *self_ip_address,uint32_t scope_id struct sockaddr_in *sa = (struct sockaddr_in *)&local; sport = ntohs(sa->sin_port); } - + *sock = local_socket; return sport; } -void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t active_remote, int *lsport, - int *lcport, int *ltport) { - +void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t active_remote, + int *lsport, int *lcport, int *ltport) { + // this gets the local and remote ip numbers (and ports used for the TCD stuff) // we use the local stuff to specify the address we are coming from and // we use the remote stuff to specify where we're goint to - + if (running) die("rtp_setup called with active stream!"); @@ -617,13 +636,13 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t client_active_remote = active_remote; // print out what we know about the client - void *client_addr,*self_addr; - int client_port,self_port; + void *client_addr, *self_addr; + int client_port, self_port; char client_port_str[64]; char self_addr_str[64]; - + connection_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client - + #ifdef AF_INET6 if (connection_ip_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote; @@ -644,13 +663,10 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t self_port = ntohs(sa4->sin_port); } - inet_ntop(connection_ip_family, client_addr, client_ip_string, - sizeof(client_ip_string)); - inet_ntop(connection_ip_family, self_addr, self_ip_string, - sizeof(self_ip_string)); - - debug(1, "Set up play connection from %s to self at %s.", client_ip_string,self_ip_string); + inet_ntop(connection_ip_family, client_addr, client_ip_string, sizeof(client_ip_string)); + inet_ntop(connection_ip_family, self_addr, self_ip_string, sizeof(self_ip_string)); + debug(1, "Set up play connection from %s to self at %s.", client_ip_string, self_ip_string); // set up a the record of the remote's control socket struct addrinfo hints; @@ -692,23 +708,24 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t // now, we open three sockets -- one for the audio stream, one for the timing and one for the // control - *lsport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&audio_socket); - *lcport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&control_socket); - *ltport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&timing_socket); + *lsport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &audio_socket); + *lcport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &control_socket); + *ltport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &timing_socket); debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, *ltport); reference_timestamp = 0; - //pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL); - //pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL); - //pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL); + // pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL); + // pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL); + // pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL); running = 1; request_sent = 0; } -void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time) { +void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, + uint64_t *remote_timestamp_time) { // types okay pthread_mutex_lock(&reference_time_mutex); *timestamp = reference_timestamp; @@ -726,25 +743,25 @@ void clear_reference_timestamp(void) { void rtp_shutdown(void) { if (!running) - debug(1,"rtp_shutdown called without active stream!"); + debug(1, "rtp_shutdown called without active stream!"); debug(2, "shutting down RTP thread"); clear_reference_timestamp(); -// debug(1,"Shut down audio, control and timing threads"); -// usleep(3000000); // hack -// pthread_kill(rtp_audio_thread, SIGUSR1); -// pthread_kill(rtp_control_thread, SIGUSR1); -// pthread_kill(rtp_timing_thread, SIGUSR1); -// pthread_join(rtp_audio_thread, &retval); -// pthread_join(rtp_control_thread, &retval); -// pthread_join(rtp_timing_thread, &retval); + // debug(1,"Shut down audio, control and timing threads"); + // usleep(3000000); // hack + // pthread_kill(rtp_audio_thread, SIGUSR1); + // pthread_kill(rtp_control_thread, SIGUSR1); + // pthread_kill(rtp_timing_thread, SIGUSR1); + // pthread_join(rtp_audio_thread, &retval); + // pthread_join(rtp_control_thread, &retval); + // pthread_join(rtp_timing_thread, &retval); running = 0; } void rtp_request_resend(seq_t first, uint32_t count) { if (running) { - //if (!request_sent) { - debug(3, "requesting resend of %d packets starting at %u.", count, first); + // if (!request_sent) { + debug(3, "requesting resend of %d packets starting at %u.", count, first); // request_sent = 1; //} @@ -765,8 +782,8 @@ void rtp_request_resend(seq_t first, uint32_t count) { perror("Error sendto-ing to audio socket"); } } else { - //if (!request_sent) { - debug(2, "rtp_request_resend called without active stream!"); + // if (!request_sent) { + debug(2, "rtp_request_resend called without active stream!"); // request_sent = 1; //} } diff --git a/rtp.h b/rtp.h index 195f6738..f4c8ab0b 100644 --- a/rtp.h +++ b/rtp.h @@ -5,21 +5,21 @@ #include "player.h" -typedef struct inter_threads_record { - uint32_t please_stop; -} inter_threads_record; +typedef struct inter_threads_record { uint32_t please_stop; } inter_threads_record; void *rtp_audio_receiver(void *arg); void *rtp_control_receiver(void *arg); void *rtp_timing_receiver(void *arg); -void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int controlport, int timingport, uint32_t active_remote, - int *local_server_port, int *local_control_port, int *local_timing_port); +void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int controlport, int timingport, + uint32_t active_remote, int *local_server_port, int *local_control_port, + int *local_timing_port); void rtp_shutdown(void); void rtp_request_resend(seq_t first, uint32_t count); void rtp_request_client_pause(void); // ask the client to pause -void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time); +void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, + uint64_t *remote_timestamp_time); void clear_reference_timestamp(void); uint64_t static local_to_remote_time_jitters; diff --git a/rtsp.c b/rtsp.c index 3f6d9646..e8dfe35b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -26,23 +26,23 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include #include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include "config.h" @@ -55,9 +55,9 @@ #endif #include "common.h" +#include "mdns.h" #include "player.h" #include "rtp.h" -#include "mdns.h" #ifdef AF_INET6 #define INETx_ADDRSTRLEN INET6_ADDRSTRLEN @@ -82,7 +82,7 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; // only one thread is allowed to use the player at once. // it monitors the request variable (at least when interrupted) -//static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; +// static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; // static int please_shutdown = 0; // static pthread_t playing_thread = 0; @@ -90,14 +90,15 @@ typedef struct { int fd; int authorized; // set is a password is required and has been supplied stream_cfg stream; - SOCKADDR remote,local; + SOCKADDR remote, local; int stop; int running; pthread_t thread; pthread_t player_thread; } rtsp_conn_info; -static rtsp_conn_info *playing_conn = NULL; // the data structure representing the connection that has the player. +static rtsp_conn_info *playing_conn = + NULL; // the data structure representing the connection that has the player. static rtsp_conn_info **conns = NULL; void memory_barrier() { @@ -144,8 +145,7 @@ typedef struct { rtsp_message *carrier; } metadata_package; -void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, - uint32_t number_of_items) { +void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items) { pthread_mutex_init(&the_queue->pc_queue_lock, NULL); pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL); pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL); @@ -157,8 +157,8 @@ void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, the_queue->eoq = 0; } -int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, - rtsp_message *carrier, int block); +int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, + int block); int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) { return send_metadata('ssnc', code, data, length, NULL, block); @@ -176,8 +176,7 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { if (rc) debug(1, "Error locking for pc_queue_add_item"); while (the_queue->count == the_queue->capacity) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, - &the_queue->pc_queue_lock); + rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed"); } @@ -214,8 +213,7 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { if (rc) debug(1, "Error locking for pc_queue_get_item"); while (the_queue->count == 0) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, - &the_queue->pc_queue_lock); + rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be added"); } @@ -263,11 +261,10 @@ static inline int rtsp_playing(void) { void rtsp_request_shutdown_stream(void) { debug(1, "Request to shut down all rtsp conversation threads"); - ask_other_rtsp_conversation_threads_to_stop( - 0); // i.e. ask all playing threads to stop + ask_other_rtsp_conversation_threads_to_stop(0); // i.e. ask all playing threads to stop } -//static void rtsp_take_player(void) { +// static void rtsp_take_player(void) { // if (rtsp_playing()) // return; @@ -314,8 +311,7 @@ void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread) { int i; debug(2, "asking playing threads to stop"); for (i = 0; i < nconns; i++) { - if (((except_this_thread == 0) || - (pthread_equal(conns[i]->thread, except_this_thread) == 0)) && + if (((except_this_thread == 0) || (pthread_equal(conns[i]->thread, except_this_thread) == 0)) && (conns[i]->running != 0)) { conns[i]->stop = 1; pthread_kill(conns[i]->thread, SIGUSR1); @@ -363,8 +359,7 @@ static void msg_retain(rtsp_message *msg) { static rtsp_message *msg_init(void) { rtsp_message *msg = malloc(sizeof(rtsp_message)); memset(msg, 0, sizeof(rtsp_message)); - msg->referenceCount = - 1; // from now on, any access to this must be protected with the lock + msg->referenceCount = 1; // from now on, any access to this must be protected with the lock return msg; } @@ -405,8 +400,7 @@ static void msg_free(rtsp_message *msg) { msg->referenceCount--; rc = pthread_mutex_unlock(&reference_counter_lock); if (rc) - debug(1, "Error %d unlocking reference counter lock during msg_free()", - rc); + debug(1, "Error %d unlocking reference counter lock during msg_free()", rc); if (msg->referenceCount == 0) { int i; for (i = 0; i < msg->nheaders; i++) { @@ -479,8 +473,8 @@ fail: return 0; } -static enum rtsp_read_request_response -rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { +static enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, + rtsp_message **the_packet) { enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 512; char *buf = malloc(buflen + 1); @@ -499,8 +493,8 @@ rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { goto shutdown; } nread = read(conn->fd, buf + inbuf, buflen - inbuf); - - if (nread==0) { + + if (nread == 0) { // a blocking read that returns zero means eof -- implies connection closed debug(3, "RTSP connection closed."); reply = rtsp_read_request_response_shutdown_requested; @@ -542,8 +536,8 @@ rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { buflen = msg_size; } - uint64_t threshold_time = get_absolute_time_in_fp() + - ((uint64_t)5 << 32); // i.e. five seconds from now + uint64_t threshold_time = + get_absolute_time_in_fp() + ((uint64_t)5 << 32); // i.e. five seconds from now int warning_message_sent = 0; const size_t max_read_chunk = 50000; @@ -628,8 +622,7 @@ static void msg_write_response(int fd, rtsp_message *resp) { int ignore = write(fd, pkt, p - pkt + 2); } -static void handle_record(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // debug(1,"Handle Record"); resp->respcode = 200; // I think this is for telling the client what the absolute minimum latency @@ -663,16 +656,14 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req, } } -static void handle_options(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_options(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { resp->respcode = 200; msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " "PAUSE, FLUSH, TEARDOWN, " "OPTIONS, GET_PARAMETER, SET_PARAMETER"); } -static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { if (!rtsp_playing()) debug(1, "This RTSP conversation thread doesn't think it's playing, but " "it's sending a response to teardown anyway"); @@ -681,8 +672,7 @@ static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, conn->stop = 1; } -static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { if (!rtsp_playing()) debug(1, "This RTSP conversation thread doesn't think it's playing, but " "it's sending a response to flush anyway"); @@ -705,8 +695,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, resp->respcode = 200; } -static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // debug(1,"Handle Setup"); int cport, tport; int lsport, lcport, ltport; @@ -768,10 +757,8 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, config.latency = config.iTunesLatency; } } else if (strstr(ua, "AirPlay") == ua) { - debug( - 2, - "User-Agent is AirPlay; selecting the AirPlay latency of %d frames.", - config.AirPlayLatency); + debug(2, "User-Agent is AirPlay; selecting the AirPlay latency of %d frames.", + config.AirPlayLatency); config.latency = config.AirPlayLatency; } else if (strstr(ua, "forked-daapd") == ua) { debug(2, "User-Agent is forked-daapd; selecting the forked-daapd latency " @@ -779,8 +766,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, config.ForkedDaapdLatency); config.latency = config.ForkedDaapdLatency; } else { - debug(2, "Unrecognised User-Agent. Using latency of %d frames.", - config.latency); + debug(2, "Unrecognised User-Agent. Using latency of %d frames.", config.latency); } } @@ -809,9 +795,8 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, p = strchr(p, '=') + 1; tport = atoi(p); -// rtsp_take_player(); - rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, - <port); + // rtsp_take_player(); + rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, <port); if (!lsport) goto error; char *q; @@ -853,13 +838,11 @@ error: resp->respcode = 451; // invalid arguments } -static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { resp->respcode = 200; } -static void handle_set_parameter_parameter(rtsp_conn_info *conn, - rtsp_message *req, +static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { char *cp = req->content; int cp_left = req->contentlength; @@ -888,7 +871,7 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, #ifdef CONFIG_METADATA if (!strncmp(cp, "progress: ", 10)) { char *progress = cp + 10; - //debug(2, "progress: \"%s\"\n", + // debug(2, "progress: \"%s\"\n", // progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); } else @@ -978,12 +961,11 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, // add _so to end of name to avoid confusion with polarssl's implementation -static char encoding_table[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; +static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; static int mod_table[] = {0, 2, 1}; @@ -992,8 +974,8 @@ static int mod_table[] = {0, 2, 1}; // containing its maximum length // the actual length will be returned. -char *base64_encode_so(const unsigned char *data, size_t input_length, - char *encoded_data, size_t *output_length) { +char *base64_encode_so(const unsigned char *data, size_t input_length, char *encoded_data, + size_t *output_length) { size_t calculated_output_length = 4 * ((input_length + 2) / 3); if (calculated_output_length > *output_length) @@ -1090,8 +1072,7 @@ static void metadata_close(void) { fd = -1; } -void metadata_process(uint32_t type, uint32_t code, char *data, - uint32_t length) { +void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { // debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length); int ret; @@ -1105,7 +1086,8 @@ void metadata_process(uint32_t type, uint32_t code, char *data, memcpy(ptr, &v, 4); ptr += 4; memcpy(ptr, data, length); - sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr)); + sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr, + sizeof(metadata_sockaddr)); } // readers may go away and come back @@ -1114,9 +1096,8 @@ void metadata_process(uint32_t type, uint32_t code, char *data, if (fd < 0) return; char thestring[1024]; - snprintf(thestring, 1024, - "%x%x%u", type, - code, length); + snprintf(thestring, 1024, "%x%x%u", type, code, + length); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 0) { // debug(1,"metadata_process error %d exit 1",ret); @@ -1141,10 +1122,9 @@ void metadata_process(uint32_t type, uint32_t code, char *data, size_t towrite_count = remaining_count; if (towrite_count > 57) towrite_count = 57; - size_t outbuf_size = - 76; // size of output buffer on entry, length of result on exit - if (base64_encode_so((unsigned char *)remaining_data, towrite_count, - outbuf, &outbuf_size) == NULL) + size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit + if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) == + NULL) debug(1, "Error encoding base64 data."); // debug(1,"Remaining count: %d ret: %d, outbuf_size: // %d.",remaining_count,ret,outbuf_size); @@ -1188,16 +1168,15 @@ void *metadata_thread_function(void *ignore) { void metadata_init(void) { // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, - sizeof(metadata_package), metadata_queue_size); - int ret = - pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); + pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), + metadata_queue_size); + int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); if (ret) debug(1, "Failed to create metadata thread!"); } -int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, - rtsp_message *carrier, int block) { +int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, + int block) { // parameters: type, code, pointer to data or NULL, length of data or NULL, // the rtsp_message or @@ -1237,14 +1216,11 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, if ((rc == EBUSY) && (carrier)) msg_free(carrier); if (rc == EBUSY) - warn( - "Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", - type, code); + warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code); return rc; } -static void handle_set_parameter_metadata(rtsp_conn_info *conn, - rtsp_message *req, +static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { char *cp = req->content; int cl = req->contentlength; @@ -1254,14 +1230,12 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, uint32_t itag, vl; while (off < cl) { // pick up the metadata tag as an unsigned longint - memcpy(&itag, (uint32_t *)(cp + off), - sizeof(uint32_t)); /* can be misaligned, thus memcpy */ + memcpy(&itag, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */ itag = ntohl(itag); off += sizeof(uint32_t); // pick up the length of the data - memcpy(&vl, (uint32_t *)(cp + off), - sizeof(uint32_t)); /* can be misaligned, thus memcpy */ + memcpy(&vl, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */ vl = ntohl(vl); off += sizeof(uint32_t); @@ -1278,14 +1252,12 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, #endif -static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(1, "received GET_PARAMETER request."); resp->respcode = 200; } -static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // if (!req->contentlength) // debug(1, "received empty SET_PARAMETER request."); @@ -1294,7 +1266,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, char *ct = msg_get_header(req, "Content-Type"); if (ct) { - // debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); +// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); #ifdef CONFIG_METADATA // It seems that the rtptime of the message is used as a kind of an ID that @@ -1322,8 +1294,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, if (p == NULL) debug(1, "Missing RTP-Time info for metadata"); if (p) - send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req, - 1); // metadata starting + send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req, 1); // metadata starting else send_metadata('ssnc', 'mdst', NULL, 0, NULL, 0); // metadata starting, if rtptime is not available @@ -1331,8 +1302,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, handle_set_parameter_metadata(conn, req, resp); if (p) - send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req, - 1); // metadata ending + send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req, 1); // metadata ending else send_metadata('ssnc', 'mden', NULL, 0, NULL, 0); // metadata starting, if rtptime is not available @@ -1348,8 +1318,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, if (p == NULL) debug(1, "Missing RTP-Time info for picture item"); if (p) - send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req, - 1); // picture starting + send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req, 1); // picture starting else send_metadata('ssnc', 'pcst', NULL, 0, NULL, 0); // picture starting, if rtptime is not available @@ -1357,8 +1326,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1); if (p) - send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req, - 1); // picture ending + send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req, 1); // picture ending else send_metadata('ssnc', 'pcen', NULL, 0, NULL, 0); // picture ending, if rtptime is not available @@ -1369,11 +1337,9 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, #endif if (!strncmp(ct, "text/parameters", 15)) { // debug(2, "received parameters in SET_PARAMETER request."); - handle_set_parameter_parameter(conn, req, - resp); // this could be volume or progress + handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress } else { - debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", - ct); + debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", ct); } } else { debug(1, "missing Content-Type header in SET_PARAMETER request."); @@ -1382,8 +1348,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, resp->respcode = 200; } -static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { int have_the_player = 0; // interrupt session if permitted @@ -1400,15 +1365,15 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, die("Non existent the_playing_conn with play_lock enabled."); } usleep(1000000); // here, it is possible for other connections to come in and nab the player. - debug(1,"Try to get the player now"); - //pthread_mutex_lock(&play_lock); + debug(1, "Try to get the player now"); + // pthread_mutex_lock(&play_lock); if (pthread_mutex_trylock(&play_lock) == 0) have_the_player = 1; } } - - if (have_the_player) { - playing_conn = conn; // the present connection is now playing + + if (have_the_player) { + playing_conn = conn; // the present connection is now playing resp->respcode = 456; // 456 - Header Field Not Valid for Resource char *paesiv = NULL; char *prsaaeskey = NULL; @@ -1479,8 +1444,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, free(aeskey); } int i; - for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); - i++) + for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); char *hdr = msg_get_header(req, "X-Apple-Client-Name"); @@ -1655,8 +1619,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { md5_update(&tctx, (unsigned char *)":", 1); md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)config.password, - strlen(config.password)); + md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); md5_finish(&tctx, digest_urp); md5_starts(&tctx); md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); @@ -1728,8 +1691,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { do { reply = rtsp_read_request(conn, &req); if (reply == rtsp_read_request_response_ok) { - debug(3,"RTSP Packet received of type \"%s\":",req->method), - debug_print_msg_headers(3,req); + debug(3, "RTSP Packet received of type \"%s\":", req->method), + debug_print_msg_headers(3, req); resp = msg_init(); resp->respcode = 400; @@ -1737,26 +1700,25 @@ static void *rtsp_conversation_thread_func(void *pconn) { hdr = msg_get_header(req, "CSeq"); if (hdr) msg_add_header(resp, "CSeq", hdr); -// msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); + // msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); msg_add_header(resp, "Server", "AirTunes/105.1"); - - if ((conn->authorized==1) || (rtsp_auth(&auth_nonce, req, resp))==0) { - conn->authorized=1; // it must have been authorized or didn't need a password - struct method_handler *mh; - int method_selected = 0; - for (mh = method_handlers; mh->method; mh++) { - if (!strcmp(mh->method, req->method)) { - method_selected = 1; - mh->handler(conn, req, resp); - break; - } - } - if (method_selected == 0) - debug(1, "Unrecognised and unhandled rtsp request \"%s\".", - req->method); - } - debug(3,"RTSP Response:"); - debug_print_msg_headers(3,resp); + + if ((conn->authorized == 1) || (rtsp_auth(&auth_nonce, req, resp)) == 0) { + conn->authorized = 1; // it must have been authorized or didn't need a password + struct method_handler *mh; + int method_selected = 0; + for (mh = method_handlers; mh->method; mh++) { + if (!strcmp(mh->method, req->method)) { + method_selected = 1; + mh->handler(conn, req, resp); + break; + } + } + if (method_selected == 0) + debug(1, "Unrecognised and unhandled rtsp request \"%s\".", req->method); + } + debug(3, "RTSP Response:"); + debug_print_msg_headers(3, resp); msg_write_response(conn->fd, resp); msg_free(req); msg_free(resp); @@ -1778,11 +1740,11 @@ static void *rtsp_conversation_thread_func(void *pconn) { close(conn->fd); if (auth_nonce) free(auth_nonce); -// pthread_mutex_unlock(&playing_mutex); - // usleep(1000000); -// } // else { - //debug(1, "This RTSP conversation thread doesn't think it's playing for a " - // "close RTSP connection."); + // pthread_mutex_unlock(&playing_mutex); + // usleep(1000000); + // } // else { + // debug(1, "This RTSP conversation thread doesn't think it's playing for a " + // "close RTSP connection."); // } debug(2, "RTSP conversation thread terminated."); // please_shutdown = 0; @@ -1828,7 +1790,7 @@ void rtsp_listen_loop(void) { } for (p = info; p; p = p->ai_next) { - ret = 0; + ret = 0; int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); int yes = 1; @@ -1862,14 +1824,15 @@ void rtsp_listen_loop(void) { // report its availability. do not complain. if (ret) { - char *family; + char *family; #ifdef AF_INET6 - if (p->ai_family == AF_INET6) { - family = "IPv6"; - } else + if (p->ai_family == AF_INET6) { + family = "IPv6"; + } else #endif - family = "IPv4"; - debug(1, "Unable to listen on %s port %d. The error is: \"%s\".", family, config.port,strerror(errno)); + family = "IPv4"; + debug(1, "Unable to listen on %s port %d. The error is: \"%s\".", family, config.port, + strerror(errno)); continue; } @@ -1882,7 +1845,9 @@ void rtsp_listen_loop(void) { freeaddrinfo(info); if (!nsock) - die("Could not establish a service on port %d -- program terminating. Is another instance of Shairport Sync running on this device?",config.port); + die("Could not establish a service on port %d -- program terminating. Is another instance of " + "Shairport Sync running on this device?", + config.port); int maxfd = -1; fd_set fds; @@ -1935,49 +1900,51 @@ void rtsp_listen_loop(void) { perror("failed to accept connection"); free(conn); } else { - SOCKADDR *local_info = (SOCKADDR*)&conn->local; + SOCKADDR *local_info = (SOCKADDR *)&conn->local; socklen_t size_of_reply = sizeof(*local_info); - memset(local_info,0,sizeof(SOCKADDR)); - if (getsockname(conn->fd, (struct sockaddr*)local_info, &size_of_reply)==0) { - + memset(local_info, 0, sizeof(SOCKADDR)); + if (getsockname(conn->fd, (struct sockaddr *)local_info, &size_of_reply) == 0) { + // IPv4: - if (local_info->SAFAMILY==AF_INET) { - char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string - char remote_ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string - struct sockaddr_in *sa = (struct sockaddr_in*)local_info; + if (local_info->SAFAMILY == AF_INET) { + char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string + char remote_ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string + struct sockaddr_in *sa = (struct sockaddr_in *)local_info; inet_ntop(AF_INET, &(sa->sin_addr), ip4, INET_ADDRSTRLEN); unsigned short int tport = ntohs(sa->sin_port); - sa = (struct sockaddr_in*)&conn->remote; + sa = (struct sockaddr_in *)&conn->remote; inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN); unsigned short int rport = ntohs(sa->sin_port); - debug(1,"New RTSP connection from %s:%u to self at %s:%u.",remote_ip4,rport,ip4,tport); + debug(1, "New RTSP connection from %s:%u to self at %s:%u.", remote_ip4, rport, ip4, + tport); } #ifdef AF_INET6 - if (local_info->SAFAMILY==AF_INET6) { + if (local_info->SAFAMILY == AF_INET6) { // IPv6: - char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string + char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string char remote_ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string - struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)local_info; // pretend this is loaded with something + struct sockaddr_in6 *sa6 = + (struct sockaddr_in6 *)local_info; // pretend this is loaded with something inet_ntop(AF_INET6, &(sa6->sin6_addr), ip6, INET6_ADDRSTRLEN); u_int16_t tport = ntohs(sa6->sin6_port); - - sa6 = (struct sockaddr_in6*)&conn->remote; // pretend this is loaded with something + + sa6 = (struct sockaddr_in6 *)&conn->remote; // pretend this is loaded with something inet_ntop(AF_INET6, &(sa6->sin6_addr), remote_ip6, INET6_ADDRSTRLEN); u_int16_t rport = ntohs(sa6->sin6_port); - debug(1,"New RTSP connection from [%s]:%u to self at [%s]:%u.",remote_ip6,rport,ip6,tport); + debug(1, "New RTSP connection from [%s]:%u to self at [%s]:%u.", remote_ip6, rport, ip6, + tport); } - #endif - +#endif + } else { - debug(1,"Error figuring out Shairport Sync's own IP number."); + debug(1, "Error figuring out Shairport Sync's own IP number."); } usleep(500000); pthread_t rtsp_conversation_thread; - ret = pthread_create(&rtsp_conversation_thread, NULL, - rtsp_conversation_thread_func, conn); + ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn); if (ret) die("Failed to create RTSP receiver thread!"); diff --git a/rtsp.h b/rtsp.h index a2f6a952..5058c9a1 100644 --- a/rtsp.h +++ b/rtsp.h @@ -5,7 +5,6 @@ void rtsp_listen_loop(void); // void rtsp_shutdown_stream(void); void rtsp_request_shutdown_stream(void); - // initialise the metadata stuff void metadata_init(void); diff --git a/shairport.c b/shairport.c index 538f418e..e8326233 100644 --- a/shairport.c +++ b/shairport.c @@ -25,20 +25,20 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include #include -#include -#include #include -#include -#include #include +#include +#include +#include +#include +#include #include #include -#include -#include -#include +#include +#include +#include +#include #include "config.h" @@ -51,20 +51,20 @@ #endif #include "common.h" -#include "rtsp.h" -#include "rtp.h" #include "mdns.h" +#include "rtp.h" +#include "rtsp.h" +#include #include -#include #include #include -#include +#include static int shutting_down = 0; static char *appName = NULL; -char configuration_file_path[4096+1]; -char actual_configuration_file_path[4096+1]; +char configuration_file_path[4096 + 1]; +char actual_configuration_file_path[4096 + 1]; void shairport_shutdown() { if (shutting_down) @@ -80,7 +80,7 @@ static void sig_ignore(int foo, siginfo_t *bar, void *baz) {} static void sig_shutdown(int foo, siginfo_t *bar, void *baz) { debug(1, "shutdown requested..."); shairport_shutdown(); -// daemon_log(LOG_NOTICE, "exit..."); + // daemon_log(LOG_NOTICE, "exit..."); daemon_retval_send(255); daemon_pid_file_remove(); exit(0); @@ -105,52 +105,52 @@ static void sig_connect_audio_output(int foo, siginfo_t *bar, void *baz) { set_requested_connection_state_to_output(1); } -char* get_version_string() { - char* version_string = malloc(200); +char *get_version_string() { + char *version_string = malloc(200); if (version_string) { strcpy(version_string, PACKAGE_VERSION); - #ifdef HAVE_LIBPOLARSSL +#ifdef HAVE_LIBPOLARSSL strcat(version_string, "-PolarSSL"); - #endif - #ifdef HAVE_LIBSSL +#endif +#ifdef HAVE_LIBSSL strcat(version_string, "-OpenSSL"); - #endif - #ifdef CONFIG_TINYSVCMDNS +#endif +#ifdef CONFIG_TINYSVCMDNS strcat(version_string, "-tinysvcmdns"); - #endif - #ifdef CONFIG_AVAHI +#endif +#ifdef CONFIG_AVAHI strcat(version_string, "-Avahi"); - #endif - #ifdef CONFIG_DNS_SD +#endif +#ifdef CONFIG_DNS_SD strcat(version_string, "-dns_sd"); - #endif - #ifdef CONFIG_ALSA +#endif +#ifdef CONFIG_ALSA strcat(version_string, "-ALSA"); - #endif - #ifdef CONFIG_SNDIO +#endif +#ifdef CONFIG_SNDIO strcat(version_string, "-sndio"); - #endif - #ifdef CONFIG_AO +#endif +#ifdef CONFIG_AO strcat(version_string, "-ao"); - #endif - #ifdef CONFIG_PULSE +#endif +#ifdef CONFIG_PULSE strcat(version_string, "-pulse"); - #endif - #ifdef CONFIG_DUMMY +#endif +#ifdef CONFIG_DUMMY strcat(version_string, "-dummy"); - #endif - #ifdef CONFIG_STDOUT +#endif +#ifdef CONFIG_STDOUT strcat(version_string, "-stdout"); - #endif - #ifdef CONFIG_PIPE +#endif +#ifdef CONFIG_PIPE strcat(version_string, "-pipe"); - #endif - #ifdef HAVE_LIBSOXR +#endif +#ifdef HAVE_LIBSOXR strcat(version_string, "-soxr"); - #endif - #ifdef CONFIG_METADATA +#endif +#ifdef CONFIG_METADATA strcat(version_string, "-metadata"); - #endif +#endif strcat(version_string, "-sysconfdir:"); strcat(version_string, SYSCONFDIR); } @@ -158,12 +158,12 @@ char* get_version_string() { } void print_version(void) { - char* version_string = get_version_string(); + char *version_string = get_version_string(); if (version_string) { printf("%s\n", version_string); free(version_string); } else { - debug(1,"Can't print version string!"); + debug(1, "Can't print version string!"); } } @@ -182,21 +182,25 @@ void usage(char *progname) { "/etc/shairport-sync.conf.\n"); printf("\n"); - printf("The following general options are for backward compatability. These and all new options have settings in the configuration file, by default /etc/shairport-sync.conf:\n"); + printf("The following general options are for backward compatability. These and all new options " + "have settings in the configuration file, by default /etc/shairport-sync.conf:\n"); printf(" -v, --verbose -v print debug information; -vv more; -vvv lots.\n"); printf(" -p, --port=PORT set RTSP listening port.\n"); printf(" -a, --name=NAME set advertised name.\n"); - printf( - " -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an AirPlay device.\n"); + printf(" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an " + "AirPlay device.\n"); printf(" The default is to set it automatically.\n"); - printf( - " -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes 10 or later.\n"); + printf(" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes " + "10 or later.\n"); printf(" The default is to set it automatically.\n"); - printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown device.\n"); + printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown " + "device.\n"); printf(" The default is to set it automatically.\n"); - printf(" --forkedDaapdLatency=FRAMES [Deprecated] Set the latency for audio sent from forked-daapd.\n"); + printf(" --forkedDaapdLatency=FRAMES [Deprecated] Set the latency for audio sent from " + "forked-daapd.\n"); printf(" The default is to set it automatically.\n"); - printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, where \n"); + printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, " + "where \n"); printf(" \"basic\" (default) inserts or deletes audio frames from " "packet frames with low processor overhead, and \n"); printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- " @@ -209,12 +213,14 @@ void usage(char *progname) { "e.g. /usr/bin/logger.\n"); printf(" Executable scripts work, but must have #!/bin/sh (or " "whatever) in the headline.\n"); - printf(" -w, --wait-cmd wait until the -B or -E programs finish before continuing.\n"); + printf( + " -w, --wait-cmd wait until the -B or -E programs finish before continuing.\n"); printf(" -o, --output=BACKEND select audio output method.\n"); printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service.\n"); printf(" if no mdns provider is specified,\n"); printf(" shairport tries them all until one works.\n"); - printf(" -r, --resync=THRESHOLD resync if timing error exceeds this number of seconds. Set to 0 to " + printf(" -r, --resync=THRESHOLD resync if timing error exceeds this number of seconds. Set " + "to 0 to " "stop resyncing.\n"); printf(" -t, --timeout=SECONDS go back to idle mode from play mode after a break in " "communications of this many seconds (default 120). Set to 0 never to exit play mode.\n"); @@ -237,12 +243,13 @@ void usage(char *progname) { } int parse_options(int argc, char **argv) { - // there are potential memory leaks here -- it's called a second time, previously allocated strings will dangle. + // there are potential memory leaks here -- it's called a second time, previously allocated + // strings will dangle. char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */ - char *stuffing = NULL; /* used for picking up the stuffing option */ - signed char c; /* used for argument parsing */ - int i = 0; /* used for tracking options */ - poptContext optCon; /* context for parsing command-line options */ + char *stuffing = NULL; /* used for picking up the stuffing option */ + signed char c; /* used for argument parsing */ + int i = 0; /* used for tracking options */ + poptContext optCon; /* context for parsing command-line options */ struct poptOption optionsTable[] = { {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL}, {"disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL}, @@ -272,10 +279,9 @@ int parse_options(int argc, char **argv) { {"metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL}, {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL}, #endif - POPT_AUTOHELP - {NULL, 0, 0, NULL, 0}}; + POPT_AUTOHELP{NULL, 0, 0, NULL, 0}}; -// we have to parse the command line arguments to look for a config file + // we have to parse the command line arguments to look for a config file int optind; optind = argc; int j; @@ -285,8 +291,8 @@ int parse_options(int argc, char **argv) { optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0); poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); - - /* Now do options processing just to get a debug level */ + + /* Now do options processing just to get a debug level */ debuglev = 0; while ((c = poptGetNextOpt(optCon)) >= 0) { switch (c) { @@ -300,17 +306,17 @@ int parse_options(int argc, char **argv) { } config_setting_t *setting; - const char *str=0; - int value=0; + const char *str = 0; + int value = 0; double dvalue = 0.0; - - debug(1,"Looking for the configuration file \"%s\".",config.configfile); - + + debug(1, "Looking for the configuration file \"%s\".", config.configfile); + config_init(&config_file_stuff); - + char *config_file_real_path = realpath(config.configfile, NULL); - if (config_file_real_path==NULL) { - debug(2,"Can't resolve the configuration file \"%s\".",config.configfile); + if (config_file_real_path == NULL) { + debug(2, "Can't resolve the configuration file \"%s\".", config.configfile); } else { debug(2, "Looking for configuration file at full path \"%s\"", config_file_real_path); /* Read the file. If there is an error, report it and exit. */ @@ -358,7 +364,8 @@ int parse_options(int argc, char **argv) { config.udp_port_base = value; } - /* Get the udp port range setting. This is number of ports that will be tried for free ports , starting at the port base. Only three ports are needed. */ + /* Get the udp port range setting. This is number of ports that will be tried for free ports , + * starting at the port base. Only three ports are needed. */ if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) { if ((value < 0) || (value > 65535)) die("Invalid port range \"%sd\". It should be between 0 and 65535, default is 100", @@ -419,7 +426,7 @@ int parse_options(int argc, char **argv) { } /* Get the playback_mode setting */ - if (config_lookup_string(config.cfg, "general.playback_mode", &str)) { + if (config_lookup_string(config.cfg, "general.playback_mode", &str)) { if (strcasecmp(str, "stereo") == 0) config.playback_mode = ST_stereo; else if (strcasecmp(str, "mono") == 0) @@ -428,15 +435,17 @@ int parse_options(int argc, char **argv) { die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default) or \"mono\""); } - /* Get the regtype -- the service type and protocol, separated by a dot. Default is "_raop._tcp" */ + /* Get the regtype -- the service type and protocol, separated by a dot. Default is + * "_raop._tcp" */ if (config_lookup_string(config.cfg, "general.regtype", &str)) config.regtype = strdup(str); - - /* Get the volume range, in dB, that should be used If not set, it means you just use the range set by the mixer. */ + /* Get the volume range, in dB, that should be used If not set, it means you just use the + * range set by the mixer. */ if (config_lookup_int(config.cfg, "general.volume_range_db", &value)) { if ((value < 30) || (value > 150)) - die("Invalid volume range \"%sd\". It should be between 30 and 150 dB. Zero means use the mixer's native range", + die("Invalid volume range \"%sd\". It should be between 30 and 150 dB. Zero means use " + "the mixer's native range", value); else config.volume_range_db = value; @@ -447,15 +456,16 @@ int parse_options(int argc, char **argv) { if (strcasecmp(str, "hammerton") == 0) config.use_apple_decoder = 0; else if (strcasecmp(str, "apple") == 0) { - if ((config.decoders_supported & 1< 65000 ? 65000 : value; } - #endif +#endif if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { config.cmd_start = (char *)str; @@ -534,7 +544,8 @@ int parse_options(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.allow_session_interruption = 1; else - die("Invalid session control allow_interruption option choice \"%s\". It should be \"yes\" " + die("Invalid session control allow_interruption option choice \"%s\". It should be " + "\"yes\" " "or \"no\""); } @@ -555,8 +566,9 @@ int parse_options(int argc, char **argv) { free(config_file_real_path); } -// now, do the command line options again, but this time do them fully -- it's a unix convention that command line -// arguments have precedence over configuration file settings. + // now, do the command line options again, but this time do them fully -- it's a unix convention + // that command line + // arguments have precedence over configuration file settings. optind = argc; for (j = 0; j < argc; j++) @@ -609,34 +621,34 @@ int parse_options(int argc, char **argv) { if (c < -1) { die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); } - + #ifdef CONFIG_METADATA if ((config.metadata_enabled == 1) && (config.metadata_pipename == NULL)) - config.metadata_pipename=strdup("/tmp/shairport-sync-metadata"); + config.metadata_pipename = strdup("/tmp/shairport-sync-metadata"); #endif -/* if the regtype hasn't been set, do it now */ - if (config.regtype==NULL) + /* if the regtype hasn't been set, do it now */ + if (config.regtype == NULL) config.regtype = strdup("_raop._tcp"); - - if (tdebuglev!=0) + + if (tdebuglev != 0) debuglev = tdebuglev; -/* if the Service Name wasn't specified, do it now */ + /* if the Service Name wasn't specified, do it now */ - if (raw_service_name==NULL) + if (raw_service_name == NULL) raw_service_name = strdup("%H"); - -// now, do the substitutions in the service name + + // now, do the substitutions in the service name char hostname[100]; gethostname(hostname, 100); - char *i1 = str_replace(raw_service_name,"%h",hostname); - if ((hostname[0]>='a') && (hostname[0]<='z')) - hostname[0] = hostname[0]-0x20; // convert a lowercase first letter into a capital letter - char *i2 = str_replace(i1,"%H",hostname); - char *i3 = str_replace(i2,"%v",PACKAGE_VERSION); + char *i1 = str_replace(raw_service_name, "%h", hostname); + if ((hostname[0] >= 'a') && (hostname[0] <= 'z')) + hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter + char *i2 = str_replace(i1, "%H", hostname); + char *i3 = str_replace(i2, "%v", PACKAGE_VERSION); char *vs = get_version_string(); - config.service_name = str_replace(i3,"%V",vs); + config.service_name = str_replace(i3, "%V", vs); free(i1); free(i2); free(i3); @@ -724,27 +736,27 @@ int main(int argc, char **argv) { free(basec); // set defaults - - + // get thje endianness union { - uint32_t u32; - uint8_t arr[4]; + uint32_t u32; + uint8_t arr[4]; } xn; - xn.arr[0] = 0x44; /* Lowest-address byte */ + xn.arr[0] = 0x44; /* Lowest-address byte */ xn.arr[1] = 0x33; xn.arr[2] = 0x22; - xn.arr[3] = 0x11; /* Highest-address byte */ - - if (xn.u32==0x11223344) + xn.arr[3] = 0x11; /* Highest-address byte */ + + if (xn.u32 == 0x11223344) endianness = SS_LITTLE_ENDIAN; - else if (xn.u32==0x33441122) + else if (xn.u32 == 0x33441122) endianness = SS_PDP_ENDIAN; - else if (xn.u32==0x44332211) + else if (xn.u32 == 0x44332211) endianness = SS_BIG_ENDIAN; - else die("Can not recognise the endianness of the processor."); - + else + die("Can not recognise the endianness of the processor."); + strcpy(configuration_file_path, SYSCONFDIR); strcat(configuration_file_path, "/"); strcat(configuration_file_path, appName); @@ -752,38 +764,44 @@ int main(int argc, char **argv) { config.configfile = configuration_file_path; config.statistics_requested = 0; // don't print stats in the log - config.latency = -1; // -1 means not set. 88200 works well. This is also reset in rtsp.c when play is about to start - config.userSuppliedLatency = 0; // zero means none supplied - config.iTunesLatency = -1; // -1 means not supplied. 99400 seems to work pretty well for iTunes from Version 10 (?) - // upwards-- two left-ear headphones, one from the iMac jack, one - // from an NSLU2 running a cheap "3D Sound" USB Soundcard - config.AirPlayLatency = -1; // -1 means not set. 88200 seems to work well for AirPlay -- Syncs sound and - // vision on AppleTV, but also used for iPhone/iPod/iPad sources - config.ForkedDaapdLatency = -1; // -1 means not set. 99400 seems to be right - config.resyncthreshold = 0.05; // 50 ms + config.latency = -1; // -1 means not set. 88200 works well. This is also reset in rtsp.c when play + // is about to start + config.userSuppliedLatency = 0; // zero means none supplied + config.iTunesLatency = + -1; // -1 means not supplied. 99400 seems to work pretty well for iTunes from Version 10 (?) + // upwards-- two left-ear headphones, one from the iMac jack, one + // from an NSLU2 running a cheap "3D Sound" USB Soundcard + config.AirPlayLatency = + -1; // -1 means not set. 88200 seems to work well for AirPlay -- Syncs sound and + // vision on AppleTV, but also used for iPhone/iPod/iPad sources + config.ForkedDaapdLatency = -1; // -1 means not set. 99400 seems to be right + config.resyncthreshold = 0.05; // 50 ms config.timeout = 120; // this number of seconds to wait for [more] audio before switching to idle. - config.tolerance = 0.002; // this number of seconds of timing error before attempting to correct it. + config.tolerance = + 0.002; // this number of seconds of timing error before attempting to correct it. config.buffer_start_fill = 220; config.port = 5000; config.packet_stuffing = ST_basic; // simple interpolation or deletion - //char hostname[100]; - //gethostname(hostname, 100); - //config.service_name = malloc(20 + 100); - //snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname); - set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device + // char hostname[100]; + // gethostname(hostname, 100); + // config.service_name = malloc(20 + 100); + // snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname); + set_requested_connection_state_to_output( + 1); // we expect to be able to connect to the output device config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. config.udp_port_base = 6001; config.udp_port_range = 100; config.output_format = SPS_FORMAT_S16_LE; // default - config.output_rate = 44100; // default - config.decoders_supported = 1<= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { @@ -878,7 +896,7 @@ int main(int argc, char **argv) { // mDNS supports maximum of 63-character names (we append 13). if (strlen(config.service_name) > 50) { warn("Supplied name too long (max 50 characters)"); - config.service_name[50] = '\0'; //truncate it and carry on... + config.service_name[50] = '\0'; // truncate it and carry on... } /* here, daemonise with libdaemon */ @@ -948,69 +966,84 @@ int main(int argc, char **argv) { config.output->init(argc - audio_arg, argv + audio_arg); // daemon_log(LOG_NOTICE, "startup"); - + switch (endianness) { - case SS_LITTLE_ENDIAN: - debug(2,"The processor is running little-endian."); - break; - case SS_BIG_ENDIAN: - debug(2,"The processor is running big-endian."); - break; - case SS_PDP_ENDIAN: - debug(2,"The processor is running pdp-endian."); - break; + case SS_LITTLE_ENDIAN: + debug(2, "The processor is running little-endian."); + break; + case SS_BIG_ENDIAN: + debug(2, "The processor is running big-endian."); + break; + case SS_PDP_ENDIAN: + debug(2, "The processor is running pdp-endian."); + break; } - + /* Mess around with the latency options */ - // Basically, we used to rely on static latencies -- 99400 for iTunes 10 or later and forkedDaapd, 88200 for everything else - // Nowadays we allow the source to set the latency, which works out at 99651 for iTunes 10 and forkedDaapd and 88220 for everything else - // What we want to do here is allow the source to set the latency unless the user has specified an non-standard latency. + // Basically, we used to rely on static latencies -- 99400 for iTunes 10 or later and forkedDaapd, + // 88200 for everything else + // Nowadays we allow the source to set the latency, which works out at 99651 for iTunes 10 and + // forkedDaapd and 88220 for everything else + // What we want to do here is allow the source to set the latency unless the user has specified an + // non-standard latency. // If the user has specified a standard latency, we suggest to them to stop doing it. - // If they specify a non-standard latency, we suggest the user to use the audio_backend_latency_offset instead. - - if (config.AirPlayLatency!=-1) { - if (config.AirPlayLatency==88200) { - inform("It is not necessary to set the AirPlay latency to 88200 -- you should remove this setting or configuration option, as it is deprecated."); - config.AirPlayLatency= -1; + // If they specify a non-standard latency, we suggest the user to use the + // audio_backend_latency_offset instead. + + if (config.AirPlayLatency != -1) { + if (config.AirPlayLatency == 88200) { + inform("It is not necessary to set the AirPlay latency to 88200 -- you should remove this " + "setting or configuration option, as it is deprecated."); + config.AirPlayLatency = -1; } else { - inform("The AirPlay latency setting is deprecated, as Shairport Sync can now get the correct latency from the source."); - inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere."); + inform("The AirPlay latency setting is deprecated, as Shairport Sync can now get the correct " + "latency from the source."); + inform("Please remove this setting and use the relevant audio_backend_latency_offset " + "setting, if necessary, to compensate for delays elsewhere."); } } - - if (config.iTunesLatency!=-1) { - if (config.iTunesLatency==99400) { - inform("It is not necessary to set the iTunes latency to 99400 -- you should remove this setting or configuration option, as it is deprecated and ignored."); - config.iTunesLatency= -1; + + if (config.iTunesLatency != -1) { + if (config.iTunesLatency == 99400) { + inform("It is not necessary to set the iTunes latency to 99400 -- you should remove this " + "setting or configuration option, as it is deprecated and ignored."); + config.iTunesLatency = -1; } else { - inform("The iTunes latency setting is deprecated, as Shairport Sync can now get the correct latency from the source."); - inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere."); + inform("The iTunes latency setting is deprecated, as Shairport Sync can now get the correct " + "latency from the source."); + inform("Please remove this setting and use the relevant audio_backend_latency_offset " + "setting, if necessary, to compensate for delays elsewhere."); } } - - if (config.ForkedDaapdLatency!=-1) { - if (config.ForkedDaapdLatency==99400) { - inform("It is not necessary to set the forkedDaapd latency to 99400 -- you should remove this setting or configuration option, as it is deprecated and ignored."); - config.ForkedDaapdLatency= -1; + + if (config.ForkedDaapdLatency != -1) { + if (config.ForkedDaapdLatency == 99400) { + inform("It is not necessary to set the forkedDaapd latency to 99400 -- you should remove " + "this setting or configuration option, as it is deprecated and ignored."); + config.ForkedDaapdLatency = -1; } else { - inform("The forkedDaapd latency setting is deprecated, as Shairport Sync can now get the correct latency from the source."); - inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere."); + inform("The forkedDaapd latency setting is deprecated, as Shairport Sync can now get the " + "correct latency from the source."); + inform("Please remove this setting and use the relevant audio_backend_latency_offset " + "setting, if necessary, to compensate for delays elsewhere."); } } if (config.userSuppliedLatency) { - inform("The default latency setting is deprecated, as Shairport Sync can now get the correct latency from the source."); - inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere."); + inform("The default latency setting is deprecated, as Shairport Sync can now get the correct " + "latency from the source."); + inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, " + "if necessary, to compensate for delays elsewhere."); } - + /* print out version */ - - char* version_dbs = get_version_string(); + + char *version_dbs = get_version_string(); if (version_dbs) { - debug(1,"Version: \"%s\"",version_dbs); + debug(1, "Version: \"%s\"", version_dbs); free(version_dbs); } else { - debug(1,"Can't print the version information!"); + debug(1, "Can't print the version information!"); } /* Print out options */ @@ -1045,22 +1078,25 @@ int main(int argc, char **argv) { debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); - debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", config.volume_range_db); + debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", + config.volume_range_db); debug(1, "zeroconf regtype is \"%s\".", config.regtype); debug(1, "decoders_supported field is %d.", config.decoders_supported); debug(1, "use_apple_decoder is %d.", config.use_apple_decoder); - - char *realConfigPath = realpath(config.configfile,NULL); + + char *realConfigPath = realpath(config.configfile, NULL); if (realConfigPath) { - debug(1, "configuration file name \"%s\" resolves to \"%s\".", config.configfile,realConfigPath); + debug(1, "configuration file name \"%s\" resolves to \"%s\".", config.configfile, + realConfigPath); free(realConfigPath); } else { - debug(1, "configuration file name \"%s\" can not be resolved.", config.configfile); - } + debug(1, "configuration file name \"%s\" can not be resolved.", config.configfile); + } #ifdef CONFIG_METADATA debug(1, "metdata enabled is %d.", config.metadata_enabled); debug(1, "metadata pipename is \"%s\".", config.metadata_pipename); - debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr, config.metadata_sockport); + debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr, + config.metadata_sockport); debug(1, "metadata socket packet size is \"%d\".", config.metadata_sockmsglength); debug(1, "get-coverart is %d.", config.get_coverart); #endif @@ -1084,7 +1120,7 @@ int main(int argc, char **argv) { #ifdef CONFIG_METADATA metadata_init(); // create the metadata pipe if necessary #endif - daemon_log(LOG_INFO,"Successful Startup"); + daemon_log(LOG_INFO, "Successful Startup"); rtsp_listen_loop(); // should not reach this... diff --git a/tinysvcmdns.c b/tinysvcmdns.c index cadea483..bf261891 100644 --- a/tinysvcmdns.c +++ b/tinysvcmdns.c @@ -48,15 +48,15 @@ //******************************************************// // mdns.c // //******************************************************// -#include -#include -#include -#include #include +#include +#include +#include +#include #ifdef _WIN32 -#include #include +#include #else #include #endif @@ -1029,26 +1029,26 @@ size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len #include #define LOG_ERR 3 #else -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include #endif -#include -#include +#include #include +#include #include -#include +#include #include #include -#include +#include +#include +#include #include -#include -#include /* * Define a proper IP socket level if not already done. From e527d9f55f4113a861e4355721d9341dbba6aa8a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 11 Sep 2016 11:33:37 +0100 Subject: [PATCH 040/135] Soxr interpolation working. --- player.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 112 insertions(+), 9 deletions(-) diff --git a/player.c b/player.c index 0e226c1c..890a04fe 100644 --- a/player.c +++ b/player.c @@ -403,7 +403,8 @@ static int init_decoder(int32_t fmtp[12]) { field compatibleVersion uint8_t indicating compatible version, value must be set to 0 - field bitDepth uint8_t describes the bit depth of the source + field bitDepth uint8_t describes the bit depth of the + source PCM data (maximum @@ -419,7 +420,8 @@ static int init_decoder(int32_t fmtp[12]) { field kb uint8_t currently unused tuning parameter. value should be set to 10 - field numChannels uint8_t describes the channel count (1 = mono, + field numChannels uint8_t describes the channel count (1 = + mono, 2 = stereo, @@ -439,7 +441,8 @@ static int init_decoder(int32_t fmtp[12]) { the encoded stream. value of 0 indicates unknown - field avgBitRate uint32_t the average bit rate in bits per second + field avgBitRate uint32_t the average bit rate in bits per + second of the Apple @@ -1222,7 +1225,7 @@ static inline int32_t mean_32(int32_t a, int32_t b) { // (b) multiplies each sample by the fixedvolume (a 16-bit quantity) // (c) dithers the result to the output size 32/24/16 bits // (d) outputs the result in the approprate format -// formats accepted so far include U8, S8, S16_LE, S24_LE and S32_LE on a little endinan machine. +// formats accepted so far include U8, S8, S16_LE, S24_LE and S32_LE on a little endian machine. // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, @@ -1323,9 +1326,97 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff } #ifdef HAVE_LIBSOXR -static int stuff_buffer_soxr_32(int32_t *inptr, int length, int32_t *outptr, int stuff, - int l_output_bit_depth) { - return length; +// this takes an array of signed 32-bit integers and +// (a) uses libsoxr to +// resample the array to have one more or one less frame, as specified in +// stuff, +// (b) multiplies each sample by the fixedvolume (a 16-bit quantity) +// (c) dithers the result to the output size 32/24/16 bits +// (d) outputs the result in the approprate format +// formats accepted so far include U8, S8, S16_LE, S24_LE and S32_LE on a little endian machine. + +static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length, + enum sps_format_t l_output_format, char *outptr, int stuff, + int dither) { + if (scratchBuffer == NULL) { + die("soxr scratchBuffer not initialised."); + } + int tstuff = stuff; + if ((stuff > 1) || (stuff < -1) || (length < 100)) { + // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); + tstuff = 0; // if any of these conditions hold, don't stuff anything/ + } + + if (tstuff) { + // debug(1,"Stuff %d.",stuff); + soxr_io_spec_t io_spec; + io_spec.itype = SOXR_INT32_I; + io_spec.otype = SOXR_INT32_I; + io_spec.scale = 1.0; // this seems to crash if not = 1.0 + io_spec.e = NULL; + io_spec.flags = 0; + + size_t odone; + + soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, /* Rates and # of chans. */ + inptr, length, NULL, /* Input. */ + scratchBuffer, length + tstuff, &odone, /* Output. */ + &io_spec, /* Input, output and transfer spec. */ + NULL, NULL); /* Default configuration.*/ + + if (error) + die("soxr error: %s\n", "error: %s\n", soxr_strerror(error)); + + if (odone > length + 1) + die("odone = %d!\n", odone); + + int i; + int32_t *ip, *op; + ip = inptr; + op = scratchBuffer; + + const int gpm = 5; + // keep the first (dpm) samples, to mitigate the Gibbs phenomenon + for (i = 0; i < gpm; i++) { + *op++ = *ip++; + *op++ = *ip++; + } + + // keep the last (dpm) samples, to mitigate the Gibbs phenomenon + op = scratchBuffer + (length + tstuff - gpm) * sizeof(int32_t); + ip = inptr + (length - gpm) * sizeof(int32_t); + for (i = 0; i < gpm; i++) { + *op++ = *ip++; + *op++ = *ip++; + } + + // now, do the volume, dither and formatting processing + ip = scratchBuffer; + char *l_outptr = outptr; + for (i = 0; i < length + tstuff; i++) { + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither); + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither); + //*op = dithered_vol(*op); + // op++; + //*op = dithered_vol(*op); + // op++; + }; + + } else { // the whole frame, if no stuffing + + // now, do the volume, dither and formatting processing + int32_t *ip = inptr; + char *l_outptr = outptr; + int i; + + for (i = 0; i < length; i++) { + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither); + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither); + //*op++ = dithered_vol(*ip++); + //*op++ = dithered_vol(*ip++); + }; + } + return length + tstuff; } // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) { @@ -1485,6 +1576,8 @@ static void *player_thread_func(void *arg) { signed short *inbuf, *tbuf, *silence; + int32_t *sbuf; + char *outbuf; int inbuflength; @@ -1517,6 +1610,13 @@ static void *player_thread_func(void *arg) { (max_frames_per_packet * output_sample_ratio + max_frame_size_change)); if (tbuf == NULL) debug(1, "Failed to allocate memory for the transition buffer."); + sbuf = 0; + if (config.packet_stuffing == ST_soxr) { // needed for stuffing + sbuf = malloc(sizeof(int32_t) * 2 * + (max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + if (sbuf == NULL) + debug(1, "Failed to allocate memory for the transition buffer."); + } } else { tbuf = 0; } @@ -1856,8 +1956,9 @@ static void *player_thread_func(void *arg) { case ST_soxr: // if (amount_to_stuff) debug(1,"Soxr stuff..."); if (tbuf) - play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, inbuflength, (int32_t *)outbuf, - amount_to_stuff, output_bit_depth); + play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, (int32_t *)sbuf, inbuflength, + config.output_format, outbuf, amount_to_stuff, + enable_dither); else play_samples = stuff_buffer_soxr(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); @@ -2101,6 +2202,8 @@ static void *player_thread_func(void *arg) { free(silence); if (tbuf) free(tbuf); + if (sbuf) + free(sbuf); debug(1, "Shut down audio, control and timing threads"); itr.please_stop = 1; pthread_kill(rtp_audio_thread, SIGUSR1); From e9fef46fddee09848ccdf6fd324e6084531a40be Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 11 Sep 2016 15:40:59 +0100 Subject: [PATCH 041/135] Fix 8-bit dithering, fix software volume control --- player.c | 83 +++++++++++++++----------------------------------------- 1 file changed, 22 insertions(+), 61 deletions(-) diff --git a/player.c b/player.c index 890a04fe..358aee81 100644 --- a/player.c +++ b/player.c @@ -625,11 +625,8 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t int dither) { int64_t hyper_sample = sample; int result; - // first, modify volume, if necessary - if (volume == 0x10000) - hyper_sample <<= 32; - else - hyper_sample *= (volume << 16); // this is 32*32 -> 64 bit bit multiplication -- we may need to + int64_t hyper_volume = (int64_t) volume<<16; + hyper_sample = hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need to // dither it down to its // target resolution @@ -661,7 +658,7 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t break; case SPS_FORMAT_S8: case SPS_FORMAT_U8: - dither_mask = (int64_t)1 << (64 + 1 - 24); + dither_mask = (int64_t)1 << (64 + 1 - 8); break; } dither_mask -= 1; @@ -717,54 +714,6 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t *outp += result; } -static inline int32_t dithered_vol_32(int32_t sample, int output_precision) { - if ((fix_volume == 0x1000) && (output_precision == 32)) { - return sample; - } else { - int64_t s = sample; // 64 bit signed - int64_t v = fix_volume & 0x1FFFF; // 17 bit unsigned made a 33 bit unsigned - v <<= 16; - s = s * v; // this is 32*32 -> 64 bit bit multiplication -- we may need to dither it down to its - // target resolution - - // add a TPDF dither -- see - // http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf - // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25 - - // I think, for a 32 --> 16 bits, the range of - // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from - // -32768 to +32767 - - // See the original paper at - // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf - // by Lipshitz, Wannamaker and Vanderkooy, 1992. - - int64_t r = r64i(); - int64_t mask = (int64_t)1 << (64 + 1 - output_precision); - mask -= 1; - int64_t tpdf = (r & mask) - (previous_random_number & mask); - - previous_random_number = r; - - // Check there's no clipping -- if there is, use INT64_MAX or INT64_MIN - - if (tpdf >= 0) { - if (INT64_MAX - tpdf >= s) - s += tpdf; - else - s = INT64_MAX; - } else { - if (INT64_MIN - tpdf <= s) - s += tpdf; - else - s = INT64_MIN; - } - - s >>= (64 - output_precision); - return s; - } -} - static inline short dithered_vol(short sample) { long out; @@ -1585,6 +1534,10 @@ static void *player_thread_func(void *arg) { int output_bit_depth = 16; // default; switch (config.output_format) { + case SPS_FORMAT_S8: + case SPS_FORMAT_U8: + output_bit_depth = 8; + break; case SPS_FORMAT_S16_LE: output_bit_depth = 16; break; @@ -1597,6 +1550,15 @@ static void *player_thread_func(void *arg) { } debug(1, "Output bit depth is %d.", output_bit_depth); + + if (input_bit_depth>output_bit_depth) { + debug(1,"Dithering will be enabled because the input bit depth is greater than the output bit depth"); + } + if (fix_volume!=0x10000) { + debug(1,"Dithering will be enabled the output volume is being altered in software"); + } + + // if we are changing any of the parameters of the input, like sample rate or sample depth, then // we @@ -1700,11 +1662,9 @@ static void *player_thread_func(void *arg) { } } else { - // int enable_dither = 0; - int enable_dither = - !((fix_volume == 0x10000) && - (input_bit_depth == - output_bit_depth)); // avoid dither on audio being sent through without alteration + int enable_dither = 0; + if ((fix_volume!=0x10000) || (input_bit_depth>output_bit_depth)) + enable_dither=1; // here, let's transform the frame of data, if necessary @@ -2363,11 +2323,12 @@ void player_volume(double airplay_volume) { double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of // %f",software_attenuation,temp_fix_volume,airplay_volume); - + + pthread_mutex_lock(&vol_mutex); fix_volume = temp_fix_volume; pthread_mutex_unlock(&vol_mutex); - + #ifdef CONFIG_METADATA char *dv = malloc(128); // will be freed in the metadata thread if (dv) { From 8cabb7d0abe0b3a31becfac9575682f6dc0aca9a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 11 Sep 2016 15:42:33 +0100 Subject: [PATCH 042/135] format player.c --- player.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/player.c b/player.c index 358aee81..56bf83b6 100644 --- a/player.c +++ b/player.c @@ -625,10 +625,10 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t int dither) { int64_t hyper_sample = sample; int result; - int64_t hyper_volume = (int64_t) volume<<16; - hyper_sample = hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need to - // dither it down to its - // target resolution + int64_t hyper_volume = (int64_t)volume << 16; + hyper_sample = hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need to + // dither it down to its + // target resolution // next, do dither, if necessary if (dither) { @@ -1550,15 +1550,14 @@ static void *player_thread_func(void *arg) { } debug(1, "Output bit depth is %d.", output_bit_depth); - - if (input_bit_depth>output_bit_depth) { - debug(1,"Dithering will be enabled because the input bit depth is greater than the output bit depth"); - } - if (fix_volume!=0x10000) { - debug(1,"Dithering will be enabled the output volume is being altered in software"); - } - + if (input_bit_depth > output_bit_depth) { + debug(1, "Dithering will be enabled because the input bit depth is greater than the output bit " + "depth"); + } + if (fix_volume != 0x10000) { + debug(1, "Dithering will be enabled the output volume is being altered in software"); + } // if we are changing any of the parameters of the input, like sample rate or sample depth, then // we @@ -1663,8 +1662,8 @@ static void *player_thread_func(void *arg) { } else { int enable_dither = 0; - if ((fix_volume!=0x10000) || (input_bit_depth>output_bit_depth)) - enable_dither=1; + if ((fix_volume != 0x10000) || (input_bit_depth > output_bit_depth)) + enable_dither = 1; // here, let's transform the frame of data, if necessary @@ -2323,12 +2322,11 @@ void player_volume(double airplay_volume) { double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of // %f",software_attenuation,temp_fix_volume,airplay_volume); - - + pthread_mutex_lock(&vol_mutex); fix_volume = temp_fix_volume; pthread_mutex_unlock(&vol_mutex); - + #ifdef CONFIG_METADATA char *dv = malloc(128); // will be freed in the metadata thread if (dv) { From 3db9769d9a303466417207f41ec38daa9fb75121 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 11 Sep 2016 15:44:51 +0100 Subject: [PATCH 043/135] Soxr interpolation working. Dithering seems to work. Software volume seems to work --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index c482fa0d..67008ca2 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0build11)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0build12)], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 0d45f0b738112177d5e3d759b28f1f4f7d108faf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 11 Sep 2016 15:50:17 +0100 Subject: [PATCH 044/135] trying to get a sane build numbering system. 'd' for 'development'... --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 67008ca2..4ba651d7 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0build12)], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0-d12], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 0499743bd9b2660baba8a0859400c97dbcf7bcd3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 12 Sep 2016 22:01:57 +0100 Subject: [PATCH 045/135] Make endian-independent. Allow users to specify only U8/S8/S16/S24/S32 --- audio_alsa.c | 41 +++++++++++++++++------- common.h | 62 +++---------------------------------- player.c | 30 +++++++++--------- scripts/shairport-sync.conf | 2 +- shairport.c | 4 +-- 5 files changed, 52 insertions(+), 87 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 3319a7cf..ca5a1e2f 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -204,19 +204,19 @@ static int init(int argc, char **argv) { /* Get the output format, using the same names as aplay does*/ if (config_lookup_string(config.cfg, "alsa.output_format", &str)) { - if (strcasecmp(str, "S16_LE") == 0) - config.output_format = SND_PCM_FORMAT_S16_LE; - else if (strcasecmp(str, "S24_LE") == 0) - config.output_format = SND_PCM_FORMAT_S24_LE; - else if (strcasecmp(str, "S32_LE") == 0) - config.output_format = SND_PCM_FORMAT_S32_LE; + if (strcasecmp(str, "S16") == 0) + config.output_format = SPS_FORMAT_S16; + else if (strcasecmp(str, "S24") == 0) + config.output_format = SPS_FORMAT_S24; + else if (strcasecmp(str, "S32") == 0) + config.output_format = SPS_FORMAT_S32; else if (strcasecmp(str, "U8") == 0) config.output_format = SPS_FORMAT_U8; else if (strcasecmp(str, "S8") == 0) config.output_format = SPS_FORMAT_S8; else - die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16_LE\", \"S24_LE\" or " - "\"S32_LE\"", + die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\" or " + "\"S32\"", str); } @@ -445,10 +445,27 @@ int open_alsa_device(void) { die("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev, snd_strerror(ret)); } - - ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sample_format); + snd_pcm_format_t sf; + switch (sample_format) { + case SPS_FORMAT_S8: + sf = SND_PCM_FORMAT_S8; + break; + case SPS_FORMAT_U8: + sf = SND_PCM_FORMAT_U8; + break; + case SPS_FORMAT_S16: + sf = SND_PCM_FORMAT_S16; + break; + case SPS_FORMAT_S24: + sf = SND_PCM_FORMAT_S24; + break; + case SPS_FORMAT_S32: + sf = SND_PCM_FORMAT_S32; + break; + } + ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); if (ret < 0) { - die("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, + die("audio_alsa: Sample format %d not available for device \"%s\": %s", sf, alsa_out_dev, snd_strerror(ret)); } @@ -678,7 +695,7 @@ static void start(int i_sample_rate, int i_sample_format) { desired_sample_rate = i_sample_rate; // must be a variable if (i_sample_format == 0) - sample_format = SND_PCM_FORMAT_S16_LE; // default + sample_format = SPS_FORMAT_S16; // default else sample_format = i_sample_format; } diff --git a/common.h b/common.h index 64b9b44f..536f24c5 100644 --- a/common.h +++ b/common.h @@ -59,64 +59,12 @@ enum decoders_supported_type { // the following enum must _exactly match the snd_pcm_format_t definition in the alsa pcm.h file. enum sps_format_t { - SPS_FORMAT_UNKNOWN = -1, - SPS_FORMAT_S8 = 0, + SPS_FORMAT_UNKNOWN = 0, + SPS_FORMAT_S8, SPS_FORMAT_U8, - SPS_FORMAT_S16_LE, - SPS_FORMAT_S16_BE, - SPS_FORMAT_U16_LE, - SPS_FORMAT_U16_BE, - SPS_FORMAT_S24_LE, - SPS_FORMAT_S24_BE, - SPS_FORMAT_U24_LE, - SPS_FORMAT_U24_BE, - SPS_FORMAT_S32_LE, - SPS_FORMAT_S32_BE, - SPS_FORMAT_U32_LE, - SPS_FORMAT_U32_BE, - SPS_FORMAT_FLOAT_LE, - SPS_FORMAT_FLOAT_BE, - SPS_FORMAT_FLOAT64_LE, - SPS_FORMAT_FLOAT64_BE, - SPS_FORMAT_IEC958_SUBFRAME_LE, - SPS_FORMAT_IEC958_SUBFRAME_BE, - SPS_FORMAT_MU_LAW, - SPS_FORMAT_A_LAW, - SPS_FORMAT_IMA_ADPCM, - SPS_FORMAT_MPEG, - SPS_FORMAT_GSM, - SPS_FORMAT_SPECIAL = 31, - SPS_FORMAT_S24_3LE = 32, - SPS_FORMAT_S24_3BE, - SPS_FORMAT_U24_3LE, - SPS_FORMAT_U24_3BE, - SPS_FORMAT_S20_3LE, - SPS_FORMAT_S20_3BE, - SPS_FORMAT_U20_3LE, - SPS_FORMAT_U20_3BE, - SPS_FORMAT_S18_3LE, - SPS_FORMAT_S18_3BE, - SPS_FORMAT_U18_3LE, - SPS_FORMAT_U18_3BE, - SPS_FORMAT_G723_24, - SPS_FORMAT_G723_24_1B, - SPS_FORMAT_G723_40, - SPS_FORMAT_G723_40_1B, - SPS_FORMAT_DSD_U8, - SPS_FORMAT_DSD_U16_LE, - SPS_FORMAT_DSD_U32_LE, - SPS_FORMAT_DSD_U16_BE, - SPS_FORMAT_DSD_U32_BE, - SPS_FORMAT_LAST = SPS_FORMAT_DSD_U32_BE, - SPS_FORMAT_S16 = SPS_FORMAT_S16_LE, - SPS_FORMAT_U16 = SPS_FORMAT_U16_LE, - SPS_FORMAT_S24 = SPS_FORMAT_S24_LE, - SPS_FORMAT_U24 = SPS_FORMAT_U24_LE, - SPS_FORMAT_S32 = SPS_FORMAT_S32_LE, - SPS_FORMAT_U32 = SPS_FORMAT_U32_LE, - SPS_FORMAT_FLOAT = SPS_FORMAT_FLOAT_LE, - SPS_FORMAT_FLOAT64 = SPS_FORMAT_FLOAT64_LE, - SPS_FORMAT_IEC958_SUBFRAME = SPS_FORMAT_IEC958_SUBFRAME_LE + SPS_FORMAT_S16, + SPS_FORMAT_S24, + SPS_FORMAT_S32, } sps_format_t; typedef struct { diff --git a/player.c b/player.c index 56bf83b6..b5a8d28d 100644 --- a/player.c +++ b/player.c @@ -647,13 +647,13 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t int64_t dither_mask; switch (format) { - case SPS_FORMAT_S32_LE: + case SPS_FORMAT_S32: dither_mask = (int64_t)1 << (64 + 1 - 32); break; - case SPS_FORMAT_S24_LE: + case SPS_FORMAT_S24: dither_mask = (int64_t)1 << (64 + 1 - 24); break; - case SPS_FORMAT_S16_LE: + case SPS_FORMAT_S16: dither_mask = (int64_t)1 << (64 + 1 - 16); break; case SPS_FORMAT_S8: @@ -683,29 +683,29 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t // move the result to the desired position in the int64_t char *op = *outp; switch (format) { - case SPS_FORMAT_S32_LE: + case SPS_FORMAT_S32: hyper_sample >>= (64 - 32); *(int32_t *)op = hyper_sample; result = 4; break; - case SPS_FORMAT_S24_LE: + case SPS_FORMAT_S24: hyper_sample >>= (64 - 24); *(int32_t *)op = hyper_sample; result = 4; break; - case SPS_FORMAT_S16_LE: + case SPS_FORMAT_S16: hyper_sample >>= (64 - 16); - *(int16_t *)op = hyper_sample; + *(int16_t *)op = (int16_t)hyper_sample; result = 2; break; case SPS_FORMAT_S8: - hyper_sample >>= (64 - 8); + hyper_sample >>= (int8_t)(64 - 8); *op = hyper_sample; result = 1; break; case SPS_FORMAT_U8: - hyper_sample >>= (64 - 8); - hyper_sample += 128; // this is just a guess! + hyper_sample >>= (uint8_t)(64 - 8); + hyper_sample += 128; *op = hyper_sample; result = 1; break; @@ -1465,10 +1465,10 @@ static void *player_thread_func(void *arg) { output_bytes_per_frame = 4; switch (config.output_format) { - case SPS_FORMAT_S24_LE: + case SPS_FORMAT_S24: output_bytes_per_frame = 8; break; - case SPS_FORMAT_S32_LE: + case SPS_FORMAT_S32: output_bytes_per_frame = 8; break; } @@ -1538,13 +1538,13 @@ static void *player_thread_func(void *arg) { case SPS_FORMAT_U8: output_bit_depth = 8; break; - case SPS_FORMAT_S16_LE: + case SPS_FORMAT_S16: output_bit_depth = 16; break; - case SPS_FORMAT_S24_LE: + case SPS_FORMAT_S24: output_bit_depth = 24; break; - case SPS_FORMAT_S32_LE: + case SPS_FORMAT_S32: output_bit_depth = 32; break; } diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 50d0f797..cd7688bb 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -61,7 +61,7 @@ alsa = // mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. // mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. // output_rate = 44100; // can be 44100, 88200, 176400, 352800, but the device must be capable of it -// output_format = "S16_LE"; // can be "S16_LE", "S24_LE" or "S32_LE", but the device must be capable of it +// output_format = "S16"; // can be "U8", "S8", "S16", "S24" or "S32", with be LE or BE depending on the processor, but the device must be capable of it // audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. // audio_backend_buffer_desired_length = 0.15; // Given in seconds. If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. // disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". diff --git a/shairport.c b/shairport.c index e8326233..4fec3541 100644 --- a/shairport.c +++ b/shairport.c @@ -791,7 +791,7 @@ int main(int argc, char **argv) { config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. config.udp_port_base = 6001; config.udp_port_range = 100; - config.output_format = SPS_FORMAT_S16_LE; // default + config.output_format = SPS_FORMAT_S16; // default config.output_rate = 44100; // default config.decoders_supported = 1 << decoder_hammerton; // David Hammerton's decoder supported by default @@ -1074,7 +1074,7 @@ int main(int argc, char **argv) { debug(1, "disable_synchronization is %d.", config.no_sync); debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); debug(1, "output_rate is %d (0 means 44,100).", config.output_rate); - debug(1, "output_format is %d (2 is the default SPS_FORMAT_S16_LE).", config.output_format); + debug(1, "output_format is %d (3 is the default SPS_FORMAT_S16).", config.output_format); debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); From cb6b7c34c909c67bd3d3e775fd3dccc28db71b94 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 12 Sep 2016 22:17:41 +0100 Subject: [PATCH 046/135] Make endian-independent. Allow users to specify only U8/S8/S16/S24/S32 --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4ba651d7..4ab4e6e6 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0-d12], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0-d13], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 43788faa964ad6037029538ececaab3c4a6fe769 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 13 Sep 2016 12:19:28 +0100 Subject: [PATCH 047/135] Fix error checking of alsa latency and buffer legth settings. --- audio_alsa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index ca5a1e2f..04f74370 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -148,7 +148,7 @@ static int init(int argc, char **argv) { if (config.cfg != NULL) { /* Get the desired buffer size setting. */ if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length", &dvalue)) { - if ((dvalue < 0) || (value > 1.5)) + if ((dvalue < 0) || (dvalue > 1.5)) die("Invalid alsa audio backend buffer desired length \"%f\". It " "should be between 0 and " "1.5, default is 0.15 seconds", @@ -160,7 +160,7 @@ static int init(int argc, char **argv) { /* Get the latency offset. */ if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset", &dvalue)) { - if ((dvalue < -1.0) || (value > 1.5)) + if ((dvalue < -1.0) || (dvalue > 1.5)) die("Invalid alsa audio backend buffer latency offset \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", dvalue); From 8a9665f68ef6e9b4f5823633d904756583b7577c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 15 Sep 2016 17:43:13 +0100 Subject: [PATCH 048/135] Get different playback modes working and update the configuration sample --- scripts/shairport-sync.conf | 2 +- shairport.c | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index cd7688bb..dea8ba6f 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -25,7 +25,7 @@ general = // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. // volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". -// playback_mode = "stereo"; // This can be "stereo" or "mono". Default is "stereo". +// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". // alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose // the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. }; diff --git a/shairport.c b/shairport.c index 4fec3541..9356ff8c 100644 --- a/shairport.c +++ b/shairport.c @@ -431,8 +431,14 @@ int parse_options(int argc, char **argv) { config.playback_mode = ST_stereo; else if (strcasecmp(str, "mono") == 0) config.playback_mode = ST_mono; + else if (strcasecmp(str, "reverse stereo") == 0) + config.playback_mode = ST_reverse_stereo; + else if (strcasecmp(str, "both left") == 0) + config.playback_mode = ST_left_only; + else if (strcasecmp(str, "both right") == 0) + config.playback_mode = ST_right_only; else - die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default) or \"mono\""); + die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", \"reverse stereo\", \"both left\", \"both right\""); } /* Get the regtype -- the service type and protocol, separated by a dot. Default is @@ -1070,11 +1076,11 @@ int main(int argc, char **argv) { debug(1, "drift tolerance is %f seconds.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); - debug(1, "playback_mode is %d (0-stereo, 1-mono).", config.playback_mode); + debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).", config.playback_mode); debug(1, "disable_synchronization is %d.", config.no_sync); debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); - debug(1, "output_rate is %d (0 means 44,100).", config.output_rate); - debug(1, "output_format is %d (3 is the default SPS_FORMAT_S16).", config.output_format); + debug(1, "output_rate is %d.", config.output_rate); + debug(1, "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S32).", config.output_format); debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); From 5a8241c71a7aa4c974f8f7875d6fe9cb51fc104f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 18 Sep 2016 12:56:22 +0100 Subject: [PATCH 049/135] Change names of drift, resync, alsa buffer offset and alsa buffer length to distinguish them from the old 44100-based settings. Add warning messages. --- audio_alsa.c | 20 ++++++++++++++++---- scripts/shairport-sync.conf | 8 ++++---- shairport.c | 12 ++++++++++-- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 04f74370..89517a25 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -146,10 +146,22 @@ static int init(int argc, char **argv) { // command line options if (config.cfg != NULL) { + + + if (config_lookup_int(config.cfg, + "alsa.audio_backend_buffer_desired_length", &value)) + inform("The setting audio_backend_buffer_desired_length is no longer used and has been ignored. Use audio_backend_buffer_desired_length_in_seconds instead."); + + + /* Get the latency offset. */ + if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", + &value)) + inform("The setting audio_backend_latency_offset is no longer used and has been ignored. Use audio_backend_latency_offset_in_seconds instead."); + /* Get the desired buffer size setting. */ - if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length", &dvalue)) { + if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length_in_seconds", &dvalue)) { if ((dvalue < 0) || (dvalue > 1.5)) - die("Invalid alsa audio backend buffer desired length \"%f\". It " + die("Invalid alsa audio backend buffer desired time \"%f\". It " "should be between 0 and " "1.5, default is 0.15 seconds", dvalue); @@ -159,9 +171,9 @@ static int init(int argc, char **argv) { } /* Get the latency offset. */ - if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset", &dvalue)) { + if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset_in_seconds", &dvalue)) { if ((dvalue < -1.0) || (dvalue > 1.5)) - die("Invalid alsa audio backend buffer latency offset \"%f\". It " + die("Invalid alsa audio backend buffer latency offset time \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", dvalue); else diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index dea8ba6f..4f4ebe0a 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -19,8 +19,8 @@ general = // udp_port_base = 6001; // start allocating UDP ports from this port number when needed // udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base (only three are needed). // statistics = "no"; // set to "yes" to print statistics in the log -// drift = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it -// resync_threshold = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it +// drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it +// resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. // volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. @@ -62,8 +62,8 @@ alsa = // mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. // output_rate = 44100; // can be 44100, 88200, 176400, 352800, but the device must be capable of it // output_format = "S16"; // can be "U8", "S8", "S16", "S24" or "S32", with be LE or BE depending on the processor, but the device must be capable of it -// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. -// audio_backend_buffer_desired_length = 0.15; // Given in seconds. If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +// audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. +// audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. // disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". // period_size = ; // Use this optional advanced setting to set the alsa period size near to this value // buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value diff --git a/shairport.c b/shairport.c index 9356ff8c..2631e40c 100644 --- a/shairport.c +++ b/shairport.c @@ -397,12 +397,20 @@ int parse_options(int argc, char **argv) { die("Invalid statistics option choice \"%s\". It should be \"yes\" or \"no\""); } + /* The old drift tolerance setting. */ + if (config_lookup_int(config.cfg, "general.drift", &value)) + inform("The drift setting is deprecated and has been ignored. Use drift_tolerance_in_seconds instead"); + + /* The old resync setting. */ + if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) + inform("The resync_threshold setting is deprecated and has been ignored. Use resync_threshold_in_seconds instead"); + /* Get the drift tolerance setting. */ - if (config_lookup_float(config.cfg, "general.drift", &dvalue)) + if (config_lookup_float(config.cfg, "general.drift_tolerance_in_seconds", &dvalue)) config.tolerance = dvalue; /* Get the resync setting. */ - if (config_lookup_float(config.cfg, "general.resync_threshold", &dvalue)) + if (config_lookup_float(config.cfg, "general.resync_threshold_in_seconds", &dvalue)) config.resyncthreshold = dvalue; /* Get the verbosity setting. */ From c80d59b3c4f90e3a680942a99ce5e44b1a7db66e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 18 Sep 2016 13:08:36 +0100 Subject: [PATCH 050/135] Tidy up source with clang-format --- audio_alsa.c | 51 +++++++++++++++++++++++++-------------------------- shairport.c | 17 +++++++++++------ 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 89517a25..afeb03c3 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -147,19 +147,18 @@ static int init(int argc, char **argv) { if (config.cfg != NULL) { - - if (config_lookup_int(config.cfg, - "alsa.audio_backend_buffer_desired_length", &value)) - inform("The setting audio_backend_buffer_desired_length is no longer used and has been ignored. Use audio_backend_buffer_desired_length_in_seconds instead."); - + if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) + inform("The setting audio_backend_buffer_desired_length is no longer used and has been " + "ignored. Use audio_backend_buffer_desired_length_in_seconds instead."); /* Get the latency offset. */ - if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", - &value)) - inform("The setting audio_backend_latency_offset is no longer used and has been ignored. Use audio_backend_latency_offset_in_seconds instead."); + if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) + inform("The setting audio_backend_latency_offset is no longer used and has been ignored. Use " + "audio_backend_latency_offset_in_seconds instead."); /* Get the desired buffer size setting. */ - if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length_in_seconds", &dvalue)) { + if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length_in_seconds", + &dvalue)) { if ((dvalue < 0) || (dvalue > 1.5)) die("Invalid alsa audio backend buffer desired time \"%f\". It " "should be between 0 and " @@ -459,26 +458,26 @@ int open_alsa_device(void) { } snd_pcm_format_t sf; switch (sample_format) { - case SPS_FORMAT_S8: - sf = SND_PCM_FORMAT_S8; - break; - case SPS_FORMAT_U8: - sf = SND_PCM_FORMAT_U8; - break; - case SPS_FORMAT_S16: - sf = SND_PCM_FORMAT_S16; - break; - case SPS_FORMAT_S24: - sf = SND_PCM_FORMAT_S24; - break; - case SPS_FORMAT_S32: - sf = SND_PCM_FORMAT_S32; - break; + case SPS_FORMAT_S8: + sf = SND_PCM_FORMAT_S8; + break; + case SPS_FORMAT_U8: + sf = SND_PCM_FORMAT_U8; + break; + case SPS_FORMAT_S16: + sf = SND_PCM_FORMAT_S16; + break; + case SPS_FORMAT_S24: + sf = SND_PCM_FORMAT_S24; + break; + case SPS_FORMAT_S32: + sf = SND_PCM_FORMAT_S32; + break; } ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); if (ret < 0) { - die("audio_alsa: Sample format %d not available for device \"%s\": %s", sf, - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Sample format %d not available for device \"%s\": %s", sf, alsa_out_dev, + snd_strerror(ret)); } ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2); diff --git a/shairport.c b/shairport.c index 2631e40c..1406de66 100644 --- a/shairport.c +++ b/shairport.c @@ -399,11 +399,13 @@ int parse_options(int argc, char **argv) { /* The old drift tolerance setting. */ if (config_lookup_int(config.cfg, "general.drift", &value)) - inform("The drift setting is deprecated and has been ignored. Use drift_tolerance_in_seconds instead"); + inform("The drift setting is deprecated and has been ignored. Use " + "drift_tolerance_in_seconds instead"); /* The old resync setting. */ if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) - inform("The resync_threshold setting is deprecated and has been ignored. Use resync_threshold_in_seconds instead"); + inform("The resync_threshold setting is deprecated and has been ignored. Use " + "resync_threshold_in_seconds instead"); /* Get the drift tolerance setting. */ if (config_lookup_float(config.cfg, "general.drift_tolerance_in_seconds", &dvalue)) @@ -446,7 +448,8 @@ int parse_options(int argc, char **argv) { else if (strcasecmp(str, "both right") == 0) config.playback_mode = ST_right_only; else - die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", \"reverse stereo\", \"both left\", \"both right\""); + die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", " + "\"reverse stereo\", \"both left\", \"both right\""); } /* Get the regtype -- the service type and protocol, separated by a dot. Default is @@ -806,7 +809,7 @@ int main(int argc, char **argv) { config.udp_port_base = 6001; config.udp_port_range = 100; config.output_format = SPS_FORMAT_S16; // default - config.output_rate = 44100; // default + config.output_rate = 44100; // default config.decoders_supported = 1 << decoder_hammerton; // David Hammerton's decoder supported by default #ifdef HAVE_APPLE_ALAC @@ -1084,11 +1087,13 @@ int main(int argc, char **argv) { debug(1, "drift tolerance is %f seconds.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); - debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).", config.playback_mode); + debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).", + config.playback_mode); debug(1, "disable_synchronization is %d.", config.no_sync); debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); debug(1, "output_rate is %d.", config.output_rate); - debug(1, "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S32).", config.output_format); + debug(1, "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S32).", + config.output_format); debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); From b0c173a3f24c32d8924eb47a26c448cb2ae5604c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 18 Sep 2016 14:54:46 +0100 Subject: [PATCH 051/135] Change names of drift, resync, alsa buffer offset and alsa buffer length to distinguish them from the old 44100-based settings. Add warning messages. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4ab4e6e6..bf3929da 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0-d13], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0-d14], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 622549c7df8688b8b2f2c2636488ac48090a7870 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 21 Sep 2016 20:57:33 +0100 Subject: [PATCH 052/135] If sync disabled on the alsa device, don't keep asking for buffer length. Also emit sender IP number as metadata. --- audio_alsa.c | 5 +++++ rtsp.c | 11 +++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index afeb03c3..e5289ad0 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -418,6 +418,11 @@ int open_alsa_device(void) { snd_pcm_uframes_t buffer_size, actual_buffer_length; snd_pcm_access_t access; + // ensure no calls are made to the alsa device enquiring about the buffer length if + // synchronisation is disabled. + if (config.no_sync != 0) + audio_alsa.delay = NULL; + ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); if (ret < 0) return (ret); diff --git a/rtsp.c b/rtsp.c index e8dfe35b..5f905d90 100644 --- a/rtsp.c +++ b/rtsp.c @@ -950,7 +950,9 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r // string to identify the source's remote control on the network. // 'acre' -- this is the source's Active-Remote token, necessary if you want // to send commands to the source's remote control (if it has one). -// +// `clip` -- the payload is the IP number of the client, i.e. the sender of audio. +// Can be an IPv4 or an IPv6 number. + // including a simple base64 encoder to minimise malloc/free activity // From Stack Overflow, with thanks: @@ -1915,6 +1917,9 @@ void rtsp_listen_loop(void) { sa = (struct sockaddr_in *)&conn->remote; inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN); unsigned short int rport = ntohs(sa->sin_port); +#ifdef CONFIG_METADATA + send_ssnc_metadata('clip', strdup(ip4), strlen(ip4), 1); +#endif debug(1, "New RTSP connection from %s:%u to self at %s:%u.", remote_ip4, rport, ip4, tport); } @@ -1932,7 +1937,9 @@ void rtsp_listen_loop(void) { sa6 = (struct sockaddr_in6 *)&conn->remote; // pretend this is loaded with something inet_ntop(AF_INET6, &(sa6->sin6_addr), remote_ip6, INET6_ADDRSTRLEN); u_int16_t rport = ntohs(sa6->sin6_port); - +#ifdef CONFIG_METADATA + send_ssnc_metadata('clip', strdup(ip6), strlen(ip6), 1); +#endif debug(1, "New RTSP connection from [%s]:%u to self at [%s]:%u.", remote_ip6, rport, ip6, tport); } From 909a2d05cbd8bec050c77eb58e1ab770e353926b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 21 Sep 2016 20:58:36 +0100 Subject: [PATCH 053/135] If sync disabled on the alsa device, don't keep asking for buffer length. Also emit sender IP number as metadata. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index bf3929da..aa810953 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0-d14], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0-d15], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 0542f5d204c2d23c96896c7c01121f9d25d146b9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Sep 2016 16:24:41 +0100 Subject: [PATCH 054/135] Fix a few typos in the comments --- scripts/shairport-sync.conf | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 4f4ebe0a..bc498c7e 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -4,12 +4,13 @@ // General Settings general = { -// name = "Shairport Sync on %H"; // This is the name the service will advertise to iTunes. -// The default is "" -- the first letter is capitalised (ASCII only.) -// You can use %h for the hostname, +// name = "%H"; // This means "Hostname" -- see below. This is the name the service will advertise to iTunes. +// The default is "Hostname" -- i.e. the machine's hostname with the first letter capitalised (ASCII only.) +// You can use the following substitutions: +// %h for the hostname, // %H for the Hostname (i.e. with first letter capitalised (ASCII only)), -// %v for the version number, e.g. 3.2.1 and -// %V for the full version string, e.g. 3.2.1-OpenSSL-Avahi-ALSA-soxr-metadata +// %v for the version number, e.g. 2.8.5 and +// %V for the full version string, e.g. 2.8.5-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc // Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H". // password = "secret"; // leave this commented out if you don't want to require a password // interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor. @@ -33,7 +34,7 @@ general = // How to deal with metadata, including artwork metadata = { -// enabled = "no"; // et to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe +// enabled = "no"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe // include_cover_art = "no"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes". // pipe_name = "/tmp/shairport-sync-metadata"; // pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up @@ -60,8 +61,8 @@ alsa = // output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc. // mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. // mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. -// output_rate = 44100; // can be 44100, 88200, 176400, 352800, but the device must be capable of it -// output_format = "S16"; // can be "U8", "S8", "S16", "S24" or "S32", with be LE or BE depending on the processor, but the device must be capable of it +// output_rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability. +// output_format = "S16"; // can be "U8", "S8", "S16", "S24" or "S32", but the device must have the capability. Endianness matches that of the processor. // audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. // audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. // disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". From d9b28443fc0e465f41dbe54371a1c8ca62c2be6e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Sep 2016 16:52:45 +0100 Subject: [PATCH 055/135] Version 3.0d! --- RELEASENOTES.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 13e6f2af..0d5a6264 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,27 @@ +Version 3.0d15 – Development Version +---- + +Big Update +==== + +This is a big update, so please be careful, as there are likely to be lots of bugs and maybe even regressions. It's still very much a moving target. The processor load is about 25% higher. + +The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC. + +**New Features** +* 8-bit, 16-bit, 24-bit and 32-bit output to ALSA. (Other back ends are not updated yet.) +* 44,100, 88,200, 176,400 and 352,800 sample per second output. +* Internal processing including software volume control and interpolation is done after sample size and rate conversion. +* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. + +Pesky Changes You Cannot Ignore +==== +* Settings have changed -- basically, any timings that were denominated in frames (44,100 frames per second assumed) are now denominated in seconds. Please refer to the shairport-sync.conf.sample file for details. +* Sox-based interpolation at 176,400 samples per second may very well overload your CPU. + +**Bugs** +* Documentation is not updated. + Version 2.8.4.8 – Development Version ---- **Enhancements** From a0f6dc8b73ac0f15035759be45e25455a2d105da Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Sep 2016 16:59:39 +0100 Subject: [PATCH 056/135] 3.0d15! --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0d5a6264..4fb308c4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,7 +4,7 @@ Version 3.0d15 – Development Version Big Update ==== -This is a big update, so please be careful, as there are likely to be lots of bugs and maybe even regressions. It's still very much a moving target. The processor load is about 25% higher. +This is a big update, so please be careful, as there are likely to be lots of bugs and maybe even regressions. It's still very much a moving target. The processor load is about 25% higher. There is still quite a bit of work to do, especially on the backends apart from ALSA. The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC. From 49cbd6555b88ec5fb7930db06e3b6a447d8d8dd7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Sep 2016 17:03:49 +0100 Subject: [PATCH 057/135] Version 3.0! --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf05aa88..bcfec001 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ Shairport Sync ============= +**Welcome to Version 3 of Shairport Sync. This is a big update from version 2.X and is still a work in progress. Please see the Releas Notes for some more information** + Shairport Sync is an AirPlay audio player — it plays audio streamed from iTunes, iOS devices and other AirPlay sources such as Quicktime Player and ForkedDaapd, among others. Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.) Shairport Sync does not support AirPlay video or photo streaming. -**Please note that, between 2.8.4.6 and 2.8.4.7, there is a change in the standard ./configure arguments which you should not ignore!** - This is the unstable "development" branch. Changes and updates are incorporated into this branch quickly. To access the stable version, where changes are made after due time, please switch to the "master" branch. + More Information ---------- Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to play audio at exactly the right time. It does this by monitoring and controlling the *latency* — the time-gap between when a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC). From d0f3b7f3e6430692ca85aaa55d91deccce2c38ca Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Sep 2016 17:04:20 +0100 Subject: [PATCH 058/135] Version 3.0! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bcfec001..1401d163 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Shairport Sync ============= -**Welcome to Version 3 of Shairport Sync. This is a big update from version 2.X and is still a work in progress. Please see the Releas Notes for some more information** +**Welcome to Version 3 of Shairport Sync. This is a big update from version 2.X and is still a work in progress. Please see the Release Notes for some more information** Shairport Sync is an AirPlay audio player — it plays audio streamed from iTunes, iOS devices and other AirPlay sources such as Quicktime Player and ForkedDaapd, among others. Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.) From 4a556b37cd9bf87dadbe6be8f874335ab8384364 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Sep 2016 17:09:10 +0100 Subject: [PATCH 059/135] Version 3.0! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1401d163..a66719a3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Shairport Sync ============= -**Welcome to Version 3 of Shairport Sync. This is a big update from version 2.X and is still a work in progress. Please see the Release Notes for some more information** +**Welcome to Version 3 of Shairport Sync. This is a big update from version 2.X and is still a work in progress. Please see the Release Notes for more information.** Shairport Sync is an AirPlay audio player — it plays audio streamed from iTunes, iOS devices and other AirPlay sources such as Quicktime Player and ForkedDaapd, among others. Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.) From b5ee350b3c2fa683c4add19d174032581cfaff4c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 28 Sep 2016 11:47:44 +0100 Subject: [PATCH 060/135] Add *untested* support for S24_3LE and S24_3BE output format. --- audio_alsa.c | 22 +++++++++++++++++----- common.h | 2 ++ player.c | 33 +++++++++++++++++++++++++++++++-- shairport.c | 3 ++- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 3ebe4642..99b70cd3 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -219,6 +219,10 @@ static int init(int argc, char **argv) { config.output_format = SPS_FORMAT_S16; else if (strcasecmp(str, "S24") == 0) config.output_format = SPS_FORMAT_S24; + else if (strcasecmp(str, "S24_3LE") == 0) + config.output_format = SPS_FORMAT_S24_3LE; + else if (strcasecmp(str, "S24_3BE") == 0) + config.output_format = SPS_FORMAT_S24_3BE; else if (strcasecmp(str, "S32") == 0) config.output_format = SPS_FORMAT_S32; else if (strcasecmp(str, "U8") == 0) @@ -226,7 +230,8 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "S8") == 0) config.output_format = SPS_FORMAT_S8; else - die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\" or " + die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", " + "\"S24_3LE\", \"S24_3BE\" or " "\"S32\"", str); } @@ -417,10 +422,11 @@ int open_alsa_device(void) { // snd_pcm_uframes_t frames = 441 * 10; snd_pcm_uframes_t buffer_size, actual_buffer_length; snd_pcm_access_t access; - - // ensure no calls are made to the alsa device enquiring about the buffer length if synchronisation is disabled. - if (config.no_sync!=0) - audio_alsa.delay = NULL; + + // ensure no calls are made to the alsa device enquiring about the buffer length if + // synchronisation is disabled. + if (config.no_sync != 0) + audio_alsa.delay = NULL; // ensure no calls are made to the alsa device enquiring about the buffer length if // synchronisation is disabled. @@ -479,6 +485,12 @@ int open_alsa_device(void) { case SPS_FORMAT_S24: sf = SND_PCM_FORMAT_S24; break; + case SPS_FORMAT_S24_3LE: + sf = SND_PCM_FORMAT_S24_3LE; + break; + case SPS_FORMAT_S24_3BE: + sf = SND_PCM_FORMAT_S24_3BE; + break; case SPS_FORMAT_S32: sf = SND_PCM_FORMAT_S32; break; diff --git a/common.h b/common.h index 536f24c5..e52f82d2 100644 --- a/common.h +++ b/common.h @@ -64,6 +64,8 @@ enum sps_format_t { SPS_FORMAT_U8, SPS_FORMAT_S16, SPS_FORMAT_S24, + SPS_FORMAT_S24_3LE, + SPS_FORMAT_S24_3BE, SPS_FORMAT_S32, } sps_format_t; diff --git a/player.c b/player.c index b5a8d28d..c09ec67c 100644 --- a/player.c +++ b/player.c @@ -651,6 +651,8 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t dither_mask = (int64_t)1 << (64 + 1 - 32); break; case SPS_FORMAT_S24: + case SPS_FORMAT_S24_3LE: + case SPS_FORMAT_S24_3BE: dither_mask = (int64_t)1 << (64 + 1 - 24); break; case SPS_FORMAT_S16: @@ -682,12 +684,33 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t // move the result to the desired position in the int64_t char *op = *outp; + uint8_t byt; switch (format) { case SPS_FORMAT_S32: hyper_sample >>= (64 - 32); *(int32_t *)op = hyper_sample; result = 4; break; + case SPS_FORMAT_S24_3LE: + hyper_sample >>= (64 - 24); + byt = (uint8_t)hyper_sample; + *op++ = byt; + byt = (uint8_t)(hyper_sample >> 8); + *op++ = byt; + byt = (uint8_t)(hyper_sample >> 16); + *op++ = byt; + result = 3; + break; + case SPS_FORMAT_S24_3BE: + hyper_sample >>= (64 - 24); + byt = (uint8_t)(hyper_sample >> 16); + *op++ = byt; + byt = (uint8_t)(hyper_sample >> 8); + *op++ = byt; + byt = (uint8_t)hyper_sample; + *op++ = byt; + result = 3; + break; case SPS_FORMAT_S24: hyper_sample >>= (64 - 24); *(int32_t *)op = hyper_sample; @@ -1174,7 +1197,7 @@ static inline int32_t mean_32(int32_t a, int32_t b) { // (b) multiplies each sample by the fixedvolume (a 16-bit quantity) // (c) dithers the result to the output size 32/24/16 bits // (d) outputs the result in the approprate format -// formats accepted so far include U8, S8, S16_LE, S24_LE and S32_LE on a little endian machine. +// formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32 // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, @@ -1282,7 +1305,7 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff // (b) multiplies each sample by the fixedvolume (a 16-bit quantity) // (c) dithers the result to the output size 32/24/16 bits // (d) outputs the result in the approprate format -// formats accepted so far include U8, S8, S16_LE, S24_LE and S32_LE on a little endian machine. +// formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32 static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length, enum sps_format_t l_output_format, char *outptr, int stuff, @@ -1465,6 +1488,10 @@ static void *player_thread_func(void *arg) { output_bytes_per_frame = 4; switch (config.output_format) { + case SPS_FORMAT_S24_3LE: + case SPS_FORMAT_S24_3BE: + output_bytes_per_frame = 6; + break; case SPS_FORMAT_S24: output_bytes_per_frame = 8; break; @@ -1542,6 +1569,8 @@ static void *player_thread_func(void *arg) { output_bit_depth = 16; break; case SPS_FORMAT_S24: + case SPS_FORMAT_S24_3LE: + case SPS_FORMAT_S24_3BE: output_bit_depth = 24; break; case SPS_FORMAT_S32: diff --git a/shairport.c b/shairport.c index 1406de66..fd2cd965 100644 --- a/shairport.c +++ b/shairport.c @@ -1092,7 +1092,8 @@ int main(int argc, char **argv) { debug(1, "disable_synchronization is %d.", config.no_sync); debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); debug(1, "output_rate is %d.", config.output_rate); - debug(1, "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S32).", + debug(1, + "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S24_3LE, 6-S24_3BE, 7-S32).", config.output_format); debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); From 93740bee87ffdabc3d8350abb4adf9df37ca722b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 28 Sep 2016 11:49:11 +0100 Subject: [PATCH 061/135] Add *untested* support for S24_3LE and S24_3BE output format. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index aa810953..bf3b25f7 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0-d15], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0d16], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 4e3060b6216d1498a946c085dcea2510f67671bc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 28 Sep 2016 11:57:44 +0100 Subject: [PATCH 062/135] Add *untested* support for S24_3LE and S24_3BE output format. --- scripts/shairport-sync.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index bc498c7e..3a87f1f2 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -62,7 +62,7 @@ alsa = // mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. // mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. // output_rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability. -// output_format = "S16"; // can be "U8", "S8", "S16", "S24" or "S32", but the device must have the capability. Endianness matches that of the processor. +// output_format = "S16"; // can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor. // audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. // audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. // disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". From b5744dfd2a3eb6d3922cc1b6b64e1d2232877fb2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 28 Sep 2016 12:02:25 +0100 Subject: [PATCH 063/135] =?UTF-8?q?3.0d16=20=E2=80=93=20added=2024-bit=20t?= =?UTF-8?q?hree-byte=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Untested! --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4fb308c4..587fcca6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 3.0d15 – Development Version +Version 3.0d16 – Development Version ---- Big Update @@ -9,7 +9,7 @@ This is a big update, so please be careful, as there are likely to be lots of bu The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC. **New Features** -* 8-bit, 16-bit, 24-bit and 32-bit output to ALSA. (Other back ends are not updated yet.) +* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA. (Other back ends are not updated yet.) * 44,100, 88,200, 176,400 and 352,800 sample per second output. * Internal processing including software volume control and interpolation is done after sample size and rate conversion. * Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. From 86a2902a45a201e34b8d06b1c1729b3ef7495568 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 28 Sep 2016 12:10:38 +0100 Subject: [PATCH 064/135] =?UTF-8?q?3.0d16=20=E2=80=93=2024-bit=20three-byt?= =?UTF-8?q?e=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Untested. --- RELEASENOTES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 587fcca6..c7a77210 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,15 +9,15 @@ This is a big update, so please be careful, as there are likely to be lots of bu The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC. **New Features** -* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA. (Other back ends are not updated yet.) -* 44,100, 88,200, 176,400 and 352,800 sample per second output. +* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. (Other back ends are not updated yet.) +* 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. * Internal processing including software volume control and interpolation is done after sample size and rate conversion. * Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. Pesky Changes You Cannot Ignore ==== -* Settings have changed -- basically, any timings that were denominated in frames (44,100 frames per second assumed) are now denominated in seconds. Please refer to the shairport-sync.conf.sample file for details. -* Sox-based interpolation at 176,400 samples per second may very well overload your CPU. +* Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details. +* Sox-based interpolation at higher sample rates may overload your CPU -- yopu might have to choose between higher sample rates and sox-based interpolation. **Bugs** * Documentation is not updated. From e5ec3da9da8020b1ca650e2ba54e626703187663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Krause?= Date: Thu, 24 Nov 2016 12:34:11 +0100 Subject: [PATCH 065/135] mdns: avahi: set AVAHI_CLIENT_NO_FAIL in avahi_client_new() For whatever reasons `avahi_client_new` passes the value `0` as its second parameter. However, the second parameter is of type [`AvahiClientFlags`](http://avahi.org/doxygen/html/client_8h_source.html#l00048) with the possible values: ``` c typedef enum { AVAHI_CLIENT_IGNORE_USER_CONFIG = 1, AVAHI_CLIENT_NO_FAIL = 2 } AvahiClientFlags; ``` Setting `AVAHI_CLIENT_NO_FAIL` has the benefit that the shairport-sync daemon does not depend on the order of the init process whether the avahi daemon is already up. --- mdns_avahi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdns_avahi.c b/mdns_avahi.c index 12df0505..7fe6c187 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -273,7 +273,7 @@ static int avahi_register(char *srvname, int srvport) { return -1; } if (!(client = - avahi_client_new(avahi_threaded_poll_get(tpoll), 0, client_callback, NULL, &err))) { + avahi_client_new(avahi_threaded_poll_get(tpoll), AVAHI_CLIENT_NO_FAIL, client_callback, NULL, &err))) { warn("couldn't create avahi client: %s!", avahi_strerror(err)); return -1; } From 9e61da91c08d3c705746b3293ba7047f8423f1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Krause?= Date: Thu, 24 Nov 2016 14:48:03 +0100 Subject: [PATCH 066/135] mdns: avahi: handle avahi client failure Handle the avahi client failure so that in case of a restart of the avahi daemon the shairport-sync daemon has not to be restarted, too. The handling is based on the error handling of avahi-publish [1]. [1] https://github.com/lathiat/avahi/blob/master/avahi-utils/avahi-publish.c#L156-L179 --- mdns_avahi.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/mdns_avahi.c b/mdns_avahi.c index 7fe6c187..b9664674 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -234,6 +234,8 @@ static void register_service(AvahiClient *c) { static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata) { + int err; + switch (state) { case AVAHI_CLIENT_S_REGISTERING: if (group) @@ -245,7 +247,25 @@ static void client_callback(AvahiClient *c, AvahiClientState state, break; case AVAHI_CLIENT_FAILURE: - debug(1, "avahi: client failure"); + err = avahi_client_errno(c); + debug(1, "avahi: client failure: %s", avahi_strerror(err)); + + if (err == AVAHI_ERR_DISCONNECTED) { + /* We have been disconnected, so lets reconnect */ + avahi_client_free(c); + c = NULL; + group = NULL; + + if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), + AVAHI_CLIENT_NO_FAIL, client_callback, + userdata, &err))) { + warn("avahi: failed to create client object: %s", avahi_strerror(err)); + avahi_threaded_poll_quit(tpoll); + } + } else { + warn("avahi: client failure: %s", avahi_strerror(err)); + avahi_threaded_poll_quit(tpoll); + } break; case AVAHI_CLIENT_S_COLLISION: From 5c85e614c0c997d0f8f504c0dda5dade8eb5516d Mon Sep 17 00:00:00 2001 From: jneubrand Date: Sat, 26 Nov 2016 11:48:03 -0800 Subject: [PATCH 067/135] Merge fix-clip --- rtsp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rtsp.c b/rtsp.c index 5f905d90..9464a22b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1918,7 +1918,8 @@ void rtsp_listen_loop(void) { inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN); unsigned short int rport = ntohs(sa->sin_port); #ifdef CONFIG_METADATA - send_ssnc_metadata('clip', strdup(ip4), strlen(ip4), 1); + send_ssnc_metadata('clip', strdup(remote_ip4), strlen(remote_ip4) , 1); + send_ssnc_metadata('svip', strdup(ip4), strlen(ip4) , 1); #endif debug(1, "New RTSP connection from %s:%u to self at %s:%u.", remote_ip4, rport, ip4, tport); @@ -1938,7 +1939,8 @@ void rtsp_listen_loop(void) { inet_ntop(AF_INET6, &(sa6->sin6_addr), remote_ip6, INET6_ADDRSTRLEN); u_int16_t rport = ntohs(sa6->sin6_port); #ifdef CONFIG_METADATA - send_ssnc_metadata('clip', strdup(ip6), strlen(ip6), 1); + send_ssnc_metadata('clip', strdup(remote_ip6), strlen(remote_ip6), 1); + send_ssnc_metadata('svip', strdup(ip6), strlen(ip6), 1); #endif debug(1, "New RTSP connection from [%s]:%u to self at [%s]:%u.", remote_ip6, rport, ip6, tport); From 71fc1a2c70d0ffa4e9b00e93775d6ca22a5f5e97 Mon Sep 17 00:00:00 2001 From: shtirlic Date: Sun, 4 Dec 2016 00:03:23 +0200 Subject: [PATCH 068/135] Initial support for libsoundio --- Makefile.am | 4 + audio.c | 6 ++ audio_soundio.c | 215 ++++++++++++++++++++++++++++++++++++++++++++++++ configure.ac | 8 ++ shairport.c | 3 + 5 files changed, 236 insertions(+) create mode 100644 audio_soundio.c diff --git a/Makefile.am b/Makefile.am index 1be55de5..c6e4d5e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -47,6 +47,10 @@ if USE_AO shairport_sync_SOURCES += audio_ao.c endif +if USE_SOUNDIO +shairport_sync_SOURCES += audio_soundio.c +endif + if USE_PULSE shairport_sync_SOURCES += audio_pulse.c endif diff --git a/audio.c b/audio.c index e40006c4..915cb802 100644 --- a/audio.c +++ b/audio.c @@ -35,6 +35,9 @@ extern audio_output audio_sndio; #ifdef CONFIG_AO extern audio_output audio_ao; #endif +#ifdef CONFIG_SOUNDIO +extern audio_output audio_soundio; +#endif #ifdef CONFIG_PULSE extern audio_output audio_pulse; #endif @@ -64,6 +67,9 @@ static audio_output *outputs[] = { #ifdef CONFIG_AO &audio_ao, #endif +#ifdef CONFIG_SOUNDIO + &audio_soundio, +#endif #ifdef CONFIG_DUMMY &audio_dummy, #endif diff --git a/audio_soundio.c b/audio_soundio.c new file mode 100644 index 00000000..b76a4ca6 --- /dev/null +++ b/audio_soundio.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include "common.h" +#include "audio.h" + +#include + +int Fs; +long long starttime, samples_played; + +struct SoundIoOutStream *outstream; +struct SoundIo *soundio; +struct SoundIoDevice *device; +struct SoundIoRingBuffer *ring_buffer = NULL; + +static int min_int(int a, int b) { + return (a < b) ? a : b; +} + +static void write_callback(struct SoundIoOutStream *outstream, + int frame_count_min, int frame_count_max) +{ + struct SoundIoChannelArea *areas; + int frame_count; + int err; + + char *read_ptr = soundio_ring_buffer_read_ptr(ring_buffer); + int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer); + int fill_count = fill_bytes / outstream->bytes_per_frame; + + debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , outstream->bytes_per_frame: %d", frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame ); + + if (frame_count_min > fill_count) { + int frame_count = frame_count_min; + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))){ + debug(0, "[--->>] begin write error: %s", soundio_strerror(err)); + } + for (int frame = 0; frame < frame_count; frame += 1) { + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + memset(areas[ch].ptr, 0, outstream->bytes_per_sample); + areas[ch].ptr += areas[ch].step; + } + } + if ((err = soundio_outstream_end_write(outstream))) + debug(0,"[--->>] end write error: %s", soundio_strerror(err)); + return; + } + + int read_count = min_int(frame_count_max, fill_count); + int frames_left = read_count; + + while (frames_left > 0) { + int frame_count = frames_left; + + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) + debug(0, "[--->>] begin write error: %s", soundio_strerror(err)); + + if (frame_count <= 0) + break; + + for (int frame = 0; frame < frame_count; frame += 1) { + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + memcpy(areas[ch].ptr, read_ptr, outstream->bytes_per_sample); + areas[ch].ptr += areas[ch].step; + read_ptr += outstream->bytes_per_sample; + } + } + + if ((err = soundio_outstream_end_write(outstream))) + debug(0, "[--->>] end write error: %s", soundio_strerror(err)); + + frames_left -= frame_count; + } + + debug(3,"[--->>] Wrote: %d", read_count * outstream->bytes_per_frame); + soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream->bytes_per_frame); +} + +static void underflow_callback(struct SoundIoOutStream *outstream) { + static int count = 0; + debug(0, "underflow %d\n", ++count); +} + +static int init(int argc, char **argv) { + int err; + config.audio_backend_buffer_desired_length = 2.0; + + soundio = soundio_create(); + if (!soundio) { + debug(0, "out of memory\n"); + return 1; + } + if ((err = soundio_connect_backend(soundio, SoundIoBackendCoreAudio))) { + debug(0, "error connecting: %s", soundio_strerror(err)); + return 1; + } + soundio_flush_events(soundio); + + int default_out_device_index = soundio_default_output_device_index(soundio); + if (default_out_device_index < 0) { + debug(0, "no output device found"); + return 1; + } + + device = soundio_get_output_device(soundio, default_out_device_index); + if (!device) { + debug(0, "out of memory"); + return 1; + } + debug(0, "Output device: %s\n", device->name); + return 0; +} + +static void deinit(void) { + soundio_ring_buffer_destroy(ring_buffer); + soundio_device_unref(device); + soundio_destroy(soundio); + debug(0, "soundio audio deinit\n"); +} + +static void start(int sample_rate, int sample_format) { + Fs = sample_rate; + starttime = 0; + samples_played = 0; + int err; + + debug(1, "soundion rate: %d, format: %d", sample_rate, sample_format); + + // soundio_device_sort_channel_layouts(device); + + outstream = soundio_outstream_create(device); + outstream->format = SoundIoFormatS16NE; + outstream->sample_rate = sample_rate; + outstream->layout.channel_count = 2; + outstream->write_callback = write_callback; + outstream->underflow_callback = underflow_callback; + // outstream->software_latency = 0; + + if ((err = soundio_outstream_open(outstream))) { + debug(0, "unable to open device: %s", soundio_strerror(err)); + } + if (outstream->layout_error) + debug(0, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error)); + + int capacity = outstream->sample_rate * outstream->bytes_per_frame; + ring_buffer = soundio_ring_buffer_create(soundio, capacity); + if (!ring_buffer) + debug(0, "unable to create ring buffer: out of memory"); + char *buf = soundio_ring_buffer_write_ptr(ring_buffer); + memset(buf, 0, capacity); + soundio_ring_buffer_advance_write_ptr(ring_buffer, capacity); + + if ((err = soundio_outstream_start(outstream))) { + debug(0, "unable to start outstream: %s", soundio_strerror(err)); + } + + debug(1, "libsoundio output started\n"); +} + +static void play(short buf[], int samples) { + int err; + int free_bytes = soundio_ring_buffer_free_count(ring_buffer); + int written_bytes = 0; + int write_bytes = 0; + int left_bytes = samples * outstream->bytes_per_frame; + char *write_ptr = soundio_ring_buffer_write_ptr(ring_buffer); + + debug(3, "[<<---] samples: %d , size: %d", samples, left_bytes ); + write_bytes = min_int(left_bytes, free_bytes); + debug(3, "[<<---] left_bytes: %d, write_bytes: %d, free_bytes: %d\n", left_bytes, write_bytes, free_bytes); + + if (write_bytes){ + memcpy(write_ptr, (char*) buf, write_bytes); + written_bytes += write_bytes; + soundio_ring_buffer_advance_write_ptr(ring_buffer, write_bytes); + debug(3, "[<<---] Written to buffer : %d\n", written_bytes); + } +} + +static void parameters(audio_parameters *info) { + info->minimum_volume_dB = -30.0; + info->maximum_volume_dB = 0.0; +debug(2, "Parameters\n"); +debug(2, "Current Volume dB: %f\n", info->current_volume_dB); +debug(2, "Minimum Volume dB: %d\n", info->minimum_volume_dB); +debug(2, "Maximum Volume dB: %d\n", info->maximum_volume_dB); +} + +static void stop(void) { + soundio_outstream_destroy(outstream); + soundio_ring_buffer_clear(ring_buffer); + debug(1, "libsoundio output stopped\n"); + } + +static void flush(void) { + soundio_ring_buffer_clear(ring_buffer); + debug(1, "libsoundio output flushed\n"); +} + +static void help(void) { printf(" There are no options for libsoundio.\n"); } + +audio_output audio_soundio = {.name = "soundio", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = &flush, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = ¶meters, + .mute = NULL}; diff --git a/configure.ac b/configure.ac index bf3b25f7..d2d1c0fb 100644 --- a/configure.ac +++ b/configure.ac @@ -214,6 +214,14 @@ AC_ARG_WITH(ao, [ --with-ao = choose AO (Audio Output?) API support. N.B. no sy AC_CHECK_LIB([ao], [ao_initialize], , AC_MSG_ERROR(AO support requires the ao library -- libao-dev suggested))], ) AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"]) +# Look for Soundio flag +AC_ARG_WITH(soundio, [ --with-soundio = choose soundio API support.], [ + AC_MSG_RESULT(>>Including an soundio back end) + HAS_SOUNDIO=1 + AC_DEFINE([CONFIG_SOUNDIO], 1, [Needed by the compiler.]) + AC_CHECK_LIB([soundio], [soundio_create], , AC_MSG_ERROR(soundio support requires the soundio library!))], ) +AM_CONDITIONAL([USE_SOUNDIO], [test "x$HAS_SOUNDIO" = "x1"]) + # Look for pulseaudio flag AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose PulseAudio API support. N.B. no synchronisation -- so underflow or overflow is inevitable!], [ AC_MSG_RESULT(>>Including a PulseAudio back end. N.B. no synchronisation -- so underflow or overflow is inevitable!) diff --git a/shairport.c b/shairport.c index fd2cd965..6d37f96c 100644 --- a/shairport.c +++ b/shairport.c @@ -136,6 +136,9 @@ char *get_version_string() { #ifdef CONFIG_PULSE strcat(version_string, "-pulse"); #endif +#ifdef CONFIG_SOUNDIO + strcat(version_string, "-soundio"); +#endif #ifdef CONFIG_DUMMY strcat(version_string, "-dummy"); #endif From 71e7dc0e93d52b05cdc34ba95ee859af68215e33 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 Jan 2017 17:30:11 +0000 Subject: [PATCH 069/135] Fix small bug Thanks to @jief666 for the fix. --- rtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp.c b/rtp.c index aad447fb..7f8abee2 100644 --- a/rtp.c +++ b/rtp.c @@ -480,7 +480,7 @@ void *rtp_timing_receiver(void *arg) { if (first_local_time == 0) { first_local_time = local_time_chosen; first_remote_time = remote_time_chosen; - uint64_t clock_drift = 0; + clock_drift = 0; } else { uint64_t local_time_change = local_time_chosen - first_local_time; uint64_t remote_time_change = remote_time_chosen - first_remote_time; From 62aaf07478aa9883073133223878075bb411058b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 08:21:12 +0000 Subject: [PATCH 070/135] Add support for mbed TLS and deprecate polarssl support --- common.c | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++- player.c | 17 ++++++ rtsp.c | 33 ++++++++++++ shairport.c | 14 +++++ 4 files changed, 210 insertions(+), 1 deletion(-) diff --git a/common.c b/common.c index 3389e5f9..d2eb17e4 100644 --- a/common.c +++ b/common.c @@ -67,6 +67,17 @@ #endif #endif +#ifdef HAVE_LIBMBEDTLS +#include +#include +#include +#include +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" + +#endif + + #include // true if Shairport Sync is supposed to be sending output to the output device, false otherwise @@ -125,6 +136,59 @@ void inform(char *format, ...) { daemon_log(LOG_INFO, "%s", s); } +#ifdef HAVE_LIBMBEDTLS +char *base64_enc(uint8_t *input, int length) { + char *buf = NULL; + size_t dlen = 0; + int rc = mbedtls_base64_encode(NULL, 0, &dlen, input, length); + if (rc && (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)) + debug(1, "Error %d getting length of base64 encode.", rc); + else { + buf = (char *)malloc(dlen); + rc = mbedtls_base64_encode((unsigned char *)buf, dlen, &dlen, input, length); + if (rc != 0) + debug(1, "Error %d encoding base64.", rc); + } + return buf; +} + +uint8_t *base64_dec(char *input, int *outlen) { + // slight problem here is that Apple cut the padding off their challenges. We must restore it + // before passing it in to the decoder, it seems + uint8_t *buf = NULL; + size_t dlen = 0; + int inbufsize = ((strlen(input) + 3) / 4) * 4; // this is the size of the input buffer we will + // send to the decoder, but we need space for 3 + // extra "="s and a NULL + char *inbuf = malloc(inbufsize + 4); + if (inbuf == 0) + debug(1, "Can't malloc memory for inbuf in base64_decode."); + else { + strcpy(inbuf, input); + strcat(inbuf, "==="); + // debug(1,"base64_dec called with string \"%s\", length %d, filled string: \"%s\", length %d.", + // input,strlen(input),inbuf,inbufsize); + int rc = mbedtls_base64_decode(NULL, 0, &dlen, (unsigned char *)inbuf, inbufsize); + if (rc && (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)) + debug(1, "Error %d getting decode length, result is %d.", rc, dlen); + else { + // debug(1,"Decode size is %d.",dlen); + buf = malloc(dlen); + if (buf == 0) + debug(1, "Can't allocate memory in base64_dec."); + else { + rc = mbedtls_base64_decode(buf, dlen, &dlen, (unsigned char *)inbuf, inbufsize); + if (rc != 0) + debug(1, "Error %d in base64_dec.", rc); + } + } + free(inbuf); + } + *outlen = dlen; + return buf; +} +#endif + #ifdef HAVE_LIBPOLARSSL char *base64_enc(uint8_t *input, int length) { char *buf = NULL; @@ -279,6 +343,62 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { } #endif +#ifdef HAVE_LIBMBEDTLS +uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { + mbedtls_pk_context pkctx; + mbedtls_rsa_context *trsa; + const char *pers = "rsa_encrypt"; + size_t olen = *outlen; + int rc; + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + + mbedtls_entropy_init(&entropy); + + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + (const unsigned char *)pers, strlen(pers)); + + mbedtls_pk_init(&pkctx); + + rc = mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key, sizeof(super_secret_key), NULL, 0); + if (rc != 0) + debug(1, "Error %d reading the private key.", rc); + + uint8_t *outbuf = NULL; + trsa = mbedtls_pk_rsa(pkctx); + + switch (mode) { + case RSA_MODE_AUTH: + mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE); + outbuf = malloc(trsa->len); + rc = mbedtls_rsa_pkcs1_encrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, + inlen, input, outbuf); + if (rc != 0) + debug(1, "mbedtls_pk_encrypt error %d.", rc); + *outlen = trsa->len; + break; + case RSA_MODE_KEY: + mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1); + outbuf = malloc(trsa->len); + rc = mbedtls_rsa_pkcs1_decrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, + &olen, input, outbuf, trsa->len); + if (rc != 0) + debug(1, "mbedtls_pk_decrypt error %d.", rc); + *outlen = olen; + break; + default: + die("bad rsa mode"); + } + + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + mbedtls_pk_free(&pkctx); + return outbuf; +} +#endif + #ifdef HAVE_LIBPOLARSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { rsa_context trsa; @@ -583,7 +703,7 @@ char *str_replace(const char *string, const char *substr, const char *replacemen /* from http://burtleburtle.net/bob/rand/smallprng.html */ -typedef uint64_t u8; +// typedef uint64_t u8; typedef struct ranctx { uint64_t a; uint64_t b; @@ -616,3 +736,28 @@ void r64init(uint64_t seed) { raninit(&rx, seed); } uint64_t r64u() { return (ranval(&rx)); } int64_t r64i() { return (ranval(&rx) >> 1); } + +/* generate an array of 64-bit random numbers */ +const int ranarraylength = 1009; // these will be 8-byte numbers. + +uint64_t *ranarray; + +int ranarraynext; + +void ranarrayinit() { + ranarray = (uint64_t*)malloc(ranarraylength*sizeof(uint64_t)); + int i; + for (i=0;i> 1); } + diff --git a/player.c b/player.c index c09ec67c..edc3dfbf 100644 --- a/player.c +++ b/player.c @@ -47,6 +47,11 @@ #include "config.h" +#ifdef HAVE_LIBMBEDTLS +#include +#include +#endif + #ifdef HAVE_LIBPOLARSSL #include #include @@ -92,6 +97,10 @@ static int max_frame_size_change; // maximal resampling shift - conservative //#define OUTFRAME_BYTES(max_frames_per_packet) (4 * (max_frames_per_packet + 3)) +#ifdef HAVE_LIBMBEDTLS +static mbedtls_aes_context dctx; +#endif + #ifdef HAVE_LIBPOLARSSL static aes_context dctx; #endif @@ -315,6 +324,9 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { unsigned char iv[16]; int aeslen = len & ~0xf; memcpy(iv, aesiv, sizeof(iv)); +#ifdef HAVE_LIBMBEDTLS + mbedtls_aes_crypt_cbc(&dctx, MBEDTLS_AES_DECRYPT, aeslen, iv, buf, packet); +#endif #ifdef HAVE_LIBPOLARSSL aes_crypt_cbc(&dctx, AES_DECRYPT, aeslen, iv, buf, packet); #endif @@ -2389,6 +2401,11 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); if (encrypted) { +#ifdef HAVE_LIBMBEDTLS + memset(&dctx, 0, sizeof(mbedtls_aes_context)); + mbedtls_aes_setkey_dec(&dctx, stream->aeskey, 128); +#endif + #ifdef HAVE_LIBPOLARSSL memset(&dctx, 0, sizeof(aes_context)); aes_setkey_dec(&dctx, stream->aeskey, 128); diff --git a/rtsp.c b/rtsp.c index 9464a22b..e58c10b4 100644 --- a/rtsp.c +++ b/rtsp.c @@ -50,6 +50,10 @@ #include #endif +#ifdef HAVE_LIBMBEDTLS +#include +#endif + #ifdef HAVE_LIBPOLARSSL #include #endif @@ -1614,6 +1618,23 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { MD5_Final(digest_mu, &ctx); #endif +#ifdef HAVE_LIBMBEDTLS + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)config.password, + strlen(config.password)); + mbedtls_md5_finish(&tctx, digest_urp); + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); + mbedtls_md5_finish(&tctx, digest_mu); +#endif + #ifdef HAVE_LIBPOLARSSL md5_context tctx; md5_starts(&tctx); @@ -1647,6 +1668,18 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { MD5_Final(digest_total, &ctx); #endif +#ifdef HAVE_LIBMBEDTLS + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, buf, 32); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + for (i = 0; i < 16; i++) + sprintf((char *)buf + 2 * i, "%02x", digest_mu[i]); + mbedtls_md5_update(&tctx, buf, 32); + mbedtls_md5_finish(&tctx, digest_total); +#endif + #ifdef HAVE_LIBPOLARSSL md5_starts(&tctx); md5_update(&tctx, buf, 32); diff --git a/shairport.c b/shairport.c index 6d37f96c..9b3065a6 100644 --- a/shairport.c +++ b/shairport.c @@ -42,6 +42,10 @@ #include "config.h" +#ifdef HAVE_LIBMBEDTLS +#include +#endif + #ifdef HAVE_LIBPOLARSSL #include #endif @@ -109,6 +113,9 @@ char *get_version_string() { char *version_string = malloc(200); if (version_string) { strcpy(version_string, PACKAGE_VERSION); +#ifdef HAVE_LIBMBEDTLS + strcat(version_string, "-mbedTLS"); +#endif #ifdef HAVE_LIBPOLARSSL strcat(version_string, "-PolarSSL"); #endif @@ -1133,6 +1140,13 @@ int main(int argc, char **argv) { MD5_Final(ap_md5, &ctx); #endif +#ifdef HAVE_LIBMBEDTLS + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish(&tctx, ap_md5); +#endif + #ifdef HAVE_LIBPOLARSSL md5_context tctx; md5_starts(&tctx); From 96ee2608686ff5e5453a939f094ad5de9b4924d1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 08:21:35 +0000 Subject: [PATCH 071/135] Note addition of support for mbed TLS and deprecate polarssl support --- configure.ac | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index d2d1c0fb..62e6bc83 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0d16], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0d17], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) @@ -125,11 +125,11 @@ AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory AM_CONDITIONAL([USE_CUSTOMPIDDIR], [test "x$HAS_CUSTOMPIDDIR" = "x1"]) # Check --with-ssl=argument -AC_ARG_WITH(ssl, [ choose --with-ssl=openssl or --with-ssl=polarssl for encryption services], [ +AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=polarssl (deprecated) for encryption services ], [ AC_MSG_CHECKING(encryption libraries chosen) if test "x${with_ssl}" = x -o "x${with_ssl}" = xyes ; then AC_MSG_RESULT(not found) - AC_MSG_ERROR(choose either "openssl" or "polarssl" encryption) + AC_MSG_ERROR(choose "openssl", "mbedtls" or "polarssl" encryption) fi if test "x${with_ssl}" = xopenssl ; then if test "x${with_pkg_config}" = xyes ; then @@ -144,10 +144,17 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl or --with-ssl=polarssl for encrypti AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto]) AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl]) fi + elif test "x${with_ssl}" = xmbedtls ; then + AC_CHECK_LIB([mbedtls],[mbedtls_ssl_init],, + [AC_MSG_ERROR([Cannot find required libray: libmbedtls],1)]) + AC_CHECK_LIB([mbedcrypto], [mbedtls_entropy_func],, + [AC_MSG_ERROR([Cannot find required library: libmbedcrypto],1)]) + AC_CHECK_LIB([mbedx509], [mbedtls_pk_init],, + [AC_MSG_ERROR([Cannot find required library: libmbedx509],1)]) elif test "x${with_ssl}" = xpolarssl ; then - AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL selected but the library cannot be found!)) + AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL is selected but the library cannot be found and is deprecated. Consider selecting mbed TLS instead using --with-ssl=mbedtls.)) else - AC_MSG_ERROR(unknown option "${with_ssl}"." Please choose with "openssl" or "polarssl") + AC_MSG_ERROR(unknown option "${with_ssl}"." Please choose with "openssl", "mbedtls" or "polarssl") fi ], ) From dbb71e66b7905078d6847ca645097c9e266503a2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 08:29:06 +0000 Subject: [PATCH 072/135] Add support for mbed TLS --- RELEASENOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c7a77210..e21a17b3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,7 @@ +Version 3.0d17 – Development Version +---- +Add support for mbed TLS and deprecate the use of PolarSSL, as mbed TLS is a development of PolarSSL and PolarSSL itself is not being supported. + Version 3.0d16 – Development Version ---- From f55b1357ba422a4b3b856b9bbf3219ef0dc4de01 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 08:30:31 +0000 Subject: [PATCH 073/135] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e21a17b3..733233db 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,6 @@ Version 3.0d17 – Development Version ---- -Add support for mbed TLS and deprecate the use of PolarSSL, as mbed TLS is a development of PolarSSL and PolarSSL itself is not being supported. +Add support for `mbed TLS` and deprecate the use of `PolarSSL`, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. Version 3.0d16 – Development Version ---- From fbf9757bc53a280fe9b6d76cbc097931f96d26c8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 17:01:23 +0000 Subject: [PATCH 074/135] Update RELEASENOTES.md --- RELEASENOTES.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 733233db..6959bb50 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,9 +1,5 @@ Version 3.0d17 – Development Version ---- -Add support for `mbed TLS` and deprecate the use of `PolarSSL`, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. - -Version 3.0d16 – Development Version ----- Big Update ==== @@ -17,6 +13,8 @@ The most obvious audible change is if you are using software volume control and * 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. * Internal processing including software volume control and interpolation is done after sample size and rate conversion. * Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. +* Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. + Pesky Changes You Cannot Ignore ==== From a1001bf7f4a10b2a68ca141a189079a7fab15df2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 17:08:20 +0000 Subject: [PATCH 075/135] Use mbed TLS instead of PolarSSL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a66719a3..7af4279c 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ The following procedures will install the shairport-sync application into your s To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions. The following libraries are required: -* OpenSSL or PolarSSL +* OpenSSL or mbed TLS (PolarSSL is supported but deprecated) * Avahi * ALSA * libdaemon @@ -73,15 +73,15 @@ The following libraries are required: Optional: * libsoxr -Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or PolarSSL. Libsoxr is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up. +Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up. Debian, Ubuntu and Raspbian users can get the basics with: - `apt-get install build-essential git xmltoman` – these may already be installed. - `apt-get install autoconf automake libtool libdaemon-dev libasound2-dev libpopt-dev libconfig-dev` - `apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended). -- `apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use PolarSSL otherwise. -- `apt-get install libpolarssl-dev` if you want to use PolarSSL, or use OpenSSL/libcrypto otherwise. +- `apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise. +- `apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. (You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported. It is suggested you use mbed TLS instead.) - `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspbian Jessie; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/development/LIBSOXR.md). **Download Shairport Sync:** @@ -102,7 +102,7 @@ $ autoreconf -i -f - `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout). - `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe. - `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15. -- `--with-ssl=openssl` or `--with-ssl=polarssl` for encryption and related utilities using either OpenSSL or PolarSSL. +- `--with-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL. - `--with-soxr` for libsoxr-based resampling. - `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location. - `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. From 5f61e30510406f175eb358b65abda217eada04f1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 18:23:49 +0000 Subject: [PATCH 076/135] Recognise and use deprecated settings and issue warnings. --- audio_alsa.c | 49 +++++++++++++++++++++++++++--------- audio_ao.c | 53 ++++++++++++++++++++++++++++++++------- audio_pipe.c | 53 ++++++++++++++++++++++++++++++++------- audio_stdout.c | 55 ++++++++++++++++++++++++++++++++++------- shairport.c | 67 ++++++++++++++++++++++++++++++++++++-------------- 5 files changed, 219 insertions(+), 58 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 99b70cd3..f34e55be 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -147,36 +147,61 @@ static int init(int argc, char **argv) { if (config.cfg != NULL) { - if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) - inform("The setting audio_backend_buffer_desired_length is no longer used and has been " - "ignored. Use audio_backend_buffer_desired_length_in_seconds instead."); - - /* Get the latency offset. */ - if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) - inform("The setting audio_backend_latency_offset is no longer used and has been ignored. Use " - "audio_backend_latency_offset_in_seconds instead."); + /* Get the desired buffer size setting. */ + if (config_lookup_int(config.cfg, + "alsa.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 66150)) { + inform("The setting alsa.audio_backend_buffer_desired_length is deprecated. " + "Use alsa.audio_backend_buffer_desired_length_in_seconds instead."); + die("Invalid alsa audio backend buffer desired length \"%d\". It " + "should be between 0 and " + "66150, default is 6615", + value); + } else { + inform("The setting alsa.audio_backend_buffer_desired_length is deprecated. " + "Use alsa.audio_backend_buffer_desired_length_in_seconds instead."); + config.audio_backend_buffer_desired_length = 1.0*value/44100; + } + } /* Get the desired buffer size setting. */ if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length_in_seconds", &dvalue)) { - if ((dvalue < 0) || (dvalue > 1.5)) + if ((dvalue < 0) || (dvalue > 1.5)) { die("Invalid alsa audio backend buffer desired time \"%f\". It " "should be between 0 and " "1.5, default is 0.15 seconds", dvalue); - else { + } else { config.audio_backend_buffer_desired_length = dvalue; } } + /* Get the latency offset. */ + if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", + &value)) { + if ((value < -66150) || (value > 66150)) { + inform("The setting alsa.audio_backend_latency_offset is deprecated. " + "Use alsa.audio_backend_latency_offset_in_seconds instead."); + die("Invalid alsa audio backend buffer latency offset \"%d\". It " + "should be between -66150 and +66150, default is 0", + value); + } else { + inform("The setting alsa.audio_backend_latency_offset is deprecated. " + "Use alsa.audio_backend_latency_offset_in_seconds instead."); + config.audio_backend_latency_offset = 1.0*value/44100; + } + } + /* Get the latency offset. */ if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset_in_seconds", &dvalue)) { - if ((dvalue < -1.0) || (dvalue > 1.5)) + if ((dvalue < -1.0) || (dvalue > 1.5)) { die("Invalid alsa audio backend buffer latency offset time \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", dvalue); - else + } else { config.audio_backend_latency_offset = dvalue; + } } /* Get the Output Device Name. */ diff --git a/audio_ao.c b/audio_ao.c index 69867caa..9162e8ba 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -55,25 +55,60 @@ static int init(int argc, char **argv) { if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_float(config.cfg, "ao.audio_backend_buffer_desired_length", &dvalue)) { - if ((dvalue < 0) || (value > 1.5)) - die("Invalid ao audio backend buffer desired length \"%f\". It " + if (config_lookup_int(config.cfg, + "ao.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 66150)) { + inform("The setting ao.audio_backend_buffer_desired_length is deprecated. " + "Use ao.audio_backend_buffer_desired_length_in_seconds instead."); + die("Invalid ao audio backend buffer desired length \"%d\". It " "should be between 0 and " - "1.5, default is 1.0 second", + "66150, default is 6615", + value); + } else { + inform("The setting ao.audio_backend_buffer_desired_length is deprecated. " + "Use ao.audio_backend_buffer_desired_length_in_seconds instead."); + config.audio_backend_buffer_desired_length = 1.0*value/44100; + } + } + + /* Get the desired buffer size setting. */ + if (config_lookup_float(config.cfg, "ao.audio_backend_buffer_desired_length_in_seconds", + &dvalue)) { + if ((dvalue < 0) || (dvalue > 1.5)) { + die("Invalid ao audio backend buffer desired time \"%f\". It " + "should be between 0 and " + "1.5, default is 0.15 seconds", dvalue); - else { + } else { config.audio_backend_buffer_desired_length = dvalue; } } /* Get the latency offset. */ - if (config_lookup_float(config.cfg, "ao.audio_backend_latency_offset", &dvalue)) { - if ((dvalue < -1.0) || (value > 1.5)) - die("Invalid ao audio backend buffer latency offset \"%f\". It " + if (config_lookup_int(config.cfg, "ao.audio_backend_latency_offset", + &value)) { + if ((value < -66150) || (value > 66150)) { + inform("The setting ao.audio_backend_latency_offset is deprecated. " + "Use ao.audio_backend_latency_offset_in_seconds instead."); + die("Invalid ao audio backend buffer latency offset \"%d\". It " + "should be between -66150 and +66150, default is 0", + value); + } else { + inform("The setting ao.audio_backend_latency_offset is deprecated. " + "Use ao.audio_backend_latency_offset_in_seconds instead."); + config.audio_backend_latency_offset = 1.0*value/44100; + } + } + + /* Get the latency offset. */ + if (config_lookup_float(config.cfg, "ao.audio_backend_latency_offset_in_seconds", &dvalue)) { + if ((dvalue < -1.0) || (dvalue > 1.5)) { + die("Invalid ao audio backend buffer latency offset time \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", dvalue); - else + } else { config.audio_backend_latency_offset = dvalue; + } } } diff --git a/audio_pipe.c b/audio_pipe.c index 550893dd..6e535a37 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -85,25 +85,60 @@ static int init(int argc, char **argv) { die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead."); /* Get the desired buffer size setting. */ - if (config_lookup_float(config.cfg, "pipe.audio_backend_buffer_desired_length", &dvalue)) { - if ((dvalue < 0) || (value > 1.5)) - die("Invalid pipe audio backend buffer desired length \"%f\". It " + if (config_lookup_int(config.cfg, + "pipe.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 66150)) { + inform("The setting pipe.audio_backend_buffer_desired_length is deprecated. " + "Use pipe.audio_backend_buffer_desired_length_in_seconds instead."); + die("Invalid pipe audio backend buffer desired length \"%d\". It " "should be between 0 and " - "1.5, default is 1.0 second", + "66150, default is 6615", + value); + } else { + inform("The setting pipe.audio_backend_buffer_desired_length is deprecated. " + "Use pipe.audio_backend_buffer_desired_length_in_seconds instead."); + config.audio_backend_buffer_desired_length = 1.0*value/44100; + } + } + + /* Get the desired buffer size setting. */ + if (config_lookup_float(config.cfg, "pipe.audio_backend_buffer_desired_length_in_seconds", + &dvalue)) { + if ((dvalue < 0) || (dvalue > 1.5)) { + die("Invalid pipe audio backend buffer desired time \"%f\". It " + "should be between 0 and " + "1.5, default is 0.15 seconds", dvalue); - else { + } else { config.audio_backend_buffer_desired_length = dvalue; } } /* Get the latency offset. */ - if (config_lookup_float(config.cfg, "pipe.audio_backend_latency_offset", &dvalue)) { - if ((dvalue < -1.0) || (value > 1.5)) - die("Invalid pipe audio backend buffer latency offset \"%f\". It " + if (config_lookup_int(config.cfg, "pipe.audio_backend_latency_offset", + &value)) { + if ((value < -66150) || (value > 66150)) { + inform("The setting pipe.audio_backend_latency_offset is deprecated. " + "Use pipe.audio_backend_latency_offset_in_seconds instead."); + die("Invalid pipe audio backend buffer latency offset \"%d\". It " + "should be between -66150 and +66150, default is 0", + value); + } else { + inform("The setting pipe.audio_backend_latency_offset is deprecated. " + "Use pipe.audio_backend_latency_offset_in_seconds instead."); + config.audio_backend_latency_offset = 1.0*value/44100; + } + } + + /* Get the latency offset. */ + if (config_lookup_float(config.cfg, "pipe.audio_backend_latency_offset_in_seconds", &dvalue)) { + if ((dvalue < -1.0) || (dvalue > 1.5)) { + die("Invalid pipe audio backend buffer latency offset time \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", dvalue); - else + } else { config.audio_backend_latency_offset = dvalue; + } } } if ((pipename == NULL) && (argc != 1)) diff --git a/audio_stdout.c b/audio_stdout.c index fff9c654..8caac9a2 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -56,26 +56,63 @@ static int init(int argc, char **argv) { if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_float(config.cfg, "stdout.audio_backend_buffer_desired_length", &dvalue)) { - if ((dvalue < 0) || (dvalue > 1.5)) - die("Invalid stdout audio backend buffer desired length \"%f\". It " + if (config_lookup_int(config.cfg, + "stdout.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 66150)) { + inform("The setting audio_backend_buffer_desired_length is deprecated. " + "Use audio_backend_buffer_desired_length_in_seconds instead."); + die("Invalid stdout audio backend buffer desired length \"%d\". It " "should be between 0 and " - "1.5, default is 1.0 second", + "66150, default is 6615", + value); + } else { + inform("The stdout.setting audio_backend_buffer_desired_length is deprecated. " + "Use stdout.audio_backend_buffer_desired_length_in_seconds instead."); + config.audio_backend_buffer_desired_length = 1.0*value/44100; + } + } + + /* Get the desired buffer size setting. */ + if (config_lookup_float(config.cfg, "stdout.audio_backend_buffer_desired_length_in_seconds", + &dvalue)) { + if ((dvalue < 0) || (dvalue > 1.5)) { + die("Invalid stdout audio backend buffer desired time \"%f\". It " + "should be between 0 and " + "1.5, default is 0.15 seconds", dvalue); - else { + } else { config.audio_backend_buffer_desired_length = dvalue; } } /* Get the latency offset. */ - if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset", &dvalue)) { - if ((dvalue < -1.0) || (value > 1.5)) - die("Invalid stdout audio backend buffer latency offset \"%f\". It " + if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", + &value)) { + if ((value < -66150) || (value > 66150)) { + inform("The setting stdout.audio_backend_latency_offset is deprecated. " + "Use stdout.audio_backend_latency_offset_in_seconds instead."); + die("Invalid stdout audio backend buffer latency offset \"%d\". It " + "should be between -66150 and +66150, default is 0", + value); + } else { + inform("The setting stdout.audio_backend_latency_offset is deprecated. " + "Use stdout.audio_backend_latency_offset_in_seconds instead."); + config.audio_backend_latency_offset = 1.0*value/44100; + } + } + + /* Get the latency offset. */ + if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset_in_seconds", &dvalue)) { + if ((dvalue < -1.0) || (dvalue > 1.5)) { + die("Invalid stdout audio backend buffer latency offset time \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", dvalue); - else + } else { config.audio_backend_latency_offset = dvalue; + } } + + } return 0; } diff --git a/shairport.c b/shairport.c index 9b3065a6..6106aca6 100644 --- a/shairport.c +++ b/shairport.c @@ -2,7 +2,7 @@ * Shairport, an Apple Airplay receiver * Copyright (c) James Laird 2013 * All rights reserved. - * Modifications (c) Mike Brady 2014--2016 + * Modifications (c) Mike Brady 2014--2017 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -197,12 +197,12 @@ void usage(char *progname) { printf(" -v, --verbose -v print debug information; -vv more; -vvv lots.\n"); printf(" -p, --port=PORT set RTSP listening port.\n"); printf(" -a, --name=NAME set advertised name.\n"); - printf(" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an " - "AirPlay device.\n"); - printf(" The default is to set it automatically.\n"); - printf(" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes " - "10 or later.\n"); - printf(" The default is to set it automatically.\n"); +// printf(" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an " +// "AirPlay device.\n"); +// printf(" The default is to set it automatically.\n"); +// printf(" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes " +// "10 or later.\n"); +// printf(" The default is to set it automatically.\n"); printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown " "device.\n"); printf(" The default is to set it automatically.\n"); @@ -229,15 +229,14 @@ void usage(char *progname) { printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service.\n"); printf(" if no mdns provider is specified,\n"); printf(" shairport tries them all until one works.\n"); - printf(" -r, --resync=THRESHOLD resync if timing error exceeds this number of seconds. Set " - "to 0 to " + printf(" -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. Set to 0 to " "stop resyncing.\n"); printf(" -t, --timeout=SECONDS go back to idle mode from play mode after a break in " "communications of this many seconds (default 120). Set to 0 never to exit play mode.\n"); printf(" --statistics print some interesting statistics -- output to the logfile " "if running as a daemon.\n"); - printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE seconds (default " - "0.002) before trying to correct it.\n"); + printf(" --tolerance=TOLERANCE [Deprecated] allow a synchronization error of TOLERANCE frames (default " + "88) before trying to correct it.\n"); printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a " "password.\n"); #ifdef CONFIG_METADATA @@ -259,6 +258,8 @@ int parse_options(int argc, char **argv) { char *stuffing = NULL; /* used for picking up the stuffing option */ signed char c; /* used for argument parsing */ int i = 0; /* used for tracking options */ + int fResyncthreshold = (int)config.resyncthreshold*44100; + int fTolerance = (int)config.tolerance*44100; poptContext optCon; /* context for parsing command-line options */ struct poptOption optionsTable[] = { {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL}, @@ -279,12 +280,12 @@ int parse_options(int argc, char **argv) { {"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL}, {"AirPlayLatency", 'A', POPT_ARG_INT, &config.AirPlayLatency, 0, NULL}, {"iTunesLatency", 'i', POPT_ARG_INT, &config.iTunesLatency, 0, NULL}, - {"forkedDaapdLatency", 0, POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL}, + {"forkedDaapdLatency", 'f', POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL}, {"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL}, - {"resync", 'r', POPT_ARG_DOUBLE, &config.resyncthreshold, 0, NULL}, + {"resync", 'r', POPT_ARG_INT, &fResyncthreshold, 0, NULL}, {"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL}, {"password", 0, POPT_ARG_STRING, &config.password, 0, NULL}, - {"tolerance", 0, POPT_ARG_DOUBLE, &config.tolerance, 0, NULL}, + {"tolerance", 'z', POPT_ARG_INT, &fTolerance, 0, NULL}, #ifdef CONFIG_METADATA {"metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL}, {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL}, @@ -309,11 +310,35 @@ int parse_options(int argc, char **argv) { case 'v': debuglev++; break; - } + case 'D': + inform("Warning: the option -D or --disconnectFromOutput is deprecated."); + break; + case 'R': + inform("Warning: the option -R or --reconnectToOutput is deprecated."); + break; + case 'A': + inform("Warning: the option -A or --AirPlayLatency is deprecated. This setting is now automatically received from the AirPlay device."); + break; + case 'i': + inform("Warning: the option -i or --iTunesLatency is deprecated. This setting is now automatically received from iTunes"); + break; + case 'f': + inform("Warning: the option --forkedDaapdLatency is deprecated. This setting is now automatically received from forkedDaapd"); + break; + case 'r': + inform("Warning: the option -r or --resync is deprecated. Please use the \"resync_threshold_in_seconds\" setting in the config file instead."); + break; + case 'z': + inform("Warning: the option --tolerance is deprecated. Please use the \"drift_tolerance_in_seconds\" setting in the config file instead."); + break; + } } if (c < -1) { die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); } + + config.resyncthreshold = 1.0*fResyncthreshold/44100; + config.tolerance = 1.0*fTolerance/44100; config_setting_t *setting; const char *str = 0; @@ -408,14 +433,18 @@ int parse_options(int argc, char **argv) { } /* The old drift tolerance setting. */ - if (config_lookup_int(config.cfg, "general.drift", &value)) - inform("The drift setting is deprecated and has been ignored. Use " + if (config_lookup_int(config.cfg, "general.drift", &value)) { + inform("The drift setting is deprecated. Use " "drift_tolerance_in_seconds instead"); + config.tolerance = 1.0*value/44100; + } /* The old resync setting. */ - if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) - inform("The resync_threshold setting is deprecated and has been ignored. Use " + if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) { + inform("The resync_threshold setting is deprecated. Use " "resync_threshold_in_seconds instead"); + config.resyncthreshold = 1.0*value/44100; + } /* Get the drift tolerance setting. */ if (config_lookup_float(config.cfg, "general.drift_tolerance_in_seconds", &dvalue)) From 07641995451bb00b75794092c8e499c003d3ed8a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 18:34:09 +0000 Subject: [PATCH 077/135] Add possibility of using an array of precalculated pseudo random numbers to reduce processor load. --- common.c | 2 ++ common.h | 4 ++++ player.c | 2 ++ shairport.c | 4 ++++ 4 files changed, 12 insertions(+) diff --git a/common.c b/common.c index d2eb17e4..cf1138a1 100644 --- a/common.c +++ b/common.c @@ -757,6 +757,8 @@ uint64_t ranarrayval() { ranarraynext = (ranarraynext++)%ranarraylength; } +void r64arrayinit() { ranarrayinit(); } + uint64_t ranarray64u() { return (ranarrayval()); } int64_t ranarray64i() { return (ranarrayval(&rx) >> 1); } diff --git a/common.h b/common.h index e52f82d2..e15edf1a 100644 --- a/common.h +++ b/common.h @@ -152,6 +152,10 @@ void r64init(uint64_t seed); uint64_t r64u(); int64_t r64i(); +void r64arrayinit(); +uint64_t ranarray64u(); +int64_t ranarray64i(); + int debuglev; void die(char *format, ...); void warn(char *format, ...); diff --git a/player.c b/player.c index edc3dfbf..edf2f4b0 100644 --- a/player.c +++ b/player.c @@ -677,6 +677,8 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t } dither_mask -= 1; int64_t r = r64i(); + // int64_t r = ranarray64i(); + int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask); previous_random_number = r; // add dither, allowing for clipping diff --git a/shairport.c b/shairport.c index 6106aca6..4afd8e5d 100644 --- a/shairport.c +++ b/shairport.c @@ -858,6 +858,10 @@ int main(int argc, char **argv) { // initialise random number generator r64init(0); + + // initialise the randomw number array + + r64arrayinit(); /* Check if we are called with -V or --version parameter */ if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { From fe309090010f15673579d269d805a576e586c539 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 18:38:19 +0000 Subject: [PATCH 078/135] Add nice deprecations --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6959bb50..611569da 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 3.0d17 – Development Version +Version 3.0d18 – Development Version ---- Big Update @@ -14,7 +14,7 @@ The most obvious audible change is if you are using software volume control and * Internal processing including software volume control and interpolation is done after sample size and rate conversion. * Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. * Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. - +* Settings that were denominated in frames are now deprecated but still honoured. Deprecation warnings are issued. Pesky Changes You Cannot Ignore ==== From 5afa185b570260ce35d2c4a1391b8d764d29ade3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 18:39:50 +0000 Subject: [PATCH 079/135] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 62e6bc83..0034abb8 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0d17], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0d18], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From d9811e37f616e5b0dd5c116bd6f2007fb91cd405 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 18:53:16 +0000 Subject: [PATCH 080/135] Include some information about Apple ALAC --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7af4279c..bfbbc0b5 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ What else? -------------- * Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range. * Hardware Mute — Shairport Sync will mute properly if the hardware supports it. +* Support for the Apple ALAC decoder. * Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds. * Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) * Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. @@ -72,6 +73,7 @@ The following libraries are required: Optional: * libsoxr +* libalac (This is a library containing the Apple ALAC decoder.) Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up. @@ -84,6 +86,8 @@ Debian, Ubuntu and Raspbian users can get the basics with: - `apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. (You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported. It is suggested you use mbed TLS instead.) - `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspbian Jessie; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/development/LIBSOXR.md). +If you wish to include the Apple ALAC decoder, you need install it first -- please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information. + **Download Shairport Sync:** `git clone https://github.com/mikebrady/shairport-sync.git` @@ -110,6 +114,7 @@ $ autoreconf -i -f - `--with-systemd` to install a systemd service description at the `make install` stage. Default is not to to install. - `--with-configfile` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten. - `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use. +- `--with-apple-alac` to include the Apple ALAC Decoder. **Determine if it's a `systemd` or a "System V" installation:** From 49239c3842ad13be6c746a063c46f14746b207a3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 21:05:52 +0000 Subject: [PATCH 081/135] Use a precalculated array of pseudorandom numbers to do dither, reducing runtime load on low-powered processors. --- player.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index edf2f4b0..295b815a 100644 --- a/player.c +++ b/player.c @@ -676,8 +676,8 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t break; } dither_mask -= 1; - int64_t r = r64i(); - // int64_t r = ranarray64i(); + // int64_t r = r64i(); + int64_t r = ranarray64i(); // use an array of precalculated pseudorandom numbers rather than calculating them on the fly. Should be easier on low-powered processors int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask); previous_random_number = r; From 16c3ba5e9ebb36281eb37003443cfa5a2ed0b24a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 21:11:39 +0000 Subject: [PATCH 082/135] Update RELEASENOTES.md --- RELEASENOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 611569da..82f3137e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,9 @@ +Version 3.0d19 – Development Version +---- + +**New Feature** +* Reduces processor load back to V2.X levels by using a precalculated array of pseudorandom numbers to do dithering. Doesn't seem to make any audible difference. + Version 3.0d18 – Development Version ---- From 83416b680f3687b83edbe1e9dc5211b31a08bdef Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 21:13:32 +0000 Subject: [PATCH 083/135] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0034abb8..56c1a97f 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0d18], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0d19], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From a6029474cf8c93a3c4a38f495d56ac70457fd11b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 21:20:58 +0000 Subject: [PATCH 084/135] Update .travis.yml --- .travis.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index aded32f9..c776a74a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,26 +7,10 @@ env: - CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemv" - CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemd" - CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-stdio --with-avahi --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-stdio --with-avahi --with-ssl=polarssl --with-metadata --with-systemd" - - CFG="--with-pipe --with-avahi --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-pipe --with-avahi --with-ssl=polarssl --with-metadata --with-systemd" - - CFG="--with-ao --with-avahi --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-ao --with-avahi --with-ssl=polarssl --with-metadata --with-systemd" - CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemv" - CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemv" - CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemd" - CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-stdio --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-stdio --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd" - - CFG="--with-pipe --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-pipe --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd" - - CFG="--with-ao --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-ao --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd" script: - autoreconf -fi From befee9606e3a2d045f8c2eff6e42d68b82d0eeaf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 21:30:00 +0000 Subject: [PATCH 085/135] Try if it's got libmbedtls-dev --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c776a74a..36a216d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ script: before_install: - sudo apt-get -qq update - - sudo apt-get install -y libdaemon-dev avahi-daemon libavahi-client-dev + - sudo apt-get install -y libdaemon-dev avahi-daemon libavahi-client-dev addons: apt: @@ -32,3 +32,4 @@ addons: - libconfig-dev - xmltoman - libao-dev + - libmbedtls-dev From 0cb76648c8b34049f4e82a880ea6dee051da2c21 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 22:26:20 +0000 Subject: [PATCH 086/135] Only check systemd stuff if systemd has been asked for. --- configure.ac | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/configure.ac b/configure.ac index 56c1a97f..27947027 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0d19], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0d20], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) @@ -246,21 +246,25 @@ AC_ARG_WITH(dns_sd, [ --with-dns_sd = choose dns_sd mDNS support], [ AC_SEARCH_LIBS([DNSServiceRefDeallocate], [dns_sd], , AC_MSG_ERROR(dns_sd support requires the dns_sd library!))], ) AM_CONDITIONAL([USE_DNS_SD], [test "x$HAS_DNS_SD" = "x1"]) -# Find systemd unit dir -AC_ARG_WITH([systemdsystemunitdir], - [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],, - [with_systemdsystemunitdir=auto]) -AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [ - def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) +if test "x${with_systemd}" = xyes ; then + # Find systemd unit dir + AC_ARG_WITH([systemdsystemunitdir], + [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],, + [with_systemdsystemunitdir=auto]) + AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [ + def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) - AS_IF([test "x$def_systemdsystemunitdir" = "x"], - [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], - [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) - with_systemdsystemunitdir=no], - [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) -AS_IF([test "x$with_systemdsystemunitdir" != "xno"], - [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) -AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) + AS_IF([test "x$def_systemdsystemunitdir" = "x"], + [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], + [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) + with_systemdsystemunitdir=no], + [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) + AS_IF([test "x$with_systemdsystemunitdir" != "xno"], + [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) + AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) +else + AM_CONDITIONAL([HAVE_SYSTEMD], false) +fi # Look for xmltoman AC_CHECK_PROGS([XMLTOMAN], [xmltoman]) From 957249f5f905496faf8188107035f299d2a8d370 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 22:55:58 +0000 Subject: [PATCH 087/135] Fix a bug whereby the resync time would be zero, meaning no resync would occur. --- shairport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shairport.c b/shairport.c index 4afd8e5d..ee69f8af 100644 --- a/shairport.c +++ b/shairport.c @@ -258,8 +258,8 @@ int parse_options(int argc, char **argv) { char *stuffing = NULL; /* used for picking up the stuffing option */ signed char c; /* used for argument parsing */ int i = 0; /* used for tracking options */ - int fResyncthreshold = (int)config.resyncthreshold*44100; - int fTolerance = (int)config.tolerance*44100; + int fResyncthreshold = (int)(config.resyncthreshold*44100); + int fTolerance = (int)(config.tolerance*44100); poptContext optCon; /* context for parsing command-line options */ struct poptOption optionsTable[] = { {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL}, From 0a76c2d41e963d76f9d2adf44db5e1522b07ce13 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 22:56:52 +0000 Subject: [PATCH 088/135] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 27947027..b20165c3 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0d20], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0d21], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From e8dcd8b94354fa8ed26c2571805eefb69ff820bc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 22:59:23 +0000 Subject: [PATCH 089/135] Update RELEASENOTES.md --- RELEASENOTES.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 82f3137e..45e22432 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,15 @@ +Version 3.0d21 – Development Version +---- + +**Bug Fix** +* Fixed a bug which turned off resync by default. Duh. + +Version 3.0d20 – Development Version +---- + +**Bug Fix** +* Fix a small and generally silent error in configure.ac so that it only looks for the systemd direcotry if systemd has been chosen. It caused a warning when cross-compiling. + Version 3.0d19 – Development Version ---- From 28789069d75a89f8e7e687dc7ee9f12ceae75522 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jan 2017 23:02:07 +0000 Subject: [PATCH 090/135] Update .travis.yml --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 36a216d2..37c4fd66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,4 +32,3 @@ addons: - libconfig-dev - xmltoman - libao-dev - - libmbedtls-dev From 73e504ac2b2361a5dfb5611c6c8d100cc1c72e56 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 31 Jan 2017 16:06:00 +0000 Subject: [PATCH 091/135] Ensure apple_alac and the associated libstdc++ is not included unless apple_alac is selected. --- player.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/player.c b/player.c index 295b815a..df6e515e 100644 --- a/player.c +++ b/player.c @@ -72,7 +72,9 @@ #include "alac.h" +#ifdef HAVE_APPLE_ALAC #include "apple_alac.h" +#endif // parameters from the source static unsigned char *aesiv; From 6d454c120774a1a4d5c53f844242046787d70a12 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 31 Jan 2017 16:06:13 +0000 Subject: [PATCH 092/135] Ensure apple_alac and the associated libstdc++ is not included unless apple_alac is selected. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index b20165c3..d8850b69 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0d21], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0d22], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 1c82b464c296f92fc8c778b16945254ce686346d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 31 Jan 2017 16:11:52 +0000 Subject: [PATCH 093/135] Update RELEASENOTES.md --- RELEASENOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 45e22432..960a07dc 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,9 @@ +Version 3.0d22 – Development Version +---- + +**Bug Fix** +* Fixed a bug which prevented successful building in the OpenWrt toolchain. The problem was caused by an unconditional inclusion of a reference to apple_alac.h in player.c whether apple_alac was selected or not. This caused the OpenWrt build system to expect the standard C++ library to be referenced, and it was not there. Solution was to make the #include conditional on apple_alac having been selected. + Version 3.0d21 – Development Version ---- From 080e07217f287382df4a8f0567f2c0efb95d8ea8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 31 Jan 2017 16:19:08 +0000 Subject: [PATCH 094/135] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 960a07dc..3b5af0ae 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,7 @@ Version 3.0d22 – Development Version ---- **Bug Fix** -* Fixed a bug which prevented successful building in the OpenWrt toolchain. The problem was caused by an unconditional inclusion of a reference to apple_alac.h in player.c whether apple_alac was selected or not. This caused the OpenWrt build system to expect the standard C++ library to be referenced, and it was not there. Solution was to make the #include conditional on apple_alac having been selected. +* Fixed a bug which prevented successful building in the OpenWrt toolchain. The problem was caused by an unconditional inclusion of a reference to apple_alac.h in player.c whether apple_alac was selected or not. This caused the OpenWrt build system to expect the standard C++ library to be referenced, and it was not there. Solution was to make the `#include` conditional on apple_alac having been selected. Version 3.0d21 – Development Version ---- From 5c42d8203ffc6fb5c24f8eb405a3377d0302cea1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 31 Jan 2017 16:25:36 +0000 Subject: [PATCH 095/135] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3b5af0ae..9522b88e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,7 @@ Version 3.0d22 – Development Version ---- **Bug Fix** -* Fixed a bug which prevented successful building in the OpenWrt toolchain. The problem was caused by an unconditional inclusion of a reference to apple_alac.h in player.c whether apple_alac was selected or not. This caused the OpenWrt build system to expect the standard C++ library to be referenced, and it was not there. Solution was to make the `#include` conditional on apple_alac having been selected. +* Fixed a bug which prevented successful building in the OpenWrt build system. The problem was caused by an `#include apple_alac.h` in `player.c` which was actioned even if the apple alac decoder was not selected. This caused the OpenWrt build system to expect the standard C++ library – required by the apple alac code – to be referenced, but it was not specified on the build manifest and therefore stopped the build. The solution was to make the `#include` conditional on selecting the apple alac decoder. Version 3.0d21 – Development Version ---- From 2cab35cd55eb30c28551927ea7a6435603763889 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 13:47:01 +0000 Subject: [PATCH 096/135] Add a new setting to specify the interface the service should be provided on. --- common.h | 2 ++ mdns_avahi.c | 9 ++++++-- mdns_tinysvcmdns.c | 52 ++++++++++++++++++++++++++-------------------- shairport.c | 46 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 82 insertions(+), 27 deletions(-) diff --git a/common.h b/common.h index e15edf1a..d84de86d 100644 --- a/common.h +++ b/common.h @@ -123,6 +123,8 @@ typedef struct { char *configfile; char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by // default “_raop._tcp.”. + char *interface; // a string containg the interface name, or NULL if nothing specified + int interface_index; // only valid if the interface string is non-NULL double audio_backend_buffer_desired_length; // this will be the length in seconds of the // audio backend buffer -- the DAC buffer for ALSA double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any diff --git a/mdns_avahi.c b/mdns_avahi.c index b9664674..d8fef9a9 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -204,16 +204,21 @@ static void register_service(AvahiClient *c) { return; int ret; + AvahiIfIndex selected_interface; + if (config.interface!=NULL) + selected_interface = config.interface_index; + else + selected_interface = AVAHI_IF_UNSPEC; #ifdef CONFIG_METADATA if (config.metadata_enabled) { - ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, + ret = avahi_entry_group_add_service(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, name, config.regtype, NULL, NULL, port, MDNS_RECORD_WITH_METADATA, NULL); if (ret == 0) debug(1, "avahi: request to add \"%s\" service with metadata", config.regtype); } else { #endif - ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, + ret = avahi_entry_group_add_service(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, name, config.regtype, NULL, NULL, port, MDNS_RECORD_WITHOUT_METADATA, NULL); if (ret == 0) diff --git a/mdns_tinysvcmdns.c b/mdns_tinysvcmdns.c index 8d51d4bf..57f41ae9 100644 --- a/mdns_tinysvcmdns.c +++ b/mdns_tinysvcmdns.c @@ -70,17 +70,21 @@ static int mdns_tinysvcmdns_register(char *apname, int port) { // Look for an ipv4/ipv6 non-loopback interface to use as the main one. for (ifa = ifalist; ifa != NULL; ifa = ifa->ifa_next) { - if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) { - uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; + // only check for the named interface, if specified + if ((config.interface==NULL) || (strcmp(config.interface,ifa->ifa_name)==0)) { + + if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) { + uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; - mdnsd_set_hostname(svr, hostname, main_ip); // TTL should be 120 seconds - break; - } else if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && - ifa->ifa_addr->sa_family == AF_INET6) { - struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; + mdnsd_set_hostname(svr, hostname, main_ip); // TTL should be 120 seconds + break; + } else if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && + ifa->ifa_addr->sa_family == AF_INET6) { + struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; - mdnsd_set_hostname_v6(svr, hostname, addr); // TTL should be 120 seconds - break; + mdnsd_set_hostname_v6(svr, hostname, addr); // TTL should be 120 seconds + break; + } } } @@ -92,20 +96,22 @@ static int mdns_tinysvcmdns_register(char *apname, int port) { // Skip the first one, it was already added by set_hostname for (ifa = ifa->ifa_next; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_flags & IFF_LOOPBACK) // Skip loop-back interfaces - continue; - - switch (ifa->ifa_addr->sa_family) { - case AF_INET: { // ipv4 - uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; - struct rr_entry *a_e = rr_create_a(create_nlabel(hostname), ip); // TTL should be 120 seconds - mdnsd_add_rr(svr, a_e); - } break; - case AF_INET6: { // ipv6 - struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; - struct rr_entry *aaaa_e = - rr_create_aaaa(create_nlabel(hostname), addr); // TTL should be 120 seconds - mdnsd_add_rr(svr, aaaa_e); - } break; + continue; + // only check for the named interface, if specified + if ((config.interface==NULL) || (strcmp(config.interface,ifa->ifa_name)==0)) { + switch (ifa->ifa_addr->sa_family) { + case AF_INET: { // ipv4 + uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; + struct rr_entry *a_e = rr_create_a(create_nlabel(hostname), ip); // TTL should be 120 seconds + mdnsd_add_rr(svr, a_e); + } break; + case AF_INET6: { // ipv6 + struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; + struct rr_entry *aaaa_e = + rr_create_aaaa(create_nlabel(hostname), addr); // TTL should be 120 seconds + mdnsd_add_rr(svr, aaaa_e); + } break; + } } } diff --git a/shairport.c b/shairport.c index ee69f8af..5a147005 100644 --- a/shairport.c +++ b/shairport.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include "config.h" @@ -490,7 +491,45 @@ int parse_options(int argc, char **argv) { die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", " "\"reverse stereo\", \"both left\", \"both right\""); } + + + + /* Get the interface to listen on, if specified Default is all interfaces */ + /* we keep the interface name and the index */ + + if (config_lookup_string(config.cfg, "general.interface", &str)) + config.interface = strdup(str); + + if (config_lookup_string(config.cfg, "general.interface", &str)) { + int specified_interface_found = 0; + + struct if_nameindex *if_ni, *i; + + if_ni = if_nameindex(); + if (if_ni == NULL) { + debug(1,"Can't get a list of interface names."); + } else { + for (i = if_ni; ! (i->if_index == 0 && i->if_name == NULL); i++) { + // printf("%u: %s\n", i->if_index, i->if_name); + if (strcmp(i->if_name,str)==0) { + config.interface_index = i->if_index; + specified_interface_found = 1; + } + } + + } + + if_freenameindex(if_ni); + + if (specified_interface_found==0) { + inform("The mdns service interface \"%s\" was not found, so the setting has been ignored.",config.interface); + free(config.interface); + config.interface = NULL; + config.interface_index = 0; + } + } + /* Get the regtype -- the service type and protocol, separated by a dot. Default is * "_raop._tcp" */ if (config_lookup_string(config.cfg, "general.regtype", &str)) @@ -762,7 +801,6 @@ void shairport_startup_complete(void) { const char *pid_file_proc(void) { #ifdef HAVE_ASPRINTF static char *fn = NULL; - free(fn); asprintf(&fn, "%s/%s.pid", PIDDIR, daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); #else static char fn[8192]; @@ -777,6 +815,7 @@ const char *pid_file_proc(void) { void exit_function() { if (config.cfg) config_destroy(config.cfg); + // probably should be freeing malloc'ed memory here, including strdup-created strings... } int main(int argc, char **argv) { @@ -1146,7 +1185,10 @@ int main(int argc, char **argv) { debug(1, "zeroconf regtype is \"%s\".", config.regtype); debug(1, "decoders_supported field is %d.", config.decoders_supported); debug(1, "use_apple_decoder is %d.", config.use_apple_decoder); - + if (config.interface) + debug(1, "mdns service interface \"%s\" requested.",config.interface); + else + debug(1, "no special mdns service interface was requested."); char *realConfigPath = realpath(config.configfile, NULL); if (realConfigPath) { debug(1, "configuration file name \"%s\" resolves to \"%s\".", config.configfile, From 09369e6507707c18a0c8d6aa3592c0d48afc82e6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 13:47:35 +0000 Subject: [PATCH 097/135] Add a new setting to specify the interface the service should be provided on. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d8850b69..5cd0ea4d 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0d22], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0d23], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From b1723316ac197070c3a369c72eac7b0e88d9b119 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 13:57:04 +0000 Subject: [PATCH 098/135] Update RELEASENOTES.md --- RELEASENOTES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9522b88e..d03a25e1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,10 @@ +Version 3.0d23 – Development Version +---- +Version 3 is a big update -- please see below. + +**New Feature** +* Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. + Version 3.0d22 – Development Version ---- From 61caf4d24426ed006b8cbe839184698973e6c91f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 13:59:31 +0000 Subject: [PATCH 099/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 3a87f1f2..271d2b3e 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -25,7 +25,7 @@ general = // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. // volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. -// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". +// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". // playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". // alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose // the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. From c59a261128ca422ccd16e0b3a45ccf50119befee Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 14:00:10 +0000 Subject: [PATCH 100/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 271d2b3e..78e62b25 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -23,12 +23,12 @@ general = // drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it // resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. -// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. -// volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. -// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". -// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". -// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose -// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. +// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. +// volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. +// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". +// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". +// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose +// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. }; // How to deal with metadata, including artwork From b83bdc48fdf42b7aeac214003cb9152b88261908 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 14:03:41 +0000 Subject: [PATCH 101/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 78e62b25..ef5b7aa7 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -29,6 +29,7 @@ general = // playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". // alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose // the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. +// interface = "name"; // Use this optional advanced setting to specify on which interface Shairport Sync should provide its service. Leave it commented out the setting to get the default, which is to select the interface(s) automatically. }; // How to deal with metadata, including artwork From 3567b2c4c788a0fbd702fe9ea60e8358747d39a3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 14:06:43 +0000 Subject: [PATCH 102/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index ef5b7aa7..37f9707e 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -25,6 +25,7 @@ general = // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. // volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. +// volume_max_db = 0 ; // use this to set the maximum volume, in dB, you wish to use, which must be in the attenuator's range (0 – -98 for software attenuator). Leave it commented out to use mixer's maximum volume. // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". // playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". // alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose From afa2d1c61b71671c2bce622596d2ec3d1b385349 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 14:07:43 +0000 Subject: [PATCH 103/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 37f9707e..ff5e87fd 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -24,7 +24,6 @@ general = // resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. -// volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. // volume_max_db = 0 ; // use this to set the maximum volume, in dB, you wish to use, which must be in the attenuator's range (0 – -98 for software attenuator). Leave it commented out to use mixer's maximum volume. // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". // playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". From 2a4a4ed22b72be845d1ab95102b3132bb3423133 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 16:18:05 +0000 Subject: [PATCH 104/135] Add volume_max_db setting which works on the hw mixer if it exists or the sw attenuator otherwise. --- common.c | 2 +- common.h | 2 ++ player.c | 47 +++++++++++++++++++++++++++++++++++++---------- shairport.c | 10 +++++++--- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/common.c b/common.c index cf1138a1..30b5e6f8 100644 --- a/common.c +++ b/common.c @@ -559,7 +559,7 @@ double vol2attn(double vol, long max_db, long min_db) { double vol_setting = 0; if ((vol <= 0.0) && (vol >= -30.0)) { - long range_db = max_db - min_db; // this will be a positive nunmber + long range_db = max_db - min_db; // this will be a positive number // debug(1,"Volume min %ddB, max %ddB, range %ddB.",min_db,max_db,range_db); // double first_slope = -3000.0; // this is the slope of the attenuation at the high end -- 30dB // for the full rotation. diff --git a/common.h b/common.h index d84de86d..af965e92 100644 --- a/common.h +++ b/common.h @@ -87,6 +87,8 @@ typedef struct { int udp_port_base; int udp_port_range; int ignore_volume_control; + int volume_max_db_set; // set to 1 if a maximum volume db has been set + int volume_max_db; int no_sync; // disable synchronisation, even if it's available int no_mmap; // disable use of mmap-based output, even if it's available double resyncthreshold; // if it get's out of whack my more than this number of seconds, resync. diff --git a/player.c b/player.c index df6e515e..c121969f 100644 --- a/player.c +++ b/player.c @@ -2259,16 +2259,6 @@ void player_volume(double airplay_volume) { int32_t hw_min_db, hw_max_db, hw_range_db, range_to_use, min_db, max_db; // hw_range_db is a flag; if 0 means no mixer - int32_t sw_min_db = -9630; - int32_t sw_max_db = 0; - int32_t sw_range_db = sw_max_db - sw_min_db; - int32_t desired_range_db; // this is used as a flag; if 0 means no desired range - - if (config.volume_range_db) - desired_range_db = (int32_t)trunc(config.volume_range_db * 100); - else - desired_range_db = 0; - if (config.output->parameters) { // have a hardware mixer config.output->parameters(&audio_information); @@ -2279,7 +2269,34 @@ void player_volume(double airplay_volume) { // don't have a hardware mixer hw_max_db = hw_min_db = hw_range_db = 0; } + + int32_t sw_min_db = -9630; + int32_t sw_max_db = 0; + int32_t sw_range_db = sw_max_db - sw_min_db; + int32_t desired_range_db = 0; // this is used as a flag; if 0 means no desired range + if (config.volume_range_db) + desired_range_db = (int32_t)trunc(config.volume_range_db * 100); + + if (config.volume_max_db_set) { + if (hw_range_db) { + if (((config.volume_max_db*100)hw_min_db)) { + hw_max_db = (int)config.volume_max_db*100; + hw_range_db = hw_max_db-hw_min_db; + } else { + inform("The volume_max_db setting is out of range of the hardware mixers's limits of %d dB to %d dB. It will be ignored.", (int)(hw_max_db/100), (int)(hw_min_db/100)); + } + } else { + if (((config.volume_max_db*100)sw_min_db)) { + sw_max_db = (int)config.volume_max_db*100; + sw_range_db = sw_max_db-sw_min_db; + } else { + inform("The volume_max_db setting is out of range of the software attenuation's limits of 0 dB to -96.3 dB. It will be ignored."); + } + } + } + + if (desired_range_db) { // debug(1,"An attenuation range of %d is requested.",desired_range_db); // we have a desired volume range. @@ -2322,6 +2339,16 @@ void player_volume(double airplay_volume) { } } +/* + if (config.volume_max_db_set) { + if ((config.volume_max_db*100<=max_db) && (config.volume_max_db*100>=min_db)) { + debug(1,"Reducing the maximum volume from %d to %d.",max_db/100,config.volume_max_db); + max_db = (int)(config.volume_max_db*100); + } else { + inform("The value of volume_max_db is invalid. It must be in the range %d to %d.",max_db,min_db); + } + } +*/ double hardware_attenuation, software_attenuation; double scaled_attenuation = hw_min_db + sw_min_db; diff --git a/shairport.c b/shairport.c index 5a147005..41520afe 100644 --- a/shairport.c +++ b/shairport.c @@ -474,6 +474,13 @@ int parse_options(int argc, char **argv) { else die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } + + /* Get the optional volume_max_db setting. */ + if (config_lookup_float(config.cfg, "general.volume_max_db", &dvalue)) { + debug(1,"Max volume setting of %f dB",dvalue); + config.volume_max_db = dvalue; + config.volume_max_db_set = 1; + } /* Get the playback_mode setting */ if (config_lookup_string(config.cfg, "general.playback_mode", &str)) { @@ -491,9 +498,6 @@ int parse_options(int argc, char **argv) { die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", " "\"reverse stereo\", \"both left\", \"both right\""); } - - - /* Get the interface to listen on, if specified Default is all interfaces */ /* we keep the interface name and the index */ From 5fe78148b019f39a6bbad303f105078934af9178 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 16:18:33 +0000 Subject: [PATCH 105/135] Add volume_max_db setting which works on the hw mixer if it exists or the sw attenuator otherwise. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5cd0ea4d..cea7492f 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0d23], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.0d24], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 4547d08716cfdd6c4bbde1d449b91987c26cb171 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 16:25:40 +0000 Subject: [PATCH 106/135] Update RELEASENOTES.md --- RELEASENOTES.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d03a25e1..8217e3f4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,9 +1,15 @@ -Version 3.0d23 – Development Version +Version 3.0d24 – Development Version ---- Version 3 is a big update -- please see below. **New Feature** -* Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. +* Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3. + +Version 3.0d23 – Development Version +---- + +**New Feature** +* Choose Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. Version 3.0d22 – Development Version ---- From f7f2a6bfea17480086d3c6a0cb446fcb10316eea Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 16:33:22 +0000 Subject: [PATCH 107/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index ff5e87fd..caae9ae3 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -24,7 +24,10 @@ general = // resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. -// volume_max_db = 0 ; // use this to set the maximum volume, in dB, you wish to use, which must be in the attenuator's range (0 – -98 for software attenuator). Leave it commented out to use mixer's maximum volume. +// volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. +// volume_max_db = 0.0 ; // use this, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use. +// The setting is for the hardware mixer, if chosen, or the software mixer otherwise. The value must be in the mixer's range (0.0 to -96.2 for the software mixer). +// Leave it commented out to use mixer's maximum volume. // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". // playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". // alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose From 51b5544b73d610276a289e4753668da0949a2cc2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 16:34:21 +0000 Subject: [PATCH 108/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index caae9ae3..77dddbd0 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -24,7 +24,7 @@ general = // resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. -// volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. +// volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. // volume_max_db = 0.0 ; // use this, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use. // The setting is for the hardware mixer, if chosen, or the software mixer otherwise. The value must be in the mixer's range (0.0 to -96.2 for the software mixer). // Leave it commented out to use mixer's maximum volume. From ed8bae79280f4c5cb2df15713efee0d249c71c98 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 16:35:19 +0000 Subject: [PATCH 109/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 77dddbd0..f6ac4253 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -26,8 +26,8 @@ general = // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. // volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. // volume_max_db = 0.0 ; // use this, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use. -// The setting is for the hardware mixer, if chosen, or the software mixer otherwise. The value must be in the mixer's range (0.0 to -96.2 for the software mixer). -// Leave it commented out to use mixer's maximum volume. +// The setting is for the hardware mixer, if chosen, or the software mixer otherwise. The value must be in the mixer's range (0.0 to -96.2 for the software mixer). +// Leave it commented out to use mixer's maximum volume. // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". // playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". // alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose From 29cbdcd0c2281b01571452ac58dae8cc6dc9ce40 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 16:37:44 +0000 Subject: [PATCH 110/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index f6ac4253..7b2a82a1 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -24,15 +24,15 @@ general = // resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. -// volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. -// volume_max_db = 0.0 ; // use this, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use. +// volume_range_db = 60 ; // use this advanced setting to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. +// volume_max_db = 0.0 ; // use this advanced setting, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use. // The setting is for the hardware mixer, if chosen, or the software mixer otherwise. The value must be in the mixer's range (0.0 to -96.2 for the software mixer). // Leave it commented out to use mixer's maximum volume. // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". // playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". // alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose // the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. -// interface = "name"; // Use this optional advanced setting to specify on which interface Shairport Sync should provide its service. Leave it commented out the setting to get the default, which is to select the interface(s) automatically. +// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically. }; // How to deal with metadata, including artwork From 7e831237e88fc578bef8951b9b8f37970c34038f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Feb 2017 17:20:35 +0000 Subject: [PATCH 111/135] Include interface and max_volume_db in the debug message --- shairport.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shairport.c b/shairport.c index 41520afe..56dff447 100644 --- a/shairport.c +++ b/shairport.c @@ -1173,6 +1173,10 @@ int main(int argc, char **argv) { debug(1, "drift tolerance is %f seconds.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); + if (config.volume_max_db_set) + debug(1, "volume_max_db is %d.", config.volume_max_db); + else + debug(1, "volume_max_db is not set"); debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).", config.playback_mode); debug(1, "disable_synchronization is %d.", config.no_sync); From 0de80b9be713f86d5485385b769bcd802e4c7a34 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Feb 2017 19:58:40 +0000 Subject: [PATCH 112/135] Update README.md Begin update for version 3 --- README.md | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index bfbbc0b5..268ebaf4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ More Information ---------- Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to play audio at exactly the right time. It does this by monitoring and controlling the *latency* — the time-gap between when a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC). -The latency to be used is specified by the source when it negotiates with Shairport Sync. Most sources set a latency of 88,200 frames — exactly two seconds. Recent versions of iTunes and forkedDaapd use a latency of 99,577 frames. +The latency to be used is specified by the source when it negotiates with Shairport Sync. Most sources set a latency of exactly two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. Timestamps are measured relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. This is done by synchronising the two clocks, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. @@ -31,6 +31,7 @@ What else? * Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range. * Hardware Mute — Shairport Sync will mute properly if the hardware supports it. * Support for the Apple ALAC decoder. +* Bit depths of 8, 16, 24 and 32 bits, rahter than the standard 16 bits. * Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds. * Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) * Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. @@ -41,7 +42,7 @@ Status ------ Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Wheezy and Jessie, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. It will not work properly — if at all — with a PulseAudio (pseudo-)output device. Using a port of the `alsa` system, Shairport Sync runs rather well on FreeBSD. -Shairport Sync runs well on the Raspberry Pi. It can drive the built-in sound card, though the audio out of the card is of poor quality (see the note below on configuring the Raspberry Pi to make best use of it). USB-connected sound cards work well, though [very] old versions of Raspbian appear to suffer from a problem — see http://www.raspberrypi.org/forums/viewtopic.php?t=23544, so it is wise to update. Shairport Sync works well with the IQAudIO Pi-DAC — see http://www.iqaudio.com. +Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card, though the audio out of the card is of poor quality – see the note below on configuring the Raspberry Pi to make best use of it. At the time of writing, OpenWrt trunk does not support USB audio well on the Raspberry Pi. @@ -56,7 +57,6 @@ Note: Historically, Shairport Sync has taken its settings from command line argu Building And Installing the Development Version --------------------- The following procedures will install the shairport-sync application into your system. Before continuing, you should check to see if shairport-sync is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. -(If the existing installation of shairport-sync is where the new copy will be installed into, it will be overwritten; sometimes, however, the installation is to another location, so it is safer, initially, to delete previous versions manually.) To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions. @@ -181,7 +181,7 @@ To enable Shairport Sync to start automatically at system startup, enter: `$sudo systemctl enable shairport-sync` **Install into a System V system** -If you are installing onto a System V system: +If you are installing onto a System V system, enter: ``` $sudo make install @@ -370,38 +370,43 @@ Latency ------- Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer. -Shairport Sync uses latencies supplied by the source, typically either 88,200 or 99,577 frames. You shouldn't need to change them. (The `latencies` stanza in the configuration file and the various latency command-line options are now obselete and are deprecated.) +Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them. (The `latencies` stanza in the configuration file and the various latency command-line options are now obselete and are deprecated.) -Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -4410. +Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset_in_seconds`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -0.1. Resynchronisation ------------- Shairport Sync actively maintains synchronisation with the source. -If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 50 ms — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above. -* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold` setting. +If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above. +* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting. Tolerance --------- -Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections. -* You can vary the tolerance with the `general` `drift` setting. +Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 0.002 seconds, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections. +* You can vary the tolerance with the `general` `drift_tolerance_in_seconds` setting. Some Statistics --------------- -If you turn on the `general` `statistics` setting, statistics like this will be printed at intervals on the console (or in the logfile if running in daemon mode): +If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file: +``` +sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy +``` +This will be followed by the statistics themselves at regular intervals, for example: +``` + -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270 +``` -`Sync error: -35.4 (frames); net correction: 24.2 (ppm); corrections: 24.2 (ppm); missing packets 0; late packets 5; too late packets 0; resend requests 6; min DAC queue size 4430.` +"Sync error in milliseconds" is the average deviation from exact synchronisation. The example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. -"Sync error" is the average deviation from exact synchronisation. The example above indicates that the output is on average 35.4 frames ahead of exact synchronisation. Sync is allowed to drift by the `general` `drift` setting — 88 frames (± 2 milliseconds) by default — before a correction will be made. +"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The example above indicates that the output device is consuming frames 24.2 ppm faster than the source is generating them. -"Net correction" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The example above indicates that the output device is consuming frames 24.2 ppm faster than the source is generating them. - -"Corrections" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. +"Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from a second-generation iPod driving the Raspberry Pi which is connected over Ethernet. It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... -"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 samples, and will go low if the processor is very busy. If it goes below about 2,000 then it's a sign that the processor can't really keep up. +"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's a sign that the processor can't really keep up. Troubleshooting --------------- From 648a68d2817c6a3647342a40757c9c4e614693e6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Feb 2017 20:02:39 +0000 Subject: [PATCH 113/135] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 268ebaf4..5efdf647 100644 --- a/README.md +++ b/README.md @@ -394,15 +394,17 @@ sync error in milliseconds, net correction in ppm, corrections in ppm, total pac This will be followed by the statistics themselves at regular intervals, for example: ``` -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270 + -2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267 + -2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268 ``` -"Sync error in milliseconds" is the average deviation from exact synchronisation. The example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. +"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. -"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The example above indicates that the output device is consuming frames 24.2 ppm faster than the source is generating them. +"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. "Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. -For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from a second-generation iPod driving the Raspberry Pi which is connected over Ethernet. +For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3. It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... From 2ba3b8562bb7582bcce16e1ead2c745b2a38f973 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Feb 2017 20:08:53 +0000 Subject: [PATCH 114/135] Update README.md --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5efdf647..b94ef90b 100644 --- a/README.md +++ b/README.md @@ -396,19 +396,24 @@ This will be followed by the statistics themselves at regular intervals, for exa -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270 -2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267 -2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268 + -2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267 + -2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268 + -2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267 + -2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267 + -2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266 ``` "Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. -"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. +"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm. "Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. -For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3. +For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+. It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... -"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's a sign that the processor can't really keep up. +"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's an indication that the processor can't really keep up. Troubleshooting --------------- From baa13d761fa7bbd2c5732e97ecb12c91707d0a30 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Feb 2017 20:11:29 +0000 Subject: [PATCH 115/135] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b94ef90b..1706a6d1 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ To maintain the exact latency required, if an output device is running slow rela Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0. -Shairport Sync is mainly designed for `alsa` and thus for Linux, although since `alsa` has been ported to FreeBSD, Shairport Sync runs in FreeBSD too. It must have direct access to the output device, which must be a real sound card capable of working with 44,100 samples per second interleaved PCM stereo (you'll get a message in the logfile if there's a problem). +Shairport Sync is mainly designed for `alsa` and thus for Linux, although since `alsa` has been ported to FreeBSD, Shairport Sync runs in FreeBSD too. It must have direct access to the output device, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. the default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem). For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki. From b2b1a525a58a4803e02d3de4ae6e329aa7ccc78c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Feb 2017 20:12:01 +0000 Subject: [PATCH 116/135] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1706a6d1..6bcc325b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ To maintain the exact latency required, if an output device is running slow rela Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0. -Shairport Sync is mainly designed for `alsa` and thus for Linux, although since `alsa` has been ported to FreeBSD, Shairport Sync runs in FreeBSD too. It must have direct access to the output device, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. the default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem). +Shairport Sync is mainly designed for `alsa` and thus for Linux, although since `alsa` has been ported to FreeBSD, Shairport Sync runs in FreeBSD too. It must have direct access to the output device, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem). For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki. From d3b0a9015c06a811b05b3dc11eaa149f269a712b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Feb 2017 20:57:26 +0000 Subject: [PATCH 117/135] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6bcc325b..69348616 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ What else? * Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range. * Hardware Mute — Shairport Sync will mute properly if the hardware supports it. * Support for the Apple ALAC decoder. -* Bit depths of 8, 16, 24 and 32 bits, rahter than the standard 16 bits. +* Bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits. * Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds. * Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) * Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. From a827b2bc0cc341b4670efe70ff321df1decb5edb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Feb 2017 22:22:19 +0000 Subject: [PATCH 118/135] Start updating man pages for version 3. --- man/shairport-sync.7 | 111 +++++++++------------- man/shairport-sync.7.xml | 192 ++++++++++++++++----------------------- man/shairport-sync.html | 190 +++++++++++++++----------------------- 3 files changed, 195 insertions(+), 298 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index ccbc9929..5f5dbac8 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -2,7 +2,7 @@ .SH NAME shairport-sync \- Synchronised Audio Player for iTunes / AirPlay .SH SYNOPSIS -\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--forkedDaapdLatency=\fB\fIlatency\fB]\fB [--get-cover-art]\fB [-i \fB\fIlatency\fB]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB +\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--get-cover-art]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB shairport-sync -D\fB @@ -89,11 +89,11 @@ When shairport-sync starts to play audio, it establises three UDP connections to \fBudp_port_range=\f1\fIrange\f1\fB;\f1 Use this in conjunction with the prevous setting to specify the \fIrange\f1 of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found. .TP -\fBdrift=\f1\fIframes\f1\fB;\f1 -Allow playback to drift up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fBstatistics\f1 setting to monitor correction levels. Corrections should not greatly exceed net corrections. +\fBdrift_tolerance_in_seconds=\f1\fIseconds\f1\fB;\f1 +Allow playback to drift up to \fIseconds\f1 out of exact synchronization before attempting to correct it. The default is 0.002 seconds, i.e. 2 milliseconds. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fBstatistics\f1 setting to monitor correction levels. Corrections should not greatly exceed net corrections. This setting replaces the deprecated \fBdrift\f1 setting. .TP -\fBresync_threshold=\f1\fIthreshold\f1\fB;\f1 -Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify 0 to disable resynchronisation. +\fBresync_threshold_in_seconds=\f1\fIthreshold\f1\fB;\f1 +Resynchronise if timings differ by more than \fIthreshold\f1 seconds. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 0.050 seconds, i.e. 50 milliseconds. Specify 0.0 to disable resynchronisation. This setting replaces the deprecated \fBresync_threshold\f1 setting. .TP \fBlog_verbosity=\f1\fI0\f1\fB;\f1 Use this to specify how much debugging information should be output or logged. "0" means no debug information, "3" means most debug information. The default is "0". @@ -101,6 +101,9 @@ Use this to specify how much debugging information should be output or logged. " \fBignore_volume_control=\f1\fI"choice"\f1\fB;\f1 Set this \fIchoice\f1 to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is "no". .TP +\fBvolume_max_db=\f1\fIdBvalue\f1\fB;\f1 +Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB. +.TP \fBvolume_range_db=\f1\fIdBvalue\f1\fB;\f1 Use this \fIdBvalue\f1 to reduce or increase the attenuation range, in decibels, between the minimum and maximum volume. @@ -116,7 +119,13 @@ If you omit this setting, the full "native" range of the mixer is used. Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". .TP \fBplayback_mode=\f1\fI"mode"\f1\fB;\f1 -The \fImode\f1 can be "stereo" or "mono". Default is "stereo". +The \fImode\f1 can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". +.TP +\fBinterface=\f1\fI"name"\f1\fB;\f1 +Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour. +.TP +\fBalac_decoder=\f1\fI"decodername"\f1\fB;\f1 +This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work. .TP \fB"ALSA" SETTINGS\f1 These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system. (By the way, you can use tools such as \fBalsamixer\f1 or \fBaplay\f1 to discover what devices are available.) Use these settings to select the output device and the mixer control to be used to control the output volume. You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the \fBalsa\f1 group settings: @@ -132,11 +141,17 @@ This setting is deprecated and is ignored. For your information, its functionali \fBmixer_device=\f1\fI"mixer_device"\f1\fB;\f1 By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device. .TP -\fBaudio_backend_latency_offset=\f1\fIoffset\f1\fB;\f1 -Set this \fIoffset\f1, in frames, to compensate for a fixed delay in the audio back end. For example, if the output device delays by 100 ms, set this to -4410. +\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset\f1\fB;\f1 +Set this \fIoffset\f1, in seconds, to compensate for a fixed delay in the audio back end. For example, if the output device delays by 100 ms, set this to -0.1. .TP -\fBaudio_backend_buffer_desired_length=\f1\fIlength\f1\fB;\f1 -Use this to set the desired number frames to be in the output device's hardware output buffer. The default is 6,615 frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying, or it may exceed the hardware buffer size. It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata. +\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIlength\f1\fB;\f1 +Use this to set the desired length, in seconds, of the queue of audio frames in the output device's hardware output buffer. The default is 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying, or it may exceed the hardware buffer size. It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata. +.TP +\fBoutput_rate=\f1\fIframe rate\f1\fB;\f1 +Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available. +.TP +\fBoutput_format=\f1\fI"format"\f1\fB;\f1 +Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify. "S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16". If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither. .TP \fBdisable_synchronization=\f1\fI"no"\f1\fB;\f1 This is an advanced setting and is for debugging only. Set to "yes" to disable synchronization. Default is "no". If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to audio buffer overflow or underflow. @@ -152,44 +167,44 @@ These settings are for the PIPE backend, used to route audio to a named unix pip Use the \fIname\f1 setting to set the name and location of the pipe. -There are two further settings affecting timing that might be useful if the pipe reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length\f1 setting affects the nominal output buffer size. +There are two further settings affecting timing that might be useful if the pipe reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size. These are the settings available within the \fBpipe\f1 group: .TP \fBname=\f1\fI"/path/to/pipe"\f1\fB;\f1 Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up and will be closed upon shutdown. Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. The sender will wait for up to five seconds for a packet to be written before discarding it. .TP -\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1 -Packets of audio frames are written to the pipe synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. +\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1 +Packets of audio frames are written to the pipe synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0. .TP -\fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the pipe reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100 frames. +\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1 +Use this setting, in seconds, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 1 second. Note that if the pipe reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 1.0 seconds. .TP \fB"STDOUT" SETTINGS\f1 -These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, interleaved stereo. +These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). The audio is in raw CD audio format, usually PCM 16 bit little endian, 44,100 samples per second, interleaved stereo. -There are two settings affecting timing that might be useful if the stdout reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length\f1 setting affects the nominal output buffer size. +There are two settings affecting timing that might be useful if the stdout reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size. These are the settings available within the \fBstdout\f1 group: .TP -\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1 -Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. +\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1 +Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0. .TP -\fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100 frames. +\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1 +Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 1 second. Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 1.0 seconds. .TP \fB"AO" SETTINGS\f1 These settings are for the AO backend, used for the libao audio library. -There are two settings affecting timing. The \fIaudio_backend_latency_offset\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length\f1 setting affects the nominal output buffer size. +There are two settings affecting timing. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size. These are the settings available within the \fBao\f1 group: .TP -\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1 -Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. +\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1 +Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0. .TP -\fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the libao system. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 44,100 frames. +\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1 +Use this setting, in seconds, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the libao system. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 1 second. Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 1.0 seconds. .TP \fB"METADATA" SETTINGS\f1 shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It sends the metadata to a pipe, by default \fI/tmp/shairport-sync-metadata\f1. To process metadata, shairport-sync must have been compiled with metadata support included. You can check that this is so by running the command \fB$ shairport-sync -V\f1; the identification string will contain the word \fBmetadata\f1. @@ -233,29 +248,6 @@ If \fBchoice\f1 is set to "yes", then another source will be able to interrupt a .TP \fBsession_timeout=\f1\fIseconds\f1\fB;\f1 If a play session has been established and the source disappears without warning (such as a device going out of range of a network) then wait for \fIseconds\f1 seconds before ending the session. Once the session has terminated, other devices can use it. The default is 120 seconds. -.TP -\fB"LATENCIES" SETTINGS\f1 -The latencies settings are now deprecated. Do not use them for new installations. They will be removed from a future version of shairport-sync. - -Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer. - -shairport-sync now sets latencies automatically using information supplied by the source, typically either 88,200 or 99,577 frames. - -The following relates to the old scheme of using fixed latencies, which ignored the latency information supplied by the source. There are four default latency settings. One latency matches the latency used by recent versions of iTunes when playing audio and another matches the latency used by so-called "AirPlay" devices, including iOS devices and iTunes and Quicktime Player when they are playing video. A third latency is used when the audio source is forked-daapd. The fourth latency is the default if no other latency is chosen and is used for older versions of iTunes. - -Note: If you are thinking of changing latencies to compensate for a delay in the audio output device, then instead of changing these individual latencies, use the \fBaudio_backend_latency_offset\f1 setting in the \fBalsa\f1 group (or the appropriate other group if you're not outputing through the alsa backend). -.TP -\fBitunes=\f1\fIlatency\f1\fB;\f1 -This is the \fIlatency\f1, in frames, used for iTunes 10 or later. Default is 99,400. -.TP -\fBairplay=\f1\fIlatency\f1\fB;\f1 -This is the \fIlatency\f1, in frames, used for AirPlay devices, including iOS devices and iTunes and Quicktime Player when they are playing video. Default is 88,200. -.TP -\fBforkedDaapd=\f1\fIlatency\f1\fB;\f1 -This is the \fIlatency\f1, in frames, used for forkedDaapd sources. Default is 99,400. -.TP -\fBdefault=\f1\fIlatency\f1\fB;\f1 -This is the \fIlatency\f1, in frames, used when the source is unrecognised. Default is 88,200. .SH OPTIONS This section is about the command-line options available in shairport-sync. @@ -268,15 +260,10 @@ There are two kinds of command-line options for shairport-sync: regular \fBprogr These command-line options are used by shairport-sync itself. .TP \fB-a \f1\fIservice name\f1\fB | --name=\f1\fIservice name\f1 -Use this \fIservice name\f1 to identify this player in iTunes, etc. The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "2.8.4" and \fB%V\f1 for the shairport-sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata". +Use this \fIservice name\f1 to identify this player in iTunes, etc. The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "3.0.1" and \fB%V\f1 for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc". The default is "%H", which is replaced by the hostname with the first letter capitalised. .TP -\fB-A | --AirPlayLatency=\f1\fIlatency\f1 -Use this \fIlatency\f1, in frames, for audio streamed from an AirPlay device. The default is 88,200 frames, where there are 44,100 frames to the second. - -Please note that this feature is deprecated and will be removed in a future version of shairport-sync. -.TP \fB-B \f1\fIprogram\f1\fB | --on-start=\f1\fIprogram\f1 Execute \fIprogram\f1 when playback is about to begin. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have \fI#!/bin/sh\f1 (or whatever is appropriate) in the headline. @@ -298,11 +285,6 @@ Execute \fIprogram\f1 when playback has ended. Specify the full path to the prog If you want shairport-sync to wait until the command has completed before continuing, select the \fB-w\f1 option as well. .TP -\fB--forkedDaapdLatency=\f1\fIlatency\f1 -Use this \fIlatency\f1, in frames, for audio streamed from a forked-daapd based source. The default is 99,400 frames, where there are 44,100 frames to the second. - -Please note that this feature is deprecated and will be removed in a future version of shairport-sync. -.TP \fB--get-coverart\f1 This option requires the \fB--meta-dir\f1 option to be set, and enables shairport-sync to request cover art from the source and to transmit it through the metadata pipe. @@ -311,11 +293,6 @@ Please note that cover art data may be very large, and may place too great a bur \fB-h | --help\f1 Print brief help message and exit. .TP -\fB-i | --iTunesLatency=\f1\fIlatency\f1 -Use this \fIlatency\f1, in frames, for audio streamed from an iTunes source, where iTunes is Version 10 or later. The default is 99,400 frames, where there are 44,100 frames to the second. If the source is iTunes but is earler than Version 10, the \fIdefault latency\f1 is used (see the \fB-L\f1 option). Some third party programs masquerade as older versions of iTunes. - -Please note that this feature is deprecated and will be removed in a future version of shairport-sync. -.TP \fB-k | --kill\f1 Kill the shairport-sync daemon and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option). .TP @@ -345,7 +322,7 @@ Reconnect the shairport-sync daemon to the output device and exit. It may take a Please note that this feature is deprecated and will be removed in a future version of shairport-sync. .TP \fB-r \f1\fIthreshold\f1\fB | --resync=\f1\fIthreshold\f1 -Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fB0\f1 to disable resynchronisation. +Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fB0\f1 to disable resynchronisation. This setting is deprecated and will be removed in a future version of shairport-sync. .TP \fB--statistics\f1 Print some statistics in the standard output, or in the logfile if in daemon mode. @@ -359,7 +336,7 @@ Exit play mode if the stream disappears for more than \fItimeout\f1 seconds. When shairport-sync plays an audio stream, it starts a play session and will return a busy signal to any other sources that attempt to use it. If the audio stream disappears for longer than \fItimeout\f1 seconds, the play session will be terminated. If you specify a timeout time of \fB0\f1, shairport-sync will never signal that it is busy and will not prevent other sources from "barging in" on an existing play session. The default value is 120 seconds. .TP \fB--tolerance=\f1\fIframes\f1 -Allow playback to be up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fB--statistics\f1 option to monitor correction levels. Corrections should not greatly exceed net corrections. +Allow playback to be up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fB--statistics\f1 option to monitor correction levels. Corrections should not greatly exceed net corrections. This setting is deprecated and will be removed in a future version of shairport-sync. .TP \fB-V | --version\f1 Print version information and exit. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index c4ff2d86..77e4080b 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -4,7 +4,7 @@ >] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , outstream->bytes_per_frame: %d", frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame ); + debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , " + "outstream->bytes_per_frame: %d", + frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame); - if (frame_count_min > fill_count) { - int frame_count = frame_count_min; - if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))){ - debug(0, "[--->>] begin write error: %s", soundio_strerror(err)); - } - for (int frame = 0; frame < frame_count; frame += 1) { - for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { - memset(areas[ch].ptr, 0, outstream->bytes_per_sample); - areas[ch].ptr += areas[ch].step; - } - } - if ((err = soundio_outstream_end_write(outstream))) - debug(0,"[--->>] end write error: %s", soundio_strerror(err)); - return; - } + if (frame_count_min > fill_count) { + int frame_count = frame_count_min; + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) { + debug(0, "[--->>] begin write error: %s", soundio_strerror(err)); + } + for (int frame = 0; frame < frame_count; frame += 1) { + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + memset(areas[ch].ptr, 0, outstream->bytes_per_sample); + areas[ch].ptr += areas[ch].step; + } + } + if ((err = soundio_outstream_end_write(outstream))) + debug(0, "[--->>] end write error: %s", soundio_strerror(err)); + return; + } - int read_count = min_int(frame_count_max, fill_count); - int frames_left = read_count; + int read_count = min_int(frame_count_max, fill_count); + int frames_left = read_count; - while (frames_left > 0) { - int frame_count = frames_left; + while (frames_left > 0) { + int frame_count = frames_left; - if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) - debug(0, "[--->>] begin write error: %s", soundio_strerror(err)); + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) + debug(0, "[--->>] begin write error: %s", soundio_strerror(err)); - if (frame_count <= 0) - break; + if (frame_count <= 0) + break; - for (int frame = 0; frame < frame_count; frame += 1) { - for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { - memcpy(areas[ch].ptr, read_ptr, outstream->bytes_per_sample); - areas[ch].ptr += areas[ch].step; - read_ptr += outstream->bytes_per_sample; - } - } + for (int frame = 0; frame < frame_count; frame += 1) { + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + memcpy(areas[ch].ptr, read_ptr, outstream->bytes_per_sample); + areas[ch].ptr += areas[ch].step; + read_ptr += outstream->bytes_per_sample; + } + } - if ((err = soundio_outstream_end_write(outstream))) - debug(0, "[--->>] end write error: %s", soundio_strerror(err)); + if ((err = soundio_outstream_end_write(outstream))) + debug(0, "[--->>] end write error: %s", soundio_strerror(err)); - frames_left -= frame_count; - } + frames_left -= frame_count; + } - debug(3,"[--->>] Wrote: %d", read_count * outstream->bytes_per_frame); - soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream->bytes_per_frame); + debug(3, "[--->>] Wrote: %d", read_count * outstream->bytes_per_frame); + soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream->bytes_per_frame); } static void underflow_callback(struct SoundIoOutStream *outstream) { - static int count = 0; - debug(0, "underflow %d\n", ++count); + static int count = 0; + debug(0, "underflow %d\n", ++count); } static int init(int argc, char **argv) { @@ -89,25 +88,25 @@ static int init(int argc, char **argv) { soundio = soundio_create(); if (!soundio) { - debug(0, "out of memory\n"); - return 1; + debug(0, "out of memory\n"); + return 1; } if ((err = soundio_connect_backend(soundio, SoundIoBackendCoreAudio))) { - debug(0, "error connecting: %s", soundio_strerror(err)); - return 1; + debug(0, "error connecting: %s", soundio_strerror(err)); + return 1; } soundio_flush_events(soundio); int default_out_device_index = soundio_default_output_device_index(soundio); if (default_out_device_index < 0) { - debug(0, "no output device found"); - return 1; + debug(0, "no output device found"); + return 1; } device = soundio_get_output_device(soundio, default_out_device_index); if (!device) { - debug(0, "out of memory"); - return 1; + debug(0, "out of memory"); + return 1; } debug(0, "Output device: %s\n", device->name); return 0; @@ -139,21 +138,21 @@ static void start(int sample_rate, int sample_format) { // outstream->software_latency = 0; if ((err = soundio_outstream_open(outstream))) { - debug(0, "unable to open device: %s", soundio_strerror(err)); + debug(0, "unable to open device: %s", soundio_strerror(err)); } if (outstream->layout_error) - debug(0, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error)); + debug(0, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error)); int capacity = outstream->sample_rate * outstream->bytes_per_frame; ring_buffer = soundio_ring_buffer_create(soundio, capacity); if (!ring_buffer) - debug(0, "unable to create ring buffer: out of memory"); + debug(0, "unable to create ring buffer: out of memory"); char *buf = soundio_ring_buffer_write_ptr(ring_buffer); memset(buf, 0, capacity); soundio_ring_buffer_advance_write_ptr(ring_buffer, capacity); if ((err = soundio_outstream_start(outstream))) { - debug(0, "unable to start outstream: %s", soundio_strerror(err)); + debug(0, "unable to start outstream: %s", soundio_strerror(err)); } debug(1, "libsoundio output started\n"); @@ -167,12 +166,13 @@ static void play(short buf[], int samples) { int left_bytes = samples * outstream->bytes_per_frame; char *write_ptr = soundio_ring_buffer_write_ptr(ring_buffer); - debug(3, "[<<---] samples: %d , size: %d", samples, left_bytes ); + debug(3, "[<<---] samples: %d , size: %d", samples, left_bytes); write_bytes = min_int(left_bytes, free_bytes); - debug(3, "[<<---] left_bytes: %d, write_bytes: %d, free_bytes: %d\n", left_bytes, write_bytes, free_bytes); + debug(3, "[<<---] left_bytes: %d, write_bytes: %d, free_bytes: %d\n", left_bytes, write_bytes, + free_bytes); - if (write_bytes){ - memcpy(write_ptr, (char*) buf, write_bytes); + if (write_bytes) { + memcpy(write_ptr, (char *)buf, write_bytes); written_bytes += write_bytes; soundio_ring_buffer_advance_write_ptr(ring_buffer, write_bytes); debug(3, "[<<---] Written to buffer : %d\n", written_bytes); @@ -182,17 +182,17 @@ static void play(short buf[], int samples) { static void parameters(audio_parameters *info) { info->minimum_volume_dB = -30.0; info->maximum_volume_dB = 0.0; -debug(2, "Parameters\n"); -debug(2, "Current Volume dB: %f\n", info->current_volume_dB); -debug(2, "Minimum Volume dB: %d\n", info->minimum_volume_dB); -debug(2, "Maximum Volume dB: %d\n", info->maximum_volume_dB); + debug(2, "Parameters\n"); + debug(2, "Current Volume dB: %f\n", info->current_volume_dB); + debug(2, "Minimum Volume dB: %d\n", info->minimum_volume_dB); + debug(2, "Maximum Volume dB: %d\n", info->maximum_volume_dB); } static void stop(void) { soundio_outstream_destroy(outstream); soundio_ring_buffer_clear(ring_buffer); debug(1, "libsoundio output stopped\n"); - } +} static void flush(void) { soundio_ring_buffer_clear(ring_buffer); @@ -202,14 +202,14 @@ static void flush(void) { static void help(void) { printf(" There are no options for libsoundio.\n"); } audio_output audio_soundio = {.name = "soundio", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = &flush, - .delay = NULL, - .play = &play, - .volume = NULL, - .parameters = ¶meters, - .mute = NULL}; + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = &flush, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = ¶meters, + .mute = NULL}; diff --git a/audio_stdout.c b/audio_stdout.c index 8caac9a2..2e79f76b 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -56,19 +56,18 @@ static int init(int argc, char **argv) { if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_int(config.cfg, - "stdout.audio_backend_buffer_desired_length", &value)) { + if (config_lookup_int(config.cfg, "stdout.audio_backend_buffer_desired_length", &value)) { if ((value < 0) || (value > 66150)) { inform("The setting audio_backend_buffer_desired_length is deprecated. " - "Use audio_backend_buffer_desired_length_in_seconds instead."); + "Use audio_backend_buffer_desired_length_in_seconds instead."); die("Invalid stdout audio backend buffer desired length \"%d\". It " "should be between 0 and " "66150, default is 6615", value); } else { inform("The stdout.setting audio_backend_buffer_desired_length is deprecated. " - "Use stdout.audio_backend_buffer_desired_length_in_seconds instead."); - config.audio_backend_buffer_desired_length = 1.0*value/44100; + "Use stdout.audio_backend_buffer_desired_length_in_seconds instead."); + config.audio_backend_buffer_desired_length = 1.0 * value / 44100; } } @@ -86,23 +85,23 @@ static int init(int argc, char **argv) { } /* Get the latency offset. */ - if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", - &value)) { + if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", &value)) { if ((value < -66150) || (value > 66150)) { inform("The setting stdout.audio_backend_latency_offset is deprecated. " - "Use stdout.audio_backend_latency_offset_in_seconds instead."); + "Use stdout.audio_backend_latency_offset_in_seconds instead."); die("Invalid stdout audio backend buffer latency offset \"%d\". It " "should be between -66150 and +66150, default is 0", value); } else { inform("The setting stdout.audio_backend_latency_offset is deprecated. " - "Use stdout.audio_backend_latency_offset_in_seconds instead."); - config.audio_backend_latency_offset = 1.0*value/44100; + "Use stdout.audio_backend_latency_offset_in_seconds instead."); + config.audio_backend_latency_offset = 1.0 * value / 44100; } } /* Get the latency offset. */ - if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset_in_seconds", &dvalue)) { + if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset_in_seconds", + &dvalue)) { if ((dvalue < -1.0) || (dvalue > 1.5)) { die("Invalid stdout audio backend buffer latency offset time \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", @@ -111,8 +110,6 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = dvalue; } } - - } return 0; } diff --git a/common.c b/common.c index 30b5e6f8..53d7715e 100644 --- a/common.c +++ b/common.c @@ -68,16 +68,15 @@ #endif #ifdef HAVE_LIBMBEDTLS -#include -#include -#include -#include -#include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" +#include "mbedtls/entropy.h" +#include +#include +#include +#include #endif - #include // true if Shairport Sync is supposed to be sending output to the output device, false otherwise @@ -357,24 +356,25 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&ctr_drbg); - mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, - (const unsigned char *)pers, strlen(pers)); + mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers, + strlen(pers)); mbedtls_pk_init(&pkctx); - rc = mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key, sizeof(super_secret_key), NULL, 0); + rc = mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key, sizeof(super_secret_key), + NULL, 0); if (rc != 0) debug(1, "Error %d reading the private key.", rc); uint8_t *outbuf = NULL; - trsa = mbedtls_pk_rsa(pkctx); + trsa = mbedtls_pk_rsa(pkctx); switch (mode) { case RSA_MODE_AUTH: mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE); outbuf = malloc(trsa->len); rc = mbedtls_rsa_pkcs1_encrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, - inlen, input, outbuf); + inlen, input, outbuf); if (rc != 0) debug(1, "mbedtls_pk_encrypt error %d.", rc); *outlen = trsa->len; @@ -382,8 +382,8 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { case RSA_MODE_KEY: mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1); outbuf = malloc(trsa->len); - rc = mbedtls_rsa_pkcs1_decrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, - &olen, input, outbuf, trsa->len); + rc = mbedtls_rsa_pkcs1_decrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, + &olen, input, outbuf, trsa->len); if (rc != 0) debug(1, "mbedtls_pk_decrypt error %d.", rc); *outlen = olen; @@ -745,16 +745,16 @@ uint64_t *ranarray; int ranarraynext; void ranarrayinit() { - ranarray = (uint64_t*)malloc(ranarraylength*sizeof(uint64_t)); + ranarray = (uint64_t *)malloc(ranarraylength * sizeof(uint64_t)); int i; - for (i=0;i> 1); } - diff --git a/common.h b/common.h index af965e92..3a1cab58 100644 --- a/common.h +++ b/common.h @@ -87,7 +87,7 @@ typedef struct { int udp_port_base; int udp_port_range; int ignore_volume_control; - int volume_max_db_set; // set to 1 if a maximum volume db has been set + int volume_max_db_set; // set to 1 if a maximum volume db has been set int volume_max_db; int no_sync; // disable synchronisation, even if it's available int no_mmap; // disable use of mmap-based output, even if it's available @@ -125,7 +125,7 @@ typedef struct { char *configfile; char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by // default “_raop._tcp.”. - char *interface; // a string containg the interface name, or NULL if nothing specified + char *interface; // a string containg the interface name, or NULL if nothing specified int interface_index; // only valid if the interface string is non-NULL double audio_backend_buffer_desired_length; // this will be the length in seconds of the // audio backend buffer -- the DAC buffer for ALSA diff --git a/mdns_avahi.c b/mdns_avahi.c index d8fef9a9..31c02bea 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -205,7 +205,7 @@ static void register_service(AvahiClient *c) { int ret; AvahiIfIndex selected_interface; - if (config.interface!=NULL) + if (config.interface != NULL) selected_interface = config.interface_index; else selected_interface = AVAHI_IF_UNSPEC; @@ -261,9 +261,8 @@ static void client_callback(AvahiClient *c, AvahiClientState state, c = NULL; group = NULL; - if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), - AVAHI_CLIENT_NO_FAIL, client_callback, - userdata, &err))) { + if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), AVAHI_CLIENT_NO_FAIL, + client_callback, userdata, &err))) { warn("avahi: failed to create client object: %s", avahi_strerror(err)); avahi_threaded_poll_quit(tpoll); } @@ -297,8 +296,8 @@ static int avahi_register(char *srvname, int srvport) { warn("couldn't create avahi threaded tpoll!"); return -1; } - if (!(client = - avahi_client_new(avahi_threaded_poll_get(tpoll), AVAHI_CLIENT_NO_FAIL, client_callback, NULL, &err))) { + if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), AVAHI_CLIENT_NO_FAIL, + client_callback, NULL, &err))) { warn("couldn't create avahi client: %s!", avahi_strerror(err)); return -1; } diff --git a/mdns_tinysvcmdns.c b/mdns_tinysvcmdns.c index 57f41ae9..34137ef0 100644 --- a/mdns_tinysvcmdns.c +++ b/mdns_tinysvcmdns.c @@ -71,9 +71,10 @@ static int mdns_tinysvcmdns_register(char *apname, int port) { // Look for an ipv4/ipv6 non-loopback interface to use as the main one. for (ifa = ifalist; ifa != NULL; ifa = ifa->ifa_next) { // only check for the named interface, if specified - if ((config.interface==NULL) || (strcmp(config.interface,ifa->ifa_name)==0)) { - - if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) { + if ((config.interface == NULL) || (strcmp(config.interface, ifa->ifa_name) == 0)) { + + if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && + ifa->ifa_addr->sa_family == AF_INET) { uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; mdnsd_set_hostname(svr, hostname, main_ip); // TTL should be 120 seconds @@ -96,21 +97,22 @@ static int mdns_tinysvcmdns_register(char *apname, int port) { // Skip the first one, it was already added by set_hostname for (ifa = ifa->ifa_next; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_flags & IFF_LOOPBACK) // Skip loop-back interfaces - continue; + continue; // only check for the named interface, if specified - if ((config.interface==NULL) || (strcmp(config.interface,ifa->ifa_name)==0)) { + if ((config.interface == NULL) || (strcmp(config.interface, ifa->ifa_name) == 0)) { switch (ifa->ifa_addr->sa_family) { - case AF_INET: { // ipv4 - uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; - struct rr_entry *a_e = rr_create_a(create_nlabel(hostname), ip); // TTL should be 120 seconds - mdnsd_add_rr(svr, a_e); - } break; - case AF_INET6: { // ipv6 - struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; - struct rr_entry *aaaa_e = - rr_create_aaaa(create_nlabel(hostname), addr); // TTL should be 120 seconds - mdnsd_add_rr(svr, aaaa_e); - } break; + case AF_INET: { // ipv4 + uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; + struct rr_entry *a_e = + rr_create_a(create_nlabel(hostname), ip); // TTL should be 120 seconds + mdnsd_add_rr(svr, a_e); + } break; + case AF_INET6: { // ipv6 + struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; + struct rr_entry *aaaa_e = + rr_create_aaaa(create_nlabel(hostname), addr); // TTL should be 120 seconds + mdnsd_add_rr(svr, aaaa_e); + } break; } } } diff --git a/player.c b/player.c index c121969f..9b265279 100644 --- a/player.c +++ b/player.c @@ -679,8 +679,10 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t } dither_mask -= 1; // int64_t r = r64i(); - int64_t r = ranarray64i(); // use an array of precalculated pseudorandom numbers rather than calculating them on the fly. Should be easier on low-powered processors - + int64_t r = ranarray64i(); // use an array of precalculated pseudorandom numbers rather than + // calculating them on the fly. Should be easier on low-powered + // processors + int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask); previous_random_number = r; // add dither, allowing for clipping @@ -2269,7 +2271,7 @@ void player_volume(double airplay_volume) { // don't have a hardware mixer hw_max_db = hw_min_db = hw_range_db = 0; } - + int32_t sw_min_db = -9630; int32_t sw_max_db = 0; int32_t sw_range_db = sw_max_db - sw_min_db; @@ -2278,25 +2280,29 @@ void player_volume(double airplay_volume) { if (config.volume_range_db) desired_range_db = (int32_t)trunc(config.volume_range_db * 100); - if (config.volume_max_db_set) { + if (config.volume_max_db_set) { if (hw_range_db) { - if (((config.volume_max_db*100)hw_min_db)) { - hw_max_db = (int)config.volume_max_db*100; - hw_range_db = hw_max_db-hw_min_db; + if (((config.volume_max_db * 100) < hw_max_db) && + ((config.volume_max_db * 100) > hw_min_db)) { + hw_max_db = (int)config.volume_max_db * 100; + hw_range_db = hw_max_db - hw_min_db; } else { - inform("The volume_max_db setting is out of range of the hardware mixers's limits of %d dB to %d dB. It will be ignored.", (int)(hw_max_db/100), (int)(hw_min_db/100)); + inform("The volume_max_db setting is out of range of the hardware mixers's limits of %d dB " + "to %d dB. It will be ignored.", + (int)(hw_max_db / 100), (int)(hw_min_db / 100)); } } else { - if (((config.volume_max_db*100)sw_min_db)) { - sw_max_db = (int)config.volume_max_db*100; - sw_range_db = sw_max_db-sw_min_db; + if (((config.volume_max_db * 100) < sw_max_db) && + ((config.volume_max_db * 100) > sw_min_db)) { + sw_max_db = (int)config.volume_max_db * 100; + sw_range_db = sw_max_db - sw_min_db; } else { - inform("The volume_max_db setting is out of range of the software attenuation's limits of 0 dB to -96.3 dB. It will be ignored."); + inform("The volume_max_db setting is out of range of the software attenuation's limits of " + "0 dB to -96.3 dB. It will be ignored."); } - } - } + } + } - if (desired_range_db) { // debug(1,"An attenuation range of %d is requested.",desired_range_db); // we have a desired volume range. @@ -2339,16 +2345,17 @@ void player_volume(double airplay_volume) { } } -/* - if (config.volume_max_db_set) { - if ((config.volume_max_db*100<=max_db) && (config.volume_max_db*100>=min_db)) { - debug(1,"Reducing the maximum volume from %d to %d.",max_db/100,config.volume_max_db); - max_db = (int)(config.volume_max_db*100); - } else { - inform("The value of volume_max_db is invalid. It must be in the range %d to %d.",max_db,min_db); - } - } -*/ + /* + if (config.volume_max_db_set) { + if ((config.volume_max_db*100<=max_db) && (config.volume_max_db*100>=min_db)) { + debug(1,"Reducing the maximum volume from %d to %d.",max_db/100,config.volume_max_db); + max_db = (int)(config.volume_max_db*100); + } else { + inform("The value of volume_max_db is invalid. It must be in the range %d to + %d.",max_db,min_db); + } + } + */ double hardware_attenuation, software_attenuation; double scaled_attenuation = hw_min_db + sw_min_db; diff --git a/rtsp.c b/rtsp.c index e58c10b4..59594019 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1625,8 +1625,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { mbedtls_md5_update(&tctx, (unsigned char *)":", 1); mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)config.password, - strlen(config.password)); + mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); mbedtls_md5_finish(&tctx, digest_urp); mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); @@ -1951,8 +1950,8 @@ void rtsp_listen_loop(void) { inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN); unsigned short int rport = ntohs(sa->sin_port); #ifdef CONFIG_METADATA - send_ssnc_metadata('clip', strdup(remote_ip4), strlen(remote_ip4) , 1); - send_ssnc_metadata('svip', strdup(ip4), strlen(ip4) , 1); + send_ssnc_metadata('clip', strdup(remote_ip4), strlen(remote_ip4), 1); + send_ssnc_metadata('svip', strdup(ip4), strlen(ip4), 1); #endif debug(1, "New RTSP connection from %s:%u to self at %s:%u.", remote_ip4, rport, ip4, tport); diff --git a/shairport.c b/shairport.c index 56dff447..c34a54a7 100644 --- a/shairport.c +++ b/shairport.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -38,7 +39,6 @@ #include #include #include -#include #include #include "config.h" @@ -198,12 +198,13 @@ void usage(char *progname) { printf(" -v, --verbose -v print debug information; -vv more; -vvv lots.\n"); printf(" -p, --port=PORT set RTSP listening port.\n"); printf(" -a, --name=NAME set advertised name.\n"); -// printf(" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an " -// "AirPlay device.\n"); -// printf(" The default is to set it automatically.\n"); -// printf(" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes " -// "10 or later.\n"); -// printf(" The default is to set it automatically.\n"); + // printf(" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an " + // "AirPlay device.\n"); + // printf(" The default is to set it automatically.\n"); + // printf(" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes + // " + // "10 or later.\n"); + // printf(" The default is to set it automatically.\n"); printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown " "device.\n"); printf(" The default is to set it automatically.\n"); @@ -230,13 +231,15 @@ void usage(char *progname) { printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service.\n"); printf(" if no mdns provider is specified,\n"); printf(" shairport tries them all until one works.\n"); - printf(" -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. Set to 0 to " + printf(" -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. " + "Set to 0 to " "stop resyncing.\n"); printf(" -t, --timeout=SECONDS go back to idle mode from play mode after a break in " "communications of this many seconds (default 120). Set to 0 never to exit play mode.\n"); printf(" --statistics print some interesting statistics -- output to the logfile " "if running as a daemon.\n"); - printf(" --tolerance=TOLERANCE [Deprecated] allow a synchronization error of TOLERANCE frames (default " + printf(" --tolerance=TOLERANCE [Deprecated] allow a synchronization error of TOLERANCE " + "frames (default " "88) before trying to correct it.\n"); printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a " "password.\n"); @@ -259,9 +262,9 @@ int parse_options(int argc, char **argv) { char *stuffing = NULL; /* used for picking up the stuffing option */ signed char c; /* used for argument parsing */ int i = 0; /* used for tracking options */ - int fResyncthreshold = (int)(config.resyncthreshold*44100); - int fTolerance = (int)(config.tolerance*44100); - poptContext optCon; /* context for parsing command-line options */ + int fResyncthreshold = (int)(config.resyncthreshold * 44100); + int fTolerance = (int)(config.tolerance * 44100); + poptContext optCon; /* context for parsing command-line options */ struct poptOption optionsTable[] = { {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL}, {"disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL}, @@ -318,28 +321,33 @@ int parse_options(int argc, char **argv) { inform("Warning: the option -R or --reconnectToOutput is deprecated."); break; case 'A': - inform("Warning: the option -A or --AirPlayLatency is deprecated. This setting is now automatically received from the AirPlay device."); + inform("Warning: the option -A or --AirPlayLatency is deprecated. This setting is now " + "automatically received from the AirPlay device."); break; case 'i': - inform("Warning: the option -i or --iTunesLatency is deprecated. This setting is now automatically received from iTunes"); + inform("Warning: the option -i or --iTunesLatency is deprecated. This setting is now " + "automatically received from iTunes"); break; case 'f': - inform("Warning: the option --forkedDaapdLatency is deprecated. This setting is now automatically received from forkedDaapd"); + inform("Warning: the option --forkedDaapdLatency is deprecated. This setting is now " + "automatically received from forkedDaapd"); break; case 'r': - inform("Warning: the option -r or --resync is deprecated. Please use the \"resync_threshold_in_seconds\" setting in the config file instead."); + inform("Warning: the option -r or --resync is deprecated. Please use the " + "\"resync_threshold_in_seconds\" setting in the config file instead."); break; case 'z': - inform("Warning: the option --tolerance is deprecated. Please use the \"drift_tolerance_in_seconds\" setting in the config file instead."); + inform("Warning: the option --tolerance is deprecated. Please use the " + "\"drift_tolerance_in_seconds\" setting in the config file instead."); break; - } + } } if (c < -1) { die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); } - - config.resyncthreshold = 1.0*fResyncthreshold/44100; - config.tolerance = 1.0*fTolerance/44100; + + config.resyncthreshold = 1.0 * fResyncthreshold / 44100; + config.tolerance = 1.0 * fTolerance / 44100; config_setting_t *setting; const char *str = 0; @@ -437,14 +445,14 @@ int parse_options(int argc, char **argv) { if (config_lookup_int(config.cfg, "general.drift", &value)) { inform("The drift setting is deprecated. Use " "drift_tolerance_in_seconds instead"); - config.tolerance = 1.0*value/44100; + config.tolerance = 1.0 * value / 44100; } /* The old resync setting. */ if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) { inform("The resync_threshold setting is deprecated. Use " "resync_threshold_in_seconds instead"); - config.resyncthreshold = 1.0*value/44100; + config.resyncthreshold = 1.0 * value / 44100; } /* Get the drift tolerance setting. */ @@ -474,10 +482,10 @@ int parse_options(int argc, char **argv) { else die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } - - /* Get the optional volume_max_db setting. */ + + /* Get the optional volume_max_db setting. */ if (config_lookup_float(config.cfg, "general.volume_max_db", &dvalue)) { - debug(1,"Max volume setting of %f dB",dvalue); + debug(1, "Max volume setting of %f dB", dvalue); config.volume_max_db = dvalue; config.volume_max_db_set = 1; } @@ -501,39 +509,40 @@ int parse_options(int argc, char **argv) { /* Get the interface to listen on, if specified Default is all interfaces */ /* we keep the interface name and the index */ - - if (config_lookup_string(config.cfg, "general.interface", &str)) + + if (config_lookup_string(config.cfg, "general.interface", &str)) config.interface = strdup(str); - if (config_lookup_string(config.cfg, "general.interface", &str)) { + if (config_lookup_string(config.cfg, "general.interface", &str)) { int specified_interface_found = 0; - + struct if_nameindex *if_ni, *i; if_ni = if_nameindex(); if (if_ni == NULL) { - debug(1,"Can't get a list of interface names."); + debug(1, "Can't get a list of interface names."); } else { - for (i = if_ni; ! (i->if_index == 0 && i->if_name == NULL); i++) { - // printf("%u: %s\n", i->if_index, i->if_name); - if (strcmp(i->if_name,str)==0) { + for (i = if_ni; !(i->if_index == 0 && i->if_name == NULL); i++) { + // printf("%u: %s\n", i->if_index, i->if_name); + if (strcmp(i->if_name, str) == 0) { config.interface_index = i->if_index; specified_interface_found = 1; } } - } if_freenameindex(if_ni); - - if (specified_interface_found==0) { - inform("The mdns service interface \"%s\" was not found, so the setting has been ignored.",config.interface); + + if (specified_interface_found == 0) { + inform( + "The mdns service interface \"%s\" was not found, so the setting has been ignored.", + config.interface); free(config.interface); config.interface = NULL; - config.interface_index = 0; - } + config.interface_index = 0; + } } - + /* Get the regtype -- the service type and protocol, separated by a dot. Default is * "_raop._tcp" */ if (config_lookup_string(config.cfg, "general.regtype", &str)) @@ -901,9 +910,9 @@ int main(int argc, char **argv) { // initialise random number generator r64init(0); - + // initialise the randomw number array - + r64arrayinit(); /* Check if we are called with -V or --version parameter */ @@ -1194,7 +1203,7 @@ int main(int argc, char **argv) { debug(1, "decoders_supported field is %d.", config.decoders_supported); debug(1, "use_apple_decoder is %d.", config.use_apple_decoder); if (config.interface) - debug(1, "mdns service interface \"%s\" requested.",config.interface); + debug(1, "mdns service interface \"%s\" requested.", config.interface); else debug(1, "no special mdns service interface was requested."); char *realConfigPath = realpath(config.configfile, NULL); From 052c4da1c287c20242f9a5672993685e184cdc3b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 11 Feb 2017 12:15:17 +0000 Subject: [PATCH 125/135] Update man page and equivalent html page --- man/shairport-sync.7 | 2 +- man/shairport-sync.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 5f5dbac8..1f866811 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -61,7 +61,7 @@ These are the settings available within the \fBgeneral\f1 group: \fBname=\f1\fI"service_name"\f1\fB;\f1 Use this \fIservice_name\f1 to identify this player in iTunes, etc. -The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "2.8.4" and \fB%V\f1 for the shairport-sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata". +The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "3.0.1" and \fB%V\f1 for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc". The default is "%H", which is replaced by the hostname with the first letter capitalised. .TP diff --git a/man/shairport-sync.html b/man/shairport-sync.html index b3ef1ba8..858863ab 100644 --- a/man/shairport-sync.html +++ b/man/shairport-sync.html @@ -101,8 +101,8 @@

The following substitutions are allowed: %h for the computer's hostname, %H for the computer's hostname with the first letter capitalised (ASCII only), - %v for the shairport-sync version number, e.g. "2.8.4" and - %V for the shairport-sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata".

+ %v for the shairport-sync version number, e.g. "3.0.1" and + %V for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".

The default is "%H", which is replaced by the hostname with the first letter capitalised.

From e65d30998b765ad5b4be2403956da458d9b82e8e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 11 Feb 2017 12:28:19 +0000 Subject: [PATCH 126/135] Update LICENSES --- LICENSES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LICENSES b/LICENSES index 63ff1826..583fdf4f 100644 --- a/LICENSES +++ b/LICENSES @@ -11,6 +11,9 @@ tinysvcmdns.c, tinysvcmdns.h: Shairport: Copyright (c) 2011-2013 James Laird +Shairport Sync: + Copyright (c) 2014–2017 Mike Brady + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without From ef15654f6b6bbb287340cc6771040db2564d390e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 11 Feb 2017 12:35:06 +0000 Subject: [PATCH 127/135] Update shairport-sync.conf --- scripts/shairport-sync.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 7b2a82a1..53909bf5 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -9,8 +9,8 @@ general = // You can use the following substitutions: // %h for the hostname, // %H for the Hostname (i.e. with first letter capitalised (ASCII only)), -// %v for the version number, e.g. 2.8.5 and -// %V for the full version string, e.g. 2.8.5-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc +// %v for the version number, e.g. 3.0 and +// %V for the full version string, e.g. 3.0-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc // Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H". // password = "secret"; // leave this commented out if you don't want to require a password // interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor. From 9ed9821ff6c3997fd5e5f9cf98966ee88ea1a0b0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 11 Feb 2017 12:47:30 +0000 Subject: [PATCH 128/135] Update RELEASENOTES.md --- RELEASENOTES.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8217e3f4..a0b9ef42 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,16 @@ -Version 3.0d24 – Development Version +Version 3.0rc0 – Release Candidate 0 ---- Version 3 is a big update -- please see below. +**New Feature** +* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [shtirlic](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details. + +**Other changes** +* Updates to `man` page and [README](https://github.com/mikebrady/shairport-sync/blob/development/README.md). Reports of typos or suggestions for improvement are welcome! + +Version 3.0d24 – Development Version +---- + **New Feature** * Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3. From 6195a46082c71a8f8a11828fb7301e3c5dd121bd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 11 Feb 2017 12:48:02 +0000 Subject: [PATCH 129/135] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a0b9ef42..99d40576 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,7 +3,7 @@ Version 3.0rc0 – Release Candidate 0 Version 3 is a big update -- please see below. **New Feature** -* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [shtirlic](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details. +* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details. **Other changes** * Updates to `man` page and [README](https://github.com/mikebrady/shairport-sync/blob/development/README.md). Reports of typos or suggestions for improvement are welcome! From 020f948b445825ec5768d882aae44ac804959a01 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Feb 2017 20:29:27 +0000 Subject: [PATCH 130/135] Update RELEASENOTES.md --- RELEASENOTES.md | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 99d40576..5a201849 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,34 @@ +Version 3.0 +==== + +Big Update +---- +Version 3 brings in support for 24-bit and 32-bit (and 8 bit!) DACs and for DACs running at multiples of 44,100 samples per second. + +The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC. + +**New Features** +* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. This will give audibly better results if you are using software volume control. (Other back ends are not updated yet.) +* 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. It's only worth doing if 44,100 samples per second output is not available. +* Internal processing including software volume control and interpolation is done after sample size and rate conversion. +* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. There is no obvious audible different between this and the original decoder by David Hammerton. +* Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. +* Settings that were denominated in frames are now deprecated but still honoured. Deprecation warnings are issued. +* Choose Network Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. +* Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3. +* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details. + + +Pesky Changes You Cannot Ignore +---- +* Processor load is up by about 10%. +* Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details. +* Sox-based interpolation at higher sample rates may overload your CPU -- yopu might have to choose between higher sample rates and sox-based interpolation. + + Version 3.0rc0 – Release Candidate 0 ---- -Version 3 is a big update -- please see below. +Note: all Version 3 changes are summarized above. **New Feature** * An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details. @@ -10,49 +38,49 @@ Version 3 is a big update -- please see below. Version 3.0d24 – Development Version ---- +Note: all Version 3 changes are summarized above. **New Feature** * Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3. Version 3.0d23 – Development Version ---- +Note: all Version 3 changes are summarized above. **New Feature** * Choose Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. Version 3.0d22 – Development Version ---- +Note: all Version 3 changes are summarized above. **Bug Fix** * Fixed a bug which prevented successful building in the OpenWrt build system. The problem was caused by an `#include apple_alac.h` in `player.c` which was actioned even if the apple alac decoder was not selected. This caused the OpenWrt build system to expect the standard C++ library – required by the apple alac code – to be referenced, but it was not specified on the build manifest and therefore stopped the build. The solution was to make the `#include` conditional on selecting the apple alac decoder. Version 3.0d21 – Development Version ---- +Note: all Version 3 changes are summarized above. **Bug Fix** * Fixed a bug which turned off resync by default. Duh. Version 3.0d20 – Development Version ---- +Note: all Version 3 changes are summarized above. **Bug Fix** * Fix a small and generally silent error in configure.ac so that it only looks for the systemd direcotry if systemd has been chosen. It caused a warning when cross-compiling. Version 3.0d19 – Development Version ---- +Note: all Version 3 changes are summarized above. **New Feature** * Reduces processor load back to V2.X levels by using a precalculated array of pseudorandom numbers to do dithering. Doesn't seem to make any audible difference. Version 3.0d18 – Development Version ---- - -Big Update -==== - -This is a big update, so please be careful, as there are likely to be lots of bugs and maybe even regressions. It's still very much a moving target. The processor load is about 25% higher. There is still quite a bit of work to do, especially on the backends apart from ALSA. - -The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC. +Note: all Version 3 changes are summarized above. **New Features** * 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. (Other back ends are not updated yet.) From 63c6003aa70067f0e59e0610bd7fa5293f3dbe77 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Feb 2017 20:30:18 +0000 Subject: [PATCH 131/135] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5a201849..14a8e493 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,7 +8,7 @@ Version 3 brings in support for 24-bit and 32-bit (and 8 bit!) DACs and for DACs The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC. **New Features** -* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. This will give audibly better results if you are using software volume control. (Other back ends are not updated yet.) +* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. This will give audibly better results if you are using software volume control on a 24- or 32-bit-capable DAC. (Other back ends are not updated yet.) * 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. It's only worth doing if 44,100 samples per second output is not available. * Internal processing including software volume control and interpolation is done after sample size and rate conversion. * Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. There is no obvious audible different between this and the original decoder by David Hammerton. From e46a5b63ad5ffd809dd399a6f6cb255a16afee7a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Feb 2017 20:31:00 +0000 Subject: [PATCH 132/135] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 14a8e493..f0e0d9d0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -11,7 +11,7 @@ The most obvious audible change is if you are using software volume control and * 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. This will give audibly better results if you are using software volume control on a 24- or 32-bit-capable DAC. (Other back ends are not updated yet.) * 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. It's only worth doing if 44,100 samples per second output is not available. * Internal processing including software volume control and interpolation is done after sample size and rate conversion. -* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. There is no obvious audible different between this and the original decoder by David Hammerton. +* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. There is no obvious audible difference between this and the original decoder by David Hammerton. * Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. * Settings that were denominated in frames are now deprecated but still honoured. Deprecation warnings are issued. * Choose Network Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. From 514b99f748975308df9ef6440c2a08e728f0c0c9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Feb 2017 20:35:11 +0000 Subject: [PATCH 133/135] Update RELEASENOTES.md --- RELEASENOTES.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f0e0d9d0..bb0070be 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,13 +7,14 @@ Version 3 brings in support for 24-bit and 32-bit (and 8 bit!) DACs and for DACs The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC. +Here is the list of new features: + **New Features** * 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. This will give audibly better results if you are using software volume control on a 24- or 32-bit-capable DAC. (Other back ends are not updated yet.) * 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. It's only worth doing if 44,100 samples per second output is not available. * Internal processing including software volume control and interpolation is done after sample size and rate conversion. * Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. There is no obvious audible difference between this and the original decoder by David Hammerton. * Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. -* Settings that were denominated in frames are now deprecated but still honoured. Deprecation warnings are issued. * Choose Network Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. * Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3. * An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details. @@ -23,7 +24,7 @@ Pesky Changes You Cannot Ignore ---- * Processor load is up by about 10%. * Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details. -* Sox-based interpolation at higher sample rates may overload your CPU -- yopu might have to choose between higher sample rates and sox-based interpolation. +* Sox-based interpolation at higher sample rates may overload your CPU -- you might have to choose between higher sample rates and sox-based interpolation. Version 3.0rc0 – Release Candidate 0 From 3a4c9d00d6d72e13b07527b3f7db93a6b2bc169e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Feb 2017 20:37:05 +0000 Subject: [PATCH 134/135] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bb0070be..f58cc94a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -22,7 +22,7 @@ Here is the list of new features: Pesky Changes You Cannot Ignore ---- -* Processor load is up by about 10%. +* Processor load is up by about 11%. * Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details. * Sox-based interpolation at higher sample rates may overload your CPU -- you might have to choose between higher sample rates and sox-based interpolation. From f4ed073cdd6e02057f4f51a46ca1db8c3d99988c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Feb 2017 20:47:53 +0000 Subject: [PATCH 135/135] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f58cc94a..3e1788fe 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -10,10 +10,10 @@ The most obvious audible change is if you are using software volume control and Here is the list of new features: **New Features** -* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. This will give audibly better results if you are using software volume control on a 24- or 32-bit-capable DAC. (Other back ends are not updated yet.) +* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. * 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. It's only worth doing if 44,100 samples per second output is not available. * Internal processing including software volume control and interpolation is done after sample size and rate conversion. -* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. There is no obvious audible difference between this and the original decoder by David Hammerton. +* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac), to be installed. Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. * Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. * Choose Network Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. * Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3.