output/oss: rewrite of the audio format detection
Removed the "supported" / "unsupported" arrays, and auto-detect on every open() call, just like the ALSA output plugin.
This commit is contained in:
parent
5fa1c703de
commit
67c48f66a6
@ -30,6 +30,7 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#undef G_LOG_DOMAIN
|
#undef G_LOG_DOMAIN
|
||||||
#define G_LOG_DOMAIN "oss"
|
#define G_LOG_DOMAIN "oss"
|
||||||
@ -43,24 +44,12 @@
|
|||||||
struct oss_data {
|
struct oss_data {
|
||||||
int fd;
|
int fd;
|
||||||
const char *device;
|
const char *device;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current input audio format. This is needed to reopen
|
||||||
|
* the device after cancel().
|
||||||
|
*/
|
||||||
struct audio_format audio_format;
|
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,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,188 +61,6 @@ oss_output_quark(void)
|
|||||||
return g_quark_from_static_string("oss_output");
|
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 *
|
static struct oss_data *
|
||||||
oss_data_new(void)
|
oss_data_new(void)
|
||||||
{
|
{
|
||||||
@ -262,38 +69,12 @@ oss_data_new(void)
|
|||||||
ret->device = NULL;
|
ret->device = NULL;
|
||||||
ret->fd = -1;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
oss_data_free(struct oss_data *od)
|
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);
|
g_free(od);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,37 +198,6 @@ oss_output_finish(void *data)
|
|||||||
oss_data_free(od);
|
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
|
static void
|
||||||
oss_close(struct oss_data *od)
|
oss_close(struct oss_data *od)
|
||||||
{
|
{
|
||||||
@ -456,76 +206,344 @@ oss_close(struct oss_data *od)
|
|||||||
od->fd = -1;
|
od->fd = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tri-state type for oss_try_ioctl().
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
assert(fd >= 0);
|
||||||
|
assert(value_r != NULL);
|
||||||
|
assert(msg != NULL);
|
||||||
|
assert(error_r == NULL || *error_r == NULL);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
return AFMT_S8;
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_S16:
|
||||||
|
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:
|
||||||
|
return SAMPLE_FORMAT_UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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.
|
* Sets up the OSS device which was opened before.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
oss_setup(struct oss_data *od, GError **error)
|
oss_setup(struct oss_data *od, struct audio_format *audio_format,
|
||||||
|
GError **error_r)
|
||||||
{
|
{
|
||||||
int tmp;
|
return oss_setup_channels(od->fd, audio_format, error_r) &&
|
||||||
|
oss_setup_sample_rate(od->fd, audio_format, error_r) &&
|
||||||
tmp = od->audio_format.channels;
|
oss_setup_sample_format(od->fd, audio_format, error_r);
|
||||||
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));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
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));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
od->audio_format.sample_rate = tmp;
|
|
||||||
|
|
||||||
switch (od->audio_format.format) {
|
|
||||||
case SAMPLE_FORMAT_S8:
|
|
||||||
tmp = AFMT_S8;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SAMPLE_FORMAT_S16:
|
|
||||||
tmp = AFMT_S16_NE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
/* not supported by OSS - fall back to 16 bit */
|
|
||||||
od->audio_format.format = SAMPLE_FORMAT_S16;
|
|
||||||
tmp = AFMT_S16_NE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reopen the device with the saved audio_format, without any probing.
|
||||||
|
*/
|
||||||
static bool
|
static bool
|
||||||
oss_open(struct oss_data *od, GError **error)
|
oss_reopen(struct oss_data *od, GError **error_r)
|
||||||
{
|
{
|
||||||
bool success;
|
assert(od->fd < 0);
|
||||||
|
|
||||||
od->fd = open_cloexec(od->device, O_WRONLY, 0);
|
od->fd = open_cloexec(od->device, O_WRONLY, 0);
|
||||||
if (od->fd < 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",
|
"Error opening OSS device \"%s\": %s",
|
||||||
od->device, strerror(errno));
|
od->device, strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
success = oss_setup(od, error);
|
enum oss_setup_result result;
|
||||||
if (!success) {
|
|
||||||
|
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);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,18 +553,23 @@ oss_open(struct oss_data *od, GError **error)
|
|||||||
static bool
|
static bool
|
||||||
oss_output_open(void *data, struct audio_format *audio_format, GError **error)
|
oss_output_open(void *data, struct audio_format *audio_format, GError **error)
|
||||||
{
|
{
|
||||||
bool ret;
|
|
||||||
struct oss_data *od = data;
|
struct oss_data *od = data;
|
||||||
|
|
||||||
od->audio_format = *audio_format;
|
od->fd = open_cloexec(od->device, O_WRONLY, 0);
|
||||||
|
if (od->fd < 0) {
|
||||||
ret = oss_open(od, error);
|
g_set_error(error, oss_output_quark(), errno,
|
||||||
if (!ret)
|
"Error opening OSS device \"%s\": %s",
|
||||||
|
od->device, strerror(errno));
|
||||||
return false;
|
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
|
static void
|
||||||
@ -575,7 +598,7 @@ oss_output_play(void *data, const void *chunk, size_t size, GError **error)
|
|||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
|
|
||||||
/* reopen the device since it was closed by dropBufferedAudio */
|
/* 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;
|
return 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
Loading…
Reference in New Issue
Block a user