diff options
-rw-r--r-- | include/tinyalsa/asoundlib.h | 48 | ||||
-rw-r--r-- | mixer.c | 103 | ||||
-rw-r--r-- | pcm.c | 99 | ||||
-rw-r--r-- | tinycap.c | 76 | ||||
-rw-r--r-- | tinymix.c | 111 | ||||
-rw-r--r-- | tinypcminfo.c | 106 |
6 files changed, 467 insertions, 76 deletions
diff --git a/include/tinyalsa/asoundlib.h b/include/tinyalsa/asoundlib.h index ddd7a01..a85e810 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,27 +145,32 @@ 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 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 @@ -178,10 +190,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); @@ -201,6 +216,9 @@ struct mixer_ctl; 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); @@ -215,6 +233,12 @@ 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); @@ -52,7 +52,8 @@ struct mixer_ctl { struct mixer { int fd; - struct snd_ctl_elem_info *info; + struct snd_ctl_card_info card_info; + struct snd_ctl_elem_info *elem_info; struct mixer_ctl *ctl; unsigned int count; }; @@ -79,8 +80,8 @@ void mixer_close(struct mixer *mixer) free(mixer->ctl); } - if (mixer->info) - free(mixer->info); + if (mixer->elem_info) + free(mixer->elem_info); free(mixer); @@ -111,8 +112,11 @@ struct mixer *mixer_open(unsigned int card) goto fail; mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl)); - mixer->info = calloc(elist.count, sizeof(struct snd_ctl_elem_info)); - if (!mixer->ctl || !mixer->info) + 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)); @@ -127,7 +131,7 @@ struct mixer *mixer_open(unsigned int card) goto fail; for (n = 0; n < mixer->count; n++) { - struct snd_ctl_elem_info *ei = mixer->info + 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; @@ -165,6 +169,11 @@ fail: 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) @@ -189,12 +198,17 @@ struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name) return NULL; for (n = 0; n < mixer->count; n++) - if (!strcmp(name, (char*) mixer->info[n].id.name)) + 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) @@ -245,14 +259,11 @@ unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl) 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; } @@ -320,7 +331,7 @@ 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; @@ -330,21 +341,41 @@ int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count) 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: + 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; + + tlv = calloc(1, sizeof(*tlv) + count); + 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; @@ -375,6 +406,11 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int 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; @@ -382,6 +418,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; } @@ -409,8 +449,23 @@ 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; + tlv = calloc(1, sizeof(*tlv) + count); + 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: @@ -159,6 +159,7 @@ struct pcm { int fd; unsigned int flags; int running:1; + int prepared:1; int underruns; unsigned int buffer_size; unsigned int boundary; @@ -212,10 +213,11 @@ static unsigned int pcm_format_to_alsa(enum pcm_format format) }; } -static unsigned int pcm_format_to_bits(enum pcm_format format) +unsigned int pcm_format_to_bits(enum pcm_format format) { switch (format) { case PCM_FORMAT_S32_LE: + case PCM_FORMAT_S24_LE: return 32; default: case PCM_FORMAT_S16_LE: @@ -299,7 +301,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); @@ -307,12 +309,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; @@ -322,7 +330,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); @@ -385,14 +393,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 @@ -428,6 +438,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 */ @@ -493,6 +504,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; @@ -535,6 +552,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) { @@ -581,6 +615,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; @@ -718,6 +753,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; @@ -735,11 +781,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); @@ -755,6 +814,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; } @@ -893,7 +953,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; @@ -935,6 +995,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, @@ -954,7 +1015,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, @@ -969,3 +1030,19 @@ 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); +} @@ -57,10 +57,11 @@ 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, - unsigned int bits, unsigned int period_size, + enum pcm_format format, unsigned int period_size, unsigned int period_count); void sigint_handler(int sig) @@ -80,17 +81,26 @@ int main(int argc, char **argv) unsigned int frames; unsigned int period_size = 1024; unsigned int period_count = 4; + 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]\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 */ @@ -137,26 +147,48 @@ int main(int argc, char **argv) header.audio_format = FORMAT_PCM; header.num_channels = channels; header.sample_rate = rate; - header.bits_per_sample = bits; + + 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); + 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, header.bits_per_sample, + header.sample_rate, format, period_size, period_count); - printf("Captured %d frames\n", frames); + if (prinfo) { + 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); + 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); @@ -165,7 +197,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, - unsigned int bits, unsigned int period_size, + enum pcm_format format, unsigned int period_size, unsigned int period_count) { struct pcm_config config; @@ -179,10 +211,7 @@ unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, 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.format = format; config.start_threshold = 0; config.stop_threshold = 0; config.silence_threshold = 0; @@ -203,7 +232,10 @@ unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, return 0; } - printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate, bits); + 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 (fwrite(buffer, 1, size, file) != size) { @@ -215,6 +247,6 @@ unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, free(buffer); pcm_close(pcm); - return bytes_read / ((bits / 8) * channels); + return pcm_bytes_to_frames(pcm, bytes_read); } @@ -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, @@ -61,14 +64,17 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - if (argc == 1) + + if (argc == 1) { + printf("Mixer name: '%s'\n", mixer_get_name(mixer)); tinymix_list_controls(mixer); - else if (argc == 2) + } else if (argc == 2) { tinymix_detail_control(mixer, argv[1], 1); - else if (argc >= 3) + } else if (argc >= 3) { tinymix_set_value(mixer, argv[1], &argv[2], argc - 2); - else + } else { printf("Usage: tinymix [-D card] [control id] [value to set]\n"); + } mixer_close(mixer); @@ -124,6 +130,9 @@ 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; + size_t len; if (isdigit(control[0])) ctl = mixer_get_ctl(mixer, atoi(control)); @@ -138,6 +147,23 @@ 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) { + + buf = calloc(1, num_values); + if (buf == NULL) { + fprintf(stderr, "Failed to alloc mem for bytes %d\n", num_values); + return; + } + + len = num_values; + ret = mixer_ctl_get_array(ctl, buf, len); + 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)); @@ -153,8 +179,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"); @@ -169,9 +195,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 %d\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) { @@ -193,7 +285,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]); diff --git a/tinypcminfo.c b/tinypcminfo.c index 3282186..99eec34 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; @@ -63,6 +129,7 @@ int main(int argc, char **argv) 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); |