diff --git a/Makefile.common b/Makefile.common index 6b016603d6..cdda9fb0fc 100644 --- a/Makefile.common +++ b/Makefile.common @@ -383,6 +383,12 @@ endif DEFINES += $(ALSA_CFLAGS) endif +ifeq ($(HAVE_TINYALSA), 1) + OBJ += audio/drivers/tinyalsa.o\ + deps/tinyalsa/pcm.o + DEFINES += -DHAVE_TINYALSA +endif + ifeq ($(HAVE_ROAR), 1) OBJ += audio/drivers/roar.o LIBS += $(ROAR_LIBS) diff --git a/audio/audio_driver.c b/audio/audio_driver.c index 5a36933958..cefab30066 100644 --- a/audio/audio_driver.c +++ b/audio/audio_driver.c @@ -55,6 +55,9 @@ static const audio_driver_t *audio_drivers[] = { &audio_alsathread, #endif #endif +#ifdef HAVE_TINYALSA + &audio_tinyalsa, +#endif #if defined(HAVE_OSS) || defined(HAVE_OSS_BSD) &audio_oss, #endif diff --git a/audio/audio_driver.h b/audio/audio_driver.h index 7df25d6b31..3a62b95c29 100644 --- a/audio/audio_driver.h +++ b/audio/audio_driver.h @@ -259,6 +259,7 @@ extern audio_driver_t audio_rsound; extern audio_driver_t audio_oss; extern audio_driver_t audio_alsa; extern audio_driver_t audio_alsathread; +extern audio_driver_t audio_tinyalsa; extern audio_driver_t audio_roar; extern audio_driver_t audio_openal; extern audio_driver_t audio_opensl; diff --git a/audio/drivers/tinyalsa.c b/audio/drivers/tinyalsa.c new file mode 100644 index 0000000000..24aee1ea55 --- /dev/null +++ b/audio/drivers/tinyalsa.c @@ -0,0 +1,212 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2017 - Daniel De Matteis + * Copyright (C) 2017 - Charlton Head + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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 RetroArch. + * If not, see . + */ + +/* See https://github.com/tinyalsa/tinyalsa */ + +#include <../../deps/tinyalsa/pcm.h> +#include + +#include "../audio_driver.h" + +typedef struct tinyalsa { + struct pcm *pcm; + size_t buffer_size; + bool nonblock; + bool has_float; + bool can_pause; + bool is_paused; + unsigned int frame_bits; +} tinyalsa_t; + +typedef long pcm_sframes_t; + +#define BYTES_TO_FRAMES(bytes, frame_bits) ((bytes) * 8 / frame_bits) + +static void * +tinyalsa_init(const char *device, unsigned rate, + unsigned latency, unsigned block_frames, + unsigned *new_rate) +{ + pcm_sframes_t buffer_size; + struct pcm_config config; + + tinyalsa_t *tinyalsa = (tinyalsa_t*)calloc(1, sizeof(tinyalsa_t)); + if (!tinyalsa) + return NULL; + + config.rate = rate; + config.format = PCM_FORMAT_S16_LE; + config.channels = 2; + config.period_size = 768; + config.period_count = 4; + config.start_threshold = 2048; + config.silence_threshold = 1024 * 2; + config.stop_threshold = 1024 * 2; + + tinyalsa->pcm = pcm_open(0, 0, PCM_OUT, &config); + + if (tinyalsa->pcm == NULL) { + RARCH_ERR("[TINYALSA]: Failed to allocate memory for pcm.\n"); + goto error; + } else if (!pcm_is_ready(tinyalsa->pcm)) { + RARCH_ERR("[TINYALSA]: Cannot open audio device.\n"); + goto error; + } + + buffer_size = pcm_get_buffer_size(tinyalsa->pcm); + tinyalsa->buffer_size = pcm_frames_to_bytes(tinyalsa->pcm, buffer_size); + tinyalsa->frame_bits = pcm_format_to_bits(config.format) * 2; + + tinyalsa->can_pause = true; + tinyalsa->has_float = false; + + RARCH_LOG("[TINYALSA] %u \n", (unsigned int)tinyalsa->buffer_size); + + return tinyalsa; + +error: + RARCH_ERR("[TINYALSA]: Failed to initialize tinyalsa driver.\n"); + return NULL; +} + +static ssize_t +tinyalsa_write(void *data, const void *buf_, size_t size_) +{ + tinyalsa_t *tinyalsa = (tinyalsa_t*)data; + const uint8_t *buf = (const uint8_t*)buf_; + pcm_sframes_t written = 0, frames = 0; + pcm_sframes_t size = BYTES_TO_FRAMES(size_, tinyalsa->frame_bits); + size_t frames_size = tinyalsa->has_float ? sizeof(float) : sizeof(int16_t); + + while (size) { + frames = pcm_writei(tinyalsa->pcm, buf, size); + + written += frames; + buf += (frames << 1) * frames_size; + size -= frames; + } + + return written; + +} + +static bool +tinyalsa_stop(void *data) +{ + tinyalsa_t *tinyalsa = (tinyalsa_t*)data; + + if (tinyalsa->can_pause && !tinyalsa->is_paused) { + int ret = pcm_start(tinyalsa->pcm); + if (ret < 0) + return false; + + tinyalsa->is_paused = true; + } + + return true; +} + +static bool +tinyalsa_alive(void *data) +{ + tinyalsa_t *tinyalsa = (tinyalsa_t*)data; + + if (tinyalsa) + return !tinyalsa->is_paused; + + return false; +} + +static bool +tinyalsa_start(void *data, bool is_shutdown) +{ + tinyalsa_t *tinyalsa = (tinyalsa_t*)data; + + if (tinyalsa->can_pause && tinyalsa->is_paused) { + int ret = pcm_stop(tinyalsa->pcm); + + if (ret < 0) { + RARCH_ERR("[TINYALSA]: Failed to unpause.\n"); + return false; + } + + tinyalsa->is_paused = false; + } + + return true; +} + +static void +tinyalsa_set_nonblock_state(void *data, bool state) +{ + tinyalsa_t *tinyalsa = (tinyalsa_t*)data; + tinyalsa->nonblock = state; +} + +static bool +tinyalsa_use_float(void *data) +{ + tinyalsa_t *tinyalsa = (tinyalsa_t*)data; + + return tinyalsa->has_float; +} + +static void +tinyalsa_free(void *data) +{ + tinyalsa_t *tinyalsa = (tinyalsa_t*)data; + + if (tinyalsa) { + if (tinyalsa->pcm) { + pcm_close(tinyalsa->pcm); + tinyalsa->pcm = NULL; + } + free(tinyalsa); + } +} + + +static size_t +tinyalsa_write_avail(void *data) +{ + /*TODO*/ + return 0; +} + +static size_t +tinyalsa_buffer_size(void *data) +{ + tinyalsa_t *tinyalsa = (tinyalsa_t*)data; + + return tinyalsa->buffer_size; +} + +audio_driver_t audio_tinyalsa = { + tinyalsa_init, /* AUDIO_init */ + tinyalsa_write, /* AUDIO_write */ + tinyalsa_stop, /* AUDIO_stop */ + tinyalsa_start, /* AUDIO_start */ + tinyalsa_alive, /* AUDIO_alive */ + tinyalsa_set_nonblock_state, /* AUDIO_set_nonblock_sate */ + tinyalsa_free, /* AUDIO_free */ + tinyalsa_use_float, /* AUDIO_use_float */ + "tinyalsa", /* "AUDIO" */ + NULL, /* AUDIO_device_list_new */ /*TODO*/ + NULL, /* AUDIO_device_list_free */ /*TODO*/ +/* tinyalsa_write_avail, AUDIO_write_avail */ /*TODO*/ + NULL, /* AUDIO_buffer_size */ /*TODO*/ +}; \ No newline at end of file diff --git a/config.features.h b/config.features.h index a4327e01fe..5e0ef72b6e 100644 --- a/config.features.h +++ b/config.features.h @@ -134,6 +134,12 @@ static const bool _alsa_supp = true; static const bool _alsa_supp = false; #endif +#ifdef HAVE_TINYALSA +static const bool _tinyalsa_supp = true; +#else +static const bool _tinyalsa_supp = false; +#endif + #ifdef HAVE_COREAUDIO static const bool _coreaudio_supp = true; #else diff --git a/configuration.c b/configuration.c index 4c100d6aa4..0cd9e2b497 100644 --- a/configuration.c +++ b/configuration.c @@ -139,6 +139,7 @@ enum audio_driver_enum AUDIO_OSS, AUDIO_ALSA, AUDIO_ALSATHREAD, + AUDIO_TINYALSA, AUDIO_ROAR, AUDIO_AL, AUDIO_SL, @@ -312,6 +313,8 @@ static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_PULSE; static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_ALSATHREAD; #elif defined(HAVE_ALSA) static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_ALSA; +#elif defined(HAVE_TINYALSA) +static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_TINYALSA; #elif defined(HAVE_OSS) static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_OSS; #elif defined(HAVE_JACK) @@ -546,6 +549,8 @@ const char *config_get_default_audio(void) return "alsa"; case AUDIO_ALSATHREAD: return "alsathread"; + case AUDIO_TINYALSA: + return "tinyalsa"; case AUDIO_ROAR: return "roar"; case AUDIO_COREAUDIO: diff --git a/deps/tinyalsa/pcm.c b/deps/tinyalsa/pcm.c new file mode 100644 index 0000000000..ef38c9cf6e --- /dev/null +++ b/deps/tinyalsa/pcm.c @@ -0,0 +1,1480 @@ +/* pcm.c +** +** Copyright 2011, The Android Open Source Project +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +** DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#define __force +#define __bitwise +#define __user +#include + +#include +#include + +#define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL +#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2) + +static inline int param_is_mask(int p) +{ + return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && + (p <= SNDRV_PCM_HW_PARAM_LAST_MASK); +} + +static inline int param_is_interval(int p) +{ + return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) && + (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL); +} + +static inline const struct snd_interval *param_get_interval(const struct snd_pcm_hw_params *p, int n) +{ + return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]); +} + +static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n) +{ + return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]); +} + +static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n) +{ + return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]); +} + +static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned int bit) +{ + if (bit >= SNDRV_MASK_MAX) + return; + if (param_is_mask(n)) { + struct snd_mask *m = param_to_mask(p, n); + m->bits[0] = 0; + m->bits[1] = 0; + m->bits[bit >> 5] |= (1 << (bit & 31)); + } +} + +static void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned int val) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + i->min = val; + } +} + +static unsigned int param_get_min(const struct snd_pcm_hw_params *p, int n) +{ + if (param_is_interval(n)) { + const struct snd_interval *i = param_get_interval(p, n); + return i->min; + } + return 0; +} + +static unsigned int param_get_max(const struct snd_pcm_hw_params *p, int n) +{ + if (param_is_interval(n)) { + const struct snd_interval *i = param_get_interval(p, n); + return i->max; + } + return 0; +} + +static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + i->min = val; + i->max = val; + i->integer = 1; + } +} + +static unsigned int param_get_int(struct snd_pcm_hw_params *p, int n) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + if (i->integer) + return i->max; + } + return 0; +} + +static void param_init(struct snd_pcm_hw_params *p) +{ + int n; + + memset(p, 0, sizeof(*p)); + for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK; + n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) { + struct snd_mask *m = param_to_mask(p, n); + m->bits[0] = ~0; + m->bits[1] = ~0; + } + for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; + n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) { + struct snd_interval *i = param_to_interval(p, n); + i->min = 0; + i->max = ~0; + } + p->rmask = ~0U; + p->cmask = 0; + p->info = ~0U; +} + +static unsigned int pcm_format_to_alsa(enum pcm_format format) +{ + switch (format) { + + case PCM_FORMAT_S8: + return SNDRV_PCM_FORMAT_S8; + + default: + case PCM_FORMAT_S16_LE: + return SNDRV_PCM_FORMAT_S16_LE; + case PCM_FORMAT_S16_BE: + return SNDRV_PCM_FORMAT_S16_BE; + + case PCM_FORMAT_S24_LE: + return SNDRV_PCM_FORMAT_S24_LE; + case PCM_FORMAT_S24_BE: + return SNDRV_PCM_FORMAT_S24_BE; + + case PCM_FORMAT_S24_3LE: + return SNDRV_PCM_FORMAT_S24_3LE; + case PCM_FORMAT_S24_3BE: + return SNDRV_PCM_FORMAT_S24_3BE; + + case PCM_FORMAT_S32_LE: + return SNDRV_PCM_FORMAT_S32_LE; + case PCM_FORMAT_S32_BE: + return SNDRV_PCM_FORMAT_S32_BE; + }; +} + +#define PCM_ERROR_MAX 128 + +/** A PCM handle. + * @ingroup libtinyalsa-pcm + */ +struct pcm { + /** The PCM's file descriptor */ + int fd; + /** Flags that were passed to @ref pcm_open */ + unsigned int flags; + /** Whether the PCM is running or not */ + int running:1; + /** Whether or not the PCM has been prepared */ + int prepared:1; + /** The number of underruns that have occured */ + int underruns; + /** Size of the buffer */ + unsigned int buffer_size; + /** The boundary for ring buffer pointers */ + unsigned int boundary; + /** Description of the last error that occured */ + char error[PCM_ERROR_MAX]; + /** Configuration that was passed to @ref pcm_open */ + struct pcm_config config; + struct snd_pcm_mmap_status *mmap_status; + struct snd_pcm_mmap_control *mmap_control; + struct snd_pcm_sync_ptr *sync_ptr; + void *mmap_buffer; + unsigned int noirq_frames_per_msec; + /** The delay of the PCM, in terms of frames */ + long pcm_delay; + /** The subdevice corresponding to the PCM */ + unsigned int subdevice; +}; + +static int oops(struct pcm *pcm, int e, const char *fmt, ...) +{ + va_list ap; + int sz; + + va_start(ap, fmt); + vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap); + va_end(ap); + sz = strlen(pcm->error); + + if (errno) + snprintf(pcm->error + sz, PCM_ERROR_MAX - sz, + ": %s", strerror(e)); + return -1; +} + +/** Gets the buffer size of the PCM. + * @param pcm A PCM handle. + * @return The buffer size of the PCM. + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_get_buffer_size(const struct pcm *pcm) +{ + return pcm->buffer_size; +} + +/** Gets the channel count of the PCM. + * @param pcm A PCM handle. + * @return The channel count of the PCM. + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_get_channels(const struct pcm *pcm) +{ + return pcm->config.channels; +} + +/** Gets the PCM configuration. + * @param pcm A PCM handle. + * @return The PCM configuration. + * This function only returns NULL if + * @p pcm is NULL. + * @ingroup libtinyalsa-pcm + * */ +const struct pcm_config * pcm_get_config(const struct pcm *pcm) +{ + if (pcm == NULL) + return NULL; + return &pcm->config; +} + +/** Gets the rate of the PCM. + * The rate is given in frames per second. + * @param pcm A PCM handle. + * @return The rate of the PCM. + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_get_rate(const struct pcm *pcm) +{ + return pcm->config.rate; +} + +/** Gets the format of the PCM. + * @param pcm A PCM handle. + * @return The format of the PCM. + * @ingroup libtinyalsa-pcm + */ +enum pcm_format pcm_get_format(const struct pcm *pcm) +{ + return pcm->config.format; +} + +/** Gets the file descriptor of the PCM. + * Useful for extending functionality of the PCM when needed. + * @param pcm A PCM handle. + * @return The file descriptor of the PCM. + * @ingroup libtinyalsa-pcm + */ +int pcm_get_file_descriptor(const struct pcm *pcm) +{ + return pcm->fd; +} + +/** Gets the error message for the last error that occured. + * If no error occured and this function is called, the results are undefined. + * @param pcm A PCM handle. + * @return The error message of the last error that occured. + * @ingroup libtinyalsa-pcm + */ +const char* pcm_get_error(const struct pcm *pcm) +{ + return pcm->error; +} + +/** Sets the PCM configuration. + * @param pcm A PCM handle. + * @param config The configuration to use for the + * PCM. This parameter may be NULL, in which case + * the default configuration is used. + * @returns Zero on success, a negative errno value + * on failure. + * @ingroup libtinyalsa-pcm + * */ +int pcm_set_config(struct pcm *pcm, const struct pcm_config *config) +{ + if (pcm == NULL) + return -EFAULT; + else if (config == NULL) { + config = &pcm->config; + pcm->config.channels = 2; + pcm->config.rate = 48000; + pcm->config.period_size = 1024; + pcm->config.period_count = 4; + pcm->config.format = PCM_FORMAT_S16_LE; + pcm->config.start_threshold = config->period_count * config->period_size; + pcm->config.stop_threshold = config->period_count * config->period_size; + pcm->config.silence_threshold = 0; + } else + pcm->config = *config; + + struct snd_pcm_hw_params params; + param_init(¶ms); + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT, + pcm_format_to_alsa(config->format)); + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT, + SNDRV_PCM_SUBFORMAT_STD); + param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + pcm_format_to_bits(config->format)); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS, + pcm_format_to_bits(config->format) * config->channels); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS, + config->channels); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate); + + if (pcm->flags & PCM_NOIRQ) { + + if (!(pcm->flags & PCM_MMAP)) { + oops(pcm, -EINVAL, "noirq only currently supported with mmap()."); + return -EINVAL; + } + + params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP; + pcm->noirq_frames_per_msec = config->rate / 1000; + } + + if (pcm->flags & PCM_MMAP) + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, + SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, + SNDRV_PCM_ACCESS_RW_INTERLEAVED); + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { + int errno_copy = errno; + oops(pcm, -errno, "cannot set hw params"); + return -errno_copy; + } + + /* get our refined hw_params */ + pcm->config.period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + pcm->config.period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS); + pcm->buffer_size = config->period_count * config->period_size; + + if (pcm->flags & PCM_MMAP) { + pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size), + PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0); + if (pcm->mmap_buffer == MAP_FAILED) { + int errno_copy = errno; + oops(pcm, -errno, "failed to mmap buffer %d bytes\n", + pcm_frames_to_bytes(pcm, pcm->buffer_size)); + return -errno_copy; + } + } + + struct snd_pcm_sw_params sparams; + memset(&sparams, 0, sizeof(sparams)); + sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE; + sparams.period_step = 1; + sparams.avail_min = 1; + + if (!config->start_threshold) { + if (pcm->flags & PCM_IN) + pcm->config.start_threshold = sparams.start_threshold = 1; + else + pcm->config.start_threshold = sparams.start_threshold = + config->period_count * config->period_size / 2; + } else + sparams.start_threshold = config->start_threshold; + + /* pick a high stop threshold - todo: does this need further tuning */ + if (!config->stop_threshold) { + if (pcm->flags & PCM_IN) + pcm->config.stop_threshold = sparams.stop_threshold = + config->period_count * config->period_size * 10; + else + pcm->config.stop_threshold = sparams.stop_threshold = + config->period_count * config->period_size; + } + else + sparams.stop_threshold = config->stop_threshold; + + sparams.xfer_align = config->period_size / 2; /* needed for old kernels */ + sparams.silence_size = 0; + sparams.silence_threshold = config->silence_threshold; + pcm->boundary = sparams.boundary = pcm->buffer_size; + + while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size) + pcm->boundary *= 2; + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { + int errno_copy = errno; + oops(pcm, -errno, "cannot set sw params"); + return -errno_copy; + } + + return 0; +} + +/** Gets the subdevice on which the pcm has been opened. + * @param pcm A PCM handle. + * @return The subdevice on which the pcm has been opened */ +unsigned int pcm_get_subdevice(const struct pcm *pcm) +{ + return pcm->subdevice; +} + +/** Determines the number of bits occupied by a @ref pcm_format. + * @param format A PCM format. + * @return The number of bits associated with @p format + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_format_to_bits(enum pcm_format format) +{ + switch (format) { + case PCM_FORMAT_S32_LE: + case PCM_FORMAT_S32_BE: + case PCM_FORMAT_S24_LE: + case PCM_FORMAT_S24_BE: + return 32; + case PCM_FORMAT_S24_3LE: + case PCM_FORMAT_S24_3BE: + return 24; + default: + case PCM_FORMAT_S16_LE: + case PCM_FORMAT_S16_BE: + return 16; + case PCM_FORMAT_S8: + return 8; + }; +} + +/** Determines how many frames of a PCM can fit into a number of bytes. + * @param pcm A PCM handle. + * @param bytes The number of bytes. + * @return The number of frames that may fit into @p bytes + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_bytes_to_frames(const struct pcm *pcm, unsigned int bytes) +{ + return bytes / (pcm->config.channels * + (pcm_format_to_bits(pcm->config.format) >> 3)); +} + +/** Determines how many bytes are occupied by a number of frames of a PCM. + * @param pcm A PCM handle. + * @param frames The number of frames of a PCM. + * @return The bytes occupied by @p frames. + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_frames_to_bytes(const struct pcm *pcm, unsigned int frames) +{ + return frames * pcm->config.channels * + (pcm_format_to_bits(pcm->config.format) >> 3); +} + +static int pcm_sync_ptr(struct pcm *pcm, int flags) +{ + if (pcm->sync_ptr) { + pcm->sync_ptr->flags = flags; + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0) { + oops(pcm, errno, "failed to sync mmap ptr"); + return -1; + } + return 0; + } + return -1; +} + +static int pcm_hw_mmap_status(struct pcm *pcm) +{ + if (pcm->sync_ptr) + return 0; + + int page_size = sysconf(_SC_PAGE_SIZE); + pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED, + pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); + if (pcm->mmap_status == MAP_FAILED) + pcm->mmap_status = NULL; + if (!pcm->mmap_status) + goto mmap_error; + + pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL); + if (pcm->mmap_control == MAP_FAILED) + pcm->mmap_control = NULL; + if (!pcm->mmap_control) { + munmap(pcm->mmap_status, page_size); + pcm->mmap_status = NULL; + goto mmap_error; + } + pcm->mmap_control->avail_min = 1; + + return 0; + +mmap_error: + + pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr)); + if (!pcm->sync_ptr) + return -ENOMEM; + pcm->mmap_status = &pcm->sync_ptr->s.status; + pcm->mmap_control = &pcm->sync_ptr->c.control; + pcm->mmap_control->avail_min = 1; + pcm_sync_ptr(pcm, 0); + + return 0; +} + +static void pcm_hw_munmap_status(struct pcm *pcm) { + if (pcm->sync_ptr) { + free(pcm->sync_ptr); + pcm->sync_ptr = NULL; + } else { + int page_size = sysconf(_SC_PAGE_SIZE); + if (pcm->mmap_status) + munmap(pcm->mmap_status, page_size); + if (pcm->mmap_control) + munmap(pcm->mmap_control, page_size); + } + pcm->mmap_status = NULL; + pcm->mmap_control = NULL; +} + +static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset, + char *buf, unsigned int src_offset, + unsigned int frames) +{ + int size_bytes = pcm_frames_to_bytes(pcm, frames); + int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset); + int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset); + + /* interleaved only atm */ + if (pcm->flags & PCM_IN) + memcpy(buf + src_offset_bytes, + (char*)pcm->mmap_buffer + pcm_offset_bytes, + size_bytes); + else + memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes, + buf + src_offset_bytes, + size_bytes); + return 0; +} + +static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf, + unsigned int offset, unsigned int size) +{ + void *pcm_areas; + int commit; + unsigned int pcm_offset, frames, count = 0; + + while (size > 0) { + frames = size; + pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); + pcm_areas_copy(pcm, pcm_offset, buf, offset, frames); + commit = pcm_mmap_commit(pcm, pcm_offset, frames); + if (commit < 0) { + oops(pcm, commit, "failed to commit %d frames\n", frames); + return commit; + } + + offset += commit; + count += commit; + size -= commit; + } + return count; +} + +/** Returns available frames in pcm buffer and corresponding time stamp. + * The clock is CLOCK_MONOTONIC if flag @ref PCM_MONOTONIC was specified in @ref pcm_open, + * otherwise the clock is CLOCK_REALTIME. + * For an input stream, frames available are frames ready for the application to read. + * For an output stream, frames available are the number of empty frames available for the application to write. + * Only available for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param avail The number of available frames + * @param tstamp The timestamp + * @return On success, zero is returned; on failure, negative one. + */ +int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, + struct timespec *tstamp) +{ + int frames; + int rc; + snd_pcm_uframes_t hw_ptr; + + if (!pcm_is_ready(pcm)) + return -1; + + rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_HWSYNC); + if (rc < 0) + return -1; + + if ((pcm->mmap_status->state != PCM_STATE_RUNNING) && + (pcm->mmap_status->state != PCM_STATE_DRAINING)) + return -1; + + *tstamp = pcm->mmap_status->tstamp; + if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0) + return -1; + + hw_ptr = pcm->mmap_status->hw_ptr; + if (pcm->flags & PCM_IN) + frames = hw_ptr - pcm->mmap_control->appl_ptr; + else + frames = hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr; + + if (frames < 0) + return -1; + + *avail = (unsigned int)frames; + + return 0; +} + +/** Writes audio samples to PCM. + * If the PCM has not been started, it is started in this function. + * This function is only valid for PCMs opened with the @ref PCM_OUT flag. + * This function is not valid for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param data The audio sample array + * @param frame_count The number of frames occupied by the sample array. + * This value should not be greater than @ref TINYALSA_FRAMES_MAX + * or INT_MAX. + * @return On success, this function returns the number of frames written; otherwise, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count) +{ + struct snd_xferi x; + + if (pcm->flags & PCM_IN) + return -EINVAL; +#if UINT_MAX > TINYALSA_FRAMES_MAX + if (frame_count > TINYALSA_FRAMES_MAX) + return -EINVAL; +#endif + if (frame_count > INT_MAX) + return -EINVAL; + + x.buf = (void*)data; + x.frames = frame_count; + x.result = 0; + for (;;) { + if (!pcm->running) { + int prepare_error = pcm_prepare(pcm); + if (prepare_error) + return prepare_error; + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) + return oops(pcm, errno, "cannot write initial data"); + pcm->running = 1; + return 0; + } + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) { + pcm->prepared = 0; + pcm->running = 0; + if (errno == EPIPE) { + /* we failed to make our window -- try to restart if we are + * allowed to do so. Otherwise, simply allow the EPIPE error to + * propagate up to the app level */ + pcm->underruns++; + if (pcm->flags & PCM_NORESTART) + return -EPIPE; + continue; + } + return oops(pcm, errno, "cannot write stream data"); + } + return x.result; + } +} + +/** Reads audio samples from PCM. + * If the PCM has not been started, it is started in this function. + * This function is only valid for PCMs opened with the @ref PCM_IN flag. + * This function is not valid for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param data The audio sample array + * @param frame_count The number of frames occupied by the sample array. + * This value should not be greater than @ref TINYALSA_FRAMES_MAX + * or INT_MAX. + * @return On success, this function returns the number of frames written; otherwise, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count) +{ + struct snd_xferi x; + + if (!(pcm->flags & PCM_IN)) + return -EINVAL; +#if UINT_MAX > TINYALSA_FRAMES_MAX + if (frame_count > TINYALSA_FRAMES_MAX) + return -EINVAL; +#endif + if (frame_count > INT_MAX) + return -EINVAL; + + x.buf = data; + x.frames = frame_count; + x.result = 0; + for (;;) { + if ((!pcm->running) && (pcm_start(pcm) < 0)) + return -errno; + else if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) { + pcm->prepared = 0; + pcm->running = 0; + if (errno == EPIPE) { + /* we failed to make our window -- try to restart */ + pcm->underruns++; + continue; + } + return oops(pcm, errno, "cannot read stream data"); + } + return x.result; + } +} + +/** Writes audio samples to PCM. + * If the PCM has not been started, it is started in this function. + * This function is only valid for PCMs opened with the @ref PCM_OUT flag. + * This function is not valid for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param data The audio sample array + * @param count The number of bytes occupied by the sample array. + * @return On success, this function returns zero; otherwise, a negative number. + * @deprecated + * @ingroup libtinyalsa-pcm + */ +int pcm_write(struct pcm *pcm, const void *data, unsigned int count) +{ + return pcm_writei(pcm, data, pcm_bytes_to_frames(pcm, count)); +} + +/** Reads audio samples from PCM. + * If the PCM has not been started, it is started in this function. + * This function is only valid for PCMs opened with the @ref PCM_IN flag. + * This function is not valid for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param data The audio sample array + * @param count The number of bytes occupied by the sample array. + * @return On success, this function returns zero; otherwise, a negative number. + * @deprecated + * @ingroup libtinyalsa-pcm + */ +int pcm_read(struct pcm *pcm, void *data, unsigned int count) +{ + return pcm_readi(pcm, data, pcm_bytes_to_frames(pcm, count)); +} + +static struct pcm bad_pcm = { + .fd = -1, +}; + +/** Gets the hardware parameters of a PCM, without created a PCM handle. + * @param card The card of the PCM. + * The default card is zero. + * @param device The device of the PCM. + * The default device is zero. + * @param flags Specifies whether the PCM is an input or output. + * May be one of the following: + * - @ref PCM_IN + * - @ref PCM_OUT + * @return On success, the hardware parameters of the PCM; on failure, NULL. + * @ingroup libtinyalsa-pcm + */ +struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, + unsigned int flags) +{ + struct snd_pcm_hw_params *params; + char fn[256]; + int fd; + + snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, + flags & PCM_IN ? 'c' : 'p'); + + fd = open(fn, O_RDWR); + if (fd < 0) { + fprintf(stderr, "cannot open device '%s'\n", fn); + goto err_open; + } + + params = calloc(1, sizeof(struct snd_pcm_hw_params)); + if (!params) + goto err_calloc; + + param_init(params); + if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) { + fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno); + goto err_hw_refine; + } + + close(fd); + + return (struct pcm_params *)params; + +err_hw_refine: + free(params); +err_calloc: + close(fd); +err_open: + return NULL; +} + +/** Frees the hardware parameters returned by @ref pcm_params_get. + * @param pcm_params Hardware parameters of a PCM. + * May be NULL. + * @ingroup libtinyalsa-pcm + */ +void pcm_params_free(struct pcm_params *pcm_params) +{ + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + + if (params) + free(params); +} + +static int pcm_param_to_alsa(enum pcm_param param) +{ + switch (param) { + case PCM_PARAM_ACCESS: + return SNDRV_PCM_HW_PARAM_ACCESS; + case PCM_PARAM_FORMAT: + return SNDRV_PCM_HW_PARAM_FORMAT; + case PCM_PARAM_SUBFORMAT: + return SNDRV_PCM_HW_PARAM_SUBFORMAT; + case PCM_PARAM_SAMPLE_BITS: + return SNDRV_PCM_HW_PARAM_SAMPLE_BITS; + break; + case PCM_PARAM_FRAME_BITS: + return SNDRV_PCM_HW_PARAM_FRAME_BITS; + break; + case PCM_PARAM_CHANNELS: + return SNDRV_PCM_HW_PARAM_CHANNELS; + break; + case PCM_PARAM_RATE: + return SNDRV_PCM_HW_PARAM_RATE; + break; + case PCM_PARAM_PERIOD_TIME: + return SNDRV_PCM_HW_PARAM_PERIOD_TIME; + break; + case PCM_PARAM_PERIOD_SIZE: + return SNDRV_PCM_HW_PARAM_PERIOD_SIZE; + break; + case PCM_PARAM_PERIOD_BYTES: + return SNDRV_PCM_HW_PARAM_PERIOD_BYTES; + break; + case PCM_PARAM_PERIODS: + return SNDRV_PCM_HW_PARAM_PERIODS; + break; + case PCM_PARAM_BUFFER_TIME: + return SNDRV_PCM_HW_PARAM_BUFFER_TIME; + break; + case PCM_PARAM_BUFFER_SIZE: + return SNDRV_PCM_HW_PARAM_BUFFER_SIZE; + break; + case PCM_PARAM_BUFFER_BYTES: + return SNDRV_PCM_HW_PARAM_BUFFER_BYTES; + break; + case PCM_PARAM_TICK_TIME: + return SNDRV_PCM_HW_PARAM_TICK_TIME; + break; + + default: + return -1; + } +} + +/** Gets a mask from a PCM's hardware parameters. + * @param pcm_params A PCM's hardware parameters. + * @param param The parameter to get. + * @return If @p pcm_params is NULL or @p param is not a mask, NULL is returned. + * Otherwise, the mask associated with @p param is returned. + * @ingroup libtinyalsa-pcm + */ +const struct pcm_mask *pcm_params_get_mask(const struct pcm_params *pcm_params, + enum pcm_param param) +{ + int p; + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + if (params == NULL) { + return NULL; + } + + p = pcm_param_to_alsa(param); + if (p < 0 || !param_is_mask(p)) { + return NULL; + } + + return (const struct pcm_mask *)param_to_mask(params, p); +} + +/** Get the minimum of a specified PCM parameter. + * @param pcm_params A PCM parameters structure. + * @param param The specified parameter to get the minimum of. + * @returns On success, the parameter minimum. + * On failure, zero. + */ +unsigned int pcm_params_get_min(const struct pcm_params *pcm_params, + enum pcm_param param) +{ + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + int p; + + if (!params) + return 0; + + p = pcm_param_to_alsa(param); + if (p < 0) + return 0; + + return param_get_min(params, p); +} + +/** Get the maximum of a specified PCM parameter. + * @param pcm_params A PCM parameters structure. + * @param param The specified parameter to get the maximum of. + * @returns On success, the parameter maximum. + * On failure, zero. + */ +unsigned int pcm_params_get_max(const struct pcm_params *pcm_params, + enum pcm_param param) +{ + const struct snd_pcm_hw_params *params = (const struct snd_pcm_hw_params *)pcm_params; + int p; + + if (!params) + return 0; + + p = pcm_param_to_alsa(param); + if (p < 0) + return 0; + + return param_get_max(params, p); +} + +/** Closes a PCM returned by @ref pcm_open. + * @param pcm A PCM returned by @ref pcm_open. + * May not be NULL. + * @return Always returns zero. + * @ingroup libtinyalsa-pcm + */ +int pcm_close(struct pcm *pcm) +{ + if (pcm == &bad_pcm) + return 0; + + pcm_hw_munmap_status(pcm); + + if (pcm->flags & PCM_MMAP) { + pcm_stop(pcm); + munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); + } + + if (pcm->fd >= 0) + close(pcm->fd); + pcm->prepared = 0; + pcm->running = 0; + pcm->buffer_size = 0; + pcm->fd = -1; + free(pcm); + return 0; +} + +/** Opens a PCM by it's name. + * @param name The name of the PCM. + * The name is given in the format: hw:card,device + * @param flags Specify characteristics and functionality about the pcm. + * May be a bitwise AND of the following: + * - @ref PCM_IN + * - @ref PCM_OUT + * - @ref PCM_MMAP + * - @ref PCM_NOIRQ + * - @ref PCM_MONOTONIC + * @param config The hardware and software parameters to open the PCM with. + * @returns A PCM structure. + * If an error occurs allocating memory for the PCM, NULL is returned. + * Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready. + * If @ref pcm_is_ready, check @ref pcm_get_error for more information. + * @ingroup libtinyalsa-pcm + */ +struct pcm *pcm_open_by_name(const char *name, + unsigned int flags, + const struct pcm_config *config) +{ + unsigned int card, device; + if ((name[0] != 'h') + || (name[1] != 'w') + || (name[2] != ':')) { + return NULL; + } else if (sscanf(&name[3], "%u,%u", &card, &device) != 2) { + return NULL; + } + return pcm_open(card, device, flags, config); +} + +/** Opens a PCM. + * @param card The card that the pcm belongs to. + * The default card is zero. + * @param device The device that the pcm belongs to. + * The default device is zero. + * @param flags Specify characteristics and functionality about the pcm. + * May be a bitwise AND of the following: + * - @ref PCM_IN + * - @ref PCM_OUT + * - @ref PCM_MMAP + * - @ref PCM_NOIRQ + * - @ref PCM_MONOTONIC + * @param config The hardware and software parameters to open the PCM with. + * @returns A PCM structure. + * If an error occurs allocating memory for the PCM, NULL is returned. + * Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready. + * If @ref pcm_is_ready, check @ref pcm_get_error for more information. + * @ingroup libtinyalsa-pcm + */ +struct pcm *pcm_open(unsigned int card, unsigned int device, + unsigned int flags, const struct pcm_config *config) +{ + struct pcm *pcm; + struct snd_pcm_info info; + char fn[256]; + int rc; + + pcm = calloc(1, sizeof(struct pcm)); + if (!pcm) + return &bad_pcm; + + snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, + flags & PCM_IN ? 'c' : 'p'); + + pcm->flags = flags; + pcm->fd = open(fn, O_RDWR); + if (pcm->fd < 0) { + oops(pcm, errno, "cannot open device '%s'", fn); + return pcm; + } + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { + oops(pcm, errno, "cannot get info"); + goto fail_close; + } + pcm->subdevice = info.subdevice; + + if (pcm_set_config(pcm, config) != 0) + goto fail_close; + + rc = pcm_hw_mmap_status(pcm); + if (rc < 0) { + oops(pcm, rc, "mmap status failed"); + goto fail; + } + +#ifdef SNDRV_PCM_IOCTL_TTSTAMP + if (pcm->flags & PCM_MONOTONIC) { + int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC; + rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg); + if (rc < 0) { + oops(pcm, rc, "cannot set timestamp type"); + goto fail; + } + } +#endif + + pcm->underruns = 0; + return pcm; + +fail: + if (flags & PCM_MMAP) + munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); +fail_close: + close(pcm->fd); + pcm->fd = -1; + return pcm; +} + +/** Checks if a PCM file has been opened without error. + * @param pcm A PCM handle. + * May be NULL. + * @return If a PCM's file descriptor is not valid or the pointer is NULL, it returns zero. + * Otherwise, the function returns one. + * @ingroup libtinyalsa-pcm + */ +int pcm_is_ready(const struct pcm *pcm) +{ + if (pcm != NULL) { + return pcm->fd >= 0; + } + return 0; +} + +/** Links two PCMs. + * After this function is called, the two PCMs will prepare, start and stop in sync (at the same time). + * If an error occurs, the error message will be written to @p pcm1. + * @param pcm1 A PCM handle. + * @param pcm2 Another PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_link(struct pcm *pcm1, struct pcm *pcm2) +{ + int err = ioctl(pcm1->fd, SNDRV_PCM_IOCTL_LINK, pcm2->fd); + if (err == -1) { + return oops(pcm1, errno, "cannot link PCM"); + } + return 0; +} + +/** Unlinks a PCM. + * @see @ref pcm_link + * @param pcm A PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_unlink(struct pcm *pcm) +{ + int err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_UNLINK); + if (err == -1) { + return oops(pcm, errno, "cannot unlink PCM"); + } + return 0; +} + +/** Prepares a PCM, if it has not been prepared already. + * @param pcm A PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_prepare(struct pcm *pcm) +{ + if (pcm->prepared) + return 0; + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0) + return oops(pcm, errno, "cannot prepare channel"); + + pcm->prepared = 1; + return 0; +} + +/** Starts a PCM. + * If the PCM has not been prepared, + * it is prepared in this function. + * @param pcm A PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_start(struct pcm *pcm) +{ + int prepare_error = pcm_prepare(pcm); + if (prepare_error) + return prepare_error; + + if (pcm->flags & PCM_MMAP) + pcm_sync_ptr(pcm, 0); + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0) + return oops(pcm, errno, "cannot start channel"); + + pcm->running = 1; + return 0; +} + +/** Stops a PCM. + * @param pcm A PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_stop(struct pcm *pcm) +{ + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0) + return oops(pcm, errno, "cannot stop channel"); + + pcm->prepared = 0; + pcm->running = 0; + return 0; +} + +static inline int pcm_mmap_playback_avail(struct pcm *pcm) +{ + int avail; + + avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr; + + if (avail < 0) + avail += pcm->boundary; + else if (avail >= (int)pcm->boundary) + avail -= pcm->boundary; + + return avail; +} + +static inline int pcm_mmap_capture_avail(struct pcm *pcm) +{ + int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr; + if (avail < 0) + avail += pcm->boundary; + return avail; +} + +static inline int pcm_mmap_avail(struct pcm *pcm) +{ + pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC); + if (pcm->flags & PCM_IN) + return pcm_mmap_capture_avail(pcm); + else + return pcm_mmap_playback_avail(pcm); +} + +static void pcm_mmap_appl_forward(struct pcm *pcm, int frames) +{ + unsigned int appl_ptr = pcm->mmap_control->appl_ptr; + appl_ptr += frames; + + /* check for boundary wrap */ + if (appl_ptr > pcm->boundary) + appl_ptr -= pcm->boundary; + pcm->mmap_control->appl_ptr = appl_ptr; +} + +int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, + unsigned int *frames) +{ + unsigned int continuous, copy_frames, avail; + + /* return the mmap buffer */ + *areas = pcm->mmap_buffer; + + /* and the application offset in frames */ + *offset = pcm->mmap_control->appl_ptr % pcm->buffer_size; + + avail = pcm_mmap_avail(pcm); + if (avail > pcm->buffer_size) + avail = pcm->buffer_size; + continuous = pcm->buffer_size - *offset; + + /* we can only copy frames if the are availabale and continuos */ + copy_frames = *frames; + if (copy_frames > avail) + copy_frames = avail; + if (copy_frames > continuous) + copy_frames = continuous; + *frames = copy_frames; + + return 0; +} + +int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames) +{ + int ret; + + /* not used */ + (void) offset; + + /* update the application pointer in userspace and kernel */ + pcm_mmap_appl_forward(pcm, frames); + ret = pcm_sync_ptr(pcm, 0); + if (ret != 0){ + printf("%d\n", ret); + return ret; + } + + return frames; +} + +int pcm_avail_update(struct pcm *pcm) +{ + pcm_sync_ptr(pcm, 0); + return pcm_mmap_avail(pcm); +} + +int pcm_state(struct pcm *pcm) +{ + int err = pcm_sync_ptr(pcm, 0); + if (err < 0) + return err; + + return pcm->mmap_status->state; +} + +/** Waits for frames to be available for read or write operations. + * @param pcm A PCM handle. + * @param timeout The maximum amount of time to wait for, in terms of milliseconds. + * @returns If frames became available, one is returned. + * If a timeout occured, zero is returned. + * If an error occured, a negative number is returned. + * @ingroup libtinyalsa-pcm + */ +int pcm_wait(struct pcm *pcm, int timeout) +{ + struct pollfd pfd; + int err; + + pfd.fd = pcm->fd; + pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL; + + do { + /* let's wait for avail or timeout */ + err = poll(&pfd, 1, timeout); + if (err < 0) + return -errno; + + /* timeout ? */ + if (err == 0) + return 0; + + /* have we been interrupted ? */ + if (errno == -EINTR) + continue; + + /* check for any errors */ + if (pfd.revents & (POLLERR | POLLNVAL)) { + switch (pcm_state(pcm)) { + case PCM_STATE_XRUN: + return -EPIPE; + case PCM_STATE_SUSPENDED: + return -ESTRPIPE; + case PCM_STATE_DISCONNECTED: + return -ENODEV; + default: + return -EIO; + } + } + /* poll again if fd not ready for IO */ + } while (!(pfd.revents & (POLLIN | POLLOUT))); + + return 1; +} + +int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes) +{ + int err = 0, frames, avail; + unsigned int offset = 0, count; + + if (bytes == 0) + return 0; + + count = pcm_bytes_to_frames(pcm, bytes); + + while (count > 0) { + + /* get the available space for writing new frames */ + avail = pcm_avail_update(pcm); + if (avail < 0) { + fprintf(stderr, "cannot determine available mmap frames"); + return err; + } + + /* start the audio if we reach the threshold */ + if (!pcm->running && + (pcm->buffer_size - avail) >= pcm->config.start_threshold) { + if (pcm_start(pcm) < 0) { + fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n", + (unsigned int)pcm->mmap_status->hw_ptr, + (unsigned int)pcm->mmap_control->appl_ptr, + avail); + return -errno; + } + } + + /* sleep until we have space to write new frames */ + if (pcm->running && + (unsigned int)avail < pcm->mmap_control->avail_min) { + int time = -1; + + if (pcm->flags & PCM_NOIRQ) + time = (pcm->buffer_size - avail - pcm->mmap_control->avail_min) + / pcm->noirq_frames_per_msec; + + err = pcm_wait(pcm, time); + if (err < 0) { + pcm->prepared = 0; + pcm->running = 0; + fprintf(stderr, "wait error: hw 0x%x app 0x%x avail 0x%x\n", + (unsigned int)pcm->mmap_status->hw_ptr, + (unsigned int)pcm->mmap_control->appl_ptr, + avail); + pcm->mmap_control->appl_ptr = 0; + return err; + } + continue; + } + + frames = count; + if (frames > avail) + frames = avail; + + if (!frames) + break; + + /* copy frames from buffer */ + frames = pcm_mmap_transfer_areas(pcm, (void *)buffer, offset, frames); + if (frames < 0) { + fprintf(stderr, "write error: hw 0x%x app 0x%x avail 0x%x\n", + (unsigned int)pcm->mmap_status->hw_ptr, + (unsigned int)pcm->mmap_control->appl_ptr, + avail); + return frames; + } + + offset += frames; + count -= frames; + } + + return 0; +} + +int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count) +{ + if ((~pcm->flags) & (PCM_OUT | PCM_MMAP)) + return -ENOSYS; + + return pcm_mmap_transfer(pcm, (void *)data, count); +} + +int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count) +{ + if ((~pcm->flags) & (PCM_IN | PCM_MMAP)) + return -ENOSYS; + + return pcm_mmap_transfer(pcm, data, count); +} + +/** Gets the delay of the PCM, in terms of frames. + * @param pcm A PCM handle. + * @returns On success, the delay of the PCM. + * On failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +long pcm_get_delay(struct pcm *pcm) +{ + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DELAY, &pcm->pcm_delay) < 0) + return -1; + + return pcm->pcm_delay; +} + diff --git a/deps/tinyalsa/pcm.h b/deps/tinyalsa/pcm.h new file mode 100644 index 0000000000..01f8b9da7b --- /dev/null +++ b/deps/tinyalsa/pcm.h @@ -0,0 +1,318 @@ +/* pcm.h +** +** Copyright 2011, The Android Open Source Project +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +** DAMAGE. +*/ + +/** @file */ + +/** @defgroup libtinyalsa-pcm PCM Interface + * @brief All macros, structures and functions that make up the PCM interface. + */ + +#ifndef TINYALSA_PCM_H +#define TINYALSA_PCM_H + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +/** A flag that specifies that the PCM is an output. + * May not be bitwise AND'd with @ref PCM_IN. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_OUT 0x00000000 + +/** Specifies that the PCM is an input. + * May not be bitwise AND'd with @ref PCM_OUT. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_IN 0x10000000 + +/** Specifies that the PCM will use mmap read and write methods. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_MMAP 0x00000001 + +/** Specifies no interrupt requests. + * May only be bitwise AND'd with @ref PCM_MMAP. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_NOIRQ 0x00000002 + +/** When set, calls to @ref pcm_write + * for a playback stream will not attempt + * to restart the stream in the case of an + * underflow, but will return -EPIPE instead. + * After the first -EPIPE error, the stream + * is considered to be stopped, and a second + * call to pcm_write will attempt to restart + * the stream. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_NORESTART 0x00000004 + +/** Specifies monotonic timestamps. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_MONOTONIC 0x00000008 + +/** For inputs, this means the PCM is recording audio samples. + * For outputs, this means the PCM is playing audio samples. + * @ingroup libtinyalsa-pcm + */ +#define PCM_STATE_RUNNING 0x03 + +/** For inputs, this means an overrun occured. + * For outputs, this means an underrun occured. + */ +#define PCM_STATE_XRUN 0x04 + +/** For outputs, this means audio samples are played. + * A PCM is in a draining state when it is coming to a stop. + */ +#define PCM_STATE_DRAINING 0x05 + +/** Means a PCM is suspended. + * @ingroup libtinyalsa-pcm + */ +#define PCM_STATE_SUSPENDED 0x07 + +/** Means a PCM has been disconnected. + * @ingroup libtinyalsa-pcm + */ +#define PCM_STATE_DISCONNECTED 0x08 + +/** Audio sample format of a PCM. + * The first letter specifiers whether the sample is signed or unsigned. + * The letter 'S' means signed. The letter 'U' means unsigned. + * The following number is the amount of bits that the sample occupies in memory. + * Following the underscore, specifiers whether the sample is big endian or little endian. + * The letters 'LE' mean little endian. + * The letters 'BE' mean big endian. + * This enumeration is used in the @ref pcm_config structure. + * @ingroup libtinyalsa-pcm + */ +enum pcm_format { + /** Signed, 8-bit */ + PCM_FORMAT_S8 = 1, + /** Signed 16-bit, little endian */ + PCM_FORMAT_S16_LE = 0, + /** Signed, 16-bit, big endian */ + PCM_FORMAT_S16_BE = 2, + /** Signed, 24-bit (32-bit in memory), little endian */ + PCM_FORMAT_S24_LE, + /** Signed, 24-bit (32-bit in memory), big endian */ + PCM_FORMAT_S24_BE, + /** Signed, 24-bit, little endian */ + PCM_FORMAT_S24_3LE, + /** Signed, 24-bit, big endian */ + PCM_FORMAT_S24_3BE, + /** Signed, 32-bit, little endian */ + PCM_FORMAT_S32_LE, + /** Signed, 32-bit, big endian */ + PCM_FORMAT_S32_BE, + /** Max of the enumeration list, not an actual format. */ + PCM_FORMAT_MAX +}; + +/** A bit mask of 256 bits (32 bytes) that describes some hardware parameters of a PCM */ +struct pcm_mask { + /** bits of the bit mask */ + unsigned int bits[32 / sizeof(unsigned int)]; +}; + +/** Encapsulates the hardware and software parameters of a PCM. + * @ingroup libtinyalsa-pcm + */ +struct pcm_config { + /** The number of channels in a frame */ + unsigned int channels; + /** The number of frames per second */ + unsigned int rate; + /** The number of frames in a period */ + unsigned int period_size; + /** The number of periods in a PCM */ + unsigned int period_count; + /** The sample format of a PCM */ + enum pcm_format format; + /* Values to use for the ALSA start, stop and silence thresholds. Setting + * any one of these values to 0 will cause the default tinyalsa values to be + * used instead. Tinyalsa defaults are as follows. + * + * start_threshold : period_count * period_size + * stop_threshold : period_count * period_size + * silence_threshold : 0 + */ + /** The minimum number of frames required to start the PCM */ + unsigned int start_threshold; + /** The minimum number of frames required to stop the PCM */ + unsigned int stop_threshold; + /** The minimum number of frames to silence the PCM */ + unsigned int silence_threshold; +}; + +/** Enumeration of a PCM's hardware parameters. + * Each of these parameters is either a mask or an interval. + * @ingroup libtinyalsa-pcm + */ +enum pcm_param +{ + /** A mask that represents the type of read or write method available (e.g. interleaved, mmap). */ + PCM_PARAM_ACCESS, + /** A mask that represents the @ref pcm_format available (e.g. @ref PCM_FORMAT_S32_LE) */ + PCM_PARAM_FORMAT, + /** A mask that represents the subformat available */ + PCM_PARAM_SUBFORMAT, + /** An interval representing the range of sample bits available (e.g. 8 to 32) */ + PCM_PARAM_SAMPLE_BITS, + /** An interval representing the range of frame bits available (e.g. 8 to 64) */ + PCM_PARAM_FRAME_BITS, + /** An interval representing the range of channels available (e.g. 1 to 2) */ + PCM_PARAM_CHANNELS, + /** An interval representing the range of rates available (e.g. 44100 to 192000) */ + PCM_PARAM_RATE, + PCM_PARAM_PERIOD_TIME, + /** The number of frames in a period */ + PCM_PARAM_PERIOD_SIZE, + /** The number of bytes in a period */ + PCM_PARAM_PERIOD_BYTES, + /** The number of periods for a PCM */ + PCM_PARAM_PERIODS, + PCM_PARAM_BUFFER_TIME, + PCM_PARAM_BUFFER_SIZE, + PCM_PARAM_BUFFER_BYTES, + PCM_PARAM_TICK_TIME, +}; /* enum pcm_param */ + +struct pcm_params; + +struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, + unsigned int flags); + +void pcm_params_free(struct pcm_params *pcm_params); + +const struct pcm_mask *pcm_params_get_mask(const struct pcm_params *pcm_params, enum pcm_param param); + +unsigned int pcm_params_get_min(const struct pcm_params *pcm_params, enum pcm_param param); + +unsigned int pcm_params_get_max(const struct pcm_params *pcm_params, enum pcm_param param); + +struct pcm; + +struct pcm *pcm_open(unsigned int card, + unsigned int device, + unsigned int flags, + const struct pcm_config *config); + +struct pcm *pcm_open_by_name(const char *name, + unsigned int flags, + const struct pcm_config *config); + +int pcm_close(struct pcm *pcm); + +int pcm_is_ready(const struct pcm *pcm); + +unsigned int pcm_get_channels(const struct pcm *pcm); + +const struct pcm_config * pcm_get_config(const struct pcm *pcm); + +unsigned int pcm_get_rate(const struct pcm *pcm); + +enum pcm_format pcm_get_format(const struct pcm *pcm); + +int pcm_get_file_descriptor(const struct pcm *pcm); + +const char *pcm_get_error(const struct pcm *pcm); + +int pcm_set_config(struct pcm *pcm, const struct pcm_config *config); + +unsigned int pcm_format_to_bits(enum pcm_format format); + +unsigned int pcm_get_buffer_size(const struct pcm *pcm); + +unsigned int pcm_frames_to_bytes(const struct pcm *pcm, unsigned int frames); + +unsigned int pcm_bytes_to_frames(const struct pcm *pcm, unsigned int bytes); + +int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, struct timespec *tstamp); + +unsigned int pcm_get_subdevice(const struct pcm *pcm); + +int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count); + +int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count); + +#ifdef __GNUC__ + +int pcm_write(struct pcm *pcm, const void *data, unsigned int count) __attribute((deprecated)); + +int pcm_read(struct pcm *pcm, void *data, unsigned int count) __attribute((deprecated)); + +#else + +int pcm_write(struct pcm *pcm, const void *data, unsigned int count); + +int pcm_read(struct pcm *pcm, void *data, unsigned int count); + +#endif + +int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count); + +int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count); + +int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, unsigned int *frames); + +int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames); + +int pcm_link(struct pcm *pcm1, struct pcm *pcm2); + +int pcm_unlink(struct pcm *pcm); + +int pcm_prepare(struct pcm *pcm); + +int pcm_start(struct pcm *pcm); + +int pcm_stop(struct pcm *pcm); + +int pcm_wait(struct pcm *pcm, int timeout); + +long pcm_get_delay(struct pcm *pcm); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif + diff --git a/griffin/griffin.c b/griffin/griffin.c index 86721e0a77..477141455b 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -650,6 +650,10 @@ AUDIO #endif #endif +#ifdef HAVE_TINYALSA +#include "../audio/drivers/tinyalsa.c" +#endif + #ifdef HAVE_AL #include "../audio/drivers/openal.c" #endif diff --git a/qb/config.libs.sh b/qb/config.libs.sh index 3073b2ce14..6f2daf2369 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -227,6 +227,10 @@ check_header OSS sys/soundcard.h check_header OSS_BSD soundcard.h check_lib OSS_LIB -lossaudio +if [ "$OS" = 'Linux' ]; then + HAVE_TINYALSA=yes +fi + if [ "$OS" = 'Darwin' ]; then check_lib AL "-framework OpenAL" alcOpenDevice HAVE_SDL=no diff --git a/qb/config.params.sh b/qb/config.params.sh index d64b5ce634..9386d6b960 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -58,6 +58,7 @@ HAVE_ZLIB=auto # zlib support (ZIP extract, PNG decoding/encoding) HAVE_FBO=auto # render-to-texture (FBO) support HAVE_ALSA=auto # ALSA support C89_ALSA=no +HAVE_TINYALSA=auto # TinyALSA support HAVE_OSS=auto # OSS support HAVE_RSOUND=auto # RSound support HAVE_ROAR=auto # RoarAudio support