diff options
-rw-r--r-- | Android.mk | 18 | ||||
-rw-r--r-- | Makefile | 46 | ||||
-rw-r--r-- | include/sound/asound.h | 1 | ||||
-rw-r--r-- | include/tinyalsa/asoundlib.h | 40 | ||||
-rw-r--r-- | mixer.c | 176 | ||||
-rw-r--r-- | pcm.c | 122 | ||||
-rw-r--r-- | tinycap.c | 64 | ||||
-rw-r--r-- | tinymix.c | 105 | ||||
-rw-r--r-- | tinypcminfo.c | 108 | ||||
-rw-r--r-- | tinyplay.c | 94 | ||||
-rw-r--r-- | tinywavinfo.c | 214 |
11 files changed, 835 insertions, 153 deletions
@@ -21,6 +21,24 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_C_INCLUDES:= external/tinyalsa/include +LOCAL_SRC_FILES:= tinywavinfo.c +LOCAL_MODULE := tinywavinfo +LOCAL_SHARED_LIBRARIES:= libcutils libutils libm +LOCAL_MODULE_TAGS := optional +include $(BUILD_EXECUTABLE) + +ifeq ($(HOST_OS), linux) +include $(CLEAR_VARS) +LOCAL_C_INCLUDES:= external/tinyalsa/include +LOCAL_SRC_FILES:= tinywavinfo.c +LOCAL_MODULE := tinywavinfo +LOCAL_STATIC_LIBRARIES:= libcutils libutils +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 @@ -1,29 +1,45 @@ -CFLAGS = -c -fPIC -Wall +CFLAGS ?= -Wall +LDFLAGS ?= INC = include OBJECTS = mixer.o pcm.o -LIB = libtinyalsa.so +LIB = libtinyalsa.a +SHLIB = libtinyalsa.so CROSS_COMPILE = +PREFIX = /usr/local -all: $(LIB) tinyplay tinycap tinymix tinypcminfo +.PHONY: all +all: $(LIB) $(SHLIB) tinyplay tinycap tinymix tinypcminfo -tinyplay: $(LIB) tinyplay.o - $(CROSS_COMPILE)gcc tinyplay.o -L. -ltinyalsa -o tinyplay +tinyplay: $(SHLIB) tinyplay.o + $(CROSS_COMPILE)$(CC) $(LDFLAGS) tinyplay.o -L. -ltinyalsa -o tinyplay -tinycap: $(LIB) tinycap.o - $(CROSS_COMPILE)gcc tinycap.o -L. -ltinyalsa -o tinycap +tinycap: $(SHLIB) tinycap.o + $(CROSS_COMPILE)$(CC) $(LDFLAGS) tinycap.o -L. -ltinyalsa -o tinycap -tinymix: $(LIB) tinymix.o - $(CROSS_COMPILE)gcc tinymix.o -L. -ltinyalsa -o tinymix +tinymix: $(SHLIB) tinymix.o + $(CROSS_COMPILE)$(CC) $(LDFLAGS) tinymix.o -L. -ltinyalsa -o tinymix -tinypcminfo: $(LIB) tinypcminfo.o - $(CROSS_COMPILE)gcc tinypcminfo.o -L. -ltinyalsa -o tinypcminfo +tinypcminfo: $(SHLIB) tinypcminfo.o + $(CROSS_COMPILE)$(CC) $(LDFLAGS) tinypcminfo.o -L. -ltinyalsa -o tinypcminfo + +$(SHLIB): $(OBJECTS) + $(CROSS_COMPILE)$(CC) $(LDFLAGS) -shared $(OBJECTS) -o $(SHLIB) $(LIB): $(OBJECTS) - $(CROSS_COMPILE)gcc -shared $(OBJECTS) -o $(LIB) + $(CROSS_COMPILE)$(AR) rcs $@ $^ -.c.o: - $(CROSS_COMPILE)gcc $(CFLAGS) $< -I$(INC) +%.o: %.c + $(CROSS_COMPILE)$(CC) $(CFLAGS) -fPIC -c $^ -I$(INC) -o $@ +.PHONY: clean clean: - -rm $(LIB) $(OBJECTS) tinyplay.o tinyplay tinycap.o tinycap \ + -rm $(LIB) $(SHLIB) $(OBJECTS) tinyplay.o tinyplay tinycap.o tinycap \ tinymix.o tinymix tinypcminfo.o tinypcminfo + +.PHONY: install +install: $(LIB) $(SHLIB) + cp -u $(SHLIB) $(PREFIX)/lib/$(SHLIB) + cp -u $(LIB) $(PREFIX)/lib/$(LIB) + mkdir -p $(PREFIX)/include/tinyalsa + cp -u $(INC)/tinyalsa/asoundlib.h $(PREFIX)/include/tinyalsa/asoundlib.h + diff --git a/include/sound/asound.h b/include/sound/asound.h index a041628..7c6de81 100644 --- a/include/sound/asound.h +++ b/include/sound/asound.h @@ -12,6 +12,7 @@ #ifndef __SOUND_ASOUND_H #define __SOUND_ASOUND_H +#include <time.h> #include <linux/types.h> #define SNDRV_PROTOCOL_VERSION(major, minor, subminor) (((major)<<16)|((minor)<<8)|(subminor)) diff --git a/include/tinyalsa/asoundlib.h b/include/tinyalsa/asoundlib.h index 873b9c9..8c215ce 100644 --- a/include/tinyalsa/asoundlib.h +++ b/include/tinyalsa/asoundlib.h @@ -55,15 +55,12 @@ struct pcm; * 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_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 @@ -77,6 +74,11 @@ enum pcm_format { 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; @@ -101,6 +103,11 @@ struct pcm_config { /* 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, @@ -138,14 +145,16 @@ int pcm_is_ready(struct pcm *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); unsigned int pcm_params_get_max(struct pcm_params *pcm_params, enum pcm_param param); -/* 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 the file descriptor associated with the pcm */ +int pcm_get_file_descriptor(struct pcm *pcm); /* Returns a human readable reason for the last error */ const char *pcm_get_error(struct pcm *pcm); @@ -162,10 +171,9 @@ 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 @@ -174,6 +182,9 @@ unsigned int pcm_get_latency(struct pcm *pcm); int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, struct timespec *tstamp); +/* Returns the subdevice on which the pcm has been opened */ +unsigned int pcm_get_subdevice(struct pcm *pcm); + /* Write data to the fifo. * Will start playback on the first write or on a write that * occurs after a fifo underrun. @@ -185,10 +196,13 @@ 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); +/* 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); @@ -197,6 +211,9 @@ int pcm_stop(struct pcm *pcm); int pcm_wait(struct pcm *pcm, int timeout); +/* Get the pcm delay */ +long pcm_get_delay(struct pcm *pcm); + /* * MIXER API */ @@ -220,6 +237,7 @@ struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer, unsigned int index); /* Get info about mixer controls */ +unsigned int mixer_ctl_get_id(struct mixer_ctl *ctl); 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); @@ -28,11 +28,14 @@ #include <stdio.h> #include <stdlib.h> +#include <stdint.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <ctype.h> +#include <limits.h> +#include <time.h> #include <sys/ioctl.h> @@ -46,14 +49,13 @@ struct mixer_ctl { struct mixer *mixer; - struct snd_ctl_elem_info *info; + 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; }; @@ -71,7 +73,7 @@ void mixer_close(struct mixer *mixer) 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; + 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); @@ -80,9 +82,6 @@ void mixer_close(struct mixer *mixer) free(mixer->ctl); } - if (mixer->elem_info) - free(mixer->elem_info); - free(mixer); /* TODO: verify frees */ @@ -112,8 +111,7 @@ struct mixer *mixer_open(unsigned int card) 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) + if (!mixer->ctl) goto fail; if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0) @@ -131,11 +129,10 @@ struct mixer *mixer_open(unsigned int card) goto fail; for (n = 0; n < mixer->count; n++) { - struct snd_ctl_elem_info *ei = mixer->elem_info + n; + struct snd_ctl_elem_info *ei = &mixer->ctl[n].info; 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*)); @@ -200,12 +197,15 @@ struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer, unsigned int index) { unsigned int n; + struct mixer_ctl *ctl; if (!mixer) return NULL; + ctl = mixer->ctl; + for (n = 0; n < mixer->count; n++) - if (!strcmp(name, (char*) mixer->elem_info[n].id.name)) + if (!strcmp(name, (char*) ctl[n].info.id.name)) if (index-- == 0) return mixer->ctl + n; @@ -217,12 +217,23 @@ void mixer_ctl_update(struct mixer_ctl *ctl) ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info); } +unsigned int mixer_ctl_get_id(struct mixer_ctl *ctl) +{ + if (!ctl) + return UINT_MAX; + + /* numid values start at 1, return a 0-base value that + * can be passed to mixer_get_ctl() + */ + return ctl->info.id.numid - 1; +} + const char *mixer_ctl_get_name(struct mixer_ctl *ctl) { if (!ctl) return NULL; - return (const char *)ctl->info->id.name; + return (const char *)ctl->info.id.name; } enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl) @@ -230,7 +241,7 @@ enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl) if (!ctl) return MIXER_CTL_TYPE_UNKNOWN; - switch (ctl->info->type) { + 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; @@ -246,7 +257,7 @@ const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl) if (!ctl) return ""; - switch (ctl->info->type) { + 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"; @@ -262,19 +273,16 @@ unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl) if (!ctl) return 0; - return ctl->info->count; + 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; + if ((percent > 100) || (percent < 0)) { + return -EINVAL; + } - range = (ei->value.integer.max - ei->value.integer.min); + int range = (ei->value.integer.max - ei->value.integer.min); return ei->value.integer.min + (range * percent) / 100; } @@ -291,18 +299,18 @@ static int int_to_percent(struct snd_ctl_elem_info *ei, int value) int mixer_ctl_get_percent(struct mixer_ctl *ctl, unsigned int id) { - if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER)) return -EINVAL; - return int_to_percent(ctl->info, mixer_ctl_get_value(ctl, id)); + 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)) + 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)); + 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) @@ -310,16 +318,16 @@ 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)) + if (!ctl || (id >= ctl->info.count)) return -EINVAL; memset(&ev, 0, sizeof(ev)); - ev.id.numid = ctl->info->id.numid; + 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) { + switch (ctl->info.type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return !!ev.value.integer.value[id]; @@ -342,31 +350,55 @@ 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) { struct snd_ctl_elem_value ev; - int ret; + int ret = 0; size_t size; void *source; - if (!ctl || (count > ctl->info->count) || !count || !array) + if (!ctl || (count > ctl->info.count) || !count || !array) return -EINVAL; memset(&ev, 0, sizeof(ev)); - ev.id.numid = ctl->info->id.numid; + 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) { + switch (ctl->info.type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: case SNDRV_CTL_ELEM_TYPE_INTEGER: + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + if (ret < 0) + return ret; size = sizeof(ev.value.integer.value[0]); source = ev.value.integer.value; break; case SNDRV_CTL_ELEM_TYPE_BYTES: - size = sizeof(ev.value.bytes.data[0]); - source = ev.value.bytes.data; - break; + /* check if this is new bytes TLV */ + if (ctl->info.access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) { + struct snd_ctl_tlv *tlv; + int ret; + + if (count > SIZE_MAX - sizeof(*tlv)) + return -EINVAL; + tlv = calloc(1, sizeof(*tlv) + count); + if (!tlv) + return -ENOMEM; + tlv->numid = ctl->info.id.numid; + tlv->length = count; + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_READ, tlv); + + source = tlv->tlv; + memcpy(array, source, count); + + free(tlv); + + return ret; + } else { + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + if (ret < 0) + return ret; + size = sizeof(ev.value.bytes.data[0]); + source = ev.value.bytes.data; + break; + } default: return -EINVAL; @@ -382,21 +414,26 @@ 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)) + if (!ctl || (id >= ctl->info.count)) return -EINVAL; memset(&ev, 0, sizeof(ev)); - ev.id.numid = ctl->info->id.numid; + 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) { + switch (ctl->info.type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: ev.value.integer.value[id] = !!value; break; case SNDRV_CTL_ELEM_TYPE_INTEGER: + if ((value < mixer_ctl_get_range_min(ctl)) || + (value > mixer_ctl_get_range_max(ctl))) { + return -EINVAL; + } + ev.value.integer.value[id] = value; break; @@ -404,6 +441,10 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value) ev.value.enumerated.item[id] = value; break; + case SNDRV_CTL_ELEM_TYPE_BYTES: + ev.value.bytes.data[id] = value; + break; + default: return -EINVAL; } @@ -417,13 +458,13 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) size_t size; void *dest; - if (!ctl || (count > ctl->info->count) || !count || !array) + if (!ctl || (count > ctl->info.count) || !count || !array) return -EINVAL; memset(&ev, 0, sizeof(ev)); - ev.id.numid = ctl->info->id.numid; + ev.id.numid = ctl->info.id.numid; - switch (ctl->info->type) { + switch (ctl->info.type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: case SNDRV_CTL_ELEM_TYPE_INTEGER: size = sizeof(ev.value.integer.value[0]); @@ -431,8 +472,27 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) break; case SNDRV_CTL_ELEM_TYPE_BYTES: - size = sizeof(ev.value.bytes.data[0]); - dest = ev.value.bytes.data; + /* check if this is new bytes TLV */ + if (ctl->info.access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) { + struct snd_ctl_tlv *tlv; + int ret = 0; + if (count > SIZE_MAX - sizeof(*tlv)) + return -EINVAL; + tlv = calloc(1, sizeof(*tlv) + count); + if (!tlv) + return -ENOMEM; + tlv->numid = ctl->info.id.numid; + tlv->length = count; + memcpy(tlv->tlv, array, count); + + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_WRITE, tlv); + free(tlv); + + return ret; + } else { + size = sizeof(ev.value.bytes.data[0]); + dest = ev.value.bytes.data; + } break; default: @@ -446,18 +506,18 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) int mixer_ctl_get_range_min(struct mixer_ctl *ctl) { - if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER)) return -EINVAL; - return ctl->info->value.integer.min; + 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)) + if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER)) return -EINVAL; - return ctl->info->value.integer.max; + return ctl->info.value.integer.max; } unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl) @@ -465,14 +525,14 @@ unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl) if (!ctl) return 0; - return ctl->info->value.enumerated.items; + 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)) + 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]; @@ -484,15 +544,15 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string) struct snd_ctl_elem_value ev; int ret; - if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED)) + if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_ENUMERATED)) return -EINVAL; - num_enums = ctl->info->value.enumerated.items; + 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; + ev.id.numid = ctl->info.id.numid; ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); if (ret < 0) return ret; @@ -38,6 +38,7 @@ #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/time.h> +#include <time.h> #include <limits.h> #include <linux/ioctl.h> @@ -159,6 +160,7 @@ struct pcm { int fd; unsigned int flags; int running:1; + int prepared:1; int underruns; unsigned int buffer_size; unsigned int boundary; @@ -169,6 +171,8 @@ struct pcm { struct snd_pcm_sync_ptr *sync_ptr; void *mmap_buffer; unsigned int noirq_frames_per_msec; + long pcm_delay; + unsigned int subdevice; }; unsigned int pcm_get_buffer_size(struct pcm *pcm) @@ -176,11 +180,21 @@ unsigned int pcm_get_buffer_size(struct pcm *pcm) return pcm->buffer_size; } +int pcm_get_file_descriptor(struct pcm *pcm) +{ + return pcm->fd; +} + const char* pcm_get_error(struct pcm *pcm) { return pcm->error; } +unsigned int pcm_get_subdevice(struct pcm *pcm) +{ + return pcm->subdevice; +} + static int oops(struct pcm *pcm, int e, const char *fmt, ...) { va_list ap; @@ -300,7 +314,7 @@ static void pcm_hw_munmap_status(struct pcm *pcm) { } static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset, - const char *src, unsigned int src_offset, + char *buf, unsigned int src_offset, unsigned int frames) { int size_bytes = pcm_frames_to_bytes(pcm, frames); @@ -308,12 +322,18 @@ static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset, int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset); /* interleaved only atm */ - memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes, - src + src_offset_bytes, size_bytes); + 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_write_areas(struct pcm *pcm, const char *src, +static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf, unsigned int offset, unsigned int size) { void *pcm_areas; @@ -323,7 +343,7 @@ static int pcm_mmap_write_areas(struct pcm *pcm, const char *src, while (size > 0) { frames = size; pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); - pcm_areas_copy(pcm, pcm_offset, src, 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); @@ -386,14 +406,16 @@ int pcm_write(struct pcm *pcm, const void *data, unsigned int count) for (;;) { if (!pcm->running) { - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE)) - return oops(pcm, errno, "cannot prepare channel"); + 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 @@ -429,6 +451,7 @@ int pcm_read(struct pcm *pcm, void *data, unsigned int count) } } 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 */ @@ -494,6 +517,12 @@ void pcm_params_free(struct pcm_params *pcm_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; @@ -536,6 +565,23 @@ static int pcm_param_to_alsa(enum pcm_param param) } } +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) { @@ -582,6 +628,7 @@ int pcm_close(struct pcm *pcm) if (pcm->fd >= 0) close(pcm->fd); + pcm->prepared = 0; pcm->running = 0; pcm->buffer_size = 0; pcm->fd = -1; @@ -619,6 +666,7 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, oops(pcm, errno, "cannot get info"); goto fail_close; } + pcm->subdevice = info.subdevice; param_init(¶ms); param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT, @@ -719,6 +767,17 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, 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; @@ -736,11 +795,24 @@ int pcm_is_ready(struct pcm *pcm) return pcm->fd >= 0; } -int pcm_start(struct pcm *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; +} + +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); @@ -756,6 +828,7 @@ 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; } @@ -768,7 +841,7 @@ static inline int pcm_mmap_playback_avail(struct pcm *pcm) if (avail < 0) avail += pcm->boundary; - else if (avail > (int)pcm->boundary) + else if (avail >= (int)pcm->boundary) avail -= pcm->boundary; return avail; @@ -859,7 +932,7 @@ int pcm_wait(struct pcm *pcm, int timeout) int err; pfd.fd = pcm->fd; - pfd.events = POLLOUT | POLLERR | POLLNVAL; + pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL; do { /* let's wait for avail or timeout */ @@ -894,7 +967,7 @@ int pcm_wait(struct pcm *pcm, int timeout) return 1; } -int pcm_mmap_write(struct pcm *pcm, const void *buffer, unsigned int bytes) +int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes) { int err = 0, frames, avail; unsigned int offset = 0, count; @@ -936,6 +1009,7 @@ int pcm_mmap_write(struct pcm *pcm, const void *buffer, unsigned int bytes) 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, @@ -955,7 +1029,7 @@ int pcm_mmap_write(struct pcm *pcm, const void *buffer, unsigned int bytes) break; /* copy frames from buffer */ - frames = pcm_mmap_write_areas(pcm, buffer, offset, frames); + 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, @@ -970,3 +1044,27 @@ int pcm_mmap_write(struct pcm *pcm, const void *buffer, unsigned int bytes) 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); +} + +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; +} @@ -57,11 +57,12 @@ struct wav_header { }; int capturing = 1; +int prinfo = 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); + unsigned int period_count, unsigned int capture_time); void sigint_handler(int sig) { @@ -80,18 +81,27 @@ int main(int argc, char **argv) unsigned int frames; unsigned int period_size = 1024; unsigned int period_count = 4; + unsigned int capture_time = 0; enum pcm_format format; + int no_header = 0; 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]); + fprintf(stderr, "Usage: %s {file.wav | --} [-D card] [-d device] [-c channels] " + "[-r rate] [-b bits] [-p period_size] [-n n_periods] [-t time_in_seconds]\n\n" + "Use -- for filename to send raw PCM to stdout\n", argv[0]); return 1; } - file = fopen(argv[1], "wb"); - if (!file) { - fprintf(stderr, "Unable to create file '%s'\n", argv[1]); - return 1; + if (strcmp(argv[1],"--") == 0) { + file = stdout; + prinfo = 0; + no_header = 1; + } else { + file = fopen(argv[1], "wb"); + if (!file) { + fprintf(stderr, "Unable to create file '%s'\n", argv[1]); + return 1; + } } /* parse command line arguments */ @@ -125,6 +135,10 @@ int main(int argc, char **argv) argv++; if (*argv) period_count = atoi(*argv); + } else if (strcmp(*argv, "-t") == 0) { + argv++; + if (*argv) + capture_time = atoi(*argv); } if (*argv) argv++; @@ -150,7 +164,7 @@ int main(int argc, char **argv) format = PCM_FORMAT_S16_LE; break; default: - fprintf(stderr, "%d bits is not supported.\n", bits); + fprintf(stderr, "%u bits is not supported.\n", bits); return 1; } @@ -160,20 +174,26 @@ int main(int argc, char **argv) header.data_id = ID_DATA; /* leave enough room for header */ - fseek(file, sizeof(struct wav_header), SEEK_SET); + if (!no_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); + period_size, period_count, capture_time); + if (prinfo) { + printf("Captured %u 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); + if (!no_header) { + 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); @@ -183,7 +203,7 @@ int main(int argc, char **argv) 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) + unsigned int period_count, unsigned int capture_time) { struct pcm_config config; struct pcm *pcm; @@ -191,6 +211,7 @@ unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, unsigned int size; unsigned int bytes_read = 0; + memset(&config, 0, sizeof(config)); config.channels = channels; config.rate = rate; config.period_size = period_size; @@ -210,16 +231,23 @@ unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, 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); + fprintf(stderr, "Unable to allocate %u bytes\n", size); free(buffer); pcm_close(pcm); return 0; } - printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate, + if (prinfo) { + 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 (capture_time > 0 && + ((bytes_read + size) > pcm_frames_to_bytes(pcm, capture_time * rate))) { + size = pcm_frames_to_bytes(pcm, capture_time * rate) - bytes_read; + capturing = 0; + } if (fwrite(buffer, 1, size, file) != size) { fprintf(stderr,"Error capturing sample\n"); break; @@ -27,10 +27,13 @@ */ #include <tinyalsa/asoundlib.h> +#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> +#include <limits.h> +#include <errno.h> static void tinymix_list_controls(struct mixer *mixer); static void tinymix_detail_control(struct mixer *mixer, const char *control, @@ -87,7 +90,7 @@ static void tinymix_list_controls(struct mixer *mixer) num_ctls = mixer_get_num_ctls(mixer); - printf("Number of controls: %d\n", num_ctls); + printf("Number of controls: %u\n", num_ctls); printf("ctl\ttype\tnum\t%-40s value\n", "name"); for (i = 0; i < num_ctls; i++) { @@ -96,7 +99,7 @@ static void tinymix_list_controls(struct mixer *mixer) 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); + printf("%u\t%s\t%u\t%-40s", i, type, num_values, name); tinymix_detail_control(mixer, name, 0); } } @@ -127,6 +130,8 @@ static void tinymix_detail_control(struct mixer *mixer, const char *control, unsigned int num_values; unsigned int i; int min, max; + int ret; + char *buf = NULL; if (isdigit(control[0])) ctl = mixer_get_ctl(mixer, atoi(control)); @@ -141,6 +146,21 @@ static void tinymix_detail_control(struct mixer *mixer, const char *control, type = mixer_ctl_get_type(ctl); num_values = mixer_ctl_get_num_values(ctl); + if ((type == MIXER_CTL_TYPE_BYTE) && (num_values > 0)) { + buf = calloc(1, num_values); + if (buf == NULL) { + fprintf(stderr, "Failed to alloc mem for bytes %u\n", num_values); + return; + } + + ret = mixer_ctl_get_array(ctl, buf, num_values); + if (ret < 0) { + fprintf(stderr, "Failed to mixer_ctl_get_array\n"); + free(buf); + return; + } + } + if (print_all) printf("%s:", mixer_ctl_get_name(ctl)); @@ -156,8 +176,8 @@ static void tinymix_detail_control(struct mixer *mixer, const char *control, 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)); + case MIXER_CTL_TYPE_BYTE: + printf("%02x", buf[i]); break; default: printf(" unknown"); @@ -172,9 +192,75 @@ static void tinymix_detail_control(struct mixer *mixer, const char *control, printf(" (range %d->%d)", min, max); } } + + free(buf); + printf("\n"); } +static void tinymix_set_byte_ctl(struct mixer_ctl *ctl, + char **values, unsigned int num_values) +{ + int ret; + char *buf; + char *end; + unsigned int i; + long n; + + buf = calloc(1, num_values); + if (buf == NULL) { + fprintf(stderr, "set_byte_ctl: Failed to alloc mem for bytes %u\n", num_values); + exit(EXIT_FAILURE); + } + + for (i = 0; i < num_values; i++) { + errno = 0; + n = strtol(values[i], &end, 0); + if (*end) { + fprintf(stderr, "%s not an integer\n", values[i]); + goto fail; + } + if (errno) { + fprintf(stderr, "strtol: %s: %s\n", values[i], + strerror(errno)); + goto fail; + } + if (n < 0 || n > 0xff) { + fprintf(stderr, "%s should be between [0, 0xff]\n", + values[i]); + goto fail; + } + buf[i] = n; + } + + ret = mixer_ctl_set_array(ctl, buf, num_values); + if (ret < 0) { + fprintf(stderr, "Failed to set binary control\n"); + goto fail; + } + + free(buf); + return; + +fail: + free(buf); + exit(EXIT_FAILURE); +} + +static int is_int(char *value) +{ + char* end; + long int result; + + errno = 0; + result = strtol(value, &end, 10); + + if (result == LONG_MIN || result == LONG_MAX) + return 0; + + return errno == 0 && *end == '\0'; +} + static void tinymix_set_value(struct mixer *mixer, const char *control, char **values, unsigned int num_values) { @@ -196,7 +282,12 @@ static void tinymix_set_value(struct mixer *mixer, const char *control, type = mixer_ctl_get_type(ctl); num_ctl_values = mixer_ctl_get_num_values(ctl); - if (isdigit(values[0][0])) { + if (type == MIXER_CTL_TYPE_BYTE) { + tinymix_set_byte_ctl(ctl, values, num_values); + return; + } + + if (is_int(values[0])) { if (num_values == 1) { /* Set all values the same */ int value = atoi(values[0]); @@ -211,13 +302,13 @@ static void tinymix_set_value(struct mixer *mixer, const char *control, /* Set multiple values */ if (num_values > num_ctl_values) { fprintf(stderr, - "Error: %d values given, but control only takes %d\n", + "Error: %u values given, but control only takes %u\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); + fprintf(stderr, "Error: invalid value for index %u\n", i); return; } } diff --git a/tinypcminfo.c b/tinypcminfo.c index 3282186..4eb0afa 100644 --- a/tinypcminfo.c +++ b/tinypcminfo.c @@ -31,6 +31,72 @@ #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. + */ +static 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; @@ -59,10 +125,11 @@ int main(int argc, char **argv) argv++; } - printf("Info for card %d, device %d:\n", card, device); + printf("Info for card %u, device %u:\n", card, device); for (i = 0; i < 2; i++) { struct pcm_params *params; + struct pcm_mask *m; unsigned int min; unsigned int max; @@ -74,6 +141,45 @@ int main(int argc, char **argv) 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); @@ -81,12 +81,18 @@ int main(int argc, char **argv) unsigned int card = 0; unsigned int period_size = 1024; unsigned int period_count = 4; + unsigned int channels = 2; + unsigned int rate = 48000; + unsigned int bits = 16; + unsigned int is_raw = 0; /* Default wav file */ char *filename; int more_chunks = 1; if (argc < 2) { - fprintf(stderr, "Usage: %s file.wav [-D card] [-d device] [-p period_size]" + fprintf(stderr, "Usage1: %s file.wav [-D card] [-d device] [-p period_size]" " [-n n_periods] \n", argv[0]); + fprintf(stderr, "Usage2: %s file.raw [-D card] [-d device] [-p period_size] " + "[-n n_periods] [-c channels] [-r rate] [-b bits] -i raw \n", argv[0]); return 1; } @@ -97,34 +103,6 @@ int main(int argc, char **argv) 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) { @@ -148,12 +126,65 @@ int main(int argc, char **argv) if (*argv) card = atoi(*argv); } + if (strcmp(*argv, "-c") == 0) { + argv++; + if (*argv) + channels = atoi(*argv); + } + if (strcmp(*argv, "-r") == 0) { + argv++; + if (*argv) + rate = atoi(*argv); + } + if (strcmp(*argv, "-b") == 0) { + argv++; + if (*argv) + bits = atoi(*argv); + } + if (strcmp(*argv, "-i") == 0) { + argv++; + if (*argv) { + if (strcasecmp(*argv, "raw") == 0) { + is_raw = 1; + } + } + } 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); + if ( !is_raw ) { + 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); + channels = chunk_fmt.num_channels; + rate = chunk_fmt.sample_rate; + bits = chunk_fmt.bits_per_sample; + } + + play_sample(file, card, device, channels, rate, bits, period_size, period_count); fclose(file); @@ -218,6 +249,7 @@ void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned in int size; int num_read; + memset(&config, 0, sizeof(config)); config.channels = channels; config.rate = rate; config.period_size = period_size; diff --git a/tinywavinfo.c b/tinywavinfo.c new file mode 100644 index 0000000..99ee5da --- /dev/null +++ b/tinywavinfo.c @@ -0,0 +1,214 @@ +/* tinywavinfo.c +** +** Copyright 2015, 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 <stdint.h> +#include <string.h> +#include <signal.h> +#include <math.h> +#include <malloc.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 analyse_sample(FILE *file, unsigned int channels, unsigned int bits, + unsigned int data_chunk_size); + +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; + char *filename; + int more_chunks = 1; + + if (argc < 2) { + fprintf(stderr, "Usage: %s file.wav \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); + + printf("Input File : %s \n", filename); + printf("Channels : %u \n", chunk_fmt.num_channels); + printf("Sample Rate : %u \n", chunk_fmt.sample_rate); + printf("Bits per sample : %u \n\n", chunk_fmt.bits_per_sample); + + analyse_sample(file, chunk_fmt.num_channels, chunk_fmt.bits_per_sample, + chunk_header.sz); + + fclose(file); + + return 0; +} + +void analyse_sample(FILE *file, unsigned int channels, unsigned int bits, + unsigned int data_chunk_size) +{ + void *buffer; + int size; + int num_read; + int i; + unsigned int ch; + int frame_size = 1024; + unsigned int byte_align = 0; + float *power; + int total_sample_per_channel; + float normalization_factor; + + if (bits == 32) + byte_align = 4; + else if (bits == 16) + byte_align = 2; + + normalization_factor = (float)pow(2.0, (bits-1)); + + size = channels * byte_align * frame_size; + buffer = memalign(byte_align, size); + + if (!buffer) { + fprintf(stderr, "Unable to allocate %d bytes\n", size); + free(buffer); + return; + } + + power = (float *) calloc(channels, sizeof(float)); + + total_sample_per_channel = data_chunk_size / (channels * byte_align); + + /* catch ctrl-c to shutdown cleanly */ + signal(SIGINT, stream_close); + + do { + num_read = fread(buffer, 1, size, file); + if (num_read > 0) { + if (2 == byte_align) { + short *buffer_ptr = (short *)buffer; + for (i = 0; i < num_read; i += channels) { + for (ch = 0; ch < channels; ch++) { + int temp = *buffer_ptr++; + /* Signal Normalization */ + float f = (float) temp / normalization_factor; + *(power + ch) += (float) (f * f); + } + } + } + if (4 == byte_align) { + int *buffer_ptr = (int *)buffer; + for (i = 0; i < num_read; i += channels) { + for (ch = 0; ch < channels; ch++) { + int temp = *buffer_ptr++; + /* Signal Normalization */ + float f = (float) temp / normalization_factor; + *(power + ch) += (float) (f * f); + } + } + } + } + }while (!close && num_read > 0); + + for (ch = 0; ch < channels; ch++) { + float average_power = 10 * log10((*(power + ch)) / total_sample_per_channel); + if(isinf (average_power)) { + printf("Channel [%2u] Average Power : NO signal or ZERO signal\n", ch); + } else { + printf("Channel [%2u] Average Power : %.2f dB\n", ch, average_power); + } + } + + free(buffer); + free(power); + +} + |