This commit is contained in:
Mike Brady
2020-07-06 17:09:15 +01:00
parent 8e46117c7c
commit bdc2dca3ec
37 changed files with 3154 additions and 3687 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ Next, configure the build and compile it:
```
$ autoreconf -i -f
./configure --with-libdaemon --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
$ ./configure --with-libdaemon --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
$ make
```
Add `--with-alsa` if you wish to include the ALSA back end. Omit the `--with-sndio` if you don't want the `sndio` back end. Omit the `--with-freebsd-service` if you don't want to install a FreeBSD startup script, runtime folder and user and group -- see below for more details.
+1 -1
View File
@@ -537,7 +537,7 @@ Playback synchronisation is allowed to wander — to "drift" — a small amount
Some Statistics
---------------
If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file:
If you turn on the `general` `statistics` setting, a heading like this will be output to the log file (or to `STDERR` if the `-u` command line option is chosen):
```
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, source nominal frames per second, source actual frames per second, output frames per second, source clock drift in ppm, source clock drift sample count, rough calculated correction in ppm
```
+9 -11
View File
@@ -62,7 +62,7 @@ void going_active(int block) {
command_execute(config.cmd_active_start, "", block);
#ifdef CONFIG_METADATA
debug(2, "abeg"); // active mode begin
send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
send_ssnc_metadata('abeg', NULL, 0, 1); // contains cancellation points
#endif
#ifdef CONFIG_DBUS_INTERFACE
@@ -89,7 +89,7 @@ void going_inactive(int block) {
command_execute(config.cmd_active_stop, "", block);
#ifdef CONFIG_METADATA
debug(2, "aend"); // active mode end
send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
send_ssnc_metadata('aend', NULL, 0, 1); // contains cancellation points
#endif
#ifdef CONFIG_DBUS_INTERFACE
@@ -196,21 +196,19 @@ void *activity_monitor_thread_code(void *arg) {
} else {
state = am_timing_out;
uint64_t time_to_wait_for_wakeup_fp =
(uint64_t)(config.active_state_timeout * 1000000); // resolution of microseconds
time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32;
time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000;
uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(config.active_state_timeout * 1000000000);
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp;
sec = time_of_wakeup_fp >> 32;
nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
sec = time_of_wakeup_ns / 1000000000;
nsec = time_of_wakeup_ns % 1000000000;
time_for_wait.tv_sec = sec;
time_for_wait.tv_nsec = nsec;
#endif
#ifdef COMPILE_FOR_OSX
sec = time_to_wait_for_wakeup_fp >> 32;
nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
sec = time_to_wait_for_wakeup_ns / 1000000000;
nsec = time_to_wait_for_wakeup_ns % 1000000000;
time_for_wait.tv_sec = sec;
time_for_wait.tv_nsec = nsec;
#endif
-1
View File
@@ -27,7 +27,6 @@
*
*/
#ifndef __ALAC__DECOMP_H
#define __ALAC__DECOMP_H
+30 -10
View File
@@ -126,6 +126,7 @@ void parse_general_audio_options(void) {
* are set before any options are chosen */
int value;
double dvalue;
const char *str = 0;
if (config.cfg != NULL) {
/* Get the desired buffer size setting (deprecated). */
@@ -147,10 +148,10 @@ void parse_general_audio_options(void) {
/* Get the desired buffer size setting in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
if ((dvalue < 0) || (dvalue > 1.5)) {
if (dvalue < 0) {
die("Invalid audio_backend_buffer_desired_length_in_seconds value: \"%f\". It "
"should be between 0 and "
"1.5, default is %.3f seconds",
"should be 0.0 or greater."
" The default is %.3f seconds",
dvalue, config.audio_backend_buffer_desired_length);
} else {
config.audio_backend_buffer_desired_length = dvalue;
@@ -190,23 +191,42 @@ void parse_general_audio_options(void) {
/* Get the latency offset in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds",
&dvalue)) {
if ((dvalue < -1.75) || (dvalue > 1.75)) {
die("Invalid audio_backend_latency_offset_in_seconds \"%f\". It "
"should be between -1.75 and +1.75, default is 0 seconds",
dvalue);
} else {
config.audio_backend_latency_offset = dvalue;
}
/* Check if the length of the silent lead-in ia set to \"auto\". */
if (config_lookup_string(config.cfg, "general.audio_backend_silent_lead_in_time", &str)) {
if (strcasecmp(str, "auto") == 0) {
config.audio_backend_silent_lead_in_time_auto = 1;
} else {
if (config.audio_backend_silent_lead_in_time_auto == 1)
warn("Invalid audio_backend_silent_lead_in_time \"%s\". It should be \"auto\" or the "
"lead-in time in seconds. "
"It remains set to \"auto\". Note: numbers should not be placed in quotes.",
str);
else
warn("Invalid output rate \"%s\". It should be \"auto\" or the lead-in time in seconds. "
"It remains set to %f. Note: numbers should not be placed in quotes.",
str, config.audio_backend_silent_lead_in_time);
}
}
/* Get the desired length of the silent lead-in. */
if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_time", &dvalue)) {
if ((dvalue < 0.0) || (dvalue > 4)) {
die("Invalid audio_backend_silent_lead_in_time \"%f\". It "
"must be between 0.0 and 4.0 seconds. Omit setting to use the default value",
if (config.audio_backend_silent_lead_in_time_auto == 1)
warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
"must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
"The setting remains at \"auto\".",
dvalue);
else
warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
"must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
"It remains set to %f.",
dvalue, config.audio_backend_silent_lead_in_time);
} else {
config.audio_backend_silent_lead_in_time = dvalue;
config.audio_backend_silent_lead_in_time_auto = 0;
}
}
}
+34 -36
View File
@@ -210,9 +210,6 @@ int precision_delay_available() {
generate_zero_frames(silence, frames_of_silence, config.output_format,
use_dither, // i.e. with dither
dither_random_number_store);
// debug(1,"Play %d frames of silence with most_recent_write_time of
// %" PRIx64 ".",
// frames_of_silence,most_recent_write_time);
do_play(silence, frames_of_silence);
pthread_cleanup_pop(1);
// now we can get the delay, and we'll note if it uses update timestamps
@@ -255,8 +252,6 @@ static uint64_t frames_played_at_measurement_start_time;
static uint64_t measurement_time;
static uint64_t frames_played_at_measurement_time;
volatile uint64_t most_recent_write_time;
static uint64_t frames_sent_for_playing;
static uint64_t frame_index;
static int measurement_data_is_valid;
@@ -342,7 +337,8 @@ void actual_close_alsa_device() {
if (alsa_handle) {
int derr;
if ((derr = snd_pcm_hw_free(alsa_handle)))
debug(1, "Error %d (\"%s\") freeing the output device hardware while "
debug(1,
"Error %d (\"%s\") freeing the output device hardware while "
"closing it.",
derr, snd_strerror(derr));
@@ -358,7 +354,10 @@ void actual_close_alsa_device() {
// The lowest rate that the DAC is capable of is chosen.
unsigned int auto_speed_output_rates[] = {
44100, 88200, 176400, 352800,
44100,
88200,
176400,
352800,
};
// This array is of all the formats known to Shairport Sync, in order of the SPS_FORMAT definitions,
@@ -707,7 +706,8 @@ int actual_open_alsa_device(int do_auto_setup) {
buffer_size);
}
*/
debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend "
debug(1,
"The alsa buffer is smaller (%lu bytes) than the desired backend "
"buffer "
"length (%ld) you have chosen.",
actual_buffer_length, config.audio_backend_buffer_desired_length);
@@ -885,8 +885,8 @@ int prepare_mixer() {
// obtained
pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
if (open_mixer() == 1) {
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,
@@ -931,10 +931,10 @@ int prepare_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, "alsa: hardware mixer \"%s\" selected, with dB volume "
debug(1,
"alsa: hardware mixer \"%s\" selected, with dB volume "
"from %f to %f.",
alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0,
(1.0 * alsa_mix_maxdb) / 100.0);
alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0);
has_softvol = 1;
audio_alsa.volume = &volume; // insert the volume function now
// we know it can do dB stuff
@@ -979,10 +979,7 @@ int prepare_mixer() {
return response;
}
int alsa_device_init() {
return prepare_mixer();
}
int alsa_device_init() { return prepare_mixer(); }
static int init(int argc, char **argv) {
// for debugging
@@ -1140,8 +1137,10 @@ static int init(int argc, char **argv) {
"\"S16\", \"S24\", \"S24_LE\", \"S24_BE\", "
"\"S24_3LE\", \"S24_3BE\" or "
"\"S32\", \"S32_LE\", \"S32_BE\". It remains set to \"%s\".",
str, config.output_format_auto_requested == 1 ? "auto" : sps_format_description_string(
config.output_format));
str,
config.output_format_auto_requested == 1
? "auto"
: sps_format_description_string(config.output_format));
}
}
@@ -1291,9 +1290,9 @@ static int init(int argc, char **argv) {
warn("Invalid use_precision_timing option choice \"%s\". It should be "
"\"yes\", \"auto\" or \"no\". "
"It remains set to \"%s\".",
config.use_precision_timing == YNA_NO ? "no" : config.use_precision_timing == YNA_AUTO
? "auto"
: "yes");
config.use_precision_timing == YNA_NO
? "no"
: config.use_precision_timing == YNA_AUTO ? "auto" : "yes");
}
}
@@ -1350,7 +1349,6 @@ static int init(int argc, char **argv) {
// length of the queue
// if the queue gets too short, stuff it with silence
most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code
pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL);
return response;
@@ -1525,12 +1523,14 @@ int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) ||
((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) {
debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64
debug(2,
"DAC seems to have stalled with time_now_ns: %" PRIX64
", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64
", stall_monitor_error_threshold %" PRIX64 ".",
time_now_ns, update_timestamp_ns, stall_monitor_start_time,
stall_monitor_error_threshold);
debug(2, "DAC seems to have stalled with time_now: %lx,%lx"
debug(2,
"DAC seems to have stalled with time_now: %lx,%lx"
", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64
", stall_monitor_error_threshold %" PRIX64 ".",
tn.tv_sec, tn.tv_nsec, update_timestamp.tv_sec, update_timestamp.tv_nsec,
@@ -1605,6 +1605,7 @@ int delay(long *the_delay) {
}
int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) {
// elapsed_time is in nanoseconds
int response = 0; // zero means okay
if (measurement_data_is_valid) {
*elapsed_time = measurement_time - measurement_start_time;
@@ -1657,7 +1658,7 @@ int do_play(void *buf, int samples) {
if ((frame_index == start_measurement_from_this_frame) ||
((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) {
measurement_time = get_absolute_time_in_fp();
measurement_time = get_absolute_time_in_ns();
frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples;
if (frame_index == start_measurement_from_this_frame) {
@@ -1697,7 +1698,8 @@ int do_play(void *buf, int samples) {
}
}
} else {
debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* "
debug(1,
"alsa: device status returns fault status %d and SND_PCM_STATE_* "
"%d for play.",
ret, state);
frame_index = 0;
@@ -1744,6 +1746,7 @@ int do_close() {
// debug(1,"alsa: do_close() -- closing the output device");
if ((derr = snd_pcm_drop(alsa_handle)))
debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr));
usleep(5000);
if ((derr = snd_pcm_hw_free(alsa_handle)))
debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr));
@@ -1946,7 +1949,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
alsa_device_init();
alsa_device_initialised = 1;
}
int sleep_time_ms = (int)(config.disable_standby_mode_silence_scan_interval * 1000);
int sleep_time_us = (int)(config.disable_standby_mode_silence_scan_interval * 1000000);
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0);
// check possible state transitions here
if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) {
@@ -1974,8 +1977,6 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
int reply;
long buffer_size = 0;
snd_pcm_state_t state;
uint64_t present_time = get_absolute_time_in_fp();
if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) {
reply = delay_and_status(&state, &buffer_size, NULL);
if (reply != 0) {
buffer_size = 0;
@@ -1988,9 +1989,6 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
(long)(config.disable_standby_mode_silence_threshold * config.output_rate);
size_t size_of_silence_buffer;
if (buffer_size < buffer_size_threshold) {
uint64_t sleep_time_in_fp = sleep_time_ms;
sleep_time_in_fp = sleep_time_in_fp << 32;
sleep_time_in_fp = sleep_time_in_fp / 1000;
int frames_of_silence = 1024;
size_of_silence_buffer = frames_of_silence * frame_size;
void *silence = malloc(size_of_silence_buffer);
@@ -2016,7 +2014,8 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
error_count++;
char errorstring[1024];
strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
debug(2, "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
debug(2,
"alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
"to alsa device -- %d errors in %d trials.",
ret, (char *)errorstring, frames_of_silence, error_count, frame_count);
if ((error_count > 40) && (frame_count < 100)) {
@@ -2029,10 +2028,9 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
}
}
}
}
debug_mutex_unlock(&alsa_mutex, 0);
pthread_cleanup_pop(0); // release the mutex
usleep(sleep_time_ms * 1000); // has a cancellation point in it
usleep(sleep_time_us); // has a cancellation point in it
}
pthread_exit(NULL);
}
+1 -1
View File
@@ -39,7 +39,7 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
static void deinit(void) {}
static void start(int sample_rate, __attribute__((unused)) int sample_format) {
debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate);
debug(1, "dummy audio output started at %d frames per second.", sample_rate);
}
static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {
+11 -30
View File
@@ -89,14 +89,9 @@ typedef struct soxr_quality {
const char *name;
} soxr_quality_t;
static soxr_quality_t soxr_quality_table[] = {
{ SOXR_VHQ, "very high" },
{ SOXR_HQ, "high" },
{ SOXR_MQ, "medium" },
{ SOXR_LQ, "low" },
{ SOXR_QQ, "quick" },
{ -1, NULL }
};
static soxr_quality_t soxr_quality_table[] = {{SOXR_VHQ, "very high"}, {SOXR_HQ, "high"},
{SOXR_MQ, "medium"}, {SOXR_LQ, "low"},
{SOXR_QQ, "quick"}, {-1, NULL}};
static int parse_soxr_quality_name(const char *name) {
for (soxr_quality_t *s = soxr_quality_table; s->name != NULL; ++s) {
@@ -122,8 +117,7 @@ static inline sample_t sample_conv(short sample) {
return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX));
}
static void deinterleave(const char *interleaved_input_buffer,
sample_t *jack_output_buffer[],
static void deinterleave(const char *interleaved_input_buffer, sample_t *jack_output_buffer[],
jack_nframes_t offset, jack_nframes_t nframes) {
jack_nframes_t f;
// We're dealing with 16bit audio here:
@@ -353,8 +347,7 @@ void jack_deinit() {
#endif
}
void jack_start(int i_sample_rate,
__attribute__((unused)) int i_sample_format) {
void jack_start(int i_sample_rate, __attribute__((unused)) int i_sample_format) {
// Nothing to do, JACK client has already been set up at jack_init().
// Also, we have no say over the sample rate or sample format of JACK,
// We convert the 16bit samples to float, and die if the sample rate is != 44k1 without soxr.
@@ -365,13 +358,7 @@ void jack_start(int i_sample_rate,
soxr_delete(soxr);
}
soxr_error_t e = NULL;
soxr = soxr_create(i_sample_rate,
sample_rate,
NPORTS,
&e,
&io_spec,
&quality_spec,
NULL);
soxr = soxr_create(i_sample_rate, sample_rate, NPORTS, &e, &io_spec, &quality_spec, NULL);
if (!soxr) {
die("Unable to create soxr resampler for JACK: %s", e);
}
@@ -395,13 +382,13 @@ int jack_delay(long *the_delay) {
// occupancy after another transfer had occurred, so we could "lose" a full transfer
// (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds)
pthread_mutex_lock(&buffer_mutex);
int64_t time_now = get_absolute_time_in_fp();
int64_t delta = time_now - time_of_latest_transfer;
int64_t time_now = get_absolute_time_in_ns();
int64_t delta = time_now - time_of_latest_transfer; // nanoseconds
size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf) / bytes_per_frame;
debug(2, "audio_occupancy_now is %d.", audio_occupancy_now);
pthread_mutex_unlock(&buffer_mutex);
int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) >> 32;
int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) / 1000000000;
// debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check);
// jack_latency is set by the graph() callback, it's the average of the maximum
// latencies of all our output ports. Adjust this constant baseline delay according
@@ -428,13 +415,7 @@ int play(void *buf, int samples) {
size_t i_done, o_done;
soxr_error_t e;
while (samples > 0 && thisbuf > 0) {
e = soxr_process(soxr,
(soxr_in_t)in,
samples,
&i_done,
(soxr_out_t)out,
thisbuf,
&o_done);
e = soxr_process(soxr, (soxr_in_t)in, samples, &i_done, (soxr_out_t)out, thisbuf, &o_done);
if (e)
die("Error during soxr process: %s", e);
@@ -456,7 +437,7 @@ int play(void *buf, int samples) {
}
#endif
}
time_of_latest_transfer = get_absolute_time_in_fp();
time_of_latest_transfer = get_absolute_time_in_ns();
pthread_mutex_unlock(&buffer_mutex);
if (samples) {
warn("JACK ringbuffer overrun. Dropped %d samples.", samples);
+7 -1
View File
@@ -85,6 +85,11 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
if (config.cfg != NULL) {
const char *str;
/* Get the PulseAudio server name. */
if (config_lookup_string(config.cfg, "pa.server", &str)) {
config.pa_server = (char *)str;
}
/* Get the Application Name. */
if (config_lookup_string(config.cfg, "pa.application_name", &str)) {
config.pa_application_name = (char *)str;
@@ -126,7 +131,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
// Start the mainloop
if (pa_threaded_mainloop_start(mainloop) != 0)
die("could not start the pulseaudio threaded mainloop");
if (pa_context_connect(context, NULL, 0, NULL) != 0)
if (pa_context_connect(context, config.pa_server, 0, NULL) != 0)
die("failed to connect to the pulseaudio context -- the error message is \"%s\".",
pa_strerror(pa_context_errno(context)));
+21 -21
View File
@@ -42,7 +42,6 @@
static int fd = -1;
char *pipename = NULL;
int warned = 0;
static void start(__attribute__((unused)) int sample_rate,
__attribute__((unused)) int sample_format) {
@@ -51,14 +50,18 @@ static void start(__attribute__((unused)) int sample_rate,
// we check that it's not a "real" error though. From the "man 2 open" page:
// "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
// open for reading."
fd = open(pipename, O_WRONLY | O_NONBLOCK);
if ((fd == -1) && (errno != ENXIO) && (warned == 0)) {
fd = try_to_open_pipe_for_writing(pipename);
// we check that it's not a "real" error. From the "man 2 open" page:
// "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
// open for reading." Which is okay.
if ((fd == -1) && (errno != ENXIO)) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "pipe: start -- error %d (\"%s\") opening the pipe named \"%s\".", errno,
debug(1, "audio_pipe start -- error %d (\"%s\") opening pipe: \"%s\".", errno,
(char *)errorstring, pipename);
warn("can not open audio pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
(char *)errorstring, pipename);
warn("Error %d opening the pipe named \"%s\".", errno, pipename);
warned = 1;
}
}
@@ -66,32 +69,27 @@ static int play(void *buf, int samples) {
// if the file is not open, try to open it.
char errorstring[1024];
if (fd == -1) {
fd = open(pipename, O_WRONLY | O_NONBLOCK);
fd = try_to_open_pipe_for_writing(pipename);
}
// if it's got a reader, write to it.
if (fd > 0) {
int rc = non_blocking_write(fd, buf, samples * 4);
if ((rc < 0) && (warned == 0)) {
//int rc = non_blocking_write(fd, buf, samples * 4);
int rc = write(fd, buf, samples * 4);
if ((rc < 0) && (errno != EPIPE)) {
strerror_r(errno, (char *)errorstring, 1024);
warn("Error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
warned = 1;
debug(1, "audio_pip play: error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
}
} else if ((fd == -1) && (errno != ENXIO) && (warned == 0)) {
strerror_r(errno, (char *)errorstring, 1024);
warn("Error %d opening the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
warned = 1;
}
return warned;
return 0;
}
static void stop(void) {
// Don't close the pipe just because a play session has stopped.
// if (fd > 0)
// close(fd);
}
static int init(int argc, char **argv) {
debug(1, "pipe init");
// debug(1, "pipe init");
// const char *str;
// int value;
// double dvalue;
@@ -122,10 +120,12 @@ static int init(int argc, char **argv) {
pipename = strdup(argv[0]);
// here, create the pipe
mode_t oldumask = umask(000);
if (mkfifo(pipename, 0644) && errno != EEXIST)
die("Could not create output pipe \"%s\"", pipename);
die("Could not create audio pipe \"%s\"", pipename);
umask(oldumask);
debug(1, "Pipename is \"%s\"", pipename);
debug(1, "audio pipe name is \"%s\"", pipename);
return 0;
}
+9 -5
View File
@@ -247,7 +247,7 @@ static void stop() {
}
static void onmove_cb(__attribute__((unused)) void *arg, int delta) {
time_of_last_onmove_cb = get_absolute_time_in_fp();
time_of_last_onmove_cb = get_absolute_time_in_ns();
at_least_one_onmove_cb_seen = 1;
played += delta;
}
@@ -258,10 +258,14 @@ static int delay(long *_delay) {
if (at_least_one_onmove_cb_seen) { // when output starts, the onmove_cb callback will be made
// calculate the difference in time between now and when the last callback occurred,
// and use it to estimate the frames that would have been output
uint64_t time_difference = get_absolute_time_in_fp() - time_of_last_onmove_cb;
uint64_t frame_difference = time_difference * par.rate;
uint64_t frame_difference_big_integer = frame_difference >> 32;
estimated_extra_frames_output = frame_difference_big_integer;
uint64_t time_difference = get_absolute_time_in_ns() - time_of_last_onmove_cb;
uint64_t frame_difference = (time_difference * par.rate) / 1000000000;
estimated_extra_frames_output = frame_difference;
// sanity check -- total estimate can not exceed frames written.
if ((estimated_extra_frames_output + played) > written / framesize) {
// debug(1,"play estimate fails sanity check, possibly due to running on a VM");
estimated_extra_frames_output = 0; // can't make any sensible guess
}
// debug(1,"Frames played to last cb: %d, estimated to current time:
// %d.",played,estimated_extra_frames_output);
}
+2 -1
View File
@@ -24,7 +24,8 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m
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 , "
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);
+1 -1
View File
@@ -56,7 +56,7 @@ static int play(void *buf, int samples) {
}
static void stop(void) {
// don't close stdout
// Do nothing when play stops
}
static int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
+216 -87
View File
@@ -40,6 +40,8 @@
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <libgen.h>
#ifdef COMPILE_FOR_OSX
#include <CoreServices/CoreServices.h>
@@ -88,10 +90,11 @@ void set_alsa_out_dev(char *);
#endif
config_t config_file_stuff;
int emergency_exit;
pthread_t main_thread_id;
uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
// always lock use this when accessing the fp_time_at_last_debug_message
// always lock use this when accessing the ns_time_at_last_debug_message
static pthread_mutex_t debug_timing_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t the_conn_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -101,7 +104,7 @@ const char *sps_format_description_string_array[] = {
"S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "auto", "invalid"};
const char *sps_format_description_string(sps_format_t format) {
if ((format >= SPS_FORMAT_UNKNOWN) && (format <= SPS_FORMAT_AUTO))
if (format <= SPS_FORMAT_AUTO)
return sps_format_description_string_array[format];
else
return sps_format_description_string_array[SPS_FORMAT_INVALID];
@@ -120,7 +123,7 @@ static void (*sps_log)(int prio, const char *t, ...) = daemon_log;
static void (*sps_log)(int prio, const char *t, ...) = syslog;
#endif
void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
void do_sps_log_to_stderr(__attribute__((unused)) int prio, const char *t, ...) {
char s[1024];
va_list args;
va_start(args, t);
@@ -129,7 +132,86 @@ void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
fprintf(stderr, "%s\n", s);
}
void log_to_stderr() { sps_log = do_sps_log; }
void do_sps_log_to_stdout(__attribute__((unused)) int prio, const char *t, ...) {
char s[1024];
va_list args;
va_start(args, t);
vsnprintf(s, sizeof(s), t, args);
va_end(args);
fprintf(stdout, "%s\n", s);
}
int create_log_file(const char* path) {
int fd = -1;
if (path != NULL) {
char *dirc = strdup(path);
if (dirc) {
char *dname = dirname(dirc);
// create the directory, if necessary
int result = 0;
if (dname) {
char *pdir = realpath(dname, NULL); // will return a NULL if the directory doesn't exist
if (pdir == NULL) {
mode_t oldumask = umask(000);
result = mkpath(dname, 0777);
umask(oldumask);
} else {
free(pdir);
}
if ((result == 0) || (result == -EEXIST)) {
// now open the file
fd = open(path, O_WRONLY | O_NONBLOCK | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if ((fd == -1) && (errno == EEXIST))
fd = open(path, O_WRONLY | O_APPEND | O_NONBLOCK);
if (fd >= 0) {
// now we switch to blocking mode
int flags = fcntl(fd, F_GETFL);
if (flags == -1) {
// strerror_r(errno, (char *)errorstring, sizeof(errorstring));
// debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
// (char *)errorstring, pathname);
} else {
flags = fcntl(fd, F_SETFL,flags & ~O_NONBLOCK);
// if (flags == -1) {
// strerror_r(errno, (char *)errorstring, sizeof(errorstring));
// debug(1, "create_log_file -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
// (char *)errorstring, pathname);
}
}
}
}
free(dirc);
}
}
return fd;
}
void do_sps_log_to_fd(__attribute__((unused)) int prio, const char *t, ...) {
char s[1024];
va_list args;
va_start(args, t);
vsnprintf(s, sizeof(s), t, args);
va_end(args);
if (config.log_fd == -1)
config.log_fd = create_log_file(config.log_file_path);
if (config.log_fd >= 0) {
dprintf(config.log_fd, "%s\n", s);
} else if (errno != ENXIO) { // maybe there is a pipe there but not hooked up
fprintf(stderr, "%s\n", s);
}
}
void log_to_stderr() { sps_log = do_sps_log_to_stderr; }
void log_to_stdout() { sps_log = do_sps_log_to_stdout; }
void log_to_file() { sps_log = do_sps_log_to_fd; }
void log_to_syslog() {
#ifdef CONFIG_LIBDAEMON
sps_log = daemon_log;
#else
sps_log = syslog;
#endif
}
shairport_cfg config;
@@ -202,10 +284,10 @@ char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss
insertion_point = insertion_point + strlen(insertion_point);
space_remaining = space_remaining - strlen(insertion_point);
}
if (prefix) {
snprintf(insertion_point, space_remaining, "%s", prefix);
insertion_point = insertion_point + strlen(insertion_point);
space_remaining = space_remaining - strlen(insertion_point);
}
return insertion_point;
}
@@ -218,17 +300,17 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
uint64_t time_now = get_absolute_time_in_fp();
uint64_t time_since_start = time_now - fp_time_at_startup;
uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
fp_time_at_last_debug_message = time_now;
uint64_t time_now = get_absolute_time_in_ns();
uint64_t time_since_start = time_now - ns_time_at_startup;
uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
uint64_t divisor = (uint64_t)1 << 32;
s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
1.0 * time_since_last_debug_message / divisor, filename,
s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *fatal error: ");
} else {
s = b;
strncpy(b, "fatal error: ", sizeof(b));
s = b+strlen(b);
}
va_list args;
va_start(args, format);
@@ -236,7 +318,8 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
va_end(args);
sps_log(LOG_ERR, "%s", b);
pthread_setcancelstate(oldState, NULL);
abort(); // exit() doesn't always work, by heaven.
emergency_exit = 1;
exit(EXIT_FAILURE);
}
void _warn(const char *filename, const int linenumber, const char *format, ...) {
@@ -247,17 +330,17 @@ void _warn(const char *filename, const int linenumber, const char *format, ...)
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
uint64_t time_now = get_absolute_time_in_fp();
uint64_t time_since_start = time_now - fp_time_at_startup;
uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
fp_time_at_last_debug_message = time_now;
uint64_t time_now = get_absolute_time_in_ns();
uint64_t time_since_start = time_now - ns_time_at_startup;
uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
uint64_t divisor = (uint64_t)1 << 32;
s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
1.0 * time_since_last_debug_message / divisor, filename,
s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *warning: ");
} else {
s = b;
strncpy(b, "warning: ", sizeof(b));
s = b+strlen(b);
}
va_list args;
va_start(args, format);
@@ -275,14 +358,13 @@ void _debug(const char *filename, const int linenumber, int level, const char *f
char b[1024];
b[0] = 0;
pthread_mutex_lock(&debug_timing_lock);
uint64_t time_now = get_absolute_time_in_fp();
uint64_t time_since_start = time_now - fp_time_at_startup;
uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
fp_time_at_last_debug_message = time_now;
uint64_t time_now = get_absolute_time_in_ns();
uint64_t time_since_start = time_now - ns_time_at_startup;
uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
uint64_t divisor = (uint64_t)1 << 32;
char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
1.0 * time_since_last_debug_message / divisor, filename,
char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " ");
va_list args;
va_start(args, format);
@@ -300,14 +382,13 @@ void _inform(const char *filename, const int linenumber, const char *format, ...
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
uint64_t time_now = get_absolute_time_in_fp();
uint64_t time_since_start = time_now - fp_time_at_startup;
uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
fp_time_at_last_debug_message = time_now;
uint64_t time_now = get_absolute_time_in_ns();
uint64_t time_since_start = time_now - ns_time_at_startup;
uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
uint64_t divisor = (uint64_t)1 << 32;
s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
1.0 * time_since_last_debug_message / divisor, filename,
s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " ");
} else {
s = b;
@@ -1013,43 +1094,77 @@ uint64_t get_absolute_time_in_fp() {
return time_now_fp;
}
ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout) {
// timeout is in milliseconds
void *ibuf = (void *)buf;
size_t bytes_remaining = count;
int rc = 1;
struct pollfd ufds[1];
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, timeout);
if (rc < 0) {
// debug(1, "non-blocking write error waiting for pipe to become ready for writing...");
} else if (rc == 0) {
// warn("non-blocking write timeout waiting for pipe to become ready for writing");
rc = -1;
errno = -ETIMEDOUT;
} else { // rc > 0, implying it might be ready
ssize_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 = bytes_written; // to imitate the return from write()
} else {
ibuf += bytes_written;
bytes_remaining -= bytes_written;
uint64_t get_absolute_time_in_ns() {
uint64_t time_now_ns;
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
struct timespec tn;
// can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
clock_gettime(CLOCK_MONOTONIC, &tn);
uint64_t tnnsec = tn.tv_sec;
tnnsec = tnnsec * 1000000000;
uint64_t tnjnsec = tn.tv_nsec;
time_now_ns = tnnsec + tnjnsec;
#endif
#ifdef COMPILE_FOR_OSX
uint64_t time_now_mach;
uint64_t elapsedNano;
static mach_timebase_info_data_t sTimebaseInfo = {0, 0};
time_now_mach = mach_absolute_time();
// If this is the first time we've run, get the timebase.
// We can use denom == 0 to indicate that sTimebaseInfo is
// uninitialised because it makes no sense to have a zero
// denominator in a fraction.
if (sTimebaseInfo.denom == 0) {
debug(1, "Mac initialise timebase info.");
(void)mach_timebase_info(&sTimebaseInfo);
}
}
}
if (rc > 0)
return count - bytes_remaining; // this is just to mimic a normal write/3.
else
return rc;
// return write(fd,buf,count);
// Do the maths. We hope that the multiplication doesn't
// overflow; the price you pay for working in fixed point.
// this gives us nanoseconds
time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
#endif
return time_now_ns;
}
ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
return non_blocking_write_with_timeout(fd, buf, count, 5000); // default is 5 seconds.
int try_to_open_pipe_for_writing(const char* pathname) {
// tries to open the pipe in non-blocking mode first.
// if it succeeds, it sets it to blocking.
// if not, it returns -1.
int fdis = open(pathname, O_WRONLY | O_NONBLOCK); // open it in non blocking mode first
// we check that it's not a "real" error. From the "man 2 open" page:
// "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
// open for reading." Which is okay.
// This is checked by the caller.
if (fdis >= 0) {
// now we switch to blocking mode
int flags = fcntl(fdis, F_GETFL);
if (flags == -1) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "try_to_open_pipe -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
(char *)errorstring, pathname);
} else {
flags = fcntl(fdis, F_SETFL,flags & ~O_NONBLOCK);
if (flags == -1) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "try_to_open_pipe -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
(char *)errorstring, pathname);
}
}
}
return fdis;
}
/* from
@@ -1093,7 +1208,9 @@ char *str_replace(const char *string, const char *substr, const char *replacemen
/* from http://burtleburtle.net/bob/rand/smallprng.html */
// this is not thread-safe, so we need a mutex on it to use it properly// always lock use this when accessing the fp_time_at_last_debug_message
// this is not thread-safe, so we need a mutex on it to use it properly.
// always lock use this when accessing the fp_time_at_last_debug_message
pthread_mutex_t r64_mutex = PTHREAD_MUTEX_INITIALIZER;
// typedef uint64_t u8;
@@ -1201,18 +1318,17 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
timeoutTime.tv_sec = time_then;
timeoutTime.tv_nsec = time_then_nsec;
int64_t start_time = get_absolute_time_in_fp();
uint64_t start_time = get_absolute_time_in_ns();
int r = pthread_mutex_timedlock(mutex, &timeoutTime);
int64_t et = get_absolute_time_in_fp() - start_time;
uint64_t et = get_absolute_time_in_ns() - start_time;
if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
et = (et * 1000000) >> 32; // microseconds
char errstr[1000];
if (r == ETIMEDOUT)
debug(debuglevel,
"timed out waiting for a mutex, having waiting %f seconds, with a maximum "
"timed out waiting for a mutex, having waited %f microseconds, with a maximum "
"waiting time of %d microseconds. \"%s\".",
(1.0 * et) / 1000000, dally_time, debugmessage);
(1.0E6 * et) / 1000000000, dally_time, debugmessage);
else
debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
strerror_r(r, errstr, sizeof(errstr)), debugmessage);
@@ -1261,7 +1377,7 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
return pthread_mutex_lock(mutex);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
uint64_t time_at_start = get_absolute_time_in_fp();
uint64_t time_at_start = get_absolute_time_in_ns();
char dstring[1000];
memset(dstring, 0, sizeof(dstring));
snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
@@ -1270,12 +1386,10 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
int result = sps_pthread_mutex_timedlock(mutex, dally_time, dstring, debuglevel);
if (result == ETIMEDOUT) {
result = pthread_mutex_lock(mutex);
uint64_t time_delay = get_absolute_time_in_fp() - time_at_start;
uint64_t divisor = (uint64_t)1 << 32;
double delay = 1.0 * time_delay / divisor;
uint64_t time_delay = get_absolute_time_in_ns() - time_at_start;
debug(debuglevel,
"mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.",
mutexname, dstring, (1.0 * dally_time) / 1000000, delay);
"mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f microseconds.",
mutexname, dstring, (1.0 * dally_time), 0.001 * time_delay);
}
pthread_setcancelstate(oldState, NULL);
return result;
@@ -1313,6 +1427,9 @@ char *get_version_string() {
if (version_string) {
strcpy(version_string, PACKAGE_VERSION);
#ifdef CONFIG_APPLE_ALAC
strcat(version_string, "-alac");
#endif
#ifdef CONFIG_LIBDAEMON
strcat(version_string, "-libdaemon");
#endif
@@ -1543,9 +1660,11 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t f
return previous_random_number;
}
// This will check the incoming string "s" of length "len" with the existing NUL-terminated string "str" and update "flag" accordingly.
// This will check the incoming string "s" of length "len" with the existing NUL-terminated string
// "str" and update "flag" accordingly.
// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings are stored.
// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings
// are stored.
// If the strings are different, the str is free'd and replaced by a pointer
// to a newly strdup'd string and the flag is set
@@ -1558,7 +1677,7 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
free(*str);
//*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
char *p = malloc(len + 1);
memcpy(p,s,len);
memcpy(p, s, len);
p[len] = '\0';
*str = p;
*flag = 1;
@@ -1575,7 +1694,7 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
if ((s) && (len)) {
//*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
char *p = malloc(len + 1);
memcpy(p,s,len);
memcpy(p, s, len);
p[len] = '\0';
*str = p;
*flag = 1;
@@ -1586,3 +1705,13 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
}
return *flag;
}
// from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks
void* memdup(const void* mem, size_t size) {
void* out = malloc(size);
if(out != NULL)
memcpy(out, mem, size);
return out;
}
+31 -13
View File
@@ -100,9 +100,13 @@ typedef enum {
const char *sps_format_description_string(sps_format_t format);
typedef struct {
double resend_control_first_check_time; // wait this long before asking for a missing packet to be resent
double missing_port_dacp_scan_interval_seconds; // if no DACP port number can be found, check at
// these intervals
double resend_control_first_check_time; // wait this long before asking for a missing packet to be
// resent
double resend_control_check_interval_time; // wait this long between making requests
double resend_control_last_check_time; // if the packet is missing this close to the time of use, give up
double resend_control_last_check_time; // if the packet is missing this close to the time of use,
// give up
pthread_mutex_t lock;
config_t *cfg;
int endianness;
@@ -113,6 +117,7 @@ typedef struct {
// on host %h"
#ifdef CONFIG_PA
char *pa_server; // the pulseaudio server address that Shairport Sync will play on.
char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in
// the Sound Preferences in most desktop Linuxes.
// Defaults to "Shairport Sync". Shairport Sync must be playing to see it.
@@ -177,6 +182,8 @@ typedef struct {
char *pidfile;
#endif
int log_fd; // file descriptor of the file or pipe to log stuff to.
char *log_file_path; // path to file or pipe to log to, if any
int logOutputLevel; // log output level
int debugger_show_elapsed_time; // in the debug message, display the time since startup
int debugger_show_relative_time; // in the debug message, display the time since the last one
@@ -212,7 +219,11 @@ typedef struct {
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
int audio_backend_silent_lead_in_time_auto; // true if the lead-in time should be from as soon as
// packets are received
double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play.
uint32_t minimum_free_buffer_headroom; // when effective latency is calculated, ensure this number
// of buffers are unallocated
double active_state_timeout; // the amount of time from when play ends to when the system leaves
// into the "active" mode.
uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
@@ -274,7 +285,10 @@ typedef struct {
int jack_soxr_resample_quality;
#endif
#endif
void *gradients; // a linked list of the clock gradients discovered for all DACP IDs
// can't use IP numbers as they might be given to different devices
// can't get hold of MAC addresses.
// can't define the nvll linked list struct here
} shairport_cfg;
// accessors to config for multi-thread access
@@ -286,7 +300,12 @@ uint16_t nctohs(const uint8_t *p); // read 2 characters from *p and do ntohs on
void memory_barrier();
void log_to_stderr(); // call this to director logging to stderr;
void log_to_stderr(); // call this to direct logging to stderr;
void log_to_stdout(); // call this to direct logging to stdout;
void log_to_syslog(); // call this to direct logging to the system log;
void log_to_file(); // call this to direct logging to a file or (pre-existing) pipe;
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
@@ -294,10 +313,7 @@ int get_requested_connection_state_to_output();
void set_requested_connection_state_to_output(int v);
ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count,
int timeout); // timeout in milliseconds
ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places
int try_to_open_pipe_for_writing(const char* pathname); // open it without blocking if it's not hooked up
/* from
* http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
@@ -349,10 +365,11 @@ double flat_vol2attn(double vol, long max_db, long min_db);
double vol2attn(double vol, long max_db, long min_db);
// return a monolithic (always increasing) time in nanoseconds
uint64_t get_absolute_time_in_fp(void);
// uint64_t get_absolute_time_in_fp(void); // obselete
uint64_t get_absolute_time_in_ns(void);
// time at startup for debugging timing
extern uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
extern uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
@@ -363,6 +380,7 @@ extern pthread_t main_thread_id;
extern shairport_cfg config;
extern config_t config_file_stuff;
extern int emergency_exit;
int config_set_lookup_bool(config_t *cfg, char *where, int *dst);
@@ -420,9 +438,6 @@ extern pthread_mutex_t r64_mutex;
char *get_version_string(); // mallocs a string space -- remember to free it afterwards
void sps_nanosleep(const time_t sec,
const long nanosec); // waits for this time, even through interruptions
int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t format,
int with_dither, int64_t random_number_in);
@@ -430,4 +445,7 @@ void malloc_cleanup(void *arg);
int string_update_with_size(char **str, int *flag, char *s, size_t len);
// from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks
void* memdup(const void* mem, size_t size);
#endif // _COMMON_H
+73 -24
View File
@@ -1,6 +1,6 @@
/*
* DACP protocol handler. This file is part of Shairport Sync.
* Copyright (c) Mike Brady 2017 -- 2019
* Copyright (c) Mike Brady 2017 -- 2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -49,7 +49,7 @@
typedef struct {
int players_connection_thread_index; // the connection thread index when a player thread is
// associated with this, zero otherwise
int scan_enable; // set to 1 if if sacanning should be considered
int scan_enable; // set to 1 if scanning should be considered
char dacp_id[256]; // the DACP ID string
uint16_t port; // zero if no port discovered
short connection_family; // AF_INET6 or AF_INET
@@ -123,7 +123,7 @@ static const struct http_funcs responseFuncs = {
// static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t dacp_conversation_lock;
static pthread_mutex_t dacp_server_information_lock;
static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
static pthread_cond_t dacp_server_information_cv;
void addrinfo_cleanup(void *arg) {
// debug(1, "addrinfo cleanup called.");
@@ -154,7 +154,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"dacp_send_command: command is: \"%s\".",command);
if (dacp_server.port == 0) {
debug(1, "No DACP port specified yet");
debug(3, "No DACP port specified yet");
result = 490; // no port specified
} else {
@@ -169,6 +169,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// 493 Client failed to send a message
// 492 Argument out of range
// 491 Client refused connection
// 490 No port specified
struct addrinfo hints, *res;
int sockfd;
@@ -201,7 +202,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
response.code = 498; // Bad Address information for the DACP server
} else {
uint64_t start_time = get_absolute_time_in_fp();
uint64_t start_time = get_absolute_time_in_ns();
pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
// only do this one at a time -- not sure it is necessary, but better safe than sorry
@@ -221,7 +222,6 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
// debug(2, "dacp_send_command: open socket %d.",sockfd);
// This is for limiting the time to be spent waiting for a response.
struct timeval tv;
@@ -232,7 +232,6 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
debug(1, "dacp_send_command: error %d setting send timeout.", errno);
// connect!
// debug(1, "DACP socket created.");
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
@@ -352,10 +351,9 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
}
pthread_cleanup_pop(1); // this should free the addrinfo
// freeaddrinfo(res);
uint64_t et = get_absolute_time_in_fp() - start_time;
et = (et * 1000000) >> 32; // microseconds
uint64_t et = get_absolute_time_in_ns() - start_time; // this will be in nanoseconds
debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
(1.0 * et) / 1000000, response.code, command);
(1.0 * et) / 1000000000, response.code, command);
}
*body = response.body;
*bodysize = response.size;
@@ -418,13 +416,11 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
// which return immediately with a 403 code if there are no changes.
dacp_server.always_use_revision_number_1 = 0;
char *p = strstr(conn->UserAgent, "forked-daapd");
if ((p != 0) && (p == conn->UserAgent)) {// must exist and be at the start of the UserAgent string
if ((p != 0) &&
(p == conn->UserAgent)) { // must exist and be at the start of the UserAgent string
dacp_server.always_use_revision_number_1 = 1;
}
mdns_dacp_monitor_set_id(dacp_server.dacp_id);
metadata_hub_modify_prolog();
int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
metadata_store.dacp_server_active = dacp_server.scan_enable;
@@ -508,27 +504,63 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
(metadata_store.advanced_dacp_server_active != 0);
metadata_store.dacp_server_active = 0;
metadata_store.advanced_dacp_server_active = 0;
debug(2,
debug(3,
"setting metadata_store.dacp_server_active and "
"metadata_store.advanced_dacp_server_active to 0 with an update "
"flag value of %d",
ch);
metadata_hub_modify_epilog(ch);
while (dacp_server.scan_enable == 0) {
// debug(1, "dacp_monitor_thread_code wait for an event to possible enable scan");
pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
// debug(1,"dacp_monitor_thread_code wake up.");
uint64_t time_to_wait_for_wakeup_ns =
(uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
uint64_t sec = time_of_wakeup_ns / 1000000000;
uint64_t nsec = time_of_wakeup_ns % 1000000000;
#endif
#ifdef COMPILE_FOR_OSX
uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
uint64_t nsec = time_to_wait_for_wakeup_ns % 1000000000;
#endif
struct timespec time_to_wait;
time_to_wait.tv_sec = sec;
time_to_wait.tv_nsec = nsec;
while ((dacp_server.scan_enable == 0) && (result != ETIMEDOUT)) {
// debug(1, "dacp_monitor_thread_code wait for an event to possibly enable scan");
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
result = pthread_cond_timedwait(&dacp_server_information_cv, &dacp_server_information_lock,
&time_to_wait); // this is a pthread cancellation point
// debug(1, "result is %d, dacp_server.scan_enable is %d, time_to_wait_for_wakeup_ns is %"
// PRId64 ".", result, dacp_server.scan_enable, time_to_wait_for_wakeup_ns);
#endif
#ifdef COMPILE_FOR_OSX
result = pthread_cond_timedwait_relative_np(&dacp_server_information_cv,
&dacp_server_information_lock, &time_to_wait);
#endif
}
// so dacp_server.scan_enable will be true at this point
if (dacp_server.scan_enable == 1) {
bad_result_count = 0;
idle_scan_count = 0;
}
}
always_use_revision_number_1 = dacp_server.always_use_revision_number_1; // set this while access is locked
always_use_revision_number_1 =
dacp_server.always_use_revision_number_1; // set this while access is locked
result = dacp_get_volume(&the_volume); // just want the http code
pthread_cleanup_pop(1);
if (result == 490) { // 490 means no port was specified
if (((char *)dacp_server.dacp_id != NULL) && (strlen(dacp_server.dacp_id) != 0)) {
// debug(1,"mdns_dacp_monitor_set_id");
mdns_dacp_monitor_set_id(dacp_server.dacp_id);
}
} else {
scan_index++;
// debug(1,"DACP Scan Result: %d.", result);
@@ -568,8 +600,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
else
idle_scan_count = 0;
debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
idle_scan_count);
debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result,
bad_result_count, idle_scan_count);
/* not used
// decide if idle for too long
@@ -884,12 +916,27 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
else
sleep(config.scan_interval_when_inactive);
}
}
debug(1, "DACP monitor thread exiting -- should never happen.");
pthread_exit(NULL);
}
void dacp_monitor_start() {
int rc;
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
pthread_condattr_t attr;
pthread_condattr_init(&attr);
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
rc = pthread_cond_init(&dacp_server_information_cv, &attr);
if (rc)
debug(1, "Error initialising the DACP Server Information Condition Variable");
pthread_condattr_destroy(&attr);
#endif
#ifdef COMPILE_FOR_OSX
rc = pthread_cond_init(&dacp_server_information_cv, NULL);
#endif
pthread_mutexattr_t mta;
rc = pthread_mutexattr_init(&mta);
@@ -948,6 +995,8 @@ void dacp_monitor_stop() {
pthread_mutex_destroy(&dacp_server_information_lock);
debug(3, "DACP Conversation Lock Mutex Destroyed");
pthread_mutex_destroy(&dacp_conversation_lock);
pthread_cond_destroy(&dacp_server_information_cv);
debug(3, "DACP Server Information Condition Variable destroyed.");
}
}
@@ -1200,7 +1249,7 @@ int dacp_get_volume(int32_t *the_actual_volume) {
} else {
debug(1, "Unexpected return code %d from dacp_get_speaker_list.", http_response);
}
} else {
} else if (http_response != 400) {
debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response);
}
if (the_actual_volume) {
+29 -26
View File
@@ -24,10 +24,10 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "config.h"
@@ -69,8 +69,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairport_sync_remote_control_set_client(shairportSyncRemoteControlSkeleton, argc->client_ip);
// although it's a DACP server, the server is in fact, part of the the AirPlay "client" (their term).
// although it's a DACP server, the server is in fact, part of the the AirPlay "client" (their
// term).
if (argc->dacp_server_active) {
shairport_sync_remote_control_set_available(shairportSyncRemoteControlSkeleton, TRUE);
} else {
@@ -166,7 +166,6 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairportSyncAdvancedRemoteControlSkeleton, response);
}
switch (argc->shuffle_status) {
case SS_NOT_AVAILABLE:
new_status = FALSE;
@@ -188,8 +187,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
// only set this if it's different
if (current_status != new_status) {
debug(3, "Shuffle State should be changed");
shairport_sync_advanced_remote_control_set_shuffle(
shairportSyncAdvancedRemoteControlSkeleton, new_status);
shairport_sync_advanced_remote_control_set_shuffle(shairportSyncAdvancedRemoteControlSkeleton,
new_status);
}
// Build the metadata array
@@ -369,7 +368,8 @@ static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton,
}
static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation, const gdouble volume,
GDBusMethodInvocation *invocation,
const gdouble volume,
__attribute__((unused)) gpointer user_data) {
debug(2, "Set airplay volume to %.6f.", volume);
char command[256] = "";
@@ -379,8 +379,6 @@ static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleto
return TRUE;
}
gboolean notify_elapsed_time_callback(ShairportSyncDiagnostics *skeleton,
__attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_elapsed_time_callback\" called.");
@@ -468,7 +466,8 @@ gboolean notify_convolution_callback(ShairportSync *skeleton,
if (shairport_sync_get_convolution(skeleton)) {
debug(1, ">> activating convolution");
config.convolution = 1;
config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
config.convolver_valid =
convolver_init(config.convolution_ir_file, config.convolution_max_length);
} else {
debug(1, ">> deactivating convolution");
config.convolution = 0;
@@ -506,25 +505,29 @@ gboolean notify_convolution_gain_callback(__attribute__((unused)) ShairportSync
#endif
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_impulse_response_file_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
__attribute__((unused))
gpointer user_data) {
char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
if (config.convolution_ir_file)
free(config.convolution_ir_file);
config.convolution_ir_file = strdup(th);
debug(1, ">> setting configuration impulse response filter file to \"%s\".", config.convolution_ir_file);
config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
debug(1, ">> setting configuration impulse response filter file to \"%s\".",
config.convolution_ir_file);
config.convolver_valid =
convolver_init(config.convolution_ir_file, config.convolution_max_length);
return TRUE;
}
#else
gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused)) ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
__attribute__((unused)) char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused))
ShairportSync *skeleton,
__attribute__((unused))
gpointer user_data) {
__attribute__((unused)) char *th =
(char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
return TRUE;
}
#endif
gboolean notify_loudness_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_loudness_callback\" called.");
@@ -781,7 +784,6 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
return TRUE;
}
static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name,
__attribute__((unused)) gpointer user_data) {
@@ -823,8 +825,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
G_CALLBACK(notify_convolution_gain_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::convolution-impulse-response-file",
G_CALLBACK(notify_convolution_impulse_response_file_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::loudness",
G_CALLBACK(notify_loudness_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::loudness", G_CALLBACK(notify_loudness_callback),
NULL);
g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold",
G_CALLBACK(notify_loudness_threshold_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::drift-tolerance",
@@ -879,7 +881,6 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-set-airplay-volume",
G_CALLBACK(on_handle_set_airplay_volume), NULL);
g_signal_connect(shairportSyncAdvancedRemoteControlSkeleton, "handle-set-volume",
G_CALLBACK(on_handle_set_volume), NULL);
@@ -975,9 +976,11 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
shairport_sync_set_convolution(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
}
if (config.convolution_ir_file)
shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), config.convolution_ir_file);
shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton),
config.convolution_ir_file);
// else
// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), NULL);
// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton),
// NULL);
#endif
shairport_sync_set_version(SHAIRPORT_SYNC(shairportSyncSkeleton), PACKAGE_VERSION);
@@ -1047,14 +1050,14 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
__attribute__((unused)) const gchar *name,
__attribute__((unused)) gpointer user_data) {
warn("Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", name,
warn("could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", name,
(config.dbus_service_bus_type == DBT_session) ? "session" : "system");
}
static void on_dbus_name_lost(__attribute__((unused)) GDBusConnection *connection,
__attribute__((unused)) const gchar *name,
__attribute__((unused)) gpointer user_data) {
// debug(1, "Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus --
// debug(1, "could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus --
// will try adding the process "
// "number to the end of it.",
// name, (config.dbus_service_bus_type == DBT_session) ? "session" : "system");
+9 -11
View File
@@ -35,8 +35,6 @@ Within the configuration file, settings are organised into groups, for example,
\fBname = "Mike's Boombox";\f1
\fBinterpolation = "soxr";\f1
\fBpassword = "secret";\f1
\fBoutput_backend = "alsa";\f1
@@ -53,7 +51,7 @@ Within the configuration file, settings are organised into groups, for example,
\fB};\f1
Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specified. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.
Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the mixer name should be specified. It is important to do this if the mixer exists. Otherwise, the maximum output from the output device will be whatever setting the mixer happens to have, which will be a matter of chance and which could be very low or even silent.
A sample configuration file with all possible settings, but with all of them commented out, is installed at \fIshairport-sync.conf.sample\f1, within the System Configuration Directory -- \fI/etc\f1 in Linux, \fI/usr/local/etc\f1 in BSD unixes.
@@ -67,7 +65,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. "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 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.3.6" and \fB%V\f1 for the shairport-sync version string, e.g. "3.3.6-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
The default is "%H", which is replaced by the hostname with the first letter capitalised.
.TP
@@ -75,7 +73,7 @@ The default is "%H", which is replaced by the hostname with the first letter cap
Require the password \fIpassword\f1 to connect to the service. If you leave this setting commented out, no password is needed.
.TP
\fBinterpolation=\f1\fI"mode"\f1\fB;\f1
Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation here refers to the process of adding or removing frames of audio to or from the stream sent to the output device to keep it exactly in synchrony with the player. The default mode, "basic", is normally almost completely inaudible. The alternative mode, "soxr", is even less obtrusive but requires much more processing power. For this mode, support for libsoxr, the SoX Resampler Library, must be selected when shairport-sync is compiled.
Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation here refers to the process of adding or removing frames of audio to or from the stream sent to the output device to keep it exactly synchronised with the player. The "basic" mode is normally almost completely inaudible. The alternative mode, "soxr", is even less obtrusive but requires much more processing power. For this mode, support for libsoxr, the SoX Resampler Library, must be selected when shairport-sync is compiled. The default setting is "auto", which will choose "soxr" if support for it has been compiled into the build of Shairport Synce and if the CPU is fast enough. Otherwise, "basic" stuffing will be chosen.
.TP
\fBoutput_backend=\f1\fI"backend"\f1\fB;\f1
shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends -- the default is the first on this list. Only the "alsa", "sndio" and "pa" backends support synchronisation.
@@ -218,14 +216,14 @@ Specify the \fIname\f1 of the mixer control to be used by shairport-sync to cont
By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.
.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.
Use this setting to specify the frame rate to output to the ALSA device. Allowable values are "auto" (default), 44100, 88200, 176400 and 352800. The device must have the capability to accept the rate you specify. There is no particular reason to use anything other than 44100 if it is available, and if "auto" is selected, the lowest of these rates available, starting at 44100, will be selected.
.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.
Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "auto" (default), "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 unmodified 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.
If you are using a hardware mixer, S16 is fine, as audio will pass through Shairport Sync unmodified except for interpolation, but any of the higher-resolution formats are okay too. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, in order to get the lowest possible levels of dither. The "auto" setting will cause Shairport Sync to choose the highest resolution available.
.TP
\fBdisable_synchronization=\f1\fI"no"\f1\fB;\f1
This is an advanced setting and is for debugging only. Set to \fI"yes"\f1 to disable synchronization. Default is \fI"no"\f1. If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to audio buffer overflow or underflow.
@@ -326,10 +324,10 @@ The maximum packet size for any UDP metadata. This must be between 500 or 65000.
\fB"DIAGNOSTICS" SETTINGS\f1
.TP
\fBstatistics=\f1\fI"setting"\f1\fB;\f1
Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information to the system log (or to \fISTDERR\f1 if the \fB-u\f1 command line option is chosen). The default is to disable statistics.
.TP
\fBlog_verbosity=\f1\fI0\f1\fB;\f1
Use this to specify how much debugging information should be output or logged. The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
Use this to specify how much debugging information should sent to the system log (or to \fISTDERR\f1 if the \fB-u\f1 command line option is chosen). The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
.SH OPTIONS
This section is about the command-line options available in shairport-sync.
@@ -425,7 +423,7 @@ If you are running shairport-sync from the command line and want logs to appear
Print version information and exit.
.TP
\fB-v | --verbose\f1
Print debug information. Repeat up to three times to get more detail.
Print debug information to the system log, or or to \fISTDERR\f1 if the \fB-u\f1 command line option is also chosen. Repeat up to three times to get more detail.
.TP
\fB-w | --wait-cmd\f1
Wait for commands specified using \fB-B\f1 or \fB-E\f1 to complete before continuing execution.
+29 -21
View File
@@ -118,7 +118,6 @@
<p><opt>general = {</opt></p>
<p><p><opt>name = "Mike's Boombox";</opt></p></p>
<p><p><opt>interpolation = "soxr";</opt></p></p>
<p><p><opt>password = "secret";</opt></p></p>
<p><p><opt>output_backend = "alsa";</opt></p></p>
<p><opt>};</opt></p>
@@ -131,10 +130,10 @@
<p>Most settings have sensible default values, so -- as in the example above -- users
generally only need to set (1) the service name, (2) a password (if desired) and
(3) the output device. If the output device has a mixer that can be used for volume
control, then (4) the volume control's name should be specified. It is highly
desirable to use the output device's mixer for volume control, if available --
response time is reduced to zero and the processor load is reduced. In the example
above, "soxr" interpolation was also enabled.</p>
control, then (4) the mixer name should be specified. It is important
to do this if the mixer exists. Otherwise, the
maximum output from the output device will be whatever setting the mixer happens to
have, which will be a matter of chance and which could be very low or even silent.</p>
<p>A sample configuration file with all possible settings, but with all of them
commented out, is installed at <file>shairport-sync.conf.sample</file>, within the
@@ -159,9 +158,9 @@
<opt>%h</opt> for the computer's hostname,
<opt>%H</opt> for the computer's hostname with the first letter capitalised (ASCII
only),
<opt>%v</opt> for the shairport-sync version number, e.g. "3.0.1" and
<opt>%v</opt> for the shairport-sync version number, e.g. "3.3.6" and
<opt>%V</opt> for the shairport-sync version string, e.g.
"3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".</p>
"3.3.6-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".</p>
<p>The default is "%H", which is replaced by the hostname with the first letter
capitalised.</p>
</optdesc>
@@ -178,13 +177,17 @@
<optdesc><p>Interpolate, or "stuff", the audio stream using the <arg>mode</arg>.
Interpolation here refers to the
process of adding or removing frames of audio to or from the
stream sent to the output device to keep it exactly in synchrony
stream sent to the output device to keep it exactly synchronised
with the player.
The default mode, "basic", is normally almost completely inaudible.
The "basic" mode is normally almost completely inaudible.
The alternative mode, "soxr", is even less obtrusive but
requires much more processing power. For this mode, support for
libsoxr, the SoX Resampler Library, must be selected when
shairport-sync is compiled.
The default setting is "auto", which will choose "soxr" if support for it has been
compiled into the build of Shairport Synce and if the CPU is fast enough. Otherwise,
"basic" stuffing will be chosen.
</p></optdesc>
</option>
@@ -569,23 +572,26 @@
<option>
<p><opt>output_rate=</opt><arg>frame rate</arg><opt>;</opt></p>
<optdesc><p>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.
Allowable values are "auto" (default), 44100, 88200, 176400 and 352800. The device must have
the capability to accept the rate you specify. There is no particular reason to use
anything other than 44100 if it is available, and if "auto" is selected, the lowest
of these rates available, starting at 44100, will be selected.
</p></optdesc>
</option>
<option>
<p><opt>output_format=</opt><arg>"format"</arg><opt>;</opt></p>
<optdesc><p>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"
the ALSA device. Allowable values are "auto" (default), "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE"
or "S32". The device must have the capability to accept the format you
specify.</p><p>"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".</p><p>If you are using a hardware mixer, the best
setting is S16, as audio will pass through Shairport Sync unmodified 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.
processor. The default is "S16".</p><p>If you are using a hardware mixer, S16 is fine,
as audio will pass through Shairport Sync unmodified except for
interpolation, but any of the higher-resolution formats are okay too.
If you are using the software mixer, use 32- or 24-bit, if your device
is capable of it, in order to get the lowest possible levels of dither.
The "auto" setting will cause Shairport Sync to choose the highest resolution available.
</p></optdesc>
</option>
@@ -798,14 +804,15 @@
<option>
<p><opt>statistics=</opt><arg>"setting"</arg><opt>;</opt></p>
<optdesc><p>Use this <arg>setting</arg> to enable ("yes") or disable ("no") the output
of some statistical information on the console or in the log. The default is to
of some statistical information to the system log (or to <file>STDERR</file> if the <opt>-u</opt> command line option is chosen). The default is to
disable statistics.</p></optdesc>
</option>
<option>
<p><opt>log_verbosity=</opt><arg>0</arg><opt>;</opt></p>
<optdesc><p>Use this to specify how much debugging information should be output or
logged. The value <arg>0</arg> means no debug information, <arg>3</arg> means most
<optdesc><p>Use this to specify how much debugging information should sent to the system log
(or to <file>STDERR</file> if the <opt>-u</opt> command line option is chosen).
The value <arg>0</arg> means no debug information, <arg>3</arg> means most
debug information. The default is <arg>0</arg>.</p></optdesc>
</option>
@@ -1085,7 +1092,8 @@
<option>
<p><opt>-v | --verbose</opt></p>
<optdesc><p>
Print debug information. Repeat up to three times to get more detail.
Print debug information to the system log, or or to <file>STDERR</file> if the <opt>-u</opt> command line option is also chosen.
Repeat up to three times to get more detail.
</p></optdesc>
</option>
+24 -1195
View File
File diff suppressed because it is too large Load Diff
+7 -5
View File
@@ -96,6 +96,8 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
char *dacpid = strstr(name, "iTunes_Ctrl_");
if (dacpid) {
dacpid += strlen("iTunes_Ctrl_");
while (*dacpid == '0')
dacpid++; // remove any leading zeroes
if (strcmp(dacpid, dbs->dacp_id) == 0) {
debug(3, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
#ifdef CONFIG_DACP_CLIENT
@@ -340,17 +342,17 @@ static int avahi_register(char *srvname, int srvport) {
static void avahi_unregister(void) {
// debug(1, "avahi_unregister.");
if (tpoll) {
debug(1, "avahi: stop the threaded poll.");
debug(2, "avahi: stop the threaded poll.");
avahi_threaded_poll_stop(tpoll);
if (client) {
debug(1, "avahi: free the client.");
debug(2, "avahi: free the client.");
avahi_client_free(client);
client = NULL;
} else {
debug(1, "avahi attempting to unregister a NULL client");
}
debug(1, "avahi: free the threaded poll.");
debug(2, "avahi: free the threaded poll.");
avahi_threaded_poll_free(tpoll);
tpoll = NULL;
} else {
@@ -358,7 +360,7 @@ static void avahi_unregister(void) {
}
if (service_name) {
debug(1, "avahi: free the service name.");
debug(2, "avahi: free the service name.");
free(service_name);
} else
debug(1, "avahi attempt to free NULL service name");
@@ -412,7 +414,7 @@ void avahi_dacp_monitor_stop() {
}
avahi_threaded_poll_unlock(tpoll);
free(dbs->dacp_id);
debug(1, "avahi_dacp_monitor_stop Avahi DACP monitor successfully stopped");
debug(2, "avahi_dacp_monitor_stop Avahi DACP monitor successfully stopped");
}
mdns_backend mdns_avahi = {.name = "avahi",
+1 -1
View File
@@ -24,8 +24,8 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "mdns.h"
#include "common.h"
#include "mdns.h"
#include <arpa/inet.h>
#include <dns_sd.h>
#include <stdlib.h>
+1 -1
View File
@@ -25,8 +25,8 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "mdns.h"
#include "common.h"
#include "mdns.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
+97 -68
View File
@@ -35,10 +35,10 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <inttypes.h>
#include "config.h"
@@ -92,13 +92,6 @@ void add_metadata_watcher(metadata_watcher fn, void *userdata) {
}
}
/*
void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) {
// debug(1, "metadata_hub_unlock_hub_mutex_cleanup called.");
pthread_rwlock_unlock(&metadata_hub_re_lock);
}
*/
void run_metadata_watchers(void) {
int i;
for (i = 0; i < number_of_watchers; i++) {
@@ -129,44 +122,73 @@ void run_metadata_watchers(void) {
metadata_store.songtime_in_milliseconds_changed = 0;
}
void metadata_hub_modify_prolog(void) {
void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) {
debug(1, "metadata_hub_unlock_hub_mutex_cleanup called.");
metadata_hub_modify_epilog(0);
}
char *last_metadata_hub_modify_prolog_file = NULL;
int last_metadata_hub_modify_prolog_line;
int metadata_hub_re_lock_access_is_delayed;
void _metadata_hub_modify_prolog(const char *filename, const int linenumber) {
// always run this before changing an entry or a sequence of entries in the metadata_hub
// debug(1, "locking metadata hub for writing");
if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) {
debug(2, "Metadata_hub write lock is already taken -- must wait.");
if (last_metadata_hub_modify_prolog_file)
debug(2, "Metadata_hub write lock at \"%s:%d\" is already taken at \"%s:%d\" -- must wait.", filename, linenumber, last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line);
else
debug(2, "Metadata_hub write lock is already taken by unknown -- must wait.");
metadata_hub_re_lock_access_is_delayed = 0;
pthread_rwlock_wrlock(&metadata_hub_re_lock);
debug(2, "Okay -- acquired the metadata_hub write lock.");
debug(2, "Okay -- acquired the metadata_hub write lock at \"%s:%d\".", filename, linenumber);
} else {
if (last_metadata_hub_modify_prolog_file) {
free(last_metadata_hub_modify_prolog_file);
}
last_metadata_hub_modify_prolog_file = strdup(filename);
last_metadata_hub_modify_prolog_line = linenumber;
debug(3, "Metadata_hub write lock acquired.");
}
metadata_hub_re_lock_access_is_delayed = 0;
}
void metadata_hub_modify_epilog(int modified) {
void _metadata_hub_modify_epilog(int modified, const char *filename, const int linenumber) {
metadata_store.dacp_server_has_been_active =
metadata_store.dacp_server_active; // set the scanner_has_been_active now.
if (modified) {
run_metadata_watchers();
}
if (metadata_hub_re_lock_access_is_delayed) {
if (last_metadata_hub_modify_prolog_file) {
debug(1, "Metadata_hub write lock taken at \"%s:%d\" is freed at \"%s:%d\".", last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line, filename, linenumber);
free(last_metadata_hub_modify_prolog_file);
last_metadata_hub_modify_prolog_file = NULL;
} else {
debug(1, "Metadata_hub write lock taken at an unknown place is freed at \"%s:%d\".", filename, linenumber);
}
}
pthread_rwlock_unlock(&metadata_hub_re_lock);
debug(3, "Metadata_hub write lock unlocked.");
}
void metadata_hub_read_prolog(void) {
/*
void _metadata_hub_read_prolog(const char *filename, const int linenumber) {
// always run this before reading an entry or a sequence of entries in the metadata_hub
// debug(1, "locking metadata hub for reading");
if (pthread_rwlock_tryrdlock(&metadata_hub_re_lock) != 0) {
debug(2, "Metadata_hub read lock is already taken -- must wait.");
debug(1, "Metadata_hub read lock is already taken -- must wait.");
pthread_rwlock_rdlock(&metadata_hub_re_lock);
debug(2, "Okay -- acquired the metadata_hub read lock.");
debug(1, "Okay -- acquired the metadata_hub read lock.");
}
}
void metadata_hub_read_epilog(void) {
void _metadata_hub_read_epilog(const char *filename, const int linenumber) {
// always run this after reading an entry or a sequence of entries in the metadata_hub
// debug(1, "unlocking metadata hub for reading");
pthread_rwlock_unlock(&metadata_hub_re_lock);
}
*/
char *metadata_write_image_file(const char *buf, int len) {
// warning -- this removes all files from the directory apart from this one, if it exists
@@ -174,38 +196,38 @@ char *metadata_write_image_file(const char *buf, int len) {
// free it if you don't need it.
char *path = NULL; // this will be what is returned
if (strcmp(config.cover_art_cache_dir,"") != 0) { // an empty string means do not write the file
if (strcmp(config.cover_art_cache_dir, "") != 0) { // an empty string means do not write the file
uint8_t img_md5[16];
// uint8_t ap_md5[16];
#ifdef CONFIG_OPENSSL
#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, buf, len);
MD5_Final(img_md5, &ctx);
#endif
#endif
#ifdef CONFIG_MBEDTLS
#if MBEDTLS_VERSION_MINOR >= 7
#ifdef CONFIG_MBEDTLS
#if MBEDTLS_VERSION_MINOR >= 7
mbedtls_md5_context tctx;
mbedtls_md5_starts_ret(&tctx);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len);
mbedtls_md5_finish_ret(&tctx, img_md5);
#else
#else
mbedtls_md5_context tctx;
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
mbedtls_md5_finish(&tctx, img_md5);
#endif
#endif
#endif
#endif
#ifdef CONFIG_POLARSSL
#ifdef CONFIG_POLARSSL
md5_context tctx;
md5_starts(&tctx);
md5_update(&tctx, (const unsigned char *)buf, len);
md5_finish(&tctx, img_md5);
#endif
#endif
char img_md5_str[33];
memset(img_md5_str, 0, sizeof(img_md5_str));
@@ -232,8 +254,8 @@ char *metadata_write_image_file(const char *buf, int len) {
// if it exists, we're done
char *prefix = "cover-";
size_t pl = strlen(config.cover_art_cache_dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 +
strlen(ext);
size_t pl = strlen(config.cover_art_cache_dir) + 1 + strlen(prefix) + strlen(img_md5_str) +
1 + strlen(ext);
path = malloc(pl + 1);
snprintf(path, pl + 1, "%s/%s%s.%s", config.cover_art_cache_dir, prefix, img_md5_str, ext);
@@ -303,16 +325,22 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
// all the following items of metadata are contained in one metadata packet
// they are preceded by an 'ssnc' 'mdst' item and followed by an 'ssnc 'mden' item.
// we don't set "changed" for them individually; instead we set it when the 'mden' token
// comes in if the metadata_packet_item_changed is set.
int changed = 0;
int metadata_packet_item_changed = 0;
metadata_hub_modify_prolog();
pthread_cleanup_push(metadata_hub_unlock_hub_mutex_cleanup, NULL);
char *cs;
int changed = 0;
if (type == 'core') {
switch (code) {
case 'mper': {
// get the 64-bit number as a uint64_t by reading two uint32_t s and combining them
uint64_t vl = ntohl(*(uint32_t*)data); // get the high order 32 bits
uint64_t vl = ntohl(*(uint32_t *)data); // get the high order 32 bits
vl = vl << 32; // shift them into the correct location
uint64_t ul = ntohl(*(uint32_t*)(data+sizeof(uint32_t))); // and the low order 32 bits
uint64_t ul = ntohl(*(uint32_t *)(data + sizeof(uint32_t))); // and the low order 32 bits
vl = vl + ul;
debug(2, "MH Item ID seen: \"%" PRIx64 "\" of length %u.", vl, length);
if (vl != metadata_store.item_id) {
@@ -320,9 +348,9 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
metadata_store.item_id_changed = 1;
metadata_store.item_id_received = 1;
debug(2, "MH Item ID set to: \"%" PRIx64 "\"", metadata_store.item_id);
metadata_packet_item_changed = 1;
}
}
break;
} break;
case 'astm': {
uint32_t ui = ntohl(*(uint32_t *)data);
debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length);
@@ -330,13 +358,14 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
metadata_store.songtime_in_milliseconds = ui;
metadata_store.songtime_in_milliseconds_changed = 1;
debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds);
metadata_packet_item_changed = 1;
}
}
break;
} break;
case 'asal':
cs = strndup(data, length);
if (string_update(&metadata_store.album_name, &metadata_store.album_name_changed, cs)) {
debug(2, "MH Album name set to: \"%s\"", metadata_store.album_name);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -344,6 +373,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.artist_name, &metadata_store.artist_name_changed, cs)) {
debug(2, "MH Artist name set to: \"%s\"", metadata_store.artist_name);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -352,6 +382,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
if (string_update(&metadata_store.album_artist_name,
&metadata_store.album_artist_name_changed, cs)) {
debug(2, "MH Album Artist name set to: \"%s\"", metadata_store.album_artist_name);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -359,6 +390,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.comment, &metadata_store.comment_changed, cs)) {
debug(2, "MH Comment set to: \"%s\"", metadata_store.comment);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -366,6 +398,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.genre, &metadata_store.genre_changed, cs)) {
debug(2, "MH Genre set to: \"%s\"", metadata_store.genre);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -373,6 +406,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.track_name, &metadata_store.track_name_changed, cs)) {
debug(2, "MH Track Name set to: \"%s\"", metadata_store.track_name);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -380,6 +414,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.composer, &metadata_store.composer_changed, cs)) {
debug(2, "MH Composer set to: \"%s\"", metadata_store.composer);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -396,6 +431,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
if (string_update(&metadata_store.song_album_artist,
&metadata_store.song_album_artist_changed, cs)) {
debug(2, "MH Song Album Artist set to: \"%s\"", metadata_store.song_album_artist);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -403,6 +439,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.sort_name, &metadata_store.sort_name_changed, cs)) {
debug(2, "MH Sort Name set to: \"%s\"", metadata_store.sort_name);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -410,6 +447,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.sort_artist, &metadata_store.sort_artist_changed, cs)) {
debug(2, "MH Sort Artist set to: \"%s\"", metadata_store.sort_artist);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -417,6 +455,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.sort_album, &metadata_store.sort_album_changed, cs)) {
debug(2, "MH Sort Album set to: \"%s\"", metadata_store.sort_album);
metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -424,6 +463,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.sort_composer, &metadata_store.sort_composer_changed, cs)) {
debug(2, "MH Sort Composer set to: \"%s\"", metadata_store.sort_composer);
metadata_packet_item_changed = 1;
}
free(cs);
default:
@@ -456,43 +496,45 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
break;
case 'mdst':
debug(2, "MH Metadata stream processing start.");
metadata_hub_modify_prolog();
metadata_packet_item_changed = 0;
break;
case 'mden':
debug(2, "MH Metadata stream processing end.");
metadata_hub_modify_epilog(1);
debug(2, "MH Metadata stream processing epilog complete.");
changed = metadata_packet_item_changed;
break;
case 'PICT':
metadata_hub_modify_prolog();
debug(2, "MH Picture received, length %u bytes.", length);
char uri[2048];
if ((length > 16) && (strcmp(config.cover_art_cache_dir,"")!=0)) { // if it's okay to write the file
// make this uncancellable
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
char *pathname = metadata_write_image_file(data, length);
snprintf(uri, sizeof(uri), "file://%s", pathname);
free(pathname);
pthread_setcancelstate(oldState, NULL);
} else {
uri[0] = '\0';
}
if (string_update(&metadata_store.cover_art_pathname,
&metadata_store.cover_art_pathname_changed,
uri)) // if the picture's file path is different from the stored one...
metadata_hub_modify_epilog(1);
changed = 1;
else
metadata_hub_modify_epilog(0);
changed = 0;
// pthread_cleanup_pop(0); // don't remove the lock -- it'll have been done
break;
case 'clip':
metadata_hub_modify_prolog();
cs = strndup(data, length);
if (string_update(&metadata_store.client_ip, &metadata_store.client_ip_changed, cs)) {
changed = 1;
debug(2, "MH Client IP set to: \"%s\"", metadata_store.client_ip);
}
free(cs);
metadata_hub_modify_epilog(changed);
break;
case 'prgr':
metadata_hub_modify_prolog();
cs = strndup(data, length);
if (string_update(&metadata_store.progress_string, &metadata_store.progress_string_changed,
cs)) {
@@ -500,64 +542,50 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
debug(2, "MH Progress String set to: \"%s\"", metadata_store.progress_string);
}
free(cs);
metadata_hub_modify_epilog(changed);
break;
case 'svip':
metadata_hub_modify_prolog();
cs = strndup(data, length);
if (string_update(&metadata_store.server_ip, &metadata_store.server_ip_changed, cs)) {
changed = 1;
debug(2, "MH Server IP set to: \"%s\"", metadata_store.server_ip);
}
free(cs);
metadata_hub_modify_epilog(changed);
break;
case 'abeg':
metadata_hub_modify_prolog();
changed = (metadata_store.active_state != AM_ACTIVE);
metadata_store.active_state = AM_ACTIVE;
metadata_hub_modify_epilog(changed);
break;
case 'aend':
metadata_hub_modify_prolog();
changed = (metadata_store.active_state != AM_INACTIVE);
metadata_store.active_state = AM_INACTIVE;
metadata_hub_modify_epilog(changed);
break;
case 'pbeg':
metadata_hub_modify_prolog();
changed = ((metadata_store.player_state != PS_PLAYING) || (metadata_store.player_thread_active == 0));
changed = ((metadata_store.player_state != PS_PLAYING) ||
(metadata_store.player_thread_active == 0));
metadata_store.player_state = PS_PLAYING;
metadata_store.player_thread_active = 1;
metadata_hub_modify_epilog(changed);
break;
case 'pend':
metadata_hub_modify_prolog();
changed = ((metadata_store.player_state != PS_STOPPED) || (metadata_store.player_thread_active == 1));
changed = ((metadata_store.player_state != PS_STOPPED) ||
(metadata_store.player_thread_active == 1));
metadata_store.player_state = PS_STOPPED;
metadata_store.player_thread_active = 0;
metadata_hub_modify_epilog(changed);
break;
case 'pfls':
metadata_hub_modify_prolog();
changed = (metadata_store.player_state != PS_PAUSED);
metadata_store.player_state = PS_PAUSED;
metadata_hub_modify_epilog(changed);
break;
case 'pffr': // this is sent when the first frame has been received
case 'prsm':
metadata_hub_modify_prolog();
changed = (metadata_store.player_state != PS_PLAYING);
metadata_store.player_state = PS_PLAYING;
metadata_hub_modify_epilog(changed);
break;
case 'pvol': {
metadata_hub_modify_prolog();
// Note: it's assumed that the config.airplay volume has already been correctly set.
//int32_t actual_volume;
//int gv = dacp_get_volume(&actual_volume);
//metadata_hub_modify_prolog();
//if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) {
// int32_t actual_volume;
// int gv = dacp_get_volume(&actual_volume);
// metadata_hub_modify_prolog();
// if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) {
// metadata_store.speaker_volume = actual_volume;
// changed = 1;
//}
@@ -565,7 +593,6 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
metadata_store.airplay_volume = config.airplay_volume;
changed = 1;
}
metadata_hub_modify_epilog(changed); // change
} break;
default: {
@@ -588,4 +615,6 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
}
}
}
pthread_cleanup_pop(0); // don't remove the lock
metadata_hub_modify_epilog(changed);
}
+12 -4
View File
@@ -148,9 +148,17 @@ void metadata_hub_release_track_artwork(void);
// these functions lock and unlock the read-write mutex on the metadata hub and run the watchers
// afterwards
void metadata_hub_modify_prolog(void);
void metadata_hub_modify_epilog(int modified); // set to true if modifications occurred, 0 otherwise
void _metadata_hub_modify_prolog(const char *filename, const int linenumber);
void _metadata_hub_modify_epilog(int modified, const char *filename, const int linenumber); // set to true if modifications occurred, 0 otherwise
/*
// these are for safe reading
void metadata_hub_read_prolog(void);
void metadata_hub_read_epilog(void);
void _metadata_hub_read_prolog(const char *filename, const int linenumber);
void _metadata_hub_read_epilog(const char *filename, const int linenumber);
*/
#define metadata_hub_modify_prolog(void) _metadata_hub_modify_prolog(__FILE__, __LINE__)
#define metadata_hub_modify_epilog(modified) _metadata_hub_modify_epilog(modified, __FILE__, __LINE__)
#define metadata_hub_read_prolog(void) _metadata_hub_read_prolog(__FILE__, __LINE__)
#define metadata_hub_read_epilog(void) _metadata_hub_modify_epilog(__FILE__, __LINE__)
+8 -7
View File
@@ -23,9 +23,9 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "config.h"
@@ -48,12 +48,12 @@ double airplay_volume_to_mpris_volume(double sp) {
sp = -30.0;
if (sp > 0.0)
sp = 0.0;
sp = (sp/30.0)+1;
sp = (sp / 30.0) + 1;
return sp;
}
double mpris_volume_to_airplay_volume(double sp) {
sp = (sp-1.0)*30.0;
sp = (sp - 1.0) * 30.0;
if (sp < -30.0)
sp = -30.0;
if (sp > 0.0)
@@ -64,7 +64,8 @@ double mpris_volume_to_airplay_volume(double sp) {
void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
// debug(1, "MPRIS metadata watcher called");
char response[100];
media_player2_player_set_volume(mprisPlayerPlayerSkeleton, airplay_volume_to_mpris_volume(argc->airplay_volume));
media_player2_player_set_volume(mprisPlayerPlayerSkeleton,
airplay_volume_to_mpris_volume(argc->airplay_volume));
switch (argc->repeat_status) {
case RS_NOT_AVAILABLE:
strcpy(response, "Not Available");
@@ -175,7 +176,8 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
// Add in the Track ID based on the 'mper' metadata if it is non-zero
if (argc->item_id != 0) {
char trackidstring[128];
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "", argc->item_id);
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "",
argc->item_id);
GVariant *trackid = g_variant_new("o", trackidstring);
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
}
@@ -334,7 +336,6 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume),
NULL);
add_metadata_watcher(mpris_metadata_watcher, NULL);
debug(1, "MPRIS service started at \"%s\" on the %s bus.", name,
@@ -344,7 +345,7 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
static void on_mpris_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
const gchar *name,
__attribute__((unused)) gpointer user_data) {
warn("Could not acquire an MPRIS interface named \"%s\" on the %s bus.", name,
warn("could not acquire an MPRIS interface named \"%s\" on the %s bus.", name,
(config.mpris_service_bus_type == DBT_session) ? "session" : "system");
}
+7 -7
View File
@@ -24,10 +24,10 @@ void _cb_log(__attribute__((unused)) struct mosquitto *mosq, __attribute__((unus
int level, const char *str) {
switch (level) {
case MOSQ_LOG_DEBUG:
debug(1, str);
debug(3, str);
break;
case MOSQ_LOG_INFO:
debug(2, str);
debug(3, str);
break;
case MOSQ_LOG_NOTICE:
debug(3, str);
@@ -50,7 +50,7 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
memcpy(payload, msg->payload, msg->payloadlen);
payload[msg->payloadlen] = 0;
debug(1, "[MQTT]: received Message on topic %s: %s\n", msg->topic, payload);
debug(2, "[MQTT]: received Message on topic %s: %s\n", msg->topic, payload);
// All recognized commands
char *commands[] = {"command", "beginff", "beginrew", "mutetoggle", "nextitem",
@@ -63,7 +63,7 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
while (commands[it] != NULL) {
if ((size_t)msg->payloadlen >= strlen(commands[it]) &&
strncmp(msg->payload, commands[it], strlen(commands[it])) == 0) {
debug(1, "[MQTT]: DACP Command: %s\n", commands[it]);
debug(2, "[MQTT]: DACP Command: %s\n", commands[it]);
send_simple_dacp_command(commands[it]);
break;
}
@@ -74,13 +74,13 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
void on_disconnect(__attribute__((unused)) struct mosquitto *mosq,
__attribute__((unused)) void *userdata, __attribute__((unused)) int rc) {
connected = 0;
debug(1, "[MQTT]: disconnected");
debug(2, "[MQTT]: disconnected");
}
void on_connect(struct mosquitto *mosq, __attribute__((unused)) void *userdata,
__attribute__((unused)) int rc) {
connected = 1;
debug(1, "[MQTT]: connected");
debug(2, "[MQTT]: connected");
// subscribe if requested
if (config.mqtt_enable_remote) {
@@ -95,7 +95,7 @@ void mqtt_publish(char *topic, char *data, uint32_t length) {
char fulltopic[strlen(config.mqtt_topic) + strlen(topic) + 3];
snprintf(fulltopic, strlen(config.mqtt_topic) + strlen(topic) + 2, "%s/%s", config.mqtt_topic,
topic);
debug(1, "[MQTT]: publishing under %s", fulltopic);
debug(2, "[MQTT]: publishing under %s", fulltopic);
int rc;
if ((rc = mosquitto_publish(global_mosq, NULL, fulltopic, length, data, 0, 0)) !=
+581 -533
View File
File diff suppressed because it is too large Load Diff
+16 -5
View File
@@ -24,10 +24,10 @@
#include "alac.h"
#include "audio.h"
#define time_ping_history 128 // at 1 per three seconds, approximately six minutes of records
#define time_ping_history_power_of_two 7
#define time_ping_history (1 << time_ping_history_power_of_two) // 2^7 is 128. At 1 per three seconds, approximately six minutes of records
typedef struct time_ping_record {
uint64_t local_to_remote_difference;
uint64_t dispersion;
uint64_t local_time;
uint64_t remote_time;
@@ -43,12 +43,17 @@ typedef struct audio_buffer_entry { // decoded audio packets
uint16_t resend_request_number;
signed short *data;
seq_t sequence_number;
uint64_t initialisation_time; // the time the packet was added or the time it was noticed the packet was missing
uint64_t initialisation_time; // the time the packet was added or the time it was noticed the
// packet was missing
uint64_t resend_time; // time of last resend request or zero
uint32_t given_timestamp; // for debugging and checking
int length; // the length of the decoded data
} abuf_t;
typedef struct stats { // statistics for running averages
int64_t sync_error, correction, drift;
} stats_t;
// default buffer size
// This needs to be a power of 2 because of the way BUFIDX(seqno) works.
// 512 is the minimum for normal operation -- it gives 512*352/44100 or just over 4 seconds of
@@ -87,6 +92,7 @@ typedef struct {
uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero
// otherwise
int software_mute_enabled; // if we don't have a real mute that we can use
int unachievable_audio_backend_latency_offset_notified; // set when a latency warning is issued
int fd;
int authorized; // set if a password is required and has been supplied
@@ -108,6 +114,10 @@ typedef struct {
int32_t *sbuf;
char *outbuf;
// for generating running statistics...
stats_t *statistics;
// for holding the output rate information until printed out at the end of a session
double frame_rate;
int frame_rate_status;
@@ -151,6 +161,7 @@ typedef struct {
int ab_buffering, ab_synced;
int64_t first_packet_timestamp;
int flush_requested;
int flush_output_flushed; // true if the output device has been flushed.
uint32_t flush_rtp_timestamp;
uint64_t time_of_last_audio_packet;
seq_t ab_read, ab_write;
@@ -177,7 +188,7 @@ typedef struct {
// RTP stuff
// only one RTP session can be active at a time.
int rtp_running;
uint64_t rtp_time_of_last_resend_request_error_fp;
uint64_t rtp_time_of_last_resend_request_error_ns;
char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it
@@ -256,7 +267,7 @@ typedef struct {
void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled.
int enable_dither; // needed for filling silences before play actually starts
int64_t dac_buffer_queue_minimum_length;
uint64_t dac_buffer_queue_minimum_length;
} rtsp_conn_info;
uint32_t modulo_32_offset(uint32_t from, uint32_t to);
+173 -201
View File
@@ -45,11 +45,19 @@
#include <time.h>
#include <unistd.h>
struct Nvll {
char* name;
double value;
struct Nvll *next;
};
typedef struct Nvll nvll;
uint64_t local_to_remote_time_jitter;
uint64_t local_to_remote_time_jitter_count;
void rtp_initialise(rtsp_conn_info *conn) {
conn->rtp_time_of_last_resend_request_error_fp = 0;
conn->rtp_time_of_last_resend_request_error_ns = 0;
conn->rtp_running = 0;
// initialise the timer mutex
int rc = pthread_mutex_init(&conn->reference_time_mutex, NULL);
@@ -69,32 +77,20 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) {
// this is an attempt to compensate for clock drift since the last time ping that was used
// so, if we have a non-zero clock drift, we will calculate the drift there would
// be from the time of the last time ping
uint64_t local_time_now_fp = get_absolute_time_in_fp();
uint64_t time_since_last_local_to_remote_time_difference_measurement =
local_time_now_fp - conn->local_to_remote_time_difference_measurement_time;
get_absolute_time_in_ns() - conn->local_to_remote_time_difference_measurement_time;
uint64_t remote_time_since_last_local_to_remote_time_difference_measurement =
(uint64_t)(conn->local_to_remote_time_gradient *
uint64_t result = conn->local_to_remote_time_difference;
if (conn->local_to_remote_time_gradient >= 1.0) {
result = conn->local_to_remote_time_difference +
(uint64_t)((conn->local_to_remote_time_gradient - 1.0) *
time_since_last_local_to_remote_time_difference_measurement);
double drift;
if (remote_time_since_last_local_to_remote_time_difference_measurement >=
time_since_last_local_to_remote_time_difference_measurement)
drift = (1.0 * (remote_time_since_last_local_to_remote_time_difference_measurement -
time_since_last_local_to_remote_time_difference_measurement)) /
(uint64_t)0x100000000;
else
drift = -((1.0 * (time_since_last_local_to_remote_time_difference_measurement -
remote_time_since_last_local_to_remote_time_difference_measurement)) /
(uint64_t)0x100000000);
// double interval_ms =
// 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32);
// debug(1,"Measurement drift is %.2f microseconds (0x%" PRIx64 " in 64-bit fp) over %.2f
// milliseconds with drift of %.2f
// ppm.",drift*1000000,(uint64_t)(drift*(uint64_t)0x100000000),interval_ms,(1.0-conn->local_to_remote_time_gradient)*1000000);
// return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t 0x100000000));
return conn->local_to_remote_time_difference + (uint64_t)(drift * (uint64_t)0x100000000);
} else {
result = conn->local_to_remote_time_difference -
(uint64_t)((1.0 - conn->local_to_remote_time_gradient) *
time_since_last_local_to_remote_time_difference_measurement);
}
return result;
}
void rtp_audio_receiver_cleanup_handler(__attribute__((unused)) void *arg) {
@@ -108,7 +104,7 @@ void *rtp_audio_receiver(void *arg) {
int32_t last_seqno = -1;
uint8_t packet[2048], *pktp;
uint64_t time_of_previous_packet_fp = 0;
uint64_t time_of_previous_packet_ns = 0;
float longest_packet_time_interval_us = 0.0;
// mean and variance calculations from "online_variance" algorithm at
@@ -125,11 +121,10 @@ void *rtp_audio_receiver(void *arg) {
frame_count++;
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;
time_of_previous_packet_fp = local_time_now_fp;
uint64_t local_time_now_ns = get_absolute_time_in_ns();
if (time_of_previous_packet_ns) {
float time_interval_us = (local_time_now_ns - time_of_previous_packet_ns) * 0.001;
time_of_previous_packet_ns = local_time_now_ns;
if (time_interval_us > longest_packet_time_interval_us)
longest_packet_time_interval_us = time_interval_us;
stat_n += 1;
@@ -137,17 +132,18 @@ void *rtp_audio_receiver(void *arg) {
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 "
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;
time_of_previous_packet_fp = 0;
time_of_previous_packet_ns = 0;
longest_packet_time_interval_us = 0.0;
}
} else {
time_of_previous_packet_fp = local_time_now_fp;
time_of_previous_packet_ns = local_time_now_ns;
}
if (nread >= 0) {
@@ -248,9 +244,6 @@ void *rtp_control_receiver(void *arg) {
ssize_t nread;
while (1) {
nread = recv(conn->control_socket, packet, sizeof(packet), 0);
// local_time_now = get_absolute_time_in_fp();
// clock_gettime(CLOCK_MONOTONIC,&tn);
// local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000;
if (nread >= 0) {
@@ -303,11 +296,16 @@ void *rtp_control_receiver(void *arg) {
*/
if (conn->local_to_remote_time_difference) { // need a time packet to be interchanged
// first...
uint64_t ps, pn;
remote_time_of_sync = (uint64_t)nctohl(&packet[8]) << 32;
remote_time_of_sync += nctohl(&packet[12]);
ps = nctohl(&packet[8]);
ps = ps * 1000000000; // this many nanoseconds from the whole seconds
pn = nctohl(&packet[12]);
pn = pn * 1000000000;
pn = pn >> 32; // this many nanoseconds from the fractional part
remote_time_of_sync = ps + pn;
// debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync);
// debug(1,"Remote Sync Time: " PRIu64 "",remote_time_of_sync);
sync_rtp_timestamp = nctohl(&packet[16]);
uint32_t rtp_timestamp_less_latency = nctohl(&packet[4]);
@@ -368,7 +366,8 @@ void *rtp_control_receiver(void *arg) {
if (la != conn->latency) {
conn->latency = la;
debug(3, "New latency detected: %" PRIu32 ", sync latency: %" PRIu32
debug(3,
"New latency detected: %" PRIu32 ", sync latency: %" PRIu32
", minimum latency: %" PRIu32 ", maximum "
"latency: %" PRIu32 ", fixed offset: %" PRIu32 ".",
la, sync_rtp_timestamp - rtp_timestamp_less_latency, conn->minimum_latency,
@@ -390,15 +389,8 @@ void *rtp_control_receiver(void *arg) {
conn->initial_reference_time; // here, this should never be zero
if (remote_frame_time_interval) {
conn->remote_frame_rate =
(1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) /
remote_frame_time_interval; // an IEEE double calculation with a 32-bit
// numerator and 64-bit denominator
// integers
conn->remote_frame_rate =
conn->remote_frame_rate * (uint64_t)0x100000000; // this should just change the
// [binary] exponent in the IEEE
// FP representation; the
// mantissa should be unaffected.
(1.0E9 * (conn->reference_timestamp - conn->initial_reference_timestamp)) /
remote_frame_time_interval;
} else {
conn->remote_frame_rate = 0.0; // use as a flag.
}
@@ -423,28 +415,6 @@ void *rtp_control_receiver(void *arg) {
else
conn->reference_to_previous_frame_difference =
sync_rtp_timestamp - old_reference_timestamp;
// int64_t delayed_frame_difference = rtp_timestamp_less_latency -
// old_latency_delayed_timestamp;
/*
if (old_remote_reference_time)
debug(1,"Time difference: %" PRIu64 " reference and delayed frame differences: %"
PRId64 " and %" PRId64 ", giving rates _at source!!_ of %f and %f respectively.",
(conn->reference_to_previous_time_difference*1000000)>>32,conn->reference_to_previous_frame_difference,delayed_frame_difference,
(1.0*(conn->reference_to_previous_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32));
else
debug(1,"First sync received");
*/
// debug(1,"New Reference timestamp and timestamp time...");
// get estimated remote time now
// remote_time_now = local_time_now + local_to_remote_time_difference;
// debug(1,"Sync Time is %lld us late (remote
// times).",((remote_time_now-remote_time_of_sync)*1000000)>>32);
// debug(1,"Sync Time is %lld us late (local
// times).",((local_time_now-reference_timestamp_time)*1000000)>>32);
} else {
debug(2, "Sync packet received before we got a timing packet back.");
}
@@ -520,8 +490,7 @@ void *rtp_timing_sender(void *arg) {
req.filler = 0;
req.origin = req.receive = req.transmit = 0;
// clock_gettime(CLOCK_MONOTONIC,&dtt);
conn->departure_time = get_absolute_time_in_fp();
conn->departure_time = get_absolute_time_in_ns();
socklen_t msgsize = sizeof(struct sockaddr_in);
#ifdef AF_INET6
if (conn->rtp_client_timing_socket.SAFAMILY == AF_INET6) {
@@ -542,8 +511,8 @@ void *rtp_timing_sender(void *arg) {
request_number++;
if (request_number <= 4)
usleep(500000); // these are thread cancellation points
if (request_number <= 6)
usleep(300000); // these are thread cancellation points
else
usleep(3000000);
}
@@ -553,8 +522,29 @@ void *rtp_timing_sender(void *arg) {
}
void rtp_timing_receiver_cleanup_handler(void *arg) {
debug(3, "Timing Receiver Cleanup.");
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
debug(3, "Timing Receiver Cleanup.");
// walk down the list of DACP / gradient pairs, if any
nvll *gradients = config.gradients;
if (conn->dacp_id)
while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string,gradients->name) != 0))
gradients = gradients->next;
// if gradients comes out of this non-null, it is pointing to the DACP and it's last-known gradient
if (gradients) {
gradients->value = conn->local_to_remote_time_gradient;
// debug(1,"Updating a drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, gradients->name);
} else {
nvll *new_entry = (nvll*)malloc(sizeof(nvll));
if (new_entry) {
new_entry->name = strdup((const char *)&conn->client_ip_string);
new_entry->value = conn->local_to_remote_time_gradient;
new_entry->next = config.gradients;
config.gradients = new_entry;
// debug(1,"Setting a new drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, new_entry->name);
}
}
debug(3, "Cancel Timing Requester.");
pthread_cancel(conn->timer_requester);
int oldState;
@@ -580,6 +570,34 @@ void *rtp_timing_receiver(void *arg) {
// uint64_t first_local_time = 0;
uint64_t first_local_to_remote_time_difference = 0;
conn->local_to_remote_time_gradient = 1.0; // initial value.
// walk down the list of DACP / gradient pairs, if any
nvll *gradients = config.gradients;
while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string,gradients->name) != 0))
gradients = gradients->next;
// if gradients comes out of this non-null, it is pointing to the IP and it's last-known gradient
if (gradients) {
conn->local_to_remote_time_gradient = gradients->value;
// debug(1,"Using a stored drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, gradients->name);
}
// calculate diffusion factor
// at the end of the array of time pings, the diffusion factor
// must be diffusion_expansion_factor
// this, at each step, the diffusion multiplication constant must
// be the nth root of diffusion_expansion_factor
// where n is the number of elements in the array
const double diffusion_expansion_factor = 10;
double log_of_multiplier = log10(diffusion_expansion_factor)/time_ping_history;
double multiplier = pow(10,log_of_multiplier);
uint64_t dispersion_factor = (uint64_t)(multiplier * 100);
// debug(1,"dispersion factor is %" PRIu64 ".", dispersion_factor);
// uint64_t first_local_to_remote_time_difference_time;
// uint64_t l2rtd = 0;
int sequence_number = 0;
@@ -596,45 +614,37 @@ void *rtp_timing_receiver(void *arg) {
if ((config.diagnostic_drop_packet_fraction == 0.0) ||
(drand48() > config.diagnostic_drop_packet_fraction)) {
arrival_time = get_absolute_time_in_fp();
arrival_time = get_absolute_time_in_ns();
// ssize_t plen = nread;
// debug(1,"Packet Received on Timing Port.");
if (packet[1] == 0xd3) { // timing reply
/*
char obf[4096];
char *obfp = obf;
int obfc;
for (obfc=0;obfc<plen;obfc++) {
snprintf(obfp, 3, "%02X", packet[obfc]);
obfp+=2;
};
*obfp=0;
debug(1,"Timing Packet Received: \"%s\"",obf);
*/
// arrival_time = ((uint64_t)att.tv_sec<<32)+((uint64_t)att.tv_nsec<<32)/1000000000;
// departure_time = ((uint64_t)dtt.tv_sec<<32)+((uint64_t)dtt.tv_nsec<<32)/1000000000;
return_time = arrival_time - conn->departure_time;
debug(3,"clock synchronisation request: return time is %8.3f milliseconds.",0.000001*return_time);
// uint64_t rtus = (return_time * 1000000) >> 32;
if (((return_time * 1000000) >> 32) < 300000) {
// debug(2,"Synchronisation ping return time is %f milliseconds.",(rtus*1.0)/1000);
if (return_time < 200000000) { // must be less than 0.2 seconds
// distant_receive_time =
// ((uint64_t)ntohl(*((uint32_t*)&packet[16])))<<32+ntohl(*((uint32_t*)&packet[20]));
distant_receive_time = (uint64_t)nctohl(&packet[16]) << 32;
distant_receive_time += nctohl(&packet[20]);
uint64_t ps, pn;
ps = nctohl(&packet[16]);
ps = ps * 1000000000; // this many nanoseconds from the whole seconds
pn = nctohl(&packet[20]);
pn = pn * 1000000000;
pn = pn >> 32; // this many nanoseconds from the fractional part
distant_receive_time = ps + pn;
// distant_transmit_time =
// ((uint64_t)ntohl(*((uint32_t*)&packet[24])))<<32+ntohl(*((uint32_t*)&packet[28]));
distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32;
distant_transmit_time += nctohl(&packet[28]);
ps = nctohl(&packet[24]);
ps = ps * 1000000000; // this many nanoseconds from the whole seconds
pn = nctohl(&packet[28]);
pn = pn * 1000000000;
pn = pn >> 32; // this many nanoseconds from the fractional part
distant_transmit_time = ps + pn;
uint64_t remote_processing_time = 0;
@@ -644,13 +654,8 @@ void *rtp_timing_receiver(void *arg) {
debug(1, "Yikes: distant_transmit_time is before distant_receive_time; remote "
"processing time set to zero.");
}
// debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 "
// uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32);
uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2;
// remove the remote processing time from the record of the return time, as long at the
// processing time looks sensible.
// debug(1,"Return trip time: %" PRIu64 " nS, remote processing time: %" PRIu64 "
// nS.",return_time, remote_processing_time);
if (remote_processing_time < return_time)
return_time -= remote_processing_time;
@@ -658,24 +663,25 @@ void *rtp_timing_receiver(void *arg) {
debug(1, "Remote processing time greater than return time -- ignored.");
int cc;
// debug(1, "time ping history is %d entries.", time_ping_history);
for (cc = time_ping_history - 1; cc > 0; cc--) {
conn->time_pings[cc] = conn->time_pings[cc - 1];
// if ((conn->time_ping_count) && (conn->time_ping_count < 10))
// conn->time_pings[cc].dispersion =
// conn->time_pings[cc].dispersion * pow(2.14,
// 1.0/conn->time_ping_count);
if (conn->time_pings[cc].dispersion > UINT64_MAX / dispersion_factor)
debug(1,"dispersion factor is too large at %" PRIu64 ".");
else
conn->time_pings[cc].dispersion =
(conn->time_pings[cc].dispersion * 110) /
(conn->time_pings[cc].dispersion * dispersion_factor) /
100; // make the dispersions 'age' by this rational factor
}
// these are used for doing a least squares calculation to get the drift
conn->time_pings[0].local_time = arrival_time;
conn->time_pings[0].remote_time = distant_transmit_time;
conn->time_pings[0].remote_time = distant_transmit_time + return_time / 2;
conn->time_pings[0].sequence_number = sequence_number++;
conn->time_pings[0].chosen = 0;
conn->time_pings[0].local_to_remote_difference =
local_time_by_remote_clock - arrival_time;
conn->time_pings[0].dispersion = return_time;
if (conn->time_ping_count < time_ping_history)
conn->time_ping_count++;
@@ -685,28 +691,27 @@ void *rtp_timing_receiver(void *arg) {
// mean and variance calculations from "online_variance" algorithm at
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
double rtfus = 1.0 * ((return_time * 1000000) >> 32);
stat_n += 1;
double stat_delta = rtfus - stat_mean;
double stat_delta = return_time - stat_mean;
stat_mean += stat_delta / stat_n;
stat_M2 += stat_delta * (rtfus - stat_mean);
stat_M2 += stat_delta * (return_time - stat_mean);
// debug(1, "Timing packet return time stats: current, mean and standard deviation over
// %d packets: %.1f, %.1f, %.1f (microseconds).",
// stat_n,rtfus,stat_mean, sqrtf(stat_M2 / (stat_n - 1)));
// %d packets: %.1f, %.1f, %.1f (nanoseconds).",
// stat_n,return_time,stat_mean, sqrtf(stat_M2 / (stat_n - 1)));
// here, pick the record with the least dispersion, and record that it's been chosen
// 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 = conn->time_pings[0].local_to_remote_difference;
uint64_t rt = conn->time_pings[0].remote_time;
uint64_t lt = conn->time_pings[0].local_time;
uint64_t tld = conn->time_pings[0].dispersion;
int chosen = 0;
for (cc = 1; cc < conn->time_ping_count; cc++)
if (conn->time_pings[cc].dispersion < tld) {
chosen = cc;
l2rtd = conn->time_pings[cc].local_to_remote_difference;
rt = conn->time_pings[cc].remote_time;
lt = conn->time_pings[cc].local_time;
tld = conn->time_pings[cc].dispersion;
// local_time_chosen = conn->time_pings[cc].local_time;
@@ -716,46 +721,8 @@ void *rtp_timing_receiver(void *arg) {
// dispersion.",chosen,1.0*((tld * 1000000) >> 32));
conn->time_pings[chosen].chosen = 1; // record the fact that it has been used for timing
/*
// calculate the jitter -- the absolute time between the current
local_to_remote_time_difference and the new one and add it to the total jitter count
int64_t ji;
int64_t ltd =0; // local time difference for the jitter
if (conn->time_ping_count > 1) {
if (l2rtd > conn->local_to_remote_time_difference) {
local_to_remote_time_jitter =
local_to_remote_time_jitter + l2rtd - conn->local_to_remote_time_difference;
ji = l2rtd - conn->local_to_remote_time_difference; // this is the difference
between the present local-to-remote-time-difference and the new one, i.e. the jitter
step
} else {
local_to_remote_time_jitter =
local_to_remote_time_jitter + conn->local_to_remote_time_difference - l2rtd;
ji = -(conn->local_to_remote_time_difference - l2rtd);
}
local_to_remote_time_jitter_count += 1;
}
if (conn->local_to_remote_time_difference_measurement_time < lt)
ltd = lt-conn->local_to_remote_time_difference_measurement_time;
else
ltd = -(conn->local_to_remote_time_difference_measurement_time-lt);
if (ltd) {
debug(1,"Jitter: %" PRId64 " microseconds in %" PRId64 " microseconds.", (ji *
(int64_t)1000000)>>32, (ltd * (int64_t)1000000)>>32);
debug(1,"Source clock to local clock drift: %.2f ppm.",((1.0*ji)/ltd)*1000000.0);
}
// uncomment below to print jitter between client's clock and our clock
if (ji) {
int64_t rtus = (tld*1000000)>>32;
debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " us with an
adjustment of %" PRId64 " us",chosen, rtus, (ji*1000000)>>32);
}
*/
conn->local_to_remote_time_difference =
l2rtd; // make this the new local-to-remote-time-difference
rt - lt; // make this the new local-to-remote-time-difference
conn->local_to_remote_time_difference_measurement_time = lt; // done at this time.
if (first_local_to_remote_time_difference == 0) {
@@ -785,53 +752,68 @@ void *rtp_timing_receiver(void *arg) {
if ((conn->time_pings[cc].chosen) &&
(conn->time_pings[cc].sequence_number >
(settling_time / 3))) { // wait for a approximate settling time
y_bar += (conn->time_pings[cc].remote_time >>
12); // precision is down to 1/4th of a microsecond
x_bar += (conn->time_pings[cc].local_time >> 12);
// have to scale them down so that the sum, possibly over every term in the array, doesn't overflow
y_bar += (conn->time_pings[cc].remote_time >> time_ping_history_power_of_two);
x_bar += (conn->time_pings[cc].local_time >> time_ping_history_power_of_two);
sample_count++;
}
conn->local_to_remote_time_gradient_sample_count = sample_count;
if (sample_count > sample_point_minimum) {
y_bar = y_bar / sample_count;
x_bar = x_bar / sample_count;
int64_t xid, yid;
int64_t mtl, mbl;
double mtl, mbl;
mtl = 0;
mbl = 0;
for (cc = 0; cc < conn->time_ping_count; cc++)
if ((conn->time_pings[cc].chosen) &&
(conn->time_pings[cc].sequence_number > (settling_time / 3))) {
uint64_t slt = conn->time_pings[cc].local_time >> 12;
uint64_t slt = conn->time_pings[cc].local_time >> time_ping_history_power_of_two;
if (slt > x_bar)
xid = slt - x_bar;
else
xid = -(x_bar - slt);
uint64_t srt = conn->time_pings[cc].remote_time >> 12;
uint64_t srt = conn->time_pings[cc].remote_time >> time_ping_history_power_of_two;
if (srt > y_bar)
yid = srt - y_bar;
else
yid = -(y_bar - srt);
mtl = mtl + xid * yid;
mbl = mbl + xid * xid;
mtl = mtl + (1.0 * xid) * yid;
mbl = mbl + (1.0 * xid) * xid;
}
conn->local_to_remote_time_gradient_sample_count = sample_count;
if (mbl)
conn->local_to_remote_time_gradient = (1.0 * mtl) / mbl;
conn->local_to_remote_time_gradient = mtl / mbl;
else {
conn->local_to_remote_time_gradient = 1.0;
debug(1, "rtp_timing_receiver: mbl is 0");
// conn->local_to_remote_time_gradient = 1.0;
debug(1,"mbl is zero. Drift remains at %.2f ppm.", (conn->local_to_remote_time_gradient - 1.0)*1000000);
}
// scale the numbers back up
uint64_t ybf = y_bar << time_ping_history_power_of_two;
uint64_t xbf = x_bar << time_ping_history_power_of_two;
conn->local_to_remote_time_difference =
ybf - xbf; // make this the new local-to-remote-time-difference
conn->local_to_remote_time_difference_measurement_time = xbf;
} else {
conn->local_to_remote_time_gradient = 1.0;
debug(3,"not enough samples to estimate drift -- remaining at %.2f ppm.", (conn->local_to_remote_time_gradient - 1.0)*1000000);
// conn->local_to_remote_time_gradient = 1.0;
}
// debug(1,"local to remote time gradient is %12.2f ppm, based on %d
// samples.",conn->local_to_remote_time_gradient*1000000,sample_count);
} else {
debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.",
(return_time * 1000000) >> 32);
debug(1,
"Time ping turnaround time: %" PRIu64
" ns -- it looks like a timing ping was lost.",
return_time);
}
} else {
debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread);
@@ -1039,16 +1021,13 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport
conn->local_control_port, conn->local_timing_port);
conn->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);
conn->request_sent = 0;
conn->rtp_running = 1;
#ifdef CONFIG_METADATA
send_ssnc_metadata('clip', strdup(conn->client_ip_string), strlen(conn->client_ip_string), 1);
send_ssnc_metadata('svip', strdup(conn->self_ip_string), strlen(conn->self_ip_string), 1);
send_ssnc_metadata('clip', conn->client_ip_string, strlen(conn->client_ip_string), 1);
send_ssnc_metadata('svip', conn->self_ip_string, strlen(conn->self_ip_string), 1);
#endif
}
}
@@ -1061,9 +1040,6 @@ void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time
*remote_timestamp_time = conn->remote_reference_timestamp_time;
*timestamp_time =
conn->remote_reference_timestamp_time - local_to_remote_time_difference_now(conn);
// if ((*timestamp == 0) && (*timestamp_time == 0)) {
// debug(1,"Reference timestamp is invalid.");
//}
debug_mutex_unlock(&conn->reference_time_mutex, 0);
}
@@ -1088,9 +1064,8 @@ const int use_nominal_rate = 0; // specify whether to use the nominal input rate
int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn) {
int result = 1;
uint32_t fs = conn->input_rate;
*frames = fs;
uint64_t one_fp = (uint64_t)(0x100000000); // one second in fp form
*time = one_fp;
*frames = fs; // default value to return
*time = 1000000000; // default value to return
if ((conn->initial_reference_time) && (conn->initial_reference_timestamp)) {
// uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp;
uint32_t local_frames =
@@ -1101,7 +1076,7 @@ int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_con
} else {
double calculated_frame_rate = conn->input_rate;
if (local_time)
calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp;
calculated_frame_rate = (1.0E9 * local_frames) / local_time;
else
debug(1, "sanitised_source_rate_information: local_time is zero");
if ((local_time == 0) || ((calculated_frame_rate / conn->input_rate) > 1.002) ||
@@ -1173,7 +1148,7 @@ int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) {
// here, we calculate the time interval, in terms of remote time
uint64_t offset = modulo_64_offset(conn->remote_reference_timestamp_time, remote_time);
int reference_time_was_earlier = (offset <= (uint64_t)0x100000000 * 3600);
int reference_time_was_earlier = (offset <= (uint64_t)3600000000000);
if (reference_time_was_earlier) // if we haven't had a reference within the last hour, it'll be
// taken as afterwards
time_interval = remote_time - conn->remote_reference_timestamp_time;
@@ -1217,11 +1192,10 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
msgsize = sizeof(struct sockaddr_in6);
}
#endif
uint64_t time_of_sending_fp = get_absolute_time_in_fp();
uint64_t resend_error_backoff_time = (uint64_t)1000000 * 0.3; // 0.3 seconds
resend_error_backoff_time = (resend_error_backoff_time << 32) / 1000000;
if ((conn->rtp_time_of_last_resend_request_error_fp == 0) ||
((time_of_sending_fp - conn->rtp_time_of_last_resend_request_error_fp) >
uint64_t time_of_sending_ns = get_absolute_time_in_ns();
uint64_t resend_error_backoff_time = 300000000; // 0.3 seconds
if ((conn->rtp_time_of_last_resend_request_error_ns == 0) ||
((time_of_sending_ns - conn->rtp_time_of_last_resend_request_error_ns) >
resend_error_backoff_time)) {
if ((config.diagnostic_drop_packet_fraction == 0.0) ||
(drand48() > config.diagnostic_drop_packet_fraction)) {
@@ -1239,22 +1213,20 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
(struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) {
char em[1024];
strerror_r(errno, em, sizeof(em));
debug(2, "Error %d using sendto to request a resend: \"%s\".",
errno, em);
conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
debug(2, "Error %d using sendto to request a resend: \"%s\".", errno, em);
conn->rtp_time_of_last_resend_request_error_ns = time_of_sending_ns;
} else {
conn->rtp_time_of_last_resend_request_error_fp = 0;
conn->rtp_time_of_last_resend_request_error_ns = 0;
}
} else {
debug(
3,
"Dropping resend request packet to simulate a bad network. Backing off for 0.3 "
debug(3, "Dropping resend request packet to simulate a bad network. Backing off for 0.3 "
"second.");
conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
conn->rtp_time_of_last_resend_request_error_ns = time_of_sending_ns;
}
} else {
debug(1, "Suppressing a resend request due to a resend sendto error in the last 0.3 seconds.");
debug(1,
"Suppressing a resend request due to a resend sendto error in the last 0.3 seconds.");
}
} else {
// if (!request_sent) {
+330 -82
View File
@@ -114,6 +114,7 @@ typedef struct {
pthread_mutex_t pc_queue_lock;
pthread_cond_t pc_queue_item_added_signal;
pthread_cond_t pc_queue_item_removed_signal;
char *name;
size_t item_size; // number of bytes in each item
uint32_t count; // number of items in the queue
uint32_t capacity; // maximum number of items
@@ -151,7 +152,11 @@ 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, const char* name) {
if (name)
debug(2, "Creating metadata queue \"%s\".", name);
else
debug(1, "Creating an unnamed metadata queue.");
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);
@@ -161,12 +166,26 @@ void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t
the_queue->capacity = number_of_items;
the_queue->toq = 0;
the_queue->eoq = 0;
if (name == NULL)
the_queue->name = NULL;
else
the_queue->name = strdup(name);
}
void pc_queue_delete(pc_queue *the_queue) {
if (the_queue->name)
debug(2, "Deleting metadata queue \"%s\".", the_queue->name);
else
debug(1, "Deleting an unnamed metadata queue.");
if (the_queue->name != NULL)
free(the_queue->name);
// debug(2, "destroying pc_queue_item_removed_signal");
pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal);
// debug(2, "destroying pc_queue_item_added_signal");
pthread_cond_destroy(&the_queue->pc_queue_item_added_signal);
// debug(2, "destroying pc_queue_lock");
pthread_mutex_destroy(&the_queue->pc_queue_lock);
// debug(2, "destroying signals and locks done");
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
@@ -185,6 +204,7 @@ void pc_queue_cleanup_handler(void *arg) {
}
int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
int response = 0;
int rc;
if (the_queue) {
if (block == 0) {
@@ -196,11 +216,16 @@ 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");
pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
// leave this out if you want this to return if the queue is already full
// irrespective of the block flag.
/*
while (the_queue->count == the_queue->capacity) {
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");
}
*/
if (the_queue->count < the_queue->capacity) {
uint32_t i = the_queue->eoq;
void *p = the_queue->items + the_queue->item_size * i;
// void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq;
@@ -213,16 +238,21 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
i = 0;
the_queue->eoq = i;
the_queue->count++;
//debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count, the_queue->capacity);
if (the_queue->count == the_queue->capacity)
debug(1, "pc_queue is full with %d items in it!", the_queue->count);
debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name, the_queue->count);
rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
if (rc)
debug(1, "Error signalling after pc_queue_add_item");
debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item", the_queue->name);
} else {
response = EWOULDBLOCK; // a bit arbitrary, this.
debug(3,"metadata queue \"%s\": is already full with %d items in it. Not adding this item to the queue.", the_queue->name, the_queue->count);
}
pthread_cleanup_pop(1); // unlock the queue lock.
} else {
debug(1, "Adding an item to a NULL queue");
}
return 0;
return response;
}
int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
@@ -230,12 +260,12 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
if (the_queue) {
rc = pthread_mutex_lock(&the_queue->pc_queue_lock);
if (rc)
debug(1, "Error locking for pc_queue_get_item");
debug(1, "metadata queue \"%s\": error locking for pc_queue_get_item", the_queue->name);
pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
while (the_queue->count == 0) {
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");
debug(1, "metadata queue \"%s\": error waiting for item to be added", the_queue->name);
}
uint32_t i = the_queue->toq;
// void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq;
@@ -249,9 +279,10 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
i = 0;
the_queue->toq = i;
the_queue->count--;
debug(3,"metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count, the_queue->capacity);
rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal);
if (rc)
debug(1, "Error signalling after pc_queue_removed_item");
debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name);
pthread_cleanup_pop(1); // unlock the queue lock.
} else {
debug(1, "Removing an item from a NULL queue");
@@ -286,7 +317,7 @@ void *player_watchdog_thread_code(void *arg) {
uint64_t last_watchdog_bark_time = conn->watchdog_bark_time;
debug_mutex_unlock(&conn->watchdog_mutex, 0);
if (last_watchdog_bark_time != 0) {
uint64_t time_since_last_bark = (get_absolute_time_in_fp() - last_watchdog_bark_time) >> 32;
uint64_t time_since_last_bark = (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000;
uint64_t ct = config.timeout; // go from int to 64-bit int
if (time_since_last_bark >= ct) {
@@ -337,11 +368,11 @@ static void track_thread(rtsp_conn_info *conn) {
void cancel_all_RTSP_threads(void) {
int i;
for (i = 0; i < nconns; i++) {
debug(1, "Connection %d: cancelling.", conns[i]->connection_number);
debug(2, "Connection %d: cancelling.", conns[i]->connection_number);
pthread_cancel(conns[i]->thread);
}
for (i = 0; i < nconns; i++) {
debug(1, "Connection %d: joining.", conns[i]->connection_number);
debug(2, "Connection %d: joining.", conns[i]->connection_number);
pthread_join(conns[i]->thread, NULL);
free(conns[i]);
}
@@ -415,6 +446,7 @@ void msg_retain(rtsp_message *msg) {
debug(1, "Error %d locking reference counter lock");
if (msg > (rtsp_message *)0x00010000) {
msg->referenceCount++;
debug(3,"msg_free increment reference counter message %d to %d.", msg->index_number, msg->referenceCount);
// debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount);
rc = pthread_mutex_unlock(&reference_counter_lock);
if (rc)
@@ -430,6 +462,7 @@ rtsp_message *msg_init(void) {
memset(msg, 0, sizeof(rtsp_message));
msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
msg->index_number = msg_indexes++;
debug(3,"msg_init message %d", msg->index_number);
} else {
die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes);
}
@@ -493,6 +526,8 @@ void msg_free(rtsp_message **msgh) {
if (*msgh > (rtsp_message *)0x00010000) {
rtsp_message *msg = *msgh;
msg->referenceCount--;
if (msg->referenceCount)
debug(3,"msg_free decrement reference counter message %d to %d", msg->index_number, msg->referenceCount);
if (msg->referenceCount == 0) {
unsigned int i;
for (i = 0; i < msg->nheaders; i++) {
@@ -507,6 +542,7 @@ void msg_free(rtsp_message **msgh) {
index = 0x10000; // ensure it doesn't fold to zero.
*msgh =
(rtsp_message *)(index); // put a version of the index number of the freed message in here
debug(3,"msg_free freed message %d", msg->index_number);
free(msg);
} else {
// debug(1,"msg_free item %d -- decrement reference to
@@ -666,7 +702,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
}
uint64_t threshold_time =
get_absolute_time_in_fp() + ((uint64_t)15 << 32); // i.e. fifteen seconds from now
get_absolute_time_in_ns() + ((uint64_t)15000000000); // i.e. fifteen seconds from now
int warning_message_sent = 0;
const size_t max_read_chunk = 1024 * 1024 / 16;
@@ -679,7 +715,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
// metadata
if (warning_message_sent == 0) {
uint64_t time_now = get_absolute_time_in_fp();
uint64_t time_now = get_absolute_time_in_ns();
if (time_now > threshold_time) { // it's taking too long
debug(1, "Error receiving metadata from source -- transmission seems "
"to be stalled.");
@@ -894,12 +930,17 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
}
}
// debug(1,"RTSP Flush Requested: %u.",rtptime);
// the following is now done better by the player_flush routine as a 'pfls'
/*
#ifdef CONFIG_METADATA
if (p)
send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1);
else
send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
#endif
*/
player_flush(rtptime, conn); // will not crash even it there is no player thread.
resp->respcode = 200;
@@ -1070,7 +1111,7 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
char *progress = cp + strlen("progress: ");
// debug(2, "progress: \"%s\"",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per
// second
send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1);
send_ssnc_metadata('prgr', progress, strlen(progress), 1);
} else
#endif
@@ -1113,7 +1154,10 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
// 'aend' -- active mode exited. No arguments
// 'pbeg' -- play stream begin. No arguments
// 'pend' -- play stream end. No arguments
// 'pfls' -- play stream flush. No arguments
// 'pfls' -- play stream flush. The argument is an unsigned 32-bit
// frame number. It seems that all frames up to but not
// including this frame are to be flushed.
//
// 'prsm' -- play stream resume. No arguments
// `pffr` -- the first frame of a play session has been received and has been validly
// timed.
@@ -1153,8 +1197,11 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
// 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.
// `svip` -- the payload is the IP number of the server, i.e. the player itself.
// Can be an IPv4 or an IPv6 number.
// `dapo` -- the payload is the port number (as text) on the server to which remote
// control commands should be sent. It is 3689 for iTunes but varies for iOS devices.
// A special sub-protocol is used for sending large data items over UDP
// If the payload exceeded 4 MB, it is chunked using the following format:
// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
@@ -1217,14 +1264,36 @@ char *base64_encode_so(const unsigned char *data, size_t input_length, char *enc
static int fd = -1;
// static int dirty = 0;
pc_queue metadata_queue;
#define metadata_queue_size 500
metadata_package metadata_queue_items[metadata_queue_size];
pthread_t metadata_thread;
#ifdef CONFIG_METADATA_HUB
pc_queue metadata_hub_queue;
#define metadata_hub_queue_size 500
metadata_package metadata_hub_queue_items[metadata_hub_queue_size];
pthread_t metadata_hub_thread;
#endif
#ifdef CONFIG_MQTT
pc_queue metadata_mqtt_queue;
#define metadata_mqtt_queue_size 500
metadata_package metadata_mqtt_queue_items[metadata_mqtt_queue_size];
pthread_t metadata_mqtt_thread;
#endif
static int metadata_sock = -1;
static struct sockaddr_in metadata_sockaddr;
static char *metadata_sockmsg;
#define metadata_queue_size 500
metadata_package metadata_queue_items[metadata_queue_size];
pc_queue metadata_multicast_queue;
#define metadata_multicast_queue_size 500
metadata_package metadata_multicast_queue_items[metadata_queue_size];
pthread_t metadata_multicast_thread;
pthread_t metadata_thread;
void metadata_create_multicast_socket(void) {
if (config.metadata_enabled == 0)
@@ -1252,19 +1321,6 @@ void metadata_create_multicast_socket(void) {
}
}
}
size_t pl = strlen(config.metadata_pipename) + 1;
char *path = malloc(pl + 1);
snprintf(path, pl + 1, "%s", config.metadata_pipename);
mode_t oldumask = umask(000);
if (mkfifo(path, 0666) && errno != EEXIST)
die("Could not create metadata FIFO %s", path);
free(path);
umask(oldumask);
}
void metadata_delete_multicast_socket(void) {
@@ -1276,6 +1332,7 @@ void metadata_delete_multicast_socket(void) {
free(metadata_sockmsg);
}
void metadata_open(void) {
if (config.metadata_enabled == 0)
return;
@@ -1285,11 +1342,7 @@ void metadata_open(void) {
char *path = malloc(pl + 1);
snprintf(path, pl + 1, "%s", config.metadata_pipename);
fd = open(path, O_WRONLY | O_NONBLOCK);
// if (fd < 0)
// debug(1, "Could not open metadata FIFO %s. Will try again later.",
// path);
fd = try_to_open_pipe_for_writing(path);
free(path);
}
@@ -1300,10 +1353,8 @@ static void metadata_close(void) {
fd = -1;
}
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;
void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
// debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code, length);
if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) {
char *ptr = metadata_sockmsg;
uint32_t v;
@@ -1358,8 +1409,13 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
break;
} while (1);
}
}
void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
// debug(1, "Process metadata with type %x, code %x and length %u.", type, code, length);
int ret = 0;
// readers may go away and come back
if (fd < 0)
metadata_open();
if (fd < 0)
@@ -1367,14 +1423,16 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
char thestring[1024];
snprintf(thestring, 1024, "<item><type>%x</type><code>%x</code><length>%u</length>", type, code,
length);
ret = non_blocking_write(fd, thestring, strlen(thestring));
// ret = non_blocking_write(fd, thestring, strlen(thestring));
ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 1",ret);
return;
}
if ((data != NULL) && (length > 0)) {
snprintf(thestring, 1024, "\n<data encoding=\"base64\">\n");
ret = non_blocking_write(fd, thestring, strlen(thestring));
// ret = non_blocking_write(fd, thestring, strlen(thestring));
ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 2",ret);
return;
@@ -1397,7 +1455,8 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
debug(1, "Error encoding base64 data.");
// debug(1,"Remaining count: %d ret: %d, outbuf_size:
// %d.",remaining_count,ret,outbuf_size);
ret = non_blocking_write(fd, outbuf, outbuf_size);
//ret = non_blocking_write(fd, outbuf, outbuf_size);
ret = write(fd, outbuf, outbuf_size);
if (ret < 0) {
// debug(1,"metadata_process error %d exit 3",ret);
return;
@@ -1406,27 +1465,22 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
remaining_count -= towrite_count;
}
snprintf(thestring, 1024, "</data>");
ret = non_blocking_write(fd, thestring, strlen(thestring));
// ret = non_blocking_write(fd, thestring, strlen(thestring));
ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 4",ret);
return;
}
}
snprintf(thestring, 1024, "</item>\n");
ret = non_blocking_write(fd, thestring, strlen(thestring));
//ret = non_blocking_write(fd, thestring, strlen(thestring));
ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 5",ret);
return;
}
}
void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) {
debug(2, "metadata_thread_cleanup_function called");
metadata_delete_multicast_socket();
metadata_close();
pc_queue_delete(&metadata_queue);
}
void metadata_pack_cleanup_function(void *arg) {
// debug(1, "metadata_pack_cleanup_function called");
metadata_package *pack = (metadata_package *)arg;
@@ -1434,12 +1488,19 @@ void metadata_pack_cleanup_function(void *arg) {
msg_free(&pack->carrier); // release the message
else if (pack->data)
free(pack->data);
// debug(1, "metadata_pack_cleanup_function exit");
}
void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_thread_cleanup_function called");
metadata_close();
pc_queue_delete(&metadata_queue);
}
void *metadata_thread_function(__attribute__((unused)) void *ignore) {
// 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);
metadata_queue_size, "pipe");
metadata_create_multicast_socket();
metadata_package pack;
pthread_cleanup_push(metadata_thread_cleanup_function, NULL);
@@ -1447,16 +1508,13 @@ void *metadata_thread_function(__attribute__((unused)) void *ignore) {
pc_queue_get_item(&metadata_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (config.metadata_enabled) {
metadata_process(pack.type, pack.code, pack.data, pack.length);
#ifdef CONFIG_METADATA_HUB
metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
#endif
#ifdef CONFIG_MQTT
if (config.mqtt_enabled) {
mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
if (pack.carrier) {
debug(3, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
} else {
debug(3, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
}
#endif
metadata_process(pack.type, pack.code, pack.data, pack.length);
debug(3, " pipe: done.");
}
pthread_cleanup_pop(1);
}
@@ -1464,22 +1522,185 @@ void *metadata_thread_function(__attribute__((unused)) void *ignore) {
pthread_exit(NULL);
}
void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_multicast_thread_cleanup_function called");
metadata_delete_multicast_socket();
pc_queue_delete(&metadata_multicast_queue);
}
void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items, sizeof(metadata_package),
metadata_multicast_queue_size, "multicast");
metadata_create_multicast_socket();
metadata_package pack;
pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL);
while (1) {
pc_queue_get_item(&metadata_multicast_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (config.metadata_enabled) {
if (pack.carrier) {
debug(3, " multicast: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
} else {
debug(3, " multicast: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
}
metadata_multicast_process(pack.type, pack.code, pack.data, pack.length);
debug(3, " multicast: done.");
}
pthread_cleanup_pop(1);
}
pthread_cleanup_pop(1); // will never happen
pthread_exit(NULL);
}
#ifdef CONFIG_METADATA_HUB
void metadata_hub_close(void) {
}
void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_hub_thread_cleanup_function called");
metadata_hub_close();
pc_queue_delete(&metadata_hub_queue);
}
void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
// create the fifo, if necessary
size_t pl = strlen(config.metadata_pipename) + 1;
char *path = malloc(pl + 1);
snprintf(path, pl + 1, "%s", config.metadata_pipename);
mode_t oldumask = umask(000);
if (mkfifo(path, 0644) && errno != EEXIST)
die("Could not create metadata pipe \"%s\".", path);
umask(oldumask);
debug(1, "metadata pipe name is \"%s\".", path);
// try to open it
fd = try_to_open_pipe_for_writing(path);
// we check that it's not a "real" error. From the "man 2 open" page:
// "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
// open for reading." Which is okay.
if ((fd == -1) && (errno != ENXIO)) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno,
(char *)errorstring, path);
warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
(char *)errorstring, path);
}
free(path);
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package),
metadata_hub_queue_size, "hub");
metadata_package pack;
pthread_cleanup_push(metadata_hub_thread_cleanup_function, NULL);
while (1) {
pc_queue_get_item(&metadata_hub_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (pack.carrier) {
debug(3, " hub: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
} else {
debug(3, " hub: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
}
metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
debug(3, " hub: done.");
pthread_cleanup_pop(1);
}
pthread_cleanup_pop(1); // will never happen
pthread_exit(NULL);
}
#endif
#ifdef CONFIG_MQTT
void metadata_mqtt_close(void) {
}
void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_mqtt_thread_cleanup_function called");
metadata_mqtt_close();
pc_queue_delete(&metadata_mqtt_queue);
// debug(2, "metadata_mqtt_thread_cleanup_function done");
}
void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_mqtt_queue, (char *)&metadata_mqtt_queue_items, sizeof(metadata_package),
metadata_mqtt_queue_size, "mqtt");
metadata_package pack;
pthread_cleanup_push(metadata_mqtt_thread_cleanup_function, NULL);
while (1) {
pc_queue_get_item(&metadata_mqtt_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (config.mqtt_enabled) {
if (pack.carrier) {
debug(3, " mqtt: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
} else {
debug(3, " mqtt: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
}
mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
debug(3, " mqtt: done.");
}
pthread_cleanup_pop(1);
}
pthread_cleanup_pop(1); // will never happen
pthread_exit(NULL);
}
#endif
void metadata_init(void) {
int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
if (ret)
debug(1, "Failed to create metadata thread!");
ret = pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, NULL);
if (ret)
debug(1, "Failed to create metadata multicast thread!");
#ifdef CONFIG_METADATA_HUB
ret = pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL);
if (ret)
debug(1, "Failed to create metadata hub thread!");
#endif
#ifdef CONFIG_MQTT
ret = pthread_create(&metadata_mqtt_thread, NULL, metadata_mqtt_thread_function, NULL);
if (ret)
debug(1, "Failed to create metadata mqtt thread!");
#endif
metadata_running = 1;
}
void metadata_stop(void) {
if (metadata_running) {
debug(2, "metadata_stop called.");
#ifdef CONFIG_MQTT
// debug(2, "metadata stop mqtt thread.");
pthread_cancel(metadata_mqtt_thread);
pthread_join(metadata_mqtt_thread, NULL);
// debug(2, "metadata stop mqtt done.");
#endif
#ifdef CONFIG_METADATA_HUB
// debug(2, "metadata stop hub thread.");
pthread_cancel(metadata_hub_thread);
pthread_join(metadata_hub_thread, NULL);
// debug(2, "metadata stop hub done.");
#endif
// debug(2, "metadata stop multicast thread.");
pthread_cancel(metadata_multicast_thread);
pthread_join(metadata_multicast_thread, NULL);
// debug(2, "metadata stop multicast done.");
// debug(2, "metadata stop metadata_thread thread.");
pthread_cancel(metadata_thread);
pthread_join(metadata_thread, NULL);
// debug(2, "metadata_stop finished successfully.");
}
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int send_metadata_to_queue(pc_queue* queue, 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,
@@ -1496,37 +1717,64 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rts
// The reading of the parameters is a bit complex
// If the rtsp_message field is non-null, then it represents an rtsp_message
// which should be freed
// in the thread handler when the parameter pointed to by the pointer and
// specified by the length
// is finished with
// If the rtsp_message is NULL, then if the pointer is non-null, it points to
// a malloc'ed block
// and should be freed when the thread is finished with it. The length of the
// data in the block is
// given in length
// and the data pointer is assumed to point to something within it.
// The reference counter of the rtsp_message is incremented here and
// should be decremented by the metadata handler when finished.
// If the reference count reduces to zero, the message will be freed.
// If the rtsp_message is NULL, then if the pointer is non-null then the data it
// points to, of the length specified, is memcpy'd and passed to the metadata
// handler. The handler should free it when done.
// If the rtsp_message is NULL and the pointer is also NULL, nothing further
// is done.
metadata_package pack;
pack.type = type;
pack.code = code;
pack.data = data;
pack.length = length;
pack.carrier = carrier;
if (pack.carrier)
pack.data = data;
if (pack.carrier) {
msg_retain(pack.carrier);
int rc = pc_queue_add_item(&metadata_queue, &pack, block);
if (rc == EBUSY) {
if (pack.carrier)
} else {
if (data)
pack.data = memdup(data,length); // only if it's not a null
}
int rc = pc_queue_add_item(queue, &pack, block);
if (rc != 0) {
if (pack.carrier) {
if (rc == EWOULDBLOCK)
debug(2, "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %x, length %u, message %d.", queue->name, pack.type, pack.code, pack.data, pack.length, pack.carrier->index_number);
msg_free(&pack.carrier);
else if (data)
free(data);
warn("Metadata queue is busy, discarding message of type 0x%08X, code 0x%08X.", type, code);
} else {
if (rc == EWOULDBLOCK)
debug(2, "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %x, length %u.", queue->name, pack.type, pack.code, pack.data, pack.length);
if (pack.data)
free(pack.data);
}
}
return rc;
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int block) {
int rc;
rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
#ifdef CONFIG_METADATA_HUB
rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
#endif
#ifdef CONFIG_MQTT
rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
#endif
return rc;
}
static void handle_set_parameter_metadata(__attribute__((unused)) rtsp_conn_info *conn,
rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {
@@ -2319,7 +2567,7 @@ static void *rtsp_conversation_thread_func(void *pconn) {
rtsp_conn_info *conn = pconn;
// create the watchdog mutex, initialise the watchdog time and start the watchdog thread;
conn->watchdog_bark_time = get_absolute_time_in_fp();
conn->watchdog_bark_time = get_absolute_time_in_ns();
pthread_mutex_init(&conn->watchdog_mutex, NULL);
pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn);
@@ -2514,7 +2762,7 @@ static const char *format_address(struct sockaddr *fsa) {
void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
debug(1, "rtsp_listen_loop_cleanup_handler called.");
debug(2, "rtsp_listen_loop_cleanup_handler called.");
cancel_all_RTSP_threads();
int *sockfd = (int *)arg;
mdns_unregister();
@@ -2733,7 +2981,7 @@ void rtsp_listen_loop(void) {
} while (1);
pthread_cleanup_pop(1); // should never happen
} else {
warn("could not establish a service on port %d -- program terminating. Is another instance of "
die("could not establish a service on port %d -- program terminating. Is another instance of "
"Shairport Sync running on this device?",
config.port);
}
+5 -1
View File
@@ -4,11 +4,15 @@
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- Only user shairport-sync can own the Shairport Sync service -->
<!-- Allow users "root" and "shairport-sync" to own the Shairport Sync service -->
<policy user="root">
<allow own="org.gnome.ShairportSync"/>
</policy>
<policy user="shairport-sync">
<allow own="org.gnome.ShairportSync"/>
</policy>
<!-- Allow anyone to invoke methods on Shairport Sync server -->
<policy context="default">
<allow send_destination="org.gnome.ShairportSync"/>
+4 -1
View File
@@ -4,10 +4,13 @@
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- Only user shairport-sync can own the Shairport Sync MPRIS service -->
<!-- Allow users "root" and "shairport-sync" to own the Shairport Sync MPRIS service -->
<policy user="shairport-sync">
<allow own="org.mpris.MediaPlayer2.ShairportSync"/>
</policy>
<policy user="root">
<allow own="org.mpris.MediaPlayer2.ShairportSync"/>
</policy>
<!-- Allow anyone to invoke methods on Shairport Sync MPRIS server -->
<policy context="default">
+33 -16
View File
@@ -18,11 +18,20 @@ general =
// interpolation = "auto"; // aka "stuffing". Default is "auto". Alternatives are "basic" or "soxr". Choose "soxr" only if you have a reasonably fast processor and Shairport Sync has been built with "soxr" support.
// output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one.
// mdns_backend = "avahi"; // Run "shairport-sync -h" to get a list of all mdns_backends. The default is the first one.
// 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.
// port = 5000; // Listen for service requests on this port
// udp_port_base = 6001; // start allocating UDP ports from this port number when needed
// udp_port_range = 10; // look for free ports in this number of places, starting at the UDP port base. Allow at least 10, though only three are needed in a steady state.
// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
// 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
// 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.
// If you build Shairport Sync with the flag --with-apple-alac, the Apple ALAC decoder will be chosen by default.
// 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 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.
@@ -36,38 +45,40 @@ general =
// The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument.
// AirPlay volume goes from 0 to -30 and -144 means "mute".
// 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.
// If you build Shairport Sync with the flag --with-apple-alac, the Apple ALAC decoder will be chosen by default.
// 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.
// 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.2; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response time to volume changes becomes annoying. Default is 0.15 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise.
// audio_backend_latency_offset_in_seconds = 0.0; // This is added to the latency requested by the player to delay or advance the output by a fixed amount.
// Use it, for example, to compensate for a fixed delay in the audio back end.
// E.g. if the output device, e.g. a soundbar, takes 100 ms to process audio, set this to -0.1 to deliver the audio
// to the output device 100 ms early, allowing it time to process the audio and output it perfectly in sync.
// audio_backend_buffer_desired_length_in_seconds = 0.2; // If set too small, buffer underflow occurs on low-powered machines.
// Too long and the response time to volume changes becomes annoying.
// Default is 0.2 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise.
// audio_backend_buffer_interpolation_threshold_in_seconds = 0.075; // Advanced feature. If the buffer size drops below this, stop using time-consuming interpolation like soxr to avoid dropouts due to underrun.
// audio_backend_silent_lead_in_time = 2.0; // This optional advanced setting, from 0.0 and 4.0 seconds, sets the length of the period of silence that precedes the start of the audio. The default is the latency, usually 2.0 seconds. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation.
// audio_backend_silent_lead_in_time = "auto"; // This optional advanced setting, either "auto" or a positive number, sets the length of the period of silence that precedes the start of the audio.
// The default is "auto" -- the silent lead-in starts as soon as the player starts sending packets.
// Values greater than the latency are ignored. Values that are too low will affect initial synchronisation.
// dbus_service_bus = "system"; // The Shairport Sync dbus interface, if selected at compilation, will appear
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
// mpris_service_bus = "system"; // The Shairport Sync mpris interface, if selected at compilation, will appear
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
// resend_control_first_check_time = 0.10; // Use this optional advanced setting to set the wait time in seconds before deciding a packet is missing.
// resend_control_check_interval_time = 0.25; // Use this optional advanced setting to set the time in seconds between requests for a missing packet.
// resend_control_last_check_time = 0.10; // Use this optional advanced setting to set the latest time, in seconds, by which the last check should be done before the estimated time of a missing packet's transfer to the output buffer.
//
// missing_port_dacp_scan_interval_seconds = 2.0; // Use this optional advanced setting to set the time interval between scans for a DACP port number if no port number has been provided by the player for remote control commands
};
// Advanced parameters for controlling how Shairport Sync stays active and how it runs a session
sessioncontrol =
{
// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// "active" state starts when play begins and ends when the active_state_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed.
// run_this_before_entering_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// run_this_after_exiting_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// active_state_timeout = 10.0; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins.
// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument.
// Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem.
// You could hook on a program to do this automatically, but beware -- the device may then power off and restart without warning!
@@ -84,18 +95,22 @@ sessioncontrol =
// --with-alsa
alsa =
{
// output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
// output_device = "default"; // the name of the alsa output device. Use "shairport-sync -h" to discover the names of ALSA hardware devices. 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 = "auto"; // can be "auto", 44100, 88200, 176400 or 352800, but the device must have the capability.
// output_format = "auto"; // can be "auto", "U8", "S8", "S16", "S16_LE", "S16_BE", "S24", "S24_LE", "S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE" or "S32_BE" but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no".
// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no" This is really meant for troubleshootingG.
// period_size = <number>; // Use this optional advanced setting to set the alsa period size near to this value
// buffer_size = <number>; // 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"
// use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players.
// maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms.
// use_precision_timing = "auto"; // Use this optional advanced setting to control how Shairport Sync gathers timing information. When set to "auto", if the output device is a real hardware device, precision timing will be used. Choose "no" for more compatible standard timing, choose "yes" to force the use of precision timing, which may cause problems.
// disable_standby_mode = "never"; // This setting prevents the DAC from entering the standby mode. Some DACs make small "popping" noises when they go in and out of standby mode. Settings can be: "always", "auto" or "never". Default is "never", but only for backwards compatibility. The "auto" setting prevents entry to standby mode while Shairport Sync is in the "active" mode. You can use "yes" instead of "always" and "no" instead of "never".
// disable_standby_mode_silence_threshold = 0.040; // Use this optional advanced setting to control how little audio should remain in the output buffer before the disable_standby code should start sending silence to the output device.
// disable_standby_mode_silence_scan_interval = 0.004; // Use this optional advanced setting to control how often the amount of audio remaining in the output buffer should be checked.
@@ -118,6 +133,7 @@ sndio =
// --with-pa
pa =
{
// server = "host"; // Set this to override the default pulseaudio server that should be used.
// application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active.
};
@@ -229,6 +245,7 @@ mqtt =
diagnostics =
{
// disable_resend_requests = "no"; // set this to yes to stop Shairport Sync from requesting the retransmission of missing packets. Default is "no".
// log_output_to = "syslog"; // set this to "syslog" (default), "stderr" or "stdout" or a file or pipe path to specify were all logs, statistics and diagnostic messages are written to. If there's anything wrong with the file spec, output will be to "stderr".
// statistics = "no"; // set to "yes" to print statistics in the log
// log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose.
// log_show_file_and_line = "yes"; // set this to yes if you want the file and line number of the message source in the log file
+16 -10
View File
@@ -186,11 +186,10 @@ int main(int argc, char *argv[]) {
g_signal_connect(proxy4, "notify::volume", G_CALLBACK(notify_volume_callback),
"ShairportSync.AdvancedRemoteControl");
g_print("Starting test...\n");
g_print("Using the RemoteControl interface, play for five seconds, pause for five seconds and then resume play...\n");
g_print("Using the RemoteControl interface, play for five seconds, pause for five seconds and "
"then resume play...\n");
g_print("Play...\n");
shairport_sync_remote_control_call_play(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, 0);
sleep(5);
@@ -200,24 +199,31 @@ int main(int argc, char *argv[]) {
g_print("Play...\n");
shairport_sync_remote_control_call_play(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, 0);
sleep(5);
g_print("Using the RemoteControl interface, set AirPlay Volume (range -30 to 0) to -30, -20, -10, 0 and -15 for five seconds each...\n");
g_print("Using the RemoteControl interface, set AirPlay Volume (range -30 to 0) to -30, -20, "
"-10, 0 and -15 for five seconds each...\n");
g_print("Set AirPlay Volume (range -30 to 0) to -30\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -30, NULL, NULL, 0);
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -30,
NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -20\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -20, NULL, NULL, 0);
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -20,
NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -10\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -10, NULL, NULL, 0);
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -10,
NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -0\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), 0, NULL, NULL, 0);
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), 0,
NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -15\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -15, NULL, NULL, 0);
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -15,
NULL, NULL, 0);
sleep(5);
g_print("Using the AdvancedRemoteControl interface, set Volume to 20%%, 100%%, 40%% and 60%% for five seconds each...\n");
g_print("Using the AdvancedRemoteControl interface, set Volume to 20%%, 100%%, 40%% and 60%% for "
"five seconds each...\n");
g_print("Set Volume to 20%%\n");
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 20, NULL, NULL, 0);
+150 -74
View File
@@ -143,10 +143,11 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
int i;
int number_of_iterations = 0;
uint64_t soxr_start_time = get_absolute_time_in_fp();
uint64_t soxr_start_time = get_absolute_time_in_ns();
uint64_t loop_until_time =
(uint64_t)0x180000000 + soxr_start_time; // loop for a second and a half, max -- no need to be able to cancel it, do _don't even try_!
while (get_absolute_time_in_fp() < loop_until_time) {
(uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be
// able to cancel it, do _don't even try_!
while (get_absolute_time_in_ns() < loop_until_time) {
number_of_iterations++;
for (i = 0; i < buffer_length; i++) {
@@ -184,11 +185,10 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
NULL, NULL); // Default configuration.
}
double soxr_execution_time_us =
(((get_absolute_time_in_fp() - soxr_start_time) * 1000000) >> 32) * 1.0;
double soxr_execution_time_ns = (get_absolute_time_in_ns() - soxr_start_time) * 1.0;
// free(outbuffer);
// free(inbuffer);
config.soxr_delay_index = (int)(0.9 + soxr_execution_time_us / (number_of_iterations * 1000));
config.soxr_delay_index = (int)(0.9 + soxr_execution_time_ns / (number_of_iterations * 1000000));
debug(2, "soxr_delay_index: %d.", config.soxr_delay_index);
if ((config.packet_stuffing == ST_soxr) &&
(config.soxr_delay_index > config.soxr_delay_threshold))
@@ -268,7 +268,7 @@ void usage(char *progname) {
printf(" The default is /tmp/shairport-sync-metadata.\n");
printf(" --get-coverart send cover art through the metadata pipe.\n");
#endif
printf(" -u, --use-stderr log messages through STDERR rather than syslog.\n");
printf(" -u, --use-stderr log messages through STDERR rather than the system log.\n");
printf("\n");
mdns_ls_backends();
printf("\n");
@@ -388,7 +388,8 @@ int parse_options(int argc, char **argv) {
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
config.tolerance = 1.0 * fTolerance / 44100;
config.audio_backend_silent_lead_in_time = -1.0; // flag to indicate it has not been set
config.audio_backend_silent_lead_in_time_auto =
1; // start outputting silence as soon as packets start arriving
config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if
// nothing else comes in first.
config.fixedLatencyOffset = 11025; // this sounds like it works properly.
@@ -399,12 +400,23 @@ int parse_options(int argc, char **argv) {
// automatically.
config.volume_range_hw_priority =
0; // if combining software and hardware volume control, give the software priority
// i.e. when reducing volume, reduce the sw first before reducing the software.
// this is because some hw mixers mute at the bottom of their range, and they don't always advertise
// this fact
config.resend_control_first_check_time = 0.10; // wait this many seconds before requesting the resending of a missing packet
config.resend_control_check_interval_time = 0.25; // wait this many seconds before again requesting the resending of a missing packet
config.resend_control_last_check_time = 0.10; // give up if the packet is still missing this close to when it's needed
// i.e. when reducing volume, reduce the sw first before reducing the software.
// this is because some hw mixers mute at the bottom of their range, and they don't always
// advertise this fact
config.resend_control_first_check_time =
0.10; // wait this many seconds before requesting the resending of a missing packet
config.resend_control_check_interval_time =
0.25; // wait this many seconds before again requesting the resending of a missing packet
config.resend_control_last_check_time =
0.10; // give up if the packet is still missing this close to when it's needed
config.missing_port_dacp_scan_interval_seconds =
2.0; // check at this interval if no DACP port number is known
config.minimum_free_buffer_headroom = 125; // leave approximately one second's worth of buffers
// free after calculating the effective latency.
// e.g. if we have 1024 buffers or 352 frames = 8.17 seconds and we have a nominal latency of 2.0
// seconds then we can add an offset of 5.17 seconds and still leave a second's worth of buffers
// for unexpected circumstances
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
@@ -414,8 +426,9 @@ int parse_options(int argc, char **argv) {
1; // number of seconds between DACP server scans when playing nothing
config.scan_max_bad_response_count =
5; // number of successive bad results to ignore before giving up
//config.scan_max_inactive_count =
// (365 * 24 * 60 * 60) / config.scan_interval_when_inactive; // number of scans to do before stopping if
// config.scan_max_inactive_count =
// (365 * 24 * 60 * 60) / config.scan_interval_when_inactive; // number of scans to do before
// stopping if
// not made active again (not used)
#endif
@@ -430,9 +443,9 @@ int parse_options(int argc, char **argv) {
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);
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);
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. */
if (config_read_file(&config_file_stuff, config_file_real_path)) {
free(config_file_real_path);
@@ -649,6 +662,20 @@ int parse_options(int argc, char **argv) {
dvalue);
}
/* Get the diagnostics output default. */
if (config_lookup_string(config.cfg, "diagnostics.log_output_to", &str)) {
if (strcasecmp(str, "syslog") == 0)
log_to_syslog();
else if (strcasecmp(str, "stdout") == 0) {
log_to_stdout();
} else if (strcasecmp(str, "stderr") == 0) {
log_to_stderr();
} else {
config.log_file_path = (char *)str;
config.log_fd = -1;
log_to_file();
}
}
/* Get the ignore_volume_control setting. */
if (config_lookup_string(config.cfg, "general.ignore_volume_control", &str)) {
if (strcasecmp(str, "no") == 0)
@@ -746,9 +773,9 @@ int parse_options(int argc, char **argv) {
* 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",
value);
die("Invalid volume range %d dB. It should be between 30 and 150 dB. Zero means use "
"the mixer's native range. The setting reamins at %d.",
value, config.volume_range_db);
else
config.volume_range_db = value;
}
@@ -767,44 +794,52 @@ int parse_options(int argc, char **argv) {
die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\"");
}
/* Get the resend control settings. */
if (config_lookup_float(config.cfg, "general.resend_control_first_check_time",
&dvalue)) {
if (config_lookup_float(config.cfg, "general.resend_control_first_check_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_first_check_time = dvalue;
else
warn("Invalid general resend_control_first_check_time setting \"%d\". It should "
warn("Invalid general resend_control_first_check_time setting \"%f\". It should "
"be "
"between 0.0 and 3.0, "
"inclusive. The setting remains at %f seconds.",
dvalue, config.resend_control_first_check_time);
}
if (config_lookup_float(config.cfg, "general.resend_control_check_interval_time",
&dvalue)) {
if (config_lookup_float(config.cfg, "general.resend_control_check_interval_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_check_interval_time = dvalue;
else
warn("Invalid general resend_control_check_interval_time setting \"%d\". It should "
warn("Invalid general resend_control_check_interval_time setting \"%f\". It should "
"be "
"between 0.0 and 3.0, "
"inclusive. The setting remains at %f seconds.",
dvalue, config.resend_control_check_interval_time);
}
if (config_lookup_float(config.cfg, "general.resend_control_last_check_time",
&dvalue)) {
if (config_lookup_float(config.cfg, "general.resend_control_last_check_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_last_check_time = dvalue;
else
warn("Invalid general resend_control_last_check_time setting \"%d\". It should "
warn("Invalid general resend_control_last_check_time setting \"%f\". It should "
"be "
"between 0.0 and 3.0, "
"inclusive. The setting remains at %f seconds.",
dvalue, config.resend_control_last_check_time);
}
if (config_lookup_float(config.cfg, "general.missing_port_dacp_scan_interval_seconds",
&dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 300.0))
config.missing_port_dacp_scan_interval_seconds = dvalue;
else
warn("Invalid general missing_port_dacp_scan_interval_seconds setting \"%f\". It should "
"be "
"between 0.0 and 300.0, "
"inclusive. The setting remains at %f seconds.",
dvalue, config.missing_port_dacp_scan_interval_seconds);
}
/* Get the default latency. Deprecated! */
if (config_lookup_int(config.cfg, "latencies.default", &value))
config.userSuppliedLatency = value;
@@ -963,7 +998,8 @@ int parse_options(int argc, char **argv) {
if (config_lookup_string(config.cfg, "dsp.convolution_ir_file", &str)) {
config.convolution_ir_file = strdup(str);
config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
config.convolver_valid =
convolver_init(config.convolution_ir_file, config.convolution_max_length);
}
if (config.convolution && config.convolution_ir_file == NULL) {
@@ -1159,7 +1195,7 @@ int parse_options(int argc, char **argv) {
poptFreeContext(optCon);
// here, we are finally finished reading the options
// here, we are finally finished reading the options
#ifdef CONFIG_LIBDAEMON
if ((daemonisewith) && (daemonisewithout))
@@ -1173,7 +1209,8 @@ int parse_options(int argc, char **argv) {
#else
/* Check if we are called with -d or --daemon or -j or justDaemoniseNoPIDFile options*/
if ((daemonisewith != 0) || (daemonisewithout != 0)) {
fprintf(stderr, "%s was built without libdaemon, so does not support daemonisation using the "
fprintf(stderr,
"%s was built without libdaemon, so does not support daemonisation using the "
"-d, --daemon, -j or --justDaemoniseNoPIDFile options\n",
config.appName);
exit(EXIT_FAILURE);
@@ -1197,8 +1234,6 @@ int parse_options(int argc, char **argv) {
char hostname[100];
gethostname(hostname, 100);
char *i0;
if (raw_service_name == NULL)
i0 = strdup("%H"); // this is the default it the Service Name wasn't specified
@@ -1269,34 +1304,32 @@ const char *pid_file_proc(void) {
}
#endif
void exit_function() {
// the following is to ensure that if libdaemon has been included
// that most of this code will be skipped when the parent process is exiting
// exec
if (emergency_exit == 0) {
// the following is to ensure that if libdaemon has been included
// that most of this code will be skipped when the parent process is exiting
// exec
#ifdef CONFIG_LIBDAEMON
if (this_is_the_daemon_process) { //this is the daemon that is exiting
if ((this_is_the_daemon_process) || (config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not actually deamonised at all
#endif
debug(1, "exit function called...");
/*
Actually, there is no terminate_mqtt() function.
#ifdef CONFIG_MQTT
debug(2, "exit function called...");
/*
Actually, there is no terminate_mqtt() function.
#ifdef CONFIG_MQTT
if (config.mqtt_enabled) {
terminate_mqtt();
}
#endif
*/
#endif
*/
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
/*
Actually, there is no stop_mpris_service() function.
#ifdef CONFIG_MPRIS_INTERFACE
/*
Actually, there is no stop_mpris_service() function.
#ifdef CONFIG_MPRIS_INTERFACE
stop_mpris_service();
#endif
*/
#endif
*/
#ifdef CONFIG_DBUS_INTERFACE
stop_dbus_service();
#endif
@@ -1333,7 +1366,6 @@ Actually, there is no stop_mpris_service() function.
pthread_join(soxr_time_check_thread, NULL);
#endif
if (conns)
free(conns); // make sure the connections have been deleted first
@@ -1349,12 +1381,14 @@ Actually, there is no stop_mpris_service() function.
free(config.regtype);
#ifdef CONFIG_LIBDAEMON
if (this_is_the_daemon_process) {
daemon_retval_send(0);
daemon_pid_file_remove();
daemon_signal_done();
if (config.computed_piddir)
free(config.computed_piddir);
}
}
#endif
if (config.cfg)
@@ -1362,6 +1396,23 @@ Actually, there is no stop_mpris_service() function.
if (config.appName)
free(config.appName);
// probably should be freeing malloc'ed memory here, including strdup-created strings...
#ifdef CONFIG_LIBDAEMON
if (this_is_the_daemon_process) { // this is the daemon that is exiting
debug(1,"libdaemon daemon exit");
} else {
if (config.daemonise)
debug(1,"libdaemon parent exit");
else
debug(1,"exit");
}
#else
debug(1,"exit");
#endif
} else {
debug(1,"emergency exit");
}
}
// for removing zombie script processes
@@ -1370,10 +1421,17 @@ Actually, there is no stop_mpris_service() function.
void handle_sigchld(__attribute__((unused)) int sig) {
int saved_errno = errno;
while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {}
while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {
}
errno = saved_errno;
}
void main_thread_cleanup_handler(__attribute__((unused)) void *arg) {
debug(2,"main thread cleanup handler called");
exit(EXIT_SUCCESS);
}
int main(int argc, char **argv) {
/* Check if we are called with -V or --version parameter */
if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) {
@@ -1390,11 +1448,12 @@ int main(int argc, char **argv) {
#ifdef CONFIG_LIBDAEMON
pid = getpid();
#endif
config.log_fd = -1;
conns = NULL; // no connections active
memset((void *)&main_thread_id, 0, sizeof(main_thread_id));
memset(&config, 0, sizeof(config)); // also clears all strings, BTW
fp_time_at_startup = get_absolute_time_in_fp();
fp_time_at_last_debug_message = fp_time_at_startup;
ns_time_at_startup = get_absolute_time_in_ns();
ns_time_at_last_debug_message = ns_time_at_startup;
// this is a bit weird, but necessary -- basename() may modify the argument passed in
char *basec = strdup(argv[0]);
char *bname = basename(basec);
@@ -1410,6 +1469,7 @@ int main(int argc, char **argv) {
setlogmask(LOG_UPTO(LOG_DEBUG));
openlog(NULL, 0, LOG_DAEMON);
#endif
emergency_exit = 0; // what to do or skip in the exit_function
atexit(exit_function);
// set defaults
@@ -1472,7 +1532,7 @@ int main(int argc, char **argv) {
// 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.audio_backend_buffer_desired_length = 0.15; // seconds
config.udp_port_base = 6001;
config.udp_port_range = 10;
config.output_format = SPS_FORMAT_S16_LE; // default
@@ -1527,10 +1587,17 @@ int main(int argc, char **argv) {
/* Kill daemon with SIGTERM */
/* Check if the new function daemon_pid_file_kill_wait() is available, if it is, use it. */
if ((ret = daemon_pid_file_kill_wait(SIGTERM, 5)) < 0)
daemon_log(LOG_WARNING, "Failed to kill daemon: %s", strerror(errno));
else {
// debug(1,"Successfully killed the shairport sync daemon.");
if ((ret = daemon_pid_file_kill_wait(SIGTERM, 5)) < 0) {
if (errno == ENOENT)
daemon_log(LOG_WARNING, "Failed to kill %s daemon: PID file not found.", config.appName);
else
daemon_log(LOG_WARNING, "Failed to kill %s daemon: \"%s\", errno %u.", config.appName, strerror(errno), errno);
} else {
// debug(1,"Successfully killed the %s daemon.", config.appName);
if (daemon_pid_file_remove() == 0)
debug(2, "killed the %s daemon.", config.appName);
else
daemon_log(LOG_WARNING, "killed the %s deamon, but cannot remove old PID file: \"%s\", errno %u.", config.appName, strerror(errno), errno);
}
return ret < 0 ? 1 : 0;
#else
@@ -1543,7 +1610,7 @@ int main(int argc, char **argv) {
#ifdef CONFIG_LIBDAEMON
/* If we are going to daemonise, check that the daemon is not running already.*/
if ((config.daemonise) && ((pid = daemon_pid_file_is_running()) >= 0)) {
daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid);
daemon_log(LOG_ERR, "The %s daemon is already running as PID %u", config.appName, pid);
return 1;
}
@@ -1578,16 +1645,16 @@ int main(int argc, char **argv) {
break;
case 1:
daemon_log(LOG_ERR,
"daemon failed to launch: could not close open file descriptors after forking.");
"the %s daemon failed to launch: could not close open file descriptors after forking.", config.appName);
break;
case 2:
daemon_log(LOG_ERR, "daemon failed to launch: could not create PID file.");
daemon_log(LOG_ERR, "the %s daemon failed to launch: could not create PID file.", config.appName);
break;
case 3:
daemon_log(LOG_ERR, "daemon failed to launch: could not create or access PID directory.");
daemon_log(LOG_ERR, "the %s daemon failed to launch: could not create or access PID directory.", config.appName);
break;
default:
daemon_log(LOG_ERR, "daemon failed to launch, error %i.", ret);
daemon_log(LOG_ERR, "the %s daemon failed to launch, error %i.", config.appName, ret);
}
return ret;
} else { /* pid == 0 means we are the daemon */
@@ -1635,6 +1702,9 @@ int main(int argc, char **argv) {
#endif
debug(1, "Started!");
// stop a pipe signal from killing the program
signal(SIGPIPE, SIG_IGN);
// install a zombie process reaper
// see: http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html
struct sigaction sa;
@@ -1672,7 +1742,7 @@ int main(int argc, char **argv) {
}
config.output->init(argc - audio_arg, argv + audio_arg);
// pthread_cleanup_push(main_cleanup_handler, NULL);
pthread_cleanup_push(main_thread_cleanup_handler, NULL);
// daemon_log(LOG_NOTICE, "startup");
@@ -1712,7 +1782,8 @@ int main(int argc, char **argv) {
/* Print out options */
debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off");
debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped "
debug(1,
"diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped "
"deliberately.",
config.diagnostic_drop_packet_fraction);
debug(1, "statistics_requester status is %d.", config.statistics_requested);
@@ -1736,8 +1807,8 @@ int main(int argc, char **argv) {
debug(1, "mdns backend \"%s\".", config.mdns_name);
debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency);
debug(1, "interpolation setting is \"%s\".",
config.packet_stuffing == ST_basic ? "basic" : config.packet_stuffing == ST_soxr ? "soxr"
: "auto");
config.packet_stuffing == ST_basic ? "basic"
: config.packet_stuffing == ST_soxr ? "soxr" : "auto");
debug(1, "interpolation soxr_delay_threshold is %d.", config.soxr_delay_threshold);
debug(1, "resync time is %f seconds.", config.resyncthreshold);
debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption);
@@ -1751,7 +1822,8 @@ int main(int argc, char **argv) {
debug(1, "volume_max_db is not set");
debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.",
config.volume_range_db);
debug(1, "volume_range_combined_hardware_priority (1 means hardware mixer attenuation is used "
debug(1,
"volume_range_combined_hardware_priority (1 means hardware mixer attenuation is used "
"first) is %d.",
config.volume_range_hw_priority);
debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).",
@@ -1771,7 +1843,10 @@ int main(int argc, char **argv) {
debug(1, "audio_backend_buffer_interpolation_threshold_in_seconds is %f seconds.",
config.audio_backend_buffer_interpolation_threshold_in_seconds);
debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset);
debug(1, "audio backend silence lead-in time is %f seconds. A value -1.0 means use the default.",
if (config.audio_backend_silent_lead_in_time_auto == 1)
debug(1, "audio backend silence lead-in time is \"auto\".");
else
debug(1, "audio backend silence lead-in time is %f seconds.",
config.audio_backend_silent_lead_in_time);
debug(1, "zeroconf regtype is \"%s\".", config.regtype);
debug(1, "decoders_supported field is %d.", config.decoders_supported);
@@ -1884,5 +1959,6 @@ int main(int argc, char **argv) {
activity_monitor_start();
rtsp_listen_loop();
pthread_cleanup_pop(1);
return 0;
}
+4 -3
View File
@@ -1466,7 +1466,7 @@ int close_pipe(int s) {
// main loop to receive, process and send out MDNS replies
// also handles MDNS service announces
void* main_loop(struct mdnsd *svr) {
void *main_loop(struct mdnsd *svr) {
fd_set sockfd_set;
int max_fd = svr->sockfd;
char notify_buf[2]; // buffer for reading of notify_pipe
@@ -1724,7 +1724,7 @@ struct mdnsd *mdnsd_start() {
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&tid, &attr, (void * (*)(void *))&main_loop, (void *)server) != 0) {
if (pthread_create(&tid, &attr, (void *(*)(void *)) & main_loop, (void *)server) != 0) {
pthread_mutex_destroy(&server->data_lock);
free(server);
return NULL;
@@ -1737,7 +1737,8 @@ void mdnsd_stop(struct mdnsd *s) {
assert(s != NULL);
struct timeval tv = {
.tv_sec = 0, .tv_usec = 500 * 1000,
.tv_sec = 0,
.tv_usec = 500 * 1000,
};
s->stop_flag = 1;