diff options
-rw-r--r-- | include/tinyalsa/asoundlib.h | 22 | ||||
-rw-r--r-- | mixer.c | 78 | ||||
-rw-r--r-- | pcm.c | 29 | ||||
-rw-r--r-- | tinycap.c | 37 | ||||
-rw-r--r-- | tinymix.c | 78 |
5 files changed, 206 insertions, 38 deletions
diff --git a/include/tinyalsa/asoundlib.h b/include/tinyalsa/asoundlib.h index 42f5348..01a6303 100644 --- a/include/tinyalsa/asoundlib.h +++ b/include/tinyalsa/asoundlib.h @@ -142,6 +142,13 @@ unsigned int pcm_params_get_max(struct pcm_params *pcm_params, /* 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); @@ -171,6 +178,8 @@ 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); @@ -190,6 +199,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); @@ -204,14 +216,20 @@ 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_bytes(struct mixer_ctl *ctl, void *data, size_t len); +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_bytes(struct mixer_ctl *ctl, const void *data, size_t len); +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 */ @@ -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) @@ -317,13 +331,14 @@ int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id) return 0; } -int mixer_ctl_get_bytes(struct mixer_ctl *ctl, void *data, size_t len) +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; + void *source; - if (!ctl || (len > ctl->info->count) || !len || !data || - (ctl->info->type != SNDRV_CTL_ELEM_TYPE_BYTES)) + if (!ctl || (count > ctl->info->count) || !count || !array) return -EINVAL; memset(&ev, 0, sizeof(ev)); @@ -333,7 +348,23 @@ int mixer_ctl_get_bytes(struct mixer_ctl *ctl, void *data, size_t len) if (ret < 0) return ret; - memcpy(data, ev.value.bytes.data, len); + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + 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; + + default: + return -EINVAL; + } + + memcpy(array, source, size * count); return 0; } @@ -372,18 +403,35 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value) return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); } -int mixer_ctl_set_bytes(struct mixer_ctl *ctl, const void *data, size_t len) +int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) { struct snd_ctl_elem_value ev; + size_t size; + void *dest; - if (!ctl || (len > ctl->info->count) || !len || !data || - (ctl->info->type != SNDRV_CTL_ELEM_TYPE_BYTES)) + if (!ctl || (count > ctl->info->count) || !count || !array) return -EINVAL; memset(&ev, 0, sizeof(ev)); ev.id.numid = ctl->info->id.numid; - memcpy(ev.value.bytes.data, data, len); + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + size = sizeof(ev.value.integer.value[0]); + dest = ev.value.integer.value; + break; + + case SNDRV_CTL_ELEM_TYPE_BYTES: + size = sizeof(ev.value.bytes.data[0]); + dest = ev.value.bytes.data; + break; + + default: + return -EINVAL; + } + + memcpy(dest, array, size * count); return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); } @@ -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: @@ -385,14 +387,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 +432,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 */ @@ -581,6 +586,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; @@ -735,11 +741,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 +774,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; } @@ -935,6 +955,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, @@ -60,7 +60,7 @@ int capturing = 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,6 +80,7 @@ int main(int argc, char **argv) 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] " @@ -137,7 +138,23 @@ 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; @@ -148,7 +165,7 @@ int main(int argc, char **argv) /* 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); @@ -165,7 +182,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; @@ -178,10 +195,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; @@ -193,7 +207,7 @@ unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, return 0; } - size = pcm_get_buffer_size(pcm); + 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); @@ -202,7 +216,8 @@ 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); + 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) { @@ -214,6 +229,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,6 +27,7 @@ */ #include <tinyalsa/asoundlib.h> +#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <ctype.h> @@ -61,14 +62,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 +128,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[512] = { 0 }; + size_t len; if (isdigit(control[0])) ctl = mixer_get_ctl(mixer, atoi(control)); @@ -138,6 +145,19 @@ 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) { + len = num_values; + if (len > sizeof(buf)) { + fprintf(stderr, "Truncating get to %zu bytes\n", sizeof(buf)); + len = sizeof(buf); + } + ret = mixer_ctl_get_array(ctl, buf, len); + if (ret < 0) { + fprintf(stderr, "Failed to mixer_ctl_get_array\n"); + return; + } + } + if (print_all) printf("%s:", mixer_ctl_get_name(ctl)); @@ -153,8 +173,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,6 +192,47 @@ static void tinymix_detail_control(struct mixer *mixer, const char *control, printf("\n"); } +static void tinymix_set_byte_ctl(struct mixer_ctl *ctl, const char *control, + char **values, unsigned int num_values) +{ + int ret; + char buf[512] = { 0 }; + char *end; + int i; + long n; + + if (num_values > sizeof(buf)) { + fprintf(stderr, "Truncating set to %zu bytes\n", sizeof(buf)); + num_values = sizeof(buf); + } + + 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]); + exit(EXIT_FAILURE); + } + if (errno) { + fprintf(stderr, "strtol: %s: %s\n", values[i], + strerror(errno)); + exit(EXIT_FAILURE); + } + if (n < 0 || n > 0xff) { + fprintf(stderr, "%s should be between [0, 0xff]\n", + values[i]); + exit(EXIT_FAILURE); + } + buf[i] = n; + } + + ret = mixer_ctl_set_array(ctl, buf, num_values); + if (ret < 0) { + fprintf(stderr, "Failed to set binary control\n"); + exit(EXIT_FAILURE); + } +} + static void tinymix_set_value(struct mixer *mixer, const char *control, char **values, unsigned int num_values) { @@ -193,6 +254,11 @@ 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 (type == MIXER_CTL_TYPE_BYTE) { + tinymix_set_byte_ctl(ctl, control, values, num_values); + return; + } + if (isdigit(values[0][0])) { if (num_values == 1) { /* Set all values the same */ |