diff --git a/Makefile.am b/Makefile.am index 7bcff13..d9e0bcc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,17 +55,20 @@ lib_LTLIBRARIES += src/libpcaudio.la src_libpcaudio_la_LDFLAGS = -version-info $(LIBPCAUDIO_VERSION) \ ${ALSA_LIBS} \ + ${SNDIO_LIBS} \ ${PULSEAUDIO_LIBS} \ ${QSA_LIBS} \ ${COREAUDIO_LIBS} src_libpcaudio_la_CFLAGS = ${AM_CFLAGS} \ ${ALSA_CFLAGS} \ + ${SNDIO_CFLAGS} \ ${PULSEAUDIO_CFLAGS} \ ${COREAUDIO_CFLAGS} src_libpcaudio_la_SOURCES = \ src/alsa.c \ + src/sndio.c \ src/qsa.c \ src/oss.c \ src/pulseaudio.c \ diff --git a/README.md b/README.md index bdfa431..c60de8f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ devices. It supports: | PulseAudio | Linux | | QSA | QNX | | XAudio2 | Windows | +| sndio | BSD, Linux | See the [CHANGELOG](CHANGELOG.md) for a description of the changes in the various releases. @@ -53,6 +54,7 @@ Optional Libraries: |----------------|--------------------------------------------| | alsa | `sudo apt-get install libasound2-dev` | | pulseaudio | `sudo apt-get install libpulse-dev` | +| sndio | `sudo apt-get install libsndio-dev` | ### Mac OS diff --git a/configure.ac b/configure.ac index 29e62cb..0f065de 100644 --- a/configure.ac +++ b/configure.ac @@ -66,6 +66,29 @@ AS_IF([test "x$with_alsa" = "xno"], [ AC_SUBST(ALSA_CFLAGS) AC_SUBST(ALSA_LIBS) +dnl ================================================================ +dnl sndio checks. +dnl ================================================================ + +AC_ARG_WITH([sndio], + [AS_HELP_STRING([--with-sndio], [support for sndio audio output @<:@default=yes@:>@])], + []) + +AS_IF([test "x$with_sndio" = "xno"], [ + echo "Disabling sndio audio output support"; + have_sndio=no + ], [ + PKG_CHECK_MODULES(SNDIO, [sndio], + [ + AC_DEFINE(HAVE_SNDIO_H, [], [Do we have sndio]) + have_sndio=yes + ],[ + have_sndio=no + ])]) + +AC_SUBST(SNDIO_CFLAGS) +AC_SUBST(SNDIO_LIBS) + dnl ================================================================ dnl QSA checks. dnl ================================================================ @@ -172,4 +195,5 @@ AC_MSG_NOTICE([ QSA support: ${have_qsa} Coreaudio support: ${have_coreaudio} OSS support: ${have_oss} + sndio support: ${have_sndio} ]) diff --git a/src/audio.c b/src/audio.c index 745d7b6..d5952fc 100644 --- a/src/audio.c +++ b/src/audio.c @@ -98,6 +98,8 @@ create_audio_device_object(const char *device, #else if ((object = create_pulseaudio_object(device, application_name, description)) != NULL) return object; + if ((object = create_sndio_object(device, application_name, description)) != NULL) + return object; if ((object = create_alsa_object(device, application_name, description)) != NULL) return object; if ((object = create_qsa_object(device, application_name, description)) != NULL) diff --git a/src/audio_priv.h b/src/audio_priv.h index 669a037..633da4a 100644 --- a/src/audio_priv.h +++ b/src/audio_priv.h @@ -100,6 +100,11 @@ create_pulseaudio_object(const char *device, const char *application_name, const char *description); +struct audio_object * +create_sndio_object(const char *device, + const char *application_name, + const char *description); + struct audio_object * create_alsa_object(const char *device, const char *application_name, diff --git a/src/sndio.c b/src/sndio.c new file mode 100644 index 0000000..69f2bff --- /dev/null +++ b/src/sndio.c @@ -0,0 +1,284 @@ +/* sndio Output. + * + * Copyright (C) 2021 Haelwenn (lanodan) Monnier + * + * This file is part of pcaudiolib. + * + * pcaudiolib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * pcaudiolib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pcaudiolib. If not, see . + */ + +#include "config.h" +#include "audio_priv.h" + +#ifdef HAVE_SNDIO_H + +#include +#include /* strdup */ + +struct sndio_object +{ + struct audio_object vtable; + struct sio_hdl *hdl; + char *application_name; + char *description; +}; + +#define to_sndio_object(object) container_of(object, struct sndio_object, vtable) + +int +audio_format_get_bits(enum audio_object_format format) { + switch(format) + { + case AUDIO_OBJECT_FORMAT_S8: + case AUDIO_OBJECT_FORMAT_U8: + return 8; + break; + + case AUDIO_OBJECT_FORMAT_S16LE: + case AUDIO_OBJECT_FORMAT_S16BE: + case AUDIO_OBJECT_FORMAT_U16LE: + case AUDIO_OBJECT_FORMAT_U16BE: + return 16; + break; + + case AUDIO_OBJECT_FORMAT_S18LE: + case AUDIO_OBJECT_FORMAT_S18BE: + case AUDIO_OBJECT_FORMAT_U18LE: + case AUDIO_OBJECT_FORMAT_U18BE: + return 18; + break; + + case AUDIO_OBJECT_FORMAT_S20LE: + case AUDIO_OBJECT_FORMAT_S20BE: + case AUDIO_OBJECT_FORMAT_U20LE: + case AUDIO_OBJECT_FORMAT_U20BE: + return 20; + break; + + case AUDIO_OBJECT_FORMAT_S24LE: + case AUDIO_OBJECT_FORMAT_S24BE: + case AUDIO_OBJECT_FORMAT_U24LE: + case AUDIO_OBJECT_FORMAT_U24BE: + return 24; + break; + + case AUDIO_OBJECT_FORMAT_S24_32LE: + case AUDIO_OBJECT_FORMAT_S24_32BE: + case AUDIO_OBJECT_FORMAT_U24_32LE: + case AUDIO_OBJECT_FORMAT_U24_32BE: + return -1; // ??? + break; + + case AUDIO_OBJECT_FORMAT_S32LE: + case AUDIO_OBJECT_FORMAT_S32BE: + case AUDIO_OBJECT_FORMAT_U32LE: + case AUDIO_OBJECT_FORMAT_U32BE: + return 32; + break; + default: + return -1; + } +} + +int +audio_format_is_sig(enum audio_object_format format) { + switch(format) + { + case AUDIO_OBJECT_FORMAT_S8: + case AUDIO_OBJECT_FORMAT_S16LE: + case AUDIO_OBJECT_FORMAT_S16BE: + case AUDIO_OBJECT_FORMAT_S18LE: + case AUDIO_OBJECT_FORMAT_S18BE: + case AUDIO_OBJECT_FORMAT_S20LE: + case AUDIO_OBJECT_FORMAT_S20BE: + case AUDIO_OBJECT_FORMAT_S24LE: + case AUDIO_OBJECT_FORMAT_S24BE: + case AUDIO_OBJECT_FORMAT_S24_32LE: + case AUDIO_OBJECT_FORMAT_S24_32BE: + case AUDIO_OBJECT_FORMAT_S32LE: + case AUDIO_OBJECT_FORMAT_S32BE: + return 1; + case AUDIO_OBJECT_FORMAT_U8: + case AUDIO_OBJECT_FORMAT_U16LE: + case AUDIO_OBJECT_FORMAT_U16BE: + case AUDIO_OBJECT_FORMAT_U18LE: + case AUDIO_OBJECT_FORMAT_U18BE: + case AUDIO_OBJECT_FORMAT_U20LE: + case AUDIO_OBJECT_FORMAT_U20BE: + case AUDIO_OBJECT_FORMAT_U24LE: + case AUDIO_OBJECT_FORMAT_U24BE: + case AUDIO_OBJECT_FORMAT_U24_32LE: + case AUDIO_OBJECT_FORMAT_U24_32BE: + case AUDIO_OBJECT_FORMAT_U32LE: + case AUDIO_OBJECT_FORMAT_U32BE: + return 0; + break; + default: + return -1; + } +} + +int +audio_format_is_le(enum audio_object_format format) { + switch(format) + { + case AUDIO_OBJECT_FORMAT_S8: + case AUDIO_OBJECT_FORMAT_U8: + case AUDIO_OBJECT_FORMAT_S16BE: + case AUDIO_OBJECT_FORMAT_U16BE: + case AUDIO_OBJECT_FORMAT_S18BE: + case AUDIO_OBJECT_FORMAT_U18BE: + case AUDIO_OBJECT_FORMAT_S20BE: + case AUDIO_OBJECT_FORMAT_U20BE: + case AUDIO_OBJECT_FORMAT_S24BE: + case AUDIO_OBJECT_FORMAT_U24BE: + case AUDIO_OBJECT_FORMAT_S24_32BE: + case AUDIO_OBJECT_FORMAT_U24_32BE: + return 0; + + case AUDIO_OBJECT_FORMAT_S16LE: + case AUDIO_OBJECT_FORMAT_U16LE: + case AUDIO_OBJECT_FORMAT_S18LE: + case AUDIO_OBJECT_FORMAT_U18LE: + case AUDIO_OBJECT_FORMAT_S20LE: + case AUDIO_OBJECT_FORMAT_U20LE: + case AUDIO_OBJECT_FORMAT_S24LE: + case AUDIO_OBJECT_FORMAT_U24LE: + case AUDIO_OBJECT_FORMAT_S24_32LE: + case AUDIO_OBJECT_FORMAT_U24_32LE: + return 1; + + default: + return -1; + } +} + +int +sndio_object_open(struct audio_object *object, + enum audio_object_format format, + uint32_t rate, + uint8_t channels) +{ + struct sndio_object *self = to_sndio_object(object); + struct sio_par par; + if (self->hdl) + return -1; + + self->hdl = sio_open(self->application_name, SIO_PLAY, 0); + if(!self->hdl) { + return -1; + } + + sio_initpar(&par); + + int bits = audio_format_get_bits(format); + if(bits != -1) { + par.bits = bits; + } + + int sig = audio_format_is_sig(format); + if(sig != -1) { + par.sig = sig; + } + + int le = audio_format_is_le(format); + if(le != -1) { + par.le = le; + } + + par.rate = rate; + par.rchan = 0; + par.pchan = channels; + + if(!sio_setpar(self->hdl, &par)) + return -1; + + if(!sio_getpar(self->hdl, &par)) + return -1; + + if(par.bits != bits) + return -1; + if(par.sig != sig) + return -1; + if(par.le != le) + return -1; + + return 0; +} + +void +sndio_object_close(struct audio_object *object) +{ + struct sndio_object *self = to_sndio_object(object); + + if (self->hdl) { + sio_close(self->hdl); + self->hdl = NULL; + } +} + +void +sndio_object_destroy(struct audio_object *object) +{ + struct sndio_object *self = to_sndio_object(object); + + free(self->application_name); + free(self->description); + free(self); +} + +int +sndio_object_write(struct audio_object *object, + const void *data, + size_t bytes) +{ + struct sndio_object *self = to_sndio_object(object); + if (!self->hdl) + return 0; + + return sio_write(self->hdl, data, bytes) == bytes; +} + +struct audio_object * +create_sndio_object(const char *device, + const char *application_name, + const char *description) +{ + struct sndio_object *self = malloc(sizeof(struct sndio_object)); + if (!self) + return NULL; + + self->hdl = NULL; + self->application_name = application_name ? strdup(application_name) : NULL; + self->description = description ? strdup(description) : NULL; + + self->vtable.open = sndio_object_open; + self->vtable.close = sndio_object_close; + self->vtable.destroy = sndio_object_destroy; + self->vtable.write = sndio_object_write; + + return &self->vtable; +} + +#else + +struct audio_object * +create_sndio_object(const char *device, + const char *application_name, + const char *description) +{ + return NULL; +} + +#endif