// will change dynamically, so keep watching it. Implemented in ALSA only.
// returns a negative error code if there's a problem
int (*delay)(long *the_delay); // snd_pcm_sframes_t is a signed long
- int (*rate_info)(uint64_t *elapsed_time, uint64_t *frames_played); // use this to get the true rate of the DAC
++ int (*rate_info)(uint64_t *elapsed_time,
++ uint64_t *frames_played); // use this to get the true rate of the DAC
// may be NULL, in which case soft volume is applied
void (*volume)(double vol);
#include "audio.h"
#include "common.h"
#include <alsa/asoundlib.h>
++#include <inttypes.h>
#include <math.h>
#include <memory.h>
#include <pthread.h>
snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index);
snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl);
- if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0)
- die("Failed to open mixer");
- debug(3, "Mixer device name is \"%s\".", alsa_mix_dev);
- if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0)
- die("Failed to attach mixer");
- if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0)
- die("Failed to register mixer element");
-
- ret = snd_mixer_load(alsa_mix_handle);
- if (ret < 0)
- die("Failed to load mixer element");
- debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl);
- alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
- if (!alsa_mix_elem)
- die("Failed to find mixer element");
- return 1;
- } else {
- return 0;
+ if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0) {
- debug(1,"Failed to open mixer");
++ debug(1, "Failed to open mixer");
+ response = -1;
+ } else {
+ debug(3, "Mixer device name is \"%s\".", alsa_mix_dev);
+ if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) {
- debug(1,"Failed to attach mixer");
++ debug(1, "Failed to attach mixer");
+ response = -2;
+ } else {
+ if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) {
- debug(1,"Failed to register mixer element");
++ debug(1, "Failed to register mixer element");
+ response = -3;
+ } else {
+ ret = snd_mixer_load(alsa_mix_handle);
+ if (ret < 0) {
- debug(1,"Failed to load mixer element");
++ debug(1, "Failed to load mixer element");
+ response = -4;
+ } else {
+ debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl);
+ alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
+ if (!alsa_mix_elem) {
- debug(1,"Failed to find mixer element");
++ debug(1, "Failed to find mixer element");
+ response = -5;
+ } else {
+ response = 1; // we found a hardware mixer and successfully opened it
+ }
+ }
+ }
+ }
+ }
- }
+ }
+ return response;
}
void close_mixer() {
else if (strcasecmp(str, "yes") == 0)
config.no_sync = 1;
else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\"");
- warn("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\". It is set to \"no\".");
++ warn("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or "
++ "\"no\". It is set to \"no\".");
+ config.no_sync = 0;
}
}
else if (strcasecmp(str, "yes") == 0)
config.alsa_use_hardware_mute = 1;
else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or "
- "\"no\"");
+ warn("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or "
- "\"no\". It is set to \"no\".");
++ "\"no\". It is set to \"no\".");
+ config.alsa_use_hardware_mute = 0;
}
}
else if (strcasecmp(str, "yes") == 0)
config.alsa_use_hardware_mute = 1;
else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or "
- "\"no\"");
+ warn("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or "
- "\"no\". It is set to \"no\".");
++ "\"no\". It is set to \"no\".");
+ config.alsa_use_hardware_mute = 0;
}
}
else if (strcasecmp(str, "S8") == 0)
config.output_format = SPS_FORMAT_S8;
else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", "
- "\"S24_3LE\", \"S24_3BE\" or "
- "\"S32\"",
- str);
+ warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", "
- "\"S24_3LE\", \"S24_3BE\" or "
- "\"S32\". It is set to \"S16\".",
- str);
++ "\"S24_3LE\", \"S24_3BE\" or "
++ "\"S32\". It is set to \"S16\".",
++ str);
+ config.output_format = SPS_FORMAT_S16;
}
}
config.output_rate = value;
break;
default:
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800", value);
- warn("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800. It is set to 44,100", value);
++ warn("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800. It is "
++ "set to 44,100",
++ value);
+ config.output_rate = 44100;
}
}
else if (strcasecmp(str, "yes") == 0)
config.no_mmap = 0;
else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\"");
- warn("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\". It is set to \"yes\".");
++ warn("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\". "
++ "It is set to \"yes\".");
+ config.no_mmap = 0;
}
}
/* Get the optional period size value */
set_period_size_request = 1;
debug(1, "Value read for period size is %d.", value);
if (value < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid alsa period size setting \"%d\". It "
- "must be greater than 0.",
- value);
+ warn("Invalid alsa period size setting \"%d\". It "
- "must be greater than 0. No setting is made.",
- value);
++ "must be greater than 0. No setting is made.",
++ value);
+ set_period_size_request = 0;
} else {
period_size_requested = value;
}
set_buffer_size_request = 1;
debug(1, "Value read for buffer size is %d.", value);
if (value < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid alsa buffer size setting \"%d\". It "
- "must be greater than 0.",
- value);
+ warn("Invalid alsa buffer size setting \"%d\". It "
- "must be greater than 0. No setting is made.",
- value);
++ "must be greater than 0. No setting is made.",
++ value);
+ set_buffer_size_request = 0;
} else {
buffer_size_requested = value;
}
if (alsa_mix_dev == NULL)
alsa_mix_dev = alsa_out_dev;
- // Open mixer
-
- open_mixer();
-
- if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
- 0)
- debug(1, "Can't read mixer's [linear] min and max volumes.");
- else {
- if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) ==
- 0) {
-
- audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
- audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
- // For instance, the Raspberry Pi does this
- debug(1, "Lowest dB value is a mute");
- mixer_volume_setting_gives_mute = 1;
- alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always
- // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
- // debug(1, "Try minimum volume + 1 as lowest true attenuation value");
- if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
- &alsa_mix_mindb) != 0)
- debug(1, "Can't get dB value corresponding to a minimum volume + 1.");
- }
- debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
- (1.0 * alsa_mix_maxdb) / 100.0);
- } else {
- // use the linear scale and do the db conversion ourselves
- debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have "
- "a dB volume scale.",
- alsa_mix_ctrl);
-
- if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Cannot open control \"%s\"", alsa_mix_dev);
- }
- if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Cannot allocate memory for control \"%s\"", alsa_mix_dev);
- }
- snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
- snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);
+ // Now, start trying to initialise the alsa device with the settings obtained
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1);
- if (open_mixer()==1) {
++ if (open_mixer() == 1) {
+ if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
+ 0)
+ debug(1, "Can't read mixer's [linear] min and max volumes.");
+ else {
- if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) ==
- 0) {
++ if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
++ &alsa_mix_maxdb) == 0) {
- if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) {
- debug(1, "Volume control \"%s\" has dB volume from %f to %f.", alsa_mix_ctrl,
- (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0);
- has_softvol = 1;
audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
+ if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
+ // For instance, the Raspberry Pi does this
+ debug(1, "Lowest dB value is a mute");
+ mixer_volume_setting_gives_mute = 1;
+ alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always
++ // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
+ // debug(1, "Try minimum volume + 1 as lowest true attenuation value");
+ if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
+ &alsa_mix_mindb) != 0)
+ debug(1, "Can't get dB value corresponding to a minimum volume + 1.");
+ }
+ debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
+ (1.0 * alsa_mix_maxdb) / 100.0);
} else {
- debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl);
+ // use the linear scale and do the db conversion ourselves
+ warn("The hardware mixer specified -- \"%s\" -- does not have "
- "a dB volume scale.",
- alsa_mix_ctrl);
++ "a dB volume scale.",
++ alsa_mix_ctrl);
+
+ if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
+ warn("Cannot open control \"%s\"", alsa_mix_dev);
+ response = -1;
+ }
+ if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
- debug(1,"Cannot allocate memory for control \"%s\"", alsa_mix_dev);
++ debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev);
+ elem_id = NULL;
+ response = -2;
+ } else {
+ snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
+ snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);
+
+ if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) {
+ debug(1, "Volume control \"%s\" has dB volume from %f to %f.", alsa_mix_ctrl,
+ (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0);
+ has_softvol = 1;
- audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
++ audio_alsa.volume =
++ &volume; // insert the volume function now we know it can do dB stuff
+ audio_alsa.parameters = ¶meters; // likewise the parameters stuff
+ } else {
+ debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl);
+ }
+ }
+ /*
+ debug(1, "Min and max volumes are %d and
+ %d.",alsa_mix_minv,alsa_mix_maxv);
+ alsa_mix_maxdb = 0;
+ if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
+ alsa_mix_mindb =
+ -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
+ else if (alsa_mix_maxv!=0)
+ alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
+ audio_alsa.volume = &linear_volume; // insert the linear volume function
+ audio_alsa.parameters = ¶meters; // likewise the parameters stuff
+ debug(1,"Max and min dB calculated are %d and
+ %d.",alsa_mix_maxdb,alsa_mix_mindb);
+ */
}
-
- /*
- debug(1, "Min and max volumes are %d and
- %d.",alsa_mix_minv,alsa_mix_maxv);
- alsa_mix_maxdb = 0;
- if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
- alsa_mix_mindb =
- -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
- else if (alsa_mix_maxv!=0)
- alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
- audio_alsa.volume = &linear_volume; // insert the linear volume function
- audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- debug(1,"Max and min dB calculated are %d and
- %d.",alsa_mix_maxdb,alsa_mix_mindb);
- */
}
+ if (((config.alsa_use_hardware_mute == 1) &&
+ (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
+ mixer_volume_setting_gives_mute) {
+ audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff
+ // debug(1, "Has mixer and mute ability we will use.");
+ } else {
+ // debug(1, "Has mixer but not using hardware mute.");
+ }
+ close_mixer();
}
- if (((config.alsa_use_hardware_mute == 1) &&
- (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
- mixer_volume_setting_gives_mute) {
- audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff
- // debug(1, "Has mixer and mute ability we will use.");
- } else {
- // debug(1, "Has mixer but not using hardware mute.");
- }
- close_mixer();
+ debug_mutex_unlock(&alsa_mutex, 3);
+ pthread_cleanup_pop(0); // release the mutex
} else {
// debug(1, "Has no mixer and thus no hardware mute.");
}
ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- ;
- die("audio_alsa: Broken configuration for device \"%s\": no configurations "
- "available",
- alsa_out_dev);
+ warn("audio_alsa: Broken configuration for device \"%s\": no configurations "
- "available",
- alsa_out_dev);
++ "available",
++ alsa_out_dev);
+ return -11;
}
if ((config.no_mmap == 0) &&
ret = snd_pcm_hw_params_set_access(alsa_handle, alsa_params, access);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev,
- snd_strerror(ret));
+ warn("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev,
- snd_strerror(ret));
++ snd_strerror(ret));
+ return -12;
}
snd_pcm_format_t sf;
switch (sample_format) {
sf = SND_PCM_FORMAT_S32;
break;
default:
- debug_mutex_unlock(&alsa_mutex, 3);
sf = SND_PCM_FORMAT_S16; // this is just to quieten a compiler warning
- die("Unsupported output format at audio_alsa.c");
- debug(1,"Unsupported output format at audio_alsa.c");
++ debug(1, "Unsupported output format at audio_alsa.c");
+ return -1;
}
ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format,
- alsa_out_dev, snd_strerror(ret));
+ warn("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format,
- alsa_out_dev, snd_strerror(ret));
++ alsa_out_dev, snd_strerror(ret));
+ return -2;
}
ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev,
- snd_strerror(ret));
+ warn("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev,
- snd_strerror(ret));
++ snd_strerror(ret));
+ return -3;
}
ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir);
if (ret < 0) {
- die("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate,
- snd_strerror(ret));
+ warn("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate,
- snd_strerror(ret));
++ snd_strerror(ret));
+ return -4;
}
if (set_period_size_request != 0) {
ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested,
&dir);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: cannot set period size of %lu: %s", period_size_requested,
- snd_strerror(ret));
+ warn("audio_alsa: cannot set period size of %lu: %s", period_size_requested,
- snd_strerror(ret));
++ snd_strerror(ret));
+ return -5;
+ } else {
snd_pcm_uframes_t actual_period_size;
snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir);
if (actual_period_size != period_size_requested)
debug(1, "Attempting to set the buffer size to %lu", buffer_size_requested);
ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested,
- snd_strerror(ret));
+ warn("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested,
- snd_strerror(ret));
++ snd_strerror(ret));
+ return -6;
+ } else {
+ snd_pcm_uframes_t actual_buffer_size;
+ snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size);
+ if (actual_buffer_size != buffer_size_requested)
+ inform("Actual period size set to a different value than requested. Requested: %lu, actual "
+ "setting: %lu",
+ buffer_size_requested, actual_buffer_size);
}
- snd_pcm_uframes_t actual_buffer_size;
- snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size);
- if (actual_buffer_size != buffer_size_requested)
- inform("Actual period size set to a different value than requested. Requested: %lu, actual "
- "setting: %lu",
- buffer_size_requested, actual_buffer_size);
}
ret = snd_pcm_hw_params(alsa_handle, alsa_params);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev,
- snd_strerror(ret));
+ warn("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev,
- snd_strerror(ret));
++ snd_strerror(ret));
+ return -7;
}
if (my_sample_rate != desired_sample_rate) {
ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev,
- snd_strerror(ret));
+ warn("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev,
- snd_strerror(ret));
++ snd_strerror(ret));
+ return -9;
}
if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) {
sample_format = SPS_FORMAT_S16; // default
else
sample_format = i_sample_format;
-
++
+ frame_index = 0;
+ measurement_data_is_valid = 0;
}
int delay(long *the_delay) {
snd_strerror(reply), *the_delay);
snd_pcm_recover(alsa_handle, reply, 1);
}
- } else if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) {
- *the_delay = 0;
- reply = 0; // no error
} else {
- if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) {
- frame_index = 0; //we'll be starting over...
++ frame_index = 0; // we'll be starting over...
+ measurement_data_is_valid = 0;
+
+ if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) {
*the_delay = 0;
reply = 0; // no error
} else {
}
}
- static void play(void *buf, int samples) {
+ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) {
+ if (measurement_data_is_valid) {
+ *elapsed_time = measurement_time - measurement_start_time;
+ *frames_played = frames_played_at_measurement_time - frames_played_at_measurement_start_time;
+ } else {
+ *elapsed_time = 0;
- *frames_played = 0;
++ *frames_played = 0;
+ }
+ return 0;
+ }
+
+ static int play(void *buf, int samples) {
// debug(3,"audio_alsa play called.");
int ret = 0;
if (alsa_handle == NULL) {
if (audio_alsa.mute)
do_mute(0);
}
-
++
debug_mutex_unlock(&alsa_mutex, 3);
+ pthread_cleanup_pop(0); // release the mutex
}
if (ret == 0) {
- debug_mutex_lock(&alsa_mutex, 10000, 1);
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1);
// snd_pcm_sframes_t current_delay = 0;
- int err;
+ int err, err2;
if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) {
if ((err = snd_pcm_prepare(alsa_handle))) {
snd_pcm_recover(alsa_handle, err, 1);
debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err));
}
- }
- if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
- (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) {
+ frame_index = 0; // we'll be starting over
+ measurement_data_is_valid = 0;
+ } else if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
- (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) {
++ (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) {
if (buf == NULL)
debug(1, "NULL buffer passed to pcm_writei -- skipping it");
if (samples == 0)
snd_strerror(err));
snd_pcm_recover(alsa_handle, err, 1);
}
- if (frame_index==0) {
++ if (frame_index == 0) {
+ frames_sent_for_playing = samples;
+ } else {
+ frames_sent_for_playing += samples;
+ }
- const uint64_t start_measurement_from_this_frame = (2*44100)/352; // two seconds of frames…
++ const uint64_t start_measurement_from_this_frame =
++ (2 * 44100) / 352; // two seconds of frames…
+ frame_index++;
+ if ((frame_index == start_measurement_from_this_frame) || (frame_index % 32 == 0)) {
+ long fl = 0;
- err2 = snd_pcm_delay(alsa_handle,&fl);
++ err2 = snd_pcm_delay(alsa_handle, &fl);
+ if (err2 != 0) {
+ frame_index = 0;
+ measurement_data_is_valid = 0;
+ debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", err2,
+ snd_strerror(err2), fl);
+ snd_pcm_recover(alsa_handle, err2, 1);
+ }
+ uint64_t tf = get_absolute_time_in_fp();
+ frames_played_at_measurement_time = frames_sent_for_playing - fl;
+ if (frame_index == start_measurement_from_this_frame) {
+ frames_played_at_measurement_start_time = frames_played_at_measurement_time;
+ measurement_start_time = tf;
+ } else {
+ frames_played_at_measurement_time = frames_played_at_measurement_time;
+ measurement_time = tf;
+ measurement_data_is_valid = 1;
+ }
+ }
}
} else {
debug(1, "Error -- ALSA device in incorrect state (%d) for play.",
static void flush(void) {
// debug(2,"audio_alsa flush called.");
- debug_mutex_lock(&alsa_mutex, 10000, 1);
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1);
int derr;
do_mute(1);
-
++
if (alsa_handle) {
if ((derr = snd_pcm_drop(alsa_handle)))
void do_volume(double vol) { // caller is assumed to have the alsa_mutex when using this function
debug(3, "Setting volume db to %f.", vol);
set_volume = vol;
- if (volume_set_request && open_mixer()) {
- if (volume_set_request && (open_mixer()==1)) {
++ if (volume_set_request && (open_mixer() == 1)) {
if (has_softvol) {
if (ctl && elem_id) {
snd_ctl_elem_value_t *value;
if (config.alsa_use_hardware_mute == 1) {
if (mute_request_pending == 0)
local_mute_state_requested = mute_state_requested;
- if (open_mixer()) {
- if (open_mixer()==1) {
++ if (open_mixer() == 1) {
if (local_mute_state_requested) {
// debug(1,"Playback Switch mute actually done");
if (snd_mixer_selem_has_playback_switch(alsa_mix_elem))
static void start(__attribute__((unused)) int sample_rate,
__attribute__((unused)) int sample_format) {}
- static void play(void *buf, int samples) { ao_play(dev, buf, samples * 4); }
-static int play(void *buf, int samples) {
- return ao_play(dev, buf, samples * 4);
-}
++static int play(void *buf, int samples) { return ao_play(dev, buf, samples * 4); }
static void stop(void) {}
debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate);
}
- static void play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {}
-static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { return 0; }
++static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {
++ return 0;
++}
static void stop(void) { debug(1, "dummy audio stopped\n"); }
double vol2attn(double vol, long max_db, long min_db) {
- // We use a little coordinate geometry to build a transfer function from the volume passed in to the
- // device's dynamic range.
- // (See the diagram in the documents folder.)
- // The x axis is the "volume in" which will be from -30 to 0. The y axis will be the "volume out"
- // which will be from the bottom of the range to the top.
- // We build the transfer function from one or more lines. We characterise each line with two
- // numbers:
- // the first is where on x the line starts when y=0 (x can be from 0 to -30); the second is where on
- // y the line stops when when x is -30.
- // thus, if the line was characterised as {0,-30}, it would be an identity transfer.
- // Assuming, for example, a dynamic range of lv=-60 to hv=0
- // Typically we'll use three lines -- a three order transfer function
- // First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
- // We use a little coordinate geometry to build a transfer function from the volume passed in to
- // the device's dynamic range. (See the diagram in the documents folder.) The x axis is the
- // "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from
- // the bottom of the range to the top. We build the transfer function from one or more lines. We
- // characterise each line with two numbers: the first is where on x the line starts when y=0 (x
- // can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the
- // line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a
- // dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer
- // function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
- // Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30
- // Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30
++// We use a little coordinate geometry to build a transfer function from the volume passed in to
++// the device's dynamic range. (See the diagram in the documents folder.) The x axis is the
++// "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from
++// the bottom of the range to the top. We build the transfer function from one or more lines. We
++// characterise each line with two numbers: the first is where on x the line starts when y=0 (x
++// can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the
++// line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a
++// dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer
++// function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
+// Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30
+// Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30
#define order 3
return r;
}
-void pthread_cleanup_debug_mutex_unlock(void *arg) {
- pthread_mutex_unlock((pthread_mutex_t* )arg);
-}
++void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); }
+
char *get_version_string() {
char *version_string = malloc(200);
if (version_string) {
#define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, __FILE__, __LINE__, d)
-
+ void pthread_cleanup_debug_mutex_unlock(void *arg);
+
-#define pthread_cleanup_debug_mutex_lock(mu, t, d) if (_debug_mutex_lock(mu, t, __FILE__, __LINE__, d) == 0) pthread_cleanup_push(pthread_cleanup_debug_mutex_unlock,(void *)mu)
++#define pthread_cleanup_debug_mutex_lock(mu, t, d) \
++ if (_debug_mutex_lock(mu, t, __FILE__, __LINE__, d) == 0) \
++ pthread_cleanup_push(pthread_cleanup_debug_mutex_unlock, (void *)mu)
+
char *get_version_string(); // mallocs a string space -- remember to free it afterwards
void sps_nanosleep(const time_t sec,
}
if (outsize > toutsize) {
-- debug(2,
-- "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- "
-- "truncated, but buffer overflow possible! Encrypted = %d.",
++ debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- "
++ "truncated, but buffer overflow possible! Encrypted = %d.",
outsize, toutsize, conn->stream.encrypted);
reply = -1; // output packet is the wrong size
}
*destlen = outsize / conn->input_bytes_per_frame;
if ((outsize % conn->input_bytes_per_frame) != 0)
-- debug(1,
-- "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) "
-- "and the audio frame size (%d).",
++ debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) "
++ "and the audio frame size (%d).",
*destlen, outsize, conn->input_bytes_per_frame);
return reply;
}
free(conn->audio_buffer[i].data);
}
- void player_thread_lock_cleanup(void *arg) {
- rtsp_conn_info *conn = (rtsp_conn_info *)arg;
- debug(3, "Cleaning up player_thread_lock.");
- pthread_rwlock_unlock(&conn->player_thread_lock);
- }
-
void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp, uint8_t *data,
int len, rtsp_conn_info *conn) {
- if (pthread_rwlock_tryrdlock(&conn->player_thread_lock) == 0) {
- pthread_cleanup_push(player_thread_lock_cleanup, (void *)conn);
- if (conn->player_thread != NULL) {
+ // all timestamps are done at the output rate
+ // the "actual_timestamp" is the one that comes in the packet, and is carried over for
+ // debugging
+ // and checking only.
- // all timestamps are done at the output rate
- // the "actual_timestamp" is the one that comes in the packet, and is carried over for
- // debugging
- // and checking only.
+ int64_t ltimestamp = timestamp * conn->output_sample_ratio;
- int64_t ltimestamp = timestamp * conn->output_sample_ratio;
-
- // ignore a request to flush that has been made before the first packet...
- if (conn->packet_count == 0) {
- debug_mutex_lock(&conn->flush_mutex, 1000, 1);
- conn->flush_requested = 0;
- conn->flush_rtp_timestamp = 0;
- debug_mutex_unlock(&conn->flush_mutex, 3);
- }
+ // ignore a request to flush that has been made before the first packet...
+ if (conn->packet_count == 0) {
+ debug_mutex_lock(&conn->flush_mutex, 1000, 1);
+ conn->flush_requested = 0;
+ conn->flush_rtp_timestamp = 0;
+ debug_mutex_unlock(&conn->flush_mutex, 3);
+ }
- debug_mutex_lock(&conn->ab_mutex, 30000, 1);
- conn->packet_count++;
- conn->time_of_last_audio_packet = get_absolute_time_in_fp();
- if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
+ debug_mutex_lock(&conn->ab_mutex, 30000, 1);
+ conn->packet_count++;
+ conn->time_of_last_audio_packet = get_absolute_time_in_fp();
+ if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
+
+ // if (flush_rtp_timestamp != 0)
+ // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp);
+
+ if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) {
+ debug(3,
+ "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to "
+ "timestamp: %lld.",
+ seqno, ltimestamp, conn->flush_rtp_timestamp);
+ } else {
+ if ((conn->flush_rtp_timestamp != 0x0) &&
+ (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time
+ conn->flush_rtp_timestamp = 0x0;
- // if (flush_rtp_timestamp != 0)
- // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp);
+ abuf_t *abuf = 0;
- if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) {
- debug(
- 3,
- "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to "
- "timestamp: %lld.",
- seqno, ltimestamp, conn->flush_rtp_timestamp);
- } else {
- if ((conn->flush_rtp_timestamp != 0x0) &&
- (ltimestamp >
- conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time
- conn->flush_rtp_timestamp = 0x0;
-
- abuf_t *abuf = 0;
-
- if (!conn->ab_synced) {
- debug(3, "syncing to seqno %u.", seqno);
- conn->ab_write = seqno;
- conn->ab_read = seqno;
- conn->ab_synced = 1;
- }
+ if (!conn->ab_synced) {
+ debug(3, "syncing to seqno %u.", seqno);
+ conn->ab_write = seqno;
+ conn->ab_read = seqno;
+ conn->ab_synced = 1;
+ }
- // here, we should check for missing frames
- int resend_interval = (((250 * 44100) / 352) / 1000); // approximately 250 ms intervals
- const int number_of_resend_attempts = 8;
- int latency_based_resend_interval =
- (conn->latency) / (number_of_resend_attempts * conn->max_frames_per_packet);
- if (latency_based_resend_interval > resend_interval)
- resend_interval = latency_based_resend_interval;
-
- if (conn->resend_interval != resend_interval) {
- debug(2, "Resend interval for latency of %" PRId64 " frames is %d frames.",
- conn->latency, resend_interval);
- conn->resend_interval = resend_interval;
- }
+ // here, we should check for missing frames
+ int resend_interval = (((250 * 44100) / 352) / 1000); // approximately 250 ms intervals
+ const int number_of_resend_attempts = 8;
+ int latency_based_resend_interval =
+ (conn->latency) / (number_of_resend_attempts * conn->max_frames_per_packet);
+ if (latency_based_resend_interval > resend_interval)
+ resend_interval = latency_based_resend_interval;
+
+ if (conn->resend_interval != resend_interval) {
+ debug(2, "Resend interval for latency of %" PRId64 " frames is %d frames.", conn->latency,
+ resend_interval);
+ conn->resend_interval = resend_interval;
+ }
- if (conn->ab_write == seqno) { // expected packet
- abuf = conn->audio_buffer + BUFIDX(seqno);
- conn->ab_write = SUCCESSOR(seqno);
- } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected
- // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8)
- // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and
- // seqno:
- // %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno);
- int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read);
- if (gap <= 0)
- debug(1, "Unexpected gap size: %d.", gap);
- int i;
- for (i = 0; i < gap; i++) {
- abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
- abuf->ready = 0; // to be sure, to be sure
- abuf->resend_level = 0;
- abuf->timestamp = 0;
- abuf->given_timestamp = 0;
- abuf->sequence_number = 0;
- }
- // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write);
- abuf = conn->audio_buffer + BUFIDX(seqno);
- // rtp_request_resend(ab_write, gap);
- // resend_requests++;
- conn->ab_write = SUCCESSOR(seqno);
- } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // late but not yet played
- conn->late_packets++;
- abuf = conn->audio_buffer + BUFIDX(seqno);
- /*
- if (abuf->ready)
- debug(1,"Late apparently duplicate packet received that is %d packets
- late.",seq_diff(seqno, conn->ab_write, conn->ab_read));
- else
- debug(1,"Late packet received that is %d packets late.",seq_diff(seqno,
- conn->ab_write, conn->ab_read));
- */
- } else { // too late.
+ if (conn->ab_write == seqno) { // expected packet
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ conn->ab_write = SUCCESSOR(seqno);
+ } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected
+ // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8)
+ // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and
+ // seqno:
+ // %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno);
+ int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read);
+ if (gap <= 0)
+ debug(1, "Unexpected gap size: %d.", gap);
+ int i;
+ for (i = 0; i < gap; i++) {
+ abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
+ abuf->ready = 0; // to be sure, to be sure
+ abuf->resend_level = 0;
+ abuf->timestamp = 0;
+ abuf->given_timestamp = 0;
+ abuf->sequence_number = 0;
+ }
+ // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write);
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ // rtp_request_resend(ab_write, gap);
+ // resend_requests++;
+ conn->ab_write = SUCCESSOR(seqno);
+ } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // late but not yet played
+ conn->late_packets++;
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ /*
+ if (abuf->ready)
+ debug(1,"Late apparently duplicate packet received that is %d packets
+ late.",seq_diff(seqno, conn->ab_write, conn->ab_read));
+ else
+ debug(1,"Late packet received that is %d packets late.",seq_diff(seqno,
+ conn->ab_write, conn->ab_read));
+ */
+ } else { // too late.
- // debug(1,"Too late packet received that is %d packets late.",seq_diff(seqno,
- // conn->ab_write, conn->ab_read));
- conn->too_late_packets++;
- }
- // pthread_mutex_unlock(&ab_mutex);
-
- if (abuf) {
- int datalen = conn->max_frames_per_packet;
- if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) {
- abuf->ready = 1;
- abuf->length = datalen;
- abuf->timestamp = ltimestamp;
- abuf->given_timestamp = actual_timestamp;
- abuf->sequence_number = seqno;
- } else {
- debug(1, "Bad audio packet detected and discarded.");
- abuf->ready = 0;
- abuf->resend_level = 0;
- abuf->timestamp = 0;
- abuf->given_timestamp = 0;
- abuf->sequence_number = 0;
- }
- }
+ // debug(1,"Too late packet received that is %d packets late.",seq_diff(seqno,
+ // conn->ab_write, conn->ab_read));
+ conn->too_late_packets++;
+ }
+ // pthread_mutex_unlock(&ab_mutex);
+
+ if (abuf) {
+ int datalen = conn->max_frames_per_packet;
+ if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) {
+ abuf->ready = 1;
+ abuf->length = datalen;
+ abuf->timestamp = ltimestamp;
+ abuf->given_timestamp = actual_timestamp;
+ abuf->sequence_number = seqno;
+ } else {
+ debug(1, "Bad audio packet detected and discarded.");
+ abuf->ready = 0;
+ abuf->resend_level = 0;
+ abuf->timestamp = 0;
+ abuf->given_timestamp = 0;
+ abuf->sequence_number = 0;
+ }
+ }
- // pthread_mutex_lock(&ab_mutex);
- int rc = pthread_cond_signal(&conn->flowcontrol);
- if (rc)
- debug(1, "Error signalling flowcontrol.");
-
- // if it's at the expected time, do a look back for missing packets
- // but release the ab_mutex when doing a resend
- if (!conn->ab_buffering) {
- int j;
- for (j = 1; j <= number_of_resend_attempts; j++) {
- // check j times, after a short period of has elapsed, assuming 352 frames per packet
- // the higher the step_exponent, the less it will try. 1 means it will try very
- // hard. 2.0 seems good.
- float step_exponent = 2.0;
- int back_step = (int)(resend_interval * pow(j, step_exponent));
- int k;
- for (k = -1; k <= 1; k++) {
- if ((back_step + k) <
- seq_diff(conn->ab_read, conn->ab_write,
- conn->ab_read)) { // if it's within the range of frames in use...
- int item_to_check = (conn->ab_write - (back_step + k)) & 0xffff;
- seq_t next = item_to_check;
- abuf_t *check_buf = conn->audio_buffer + BUFIDX(next);
- if ((!check_buf->ready) &&
- (check_buf->resend_level <
- j)) { // prevent multiple requests from the same level of lookback
- check_buf->resend_level = j;
- if (config.disable_resend_requests == 0) {
- if (((int)(resend_interval * pow(j + 1, step_exponent)) + k) >=
- seq_diff(conn->ab_read, conn->ab_write, conn->ab_read))
- debug(3,
- "Last-ditch (#%d) resend request for packet %u in range %u to %u. "
- "Looking back %d packets.",
- j, next, conn->ab_read, conn->ab_write, back_step + k);
- debug_mutex_unlock(&conn->ab_mutex, 3);
- rtp_request_resend(next, 1, conn);
- conn->resend_requests++;
- debug_mutex_lock(&conn->ab_mutex, 20000, 1);
- }
- }
+ // pthread_mutex_lock(&ab_mutex);
+ int rc = pthread_cond_signal(&conn->flowcontrol);
+ if (rc)
+ debug(1, "Error signalling flowcontrol.");
+
+ // if it's at the expected time, do a look back for missing packets
+ // but release the ab_mutex when doing a resend
+ if (!conn->ab_buffering) {
+ int j;
+ for (j = 1; j <= number_of_resend_attempts; j++) {
+ // check j times, after a short period of has elapsed, assuming 352 frames per packet
+ // the higher the step_exponent, the less it will try. 1 means it will try very
+ // hard. 2.0 seems good.
+ float step_exponent = 2.0;
+ int back_step = (int)(resend_interval * pow(j, step_exponent));
+ int k;
+ for (k = -1; k <= 1; k++) {
+ if ((back_step + k) <
+ seq_diff(conn->ab_read, conn->ab_write,
+ conn->ab_read)) { // if it's within the range of frames in use...
+ int item_to_check = (conn->ab_write - (back_step + k)) & 0xffff;
+ seq_t next = item_to_check;
+ abuf_t *check_buf = conn->audio_buffer + BUFIDX(next);
+ if ((!check_buf->ready) &&
+ (check_buf->resend_level <
+ j)) { // prevent multiple requests from the same level of lookback
+ check_buf->resend_level = j;
+ if (config.disable_resend_requests == 0) {
+ if (((int)(resend_interval * pow(j + 1, step_exponent)) + k) >=
+ seq_diff(conn->ab_read, conn->ab_write, conn->ab_read))
- debug(3,
- "Last-ditch (#%d) resend request for packet %u in range %u to %u. "
- "Looking back %d packets.",
++ debug(3, "Last-ditch (#%d) resend request for packet %u in range %u to %u. "
++ "Looking back %d packets.",
+ j, next, conn->ab_read, conn->ab_write, back_step + k);
+ debug_mutex_unlock(&conn->ab_mutex, 3);
+ rtp_request_resend(next, 1, conn);
+ conn->resend_requests++;
+ debug_mutex_lock(&conn->ab_mutex, 20000, 1);
}
}
}
// if (conn->packet_count>500) { //for testing -- about 4 seconds of play first
if ((local_time_now > conn->time_of_last_audio_packet) &&
(local_time_now - conn->time_of_last_audio_packet >= ct << 32)) {
-- debug(1,
-- "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" "
-- "from RTSP conversation %d.",
++ debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" "
++ "from RTSP conversation %d.",
conn->connection_number);
conn->stop = 1;
- pthread_kill(conn->thread, SIGUSR1);
+ pthread_cancel(conn->thread);
+ // pthread_kill(conn->thread, SIGUSR1);
}
}
int rco = get_requested_connection_state_to_output();
int64_t sync_error, correction, drift;
} stats_t;
- void *player_thread_func(void *arg) {
- // note, the thread will be started up with the player_thread_lock locked. You must release it
- // quickly.
-
+ void player_thread_cleanup_handler(void *arg) {
+ debug(1, "player_thread_cleanup_handler called");
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
- pthread_rwlock_wrlock(&conn->player_thread_lock);
+ debug(3, "Connection %d: player thread main loop exit.", conn->connection_number);
- int rc = pthread_mutex_init(&conn->ab_mutex, NULL);
- if (rc)
- debug(1, "Error initialising ab_mutex.");
- rc = pthread_mutex_init(&conn->flush_mutex, NULL);
- if (rc)
- debug(1, "Error initialising flush_mutex.");
- // set the flowcontrol condition variable to wait on a monotonic clock
- #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(&conn->flowcontrol, &attr);
- #endif
- #ifdef COMPILE_FOR_OSX
- rc = pthread_cond_init(&conn->flowcontrol, NULL);
+ if (config.statistics_requested) {
+ int rawSeconds = (int)difftime(time(NULL), conn->playstart);
+ int elapsedHours = rawSeconds / 3600;
+ int elapsedMin = (rawSeconds / 60) % 60;
+ int elapsedSec = rawSeconds % 60;
+ if (conn->frame_rate_status == 0)
- inform("Playback Stopped. Total playing time %02d:%02d:%02d at %0.2f frames per second.", elapsedHours, elapsedMin,
- elapsedSec,conn->frame_rate);
++ inform("Playback Stopped. Total playing time %02d:%02d:%02d at %0.2f frames per second.",
++ elapsedHours, elapsedMin, elapsedSec, conn->frame_rate);
+ else
+ inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin,
- elapsedSec);
++ elapsedSec);
+ }
+
+ #ifdef HAVE_DACP_CLIENT
+
+ relinquish_dacp_server_information(
+ conn); // say it doesn't belong to this conversation thread any more...
+
+ #else
+ // stop watching for DACP port number stuff
+ // this is only used for compatability, if dacp stuff isn't enabled.
+ if (conn->dapo_private_storage) {
+ mdns_dacp_dont_monitor(conn->dapo_private_storage);
+ conn->dapo_private_storage = NULL;
+ } else {
+ debug(2, "DACP Monitor already stopped");
+ }
#endif
- if (rc)
- debug(1, "Error initialising flowcontrol condition variable.");
- pthread_rwlock_unlock(&conn->player_thread_lock);
+ debug(2, "Cancelling timing, control and audio threads...");
+ debug(2, "Cancel timing thread.");
+ pthread_cancel(conn->rtp_timing_thread);
+ debug(2, "Join timing thread.");
+ pthread_join(conn->rtp_timing_thread, NULL);
+ debug(2, "Timing thread terminated.");
+ debug(2, "Cancel control thread.");
+ pthread_cancel(conn->rtp_control_thread);
+ debug(2, "Join control thread.");
+ pthread_join(conn->rtp_control_thread, NULL);
+ debug(2, "Control thread terminated.");
+ debug(2, "Cancel audio thread.");
+ pthread_cancel(conn->rtp_audio_thread);
+ debug(2, "Join audio thread.");
+ pthread_join(conn->rtp_audio_thread, NULL);
+ debug(2, "Audio thread terminated.");
+
+ if (conn->outbuf) {
+ free(conn->outbuf);
+ conn->outbuf = NULL;
+ }
+ if (conn->sbuf) {
+ free(conn->sbuf);
+ conn->sbuf = NULL;
+ }
+ if (conn->tbuf) {
+ free(conn->tbuf);
+ conn->tbuf = NULL;
+ }
+ free_audio_buffers(conn);
+ terminate_decoders(conn);
+ if (config.output->stop)
+ config.output->stop();
- // it's safe now
+ clear_reference_timestamp(conn);
+ conn->rtp_running = 0;
+ }
+
+ void *player_thread_func(void *arg) {
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
conn->packet_count = 0;
conn->previous_random_number = 0;
int32_t minimum_buffer_occupancy = INT32_MAX;
int32_t maximum_buffer_occupancy = INT32_MIN;
- time_t playstart = time(NULL);
+ conn->playstart = time(NULL);
-
++
+ conn->frame_rate = 0.0;
+ conn->frame_rate_status = -1; // zero means okay
conn->buffer_occupancy = 0;
// if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) {
// debug(1,"Define tbuf of length
// %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change));
- tbuf = malloc(
- conn->tbuf = malloc(
-- sizeof(int32_t) * 2 *
-- (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change));
- if (tbuf == NULL)
++ conn->tbuf =
++ malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio +
++ conn->max_frame_size_change));
+ if (conn->tbuf == NULL)
die("Failed to allocate memory for the transition buffer.");
- sbuf = 0;
// initialise this, because soxr stuffing might be chosen later
- sbuf = malloc(
- conn->sbuf = malloc(
-- sizeof(int32_t) * 2 *
-- (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change));
- if (sbuf == NULL)
- debug(1, "Failed to allocate memory for the sbuf buffer.");
++ conn->sbuf =
++ malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio +
++ conn->max_frame_size_change));
+ if (conn->sbuf == NULL)
+ die("Failed to allocate memory for the sbuf buffer.");
// The size of these dependents on the number of frames, the size of each frame and the maximum
// size change
} else {
// the player may change the contents of the buffer, so it has to be zeroed each time;
// might as well malloc and freee it locally
-- memset(silence, 0,
-- conn->output_bytes_per_frame * conn->max_frames_per_packet *
-- conn->output_sample_ratio);
++ memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet *
++ conn->output_sample_ratio);
config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
free(silence);
}
} else {
// the player may change the contents of the buffer, so it has to be zeroed each time;
// might as well malloc and freee it locally
-- memset(silence, 0,
-- conn->output_bytes_per_frame * conn->max_frames_per_packet *
-- conn->output_sample_ratio);
++ memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet *
++ conn->output_sample_ratio);
config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
free(silence);
}
SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay.
if (inframe->sequence_number !=
conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay
-- debug(2,
-- "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u "
-- "and ab_write: %u.",
++ debug(2, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u "
++ "and ab_write: %u.",
conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write);
conn->last_seqno_read = inframe->sequence_number; // reset warning...
}
silence_length = filler_length * 5;
char *long_silence = malloc(conn->output_bytes_per_frame * silence_length);
- if (long_silence == NULL)
- die("Failed to allocate memory for a long_silence buffer of %d frames.",
- silence_length);
- memset(long_silence, 0, conn->output_bytes_per_frame * silence_length);
- config.output->play(long_silence, silence_length);
- free(long_silence);
+ if (long_silence) {
+ memset(long_silence, 0, conn->output_bytes_per_frame * silence_length);
+ config.output->play(long_silence, silence_length);
+ free(long_silence);
+ } else {
- warn("Failed to allocate memory for a long_silence buffer of %d frames for a sync error of %" PRId64 " frames.",
- silence_length, sync_error);
++ warn("Failed to allocate memory for a long_silence buffer of %d frames for a "
++ "sync error of %" PRId64 " frames.",
++ silence_length, sync_error);
+ }
}
} else {
#ifdef CONFIG_CONVOLUTION
|| config.convolution
#endif
-- ) {
- int32_t *tbuf32 = (int32_t *)tbuf;
++ ) {
+ int32_t *tbuf32 = (int32_t *)conn->tbuf;
float fbuf_l[inbuflength];
float fbuf_r[inbuflength];
if (at_least_one_frame_seen) {
if ((config.output->delay)) {
if (config.no_sync == 0) {
- inform(
- " %*.1f," /* Sync error in milliseconds */
- "%*.1f," /* net correction in ppm */
- "%*.1f," /* corrections in ppm */
- "%*d," /* total packets */
- "%*llu," /* missing packets */
- "%*llu," /* late packets */
- "%*llu," /* too late packets */
- "%*llu," /* resend requests */
- "%*lli," /* min DAC queue size */
- "%*d," /* min buffer occupancy */
- "%*d", /* max buffer occupancy */
- 9, /* should be 10, but there's an explicit space at the start to ensure
- alignment */
- 1000 * moving_average_sync_error / config.output_rate, 10,
- moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10,
- moving_average_insertions_plus_deletions * 1000000 /
- (352 * conn->output_sample_ratio),
- 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7,
- conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size,
- 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy);
+ if (config.output->rate_info) {
+ uint64_t elapsed_play_time, frames_played;
- conn->frame_rate_status = config.output->rate_info(&elapsed_play_time,&frames_played);
- if (conn->frame_rate_status==0) {
- conn->frame_rate = 1.0*(frames_played*(uint64_t)0x100000000)/elapsed_play_time;
++ conn->frame_rate_status =
++ config.output->rate_info(&elapsed_play_time, &frames_played);
++ if (conn->frame_rate_status == 0) {
++ conn->frame_rate =
++ 1.0 * (frames_played * (uint64_t)0x100000000) / elapsed_play_time;
+ }
+ }
- inform(
- "%*.1f," /* Sync error in milliseconds */
- "%*.1f," /* net correction in ppm */
- "%*.1f," /* corrections in ppm */
- "%*d," /* total packets */
- "%*llu," /* missing packets */
- "%*llu," /* late packets */
- "%*llu," /* too late packets */
- "%*llu," /* resend requests */
- "%*lli," /* min DAC queue size */
- "%*d," /* min buffer occupancy */
- "%*d," /* max buffer occupancy */
- "%*.2f", /* frame rate */
- 10, 1000 * moving_average_sync_error / config.output_rate, 10,
- moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10,
- moving_average_insertions_plus_deletions * 1000000 /
- (352 * conn->output_sample_ratio),
- 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7,
- conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size,
- 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 10, conn->frame_rate);
++ inform("%*.1f," /* Sync error in milliseconds */
++ "%*.1f," /* net correction in ppm */
++ "%*.1f," /* corrections in ppm */
++ "%*d," /* total packets */
++ "%*llu," /* missing packets */
++ "%*llu," /* late packets */
++ "%*llu," /* too late packets */
++ "%*llu," /* resend requests */
++ "%*lli," /* min DAC queue size */
++ "%*d," /* min buffer occupancy */
++ "%*d," /* max buffer occupancy */
++ "%*.2f", /* frame rate */
++ 10,
++ 1000 * moving_average_sync_error / config.output_rate, 10,
++ moving_average_correction * 1000000 / (352 * conn->output_sample_ratio),
++ 10, moving_average_insertions_plus_deletions * 1000000 /
++ (352 * conn->output_sample_ratio),
++ 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7,
++ conn->too_late_packets, 7, conn->resend_requests, 7,
++ minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5,
++ maximum_buffer_occupancy, 10, conn->frame_rate);
} else {
- inform(" %*.1f," /* Sync error in milliseconds */
- "%*d," /* total packets */
- "%*llu," /* missing packets */
- "%*llu," /* late packets */
- "%*llu," /* too late packets */
- "%*llu," /* resend requests */
- "%*lli," /* min DAC queue size */
- "%*d," /* min buffer occupancy */
- "%*d", /* max buffer occupancy */
- 9, /* should be 10, but there's an explicit space at the start to ensure
- alignment */
+ inform("%*.1f," /* Sync error in milliseconds */
- "%*d," /* total packets */
- "%*llu," /* missing packets */
- "%*llu," /* late packets */
- "%*llu," /* too late packets */
- "%*llu," /* resend requests */
- "%*lli," /* min DAC queue size */
- "%*d," /* min buffer occupancy */
- "%*d", /* max buffer occupancy */
- 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number,
- 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets,
- 7, conn->resend_requests, 7, minimum_dac_queue_size, 5,
++ "%*d," /* total packets */
++ "%*llu," /* missing packets */
++ "%*llu," /* late packets */
++ "%*llu," /* too late packets */
++ "%*llu," /* resend requests */
++ "%*lli," /* min DAC queue size */
++ "%*d," /* min buffer occupancy */
++ "%*d", /* max buffer occupancy */
++ 10,
+ 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7,
+ conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7,
+ conn->resend_requests, 7, minimum_dac_queue_size, 5,
minimum_buffer_occupancy, 5, maximum_buffer_occupancy);
}
} else {
- inform(" %*.1f," /* Sync error in milliseconds */
- "%*d," /* total packets */
- "%*llu," /* missing packets */
- "%*llu," /* late packets */
- "%*llu," /* too late packets */
- "%*llu," /* resend requests */
- "%*d," /* min buffer occupancy */
- "%*d", /* max buffer occupancy */
- 9, /* should be 10, but there's an explicit space at the start to ensure
- alignment */
+ inform("%*.1f," /* Sync error in milliseconds */
- "%*d," /* total packets */
- "%*llu," /* missing packets */
- "%*llu," /* late packets */
- "%*llu," /* too late packets */
- "%*llu," /* resend requests */
- "%*d," /* min buffer occupancy */
- "%*d", /* max buffer occupancy */
- 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number,
- 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets,
- 7, conn->resend_requests, 5, minimum_buffer_occupancy, 5,
++ "%*d," /* total packets */
++ "%*llu," /* missing packets */
++ "%*llu," /* late packets */
++ "%*llu," /* too late packets */
++ "%*llu," /* resend requests */
++ "%*d," /* min buffer occupancy */
++ "%*d", /* max buffer occupancy */
++ 10,
+ 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7,
+ conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7,
+ conn->resend_requests, 5, minimum_buffer_occupancy, 5,
maximum_buffer_occupancy);
}
} else {
SOCKADDR remote, local;
int stop;
int running;
- pthread_t thread, timer_requester;
-
+ time_t playstart;
+ pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread;
// pthread_t *ptp;
- pthread_t *player_thread;
- pthread_rwlock_t player_thread_lock; // used to control access by "outsiders"
-
+ // buffers to delete on exit
+ signed short *tbuf;
+ int32_t *sbuf;
+ char *outbuf;
-
++
+ // for holding the rate information until printed out at the end of a session
+ double frame_rate;
+ int frame_rate_status;
+
+ pthread_t *player_thread;
abuf_t audio_buffer[BUFFER_FRAMES];
int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate;
int input_bytes_per_frame, output_bytes_per_frame, output_sample_ratio;
for (i = 0; i < nconns; i++) {
if (((except_this_thread == 0) || (pthread_equal(conns[i]->thread, except_this_thread) == 0)) &&
(conns[i]->running != 0)) {
- conns[i]->stop = 1;
- pthread_kill(conns[i]->thread, SIGUSR1);
+ pthread_cancel(conns[i]->thread);
- pthread_join(conns[i]->thread,NULL);
- debug(1,"Connection %d: asked to stop.",conns[i]->connection_number);
++ pthread_join(conns[i]->thread, NULL);
++ debug(1, "Connection %d: asked to stop.", conns[i]->connection_number);
+ // conns[i]->stop = 1;
+ // pthread_kill(conns[i]->thread, SIGUSR1);
}
}
}
#ifdef CONFIG_METADATA
if (!strncmp(cp, "progress: ", 10)) {
char *progress = cp + 10;
- // debug(2, "progress: \"%s\"\n",
- // progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second
- // debug(2, "progress: \"%s\"\n",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second
++ // debug(2, "progress: \"%s\"\n",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per
++ // second
send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1);
-
++
} else
#endif
{
resp->respcode = 200;
} else {
resp->respcode = 453;
- debug(1, "Already playing.");
- debug(1, "Connection %d: failed because a connection is already playing.", conn->connection_number);
++ debug(1, "Connection %d: failed because a connection is already playing.",
++ conn->connection_number);
}
out:
return 1;
}
- //debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.",
+ void rtsp_conversation_thread_cleanup_function(void *arg) {
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
- // conn->connection_number,conn->fd);
++ // debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.",
+ // conn->connection_number);
+ player_stop(conn);
+ if (conn->fd > 0) {
+ // debug(1, "Connection %d: closing fd %d.",
++ // conn->connection_number,conn->fd);
+ close(conn->fd);
+ }
+ if (conn->auth_nonce) {
+ free(conn->auth_nonce);
+ conn->auth_nonce = NULL;
+ }
+ rtp_terminate(conn);
+ if (playing_conn == conn) {
+ debug(3, "Unlocking play lock on RTSP conversation thread %d.", conn->connection_number);
+ playing_conn = NULL;
+ pthread_mutex_unlock(&play_lock);
+ }
+
+ if (conn->dacp_id) {
+ free(conn->dacp_id);
+ conn->dacp_id = NULL;
+ }
+
+ debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number);
+ conn->running = 0;
+
+ // remove flow control and mutexes
+ int rc = pthread_cond_destroy(&conn->flowcontrol);
+ if (rc)
+ debug(1, "Connection %d: error %d destroying flow control condition variable.",
+ conn->connection_number, rc);
+ rc = pthread_mutex_destroy(&conn->ab_mutex);
+ if (rc)
+ debug(1, "Connection %d: error %d destroying ab_mutex.", conn->connection_number, rc);
+ rc = pthread_mutex_destroy(&conn->flush_mutex);
+ if (rc)
+ debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc);
+ }
+
+ void msg_cleanup_function(void *arg) {
+ // debug(1, "msg_cleanup_function called.");
+ msg_free((rtsp_message *)arg);
+ }
+
static void *rtsp_conversation_thread_func(void *pconn) {
rtsp_conn_info *conn = pconn;
if (strcmp(req->method, "OPTIONS") !=
0) // the options message is very common, so don't log it until level 3
debug_level = 2;
- debug(debug_level,
- "RTSP thread %d received an RTSP Packet of type \"%s\":", conn->connection_number,
- req->method),
+ debug(debug_level, "RTSP thread %d received an RTSP Packet of type \"%s\":",
+ conn->connection_number, req->method),
debug_print_msg_headers(debug_level, req);
- resp = msg_init();
- resp->respcode = 400;
apple_challenge(conn->fd, req, resp);
hdr = msg_get_header(req, "CSeq");
conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &slen);
if (conn->fd < 0) {
- debug(1, "New RTSP connection on port %d not accepted:", config.port);
- debug(1, "Connection %d: New connection on port %d not accepted:", conn->connection_number, config.port);
++ debug(1, "Connection %d: New connection on port %d not accepted:", conn->connection_number,
++ config.port);
perror("failed to accept connection");
free(conn);
} else {
sa = (struct sockaddr_in *)&conn->remote;
inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN);
unsigned short int rport = ntohs(sa->sin_port);
- debug(2, "New RTSP connection from %s:%u to self at %s:%u on conversation thread %d.",
- remote_ip4, rport, ip4, tport, conn->connection_number);
+ debug(2, "Connection %d: new connection from %s:%u to self at %s:%u.",
- conn->connection_number,remote_ip4, rport, ip4, tport);
++ conn->connection_number, remote_ip4, rport, ip4, tport);
}
#ifdef AF_INET6
if (local_info->SAFAMILY == AF_INET6) {
// probably should be freeing malloc'ed memory here, including strdup-created strings...
}
- // stop_mpris_service();
+ void main_cleanup_handler(__attribute__((unused)) void *arg) {
+
+ debug(1, "main cleanup handler called.");
+ #ifdef HAVE_MQTT
+ if (config.mqtt_enabled) {
+ // terminate_mqtt();
+ }
+ #endif
+
+ #if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+ #ifdef HAVE_MPRIS
- debug(1,"Deinitialise the audio backend.");
++// stop_mpris_service();
+ #endif
+ #ifdef HAVE_DBUS
+ stop_dbus_service();
+ #endif
+ debug(1, "Stopping DBUS Loop Thread");
+ g_main_loop_quit(g_main_loop);
+ pthread_join(dbus_thread, NULL);
+ #endif
+
+ #ifdef HAVE_DACP_CLIENT
+ debug(1, "Stopping DACP Monitor");
+ dacp_monitor_stop();
+ #endif
+
+ #ifdef HAVE_METADATA_HUB
+ debug(1, "Stopping metadata hub");
+ metadata_hub_stop();
+ #endif
+
+ #ifdef CONFIG_METADATA
+ metadata_stop(); // close down the metadata pipe
+ #endif
+ if (config.output->deinit) {
++ debug(1, "Deinitialise the audio backend.");
+ config.output->deinit();
+ }
+ daemon_log(LOG_NOTICE, "Unexpected exit...");
+ daemon_retval_send(0);
+ daemon_pid_file_remove();
+ daemon_signal_done();
+ exit(0);
+ }
+
int main(int argc, char **argv) {
+ conns = NULL; // no connections active
+ memset((void *)&main_thread_id, 0, sizeof(main_thread_id));
fp_time_at_startup = get_absolute_time_in_fp();
fp_time_at_last_debug_message = fp_time_at_startup;
// debug(1,"startup");