Be more careful checking for ENODEV conditions in audio_alsa.c. Still a few places where it is not done...

This commit is contained in:
Mike Brady
2022-12-31 17:16:02 +00:00
parent dcc994ab77
commit 76387f8291
3 changed files with 96 additions and 150 deletions
+68 -122
View File
@@ -166,6 +166,22 @@ int volume_based_mute_is_active =
// use this to allow the use of snd_pcm_writei or snd_pcm_mmap_writei
snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *, snd_pcm_uframes_t) = snd_pcm_writei;
void handle_unfixable_error(int errorCode) {
if (config.unfixable_error_reported == 0) {
config.unfixable_error_reported = 1;
char messageString[1024];
messageString[0] = '\0';
snprintf(messageString, sizeof(messageString), "output_device_error_%d", errorCode);
if (config.cmd_unfixable) {
command_execute(config.cmd_unfixable, messageString, 1);
} else {
die("An unrecoverable error, \"output_device_error_%d\", has been "
"detected. Doing an emergency exit, as no run_this_if_an_unfixable_error_is_detected program.",
errorCode);
}
}
}
static int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
yndk_type *using_update_timestamps);
static int standard_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
@@ -277,40 +293,33 @@ static void help(void) {
void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; } // ugh -- not static!
// assuming pthread cancellation is disabled
// returns zero of all is okay, a Unx error code if there's a problem
static int open_mixer() {
int response = 0;
if (alsa_mix_ctrl != NULL) {
debug(3, "Open Mixer");
int ret = 0;
snd_mixer_selem_id_alloca(&alsa_mix_sid);
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) {
if ((response = snd_mixer_open(&alsa_mix_handle, 0)) < 0) {
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) {
if ((response = snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) {
debug(1, "Failed to attach mixer");
response = -2;
} else {
if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) {
if ((response = snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) {
debug(1, "Failed to register mixer element");
response = -3;
} else {
ret = snd_mixer_load(alsa_mix_handle);
if (ret < 0) {
if ((response = snd_mixer_load(alsa_mix_handle)) < 0) {
debug(1, "Failed to load mixer element");
response = -4;
} else {
debug(3, "Mixer control is \"%s\",%d.", alsa_mix_ctrl, alsa_mix_index);
alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
if (!alsa_mix_elem) {
warn("failed to find mixer control \"%s\",%d.", alsa_mix_ctrl, alsa_mix_index);
response = -5;
} else {
response = 1; // we found a hardware mixer and successfully opened it
response = -ENXIO; // don't let this be ENODEV!
}
}
}
@@ -321,21 +330,25 @@ static int open_mixer() {
}
// assuming pthread cancellation is disabled
static void close_mixer() {
static int close_mixer() {
int ret = 0;
if (alsa_mix_handle) {
snd_mixer_close(alsa_mix_handle);
ret = snd_mixer_close(alsa_mix_handle);
alsa_mix_handle = NULL;
}
return ret;
}
// assuming pthread cancellation is disabled
static void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double vol) {
if (snd_mixer_selem_set_playback_dB_all(mix_elem, vol, 0) != 0) {
static int do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double vol) {
int response = 0;
if ((response = snd_mixer_selem_set_playback_dB_all(mix_elem, vol, 0)) != 0) {
debug(1, "Can't set playback volume accurately to %f dB.", vol);
if (snd_mixer_selem_set_playback_dB_all(mix_elem, vol, -1) != 0)
if (snd_mixer_selem_set_playback_dB_all(mix_elem, vol, 1) != 0)
if ((response = snd_mixer_selem_set_playback_dB_all(mix_elem, vol, -1)) != 0)
if ((response = snd_mixer_selem_set_playback_dB_all(mix_elem, vol, 1)) != 0)
debug(1, "Could not set playback dB volume on the mixer.");
}
return response;
}
// This array is a sequence of the output rates to be tried if automatic speed selection is
@@ -444,19 +457,6 @@ static int actual_open_alsa_device(int do_auto_setup) {
warn("The output device \"%s\" is busy and can't be used by Shairport Sync at present.",
alsa_out_dev);
debug(2, "the alsa output_device \"%s\" is busy.", alsa_out_dev);
} else {
if (config.unfixable_error_reported == 0) {
config.unfixable_error_reported = 1;
char messageString[1024];
messageString[0] = '\0';
snprintf(messageString, sizeof(messageString), "output_device_error_%d", -ret);
if (config.cmd_unfixable) {
command_execute(config.cmd_unfixable, messageString, 1);
} else {
die("an unrecoverable error, \"output_device_error_%d\", has been "
"detected.", -ret);
}
}
}
alsa_handle_status = ret;
frames_sent_break_occurred = 1;
@@ -906,11 +906,11 @@ static int prepare_mixer() {
// Now, start trying to initialise the alsa device with the settings
// obtained
pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
if (open_mixer() == 1) {
if (open_mixer() == 0) {
if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
0)
0) {
debug(1, "Can't read mixer's [linear] min and max volumes.");
else {
} else {
if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
&alsa_mix_maxdb) == 0) {
@@ -940,14 +940,12 @@ static int prepare_mixer() {
"a dB volume scale.",
alsa_mix_ctrl);
if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
if ((response = 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) {
if ((response = snd_ctl_elem_id_malloc(&elem_id)) < 0) {
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);
@@ -965,22 +963,6 @@ static int prepare_mixer() {
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 = &parameters; // 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) &&
@@ -992,7 +974,8 @@ static int prepare_mixer() {
} else {
// debug(1, "Has mixer but not using hardware mute.");
}
close_mixer();
if (response == 0)
response = close_mixer();
}
debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
pthread_cleanup_pop(0);
@@ -1399,7 +1382,7 @@ static int set_mute_state() {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 10000, 0);
if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) &&
(open_mixer() == 1)) {
(open_mixer() == 0)) {
response = 0; // okay if actually using the mute facility
debug(2, "alsa: actually set_mute_state");
int mute = 0;
@@ -1718,36 +1701,20 @@ static int stats(uint64_t *raw_measurement_time, uint64_t *corrected_measurement
*the_delay = hd;
return ret;
}
/*
static 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;
*frames_played = frames_played_at_measurement_time - frames_played_at_measurement_start_time;
} else {
*elapsed_time = 0;
*frames_played = 0;
response = -1;
}
return response;
}
*/
static int do_play(void *buf, int samples) {
// assuming the alsa_mutex has been acquired
// debug(3,"audio_alsa play called.");
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
int ret = 0;
if ((samples != 0) && (buf != NULL)) {
snd_pcm_state_t state;
snd_pcm_sframes_t my_delay;
int ret = delay_and_status(&state, &my_delay, NULL);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
if (ret == 0) { // will be non-zero if an error or a stall
if ((samples != 0) && (buf != NULL)) {
snd_pcm_state_t state;
snd_pcm_sframes_t my_delay;
ret = delay_and_status(&state, &my_delay, NULL);
if (ret == 0) { // will be non-zero if an error or a stall
// just check the state of the DAC
if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) &&
@@ -1784,51 +1751,27 @@ static int do_play(void *buf, int samples) {
debug(1, "alsa: recovering from a previous underrun.");
else
debug(1, "alsa: underrun while writing %d samples to alsa device.", samples);
int tret = snd_pcm_recover(alsa_handle, ret, 1);
if (tret < 0) {
warn("alsa: can't recover from SND_PCM_STATE_XRUN: %s.", snd_strerror(tret));
}
ret = snd_pcm_recover(alsa_handle, ret, 1);
} else if (ret == -ESTRPIPE) { /* suspended */
debug(1, "alsa: suspended while writing %d samples to alsa device.", samples);
int tret;
while ((tret = snd_pcm_resume(alsa_handle)) == -EAGAIN) {
sleep(1); /* wait until the suspend flag is released */
if (tret < 0) {
warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, "
"snd_pcm_prepare() "
"failed: %s.",
snd_strerror(tret));
}
}
} else {
if (ret == -ENODEV) {
if (config.unfixable_error_reported == 0) {
config.unfixable_error_reported = 1;
if (config.cmd_unfixable) {
command_execute(config.cmd_unfixable, "output_device_error_19", 1);
} else {
die("an unrecoverable error, \"output_device_error_19\", has been "
"detected.");
}
}
} else {
char errorstring[1024];
strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
debug(1, "alsa: error %d (\"%s\") writing %d samples to alsa device.", ret,
(char *)errorstring, samples);
}
if (state != prior_state)
debug(1, "alsa: suspended while writing %d samples to alsa device.", samples);
if ((ret = snd_pcm_resume(alsa_handle)) == -ENOSYS)
ret = snd_pcm_prepare(alsa_handle);
} else if (ret >= 0) {
debug(1, "alsa: only %d of %d samples output.", ret, samples);
}
}
}
} else {
debug(1,
"alsa: device status returns fault status %d and SND_PCM_STATE_* "
"%d for play.",
ret, state);
pthread_setcancelstate(oldState, NULL);
if (ret < 0) {
char errorstring[1024];
strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
debug(1, "alsa: SND_PCM_STATE_* %d, error %d (\"%s\") writing %d samples to alsa device.",
state, ret, (char *)errorstring, samples);
}
if (ret == -ENODEV) // if the device isn't there...
handle_unfixable_error(-ret);
}
pthread_setcancelstate(oldState, NULL);
return ret;
}
@@ -1854,6 +1797,9 @@ static int do_open(int do_auto_setup) {
// any previously-reported frame count
frames_sent_for_playing = 0;
alsa_backend_state = abm_connected; // only do this if it really opened it.
} else {
if (ret == -ENODEV) // if the device isn't there...
handle_unfixable_error(-ret);
}
} else {
debug(1, "alsa: do_open() -- output device already open.");
@@ -2006,7 +1952,7 @@ static void do_volume(double vol) { // caller is assumed to have the alsa_mutex
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
set_volume = vol;
pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
if (volume_set_request && (open_mixer() == 1)) {
if (volume_set_request && (open_mixer() == 0)) {
if (has_softvol) {
if (ctl && elem_id) {
snd_ctl_elem_value_t *value;
+5 -6
View File
@@ -531,9 +531,9 @@ void player_put_packet(int original_format, seq_t seqno, uint32_t actual_timesta
(uint64_t)(config.resend_control_first_check_time * (uint64_t)1000000000);
uint64_t resend_repeat_interval =
(uint64_t)(config.resend_control_check_interval_time * (uint64_t)1000000000);
uint64_t minimum_remaining_time = (uint64_t)((config.resend_control_last_check_time +
config.audio_backend_buffer_desired_length) *
(uint64_t)1000000000);
uint64_t minimum_remaining_time = (uint64_t)(
(config.resend_control_last_check_time + config.audio_backend_buffer_desired_length) *
(uint64_t)1000000000);
uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)1000000000);
latency_time = latency_time / (uint64_t)conn->input_rate;
@@ -1982,9 +1982,8 @@ void *player_thread_func(void *arg) {
debug(3, "Output frame bytes is %d.", conn->output_bytes_per_frame);
conn->dac_buffer_queue_minimum_length =
(uint64_t)(config.audio_backend_buffer_interpolation_threshold_in_seconds *
config.output_rate);
conn->dac_buffer_queue_minimum_length = (uint64_t)(
config.audio_backend_buffer_interpolation_threshold_in_seconds * config.output_rate);
debug(3, "dac_buffer_queue_minimum_length is %" PRIu64 " frames.",
conn->dac_buffer_queue_minimum_length);
+23 -22
View File
@@ -37,6 +37,7 @@
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
@@ -47,7 +48,6 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
@@ -1223,10 +1223,10 @@ ssize_t timed_read_from_rtsp_connection(rtsp_conn_info *conn, uint64_t wait_time
uint64_t time_to_wait_to = get_absolute_time_in_ns();
;
time_to_wait_to = time_to_wait_to + wait_time;
int flags = 1;
if (setsockopt(conn->fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags))) {
debug(1,"can't enable keepalive checking on the RTSP socket");
debug(1, "can't enable keepalive checking on the RTSP socket");
}
// remaining_time will be zero if wait_time is zero
@@ -1355,7 +1355,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
// Note: the socket will be closed when the thread exits
goto shutdown;
}
// An ETIMEDOUT error usually means keepalive has failed.
if (nread < 0) {
@@ -5497,39 +5497,40 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
if (getsockname(conn->fd, (struct sockaddr *)&conn->local, &size_of_reply) == 0) {
// Thanks to https://holmeshe.me/network-essentials-setsockopt-SO_KEEPALIVE/ for this.
// turn on keepalive stuff -- wait for keepidle + (keepcnt * keepinttvl time) seconds before giving up
// an ETIMEOUT error is returned if the keepalive check fails
// turn on keepalive stuff -- wait for keepidle + (keepcnt * keepinttvl time) seconds
// before giving up an ETIMEOUT error is returned if the keepalive check fails
int keepAliveIdleTime = 35; // wait this many seconds before checking for a dropped client
int keepAliveCount = 5; // check this many times
int keepAliveInterval = 5; // wait this many seconds between checks
int keepAliveCount = 5; // check this many times
int keepAliveInterval = 5; // wait this many seconds between checks
#if defined COMPILE_FOR_BSD || defined COMPILE_FOR_OSX
#define SOL_OPTION IPPROTO_TCP
#define SOL_OPTION IPPROTO_TCP
#else
#define SOL_OPTION SOL_TCP
#define SOL_OPTION SOL_TCP
#endif
#ifdef COMPILE_FOR_OSX
#define KEEP_ALIVE_OR_IDLE_OPTION TCP_KEEPALIVE
#define KEEP_ALIVE_OR_IDLE_OPTION TCP_KEEPALIVE
#else
#define KEEP_ALIVE_OR_IDLE_OPTION TCP_KEEPIDLE
#define KEEP_ALIVE_OR_IDLE_OPTION TCP_KEEPIDLE
#endif
if (setsockopt(conn->fd, SOL_OPTION, KEEP_ALIVE_OR_IDLE_OPTION, (void *)&keepAliveIdleTime, sizeof(keepAliveIdleTime))) {
debug(1,"can't set the keepidle wait time");
if (setsockopt(conn->fd, SOL_OPTION, KEEP_ALIVE_OR_IDLE_OPTION,
(void *)&keepAliveIdleTime, sizeof(keepAliveIdleTime))) {
debug(1, "can't set the keepidle wait time");
}
if (setsockopt(conn->fd, SOL_OPTION, TCP_KEEPCNT, (void *)&keepAliveCount, sizeof(keepAliveCount))) {
debug(1,"can't set the keepidle missing count");
if (setsockopt(conn->fd, SOL_OPTION, TCP_KEEPCNT, (void *)&keepAliveCount,
sizeof(keepAliveCount))) {
debug(1, "can't set the keepidle missing count");
}
if (setsockopt(conn->fd, SOL_OPTION , TCP_KEEPINTVL, (void *)&keepAliveInterval, sizeof(keepAliveInterval))) {
debug(1,"can't set the keepidle missing count interval");
if (setsockopt(conn->fd, SOL_OPTION, TCP_KEEPINTVL, (void *)&keepAliveInterval,
sizeof(keepAliveInterval))) {
debug(1, "can't set the keepidle missing count interval");
};
// initialise the connection info
void *client_addr = NULL, *self_addr = NULL;
conn->connection_ip_family = conn->local.SAFAMILY;