#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dsp/dsp.h" #include "sysex.h" #define WARN1(x) do { warn("asound: %s %s", __func__, x); } while (0) #define WARN(x, ...) do { warn("asound: %s " x, __func__, ##__VA_ARGS__); } while (0) #define WARNX1(x) do { warnx("asound: %s %s", __func__, x); } while (0) #define WARNX(x, ...) do { warnx("asound: %s " x, __func__, ##__VA_ARGS__); } while (0) #define ERRX1(x, y) do { errx(x, "asound: %s %s", __func__, y); } while (0) #define ERRX(x, y, ...) do { errx(x, "asound: %s " y, __func__, ##__VA_ARGS__); } while (0) #define ERR(x, y, ...) do { err(x, "asound: %s " y, __func__, ##__VA_ARGS__); } while (0) #include "stubs.h" #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) const char* snd_asoundlib_version(void) { return "1.1.7"; } const char* snd_strerror(int errnum) { // Do not bother implementing this, just WARN/ERR directly in this lib // Letting applications show useful error messages never works out return "^"; } snd_lib_error_handler_t snd_lib_error; int snd_lib_error_set_handler(snd_lib_error_handler_t handler) { snd_lib_error = handler; return 0; } struct _snd_config { char noop; } s_snd_config; struct _snd_config *snd_config = &s_snd_config; int snd_card_load(int card) { return 0; } int snd_card_next(int *card) { if (card) { *card = (*card == -1 ? 0 : -1); return 0; } return -1; } int snd_card_get_index(const char *name) { if (!strcmp(name, "default")) return 0; return -1; } int snd_card_get_name(int card, char **name) { if (name) *name = "default"; return 0; } int snd_card_get_longname(int card, char **name) { if (name) *name = "default"; return 0; } struct _snd_pcm_hint { char *name, *ioid; }; int snd_device_name_hint(int card, const char *iface, void ***hints) { static struct _snd_pcm_hint defaults[] = { { .name = "default", .ioid = "Output" }, { .name = "capture", .ioid = "Input" } }; static struct _snd_pcm_hint *array[] = { &defaults[0], &defaults[1], NULL }; *hints = (void**)array; return 0; } static char* c_strdup(const char *str) { size_t len = strlen(str); char *copy; if (!(copy = calloc(1, len + 1))) return NULL; memcpy(copy, str, len); return copy; } char* snd_device_name_get_hint(const void *hint, const char *id) { const struct _snd_pcm_hint *shint = hint; if (!strcmp(id, "NAME")) return c_strdup(shint->name); else if (!strcmp(id, "IOID")) return c_strdup(shint->ioid); return NULL; } int snd_device_name_free_hint(void **hints) { return 0; } struct _snd_pcm_hw_params { struct sio_cap cap; struct sio_par par; snd_pcm_format_t alsa_format; snd_pcm_access_t access; bool needs_conversion; // for unsupported formats }; struct _snd_pcm_sw_params { char noop; }; struct _snd_pcm { struct _snd_pcm_hw_params hw, hw_requested; struct _snd_pcm_sw_params sw; struct sio_hdl *hdl; const char *name; snd_pcm_uframes_t position, written, avail; snd_pcm_stream_t stream; int mode; bool started; }; static int sndio_stream(snd_pcm_stream_t stream) { switch (stream) { case SND_PCM_STREAM_PLAYBACK: return SIO_PLAY; case SND_PCM_STREAM_CAPTURE: return SIO_REC; } ERRX(EXIT_FAILURE, "unknown stream: %u", stream); } static int sndio_mode(int mode) { switch (mode) { case SND_PCM_NONBLOCK: WARNX1("SND_PCM_NONBLOCK"); return true; // ASYNC: SIGIO will be emitted whenever a period has been completely processed by the soundcard. case SND_PCM_ASYNC: ERRX1(EXIT_FAILURE, "SND_PCM_ASYNC is not supported"); } return false; } static void onmove(void *arg, int delta) { snd_pcm_t *pcm = arg; pcm->position += delta; pcm->avail += delta; } static struct sio_hdl* device_open(snd_pcm_t *pcm, const char *name, snd_pcm_stream_t stream, int mode) { const char *sndio_name = (!name || !strcmp(name, "default") ? SIO_DEVANY : name); struct sio_hdl *hdl; if (!(hdl = sio_open(sndio_name, sndio_stream(stream), sndio_mode(mode))) && !(hdl = sio_open(SIO_DEVANY, sndio_stream(stream), sndio_mode(mode)))) { WARNX1("sio_open failed"); return NULL; } sio_onmove(hdl, onmove, pcm); pcm->mode = mode; return hdl; } int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode) { if (!(*pcm = calloc(1, sizeof(**pcm)))) { WARN1("calloc"); return -1; } if (!((*pcm)->hdl = device_open(*pcm, name, stream, mode))) goto fail; sio_initpar(&(*pcm)->hw_requested.par); (*pcm)->name = (name ? name : "default"); return (sio_getcap((*pcm)->hdl, &(*pcm)->hw.cap) && sio_getpar((*pcm)->hdl, &(*pcm)->hw.par) ? 0 : -1); fail: free(*pcm); return -1; } int snd_pcm_close(snd_pcm_t *pcm) { sio_close(pcm->hdl); free(pcm); return 0; } const char* snd_pcm_name(snd_pcm_t *pcm) { return pcm->name; } int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock) { if ((pcm->mode == SND_PCM_NONBLOCK && nonblock) || (!pcm->mode && !nonblock)) return 0; WARNX("snd_pcm_nonblock(%d)", nonblock); snd_pcm_drain(pcm); sio_close(pcm->hdl); if (!(pcm->hdl = device_open(pcm, pcm->name, pcm->stream, (nonblock ? SND_PCM_NONBLOCK : false)))) return -1; return snd_pcm_hw_params(pcm, &pcm->hw_requested); } int snd_pcm_poll_descriptors_count(snd_pcm_t *pcm) { return sio_nfds(pcm->hdl); } int snd_pcm_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space) { if (space > (unsigned int)sio_nfds(pcm->hdl)) return -1; return sio_pollfd(pcm->hdl, pfds, (pcm->stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN)); } int snd_pcm_poll_descriptors_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) { if (!revents || nfds > (unsigned int)sio_nfds(pcm->hdl)) return -1; *revents = sio_revents(pcm->hdl, pfds); return 0; } snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm) { if (pcm->started) return SND_PCM_STATE_RUNNING; return SND_PCM_STATE_OPEN; } int snd_pcm_wait(snd_pcm_t *pcm, int timeout) { return 1; // we are always ready for io } static size_t io_do(snd_pcm_t *pcm, void *buffer, const size_t frames, size_t (*io)(struct sio_hdl*, void*, size_t)) { if (pcm->hw.needs_conversion) { struct aparams params = { .bps = pcm->hw.par.bps, .bits = pcm->hw.par.bits, .le = pcm->hw.par.le, .sig = pcm->hw.par.sig, .msb = pcm->hw.par.msb }; struct conv dec, enc; dec_init(&dec, ¶ms, pcm->hw.par.pchan); enc_init(&enc, ¶ms, pcm->hw.par.pchan); size_t total_frames = frames, io_bytes = 0; unsigned char decoded[4096], encoded[sizeof(decoded)]; const size_t max_frames = snd_pcm_bytes_to_frames(pcm, sizeof(decoded)); for (unsigned char *p = buffer; total_frames > 0;) { const int todo_frames = (total_frames > max_frames ? max_frames : total_frames); total_frames -= todo_frames; // sadly can't function pointer here as some formats may need different parameters for decoder if (pcm->hw.alsa_format == SND_PCM_FORMAT_FLOAT_LE || pcm->hw.alsa_format == SND_PCM_FORMAT_FLOAT_BE) { dec_do_float(&dec, p, decoded, todo_frames); } else { dec_do(&dec, p, decoded, todo_frames); } enc_do(&enc, decoded, encoded, todo_frames); const size_t todo_bytes = snd_pcm_frames_to_bytes(pcm, todo_frames); io_bytes += io(pcm->hdl, encoded, todo_bytes); p += todo_bytes; } return io_bytes; } return io(pcm->hdl, buffer, snd_pcm_frames_to_bytes(pcm, frames)); } snd_pcm_sframes_t snd_pcm_bytes_to_frames(snd_pcm_t *pcm, ssize_t bytes) { const int bpf = (pcm->hw.par.bps * pcm->hw.par.pchan); return bytes / bpf; } ssize_t snd_pcm_frames_to_bytes(snd_pcm_t *pcm, snd_pcm_sframes_t frames) { const int bpf = (pcm->hw.par.bps * pcm->hw.par.pchan); return frames * bpf; } snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) { const snd_pcm_sframes_t ret = snd_pcm_bytes_to_frames(pcm, io_do(pcm, (void*)buffer, size, (size_t(*)(struct sio_hdl*, void*, size_t))sio_write)); pcm->written += ret; pcm->avail -= ret; return ret; } snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) { const snd_pcm_sframes_t ret = snd_pcm_bytes_to_frames(pcm, io_do(pcm, buffer, size, sio_read)); pcm->written += ret; pcm->avail -= ret; return ret; } snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm) { struct pollfd pfd[16]; int nfds = sio_nfds(pcm->hdl); assert((unsigned int)nfds < ARRAY_SIZE(pfd)); nfds = sio_pollfd(pcm->hdl, pfd, (pcm->stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN)); // XXX: timeout should be period time/buffer time(?) errno = 0; while ((nfds = poll(pfd, nfds, -1)) < 0) { if (errno == EINVAL) { WARNX1("poll EINVAL"); goto nodata; } } const int revents = sio_revents(pcm->hdl, pfd); if (!(revents & POLLOUT) && !(revents & POLLIN)) goto nodata; return pcm->avail; nodata: // NOTE: returning 1, as some programs don't check the return value :/ (namely qwebengine) // thus they SIGFPE by dividing by 0 or -1 return 1; } snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm) { return pcm->avail; } int snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { if (delayp) *delayp = pcm->written - pcm->position; return 0; } int snd_pcm_prepare(snd_pcm_t *pcm) { if (!pcm->started && sio_start(pcm->hdl)) { pcm->started = true; pcm->written = pcm->position = 0; pcm->avail = pcm->hw.par.bufsz; } return (pcm->started ? 0 : -1); } int snd_pcm_start(snd_pcm_t *pcm) { return snd_pcm_prepare(pcm); } int snd_pcm_drain(snd_pcm_t *pcm) { if (pcm->started && sio_stop(pcm->hdl)) { pcm->started = false; pcm->written = pcm->position = 0; } return (!pcm->started ? 0 : -1); } int snd_pcm_drop(snd_pcm_t *pcm) { // FIXME: not correct, we need to do emulation return snd_pcm_drain(pcm); } int snd_pcm_resume(snd_pcm_t *pcm) { // FIXME: not correct, we need to do emulation return snd_pcm_drain(pcm); } int snd_pcm_pause(snd_pcm_t *pcm, int enable) { // FIXME: not correct, we need to do emulation return (enable ? snd_pcm_drain(pcm) : snd_pcm_start(pcm)); } int snd_pcm_reset(snd_pcm_t *pcm) { return (!snd_pcm_drain(pcm) && !snd_pcm_prepare(pcm) ? 0 : -1); } size_t snd_pcm_hw_params_sizeof(void) { return sizeof(snd_pcm_hw_params_t); } void snd_pcm_hw_params_copy(snd_pcm_hw_params_t *dst, const snd_pcm_hw_params_t *src) { *dst = *src; } static void copy_important_params(struct sio_par *dst, const struct sio_par *src) { assert(dst && src); dst->rate = src->rate; dst->pchan = src->pchan; dst->rchan = src->rchan; dst->appbufsz = src->appbufsz; dst->round = src->round; } int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { params->cap = pcm->hw.cap; sio_initpar(¶ms->par); copy_important_params(¶ms->par, &pcm->hw.par); WARNX("rate: %u, round: %u, appbufsz: %u pchan: %u", params->par.rate, params->par.round, params->par.appbufsz, params->par.pchan); return 0; } int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { if (memcmp(params, &pcm->hw, sizeof(*params))) { snd_pcm_drain(pcm); pcm->hw_requested = *params; if (!sio_setpar(pcm->hdl, ¶ms->par)) { WARNX1("sio_setpar failed"); return -1; } pcm->hw = *params; if (!sio_getpar(pcm->hdl, &pcm->hw.par)) { WARNX1("sio_getpar failed"); return -1; } } WARNX("rate: %u, round: %u, appbufsz: %u, bufsz: %u, pchan: %u", pcm->hw.par.rate, pcm->hw.par.round, pcm->hw.par.appbufsz, pcm->hw.par.bufsz, pcm->hw.par.pchan); return snd_pcm_prepare(pcm); } int snd_pcm_hw_params_current(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { *params = pcm->hw; return 0; } int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access) { if (_access != SND_PCM_ACCESS_RW_INTERLEAVED) goto fail; params->access = _access; return 0; fail: WARNX("access mode `0x%x` not supported yet", _access); return -1; } int snd_pcm_hw_params_get_access(const snd_pcm_hw_params_t *params, snd_pcm_access_t *_access) { if (_access) *_access = params->access; return 0; } const char* snd_pcm_format_name(const snd_pcm_format_t format) { #define NAME(x) case x: return #x switch (format) { NAME(SND_PCM_FORMAT_U8); NAME(SND_PCM_FORMAT_S8); NAME(SND_PCM_FORMAT_S16_LE); NAME(SND_PCM_FORMAT_S16_BE); NAME(SND_PCM_FORMAT_U16_LE); NAME(SND_PCM_FORMAT_U16_BE); NAME(SND_PCM_FORMAT_S24_LE); NAME(SND_PCM_FORMAT_S24_BE); NAME(SND_PCM_FORMAT_U24_LE); NAME(SND_PCM_FORMAT_U24_BE); NAME(SND_PCM_FORMAT_S32_LE); NAME(SND_PCM_FORMAT_S32_BE); NAME(SND_PCM_FORMAT_U32_LE); NAME(SND_PCM_FORMAT_U32_BE); NAME(SND_PCM_FORMAT_FLOAT_LE); NAME(SND_PCM_FORMAT_FLOAT_BE); default: WARNX("unsupported format: 0x%x", format); return "unsupported"; } #undef NAME } static bool pcm_format(const snd_pcm_format_t format, struct sio_par *par, bool *out_needs_conversion) { switch (format) { case SND_PCM_FORMAT_U8: par->bits = 8; par->sig = 0; break; case SND_PCM_FORMAT_S8: par->bits = 8; par->sig = 1; break; case SND_PCM_FORMAT_S16_LE: par->bits = 16; par->sig = 1; par->le = 1; break; case SND_PCM_FORMAT_S16_BE: par->bits = 16; par->sig = 1; par->le = 0; break; case SND_PCM_FORMAT_U16_LE: par->bits = 16; par->sig = 0; par->le = 1; break; case SND_PCM_FORMAT_U16_BE: par->bits = 16; par->sig = 0; par->le = 0; break; case SND_PCM_FORMAT_S24_LE: par->bits = 24; par->sig = 1; par->le = 1; break; case SND_PCM_FORMAT_S24_BE: par->bits = 24; par->sig = 1; par->le = 0; break; case SND_PCM_FORMAT_U24_LE: par->bits = 24; par->sig = 0; par->le = 1; break; case SND_PCM_FORMAT_U24_BE: par->bits = 24; par->sig = 0; par->le = 0; break; case SND_PCM_FORMAT_FLOAT_LE: *out_needs_conversion = true; /* fallthrough */ case SND_PCM_FORMAT_S32_LE: par->bits = 32; par->sig = 1; par->le = 1; break; case SND_PCM_FORMAT_FLOAT_BE: *out_needs_conversion = true; /* fallthrough */ case SND_PCM_FORMAT_S32_BE: par->bits = 32; par->sig = 1; par->le = 0; break; case SND_PCM_FORMAT_U32_LE: par->bits = 32; par->sig = 0; par->le = 1; break; case SND_PCM_FORMAT_U32_BE: par->bits = 32; par->sig = 0; par->le = 0; break; default: WARNX("unsupported format: 0x%x", format); return false; } return true; } struct _snd_pcm_format_mask { snd_pcm_format_t *fmts; uint8_t nmemb; }; size_t snd_pcm_format_mask_sizeof(void) { return sizeof(snd_pcm_format_mask_t); } int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t **ptr) { // OpenAL-soft uses this :( *ptr = calloc(1, sizeof(**ptr)); return (*ptr ? 0 : -1); } void snd_pcm_hw_params_free(snd_pcm_hw_params_t *obj) { free(obj); } void snd_pcm_hw_params_get_format_mask(snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask) { static snd_pcm_format_t def_fmts[] = { SND_PCM_FORMAT_U8, SND_PCM_FORMAT_S8, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE, SND_PCM_FORMAT_U16_LE, SND_PCM_FORMAT_U16_BE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_BE, SND_PCM_FORMAT_U24_LE, SND_PCM_FORMAT_U24_BE, SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S32_BE, SND_PCM_FORMAT_U32_LE, SND_PCM_FORMAT_U32_BE, SND_PCM_FORMAT_FLOAT_LE, SND_PCM_FORMAT_FLOAT_BE }; static snd_pcm_format_mask_t def_mask = { .fmts = def_fmts, .nmemb = ARRAY_SIZE(def_fmts) }; if (mask) *mask = def_mask; } int snd_pcm_format_mask_test(const snd_pcm_format_mask_t *mask, snd_pcm_format_t val) { for (uint8_t i = 0; i < mask->nmemb; ++i) { if (mask->fmts[i] != val) continue; return true; } return false; } int snd_pcm_hw_params_test_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val) { snd_pcm_format_mask_t mask; snd_pcm_hw_params_get_format_mask(params, &mask); return (snd_pcm_format_mask_test(&mask, val) ? 0 : -1); } int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val) { // FIXME: prob should check hw caps if (!pcm_format(val, ¶ms->par, ¶ms->needs_conversion)) return -1; WARNX1(snd_pcm_format_name(val)); params->alsa_format = val; params->par.bps = SIO_BPS(params->par.bits); return 0; } static int update(snd_pcm_t *pcm, struct sio_par *par, void *curv, void *newv, const size_t size) { if (!newv) return 0; struct sio_par old = *par; memcpy(curv, newv, size); const bool was_started = pcm->started; if (was_started) snd_pcm_drain(pcm); if (!sio_setpar(pcm->hdl, par)) { WARNX1("sio_setpar failed"); *par = old; return 0; } struct sio_par hpar; if (sio_getpar(pcm->hdl, &hpar)) { copy_important_params(par, &hpar); } else { WARNX1("sio_getpar failed"); } memcpy(newv, curv, size); if (was_started) snd_pcm_prepare(pcm); return 0; } int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params, unsigned int *val) { // FIXME: need to store stream info in params if (val) *val = params->par.pchan; return 0; } int snd_pcm_hw_params_set_channels_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val) { if (val) { if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { assert(sizeof(params->par.pchan) == sizeof(*val)); return update(pcm, ¶ms->par, ¶ms->par.pchan, val, sizeof(*val)); } else { assert(sizeof(params->par.rchan) == sizeof(*val)); return update(pcm, ¶ms->par, ¶ms->par.rchan, val, sizeof(*val)); } } return 0; } int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val) { return snd_pcm_hw_params_set_channels_near(pcm, params, &val); } int snd_pcm_hw_params_get_channels_min(const snd_pcm_hw_params_t *params, unsigned int *val) { // FIXME: need to store stream info in params unsigned int min = (unsigned int)~0; for (int i = 0; i < SIO_NCHAN; ++i) { if (!(params->cap.confs[0].pchan & (1 << i))) continue; min = (params->cap.pchan[i] < min ? params->cap.pchan[i] : min); } if (val) *val = min; return 0; } int snd_pcm_hw_params_get_channels_max(const snd_pcm_hw_params_t *params, unsigned int *val) { // FIXME: need to store stream info in params unsigned int max = 0; for (int i = 0; i < SIO_NCHAN; ++i) { if (!(params->cap.confs[0].pchan & (1 << i))) continue; max = (params->cap.pchan[i] > max ? params->cap.pchan[i] : max); } if (val) *val = max; return 0; } int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { if (dir) *dir = 0; if (val) *val = params->par.rate; return 0; } int snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { if (dir) *dir = 0; assert(sizeof(params->par.rate) == sizeof(*val)); return update(pcm, ¶ms->par, ¶ms->par.rate, val, sizeof(*val)); } int snd_pcm_hw_params_set_rate(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir) { return snd_pcm_hw_params_set_rate_near(pcm, params, &val, &dir); } int snd_pcm_hw_params_get_rate_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { unsigned int min = (unsigned int)~0; for (int i = 0; i < SIO_NRATE; ++i) { if (!(params->cap.confs[0].rate & (1 << i))) continue; min = (params->cap.rate[i] < min ? params->cap.rate[i] : min); } if (dir) *dir = 0; if (val) *val = min; return 0; } int snd_pcm_hw_params_get_rate_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { unsigned int max = 0; for (int i = 0; i < SIO_NRATE; ++i) { if (!(params->cap.confs[0].rate & (1 << i))) continue; max = (params->cap.rate[i] > max ? params->cap.rate[i] : max); } if (dir) *dir = 0; if (val) *val = max; return 0; } int snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { if (val) *val = params->par.appbufsz; return 0; } int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { if (val) { unsigned int newv = *val; assert(sizeof(params->par.appbufsz) == sizeof(newv)); const int ret = update(pcm, ¶ms->par, ¶ms->par.appbufsz, &newv, sizeof(newv)); *val = newv; return ret; } return 0; } int snd_pcm_hw_params_get_buffer_size_min(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { if (val) *val = params->par.appbufsz; return 0; } int snd_pcm_hw_params_get_buffer_size_max(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { if (val) *val = params->par.appbufsz; return 0; } int snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir) { if (dir) *dir = 0; if (val) *val = params->par.round; return 0; } int snd_pcm_hw_params_set_period_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir) { if (dir) *dir = 0; assert(sizeof(params->par.round) == sizeof(*val)); return update(pcm, ¶ms->par, ¶ms->par.round, val, sizeof(*val)); } int snd_pcm_hw_params_get_period_size_min(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir) { if (dir) *dir = 0; if (val) *val = params->par.round; return 0; } int snd_pcm_hw_params_get_period_size_max(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir) { if (dir) *dir = 0; if (val) *val = params->par.round; return 0; } int snd_pcm_hw_params_get_periods(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { if (dir) *dir = 0; if (val) *val = params->par.appbufsz / params->par.round; return 0; } int snd_pcm_hw_params_set_periods_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { if (dir) *dir = 0; if (val) { unsigned int round = params->par.appbufsz / *val; assert(sizeof(params->par.round) == sizeof(round)); return update(pcm, ¶ms->par, ¶ms->par.round, &round, sizeof(*val)); } return 0; } size_t snd_pcm_sw_params_sizeof(void) { return sizeof(snd_pcm_sw_params_t); } int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t **ptr) { // OpenAL-soft uses this :( *ptr = calloc(1, sizeof(**ptr)); return (*ptr ? 0 : -1); } void snd_pcm_sw_params_free(snd_pcm_sw_params_t *ptr) { free(ptr); } int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) { *params = pcm->sw; return 0; } int snd_pcm_set_params(snd_pcm_t *pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency) { snd_pcm_hw_params_t params; return (!snd_pcm_hw_params_any(pcm, ¶ms) && !snd_pcm_hw_params_set_format(pcm, ¶ms, format) && !snd_pcm_hw_params_set_access(pcm, ¶ms, access) && !snd_pcm_hw_params_set_channels(pcm, ¶ms, channels) && !snd_pcm_hw_params_set_rate(pcm, ¶ms, rate, 0) && !snd_pcm_hw_params(pcm, ¶ms) ? 0 : -1); } int snd_pcm_get_params(snd_pcm_t *pcm, snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t *period_size) { if (buffer_size) *buffer_size = pcm->hw.par.appbufsz; if (period_size) *period_size = pcm->hw.par.round; return 0; } snd_pcm_chmap_t* snd_pcm_get_chmap(snd_pcm_t *pcm) { const unsigned int nc = (pcm->stream == SND_PCM_STREAM_PLAYBACK ? pcm->hw.par.pchan : pcm->hw.par.rchan); snd_pcm_chmap_t *map; if (!(map = calloc(1, sizeof(*map) + nc))) return NULL; map->channels = nc; if (nc == 1) { map->pos[0] = SND_CHMAP_MONO; } else if (nc == 2) { map->pos[0] = SND_CHMAP_FL; map->pos[1] = SND_CHMAP_FR; } return map; } struct _snd_ctl { char noop; }; int snd_ctl_open(snd_ctl_t **ctl, const char *name, int mode) { if (!(*ctl = calloc(1, sizeof(**ctl)))) { WARNX1("calloc"); return -1; } return 0; } int snd_ctl_close(snd_ctl_t *ctl) { free(ctl); return 0; } struct _snd_ctl_card_info { const char *name; }; size_t snd_ctl_card_info_sizeof(void) { return sizeof(snd_ctl_card_info_t); } int snd_ctl_card_info_malloc(snd_ctl_card_info_t **ptr) { if (!(*ptr = calloc(1, sizeof(**ptr)))) return -1; return 0; } void snd_ctl_card_info_free(snd_ctl_card_info_t *obj) { free(obj); } int snd_ctl_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info) { info->name = "sndio"; return 0; } const char* snd_ctl_card_info_get_name(const snd_ctl_card_info_t *obj) { return obj->name; } const char* snd_ctl_card_info_get_mixername(const snd_ctl_card_info_t *obj) { return obj->name; } struct _snd_mixer_elem { snd_mixer_elem_t *next; char name[SYSEX_NAMELEN]; unsigned int index, vol; }; struct _snd_mixer { struct mio_hdl *hdl; snd_mixer_elem_t *controls; }; #define MIXER_MAX_CHANNELS 16 static bool onsysex(unsigned char *buf, unsigned len, snd_mixer_elem_t controls[MIXER_MAX_CHANNELS + 1]) { if (len < SYSEX_SIZE(empty)) return false; union { struct sysex x; unsigned char buf[sizeof(struct sysex)]; } u; memcpy(u.buf, buf, len); if (u.x.type == SYSEX_TYPE_RT && u.x.id0 == SYSEX_CONTROL && u.x.id1 == SYSEX_MASTER) { if (len == SYSEX_SIZE(master)) { controls[0].vol = u.x.u.master.coarse; snprintf(controls[0].name, sizeof(controls[0].name), "master"); } return false; } if (u.x.type != SYSEX_TYPE_EDU || u.x.id0 != SYSEX_AUCAT) return false; switch (u.x.id1) { case SYSEX_AUCAT_MIXINFO: { unsigned cn = u.x.u.mixinfo.chan; if (cn >= MIXER_MAX_CHANNELS || !memchr(u.x.u.mixinfo.name, '\0', SYSEX_NAMELEN)) return false; snprintf(controls[cn + 1].name, sizeof(controls[cn + 1].name), "%s", u.x.u.mixinfo.name); break; } case SYSEX_AUCAT_DUMPEND: return true; } return false; } static void oncommon(unsigned char *buf, unsigned len, snd_mixer_elem_t controls[MIXER_MAX_CHANNELS + 1]) { #define MIDI_CMDMASK 0xf0 #define MIDI_CTL 0xb0 #define MIDI_CTLVOL 7 if ((buf[0] & MIDI_CMDMASK) != MIDI_CTL || buf[1] != MIDI_CTLVOL) return; #define MIDI_CHANMASK 0x0f unsigned cn = (buf[0] & MIDI_CHANMASK); if (cn >= MIXER_MAX_CHANNELS) return; controls[cn + 1].vol = buf[2]; } static void get_controls(snd_mixer_t *mixer) { const unsigned char dumpreq[] = { SYSEX_START, SYSEX_TYPE_EDU, 0, SYSEX_AUCAT, SYSEX_AUCAT_DUMPREQ, SYSEX_END, }; static snd_mixer_elem_t controls[MIXER_MAX_CHANNELS + 1]; memset(controls, 0, sizeof(controls)); unsigned char buf[0x100]; mio_write(mixer->hdl, dumpreq, sizeof(dumpreq)); for (int ready = 0; !ready;) { size_t size; if (!(size = mio_read(mixer->hdl, buf, sizeof(buf)))) { WARNX1("mio_read failed"); return; } static unsigned voice_len[] = { 3, 3, 3, 3, 2, 2, 3 }; static unsigned common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 }; unsigned char mmsg[0x100]; unsigned int mst = 0, midx = 0, mlen = 0; for (unsigned char *p = buf; size > 0; --size, ++p) { if (*p >= 0xf8) { /* clock events */ } else if (*p >= 0xf0) { if (mst == SYSEX_START && *p == SYSEX_END && midx < sizeof(mmsg)) { mmsg[midx++] = *p; ready = onsysex(mmsg, midx, controls); continue; } mmsg[0] = *p; mlen = common_len[*p & 7]; mst = *p; midx = 1; } else if (*p >= 0x80) { mmsg[0] = *p; mlen = voice_len[(*p >> 4) & 7]; mst = *p; midx = 1; } else if (mst) { if (midx == sizeof(mmsg)) continue; if (midx == 0) mmsg[midx++] = mst; mmsg[midx++] = *p; if (midx == mlen) { oncommon(mmsg, midx, controls); midx = 0; } } } } snd_mixer_elem_t **tail = &mixer->controls; for (size_t i = 0; i < ARRAY_SIZE(controls); ++i) { if (!controls[i].name[0]) continue; *tail = &controls[i]; tail = &(*tail)->next; } } int snd_mixer_open(snd_mixer_t **mixer, int mode) { if (!(*mixer = calloc(1, sizeof(**mixer)))) { WARNX1("calloc"); return -1; } if (!((*mixer)->hdl = mio_open("snd/0", MIO_OUT | MIO_IN, 0))) { WARNX1("mio_open failed"); goto fail; } get_controls(*mixer); return 0; fail: free(*mixer); return -1; } int snd_mixer_close(snd_mixer_t *mixer) { mio_close(mixer->hdl); free(mixer); return 0; } struct _snd_mixer_selem_id { char noop; }; int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t **ptr) { if (!(*ptr = calloc(1, sizeof(**ptr)))) return -1; return 0; } void snd_mixer_selem_id_free(snd_mixer_selem_id_t *obj) { free(obj); } snd_mixer_elem_t* snd_mixer_first_elem(snd_mixer_t *mixer) { return mixer->controls; } snd_mixer_elem_t* snd_mixer_elem_next(snd_mixer_elem_t *elem) { return elem->next; } const char* snd_mixer_selem_get_name(snd_mixer_elem_t *elem) { return elem->name; } int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { if (value) *value = ((float)elem->vol / (float)0x7f) * 100; return 0; } int snd_mixer_selem_get_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { if (value) *value = (long)elem->vol - 0x7f; return 0; } int snd_mixer_selem_get_playback_dB_range(snd_mixer_elem_t *elem, long *min, long *max) { if (max) *max = 0; if (min) *min = -127; return 0; } int snd_mixer_selem_has_playback_volume(snd_mixer_elem_t *elem) { return true; } int snd_mixer_selem_has_playback_volume_joined(snd_mixer_elem_t *elem) { return true; } int snd_mixer_selem_has_playback_switch(snd_mixer_elem_t *elem) { return true; } int snd_mixer_selem_has_playback_switch_joined(snd_mixer_elem_t *elem) { return true; } #include "symversioning-hell.h"