Project import
diff --git a/tinyalsa/Android.mk b/tinyalsa/Android.mk new file mode 100644 index 0000000..4d9aee1 --- /dev/null +++ b/tinyalsa/Android.mk
@@ -0,0 +1,66 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_C_INCLUDES:= external/tinyalsa/include +LOCAL_SRC_FILES:= mixer.c pcm.c +LOCAL_MODULE := libtinyalsa +LOCAL_SHARED_LIBRARIES:= libcutils libutils +LOCAL_MODULE_TAGS := optional + +include $(BUILD_SHARED_LIBRARY) + +ifeq ($(HOST_OS), linux) +include $(CLEAR_VARS) +LOCAL_C_INCLUDES:= external/tinyalsa/include +LOCAL_SRC_FILES:= mixer.c pcm.c +LOCAL_MODULE := libtinyalsa +LOCAL_STATIC_LIBRARIES:= libcutils libutils +include $(BUILD_HOST_STATIC_LIBRARY) +endif + +include $(CLEAR_VARS) +LOCAL_C_INCLUDES:= external/tinyalsa/include +LOCAL_SRC_FILES:= tinyplay.c +LOCAL_MODULE := tinyplay +LOCAL_SHARED_LIBRARIES:= libcutils libutils libtinyalsa +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) + +ifeq ($(HOST_OS), linux) +include $(CLEAR_VARS) +LOCAL_C_INCLUDES:= external/tinyalsa/include +LOCAL_SRC_FILES:= tinyplay.c +LOCAL_MODULE := tinyplay +LOCAL_STATIC_LIBRARIES:= libcutils libutils libtinyalsa +LOCAL_MODULE_TAGS := optional + +include $(BUILD_HOST_EXECUTABLE) +endif + +include $(CLEAR_VARS) +LOCAL_C_INCLUDES:= external/tinyalsa/include +LOCAL_SRC_FILES:= tinycap.c +LOCAL_MODULE := tinycap +LOCAL_SHARED_LIBRARIES:= libcutils libutils libtinyalsa +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_C_INCLUDES:= external/tinyalsa/include +LOCAL_SRC_FILES:= tinymix.c +LOCAL_MODULE := tinymix +LOCAL_SHARED_LIBRARIES:= libcutils libutils libtinyalsa +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_C_INCLUDES:= external/tinyalsa/include +LOCAL_SRC_FILES:= tinypcminfo.c +LOCAL_MODULE := tinypcminfo +LOCAL_SHARED_LIBRARIES:= libcutils libutils libtinyalsa +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE)
diff --git a/tinyalsa/MODULE_LICENSE_BSD b/tinyalsa/MODULE_LICENSE_BSD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tinyalsa/MODULE_LICENSE_BSD
diff --git a/tinyalsa/NOTICE b/tinyalsa/NOTICE new file mode 100644 index 0000000..5debd99 --- /dev/null +++ b/tinyalsa/NOTICE
@@ -0,0 +1,25 @@ +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. +
diff --git a/tinyalsa/README b/tinyalsa/README new file mode 100644 index 0000000..85b1e5e --- /dev/null +++ b/tinyalsa/README
@@ -0,0 +1,9 @@ +tinyalsa: a small library to interface with ALSA in the Linux kernel + +The aims are: + +- Provide a basic pcm and mixer API +- If it's not absolutely needed, don't add it to the API +- Avoid supporting complex and unnecessary operations that could be + dealt with at a higher level +
diff --git a/tinyalsa/include/tinyalsa/asoundlib.h b/tinyalsa/include/tinyalsa/asoundlib.h new file mode 100644 index 0000000..97ee4f3 --- /dev/null +++ b/tinyalsa/include/tinyalsa/asoundlib.h
@@ -0,0 +1,310 @@ +/* asoundlib.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. +*/ + +#ifndef ASOUNDLIB_H +#define ASOUNDLIB_H + +#include <sys/time.h> +#include <stddef.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +/* + * PCM API + */ + +struct pcm; + +#define PCM_OUT 0x00000000 +#define PCM_IN 0x10000000 +#define PCM_MMAP 0x00000001 +#define PCM_NOIRQ 0x00000002 +#define PCM_NORESTART 0x00000004 /* PCM_NORESTART - when set, calls to + * 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. + */ +#define PCM_MONOTONIC 0x00000008 /* see pcm_get_htimestamp */ + +/* PCM runtime states */ +#define PCM_STATE_OPEN 0 +#define PCM_STATE_SETUP 1 +#define PCM_STATE_PREPARED 2 +#define PCM_STATE_RUNNING 3 +#define PCM_STATE_XRUN 4 +#define PCM_STATE_DRAINING 5 +#define PCM_STATE_PAUSED 6 +#define PCM_STATE_SUSPENDED 7 +#define PCM_STATE_DISCONNECTED 8 + +/* Bit formats */ +enum pcm_format { + PCM_FORMAT_INVALID = -1, + PCM_FORMAT_S16_LE = 0, /* 16-bit signed */ + PCM_FORMAT_S32_LE, /* 32-bit signed */ + PCM_FORMAT_S8, /* 8-bit signed */ + PCM_FORMAT_S24_LE, /* 24-bits in 4-bytes */ + PCM_FORMAT_S24_3LE, /* 24-bits in 3-bytes */ + + PCM_FORMAT_MAX, +}; + +/* Bitmask has 256 bits (32 bytes) in asound.h */ +struct pcm_mask { + unsigned int bits[32 / sizeof(unsigned int)]; +}; + +/* Configuration for a stream */ +struct pcm_config { + unsigned int channels; + unsigned int rate; + unsigned int period_size; + unsigned int period_count; + enum pcm_format format; + + /* Values to use for the ALSA start, stop and silence thresholds, and + * silence size. 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 + * silence_size : 0 + */ + unsigned int start_threshold; + unsigned int stop_threshold; + unsigned int silence_threshold; + unsigned int silence_size; + + /* Minimum number of frames available before pcm_mmap_write() will actually + * write into the kernel buffer. Only used if the stream is opened in mmap mode + * (pcm_open() called with PCM_MMAP flag set). Use 0 for default. + */ + int avail_min; +}; + +/* PCM parameters */ +enum pcm_param +{ + /* mask parameters */ + PCM_PARAM_ACCESS, + PCM_PARAM_FORMAT, + PCM_PARAM_SUBFORMAT, + /* interval parameters */ + PCM_PARAM_SAMPLE_BITS, + PCM_PARAM_FRAME_BITS, + PCM_PARAM_CHANNELS, + PCM_PARAM_RATE, + PCM_PARAM_PERIOD_TIME, + PCM_PARAM_PERIOD_SIZE, + PCM_PARAM_PERIOD_BYTES, + PCM_PARAM_PERIODS, + PCM_PARAM_BUFFER_TIME, + PCM_PARAM_BUFFER_SIZE, + PCM_PARAM_BUFFER_BYTES, + PCM_PARAM_TICK_TIME, +}; + +/* Mixer control types */ +enum mixer_ctl_type { + MIXER_CTL_TYPE_BOOL, + MIXER_CTL_TYPE_INT, + MIXER_CTL_TYPE_ENUM, + MIXER_CTL_TYPE_BYTE, + MIXER_CTL_TYPE_IEC958, + MIXER_CTL_TYPE_INT64, + MIXER_CTL_TYPE_UNKNOWN, + + MIXER_CTL_TYPE_MAX, +}; + +/* Open and close a stream */ +struct pcm *pcm_open(unsigned int card, unsigned int device, + unsigned int flags, struct pcm_config *config); +int pcm_close(struct pcm *pcm); +int pcm_is_ready(struct pcm *pcm); + +/* Obtain the parameters for a PCM */ +struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, + unsigned int flags); +void pcm_params_free(struct pcm_params *pcm_params); + +struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params, + enum pcm_param param); +unsigned int pcm_params_get_min(struct pcm_params *pcm_params, + enum pcm_param param); +void pcm_params_set_min(struct pcm_params *pcm_params, + enum pcm_param param, unsigned int val); +unsigned int pcm_params_get_max(struct pcm_params *pcm_params, + enum pcm_param param); +void pcm_params_set_max(struct pcm_params *pcm_params, + enum pcm_param param, unsigned int val); + +/* Converts the pcm parameters to a human readable string. + * The string parameter is a caller allocated buffer of size bytes, + * which is then filled up to size - 1 and null terminated, + * if size is greater than zero. + * The return value is the number of bytes copied to string + * (not including null termination) if less than size; otherwise, + * the number of bytes required for the buffer. + */ +int pcm_params_to_string(struct pcm_params *params, char *string, unsigned int size); + +/* Returns 1 if the pcm_format is present (format bit set) in + * the pcm_params structure; 0 otherwise, or upon unrecognized format. + */ +int pcm_params_format_test(struct pcm_params *params, enum pcm_format format); + +/* Set and get config */ +int pcm_get_config(struct pcm *pcm, struct pcm_config *config); +int pcm_set_config(struct pcm *pcm, struct pcm_config *config); + +/* Returns a human readable reason for the last error */ +const char *pcm_get_error(struct pcm *pcm); + +/* Returns the sample size in bits for a PCM format. + * As with ALSA formats, this is the storage size for the format, whereas the + * format represents the number of significant bits. For example, + * PCM_FORMAT_S24_LE uses 32 bits of storage. + */ +unsigned int pcm_format_to_bits(enum pcm_format format); + +/* Returns the buffer size (int frames) that should be used for pcm_write. */ +unsigned int pcm_get_buffer_size(struct pcm *pcm); +unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames); +unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes); + +/* Returns the pcm latency in ms */ +unsigned int pcm_get_latency(struct pcm *pcm); + +/* Returns available frames in pcm buffer and corresponding time stamp. + * The clock is CLOCK_MONOTONIC if flag PCM_MONOTONIC was specified in 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. + */ +int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, + struct timespec *tstamp); + +/* Write data to the fifo. + * Will start playback on the first write or on a write that + * occurs after a fifo underrun. + */ +int pcm_write(struct pcm *pcm, const void *data, unsigned int count); +int pcm_read(struct pcm *pcm, void *data, unsigned int count); + +/* + * mmap() support. + */ +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_mmap_avail(struct pcm *pcm); + +/* Prepare the PCM substream to be triggerable */ +int pcm_prepare(struct pcm *pcm); +/* Start and stop a PCM channel that doesn't transfer data */ +int pcm_start(struct pcm *pcm); +int pcm_stop(struct pcm *pcm); + +/* ioctl function for PCM driver */ +int pcm_ioctl(struct pcm *pcm, int request, ...); + +/* Interrupt driven API */ +int pcm_wait(struct pcm *pcm, int timeout); +int pcm_get_poll_fd(struct pcm *pcm); + +/* Change avail_min after the stream has been opened with no need to stop the stream. + * Only accepted if opened with PCM_MMAP and PCM_NOIRQ flags + */ +int pcm_set_avail_min(struct pcm *pcm, int avail_min); + +/* + * MIXER API + */ + +struct mixer; +struct mixer_ctl; + +/* Open and close a mixer */ +struct mixer *mixer_open(unsigned int card); +void mixer_close(struct mixer *mixer); + +/* Get info about a mixer */ +const char *mixer_get_name(struct mixer *mixer); + +/* Obtain mixer controls */ +unsigned int mixer_get_num_ctls(struct mixer *mixer); +struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id); +struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name); + +/* Get info about mixer controls */ +const char *mixer_ctl_get_name(struct mixer_ctl *ctl); +enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl); +const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl); +unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl); +unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl); +const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl, + unsigned int enum_id); + +/* Some sound cards update their controls due to external events, + * such as HDMI EDID byte data changing when an HDMI cable is + * connected. This API allows the count of elements to be updated. + */ +void mixer_ctl_update(struct mixer_ctl *ctl); + +/* Set and get mixer controls */ +int mixer_ctl_get_percent(struct mixer_ctl *ctl, unsigned int id); +int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent); + +int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id); +int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count); +int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value); +int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count); +int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string); + +/* Determe range of integer mixer controls */ +int mixer_ctl_get_range_min(struct mixer_ctl *ctl); +int mixer_ctl_get_range_max(struct mixer_ctl *ctl); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif
diff --git a/tinyalsa/mixer.c b/tinyalsa/mixer.c new file mode 100644 index 0000000..120fe44 --- /dev/null +++ b/tinyalsa/mixer.c
@@ -0,0 +1,525 @@ +/* mixer.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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> + +#include <sys/ioctl.h> + +#include <linux/ioctl.h> +#define __force +#define __bitwise +#define __user +#include <sound/asound.h> + +#include <tinyalsa/asoundlib.h> + +struct mixer_ctl { + struct mixer *mixer; + struct snd_ctl_elem_info *info; + char **ename; +}; + +struct mixer { + int fd; + struct snd_ctl_card_info card_info; + struct snd_ctl_elem_info *elem_info; + struct mixer_ctl *ctl; + unsigned int count; +}; + +void mixer_close(struct mixer *mixer) +{ + unsigned int n,m; + + if (!mixer) + return; + + if (mixer->fd >= 0) + close(mixer->fd); + + if (mixer->ctl) { + for (n = 0; n < mixer->count; n++) { + if (mixer->ctl[n].ename) { + unsigned int max = mixer->ctl[n].info->value.enumerated.items; + for (m = 0; m < max; m++) + free(mixer->ctl[n].ename[m]); + free(mixer->ctl[n].ename); + } + } + free(mixer->ctl); + } + + if (mixer->elem_info) + free(mixer->elem_info); + + free(mixer); + + /* TODO: verify frees */ +} + +struct mixer *mixer_open(unsigned int card) +{ + struct snd_ctl_elem_list elist; + struct snd_ctl_elem_info tmp; + struct snd_ctl_elem_id *eid = NULL; + struct mixer *mixer = NULL; + unsigned int n, m; + int fd; + char fn[256]; + + snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card); + fd = open(fn, O_RDWR); + if (fd < 0) + return 0; + + memset(&elist, 0, sizeof(elist)); + if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) + goto fail; + + mixer = calloc(1, sizeof(*mixer)); + if (!mixer) + goto fail; + + mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl)); + mixer->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info)); + if (!mixer->ctl || !mixer->elem_info) + goto fail; + + if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0) + goto fail; + + eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id)); + if (!eid) + goto fail; + + mixer->count = elist.count; + mixer->fd = fd; + elist.space = mixer->count; + elist.pids = eid; + if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) + goto fail; + + for (n = 0; n < mixer->count; n++) { + struct snd_ctl_elem_info *ei = mixer->elem_info + n; + ei->id.numid = eid[n].numid; + if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) + goto fail; + mixer->ctl[n].info = ei; + mixer->ctl[n].mixer = mixer; + if (ei->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) { + char **enames = calloc(ei->value.enumerated.items, sizeof(char*)); + if (!enames) + goto fail; + mixer->ctl[n].ename = enames; + for (m = 0; m < ei->value.enumerated.items; m++) { + memset(&tmp, 0, sizeof(tmp)); + tmp.id.numid = ei->id.numid; + tmp.value.enumerated.item = m; + if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0) + goto fail; + enames[m] = strdup(tmp.value.enumerated.name); + if (!enames[m]) + goto fail; + } + } + } + + free(eid); + return mixer; + +fail: + /* TODO: verify frees in failure case */ + if (eid) + free(eid); + if (mixer) + mixer_close(mixer); + else if (fd >= 0) + close(fd); + return 0; +} + +const char *mixer_get_name(struct mixer *mixer) +{ + return (const char *)mixer->card_info.name; +} + +unsigned int mixer_get_num_ctls(struct mixer *mixer) +{ + if (!mixer) + return 0; + + return mixer->count; +} + +struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id) +{ + if (mixer && (id < mixer->count)) + return mixer->ctl + id; + + return NULL; +} + +struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name) +{ + unsigned int n; + + if (!mixer) + return NULL; + + for (n = 0; n < mixer->count; n++) + if (!strcmp(name, (char*) mixer->elem_info[n].id.name)) + return mixer->ctl + n; + + return NULL; +} + +void mixer_ctl_update(struct mixer_ctl *ctl) +{ + ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info); +} + +const char *mixer_ctl_get_name(struct mixer_ctl *ctl) +{ + if (!ctl) + return NULL; + + return (const char *)ctl->info->id.name; +} + +enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl) +{ + if (!ctl) + return MIXER_CTL_TYPE_UNKNOWN; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return MIXER_CTL_TYPE_BOOL; + case SNDRV_CTL_ELEM_TYPE_INTEGER: return MIXER_CTL_TYPE_INT; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return MIXER_CTL_TYPE_ENUM; + case SNDRV_CTL_ELEM_TYPE_BYTES: return MIXER_CTL_TYPE_BYTE; + case SNDRV_CTL_ELEM_TYPE_IEC958: return MIXER_CTL_TYPE_IEC958; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: return MIXER_CTL_TYPE_INT64; + default: return MIXER_CTL_TYPE_UNKNOWN; + }; +} + +const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl) +{ + if (!ctl) + return ""; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return "BOOL"; + case SNDRV_CTL_ELEM_TYPE_INTEGER: return "INT"; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return "ENUM"; + case SNDRV_CTL_ELEM_TYPE_BYTES: return "BYTE"; + case SNDRV_CTL_ELEM_TYPE_IEC958: return "IEC958"; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: return "INT64"; + default: return "Unknown"; + }; +} + +unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl) +{ + if (!ctl) + return 0; + + return ctl->info->count; +} + +static int percent_to_int(struct snd_ctl_elem_info *ei, int percent) +{ + int range; + + if (percent > 100) + percent = 100; + else if (percent < 0) + percent = 0; + + range = (ei->value.integer.max - ei->value.integer.min); + + return ei->value.integer.min + (range * percent) / 100; +} + +static int int_to_percent(struct snd_ctl_elem_info *ei, int value) +{ + int range = (ei->value.integer.max - ei->value.integer.min); + + if (range == 0) + return 0; + + return ((value - ei->value.integer.min) / range) * 100; +} + +int mixer_ctl_get_percent(struct mixer_ctl *ctl, unsigned int id) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + return -EINVAL; + + return int_to_percent(ctl->info, mixer_ctl_get_value(ctl, id)); +} + +int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + return -EINVAL; + + return mixer_ctl_set_value(ctl, id, percent_to_int(ctl->info, percent)); +} + +int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id) +{ + struct snd_ctl_elem_value ev; + int ret; + + if (!ctl || (id >= ctl->info->count)) + return -EINVAL; + + memset(&ev, 0, sizeof(ev)); + ev.id.numid = ctl->info->id.numid; + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + if (ret < 0) + return ret; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + return !!ev.value.integer.value[id]; + + case SNDRV_CTL_ELEM_TYPE_INTEGER: + return ev.value.integer.value[id]; + + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + return ev.value.enumerated.item[id]; + + case SNDRV_CTL_ELEM_TYPE_BYTES: + return ev.value.bytes.data[id]; + + default: + return -EINVAL; + } + + return 0; +} + +int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count) +{ + struct snd_ctl_elem_value ev; + int ret; + size_t size; + size_t n; + + if (!ctl || (count > ctl->info->count) || !count || !array) + return -EINVAL; + + memset(&ev, 0, sizeof(ev)); + ev.id.numid = ctl->info->id.numid; + + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + if (ret < 0) + return ret; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: { + size = sizeof(ev.value.integer.value[0]); + long *source = ev.value.integer.value; + int *dest = (int *)array; + for (n = 0; n < count; ++n) { + dest[n] = source[n]; + } + } break; + + case SNDRV_CTL_ELEM_TYPE_BYTES: { + size = sizeof(ev.value.bytes.data[0]); + unsigned char *source = ev.value.bytes.data; + unsigned char *dest = (unsigned char *)array; + for (n = 0; n < count; ++n) { + dest[n] = source[n]; + } + } break; + + case SNDRV_CTL_ELEM_TYPE_IEC958: { + size = sizeof(ev.value.iec958); + void *source = &ev.value.iec958; + memcpy(array, source, size * count); + } break; + + default: + return -EINVAL; + } + + return 0; +} + +int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value) +{ + struct snd_ctl_elem_value ev; + int ret; + + if (!ctl || (id >= ctl->info->count)) + return -EINVAL; + + memset(&ev, 0, sizeof(ev)); + ev.id.numid = ctl->info->id.numid; + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + if (ret < 0) + return ret; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + ev.value.integer.value[id] = !!value; + break; + + case SNDRV_CTL_ELEM_TYPE_INTEGER: + ev.value.integer.value[id] = value; + break; + + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + ev.value.enumerated.item[id] = value; + break; + + case SNDRV_CTL_ELEM_TYPE_BYTES: + ev.value.bytes.data[id] = value; + break; + + default: + return -EINVAL; + } + + return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); +} + +int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) +{ + struct snd_ctl_elem_value ev; + size_t size; + size_t n; + + if (!ctl || (count > ctl->info->count) || !count || !array) + return -EINVAL; + + memset(&ev, 0, sizeof(ev)); + ev.id.numid = ctl->info->id.numid; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: { + size = sizeof(int); + long *dest = ev.value.integer.value; + int *source = (int *)array; + for (n = 0; n < count; ++n) { + dest[n] = source[n]; + } + } break; + + case SNDRV_CTL_ELEM_TYPE_BYTES: { + size = sizeof(ev.value.bytes.data[0]); + unsigned char *dest = ev.value.bytes.data; + unsigned char *source = (unsigned char *)array; + for (n = 0; n < count; ++n) { + dest[n] = source[n]; + } + } break; + + case SNDRV_CTL_ELEM_TYPE_IEC958: { + size = sizeof(ev.value.iec958); + void *dest = &ev.value.iec958; + memcpy(dest, array, size * count); + } break; + + default: + return -EINVAL; + } + + return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); +} + +int mixer_ctl_get_range_min(struct mixer_ctl *ctl) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + return -EINVAL; + + return ctl->info->value.integer.min; +} + +int mixer_ctl_get_range_max(struct mixer_ctl *ctl) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + return -EINVAL; + + return ctl->info->value.integer.max; +} + +unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl) +{ + if (!ctl) + return 0; + + return ctl->info->value.enumerated.items; +} + +const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl, + unsigned int enum_id) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) || + (enum_id >= ctl->info->value.enumerated.items)) + return NULL; + + return (const char *)ctl->ename[enum_id]; +} + +int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string) +{ + unsigned int i, num_enums; + struct snd_ctl_elem_value ev; + int ret; + + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED)) + return -EINVAL; + + num_enums = ctl->info->value.enumerated.items; + for (i = 0; i < num_enums; i++) { + if (!strcmp(string, ctl->ename[i])) { + memset(&ev, 0, sizeof(ev)); + ev.value.enumerated.item[0] = i; + ev.id.numid = ctl->info->id.numid; + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); + if (ret < 0) + return ret; + return 0; + } + } + + return -EINVAL; +} +
diff --git a/tinyalsa/pcm.c b/tinyalsa/pcm.c new file mode 100644 index 0000000..488ea0c --- /dev/null +++ b/tinyalsa/pcm.c
@@ -0,0 +1,1305 @@ +/* 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 <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <poll.h> + +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/time.h> +#include <limits.h> + +#include <linux/ioctl.h> +#define __force +#define __bitwise +#define __user +#include <sound/asound.h> + +#include <tinyalsa/asoundlib.h> + +#define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL +#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2) + +/* Logs information into a string; follows snprintf() in that + * offset may be greater than size, and though no characters are copied + * into string, characters are still counted into offset. */ +#define STRLOG(string, offset, size, ...) \ + do { int temp, clipoffset = offset > size ? size : offset; \ + temp = snprintf(string + clipoffset, size - clipoffset, __VA_ARGS__); \ + if (temp > 0) offset += temp; } while (0) + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +/* refer to SNDRV_PCM_ACCESS_##index in sound/asound.h. */ +static const char * const access_lookup[] = { + "MMAP_INTERLEAVED", + "MMAP_NONINTERLEAVED", + "MMAP_COMPLEX", + "RW_INTERLEAVED", + "RW_NONINTERLEAVED", +}; + +/* refer to SNDRV_PCM_FORMAT_##index in sound/asound.h. */ +static const char * const format_lookup[] = { + /*[0] =*/ "S8", + "U8", + "S16_LE", + "S16_BE", + "U16_LE", + "U16_BE", + "S24_LE", + "S24_BE", + "U24_LE", + "U24_BE", + "S32_LE", + "S32_BE", + "U32_LE", + "U32_BE", + "FLOAT_LE", + "FLOAT_BE", + "FLOAT64_LE", + "FLOAT64_BE", + "IEC958_SUBFRAME_LE", + "IEC958_SUBFRAME_BE", + "MU_LAW", + "A_LAW", + "IMA_ADPCM", + "MPEG", + /*[24] =*/ "GSM", + /* gap */ + [31] = "SPECIAL", + "S24_3LE", + "S24_3BE", + "U24_3LE", + "U24_3BE", + "S20_3LE", + "S20_3BE", + "U20_3LE", + "U20_3BE", + "S18_3LE", + "S18_3BE", + "U18_3LE", + /*[43] =*/ "U18_3BE", +#if 0 + /* recent additions, may not be present on local asound.h */ + "G723_24", + "G723_24_1B", + "G723_40", + "G723_40_1B", + "DSD_U8", + "DSD_U16_LE", +#endif +}; + +/* refer to SNDRV_PCM_SUBFORMAT_##index in sound/asound.h. */ +static const char * const subformat_lookup[] = { + "STD", +}; + +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 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(struct snd_pcm_hw_params *p, int n) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + return i->min; + } + return 0; +} + +static void param_set_max(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->max = val; + } +} + +static unsigned int param_get_max(struct snd_pcm_hw_params *p, int n) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_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; +} + +#define PCM_ERROR_MAX 128 + +struct pcm { + int fd; + unsigned int flags; + int running:1; + int prepared:1; + int underruns; + unsigned int buffer_size; + unsigned int boundary; + char error[PCM_ERROR_MAX]; + 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; + int wait_for_avail_min; +}; + +unsigned int pcm_get_buffer_size(struct pcm *pcm) +{ + return pcm->buffer_size; +} + +const char* pcm_get_error(struct pcm *pcm) +{ + return pcm->error; +} + +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; +} + +static unsigned int pcm_format_to_alsa(enum pcm_format format) +{ + switch (format) { + case PCM_FORMAT_S32_LE: + return SNDRV_PCM_FORMAT_S32_LE; + case PCM_FORMAT_S8: + return SNDRV_PCM_FORMAT_S8; + case PCM_FORMAT_S24_3LE: + return SNDRV_PCM_FORMAT_S24_3LE; + case PCM_FORMAT_S24_LE: + return SNDRV_PCM_FORMAT_S24_LE; + default: + case PCM_FORMAT_S16_LE: + return SNDRV_PCM_FORMAT_S16_LE; + }; +} + +unsigned int pcm_format_to_bits(enum pcm_format format) +{ + switch (format) { + case PCM_FORMAT_S32_LE: + case PCM_FORMAT_S24_LE: + return 32; + case PCM_FORMAT_S24_3LE: + return 24; + default: + case PCM_FORMAT_S16_LE: + return 16; + }; +} + +unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes) +{ + return bytes / (pcm->config.channels * + (pcm_format_to_bits(pcm->config.format) >> 3)); +} + +unsigned int pcm_frames_to_bytes(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) + return -1; + } + return 0; +} + +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; + } + if (pcm->flags & PCM_MMAP) + pcm->mmap_control->avail_min = pcm->config.avail_min; + else + 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; + if (pcm->flags & PCM_MMAP) + pcm->mmap_control->avail_min = pcm->config.avail_min; + else + 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; +} + +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) + frames += pcm->boundary; + else if (frames > (int)pcm->boundary) + frames -= pcm->boundary; + + *avail = (unsigned int)frames; + + return 0; +} + +int pcm_write(struct pcm *pcm, const void *data, unsigned int count) +{ + struct snd_xferi x; + + if (pcm->flags & PCM_IN) + return -EINVAL; + + x.buf = (void*)data; + x.frames = count / (pcm->config.channels * + pcm_format_to_bits(pcm->config.format) / 8); + + 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 0; + } +} + +int pcm_read(struct pcm *pcm, void *data, unsigned int count) +{ + struct snd_xferi x; + + if (!(pcm->flags & PCM_IN)) + return -EINVAL; + + x.buf = data; + x.frames = count / (pcm->config.channels * + pcm_format_to_bits(pcm->config.format) / 8); + + for (;;) { + if (!pcm->running) { + if (pcm_start(pcm) < 0) { + fprintf(stderr, "start error"); + return -errno; + } + } + 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 0; + } +} + +static struct pcm bad_pcm = { + .fd = -1, +}; + +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; +} + +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; + } +} + +struct pcm_mask *pcm_params_get_mask(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 (struct pcm_mask *)param_to_mask(params, p); +} + +unsigned int pcm_params_get_min(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); +} + +void pcm_params_set_min(struct pcm_params *pcm_params, + enum pcm_param param, unsigned int val) +{ + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + int p; + + if (!params) + return; + + p = pcm_param_to_alsa(param); + if (p < 0) + return; + + param_set_min(params, p, val); +} + +unsigned int pcm_params_get_max(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_max(params, p); +} + +void pcm_params_set_max(struct pcm_params *pcm_params, + enum pcm_param param, unsigned int val) +{ + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + int p; + + if (!params) + return; + + p = pcm_param_to_alsa(param); + if (p < 0) + return; + + param_set_max(params, p, val); +} + +static int pcm_mask_test(struct pcm_mask *m, unsigned int index) +{ + const unsigned int bitshift = 5; /* for 32 bit integer */ + const unsigned int bitmask = (1 << bitshift) - 1; + unsigned int element; + + element = index >> bitshift; + if (element >= ARRAY_SIZE(m->bits)) + return 0; /* for safety, but should never occur */ + return (m->bits[element] >> (index & bitmask)) & 1; +} + +static int pcm_mask_to_string(struct pcm_mask *m, char *string, unsigned int size, + char *mask_name, + const char * const *bit_array_name, size_t bit_array_size) +{ + unsigned int i; + unsigned int offset = 0; + + if (m == NULL) + return 0; + if (bit_array_size < 32) { + STRLOG(string, offset, size, "%12s:\t%#08x\n", mask_name, m->bits[0]); + } else { /* spans two or more bitfields, print with an array index */ + for (i = 0; i < (bit_array_size + 31) >> 5; ++i) { + STRLOG(string, offset, size, "%9s[%d]:\t%#08x\n", + mask_name, i, m->bits[i]); + } + } + for (i = 0; i < bit_array_size; ++i) { + if (pcm_mask_test(m, i)) { + STRLOG(string, offset, size, "%12s \t%s\n", "", bit_array_name[i]); + } + } + return offset; +} + +int pcm_params_to_string(struct pcm_params *params, char *string, unsigned int size) +{ + struct pcm_mask *m; + unsigned int min, max; + unsigned int clipoffset, offset; + + m = pcm_params_get_mask(params, PCM_PARAM_ACCESS); + offset = pcm_mask_to_string(m, string, size, + "Access", access_lookup, ARRAY_SIZE(access_lookup)); + m = pcm_params_get_mask(params, PCM_PARAM_FORMAT); + clipoffset = offset > size ? size : offset; + offset += pcm_mask_to_string(m, string + clipoffset, size - clipoffset, + "Format", format_lookup, ARRAY_SIZE(format_lookup)); + m = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT); + clipoffset = offset > size ? size : offset; + offset += pcm_mask_to_string(m, string + clipoffset, size - clipoffset, + "Subformat", subformat_lookup, ARRAY_SIZE(subformat_lookup)); + min = pcm_params_get_min(params, PCM_PARAM_RATE); + max = pcm_params_get_max(params, PCM_PARAM_RATE); + STRLOG(string, offset, size, " Rate:\tmin=%uHz\tmax=%uHz\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_CHANNELS); + max = pcm_params_get_max(params, PCM_PARAM_CHANNELS); + STRLOG(string, offset, size, " Channels:\tmin=%u\t\tmax=%u\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS); + max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS); + STRLOG(string, offset, size, " Sample bits:\tmin=%u\t\tmax=%u\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE); + max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE); + STRLOG(string, offset, size, " Period size:\tmin=%u\t\tmax=%u\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_PERIODS); + max = pcm_params_get_max(params, PCM_PARAM_PERIODS); + STRLOG(string, offset, size, "Period count:\tmin=%u\t\tmax=%u\n", min, max); + return offset; +} + +int pcm_params_format_test(struct pcm_params *params, enum pcm_format format) +{ + unsigned int alsa_format = pcm_format_to_alsa(format); + + if (alsa_format == SNDRV_PCM_FORMAT_S16_LE && format != PCM_FORMAT_S16_LE) + return 0; /* caution: format not recognized is equivalent to S16_LE */ + return pcm_mask_test(pcm_params_get_mask(params, PCM_PARAM_FORMAT), alsa_format); +} + +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; +} + +struct pcm *pcm_open(unsigned int card, unsigned int device, + unsigned int flags, struct pcm_config *config) +{ + struct pcm *pcm; + struct snd_pcm_info info; + struct snd_pcm_hw_params params; + struct snd_pcm_sw_params sparams; + char fn[256]; + int rc; + + pcm = calloc(1, sizeof(struct pcm)); + if (!pcm || !config) + return &bad_pcm; /* TODO: could support default config here */ + + pcm->config = *config; + + 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; + } + + 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 (flags & PCM_NOIRQ) { + if (!(flags & PCM_MMAP)) { + oops(pcm, -EINVAL, "noirq only currently supported with mmap()."); + goto fail; + } + + params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP; + pcm->noirq_frames_per_msec = config->rate / 1000; + } + + if (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)) { + oops(pcm, errno, "cannot set hw params"); + goto fail_close; + } + + /* get our refined hw_params */ + config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS); + pcm->buffer_size = config->period_count * config->period_size; + + if (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) { + oops(pcm, -errno, "failed to mmap buffer %d bytes\n", + pcm_frames_to_bytes(pcm, pcm->buffer_size)); + goto fail_close; + } + } + + memset(&sparams, 0, sizeof(sparams)); + sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE; + sparams.period_step = 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; + + if (!pcm->config.avail_min) { + if (pcm->flags & PCM_MMAP) + pcm->config.avail_min = sparams.avail_min = pcm->config.period_size; + else + pcm->config.avail_min = sparams.avail_min = 1; + } else + sparams.avail_min = config->avail_min; + + sparams.xfer_align = config->period_size / 2; /* needed for old kernels */ + sparams.silence_threshold = config->silence_threshold; + sparams.silence_size = config->silence_size; + 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)) { + oops(pcm, errno, "cannot set sw params"); + goto fail; + } + + 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; +} + +int pcm_is_ready(struct pcm *pcm) +{ + return pcm->fd >= 0; +} + +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; +} + +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; +} + +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; +} + +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) +{ + /* update the application pointer in userspace and kernel */ + pcm_mmap_appl_forward(pcm, frames); + pcm_sync_ptr(pcm, 0); + + 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; +} + +int pcm_set_avail_min(struct pcm *pcm, int avail_min) +{ + if ((~pcm->flags) & (PCM_MMAP | PCM_NOIRQ)) + return -ENOSYS; + + pcm->config.avail_min = avail_min; + return 0; +} + +int pcm_wait(struct pcm *pcm, int timeout) +{ + struct pollfd pfd; + int err; + + pfd.fd = pcm->fd; + pfd.events = 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_get_poll_fd(struct pcm *pcm) +{ + return pcm->fd; +} + +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; + } + pcm->wait_for_avail_min = 0; + } + + /* sleep until we have space to write new frames */ + if (pcm->running) { + /* enable waiting for avail_min threshold when less frames than we have to write + * are available. */ + if (!pcm->wait_for_avail_min && (count > (unsigned int)avail)) + pcm->wait_for_avail_min = 1; + + if (pcm->wait_for_avail_min && (avail < pcm->config.avail_min)) { + int time = -1; + + /* disable waiting for avail_min threshold to allow small amounts of data to be + * written without waiting as long as there is enough room in buffer. */ + pcm->wait_for_avail_min = 0; + + if (pcm->flags & PCM_NOIRQ) + time = (pcm->config.avail_min - avail) / pcm->noirq_frames_per_msec; + + err = pcm_wait(pcm, time); + if (err < 0) { + pcm->prepared = 0; + pcm->running = 0; + oops(pcm, err, "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); +} + +int pcm_ioctl(struct pcm *pcm, int request, ...) +{ + va_list ap; + void * arg; + + if (!pcm_is_ready(pcm)) + return -1; + + va_start(ap, request); + arg = va_arg(ap, void *); + va_end(ap); + + return ioctl(pcm->fd, request, arg); +}
diff --git a/tinyalsa/tinycap.c b/tinyalsa/tinycap.c new file mode 100644 index 0000000..c838348 --- /dev/null +++ b/tinyalsa/tinycap.c
@@ -0,0 +1,235 @@ +/* tinycap.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 <tinyalsa/asoundlib.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <signal.h> +#include <string.h> + +#define ID_RIFF 0x46464952 +#define ID_WAVE 0x45564157 +#define ID_FMT 0x20746d66 +#define ID_DATA 0x61746164 + +#define FORMAT_PCM 1 + +struct wav_header { + uint32_t riff_id; + uint32_t riff_sz; + uint32_t riff_fmt; + uint32_t fmt_id; + uint32_t fmt_sz; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; + uint32_t data_id; + uint32_t data_sz; +}; + +int capturing = 1; + +unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, + unsigned int channels, unsigned int rate, + enum pcm_format format, unsigned int period_size, + unsigned int period_count); + +void sigint_handler(int sig) +{ + capturing = 0; +} + +int main(int argc, char **argv) +{ + FILE *file; + struct wav_header header; + unsigned int card = 0; + unsigned int device = 0; + unsigned int channels = 2; + unsigned int rate = 44100; + unsigned int bits = 16; + unsigned int frames; + unsigned int period_size = 1024; + unsigned int period_count = 4; + enum pcm_format format; + + if (argc < 2) { + fprintf(stderr, "Usage: %s file.wav [-D card] [-d device] [-c channels] " + "[-r rate] [-b bits] [-p period_size] [-n n_periods]\n", argv[0]); + return 1; + } + + file = fopen(argv[1], "wb"); + if (!file) { + fprintf(stderr, "Unable to create file '%s'\n", argv[1]); + return 1; + } + + /* parse command line arguments */ + argv += 2; + while (*argv) { + if (strcmp(*argv, "-d") == 0) { + argv++; + if (*argv) + device = atoi(*argv); + } else if (strcmp(*argv, "-c") == 0) { + argv++; + if (*argv) + channels = atoi(*argv); + } else if (strcmp(*argv, "-r") == 0) { + argv++; + if (*argv) + rate = atoi(*argv); + } else if (strcmp(*argv, "-b") == 0) { + argv++; + if (*argv) + bits = atoi(*argv); + } else if (strcmp(*argv, "-D") == 0) { + argv++; + if (*argv) + card = atoi(*argv); + } else if (strcmp(*argv, "-p") == 0) { + argv++; + if (*argv) + period_size = atoi(*argv); + } else if (strcmp(*argv, "-n") == 0) { + argv++; + if (*argv) + period_count = atoi(*argv); + } + if (*argv) + argv++; + } + + header.riff_id = ID_RIFF; + header.riff_sz = 0; + header.riff_fmt = ID_WAVE; + header.fmt_id = ID_FMT; + header.fmt_sz = 16; + header.audio_format = FORMAT_PCM; + header.num_channels = channels; + header.sample_rate = rate; + + switch (bits) { + case 32: + format = PCM_FORMAT_S32_LE; + break; + case 24: + format = PCM_FORMAT_S24_LE; + break; + case 16: + format = PCM_FORMAT_S16_LE; + break; + default: + fprintf(stderr, "%d bits is not supported.\n", bits); + return 1; + } + + header.bits_per_sample = pcm_format_to_bits(format); + header.byte_rate = (header.bits_per_sample / 8) * channels * rate; + header.block_align = channels * (header.bits_per_sample / 8); + header.data_id = ID_DATA; + + /* leave enough room for header */ + fseek(file, sizeof(struct wav_header), SEEK_SET); + + /* install signal handler and begin capturing */ + signal(SIGINT, sigint_handler); + frames = capture_sample(file, card, device, header.num_channels, + header.sample_rate, format, + period_size, period_count); + printf("Captured %d frames\n", frames); + + /* write header now all information is known */ + header.data_sz = frames * header.block_align; + header.riff_sz = header.data_sz + sizeof(header) - 8; + fseek(file, 0, SEEK_SET); + fwrite(&header, sizeof(struct wav_header), 1, file); + + fclose(file); + + return 0; +} + +unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, + unsigned int channels, unsigned int rate, + enum pcm_format format, unsigned int period_size, + unsigned int period_count) +{ + struct pcm_config config; + struct pcm *pcm; + char *buffer; + unsigned int size; + unsigned int bytes_read = 0; + + memset(&config, 0, sizeof(config)); + config.channels = channels; + config.rate = rate; + config.period_size = period_size; + config.period_count = period_count; + config.format = format; + config.start_threshold = 0; + config.stop_threshold = 0; + config.silence_threshold = 0; + + pcm = pcm_open(card, device, PCM_IN, &config); + if (!pcm || !pcm_is_ready(pcm)) { + fprintf(stderr, "Unable to open PCM device (%s)\n", + pcm_get_error(pcm)); + return 0; + } + + size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); + buffer = malloc(size); + if (!buffer) { + fprintf(stderr, "Unable to allocate %d bytes\n", size); + free(buffer); + pcm_close(pcm); + return 0; + } + + printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate, + pcm_format_to_bits(format)); + + while (capturing && !pcm_read(pcm, buffer, size)) { + if (fwrite(buffer, 1, size, file) != size) { + fprintf(stderr,"Error capturing sample\n"); + break; + } + bytes_read += size; + } + + free(buffer); + pcm_close(pcm); + return pcm_bytes_to_frames(pcm, bytes_read); +} +
diff --git a/tinyalsa/tinymix.c b/tinyalsa/tinymix.c new file mode 100644 index 0000000..afc2fee --- /dev/null +++ b/tinyalsa/tinymix.c
@@ -0,0 +1,238 @@ +/* tinymix.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 <tinyalsa/asoundlib.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> + +static void tinymix_list_controls(struct mixer *mixer); +static void tinymix_detail_control(struct mixer *mixer, const char *control, + int print_all); +static void tinymix_set_value(struct mixer *mixer, const char *control, + char **values, unsigned int num_values); +static void tinymix_print_enum(struct mixer_ctl *ctl, int print_all); + +int main(int argc, char **argv) +{ + struct mixer *mixer; + int card = 0; + + if ((argc > 2) && (strcmp(argv[1], "-D") == 0)) { + argv++; + if (argv[1]) { + card = atoi(argv[1]); + argv++; + argc -= 2; + } else { + argc -= 1; + } + } + + mixer = mixer_open(card); + if (!mixer) { + fprintf(stderr, "Failed to open mixer\n"); + return EXIT_FAILURE; + } + + + if (argc == 1) { + printf("Mixer name: '%s'\n", mixer_get_name(mixer)); + tinymix_list_controls(mixer); + } else if (argc == 2) { + tinymix_detail_control(mixer, argv[1], 1); + } else if (argc >= 3) { + tinymix_set_value(mixer, argv[1], &argv[2], argc - 2); + } else { + printf("Usage: tinymix [-D card] [control id] [value to set]\n"); + } + + mixer_close(mixer); + + return 0; +} + +static void tinymix_list_controls(struct mixer *mixer) +{ + struct mixer_ctl *ctl; + const char *name, *type; + unsigned int num_ctls, num_values; + unsigned int i; + + num_ctls = mixer_get_num_ctls(mixer); + + printf("Number of controls: %d\n", num_ctls); + + printf("ctl\ttype\tnum\t%-40s value\n", "name"); + for (i = 0; i < num_ctls; i++) { + ctl = mixer_get_ctl(mixer, i); + + name = mixer_ctl_get_name(ctl); + type = mixer_ctl_get_type_string(ctl); + num_values = mixer_ctl_get_num_values(ctl); + printf("%d\t%s\t%d\t%-40s", i, type, num_values, name); + tinymix_detail_control(mixer, name, 0); + } +} + +static void tinymix_print_enum(struct mixer_ctl *ctl, int print_all) +{ + unsigned int num_enums; + unsigned int i; + const char *string; + + num_enums = mixer_ctl_get_num_enums(ctl); + + for (i = 0; i < num_enums; i++) { + string = mixer_ctl_get_enum_string(ctl, i); + if (print_all) + printf("\t%s%s", mixer_ctl_get_value(ctl, 0) == (int)i ? ">" : "", + string); + else if (mixer_ctl_get_value(ctl, 0) == (int)i) + printf(" %-s", string); + } +} + +static void tinymix_detail_control(struct mixer *mixer, const char *control, + int print_all) +{ + struct mixer_ctl *ctl; + enum mixer_ctl_type type; + unsigned int num_values; + unsigned int i; + int min, max; + + if (isdigit(control[0])) + ctl = mixer_get_ctl(mixer, atoi(control)); + else + ctl = mixer_get_ctl_by_name(mixer, control); + + if (!ctl) { + fprintf(stderr, "Invalid mixer control\n"); + return; + } + + type = mixer_ctl_get_type(ctl); + num_values = mixer_ctl_get_num_values(ctl); + + if (print_all) + printf("%s:", mixer_ctl_get_name(ctl)); + + for (i = 0; i < num_values; i++) { + switch (type) + { + case MIXER_CTL_TYPE_INT: + printf(" %d", mixer_ctl_get_value(ctl, i)); + break; + case MIXER_CTL_TYPE_BOOL: + printf(" %s", mixer_ctl_get_value(ctl, i) ? "On" : "Off"); + break; + case MIXER_CTL_TYPE_ENUM: + tinymix_print_enum(ctl, print_all); + break; + case MIXER_CTL_TYPE_BYTE: + printf(" 0x%02x", mixer_ctl_get_value(ctl, i)); + break; + default: + printf(" unknown"); + break; + }; + } + + if (print_all) { + if (type == MIXER_CTL_TYPE_INT) { + min = mixer_ctl_get_range_min(ctl); + max = mixer_ctl_get_range_max(ctl); + printf(" (range %d->%d)", min, max); + } + } + printf("\n"); +} + +static void tinymix_set_value(struct mixer *mixer, const char *control, + char **values, unsigned int num_values) +{ + struct mixer_ctl *ctl; + enum mixer_ctl_type type; + unsigned int num_ctl_values; + unsigned int i; + + if (isdigit(control[0])) + ctl = mixer_get_ctl(mixer, atoi(control)); + else + ctl = mixer_get_ctl_by_name(mixer, control); + + if (!ctl) { + fprintf(stderr, "Invalid mixer control\n"); + return; + } + + type = mixer_ctl_get_type(ctl); + num_ctl_values = mixer_ctl_get_num_values(ctl); + + if (isdigit(values[0][0])) { + if (num_values == 1) { + /* Set all values the same */ + int value = atoi(values[0]); + + for (i = 0; i < num_ctl_values; i++) { + if (mixer_ctl_set_value(ctl, i, value)) { + fprintf(stderr, "Error: invalid value\n"); + return; + } + } + } else { + /* Set multiple values */ + if (num_values > num_ctl_values) { + fprintf(stderr, + "Error: %d values given, but control only takes %d\n", + num_values, num_ctl_values); + return; + } + for (i = 0; i < num_values; i++) { + if (mixer_ctl_set_value(ctl, i, atoi(values[i]))) { + fprintf(stderr, "Error: invalid value for index %d\n", i); + return; + } + } + } + } else { + if (type == MIXER_CTL_TYPE_ENUM) { + if (num_values != 1) { + fprintf(stderr, "Enclose strings in quotes and try again\n"); + return; + } + if (mixer_ctl_set_enum_by_string(ctl, values[0])) + fprintf(stderr, "Error: invalid enum value\n"); + } else { + fprintf(stderr, "Error: only enum types can be set with strings\n"); + } + } +} +
diff --git a/tinyalsa/tinypcminfo.c b/tinyalsa/tinypcminfo.c new file mode 100644 index 0000000..b2d11bc --- /dev/null +++ b/tinyalsa/tinypcminfo.c
@@ -0,0 +1,203 @@ +/* tinypcminfo.c +** +** Copyright 2012, 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 <tinyalsa/asoundlib.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) +#endif + +/* The format_lookup is in order of SNDRV_PCM_FORMAT_##index and + * matches the grouping in sound/asound.h. Note this is not + * continuous and has an empty gap from (25 - 30). + */ +static const char *format_lookup[] = { + /*[0] =*/ "S8", + "U8", + "S16_LE", + "S16_BE", + "U16_LE", + "U16_BE", + "S24_LE", + "S24_BE", + "U24_LE", + "U24_BE", + "S32_LE", + "S32_BE", + "U32_LE", + "U32_BE", + "FLOAT_LE", + "FLOAT_BE", + "FLOAT64_LE", + "FLOAT64_BE", + "IEC958_SUBFRAME_LE", + "IEC958_SUBFRAME_BE", + "MU_LAW", + "A_LAW", + "IMA_ADPCM", + "MPEG", + /*[24] =*/ "GSM", + [31] = "SPECIAL", + "S24_3LE", + "S24_3BE", + "U24_3LE", + "U24_3BE", + "S20_3LE", + "S20_3BE", + "U20_3LE", + "U20_3BE", + "S18_3LE", + "S18_3BE", + "U18_3LE", + /*[43] =*/ "U18_3BE", +#if 0 + /* recent additions, may not be present on local asound.h */ + "G723_24", + "G723_24_1B", + "G723_40", + "G723_40_1B", + "DSD_U8", + "DSD_U16_LE", +#endif +}; + +/* Returns a human readable name for the format associated with bit_index, + * NULL if bit_index is not known. + */ +inline const char *pcm_get_format_name(unsigned bit_index) +{ + return bit_index < ARRAY_SIZE(format_lookup) ? format_lookup[bit_index] : NULL; +} + +int main(int argc, char **argv) +{ + unsigned int device = 0; + unsigned int card = 0; + int i; + + if (argc < 3) { + fprintf(stderr, "Usage: %s -D card -d device\n", argv[0]); + return 1; + } + + /* parse command line arguments */ + argv += 1; + while (*argv) { + if (strcmp(*argv, "-D") == 0) { + argv++; + if (*argv) + card = atoi(*argv); + } + if (strcmp(*argv, "-d") == 0) { + argv++; + if (*argv) + device = atoi(*argv); + } + if (*argv) + argv++; + } + + printf("Info for card %d, device %d:\n", card, device); + + for (i = 0; i < 2; i++) { + struct pcm_params *params; + struct pcm_mask *m; + unsigned int min; + unsigned int max; + + printf("\nPCM %s:\n", i == 0 ? "out" : "in"); + + params = pcm_params_get(card, device, i == 0 ? PCM_OUT : PCM_IN); + if (params == NULL) { + printf("Device does not exist.\n"); + continue; + } + + m = pcm_params_get_mask(params, PCM_PARAM_ACCESS); + if (m) { /* bitmask, refer to SNDRV_PCM_ACCESS_*, generally interleaved */ + printf(" Access:\t%#08x\n", m->bits[0]); + } + m = pcm_params_get_mask(params, PCM_PARAM_FORMAT); + if (m) { /* bitmask, refer to: SNDRV_PCM_FORMAT_* */ + unsigned j, k, count = 0; + const unsigned bitcount = sizeof(m->bits[0]) * 8; + + /* we only check first two format masks (out of 8) - others are zero. */ + printf(" Format[0]:\t%#08x\n", m->bits[0]); + printf(" Format[1]:\t%#08x\n", m->bits[1]); + + /* print friendly format names, if they exist */ + for (k = 0; k < 2; ++k) { + for (j = 0; j < bitcount; ++j) { + const char *name; + + if (m->bits[k] & (1 << j)) { + name = pcm_get_format_name(j + k*bitcount); + if (name) { + if (count++ == 0) { + printf(" Format Name:\t"); + } else { + printf (", "); + } + printf("%s", name); + } + } + } + } + if (count) { + printf("\n"); + } + } + m = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT); + if (m) { /* bitmask, should be 1: SNDRV_PCM_SUBFORMAT_STD */ + printf(" Subformat:\t%#08x\n", m->bits[0]); + } + min = pcm_params_get_min(params, PCM_PARAM_RATE); + max = pcm_params_get_max(params, PCM_PARAM_RATE); + printf(" Rate:\tmin=%uHz\tmax=%uHz\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_CHANNELS); + max = pcm_params_get_max(params, PCM_PARAM_CHANNELS); + printf(" Channels:\tmin=%u\t\tmax=%u\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS); + max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS); + printf(" Sample bits:\tmin=%u\t\tmax=%u\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE); + max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE); + printf(" Period size:\tmin=%u\t\tmax=%u\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_PERIODS); + max = pcm_params_get_max(params, PCM_PARAM_PERIODS); + printf("Period count:\tmin=%u\t\tmax=%u\n", min, max); + + pcm_params_free(params); + } + + return 0; +}
diff --git a/tinyalsa/tinyplay.c b/tinyalsa/tinyplay.c new file mode 100644 index 0000000..88c54ae --- /dev/null +++ b/tinyalsa/tinyplay.c
@@ -0,0 +1,272 @@ +/* tinyplay.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 <tinyalsa/asoundlib.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <signal.h> + +#define ID_RIFF 0x46464952 +#define ID_WAVE 0x45564157 +#define ID_FMT 0x20746d66 +#define ID_DATA 0x61746164 + +struct riff_wave_header { + uint32_t riff_id; + uint32_t riff_sz; + uint32_t wave_id; +}; + +struct chunk_header { + uint32_t id; + uint32_t sz; +}; + +struct chunk_fmt { + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; +}; + +static int close = 0; + +void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels, + unsigned int rate, unsigned int bits, unsigned int period_size, + unsigned int period_count); + +void stream_close(int sig) +{ + /* allow the stream to be closed gracefully */ + signal(sig, SIG_IGN); + close = 1; +} + +int main(int argc, char **argv) +{ + FILE *file; + struct riff_wave_header riff_wave_header; + struct chunk_header chunk_header; + struct chunk_fmt chunk_fmt; + unsigned int device = 0; + unsigned int card = 0; + unsigned int period_size = 1024; + unsigned int period_count = 4; + char *filename; + int more_chunks = 1; + + if (argc < 2) { + fprintf(stderr, "Usage: %s file.wav [-D card] [-d device] [-p period_size]" + " [-n n_periods] \n", argv[0]); + return 1; + } + + filename = argv[1]; + file = fopen(filename, "rb"); + if (!file) { + fprintf(stderr, "Unable to open file '%s'\n", filename); + return 1; + } + + fread(&riff_wave_header, sizeof(riff_wave_header), 1, file); + if ((riff_wave_header.riff_id != ID_RIFF) || + (riff_wave_header.wave_id != ID_WAVE)) { + fprintf(stderr, "Error: '%s' is not a riff/wave file\n", filename); + fclose(file); + return 1; + } + + do { + fread(&chunk_header, sizeof(chunk_header), 1, file); + + switch (chunk_header.id) { + case ID_FMT: + fread(&chunk_fmt, sizeof(chunk_fmt), 1, file); + /* If the format header is larger, skip the rest */ + if (chunk_header.sz > sizeof(chunk_fmt)) + fseek(file, chunk_header.sz - sizeof(chunk_fmt), SEEK_CUR); + break; + case ID_DATA: + /* Stop looking for chunks */ + more_chunks = 0; + break; + default: + /* Unknown chunk, skip bytes */ + fseek(file, chunk_header.sz, SEEK_CUR); + } + } while (more_chunks); + + /* parse command line arguments */ + argv += 2; + while (*argv) { + if (strcmp(*argv, "-d") == 0) { + argv++; + if (*argv) + device = atoi(*argv); + } + if (strcmp(*argv, "-p") == 0) { + argv++; + if (*argv) + period_size = atoi(*argv); + } + if (strcmp(*argv, "-n") == 0) { + argv++; + if (*argv) + period_count = atoi(*argv); + } + if (strcmp(*argv, "-D") == 0) { + argv++; + if (*argv) + card = atoi(*argv); + } + if (*argv) + argv++; + } + + play_sample(file, card, device, chunk_fmt.num_channels, chunk_fmt.sample_rate, + chunk_fmt.bits_per_sample, period_size, period_count); + + fclose(file); + + return 0; +} + +int check_param(struct pcm_params *params, unsigned int param, unsigned int value, + char *param_name, char *param_unit) +{ + unsigned int min; + unsigned int max; + int is_within_bounds = 1; + + min = pcm_params_get_min(params, param); + if (value < min) { + fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value, + param_unit, min, param_unit); + is_within_bounds = 0; + } + + max = pcm_params_get_max(params, param); + if (value > max) { + fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value, + param_unit, max, param_unit); + is_within_bounds = 0; + } + + return is_within_bounds; +} + +int sample_is_playable(unsigned int card, unsigned int device, unsigned int channels, + unsigned int rate, unsigned int bits, unsigned int period_size, + unsigned int period_count) +{ + struct pcm_params *params; + int can_play; + + params = pcm_params_get(card, device, PCM_OUT); + if (params == NULL) { + fprintf(stderr, "Unable to open PCM device %u.\n", device); + return 0; + } + + can_play = check_param(params, PCM_PARAM_RATE, rate, "Sample rate", "Hz"); + can_play &= check_param(params, PCM_PARAM_CHANNELS, channels, "Sample", " channels"); + can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, bits, "Bitrate", " bits"); + can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, period_size, "Period size", "Hz"); + can_play &= check_param(params, PCM_PARAM_PERIODS, period_count, "Period count", "Hz"); + + pcm_params_free(params); + + return can_play; +} + +void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels, + unsigned int rate, unsigned int bits, unsigned int period_size, + unsigned int period_count) +{ + struct pcm_config config; + struct pcm *pcm; + char *buffer; + int size; + int num_read; + + memset(&config, 0, sizeof(config)); + config.channels = channels; + config.rate = rate; + config.period_size = period_size; + config.period_count = period_count; + if (bits == 32) + config.format = PCM_FORMAT_S32_LE; + else if (bits == 16) + config.format = PCM_FORMAT_S16_LE; + config.start_threshold = 0; + config.stop_threshold = 0; + config.silence_threshold = 0; + + if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count)) { + return; + } + + pcm = pcm_open(card, device, PCM_OUT, &config); + if (!pcm || !pcm_is_ready(pcm)) { + fprintf(stderr, "Unable to open PCM device %u (%s)\n", + device, pcm_get_error(pcm)); + return; + } + + size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); + buffer = malloc(size); + if (!buffer) { + fprintf(stderr, "Unable to allocate %d bytes\n", size); + free(buffer); + pcm_close(pcm); + return; + } + + printf("Playing sample: %u ch, %u hz, %u bit\n", channels, rate, bits); + + /* catch ctrl-c to shutdown cleanly */ + signal(SIGINT, stream_close); + + do { + num_read = fread(buffer, 1, size, file); + if (num_read > 0) { + if (pcm_write(pcm, buffer, num_read)) { + fprintf(stderr, "Error playing sample\n"); + break; + } + } + } while (!close && num_read > 0); + + free(buffer); + pcm_close(pcm); +} +