Commit 7057f845 authored by David Reid's avatar David Reid

Initial work on sndio backend.

parent 88a694af
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
// - ALSA // - ALSA
// - PulseAudio // - PulseAudio
// - JACK // - JACK
// - sndio (BSD)
// - audioio (NetBSD) // - audioio (NetBSD)
// - OSS (FreeBSD) // - OSS (FreeBSD)
// - OpenSL|ES (Android only) // - OpenSL|ES (Android only)
...@@ -180,6 +181,9 @@ ...@@ -180,6 +181,9 @@
// #define MAL_NO_COREAUDIO // #define MAL_NO_COREAUDIO
// Disables the Core Audio backend. // Disables the Core Audio backend.
// //
// #define MAL_NO_SNDIO
// Disables the sndio backend.
//
// #define MAL_NO_AUDIOIO // #define MAL_NO_AUDIOIO
// Disables the audioio backend. // Disables the audioio backend.
// //
...@@ -576,6 +580,7 @@ typedef enum ...@@ -576,6 +580,7 @@ typedef enum
mal_standard_channel_map_rfc3551, // Based off AIFF. mal_standard_channel_map_rfc3551, // Based off AIFF.
mal_standard_channel_map_flac, mal_standard_channel_map_flac,
mal_standard_channel_map_vorbis, mal_standard_channel_map_vorbis,
mal_standard_channel_map_sndio, // www.sndio.org/tips.html
mal_standard_channel_map_default = mal_standard_channel_map_microsoft mal_standard_channel_map_default = mal_standard_channel_map_microsoft
} mal_standard_channel_map; } mal_standard_channel_map;
...@@ -1145,6 +1150,9 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form ...@@ -1145,6 +1150,9 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form
#if defined(MAL_ANDROID) #if defined(MAL_ANDROID)
#define MAL_SUPPORT_OPENSL #define MAL_SUPPORT_OPENSL
#endif #endif
#if defined(MAL_BSD)
#define MAL_SUPPORT_SNDIO // sndio can be used on all flavours of BSD as far as I'm aware of.
#endif
#if defined(__NetBSD__) #if defined(__NetBSD__)
#define MAL_SUPPORT_AUDIOIO // Only support audioio on platforms with known support. #define MAL_SUPPORT_AUDIOIO // Only support audioio on platforms with known support.
#endif #endif
...@@ -1186,6 +1194,9 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form ...@@ -1186,6 +1194,9 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form
#if !defined(MAL_NO_COREAUDIO) && defined(MAL_SUPPORT_COREAUDIO) #if !defined(MAL_NO_COREAUDIO) && defined(MAL_SUPPORT_COREAUDIO)
#define MAL_ENABLE_COREAUDIO #define MAL_ENABLE_COREAUDIO
#endif #endif
#if !defined(MAL_NO_SNDIO) && defined(MAL_SUPPORT_SNDIO)
#define MAL_ENABLE_SNDIO
#endif
#if !defined(MAL_NO_AUDIOIO) && defined(MAL_SUPPORT_AUDIOIO) #if !defined(MAL_NO_AUDIOIO) && defined(MAL_SUPPORT_AUDIOIO)
#define MAL_ENABLE_AUDIOIO #define MAL_ENABLE_AUDIOIO
#endif #endif
...@@ -1216,6 +1227,7 @@ typedef enum ...@@ -1216,6 +1227,7 @@ typedef enum
mal_backend_pulseaudio, mal_backend_pulseaudio,
mal_backend_jack, mal_backend_jack,
mal_backend_coreaudio, mal_backend_coreaudio,
mal_backend_sndio,
mal_backend_audioio, mal_backend_audioio,
mal_backend_oss, mal_backend_oss,
mal_backend_opensl, mal_backend_opensl,
...@@ -1351,6 +1363,9 @@ typedef union ...@@ -1351,6 +1363,9 @@ typedef union
#ifdef MAL_SUPPORT_COREAUDIO #ifdef MAL_SUPPORT_COREAUDIO
char coreaudio[256]; // Core Audio uses a string for identification. char coreaudio[256]; // Core Audio uses a string for identification.
#endif #endif
#ifdef MAL_SUPPORT_SNDIO
char sndio[256]; // "snd/0", etc.
#endif
#ifdef MAL_SUPPORT_AUDIOIO #ifdef MAL_SUPPORT_AUDIOIO
char audioio[256]; // "/dev/audio", etc. char audioio[256]; // "/dev/audio", etc.
#endif #endif
...@@ -1419,6 +1434,13 @@ typedef struct ...@@ -1419,6 +1434,13 @@ typedef struct
{ {
const char* pStreamName; const char* pStreamName;
} pulse; } pulse;
struct
{
const char* hostname;
const char* unit;
const char* option;
} sndio;
} mal_device_config; } mal_device_config;
typedef struct typedef struct
...@@ -1662,6 +1684,29 @@ struct mal_context ...@@ -1662,6 +1684,29 @@ struct mal_context
mal_proc AudioUnitRender; mal_proc AudioUnitRender;
} coreaudio; } coreaudio;
#endif #endif
#ifdef MAL_SUPPORT_SNDIO
struct
{
mal_handle sndioSO;
mal_proc sio_open;
mal_proc sio_close;
mal_proc sio_setpar;
mal_proc sio_getpar;
mal_proc sio_getcap;
mal_proc sio_start;
mal_proc sio_stop;
mal_proc sio_read;
mal_proc sio_write;
mal_proc sio_onmove;
mal_proc sio_nfds;
mal_proc sio_pollfd;
mal_proc sio_revents;
mal_proc sio_eof;
mal_proc sio_setvol;
mal_proc sio_onvol;
mal_proc sio_initpar;
} sndio;
#endif
#ifdef MAL_SUPPORT_AUDIOIO #ifdef MAL_SUPPORT_AUDIOIO
struct struct
{ {
...@@ -1960,6 +2005,15 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -1960,6 +2005,15 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
/*AudioBufferList**/ mal_ptr pAudioBufferList; // Only used for input devices. /*AudioBufferList**/ mal_ptr pAudioBufferList; // Only used for input devices.
} coreaudio; } coreaudio;
#endif #endif
#ifdef MAL_SUPPORT_SNDIO
struct
{
mal_ptr handle;
mal_uint32 fragmentSizeInFrames;
mal_bool32 breakFromMainLoop;
void* pIntermediaryBuffer;
} sndio;
#endif
#ifdef MAL_SUPPORT_AUDIOIO #ifdef MAL_SUPPORT_AUDIOIO
struct struct
{ {
...@@ -2042,6 +2096,7 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -2042,6 +2096,7 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
// - DirectSound // - DirectSound
// - WinMM // - WinMM
// - Core Audio (Apple) // - Core Audio (Apple)
// - sndio
// - audioio // - audioio
// - OSS // - OSS
// - PulseAudio // - PulseAudio
...@@ -2941,6 +2996,23 @@ static MAL_INLINE mal_bool32 mal_has_neon() ...@@ -2941,6 +2996,23 @@ static MAL_INLINE mal_bool32 mal_has_neon()
} }
static MAL_INLINE mal_bool32 mal_is_little_endian()
{
#if defined(MAL_X86) || defined(MAL_X64)
return MAL_TRUE;
#else
int n = 1;
return (*(char*)&n) == 1;
#endif
}
static MAL_INLINE mal_bool32 mal_is_big_endian()
{
return !mal_is_little_endian();
}
#ifndef MAL_PI #ifndef MAL_PI
#define MAL_PI 3.14159265358979323846264f #define MAL_PI 3.14159265358979323846264f
#endif #endif
...@@ -3021,6 +3093,7 @@ mal_format g_malFormatPriorities[] = { ...@@ -3021,6 +3093,7 @@ mal_format g_malFormatPriorities[] = {
}; };
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
// Standard Library Stuff // Standard Library Stuff
...@@ -3544,6 +3617,17 @@ void mal_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_ ...@@ -3544,6 +3617,17 @@ void mal_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_
#endif #endif
mal_uint32 mal_get_standard_sample_rate_priority_index(mal_uint32 sampleRate) // Lower = higher priority
{
for (mal_uint32 i = 0; i < mal_countof(g_malStandardSampleRatePriorities); ++i) {
if (g_malStandardSampleRatePriorities[i] == sampleRate) {
return i;
}
}
return (mal_uint32)-1;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
...@@ -3612,6 +3696,9 @@ void mal_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_ ...@@ -3612,6 +3696,9 @@ void mal_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_
#ifdef MAL_ENABLE_COREAUDIO #ifdef MAL_ENABLE_COREAUDIO
#define MAL_HAS_COREAUDIO #define MAL_HAS_COREAUDIO
#endif #endif
#ifdef MAL_ENABLE_SNDIO
#define MAL_HAS_SNDIO
#endif
#ifdef MAL_ENABLE_AUDIOIO #ifdef MAL_ENABLE_AUDIOIO
#define MAL_HAS_AUDIOIO // When enabled, always assume audioio is available. #define MAL_HAS_AUDIOIO // When enabled, always assume audioio is available.
#endif #endif
...@@ -3653,6 +3740,46 @@ void mal_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_ ...@@ -3653,6 +3740,46 @@ void mal_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_
#define MAL_HAS_NULL // Everything supports the null backend. #define MAL_HAS_NULL // Everything supports the null backend.
#endif #endif
const mal_backend g_malDefaultBackends[] = {
mal_backend_wasapi,
mal_backend_dsound,
mal_backend_winmm,
mal_backend_coreaudio,
mal_backend_sndio,
mal_backend_audioio,
mal_backend_oss,
mal_backend_pulseaudio,
mal_backend_alsa,
mal_backend_jack,
mal_backend_opensl,
mal_backend_openal,
mal_backend_sdl,
mal_backend_null
};
const char* mal_get_backend_name(mal_backend backend)
{
switch (backend)
{
case mal_backend_null: return "Null";
case mal_backend_wasapi: return "WASAPI";
case mal_backend_dsound: return "DirectSound";
case mal_backend_winmm: return "WinMM";
case mal_backend_alsa: return "ALSA";
case mal_backend_pulseaudio: return "PulseAudio";
case mal_backend_jack: return "JACK";
case mal_backend_coreaudio: return "Core Audio";
case mal_backend_sndio: return "sndio";
case mal_backend_audioio: return "audioio";
case mal_backend_oss: return "OSS";
case mal_backend_opensl: return "OpenSL|ES";
case mal_backend_openal: return "OpenAL";
case mal_backend_sdl: return "SDL";
default: return "Unknown";
}
}
#ifdef MAL_WIN32 #ifdef MAL_WIN32
#define MAL_THREADCALL WINAPI #define MAL_THREADCALL WINAPI
...@@ -4276,28 +4403,6 @@ mal_uint32 mal_get_closest_standard_sample_rate(mal_uint32 sampleRateIn) ...@@ -4276,28 +4403,6 @@ mal_uint32 mal_get_closest_standard_sample_rate(mal_uint32 sampleRateIn)
} }
const char* mal_get_backend_name(mal_backend backend)
{
switch (backend)
{
case mal_backend_null: return "Null";
case mal_backend_wasapi: return "WASAPI";
case mal_backend_dsound: return "DirectSound";
case mal_backend_winmm: return "WinMM";
case mal_backend_alsa: return "ALSA";
case mal_backend_pulseaudio: return "PulseAudio";
case mal_backend_jack: return "JACK";
case mal_backend_coreaudio: return "Core Audio";
case mal_backend_audioio: return "audioio";
case mal_backend_oss: return "OSS";
case mal_backend_opensl: return "OpenSL|ES";
case mal_backend_openal: return "OpenAL";
case mal_backend_sdl: return "SDL";
default: return "Unknown";
}
}
typedef struct typedef struct
{ {
mal_uint8* pInputFrames; mal_uint8* pInputFrames;
...@@ -14841,17 +14946,35 @@ mal_result mal_context_init__coreaudio(mal_context* pContext) ...@@ -14841,17 +14946,35 @@ mal_result mal_context_init__coreaudio(mal_context* pContext)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
// audioio Backend // sndio Backend
// //
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_HAS_AUDIOIO #ifdef MAL_HAS_SNDIO
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/audioio.h> #include <sys/audioio.h>
#include <sndio.h>
void mal_construct_device_id__audioio(char* id, size_t idSize, const char* base, int deviceIndex) // A note on sndio and device capabilities.
//
// Because sndio performs it's own data conversions it reports support a wide range of formats, channels
// and sample rates. The only way I can think of to find the _actual_ device caps is to use sys/audioio.h.
//
// For the moment while I'm still figuring it all out I'm pretending _all_ hardware is s16/stereo/48000.
//#define MAL_SNDIO_USE_AUDIOIO_FOR_DEVICE_CAPS /* <-- Experimenting. */
#ifndef MAL_SNDIO_DEVICE_FORMAT
#define MAL_SNDIO_DEVICE_FORMAT mal_format_s16
#endif
#ifndef MAL_SNDIO_DEVICE_CHANNELS
#define MAL_SNDIO_DEVICE_CHANNELS 2
#endif
#ifndef MAL_SNDIO_DEVICE_SAMPLE_RATE
#define MAL_SNDIO_DEVICE_SAMPLE_RATE 48000
#endif
/*
void mal_construct_device_id__sndio(char* id, size_t idSize, const char* base, int deviceIndex)
{ {
mal_assert(id != NULL); mal_assert(id != NULL);
mal_assert(idSize > 0); mal_assert(idSize > 0);
...@@ -14864,7 +14987,7 @@ void mal_construct_device_id__audioio(char* id, size_t idSize, const char* base, ...@@ -14864,7 +14987,7 @@ void mal_construct_device_id__audioio(char* id, size_t idSize, const char* base,
mal_itoa_s(deviceIndex, id+baseLen, idSize-baseLen, 10); mal_itoa_s(deviceIndex, id+baseLen, idSize-baseLen, 10);
} }
mal_result mal_extract_device_index_from_id__audioio(const char* id, const char* base, int* pIndexOut) mal_result mal_extract_device_index_from_id__sndio(const char* id, const char* base, int* pIndexOut)
{ {
mal_assert(id != NULL); mal_assert(id != NULL);
mal_assert(base != NULL); mal_assert(base != NULL);
...@@ -14891,166 +15014,961 @@ mal_result mal_extract_device_index_from_id__audioio(const char* id, const char* ...@@ -14891,166 +15014,961 @@ mal_result mal_extract_device_index_from_id__audioio(const char* id, const char*
return MAL_SUCCESS; return MAL_SUCCESS;
} }
*/
mal_bool32 mal_context_is_device_id_equal__audioio(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) mal_format mal_format_from_sio_enc__sndio(unsigned int bits, unsigned int bps, unsigned int sig, unsigned int le, unsigned int msb)
{
mal_assert(pContext != NULL);
mal_assert(pID0 != NULL);
mal_assert(pID1 != NULL);
(void)pContext;
return mal_strcmp(pID0->audioio, pID1->audioio) == 0;
}
mal_format mal_format_from_encoding__audioio(unsigned int encoding, unsigned int precision)
{ {
if (precision == 8 && (encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR_LE || encoding == AUDIO_ENCODING_ULINEAR_BE)) { // We only support native-endian right now.
return mal_format_u8; if ((mal_is_little_endian() && le == 0) || (mal_is_big_endian() && le == 1)) {
} else { return mal_format_unknown;
if (encoding == AUDIO_ENCODING_SLINEAR_LE) {
if (precision == 16) {
return mal_format_s16;
} else if (precision == 24) {
return mal_format_s24;
} else if (precision == 32) {
return mal_format_s32;
}
}
} }
return mal_format_unknown; // Encoding not supported.
}
mal_format mal_format_from_prinfo__audioio(struct audio_prinfo* prinfo)
{
return mal_format_from_encoding__audioio(prinfo->encoding, prinfo->precision);
}
mal_result mal_context_get_device_info_from_fd__audioio(mal_context* pContext, mal_device_type deviceType, int fd, mal_device_info* pInfoOut)
{
mal_assert(pContext != NULL);
mal_assert(fd >= 0);
mal_assert(pInfoOut != NULL);
(void)pContext; if (bits == 8 && bps == 1 && sig == 0) {
(void)deviceType; return mal_format_u8;
audio_device_t fdDevice;
if (ioctl(fd, AUDIO_GETDEV, &fdDevice) < 0) {
return MAL_ERROR; // Failed to retrieve device info.
} }
if (bits == 16 && bps == 2 && sig == 1) {
// Name. return mal_format_s16;
mal_strcpy_s(pInfoOut->name, sizeof(pInfoOut->name), fdDevice.name);
// Supported formats. We get this by looking at the encodings.
int counter = 0;
for (;;) {
audio_encoding_t encoding;
mal_zero_object(&encoding);
encoding.index = counter;
if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) {
break;
}
mal_format format = mal_format_from_encoding__audioio(encoding.encoding, encoding.precision);
if (format != mal_format_unknown) {
pInfoOut->formats[pInfoOut->formatCount++] = format;
}
counter += 1;
} }
if (bits == 24 && bps == 3 && sig == 1) {
audio_info_t fdInfo; return mal_format_s24;
if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) {
return MAL_ERROR;
} }
if (bits == 24 && bps == 4 && sig == 1 && msb == 0) {
if (deviceType == mal_device_type_playback) { //return mal_format_s24_32;
pInfoOut->minChannels = fdInfo.play.channels; }
pInfoOut->maxChannels = fdInfo.play.channels; if (bits == 32 && bps == 4 && sig == 1) {
pInfoOut->minSampleRate = fdInfo.play.sample_rate; return mal_format_s32;
pInfoOut->maxSampleRate = fdInfo.play.sample_rate;
} else {
pInfoOut->minChannels = fdInfo.record.channels;
pInfoOut->maxChannels = fdInfo.record.channels;
pInfoOut->minSampleRate = fdInfo.record.sample_rate;
pInfoOut->maxSampleRate = fdInfo.record.sample_rate;
} }
return MAL_SUCCESS; return mal_format_unknown;
} }
mal_result mal_context_enumerate_devices__audioio(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) mal_format mal_find_best_format_from_sio_cap__sndio(struct sio_cap* caps)
{ {
mal_assert(pContext != NULL); mal_assert(caps != NULL);
mal_assert(callback != NULL);
const int maxDevices = 64;
// Every device will be named "/dev/audioN", with a "/dev/audioctlN" equivalent. We use the "/dev/audioctlN"
// version here since we can open it even when another process has control of the "/dev/audioN" device.
char devpath[256];
for (int iDevice = 0; iDevice < maxDevices; ++iDevice) {
mal_strcpy_s(devpath, sizeof(devpath), "/dev/audioctl");
mal_itoa_s(iDevice, devpath+strlen(devpath), sizeof(devpath)-strlen(devpath), 10);
struct stat st; #ifdef MAL_SNDIO_USE_AUDIOIO_FOR_DEVICE_CAPS
if (stat(devpath, &st) == 0) { mal_format bestFormat = mal_format_unknown;
// The device exists, but we need to check if it's usable as playback and/or capture. for (unsigned int iConfig = 0; iConfig < caps->nconf; iConfig += 1) {
int fd; for (unsigned int iEncoding = 0; iEncoding < SIO_NENC; iEncoding += 1) {
mal_bool32 isTerminating = MAL_FALSE; if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) {
continue;
// Playback.
if (!isTerminating) {
fd = open(devpath, O_RDONLY, 0);
if (fd >= 0) {
// Supports playback.
mal_device_info deviceInfo;
mal_zero_object(&deviceInfo);
mal_construct_device_id__audioio(deviceInfo.id.audioio, sizeof(deviceInfo.id.audioio), "/dev/audio", iDevice);
if (mal_context_get_device_info_from_fd__audioio(pContext, mal_device_type_playback, fd, &deviceInfo) == MAL_SUCCESS) {
isTerminating = !callback(pContext, mal_device_type_playback, &deviceInfo, pUserData);
}
close(fd);
}
} }
// Capture. unsigned int bits = caps->enc[iEncoding].bits;
if (!isTerminating) { unsigned int bps = caps->enc[iEncoding].bps;
fd = open(devpath, O_WRONLY, 0); unsigned int sig = caps->enc[iEncoding].sig;
if (fd >= 0) { unsigned int le = caps->enc[iEncoding].le;
// Supports capture. unsigned int msb = caps->enc[iEncoding].msb;
mal_device_info deviceInfo; mal_format format = mal_format_from_sio_enc__sndio(bits, bps, sig, le, msb);
mal_zero_object(&deviceInfo); if (format == mal_format_unknown) {
mal_construct_device_id__audioio(deviceInfo.id.audioio, sizeof(deviceInfo.id.audioio), "/dev/audio", iDevice); continue; // Format not supported.
if (mal_context_get_device_info_from_fd__audioio(pContext, mal_device_type_capture, fd, &deviceInfo) == MAL_SUCCESS) {
isTerminating = !callback(pContext, mal_device_type_capture, &deviceInfo, pUserData);
}
close(fd);
}
} }
if (isTerminating) { if (bestFormat == mal_format_unknown) {
break; bestFormat = format;
} else {
if (mal_get_format_priority_index(bestFormat) > mal_get_format_priority_index(format)) { // <-- Lower = better.
bestFormat = format;
}
} }
} }
} }
return MAL_SUCCESS; return mal_format_unknown;
#else
(void)caps;
return MAL_SNDIO_DEVICE_FORMAT;
#endif
} }
mal_result mal_context_get_device_info__audioio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) mal_uint32 mal_find_best_channels_from_sio_cap__sndio(struct sio_cap* caps, mal_device_type deviceType, mal_format requiredFormat)
{ {
mal_assert(pContext != NULL); mal_assert(caps != NULL);
(void)shareMode; mal_assert(requiredFormat != mal_format_unknown);
// We need to open the "/dev/audioctlN" device to get the info. To do this we need to extract the number #ifdef MAL_SNDIO_USE_AUDIOIO_FOR_DEVICE_CAPS
// from the device ID which will be in "/dev/audioN" format. // Just pick whatever configuration has the most channels.
int fd = -1; mal_uint32 maxChannels = 0;
int deviceIndex = -1; for (unsigned int iConfig = 0; iConfig < caps->nconf; iConfig += 1) {
// The encoding should be of requiredFormat.
for (unsigned int iEncoding = 0; iEncoding < SIO_NENC; iEncoding += 1) {
if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) {
continue;
}
unsigned int bits = caps->enc[iEncoding].bits;
unsigned int bps = caps->enc[iEncoding].bps;
unsigned int sig = caps->enc[iEncoding].sig;
unsigned int le = caps->enc[iEncoding].le;
unsigned int msb = caps->enc[iEncoding].msb;
mal_format format = mal_format_from_sio_enc__sndio(bits, bps, sig, le, msb);
if (format != requiredFormat) {
continue;
}
// Getting here means the format is supported. Iterate over each channel count and grab the biggest one.
for (unsigned int iChannel = 0; iChannel < SIO_NCHAN; iChannel += 1) {
unsigned int chan = 0;
if (deviceType == mal_device_type_playback) {
chan = caps->confs[iConfig].pchan;
} else {
chan = caps->confs[iConfig].rchan;
}
if ((chan & (1UL << iChannel)) == 0) {
continue;
}
unsigned int channels;
if (deviceType == mal_device_type_playback) {
channels = caps->pchan[iChannel];
} else {
channels = caps->rchan[iChannel];
}
if (maxChannels < channels) {
maxChannels = channels;
}
}
}
}
return maxChannels;
#else
(void)caps;
(void)deviceType;
(void)requiredFormat;
return MAL_SNDIO_DEVICE_CHANNELS;
#endif
}
mal_uint32 mal_find_best_sample_rate_from_sio_cap__sndio(struct sio_cap* caps, mal_device_type deviceType, mal_format requiredFormat, mal_uint32 requiredChannels)
{
mal_assert(caps != NULL);
mal_assert(requiredFormat != mal_format_unknown);
mal_assert(requiredChannels > 0);
mal_assert(requiredChannels <= MAL_MAX_CHANNELS);
#ifdef MAL_SNDIO_USE_AUDIOIO_FOR_DEVICE_CAPS
mal_uint32 firstSampleRate = 0; // <-- If the device does not support a standard rate we'll fall back to the first one that's found.
mal_uint32 bestSampleRate = 0;
for (unsigned int iConfig = 0; iConfig < caps->nconf; iConfig += 1) {
// The encoding should be of requiredFormat.
for (unsigned int iEncoding = 0; iEncoding < SIO_NENC; iEncoding += 1) {
if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) {
continue;
}
unsigned int bits = caps->enc[iEncoding].bits;
unsigned int bps = caps->enc[iEncoding].bps;
unsigned int sig = caps->enc[iEncoding].sig;
unsigned int le = caps->enc[iEncoding].le;
unsigned int msb = caps->enc[iEncoding].msb;
mal_format format = mal_format_from_sio_enc__sndio(bits, bps, sig, le, msb);
if (format != requiredFormat) {
continue;
}
// Getting here means the format is supported. Iterate over each channel count and grab the biggest one.
for (unsigned int iChannel = 0; iChannel < SIO_NCHAN; iChannel += 1) {
unsigned int chan = 0;
if (deviceType == mal_device_type_playback) {
chan = caps->confs[iConfig].pchan;
} else {
chan = caps->confs[iConfig].rchan;
}
if ((chan & (1UL << iChannel)) == 0) {
continue;
}
unsigned int channels;
if (deviceType == mal_device_type_playback) {
channels = caps->pchan[iChannel];
} else {
channels = caps->rchan[iChannel];
}
if (channels != requiredChannels) {
continue;
}
// Getting here means we have found a compatible encoding/channel pair.
for (unsigned int iRate = 0; iRate < SIO_NRATE; iRate += 1) {
mal_uint32 rate = (mal_uint32)caps->rate[iRate];
if (firstSampleRate == 0) {
firstSampleRate = rate;
}
// Disregard this rate if it's not a standard one.
mal_uint32 ratePriority = mal_get_standard_sample_rate_priority_index(rate);
if (ratePriority == (mal_uint32)-1) {
continue;
}
if (mal_get_standard_sample_rate_priority_index(bestSampleRate) > ratePriority) { // Lower = better.
bestSampleRate = rate;
}
}
}
}
}
// If a standard sample rate was not found just fall back to the first one that was iterated.
if (bestSampleRate == 0) {
bestSampleRate = firstSampleRate;
}
return bestSampleRate;
#else
(void)caps;
(void)deviceType;
(void)requiredFormat;
return MAL_SNDIO_DEVICE_SAMPLE_RATE;
#endif
}
mal_bool32 mal_context_is_device_id_equal__sndio(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1)
{
mal_assert(pContext != NULL);
mal_assert(pID0 != NULL);
mal_assert(pID1 != NULL);
(void)pContext;
return mal_strcmp(pID0->sndio, pID1->sndio) == 0;
}
mal_result mal_context_enumerate_devices__sndio(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData)
{
mal_assert(pContext != NULL);
mal_assert(callback != NULL);
// I can't find any information on how to use the sndio library to enumerate devices. I'm therefore going
// to enumerate over each device file. On OpenBSD and NetBSD this enumerates over each "/dev/audioctlN"
// device. On all other platforms it's restricted to default devices.
#if defined(__OpenBSD__) || defined(__NetBSD__)
// Enumerate over each "/dev/audioctlN" device. This is very similar to the audioio backend, except we use
// sndio_getpar() to get device properties. Also note that I don't know how to get user friendly device names
// using sndio so I'm assigning the name to the ID. An alternative is to use audioio to get the device name,
// but I haven't done that yet.
const int maxDevices = 64;
char devpath[256];
for (int iDevice = 0; iDevice < maxDevices; ++iDevice) {
mal_strcpy_s(devpath, sizeof(devpath), "/dev/audioctl");
mal_itoa_s(iDevice, devpath+strlen(devpath), sizeof(devpath)-strlen(devpath), 10);
struct stat st;
if (stat(devpath, &st) == 0) {
// The device exists, but we need to check if it's usable as playback and/or capture. This is done
// via the sndio API by using the "snd/N" format for the device name.
char devid[256];
mal_strcpy_s(devid, sizeof(devid), "snd/");
mal_itoa_s(iDevice, devid+strlen(devid), sizeof(devid)-strlen(devid), 10);
mal_bool32 isTerminating = MAL_FALSE;
struct sio_hdl* handle;
// Playback.
if (!isTerminating) {
handle = sio_open(devid, SIO_PLAY, 0);
if (handle != NULL) {
// Supports playback.
mal_device_info deviceInfo;
mal_zero_object(&deviceInfo);
mal_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), devid);
mal_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), devid);
isTerminating = !callback(pContext, mal_device_type_playback, &deviceInfo, pUserData);
sio_close(handle);
}
}
// Capture.
if (!isTerminating) {
handle = sio_open(devid, SIO_REC, 0);
if (handle != NULL) {
// Supports capture.
mal_device_info deviceInfo;
mal_zero_object(&deviceInfo);
mal_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), devid);
mal_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), devid);
isTerminating = !callback(pContext, mal_device_type_capture, &deviceInfo, pUserData);
sio_close(handle);
}
}
if (isTerminating) {
break;
}
}
}
return MAL_SUCCESS;
#else
// Fall back to default devices.
mal_bool32 isTerminating = MAL_FALSE;
struct sio_hdl* handle;
// Playback.
if (!isTerminating) {
handle = sio_open(NULL, SIO_PLAY, 0);
if (handle != NULL) {
// Supports playback.
mal_device_info deviceInfo;
mal_zero_object(&deviceInfo);
mal_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), "default");
mal_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME);
isTerminating = !callback(pContext, mal_device_type_playback, &deviceInfo, pUserData);
sio_close(handle);
}
}
// Capture.
if (!isTerminating) {
handle = sio_open(NULL, SIO_REC, 0);
if (handle != NULL) {
// Supports capture.
mal_device_info deviceInfo;
mal_zero_object(&deviceInfo);
mal_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), "default");
mal_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME);
isTerminating = !callback(pContext, mal_device_type_capture, &deviceInfo, pUserData);
sio_close(handle);
}
}
return MAL_SUCCESS;
#endif
}
mal_result mal_context_get_device_info__sndio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo)
{
mal_assert(pContext != NULL);
(void)shareMode;
// We need to open the device before we can get information about it.
char devid[256];
if (pDeviceID == NULL) {
mal_strcpy_s(devid, sizeof(devid), "default");
mal_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (deviceType == mal_device_type_playback) ? MAL_DEFAULT_PLAYBACK_DEVICE_NAME : MAL_DEFAULT_CAPTURE_DEVICE_NAME);
} else {
mal_strcpy_s(devid, sizeof(devid), pDeviceID->sndio);
mal_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), devid);
}
#ifdef MAL_SNDIO_USE_AUDIOIO_FOR_DEVICE_CAPS
struct sio_hdl* handle = sio_open(devid, (deviceType == mal_device_type_playback) ? SIO_PLAY : SIO_REC, 0);
if (handle == NULL) {
return MAL_NO_DEVICE;
}
struct sio_cap caps;
if (sio_getcap(handle, &caps) == 0) {
return MAL_ERROR;
}
for (unsigned int iConfig = 0; iConfig < caps.nconf; iConfig += 1) {
// The main thing we care about is that the encoding is supported by mini_al. If it is, we want to give
// preference to some formats over others.
for (unsigned int iEncoding = 0; iEncoding < SIO_NENC; iEncoding += 1) {
if ((caps.confs[iConfig].enc & (1UL << iEncoding)) == 0) {
continue;
}
unsigned int bits = caps.enc[iEncoding].bits;
unsigned int bps = caps.enc[iEncoding].bps;
unsigned int sig = caps.enc[iEncoding].sig;
unsigned int le = caps.enc[iEncoding].le;
unsigned int msb = caps.enc[iEncoding].msb;
mal_format format = mal_format_from_sio_enc__sndio(bits, bps, sig, le, msb);
if (format == mal_format_unknown) {
continue; // Format not supported.
}
// Add this format if it doesn't already exist.
mal_bool32 formatExists = MAL_FALSE;
for (mal_uint32 iExistingFormat = 0; iExistingFormat < pDeviceInfo->formatCount; iExistingFormat += 1) {
if (pDeviceInfo->formats[iExistingFormat] == format) {
formatExists = MAL_TRUE;
break;
}
}
if (!formatExists) {
pDeviceInfo->formats[pDeviceInfo->formatCount++] = format;
}
}
// Channels.
for (unsigned int iChannel = 0; iChannel < SIO_NCHAN; iChannel += 1) {
unsigned int chan = 0;
if (deviceType == mal_device_type_playback) {
chan = caps.confs[iConfig].pchan;
} else {
chan = caps.confs[iConfig].rchan;
}
if ((chan & (1UL << iChannel)) == 0) {
continue;
}
unsigned int channels;
if (deviceType == mal_device_type_playback) {
channels = caps.pchan[iChannel];
} else {
channels = caps.rchan[iChannel];
}
if (pDeviceInfo->minChannels > channels) {
pDeviceInfo->minChannels = channels;
}
if (pDeviceInfo->maxChannels < channels) {
pDeviceInfo->maxChannels = channels;
}
}
// Sample rates.
for (unsigned int iRate = 0; iRate < SIO_NRATE; iRate += 1) {
if ((caps.confs[iConfig].rate & (1UL << iRate)) != 0) {
unsigned int rate = caps.rate[iRate];
if (pDeviceInfo->minSampleRate > rate) {
pDeviceInfo->minSampleRate = rate;
}
if (pDeviceInfo->maxSampleRate < rate) {
pDeviceInfo->maxSampleRate = rate;
}
}
}
}
sio_close(handle);
#else
pDeviceInfo->formats[pDeviceInfo->formatCount++] = MAL_SNDIO_DEVICE_FORMAT;
pDeviceInfo->minChannels = MAL_SNDIO_DEVICE_CHANNELS;
pDeviceInfo->maxChannels = MAL_SNDIO_DEVICE_CHANNELS;
pDeviceInfo->minSampleRate = MAL_SNDIO_DEVICE_SAMPLE_RATE;
pDeviceInfo->maxSampleRate = MAL_SNDIO_DEVICE_SAMPLE_RATE;
#endif
return MAL_SUCCESS;
}
void mal_device_uninit__sndio(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
sio_close(pDevice->sndio.handle);
mal_free(pDevice->sndio.pIntermediaryBuffer);
}
mal_result mal_device_init__sndio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice)
{
(void)pContext;
mal_assert(pDevice != NULL);
mal_zero_object(&pDevice->sndio);
const char* deviceName = NULL;
if (pDeviceID != NULL) {
deviceName = pDeviceID->sndio;
}
pDevice->sndio.handle = sio_open(deviceName, (deviceType == mal_device_type_playback) ? SIO_PLAY : SIO_REC, 0);
if (pDevice->sndio.handle == NULL) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to open device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE);
}
// We need to retrieve the device caps to determine the most appropriate format to use.
struct sio_cap caps;
if (sio_getcap(pDevice->sndio.handle, &caps) == 0) {
sio_close(pDevice->sndio.handle);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps.", MAL_ERROR);
}
mal_format desiredFormat = pDevice->format;
if (pDevice->usingDefaultFormat) {
desiredFormat = mal_find_best_format_from_sio_cap__sndio(&caps);
}
if (desiredFormat == mal_format_unknown) {
desiredFormat = pDevice->format;
}
mal_uint32 desiredChannels = pDevice->channels;
if (pDevice->usingDefaultChannels) {
desiredChannels = mal_find_best_channels_from_sio_cap__sndio(&caps, deviceType, desiredFormat);
}
if (desiredChannels == 0) {
desiredChannels = pDevice->channels;
}
mal_uint32 desiredSampleRate = pDevice->sampleRate;
if (pDevice->usingDefaultSampleRate) {
desiredSampleRate = mal_find_best_sample_rate_from_sio_cap__sndio(&caps, deviceType, desiredFormat, desiredChannels);
}
if (desiredSampleRate == 0) {
desiredSampleRate = pDevice->sampleRate;
}
struct sio_par par;
sio_initpar(&par);
par.msb = 0;
par.le = SIO_LE_NATIVE;
switch (desiredFormat) {
case mal_format_u8:
{
par.bits = 8;
par.bps = 1;
par.sig = 0;
} break;
case mal_format_s24:
{
par.bits = 24;
par.bps = 3;
par.sig = 1;
} break;
case mal_format_s32:
{
par.bits = 32;
par.bps = 4;
par.sig = 1;
} break;
case mal_format_s16:
case mal_format_f32:
default:
{
par.bits = 16;
par.bps = 2;
par.sig = 1;
} break;
}
if (deviceType == mal_device_type_playback) {
par.pchan = desiredChannels;
} else {
par.rchan = desiredChannels;
}
par.rate = desiredSampleRate;
if (sio_setpar(pDevice->sndio.handle, &par) == 0) {
sio_close(pDevice->sndio.handle);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to set device parameters.", MAL_FORMAT_NOT_SUPPORTED);
}
if (sio_getpar(pDevice->sndio.handle, &par) == 0) {
sio_close(pDevice->sndio.handle);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device parameters.", MAL_FORMAT_NOT_SUPPORTED);
}
// Try calculating an appropriate default buffer size after we have the sample rate.
mal_uint32 desiredBufferSizeInFrames = pDevice->bufferSizeInFrames;
if (pDevice->usingDefaultBufferSize) {
// CPU speed factor.
float fCPUSpeed = mal_calculate_cpu_speed_factor();
// Playback vs capture latency.
float fDeviceType = 1;
// Backend tax.
float fBackend = 1;
desiredBufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, par.rate, fCPUSpeed*fDeviceType*fBackend);
}
sio_initpar(&par);
par.round = desiredBufferSizeInFrames / pDevice->periods;
par.appbufsz = par.round * pDevice->periods;
if (sio_setpar(pDevice->sndio.handle, &par) == 0) {
sio_close(pDevice->sndio.handle);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size.", MAL_FORMAT_NOT_SUPPORTED);
}
if (sio_getpar(pDevice->sndio.handle, &par) == 0) {
sio_close(pDevice->sndio.handle);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size.", MAL_FORMAT_NOT_SUPPORTED);
}
pDevice->internalFormat = mal_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb);
if (deviceType == mal_device_type_playback) {
pDevice->internalChannels = par.pchan;
} else {
pDevice->internalChannels = par.rchan;
}
pDevice->internalSampleRate = par.rate;
pDevice->bufferSizeInFrames = par.appbufsz;
pDevice->periods = par.appbufsz / par.round;
pDevice->sndio.fragmentSizeInFrames = par.round;
mal_get_standard_channel_map(mal_standard_channel_map_sndio, pDevice->internalChannels, pDevice->internalChannelMap);
pDevice->sndio.pIntermediaryBuffer = mal_malloc(pDevice->sndio.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
if (pDevice->sndio.pIntermediaryBuffer == NULL) {
sio_close(pDevice->sndio.handle);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY);
}
#ifdef MAL_DEBUG_OUTPUT
printf("DEVICE INFO\n");
printf(" Format: %s\n", mal_get_format_name(pDevice->internalFormat));
printf(" Channels: %d\n", pDevice->internalChannels);
printf(" Sample Rate: %d\n", pDevice->internalSampleRate);
printf(" Buffer Size: %d\n", pDevice->bufferSizeInFrames);
printf(" Periods: %d\n", pDevice->periods);
printf(" appbufsz: %d\n", par.appbufsz);
printf(" round: %d\n", par.round);
#endif
return MAL_SUCCESS;
}
mal_result mal_device__start_backend__sndio(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
if (sio_start((struct sio_hdl*)pDevice->sndio.handle) == 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to start backend device.", MAL_FAILED_TO_START_BACKEND_DEVICE);
}
// The device is started by the next calls to read() and write(). For playback it's simple - just read
// data from the client, then write it to the device with write() which will in turn start the device.
// For capture it's a bit less intuitive - we do nothing (it'll be started automatically by the first
// call to read().
if (pDevice->type == mal_device_type_playback) {
// Playback. Need to load the entire buffer, which means we need to write a fragment for each period.
for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; iPeriod += 1) {
mal_device__read_frames_from_client(pDevice, pDevice->sndio.fragmentSizeInFrames, pDevice->sndio.pIntermediaryBuffer);
int bytesWritten = sio_write(pDevice->sndio.handle, pDevice->sndio.pIntermediaryBuffer, pDevice->sndio.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
if (bytesWritten == 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
}
}
} else {
// Capture. Do nothing.
}
return MAL_SUCCESS;
}
mal_result mal_device__stop_backend__sndio(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
sio_stop(pDevice->sndio.handle);
return MAL_SUCCESS;
}
mal_result mal_device__break_main_loop__sndio(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
pDevice->sndio.breakFromMainLoop = MAL_TRUE;
return MAL_SUCCESS;
}
mal_result mal_device__main_loop__sndio(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
pDevice->sndio.breakFromMainLoop = MAL_FALSE;
while (!pDevice->sndio.breakFromMainLoop) {
// Break from the main loop if the device isn't started anymore. Likely what's happened is the application
// has requested that the device be stopped.
if (!mal_device_is_started(pDevice)) {
break;
}
if (pDevice->type == mal_device_type_playback) {
// Playback.
mal_device__read_frames_from_client(pDevice, pDevice->sndio.fragmentSizeInFrames, pDevice->sndio.pIntermediaryBuffer);
int bytesWritten = sio_write(pDevice->sndio.handle, pDevice->sndio.pIntermediaryBuffer, pDevice->sndio.fragmentSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat));
if (bytesWritten == 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
}
} else {
// Capture.
int bytesRead = sio_read(pDevice->sndio.handle, pDevice->sndio.pIntermediaryBuffer, pDevice->sndio.fragmentSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat));
if (bytesRead == 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE);
}
mal_uint32 framesRead = (mal_uint32)bytesRead / pDevice->internalChannels / mal_get_bytes_per_sample(pDevice->internalFormat);
mal_device__send_frames_to_client(pDevice, framesRead, pDevice->sndio.pIntermediaryBuffer);
}
}
return MAL_SUCCESS;
}
mal_result mal_context_uninit__sndio(mal_context* pContext)
{
mal_assert(pContext != NULL);
mal_assert(pContext->backend == mal_backend_sndio);
(void)pContext;
return MAL_SUCCESS;
}
mal_result mal_context_init__sndio(mal_context* pContext)
{
mal_assert(pContext != NULL);
// TODO: Dynamically load the sndio API.
pContext->onUninit = mal_context_uninit__sndio;
pContext->onDeviceIDEqual = mal_context_is_device_id_equal__sndio;
pContext->onEnumDevices = mal_context_enumerate_devices__sndio;
pContext->onGetDeviceInfo = mal_context_get_device_info__sndio;
pContext->onDeviceInit = mal_device_init__sndio;
pContext->onDeviceUninit = mal_device_uninit__sndio;
pContext->onDeviceStart = mal_device__start_backend__sndio;
pContext->onDeviceStop = mal_device__stop_backend__sndio;
pContext->onDeviceBreakMainLoop = mal_device__break_main_loop__sndio;
pContext->onDeviceMainLoop = mal_device__main_loop__sndio;
return MAL_SUCCESS;
}
#endif // sndio
///////////////////////////////////////////////////////////////////////////////
//
// audioio Backend
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_HAS_AUDIOIO
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/audioio.h>
void mal_construct_device_id__audioio(char* id, size_t idSize, const char* base, int deviceIndex)
{
mal_assert(id != NULL);
mal_assert(idSize > 0);
mal_assert(deviceIndex >= 0);
size_t baseLen = strlen(base);
mal_assert(idSize > baseLen);
mal_strcpy_s(id, idSize, base);
mal_itoa_s(deviceIndex, id+baseLen, idSize-baseLen, 10);
}
mal_result mal_extract_device_index_from_id__audioio(const char* id, const char* base, int* pIndexOut)
{
mal_assert(id != NULL);
mal_assert(base != NULL);
mal_assert(pIndexOut != NULL);
size_t idLen = strlen(id);
size_t baseLen = strlen(base);
if (idLen <= baseLen) {
return MAL_ERROR; // Doesn't look like the id starts with the base.
}
if (strncmp(id, base, baseLen) != 0) {
return MAL_ERROR; // ID does not begin with base.
}
const char* deviceIndexStr = id + baseLen;
if (deviceIndexStr[0] == '\0') {
return MAL_ERROR; // No index specified in the ID.
}
if (pIndexOut) {
*pIndexOut = atoi(deviceIndexStr);
}
return MAL_SUCCESS;
}
mal_bool32 mal_context_is_device_id_equal__audioio(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1)
{
mal_assert(pContext != NULL);
mal_assert(pID0 != NULL);
mal_assert(pID1 != NULL);
(void)pContext;
return mal_strcmp(pID0->audioio, pID1->audioio) == 0;
}
mal_format mal_format_from_encoding__audioio(unsigned int encoding, unsigned int precision)
{
if (precision == 8 && (encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR_LE || encoding == AUDIO_ENCODING_ULINEAR_BE)) {
return mal_format_u8;
} else {
if (encoding == AUDIO_ENCODING_SLINEAR_LE) {
if (precision == 16) {
return mal_format_s16;
} else if (precision == 24) {
return mal_format_s24;
} else if (precision == 32) {
return mal_format_s32;
}
}
}
return mal_format_unknown; // Encoding not supported.
}
mal_format mal_format_from_prinfo__audioio(struct audio_prinfo* prinfo)
{
return mal_format_from_encoding__audioio(prinfo->encoding, prinfo->precision);
}
mal_result mal_context_get_device_info_from_fd__audioio(mal_context* pContext, mal_device_type deviceType, int fd, mal_device_info* pInfoOut)
{
mal_assert(pContext != NULL);
mal_assert(fd >= 0);
mal_assert(pInfoOut != NULL);
(void)pContext;
(void)deviceType;
audio_device_t fdDevice;
if (ioctl(fd, AUDIO_GETDEV, &fdDevice) < 0) {
return MAL_ERROR; // Failed to retrieve device info.
}
// Name.
mal_strcpy_s(pInfoOut->name, sizeof(pInfoOut->name), fdDevice.name);
// Supported formats. We get this by looking at the encodings.
int counter = 0;
for (;;) {
audio_encoding_t encoding;
mal_zero_object(&encoding);
encoding.index = counter;
if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) {
break;
}
mal_format format = mal_format_from_encoding__audioio(encoding.encoding, encoding.precision);
if (format != mal_format_unknown) {
pInfoOut->formats[pInfoOut->formatCount++] = format;
}
counter += 1;
}
audio_info_t fdInfo;
if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) {
return MAL_ERROR;
}
if (deviceType == mal_device_type_playback) {
pInfoOut->minChannels = fdInfo.play.channels;
pInfoOut->maxChannels = fdInfo.play.channels;
pInfoOut->minSampleRate = fdInfo.play.sample_rate;
pInfoOut->maxSampleRate = fdInfo.play.sample_rate;
} else {
pInfoOut->minChannels = fdInfo.record.channels;
pInfoOut->maxChannels = fdInfo.record.channels;
pInfoOut->minSampleRate = fdInfo.record.sample_rate;
pInfoOut->maxSampleRate = fdInfo.record.sample_rate;
}
return MAL_SUCCESS;
}
mal_result mal_context_enumerate_devices__audioio(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData)
{
mal_assert(pContext != NULL);
mal_assert(callback != NULL);
const int maxDevices = 64;
// Every device will be named "/dev/audioN", with a "/dev/audioctlN" equivalent. We use the "/dev/audioctlN"
// version here since we can open it even when another process has control of the "/dev/audioN" device.
char devpath[256];
for (int iDevice = 0; iDevice < maxDevices; ++iDevice) {
mal_strcpy_s(devpath, sizeof(devpath), "/dev/audioctl");
mal_itoa_s(iDevice, devpath+strlen(devpath), sizeof(devpath)-strlen(devpath), 10);
struct stat st;
if (stat(devpath, &st) == 0) {
// The device exists, but we need to check if it's usable as playback and/or capture.
int fd;
mal_bool32 isTerminating = MAL_FALSE;
// Playback.
if (!isTerminating) {
fd = open(devpath, O_RDONLY, 0);
if (fd >= 0) {
// Supports playback.
mal_device_info deviceInfo;
mal_zero_object(&deviceInfo);
mal_construct_device_id__audioio(deviceInfo.id.audioio, sizeof(deviceInfo.id.audioio), "/dev/audio", iDevice);
if (mal_context_get_device_info_from_fd__audioio(pContext, mal_device_type_playback, fd, &deviceInfo) == MAL_SUCCESS) {
isTerminating = !callback(pContext, mal_device_type_playback, &deviceInfo, pUserData);
}
close(fd);
}
}
// Capture.
if (!isTerminating) {
fd = open(devpath, O_WRONLY, 0);
if (fd >= 0) {
// Supports capture.
mal_device_info deviceInfo;
mal_zero_object(&deviceInfo);
mal_construct_device_id__audioio(deviceInfo.id.audioio, sizeof(deviceInfo.id.audioio), "/dev/audio", iDevice);
if (mal_context_get_device_info_from_fd__audioio(pContext, mal_device_type_capture, fd, &deviceInfo) == MAL_SUCCESS) {
isTerminating = !callback(pContext, mal_device_type_capture, &deviceInfo, pUserData);
}
close(fd);
}
}
if (isTerminating) {
break;
}
}
}
return MAL_SUCCESS;
}
mal_result mal_context_get_device_info__audioio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo)
{
mal_assert(pContext != NULL);
(void)shareMode;
// We need to open the "/dev/audioctlN" device to get the info. To do this we need to extract the number
// from the device ID which will be in "/dev/audioN" format.
int fd = -1;
int deviceIndex = -1;
char ctlid[256]; char ctlid[256];
if (pDeviceID == NULL) { if (pDeviceID == NULL) {
// Default device. // Default device.
...@@ -18449,21 +19367,6 @@ mal_result mal_context_uninit_backend_apis(mal_context* pContext) ...@@ -18449,21 +19367,6 @@ mal_result mal_context_uninit_backend_apis(mal_context* pContext)
return result; return result;
} }
const mal_backend g_malDefaultBackends[] = {
mal_backend_wasapi,
mal_backend_dsound,
mal_backend_winmm,
mal_backend_coreaudio,
mal_backend_audioio,
mal_backend_oss,
mal_backend_pulseaudio,
mal_backend_alsa,
mal_backend_jack,
mal_backend_opensl,
mal_backend_openal,
mal_backend_sdl,
mal_backend_null
};
mal_bool32 mal_context_is_backend_asynchronous(mal_context* pContext) mal_bool32 mal_context_is_backend_asynchronous(mal_context* pContext)
{ {
...@@ -18547,6 +19450,12 @@ mal_result mal_context_init(const mal_backend backends[], mal_uint32 backendCoun ...@@ -18547,6 +19450,12 @@ mal_result mal_context_init(const mal_backend backends[], mal_uint32 backendCoun
result = mal_context_init__coreaudio(pContext); result = mal_context_init__coreaudio(pContext);
} break; } break;
#endif #endif
#ifdef MAL_HAS_SNDIO
case mal_backend_sndio:
{
result = mal_context_init__sndio(pContext);
} break;
#endif
#ifdef MAL_HAS_AUDIOIO #ifdef MAL_HAS_AUDIOIO
case mal_backend_audioio: case mal_backend_audioio:
{ {
...@@ -19639,6 +20548,65 @@ void mal_get_standard_channel_map_vorbis(mal_uint32 channels, mal_channel channe ...@@ -19639,6 +20548,65 @@ void mal_get_standard_channel_map_vorbis(mal_uint32 channels, mal_channel channe
} }
} }
void mal_get_standard_channel_map_sndio(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS])
{
switch (channels)
{
case 1:
{
channelMap[0] = MAL_CHANNEL_MONO;
} break;
case 2:
{
channelMap[0] = MAL_CHANNEL_LEFT;
channelMap[1] = MAL_CHANNEL_RIGHT;
} break;
case 3:
{
channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
channelMap[2] = MAL_CHANNEL_FRONT_CENTER;
} break;
case 4:
{
channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
channelMap[2] = MAL_CHANNEL_BACK_LEFT;
channelMap[3] = MAL_CHANNEL_BACK_RIGHT;
} break;
case 5:
{
channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
channelMap[2] = MAL_CHANNEL_BACK_LEFT;
channelMap[3] = MAL_CHANNEL_BACK_RIGHT;
channelMap[4] = MAL_CHANNEL_FRONT_CENTER;
} break;
case 6:
default:
{
channelMap[0] = MAL_CHANNEL_FRONT_LEFT;
channelMap[1] = MAL_CHANNEL_FRONT_RIGHT;
channelMap[2] = MAL_CHANNEL_BACK_LEFT;
channelMap[3] = MAL_CHANNEL_BACK_RIGHT;
channelMap[4] = MAL_CHANNEL_FRONT_CENTER;
channelMap[5] = MAL_CHANNEL_LFE;
} break;
}
// Remainder.
if (channels > 6) {
for (mal_uint32 iChannel = 6; iChannel < MAL_MAX_CHANNELS; ++iChannel) {
channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-6));
}
}
}
void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS])
{ {
switch (standardChannelMap) switch (standardChannelMap)
...@@ -19662,6 +20630,11 @@ void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, m ...@@ -19662,6 +20630,11 @@ void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, m
{ {
mal_get_standard_channel_map_vorbis(channels, channelMap); mal_get_standard_channel_map_vorbis(channels, channelMap);
} break; } break;
case mal_standard_channel_map_sndio:
{
mal_get_standard_channel_map_sndio(channels, channelMap);
} break;
case mal_standard_channel_map_microsoft: case mal_standard_channel_map_microsoft:
default: default:
...@@ -26,6 +26,7 @@ mal_backend g_Backends[] = { ...@@ -26,6 +26,7 @@ mal_backend g_Backends[] = {
mal_backend_dsound, mal_backend_dsound,
mal_backend_winmm, mal_backend_winmm,
mal_backend_coreaudio, mal_backend_coreaudio,
mal_backend_sndio,
mal_backend_audioio, mal_backend_audioio,
mal_backend_oss, mal_backend_oss,
mal_backend_pulseaudio, mal_backend_pulseaudio,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment