/* ALSA Output. * * Copyright (C) 2016 Reece H. Dunn * * 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_ALSA_ASOUNDLIB_H #include #include #include struct alsa_object { struct audio_object vtable; snd_pcm_t *handle; uint8_t sample_size; char *device; /* saved audio_object_open parameters */ int is_open; enum audio_object_format format; uint32_t rate; uint8_t channels; }; #define to_alsa_object(object) container_of(object, struct alsa_object, vtable) int alsa_object_open(struct audio_object *object, enum audio_object_format format, uint32_t rate, uint8_t channels) { struct alsa_object *self = to_alsa_object(object); if (self->handle) return -EEXIST; snd_pcm_format_t pcm_format; #define FORMAT(srcfmt, dstfmt, size) case srcfmt: pcm_format = dstfmt; self->sample_size = size; break; switch (format) { FORMAT(AUDIO_OBJECT_FORMAT_ALAW, SND_PCM_FORMAT_A_LAW, 1) FORMAT(AUDIO_OBJECT_FORMAT_ULAW, SND_PCM_FORMAT_MU_LAW, 1) FORMAT(AUDIO_OBJECT_FORMAT_S8, SND_PCM_FORMAT_S8, 1) FORMAT(AUDIO_OBJECT_FORMAT_U8, SND_PCM_FORMAT_U8, 1) FORMAT(AUDIO_OBJECT_FORMAT_S16LE, SND_PCM_FORMAT_S16_LE, 2) FORMAT(AUDIO_OBJECT_FORMAT_S16BE, SND_PCM_FORMAT_S16_BE, 2) FORMAT(AUDIO_OBJECT_FORMAT_U16LE, SND_PCM_FORMAT_U16_LE, 2) FORMAT(AUDIO_OBJECT_FORMAT_U16BE, SND_PCM_FORMAT_U16_BE, 2) FORMAT(AUDIO_OBJECT_FORMAT_S18LE, SND_PCM_FORMAT_S18_3LE, 3) FORMAT(AUDIO_OBJECT_FORMAT_S18BE, SND_PCM_FORMAT_S18_3BE, 3) FORMAT(AUDIO_OBJECT_FORMAT_U18LE, SND_PCM_FORMAT_U18_3LE, 3) FORMAT(AUDIO_OBJECT_FORMAT_U18BE, SND_PCM_FORMAT_U18_3BE, 3) FORMAT(AUDIO_OBJECT_FORMAT_S20LE, SND_PCM_FORMAT_S20_3LE, 3) FORMAT(AUDIO_OBJECT_FORMAT_S20BE, SND_PCM_FORMAT_S20_3BE, 3) FORMAT(AUDIO_OBJECT_FORMAT_U20LE, SND_PCM_FORMAT_U20_3LE, 3) FORMAT(AUDIO_OBJECT_FORMAT_U20BE, SND_PCM_FORMAT_U20_3BE, 3) FORMAT(AUDIO_OBJECT_FORMAT_S24LE, SND_PCM_FORMAT_S24_3LE, 3) FORMAT(AUDIO_OBJECT_FORMAT_S24BE, SND_PCM_FORMAT_S24_3BE, 3) FORMAT(AUDIO_OBJECT_FORMAT_U24LE, SND_PCM_FORMAT_U24_3LE, 3) FORMAT(AUDIO_OBJECT_FORMAT_U24BE, SND_PCM_FORMAT_U24_3BE, 3) FORMAT(AUDIO_OBJECT_FORMAT_S24_32LE, SND_PCM_FORMAT_S24_LE, 4) FORMAT(AUDIO_OBJECT_FORMAT_S24_32BE, SND_PCM_FORMAT_S24_BE, 4) FORMAT(AUDIO_OBJECT_FORMAT_U24_32LE, SND_PCM_FORMAT_U24_LE, 4) FORMAT(AUDIO_OBJECT_FORMAT_U24_32BE, SND_PCM_FORMAT_U24_BE, 4) FORMAT(AUDIO_OBJECT_FORMAT_S32LE, SND_PCM_FORMAT_S32_LE, 4) FORMAT(AUDIO_OBJECT_FORMAT_S32BE, SND_PCM_FORMAT_S32_BE, 4) FORMAT(AUDIO_OBJECT_FORMAT_U32LE, SND_PCM_FORMAT_U32_LE, 4) FORMAT(AUDIO_OBJECT_FORMAT_U32BE, SND_PCM_FORMAT_U32_BE, 4) FORMAT(AUDIO_OBJECT_FORMAT_FLOAT32LE, SND_PCM_FORMAT_FLOAT_LE, 4) FORMAT(AUDIO_OBJECT_FORMAT_FLOAT32BE, SND_PCM_FORMAT_FLOAT_BE, 4) FORMAT(AUDIO_OBJECT_FORMAT_FLOAT64LE, SND_PCM_FORMAT_FLOAT64_LE, 8) FORMAT(AUDIO_OBJECT_FORMAT_FLOAT64BE, SND_PCM_FORMAT_FLOAT64_BE, 8) FORMAT(AUDIO_OBJECT_FORMAT_IEC958LE, SND_PCM_FORMAT_IEC958_SUBFRAME_LE, 1) FORMAT(AUDIO_OBJECT_FORMAT_IEC958BE, SND_PCM_FORMAT_IEC958_SUBFRAME_BE, 1) FORMAT(AUDIO_OBJECT_FORMAT_ADPCM, SND_PCM_FORMAT_IMA_ADPCM, 1) FORMAT(AUDIO_OBJECT_FORMAT_MPEG, SND_PCM_FORMAT_MPEG, 1) FORMAT(AUDIO_OBJECT_FORMAT_GSM, SND_PCM_FORMAT_GSM, 1) default: return -EINVAL; } #undef FORMAT snd_pcm_hw_params_t *params = NULL; snd_pcm_hw_params_malloc(¶ms); int err = 0; if ((err = snd_pcm_open(&self->handle, self->device ? self->device : "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) goto error; if ((err = snd_pcm_hw_params_any(self->handle, params)) < 0) goto error; if ((err = snd_pcm_hw_params_set_access(self->handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) goto error; if ((err = snd_pcm_hw_params_set_format(self->handle, params, pcm_format)) < 0) goto error; if ((err = snd_pcm_hw_params_set_rate_near(self->handle, params, &rate, 0)) < 0) goto error; if ((err = snd_pcm_hw_params_set_channels(self->handle, params, channels)) < 0) goto error; if ((err = snd_pcm_hw_params(self->handle, params)) < 0) goto error; if ((err = snd_pcm_prepare(self->handle)) < 0) goto error; self->is_open = 1; self->format = format; self->rate = rate; self->channels = channels; return 0; error: if (params) snd_pcm_hw_params_free(params); if (self->handle) { snd_pcm_close(self->handle); self->handle = NULL; self->is_open = 0; } return err; } void alsa_object_close(struct audio_object *object) { struct alsa_object *self = to_alsa_object(object); if (self->handle) { snd_pcm_close(self->handle); self->handle = NULL; self->is_open = 1; } } void alsa_object_destroy(struct audio_object *object) { struct alsa_object *self = to_alsa_object(object); free(self->device); free(self); } int alsa_object_drain(struct audio_object *object) { struct alsa_object *self = to_alsa_object(object); int ret = 0; if (self->handle) { snd_pcm_drain(self->handle); ret = snd_pcm_prepare(self->handle); } return ret; } int alsa_object_flush(struct audio_object *object) { struct alsa_object *self = to_alsa_object(object); if (!self) return 0; // Using snd_pcm_drop does not discard the audio, so reopen the device // to reset the sound buffer. if (self->is_open) { audio_object_close(object); return audio_object_open(object, self->format, self->rate, self->channels); } return 0; } int alsa_object_write(struct audio_object *object, const void *data, size_t bytes) { struct alsa_object *self = to_alsa_object(object); if (!self->handle) return 0; int err = 0; snd_pcm_uframes_t nToWrite = bytes / self->sample_size; // Number of frames to write. snd_pcm_sframes_t nWritten = 0; // And number alsa actually wrote. while (1) { nWritten = snd_pcm_writei(self->handle, data, nToWrite); if ((nWritten >= 0) && (nWritten < nToWrite)) { // Can happen in case of a signal or underrun. nToWrite -= nWritten; data += nWritten * self->sample_size; // Open question: if a signal caused the short read, should we snd_pcm_prepare? } else if ((nWritten == -EPIPE) || (nWritten == -EBADFD)) { // Either there was an underrun or the PCM was in a bad state. err = snd_pcm_prepare(self->handle); if (err != 0) break; } else if (nWritten == -ESTRPIPE) { // Sound suspended, try to resume. do { err = snd_pcm_resume(self->handle); sleep(1); } while (err == -EAGAIN); if (err == -ENOSYS) { // Hardware doesn't support "fine resume". // So just prepare. err = snd_pcm_prepare(self->handle); } if (err < 0) { break; } } else { err = nWritten; break; } } return err >= 0 ? 0 : err; } const char * alsa_object_strerror(struct audio_object *object, int error) { return snd_strerror(error); } struct audio_object * create_alsa_object(const char *device, const char *application_name, const char *description) { struct alsa_object *self = malloc(sizeof(struct alsa_object)); if (!self) return NULL; self->handle = NULL; self->sample_size = 0; self->device = device ? strdup(device) : NULL; self->is_open = 0; self->vtable.open = alsa_object_open; self->vtable.close = alsa_object_close; self->vtable.destroy = alsa_object_destroy; self->vtable.write = alsa_object_write; self->vtable.drain = alsa_object_drain; self->vtable.flush = alsa_object_flush; self->vtable.strerror = alsa_object_strerror; return &self->vtable; } #else struct audio_object * create_alsa_object(const char *device, const char *application_name, const char *description) { return NULL; } #endif