diff options
-rw-r--r-- | src/output/oss_plugin.c | 625 |
1 files changed, 324 insertions, 301 deletions
diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c index 4c27d31c1..da1fb3401 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_plugin.c @@ -30,6 +30,7 @@ #include <errno.h> #include <stdlib.h> #include <unistd.h> +#include <assert.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "oss" @@ -43,24 +44,12 @@ struct oss_data { int fd; const char *device; - struct audio_format audio_format; - int bitFormat; - int *supported[3]; - unsigned num_supported[3]; - int *unsupported[3]; - unsigned num_unsupported[3]; -}; - -enum oss_support { - OSS_SUPPORTED = 1, - OSS_UNSUPPORTED = 0, - OSS_UNKNOWN = -1, -}; -enum oss_param { - OSS_RATE = 0, - OSS_CHANNELS = 1, - OSS_BITS = 2, + /** + * The current input audio format. This is needed to reopen + * the device after cancel(). + */ + struct audio_format audio_format; }; /** @@ -72,188 +61,6 @@ oss_output_quark(void) return g_quark_from_static_string("oss_output"); } -static enum oss_param -oss_param_from_ioctl(unsigned param) -{ - enum oss_param idx = OSS_RATE; - - switch (param) { - case SNDCTL_DSP_SPEED: - idx = OSS_RATE; - break; - case SNDCTL_DSP_CHANNELS: - idx = OSS_CHANNELS; - break; - case SNDCTL_DSP_SAMPLESIZE: - idx = OSS_BITS; - break; - } - - return idx; -} - -static bool -oss_find_supported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_supported[idx]; i++) - if (od->supported[idx][i] == val) - return true; - - return false; -} - -static bool -oss_can_convert(int idx, int val) -{ - switch (idx) { - case OSS_BITS: - if (val != 16) - return false; - break; - case OSS_CHANNELS: - if (val != 2) - return false; - break; - } - - return true; -} - -static int -oss_get_supported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - int ret = -1; - int least = val; - int diff; - - for (unsigned i = 0; i < od->num_supported[idx]; i++) { - diff = od->supported[idx][i] - val; - if (diff < 0) - diff = -diff; - if (diff < least) { - if (!oss_can_convert(idx, od->supported[idx][i])) - continue; - - least = diff; - ret = od->supported[idx][i]; - } - } - - return ret; -} - -static bool -oss_find_unsupported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_unsupported[idx]; i++) { - if (od->unsupported[idx][i] == val) - return true; - } - - return false; -} - -static void -oss_add_supported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - od->num_supported[idx]++; - od->supported[idx] = g_realloc(od->supported[idx], - od->num_supported[idx] * sizeof(int)); - od->supported[idx][od->num_supported[idx] - 1] = val; -} - -static void -oss_add_unsupported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - od->num_unsupported[idx]++; - od->unsupported[idx] = g_realloc(od->unsupported[idx], - od->num_unsupported[idx] * - sizeof(int)); - od->unsupported[idx][od->num_unsupported[idx] - 1] = val; -} - -static void -oss_remove_supported_param(struct oss_data *od, unsigned param, int val) -{ - unsigned j = 0; - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_supported[idx] - 1; i++) { - if (od->supported[idx][i] == val) - j = 1; - od->supported[idx][i] = od->supported[idx][i + j]; - } - - od->num_supported[idx]--; - od->supported[idx] = g_realloc(od->supported[idx], - od->num_supported[idx] * sizeof(int)); -} - -static void -oss_remove_unsupported_param(struct oss_data *od, unsigned param, int val) -{ - unsigned j = 0; - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_unsupported[idx] - 1; i++) { - if (od->unsupported[idx][i] == val) - j = 1; - od->unsupported[idx][i] = od->unsupported[idx][i + j]; - } - - od->num_unsupported[idx]--; - od->unsupported[idx] = g_realloc(od->unsupported[idx], - od->num_unsupported[idx] * - sizeof(int)); -} - -static enum oss_support -oss_param_is_supported(struct oss_data *od, unsigned param, int val) -{ - if (oss_find_supported_param(od, param, val)) - return OSS_SUPPORTED; - if (oss_find_unsupported_param(od, param, val)) - return OSS_UNSUPPORTED; - return OSS_UNKNOWN; -} - -static void -oss_set_supported(struct oss_data *od, unsigned param, int val) -{ - enum oss_support supported = oss_param_is_supported(od, param, val); - - if (supported == OSS_SUPPORTED) - return; - - if (supported == OSS_UNSUPPORTED) - oss_remove_unsupported_param(od, param, val); - - oss_add_supported_param(od, param, val); -} - -static void -oss_set_unsupported(struct oss_data *od, unsigned param, int val) -{ - enum oss_support supported = oss_param_is_supported(od, param, val); - - if (supported == OSS_UNSUPPORTED) - return; - - if (supported == OSS_SUPPORTED) - oss_remove_supported_param(od, param, val); - - oss_add_unsupported_param(od, param, val); -} - static struct oss_data * oss_data_new(void) { @@ -262,38 +69,12 @@ oss_data_new(void) ret->device = NULL; ret->fd = -1; - ret->supported[OSS_RATE] = NULL; - ret->supported[OSS_CHANNELS] = NULL; - ret->supported[OSS_BITS] = NULL; - ret->unsupported[OSS_RATE] = NULL; - ret->unsupported[OSS_CHANNELS] = NULL; - ret->unsupported[OSS_BITS] = NULL; - - ret->num_supported[OSS_RATE] = 0; - ret->num_supported[OSS_CHANNELS] = 0; - ret->num_supported[OSS_BITS] = 0; - ret->num_unsupported[OSS_RATE] = 0; - ret->num_unsupported[OSS_CHANNELS] = 0; - ret->num_unsupported[OSS_BITS] = 0; - - oss_set_supported(ret, SNDCTL_DSP_SPEED, 48000); - oss_set_supported(ret, SNDCTL_DSP_SPEED, 44100); - oss_set_supported(ret, SNDCTL_DSP_CHANNELS, 2); - oss_set_supported(ret, SNDCTL_DSP_SAMPLESIZE, 16); - return ret; } static void oss_data_free(struct oss_data *od) { - g_free(od->supported[OSS_RATE]); - g_free(od->supported[OSS_CHANNELS]); - g_free(od->supported[OSS_BITS]); - g_free(od->unsupported[OSS_RATE]); - g_free(od->unsupported[OSS_CHANNELS]); - g_free(od->unsupported[OSS_BITS]); - g_free(od); } @@ -417,37 +198,6 @@ oss_output_finish(void *data) oss_data_free(od); } -static int -oss_set_param(struct oss_data *od, unsigned param, int *value) -{ - int val = *value; - int copy; - enum oss_support supported = oss_param_is_supported(od, param, val); - - do { - if (supported == OSS_UNSUPPORTED) { - val = oss_get_supported_param(od, param, val); - if (copy < 0) - return -1; - } - copy = val; - if (ioctl(od->fd, param, ©)) { - oss_set_unsupported(od, param, val); - supported = OSS_UNSUPPORTED; - } else { - if (supported == OSS_UNKNOWN) { - oss_set_supported(od, param, val); - supported = OSS_SUPPORTED; - } - val = copy; - } - } while (supported == OSS_UNSUPPORTED); - - *value = val; - - return 0; -} - static void oss_close(struct oss_data *od) { @@ -457,75 +207,343 @@ oss_close(struct oss_data *od) } /** - * Sets up the OSS device which was opened before. + * A tri-state type for oss_try_ioctl(). */ -static bool -oss_setup(struct oss_data *od, GError **error) +enum oss_setup_result { + SUCCESS, + ERROR, + UNSUPPORTED, +}; + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl_r(int fd, unsigned long request, int *value_r, + const char *msg, GError **error_r) { - int tmp; + assert(fd >= 0); + assert(value_r != NULL); + assert(msg != NULL); + assert(error_r == NULL || *error_r == NULL); - tmp = od->audio_format.channels; - if (oss_set_param(od, SNDCTL_DSP_CHANNELS, &tmp)) { - g_set_error(error, oss_output_quark(), errno, - "OSS device \"%s\" does not support %u channels: %s", - od->device, od->audio_format.channels, - strerror(errno)); + int ret = ioctl(fd, request, value_r); + if (ret >= 0) + return SUCCESS; + + if (errno == EINVAL) + return UNSUPPORTED; + + g_set_error(error_r, oss_output_quark(), errno, + "%s: %s", msg, g_strerror(errno)); + return ERROR; +} + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl(int fd, unsigned long request, int value, + const char *msg, GError **error_r) +{ + return oss_try_ioctl_r(fd, request, &value, msg, error_r); +} + +/** + * Set up the channel number, and attempts to find alternatives if the + * specified number is not supported. + */ +static bool +oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) +{ + const char *const msg = "Failed to set channel count"; + int channels = audio_format->channels; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: return false; + + case UNSUPPORTED: + break; } - od->audio_format.channels = tmp; - tmp = od->audio_format.sample_rate; - if (oss_set_param(od, SNDCTL_DSP_SPEED, &tmp)) { - g_set_error(error, oss_output_quark(), errno, - "OSS device \"%s\" does not support %u Hz audio: %s", - od->device, od->audio_format.sample_rate, - strerror(errno)); + for (unsigned i = 1; i < 2; ++i) { + if (i == audio_format->channels) + /* don't try that again */ + continue; + + channels = i; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Set up the sample rate, and attempts to find alternatives if the + * specified sample rate is not supported. + */ +static bool +oss_setup_sample_rate(int fd, struct audio_format *audio_format, + GError **error_r) +{ + const char *const msg = "Failed to set sample rate"; + int sample_rate = audio_format->sample_rate; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: return false; + + case UNSUPPORTED: + break; + } + + static const int sample_rates[] = { 48000, 44100, 0 }; + for (unsigned i = 0; sample_rates[i] != 0; ++i) { + sample_rate = sample_rates[i]; + if (sample_rate == (int)audio_format->sample_rate) + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } } - od->audio_format.sample_rate = tmp; - switch (od->audio_format.format) { + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Convert a MPD sample format to its OSS counterpart. Returns + * AFMT_QUERY if there is no direct counterpart. + */ +static int +sample_format_to_oss(enum sample_format format) +{ + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + return AFMT_QUERY; + case SAMPLE_FORMAT_S8: - tmp = AFMT_S8; - break; + return AFMT_S8; case SAMPLE_FORMAT_S16: - tmp = AFMT_S16_NE; - break; + return AFMT_S16_NE; + + case SAMPLE_FORMAT_S24: + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + return AFMT_QUERY; + } + + return AFMT_QUERY; +} + +/** + * Convert an OSS sample format to its MPD counterpart. Returns + * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart. + */ +static enum sample_format +sample_format_from_oss(int format) +{ + switch (format) { + case AFMT_S8: + return SAMPLE_FORMAT_S8; + + case AFMT_S16_NE: + return SAMPLE_FORMAT_S16; default: - /* not supported by OSS - fall back to 16 bit */ - od->audio_format.format = SAMPLE_FORMAT_S16; - tmp = AFMT_S16_NE; - break; + return SAMPLE_FORMAT_UNDEFINED; } +} - if (oss_set_param(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) { - g_set_error(error, oss_output_quark(), errno, - "OSS device \"%s\" does not support %u bit audio: %s", - od->device, tmp, strerror(errno)); +/** + * Set up the sample format, and attempts to find alternatives if the + * specified format is not supported. + */ +static bool +oss_setup_sample_format(int fd, struct audio_format *audio_format, + GError **error_r) +{ + const char *const msg = "Failed to set sample format"; + int oss_format = sample_format_to_oss(audio_format->format); + enum oss_setup_result result = oss_format != AFMT_QUERY + ? oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, msg, error_r) + : UNSUPPORTED; + enum sample_format mpd_format; + switch (result) { + case SUCCESS: + mpd_format = sample_format_from_oss(oss_format); + if (mpd_format == SAMPLE_FORMAT_UNDEFINED) + break; + + audio_format->format = mpd_format; + return true; + + case ERROR: return false; + + case UNSUPPORTED: + break; } - return true; + /* the requested sample format is not available - probe for + other formats supported by MPD */ + + static const enum sample_format sample_formats[] = { + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED /* sentinel */ + }; + + for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { + mpd_format = sample_formats[i]; + if (mpd_format == audio_format->format) + /* don't try that again */ + continue; + + oss_format = sample_format_to_oss(mpd_format); + if (oss_format == AFMT_QUERY) + /* not supported by this OSS version */ + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, msg, error_r); + switch (result) { + case SUCCESS: + mpd_format = sample_format_from_oss(oss_format); + if (mpd_format == SAMPLE_FORMAT_UNDEFINED) + break; + + audio_format->format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; } +/** + * Sets up the OSS device which was opened before. + */ static bool -oss_open(struct oss_data *od, GError **error) +oss_setup(struct oss_data *od, struct audio_format *audio_format, + GError **error_r) { - bool success; + return oss_setup_channels(od->fd, audio_format, error_r) && + oss_setup_sample_rate(od->fd, audio_format, error_r) && + oss_setup_sample_format(od->fd, audio_format, error_r); +} + +/** + * Reopen the device with the saved audio_format, without any probing. + */ +static bool +oss_reopen(struct oss_data *od, GError **error_r) +{ + assert(od->fd < 0); od->fd = open_cloexec(od->device, O_WRONLY, 0); if (od->fd < 0) { - g_set_error(error, oss_output_quark(), errno, + g_set_error(error_r, oss_output_quark(), errno, "Error opening OSS device \"%s\": %s", od->device, strerror(errno)); return false; } - success = oss_setup(od, error); - if (!success) { + enum oss_setup_result result; + + const char *const msg1 = "Failed to set channel count"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, + od->audio_format.channels, msg1, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg1); + return false; + } + + const char *const msg2 = "Failed to set sample rate"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, + od->audio_format.sample_rate, msg2, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg2); + return false; + } + + const char *const msg3 = "Failed to set sample format"; + assert(sample_format_to_oss(od->audio_format.format) != AFMT_QUERY); + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, + sample_format_to_oss(od->audio_format.format), + msg3, error_r); + if (result != SUCCESS) { oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg3); return false; } @@ -535,18 +553,23 @@ oss_open(struct oss_data *od, GError **error) static bool oss_output_open(void *data, struct audio_format *audio_format, GError **error) { - bool ret; struct oss_data *od = data; - od->audio_format = *audio_format; - - ret = oss_open(od, error); - if (!ret) + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + g_set_error(error, oss_output_quark(), errno, + "Error opening OSS device \"%s\": %s", + od->device, strerror(errno)); return false; + } - *audio_format = od->audio_format; + if (!oss_setup(od, audio_format, error)) { + oss_close(od); + return false; + } - return ret; + od->audio_format = *audio_format; + return true; } static void @@ -575,7 +598,7 @@ oss_output_play(void *data, const void *chunk, size_t size, GError **error) ssize_t ret; /* reopen the device since it was closed by dropBufferedAudio */ - if (od->fd < 0 && !oss_open(od, error)) + if (od->fd < 0 && !oss_reopen(od, error)) return 0; while (true) { |