[alsa-devel] [RFCv3][PATCH 00/39] axfer: rewrite aplay, adding 'timer-based scheduling' option
Hi,
This 3rd RFC patchset updates my previous one: [alsa-devel] [RFCv2][PATCH 00/38] alsa-utils: axfer: rewrite aplay http://mailman.alsa-project.org/pipermail/alsa-devel/2017-September/125574.h...
For aim of this rewrite and its intension, please refer to my former posts.
In this version, I add an option to support 'timer-based scheduling' scenario, which PulseAudio developers introduced. Please refer to patch 32: * axfer: add support for timer-based MMAP operation
You can use this mode by adding '-sched-type=timer' option into command line.
The other difference from my previous one: - fix some bugs to handle non-interleaved buffer on mapped page frame. - use snd_pcm_status() instead of snd_pcm_state() to execute hwsync. - minor fixes.
In my plan, this is the last version including below patches. In next post, options added by below patches are categorized as obsoleted: * axfer: add an option to support volume unit meter * axfer: add a unit test for vumeter calculation * axfer: add an option for formatted filename * axfer: add an option to handle key events * axfer: add a parser for channel map API * axfer: add a feature to generate a file for process id
Additionally, for next post, I'll prepare for help messages and man for this program.
Regards
Takashi Sakamoto (39): axfer: add an entry point for this command axfer: add a sub-command to print list of PCMs/devices axfer: add a common interface to handle a file with audio-specific data format axfer: add support for a container of Microsoft/IBM RIFF/Wave format axfer: add support for a container of Sparc AU format axfer: add support for a container of Creative Tech. voice format axfer: add support for a container of raw data axfer: add unit test for container interface axfer: add a common interface to align data frames on different layout axfer: add support for a mapper for single target axfer: add support for a mapper for multiple target axfer: add a unit test for mapper interface axfer: add a parser for command-line options axfer: add a common interface to transfer data frames axfer: add support to transfer data frames by alsa-lib PCM APIs axfer: add support for blocking data transmission operation of alsa-lib PCM API axfer: add a sub-command to transfer data frames axfer: add informative output and an option to suppress it axfer: add an option to dump available hardware parameters axfer: add options related to duration and obsolete '--max-file-size' option axfer: add an option to finish transmission at XRUN axfer: add a common interface of waiter for I/O event notification axfer: add support of waiter for poll(2) axfer: add support for non-blocking operation axfer: add support for MMAP PCM operation axfer: add an option to suppress event waiting axfer: add options for buffer arrangement axfer: add options for software parameters of PCM substream axfer: add options for plugins in alsa-lib axfer: add an implementation of waiter for epoll(7) axfer: add an option for waiter type axfer: add support for timer-based MMAP operation axfer: add an option to support volume unit meter axfer: add a unit test for vumeter calculation axfer: add an option for formatted filename axfer: add an option to handle key events axfer: add a parser for channel map API axfer: add a feature to generate a file for process id axfer: obsolete some test options
Makefile.am | 2 +- axfer/Makefile.am | 61 +++ axfer/container-au.c | 207 ++++++++ axfer/container-raw.c | 66 +++ axfer/container-riff-wave.c | 581 ++++++++++++++++++++++ axfer/container-voc.c | 843 ++++++++++++++++++++++++++++++++ axfer/container.c | 470 ++++++++++++++++++ axfer/container.h | 126 +++++ axfer/key-event.c | 121 +++++ axfer/key-event.h | 19 + axfer/main.c | 194 ++++++++ axfer/mapper-multiple.c | 271 +++++++++++ axfer/mapper-single.c | 195 ++++++++ axfer/mapper.c | 152 ++++++ axfer/mapper.h | 87 ++++ axfer/misc.h | 16 + axfer/options.c | 980 ++++++++++++++++++++++++++++++++++++++ axfer/options.h | 92 ++++ axfer/subcmd-list.c | 234 +++++++++ axfer/subcmd-transfer.c | 525 ++++++++++++++++++++ axfer/subcmd.h | 18 + axfer/test/Makefile.am | 39 ++ axfer/test/container-test.c | 299 ++++++++++++ axfer/test/generator.c | 260 ++++++++++ axfer/test/generator.h | 47 ++ axfer/test/mapper-test.c | 491 +++++++++++++++++++ axfer/test/vumeter-test.c | 184 +++++++ axfer/vumeter.c | 546 +++++++++++++++++++++ axfer/vumeter.h | 55 +++ axfer/waiter-epoll.c | 81 ++++ axfer/waiter-poll.c | 68 +++ axfer/waiter.c | 84 ++++ axfer/waiter.h | 52 ++ axfer/xfer-libasound-irq-mmap.c | 271 +++++++++++ axfer/xfer-libasound-irq-rw.c | 520 ++++++++++++++++++++ axfer/xfer-libasound-timer-mmap.c | 474 ++++++++++++++++++ axfer/xfer-libasound.c | 737 ++++++++++++++++++++++++++++ axfer/xfer-libasound.h | 60 +++ axfer/xfer.c | 191 ++++++++ axfer/xfer.h | 76 +++ configure.ac | 3 +- 41 files changed, 9796 insertions(+), 2 deletions(-) create mode 100644 axfer/Makefile.am create mode 100644 axfer/container-au.c create mode 100644 axfer/container-raw.c create mode 100644 axfer/container-riff-wave.c create mode 100644 axfer/container-voc.c create mode 100644 axfer/container.c create mode 100644 axfer/container.h create mode 100644 axfer/key-event.c create mode 100644 axfer/key-event.h create mode 100644 axfer/main.c create mode 100644 axfer/mapper-multiple.c create mode 100644 axfer/mapper-single.c create mode 100644 axfer/mapper.c create mode 100644 axfer/mapper.h create mode 100644 axfer/misc.h create mode 100644 axfer/options.c create mode 100644 axfer/options.h create mode 100644 axfer/subcmd-list.c create mode 100644 axfer/subcmd-transfer.c create mode 100644 axfer/subcmd.h create mode 100644 axfer/test/Makefile.am create mode 100644 axfer/test/container-test.c create mode 100644 axfer/test/generator.c create mode 100644 axfer/test/generator.h create mode 100644 axfer/test/mapper-test.c create mode 100644 axfer/test/vumeter-test.c create mode 100644 axfer/vumeter.c create mode 100644 axfer/vumeter.h create mode 100644 axfer/waiter-epoll.c create mode 100644 axfer/waiter-poll.c create mode 100644 axfer/waiter.c create mode 100644 axfer/waiter.h create mode 100644 axfer/xfer-libasound-irq-mmap.c create mode 100644 axfer/xfer-libasound-irq-rw.c create mode 100644 axfer/xfer-libasound-timer-mmap.c create mode 100644 axfer/xfer-libasound.c create mode 100644 axfer/xfer-libasound.h create mode 100644 axfer/xfer.c create mode 100644 axfer/xfer.h
This commit adds a new command, 'axfer' ('ALSA transfer'), to transfer data frames described in asound.h. This command is intended to replace current aplay. The most of features and command line parameters come from aplay as much as possible, while it has more better feature and code to maintain.
This commit adds an entry point for this command. Current option system of aplay is still available, while this command has a sub-command system like commands in iproute2.
Currently, two sub-commands are supported; 'list' and 'transfer'. The 'list' sub-command has the same effect as '-l' and '-L' options of aplay. The 'transfer' sub-command has the same effect as the main feature of aplay. For the sub-command system, an option for stream direction is required; '-P' for playback and '-C' for capture. If you create symbolic links to this binary for aplay/arecord, please execute: $ ln -s axfer aplay $ ln -s axfer arecord
This commit adds an entry point of the command. For backward compatibility, this includes a parser of command line parameters. Actual code for each sub-command will be implemented in later commits.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- Makefile.am | 2 +- axfer/Makefile.am | 23 +++++++ axfer/main.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/misc.h | 16 +++++ axfer/subcmd.h | 14 ++++ configure.ac | 2 +- 6 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 axfer/Makefile.am create mode 100644 axfer/main.c create mode 100644 axfer/misc.h create mode 100644 axfer/subcmd.h
diff --git a/Makefile.am b/Makefile.am index 3d24b87c..a4e25ae7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,7 +14,7 @@ if ALSACONF SUBDIRS += alsaconf endif if HAVE_PCM -SUBDIRS += aplay iecset speaker-test +SUBDIRS += aplay iecset speaker-test axfer if ALSALOOP SUBDIRS += alsaloop endif diff --git a/axfer/Makefile.am b/axfer/Makefile.am new file mode 100644 index 00000000..b48b9c57 --- /dev/null +++ b/axfer/Makefile.am @@ -0,0 +1,23 @@ +bin_PROGRAMS = \ + axfer + +# To include headers for gettext and version. +AM_CPPFLAGS = \ + -I$(top_srcdir)/include + +# Unit tests. +SUBDIRS = + +LIBRT = @LIBRT@ +LDADD = \ + $(LIBINTL) \ + $(LIBRT) + +noinst_HEADERS = \ + misc.h \ + subcmd.h + +axfer_SOURCES = \ + misc.h \ + subcmd.h \ + main.c diff --git a/axfer/main.c b/axfer/main.c new file mode 100644 index 00000000..6ea10842 --- /dev/null +++ b/axfer/main.c @@ -0,0 +1,194 @@ +/* + * main.c - an entry point for this program. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Originally written as 'aplay', by Michael Beck and Jaroslav Kysela. + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "subcmd.h" +#include "misc.h" + +#include "version.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> + +enum subcmds { + SUBCMD_TRANSFER = 0, + SUBCMD_LIST, + SUBCMD_HELP, + SUBCMD_VERSION, +}; + +static void print_version(const char *const cmdname) +{ + printf("%s: version %s\n", cmdname, SND_UTIL_VERSION_STR); +} + +static void print_help(void) +{ + printf("help\n"); +} + +static void decide_subcmd(int argc, char *const *argv, enum subcmds *subcmd) +{ + static const char *const subcmds[] = { + [SUBCMD_TRANSFER] = "transfer", + [SUBCMD_LIST] = "list", + [SUBCMD_HELP] = "help", + [SUBCMD_VERSION] = "version", + }; + static const struct { + const char *const name; + enum subcmds subcmd; + } long_opts[] = { + {"--list-devices", SUBCMD_LIST}, + {"--list-pcms", SUBCMD_LIST}, + {"--help", SUBCMD_HELP}, + {"--version", SUBCMD_VERSION}, + }; + static const struct { + unsigned char c; + enum subcmds subcmd; + } short_opts[] = { + {'l', SUBCMD_LIST}, + {'L', SUBCMD_LIST}, + {'h', SUBCMD_HELP}, + }; + char *pos; + int i, j; + + if (argc == 1) { + *subcmd = SUBCMD_HELP; + return; + } + + /* sub-command system. */ + for (i = 0; i < ARRAY_SIZE(subcmds); ++i) { + if (!strcmp(argv[1], subcmds[i])) { + *subcmd = i; + return; + } + } + + /* Original command system. For long options. */ + for (i = 0; i < ARRAY_SIZE(long_opts); ++i) { + for (j = 0; j < argc; ++j) { + if (!strcmp(long_opts[i].name, argv[j])) { + *subcmd = long_opts[i].subcmd; + return; + } + } + } + + /* Original command system. For short options. */ + for (i = 1; i < argc; ++i) { + /* Pick up short options only. */ + if (argv[i][0] != '-' || argv[i][0] == '\0' || + argv[i][1] == '-' || argv[i][1] == '\0') + continue; + for (pos = argv[i]; *pos != '\0'; ++pos) { + for (j = 0; j < ARRAY_SIZE(short_opts); ++j) { + if (*pos == short_opts[j].c) { + *subcmd = short_opts[j].subcmd; + return; + } + } + } + } + + *subcmd = SUBCMD_TRANSFER; +} + +static bool decide_direction(int argc, char *const *argv, + snd_pcm_stream_t *direction) +{ + static const struct { + const char *const name; + snd_pcm_stream_t direction; + } long_opts[] = { + {"--capture", SND_PCM_STREAM_CAPTURE}, + {"--playback", SND_PCM_STREAM_PLAYBACK}, + }; + static const struct { + unsigned char c; + snd_pcm_stream_t direction; + } short_opts[] = { + {'C', SND_PCM_STREAM_CAPTURE}, + {'P', SND_PCM_STREAM_PLAYBACK}, + }; + static const char *const directions[] = { + [SND_PCM_STREAM_CAPTURE] = "arecord", + [SND_PCM_STREAM_PLAYBACK] = "aplay", + }; + int i, j; + char *pos; + + /* Original command system. For long options. */ + for (i = 0; i < ARRAY_SIZE(long_opts); ++i) { + for (j = 0; j < argc; ++j) { + if (!strcmp(long_opts[i].name, argv[j])) { + *direction = long_opts[i].direction; + return true; + } + } + } + + /* Original command system. For short options. */ + for (i = 1; i < argc; ++i) { + /* Pick up short options only. */ + if (argv[i][0] != '-' || argv[i][0] == '\0' || + argv[i][1] == '-' || argv[i][1] == '\0') + continue; + for (pos = argv[i]; *pos != '\0'; ++pos) { + for (j = 0; j < ARRAY_SIZE(short_opts); ++j) { + if (*pos == short_opts[j].c) { + *direction = short_opts[j].direction; + return true; + } + } + } + } + + /* If not decided yet, judge according to command name. */ + for (i = 0; i < ARRAY_SIZE(directions); ++i) { + for (pos = argv[0] + strlen(argv[0]); pos != argv[0]; --pos) { + if (strstr(pos, directions[i]) != NULL) { + *direction = i; + return true; + } + } + } + + return false; +} + +int main(int argc, char *const *argv) +{ + snd_pcm_stream_t direction; + enum subcmds subcmd; + int err = 0; + + if (!decide_direction(argc, argv, &direction)) + subcmd = SUBCMD_HELP; + else + decide_subcmd(argc, argv, &subcmd); + + if (subcmd == SUBCMD_TRANSFER) + printf("execute 'transfer' subcmd.\n"); + else if (subcmd == SUBCMD_LIST) + printf("execute 'list' subcmd.\n"); + else if (subcmd == SUBCMD_VERSION) + print_version(argv[0]); + else + print_help(); + if (err < 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/axfer/misc.h b/axfer/misc.h new file mode 100644 index 00000000..824617a2 --- /dev/null +++ b/axfer/misc.h @@ -0,0 +1,16 @@ +/* + * misc.h - a header file for miscellaneous tools. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_MISC__H_ +#define __ALSA_UTILS_AXFER_MISC__H_ + +#include <gettext.h> + +#define ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) + +#endif diff --git a/axfer/subcmd.h b/axfer/subcmd.h new file mode 100644 index 00000000..7b52ddc7 --- /dev/null +++ b/axfer/subcmd.h @@ -0,0 +1,14 @@ +/* + * subcmd.h - a header for each sub-commands. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_SUBCMD__H_ +#define __ALSA_UTILS_AXFER_SUBCMD__H_ + +#include <alsa/asoundlib.h> + +#endif diff --git a/configure.ac b/configure.ac index 0ff4fad1..41fac0cb 100644 --- a/configure.ac +++ b/configure.ac @@ -426,4 +426,4 @@ AC_OUTPUT(Makefile alsactl/Makefile alsactl/init/Makefile \ utils/alsa-utils.spec seq/Makefile seq/aconnect/Makefile \ seq/aplaymidi/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \ speaker-test/Makefile speaker-test/samples/Makefile \ - alsaloop/Makefile alsa-info/Makefile) + alsaloop/Makefile alsa-info/Makefile axfer/Makefile)
Original aplay implementation has a feature to output two types of list; devices and PCMs. The list of devices is a result to query sound card and pcm component structured maintained in kernel land. The list of PCMs is a result to parse runtime configuration files in alsa-lib. Entries in the former list is corresponding to ALSA PCM character device ('/dev/snd/pcm%uC%uD[p|c]'), while entries in the latter list includes some 'virtual' instances in application runtime.
This commit adds an implementation for the above functionality. This is executed by taking 'list' sub-command. A 'device' option has the same effect as '--list-devices' and '-L' of aplay. A 'pcm' option has the same effect as '--list-pcms' and '-l' of aplay. In both cases, an additional option is required for stream direction. Below is examples of new command system for this sub-command.
$ axfer list device -C (= arecord --list-devices) $ axfer list pcm -P (= aplay -l)
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/main.c | 2 +- axfer/subcmd-list.c | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/subcmd.h | 2 + 4 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 axfer/subcmd-list.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index b48b9c57..4e37b92f 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -20,4 +20,5 @@ noinst_HEADERS = \ axfer_SOURCES = \ misc.h \ subcmd.h \ - main.c + main.c \ + subcmd-list.c diff --git a/axfer/main.c b/axfer/main.c index 6ea10842..9bf02fd9 100644 --- a/axfer/main.c +++ b/axfer/main.c @@ -182,7 +182,7 @@ int main(int argc, char *const *argv) if (subcmd == SUBCMD_TRANSFER) printf("execute 'transfer' subcmd.\n"); else if (subcmd == SUBCMD_LIST) - printf("execute 'list' subcmd.\n"); + err = subcmd_list(argc, argv, direction); else if (subcmd == SUBCMD_VERSION) print_version(argv[0]); else diff --git a/axfer/subcmd-list.c b/axfer/subcmd-list.c new file mode 100644 index 00000000..de4bbd76 --- /dev/null +++ b/axfer/subcmd-list.c @@ -0,0 +1,234 @@ +/* + * subcmd-list.c - operations for list sub command. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "subcmd.h" +#include "misc.h" + +#include <getopt.h> + +static int dump_device(snd_ctl_t *handle, const char *id, const char *name, + snd_pcm_stream_t direction, snd_pcm_info_t *info) +{ + unsigned int count; + int i; + int err; + + printf(_("card %i: %s [%s], device %i: %s [%s]\n"), + snd_pcm_info_get_card(info), id, name, + snd_pcm_info_get_device(info), snd_pcm_info_get_id(info), + snd_pcm_info_get_name(info)); + + count = snd_pcm_info_get_subdevices_count(info); + printf(_(" Subdevices: %i/%i\n"), + snd_pcm_info_get_subdevices_avail(info), count); + + for (i = 0; i < count; ++i) { + snd_pcm_info_set_subdevice(info, i); + + err = snd_ctl_pcm_info(handle, info); + if (err < 0) { + printf("control digital audio playback info (%i): %s", + snd_pcm_info_get_card(info), snd_strerror(err)); + continue; + } + + printf(_(" Subdevice #%i: %s\n"), + i, snd_pcm_info_get_subdevice_name(info)); + } + + return 0; +} + +static int dump_devices(snd_ctl_t *handle, const char *id, const char *name, + snd_pcm_stream_t direction) +{ + snd_pcm_info_t *info; + int device = -1; + int err; + + err = snd_pcm_info_malloc(&info); + if (err < 0) + return err; + + while (1) { + err = snd_ctl_pcm_next_device(handle, &device); + if (err < 0) + break; + if (device < 0) + break; + + snd_pcm_info_set_device(info, device); + snd_pcm_info_set_subdevice(info, 0); + snd_pcm_info_set_stream(info, direction); + err = snd_ctl_pcm_info(handle, info); + if (err < 0) + continue; + + err = dump_device(handle, id, name, direction, info); + if (err < 0) + break; + } + + free(info); + return err; +} + +static int list_devices(snd_pcm_stream_t direction) +{ + int card = -1; + char name[32]; + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + int err; + + err = snd_ctl_card_info_malloc(&info); + if (err < 0) + return err; + + /* Not found. */ + if (snd_card_next(&card) < 0 || card < 0) + goto end; + + printf(_("**** List of %s Hardware Devices ****\n"), + snd_pcm_stream_name(direction)); + + while (card >= 0) { + sprintf(name, "hw:%d", card); + err = snd_ctl_open(&handle, name, 0); + if (err < 0) { + printf("control open (%i): %s", + card, snd_strerror(err)); + } else { + err = snd_ctl_card_info(handle, info); + if (err < 0) { + printf("control hardware info (%i): %s", + card, snd_strerror(err)); + } else { + err = dump_devices(handle, + snd_ctl_card_info_get_id(info), + snd_ctl_card_info_get_name(info), + direction); + } + snd_ctl_close(handle); + } + + if (err < 0) + break; + + /* Go to next. */ + if (snd_card_next(&card) < 0) { + printf("snd_card_next"); + break; + } + } +end: + free(info); + return err; +} + +static int list_pcms(snd_pcm_stream_t direction) +{ + static const char *const filters[] = { + [SND_PCM_STREAM_CAPTURE] = "Input", + [SND_PCM_STREAM_PLAYBACK] = "Output", + }; + const char *filter; + void **hints; + void **n; + char *io; + char *name; + char *desc; + + if (snd_device_name_hint(-1, "pcm", &hints) < 0) + return -EINVAL; + + filter = filters[direction]; + + n = hints; + for (n = hints; *n != NULL; ++n) { + io = snd_device_name_get_hint(*n, "IOID"); + if (io != NULL && strcmp(io, filter) != 0) { + free(io); + continue; + } + + name = snd_device_name_get_hint(*n, "NAME"); + desc = snd_device_name_get_hint(*n, "DESC"); + + printf("%s\n", name); + if (desc == NULL) { + free(name); + free(desc); + continue; + } + + + printf(" "); + while (*desc) { + if (*desc == '\n') + printf("\n "); + else + putchar(*desc); + desc++; + } + putchar('\n'); + } + + snd_device_name_free_hint(hints); + + return 0; +} + +static void print_help(void) +{ + printf("help for list sub-command.\n"); +} + +int subcmd_list(int argc, char *const *argv, snd_pcm_stream_t direction) +{ + static const struct { + const char *const category; + int (*func)(snd_pcm_stream_t direction); + } ops[] = { + {"device", list_devices}, + {"pcm", list_pcms}, + }; + int i; + static const char *s_opts = "hlL"; + static const struct option l_opts[] = { + {"list-devices", 0, NULL, 'l'}, + {"list-pcms", 0, NULL, 'L'}, + {NULL, 0, NULL, 0} + }; + int c; + + /* Renewed command system. */ + if (argc > 2 && !strcmp(argv[1], "list")) { + for (i = 0; i < ARRAY_SIZE(ops); ++i) { + if (!strcmp(ops[i].category, argv[2])) + return ops[i].func(direction); + } + } + + /* Original command system. */ + optind = 0; + opterr = 0; + while (1) { + c = getopt_long(argc, argv, s_opts, l_opts, NULL); + if (c < 0) + break; + if (c == 'l') + return list_devices(direction); + if (c == 'L') + return list_pcms(direction); + } + + print_help(); + + return 1; +} diff --git a/axfer/subcmd.h b/axfer/subcmd.h index 7b52ddc7..940d6814 100644 --- a/axfer/subcmd.h +++ b/axfer/subcmd.h @@ -11,4 +11,6 @@
#include <alsa/asoundlib.h>
+int subcmd_list(int argc, char *const *argv, snd_pcm_stream_t direction); + #endif
Current aplay supports several types of data format for file; Microsoft/IBM RIFF/Wave (.wav), Sparc AU (.au) and Creative Tech. voice (.voc). These formats were designed to handle audio-related data with interleaved frame alignment.
This commit adds a common interface to handle the file format, named as 'container' module. This includes several functions to build/parse the format data from any file descriptors. Furthermore, this includes several helper functions for implementations of each builder/parser.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 7 +- axfer/container.c | 434 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/container.h | 110 ++++++++++++++ 3 files changed, 549 insertions(+), 2 deletions(-) create mode 100644 axfer/container.c create mode 100644 axfer/container.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 4e37b92f..3913d0c6 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -15,10 +15,13 @@ LDADD = \
noinst_HEADERS = \ misc.h \ - subcmd.h + subcmd.h \ + container.h
axfer_SOURCES = \ misc.h \ subcmd.h \ main.c \ - subcmd-list.c + subcmd-list.c \ + container.h \ + container.c diff --git a/axfer/container.c b/axfer/container.c new file mode 100644 index 00000000..5cb879e1 --- /dev/null +++ b/axfer/container.c @@ -0,0 +1,434 @@ +/* + * container.c - an interface of parser/builder for formatted files. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" +#include "misc.h" + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +static const char *const cntr_type_labels[] = { + [CONTAINER_TYPE_PARSER] = "parser", + [CONTAINER_TYPE_BUILDER] = "builder", +}; + +static const char *const cntr_format_labels[] = { + [CONTAINER_FORMAT_COUNT] = "", +}; + +static const char *const suffixes[] = { + [CONTAINER_FORMAT_COUNT] = "", +}; + +const char *const container_suffix_from_format(enum container_format format) +{ + return suffixes[format]; +} + +int container_recursive_read(struct container_context *cntr, void *buf, + unsigned int byte_count) +{ + char *dst = buf; + ssize_t result; + size_t consumed = 0; + + while (consumed < byte_count && !cntr->interrupted) { + result = read(cntr->fd, dst + consumed, byte_count - consumed); + if (result < 0) { + /* + * This descriptor was configured with non-blocking + * mode. EINTR is not cought when get any interrupts. + */ + if (cntr->interrupted) + return -EINTR; + if (errno == EAGAIN) + continue; + return -errno; + } + /* Reach EOF. */ + if (result == 0) { + cntr->eof = true; + return 0; + } + + consumed += result; + } + + return 0; +} + +int container_recursive_write(struct container_context *cntr, void *buf, + unsigned int byte_count) +{ + char *src = buf; + ssize_t result; + size_t consumed = 0; + + while (consumed < byte_count && !cntr->interrupted) { + result = write(cntr->fd, src + consumed, byte_count - consumed); + if (result < 0) { + /* + * This descriptor was configured with non-blocking + * mode. EINTR is not cought when get any interrupts. + */ + if (cntr->interrupted) + return -EINTR; + if (errno == EAGAIN) + continue; + return -errno; + } + + consumed += result; + } + + return 0; +} + +enum container_format container_format_from_path(const char *path) +{ + const char *suffix; + const char *pos; + int i; + + for (i = 0; i < ARRAY_SIZE(suffixes); ++i) { + suffix = suffixes[i]; + + /* Check last part of the string. */ + pos = path + strlen(path) - strlen(suffix); + if (!strcmp(pos, suffix)) + return i; + } + + /* Unsupported. */ + return CONTAINER_FORMAT_COUNT; +} + +int container_seek_offset(struct container_context *cntr, off64_t offset) +{ + off64_t pos; + + pos = lseek64(cntr->fd, offset, SEEK_SET); + if (pos < 0) + return -errno; + if (pos != offset) + return -EIO; + + return 0; +} + +/* + * To avoid blocking execution at system call iteration after receiving UNIX + * signals. + */ +static int set_nonblock_flag(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) + return -errno; + + return 0; +} + +int container_parser_init(struct container_context *cntr, + const char *const path, unsigned int verbose) +{ + const struct container_parser *parsers[] = { + NULL, + }; + const struct container_parser *parser; + unsigned int size; + int i; + int err; + + assert(cntr); + assert(path); + assert(path[0] != '\0'); + + /* Detect forgotten to destruct. */ + assert(cntr->fd == 0); + assert(cntr->private_data == NULL); + + memset(cntr, 0, sizeof(*cntr)); + + /* Open a target descriptor. */ + if (!strcmp(path, "-")) { + cntr->fd = fileno(stdin); + err = set_nonblock_flag(cntr->fd); + if (err < 0) + return err; + cntr->stdio = true; + } else { + cntr->fd = open(path, O_RDONLY | O_NONBLOCK); + if (cntr->fd < 0) + return -errno; + } + + /* 4 bytes are enough to detect supported containers. */ + err = container_recursive_read(cntr, cntr->magic, sizeof(cntr->magic)); + if (err < 0) + return err; + for (i = 0; i < ARRAY_SIZE(parsers); ++i) { + parser = parsers[i]; + size = strlen(parser->magic); + if (size > 4) + size = 4; + if (!strncmp(cntr->magic, parser->magic, size)) + break; + } + /* + * Don't forget that the first 4 bytes were already read for magic + * bytes. + */ + cntr->magic_handled = false; + + /* + * Unless detected, use raw container. + */ + if (i == ARRAY_SIZE(parsers)) + return -EINVAL; + + /* Allocate private data for the parser. */ + if (parser->private_size > 0) { + cntr->private_data = malloc(parser->private_size); + if (cntr->private_data == NULL) + return -ENOMEM; + memset(cntr->private_data, 0, parser->private_size); + } + + cntr->type = CONTAINER_TYPE_PARSER; + cntr->process_bytes = container_recursive_read; + cntr->format = parser->format; + cntr->ops = &parser->ops; + cntr->max_size = parser->max_size; + cntr->verbose = verbose; + + return 0; +} + +int container_builder_init(struct container_context *cntr, + const char *const path, enum container_format format, + unsigned int verbose) +{ + const struct container_builder *builders[] = { + NULL, + }; + const struct container_builder *builder; + int err; + + assert(cntr); + assert(path); + assert(path[0] != '\0'); + + /* Detect forgotten to destruct. */ + assert(cntr->fd == 0); + assert(cntr->private_data == NULL); + + memset(cntr, 0, sizeof(*cntr)); + + /* Open a target descriptor. */ + if (path == NULL || path == '\0') + return -EINVAL; + if (!strcmp(path, "-")) { + cntr->fd = fileno(stdout); + err = set_nonblock_flag(cntr->fd); + if (err < 0) + return err; + cntr->stdio = true; + } else { + cntr->fd = open(path, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC, + 0644); + if (cntr->fd < 0) + return -errno; + } + + builder = builders[format]; + + /* Allocate private data for the builder. */ + if (builder->private_size > 0) { + cntr->private_data = malloc(builder->private_size); + if (cntr->private_data == NULL) + return -ENOMEM; + memset(cntr->private_data, 0, builder->private_size); + } + + cntr->type = CONTAINER_TYPE_BUILDER; + cntr->process_bytes = container_recursive_write; + cntr->format = builder->format; + cntr->ops = &builder->ops; + cntr->max_size = builder->max_size; + cntr->verbose = verbose; + + return 0; +} + +int container_context_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + uint64_t byte_count; + unsigned int bytes_per_frame; + int err; + + assert(cntr); + assert(format); + assert(samples_per_frame); + assert(frames_per_second); + assert(frame_count); + + if (cntr->type == CONTAINER_TYPE_BUILDER) + byte_count = cntr->max_size; + + if (cntr->ops->pre_process) { + err = cntr->ops->pre_process(cntr, format, samples_per_frame, + frames_per_second, &byte_count); + if (err < 0) + return err; + if (cntr->eof) + return 0; + } + + assert(*format >= SND_PCM_FORMAT_S8); + assert(*format <= SND_PCM_FORMAT_LAST); + assert(*samples_per_frame > 0); + assert(*frames_per_second > 0); + assert(byte_count > 0); + + cntr->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + cntr->samples_per_frame = *samples_per_frame; + cntr->frames_per_second = *frames_per_second; + + bytes_per_frame = cntr->bytes_per_sample * *samples_per_frame; + *frame_count = byte_count / bytes_per_frame; + cntr->max_size -= cntr->max_size / bytes_per_frame; + + if (cntr->verbose > 0) { + fprintf(stderr, "Container: %s\n", + cntr_type_labels[cntr->type]); + fprintf(stderr, " format: %s\n", + cntr_format_labels[cntr->format]); + fprintf(stderr, " sample format: %s\n", + snd_pcm_format_name(*format)); + fprintf(stderr, " bytes/sample: %u\n", + cntr->bytes_per_sample); + fprintf(stderr, " samples/frame: %u\n", + cntr->samples_per_frame); + fprintf(stderr, " frames/second: %u\n", + cntr->frames_per_second); + if (cntr->type == CONTAINER_TYPE_PARSER) { + fprintf(stderr, " frames: %lu\n", + *frame_count); + } else { + fprintf(stderr, " max frames: %lu\n", + *frame_count); + } + } + + return 0; +} + +int container_context_process_frames(struct container_context *cntr, + void *frame_buffer, + unsigned int *frame_count) +{ + char *buf = frame_buffer; + unsigned int bytes_per_frame; + unsigned int byte_count; + int err; + + assert(cntr); + assert(!cntr->eof); + assert(frame_buffer); + assert(frame_count); + + bytes_per_frame = cntr->bytes_per_sample * cntr->samples_per_frame; + byte_count = *frame_count * bytes_per_frame; + + /* Each container has limitation for its volume for sample data. */ + if (cntr->handled_byte_count > cntr->max_size - byte_count) + byte_count = cntr->max_size - cntr->handled_byte_count; + + /* All of supported containers include interleaved PCM frames. */ + /* TODO: process frames for truncate case. */ + err = cntr->process_bytes(cntr, buf, byte_count); + if (err < 0) { + *frame_count = 0; + return err; + } + + cntr->handled_byte_count += byte_count; + if (cntr->handled_byte_count == cntr->max_size) + cntr->eof = true; + + *frame_count = byte_count / bytes_per_frame; + + return 0; +} + +int container_context_post_process(struct container_context *cntr, + uint64_t *frame_count) +{ + int err = 0; + + assert(cntr); + assert(frame_count); + + if (cntr->verbose && cntr->handled_byte_count > 0) { + fprintf(stderr, " Handled bytes: %lu\n", + cntr->handled_byte_count); + } + + /* NOTE* we cannot seek when using standard input/output. */ + if (!cntr->stdio && cntr->ops && cntr->ops->post_process) { + /* + * Usually, need to write out processed bytes in container + * header even it this program is interrupted. + */ + cntr->interrupted = false; + + err = cntr->ops->post_process(cntr, cntr->handled_byte_count); + } + + /* Ensure to perform write-back from disk cache. */ + if (cntr->type == CONTAINER_TYPE_BUILDER) + fsync(cntr->fd); + + if (err < 0) + return err; + + if (cntr->bytes_per_sample == 0 || cntr->samples_per_frame == 0) { + *frame_count = 0; + } else { + *frame_count = cntr->handled_byte_count / + cntr->bytes_per_sample / + cntr->samples_per_frame; + } + + return 0; +} + +void container_context_destroy(struct container_context *cntr) +{ + assert(cntr); + + close(cntr->fd); + if (cntr->private_data) + free(cntr->private_data); + + cntr->fd = 0; + cntr->private_data = NULL; +} diff --git a/axfer/container.h b/axfer/container.h new file mode 100644 index 00000000..7b1f80c6 --- /dev/null +++ b/axfer/container.h @@ -0,0 +1,110 @@ +/* + * container.h - an interface of parser/builder for formatted files. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_CONTAINER__H_ +#define __ALSA_UTILS_AXFER_CONTAINER__H_ + +#define _LARGEFILE64_SOURCE +#include <sys/types.h> +#include <unistd.h> + +#include <stdbool.h> +#include <stdint.h> + +#include <alsa/asoundlib.h> + +enum container_type { + CONTAINER_TYPE_PARSER = 0, + CONTAINER_TYPE_BUILDER, + CONTAINER_TYPE_COUNT, +}; + +enum container_format { + CONTAINER_FORMAT_COUNT, +}; + +struct container_ops; + +struct container_context { + enum container_type type; + int fd; + int (*process_bytes)(struct container_context *cntr, + void *buffer, unsigned int byte_count); + bool magic_handled; + bool eof; + bool interrupted; + bool stdio; + + enum container_format format; + uint64_t max_size; + char magic[4]; + const struct container_ops *ops; + void *private_data; + + /* Available after pre-process. */ + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_second; + + unsigned int verbose; + uint64_t handled_byte_count; +}; + +const char *const container_suffix_from_format(enum container_format format); +enum container_format container_format_from_path(const char *path); +int container_parser_init(struct container_context *cntr, + const char *const path, unsigned int verbose); +int container_builder_init(struct container_context *cntr, + const char *const path, enum container_format format, + unsigned int verbose); +void container_context_destroy(struct container_context *cntr); +int container_context_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count); +int container_context_process_frames(struct container_context *cntr, + void *frame_buffer, + unsigned int *frame_count); +int container_context_post_process(struct container_context *cntr, + uint64_t *frame_count); + +/* For internal use in 'container' module. */ + +struct container_ops { + int (*pre_process)(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *byte_count); + int (*post_process)(struct container_context *cntr, + uint64_t handled_byte_count); +}; +struct container_parser { + enum container_format format; + const char const *magic; + uint64_t max_size; + struct container_ops ops; + unsigned int private_size; +}; + +struct container_builder { + enum container_format format; + const char const *suffix; + uint64_t max_size; + struct container_ops ops; + unsigned int private_size; +}; + +int container_recursive_read(struct container_context *cntr, void *buf, + unsigned int byte_count); +int container_recursive_write(struct container_context *cntr, void *buf, + unsigned int byte_count); +int container_seek_offset(struct container_context *cntr, off64_t offset); + +#endif
This commit adds support for data of Microsoft/IBM RIFF/Wave format. In this data format, values in each of field are encoded in both bit/little byte order but inner a file the same order is used. Magic bytes in the beginning of data indicated which byte order is used for the file.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/container-riff-wave.c | 581 ++++++++++++++++++++++++++++++++++++++++++++ axfer/container.c | 9 +- axfer/container.h | 4 + 4 files changed, 592 insertions(+), 5 deletions(-) create mode 100644 axfer/container-riff-wave.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 3913d0c6..092c9662 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -24,4 +24,5 @@ axfer_SOURCES = \ main.c \ subcmd-list.c \ container.h \ - container.c + container.c \ + container-riff-wave.c diff --git a/axfer/container-riff-wave.c b/axfer/container-riff-wave.c new file mode 100644 index 00000000..9b97fd1a --- /dev/null +++ b/axfer/container-riff-wave.c @@ -0,0 +1,581 @@ +/* + * container-riff-wave.c - a parser/builder for a container of RIFF/Wave File. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" +#include "misc.h" + +/* Not portable to all of UNIX platforms. */ +#include <endian.h> + +/* + * References: + * - 'Resource Interchange File Format (RIFF)' at msdn.microsoft.com + * - 'Multiple channel audio data and WAVE files' at msdn.microsoft.com + * - RFC 2361 'WAVE and AVI Codec Registries' at ietf.org + * - 'mmreg.h' in Wine project + * - 'mmreg.h' in ReactOS project + */ + +#define RIFF_MAGIC "RIF" /* A common part. */ + +#define RIFF_CHUNK_ID_LE "RIFF" +#define RIFF_CHUNK_ID_BE "RIFX" +#define RIFF_FORM_WAVE "WAVE" +#define FMT_SUBCHUNK_ID "fmt " +#define DATA_SUBCHUNK_ID "data" + +/* + * See 'WAVE and AVI Codec Registries (Historic Registry)' in 'iana.org'. + * https://www.iana.org/assignments/wave-avi-codec-registry/ + */ +enum wave_format { + WAVE_FORMAT_PCM = 0x0001, + WAVE_FORMAT_ADPCM = 0x0002, + WAVE_FORMAT_IEEE_FLOAT = 0x0003, + WAVE_FORMAT_ALAW = 0x0006, + WAVE_FORMAT_MULAW = 0x0007, + WAVE_FORMAT_G723_ADPCM = 0x0014, + /* The others are not supported. */ +}; + +struct format_map { + enum wave_format wformat; + snd_pcm_format_t format; +}; + +static const struct format_map format_maps[] = { + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U8}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S16_LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S16_BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S24_LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S24_BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S32_LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S32_BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S24_3LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S24_3BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S20_3LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S20_3BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S18_3LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S18_3BE}, + {WAVE_FORMAT_IEEE_FLOAT, SND_PCM_FORMAT_FLOAT_LE}, + {WAVE_FORMAT_IEEE_FLOAT, SND_PCM_FORMAT_FLOAT_BE}, + {WAVE_FORMAT_IEEE_FLOAT, SND_PCM_FORMAT_FLOAT64_LE}, + {WAVE_FORMAT_IEEE_FLOAT, SND_PCM_FORMAT_FLOAT64_BE}, + {WAVE_FORMAT_ALAW, SND_PCM_FORMAT_A_LAW}, + {WAVE_FORMAT_MULAW, SND_PCM_FORMAT_MU_LAW}, + /* + * Below sample formats are not currently supported, due to width of + * its sample. + * - WAVE_FORMAT_ADPCM + * - WAVE_FORMAT_G723_ADPCM + * - WAVE_FORMAT_G723_ADPCM + * - WAVE_FORMAT_G723_ADPCM + * - WAVE_FORMAT_G723_ADPCM + */ +}; + +struct riff_chunk { + uint8_t id[4]; + uint32_t size; + + uint8_t data[0]; +}; + +struct riff_chunk_data { + uint8_t id[4]; + + uint8_t subchunks[0]; +}; + +struct riff_subchunk { + uint8_t id[4]; + uint32_t size; + + uint8_t data[0]; +}; + +struct wave_fmt_subchunk { + uint8_t id[4]; + uint32_t size; + + uint16_t format; + uint16_t samples_per_frame; + uint32_t frames_per_second; + uint32_t average_bytes_per_second; + uint16_t bytes_per_frame; + uint16_t bits_per_sample; + uint8_t extension[0]; +}; + +struct wave_data_subchunk { + uint8_t id[4]; + uint32_t size; + + uint8_t frames[0]; +}; + +struct parser_state { + bool be; + enum wave_format format; + unsigned int samples_per_frame; + unsigned int frames_per_second; + unsigned int average_bytes_per_second; + unsigned int bytes_per_frame; + unsigned int bytes_per_sample; + unsigned int avail_bits_in_sample; + unsigned int byte_count; +}; + +static int parse_riff_chunk_header(struct parser_state *state, + struct riff_chunk *chunk, + uint64_t *byte_count) +{ + if (!memcmp(chunk->id, RIFF_CHUNK_ID_BE, sizeof(chunk->id))) + state->be = true; + else if (!memcmp(chunk->id, RIFF_CHUNK_ID_LE, sizeof(chunk->id))) + state->be = false; + else + return -EINVAL; + + if (state->be) + *byte_count = be32toh(chunk->size); + else + *byte_count = le32toh(chunk->size); + + return 0; +} + +static int parse_riff_chunk(struct container_context *cntr, + uint64_t *byte_count) +{ + struct parser_state *state = cntr->private_data; + union { + struct riff_chunk chunk; + struct riff_chunk_data chunk_data; + } buf = {0}; + int err; + + /* Chunk header. 4 bytes were alread read to detect container type. */ + memcpy(buf.chunk.id, cntr->magic, sizeof(cntr->magic)); + err = container_recursive_read(cntr, + (char *)&buf.chunk + sizeof(cntr->magic), + sizeof(buf.chunk) - sizeof(cntr->magic)); + if (err < 0) + return err; + if (cntr->eof) + return 0; + + err = parse_riff_chunk_header(state, &buf.chunk, byte_count); + if (err < 0) + return err; + + /* Chunk data header. */ + err = container_recursive_read(cntr, &buf, sizeof(buf.chunk_data)); + if (err < 0) + return err; + if (cntr->eof) + return 0; + + if (memcmp(buf.chunk_data.id, RIFF_FORM_WAVE, + sizeof(buf.chunk_data.id))) + return -EINVAL; + + return 0; +} + +static int parse_wave_fmt_subchunk(struct parser_state *state, + struct wave_fmt_subchunk *subchunk) +{ + if (state->be) { + state->format = be16toh(subchunk->format); + state->samples_per_frame = be16toh(subchunk->samples_per_frame); + state->frames_per_second = be32toh(subchunk->frames_per_second); + state->average_bytes_per_second = + be32toh(subchunk->average_bytes_per_second); + state->bytes_per_frame = be16toh(subchunk->bytes_per_frame); + state->avail_bits_in_sample = + be16toh(subchunk->bits_per_sample); + } else { + state->format = le16toh(subchunk->format); + state->samples_per_frame = le16toh(subchunk->samples_per_frame); + state->frames_per_second = le32toh(subchunk->frames_per_second); + state->average_bytes_per_second = + le32toh(subchunk->average_bytes_per_second); + state->bytes_per_frame = le16toh(subchunk->bytes_per_frame); + state->avail_bits_in_sample = + le16toh(subchunk->bits_per_sample); + } + + if (state->average_bytes_per_second != + state->bytes_per_frame * state->frames_per_second) + return -EINVAL; + + return 0; +} + +static int parse_wave_data_subchunk(struct parser_state *state, + struct wave_data_subchunk *subchunk) +{ + if (state->be) + state->byte_count = be32toh(subchunk->size); + else + state->byte_count = le32toh(subchunk->size); + + return 0; +} + +static int parse_wave_subchunk(struct container_context *cntr) +{ + union { + struct riff_subchunk subchunk; + struct wave_fmt_subchunk fmt_subchunk; + struct wave_data_subchunk data_subchunk; + } buf = {0}; + enum { + SUBCHUNK_TYPE_UNKNOWN = -1, + SUBCHUNK_TYPE_FMT, + SUBCHUNK_TYPE_DATA, + } subchunk_type; + struct parser_state *state = cntr->private_data; + unsigned int required_size; + unsigned int subchunk_data_size; + int err; + + while (1) { + err = container_recursive_read(cntr, &buf, + sizeof(buf.subchunk)); + if (err < 0) + return err; + if (cntr->eof) + return 0; + + /* Calculate the size of subchunk data. */ + if (state->be) + subchunk_data_size = be32toh(buf.subchunk.size); + else + subchunk_data_size = le32toh(buf.subchunk.size); + + /* Detect type of subchunk. */ + if (!memcmp(buf.subchunk.id, FMT_SUBCHUNK_ID, + sizeof(buf.subchunk.id))) { + subchunk_type = SUBCHUNK_TYPE_FMT; + } else if (!memcmp(buf.subchunk.id, DATA_SUBCHUNK_ID, + sizeof(buf.subchunk.id))) { + subchunk_type = SUBCHUNK_TYPE_DATA; + } else { + subchunk_type = SUBCHUNK_TYPE_UNKNOWN; + } + + if (subchunk_type != SUBCHUNK_TYPE_UNKNOWN) { + /* Parse data of this subchunk. */ + if (subchunk_type == SUBCHUNK_TYPE_FMT) { + required_size = + sizeof(struct wave_fmt_subchunk) - + sizeof(struct riff_chunk); + } else { + required_size = + sizeof(struct wave_data_subchunk)- + sizeof(struct riff_chunk); + } + + if (subchunk_data_size < required_size) + return -EINVAL; + + err = container_recursive_read(cntr, &buf.subchunk.data, + required_size); + if (err < 0) + return err; + if (cntr->eof) + return 0; + subchunk_data_size -= required_size; + + if (subchunk_type == SUBCHUNK_TYPE_FMT) { + err = parse_wave_fmt_subchunk(state, + &buf.fmt_subchunk); + } else if (subchunk_type == SUBCHUNK_TYPE_DATA) { + err = parse_wave_data_subchunk(state, + &buf.data_subchunk); + } + if (err < 0) + return err; + + /* Found frame data. */ + if (subchunk_type == SUBCHUNK_TYPE_DATA) + break; + } + + /* Go to next subchunk. */ + while (subchunk_data_size > 0) { + unsigned int consume; + + if (subchunk_data_size > sizeof(buf)) + consume = sizeof(buf); + else + consume = subchunk_data_size; + + err = container_recursive_read(cntr, &buf, consume); + if (err < 0) + return err; + if (cntr->eof) + return 0; + subchunk_data_size -= consume; + } + } + + return 0; +} + +static int parse_riff_wave_format(struct container_context *cntr) +{ + uint64_t byte_count; + int err; + + err = parse_riff_chunk(cntr, &byte_count); + if (err < 0) + return err; + + err = parse_wave_subchunk(cntr); + if (err < 0) + return err; + + return 0; +} + +static int wave_parser_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *byte_count) +{ + struct parser_state *state = cntr->private_data; + int phys_width; + const struct format_map *map; + int i; + int err; + + err = parse_riff_wave_format(cntr); + if (err < 0) + return err; + + phys_width = 8 * state->average_bytes_per_second / + state->samples_per_frame / state->frames_per_second; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + map = &format_maps[i]; + if (state->format != map->wformat) + continue; + if (state->avail_bits_in_sample != + snd_pcm_format_width(map->format)) + continue; + if (phys_width != snd_pcm_format_physical_width(map->format)) + continue; + + if (state->be && snd_pcm_format_big_endian(map->format) != 1) + continue; + + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + /* Set parameters. */ + *format = format_maps[i].format; + *samples_per_frame = state->samples_per_frame; + *frames_per_second = state->frames_per_second; + phys_width /= 8; + *byte_count = state->byte_count; + + return 0; +} + +struct builder_state { + bool be; + enum wave_format format; + unsigned int avail_bits_in_sample; + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_second; +}; + +static void build_riff_chunk_header(struct riff_chunk *chunk, + uint64_t byte_count, bool be) +{ + uint64_t data_size = sizeof(struct riff_chunk_data) + + sizeof(struct wave_fmt_subchunk) + + sizeof(struct wave_data_subchunk) + byte_count; + + if (be) { + memcpy(chunk->id, RIFF_CHUNK_ID_BE, sizeof(chunk->id)); + chunk->size = htobe32(data_size); + } else { + memcpy(chunk->id, RIFF_CHUNK_ID_LE, sizeof(chunk->id)); + chunk->size = htole32(data_size); + } +} + +static void build_subchunk_header(struct riff_subchunk *subchunk, + const char *const form, uint64_t size, + bool be) +{ + memcpy(subchunk->id, form, sizeof(subchunk->id)); + if (be) + subchunk->size = htobe32(size); + else + subchunk->size = htole32(size); +} + +static void build_wave_format_subchunk(struct wave_fmt_subchunk *subchunk, + struct builder_state *state) +{ + unsigned int bytes_per_frame = + state->bytes_per_sample * state->samples_per_frame; + unsigned int average_bytes_per_second = state->bytes_per_sample * + state->samples_per_frame * state->frames_per_second; + uint64_t size; + + /* No extensions. */ + size = sizeof(struct wave_fmt_subchunk) - sizeof(struct riff_subchunk); + build_subchunk_header((struct riff_subchunk *)subchunk, FMT_SUBCHUNK_ID, + size, state->be); + + if (state->be) { + subchunk->format = htobe16(state->format); + subchunk->samples_per_frame = htobe16(state->samples_per_frame); + subchunk->frames_per_second = htobe32(state->frames_per_second); + subchunk->average_bytes_per_second = + htobe32(average_bytes_per_second); + subchunk->bytes_per_frame = htobe16(bytes_per_frame); + subchunk->bits_per_sample = + htobe16(state->avail_bits_in_sample); + } else { + subchunk->format = htole16(state->format); + subchunk->samples_per_frame = htole16(state->samples_per_frame); + subchunk->frames_per_second = htole32(state->frames_per_second); + subchunk->average_bytes_per_second = + htole32(average_bytes_per_second); + subchunk->bytes_per_frame = htole16(bytes_per_frame); + subchunk->bits_per_sample = + htole16(state->avail_bits_in_sample); + } +} + +static void build_wave_data_subchunk(struct wave_data_subchunk *subchunk, + uint64_t byte_count, bool be) +{ + build_subchunk_header((struct riff_subchunk *)subchunk, + DATA_SUBCHUNK_ID, byte_count, be); +} + +static int write_riff_chunk_for_wave(struct container_context *cntr, + uint64_t byte_count) +{ + struct builder_state *state = cntr->private_data; + union { + struct riff_chunk chunk; + struct riff_chunk_data chunk_data; + struct wave_fmt_subchunk fmt_subchunk; + struct wave_data_subchunk data_subchunk; + } buf = {0}; + uint64_t total_byte_count; + int err; + + /* Chunk header. */ + total_byte_count = sizeof(struct riff_chunk_data) + + sizeof(struct wave_fmt_subchunk) + + sizeof(struct wave_data_subchunk); + if (byte_count > cntr->max_size - total_byte_count) + total_byte_count = cntr->max_size; + else + total_byte_count += byte_count; + build_riff_chunk_header(&buf.chunk, total_byte_count, state->be); + err = container_recursive_write(cntr, &buf, sizeof(buf.chunk)); + if (err < 0) + return err; + + /* Chunk data header. */ + memcpy(buf.chunk_data.id, RIFF_FORM_WAVE, sizeof(buf.chunk_data.id)); + err = container_recursive_write(cntr, &buf, sizeof(buf.chunk_data)); + if (err < 0) + return err; + + /* A subchunk in the chunk data for WAVE format. */ + build_wave_format_subchunk(&buf.fmt_subchunk, state); + err = container_recursive_write(cntr, &buf, sizeof(buf.fmt_subchunk)); + if (err < 0) + return err; + + /* A subchunk in the chunk data for WAVE data. */ + build_wave_data_subchunk(&buf.data_subchunk, byte_count, state->be); + return container_recursive_write(cntr, &buf, sizeof(buf.data_subchunk)); +} + +static int wave_builder_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *byte_count) +{ + struct builder_state *state = cntr->private_data; + int i; + + /* Validate parameters. */ + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].format == *format) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + state->format = format_maps[i].wformat; + state->avail_bits_in_sample = snd_pcm_format_width(*format); + state->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + state->samples_per_frame = *samples_per_frame; + state->frames_per_second = *frames_per_second; + + state->be = (snd_pcm_format_big_endian(*format) == 1); + + return write_riff_chunk_for_wave(cntr, *byte_count); +} + +static int wave_builder_post_process(struct container_context *cntr, + uint64_t handled_byte_count) +{ + int err; + + err = container_seek_offset(cntr, 0); + if (err < 0) + return err; + + return write_riff_chunk_for_wave(cntr, handled_byte_count); +} + +const struct container_parser container_parser_riff_wave = { + .format = CONTAINER_FORMAT_RIFF_WAVE, + .magic = RIFF_MAGIC, + .max_size = UINT32_MAX - + sizeof(struct riff_chunk_data) - + sizeof(struct wave_fmt_subchunk) - + sizeof(struct wave_data_subchunk), + .ops = { + .pre_process = wave_parser_pre_process, + }, + .private_size = sizeof(struct parser_state), +}; + +const struct container_builder container_builder_riff_wave = { + .format = CONTAINER_FORMAT_RIFF_WAVE, + .max_size = UINT32_MAX - + sizeof(struct riff_chunk_data) - + sizeof(struct wave_fmt_subchunk) - + sizeof(struct wave_data_subchunk), + .ops = { + .pre_process = wave_builder_pre_process, + .post_process = wave_builder_post_process, + }, + .private_size = sizeof(struct builder_state), +}; diff --git a/axfer/container.c b/axfer/container.c index 5cb879e1..c20af463 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -20,13 +20,14 @@ static const char *const cntr_type_labels[] = { };
static const char *const cntr_format_labels[] = { - [CONTAINER_FORMAT_COUNT] = "", + [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", };
static const char *const suffixes[] = { - [CONTAINER_FORMAT_COUNT] = "", + [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", };
+ const char *const container_suffix_from_format(enum container_format format) { return suffixes[format]; @@ -146,7 +147,7 @@ int container_parser_init(struct container_context *cntr, const char *const path, unsigned int verbose) { const struct container_parser *parsers[] = { - NULL, + [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave, }; const struct container_parser *parser; unsigned int size; @@ -223,7 +224,7 @@ int container_builder_init(struct container_context *cntr, unsigned int verbose) { const struct container_builder *builders[] = { - NULL, + [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, }; const struct container_builder *builder; int err; diff --git a/axfer/container.h b/axfer/container.h index 7b1f80c6..3c27c26f 100644 --- a/axfer/container.h +++ b/axfer/container.h @@ -25,6 +25,7 @@ enum container_type { };
enum container_format { + CONTAINER_FORMAT_RIFF_WAVE = 0, CONTAINER_FORMAT_COUNT, };
@@ -107,4 +108,7 @@ int container_recursive_write(struct container_context *cntr, void *buf, unsigned int byte_count); int container_seek_offset(struct container_context *cntr, off64_t offset);
+extern const struct container_parser container_parser_riff_wave; +extern const struct container_builder container_builder_riff_wave; + #endif
This commit adds support for data of Sparc AU format. In this data format, values in each of field are encoded in big-endian byte order and available formats of data sample are restricted in big-endian byte order.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/container-au.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/container.c | 6 +- axfer/container.h | 4 + 4 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 axfer/container-au.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 092c9662..35aa2261 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -25,4 +25,5 @@ axfer_SOURCES = \ subcmd-list.c \ container.h \ container.c \ - container-riff-wave.c + container-riff-wave.c \ + container-au.c diff --git a/axfer/container-au.c b/axfer/container-au.c new file mode 100644 index 00000000..b0ac13c6 --- /dev/null +++ b/axfer/container-au.c @@ -0,0 +1,207 @@ +/* + * container-au.c - a parser/builder for a container of Sun Audio File. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" +#include "misc.h" + +/* Not portable to all of UNIX platforms. */ +#include <endian.h> + +/* + * Reference: + * * http://pubs.opengroup.org/external/auformat.html + */ + +#define AU_MAGIC ".snd" +#define UNKNOWN_SIZE UINT32_MAX + +enum code_id { + CODE_ID_CCIT_MU_LAW_BE = 0x01, + CODE_ID_GENERIC_MBLA_S8 = 0x02, + CODE_ID_GENERIC_MBLA_S16_BE = 0x03, + CODE_ID_GENERIC_MBLA_S32_BE = 0x05, + CODE_ID_IEEE754_FLOAT_S32_BE = 0x06, + CODE_ID_IEEE754_DOUBLE_S64_BE = 0x07, + CODE_ID_CCIT_ADPCM_G721_4BIT_BE = 0x17, + CODE_ID_CCIT_ADPCM_G723_3BIT_BE = 0x19, + CODE_ID_CCIT_A_LAW_BE = 0x1b, +}; + +struct format_map { + enum code_id code_id; + snd_pcm_format_t format; +}; + +static const struct format_map format_maps[] = { + {CODE_ID_GENERIC_MBLA_S8, SND_PCM_FORMAT_S8}, + {CODE_ID_GENERIC_MBLA_S16_BE, SND_PCM_FORMAT_S16_BE}, + {CODE_ID_GENERIC_MBLA_S32_BE, SND_PCM_FORMAT_S32_BE}, + {CODE_ID_IEEE754_FLOAT_S32_BE, SND_PCM_FORMAT_FLOAT_BE}, + {CODE_ID_IEEE754_DOUBLE_S64_BE, SND_PCM_FORMAT_FLOAT64_BE}, + /* + * CODE_ID_CCIT_ADPCM_G721_4BIT_BE is not supported by ALSA. + * CODE_ID_CCIT_ADPCM_G723_3BIT_BE is not supported due to width of + * its sample. + */ + {CODE_ID_CCIT_A_LAW_BE, SND_PCM_FORMAT_A_LAW}, + {CODE_ID_CCIT_MU_LAW_BE, SND_PCM_FORMAT_MU_LAW}, +}; + +struct container_header { + uint8_t magic[4]; + uint32_t hdr_size; + uint32_t data_size; + uint32_t code_id; + uint32_t frames_per_second; + uint32_t samples_per_frame; +}; + +struct container_annotation { + uint32_t chunks[0]; +}; + +struct parser_state { + enum code_id code_id; + unsigned int samples_per_frame; + unsigned int bytes_per_sample; +}; + +static int au_parser_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *byte_count) +{ + struct parser_state *state = cntr->private_data; + struct container_header header; + enum code_id code_id; + int i; + int err; + + /* Parse header. 4 bytes are enough to detect supported containers. */ + memcpy(&header.magic, cntr->magic, sizeof(cntr->magic)); + err = container_recursive_read(cntr, + (char *)&header + sizeof(cntr->magic), + sizeof(header) - sizeof(cntr->magic)); + if (err < 0) + return err; + if (cntr->eof) + return 0; + + if (memcmp(header.magic, AU_MAGIC, sizeof(header.magic)) != 0) + return -EINVAL; + if (be32toh(header.hdr_size) != sizeof(struct container_header)) + return -EINVAL; + + code_id = be32toh(header.code_id); + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].code_id == code_id) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + *format = format_maps[i].format; + *frames_per_second = be32toh(header.frames_per_second); + *samples_per_frame = be32toh(header.samples_per_frame); + + state->code_id = code_id; + state->samples_per_frame = *samples_per_frame; + state->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + + *byte_count = be32toh(header.data_size); + + return 0; +} + +struct builder_state { + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_second; + enum code_id code_id; +}; + +static void build_container_header(struct builder_state *state, + struct container_header *header, + unsigned int frames_per_second, + uint64_t byte_count) +{ + memcpy(header->magic, AU_MAGIC, sizeof(header->magic)); + header->hdr_size = htobe32(sizeof(struct container_header)); + header->data_size = htobe32(byte_count); + header->code_id = htobe32(state->code_id); + header->frames_per_second = htobe32(frames_per_second); + header->samples_per_frame = htobe32(state->samples_per_frame); +} + +static int write_container_header(struct container_context *cntr, + uint64_t byte_count) +{ + struct builder_state *state = cntr->private_data; + struct container_header header; + + build_container_header(state, &header, state->frames_per_second, + byte_count); + + return container_recursive_write(cntr, &header, sizeof(header)); +} + +static int au_builder_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *byte_count) +{ + struct builder_state *status = cntr->private_data; + int i; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].format == *format) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + status->code_id = format_maps[i].code_id; + status->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + status->frames_per_second = *frames_per_second; + status->samples_per_frame = *samples_per_frame; + + return write_container_header(cntr, *byte_count); +} + +static int au_builder_post_process(struct container_context *cntr, + uint64_t handled_byte_count) +{ + int err; + + err = container_seek_offset(cntr, 0); + if (err < 0) + return err; + + return write_container_header(cntr, handled_byte_count); +} + +const struct container_parser container_parser_au = { + .format = CONTAINER_FORMAT_AU, + .magic = AU_MAGIC, + .max_size = UINT32_MAX, + .ops = { + .pre_process = au_parser_pre_process, + }, + .private_size = sizeof(struct parser_state), +}; + +const struct container_builder container_builder_au = { + .format = CONTAINER_FORMAT_AU, + .max_size = UINT32_MAX, + .ops = { + .pre_process = au_builder_pre_process, + .post_process = au_builder_post_process, + }, + .private_size = sizeof(struct builder_state), +}; diff --git a/axfer/container.c b/axfer/container.c index c20af463..0e208aba 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -21,10 +21,12 @@ static const char *const cntr_type_labels[] = {
static const char *const cntr_format_labels[] = { [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", + [CONTAINER_FORMAT_AU] = "au", };
static const char *const suffixes[] = { - [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", + [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", + [CONTAINER_FORMAT_AU] = ".au", };
@@ -148,6 +150,7 @@ int container_parser_init(struct container_context *cntr, { const struct container_parser *parsers[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave, + [CONTAINER_FORMAT_AU] = &container_parser_au, }; const struct container_parser *parser; unsigned int size; @@ -225,6 +228,7 @@ int container_builder_init(struct container_context *cntr, { const struct container_builder *builders[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, + [CONTAINER_FORMAT_AU] = &container_builder_au, }; const struct container_builder *builder; int err; diff --git a/axfer/container.h b/axfer/container.h index 3c27c26f..3042e1e9 100644 --- a/axfer/container.h +++ b/axfer/container.h @@ -26,6 +26,7 @@ enum container_type {
enum container_format { CONTAINER_FORMAT_RIFF_WAVE = 0, + CONTAINER_FORMAT_AU, CONTAINER_FORMAT_COUNT, };
@@ -111,4 +112,7 @@ int container_seek_offset(struct container_context *cntr, off64_t offset); extern const struct container_parser container_parser_riff_wave; extern const struct container_builder container_builder_riff_wave;
+extern const struct container_parser container_parser_au; +extern const struct container_builder container_builder_au; + #endif
This commit adds support for data of Creative Tech. voice format. In this data format, values in each of field are represented in little-endian byte order and available formats of data sample are restricted in little-endian byte order.
In version 1.10 of this format, sampling rate is represented with reciprocal number of the rate, thus we cannot calculate original sampling rate precisely just from its header. For example at 44.1kHz, file header includes 233 (=256-1,000,000/44,100), but we cannot recover the value just from the code (43478.2...). For my convenience, this commit adds a pre-computed table and lookup major rates from the table.
Additionally, this format can includes several blocks with different sample format. When handling this type of file, we need to start/stop substream for each of the block, while this brings complicated code. This type of format is enough ancient and presently quite minor. This commit takes a compromise and handles a first sample block only.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/container-voc.c | 843 ++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/container.c | 4 + axfer/container.h | 4 + 4 files changed, 853 insertions(+), 1 deletion(-) create mode 100644 axfer/container-voc.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 35aa2261..48d046e1 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -26,4 +26,5 @@ axfer_SOURCES = \ container.h \ container.c \ container-riff-wave.c \ - container-au.c + container-au.c \ + container-voc.c diff --git a/axfer/container-voc.c b/axfer/container-voc.c new file mode 100644 index 00000000..22966501 --- /dev/null +++ b/axfer/container-voc.c @@ -0,0 +1,843 @@ +/* + * container-voc.c - a parser/builder for a container of Creative Voice File. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" +#include "misc.h" + +/* Not portable to all of UNIX platforms. */ +#include <endian.h> + +/* + * References: + * - http://sox.sourceforge.net/ + */ + +#define VOC_MAGIC "Creative Voice File\x1a" +#define VOC_VERSION_1_10 0x010a +#define VOC_VERSION_1_20 0x0114 + +enum block_type { + BLOCK_TYPE_TERMINATOR = 0x00, + BLOCK_TYPE_V110_DATA = 0x01, + BLOCK_TYPE_CONTINUOUS_DATA = 0x02, + BLOCK_TYPE_SILENCE = 0x03, + BLOCK_TYPE_MARKER = 0x04, + BLOCK_TYPE_STRING = 0x05, + BLOCK_TYPE_REPEAT_START = 0x06, + BLOCK_TYPE_REPEAT_END = 0x07, + BLOCK_TYPE_EXTENDED_V110_FORMAT = 0x08, + BLOCK_TYPE_V120_DATA = 0x09, +}; + +enum code_id { + /* Version 1.10. */ + CODE_ID_GENERIC_MBLA_U8 = 0x00, + CODE_ID_CREATIVE_ADPCM_8BIT_TO_4BIT_LE = 0x01, + CODE_ID_CREATIVE_ADPCM_8BIT_TO_3BIT_LE = 0x02, + CODE_ID_CREATIVE_ADPCM_8BIT_TO_2BIT_LE = 0x03, + /* Version 1.20. */ + CODE_ID_GENERIC_MBLA_S16_LE = 0x04, + CODE_ID_CCIT_A_LAW_LE = 0x06, + CODE_ID_CCIT_MU_LAW_LE = 0x07, + CODE_ID_CREATIVE_ADPCM_16BIT_TO_4BIT_LE = 0x2000, +}; + +struct format_map { + unsigned int minimal_version; + enum code_id code_id; + snd_pcm_format_t format; +}; + +static const struct format_map format_maps[] = { + {VOC_VERSION_1_10, CODE_ID_GENERIC_MBLA_U8, SND_PCM_FORMAT_U8}, + {VOC_VERSION_1_20, CODE_ID_GENERIC_MBLA_S16_LE, SND_PCM_FORMAT_S16_LE}, + {VOC_VERSION_1_20, CODE_ID_CCIT_A_LAW_LE, SND_PCM_FORMAT_A_LAW}, + {VOC_VERSION_1_20, CODE_ID_CCIT_MU_LAW_LE, SND_PCM_FORMAT_MU_LAW}, + /* The other formats are not supported by ALSA. */ +}; + +struct container_header { + uint8_t magic[20]; + uint16_t hdr_size; + uint16_t version; + uint16_t version_compr; +}; + +/* A format for data blocks except for terminator type. */ +struct block_header { + uint8_t type; + uint8_t size[3]; + + uint8_t data[0]; +}; + +/* Data block for terminator type has an exceptional format. */ +struct block_terminator { + uint8_t type; +}; + +struct time_const { + unsigned int frames_per_second; + uint16_t code; +}; + +static const struct time_const v110_time_consts[] = { + {5512, 74}, + {8000, 130}, + {11025, 165}, + {16000, 193}, + {22050, 210}, + {32000, 224}, + {44100, 233}, + {48000, 235}, + {64000, 240}, + /* Time constant for the upper sampling rate is not identical. */ +}; + +static const struct time_const ex_v110_time_consts[] = { + {5512, 19092}, + {8000, 33536}, + {11025, 42317}, + {16000, 49536}, + {22050, 53927}, + {32000, 57536}, + {44100, 59732}, + {48000, 60203}, + {64000, 61536}, + {88200, 62634}, + {96000, 62870}, + {176400, 64085}, + {192000, 64203}, + /* This support up to 192.0 kHz. The rest is for cases with 2ch. */ + {352800, 64811}, + {384000, 64870}, +}; + +/* + * v1.10 format: + * - monaural. + * - frames_per_second = 1,000,000 / (256 - time_const) + */ +struct block_v110_data { + uint8_t type; + uint8_t size[3]; /* Equals to (2 + the size of frames). */ + + uint8_t time_const; + uint8_t code_id; + uint8_t frames[0]; /* Aligned to little-endian. */ +}; + +struct block_continuous_data { + uint8_t type; + uint8_t size[3]; /* Equals to the size of frames. */ + + uint8_t frames[0]; /* Aligned to little-endian. */ +}; + +/* + * v1.10 format: + * - monaural. + * - frames_per_second = 1,000,000 / (256 - time_const). + */ +struct block_silence { + uint8_t type; + uint8_t size[3]; /* Equals to 3. */ + + uint16_t frame_count; + uint8_t time_const; +}; + +struct block_marker { + uint8_t type; + uint8_t size[3]; /* Equals to 2. */ + + uint16_t mark; +}; + +struct block_string { + uint8_t type; + uint8_t size[3]; /* Equals to the length of string with 0x00. */ + + uint8_t chars[0]; +}; + +struct block_repeat_start { + uint8_t type; + uint8_t size[3]; /* Equals to 2. */ + + uint16_t count; +}; + +struct block_repeat_end { + uint8_t type; + uint8_t size[3]; /* Equals to 0. */ +}; + +/* + * Extended v1.10 format: + * - manaural/stereo. + * - frames_per_second = + * 256,000,000 / (samples_per_frame * (65536 - time_const)). + * - Appear just before v110_data block. + */ +struct block_extended_v110_format { + uint8_t type; + uint8_t size[3]; /* Equals to 4. */ + + uint16_t time_const; + uint8_t code_id; + uint8_t ch_mode; /* 0 is monaural, 1 is stereo. */ +}; + +/* + * v1.20 format: + * - monaural/stereo. + * - 8/16 bits_per_sample. + * - time_const is not used. + * - code_id is extended. + */ +struct block_v120_format { + uint8_t type; + uint8_t size[3]; /* Equals to (12 + ). */ + + uint32_t frames_per_second; + uint8_t bits_per_sample; + uint8_t samples_per_frame; + uint16_t code_id; + uint8_t reserved[4]; + + uint8_t frames[0]; /* Aligned to little-endian. */ +}; + +/* Aligned to little endian order but 24 bits field. */ +static uint32_t parse_block_data_size(uint8_t fields[3]) +{ + return (fields[2] << 16) | (fields[1] << 8) | fields[0]; +} + +static void build_block_data_size(uint8_t fields[3], unsigned int size) +{ + fields[0] = (size & 0x0000ff); + fields[1] = (size & 0x00ff00) >> 8; + fields[2] = (size & 0xff0000) >> 16; +} + +static int build_time_constant(unsigned int frames_per_second, + unsigned int samples_per_frame, uint16_t *code, + bool extended) +{ + int i; + + /* 16 bits are available for this purpose. */ + if (extended) { + if (samples_per_frame > 2) + return -EINVAL; + frames_per_second *= samples_per_frame; + + for (i = 0; i < ARRAY_SIZE(ex_v110_time_consts); ++i) { + if (ex_v110_time_consts[i].frames_per_second == + frames_per_second) + break; + } + if (i < ARRAY_SIZE(ex_v110_time_consts) || + frames_per_second <= 192000) { + *code = ex_v110_time_consts[i].code; + } else { + *code = 65536 - 256000000 / frames_per_second; + } + } else { + if (samples_per_frame != 1) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(v110_time_consts); ++i) { + if (v110_time_consts[i].frames_per_second == + frames_per_second) + break; + } + /* Should be within 8 bit. */ + if (i < ARRAY_SIZE(v110_time_consts)) + *code = (uint8_t)v110_time_consts[i].code; + else + *code = 256 - 1000000 / frames_per_second; + } + + return 0; +} + +static unsigned int parse_time_constant(uint16_t code, + unsigned int samples_per_frame, + unsigned int *frames_per_second, + bool extended) +{ + int i; + + if (extended) { + if (samples_per_frame > 2) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(ex_v110_time_consts); ++i) { + if (ex_v110_time_consts[i].code == code || + ex_v110_time_consts[i].code - 1 == code) + break; + } + if (i < ARRAY_SIZE(ex_v110_time_consts)) { + *frames_per_second = + ex_v110_time_consts[i].frames_per_second / + samples_per_frame; + } else { + *frames_per_second = 256000000 / samples_per_frame / + (65536 - code); + } + } else { + if (samples_per_frame != 1) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(v110_time_consts); ++i) { + if (v110_time_consts[i].code == code || + v110_time_consts[i].code - 1 == code) + break; + } + if (i < ARRAY_SIZE(v110_time_consts)) { + *frames_per_second = + v110_time_consts[i].frames_per_second; + } else { + *frames_per_second = 1000000 / (256 - code); + } + } + + return 0; +} + +struct parser_state { + unsigned int version; + bool extended; + + unsigned int frames_per_second; + unsigned int samples_per_frame; + unsigned int bytes_per_sample; + enum code_id code_id; + uint32_t byte_count; +}; + +static int parse_container_header(struct parser_state *state, + struct container_header *header) +{ + uint16_t hdr_size; + uint16_t version; + uint16_t version_compr; + + hdr_size = le16toh(header->hdr_size); + version = le16toh(header->version); + version_compr = le16toh(header->version_compr); + + if (memcmp(header->magic, VOC_MAGIC, sizeof(header->magic))) + return -EIO; + + if (hdr_size != sizeof(*header)) + return -EIO; + + if (version_compr != 0x1234 + ~version) + return -EIO; + + if (version != VOC_VERSION_1_10 && version != VOC_VERSION_1_20) + return -EIO; + + state->version = version; + + return 0; +} + +static bool check_code_id(uint8_t code_id, unsigned int version) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (code_id != format_maps[i].code_id) + continue; + if (version >= format_maps[i].minimal_version) + return true; + } + + return false; +} + +static int parse_v120_format_block(struct parser_state *state, + struct block_v120_format *block) +{ + state->frames_per_second = le32toh(block->frames_per_second); + state->bytes_per_sample = block->bits_per_sample / 8; + state->samples_per_frame = block->samples_per_frame; + state->code_id = le16toh(block->code_id); + state->byte_count = parse_block_data_size(block->size) - 12; + + if (!check_code_id(state->code_id, VOC_VERSION_1_20)) + return -EIO; + + return 0; +} + +static int parse_extended_v110_format(struct parser_state *state, + struct block_extended_v110_format *block) +{ + unsigned int time_const; + unsigned int frames_per_second; + int err; + + state->code_id = block->code_id; + if (!check_code_id(state->code_id, VOC_VERSION_1_10)) + return -EIO; + + if (block->ch_mode == 0) + state->samples_per_frame = 1; + else if (block->ch_mode == 1) + state->samples_per_frame = 2; + else + return -EIO; + + time_const = le16toh(block->time_const); + err = parse_time_constant(time_const, state->samples_per_frame, + &frames_per_second, true); + if (err < 0) + return err; + state->frames_per_second = frames_per_second; + + state->extended = true; + + return 0; +} + +static int parse_v110_data(struct parser_state *state, + struct block_v110_data *block) +{ + unsigned int time_const; + unsigned int frames_per_second; + int err; + + if (!state->extended) { + state->code_id = block->code_id; + if (!check_code_id(state->code_id, VOC_VERSION_1_10)) + return -EIO; + + time_const = block->time_const; + err = parse_time_constant(time_const, 1, &frames_per_second, + false); + if (err < 0) + return err; + state->frames_per_second = frames_per_second; + state->samples_per_frame = 1; + } + + state->bytes_per_sample = 1; + state->byte_count = parse_block_data_size(block->size) - 2; + + return 0; +} + +static int detect_container_version(struct container_context *cntr) +{ + struct parser_state *state = cntr->private_data; + struct container_header header = {0}; + int err; + + /* 4 bytes were alread read to detect container type. */ + memcpy(&header.magic, cntr->magic, sizeof(cntr->magic)); + err = container_recursive_read(cntr, + (char *)&header + sizeof(cntr->magic), + sizeof(header) - sizeof(cntr->magic)); + if (err < 0) + return err; + if (cntr->eof) + return 0; + + return parse_container_header(state, &header); +} + +static int allocate_for_block_cache(struct container_context *cntr, + struct block_header *header, void **buf) +{ + uint32_t block_size; + char *cache; + int err; + + if (header->type == BLOCK_TYPE_V110_DATA) + block_size = sizeof(struct block_v110_data); + else if (header->type == BLOCK_TYPE_CONTINUOUS_DATA) + block_size = sizeof(struct block_continuous_data); + else if (header->type == BLOCK_TYPE_EXTENDED_V110_FORMAT) + block_size = sizeof(struct block_extended_v110_format); + else if (header->type == BLOCK_TYPE_V120_DATA) + block_size = sizeof(struct block_v120_format); + else + block_size = parse_block_data_size(header->size); + + cache = malloc(block_size); + if (cache == NULL) + return -ENOMEM; + memset(cache, 0, block_size); + + memcpy(cache, header, sizeof(*header)); + err = container_recursive_read(cntr, cache + sizeof(*header), + block_size - sizeof(*header)); + if (err < 0) { + free(cache); + return err; + } + if (cntr->eof) { + free(cache); + return 0; + } + + *buf = cache; + + return 0; +} + +static int cache_data_block(struct container_context *cntr, + struct block_header *header, void **buf) +{ + int err; + + /* Check type of this block. */ + err = container_recursive_read(cntr, &header->type, + sizeof(header->type)); + if (err < 0) + return err; + if (cntr->eof) + return 0; + + if (header->type > BLOCK_TYPE_V120_DATA) + return -EIO; + if (header->type == BLOCK_TYPE_TERMINATOR) + return 0; + + /* Check size of this block. If the block includes a batch of data, */ + err = container_recursive_read(cntr, &header->size, + sizeof(header->size)); + if (err < 0) + return err; + if (cntr->eof) + return 0; + + return allocate_for_block_cache(cntr, header, buf); +} + +static int detect_format_block(struct container_context *cntr) +{ + struct parser_state *state = cntr->private_data; + struct block_header header; + void *buf = NULL; + int err; + +again: + err = cache_data_block(cntr, &header, &buf); + if (err < 0) + return err; + + if (header.type == BLOCK_TYPE_EXTENDED_V110_FORMAT) { + err = parse_extended_v110_format(state, buf); + } else if (header.type == BLOCK_TYPE_V120_DATA) { + err = parse_v120_format_block(state, buf); + } else if (header.type == BLOCK_TYPE_V110_DATA) { + err = parse_v110_data(state, buf); + } else { + free(buf); + goto again; + } + + free(buf); + + if (err < 0) + return err; + + /* Expect to detect block_v110_data. */ + if (header.type == BLOCK_TYPE_EXTENDED_V110_FORMAT) + goto again; + + return 0; +} + +static int voc_parser_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *byte_count) +{ + struct parser_state *state = cntr->private_data; + int i; + int err; + + err = detect_container_version(cntr); + if (err < 0) + return err; + + err = detect_format_block(cntr); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].code_id == state->code_id) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + *format = format_maps[i].format; + *samples_per_frame = state->samples_per_frame; + *frames_per_second = state->frames_per_second; + + /* This program handles PCM frames in this data block only. */ + *byte_count = state->byte_count; + + return 0; +} + +struct builder_state { + unsigned int version; + bool extended; + enum code_id code_id; + + unsigned int samples_per_frame; + unsigned int bytes_per_sample; +}; + +static int write_container_header(struct container_context *cntr, + struct container_header *header) +{ + struct builder_state *state = cntr->private_data; + + /* Process container header. */ + memcpy(header->magic, VOC_MAGIC, sizeof(header->magic)); + header->hdr_size = htole16(sizeof(*header)); + header->version = htole16(state->version); + header->version_compr = htole16(0x1234 + ~state->version); + + return container_recursive_write(cntr, header, sizeof(*header)); +} + +static int write_v120_format_block(struct container_context *cntr, + struct block_v120_format *block, + unsigned int frames_per_second, + uint64_t byte_count) +{ + struct builder_state *state = cntr->private_data; + + block->type = BLOCK_TYPE_V120_DATA; + build_block_data_size(block->size, 12 + byte_count); + + block->frames_per_second = htole32(frames_per_second); + block->bits_per_sample = htole16(state->bytes_per_sample * 8); + block->samples_per_frame = htole16(state->samples_per_frame); + block->code_id = htole16(state->code_id); + + return container_recursive_write(cntr, block, sizeof(*block)); +} + +static int write_extended_v110_format_block(struct container_context *cntr, + unsigned int frames_per_second, + struct block_extended_v110_format *block) +{ + struct builder_state *state = cntr->private_data; + uint16_t time_const; + int err; + + block->type = BLOCK_TYPE_EXTENDED_V110_FORMAT; + build_block_data_size(block->size, 4); + + /* 16 bits are available for this purpose. */ + err = build_time_constant(frames_per_second, state->samples_per_frame, + &time_const, true); + if (err < 0) + return err; + block->time_const = htole16(time_const); + block->code_id = htole16(state->code_id); + + if (state->samples_per_frame == 1) + block->ch_mode = 0; + else + block->ch_mode = 1; + + return container_recursive_write(cntr, block, sizeof(*block)); +} + +static int write_v110_format_block(struct container_context *cntr, + struct block_v110_data *block, + unsigned int frames_per_second, + uint64_t byte_count) +{ + struct builder_state *state = cntr->private_data; + uint16_t time_const; + int err; + + block->type = BLOCK_TYPE_V110_DATA; + build_block_data_size(block->size, 2 + byte_count); + + /* These fields were obsoleted by extension. */ + err = build_time_constant(frames_per_second, 1, &time_const, false); + if (err < 0) + return err; + block->time_const = (uint8_t)time_const; + block->code_id = state->code_id; + return container_recursive_write(cntr, block, sizeof(*block)); +} + +static int write_data_blocks(struct container_context *cntr, + unsigned int frames_per_second, + uint64_t byte_count) +{ + union { + struct container_header header; + struct block_v110_data v110_data; + struct block_extended_v110_format extended_v110_format; + struct block_v120_format v120_format; + } buf = {0}; + struct builder_state *state = cntr->private_data; + int err; + + err = write_container_header(cntr, &buf.header); + if (err < 0) + return err; + + if (state->version == VOC_VERSION_1_20) { + err = write_v120_format_block(cntr, &buf.v120_format, + frames_per_second, byte_count); + } else { + if (state->extended) { + err = write_extended_v110_format_block(cntr, + frames_per_second, + &buf.extended_v110_format); + if (err < 0) + return err; + } + err = write_v110_format_block(cntr, &buf.v110_data, + frames_per_second, byte_count); + } + + return err; +} + +static int voc_builder_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *byte_count) +{ + struct builder_state *state = cntr->private_data; + int i; + + /* Validate parameters. */ + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].format == *format) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + state->code_id = format_maps[i].code_id; + + /* Decide container version. */ + if (*samples_per_frame > 2) + state->version = VOC_VERSION_1_20; + else + state->version = format_maps[i].minimal_version; + if (state->version == VOC_VERSION_1_10) { + if (*samples_per_frame == 2) { + for (i = 0; + i < ARRAY_SIZE(ex_v110_time_consts); ++i) { + if (ex_v110_time_consts[i].frames_per_second == + *frames_per_second) + break; + } + if (i == ARRAY_SIZE(ex_v110_time_consts)) + state->version = VOC_VERSION_1_20; + else + state->extended = true; + } else { + for (i = 0; i < ARRAY_SIZE(v110_time_consts); ++i) { + if (v110_time_consts[i].frames_per_second == + *frames_per_second) + break; + } + if (i == ARRAY_SIZE(v110_time_consts)) + state->version = VOC_VERSION_1_20; + } + } + + state->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + state->samples_per_frame = *samples_per_frame; + + return write_data_blocks(cntr, *frames_per_second, *byte_count); +} + +static int write_block_terminator(struct container_context *cntr) +{ + struct block_terminator block = {0}; + + block.type = BLOCK_TYPE_TERMINATOR; + return container_recursive_write(cntr, &block, sizeof(block)); +} + +static int write_data_size(struct container_context *cntr, uint64_t byte_count) +{ + struct builder_state *state = cntr->private_data; + off64_t offset; + uint8_t size_field[3]; + int err; + + offset = sizeof(struct container_header) + sizeof(uint8_t); + if (state->version == VOC_VERSION_1_10 && state->extended) + offset += sizeof(struct block_extended_v110_format); + err = container_seek_offset(cntr, offset); + if (err < 0) + return err; + + if (state->version == VOC_VERSION_1_10) + offset = 2; + else + offset = 12; + + if (byte_count > cntr->max_size - offset) + byte_count = cntr->max_size; + else + byte_count += offset; + build_block_data_size(size_field, byte_count); + + return container_recursive_write(cntr, &size_field, sizeof(size_field)); +} + +static int voc_builder_post_process(struct container_context *cntr, + uint64_t handled_byte_count) +{ + int err; + + err = write_block_terminator(cntr); + if (err < 0) + return err; + + return write_data_size(cntr, handled_byte_count); +} + +const struct container_parser container_parser_voc = { + .format = CONTAINER_FORMAT_VOC, + .magic = VOC_MAGIC, + .max_size = 0xffffff - /* = UINT24_MAX. */ + sizeof(struct block_terminator), + .ops = { + .pre_process = voc_parser_pre_process, + }, + .private_size = sizeof(struct parser_state), +}; + +const struct container_builder container_builder_voc = { + .format = CONTAINER_FORMAT_VOC, + .max_size = 0xffffff - /* = UINT24_MAX. */ + sizeof(struct block_terminator), + .ops = { + .pre_process = voc_builder_pre_process, + .post_process = voc_builder_post_process, + }, + .private_size = sizeof(struct builder_state), +}; diff --git a/axfer/container.c b/axfer/container.c index 0e208aba..99652e65 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -22,11 +22,13 @@ static const char *const cntr_type_labels[] = { static const char *const cntr_format_labels[] = { [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", [CONTAINER_FORMAT_AU] = "au", + [CONTAINER_FORMAT_VOC] = "voc", };
static const char *const suffixes[] = { [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", [CONTAINER_FORMAT_AU] = ".au", + [CONTAINER_FORMAT_VOC] = ".voc", };
@@ -151,6 +153,7 @@ int container_parser_init(struct container_context *cntr, const struct container_parser *parsers[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave, [CONTAINER_FORMAT_AU] = &container_parser_au, + [CONTAINER_FORMAT_VOC] = &container_parser_voc, }; const struct container_parser *parser; unsigned int size; @@ -229,6 +232,7 @@ int container_builder_init(struct container_context *cntr, const struct container_builder *builders[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, [CONTAINER_FORMAT_AU] = &container_builder_au, + [CONTAINER_FORMAT_VOC] = &container_builder_voc, }; const struct container_builder *builder; int err; diff --git a/axfer/container.h b/axfer/container.h index 3042e1e9..caba7398 100644 --- a/axfer/container.h +++ b/axfer/container.h @@ -27,6 +27,7 @@ enum container_type { enum container_format { CONTAINER_FORMAT_RIFF_WAVE = 0, CONTAINER_FORMAT_AU, + CONTAINER_FORMAT_VOC, CONTAINER_FORMAT_COUNT, };
@@ -115,4 +116,7 @@ extern const struct container_builder container_builder_riff_wave; extern const struct container_parser container_parser_au; extern const struct container_builder container_builder_au;
+extern const struct container_parser container_parser_voc; +extern const struct container_builder container_builder_voc; + #endif
This commit adds support for raw data without any headers/chunks/blocks. A parser of container cannot recognize format of sample without supplemental information.
Additionally, it includes no magic bytes. A parser of container should process first several bytes as a part of PCM frames, instead of magic bytes.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 ++- axfer/container-raw.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/container.c | 39 +++++++++++++++++++++++++----- axfer/container.h | 4 ++++ 4 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 axfer/container-raw.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 48d046e1..f1ec1d1d 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -27,4 +27,5 @@ axfer_SOURCES = \ container.c \ container-riff-wave.c \ container-au.c \ - container-voc.c + container-voc.c \ + container-raw.c diff --git a/axfer/container-raw.c b/axfer/container-raw.c new file mode 100644 index 00000000..b5e11e44 --- /dev/null +++ b/axfer/container-raw.c @@ -0,0 +1,66 @@ +/* + * container-raw.c - a parser/builder for a container with raw data frame. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" +#include "misc.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +static int raw_builder_pre_process(struct container_context *cntr, + snd_pcm_format_t *sample_format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *byte_count) +{ + *byte_count = UINT64_MAX; + + return 0; +} + +static int raw_parser_pre_process(struct container_context *cntr, + snd_pcm_format_t *sample_format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *byte_count) +{ + struct stat buf = {0}; + int err; + + if (cntr->stdio) { + *byte_count = UINT64_MAX; + return 0; + } + + err = fstat(cntr->fd, &buf); + if (err < 0) + return err; + + *byte_count = buf.st_size; + if (*byte_count == 0) + *byte_count = UINT64_MAX; + + return 0; +} + +const struct container_parser container_parser_raw = { + .format = CONTAINER_FORMAT_RAW, + .max_size = UINT64_MAX, + .ops = { + .pre_process = raw_parser_pre_process, + }, +}; + +const struct container_builder container_builder_raw = { + .format = CONTAINER_FORMAT_RAW, + .max_size = UINT64_MAX, + .ops = { + .pre_process = raw_builder_pre_process, + }, +}; diff --git a/axfer/container.c b/axfer/container.c index 99652e65..bdaf99ab 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -23,15 +23,16 @@ static const char *const cntr_format_labels[] = { [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", [CONTAINER_FORMAT_AU] = "au", [CONTAINER_FORMAT_VOC] = "voc", + [CONTAINER_FORMAT_RAW] = "raw", };
static const char *const suffixes[] = { [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", [CONTAINER_FORMAT_AU] = ".au", [CONTAINER_FORMAT_VOC] = ".voc", + [CONTAINER_FORMAT_RAW] = "", };
- const char *const container_suffix_from_format(enum container_format format) { return suffixes[format]; @@ -112,7 +113,7 @@ enum container_format container_format_from_path(const char *path) }
/* Unsupported. */ - return CONTAINER_FORMAT_COUNT; + return CONTAINER_FORMAT_RAW; }
int container_seek_offset(struct container_context *cntr, off64_t offset) @@ -205,7 +206,7 @@ int container_parser_init(struct container_context *cntr, * Unless detected, use raw container. */ if (i == ARRAY_SIZE(parsers)) - return -EINVAL; + parser = &container_parser_raw;
/* Allocate private data for the parser. */ if (parser->private_size > 0) { @@ -233,6 +234,7 @@ int container_builder_init(struct container_context *cntr, [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, [CONTAINER_FORMAT_AU] = &container_builder_au, [CONTAINER_FORMAT_VOC] = &container_builder_voc, + [CONTAINER_FORMAT_RAW] = &container_builder_raw, }; const struct container_builder *builder; int err; @@ -311,6 +313,16 @@ int container_context_pre_process(struct container_context *cntr, return 0; }
+ if (cntr->format == CONTAINER_FORMAT_RAW) { + if (*format == SND_PCM_FORMAT_UNKNOWN || + *samples_per_frame == 0 || *frames_per_second == 0) { + fprintf(stderr, + "Any file format is not detected. Need to " + "indicate all of sample format, channels and " + "rate explicitly.\n"); + return -EINVAL; + } + } assert(*format >= SND_PCM_FORMAT_S8); assert(*format <= SND_PCM_FORMAT_LAST); assert(*samples_per_frame > 0); @@ -357,6 +369,7 @@ int container_context_process_frames(struct container_context *cntr, char *buf = frame_buffer; unsigned int bytes_per_frame; unsigned int byte_count; + unsigned int target_byte_count; int err;
assert(cntr); @@ -365,7 +378,21 @@ int container_context_process_frames(struct container_context *cntr, assert(frame_count);
bytes_per_frame = cntr->bytes_per_sample * cntr->samples_per_frame; - byte_count = *frame_count * bytes_per_frame; + target_byte_count = *frame_count * bytes_per_frame; + + /* + * A parser of cotainers already read first 4 bytes to detect format + * of container, however they includes PCM frames when any format was + * undetected. Surely to write out them. + */ + byte_count = target_byte_count; + if (cntr->format == CONTAINER_FORMAT_RAW && + cntr->type == CONTAINER_TYPE_PARSER && !cntr->magic_handled) { + memcpy(buf, cntr->magic, sizeof(cntr->magic)); + buf += sizeof(cntr->magic); + byte_count -= sizeof(cntr->magic); + cntr->magic_handled = true; + }
/* Each container has limitation for its volume for sample data. */ if (cntr->handled_byte_count > cntr->max_size - byte_count) @@ -379,11 +406,11 @@ int container_context_process_frames(struct container_context *cntr, return err; }
- cntr->handled_byte_count += byte_count; + cntr->handled_byte_count += target_byte_count; if (cntr->handled_byte_count == cntr->max_size) cntr->eof = true;
- *frame_count = byte_count / bytes_per_frame; + *frame_count = target_byte_count / bytes_per_frame;
return 0; } diff --git a/axfer/container.h b/axfer/container.h index caba7398..9825f48d 100644 --- a/axfer/container.h +++ b/axfer/container.h @@ -28,6 +28,7 @@ enum container_format { CONTAINER_FORMAT_RIFF_WAVE = 0, CONTAINER_FORMAT_AU, CONTAINER_FORMAT_VOC, + CONTAINER_FORMAT_RAW, CONTAINER_FORMAT_COUNT, };
@@ -119,4 +120,7 @@ extern const struct container_builder container_builder_au; extern const struct container_parser container_parser_voc; extern const struct container_builder container_builder_voc;
+const struct container_parser container_parser_raw; +const struct container_builder container_builder_raw; + #endif
In former commits, container module gets supports of parser/builder for several types of file format. This commit adds a unit test for them. This includes positive test cases only. The test cases actually generate I/O to file systems for many test cases. It takes a long time to finish.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/test/Makefile.am | 15 +++ axfer/test/container-test.c | 299 ++++++++++++++++++++++++++++++++++++++++++++ axfer/test/generator.c | 260 ++++++++++++++++++++++++++++++++++++++ axfer/test/generator.h | 47 +++++++ configure.ac | 3 +- 6 files changed, 625 insertions(+), 2 deletions(-) create mode 100644 axfer/test/Makefile.am create mode 100644 axfer/test/container-test.c create mode 100644 axfer/test/generator.c create mode 100644 axfer/test/generator.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index f1ec1d1d..55fcf716 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -6,7 +6,8 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/include
# Unit tests. -SUBDIRS = +SUBDIRS = \ + test
LIBRT = @LIBRT@ LDADD = \ diff --git a/axfer/test/Makefile.am b/axfer/test/Makefile.am new file mode 100644 index 00000000..66b30ef8 --- /dev/null +++ b/axfer/test/Makefile.am @@ -0,0 +1,15 @@ +TESTS = \ + container-test + +check_PROGRAMS = \ + container-test + +container_test_SOURCES = \ + ../container.h \ + ../container.c \ + ../container-riff-wave.c \ + ../container-au.c \ + ../container-voc.c \ + ../container-raw.c \ + generator.c \ + container-test.c diff --git a/axfer/test/container-test.c b/axfer/test/container-test.c new file mode 100644 index 00000000..2c32c0fd --- /dev/null +++ b/axfer/test/container-test.c @@ -0,0 +1,299 @@ +/* + * container-io.c - a unit test for parser/builder of supported containers. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "../container.h" +#include "../misc.h" + +#include "generator.h" + +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> + +#include <assert.h> + +struct container_trial { + enum container_format format; + + struct container_context cntr; + bool verbose; +}; + +static void test_builder(struct container_context *cntr, + enum container_format format, const char *const name, + snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second, + void *frame_buffer, unsigned int frame_count, + bool verbose) +{ + snd_pcm_format_t sample; + unsigned int channels; + unsigned int rate; + uint64_t max_frame_count; + unsigned int handled_frame_count; + uint64_t total_frame_count; + int err; + + err = container_builder_init(cntr, name, format, verbose); + assert(err == 0); + + sample = sample_format; + channels = samples_per_frame; + rate = frames_per_second; + max_frame_count = 0; + err = container_context_pre_process(cntr, &sample, &channels, &rate, + &max_frame_count); + assert(err == 0); + assert(sample == sample_format); + assert(channels == samples_per_frame); + assert(rate == frames_per_second); + assert(max_frame_count > 0); + + handled_frame_count = frame_count; + err = container_context_process_frames(cntr, frame_buffer, + &handled_frame_count); + assert(err == 0); + assert(handled_frame_count > 0); + assert(handled_frame_count <= frame_count); + + total_frame_count = 0; + err = container_context_post_process(cntr, &total_frame_count); + assert(err == 0); + assert(total_frame_count == frame_count); + + container_context_destroy(cntr); +} + +static void test_parser(struct container_context *cntr, + enum container_format format, const char *const name, + snd_pcm_access_t access, snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second, + void *frame_buffer, unsigned int frame_count, + bool verbose) +{ + snd_pcm_format_t sample; + unsigned int channels; + unsigned int rate; + uint64_t total_frame_count; + unsigned int handled_frame_count; + int err; + + err = container_parser_init(cntr, name, verbose); + assert(err == 0); + + sample = sample_format; + channels = samples_per_frame; + rate = frames_per_second; + total_frame_count = 0; + err = container_context_pre_process(cntr, &sample, &channels, &rate, + &total_frame_count); + assert(err == 0); + assert(sample == sample_format); + assert(channels == samples_per_frame); + assert(rate == frames_per_second); + assert(total_frame_count == frame_count); + + handled_frame_count = total_frame_count; + err = container_context_process_frames(cntr, frame_buffer, + &handled_frame_count); + assert(err == 0); + assert(handled_frame_count == frame_count); + + total_frame_count = 0; + err = container_context_post_process(cntr, &total_frame_count); + assert(err == 0); + assert(total_frame_count == handled_frame_count); + + container_context_destroy(cntr); +} + +static int callback(struct test_generator *gen, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, void *frame_buffer, + unsigned int frame_count) +{ + static const unsigned int entries[] = { + [0] = 44100, + [1] = 48000, + [2] = 88200, + [3] = 96000, + [4] = 176400, + [5] = 192000, + }; + struct container_trial *trial = gen->private_data; + unsigned int frames_per_second; + const char *const name = "hoge"; + unsigned int size; + void *buf; + int i; + int err = 0; + + size = frame_count * samples_per_frame * + snd_pcm_format_physical_width(sample_format) / 8; + buf = malloc(size); + if (buf == NULL) + return -ENOMEM; + + /* Remove a result of a previous trial. */ + unlink(name); + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + frames_per_second = entries[i]; + + test_builder(&trial->cntr, trial->format, name, access, + sample_format, samples_per_frame, + frames_per_second, frame_buffer, frame_count, + trial->verbose); + + test_parser(&trial->cntr, trial->format, name, access, + sample_format, samples_per_frame, frames_per_second, + buf, frame_count, trial->verbose); + + err = memcmp(buf, frame_buffer, size); + assert(err == 0); + + unlink(name); + } + + free(buf); + + return 0; +} + +int main(int argc, const char *argv[]) +{ + static const uint64_t sample_format_masks[] = { + [CONTAINER_FORMAT_RIFF_WAVE] = + (1ul << SND_PCM_FORMAT_U8) | + (1ul << SND_PCM_FORMAT_S16_LE) | + (1ul << SND_PCM_FORMAT_S16_BE) | + (1ul << SND_PCM_FORMAT_S24_LE) | + (1ul << SND_PCM_FORMAT_S24_BE) | + (1ul << SND_PCM_FORMAT_S32_LE) | + (1ul << SND_PCM_FORMAT_S32_BE) | + (1ul << SND_PCM_FORMAT_FLOAT_LE) | + (1ul << SND_PCM_FORMAT_FLOAT_BE) | + (1ul << SND_PCM_FORMAT_FLOAT64_LE) | + (1ul << SND_PCM_FORMAT_FLOAT64_BE) | + (1ul << SND_PCM_FORMAT_MU_LAW) | + (1ul << SND_PCM_FORMAT_A_LAW) | + (1ul << SND_PCM_FORMAT_S24_3LE) | + (1ul << SND_PCM_FORMAT_S24_3BE) | + (1ul << SND_PCM_FORMAT_S20_3LE) | + (1ul << SND_PCM_FORMAT_S20_3BE) | + (1ul << SND_PCM_FORMAT_S18_3LE) | + (1ul << SND_PCM_FORMAT_S18_3BE), + [CONTAINER_FORMAT_AU] = + (1ul << SND_PCM_FORMAT_S8) | + (1ul << SND_PCM_FORMAT_S16_BE) | + (1ul << SND_PCM_FORMAT_S32_BE) | + (1ul << SND_PCM_FORMAT_FLOAT_BE) | + (1ul << SND_PCM_FORMAT_FLOAT64_BE) | + (1ul << SND_PCM_FORMAT_MU_LAW) | + (1ul << SND_PCM_FORMAT_A_LAW), + [CONTAINER_FORMAT_VOC] = + (1ul << SND_PCM_FORMAT_U8) | + (1ul << SND_PCM_FORMAT_S16_LE) | + (1ul << SND_PCM_FORMAT_MU_LAW) | + (1ul << SND_PCM_FORMAT_A_LAW), + [CONTAINER_FORMAT_RAW] = + (1ul << SND_PCM_FORMAT_S8) | + (1ul << SND_PCM_FORMAT_U8) | + (1ul << SND_PCM_FORMAT_S16_LE) | + (1ul << SND_PCM_FORMAT_S16_BE) | + (1ul << SND_PCM_FORMAT_U16_LE) | + (1ul << SND_PCM_FORMAT_U16_BE) | + (1ul << SND_PCM_FORMAT_S24_LE) | + (1ul << SND_PCM_FORMAT_S24_BE) | + (1ul << SND_PCM_FORMAT_U24_LE) | + (1ul << SND_PCM_FORMAT_U24_BE) | + (1ul << SND_PCM_FORMAT_S32_LE) | + (1ul << SND_PCM_FORMAT_S32_BE) | + (1ul << SND_PCM_FORMAT_U32_LE) | + (1ul << SND_PCM_FORMAT_U32_BE) | + (1ul << SND_PCM_FORMAT_FLOAT_LE) | + (1ul << SND_PCM_FORMAT_FLOAT_BE) | + (1ul << SND_PCM_FORMAT_FLOAT64_LE) | + (1ul << SND_PCM_FORMAT_FLOAT64_BE) | + (1ul << SND_PCM_FORMAT_IEC958_SUBFRAME_LE) | + (1ul << SND_PCM_FORMAT_IEC958_SUBFRAME_BE) | + (1ul << SND_PCM_FORMAT_MU_LAW) | + (1ul << SND_PCM_FORMAT_A_LAW) | + (1ul << SND_PCM_FORMAT_S24_3LE) | + (1ul << SND_PCM_FORMAT_S24_3BE) | + (1ul << SND_PCM_FORMAT_U24_3LE) | + (1ul << SND_PCM_FORMAT_U24_3BE) | + (1ul << SND_PCM_FORMAT_S20_3LE) | + (1ul << SND_PCM_FORMAT_S20_3BE) | + (1ul << SND_PCM_FORMAT_U20_3LE) | + (1ul << SND_PCM_FORMAT_U20_3BE) | + (1ul << SND_PCM_FORMAT_S18_3LE) | + (1ul << SND_PCM_FORMAT_S18_3BE) | + (1ul << SND_PCM_FORMAT_U18_3LE) | + (1ul << SND_PCM_FORMAT_U18_3BE) | + (1ul << SND_PCM_FORMAT_DSD_U8) | + (1ul << SND_PCM_FORMAT_DSD_U16_LE) | + (1ul << SND_PCM_FORMAT_DSD_U32_LE) | + (1ul << SND_PCM_FORMAT_DSD_U16_BE) | + (1ul << SND_PCM_FORMAT_DSD_U32_BE), + }; + static const uint64_t access_mask = + (1ul << SND_PCM_ACCESS_MMAP_INTERLEAVED) | + (1ul << SND_PCM_ACCESS_RW_INTERLEAVED); + struct test_generator gen = {0}; + struct container_trial *trial; + int i; + int begin; + int end; + bool verbose; + int err; + + if (argc > 1) { + char *term; + begin = strtol(argv[1], &term, 10); + if (errno || *term != '\0') + return EXIT_FAILURE; + if (begin < CONTAINER_FORMAT_RIFF_WAVE && + begin > CONTAINER_FORMAT_RAW) + return -EXIT_FAILURE; + end = begin + 1; + verbose = true; + } else { + begin = CONTAINER_FORMAT_RIFF_WAVE; + end = CONTAINER_FORMAT_RAW + 1; + verbose = false; + } + + for (i = begin; i < end; ++i) { + err = generator_context_init(&gen, access_mask, + sample_format_masks[i], + 1, 128, 23, 4500, 1024, + sizeof(struct container_trial)); + if (err >= 0) { + trial = gen.private_data; + trial->format = i; + trial->verbose = verbose; + err = generator_context_run(&gen, callback); + } + + generator_context_destroy(&gen); + + if (err < 0) + break; + } + + if (err < 0) { + printf("%s\n", strerror(-err)); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/axfer/test/generator.c b/axfer/test/generator.c new file mode 100644 index 00000000..c26e1701 --- /dev/null +++ b/axfer/test/generator.c @@ -0,0 +1,260 @@ +/* + * allocator.h - a header of a generator for test with buffers of PCM frames. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "generator.h" + +#include <stdlib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <unistd.h> + +int generator_context_init(struct test_generator *gen, + uint64_t access_mask, uint64_t sample_format_mask, + unsigned int min_samples_per_frame, + unsigned int max_samples_per_frame, + unsigned int min_frame_count, + unsigned int max_frame_count, + unsigned int step_frame_count, + unsigned int private_size) +{ + gen->fd = open("/dev/urandom", O_RDONLY); + if (gen->fd < 0) + return -errno; + + gen->private_data = malloc(private_size); + if (gen->private_data == NULL) + return -ENOMEM; + memset(gen->private_data, 0, private_size); + + gen->access_mask = access_mask; + gen->sample_format_mask = sample_format_mask; + gen->min_samples_per_frame = min_samples_per_frame; + gen->max_samples_per_frame = max_samples_per_frame; + gen->min_frame_count = min_frame_count; + gen->max_frame_count = max_frame_count; + gen->step_frame_count = step_frame_count; + + return 0; +} + +static void *allocate_buf(snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frame_count) +{ + unsigned int bytes_per_sample; + + bytes_per_sample = snd_pcm_format_physical_width(sample_format) / 8; + + return calloc(samples_per_frame * frame_count, bytes_per_sample); +} + +static void *allocate_vector(snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frame_count) +{ + unsigned int bytes_per_sample; + char **bufs; + int i; + + bytes_per_sample = snd_pcm_format_physical_width(sample_format) / 8; + + bufs = calloc(samples_per_frame, sizeof(char *)); + if (bufs == NULL) + return NULL; + + for (i = 0; i < samples_per_frame; ++i) { + bufs[i] = calloc(frame_count, bytes_per_sample); + if (bufs[i] == NULL) { + for (; i >= 0; --i) + free(bufs[i]); + free(bufs); + return NULL; + } + } + + return bufs; +} + +static int fill_buf(int fd, void *frame_buffer, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, unsigned int frame_count) +{ + unsigned int size; + int len; + + size = snd_pcm_format_physical_width(sample_format) / 8 * + samples_per_frame * frame_count; + while (size > 0) { + len = read(fd, frame_buffer, size); + if (len < 0) + return len; + size -= len; + } + + return 0; +} + +static int fill_vector(int fd, void *frame_buffer, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, unsigned int frame_count) +{ + char **bufs = frame_buffer; + unsigned int size; + int len; + int i; + + for (i = 0; i < samples_per_frame; ++i) { + size = frame_count * + snd_pcm_format_physical_width(sample_format) / 8; + + while (size > 0) { + len = read(fd, bufs[i], size); + if (len < 0) + return len; + size -= len; + } + } + + return 0; +} + +static void deallocate_buf(void *frame_buffer, unsigned int samples_per_frame) +{ + free(frame_buffer); +} + +static void deallocate_vector(void *frame_buffer, + unsigned int samples_per_frame) +{ + char **bufs = frame_buffer; + int i; + + for (i = 0; i < samples_per_frame; ++i) + free(bufs[i]); + + free(bufs); +} + +static int test_frame_count(struct test_generator *gen, + snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame) +{ + void *(*allocator)(snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frame_count); + int (*fill)(int fd, void *frame_buffer, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, unsigned int frame_count); + void (*deallocator)(void *frame_buffer, unsigned int samples_per_frame); + void *frame_buffer; + int i; + int err = 0; + + if (access != SND_PCM_ACCESS_RW_NONINTERLEAVED) { + allocator = allocate_buf; + fill = fill_buf; + deallocator = deallocate_buf; + } else { + allocator = allocate_vector; + fill = fill_vector; + deallocator = deallocate_vector; + } + + frame_buffer = allocator(access, sample_format, samples_per_frame, + gen->max_frame_count); + if (frame_buffer == NULL) + return -ENOMEM; + + err = fill(gen->fd, frame_buffer, access, sample_format, + samples_per_frame, gen->max_frame_count); + if (err < 0) + goto end; + + + for (i = gen->min_frame_count; + i <= gen->max_frame_count; i += gen->step_frame_count) { + err = gen->cb(gen, access ,sample_format, samples_per_frame, + frame_buffer, i); + if (err < 0) + break; + } +end: + deallocator(frame_buffer, samples_per_frame); + + return err; +} + +static int test_samples_per_frame(struct test_generator *gen, + snd_pcm_access_t access, + snd_pcm_format_t sample_format) +{ + int i; + int err = 0; + + for (i = gen->min_samples_per_frame; + i <= gen->max_samples_per_frame; ++i) { + err = test_frame_count(gen, access, sample_format, i); + if (err < 0) + break; + } + + return err; +} + +static int test_sample_format(struct test_generator *gen, + snd_pcm_access_t access) +{ + int i; + int err = 0; + + for (i = 0; i <= SND_PCM_FORMAT_LAST; ++i) { + if (!((1ul << i) & gen->sample_format_mask)) + continue; + + err = test_samples_per_frame(gen, access, i); + if (err < 0) + break; + } + + return err; +} + +static int test_access(struct test_generator *gen) +{ + int i; + int err = 0; + + for (i = 0; i <= SND_PCM_ACCESS_LAST; ++i) { + if (!((1ul << i) & gen->access_mask)) + continue; + + err = test_sample_format(gen, i); + if (err < 0) + break; + } + return err; +} + +int generator_context_run(struct test_generator *gen, generator_cb_t cb) +{ + gen->cb = cb; + return test_access(gen); +} + +void generator_context_destroy(struct test_generator *gen) +{ + free(gen->private_data); + close(gen->fd); +} diff --git a/axfer/test/generator.h b/axfer/test/generator.h new file mode 100644 index 00000000..145356de --- /dev/null +++ b/axfer/test/generator.h @@ -0,0 +1,47 @@ +/* + * generator.c - a generator for test with buffers of PCM frames. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_TEST_GENERATOR__H_ +#define __ALSA_UTILS_AXFER_TEST_GENERATOR__H_ + +#include <stdint.h> +#include <alsa/asoundlib.h> + +struct test_generator; +typedef int (*generator_cb_t)(struct test_generator *gen, + snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + void *frame_buffer, unsigned int frame_count); + +struct test_generator { + int fd; + uint64_t access_mask; + uint64_t sample_format_mask; + unsigned int min_samples_per_frame; + unsigned int max_samples_per_frame; + unsigned int min_frame_count; + unsigned int max_frame_count; + unsigned int step_frame_count; + + generator_cb_t cb; + void *private_data; +}; + +int generator_context_init(struct test_generator *gen, + uint64_t access_mask, uint64_t sample_format_mask, + unsigned int min_samples_per_frame, + unsigned int max_samples_per_frame, + unsigned int min_frame_count, + unsigned int max_frame_count, + unsigned int step_frame_count, + unsigned int private_size); +int generator_context_run(struct test_generator *gen, generator_cb_t cb); +void generator_context_destroy(struct test_generator *gen); + +#endif diff --git a/configure.ac b/configure.ac index 41fac0cb..bcc180b1 100644 --- a/configure.ac +++ b/configure.ac @@ -426,4 +426,5 @@ AC_OUTPUT(Makefile alsactl/Makefile alsactl/init/Makefile \ utils/alsa-utils.spec seq/Makefile seq/aconnect/Makefile \ seq/aplaymidi/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \ speaker-test/Makefile speaker-test/samples/Makefile \ - alsaloop/Makefile alsa-info/Makefile axfer/Makefile) + alsaloop/Makefile alsa-info/Makefile \ + axfer/Makefile axfer/test/Makefile)
In current aplay, several files can be handled as source of data frames for playback, or destination of captured data frames by an option '--separate-channels' (-I).
On the other hand, in ALSA PCM kernel/user interface, several types of buffer are used to communicate between application/hardware; - mapped page frame for data frames with interleaved alignment - mapped page frame for data frames with non-interleaved alignment - buffer in user space for data frames with interleaved alignment - a list of buffer in user space for data frames with non-interleaved alignment
This commit adds a common interface, named as 'mapper' to convert frame alignment between these two sides. This interface includes two types; 'muxer' and 'demuxer'. The 'muxer' is for playback direction, to construct playback buffer with PCM frames from several files. The 'demuxer' is for capture direction, to split PCM frames from capture buffer to each of file. Unlike multimedia containers such as MPEG 2/4 Systems, the 'muxer' and 'demuxer' are for playback/capture buffer, not for file contents.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 7 ++- axfer/mapper.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/mapper.h | 79 ++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 axfer/mapper.c create mode 100644 axfer/mapper.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 55fcf716..cb4b188d 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -17,7 +17,8 @@ LDADD = \ noinst_HEADERS = \ misc.h \ subcmd.h \ - container.h + container.h \ + mapper.h
axfer_SOURCES = \ misc.h \ @@ -29,4 +30,6 @@ axfer_SOURCES = \ container-riff-wave.c \ container-au.c \ container-voc.c \ - container-raw.c + container-raw.c \ + mapper.h \ + mapper.c diff --git a/axfer/mapper.c b/axfer/mapper.c new file mode 100644 index 00000000..3073c713 --- /dev/null +++ b/axfer/mapper.c @@ -0,0 +1,125 @@ +/* + * mapper.c - an interface of muxer/demuxer between buffer with data frames and + * formatted files. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "mapper.h" +#include "misc.h" + +#include <stdio.h> + +static const char *const mapper_type_labels[] = { + [MAPPER_TYPE_MUXER] = "muxer", + [MAPPER_TYPE_DEMUXER] = "demuxer", +}; + +static const char *const mapper_target_labels[] = { + [MAPPER_TARGET_COUNT] = "", +}; + +int mapper_context_init(struct mapper_context *mapper, + enum mapper_type type, unsigned int cntr_count, + unsigned int verbose) +{ + const struct mapper_data *data = NULL; + + assert(mapper); + assert(cntr_count > 0); + + /* Detect forgotten to destruct. */ + assert(mapper->private_data == NULL); + + memset(mapper, 0, sizeof(*mapper)); + + mapper->ops = &data->ops; + mapper->type = type; + + mapper->private_data = malloc(data->private_size); + if (mapper->private_data == NULL) + return -ENOMEM; + memset(mapper->private_data, 0, data->private_size); + + mapper->cntr_count = cntr_count; + mapper->verbose = verbose; + + return 0; +} + +int mapper_context_pre_process(struct mapper_context *mapper, + snd_pcm_access_t access, + unsigned int bytes_per_sample, + unsigned int samples_per_frame, + unsigned int frames_per_buffer, + struct container_context *cntrs) +{ + int err; + + assert(mapper); + assert(access >= SND_PCM_ACCESS_MMAP_INTERLEAVED); + assert(access <= SND_PCM_ACCESS_RW_NONINTERLEAVED); + assert(bytes_per_sample > 0); + assert(samples_per_frame > 0); + assert(cntrs); + + mapper->access = access; + mapper->bytes_per_sample = bytes_per_sample; + mapper->samples_per_frame = samples_per_frame; + mapper->frames_per_buffer = frames_per_buffer; + + err = mapper->ops->pre_process(mapper, cntrs, mapper->cntr_count); + if (err < 0) + return err; + + if (mapper->verbose > 0) { + fprintf(stderr, "Mapper: %s\n", + mapper_type_labels[mapper->type]); + fprintf(stderr, " target: %s\n", + mapper_target_labels[mapper->target]); + fprintf(stderr, " access: %s\n", + snd_pcm_access_name(mapper->access)); + fprintf(stderr, " bytes/sample: %u\n", + mapper->bytes_per_sample); + fprintf(stderr, " samples/frame: %u\n", + mapper->samples_per_frame); + fprintf(stderr, " frames/buffer: %lu\n", + mapper->frames_per_buffer); + } + + return 0; +} + +int mapper_context_process_frames(struct mapper_context *mapper, + void *frame_buffer, + unsigned int *frame_count, + struct container_context *cntrs) +{ + assert(mapper); + assert(frame_buffer); + assert(frame_count); + assert(*frame_count <= mapper->frames_per_buffer); + assert(cntrs); + + return mapper->ops->process_frames(mapper, frame_buffer, frame_count, + cntrs, mapper->cntr_count); +} + +void mapper_context_post_process(struct mapper_context *mapper) +{ + assert(mapper); + + if (mapper->ops && mapper->ops->post_process) + mapper->ops->post_process(mapper); +} + +void mapper_context_destroy(struct mapper_context *mapper) +{ + assert(mapper); + + if (mapper->private_data) + free(mapper->private_data); + mapper->private_data = NULL; +} diff --git a/axfer/mapper.h b/axfer/mapper.h new file mode 100644 index 00000000..0a6c7f1c --- /dev/null +++ b/axfer/mapper.h @@ -0,0 +1,79 @@ +/* + * mapper.h - an interface of muxer/demuxer between buffer with data frames and + * formatted files. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_MAPPER__H_ +#define __ALSA_UTILS_AXFER_MAPPER__H_ + +#include "container.h" + +enum mapper_type { + MAPPER_TYPE_MUXER = 0, + MAPPER_TYPE_DEMUXER, + MAPPER_TYPE_COUNT, +}; + +enum mapper_target { + MAPPER_TARGET_COUNT, +}; + +struct mapper_ops; + +struct mapper_context { + enum mapper_type type; + enum mapper_target target; + const struct mapper_ops *ops; + unsigned int private_size; + + void *private_data; + unsigned int cntr_count; + + /* A part of parameters of PCM substream. */ + snd_pcm_access_t access; + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + snd_pcm_uframes_t frames_per_buffer; + + unsigned int verbose; +}; + +int mapper_context_init(struct mapper_context *mapper, + enum mapper_type type, unsigned int cntr_count, + unsigned int verbose); +int mapper_context_pre_process(struct mapper_context *mapper, + snd_pcm_access_t access, + unsigned int bytes_per_sample, + unsigned int samples_per_frame, + unsigned int frames_per_buffer, + struct container_context *cntrs); +int mapper_context_process_frames(struct mapper_context *mapper, + void *frame_buffer, + unsigned int *frame_count, + struct container_context *cntrs); +void mapper_context_post_process(struct mapper_context *mapper); +void mapper_context_destroy(struct mapper_context *mapper); + +/* For internal use in 'mapper' module. */ + +struct mapper_ops { + int (*pre_process)(struct mapper_context *mapper, + struct container_context *cntrs, + unsigned int cntr_count); + int (*process_frames)(struct mapper_context *mapper, + void *frame_buffer, unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count); + void (*post_process)(struct mapper_context *mapper); +}; + +struct mapper_data { + struct mapper_ops ops; + unsigned int private_size; +}; + +#endif
In usual use case of aplay, single file is used to playback or capture data frames.
This commit adds support of single type mapper for this use case. All of supported file format can include data frame with interleaved alignment, thus this mapper have a functionality to convert from several types of data frame alignment to interleaved alignment or vise versa. When handling non-interleaved buffer, a caller should use an array of buffer for each of channels with non-interleaved data frames.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/mapper-single.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/mapper.c | 14 +++- axfer/mapper.h | 4 ++ 4 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 axfer/mapper-single.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index cb4b188d..baf9b898 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -32,4 +32,5 @@ axfer_SOURCES = \ container-voc.c \ container-raw.c \ mapper.h \ - mapper.c + mapper.c \ + mapper-single.c diff --git a/axfer/mapper-single.c b/axfer/mapper-single.c new file mode 100644 index 00000000..f0b5c735 --- /dev/null +++ b/axfer/mapper-single.c @@ -0,0 +1,195 @@ +/* + * mapper-single.c - a muxer/demuxer for single containers. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "mapper.h" +#include "misc.h" + +struct single_state { + void (*align_frames)(void *frame_buf, unsigned int frame_count, + char *buf, unsigned int bytes_per_sample, + unsigned int samples_per_frame); + char *buf; +}; + +static void align_to_vector(void *frame_buf, unsigned int frame_count, + char *src, unsigned int bytes_per_sample, + unsigned samples_per_frame) +{ + char **dst_bufs = frame_buf; + char *dst; + unsigned int src_pos; + unsigned int dst_pos; + int i, j; + + /* src: interleaved => dst: a set of interleaved buffers. */ + for (i = 0; i < samples_per_frame; ++i) { + dst = dst_bufs[i]; + for (j = 0; j < frame_count; ++j) { + src_pos = bytes_per_sample * (samples_per_frame * j + i); + dst_pos = bytes_per_sample * j; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static void align_from_vector(void *frame_buf, unsigned int frame_count, + char *dst, unsigned int bytes_per_sample, + unsigned int samples_per_frame) +{ + char **src_bufs = frame_buf; + char *src; + unsigned int dst_pos; + unsigned int src_pos; + int i, j; + + /* src: a set of interleaved buffers => dst:interleaved. */ + for (i = 0; i < samples_per_frame; ++i) { + src = src_bufs[i]; + for (j = 0; j < frame_count; ++j) { + src_pos = bytes_per_sample * j; + dst_pos = bytes_per_sample * (samples_per_frame * j + i); + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static int single_pre_process(struct mapper_context *mapper, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct single_state *state = mapper->private_data; + unsigned int bytes_per_buffer; + + if (cntrs->bytes_per_sample != mapper->bytes_per_sample || + cntrs->samples_per_frame != mapper->samples_per_frame) + return -EINVAL; + + /* Decide method to align frames. */ + if (mapper->type == MAPPER_TYPE_DEMUXER) { + if (mapper->access == SND_PCM_ACCESS_RW_NONINTERLEAVED || + mapper->access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) + state->align_frames = align_from_vector; + else if (mapper->access == SND_PCM_ACCESS_RW_INTERLEAVED || + mapper->access == SND_PCM_ACCESS_MMAP_INTERLEAVED) + state->align_frames = NULL; + else + return -EINVAL; + } else { + if (mapper->access == SND_PCM_ACCESS_RW_NONINTERLEAVED || + mapper->access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) + state->align_frames = align_to_vector; + else if (mapper->access == SND_PCM_ACCESS_RW_INTERLEAVED || + mapper->access == SND_PCM_ACCESS_MMAP_INTERLEAVED) + state->align_frames = NULL; + else + return -EINVAL; + } + + if (state->align_frames) { + /* Allocate intermediate buffer as the same size as a period. */ + bytes_per_buffer = mapper->bytes_per_sample * + mapper->samples_per_frame * + mapper->frames_per_buffer; + state->buf = malloc(bytes_per_buffer); + if (state->buf == NULL) + return -ENOMEM; + memset(state->buf, 0, bytes_per_buffer); + } + + return 0; +} + +static int single_muxer_process_frames(struct mapper_context *mapper, + void *frame_buf, + unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct single_state *state = mapper->private_data; + void *src; + int err; + + /* + * If need to align PCM frames, process PCM frames to the intermediate + * buffer once. + */ + if (!state->align_frames) { + /* The most likely. */ + src = frame_buf; + } else { + src = state->buf; + } + err = container_context_process_frames(cntrs, src, frame_count); + if (err < 0) + return err; + + /* Unlikely. */ + if (src != frame_buf && *frame_count > 0) + state->align_frames(frame_buf, *frame_count, src, + mapper->bytes_per_sample, + mapper->samples_per_frame); + + return 0; +} + +static int single_demuxer_process_frames(struct mapper_context *mapper, + void *frame_buf, + unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct single_state *state = mapper->private_data; + void *dst; + + /* + * If need to align PCM frames, process PCM frames to the intermediate + * buffer once. + */ + if (!state->align_frames) { + /* The most likely. */ + dst = frame_buf; + } else { + state->align_frames(frame_buf, *frame_count, state->buf, + mapper->bytes_per_sample, + mapper->samples_per_frame); + dst = state->buf; + } + + return container_context_process_frames(cntrs, dst, frame_count); +} + +static void single_post_process(struct mapper_context *mapper) +{ + struct single_state *state = mapper->private_data; + + if (state->buf) + free(state->buf); + + state->buf = NULL; + state->align_frames = NULL; +} + +const struct mapper_data mapper_muxer_single = { + .ops = { + .pre_process = single_pre_process, + .process_frames = single_muxer_process_frames, + .post_process = single_post_process, + }, + .private_size = sizeof(struct single_state), +}; + +const struct mapper_data mapper_demuxer_single = { + .ops = { + .pre_process = single_pre_process, + .process_frames = single_demuxer_process_frames, + .post_process = single_post_process, + }, + .private_size = sizeof(struct single_state), +}; diff --git a/axfer/mapper.c b/axfer/mapper.c index 3073c713..3ba2d5e1 100644 --- a/axfer/mapper.c +++ b/axfer/mapper.c @@ -18,7 +18,7 @@ static const char *const mapper_type_labels[] = { };
static const char *const mapper_target_labels[] = { - [MAPPER_TARGET_COUNT] = "", + [MAPPER_TARGET_SINGLE] = "single", };
int mapper_context_init(struct mapper_context *mapper, @@ -35,6 +35,18 @@ int mapper_context_init(struct mapper_context *mapper,
memset(mapper, 0, sizeof(*mapper));
+ if (type == MAPPER_TYPE_MUXER) { + if (cntr_count == 1) { + data = &mapper_muxer_single; + mapper->target = MAPPER_TARGET_SINGLE; + } + } else { + if (cntr_count == 1) { + data = &mapper_demuxer_single; + mapper->target = MAPPER_TARGET_SINGLE; + } + } + mapper->ops = &data->ops; mapper->type = type;
diff --git a/axfer/mapper.h b/axfer/mapper.h index 0a6c7f1c..2e553666 100644 --- a/axfer/mapper.h +++ b/axfer/mapper.h @@ -19,6 +19,7 @@ enum mapper_type { };
enum mapper_target { + MAPPER_TARGET_SINGLE = 0, MAPPER_TARGET_COUNT, };
@@ -76,4 +77,7 @@ struct mapper_data { unsigned int private_size; };
+extern const struct mapper_data mapper_muxer_single; +extern const struct mapper_data mapper_demuxer_single; + #endif
This commit adds support of mapper for 'multiple' target. This handles several files via 'container' functions, and constructs data frame buffer for playback, or splits data frames from data frame buffer for capture. When playback source files includes data frames with several channels, the first channel is used to construct buffer. For capture direction, each of channel of data frame is stored in one file, thus the file includes one channel of data frame. When handling non-interleaved buffer, a caller should use an array of buffer for each of channels with non-interleaved data frames.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/mapper-multiple.c | 271 ++++++++++++++++++++++++++++++++++++++++++++++++ axfer/mapper.c | 15 +++ axfer/mapper.h | 4 + 4 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 axfer/mapper-multiple.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index baf9b898..f17e59b0 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -33,4 +33,5 @@ axfer_SOURCES = \ container-raw.c \ mapper.h \ mapper.c \ - mapper-single.c + mapper-single.c \ + mapper-multiple.c diff --git a/axfer/mapper-multiple.c b/axfer/mapper-multiple.c new file mode 100644 index 00000000..c40d0bd6 --- /dev/null +++ b/axfer/mapper-multiple.c @@ -0,0 +1,271 @@ +/* + * mapper-multiple.c - a muxer/demuxer for multiple containers. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "mapper.h" +#include "misc.h" + +struct multiple_state { + void (*align_frames)(void *frame_buf, unsigned int frame_count, + char **buf, unsigned int bytes_per_sample, + struct container_context *cntrs, + unsigned int cntr_count); + char **bufs; + unsigned int cntr_count; +}; + +static void align_to_i(void *frame_buf, unsigned int frame_count, + char **src_bufs, unsigned int bytes_per_sample, + struct container_context *cntrs, unsigned int cntr_count) +{ + char *dst = frame_buf; + char *src; + unsigned int dst_pos; + unsigned int src_pos; + struct container_context *cntr; + int i, j; + + /* + * src: first channel in each of interleaved buffers in containers => + * dst:interleaved. + */ + for (i = 0; i < cntr_count; ++i) { + src = src_bufs[i]; + cntr = cntrs + i; + + for (j = 0; j < frame_count; ++j) { + /* Use first src channel for each of dst channel. */ + src_pos = bytes_per_sample * cntr->samples_per_frame * j; + dst_pos = bytes_per_sample * (cntr_count * j + i); + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static void align_from_i(void *frame_buf, unsigned int frame_count, + char **dst_bufs, unsigned int bytes_per_sample, + struct container_context *cntrs, + unsigned int cntr_count) +{ + char *src = frame_buf; + char *dst; + unsigned int src_pos; + unsigned int dst_pos; + struct container_context *cntr; + int i, j; + + for (i = 0; i < cntr_count; ++i) { + dst = dst_bufs[i]; + cntr = cntrs + i; + + for (j = 0; j < frame_count; ++j) { + /* Use first src channel for each of dst channel. */ + src_pos = bytes_per_sample * (cntr_count * j + i); + dst_pos = bytes_per_sample * cntr->samples_per_frame * j; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static int multiple_pre_process(struct mapper_context *mapper, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct multiple_state *state = mapper->private_data; + struct container_context *cntr; + int i; + + /* + * Additionally, format of samples in the containers should be the same + * as the format in PCM substream. + */ + for (i = 0; i < cntr_count; ++i) { + cntr = cntrs + i; + if (mapper->bytes_per_sample != cntr->bytes_per_sample) + return -EINVAL; + } + state->cntr_count = cntr_count; + + /* Decide method to align frames. */ + if (mapper->type == MAPPER_TYPE_DEMUXER) { + if (mapper->access == SND_PCM_ACCESS_RW_INTERLEAVED || + mapper->access == SND_PCM_ACCESS_MMAP_INTERLEAVED) + state->align_frames = align_from_i; + else if (mapper->access == SND_PCM_ACCESS_RW_NONINTERLEAVED || + mapper->access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) + state->align_frames = NULL; + else + return -EINVAL; + } else { + if (mapper->access == SND_PCM_ACCESS_RW_INTERLEAVED || + mapper->access == SND_PCM_ACCESS_MMAP_INTERLEAVED) + state->align_frames = align_to_i; + else if (mapper->access == SND_PCM_ACCESS_RW_NONINTERLEAVED || + mapper->access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) + state->align_frames = NULL; + else + return -EINVAL; + } + + if (state->align_frames) { + /* + * Furthermore, in demuxer case, each container should be + * configured to store one sample per frame. + */ + if (mapper->type == MAPPER_TYPE_DEMUXER) { + for (i = 0; i < cntr_count; ++i) { + cntr = cntrs + i; + if (cntrs->samples_per_frame != 1) + return -EINVAL; + } + } + + state->bufs = calloc(cntr_count, sizeof(char *)); + if (state->bufs == NULL) + return -ENOMEM; + + for (i = 0; i < cntr_count; ++i) { + unsigned int bytes_per_buffer; + + /* + * Allocate intermediate buffer as the same size as a + * period for each of containers. + */ + cntr = cntrs + i; + + bytes_per_buffer = mapper->bytes_per_sample * + cntr->samples_per_frame * + mapper->frames_per_buffer; + + state->bufs[i] = malloc(bytes_per_buffer); + if (state->bufs[i] == NULL) + return -ENOMEM; + memset(state->bufs[i], 0, bytes_per_buffer); + } + } + + return 0; +} + +static int process_containers(char **src_bufs, unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct container_context *cntr; + char *src; + int i; + int err = 0; + + /* TODO: arrangement for *frame_count. */ + for (i = 0; i < cntr_count; ++i) { + cntr = &cntrs[i]; + src = src_bufs[i]; + + err = container_context_process_frames(cntr, src, frame_count); + if (err < 0) + break; + } + + return err; +} + +static int multiple_muxer_process_frames(struct mapper_context *mapper, + void *frame_buf, + unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct multiple_state *state = mapper->private_data; + char **src_bufs; + int err; + + /* + * If need to align PCM frames, process PCM frames to the intermediate + * buffer once. + */ + if (!state->align_frames) { + /* The most likely. */ + src_bufs = frame_buf; + } else { + src_bufs = state->bufs; + } + err = process_containers(src_bufs, frame_count, cntrs, cntr_count); + if (err < 0) + return err; + + /* Unlikely. */ + if (src_bufs != frame_buf && *frame_count > 0) { + state->align_frames(frame_buf, *frame_count, src_bufs, + mapper->bytes_per_sample, cntrs, + cntr_count); + } + + return 0; +} + +static int multiple_demuxer_process_frames(struct mapper_context *mapper, + void *frame_buf, + unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct multiple_state *state = mapper->private_data; + char **dst_bufs; + + /* + * If need to align PCM frames, process PCM frames to the intermediate + * buffer once. + */ + if (!state->align_frames) { + /* The most likely. */ + dst_bufs = frame_buf; + } else { + dst_bufs = state->bufs; + state->align_frames(frame_buf, *frame_count, dst_bufs, + mapper->bytes_per_sample, cntrs, + cntr_count); + } + + return process_containers(dst_bufs, frame_count, cntrs, cntr_count); +} + +static void multiple_post_process(struct mapper_context *mapper) +{ + struct multiple_state *state = mapper->private_data; + int i; + + if (state->bufs) { + for (i = 0; i < state->cntr_count; ++i) { + if (state->bufs[i]) + free(state->bufs[i]); + } + free(state->bufs); + } + + state->bufs = NULL; + state->align_frames = NULL; +} + +const struct mapper_data mapper_muxer_multiple = { + .ops = { + .pre_process = multiple_pre_process, + .process_frames = multiple_muxer_process_frames, + .post_process = multiple_post_process, + }, + .private_size = sizeof(struct multiple_state), +}; + +const struct mapper_data mapper_demuxer_multiple = { + .ops = { + .pre_process = multiple_pre_process, + .process_frames = multiple_demuxer_process_frames, + .post_process = multiple_post_process, + }, + .private_size = sizeof(struct multiple_state), +}; diff --git a/axfer/mapper.c b/axfer/mapper.c index 3ba2d5e1..20baef85 100644 --- a/axfer/mapper.c +++ b/axfer/mapper.c @@ -19,6 +19,7 @@ static const char *const mapper_type_labels[] = {
static const char *const mapper_target_labels[] = { [MAPPER_TARGET_SINGLE] = "single", + [MAPPER_TARGET_MULTIPLE] = "multiple", };
int mapper_context_init(struct mapper_context *mapper, @@ -39,11 +40,17 @@ int mapper_context_init(struct mapper_context *mapper, if (cntr_count == 1) { data = &mapper_muxer_single; mapper->target = MAPPER_TARGET_SINGLE; + } else { + data = &mapper_muxer_multiple; + mapper->target = MAPPER_TARGET_MULTIPLE; } } else { if (cntr_count == 1) { data = &mapper_demuxer_single; mapper->target = MAPPER_TARGET_SINGLE; + } else { + data = &mapper_demuxer_multiple; + mapper->target = MAPPER_TARGET_MULTIPLE; } }
@@ -77,6 +84,14 @@ int mapper_context_pre_process(struct mapper_context *mapper, assert(samples_per_frame > 0); assert(cntrs);
+ /* + * The purpose of multiple target is to mux/demux each channels to/from + * containers. + */ + if (mapper->target == MAPPER_TARGET_MULTIPLE && + samples_per_frame != mapper->cntr_count) + return -EINVAL; + mapper->access = access; mapper->bytes_per_sample = bytes_per_sample; mapper->samples_per_frame = samples_per_frame; diff --git a/axfer/mapper.h b/axfer/mapper.h index 2e553666..3b32e93c 100644 --- a/axfer/mapper.h +++ b/axfer/mapper.h @@ -20,6 +20,7 @@ enum mapper_type {
enum mapper_target { MAPPER_TARGET_SINGLE = 0, + MAPPER_TARGET_MULTIPLE, MAPPER_TARGET_COUNT, };
@@ -80,4 +81,7 @@ struct mapper_data { extern const struct mapper_data mapper_muxer_single; extern const struct mapper_data mapper_demuxer_single;
+extern const struct mapper_data mapper_muxer_multiple; +extern const struct mapper_data mapper_demuxer_multiple; + #endif
In former commits, mapper module gets supports of muxer/demuxer for single/multiple targets for playback source or capture destination. This commit adds a unit test for them. This includes positive test cases only. The test cases actually generate I/O to file systems for many test cases. It takes a bit long time to finish.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/test/Makefile.am | 20 +- axfer/test/mapper-test.c | 491 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 509 insertions(+), 2 deletions(-) create mode 100644 axfer/test/mapper-test.c
diff --git a/axfer/test/Makefile.am b/axfer/test/Makefile.am index 66b30ef8..76a93bfc 100644 --- a/axfer/test/Makefile.am +++ b/axfer/test/Makefile.am @@ -1,8 +1,10 @@ TESTS = \ - container-test + container-test \ + mapper-test
check_PROGRAMS = \ - container-test + container-test \ + mapper-test
container_test_SOURCES = \ ../container.h \ @@ -13,3 +15,17 @@ container_test_SOURCES = \ ../container-raw.c \ generator.c \ container-test.c + +mapper_test_SOURCES = \ + ../container.h \ + ../container.c \ + ../container-riff-wave.c \ + ../container-au.c \ + ../container-voc.c \ + ../container-raw.c \ + ../mapper.h \ + ../mapper.c \ + ../mapper-single.c \ + ../mapper-multiple.c \ + generator.c \ + mapper-test.c diff --git a/axfer/test/mapper-test.c b/axfer/test/mapper-test.c new file mode 100644 index 00000000..554e638e --- /dev/null +++ b/axfer/test/mapper-test.c @@ -0,0 +1,491 @@ +/* + * mapper-io.c - a unit test for muxer/demuxer for PCM frames on buffer. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "../mapper.h" +#include "../misc.h" + +#include "generator.h" + +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> + +#include <assert.h> + +struct mapper_trial { + enum container_format cntr_format; + struct container_context *cntrs; + + char **paths; + + struct mapper_context mapper; + bool verbose; +}; + +static void test_demuxer(struct mapper_context *mapper, snd_pcm_access_t access, + unsigned int bytes_per_sample, + unsigned int samples_per_frame, + unsigned int frames_per_buffer, + void *frame_buffer, unsigned int frame_count, + struct container_context *cntrs, + unsigned int cntr_count, bool verbose) +{ + unsigned int total_frame_count; + int err; + + err = mapper_context_init(mapper, MAPPER_TYPE_DEMUXER, cntr_count, + verbose); + assert(err == 0); + + err = mapper_context_pre_process(mapper, access, bytes_per_sample, + samples_per_frame, frames_per_buffer, + cntrs); + assert(err == 0); + + total_frame_count = frame_count; + err = mapper_context_process_frames(mapper, frame_buffer, + &total_frame_count, cntrs); + assert(err == 0); + assert(total_frame_count == frame_count); + + mapper_context_post_process(mapper); + mapper_context_destroy(mapper); +} + +static int test_demux(struct mapper_trial *trial, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second, + unsigned int frames_per_buffer, + void *frame_buffer, unsigned int frame_count, + unsigned int cntr_count) +{ + struct container_context *cntrs = trial->cntrs; + char **paths = trial->paths; + enum container_format cntr_format = trial->cntr_format; + unsigned int bytes_per_sample; + uint64_t total_frame_count; + int i; + int err; + + for (i = 0; i < cntr_count; ++i) { + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; + + err = container_builder_init(cntrs + i, paths[i], cntr_format, + 0); + if (err < 0) + goto end; + + format = sample_format; + rate = frames_per_second; + total_frame_count = frame_count; + if (cntr_count > 1) + channels = 1; + else + channels = samples_per_frame; + err = container_context_pre_process(cntrs + i, &format, + &channels, &rate, + &total_frame_count); + if (err < 0) + goto end; + assert(format == sample_format); + assert(rate == frames_per_second); + assert(total_frame_count >= 0); + if (cntr_count > 1) + assert(channels == 1); + else + assert(channels == samples_per_frame); + } + + bytes_per_sample = snd_pcm_format_physical_width(sample_format) / 8; + test_demuxer(&trial->mapper, access, bytes_per_sample, + samples_per_frame, frames_per_buffer, frame_buffer, + frame_count, cntrs, cntr_count, trial->verbose); + + for (i = 0; i < cntr_count; ++i) { + container_context_post_process(cntrs + i, &total_frame_count); + assert(total_frame_count == frame_count); + } +end: + for (i = 0; i < cntr_count; ++i) + container_context_destroy(cntrs + i); + + return err; +} + +static void test_muxer(struct mapper_context *mapper, snd_pcm_access_t access, + unsigned int bytes_per_sample, + unsigned int samples_per_frame, + unsigned int frames_per_buffer, + void *frame_buffer, unsigned int frame_count, + struct container_context *cntrs, + unsigned int cntr_count, bool verbose) +{ + unsigned int total_frame_count; + int err; + + err = mapper_context_init(mapper, MAPPER_TYPE_MUXER, cntr_count, + verbose); + assert(err == 0); + + err = mapper_context_pre_process(mapper, access, bytes_per_sample, + samples_per_frame, frames_per_buffer, + cntrs); + assert(err == 0); + + total_frame_count = frame_count; + err = mapper_context_process_frames(mapper, frame_buffer, + &total_frame_count, cntrs); + assert(err == 0); + assert(total_frame_count == frame_count); + + mapper_context_post_process(mapper); + mapper_context_destroy(mapper); +} + +static int test_mux(struct mapper_trial *trial, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second, + unsigned int frames_per_buffer, + void *frame_buffer, unsigned int frame_count, + unsigned int cntr_count) +{ + struct container_context *cntrs = trial->cntrs; + char **paths = trial->paths; + unsigned int bytes_per_sample; + uint64_t total_frame_count; + int i; + int err; + + for (i = 0; i < cntr_count; ++i) { + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; + + err = container_parser_init(cntrs + i, paths[i], 0); + if (err < 0) + goto end; + + format = sample_format; + rate = frames_per_second; + if (cntr_count > 1) + channels = 1; + else + channels = samples_per_frame; + err = container_context_pre_process(cntrs + i, &format, + &channels, &rate, + &total_frame_count); + if (err < 0) + goto end; + + assert(format == sample_format); + assert(rate == frames_per_second); + assert(total_frame_count == frame_count); + if (cntr_count > 1) + assert(channels == 1); + else + assert(channels == samples_per_frame); + } + + bytes_per_sample = snd_pcm_format_physical_width(sample_format) / 8; + test_muxer(&trial->mapper, access, bytes_per_sample, samples_per_frame, + frames_per_buffer, frame_buffer, frame_count, cntrs, + cntr_count, trial->verbose); + + for (i = 0; i < cntr_count; ++i) { + container_context_post_process(cntrs + i, &total_frame_count); + assert(total_frame_count == frame_count); + } +end: + for (i = 0; i < cntr_count; ++i) + container_context_destroy(cntrs + i); + + return err; +} + +static int test_mapper(struct mapper_trial *trial, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second, void *frame_buffer, + void *check_buffer, unsigned int frame_count, + unsigned int cntr_count) +{ + unsigned int frames_per_buffer; + int i; + int err; + + /* Use a buffer aligned by typical size of page frame. */ + frames_per_buffer = ((frame_count + 4096) / 4096) * 4096; + + err = test_demux(trial, access, sample_format, samples_per_frame, + frames_per_second, frames_per_buffer, frame_buffer, + frame_count, cntr_count); + if (err < 0) + goto end; + + err = test_mux(trial, access, sample_format, samples_per_frame, + frames_per_second, frames_per_buffer, check_buffer, + frame_count, cntr_count); +end: + for (i = 0; i < cntr_count; ++i) + unlink(trial->paths[i]); + + return err; +} + +static int test_i_buf(struct mapper_trial *trial, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second, void *frame_buffer, + unsigned int frame_count, unsigned int cntr_count) +{ + unsigned int size; + char *buf; + int err; + + size = frame_count * samples_per_frame * + snd_pcm_format_physical_width(sample_format) / 8; + buf = malloc(size); + if (buf == 0) + return -ENOMEM; + memset(buf, 0, size); + + /* Test multiple target. */ + err = test_mapper(trial, access, sample_format, samples_per_frame, + frames_per_second, frame_buffer, buf, + frame_count, cntr_count); + if (err < 0) + goto end; + err = memcmp(frame_buffer, buf, size); + assert(err == 0); + + /* Test single target. */ + err = test_mapper(trial, access, sample_format, samples_per_frame, + frames_per_second, frame_buffer, buf, + frame_count, 1); + if (err < 0) + goto end; + err = memcmp(frame_buffer, buf, size); + assert(err == 0); +end: + free(buf); + + return err; +} + +static int test_vector(struct mapper_trial *trial, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second, void *frame_buffer, + unsigned int frame_count, unsigned int cntr_count) +{ + unsigned int size; + char **bufs; + int i; + int err; + + bufs = calloc(cntr_count, sizeof(*bufs)); + if (bufs == NULL) + return -ENOMEM; + + size = frame_count * snd_pcm_format_physical_width(sample_format) / 8; + + for (i = 0; i < cntr_count; ++i) { + bufs[i] = malloc(size); + if (bufs[i] == NULL) + goto end; + memset(bufs[i], 0, size); + } + + /* Test multiple target. */ + err = test_mapper(trial, access, sample_format, samples_per_frame, + frames_per_second, frame_buffer, bufs, + frame_count, cntr_count); + if (err < 0) + goto end; + for (i = 0; i < cntr_count; ++i) { + char **target = frame_buffer; + err = memcmp(target[i], bufs[i], size); + assert(err == 0); + } + + /* Test single target. */ + err = test_mapper(trial, access, sample_format, samples_per_frame, + frames_per_second, frame_buffer, bufs, + frame_count, 1); + if (err < 0) + goto end; + for (i = 0; i < cntr_count; ++i) { + char **target = frame_buffer; + err = memcmp(target[i], bufs[i], size); + assert(err == 0); + } +end: + for (i = 0; i < cntr_count; ++i) { + if (bufs[i]) + free(bufs[i]); + } + free(bufs); + + return err; +} + +static int test_n_buf(struct mapper_trial *trial, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second, void *frame_buffer, + unsigned int frame_count, unsigned int cntr_count) +{ + char *test_buf = frame_buffer; + unsigned int size; + char **test_vec; + int i; + int err; + + size = frame_count * snd_pcm_format_physical_width(sample_format) / 8; + + test_vec = calloc(cntr_count * 2, sizeof(*test_vec)); + if (test_vec == NULL) + return -ENOMEM; + + for (i = 0; i < cntr_count; ++i) + test_vec[i] = test_buf + size * i; + + err = test_vector(trial, access, sample_format, samples_per_frame, + frames_per_second, test_vec, frame_count, cntr_count); + free(test_vec); + + return err; +} + +static int callback(struct test_generator *gen, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, void *frame_buffer, + unsigned int frame_count) +{ + + int (*handler)(struct mapper_trial *trial, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second, void *frame_buffer, + unsigned int frame_count, unsigned int cntr_count); + struct mapper_trial *trial = gen->private_data; + + if (access == SND_PCM_ACCESS_RW_NONINTERLEAVED) + handler = test_vector; + else if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) + handler = test_n_buf; + else + handler = test_i_buf; + + return handler(trial, access, sample_format, samples_per_frame, 48000, + frame_buffer, frame_count, samples_per_frame); +}; + +int main(int argc, const char *argv[]) +{ + /* Test 8/16/18/20/24/32/64 bytes per sample. */ + static const uint64_t sample_format_mask = + (1ul << SND_PCM_FORMAT_U8) | + (1ul << SND_PCM_FORMAT_S16_LE) | + (1ul << SND_PCM_FORMAT_S18_3LE) | + (1ul << SND_PCM_FORMAT_S20_3LE) | + (1ul << SND_PCM_FORMAT_S24_LE) | + (1ul << SND_PCM_FORMAT_S32_LE) | + (1ul << SND_PCM_FORMAT_FLOAT64_LE); + uint64_t access_mask; + struct test_generator gen = {0}; + struct mapper_trial *trial; + struct container_context *cntrs; + unsigned int samples_per_frame; + char **paths = NULL; + snd_pcm_access_t access; + bool verbose; + int i; + int err; + + /* Test up to 32 channels. */ + samples_per_frame = 32; + cntrs = calloc(samples_per_frame, sizeof(*cntrs)); + if (cntrs == NULL) + return -ENOMEM; + + paths = calloc(samples_per_frame, sizeof(*paths)); + if (paths == NULL) { + err = -ENOMEM; + goto end; + } + for (i = 0; i < samples_per_frame; ++i) { + paths[i] = malloc(8); + if (paths[i] == NULL) { + err = -ENOMEM; + goto end; + } + snprintf(paths[i], 8, "hoge%d", i); + } + + if (argc > 1) { + char *term; + access = strtol(argv[1], &term, 10); + if (errno != 0 || *term != '\0') { + err = -EINVAL;; + goto end; + } + if (access < SND_PCM_ACCESS_MMAP_INTERLEAVED && + access > SND_PCM_ACCESS_RW_NONINTERLEAVED) { + err = -EINVAL; + goto end; + } + if (access == SND_PCM_ACCESS_MMAP_COMPLEX) { + err = -EINVAL; + goto end; + } + + access_mask = 1ul << access; + verbose = true; + } else { + access_mask = (1ul << SND_PCM_ACCESS_MMAP_INTERLEAVED) | + (1ul << SND_PCM_ACCESS_MMAP_NONINTERLEAVED) | + (1ul << SND_PCM_ACCESS_RW_INTERLEAVED) | + (1ul << SND_PCM_ACCESS_RW_NONINTERLEAVED); + verbose = false; + } + + err = generator_context_init(&gen, access_mask, sample_format_mask, + 1, samples_per_frame, + 23, 4500, 1024, + sizeof(struct mapper_trial)); + if (err < 0) + goto end; + + trial = gen.private_data; + trial->cntrs = cntrs; + trial->cntr_format = CONTAINER_FORMAT_RIFF_WAVE; + trial->paths = paths; + trial->verbose = verbose; + err = generator_context_run(&gen, callback); + + generator_context_destroy(&gen); +end: + if (paths) { + for (i = 0; i < samples_per_frame; ++i) + free(paths[i]); + free(paths); + } + free(cntrs); + + if (err < 0) { + printf("%s\n", strerror(-err)); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +}
In aplay, many command-line options are supported. Some of them have dependency or conflicts.
This commit adds a structure and a parser for the options. In this patch, minimal set of options is supported. Additional options will be supported later.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 5 +- axfer/options.c | 594 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/options.h | 47 +++++ 3 files changed, 645 insertions(+), 1 deletion(-) create mode 100644 axfer/options.c create mode 100644 axfer/options.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index f17e59b0..3b1a87c8 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -19,6 +19,7 @@ noinst_HEADERS = \ subcmd.h \ container.h \ mapper.h + options.h
axfer_SOURCES = \ misc.h \ @@ -34,4 +35,6 @@ axfer_SOURCES = \ mapper.h \ mapper.c \ mapper-single.c \ - mapper-multiple.c + mapper-multiple.c \ + options.h \ + options.c diff --git a/axfer/options.c b/axfer/options.c new file mode 100644 index 00000000..256f93a1 --- /dev/null +++ b/axfer/options.c @@ -0,0 +1,594 @@ +/* + * options.c - a parser of commandline options. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "options.h" +#include "misc.h" + +#include <getopt.h> +#include <math.h> + +static const char *const allowed_duplication[] = { + "/dev/null", + "/dev/zero", + "/dev/full", + "/dev/random", + "/dev/urandom", +}; + +static long parse_l(const char *str, int *err) +{ + long val; + char *endptr; + + val = strtol(str, &endptr, 10); + if (errno) + return -errno; + if (*endptr != '\0') + return -EINVAL; + + return val; +} + +static int allocate_paths(struct context_options *opts, char *const *paths, + unsigned int count) +{ + bool stdio = false; + char *path; + int i; + + if (count == 0) { + stdio = true; + count = 1; + } + + opts->paths = calloc(count, sizeof(opts->paths[0])); + if (opts->paths == NULL) + return -ENOMEM; + opts->path_count = count; + + if (stdio) { + opts->paths[0] = malloc(2); + if (opts->paths[0] == NULL) + return -ENOMEM; + strcpy(opts->paths[0], "-"); + return 0; + } + + for (i = 0; i < count; ++i) { + path = malloc(strlen(paths[i]) + 1); + if (path == NULL) + return -ENOMEM; + strcpy(path, paths[i]); + opts->paths[i] = path; + } + + return 0; +} + +static int verify_cntr_format(struct context_options *opts, const char *literal) +{ + static const struct { + const char *const literal; + enum container_format cntr_format; + } *entry, entries[] = { + {"raw", CONTAINER_FORMAT_RAW}, + {"voc", CONTAINER_FORMAT_VOC}, + {"wav", CONTAINER_FORMAT_RIFF_WAVE}, + {"au", CONTAINER_FORMAT_AU}, + {"sparc", CONTAINER_FORMAT_AU}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = &entries[i]; + if (strcasecmp(literal, entry->literal)) + continue; + + opts->cntr_format = entry->cntr_format; + return 0; + } + + fprintf(stderr, "unrecognized file format '%s'\n", literal); + + return -EINVAL; +} + +/* This should be called after 'verify_cntr_format()'. */ +static int verify_sample_format(struct context_options *opts, + const char *literal) +{ + static const struct { + const char *const literal; + unsigned int frames_per_second; + unsigned int samples_per_frame; + snd_pcm_format_t le_format; + snd_pcm_format_t be_format; + } *entry, entries[] = { + {"cd", 44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE}, + {"cdr", 44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE}, + {"dat", 48000, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE}, + }; + int i; + + opts->sample_format = snd_pcm_format_value(literal); + if (opts->sample_format != SND_PCM_FORMAT_UNKNOWN) + return 0; + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = &entries[i]; + if (strcmp(entry->literal, literal)) + continue; + + if (opts->frames_per_second > 0 && + opts->frames_per_second != entry->frames_per_second) { + fprintf(stderr, + "'%s' format can't be used with rate except " + "for %u.\n", + entry->literal, entry->frames_per_second); + return -EINVAL; + } + + if (opts->samples_per_frame > 0 && + opts->samples_per_frame != entry->samples_per_frame) { + fprintf(stderr, + "'%s' format can't be used with channel except " + "for %u.\n", + entry->literal, entry->samples_per_frame); + return -EINVAL; + } + + opts->frames_per_second = entry->frames_per_second; + opts->samples_per_frame = entry->samples_per_frame; + if (opts->cntr_format == CONTAINER_FORMAT_AU) + opts->sample_format = entry->be_format; + else + opts->sample_format = entry->le_format; + + return 0; + } + + fprintf(stderr, "wrong extended format '%s'\n", literal); + + return -EINVAL; +} + +static int apply_policies(struct context_options *opts, + snd_pcm_stream_t direction, + const char *cntr_format_literal, + const char *node_literal, + const char *sample_format_literal) +{ + int err; + + if (node_literal != NULL) { + opts->node = malloc(strlen(node_literal) + 1); + if (opts->node == NULL) { + fprintf(stderr, "Fail to allocate for node: %s\n", + node_literal); + return -ENOMEM; + } + strcpy(opts->node, node_literal); + } + + if (!cntr_format_literal) { + if (direction == SND_PCM_STREAM_CAPTURE) { + /* To stdout. */ + if (opts->path_count == 1 && + !strcmp(opts->paths[0], "-")) { + opts->cntr_format = CONTAINER_FORMAT_RAW; + } else { + /* Use first path as a representative. */ + opts->cntr_format = container_format_from_path( + opts->paths[0]); + } + } + /* For playback, perform auto-detection. */ + } else { + err = verify_cntr_format(opts, cntr_format_literal); + if (err < 0) + return err; + } + + if (opts->samples_per_frame > 0) { + if (opts->samples_per_frame < 1 || + opts->samples_per_frame > 256) { + fprintf(stderr, "invalid channels argument '%u'\n", + opts->samples_per_frame); + return -EINVAL; + } + } + + if (opts->multiple_cntrs) { + if (!strcmp(opts->paths[0], "-")) { + fprintf(stderr, + "An option for separated channels is not " + "available with stdin/stdout.\n"); + return -EINVAL; + } + + /* + * For captured PCM frames, even if one path is given for + * container files, it can be used to generate several paths. + * For this purpose, please see + * 'context_options_fixup_for_capture()'. + */ + if (direction == SND_PCM_STREAM_PLAYBACK) { + /* Require several paths for containers. */ + if (opts->path_count == 1) { + fprintf(stderr, + "An option for separated channels " + "requires several files to playback " + "PCM frames.\n"); + return -EINVAL; + } + } + } else { + /* A single path is available only. */ + if (opts->path_count > 1) { + fprintf(stderr, + "When using several files, an option for " + "sepatated channels is used with.\n"); + return -EINVAL; + } + } + + if (opts->frames_per_second > 0) { + unsigned int orig = opts->frames_per_second; + + /* For backward compatibility. */ + if (opts->frames_per_second < 300) + opts->frames_per_second *= 300; + if (opts->frames_per_second < 2000 || + opts->frames_per_second > 192000) { + fprintf(stderr, "bad speed value '%i'\n", orig); + return -EINVAL; + } + } + + opts->sample_format = SND_PCM_FORMAT_UNKNOWN; + if (sample_format_literal) { + err = verify_sample_format(opts, sample_format_literal); + if (err < 0) + return err; + } + + return 0; +} + +int context_options_init(struct context_options *opts, int argc, + char *const *argv, snd_pcm_stream_t direction) +{ + static const char *s_opts = "hvt:ID:f:c:r:"; + static const struct option l_opts[] = { + /* For generic purposes. */ + {"help", 0, 0, 'h'}, + {"verbose", 0, 0, 'v'}, + /* For containers. */ + {"file-type", 1, 0, 't'}, + /* For mapper. */ + {"separate-channels", 0, 0, 'I'}, + /* For transfer backend. */ + {"device", 1, 0, 'D'}, + {"format", 1, 0, 'f'}, + {"channels", 1, 0, 'c'}, + {"rate", 1, 0, 'r'}, + {NULL, 0, 0, 0}, + }; + const char *cntr_format_literal = NULL; + const char *node_literal = NULL; + const char *sample_format_literal = NULL; + int c; + int err = 0; + + optind = 0; + opterr = 0; + while (1) { + c = getopt_long(argc, argv, s_opts, l_opts, NULL); + if (c < 0) + break; + else if (c == 'h') + opts->help = true; + else if (c == 'v') + ++opts->verbose; + else if (c == 't') + cntr_format_literal = optarg; + else if (c == 'I') + opts->multiple_cntrs = true; + else if (c == 'D') + node_literal = optarg; + else if (c == 'f') + sample_format_literal = optarg; + else if (c == 'c') + opts->samples_per_frame = parse_l(optarg, &err); + else if (c == 'r') + opts->frames_per_second = parse_l(optarg, &err); + else + continue; + + if (err < 0) + return err; + } + + err = allocate_paths(opts, argv + optind, argc - optind); + if (err < 0) + return err; + + return apply_policies(opts, direction, cntr_format_literal, + node_literal, sample_format_literal); +} + +static int generate_path_with_suffix(struct context_options *opts, + const char *template, unsigned int index, + const char *suffix) +{ + static const char *const single_format = "%s%s"; + static const char *const multiple_format = "%s-%i%s"; + unsigned int len; + + len = strlen(template) + strlen(suffix) + 1; + if (opts->path_count > 1) + len += (unsigned int)log10(opts->path_count) + 2; + + opts->paths[index] = malloc(len); + if (opts->paths[index] == NULL) + return -ENOMEM; + + if (opts->path_count == 1) { + snprintf(opts->paths[index], len, single_format, template, + suffix); + } else { + snprintf(opts->paths[index], len, multiple_format, template, + index, suffix); + } + + return 0; +} + +static int generate_path_without_suffix(struct context_options *opts, + const char *template, + unsigned int index, const char *suffix) +{ + static const char *const single_format = "%s"; + static const char *const multiple_format = "%s-%i"; + unsigned int len; + + len = strlen(template) + 1; + if (opts->path_count > 1) + len += (unsigned int)log10(opts->path_count) + 2; + + opts->paths[index] = malloc(len); + if (opts->paths[index] == NULL) + return -ENOMEM; + + if (opts->path_count == 1) { + snprintf(opts->paths[index], len, single_format, template); + } else { + snprintf(opts->paths[index], len, multiple_format, template, + index); + } + + return 0; +} + +static int generate_path(struct context_options *opts, char *template, + unsigned int index, const char *suffix) +{ + int (*generator)(struct context_options *opts, const char *template, + unsigned int index, const char *suffix); + char *pos; + + if (strlen(suffix) > 0) { + pos = template + strlen(template) - strlen(suffix); + /* Separate filename and suffix. */ + if (!strcmp(pos, suffix)) + *pos = '\0'; + } + + /* Select handlers. */ + if (strlen(suffix) > 0) + generator = generate_path_with_suffix; + else + generator = generate_path_without_suffix; + + return generator(opts, template, index, suffix); +} + +static int create_paths(struct context_options *opts, unsigned int path_count) +{ + char *template; + const char *suffix; + int i, j; + int err = 0; + + /* Can cause memory leak. */ + assert(opts->path_count == 1); + assert(opts->paths); + assert(opts->paths[0]); + assert(opts->paths[0][0] != '\0'); + + /* Release at first. */ + template = opts->paths[0]; + free(opts->paths); + opts->paths = NULL; + + /* Allocate again. */ + opts->paths = calloc(path_count, sizeof(*opts->paths)); + if (opts->paths == NULL) { + err = -ENOMEM; + goto end; + } + opts->path_count = path_count; + + suffix = container_suffix_from_format(opts->cntr_format); + + for (i = 0; i < opts->path_count; ++i) { + /* Some file names are allowed to be duplicated. */ + for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) { + if (!strcmp(opts->paths[i], allowed_duplication[j])) + break; + } + if (j < ARRAY_SIZE(allowed_duplication)) + continue; + + err = generate_path(opts, template, i, suffix); + if (err < 0) + break; + } +end: + free(template); + + return err; +} + +static int fixup_paths(struct context_options *opts) +{ + const char *suffix; + char *template; + int i, j; + int err = 0; + + suffix = container_suffix_from_format(opts->cntr_format); + + for (i = 0; i < opts->path_count; ++i) { + /* Some file names are allowed to be duplicated. */ + for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) { + if (!strcmp(opts->paths[i], allowed_duplication[j])) + break; + } + if (j < ARRAY_SIZE(allowed_duplication)) + continue; + + template = opts->paths[i]; + opts->paths[i] = NULL; + err = generate_path(opts, template, i, suffix); + free(template); + if (err < 0) + break; + } + + return err; +} + +int context_options_fixup_for_capture(struct context_options *opts, + unsigned int samples_per_frame) +{ + int i, j; + int err; + + if (!opts->multiple_cntrs) { + if (opts->path_count == 1) + err = fixup_paths(opts); + else + return -EINVAL; + } else { + if (opts->path_count == 1) { + err = create_paths(opts, samples_per_frame); + } else { + if (opts->path_count == samples_per_frame) + err = fixup_paths(opts); + else + return -EINVAL; + } + } + if (err < 0) + return err; + + /* Check duplication of the paths. */ + for (i = 0; i < opts->path_count - 1; ++i) { + /* Some file names are allowed to be duplicated. */ + for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) { + if (!strcmp(opts->paths[i], allowed_duplication[j])) + break; + } + if (j < ARRAY_SIZE(allowed_duplication)) + continue; + + for (j = i + 1; j < opts->path_count; ++j) { + if (!strcmp(opts->paths[i], opts->paths[j])) { + fprintf(stderr, + "Detect duplicated file names:\n"); + err = -EINVAL; + break; + } + } + if (j < opts->path_count) + break; + } + + if (opts->verbose > 1) + fprintf(stderr, "Handled file names:\n"); + if (err < 0 || opts->verbose > 1) { + for (i = 0; i < opts->path_count; ++i) + fprintf(stderr, " %d: %s\n", i, opts->paths[i]); + } + + return err; +} + +int context_options_fixup_for_playback(struct context_options *opts, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second) +{ + if (opts->sample_format != SND_PCM_FORMAT_UNKNOWN) { + if (sample_format != opts->sample_format) { + fprintf(stderr, + "Sample format mismatch: %s is given but %s by " + "files\n", + snd_pcm_format_name(opts->sample_format), + snd_pcm_format_name(sample_format)); + return -EINVAL; + } + } + + if (opts->samples_per_frame > 0) { + if (samples_per_frame != opts->samples_per_frame) { + fprintf(stderr, + "The number of channels mismatch: %u is given " + "but %u by files\n", + opts->samples_per_frame, samples_per_frame); + return -EINVAL; + } + } + + if (opts->frames_per_second > 0) { + if (frames_per_second != opts->frames_per_second) { + fprintf(stderr, + "Sampling rate mismatch: %u is given but %u by " + "files\n", + opts->frames_per_second, frames_per_second); + return -EINVAL; + } + } + + if (opts->verbose > 1) { + int i; + + fprintf(stderr, "Handled file names:\n"); + for (i = 0; i < opts->path_count; ++i) { + fprintf(stderr, " %d: %s\n", i, opts->paths[i]); + } + } + + return 0; +} + +void context_options_destroy(struct context_options *opts) +{ + int i; + + if (opts->paths) { + for (i = 0; i < opts->path_count; ++i) + free(opts->paths[i]); + free(opts->paths); + } + if (opts->node) + free(opts->node); + opts->paths = NULL; + opts->node = NULL; +} diff --git a/axfer/options.h b/axfer/options.h new file mode 100644 index 00000000..38bdbb74 --- /dev/null +++ b/axfer/options.h @@ -0,0 +1,47 @@ +/* + * options.h - a header for a parser of commandline options. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_OPTIONS__H_ +#define __ALSA_UTILS_AXFER_OPTIONS__H_ + +#include "container.h" + +struct context_options { + bool help; + + /* For generic purposes. */ + unsigned int verbose; + + /* For containers. */ + char **paths; + unsigned int path_count; + enum container_format cntr_format; + + /* For mapper. */ + bool multiple_cntrs; + + /* For transfer backend. */ + char *node; + snd_pcm_format_t sample_format; + unsigned int samples_per_frame; + unsigned int frames_per_second; +}; + +int context_options_init(struct context_options *opts, int argc, + char *const *argv, snd_pcm_stream_t direction); +int context_options_fixup_for_playback(struct context_options *opts, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, + unsigned int frames_per_second); +int context_options_fixup_for_capture(struct context_options *opts, + unsigned int samples_per_frame); +void context_options_calculate_duration(struct context_options *opts, + unsigned int frames_per_second, uint64_t *total_frame_count); +void context_options_destroy(struct context_options *opts); + +#endif
ALSA has PCM interface to transfer data frames. In userspace, there're some implementation to utilize this interface to produce application programming interface; alsa-lib (libasound) and tinyalsa. However, it's possible to use the interface with raw I/O operations.
This commit adds an common interface to transfer data frames for this program, named as 'xfer'. This internal interface is designed for users to select several backend for data transmission. This includes some functions expected to be called by main program just for data transmission. In an aspect to maintain PCM substream, suspend feature is required to handle a pair of SIGTSTP/SIGCONT UNIX signals.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 7 ++- axfer/xfer.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/xfer.h | 71 +++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 axfer/xfer.c create mode 100644 axfer/xfer.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 3b1a87c8..c1534411 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -19,7 +19,8 @@ noinst_HEADERS = \ subcmd.h \ container.h \ mapper.h - options.h + options.h \ + xfer.h
axfer_SOURCES = \ misc.h \ @@ -37,4 +38,6 @@ axfer_SOURCES = \ mapper-single.c \ mapper-multiple.c \ options.h \ - options.c + options.c \ + xfer.h \ + xfer.c diff --git a/axfer/xfer.c b/axfer/xfer.c new file mode 100644 index 00000000..46b36d42 --- /dev/null +++ b/axfer/xfer.c @@ -0,0 +1,153 @@ +/* + * xfer.c - receiver/transmiter of data frames. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer.h" +#include "misc.h" + +#include <stdio.h> + +static const char *const xfer_type_labels[] = { + [XFER_TYPE_COUNT] = "", +}; + +int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, + snd_pcm_stream_t direction, struct context_options *opts) +{ + struct { + enum xfer_type type; + const struct xfer_data *data; + } entries[] = { + {XFER_TYPE_COUNT, NULL}, + }; + int i; + + assert(xfer); + assert(direction >= SND_PCM_STREAM_PLAYBACK); + assert(direction <= SND_PCM_STREAM_CAPTURE); + assert(opts); + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + if (entries[i].type == type) + break; + } + if (i == ARRAY_SIZE(entries)) + return -EINVAL; + + xfer->type = type; + xfer->ops = &entries[i].data->ops; + xfer->verbose = opts->verbose; + + xfer->private_data = malloc(entries[i].data->private_size); + if (xfer->private_data == NULL) + return -ENOMEM; + memset(xfer->private_data, 0, entries[i].data->private_size); + + return xfer->ops->init(xfer, direction, opts); +} + +void xfer_context_destroy(struct xfer_context *xfer) +{ + assert(xfer); + + if (!xfer->ops) + return; + + if (xfer->ops->destroy) + xfer->ops->destroy(xfer); + if (xfer->private_data) + free(xfer->private_data); +} + +int xfer_context_pre_process(struct xfer_context *xfer, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer) +{ + int err; + + assert(xfer); + assert(format); + assert(samples_per_frame); + assert(frames_per_second); + assert(access); + assert(frames_per_buffer); + + if (!xfer->ops) + return -ENXIO; + + err = xfer->ops->pre_process(xfer, format, samples_per_frame, + frames_per_second, access, + frames_per_buffer); + if (err < 0) + return err; + + assert(*format >= SND_PCM_FORMAT_S8); + assert(*format <= SND_PCM_FORMAT_LAST); + assert(*samples_per_frame > 0); + assert(*frames_per_second > 0); + assert(*access >= SND_PCM_ACCESS_MMAP_INTERLEAVED); + assert(*access <= SND_PCM_ACCESS_LAST); + assert(*frames_per_buffer > 0); + + if (xfer->verbose > 1) { + fprintf(stderr, "Transfer: %s\n", + xfer_type_labels[xfer->type]); + fprintf(stderr, " access: %s\n", + snd_pcm_access_name(*access)); + fprintf(stderr, " sample format: %s\n", + snd_pcm_format_name(*format)); + fprintf(stderr, " bytes/sample: %u\n", + snd_pcm_format_physical_width(*format) / 8); + fprintf(stderr, " samples/frame: %u\n", + *samples_per_frame); + fprintf(stderr, " frames/second: %u\n", + *frames_per_second); + fprintf(stderr, " frames/buffer: %lu\n", + *frames_per_buffer); + } + + return 0; +} + +int xfer_context_process_frames(struct xfer_context *xfer, + struct mapper_context *mapper, + struct container_context *cntrs, + unsigned int *frame_count) +{ + assert(xfer); + assert(mapper); + assert(cntrs); + assert(frame_count); + + if (!xfer->ops) + return -ENXIO; + + return xfer->ops->process_frames(xfer, frame_count, mapper, cntrs); +} + +void xfer_context_pause(struct xfer_context *xfer, bool enable) +{ + assert(xfer); + + if (!xfer->ops) + return; + + xfer->ops->pause(xfer, enable); +} + +void xfer_context_post_process(struct xfer_context *xfer) +{ + assert(xfer); + + if (!xfer->ops) + return; + + xfer->ops->post_process(xfer); +} diff --git a/axfer/xfer.h b/axfer/xfer.h new file mode 100644 index 00000000..4e3f1f37 --- /dev/null +++ b/axfer/xfer.h @@ -0,0 +1,71 @@ +/* + * xfer.h - a header for receiver/transmiter of data frames. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_XFER__H_ +#define __ALSA_UTILS_AXFER_XFER__H_ + +#include "mapper.h" +#include "options.h" + +enum xfer_type { + XFER_TYPE_COUNT, +}; + +struct xfer_ops; + +struct xfer_context { + enum xfer_type type; + const struct xfer_ops *ops; + void *private_data; + + unsigned int verbose; +}; + +int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, + snd_pcm_stream_t direction, struct context_options *opts); +void xfer_context_destroy(struct xfer_context *xfer); +int xfer_context_pre_process(struct xfer_context *xfer, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer); +int xfer_context_process_frames(struct xfer_context *xfer, + struct mapper_context *mapper, + struct container_context *cntrs, + unsigned int *frame_count); +void xfer_context_pause(struct xfer_context *xfer, bool enable); +void xfer_context_post_process(struct xfer_context *xfer); + +/* For internal use in 'xfer' module. */ + +struct xfer_ops { + int (*init)(struct xfer_context *xfer, snd_pcm_stream_t direction, + struct context_options *opts); + int (*pre_process)(struct xfer_context *xfer, snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer); + int (*process_frames)(struct xfer_context *xfer, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs); + void (*post_process)(struct xfer_context *xfer); + void (*destroy)(struct xfer_context *xfer); + void (*pause)(struct xfer_context *xfer, bool enable); +}; + +struct xfer_data { + struct xfer_ops ops; + unsigned int private_size; +}; + +extern const struct xfer_data xfer_alsa; + +#endif
This commit adds support fo alsa-lib PCM API as a backend of 'xfer' module. In a set of alsa-lib PCM API, there're two ways to handle data frames; by calling ioctl(2) with some specific commands and buffer in user space, or copying data frames on mapped page frames. To support these two ways, this commit adds operation structure.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 7 +- axfer/xfer-libasound.c | 374 +++++++++++++++++++++++++++++++++++++++++++++++++ axfer/xfer-libasound.h | 45 ++++++ axfer/xfer.c | 4 +- axfer/xfer.h | 4 +- 5 files changed, 428 insertions(+), 6 deletions(-) create mode 100644 axfer/xfer-libasound.c create mode 100644 axfer/xfer-libasound.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index c1534411..ab4e3ae1 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -20,7 +20,8 @@ noinst_HEADERS = \ container.h \ mapper.h options.h \ - xfer.h + xfer.h \ + xfer-alsa.h
axfer_SOURCES = \ misc.h \ @@ -40,4 +41,6 @@ axfer_SOURCES = \ options.h \ options.c \ xfer.h \ - xfer.c + xfer.c \ + xfer-libasound.h \ + xfer-libasound.c diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c new file mode 100644 index 00000000..66e8e1de --- /dev/null +++ b/axfer/xfer-libasound.c @@ -0,0 +1,374 @@ +/* + * xfer-libasound.c - receive/transmit frames by alsa-lib. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer-libasound.h" +#include "misc.h" + +static int set_access_hw_param(snd_pcm_t *handle, + snd_pcm_hw_params_t *hw_params, + struct context_options *opts) +{ + snd_pcm_access_mask_t *mask; + int err; + + err = snd_pcm_access_mask_malloc(&mask); + if (err < 0) + return err; + snd_pcm_access_mask_none(mask); + snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_NONINTERLEAVED); + err = snd_pcm_hw_params_set_access_mask(handle, hw_params, mask); + snd_pcm_access_mask_free(mask); + + return err; +} + +static int xfer_libasound_init(struct xfer_context *xfer, + snd_pcm_stream_t direction, + struct context_options *opts) +{ + struct libasound_state *state = xfer->private_data; + char *node; + int err; + + err = snd_output_stdio_attach(&state->log, stderr, 0); + if (err < 0) + return err; + + state->verbose = xfer->verbose > 1; + + if (opts->node == NULL) + node = "default"; + else + node = opts->node; + + err = snd_pcm_open(&state->handle, node, direction, 0); + if (err < 0) { + logging(state, "Fail to open libasound PCM node for %s: %s\n", + snd_pcm_stream_name(direction), node); + return err; + } + + err = snd_pcm_hw_params_malloc(&state->hw_params); + if (err < 0) + return err; + err = snd_pcm_sw_params_malloc(&state->sw_params); + if (err < 0) + return err; + + err = snd_pcm_hw_params_any(state->handle, state->hw_params); + if (err < 0) + return err; + + /* TODO: Applying NO_PERIOD_WAKEUP should be done here. */ + + return set_access_hw_param(state->handle, state->hw_params, opts); +} + +static int configure_hw_params(struct libasound_state *state, + snd_pcm_format_t format, + unsigned int samples_per_frame, + unsigned int frames_per_second) +{ + int err; + + /* Configure sample format. */ + if (format == SND_PCM_FORMAT_UNKNOWN) { + snd_pcm_format_mask_t *mask; + + err = snd_pcm_format_mask_malloc(&mask); + if (err < 0) + return err; + snd_pcm_hw_params_get_format_mask(state->hw_params, mask); + for (format = 0; format <= SND_PCM_FORMAT_LAST; ++format) { + if (snd_pcm_format_mask_test(mask, format)) + break; + } + snd_pcm_format_mask_free(mask); + if (format > SND_PCM_FORMAT_LAST) { + logging(state, + "Any sample format is not available.\n"); + return -EINVAL; + } + } + err = snd_pcm_hw_params_set_format(state->handle, state->hw_params, + format); + if (err < 0) { + logging(state, + _("Sample format '%s' is not available: %s\n"), + snd_pcm_format_name(format), snd_strerror(err)); + return err; + } + + /* Configure channels. */ + if (samples_per_frame == 0) { + err = snd_pcm_hw_params_get_channels_min(state->hw_params, + &samples_per_frame); + if (err < 0) { + logging(state, + _("Any channel number is not available.\n")); + return err; + } + } + err = snd_pcm_hw_params_set_channels(state->handle, state->hw_params, + samples_per_frame); + if (err < 0) { + logging(state, + _("Channels count '%u' is not available: %s\n"), + samples_per_frame, snd_strerror(err)); + return err; + } + + /* Configure rate. */ + if (frames_per_second == 0) { + err = snd_pcm_hw_params_get_rate_min(state->hw_params, + &frames_per_second, NULL); + if (err < 0) { + logging(state, + _("Any rate is not available.\n")); + return err; + } + + } + err = snd_pcm_hw_params_set_rate(state->handle, state->hw_params, + frames_per_second, 0); + if (err < 0) { + logging(state, + _("Sampling rate '%u' is not available: %s\n"), + frames_per_second, snd_strerror(err)); + return err; + } + + return snd_pcm_hw_params(state->handle, state->hw_params); +} + +static int retrieve_actual_hw_params(snd_pcm_hw_params_t *hw_params, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer) +{ + int err; + + err = snd_pcm_hw_params_get_format(hw_params, format); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_channels(hw_params, + samples_per_frame); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_rate(hw_params, frames_per_second, + NULL); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_access(hw_params, access); + if (err < 0) + return err; + + return snd_pcm_hw_params_get_buffer_size(hw_params, frames_per_buffer); +} + +static int configure_sw_params(struct libasound_state *state, + unsigned int frames_per_second, + unsigned int frames_per_buffer) +{ + return snd_pcm_sw_params(state->handle, state->sw_params); +} + +static int xfer_libasound_pre_process(struct xfer_context *xfer, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer) +{ + struct libasound_state *state = xfer->private_data; + int err; + + if (state->handle == NULL) + return -ENXIO; + + err = configure_hw_params(state, *format, *samples_per_frame, + *frames_per_second); + if (err < 0) { + logging(state, _("Current hardware parameters:\n")); + snd_pcm_hw_params_dump(state->hw_params, state->log); + return err; + } + + /* Retrieve actual parameters. */ + err = retrieve_actual_hw_params(state->hw_params, format, + samples_per_frame, frames_per_second, + access, frames_per_buffer); + if (err < 0) + return err; + + /* Query software parameters. */ + err = snd_pcm_sw_params_current(state->handle, state->sw_params); + if (err < 0) + return err; + + /* Assign I/O operation. */ + if (state->ops->private_size > 0) { + state->private_data = malloc(state->ops->private_size); + if (state->private_data == NULL) + return -ENOMEM; + memset(state->private_data, 0, state->ops->private_size); + } + err = state->ops->pre_process(state); + if (err < 0) + return err; + + err = configure_sw_params(state, *frames_per_second, + *frames_per_buffer); + if (err < 0) { + logging(state, _("Current software parameters:\n")); + snd_pcm_sw_params_dump(state->sw_params, state->log); + return err; + } + + if (xfer->verbose > 0) + snd_pcm_dump(state->handle, state->log); + + return 0; +} + +static int xfer_libasound_process_frames(struct xfer_context *xfer, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct libasound_state *state = xfer->private_data; + int err; + + if (state->handle == NULL) + return -ENXIO; + + err = state->ops->process_frames(state, frame_count, mapper, cntrs); + if (err < 0) { + if (err == -EPIPE) { + /* + * Recover the stream and continue processing + * immediately. In this program -EPIPE comes from + * libasound implementation instead of file I/O. + */ + + err = snd_pcm_prepare(state->handle); + } + + if (err < 0) { + /* + * TODO: -EIO from libasound for hw PCM node means + * that IRQ disorder. This should be reported to help + * developers for drivers. + */ + logging(state, "Fail to process frames: %s\n", + snd_strerror(err)); + } + } + + return err; +} + +static void xfer_libasound_pause(struct xfer_context *xfer, bool enable) +{ + struct libasound_state *state = xfer->private_data; + snd_pcm_state_t s = snd_pcm_state(state->handle); + int err; + + if (state->handle == NULL) + return; + + if (enable) { + if (s != SND_PCM_STATE_RUNNING) + return; + } else { + if (s != SND_PCM_STATE_PAUSED) + return; + } + + /* Not supported. Leave the substream to enter XRUN state. */ + if (!snd_pcm_hw_params_can_pause(state->hw_params)) + return; + + err = snd_pcm_pause(state->handle, enable); + if (err < 0 && state->verbose) { + logging(state, "snd_pcm_pause(): %s\n", snd_strerror(err)); + } +} + +static void xfer_libasound_post_process(struct xfer_context *xfer) +{ + struct libasound_state *state = xfer->private_data; + snd_pcm_state_t pcm_state; + int err; + + if (state->handle == NULL) + return; + + pcm_state = snd_pcm_state(state->handle); + if (pcm_state != SND_PCM_STATE_OPEN && + pcm_state != SND_PCM_STATE_DISCONNECTED) { + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) { + err = snd_pcm_drop(state->handle); + if (err < 0) + logging(state, "snd_pcm_drop(): %s\n", + snd_strerror(err)); + } else { + err = snd_pcm_drain(state->handle); + if (err < 0) + logging(state, "snd_pcm_drain(): %s\n", + snd_strerror(err)); + } + } + + err = snd_pcm_hw_free(state->handle); + if (err < 0) + logging(state, "snd_pcm_hw_free(): %s\n", snd_strerror(err)); + + snd_pcm_close(state->handle); + state->handle = NULL; + + if (state->ops && state->ops->post_process) + state->ops->post_process(state); + if (state->private_data) + free(state->private_data); + state->private_data = NULL; +} + +static void xfer_libasound_destroy(struct xfer_context *xfer) +{ + struct libasound_state *state = xfer->private_data; + + if (state->hw_params) + snd_pcm_hw_params_free(state->hw_params); + if (state->sw_params) + snd_pcm_sw_params_free(state->sw_params); + state->hw_params = NULL; + state->sw_params = NULL; + + if (state->log) + snd_output_close(state->log); + state->log = NULL; +} + +const struct xfer_data xfer_libasound = { + .ops = { + .init = xfer_libasound_init, + .pre_process = xfer_libasound_pre_process, + .process_frames = xfer_libasound_process_frames, + .pause = xfer_libasound_pause, + .post_process = xfer_libasound_post_process, + .destroy = xfer_libasound_destroy, + }, + .private_size = sizeof(struct libasound_state), +}; diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h new file mode 100644 index 00000000..72807cdd --- /dev/null +++ b/axfer/xfer-libasound.h @@ -0,0 +1,45 @@ +/* + * xfer-libasound.h - a header for receiver/transmitter of frames by alsa-lib. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_XFER_LIBASOUND__H_ +#define __ALSA_UTILS_AXFER_XFER_LIBASOUND__H_ + +#include "xfer.h" + +#define logging(state, ...) \ + snd_output_printf(state->log, __VA_ARGS__) + +struct xfer_libasound_ops; + +struct libasound_state { + snd_pcm_t *handle; + snd_output_t *log; + + snd_pcm_hw_params_t *hw_params; + + snd_pcm_sw_params_t *sw_params; + + bool running; + + const struct xfer_libasound_ops *ops; + void *private_data; + + bool verbose; +}; + +struct xfer_libasound_ops { + int (*pre_process)(struct libasound_state *state); + int (*process_frames)(struct libasound_state *state, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs); + void (*post_process)(struct libasound_state *state); + unsigned int private_size; +}; + +#endif diff --git a/axfer/xfer.c b/axfer/xfer.c index 46b36d42..4677bd5b 100644 --- a/axfer/xfer.c +++ b/axfer/xfer.c @@ -12,7 +12,7 @@ #include <stdio.h>
static const char *const xfer_type_labels[] = { - [XFER_TYPE_COUNT] = "", + [XFER_TYPE_LIBASOUND] = "libasound", };
int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, @@ -22,7 +22,7 @@ int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, enum xfer_type type; const struct xfer_data *data; } entries[] = { - {XFER_TYPE_COUNT, NULL}, + {XFER_TYPE_LIBASOUND, &xfer_libasound}, }; int i;
diff --git a/axfer/xfer.h b/axfer/xfer.h index 4e3f1f37..b90c005b 100644 --- a/axfer/xfer.h +++ b/axfer/xfer.h @@ -13,7 +13,7 @@ #include "options.h"
enum xfer_type { - XFER_TYPE_COUNT, + XFER_TYPE_LIBASOUND = 0, };
struct xfer_ops; @@ -66,6 +66,6 @@ struct xfer_data { unsigned int private_size; };
-extern const struct xfer_data xfer_alsa; +extern const struct xfer_data xfer_libasound;
#endif
In alsa-lib PCM API, snd_pcm_read[i|n]() and snd_pcm_write[i|n]() are used to transfer data frames from/to hardware. When a handler is not opened with specific flags, these functions perform blocking operation; i.e. the function call doesn't return till all of request number of data frames are actually handled, or call is interrupted by Unix signals, or PCM substeam corrupts due to hardware reasons.
This commit adds support for this type of data transmission. For cases that requested data frames are not processed by container interface, this commit adds internal cache mechanism to handle rest of data frames in next timing.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/xfer-libasound-irq-rw.c | 406 ++++++++++++++++++++++++++++++++++++++++++ axfer/xfer-libasound.c | 6 + axfer/xfer-libasound.h | 3 + 4 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 axfer/xfer-libasound-irq-rw.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index ab4e3ae1..e928f796 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -43,4 +43,5 @@ axfer_SOURCES = \ xfer.h \ xfer.c \ xfer-libasound.h \ - xfer-libasound.c + xfer-libasound.c \ + xfer-libasound-irq-rw.c diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c new file mode 100644 index 00000000..acbeaee9 --- /dev/null +++ b/axfer/xfer-libasound-irq-rw.c @@ -0,0 +1,406 @@ +/* + * xfer-libasound-irq-rw.c - IRQ-based I/O helper for read/write operation. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer-libasound.h" +#include "misc.h" + +struct frame_cache { + void *buf; + void *buf_ptr; + + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_buffer; + snd_pcm_access_t access; + + int (*handle)(struct libasound_state *state, snd_pcm_state_t status, + unsigned int *frame_count, struct mapper_context *mapper, + struct container_context *cntrs); + void (*align_frames)(struct frame_cache *cache, + unsigned int consumed_count, + unsigned int bytes_per_sample, + unsigned int samples_per_frame); + + unsigned int remained_count; +}; + +static void align_frames_in_i(struct frame_cache *cache, + unsigned int consumed_count, + unsigned int bytes_per_sample, + unsigned int samples_per_frame) +{ + char *buf = cache->buf; + unsigned int offset; + unsigned int size; + + cache->remained_count -= consumed_count; + + offset = bytes_per_sample * samples_per_frame * consumed_count; + size = bytes_per_sample * samples_per_frame * cache->remained_count; + memcpy(buf, buf + offset, size); + + cache->buf_ptr = buf + size; +} + +static void align_frames_in_n(struct frame_cache *cache, + unsigned int consumed_count, + unsigned int bytes_per_sample, + unsigned int samples_per_frame) +{ + char **bufs = cache->buf; + char **buf_ptrs = cache->buf_ptr; + char *buf; + unsigned int offset; + unsigned int size; + int i; + + cache->remained_count -= consumed_count; + + for (i = 0; i < samples_per_frame; ++i) { + buf = bufs[i]; + offset = bytes_per_sample * consumed_count; + size = bytes_per_sample * cache->remained_count; + memcpy(buf, buf + offset, size); + + buf_ptrs[i] = buf + size; + } +} + +static int read_frames(struct libasound_state *state, unsigned int *frame_count, + unsigned int avail_count, struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct frame_cache *cache = state->private_data; + unsigned int consumed_count; + int err; + + /* Trim according up to expected frame count. */ + if (*frame_count < avail_count) + avail_count = *frame_count; + + /* Cache required amount of frames. */ + if (avail_count > cache->remained_count) { + avail_count -= cache->remained_count; + + /* + * Execute write operation according to the shape of buffer. + * These operations automatically start the substream. + */ + if (cache->access == SND_PCM_ACCESS_RW_INTERLEAVED) { + err = snd_pcm_readi(state->handle, cache->buf_ptr, + avail_count); + } else { + err = snd_pcm_readn(state->handle, cache->buf_ptr, + avail_count); + } + if (err < 0) + return err; + cache->remained_count += err; + avail_count = cache->remained_count; + } + + /* Write out to file descriptors. */ + consumed_count = avail_count; + err = mapper_context_process_frames(mapper, cache->buf, + &consumed_count, cntrs); + if (err < 0) + return err; + + cache->align_frames(cache, consumed_count, cache->bytes_per_sample, + cache->samples_per_frame); + + *frame_count = consumed_count; + + return 0; +} + +static int r_process_frames_blocking(struct libasound_state *state, + snd_pcm_state_t status, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + snd_pcm_sframes_t avail; + snd_pcm_uframes_t avail_count; + int err = 0; + + if (status == SND_PCM_STATE_RUNNING) { + /* Check available space on the buffer. */ + avail = snd_pcm_avail(state->handle); + if (avail < 0) { + err = avail; + goto error; + } + avail_count = (snd_pcm_uframes_t)avail; + + if (avail_count == 0) { + /* + * Request data frames so that blocking is just + * released. + */ + err = snd_pcm_sw_params_get_avail_min(state->sw_params, + &avail_count); + if (err < 0) + goto error; + } + } else { + /* Request data frames so that the PCM substream starts. */ + snd_pcm_uframes_t frame_count; + err = snd_pcm_sw_params_get_start_threshold(state->sw_params, + &frame_count); + if (err < 0) + goto error; + + avail_count = (unsigned int)frame_count; + } + + err = read_frames(state, frame_count, avail_count, mapper, cntrs); + if (err < 0) + goto error; + + return 0; +error: + *frame_count = 0; + return err; +} + +static int write_frames(struct libasound_state *state, + unsigned int *frame_count, unsigned int avail_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct frame_cache *cache = state->private_data; + snd_pcm_uframes_t consumed_count; + int err; + + /* Trim according up to expected frame count. */ + if (*frame_count < avail_count) + avail_count = *frame_count; + + /* Cache required amount of frames. */ + if (avail_count > cache->remained_count) { + avail_count -= cache->remained_count; + + /* Read frames to transfer. */ + err = mapper_context_process_frames(mapper, cache->buf_ptr, + &avail_count, cntrs); + if (err < 0) + return err; + cache->remained_count += avail_count; + avail_count = cache->remained_count; + } + + /* + * Execute write operation according to the shape of buffer. These + * operations automatically start the stream. + */ + consumed_count = avail_count; + if (cache->access == SND_PCM_ACCESS_RW_INTERLEAVED) + err = snd_pcm_writei(state->handle, cache->buf, consumed_count); + else + err = snd_pcm_writen(state->handle, cache->buf, consumed_count); + if (err < 0) + return err; + + consumed_count = (unsigned int)err; + cache->align_frames(cache, consumed_count, cache->bytes_per_sample, + cache->samples_per_frame); + + *frame_count = consumed_count; + + return 0; +} + +static int w_process_frames_blocking(struct libasound_state *state, + snd_pcm_state_t status, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + snd_pcm_sframes_t avail; + unsigned int avail_count; + int err; + + if (status == SND_PCM_STATE_RUNNING) { + /* Check available space on the buffer. */ + avail = snd_pcm_avail(state->handle); + if (avail < 0) { + err = avail; + goto error; + } + avail_count = (unsigned int)avail; + + if (avail_count == 0) { + /* + * Fill with data frames so that blocking is just + * released. + */ + snd_pcm_uframes_t avail_min; + err = snd_pcm_sw_params_get_avail_min(state->sw_params, + &avail_min); + if (err < 0) + goto error; + avail_count = (unsigned int)avail_min; + } + } else { + snd_pcm_uframes_t frames_for_start_threshold; + snd_pcm_uframes_t frames_per_period; + + /* Fill with data frames so that the PCM substream starts. */ + err = snd_pcm_sw_params_get_start_threshold(state->sw_params, + &frames_for_start_threshold); + if (err < 0) + goto error; + + /* + * But the above number can be too small and cause XRUN because + * I/O operation is done per period. + */ + err = snd_pcm_hw_params_get_period_size(state->hw_params, + &frames_per_period, NULL); + if (err < 0) + goto error; + + /* + * Use larger one to prevent from both of XRUN and successive + * blocking. + */ + if (frames_for_start_threshold > frames_per_period) + avail_count = (unsigned int)frames_for_start_threshold; + else + avail_count = (unsigned int)frames_per_period; + } + + err = write_frames(state, frame_count, avail_count, mapper, cntrs); + if (err < 0) + goto error; + + return 0; +error: + *frame_count = 0; + return err; +} + +static int irq_rw_pre_process(struct libasound_state *state) +{ + struct frame_cache *cache = state->private_data; + snd_pcm_format_t format; + snd_pcm_uframes_t frames_per_buffer; + int bytes_per_sample; + uint64_t bytes_per_buffer; + int err; + + err = snd_pcm_hw_params_get_format(state->hw_params, &format); + if (err < 0) + return err; + bytes_per_sample = snd_pcm_format_physical_width(format) / 8; + if (bytes_per_sample <= 0) + return -ENXIO; + cache->bytes_per_sample = bytes_per_sample; + + err = snd_pcm_hw_params_get_channels(state->hw_params, + &cache->samples_per_frame); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_buffer_size(state->hw_params, + &frames_per_buffer); + if (err < 0) + return err; + cache->frames_per_buffer = (unsigned int)frames_per_buffer; + + /* Allocate buffer and assign callback for alignment. */ + err = snd_pcm_hw_params_get_access(state->hw_params, &cache->access); + if (err < 0) + return err; + if (cache->access == SND_PCM_ACCESS_RW_INTERLEAVED) { + bytes_per_buffer = cache->bytes_per_sample * + cache->samples_per_frame * + cache->frames_per_buffer; + cache->buf = malloc(bytes_per_buffer); + if (cache->buf == NULL) + return -ENOMEM; + cache->buf_ptr = cache->buf; + cache->align_frames = align_frames_in_i; + } else if (cache->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + char **bufs; + char **buf_ptrs; + int i; + bufs = calloc(cache->samples_per_frame * 2, sizeof(*bufs)); + if (bufs == NULL) + return -ENOMEM; + buf_ptrs = bufs + cache->samples_per_frame * sizeof(*bufs); + for (i = 0; i < cache->samples_per_frame; ++i) { + bytes_per_buffer = cache->bytes_per_sample * + cache->frames_per_buffer; + bufs[i] = malloc(bytes_per_buffer); + if (bufs[i] == NULL) + return -ENOMEM; + buf_ptrs[i] = bufs[i]; + } + cache->buf = bufs; + cache->buf_ptr = buf_ptrs; + cache->align_frames = align_frames_in_n; + } else { + return -ENXIO; + } + + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) + cache->handle = r_process_frames_blocking; + else + cache->handle = w_process_frames_blocking; + + return 0; +} + +static int irq_rw_process_frames(struct libasound_state *state, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct frame_cache *cache = state->private_data; + snd_pcm_state_t status; + + /* Need to recover the stream. */ + status = snd_pcm_state(state->handle); + if (status != SND_PCM_STATE_RUNNING && status != SND_PCM_STATE_PREPARED) + return -EPIPE; + + /* NOTE: Actually, status can be shift always. */ + return cache->handle(state, status, frame_count, mapper, cntrs); +} + +static void irq_rw_post_process(struct libasound_state *state) +{ + struct frame_cache *cache = state->private_data; + + if (cache->buf == NULL) { + if (cache->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + char **bufs = cache->buf; + char **buf_ptrs = cache->buf_ptr; + int i; + for (i = 0; i < cache->samples_per_frame; ++i) { + if (bufs[i]) + free(bufs[i]); + bufs[i] = NULL; + buf_ptrs[i] = NULL; + } + free(cache->buf_ptr); + } + free(cache->buf); + } + cache->buf = NULL; + cache->buf_ptr = NULL; +} + +const struct xfer_libasound_ops xfer_libasound_irq_rw_ops = { + .pre_process = irq_rw_pre_process, + .process_frames = irq_rw_process_frames, + .post_process = irq_rw_post_process, + .private_size = sizeof(struct frame_cache), +}; diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 66e8e1de..52451fff 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -218,6 +218,12 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, return err;
/* Assign I/O operation. */ + if (*access == SND_PCM_ACCESS_RW_INTERLEAVED || + *access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + state->ops = &xfer_libasound_irq_rw_ops; + } else { + return -ENXIO; + } if (state->ops->private_size > 0) { state->private_data = malloc(state->ops->private_size); if (state->private_data == NULL) diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 72807cdd..97b26634 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -42,4 +42,7 @@ struct xfer_libasound_ops { unsigned int private_size; };
+extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_ops; +extern const struct xfer_libasound_ops xfer_libasound_irq_rw_ops; + #endif
In current aplay, default action is to transfer data frames from/to devices. This commit adds support for this functionality.
Event loop is included in an added file. In the loop, the number of handled data frames is manipulated by an appropriate way. As a result, users can stop data transmission frames by frame.
At present, below command line options are supported as a minimal set: * --help (-h) * Just output 'help' string (not written yet). * --verbose (-v) * For verbose output, including information about xfer, mapper and container. * --file-type (-f): string. one of ['wav'|'au'|'voc'|'raw'] * For format of files of given paths. For playback, this is optional because the format is auto-detected. For capture, this is optional too because the format is decided according to suffix of given path. Anyway, this option is used for cases to fail to detect or decide. * --separate-channels (-I) * When using several files as source or destination for data transmission, this option can be used with several file paths. * --device (-D): string * For PCM node in alsa-lib configuration. When this option is not used, 'default' node is used for data transmission. * --format (-f): string. format literals or one of ['cd'|'cdr'|'dat'] * For sample format supported by ALSA PCM interface. Special format can be used. For playback, this is auto-detected according to actual file format. * --channels (-c) * For the number of samples included in one data frame. For playback, this is auto-detected according to actual file format, except for 'raw' format. * --rate (-r) * For the number of data frames transferred in one second. For playback, this is auto-detected according to actual file format, except for 'raw' format.
When '--separate-channels' option is used, users can give several file paths to source/destination of data transmission, else they can give single file path for the purpose. When multiple files are handled by this option, for playback, data frames in first channel is used to construct buffer for data transmission with multi channel. For capture, data frames in each channel of buffer is written to each of given path. Furthermore, when a single path is given for capture, file paths are auto-generated according to available number of channels. For example, 'name.wav' is given for 2 channels capture, 'name-0.wav' and 'name-1.wav' are generated. Without suffix, 'name-0' and 'name-1' are generated.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/main.c | 2 +- axfer/subcmd-transfer.c | 470 ++++++++++++++++++++++++++++++++++++++++++++++++ axfer/subcmd.h | 2 + 4 files changed, 475 insertions(+), 2 deletions(-) create mode 100644 axfer/subcmd-transfer.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index e928f796..b378442b 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -44,4 +44,5 @@ axfer_SOURCES = \ xfer.c \ xfer-libasound.h \ xfer-libasound.c \ - xfer-libasound-irq-rw.c + xfer-libasound-irq-rw.c \ + subcmd-transfer.c diff --git a/axfer/main.c b/axfer/main.c index 9bf02fd9..fecf397a 100644 --- a/axfer/main.c +++ b/axfer/main.c @@ -180,7 +180,7 @@ int main(int argc, char *const *argv) decide_subcmd(argc, argv, &subcmd);
if (subcmd == SUBCMD_TRANSFER) - printf("execute 'transfer' subcmd.\n"); + err = subcmd_transfer(argc, argv, direction); else if (subcmd == SUBCMD_LIST) err = subcmd_list(argc, argv, direction); else if (subcmd == SUBCMD_VERSION) diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c new file mode 100644 index 00000000..5ce312e0 --- /dev/null +++ b/axfer/subcmd-transfer.c @@ -0,0 +1,470 @@ +/* + * subcmd-transfer.c - operations for transfer sub command. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer.h" +#include "subcmd.h" +#include "misc.h" + +#include <signal.h> + +struct context { + struct xfer_context xfer; + struct mapper_context mapper; + struct container_context *cntrs; + unsigned int cntr_count; + + struct context_options opts; + + /* NOTE: To handling Unix signal. */ + bool interrupted; + int signal; +}; + +/* NOTE: To handling Unix signal. */ +static struct context *ctx_ptr; + +static void handle_unix_signal_for_finish(int sig) +{ + int i; + + for (i = 0; i < ctx_ptr->cntr_count; ++i) + ctx_ptr->cntrs[i].interrupted = true; + + ctx_ptr->signal = sig; + ctx_ptr->interrupted = true; +} + +static void handle_unix_signal_for_suspend(int sig) +{ + sigset_t curr, prev; + struct sigaction sa = {0}; + + /* + * 1. suspend substream. + */ + xfer_context_pause(&ctx_ptr->xfer, true); + + /* + * 2. Prepare for default handler(SIG_DFL) of SIGTSTP to stop this + * process. + */ + if (sigaction(SIGTSTP, NULL, &sa) < 0) { + fprintf(stderr, "sigaction(2)\n"); + exit(EXIT_FAILURE); + } + if (sa.sa_handler == SIG_ERR) + exit(EXIT_FAILURE); + if (sa.sa_handler == handle_unix_signal_for_suspend) + sa.sa_handler = SIG_DFL; + if (sigaction(SIGTSTP, &sa, NULL) < 0) { + fprintf(stderr, "sigaction(2)\n"); + exit(EXIT_FAILURE); + } + + /* Queue SIGTSTP. */ + raise(SIGTSTP); + + /* + * Release the queued signal from being blocked. This causes an + * additional interrupt for the default handler. + */ + sigemptyset(&curr); + sigaddset(&curr, SIGTSTP); + if (sigprocmask(SIG_UNBLOCK, &curr, &prev) < 0) { + fprintf(stderr, "sigprocmask(2)\n"); + exit(EXIT_FAILURE); + } + + /* + * 3. SIGCONT is cought and rescheduled. Recover blocking status of + * UNIX signals. + */ + if (sigprocmask(SIG_SETMASK, &prev, NULL) < 0) { + fprintf(stderr, "sigprocmask(2)\n"); + exit(EXIT_FAILURE); + } + + /* Reconfigure this handler for SIGTSTP, instead of default one. */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = handle_unix_signal_for_suspend; + if (sigaction(SIGTSTP, &sa, NULL) < 0) { + fprintf(stderr, "sigaction(2)\n"); + exit(EXIT_FAILURE); + } + + /* 4. Continue the PCM substream. */ + xfer_context_pause(&ctx_ptr->xfer, false); +} + +static int prepare_signal_handler(struct context *ctx) +{ + struct sigaction sa = {0}; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handle_unix_signal_for_finish; + + if (sigaction(SIGINT, &sa, NULL) < 0) + return -errno; + if (sigaction(SIGTERM, &sa, NULL) < 0) + return -errno; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handle_unix_signal_for_suspend; + if (sigaction(SIGTSTP, &sa, NULL) < 0) + return -errno; + + ctx_ptr = ctx; + + return 0; +} + +static int context_init(struct context *ctx, snd_pcm_stream_t direction) +{ + /* Initialize transfer. */ + return xfer_context_init(&ctx->xfer, XFER_TYPE_LIBASOUND, direction, + &ctx->opts); +} + +static int capture_pre_process(struct context *ctx, snd_pcm_access_t *access, + snd_pcm_format_t *sample_format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_uframes_t *frames_per_buffer, + uint64_t *total_frame_count) +{ + unsigned int channels; + int i; + int err; + + /* + * Use given options for common parameters. Else, leave the decition to + * each backend. + */ + if (ctx->opts.sample_format != SND_PCM_FORMAT_UNKNOWN) + *sample_format = ctx->opts.sample_format; + if (ctx->opts.samples_per_frame > 0) + *samples_per_frame = ctx->opts.samples_per_frame; + if (ctx->opts.frames_per_second > 0) + *frames_per_second = ctx->opts.frames_per_second; + + err = xfer_context_pre_process(&ctx->xfer, sample_format, + samples_per_frame, frames_per_second, + access, frames_per_buffer); + if (err < 0) + return err; + + err = context_options_fixup_for_capture(&ctx->opts, *samples_per_frame); + if (err < 0) + return err; + + ctx->cntr_count = ctx->opts.path_count; + if (ctx->cntr_count > 1) + channels = 1; + else + channels = *samples_per_frame; + + /* Prepare for containers. */ + ctx->cntrs = calloc(ctx->cntr_count, sizeof(*ctx->cntrs)); + if (ctx->cntrs == NULL) + return -ENOMEM; + + *total_frame_count = 0; + for (i = 0; i < ctx->cntr_count; ++i) { + uint64_t frame_count; + + err = container_builder_init(ctx->cntrs + i, ctx->opts.paths[i], + ctx->opts.cntr_format, + ctx->opts.verbose > 1); + if (err < 0) + return err; + + err = container_context_pre_process(ctx->cntrs + i, + sample_format, &channels, + frames_per_second, + &frame_count); + if (err < 0) + return err; + + if (*total_frame_count == 0) + *total_frame_count = frame_count; + if (frame_count < *total_frame_count) + *total_frame_count = frame_count; + } + + return 0; +} + +static int playback_pre_process(struct context *ctx, snd_pcm_access_t *access, + snd_pcm_format_t *sample_format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_uframes_t *frames_per_buffer, + uint64_t *total_frame_count) +{ + int i; + int err; + + /* Prepare for containers. */ + ctx->cntrs = calloc(ctx->opts.path_count, sizeof(*ctx->cntrs)); + if (ctx->cntrs == NULL) + return -ENOMEM; + ctx->cntr_count = ctx->opts.path_count; + + for (i = 0; i < ctx->cntr_count; ++i) { + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; + uint64_t frame_count; + + err = container_parser_init(ctx->cntrs + i, ctx->opts.paths[i], + ctx->opts.verbose > 1); + if (err < 0) + return err; + + if (i == 0) { + /* For a raw container. */ + format = ctx->opts.sample_format; + channels = ctx->opts.samples_per_frame; + rate = ctx->opts.frames_per_second; + } else { + format = *sample_format; + channels = *samples_per_frame; + rate = *frames_per_second; + } + + err = container_context_pre_process(ctx->cntrs + i, &format, + &channels, &rate, + &frame_count); + if (err < 0) + return err; + + if (format == SND_PCM_FORMAT_UNKNOWN || channels == 0 || + rate == 0) { + fprintf(stderr, + _("Sample format, channels and rate should be " + "indicated for given files.\n")); + return -EINVAL; + } + + if (i == 0) { + *sample_format = format; + *samples_per_frame = channels; + *frames_per_second = rate; + *total_frame_count = frame_count; + } else { + if (format != *sample_format) { + fprintf(stderr, + _("When using several files, they " + "should include the same sample " + "format.\n")); + return -EINVAL; + } + /* + * No need to check channels to handle multiple + * containers. + */ + if (rate != *frames_per_second) { + fprintf(stderr, + _("When using several files, they " + "should include samples at the same " + "sampling rate.\n")); + return -EINVAL; + } + if (frame_count < *total_frame_count) + *total_frame_count = frame_count; + } + } + + if (ctx->cntr_count > 1) + *samples_per_frame = ctx->cntr_count; + err = context_options_fixup_for_playback(&ctx->opts, *sample_format, + *samples_per_frame, + *frames_per_second); + if (err < 0) + return err; + + /* Configure hardware with these parameters. */ + err = xfer_context_pre_process(&ctx->xfer, sample_format, + samples_per_frame, frames_per_second, + access, frames_per_buffer); + if (err < 0) + return err; + + return 0; +} + +static int context_pre_process(struct context *ctx, snd_pcm_stream_t direction, + uint64_t *total_frame_count) +{ + snd_pcm_access_t access; + snd_pcm_format_t sample_format = SND_PCM_FORMAT_UNKNOWN; + unsigned int samples_per_frame = 0; + unsigned int frames_per_second = 0; + snd_pcm_uframes_t frames_per_buffer = 0; + unsigned int bytes_per_sample = 0; + enum mapper_type mapper_type; + int err; + + if (direction == SND_PCM_STREAM_CAPTURE) { + mapper_type = MAPPER_TYPE_DEMUXER; + err = capture_pre_process(ctx, &access, &sample_format, + &samples_per_frame, + &frames_per_second, + &frames_per_buffer, + total_frame_count); + } else { + mapper_type = MAPPER_TYPE_MUXER; + err = playback_pre_process(ctx, &access, &sample_format, + &samples_per_frame, + &frames_per_second, + &frames_per_buffer, + total_frame_count); + } + if (err < 0) + return err; + + /* Prepare for mapper. */ + err = mapper_context_init(&ctx->mapper, mapper_type, ctx->cntr_count, + ctx->opts.verbose > 1); + if (err < 0) + return err; + + bytes_per_sample = snd_pcm_format_physical_width(sample_format) / 8; + if (bytes_per_sample <= 0) + return -ENXIO; + err = mapper_context_pre_process(&ctx->mapper, access, bytes_per_sample, + samples_per_frame, frames_per_buffer, + ctx->cntrs); + if (err < 0) + return err; + + ctx->opts.sample_format = sample_format; + ctx->opts.samples_per_frame = samples_per_frame; + ctx->opts.frames_per_second = frames_per_second; + + return 0; +} + +static int context_process_frames(struct context *ctx, + snd_pcm_stream_t direction, + uint64_t expected_frame_count, + uint64_t *actual_frame_count) +{ + bool verbose = ctx->opts.verbose > 2; + unsigned int frame_count; + int i; + int err = 0; + + *actual_frame_count = 0; + while (!ctx->interrupted) { + struct container_context *cntr; + + /* Tell remains to expected frame count. */ + frame_count = expected_frame_count - *actual_frame_count; + err = xfer_context_process_frames(&ctx->xfer, &ctx->mapper, + ctx->cntrs, &frame_count); + if (err < 0) { + if (err == -EAGAIN || err == -EINTR) + continue; + break; + } + if (verbose) { + fprintf(stderr, + " handled: %u\n", frame_count); + } + for (i = 0; i < ctx->cntr_count; ++i) { + cntr = &ctx->cntrs[i]; + if (cntr->eof) + break; + } + if (i < ctx->cntr_count) + break; + + *actual_frame_count += frame_count; + if (*actual_frame_count >= expected_frame_count) + break; + } + + return err; +} + +static void context_post_process(struct context *ctx, + uint64_t accumulated_frame_count) +{ + uint64_t total_frame_count; + int i; + + xfer_context_post_process(&ctx->xfer); + + if (ctx->cntrs) { + for (i = 0; i < ctx->cntr_count; ++i) { + container_context_post_process(ctx->cntrs + i, + &total_frame_count); + container_context_destroy(ctx->cntrs + i); + } + free(ctx->cntrs); + } + + mapper_context_post_process(&ctx->mapper); + mapper_context_destroy(&ctx->mapper); +} + +static void context_destroy(struct context *ctx) +{ + xfer_context_destroy(&ctx->xfer); + context_options_destroy(&ctx->opts); +} + +int subcmd_transfer(int argc, char *const *argv, snd_pcm_stream_t direction) +{ + struct context ctx = {0}; + + uint64_t expected_frame_count = 0; + uint64_t actual_frame_count = 0; + + int err = 0; + + /* Renewed command system. */ + if (argc > 2 && !strcmp(argv[1], "transfer")) { + /* Go ahead to parse file paths properly. */ + --argc; + ++argv; + } + + err = context_options_init(&ctx.opts, argc, argv, direction); + if (err < 0) + return err; + if (ctx.opts.help) { + printf("xfer help.\n"); + return 0; + } + + err = prepare_signal_handler(&ctx); + if (err < 0) + return err; + + err = context_init(&ctx, direction); + if (err < 0) + goto end; + + err = context_pre_process(&ctx, direction, &expected_frame_count); + if (err < 0) + goto end; + + err = context_process_frames(&ctx, direction, expected_frame_count, + &actual_frame_count); +end: + context_post_process(&ctx, actual_frame_count); + + context_destroy(&ctx); + + return err; +} diff --git a/axfer/subcmd.h b/axfer/subcmd.h index 940d6814..25bfa4f8 100644 --- a/axfer/subcmd.h +++ b/axfer/subcmd.h @@ -13,4 +13,6 @@
int subcmd_list(int argc, char *const *argv, snd_pcm_stream_t direction);
+int subcmd_transfer(int argc, char *const *argv, snd_pcm_stream_t direction); + #endif
In current aplay, some informative output is available as a default. This can be suppressed by a quiet option. This commit adds support for it.
Unfortunately, original implementation includes contradiction in a case to handle multiple files, thus this commit cannot keep fully compatibility.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 5 ++++- axfer/options.h | 1 + axfer/subcmd-transfer.c | 27 +++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/axfer/options.c b/axfer/options.c index 256f93a1..6154c0c2 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -263,11 +263,12 @@ static int apply_policies(struct context_options *opts, int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvt:ID:f:c:r:"; + static const char *s_opts = "hvqt:ID:f:c:r:"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, {"verbose", 0, 0, 'v'}, + {"quiet", 0, 0, 'q'}, /* For containers. */ {"file-type", 1, 0, 't'}, /* For mapper. */ @@ -295,6 +296,8 @@ int context_options_init(struct context_options *opts, int argc, opts->help = true; else if (c == 'v') ++opts->verbose; + else if (c == 'q') + opts->quiet = true; else if (c == 't') cntr_format_literal = optarg; else if (c == 'I') diff --git a/axfer/options.h b/axfer/options.h index 38bdbb74..9c9b46d7 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -16,6 +16,7 @@ struct context_options {
/* For generic purposes. */ unsigned int verbose; + bool quiet;
/* For containers. */ char **paths; diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 5ce312e0..25fa6715 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -363,6 +363,21 @@ static int context_process_frames(struct context *ctx, int i; int err = 0;
+ if (!ctx->opts.quiet) { + fprintf(stderr, + _("%s: Format '%s', Rate %u Hz, Channels "), + snd_pcm_stream_name(direction), + snd_pcm_format_description(ctx->opts.sample_format), + ctx->opts.frames_per_second); + if (ctx->opts.samples_per_frame == 1) + fprintf(stderr, _("'monaural'")); + else if (ctx->opts.samples_per_frame == 2) + fprintf(stderr, "'Stereo'"); + else + fprintf(stderr, "%u", ctx->opts.samples_per_frame); + fprintf(stderr, "\n"); + } + *actual_frame_count = 0; while (!ctx->interrupted) { struct container_context *cntr; @@ -393,6 +408,18 @@ static int context_process_frames(struct context *ctx, break; }
+ if (!ctx->opts.quiet) { + fprintf(stderr, + _("%s: Expected %lu frames, Actual %lu frames\n"), + snd_pcm_stream_name(direction), expected_frame_count, + *actual_frame_count); + if (ctx->interrupted) { + fprintf(stderr, _("Aborted by signal: %s\n"), + strsignal(ctx->signal)); + return 0; + } + } + return err; }
In ALSA PCM interface, before configuring hardware actually, applications can request available set of hardware parameters for runtime of PCM substream. The set of parameters are represented and delivered by a structure.
In alsa-lib PCM API, the above design is abstracted by a series of snd_pcm_hw_params_xxx() functions. An actual layout of the structure is hidden from applications by an opaque pointer.
In aplay, '--dump-hw-params' option is for this purpose. With this option, the command output available set of the hardware parameters.
This commit adds support for the option. Unlike aplay, this commit takes this program to finish after dumping the parameters for simplicity of usage.
I note that all of combinations in the set are not necessarily available when the PCM substream includes dependencies of parameters described by constraints and rules.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 9 +++++++++ axfer/options.h | 3 +++ axfer/subcmd-transfer.c | 2 ++ axfer/xfer-libasound.c | 11 +++++++++++ 4 files changed, 25 insertions(+)
diff --git a/axfer/options.c b/axfer/options.c index 6154c0c2..08a047dd 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -12,6 +12,11 @@ #include <getopt.h> #include <math.h>
+enum no_short_opts { + /* 128 belongs to non us-ascii character set. */ + OPT_DUMP_HW_PARAMS = 128, +}; + static const char *const allowed_duplication[] = { "/dev/null", "/dev/zero", @@ -278,6 +283,8 @@ int context_options_init(struct context_options *opts, int argc, {"format", 1, 0, 'f'}, {"channels", 1, 0, 'c'}, {"rate", 1, 0, 'r'}, + /* For debugging. */ + {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, {NULL, 0, 0, 0}, }; const char *cntr_format_literal = NULL; @@ -310,6 +317,8 @@ int context_options_init(struct context_options *opts, int argc, opts->samples_per_frame = parse_l(optarg, &err); else if (c == 'r') opts->frames_per_second = parse_l(optarg, &err); + else if (c == OPT_DUMP_HW_PARAMS) + opts->dump_hw_params = true; else continue;
diff --git a/axfer/options.h b/axfer/options.h index 9c9b46d7..743279af 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -31,6 +31,9 @@ struct context_options { snd_pcm_format_t sample_format; unsigned int samples_per_frame; unsigned int frames_per_second; + + /* For debugging. */ + bool dump_hw_params; };
int context_options_init(struct context_options *opts, int argc, diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 25fa6715..317a57e3 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -481,6 +481,8 @@ int subcmd_transfer(int argc, char *const *argv, snd_pcm_stream_t direction) err = context_init(&ctx, direction); if (err < 0) goto end; + if (ctx.opts.dump_hw_params) + goto end;
err = context_pre_process(&ctx, direction, &expected_frame_count); if (err < 0) diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 52451fff..f75dc176 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -67,6 +67,17 @@ static int xfer_libasound_init(struct xfer_context *xfer,
/* TODO: Applying NO_PERIOD_WAKEUP should be done here. */
+ if (opts->dump_hw_params) { + logging(state, _("Available HW Params of node: %s\n"), + snd_pcm_name(state->handle)); + snd_pcm_hw_params_dump(state->hw_params, state->log); + /* + * TODO: there're more parameters which are not dumped by + * alsa-lib. + */ + return 0; + } + return set_access_hw_param(state->handle, state->hw_params, opts); }
In aplay, some options are available to stop data transmission by frame unit. This commit adds support for them.
There's a similar option; '--max-file-time'. This option is used for capture data transmission to switch file to write data frame. However, this may brings complicated file handling to this program. To reduce maintaining cost, this option is obsoleted. Additionally, a handler for SIGUSR1 Unix signal has similar feature to switch the file. For the same reason, the handler is also obsoleted.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 63 ++++++++++++++++++++++++++++++++++++++----------- axfer/options.h | 2 ++ axfer/subcmd-transfer.c | 3 +++ 3 files changed, 54 insertions(+), 14 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index 08a047dd..f3d2dfcf 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -15,6 +15,8 @@ enum no_short_opts { /* 128 belongs to non us-ascii character set. */ OPT_DUMP_HW_PARAMS = 128, + /* Obsoleted. */ + OPT_MAX_FILE_TIME, };
static const char *const allowed_duplication[] = { @@ -265,15 +267,35 @@ static int apply_policies(struct context_options *opts, return 0; }
+void context_options_calculate_duration(struct context_options *opts, + unsigned int frames_per_second, uint64_t *total_frame_count) +{ + uint64_t frame_count; + + if (opts->duration_seconds > 0) { + frame_count = opts->duration_seconds * frames_per_second; + if (frame_count < *total_frame_count) + *total_frame_count = frame_count; + } + + if (opts->duration_frames > 0) { + frame_count = opts->duration_frames; + if (frame_count < *total_frame_count) + *total_frame_count = frame_count; + } +} + int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvqt:ID:f:c:r:"; + static const char *s_opts = "hvqd:s:t:ID:f:c:r:"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, {"verbose", 0, 0, 'v'}, {"quiet", 0, 0, 'q'}, + {"duration", 1, 0, 'd'}, + {"samples", 1, 0, 's'}, /* For containers. */ {"file-type", 1, 0, 't'}, /* For mapper. */ @@ -285,18 +307,21 @@ int context_options_init(struct context_options *opts, int argc, {"rate", 1, 0, 'r'}, /* For debugging. */ {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, + /* Obsoleted. */ + {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, {NULL, 0, 0, 0}, }; const char *cntr_format_literal = NULL; const char *node_literal = NULL; const char *sample_format_literal = NULL; + int l_index = 0; int c; int err = 0;
optind = 0; opterr = 0; while (1) { - c = getopt_long(argc, argv, s_opts, l_opts, NULL); + c = getopt_long(argc, argv, s_opts, l_opts, &l_index); if (c < 0) break; else if (c == 'h') @@ -305,6 +330,10 @@ int context_options_init(struct context_options *opts, int argc, ++opts->verbose; else if (c == 'q') opts->quiet = true; + else if (c == 'd') + opts->duration_seconds = parse_l(optarg, &err); + else if (c == 's') + opts->duration_frames = parse_l(optarg, &err); else if (c == 't') cntr_format_literal = optarg; else if (c == 'I') @@ -319,7 +348,13 @@ int context_options_init(struct context_options *opts, int argc, opts->frames_per_second = parse_l(optarg, &err); else if (c == OPT_DUMP_HW_PARAMS) opts->dump_hw_params = true; - else + else if (c == OPT_MAX_FILE_TIME) { + fprintf(stderr, + "An option '--%s' is obsoleted and has no " + "effect.\n", + l_opts[l_index].name); + err = -EINVAL; + } else continue;
if (err < 0) @@ -492,20 +527,20 @@ int context_options_fixup_for_capture(struct context_options *opts, int i, j; int err;
- if (!opts->multiple_cntrs) { - if (opts->path_count == 1) + if (opts->path_count == 1) { + if (!strcmp(opts->paths[0], "-")) + return 0; + if (!opts->multiple_cntrs) err = fixup_paths(opts); else - return -EINVAL; - } else { - if (opts->path_count == 1) { err = create_paths(opts, samples_per_frame); - } else { - if (opts->path_count == samples_per_frame) - err = fixup_paths(opts); - else - return -EINVAL; - } + } else { + if (!opts->multiple_cntrs) + return -EINVAL; + if (opts->path_count != samples_per_frame) + return -EINVAL; + else + err = fixup_paths(opts); } if (err < 0) return err; diff --git a/axfer/options.h b/axfer/options.h index 743279af..ddbdbfa9 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -17,6 +17,8 @@ struct context_options { /* For generic purposes. */ unsigned int verbose; bool quiet; + uint64_t duration_seconds; + uint64_t duration_frames;
/* For containers. */ char **paths; diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 317a57e3..6c7e6ad8 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -346,6 +346,9 @@ static int context_pre_process(struct context *ctx, snd_pcm_stream_t direction, if (err < 0) return err;
+ context_options_calculate_duration(&ctx->opts, frames_per_second, + total_frame_count); + ctx->opts.sample_format = sample_format; ctx->opts.samples_per_frame = samples_per_frame; ctx->opts.frames_per_second = frames_per_second;
In aplay, '--fatal-errors' option has an effect to give up recovery of PCM substream from XRUN state. This commit adds support for this option.
In original implementation, this option brings program abort. This seems to generate core dump of process VMA. However, typically, XRUN comes from timing mismatch between hardware and application, therefore core dump has less helpful. This commit finishes this program in usual way with this option at XRUN.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 4 ++++ axfer/options.h | 1 + axfer/xfer-libasound.c | 4 ++-- axfer/xfer-libasound.h | 1 + 4 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index f3d2dfcf..91708bb4 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -15,6 +15,7 @@ enum no_short_opts { /* 128 belongs to non us-ascii character set. */ OPT_DUMP_HW_PARAMS = 128, + OPT_FATAL_ERRORS, /* Obsoleted. */ OPT_MAX_FILE_TIME, }; @@ -307,6 +308,7 @@ int context_options_init(struct context_options *opts, int argc, {"rate", 1, 0, 'r'}, /* For debugging. */ {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, + {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, /* Obsoleted. */ {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, {NULL, 0, 0, 0}, @@ -348,6 +350,8 @@ int context_options_init(struct context_options *opts, int argc, opts->frames_per_second = parse_l(optarg, &err); else if (c == OPT_DUMP_HW_PARAMS) opts->dump_hw_params = true; + else if (c == OPT_FATAL_ERRORS) + opts->finish_at_xrun = true; else if (c == OPT_MAX_FILE_TIME) { fprintf(stderr, "An option '--%s' is obsoleted and has no " diff --git a/axfer/options.h b/axfer/options.h index ddbdbfa9..de9ac998 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -36,6 +36,7 @@ struct context_options {
/* For debugging. */ bool dump_hw_params; + bool finish_at_xrun; };
int context_options_init(struct context_options *opts, int argc, diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index f75dc176..7ffbc287 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -41,6 +41,7 @@ static int xfer_libasound_init(struct xfer_context *xfer, return err;
state->verbose = xfer->verbose > 1; + state->finish_at_xrun = opts->finish_at_xrun;
if (opts->node == NULL) node = "default"; @@ -272,13 +273,12 @@ static int xfer_libasound_process_frames(struct xfer_context *xfer,
err = state->ops->process_frames(state, frame_count, mapper, cntrs); if (err < 0) { - if (err == -EPIPE) { + if (err == -EPIPE && !state->finish_at_xrun) { /* * Recover the stream and continue processing * immediately. In this program -EPIPE comes from * libasound implementation instead of file I/O. */ - err = snd_pcm_prepare(state->handle); }
diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 97b26634..25768c41 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -30,6 +30,7 @@ struct libasound_state { void *private_data;
bool verbose; + bool finish_at_xrun; };
struct xfer_libasound_ops {
There're several types of system calls for multiplexed I/O. They're used to receive notifications of I/O events. Typically, userspace applications call them against file descriptor to yield CPU. When I/O is enabled on any of the descriptors, a task of the application is rescheduled, then the application execute I/O calls.
This commit adds a common interface for this type of system calls, named as 'waiter'. This is expected to be used with non-blocking file operation and operations on mapped page frame.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 7 ++++-- axfer/waiter.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/waiter.h | 45 +++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 axfer/waiter.c create mode 100644 axfer/waiter.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index b378442b..f290cca8 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -21,7 +21,8 @@ noinst_HEADERS = \ mapper.h options.h \ xfer.h \ - xfer-alsa.h + xfer-alsa.h \ + waiter.h
axfer_SOURCES = \ misc.h \ @@ -45,4 +46,6 @@ axfer_SOURCES = \ xfer-libasound.h \ xfer-libasound.c \ xfer-libasound-irq-rw.c \ - subcmd-transfer.c + subcmd-transfer.c \ + waiter.h \ + waiter.c diff --git a/axfer/waiter.c b/axfer/waiter.c new file mode 100644 index 00000000..dfe42f84 --- /dev/null +++ b/axfer/waiter.c @@ -0,0 +1,66 @@ +/* + * waiter.c - I/O event waiter. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "waiter.h" + +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "misc.h" + +int waiter_context_init(struct waiter_context *waiter, enum waiter_type type) +{ + struct { + enum waiter_type type; + const struct waiter_data *waiter; + } entries[] = { + {WAITER_TYPE_COUNT, NULL}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + if (entries[i].type == type) + break; + } + if (i == ARRAY_SIZE(entries)) + return -EINVAL; + + waiter->private_data = malloc(entries[i].waiter->private_size); + if (waiter->private_data == NULL) + return -ENOMEM; + memset(waiter->private_data, 0, entries[i].waiter->private_size); + + waiter->type = type; + waiter->ops = &entries[i].waiter->ops; + + return 0; +} + +int waiter_context_prepare(struct waiter_context *waiter, int *fds, + unsigned int fd_count) +{ + return waiter->ops->prepare(waiter, fds, fd_count); +} + +int waiter_context_wait_event(struct waiter_context *waiter, int timeout_msec) +{ + return waiter->ops->wait_event(waiter, timeout_msec); +} + +void waiter_context_release(struct waiter_context *waiter) +{ + waiter->ops->release(waiter); +} + +void waiter_context_destroy(struct waiter_context *waiter) +{ + if (waiter->private_data) + free(waiter->private_data); + waiter->private_data = NULL; +} diff --git a/axfer/waiter.h b/axfer/waiter.h new file mode 100644 index 00000000..dbd7ca70 --- /dev/null +++ b/axfer/waiter.h @@ -0,0 +1,45 @@ +/* + * waiter.h - a header for I/O event waiter. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_WAITER__H_ +#define __ALSA_UTILS_AXFER_WAITER__H_ + +enum waiter_type { + WAITER_TYPE_COUNT, +}; + +struct waiter_ops; + +struct waiter_context { + enum waiter_type type; + const struct waiter_ops *ops; + void *private_data; +}; + +int waiter_context_init(struct waiter_context *waiter, enum waiter_type type); +int waiter_context_prepare(struct waiter_context *waiter, int *fds, + unsigned int fd_count); +int waiter_context_wait_event(struct waiter_context *waiter, int timeout_msec); +void waiter_context_release(struct waiter_context *waiter); +void waiter_context_destroy(struct waiter_context *waiter); + +/* For internal use in 'waiter' module. */ + +struct waiter_ops { + int (*prepare)(struct waiter_context *waiter, int *fds, + unsigned int fd_count); + int (*wait_event)(struct waiter_context *waiter, int timeout_msec); + void (*release)(struct waiter_context *waiter); +}; + +struct waiter_data { + struct waiter_ops ops; + unsigned int private_size; +}; + +#endif
This commit adds support of waiter for poll(2) system call.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 ++- axfer/waiter-poll.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/waiter.c | 2 +- axfer/waiter.h | 3 +++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 axfer/waiter-poll.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index f290cca8..a4e9f1bc 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -48,4 +48,5 @@ axfer_SOURCES = \ xfer-libasound-irq-rw.c \ subcmd-transfer.c \ waiter.h \ - waiter.c + waiter.c \ + waiter-poll.c diff --git a/axfer/waiter-poll.c b/axfer/waiter-poll.c new file mode 100644 index 00000000..70813ee0 --- /dev/null +++ b/axfer/waiter-poll.c @@ -0,0 +1,68 @@ +/* + * waiter-waiter-poll.c - Waiter for event notification by poll(2). + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "waiter.h" +#include "misc.h" + +#include <stdlib.h> +#include <errno.h> +#include <poll.h> + +struct poll_state { + struct pollfd *pfds; + unsigned int count; +}; + +static int poll_prepare(struct waiter_context *waiter, int *fds, + unsigned int fd_count) +{ + struct poll_state *state = waiter->private_data; + int i; + + state->pfds = calloc(fd_count, sizeof(struct pollfd)); + if (state->pfds == NULL) + return -ENOMEM; + + for (i = 0; i < fd_count; ++i) { + state->pfds[i].fd = fds[i]; + state->pfds[i].events = POLLIN | POLLOUT; + } + + state->count = fd_count; + + return 0; +} + +static int poll_wait_event(struct waiter_context *waiter, int timeout_msec) +{ + struct poll_state *state = waiter->private_data; + int err; + + err = poll(state->pfds, state->count, timeout_msec); + if (err < 0) + return -errno; + + return 0; +} + +static void poll_release(struct waiter_context *waiter) +{ + struct poll_state *state = waiter->private_data; + + free(state->pfds); + state->pfds = 0; +} + +const struct waiter_data waiter_poll = { + .ops = { + .prepare = poll_prepare, + .wait_event = poll_wait_event, + .release = poll_release, + }, + .private_size = sizeof(struct poll_state), +}; diff --git a/axfer/waiter.c b/axfer/waiter.c index dfe42f84..045b7f31 100644 --- a/axfer/waiter.c +++ b/axfer/waiter.c @@ -20,7 +20,7 @@ int waiter_context_init(struct waiter_context *waiter, enum waiter_type type) enum waiter_type type; const struct waiter_data *waiter; } entries[] = { - {WAITER_TYPE_COUNT, NULL}, + {WAITER_TYPE_POLL, &waiter_poll}, }; int i;
diff --git a/axfer/waiter.h b/axfer/waiter.h index dbd7ca70..00147f45 100644 --- a/axfer/waiter.h +++ b/axfer/waiter.h @@ -10,6 +10,7 @@ #define __ALSA_UTILS_AXFER_WAITER__H_
enum waiter_type { + WAITER_TYPE_POLL = 0, WAITER_TYPE_COUNT, };
@@ -42,4 +43,6 @@ struct waiter_data { unsigned int private_size; };
+extern const struct waiter_data waiter_poll; + #endif
In alsa-lib PCM API, snd_pcm_read[i|n]() and snd_pcm_write[i|n] can be used with non-blocking mode. This is available when SND_PCM_NONBLOCK is used as 'mode' argument for a call of snd_pcm_open().
This commit adds support this type of operation. To reduce CPU usage, this commit uses added waiter interface for event notification.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 5 +- axfer/options.h | 1 + axfer/xfer-libasound-irq-rw.c | 104 ++++++++++++++++++++++++++++++++++++++++-- axfer/xfer-libasound.c | 79 +++++++++++++++++++++++++++++++- axfer/xfer-libasound.h | 4 ++ 5 files changed, 187 insertions(+), 6 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index 91708bb4..a8deaf98 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -289,7 +289,7 @@ void context_options_calculate_duration(struct context_options *opts, int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvqd:s:t:ID:f:c:r:"; + static const char *s_opts = "hvqd:s:t:ID:f:c:r:N"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -306,6 +306,7 @@ int context_options_init(struct context_options *opts, int argc, {"format", 1, 0, 'f'}, {"channels", 1, 0, 'c'}, {"rate", 1, 0, 'r'}, + {"nonblock", 0, 0, 'N'}, /* For debugging. */ {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, @@ -348,6 +349,8 @@ int context_options_init(struct context_options *opts, int argc, opts->samples_per_frame = parse_l(optarg, &err); else if (c == 'r') opts->frames_per_second = parse_l(optarg, &err); + else if (c == 'N') + opts->nonblock = true; else if (c == OPT_DUMP_HW_PARAMS) opts->dump_hw_params = true; else if (c == OPT_FATAL_ERRORS) diff --git a/axfer/options.h b/axfer/options.h index de9ac998..fc4f03c1 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -33,6 +33,7 @@ struct context_options { snd_pcm_format_t sample_format; unsigned int samples_per_frame; unsigned int frames_per_second; + bool nonblock;
/* For debugging. */ bool dump_hw_params; diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c index acbeaee9..afe2d34c 100644 --- a/axfer/xfer-libasound-irq-rw.c +++ b/axfer/xfer-libasound-irq-rw.c @@ -169,6 +169,51 @@ error: return err; }
+static int r_process_frames_nonblocking(struct libasound_state *state, + snd_pcm_state_t status, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + snd_pcm_sframes_t avail; + snd_pcm_uframes_t avail_count; + int err = 0; + + if (status != SND_PCM_STATE_RUNNING) { + err = snd_pcm_start(state->handle); + if (err < 0) + goto error; + } + + /* Wait for hardware IRQ when no available space. */ + err = waiter_context_wait_event(state->waiter, -1); + if (err < 0) + goto error; + + /* Check available space on the buffer. */ + avail = snd_pcm_avail(state->handle); + if (avail < 0) { + err = avail; + goto error; + } + avail_count = (snd_pcm_uframes_t)avail; + + if (avail_count == 0) { + /* Let's go to a next iteration. */ + err = 0; + goto error; + } + + err = read_frames(state, frame_count, avail_count, mapper, cntrs); + if (err < 0) + goto error; + + return 0; +error: + *frame_count = 0; + return err; +} + static int write_frames(struct libasound_state *state, unsigned int *frame_count, unsigned int avail_count, struct mapper_context *mapper, @@ -286,6 +331,50 @@ error: return err; }
+static int w_process_frames_nonblocking(struct libasound_state *state, + snd_pcm_state_t status, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + snd_pcm_sframes_t avail; + unsigned int avail_count; + int err; + + /* Wait for hardware IRQ when no left space. */ + err = waiter_context_wait_event(state->waiter, -1); + if (err < 0) + goto error; + + /* Check available space on the buffer. */ + avail = snd_pcm_avail(state->handle); + if (avail < 0) { + err = avail; + goto error; + } + avail_count = (unsigned int)avail; + + if (avail_count == 0) { + /* Let's go to a next iteration. */ + err = 0; + goto error; + } + + err = write_frames(state, frame_count, avail_count, mapper, cntrs); + if (err < 0) + goto error; + + /* + * NOTE: The substream starts automatically when the accumulated number + * of queued data frame exceeds start_threshold. + */ + + return 0; +error: + *frame_count = 0; + return err; +} + static int irq_rw_pre_process(struct libasound_state *state) { struct frame_cache *cache = state->private_data; @@ -350,10 +439,17 @@ static int irq_rw_pre_process(struct libasound_state *state) return -ENXIO; }
- if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) - cache->handle = r_process_frames_blocking; - else - cache->handle = w_process_frames_blocking; + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) { + if (state->nonblock) + cache->handle = r_process_frames_nonblocking; + else + cache->handle = r_process_frames_blocking; + } else { + if (state->nonblock) + cache->handle = w_process_frames_nonblocking; + else + cache->handle = w_process_frames_blocking; + }
return 0; } diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 7ffbc287..1987b573 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -9,6 +9,16 @@ #include "xfer-libasound.h" #include "misc.h"
+static int get_mode(struct context_options *options) +{ + int mode = 0; + + if (options->nonblock) + mode |= SND_PCM_NONBLOCK; + + return mode; +} + static int set_access_hw_param(snd_pcm_t *handle, snd_pcm_hw_params_t *hw_params, struct context_options *opts) @@ -34,6 +44,7 @@ static int xfer_libasound_init(struct xfer_context *xfer, { struct libasound_state *state = xfer->private_data; char *node; + int mode; int err;
err = snd_output_stdio_attach(&state->log, stderr, 0); @@ -48,13 +59,25 @@ static int xfer_libasound_init(struct xfer_context *xfer, else node = opts->node;
- err = snd_pcm_open(&state->handle, node, direction, 0); + mode = get_mode(opts); + err = snd_pcm_open(&state->handle, node, direction, mode); if (err < 0) { logging(state, "Fail to open libasound PCM node for %s: %s\n", snd_pcm_stream_name(direction), node); return err; }
+ state->nonblock = !!(mode & SND_PCM_NONBLOCK); + if (opts->nonblock) { + state->waiter = malloc(sizeof(*state->waiter)); + if (state->waiter == NULL) + return -ENOMEM; + + err = waiter_context_init(state->waiter, WAITER_TYPE_POLL); + if (err < 0) + return err; + } + err = snd_pcm_hw_params_malloc(&state->hw_params); if (err < 0) return err; @@ -82,6 +105,40 @@ static int xfer_libasound_init(struct xfer_context *xfer, return set_access_hw_param(state->handle, state->hw_params, opts); }
+static int prepare_waiter(struct libasound_state *state) +{ + int fd_count; + int *fds; + struct pollfd *pfds; + int i; + int err; + + fd_count = snd_pcm_poll_descriptors_count(state->handle); + + fds = calloc(fd_count, sizeof(int)); + if (fds == NULL) + return -ENOMEM; + + pfds = calloc(fd_count, sizeof(struct pollfd)); + if (pfds == NULL) { + free(fds); + return -ENOMEM; + } + + err = snd_pcm_poll_descriptors(state->handle, pfds, fd_count); + if (err < 0) + goto end; + + for (i = 0; i < fd_count; ++i) + fds[i] = pfds[i].fd; + + err = waiter_context_prepare(state->waiter, fds, fd_count); +end: + free(pfds); + free(fds); + return err; +} + static int configure_hw_params(struct libasound_state *state, snd_pcm_format_t format, unsigned int samples_per_frame, @@ -254,6 +311,16 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, return err; }
+ if (state->waiter) { + /* + * NOTE: This should be after configuring sw_params due to + * timer descriptor. + */ + err = prepare_waiter(state); + if (err < 0) + return err; + } + if (xfer->verbose > 0) snd_pcm_dump(state->handle, state->log);
@@ -341,7 +408,12 @@ static void xfer_libasound_post_process(struct xfer_context *xfer) logging(state, "snd_pcm_drop(): %s\n", snd_strerror(err)); } else { + /* TODO: this is a bug in kernel land. */ + if (state->nonblock) + snd_pcm_nonblock(state->handle, 0); err = snd_pcm_drain(state->handle); + if (state->nonblock) + snd_pcm_nonblock(state->handle, 1); if (err < 0) logging(state, "snd_pcm_drain(): %s\n", snd_strerror(err)); @@ -366,6 +438,11 @@ static void xfer_libasound_destroy(struct xfer_context *xfer) { struct libasound_state *state = xfer->private_data;
+ if (state->waiter) + waiter_context_destroy(state->waiter); + free(state->waiter); + state->waiter = NULL; + if (state->hw_params) snd_pcm_hw_params_free(state->hw_params); if (state->sw_params) diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 25768c41..342651d1 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -10,6 +10,7 @@ #define __ALSA_UTILS_AXFER_XFER_LIBASOUND__H_
#include "xfer.h" +#include "waiter.h"
#define logging(state, ...) \ snd_output_printf(state->log, __VA_ARGS__) @@ -25,6 +26,9 @@ struct libasound_state { snd_pcm_sw_params_t *sw_params;
bool running; + bool nonblock; + + struct waiter_context *waiter;
const struct xfer_libasound_ops *ops; void *private_data;
In alsa-lib PCM API, data frames can be handled in mapped page frame, instead of calling any system calls.
This commit support for this type of operation. To reduce CPU usage, added waiter interface is used for event notification.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/options.c | 12 +- axfer/options.h | 1 + axfer/xfer-libasound-irq-mmap.c | 264 ++++++++++++++++++++++++++++++++++++++++ axfer/xfer-libasound.c | 22 +++- axfer/xfer-libasound.h | 4 +- 6 files changed, 300 insertions(+), 6 deletions(-) create mode 100644 axfer/xfer-libasound-irq-mmap.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index a4e9f1bc..79592bc8 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -49,4 +49,5 @@ axfer_SOURCES = \ subcmd-transfer.c \ waiter.h \ waiter.c \ - waiter-poll.c + waiter-poll.c \ + xfer-libasound-irq-mmap.c diff --git a/axfer/options.c b/axfer/options.c index a8deaf98..3fa29341 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -265,6 +265,13 @@ static int apply_policies(struct context_options *opts, return err; }
+ if (opts->mmap && opts->nonblock) { + fprintf(stderr, + "An option for mmap operation should not be used with " + "nonblocking option.\n"); + return -EINVAL; + } + return 0; }
@@ -289,7 +296,7 @@ void context_options_calculate_duration(struct context_options *opts, int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvqd:s:t:ID:f:c:r:N"; + static const char *s_opts = "hvqd:s:t:ID:f:c:r:NM"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -307,6 +314,7 @@ int context_options_init(struct context_options *opts, int argc, {"channels", 1, 0, 'c'}, {"rate", 1, 0, 'r'}, {"nonblock", 0, 0, 'N'}, + {"mmap", 0, 0, 'M'}, /* For debugging. */ {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, @@ -351,6 +359,8 @@ int context_options_init(struct context_options *opts, int argc, opts->frames_per_second = parse_l(optarg, &err); else if (c == 'N') opts->nonblock = true; + else if (c == 'M') + opts->mmap = true; else if (c == OPT_DUMP_HW_PARAMS) opts->dump_hw_params = true; else if (c == OPT_FATAL_ERRORS) diff --git a/axfer/options.h b/axfer/options.h index fc4f03c1..272ce9b9 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -34,6 +34,7 @@ struct context_options { unsigned int samples_per_frame; unsigned int frames_per_second; bool nonblock; + bool mmap;
/* For debugging. */ bool dump_hw_params; diff --git a/axfer/xfer-libasound-irq-mmap.c b/axfer/xfer-libasound-irq-mmap.c new file mode 100644 index 00000000..f798c4da --- /dev/null +++ b/axfer/xfer-libasound-irq-mmap.c @@ -0,0 +1,264 @@ +/* + * xfer-libasound-irq-mmap.c - Timer-based I/O helper for mmap operation. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer-libasound.h" +#include "misc.h" + +struct map_layout { + snd_pcm_status_t *status; + + char **vector; + unsigned int samples_per_frame; +}; + +static int irq_mmap_pre_process(struct libasound_state *state) +{ + struct map_layout *layout = state->private_data; + snd_pcm_access_t access; + snd_pcm_uframes_t frame_offset; + snd_pcm_uframes_t avail = 0; + int i; + int err; + + err = snd_pcm_status_malloc(&layout->status); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_access(state->hw_params, &access); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_channels(state->hw_params, + &layout->samples_per_frame); + if (err < 0) + return err; + + if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { + layout->vector = calloc(layout->samples_per_frame, + sizeof(*layout->vector)); + if (layout->vector == NULL) + return err; + } + + if (state->verbose) { + const snd_pcm_channel_area_t *areas; + err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, + &avail); + if (err < 0) + return err; + + logging(state, "attributes for mapped page frame:\n"); + for (i = 0; i < layout->samples_per_frame; ++i) { + const snd_pcm_channel_area_t *area = areas + i; + + logging(state, " sample number: %d\n", i); + logging(state, " address: %p\n", area->addr); + logging(state, " bits for offset: %u\n", area->first); + logging(state, " bits/frame: %u\n", area->step); + } + logging(state, "\n"); + } + + return 0; +} + +static int irq_mmap_process_frames(struct libasound_state *state, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct map_layout *layout = state->private_data; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t frame_offset; + snd_pcm_uframes_t avail; + unsigned int avail_count; + void *frame_buf; + snd_pcm_sframes_t consumed_count; + int err; + + if (state->waiter) { + /* Wait for hardware IRQ when no avail space in buffer.*/ + err = waiter_context_wait_event(state->waiter, -1); + if (err < 0) + goto error; + /* + * When rescheduled, current position of data transmission was + * queried to actual hardware by a handler of IRQ. No need to + * perform it; e.g. ioctl(2) with SNDRV_PCM_IOCTL_HWSYNC. + */ + } + + avail = *frame_count; + err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail); + if (err < 0) + goto error; + + /* Trim according up to expected frame count. */ + if (*frame_count < avail) + avail_count = *frame_count; + else + avail_count = (unsigned int)avail; + + /* + * TODO: Perhaps, the complex layout can be supported as a variation of + * vector type. However, there's no driver with this layout. + */ + if (layout->vector == NULL) { + frame_buf = areas[0].addr; + frame_buf += snd_pcm_frames_to_bytes(state->handle, + frame_offset); + } else { + int i; + for (i = 0; i < layout->samples_per_frame; ++i) { + layout->vector[i] = areas[i].addr; + layout->vector[i] += snd_pcm_samples_to_bytes( + state->handle, frame_offset); + } + frame_buf = layout->vector; + } + + err = mapper_context_process_frames(mapper, frame_buf, &avail_count, + cntrs); + if (err < 0) + goto error; + if (avail_count == 0) + goto error; + + consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset, + avail_count); + if (consumed_count < 0) { + err = consumed_count; + goto error; + } + if (consumed_count != avail_count) + logging(state, "A bug of access plugin for this PCM node.\n"); + + *frame_count = consumed_count; + + return 0; +error: + *frame_count = 0; + return err; +} + +static int irq_mmap_r_process_frames(struct libasound_state *state, + unsigned *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct map_layout *layout = state->private_data; + snd_pcm_state_t s; + int err; + + /* + * To querying current status of hardware, we need to care of 3 levels: + * 1. querying status to actual hardware by driver. + * 2. status data in kernel space. + * 3. status data in user space. + * + * Kernel driver perform 1 according to requests of some ioctl(2) + * commands. Between 2 and 3, we need to care of cache coherency on used + * architecture, or concurrent access by IRQ context and process + * context. + * A call of ioctl(2) with SNDRV_PCM_IOCTL_STATUS and + * SNDRV_PCM_IOCTL_STATUS_EXT has enough care of the above three levels. + */ + err = snd_pcm_status(state->handle, layout->status); + if (err < 0) + goto error; + s = snd_pcm_status_get_state(layout->status); + + /* TODO: if reporting something, do here with the status data. */ + + /* For capture direction, need to start stream explicitly. */ + if (s != SND_PCM_STATE_RUNNING) { + if (s != SND_PCM_STATE_PREPARED) { + err = -EPIPE; + goto error; + } + + err = snd_pcm_start(state->handle); + if (err < 0) + goto error; + } + + err = irq_mmap_process_frames(state, frame_count, mapper, cntrs); + if (err < 0) + goto error; + + return 0; +error: + *frame_count = 0; + return err; +} + +static int irq_mmap_w_process_frames(struct libasound_state *state, + unsigned *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct map_layout *layout = state->private_data; + snd_pcm_state_t s; + int err; + + /* Read my comment in 'irq_mmap_r_process_frames(). */ + err = snd_pcm_status(state->handle, layout->status); + if (err < 0) + goto error; + s = snd_pcm_status_get_state(layout->status); + + /* TODO: if reporting something, do here with the status data. */ + + err = irq_mmap_process_frames(state, frame_count, mapper, cntrs); + if (err < 0) + goto error; + + /* Need to start playback stream explicitly */ + if (s != SND_PCM_STATE_RUNNING) { + if (s != SND_PCM_STATE_PREPARED) { + err = -EPIPE; + goto error; + } + + err = snd_pcm_start(state->handle); + if (err < 0) + goto error; + } + + return 0; +error: + *frame_count = 0; + return err; +} + +static void irq_mmap_post_process(struct libasound_state *state) +{ + struct map_layout *layout = state->private_data; + + if (layout->status) + snd_pcm_status_free(layout->status); + layout->status = NULL; + + if (layout->vector) + free(layout->vector); + layout->vector = NULL; +} + +const struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops = { + .pre_process = irq_mmap_pre_process, + .process_frames = irq_mmap_w_process_frames, + .post_process = irq_mmap_post_process, + .private_size = sizeof(struct map_layout), +}; + +const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops = { + .pre_process = irq_mmap_pre_process, + .process_frames = irq_mmap_r_process_frames, + .post_process = irq_mmap_post_process, + .private_size = sizeof(struct map_layout), +}; diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 1987b573..dd882e62 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -30,8 +30,15 @@ static int set_access_hw_param(snd_pcm_t *handle, if (err < 0) return err; snd_pcm_access_mask_none(mask); - snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_INTERLEAVED); - snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_NONINTERLEAVED); + if (opts->mmap) { + snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED); + snd_pcm_access_mask_set(mask, + SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + } else { + snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_access_mask_set(mask, + SND_PCM_ACCESS_RW_NONINTERLEAVED); + } err = snd_pcm_hw_params_set_access_mask(handle, hw_params, mask); snd_pcm_access_mask_free(mask);
@@ -68,7 +75,7 @@ static int xfer_libasound_init(struct xfer_context *xfer, }
state->nonblock = !!(mode & SND_PCM_NONBLOCK); - if (opts->nonblock) { + if (opts->nonblock || opts->mmap) { state->waiter = malloc(sizeof(*state->waiter)); if (state->waiter == NULL) return -ENOMEM; @@ -290,6 +297,12 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, if (*access == SND_PCM_ACCESS_RW_INTERLEAVED || *access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { state->ops = &xfer_libasound_irq_rw_ops; + } else if (*access == SND_PCM_ACCESS_MMAP_INTERLEAVED || + *access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) + state->ops = &xfer_libasound_irq_mmap_r_ops; + else + state->ops = &xfer_libasound_irq_mmap_w_ops; } else { return -ENXIO; } @@ -432,6 +445,9 @@ static void xfer_libasound_post_process(struct xfer_context *xfer) if (state->private_data) free(state->private_data); state->private_data = NULL; + + if (state->waiter) + waiter_context_release(state->waiter); }
static void xfer_libasound_destroy(struct xfer_context *xfer) diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 342651d1..d0771f8a 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -47,7 +47,9 @@ struct xfer_libasound_ops { unsigned int private_size; };
-extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_ops; extern const struct xfer_libasound_ops xfer_libasound_irq_rw_ops;
+extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops; +extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops; + #endif
In aplay, '--test-nowait' is used to suppress calls of snd_pcm_wait() when I/O operations return -EAGAIN or process truncated number of data frames. This seems to be for debugging purpose. In this program, this option is equivalent to suppress event waiting.
This commit adds support for this option.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 13 +++++++++++++ axfer/options.h | 1 + axfer/xfer-libasound-irq-mmap.c | 1 + axfer/xfer-libasound-irq-rw.c | 20 ++++++++++++-------- axfer/xfer-libasound.c | 2 +- 5 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index 3fa29341..b0ea99c3 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -16,6 +16,7 @@ enum no_short_opts { /* 128 belongs to non us-ascii character set. */ OPT_DUMP_HW_PARAMS = 128, OPT_FATAL_ERRORS, + OPT_TEST_NOWAIT, /* Obsoleted. */ OPT_MAX_FILE_TIME, }; @@ -272,6 +273,15 @@ static int apply_policies(struct context_options *opts, return -EINVAL; }
+ if (opts->test_nowait) { + if (!opts->nonblock && !opts->mmap) { + fprintf(stderr, + "An option for nowait test should be used with " + "nonblock or mmap options.\n"); + return -EINVAL; + } + } + return 0; }
@@ -318,6 +328,7 @@ int context_options_init(struct context_options *opts, int argc, /* For debugging. */ {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, + {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, /* Obsoleted. */ {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, {NULL, 0, 0, 0}, @@ -365,6 +376,8 @@ int context_options_init(struct context_options *opts, int argc, opts->dump_hw_params = true; else if (c == OPT_FATAL_ERRORS) opts->finish_at_xrun = true; + else if (c == OPT_TEST_NOWAIT) + opts->test_nowait = true; else if (c == OPT_MAX_FILE_TIME) { fprintf(stderr, "An option '--%s' is obsoleted and has no " diff --git a/axfer/options.h b/axfer/options.h index 272ce9b9..3052d01a 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -39,6 +39,7 @@ struct context_options { /* For debugging. */ bool dump_hw_params; bool finish_at_xrun; + bool test_nowait; };
int context_options_init(struct context_options *opts, int argc, diff --git a/axfer/xfer-libasound-irq-mmap.c b/axfer/xfer-libasound-irq-mmap.c index f798c4da..fb70bbc5 100644 --- a/axfer/xfer-libasound-irq-mmap.c +++ b/axfer/xfer-libasound-irq-mmap.c @@ -86,6 +86,7 @@ static int irq_mmap_process_frames(struct libasound_state *state, err = waiter_context_wait_event(state->waiter, -1); if (err < 0) goto error; + /* * When rescheduled, current position of data transmission was * queried to actual hardware by a handler of IRQ. No need to diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c index afe2d34c..ad320f6f 100644 --- a/axfer/xfer-libasound-irq-rw.c +++ b/axfer/xfer-libasound-irq-rw.c @@ -185,10 +185,12 @@ static int r_process_frames_nonblocking(struct libasound_state *state, goto error; }
- /* Wait for hardware IRQ when no available space. */ - err = waiter_context_wait_event(state->waiter, -1); - if (err < 0) - goto error; + if (state->waiter) { + /* Wait for hardware IRQ when no available space. */ + err = waiter_context_wait_event(state->waiter, -1); + if (err < 0) + goto error; + }
/* Check available space on the buffer. */ avail = snd_pcm_avail(state->handle); @@ -341,10 +343,12 @@ static int w_process_frames_nonblocking(struct libasound_state *state, unsigned int avail_count; int err;
- /* Wait for hardware IRQ when no left space. */ - err = waiter_context_wait_event(state->waiter, -1); - if (err < 0) - goto error; + if (state->waiter) { + /* Wait for hardware IRQ when no left space. */ + err = waiter_context_wait_event(state->waiter, -1); + if (err < 0) + goto error; + }
/* Check available space on the buffer. */ avail = snd_pcm_avail(state->handle); diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index dd882e62..5271d230 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -75,7 +75,7 @@ static int xfer_libasound_init(struct xfer_context *xfer, }
state->nonblock = !!(mode & SND_PCM_NONBLOCK); - if (opts->nonblock || opts->mmap) { + if ((opts->nonblock || opts->mmap) && !opts->test_nowait) { state->waiter = malloc(sizeof(*state->waiter)); if (state->waiter == NULL) return -ENOMEM;
In ALSA PCM interface, two parameters are used for size of intermediate buffer for data frames; period size and buffer size. Actual effects of these sizes differs depending on hardware, but basically the size of period is used for intervals of hardware interrupts and the size of buffer is used to maintain the intermediate buffer as ring buffer. These parameters can be configured as a part of hardware parameters by data frame unit or micro second. PCM API in alsa-lib also includes helper functions to configure them by the two units.
This commit adds support for options to the parameters by both units. When no options are given, default values are applied according to current aplay; available maximum size of buffer up to 500msec, a quarter of the size of buffer for period size. These calculation should be reconsidered perhaps.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 30 +++++++++++++++- axfer/options.h | 5 +++ axfer/subcmd-transfer.c | 6 ++-- axfer/xfer-libasound.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++-- axfer/xfer-libasound.h | 1 - axfer/xfer.c | 5 +-- axfer/xfer.h | 6 ++-- 7 files changed, 134 insertions(+), 11 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index b0ea99c3..325bc16b 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -17,6 +17,8 @@ enum no_short_opts { OPT_DUMP_HW_PARAMS = 128, OPT_FATAL_ERRORS, OPT_TEST_NOWAIT, + OPT_PERIOD_SIZE, + OPT_BUFFER_SIZE, /* Obsoleted. */ OPT_MAX_FILE_TIME, }; @@ -266,6 +268,20 @@ static int apply_policies(struct context_options *opts, return err; }
+ if (opts->msec_per_period > 0 && opts->msec_per_buffer > 0) { + if (opts->msec_per_period > opts->msec_per_buffer) { + opts->msec_per_period = opts->msec_per_buffer; + opts->msec_per_buffer = 0; + } + } + + if (opts->frames_per_period > 0 && opts->frames_per_buffer > 0) { + if (opts->frames_per_period > opts->frames_per_buffer) { + opts->frames_per_period = opts->frames_per_buffer; + opts->frames_per_buffer = 0; + } + } + if (opts->mmap && opts->nonblock) { fprintf(stderr, "An option for mmap operation should not be used with " @@ -306,7 +322,7 @@ void context_options_calculate_duration(struct context_options *opts, int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvqd:s:t:ID:f:c:r:NM"; + static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMF:B:"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -325,6 +341,10 @@ int context_options_init(struct context_options *opts, int argc, {"rate", 1, 0, 'r'}, {"nonblock", 0, 0, 'N'}, {"mmap", 0, 0, 'M'}, + {"period-time", 1, 0, 'F'}, + {"buffer-time", 1, 0, 'B'}, + {"period-size", 1, 0, OPT_PERIOD_SIZE}, + {"buffer-size", 1, 0, OPT_BUFFER_SIZE}, /* For debugging. */ {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, @@ -372,6 +392,14 @@ int context_options_init(struct context_options *opts, int argc, opts->nonblock = true; else if (c == 'M') opts->mmap = true; + else if (c == 'F') + opts->msec_per_period = parse_l(optarg, &err); + else if (c == 'B') + opts->msec_per_buffer = parse_l(optarg, &err); + else if (c == OPT_PERIOD_SIZE) + opts->frames_per_period = parse_l(optarg, &err); + else if (c == OPT_BUFFER_SIZE) + opts->frames_per_buffer = parse_l(optarg, &err); else if (c == OPT_DUMP_HW_PARAMS) opts->dump_hw_params = true; else if (c == OPT_FATAL_ERRORS) diff --git a/axfer/options.h b/axfer/options.h index 3052d01a..f4ac6c64 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -36,6 +36,11 @@ struct context_options { bool nonblock; bool mmap;
+ unsigned int msec_per_period; + unsigned int msec_per_buffer; + unsigned int frames_per_period; + unsigned int frames_per_buffer; + /* For debugging. */ bool dump_hw_params; bool finish_at_xrun; diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 6c7e6ad8..e9a8c181 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -157,7 +157,8 @@ static int capture_pre_process(struct context *ctx, snd_pcm_access_t *access,
err = xfer_context_pre_process(&ctx->xfer, sample_format, samples_per_frame, frames_per_second, - access, frames_per_buffer); + access, frames_per_buffer, + &ctx->opts); if (err < 0) return err;
@@ -294,7 +295,8 @@ static int playback_pre_process(struct context *ctx, snd_pcm_access_t *access, /* Configure hardware with these parameters. */ err = xfer_context_pre_process(&ctx->xfer, sample_format, samples_per_frame, frames_per_second, - access, frames_per_buffer); + access, frames_per_buffer, + &ctx->opts); if (err < 0) return err;
diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 5271d230..1911c160 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -149,7 +149,11 @@ end: static int configure_hw_params(struct libasound_state *state, snd_pcm_format_t format, unsigned int samples_per_frame, - unsigned int frames_per_second) + unsigned int frames_per_second, + unsigned int msec_per_period, + unsigned int msec_per_buffer, + snd_pcm_uframes_t frames_per_period, + snd_pcm_uframes_t frames_per_buffer) { int err;
@@ -220,6 +224,84 @@ static int configure_hw_params(struct libasound_state *state, return err; }
+ /* Keep one of 'frames_per_buffer' and 'msec_per_buffer'. */ + if (frames_per_buffer == 0) { + if (msec_per_buffer == 0) { + err = snd_pcm_hw_params_get_buffer_time_max( + state->hw_params, &msec_per_buffer, NULL); + if (err < 0) { + logging(state, + _("The maximum msec per buffer is not " + "available.\n")); + return err; + } + if (msec_per_buffer > 500000) + msec_per_buffer = 500000; + } + } else if (msec_per_buffer > 0) { + uint64_t msec; + + msec = 1000000 * frames_per_buffer / frames_per_second; + if (msec < msec_per_buffer) + msec_per_buffer = 0; + } + + /* Keep one of 'frames_per_period' and 'msec_per_period'. */ + if (frames_per_period == 0) { + if (msec_per_period == 0) { + if (msec_per_buffer > 0) + msec_per_period = msec_per_buffer / 4; + else + frames_per_period = frames_per_buffer / 4; + } + } else if (msec_per_period > 0) { + uint64_t msec; + + msec = 1000000 * frames_per_period / frames_per_second; + if (msec < msec_per_period) + msec_per_period = 0; + } + + if (msec_per_period) { + err = snd_pcm_hw_params_set_period_time_near(state->handle, + state->hw_params, &msec_per_period, NULL); + if (err < 0) { + logging(state, + _("Fail to configure period time: %u msec\n"), + msec_per_period); + return err; + } + } else { + err = snd_pcm_hw_params_set_period_size_near(state->handle, + state->hw_params, &frames_per_period, NULL); + if (err < 0) { + logging(state, + _("Fail to configure period size: %lu frames\n"), + frames_per_period); + return err; + } + } + + if (msec_per_buffer) { + err = snd_pcm_hw_params_set_buffer_time_near(state->handle, + state->hw_params, &msec_per_buffer, NULL); + if (err < 0) { + logging(state, + _("Fail to configure buffer time: %u msec\n"), + msec_per_buffer); + return err; + } + } else { + err = snd_pcm_hw_params_set_buffer_size_near(state->handle, + state->hw_params, &frames_per_buffer); + if (err < 0) { + logging(state, + _("Fail to configure buffer size: %lu frames\n"), + frames_per_buffer); + return err; + } + } + return snd_pcm_hw_params(state->handle, state->hw_params); }
@@ -265,7 +347,8 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, unsigned int *samples_per_frame, unsigned int *frames_per_second, snd_pcm_access_t *access, - snd_pcm_uframes_t *frames_per_buffer) + snd_pcm_uframes_t *frames_per_buffer, + struct context_options *opts) { struct libasound_state *state = xfer->private_data; int err; @@ -274,7 +357,10 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, return -ENXIO;
err = configure_hw_params(state, *format, *samples_per_frame, - *frames_per_second); + *frames_per_second, opts->msec_per_period, + opts->msec_per_buffer, + opts->frames_per_period, + opts->frames_per_buffer); if (err < 0) { logging(state, _("Current hardware parameters:\n")); snd_pcm_hw_params_dump(state->hw_params, state->log); diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index d0771f8a..db85e604 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -22,7 +22,6 @@ struct libasound_state { snd_output_t *log;
snd_pcm_hw_params_t *hw_params; - snd_pcm_sw_params_t *sw_params;
bool running; diff --git a/axfer/xfer.c b/axfer/xfer.c index 4677bd5b..2f9f9422 100644 --- a/axfer/xfer.c +++ b/axfer/xfer.c @@ -68,7 +68,8 @@ int xfer_context_pre_process(struct xfer_context *xfer, unsigned int *samples_per_frame, unsigned int *frames_per_second, snd_pcm_access_t *access, - snd_pcm_uframes_t *frames_per_buffer) + snd_pcm_uframes_t *frames_per_buffer, + struct context_options *opts) { int err;
@@ -84,7 +85,7 @@ int xfer_context_pre_process(struct xfer_context *xfer,
err = xfer->ops->pre_process(xfer, format, samples_per_frame, frames_per_second, access, - frames_per_buffer); + frames_per_buffer, opts); if (err < 0) return err;
diff --git a/axfer/xfer.h b/axfer/xfer.h index b90c005b..167c9903 100644 --- a/axfer/xfer.h +++ b/axfer/xfer.h @@ -34,7 +34,8 @@ int xfer_context_pre_process(struct xfer_context *xfer, unsigned int *samples_per_frame, unsigned int *frames_per_second, snd_pcm_access_t *access, - snd_pcm_uframes_t *frames_per_buffer); + snd_pcm_uframes_t *frames_per_buffer, + struct context_options *opts); int xfer_context_process_frames(struct xfer_context *xfer, struct mapper_context *mapper, struct container_context *cntrs, @@ -51,7 +52,8 @@ struct xfer_ops { unsigned int *samples_per_frame, unsigned int *frames_per_second, snd_pcm_access_t *access, - snd_pcm_uframes_t *frames_per_buffer); + snd_pcm_uframes_t *frames_per_buffer, + struct context_options *opts); int (*process_frames)(struct xfer_context *xfer, unsigned int *frame_count, struct mapper_context *mapper,
In ALSA PCM interface, some parameters are used to configure runtime of PCM substream independently of actual hardware. These parameters are mainly used to decide the detailed timing to start/stop PCM substream and release I/O blocking state of application. These parameters are represented and delivered by a structure.
In alsa-lib PCM API, the structure is hidden from userspace applications. The applications can set/get actual parameters by helper functions.
In aplay, three of the parameters are configurable. This commit adds support for them. When no options are given, default values are used.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 11 +++++++- axfer/options.h | 4 +++ axfer/xfer-libasound.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 86 insertions(+), 3 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index 325bc16b..a22223a0 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -322,7 +322,7 @@ void context_options_calculate_duration(struct context_options *opts, int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMF:B:"; + static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMF:B:A:R:T:"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -345,6 +345,9 @@ int context_options_init(struct context_options *opts, int argc, {"buffer-time", 1, 0, 'B'}, {"period-size", 1, 0, OPT_PERIOD_SIZE}, {"buffer-size", 1, 0, OPT_BUFFER_SIZE}, + {"avail-min", 1, 0, 'A'}, + {"start-delay", 1, 0, 'R'}, + {"stop-delay", 1, 0, 'T'}, /* For debugging. */ {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, @@ -400,6 +403,12 @@ int context_options_init(struct context_options *opts, int argc, opts->frames_per_period = parse_l(optarg, &err); else if (c == OPT_BUFFER_SIZE) opts->frames_per_buffer = parse_l(optarg, &err); + else if (c == 'A') + opts->msec_for_avail_min = parse_l(optarg, &err); + else if (c == 'R') + opts->msec_for_start_threshold = parse_l(optarg, &err); + else if (c == 'T') + opts->msec_for_stop_threshold = parse_l(optarg, &err); else if (c == OPT_DUMP_HW_PARAMS) opts->dump_hw_params = true; else if (c == OPT_FATAL_ERRORS) diff --git a/axfer/options.h b/axfer/options.h index f4ac6c64..63b36364 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -41,6 +41,10 @@ struct context_options { unsigned int frames_per_period; unsigned int frames_per_buffer;
+ unsigned int msec_for_avail_min; + unsigned int msec_for_start_threshold; + unsigned int msec_for_stop_threshold; + /* For debugging. */ bool dump_hw_params; bool finish_at_xrun; diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 1911c160..533c9492 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -337,8 +337,76 @@ static int retrieve_actual_hw_params(snd_pcm_hw_params_t *hw_params,
static int configure_sw_params(struct libasound_state *state, unsigned int frames_per_second, - unsigned int frames_per_buffer) + unsigned int frames_per_buffer, + unsigned int msec_for_avail_min, + unsigned int msec_for_start_threshold, + unsigned int msec_for_stop_threshold) { + snd_pcm_uframes_t frame_count; + int err; + + if (msec_for_avail_min > 0) { + frame_count = msec_for_avail_min * frames_per_second / 1000000; + if (frame_count == 0 || frame_count > frames_per_buffer) { + logging(state, + _("The msec for 'avail_min' is too %s: %u " + "msec (%lu frames at %u).\n"), + frame_count == 0 ? "small" : "large", + msec_for_avail_min, frame_count, + frames_per_second); + return -EINVAL; + } + err = snd_pcm_sw_params_set_avail_min(state->handle, + state->sw_params, frame_count); + if (err < 0) { + logging(state, + _("Fail to configure 'avail-min'.\n")); + return -EINVAL; + } + } + + if (msec_for_start_threshold > 0) { + frame_count = msec_for_start_threshold * frames_per_second / + 1000000; + if (frame_count == 0 || frame_count > frames_per_buffer) { + logging(state, + _("The msec for 'start-delay' is too %s: %u " + "msec (%lu frames at %u).\n"), + frame_count == 0 ? "small" : "large", + msec_for_start_threshold, frame_count, + frames_per_second); + return -EINVAL; + } + err = snd_pcm_sw_params_set_start_threshold(state->handle, + state->sw_params, frame_count); + if (err < 0) { + logging(state, + _("Fail to configure 'start-delay'.\n")); + return -EINVAL; + } + } + + if (msec_for_stop_threshold > 0) { + frame_count = msec_for_stop_threshold * frames_per_second / + 1000000; + if (frame_count == 0 || frame_count > frames_per_buffer) { + logging(state, + _("The msec for 'stop-delay' is too %s: %u " + "msec (%lu frames at %u).\n"), + frame_count == 0 ? "small" : "large", + msec_for_stop_threshold, frame_count, + frames_per_second); + return -EINVAL; + } + err = snd_pcm_sw_params_set_stop_threshold(state->handle, + state->sw_params, frame_count); + if (err < 0) { + logging(state, + _("Fail to configure 'stop-delay'.\n")); + return -EINVAL; + } + } + return snd_pcm_sw_params(state->handle, state->sw_params); }
@@ -403,7 +471,9 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, return err;
err = configure_sw_params(state, *frames_per_second, - *frames_per_buffer); + *frames_per_buffer, opts->msec_for_avail_min, + opts->msec_for_start_threshold, + opts->msec_for_stop_threshold); if (err < 0) { logging(state, _("Current software parameters:\n")); snd_pcm_sw_params_dump(state->sw_params, state->log);
As of 2017, two userspace library implementations are known; alsa-lib and tinyalsa. The latter is simple I/O library to use ALSA PCM interface. On the other hand, alsa-lib is more complicated than it. This is because it's designed to add features to transmission of data frames; e.g. sample resampling. To achieve this, alsa-lib has its configuration space and plugin system.
In aplay, some options are implemented as a flag for the plugins in alsa-lib. The flag is given to snd_pcm_open(). This commit adds support for the flags.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 17 +++++++++++++++++ axfer/options.h | 6 ++++++ axfer/xfer-libasound.c | 8 ++++++++ 3 files changed, 31 insertions(+)
diff --git a/axfer/options.c b/axfer/options.c index a22223a0..bca87d14 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -19,6 +19,10 @@ enum no_short_opts { OPT_TEST_NOWAIT, OPT_PERIOD_SIZE, OPT_BUFFER_SIZE, + OPT_DISABLE_RESAMPLE, + OPT_DISABLE_CHANNELS, + OPT_DISABLE_FORMAT, + OPT_DISABLE_SOFTVOL, /* Obsoleted. */ OPT_MAX_FILE_TIME, }; @@ -348,6 +352,11 @@ int context_options_init(struct context_options *opts, int argc, {"avail-min", 1, 0, 'A'}, {"start-delay", 1, 0, 'R'}, {"stop-delay", 1, 0, 'T'}, + /* For plugins in alsa-lib. */ + {"disable-resample", 0, 0, OPT_DISABLE_RESAMPLE}, + {"disable-channels", 0, 0, OPT_DISABLE_CHANNELS}, + {"disable-format", 0, 0, OPT_DISABLE_FORMAT}, + {"disable-softvol", 0, 0, OPT_DISABLE_SOFTVOL}, /* For debugging. */ {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, @@ -409,6 +418,14 @@ int context_options_init(struct context_options *opts, int argc, opts->msec_for_start_threshold = parse_l(optarg, &err); else if (c == 'T') opts->msec_for_stop_threshold = parse_l(optarg, &err); + else if (c == OPT_DISABLE_RESAMPLE) + opts->no_auto_resample = true; + else if (c == OPT_DISABLE_CHANNELS) + opts->no_auto_channels = true; + else if (c == OPT_DISABLE_FORMAT) + opts->no_auto_format = true; + else if (c == OPT_DISABLE_SOFTVOL) + opts->no_softvol = true; else if (c == OPT_DUMP_HW_PARAMS) opts->dump_hw_params = true; else if (c == OPT_FATAL_ERRORS) diff --git a/axfer/options.h b/axfer/options.h index 63b36364..95012ad3 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -45,6 +45,12 @@ struct context_options { unsigned int msec_for_start_threshold; unsigned int msec_for_stop_threshold;
+ /* For plugins in alsa-lib. */ + bool no_auto_resample; + bool no_auto_channels; + bool no_auto_format; + bool no_softvol; + /* For debugging. */ bool dump_hw_params; bool finish_at_xrun; diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 533c9492..e01327e2 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -15,6 +15,14 @@ static int get_mode(struct context_options *options)
if (options->nonblock) mode |= SND_PCM_NONBLOCK; + if (options->no_auto_resample) + mode |= SND_PCM_NO_AUTO_RESAMPLE; + if (options->no_auto_channels) + mode |= SND_PCM_NO_AUTO_CHANNELS; + if (options->no_auto_format) + mode |= SND_PCM_NO_AUTO_FORMAT; + if (options->no_softvol) + mode |= SND_PCM_NO_SOFTVOL;
return mode; }
This commit adds support of waiter for Linux specific epoll(7) system call. For portability to the other Unix-like systems such as xBSD, modification of Makefile.am may be required for conditional build, but this commit includes no changes for it.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/waiter-epoll.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++ axfer/waiter.c | 1 + axfer/waiter.h | 2 ++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 axfer/waiter-epoll.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 79592bc8..60ad3a84 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -50,4 +50,5 @@ axfer_SOURCES = \ waiter.h \ waiter.c \ waiter-poll.c \ - xfer-libasound-irq-mmap.c + xfer-libasound-irq-mmap.c \ + waiter-epoll.c diff --git a/axfer/waiter-epoll.c b/axfer/waiter-epoll.c new file mode 100644 index 00000000..33bf8d89 --- /dev/null +++ b/axfer/waiter-epoll.c @@ -0,0 +1,81 @@ +/* + * waiter-epoll.c - Waiter for event notification by epoll(7). + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "waiter.h" +#include "misc.h" + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/epoll.h> + +struct epoll_state { + int epfd; + struct epoll_event *events; + unsigned int count; +}; + +static int epoll_prepare(struct waiter_context *waiter, int *fds, + unsigned int fd_count) +{ + struct epoll_state *state = waiter->private_data; + int i; + + state->events = calloc(fd_count, sizeof(struct epoll_event)); + if (state->events == NULL) + return -ENOMEM; + state->count = fd_count; + + state->epfd = epoll_create(1); + if (state->epfd < 0) + return -errno; + + for (i = 0; i < fd_count; ++i) { + struct epoll_event *ev = &state->events[i]; + ev->events = EPOLLIN | EPOLLOUT; + if (epoll_ctl(state->epfd, EPOLL_CTL_ADD, fds[i], ev) < 0) + return -errno; + } + + return 0; +} + +static int epoll_wait_event(struct waiter_context *waiter, int timeout_msec) +{ + struct epoll_state *state = waiter->private_data; + int err; + + err = epoll_wait(state->epfd, state->events, state->count, timeout_msec); + if (err < 0) + return -errno; + + return 0; +} + +static void epoll_release(struct waiter_context *waiter) +{ + struct epoll_state *state = waiter->private_data; + + if (state->events) + free(state->events); + + close(state->epfd); + + state->events = NULL; + state->epfd = 0; +} + +const struct waiter_data waiter_epoll = { + .ops = { + .prepare = epoll_prepare, + .wait_event = epoll_wait_event, + .release = epoll_release, + }, + .private_size = sizeof(struct epoll_state), +}; diff --git a/axfer/waiter.c b/axfer/waiter.c index 045b7f31..f4e21a16 100644 --- a/axfer/waiter.c +++ b/axfer/waiter.c @@ -21,6 +21,7 @@ int waiter_context_init(struct waiter_context *waiter, enum waiter_type type) const struct waiter_data *waiter; } entries[] = { {WAITER_TYPE_POLL, &waiter_poll}, + {WAITER_TYPE_EPOLL, &waiter_epoll}, }; int i;
diff --git a/axfer/waiter.h b/axfer/waiter.h index 00147f45..3f0dd744 100644 --- a/axfer/waiter.h +++ b/axfer/waiter.h @@ -11,6 +11,7 @@
enum waiter_type { WAITER_TYPE_POLL = 0, + WAITER_TYPE_EPOLL, WAITER_TYPE_COUNT, };
@@ -44,5 +45,6 @@ struct waiter_data { };
extern const struct waiter_data waiter_poll; +extern const struct waiter_data waiter_epoll;
#endif
This commit is an integration to add an option for users to select waiter type. Currently, 'poll' and 'epoll' types are supported. Users give the type for '--waiter-type' ('-w') option to select it.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 36 +++++++++++++++++++++++++++++++++--- axfer/options.h | 3 +++ axfer/waiter.c | 17 +++++++++++++++++ axfer/waiter.h | 2 ++ axfer/xfer-libasound.c | 2 +- 5 files changed, 56 insertions(+), 4 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index bca87d14..c4423b6c 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -176,7 +176,8 @@ static int apply_policies(struct context_options *opts, snd_pcm_stream_t direction, const char *cntr_format_literal, const char *node_literal, - const char *sample_format_literal) + const char *sample_format_literal, + const char *waiter_type_literal) { int err;
@@ -302,6 +303,30 @@ static int apply_policies(struct context_options *opts, } }
+ if (waiter_type_literal != NULL) { + if (opts->test_nowait) { + fprintf(stderr, + "An option for waiter type should not be used " + "with nowait test option.\n"); + return -EINVAL; + } + if (!opts->nonblock && !opts->mmap) { + fprintf(stderr, + "An option for waiter type should be used with " + "nonblock or mmap options.\n"); + return -EINVAL; + } + opts->waiter_type = waiter_type_from_label(waiter_type_literal); + if (opts->waiter_type == WAITER_TYPE_UNKNOWN) { + fprintf(stderr, + "An option for waiter type is invalid: %s\n", + waiter_type_literal); + return -EINVAL; + } + } else { + opts->waiter_type = WAITER_TYPE_POLL; + } + return 0; }
@@ -326,7 +351,7 @@ void context_options_calculate_duration(struct context_options *opts, int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMF:B:A:R:T:"; + static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMw:F:B:A:R:T:"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -345,6 +370,7 @@ int context_options_init(struct context_options *opts, int argc, {"rate", 1, 0, 'r'}, {"nonblock", 0, 0, 'N'}, {"mmap", 0, 0, 'M'}, + {"waiter-type", 1, 0, 'w'}, {"period-time", 1, 0, 'F'}, {"buffer-time", 1, 0, 'B'}, {"period-size", 1, 0, OPT_PERIOD_SIZE}, @@ -368,6 +394,7 @@ int context_options_init(struct context_options *opts, int argc, const char *cntr_format_literal = NULL; const char *node_literal = NULL; const char *sample_format_literal = NULL; + const char *waiter_type_literal = NULL; int l_index = 0; int c; int err = 0; @@ -404,6 +431,8 @@ int context_options_init(struct context_options *opts, int argc, opts->nonblock = true; else if (c == 'M') opts->mmap = true; + else if (c == 'w') + waiter_type_literal = optarg; else if (c == 'F') opts->msec_per_period = parse_l(optarg, &err); else if (c == 'B') @@ -450,7 +479,8 @@ int context_options_init(struct context_options *opts, int argc, return err;
return apply_policies(opts, direction, cntr_format_literal, - node_literal, sample_format_literal); + node_literal, sample_format_literal, + waiter_type_literal); }
static int generate_path_with_suffix(struct context_options *opts, diff --git a/axfer/options.h b/axfer/options.h index 95012ad3..f105ff2e 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -10,6 +10,7 @@ #define __ALSA_UTILS_AXFER_OPTIONS__H_
#include "container.h" +#include "waiter.h"
struct context_options { bool help; @@ -36,6 +37,8 @@ struct context_options { bool nonblock; bool mmap;
+ enum waiter_type waiter_type; + unsigned int msec_per_period; unsigned int msec_per_buffer; unsigned int frames_per_period; diff --git a/axfer/waiter.c b/axfer/waiter.c index f4e21a16..8e3789b9 100644 --- a/axfer/waiter.c +++ b/axfer/waiter.c @@ -14,6 +14,23 @@
#include "misc.h"
+const char *const waiter_type_labels[] = { + [WAITER_TYPE_POLL] = "poll", + [WAITER_TYPE_EPOLL] = "epoll", +}; + +enum waiter_type waiter_type_from_label(const char *label) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(waiter_type_labels); ++i) { + if (!strcmp(waiter_type_labels[i], label)) + return i; + } + + return WAITER_TYPE_UNKNOWN; +} + int waiter_context_init(struct waiter_context *waiter, enum waiter_type type) { struct { diff --git a/axfer/waiter.h b/axfer/waiter.h index 3f0dd744..aedc42f8 100644 --- a/axfer/waiter.h +++ b/axfer/waiter.h @@ -10,6 +10,7 @@ #define __ALSA_UTILS_AXFER_WAITER__H_
enum waiter_type { + WAITER_TYPE_UNKNOWN = -1, WAITER_TYPE_POLL = 0, WAITER_TYPE_EPOLL, WAITER_TYPE_COUNT, @@ -23,6 +24,7 @@ struct waiter_context { void *private_data; };
+enum waiter_type waiter_type_from_label(const char *label); int waiter_context_init(struct waiter_context *waiter, enum waiter_type type); int waiter_context_prepare(struct waiter_context *waiter, int *fds, unsigned int fd_count); diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index e01327e2..96e0cb7c 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -88,7 +88,7 @@ static int xfer_libasound_init(struct xfer_context *xfer, if (state->waiter == NULL) return -ENOMEM;
- err = waiter_context_init(state->waiter, WAITER_TYPE_POLL); + err = waiter_context_init(state->waiter, opts->waiter_type); if (err < 0) return err; }
In 2010, ALSA PCM interface got an flag of hardware parameters to suppress periodical interrupts, according to a request from PulseAudio developer.
In typical PCM operation for usual hardware, PCM drivers configure the hardware to generate the periodical interrupts to notify that the same amount of data frames as a period of PCM buffer is actually transferred via serial sound interface. The flag can suppress this if the driver support it.
There's some merits of this configuration: - No interrupt context run for PCM substream. The PCM substream is handled in any process context only. No need to care of race conditions between interrupt/process contexts. This is good for developers of drivers and applications. - CPU time is not used for handlers on the interrupt context. The CPU time can be dedicated for the other tasks. This is good in a point of Time Sharing System. - Hardware is not configured to generate interrupts. This is good in a point of reduction of overall power consumption.
Disabling period interrupt is used for 'Timer-based scheduling' to consume data frames on PCM buffer independently of interrupt context. As noted, no interrupt context runs for PCM substream, thus any blocking operation is not released. Furthermore, system calls for multiplexed I/O is not also released without timeout.
In this scheduling model, applications need to care of available space on PCM buffer by lapse of time, typically by yielding CPU and wait for rescheduling. For the yielding, timeout is calculated for preferable amount of PCM frames to process. This is an additional merit for applications, like sound servers. when an I/O thread of the server wait for the timeout, the other threads can process data frames for server clients.
But the timeout should be calculated with enough care of hardware capabilities. To disable period interrupt, used hardware should satisfy some requirements for data transmission: 1. Even if drivers don't handle interrupts to queue next data transmission, hardware voluntarily perform the data transmission when needed (typically by requesting DMA automatically). 2. hardware has a capability to report current position of data transmission with enough accuracy against the data transmission. developers refer this as 'granularity'. If hardware can always reports updated position after the data transmission finishes, the granularity equals to the size of period of PCM buffer. 3. a fine size of data transmission in one time. This size is decided depending on configuration of hardware or DMA controller, but for efficiency it may not be one byte. Thus some amount of data frame is transferred by one data transmission. Developers refer this as 'burst-ness'.
The timeout should be calculated according to the item 2 and 3, however in current ALSA PCM interface supplemental information is not delivered from drivers to applications. Although at present userspace applications should be written by a speculative way for this point, there's few problems because there're a few hardware which satisfy the above items. However, when more drivers supports this feature, the problem may largely be exposed and bothers application developers.
This commit adds an option to use 'timer-based scheduling' for data transmission. This commit adds '--sched-type' option, and the scheduling mode is enabled when 'timer' is assigned to the option by equal sign.
There's some TODOs but you can see the scheduling mode in this simple program.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 3 +- axfer/options.c | 20 +- axfer/options.h | 9 + axfer/xfer-libasound-timer-mmap.c | 468 ++++++++++++++++++++++++++++++++++++++ axfer/xfer-libasound.c | 80 ++++++- axfer/xfer-libasound.h | 3 + 6 files changed, 568 insertions(+), 15 deletions(-) create mode 100644 axfer/xfer-libasound-timer-mmap.c
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 60ad3a84..3a0b1d26 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -51,4 +51,5 @@ axfer_SOURCES = \ waiter.c \ waiter-poll.c \ xfer-libasound-irq-mmap.c \ - waiter-epoll.c + waiter-epoll.c \ + xfer-libasound-timer-mmap.c diff --git a/axfer/options.c b/axfer/options.c index c4423b6c..b1a3184a 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -23,6 +23,7 @@ enum no_short_opts { OPT_DISABLE_CHANNELS, OPT_DISABLE_FORMAT, OPT_DISABLE_SOFTVOL, + OPT_SCHED_TYPE, /* Obsoleted. */ OPT_MAX_FILE_TIME, }; @@ -177,7 +178,8 @@ static int apply_policies(struct context_options *opts, const char *cntr_format_literal, const char *node_literal, const char *sample_format_literal, - const char *waiter_type_literal) + const char *waiter_type_literal, + const char *sched_type_literal) { int err;
@@ -303,6 +305,15 @@ static int apply_policies(struct context_options *opts, } }
+ opts->sched_type = SCHED_TYPE_IRQ; + if (sched_type_literal != NULL) { + if (!strcmp(sched_type_literal, "timer")) { + opts->sched_type = SCHED_TYPE_TIMER; + opts->mmap = true; + opts->nonblock = true; + } + } + if (waiter_type_literal != NULL) { if (opts->test_nowait) { fprintf(stderr, @@ -383,6 +394,8 @@ int context_options_init(struct context_options *opts, int argc, {"disable-channels", 0, 0, OPT_DISABLE_CHANNELS}, {"disable-format", 0, 0, OPT_DISABLE_FORMAT}, {"disable-softvol", 0, 0, OPT_DISABLE_SOFTVOL}, + /* For scheduling type. */ + {"sched-type", 1, 0, OPT_SCHED_TYPE}, /* For debugging. */ {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, @@ -395,6 +408,7 @@ int context_options_init(struct context_options *opts, int argc, const char *node_literal = NULL; const char *sample_format_literal = NULL; const char *waiter_type_literal = NULL; + const char *sched_type_literal = NULL; int l_index = 0; int c; int err = 0; @@ -455,6 +469,8 @@ int context_options_init(struct context_options *opts, int argc, opts->no_auto_format = true; else if (c == OPT_DISABLE_SOFTVOL) opts->no_softvol = true; + else if (c == OPT_SCHED_TYPE) + sched_type_literal = optarg; else if (c == OPT_DUMP_HW_PARAMS) opts->dump_hw_params = true; else if (c == OPT_FATAL_ERRORS) @@ -480,7 +496,7 @@ int context_options_init(struct context_options *opts, int argc,
return apply_policies(opts, direction, cntr_format_literal, node_literal, sample_format_literal, - waiter_type_literal); + waiter_type_literal, sched_type_literal); }
static int generate_path_with_suffix(struct context_options *opts, diff --git a/axfer/options.h b/axfer/options.h index f105ff2e..769757a8 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -12,6 +12,12 @@ #include "container.h" #include "waiter.h"
+enum sched_type { + SCHED_TYPE_IRQ = 0, + SCHED_TYPE_TIMER, + SCHED_TYPE_COUNT, +}; + struct context_options { bool help;
@@ -54,6 +60,9 @@ struct context_options { bool no_auto_format; bool no_softvol;
+ /* For scheduling type. */ + enum sched_type sched_type; + /* For debugging. */ bool dump_hw_params; bool finish_at_xrun; diff --git a/axfer/xfer-libasound-timer-mmap.c b/axfer/xfer-libasound-timer-mmap.c new file mode 100644 index 00000000..413b7958 --- /dev/null +++ b/axfer/xfer-libasound-timer-mmap.c @@ -0,0 +1,468 @@ +/* + * xfer-libasound-irq-mmap.c - Timer-based I/O helper for mmap operation. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer-libasound.h" +#include "misc.h" + +struct map_layout { + snd_pcm_status_t *status; + bool need_forward_or_rewind; + char **vector; + + unsigned int frames_per_second; + unsigned int samples_per_frame; + unsigned int frames_per_buffer; +}; + +static int timer_mmap_pre_process(struct libasound_state *state) +{ + struct map_layout *layout = state->private_data; + snd_pcm_access_t access; + snd_pcm_uframes_t frame_offset; + snd_pcm_uframes_t avail = 0; + snd_pcm_uframes_t frames_per_buffer; + int i; + int err; + + /* + * This parameter, 'period event', is a software feature in alsa-lib. + * This switch a handler in 'hw' PCM plugin from irq-based one to + * timer-based one. This handler has two file descriptors for + * ALSA PCM character device and ALSA timer device. The latter is used + * to catch suspend/resume events as wakeup event. + */ + err = snd_pcm_sw_params_set_period_event(state->handle, + state->sw_params, 1); + if (err < 0) + return err; + + err = snd_pcm_status_malloc(&layout->status); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_access(state->hw_params, &access); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_channels(state->hw_params, + &layout->samples_per_frame); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_rate(state->hw_params, + &layout->frames_per_second, NULL); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_buffer_size(state->hw_params, + &frames_per_buffer); + if (err < 0) + return err; + layout->frames_per_buffer = (unsigned int)frames_per_buffer; + + if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { + layout->vector = calloc(layout->samples_per_frame, + sizeof(*layout->vector)); + if (layout->vector == NULL) + return err; + } + + if (state->verbose) { + const snd_pcm_channel_area_t *areas; + err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, + &avail); + if (err < 0) + return err; + + logging(state, "attributes for mapped page frame:\n"); + for (i = 0; i < layout->samples_per_frame; ++i) { + const snd_pcm_channel_area_t *area = areas + i; + + logging(state, " sample number: %d\n", i); + logging(state, " address: %p\n", area->addr); + logging(state, " bits for offset: %u\n", area->first); + logging(state, " bits/frame: %u\n", area->step); + } + } + + return 0; +} + +static void *get_buffer(struct libasound_state *state, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t frame_offset) +{ + struct map_layout *layout = state->private_data; + void *frame_buf; + + if (layout->vector == NULL) { + char *buf; + buf = areas[0].addr + snd_pcm_frames_to_bytes(state->handle, + frame_offset); + frame_buf = buf; + } else { + int i; + for (i = 0; i < layout->samples_per_frame; ++i) { + layout->vector[i] = areas[i].addr; + layout->vector[i] += snd_pcm_samples_to_bytes( + state->handle, frame_offset); + } + frame_buf = layout->vector; + } + + return frame_buf; +} + +static int timer_mmap_process_frames(struct libasound_state *state, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct map_layout *layout = state->private_data; + snd_pcm_uframes_t planned_count; + snd_pcm_sframes_t avail; + snd_pcm_uframes_t avail_count; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t frame_offset; + void *frame_buf; + snd_pcm_sframes_t consumed_count; + int err; + + /* + * Retrieve avail space on PCM buffer between kernel/user spaces. + * On cache incoherent architectures, still care of data + * synchronization. + */ + avail = snd_pcm_avail_update(state->handle); + if (avail < 0) + return (int)avail; + + /* Retrieve pointers of the buffer and left space up to the boundary. */ + avail_count = (snd_pcm_uframes_t)avail; + err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, + &avail_count); + if (err < 0) + return err; + + /* MEMO: Use the amount of data frames as you like. */ + planned_count = layout->frames_per_buffer * random() / RAND_MAX; + if (frame_offset + planned_count > layout->frames_per_buffer) + planned_count = layout->frames_per_buffer - frame_offset; + + /* Trim up to expected frame count. */ + if (*frame_count < planned_count) + planned_count = *frame_count; + + /* Yield this CPU till planned amount of frames become available. */ + if (avail_count < planned_count) { + int timeout_msec; + + /* + * TODO; precise granularity of timeout; e.g. ppoll(2). + * Furthermore, wrap up according to granularity of reported + * value for hw_ptr. + */ + timeout_msec = ((planned_count - avail_count) * 1000 + + layout->frames_per_second - 1) / + layout->frames_per_second; + + /* + * TODO: However, experimentally, the above is not enough to + * keep planned amount of frames when waking up. I don't know + * exactly the mechanism yet. + */ + + err = waiter_context_wait_event(state->waiter, timeout_msec); + if (err < 0) + return err; + + /* + * MEMO: Need to perform hwsync explicitly because hwptr is not + * synchronized to actual position of data frame transmission + * on hardware because IRQ handlers are not used in this + * scheduling strategy. + */ + avail = snd_pcm_avail(state->handle); + if (avail < 0) + return (int)avail; + if (avail < planned_count) { + logging(state, + "Wake up but not enough space: %lu %lu %u\n", + planned_count, avail, timeout_msec); + planned_count = avail; + } + } + + /* Let's process data frames. */ + *frame_count = planned_count; + frame_buf = get_buffer(state, areas, frame_offset); + err = mapper_context_process_frames(mapper, frame_buf, frame_count, + cntrs); + if (err < 0) + return err; + + consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset, + *frame_count); + if (consumed_count != *frame_count) { + logging(state, + "A bug of 'hw' PCM plugin or driver for this PCM " + "node.\n"); + } + *frame_count = consumed_count; + + return 0; +} + +static int forward_appl_ptr(struct libasound_state *state) +{ + struct map_layout *layout = state->private_data; + snd_pcm_uframes_t forwardable_count; + snd_pcm_sframes_t forward_count; + + forward_count = snd_pcm_forwardable(state->handle); + if (forward_count < 0) + return (int)forward_count; + forwardable_count = forward_count; + + /* No need to add safe-gurard because hwptr goes ahead. */ + forward_count = snd_pcm_forward(state->handle, forwardable_count); + if (forward_count < 0) + return (int)forward_count; + + if (state->verbose) { + logging(state, + " forwarded: %lu/%u\n", + forward_count, layout->frames_per_buffer); + } + + return 0; +} + +static int timer_mmap_r_process_frames(struct libasound_state *state, + unsigned *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct map_layout *layout = state->private_data; + snd_pcm_state_t s; + int err; + + /* + * SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP suppresses any IRQ to notify + * period elapse for data transmission, therefore no need to care of + * concurrent access by IRQ context and process context, unlike + * IRQ-based operations. + * Here, this is just to query current status to hardware, for later + * processing. + */ + err = snd_pcm_status(state->handle, layout->status); + if (err < 0) + goto error; + s = snd_pcm_status_get_state(layout->status); + + /* TODO: if reporting something, do here with the status data. */ + + if (s == SND_PCM_STATE_RUNNING) { + /* + * Reduce delay between sampling on hardware and handling by + * this program. + */ + if (layout->need_forward_or_rewind) { + err = forward_appl_ptr(state); + if (err < 0) + goto error; + layout->need_forward_or_rewind = false; + } + + err = timer_mmap_process_frames(state, frame_count, mapper, + cntrs); + if (err < 0) + goto error; + } else { + if (s == SND_PCM_STATE_PREPARED) { + /* + * For capture direction, need to start stream + * explicitly. + */ + err = snd_pcm_start(state->handle); + if (err < 0) + goto error; + layout->need_forward_or_rewind = true; + /* Not yet. */ + *frame_count = 0; + } else { + err = -EPIPE; + goto error; + } + } + + return 0; +error: + *frame_count = 0; + return err; +} + +static int rewind_appl_ptr(struct libasound_state *state) +{ + struct map_layout *layout = state->private_data; + snd_pcm_uframes_t rewindable_count; + snd_pcm_sframes_t rewind_count; + + rewind_count = snd_pcm_rewindable(state->handle); + if (rewind_count < 0) + return (int)rewind_count; + rewindable_count = rewind_count; + + /* + * If appl_ptr were rewound just to position of hw_ptr, at next time, + * hw_ptr could catch up appl_ptr. This is overrun. We need a space + * between these two pointers to prevent this XRUN. + * This space is largely affected by time to process data frames later. + * + * TODO: a generous way to estimate a good value. + */ + if (rewindable_count < 32) + return 0; + rewindable_count -= 32; + + rewind_count = snd_pcm_rewind(state->handle, rewindable_count); + if (rewind_count < 0) + return (int)rewind_count; + + if (state->verbose) { + logging(state, + " rewound: %lu/%u\n", + rewind_count, layout->frames_per_buffer); + } + + return 0; +} + +static int fill_buffer_with_zero_samples(struct libasound_state *state) +{ + struct map_layout *layout = state->private_data; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t frame_offset; + snd_pcm_uframes_t avail_count; + snd_pcm_format_t sample_format; + snd_pcm_uframes_t consumed_count; + int err; + + err = snd_pcm_hw_params_get_buffer_size(state->hw_params, + &avail_count); + if (err < 0) + return err; + + err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, + &avail_count); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_format(state->hw_params, &sample_format); + if (err < 0) + return err; + + err = snd_pcm_areas_silence(areas, frame_offset, + layout->samples_per_frame, avail_count, + sample_format); + if (err < 0) + return err; + + consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset, + avail_count); + if (consumed_count != avail_count) + logging(state, "A bug of access plugin for this PCM node.\n"); + + return 0; +} + +static int timer_mmap_w_process_frames(struct libasound_state *state, + unsigned *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct map_layout *layout = state->private_data; + snd_pcm_state_t s; + int err; + + /* Read my comment in 'timer_mmap_w_process_frames()'. */ + err = snd_pcm_status(state->handle, layout->status); + if (err < 0) + goto error; + s = snd_pcm_status_get_state(layout->status); + + /* TODO: if reporting something, do here with the status data. */ + + if (s == SND_PCM_STATE_RUNNING) { + /* + * Reduce delay between queueing by this program and presenting + * on hardware. + */ + if (layout->need_forward_or_rewind) { + err = rewind_appl_ptr(state); + if (err < 0) + goto error; + layout->need_forward_or_rewind = false; + } + + err = timer_mmap_process_frames(state, frame_count, mapper, + cntrs); + if (err < 0) + goto error; + } else { + /* Need to start playback stream explicitly */ + if (s == SND_PCM_STATE_PREPARED) { + err = fill_buffer_with_zero_samples(state); + if (err < 0) + goto error; + + err = snd_pcm_start(state->handle); + if (err < 0) + goto error; + + layout->need_forward_or_rewind = true; + /* Not yet. */ + *frame_count = 0; + } else { + err = -EPIPE; + goto error; + } + } + + return 0; +error: + *frame_count = 0; + return err; +} + +static void timer_mmap_post_process(struct libasound_state *state) +{ + struct map_layout *layout = state->private_data; + + if (layout->status) + snd_pcm_status_free(layout->status); + layout->status = NULL; + + if (layout->vector) + free(layout->vector); + layout->vector = NULL; +} + +const struct xfer_libasound_ops xfer_libasound_timer_mmap_w_ops = { + .pre_process = timer_mmap_pre_process, + .process_frames = timer_mmap_w_process_frames, + .post_process = timer_mmap_post_process, + .private_size = sizeof(struct map_layout), +}; + +const struct xfer_libasound_ops xfer_libasound_timer_mmap_r_ops = { + .pre_process = timer_mmap_pre_process, + .process_frames = timer_mmap_r_process_frames, + .post_process = timer_mmap_post_process, + .private_size = sizeof(struct map_layout), +}; diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 96e0cb7c..12c90936 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -53,6 +53,37 @@ static int set_access_hw_param(snd_pcm_t *handle, return err; }
+static int disable_period_wakeup(struct libasound_state *state) +{ + int err; + + if (snd_pcm_type(state->handle) != SND_PCM_TYPE_HW) { + logging(state, + _("Timer-based scheduling is only available for 'hw' " + "PCM plugin.\n")); + return -ENXIO; + } + + if (!snd_pcm_hw_params_can_disable_period_wakeup(state->hw_params)) { + logging(state, + _("This hardware doesn't support the mode of no-period-" + "wakeup. In this case, timer-based scheduling is not " + "available.\n")); + return -EIO; + } + + err = snd_pcm_hw_params_set_period_wakeup(state->handle, + state->hw_params, 0); + if (err < 0) { + logging(state, + _("Fail to disable period wakeup so that the hardware " + "generates no IRQs during transmission of data " + "frames.\n")); + } + + return err; +} + static int xfer_libasound_init(struct xfer_context *xfer, snd_pcm_stream_t direction, struct context_options *opts) @@ -104,7 +135,11 @@ static int xfer_libasound_init(struct xfer_context *xfer, if (err < 0) return err;
- /* TODO: Applying NO_PERIOD_WAKEUP should be done here. */ + if (opts->sched_type == SCHED_TYPE_TIMER) { + err = disable_period_wakeup(state); + if (err < 0) + return err; + }
if (opts->dump_hw_params) { logging(state, _("Available HW Params of node: %s\n"), @@ -427,6 +462,7 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, struct context_options *opts) { struct libasound_state *state = xfer->private_data; + unsigned int flag; int err;
if (state->handle == NULL) @@ -456,17 +492,36 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, return err;
/* Assign I/O operation. */ - if (*access == SND_PCM_ACCESS_RW_INTERLEAVED || - *access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { - state->ops = &xfer_libasound_irq_rw_ops; - } else if (*access == SND_PCM_ACCESS_MMAP_INTERLEAVED || - *access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { - if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) - state->ops = &xfer_libasound_irq_mmap_r_ops; - else - state->ops = &xfer_libasound_irq_mmap_w_ops; + err = snd_pcm_hw_params_get_period_wakeup(state->handle, + state->hw_params, &flag); + if (err < 0) + return err; + + if (flag) { + if (*access == SND_PCM_ACCESS_RW_INTERLEAVED || + *access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + state->ops = &xfer_libasound_irq_rw_ops; + } else if (*access == SND_PCM_ACCESS_MMAP_INTERLEAVED || + *access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) + state->ops = &xfer_libasound_irq_mmap_r_ops; + else + state->ops = &xfer_libasound_irq_mmap_w_ops; + } else { + return -ENXIO; + } } else { - return -ENXIO; + if (*access == SND_PCM_ACCESS_MMAP_INTERLEAVED || + *access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) + state->ops = &xfer_libasound_timer_mmap_r_ops; + else + state->ops = &xfer_libasound_timer_mmap_w_ops; + } else { + return -ENXIO; + } + + } if (state->ops->private_size > 0) { state->private_data = malloc(state->ops->private_size); @@ -579,7 +634,8 @@ static void xfer_libasound_post_process(struct xfer_context *xfer) pcm_state = snd_pcm_state(state->handle); if (pcm_state != SND_PCM_STATE_OPEN && pcm_state != SND_PCM_STATE_DISCONNECTED) { - if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) { + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE || + state->ops == &xfer_libasound_timer_mmap_w_ops) { err = snd_pcm_drop(state->handle); if (err < 0) logging(state, "snd_pcm_drop(): %s\n", diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index db85e604..5d399571 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -51,4 +51,7 @@ extern const struct xfer_libasound_ops xfer_libasound_irq_rw_ops; extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops; extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops;
+const struct xfer_libasound_ops xfer_libasound_timer_mmap_w_ops; +const struct xfer_libasound_ops xfer_libasound_timer_mmap_r_ops; + #endif
In aplay, volume unit (vu) meter is implemented, by an option '--vumeter' (-V). When 'm' or 's' is given for the option, terminal displays monaural or stereo meter via stderr.
This commit adds support for this feature. Original implementation just supports meters up to 2 channel and this commit follows it. However, actual calculation is done all of channels with a consideration about future extension to display all of channels.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 7 +- axfer/options.c | 43 ++- axfer/options.h | 4 + axfer/subcmd-transfer.c | 1 - axfer/vumeter.c | 546 ++++++++++++++++++++++++++++++++++++++ axfer/vumeter.h | 55 ++++ axfer/xfer-libasound-irq-mmap.c | 16 +- axfer/xfer-libasound-irq-rw.c | 40 ++- axfer/xfer-libasound-timer-mmap.c | 16 +- axfer/xfer-libasound.c | 5 +- axfer/xfer-libasound.h | 3 +- axfer/xfer.c | 39 ++- axfer/xfer.h | 5 +- 13 files changed, 746 insertions(+), 34 deletions(-) create mode 100644 axfer/vumeter.c create mode 100644 axfer/vumeter.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 3a0b1d26..7a079e62 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -22,7 +22,8 @@ noinst_HEADERS = \ options.h \ xfer.h \ xfer-alsa.h \ - waiter.h + waiter.h \ + vumeter.h
axfer_SOURCES = \ misc.h \ @@ -52,4 +53,6 @@ axfer_SOURCES = \ waiter-poll.c \ xfer-libasound-irq-mmap.c \ waiter-epoll.c \ - xfer-libasound-timer-mmap.c + xfer-libasound-timer-mmap.c \ + vumeter.h \ + vumeter.c diff --git a/axfer/options.c b/axfer/options.c index b1a3184a..d595d857 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -114,6 +114,30 @@ static int verify_cntr_format(struct context_options *opts, const char *literal) return -EINVAL; }
+static int verify_vu_mode(struct context_options *opts, const char *literal) +{ + static const struct { + const char *const literal; + enum vumeter_mode mode; + } *entry, entries[] = { + {"m", VUMETER_MODE_MONO}, + {"s", VUMETER_MODE_STEREO}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = &entries[i]; + if (!strcmp(entry->literal, literal)) { + opts->vu_mode = entry->mode; + return 0; + } + } + + fprintf(stderr, "Invalid argument for vumeter mode: %s\n", literal); + + return -EINVAL; +} + /* This should be called after 'verify_cntr_format()'. */ static int verify_sample_format(struct context_options *opts, const char *literal) @@ -179,7 +203,8 @@ static int apply_policies(struct context_options *opts, const char *node_literal, const char *sample_format_literal, const char *waiter_type_literal, - const char *sched_type_literal) + const char *sched_type_literal, + const char *vu_mode_literal) { int err;
@@ -255,6 +280,12 @@ static int apply_policies(struct context_options *opts, } }
+ if (vu_mode_literal) { + err = verify_vu_mode(opts, vu_mode_literal); + if (err < 0) + return err; + } + if (opts->frames_per_second > 0) { unsigned int orig = opts->frames_per_second;
@@ -362,7 +393,7 @@ void context_options_calculate_duration(struct context_options *opts, int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMw:F:B:A:R:T:"; + static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMw:F:B:A:R:T:V:"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -400,6 +431,8 @@ int context_options_init(struct context_options *opts, int argc, {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, + /* Misc features. */ + {"vumeter", 1, 0, 'V'}, /* Obsoleted. */ {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, {NULL, 0, 0, 0}, @@ -409,6 +442,7 @@ int context_options_init(struct context_options *opts, int argc, const char *sample_format_literal = NULL; const char *waiter_type_literal = NULL; const char *sched_type_literal = NULL; + const char *vu_mode_literal = NULL; int l_index = 0; int c; int err = 0; @@ -477,6 +511,8 @@ int context_options_init(struct context_options *opts, int argc, opts->finish_at_xrun = true; else if (c == OPT_TEST_NOWAIT) opts->test_nowait = true; + else if (c == 'V') + vu_mode_literal = optarg; else if (c == OPT_MAX_FILE_TIME) { fprintf(stderr, "An option '--%s' is obsoleted and has no " @@ -496,7 +532,8 @@ int context_options_init(struct context_options *opts, int argc,
return apply_policies(opts, direction, cntr_format_literal, node_literal, sample_format_literal, - waiter_type_literal, sched_type_literal); + waiter_type_literal, sched_type_literal, + vu_mode_literal); }
static int generate_path_with_suffix(struct context_options *opts, diff --git a/axfer/options.h b/axfer/options.h index 769757a8..188668b0 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -11,6 +11,7 @@
#include "container.h" #include "waiter.h" +#include "vumeter.h"
enum sched_type { SCHED_TYPE_IRQ = 0, @@ -67,6 +68,9 @@ struct context_options { bool dump_hw_params; bool finish_at_xrun; bool test_nowait; + + /* Misc features. */ + enum vumeter_mode vu_mode; };
int context_options_init(struct context_options *opts, int argc, diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index e9a8c181..480b4eb5 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -128,7 +128,6 @@ static int prepare_signal_handler(struct context *ctx)
static int context_init(struct context *ctx, snd_pcm_stream_t direction) { - /* Initialize transfer. */ return xfer_context_init(&ctx->xfer, XFER_TYPE_LIBASOUND, direction, &ctx->opts); } diff --git a/axfer/vumeter.c b/axfer/vumeter.c new file mode 100644 index 00000000..4d842866 --- /dev/null +++ b/axfer/vumeter.c @@ -0,0 +1,546 @@ +/* + * vumeter.c - Volume Unit meter. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "vumeter.h" +#include "misc.h" + +#include <endian.h> + +/* + * MEMO: Casting to 'intXX_t' is important to calculate absolute value from + * 'uintXX_t' representation of PCM frame. + */ + +static void calculate_8bit_sample_buf(struct vumeter_context *vu, + void *frame_buffer, + unsigned int frame_count) +{ + int8_t *buf = frame_buffer; + unsigned int samples_per_frame = vu->samples_per_frame; + int8_t zero_point = (int8_t)vu->zero_point; + int c, i; + + for (c = 0; c < samples_per_frame; ++c) { + int8_t peak = 0; + + for (i = 0; i < frame_count; ++i) { + int8_t val; + unsigned int offset; + + offset = i * samples_per_frame + c; + + val = abs(buf[offset] ^ zero_point); + if (val > peak) + peak = val; + } + + vu->peaks[c] = (uint32_t)peak; + } +} + +static void calculate_8bit_sample_vector(struct vumeter_context *vu, + void *frame_buffer, + unsigned int frame_count) +{ + int8_t **bufs = frame_buffer; + unsigned int samples_per_frame = vu->samples_per_frame; + int8_t zero_point = (int8_t)vu->zero_point; + int c, i; + + for (c = 0; c < samples_per_frame; ++c) { + int8_t *buf = bufs[c]; + int8_t peak = 0; + for (i = 0; i < frame_count; ++i) { + int8_t val; + + val = abs(buf[i] ^ zero_point); + if (val > peak) + peak = val; + } + + vu->peaks[c] = (uint32_t)peak; + } +} + +static void calculate_16bit_sample_buf(struct vumeter_context *vu, + void *frame_buffer, + unsigned int frame_count) +{ + int16_t *buf = frame_buffer; + unsigned int samples_per_frame = vu->samples_per_frame; + bool le = vu->le; + int16_t zero_point = (int16_t)vu->zero_point; + int c, i; + + for (c = 0; c < samples_per_frame; ++c) { + int16_t peak = 0; + + for (i = 0; i < frame_count; ++i) { + int16_t val; + unsigned int offset; + + offset = i * samples_per_frame + c; + + if (le) + val = (int32_t)le16toh(buf[offset]); + else + val = (int32_t)be16toh(buf[offset]); + + val = abs(val ^ zero_point); + if (val > peak) + peak = val; + } + + vu->peaks[c] = (uint32_t)peak; + } +} + +static void calculate_16bit_sample_vector(struct vumeter_context *vu, + void *frame_buffer, + unsigned int frame_count) +{ + int16_t **bufs = frame_buffer; + unsigned int samples_per_frame = vu->samples_per_frame; + bool le = vu->le; + int16_t zero_point = vu->zero_point; + int c, i; + + for (c = 0; c < samples_per_frame; ++c) { + int16_t *buf = bufs[c]; + int16_t peak = 0; + + for (i = 0; i < frame_count; ++i) { + int16_t val; + + if (le) + val = (int16_t)le16toh(buf[i]); + else + val = (int16_t)be16toh(buf[i]); + + val = abs(val ^ zero_point); + if (val > peak) + peak = val; + } + + vu->peaks[c] = peak; + } +} + +static void calculate_24bit_sample_buf(struct vumeter_context *vu, + void *frame_buffer, + unsigned int frame_count) +{ + int8_t *buf = frame_buffer; + unsigned int samples_per_frame = vu->samples_per_frame; + unsigned int bytes_per_sample = vu->bytes_per_sample; + bool le = vu->le; + int32_t zero_point = vu->zero_point; + int c, i; + + for (c = 0; c < samples_per_frame; ++c) { + int32_t peak = 0; + + for (i = 0; i < frame_count; ++i) { + int32_t val; + unsigned int offset; + + offset = i * bytes_per_sample * samples_per_frame + c; + + if (le) { + val = (int32_t)(((buf[offset] << 8) | + (buf[offset + 1] << 16) | + (buf[offset + 2] << 24))); + } else { + val = (int32_t)(((buf[offset] << 24) | + (buf[offset + 1] << 16) | + (buf[offset + 2] << 8))); + } + + val = abs(val ^ zero_point); + if (vu->significant_bits_per_sample == 18) + val >>= 14; + else if (vu->significant_bits_per_sample == 20) + val >>= 12; + else + val >>= 8; + if (val > peak) + peak = val; + } + + vu->peaks[c] = peak; + } +} + +static void calculate_24bit_sample_vector(struct vumeter_context *vu, + void *frame_buffer, + unsigned int frame_count) +{ + int8_t **bufs = frame_buffer; + unsigned int samples_per_frame = vu->samples_per_frame; + unsigned int bytes_per_sample = vu->bytes_per_sample; + bool le = vu->le; + int32_t zero_point = vu->zero_point; + int c, i; + + for (c = 0; c < samples_per_frame; ++c) { + int8_t *buf = bufs[c]; + int32_t peak = 0; + + for (i = 0; i < frame_count; ++i) { + int32_t val; + unsigned int offset; + + offset = bytes_per_sample * i; + + if (le) { + val = (int32_t)((buf[offset] << 8) | + (buf[offset + 1] << 16) | + (buf[offset + 2] << 24)); + } else { + val = (int32_t)((buf[offset] << 24) | + (buf[offset + 1] << 16) | + (buf[offset + 2] << 8)); + } + + val = abs(val ^ zero_point); + if (vu->significant_bits_per_sample == 18) + val >>= 14; + else if (vu->significant_bits_per_sample == 20) + val >>= 12; + else + val >>= 8; + if (val > peak) + peak = val; + } + + vu->peaks[c] = peak; + } +} + +static void calculate_32bit_sample_buf(struct vumeter_context *vu, + void *frame_buffer, + unsigned int frame_count) +{ + int32_t *buf = frame_buffer; + unsigned int samples_per_frame = vu->samples_per_frame; + bool le = vu->le; + int32_t zero_point = vu->zero_point; + int c, i; + + for (c = 0; c < samples_per_frame; ++c) { + int32_t peak = 0; + + for (i = 0; i < frame_count; ++i) { + int32_t val; + unsigned int offset; + + offset = i * samples_per_frame + c; + + if (le) + val = (int32_t)le32toh(buf[offset]); + else + val = (int32_t)be32toh(buf[offset]); + + val = abs(val ^ zero_point); + /* For [S/U]24_[B|L]E. */ + if (vu->significant_bits_per_sample == 24) + val >>= 8; + if (val > peak) + peak = val; + } + + vu->peaks[c] = peak; + } +} + +static void calculate_32bit_sample_vector(struct vumeter_context *vu, + void *frame_buffer, + unsigned int frame_count) +{ + int32_t **bufs = frame_buffer; + unsigned int samples_per_frame = vu->samples_per_frame; + bool le = vu->le; + int32_t zero_point = vu->zero_point; + int c, i; + + for (c = 0; c < samples_per_frame; ++c) { + int32_t *buf = bufs[c]; + int32_t peak = 0; + + for (i = 0; i < frame_count; ++i) { + int32_t val; + + if (le) + val = (int32_t)le32toh(buf[i]); + else + val = (int32_t)be32toh(buf[i]); + + val = abs(val ^ zero_point); + /* For [S/U]24_[B|L]E. */ + if (vu->significant_bits_per_sample == 24) + val >>= 8; + if (val > peak) + peak = val; + } + + vu->peaks[c] = peak; + } +} + +static void print_monaural(struct vumeter_context *vu, char line[80]) +{ + const int bar_len = 50; + unsigned int pos; + + /* NOTE: 74 + '| 00%' + '\0' = 80 characters. */ + + /* Print peaks on current buffer. */ + pos = vu->ratios[0] * bar_len / 100; + memset(line, '#', pos); + + /* Print maximum peaks within a second. */ + pos = vu->max_ratios_in_second[0] * bar_len / 100; + line[pos] = '+'; + + if (vu->max_ratios_in_second[0] > 99) { + snprintf(line + bar_len + 1, 80 - bar_len, + "| MAX"); + } else { + snprintf(line + bar_len + 1, 80 - bar_len, + "| %02u%%", vu->max_ratios_in_second[0]); + } +} + +static void print_stereo(struct vumeter_context *vu, char line[80]) +{ + const int bar_length = 35; + bool even; + int width; + char *substr; + int c; + + /* NOTE: 35 + ' 00%|00% ' + 35 + '\0' = 80 characters. */ + + for (c = 0; c < 2; c++) { + even = (c % 2 > 0); + + /* Print peaks on current buffer. */ + width = vu->ratios[c] * bar_length / 100; + if (width > bar_length) + width = bar_length; + if (even) + memset(line + bar_length + 9, '#', width); + else + memset(line + bar_length - width, '#', width); + + /* Print maximum peaks within a second. */ + width = vu->max_ratios_in_second[c] * bar_length / 100; + if (width > bar_length) + width = bar_length; + if (even) + line[bar_length + 8 + width] = '+'; + else + line[bar_length - width] = '+'; + + /* Print numerical value of maximum peaks within a second. */ + if (even) + substr = line + bar_length + 5; + else + substr = line + bar_length + 1; + if (vu->max_ratios_in_second[c] > 99) { + snprintf(substr, 4, "MAX"); + } else { + snprintf(substr, 4, + "%02u%%", vu->max_ratios_in_second[c]); + } + substr[3] = ' '; + } + + line[bar_length + 4] = '|'; +} + +int vumeter_context_init(struct vumeter_context *vu, enum vumeter_mode mode, + snd_pcm_access_t access, snd_pcm_format_t format, + unsigned int samples_per_frame, + unsigned int frames_per_second) +{ + int bits_per_sample; + + /* Just for sample formats of PCM. */ + if (format != SND_PCM_FORMAT_S8 && + format != SND_PCM_FORMAT_U8 && + format != SND_PCM_FORMAT_S16_LE && + format != SND_PCM_FORMAT_S16_BE && + format != SND_PCM_FORMAT_U16_LE && + format != SND_PCM_FORMAT_U16_BE && + format != SND_PCM_FORMAT_S24_LE && + format != SND_PCM_FORMAT_S24_BE && + format != SND_PCM_FORMAT_U24_LE && + format != SND_PCM_FORMAT_U24_BE && + format != SND_PCM_FORMAT_S32_LE && + format != SND_PCM_FORMAT_S32_BE && + format != SND_PCM_FORMAT_U32_LE && + format != SND_PCM_FORMAT_U32_BE && + format != SND_PCM_FORMAT_S24_3LE && + format != SND_PCM_FORMAT_S24_3BE && + format != SND_PCM_FORMAT_U24_3LE && + format != SND_PCM_FORMAT_U24_3BE && + format != SND_PCM_FORMAT_S24_3LE && + format != SND_PCM_FORMAT_S24_3BE && + format != SND_PCM_FORMAT_U24_3LE && + format != SND_PCM_FORMAT_U24_3BE && + format != SND_PCM_FORMAT_S20_3LE && + format != SND_PCM_FORMAT_S20_3BE && + format != SND_PCM_FORMAT_U20_3LE && + format != SND_PCM_FORMAT_U20_3BE && + format != SND_PCM_FORMAT_S18_3LE && + format != SND_PCM_FORMAT_S18_3BE && + format != SND_PCM_FORMAT_U18_3LE && + format != SND_PCM_FORMAT_U18_3BE) + return -EINVAL; + + bits_per_sample = snd_pcm_format_physical_width(format); + if (access == SND_PCM_ACCESS_RW_INTERLEAVED || + access == SND_PCM_ACCESS_MMAP_INTERLEAVED) { + if (bits_per_sample == 8) + vu->calculator = calculate_8bit_sample_buf; + else if (bits_per_sample == 16) + vu->calculator = calculate_16bit_sample_buf; + else if (bits_per_sample == 24) + vu->calculator = calculate_24bit_sample_buf; + else if (bits_per_sample == 32) + vu->calculator = calculate_32bit_sample_buf; + else + return -EINVAL; + } else if (access == SND_PCM_ACCESS_RW_NONINTERLEAVED || + access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { + if (bits_per_sample == 8) + vu->calculator = calculate_8bit_sample_vector; + else if (bits_per_sample == 16) + vu->calculator = calculate_16bit_sample_vector; + else if (bits_per_sample == 24) + vu->calculator = calculate_24bit_sample_vector; + else if (bits_per_sample == 32) + vu->calculator = calculate_32bit_sample_vector; + else + return -EINVAL; + } else { + return -EINVAL; + } + vu->bytes_per_sample = bits_per_sample / 8; + + vu->peaks = calloc(samples_per_frame, sizeof(*vu->peaks)); + if (vu->peaks == NULL) + return -ENOMEM; + vu->samples_per_frame = samples_per_frame; + vu->frames_per_second = frames_per_second; + + if (mode == VUMETER_MODE_MONO || samples_per_frame == 1) + vu->printer = print_monaural; + else if (mode == VUMETER_MODE_STEREO) + vu->printer = print_stereo; + else + return -EINVAL; + vu->mode = mode; + + vu->ratios = calloc(samples_per_frame, sizeof(*vu->ratios)); + if (vu->ratios == NULL) + return -ENOMEM; + vu->max_ratios_in_second = calloc(samples_per_frame, + sizeof(*vu->max_ratios_in_second)); + if (vu->max_ratios_in_second == NULL) + return -ENOMEM; + + vu->significant_bits_per_sample = snd_pcm_format_width(format); + vu->max = (1 << (vu->significant_bits_per_sample - 1)) - 1; + + vu->le = !!snd_pcm_format_little_endian(format); + vu->zero_point = snd_pcm_format_silence_64(format); + /* For these sample formats, use 32bit width storage temporarily. */ + if (vu->significant_bits_per_sample == 18) + vu->zero_point <<= 14; + if (vu->significant_bits_per_sample == 20) + vu->zero_point <<= 12; + if (vu->significant_bits_per_sample == 24) + vu->zero_point <<= 8; + + return 0; +} + +void vumeter_context_prepare(struct vumeter_context *vu) +{ + int c; + + for (c = 0; c < vu->samples_per_frame; ++c) { + vu->ratios[c] = 0; + vu->max_ratios_in_second[c] = 0; + } + vu->total_frame_count = 0; +} + +void vumeter_context_calculate(struct vumeter_context *vu, void *frame_buffer, + unsigned int frame_count) +{ + int64_t val; + unsigned int ratio; + int c; + + vu->calculator(vu, frame_buffer, frame_count); + + vu->total_frame_count += frame_count; + + for (c = 0; c < vu->samples_per_frame; ++c) { + val = (int64_t)vu->peaks[c]; + ratio = val * 100 / vu->max; + + /* Reset ratios every second. */ + if (vu->total_frame_count >= vu->frames_per_second) + vu->max_ratios_in_second[c] = 0; + if (ratio > vu->max_ratios_in_second[c]) + vu->max_ratios_in_second[c] = ratio; + + vu->ratios[c] = ratio; + } + + vu->total_frame_count %= vu->frames_per_second; +} + +void vumeter_context_print(struct vumeter_context *vu) +{ + char line[80]; + + memset(line, ' ', 79); + + putc('\r', stderr); + + vu->printer(vu, line); + + line[79] = '\0'; + fputs(line, stderr); + fflush(stderr); +} + +void vumeter_context_break(struct vumeter_context *vu) +{ + fprintf(stderr, "\r"); +} + +void vumeter_context_destroy(struct vumeter_context *vu) +{ + if (vu->ratios) + free(vu->ratios); + vu->ratios = NULL; + + if (vu->max_ratios_in_second) + free(vu->max_ratios_in_second); + vu->max_ratios_in_second = NULL; + + if (vu->peaks) + free(vu->peaks); + vu->peaks = NULL; +} diff --git a/axfer/vumeter.h b/axfer/vumeter.h new file mode 100644 index 00000000..dcaa8b4f --- /dev/null +++ b/axfer/vumeter.h @@ -0,0 +1,55 @@ +/* + * vumeter.h - Volume Unit meter. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_AXFER_VUMETER__H_ +#define __ALSA_UTILS_AXFER_VUMETER__H_ + +#include <stdbool.h> +#include <stdint.h> +#include <alsa/asoundlib.h> + +enum vumeter_mode { + VUMETER_MODE_NONE = 0, + VUMETER_MODE_MONO, + VUMETER_MODE_STEREO +}; + +struct vumeter_context { + enum vumeter_mode mode; + + bool le; + unsigned int significant_bits_per_sample; + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_second; + + int64_t zero_point; + uint32_t max; + int32_t *peaks; + + unsigned int *ratios; + unsigned int *max_ratios_in_second; + uint64_t total_frame_count; + + void (*printer)(struct vumeter_context *vu, char line[80]); + void (*calculator)(struct vumeter_context *vu, void *frame_buffer, + unsigned int frame_count); +}; + +int vumeter_context_init(struct vumeter_context *vu, enum vumeter_mode mode, + snd_pcm_access_t access, snd_pcm_format_t format, + unsigned int samples_per_frame, + unsigned int frames_per_second); +void vumeter_context_prepare(struct vumeter_context *vu); +void vumeter_context_calculate(struct vumeter_context *vu, void *frame_buffer, + unsigned int frame_count); +void vumeter_context_print(struct vumeter_context *vu); +void vumeter_context_break(struct vumeter_context *vu); +void vumeter_context_destroy(struct vumeter_context *vu); + +#endif diff --git a/axfer/xfer-libasound-irq-mmap.c b/axfer/xfer-libasound-irq-mmap.c index fb70bbc5..dcb8480e 100644 --- a/axfer/xfer-libasound-irq-mmap.c +++ b/axfer/xfer-libasound-irq-mmap.c @@ -70,7 +70,8 @@ static int irq_mmap_pre_process(struct libasound_state *state) static int irq_mmap_process_frames(struct libasound_state *state, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct map_layout *layout = state->private_data; const snd_pcm_channel_area_t *areas; @@ -139,6 +140,9 @@ static int irq_mmap_process_frames(struct libasound_state *state, if (consumed_count != avail_count) logging(state, "A bug of access plugin for this PCM node.\n");
+ if (vu) + vumeter_context_calculate(vu, frame_buf, consumed_count); + *frame_count = consumed_count;
return 0; @@ -150,7 +154,8 @@ error: static int irq_mmap_r_process_frames(struct libasound_state *state, unsigned *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct map_layout *layout = state->private_data; snd_pcm_state_t s; @@ -188,7 +193,7 @@ static int irq_mmap_r_process_frames(struct libasound_state *state, goto error; }
- err = irq_mmap_process_frames(state, frame_count, mapper, cntrs); + err = irq_mmap_process_frames(state, frame_count, mapper, cntrs, vu); if (err < 0) goto error;
@@ -201,7 +206,8 @@ error: static int irq_mmap_w_process_frames(struct libasound_state *state, unsigned *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct map_layout *layout = state->private_data; snd_pcm_state_t s; @@ -215,7 +221,7 @@ static int irq_mmap_w_process_frames(struct libasound_state *state,
/* TODO: if reporting something, do here with the status data. */
- err = irq_mmap_process_frames(state, frame_count, mapper, cntrs); + err = irq_mmap_process_frames(state, frame_count, mapper, cntrs, vu); if (err < 0) goto error;
diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c index ad320f6f..ee5ce801 100644 --- a/axfer/xfer-libasound-irq-rw.c +++ b/axfer/xfer-libasound-irq-rw.c @@ -20,7 +20,8 @@ struct frame_cache {
int (*handle)(struct libasound_state *state, snd_pcm_state_t status, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs); + struct container_context *cntrs, + struct vumeter_context *vu); void (*align_frames)(struct frame_cache *cache, unsigned int consumed_count, unsigned int bytes_per_sample, @@ -73,7 +74,8 @@ static void align_frames_in_n(struct frame_cache *cache,
static int read_frames(struct libasound_state *state, unsigned int *frame_count, unsigned int avail_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct frame_cache *cache = state->private_data; unsigned int consumed_count; @@ -111,6 +113,9 @@ static int read_frames(struct libasound_state *state, unsigned int *frame_count, if (err < 0) return err;
+ if (vu) + vumeter_context_calculate(vu, cache->buf, consumed_count); + cache->align_frames(cache, consumed_count, cache->bytes_per_sample, cache->samples_per_frame);
@@ -123,7 +128,8 @@ static int r_process_frames_blocking(struct libasound_state *state, snd_pcm_state_t status, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { snd_pcm_sframes_t avail; snd_pcm_uframes_t avail_count; @@ -159,7 +165,7 @@ static int r_process_frames_blocking(struct libasound_state *state, avail_count = (unsigned int)frame_count; }
- err = read_frames(state, frame_count, avail_count, mapper, cntrs); + err = read_frames(state, frame_count, avail_count, mapper, cntrs, vu); if (err < 0) goto error;
@@ -173,7 +179,8 @@ static int r_process_frames_nonblocking(struct libasound_state *state, snd_pcm_state_t status, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { snd_pcm_sframes_t avail; snd_pcm_uframes_t avail_count; @@ -206,7 +213,7 @@ static int r_process_frames_nonblocking(struct libasound_state *state, goto error; }
- err = read_frames(state, frame_count, avail_count, mapper, cntrs); + err = read_frames(state, frame_count, avail_count, mapper, cntrs, vu); if (err < 0) goto error;
@@ -219,7 +226,8 @@ error: static int write_frames(struct libasound_state *state, unsigned int *frame_count, unsigned int avail_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct frame_cache *cache = state->private_data; snd_pcm_uframes_t consumed_count; @@ -254,6 +262,9 @@ static int write_frames(struct libasound_state *state, if (err < 0) return err;
+ if (vu) + vumeter_context_calculate(vu, cache->buf, consumed_count); + consumed_count = (unsigned int)err; cache->align_frames(cache, consumed_count, cache->bytes_per_sample, cache->samples_per_frame); @@ -267,7 +278,8 @@ static int w_process_frames_blocking(struct libasound_state *state, snd_pcm_state_t status, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { snd_pcm_sframes_t avail; unsigned int avail_count; @@ -323,7 +335,7 @@ static int w_process_frames_blocking(struct libasound_state *state, avail_count = (unsigned int)frames_per_period; }
- err = write_frames(state, frame_count, avail_count, mapper, cntrs); + err = write_frames(state, frame_count, avail_count, mapper, cntrs, vu); if (err < 0) goto error;
@@ -337,7 +349,8 @@ static int w_process_frames_nonblocking(struct libasound_state *state, snd_pcm_state_t status, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { snd_pcm_sframes_t avail; unsigned int avail_count; @@ -364,7 +377,7 @@ static int w_process_frames_nonblocking(struct libasound_state *state, goto error; }
- err = write_frames(state, frame_count, avail_count, mapper, cntrs); + err = write_frames(state, frame_count, avail_count, mapper, cntrs, vu); if (err < 0) goto error;
@@ -461,7 +474,8 @@ static int irq_rw_pre_process(struct libasound_state *state) static int irq_rw_process_frames(struct libasound_state *state, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct frame_cache *cache = state->private_data; snd_pcm_state_t status; @@ -472,7 +486,7 @@ static int irq_rw_process_frames(struct libasound_state *state, return -EPIPE;
/* NOTE: Actually, status can be shift always. */ - return cache->handle(state, status, frame_count, mapper, cntrs); + return cache->handle(state, status, frame_count, mapper, cntrs, vu); }
static void irq_rw_post_process(struct libasound_state *state) diff --git a/axfer/xfer-libasound-timer-mmap.c b/axfer/xfer-libasound-timer-mmap.c index 413b7958..90439414 100644 --- a/axfer/xfer-libasound-timer-mmap.c +++ b/axfer/xfer-libasound-timer-mmap.c @@ -121,7 +121,8 @@ static void *get_buffer(struct libasound_state *state, static int timer_mmap_process_frames(struct libasound_state *state, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct map_layout *layout = state->private_data; snd_pcm_uframes_t planned_count; @@ -215,6 +216,9 @@ static int timer_mmap_process_frames(struct libasound_state *state, } *frame_count = consumed_count;
+ if (vu) + vumeter_context_calculate(vu, frame_buf, *frame_count); + return 0; }
@@ -246,7 +250,8 @@ static int forward_appl_ptr(struct libasound_state *state) static int timer_mmap_r_process_frames(struct libasound_state *state, unsigned *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct map_layout *layout = state->private_data; snd_pcm_state_t s; @@ -280,7 +285,7 @@ static int timer_mmap_r_process_frames(struct libasound_state *state, }
err = timer_mmap_process_frames(state, frame_count, mapper, - cntrs); + cntrs, vu); if (err < 0) goto error; } else { @@ -384,7 +389,8 @@ static int fill_buffer_with_zero_samples(struct libasound_state *state) static int timer_mmap_w_process_frames(struct libasound_state *state, unsigned *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct map_layout *layout = state->private_data; snd_pcm_state_t s; @@ -411,7 +417,7 @@ static int timer_mmap_w_process_frames(struct libasound_state *state, }
err = timer_mmap_process_frames(state, frame_count, mapper, - cntrs); + cntrs, vu); if (err < 0) goto error; } else { diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 12c90936..92aba3c4 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -562,7 +562,8 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, static int xfer_libasound_process_frames(struct xfer_context *xfer, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct libasound_state *state = xfer->private_data; int err; @@ -570,7 +571,7 @@ static int xfer_libasound_process_frames(struct xfer_context *xfer, if (state->handle == NULL) return -ENXIO;
- err = state->ops->process_frames(state, frame_count, mapper, cntrs); + err = state->ops->process_frames(state, frame_count, mapper, cntrs, vu); if (err < 0) { if (err == -EPIPE && !state->finish_at_xrun) { /* diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 5d399571..004613fa 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -41,7 +41,8 @@ struct xfer_libasound_ops { int (*process_frames)(struct libasound_state *state, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs); + struct container_context *cntrs, + struct vumeter_context *vu); void (*post_process)(struct libasound_state *state); unsigned int private_size; }; diff --git a/axfer/xfer.c b/axfer/xfer.c index 2f9f9422..211d9aa5 100644 --- a/axfer/xfer.c +++ b/axfer/xfer.c @@ -47,6 +47,14 @@ int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, return -ENOMEM; memset(xfer->private_data, 0, entries[i].data->private_size);
+ if (opts->vu_mode != VUMETER_MODE_NONE) { + xfer->vu = malloc(sizeof(*xfer->vu)); + if (xfer->vu == NULL) + return -ENOMEM; + memset(xfer->vu, 0, sizeof(*xfer->vu)); + xfer->vu->mode = opts->vu_mode; + } + return xfer->ops->init(xfer, direction, opts); }
@@ -61,6 +69,10 @@ void xfer_context_destroy(struct xfer_context *xfer) xfer->ops->destroy(xfer); if (xfer->private_data) free(xfer->private_data); + + if (xfer->vu) + free(xfer->vu); + xfer->vu = NULL; }
int xfer_context_pre_process(struct xfer_context *xfer, @@ -97,6 +109,16 @@ int xfer_context_pre_process(struct xfer_context *xfer, assert(*access <= SND_PCM_ACCESS_LAST); assert(*frames_per_buffer > 0);
+ if (xfer->vu) { + err = vumeter_context_init(xfer->vu, xfer->vu->mode, *access, + *format, *samples_per_frame, + *frames_per_second); + if (err < 0) + return err; + + vumeter_context_prepare(xfer->vu); + } + if (xfer->verbose > 1) { fprintf(stderr, "Transfer: %s\n", xfer_type_labels[xfer->type]); @@ -122,6 +144,8 @@ int xfer_context_process_frames(struct xfer_context *xfer, struct container_context *cntrs, unsigned int *frame_count) { + int err; + assert(xfer); assert(mapper); assert(cntrs); @@ -130,7 +154,15 @@ int xfer_context_process_frames(struct xfer_context *xfer, if (!xfer->ops) return -ENXIO;
- return xfer->ops->process_frames(xfer, frame_count, mapper, cntrs); + err = xfer->ops->process_frames(xfer, frame_count, mapper, cntrs, + xfer->vu); + if (err < 0) + return err; + + if (xfer->vu) + vumeter_context_print(xfer->vu); + + return 0; }
void xfer_context_pause(struct xfer_context *xfer, bool enable) @@ -151,4 +183,9 @@ void xfer_context_post_process(struct xfer_context *xfer) return;
xfer->ops->post_process(xfer); + + if (xfer->vu) { + vumeter_context_break(xfer->vu); + vumeter_context_destroy(xfer->vu); + } } diff --git a/axfer/xfer.h b/axfer/xfer.h index 167c9903..eccf042c 100644 --- a/axfer/xfer.h +++ b/axfer/xfer.h @@ -23,6 +23,8 @@ struct xfer_context { const struct xfer_ops *ops; void *private_data;
+ struct vumeter_context *vu; + unsigned int verbose; };
@@ -57,7 +59,8 @@ struct xfer_ops { int (*process_frames)(struct xfer_context *xfer, unsigned int *frame_count, struct mapper_context *mapper, - struct container_context *cntrs); + struct container_context *cntrs, + struct vumeter_context *vu); void (*post_process)(struct xfer_context *xfer); void (*destroy)(struct xfer_context *xfer); void (*pause)(struct xfer_context *xfer, bool enable);
In a previous commit, vumeter is implemented. This commit adds a simple unit test for the feature. This tests positive cases only.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/test/Makefile.am | 14 +++- axfer/test/vumeter-test.c | 184 ++++++++++++++++++++++++++++++++++++++++++ axfer/xfer-libasound-irq-rw.c | 2 +- 3 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 axfer/test/vumeter-test.c
diff --git a/axfer/test/Makefile.am b/axfer/test/Makefile.am index 76a93bfc..0e48b9f2 100644 --- a/axfer/test/Makefile.am +++ b/axfer/test/Makefile.am @@ -1,10 +1,12 @@ TESTS = \ - container-test \ - mapper-test + container-test \ + mapper-test \ + vumeter-test
check_PROGRAMS = \ container-test \ - mapper-test + mapper-test \ + vumeter-test
container_test_SOURCES = \ ../container.h \ @@ -29,3 +31,9 @@ mapper_test_SOURCES = \ ../mapper-multiple.c \ generator.c \ mapper-test.c + +vumeter_test_SOURCES = \ + ../vumeter.h \ + ../vumeter.c \ + generator.c \ + vumeter-test.c diff --git a/axfer/test/vumeter-test.c b/axfer/test/vumeter-test.c new file mode 100644 index 00000000..6df7c067 --- /dev/null +++ b/axfer/test/vumeter-test.c @@ -0,0 +1,184 @@ +/* + * test-vumeter.c - A unit test for Volume Unit meter. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "../vumeter.h" +#include "../misc.h" + +#include "generator.h" + +#include <stdlib.h> + +static bool verbose; + +static int callback(struct test_generator *gen, snd_pcm_access_t access, + snd_pcm_format_t sample_format, + unsigned int samples_per_frame, void *frame_buffer, + unsigned int frame_count) +{ + static const enum vumeter_mode modes[] = { + [0] = VUMETER_MODE_MONO, + [1] = VUMETER_MODE_STEREO, + }; + struct vumeter_context *vu = gen->private_data; + int i; + int err; + + char **vector = NULL; + + if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { + char *buf; + unsigned int size; + + vector = calloc(samples_per_frame, sizeof(*vector)); + if (vector == NULL) + return -ENOMEM; + + buf = frame_buffer; + size = snd_pcm_format_physical_width(sample_format) * + frame_count / 8; + + for (i = 0; i < samples_per_frame; ++i) + vector[i] = buf + size * i; + + frame_buffer = vector; + } + + for (i = 0; i < ARRAY_SIZE(modes); ++i) { + int j; + + if (verbose) { + printf("%d %s %s %d %d\n", + modes[i], snd_pcm_access_name(access), + snd_pcm_format_name(sample_format), + samples_per_frame, frame_count); + } + + memset(vu, 0, sizeof(*vu)); + err = vumeter_context_init(vu, modes[i], access, sample_format, + samples_per_frame, 4000); + if (err < 0) + break; + assert(vu->significant_bits_per_sample >= 0); + assert(vu->bytes_per_sample >= 0); + assert(vu->samples_per_frame >= 0); + assert(vu->frames_per_second >= 0); + assert(vu->max > 0); + assert(vu->peaks != NULL); + assert(vu->ratios != NULL); + assert(vu->max_ratios_in_second != NULL); + assert(vu->printer != NULL); + assert(vu->calculator != NULL); + + vumeter_context_prepare(vu); + for (j = 0; j < samples_per_frame; ++j) { + assert(vu->ratios[j] == 0); + assert(vu->max_ratios_in_second[j] == 0); + } + + vumeter_context_calculate(vu, frame_buffer, frame_count); + for (j = 0; j < samples_per_frame; ++j) { + assert(vu->peaks[j] <= vu->max); + assert(vu->max_ratios_in_second[j] <= vu->max); + assert(vu->max_ratios_in_second[j] <= 100); + } + + vumeter_context_destroy(vu); + assert(vu->ratios == NULL); + assert(vu->max_ratios_in_second == NULL); + assert(vu->peaks == NULL); + } + + if (vector != NULL) + free(vector); + + return err; +} + +int main(int argc, const char *argv[]) +{ + const uint64_t sample_format_mask = + (1ul << SND_PCM_FORMAT_S8) | + (1ul << SND_PCM_FORMAT_U8) | + (1ul << SND_PCM_FORMAT_S16_LE) | + (1ul << SND_PCM_FORMAT_S16_BE) | + (1ul << SND_PCM_FORMAT_U16_LE) | + (1ul << SND_PCM_FORMAT_U16_BE) | + (1ul << SND_PCM_FORMAT_S24_LE) | + (1ul << SND_PCM_FORMAT_S24_BE) | + (1ul << SND_PCM_FORMAT_U24_LE) | + (1ul << SND_PCM_FORMAT_U24_BE) | + (1ul << SND_PCM_FORMAT_S32_LE) | + (1ul << SND_PCM_FORMAT_S32_BE) | + (1ul << SND_PCM_FORMAT_U32_LE) | + (1ul << SND_PCM_FORMAT_U32_BE) | + (1ul << SND_PCM_FORMAT_S24_3LE) | + (1ul << SND_PCM_FORMAT_S24_3BE) | + (1ul << SND_PCM_FORMAT_U24_3LE) | + (1ul << SND_PCM_FORMAT_U24_3BE) | + (1ul << SND_PCM_FORMAT_S24_3LE) | + (1ul << SND_PCM_FORMAT_S24_3BE) | + (1ul << SND_PCM_FORMAT_U24_3LE) | + (1ul << SND_PCM_FORMAT_U24_3BE) | + (1ul << SND_PCM_FORMAT_S20_3LE) | + (1ul << SND_PCM_FORMAT_S20_3BE) | + (1ul << SND_PCM_FORMAT_U20_3LE) | + (1ul << SND_PCM_FORMAT_U20_3BE) | + (1ul << SND_PCM_FORMAT_S18_3LE) | + (1ul << SND_PCM_FORMAT_S18_3BE) | + (1ul << SND_PCM_FORMAT_U18_3LE) | + (1ul << SND_PCM_FORMAT_U18_3BE); + uint64_t access_mask; + struct test_generator gen = {0}; + snd_pcm_access_t access; + int err; + + if (argc > 1) { + char *term; + + access = strtol(argv[1], &term, 10); + if (errno != 0 || *term != '\0') { + err = -EINVAL; + goto end; + } + if (access < SND_PCM_ACCESS_MMAP_INTERLEAVED && + access > SND_PCM_ACCESS_RW_NONINTERLEAVED) { + err = -EINVAL; + goto end; + } + if (access == SND_PCM_ACCESS_MMAP_COMPLEX) { + err = -EINVAL; + goto end; + } + access_mask = 1ul << access; + verbose = true; + } else { + access_mask = (1ul << SND_PCM_ACCESS_MMAP_INTERLEAVED) | + (1ul << SND_PCM_ACCESS_MMAP_NONINTERLEAVED) | + (1ul << SND_PCM_ACCESS_RW_INTERLEAVED) | + (1ul << SND_PCM_ACCESS_RW_NONINTERLEAVED); + verbose = false; + } + + + err = generator_context_init(&gen, access_mask, sample_format_mask, + 1, 128, 23, 4500, 1024, + sizeof(struct vumeter_context)); + if (err < 0) + return EXIT_FAILURE; + + err = generator_context_run(&gen, callback); + + generator_context_destroy(&gen); +end: + if (err < 0) { + printf("%s\n", strerror(-err)); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c index ee5ce801..a2152d92 100644 --- a/axfer/xfer-libasound-irq-rw.c +++ b/axfer/xfer-libasound-irq-rw.c @@ -261,11 +261,11 @@ static int write_frames(struct libasound_state *state, err = snd_pcm_writen(state->handle, cache->buf, consumed_count); if (err < 0) return err; + consumed_count = (unsigned int)err;
if (vu) vumeter_context_calculate(vu, cache->buf, consumed_count);
- consumed_count = (unsigned int)err; cache->align_frames(cache, consumed_count, cache->bytes_per_sample, cache->samples_per_frame);
In aplay, an option for formatted file name is supported. The format text is mainly processed by strftime(3) function, however '%v' is available as an additional format for file number.
This commit adds support for the option. However, this feature has issue that strftime(3) generates string with characters out of portable filename characters such as '/' (). This feature is unsafe for users, for instance:
$ strace ./axfer transfer -C -D hw:0,0 --use-strftime /tmp/test-%D.wav ... open("/tmp/test-09/18/17.wav", O_RDWR|O_CREAT|O_TRUNC|O_NONBLOCK, 0644) = -1 ENOENT (No such file or directory) ...
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- axfer/options.h | 1 + 2 files changed, 94 insertions(+), 5 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index d595d857..6665f893 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -24,6 +24,7 @@ enum no_short_opts { OPT_DISABLE_FORMAT, OPT_DISABLE_SOFTVOL, OPT_SCHED_TYPE, + OPT_USE_STRFTIME, /* Obsoleted. */ OPT_MAX_FILE_TIME, }; @@ -403,6 +404,7 @@ int context_options_init(struct context_options *opts, int argc, {"samples", 1, 0, 's'}, /* For containers. */ {"file-type", 1, 0, 't'}, + {"use-strftime", 0, 0, OPT_USE_STRFTIME}, /* For mapper. */ {"separate-channels", 0, 0, 'I'}, /* For transfer backend. */ @@ -465,6 +467,8 @@ int context_options_init(struct context_options *opts, int argc, opts->duration_frames = parse_l(optarg, &err); else if (c == 't') cntr_format_literal = optarg; + else if (c == OPT_USE_STRFTIME) + opts->use_strftime = true; else if (c == 'I') opts->multiple_cntrs = true; else if (c == 'D') @@ -536,9 +540,81 @@ int context_options_init(struct context_options *opts, int argc, vu_mode_literal); }
+/* + * A variant of strftime(3) that supports additional format specifiers in the + * format string: + * '%v': file number. + * + * TODO: some format string gets unportable characters such as '/'. + */ +static int generate_path_with_strftime(struct context_options *opts, + const char *template, + unsigned int index, const char *suffix, + const struct tm *now) +{ + char *format; + unsigned int len; + char *pos; + char *name; + unsigned int width; + unsigned int i; + int err = 0; + + /* This might be enough to process formatted strings. */ + format = malloc(4096); + if (format == NULL) + return -ENOMEM; + + len = strftime(format, 4096, template, now); + if (len == 0) { + fprintf(stderr, "Invalid format string for strftime(3): %s\n", + template); + err = -EINVAL; + goto end; + } + format[len] = '\0'; + + width = (unsigned int)log10(index) + 1; + + /* Estimate required length for formatted result. */ + len = 0; + for (pos = format; *pos != '\0'; ++pos) { + ++len; + /* '%v' is unique format for numbering. */ + if (*pos == '%' && *(pos + 1) == 'v') + len += width; + } + len += strlen(suffix); + + name = malloc(len + 1); + if (name == NULL) { + err = -ENOMEM; + goto end; + } + + i = 0; + for (pos = format; *pos != '\0' && i < len; ++pos) { + if (*pos == '%' && *(pos + 1) == 'v') { + i += snprintf(name + i, len - i, "%u", index); + /* Consume 2 characters in the format string. */ + ++pos; + } else { + name[i] = *pos; + ++i; + } + } + strcpy(name + i, suffix); + name[len] = '\0'; + + opts->paths[index] = name; +end: + free(format); + return err; +} + static int generate_path_with_suffix(struct context_options *opts, const char *template, unsigned int index, - const char *suffix) + const char *suffix, const struct tm *now) { static const char *const single_format = "%s%s"; static const char *const multiple_format = "%s-%i%s"; @@ -565,7 +641,8 @@ static int generate_path_with_suffix(struct context_options *opts,
static int generate_path_without_suffix(struct context_options *opts, const char *template, - unsigned int index, const char *suffix) + unsigned int index, const char *suffix, + const struct tm *now) { static const char *const single_format = "%s"; static const char *const multiple_format = "%s-%i"; @@ -593,8 +670,17 @@ static int generate_path(struct context_options *opts, char *template, unsigned int index, const char *suffix) { int (*generator)(struct context_options *opts, const char *template, - unsigned int index, const char *suffix); + unsigned int index, const char *suffix, + const struct tm *now); char *pos; + time_t now_time; + struct tm now_tm; + + now_time = time(NULL); + if ((int)now_time < 0) + return -errno; + if (localtime_r(&now_time, &now_tm) == NULL) + return -errno;
if (strlen(suffix) > 0) { pos = template + strlen(template) - strlen(suffix); @@ -604,12 +690,14 @@ static int generate_path(struct context_options *opts, char *template, }
/* Select handlers. */ - if (strlen(suffix) > 0) + if (opts->use_strftime) + generator = generate_path_with_strftime; + else if (strlen(suffix) > 0) generator = generate_path_with_suffix; else generator = generate_path_without_suffix;
- return generator(opts, template, index, suffix); + return generator(opts, template, index, suffix, &now_tm); }
static int create_paths(struct context_options *opts, unsigned int path_count) diff --git a/axfer/options.h b/axfer/options.h index 188668b0..2092796e 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -32,6 +32,7 @@ struct context_options { char **paths; unsigned int path_count; enum container_format cntr_format; + bool use_strftime;
/* For mapper. */ bool multiple_cntrs;
In current implementation of aplay, when an option '--interactive' (-i) is given, users can pause data transmission by pushing 'space' or 'enter' key. The key event is handled via control terminal.
This commit adds support for this feature. This feature includes an issue of race condition between SIGTSTOP/SIGCONT. When transmission of data frame is paused by key event, then this application goes to backend by the signal, a handler for SIGTSTOP restart the PCM substream after catching SIGCONT but this brings state contradiction for a handler of key event. Currently, this scenario is not cared in this commit.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/Makefile.am | 7 ++- axfer/key-event.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ axfer/key-event.h | 19 ++++++++ axfer/options.c | 20 +++++++- axfer/options.h | 1 + axfer/subcmd-transfer.c | 22 +++++++++ 6 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 axfer/key-event.c create mode 100644 axfer/key-event.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 7a079e62..ed7f349d 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -23,7 +23,8 @@ noinst_HEADERS = \ xfer.h \ xfer-alsa.h \ waiter.h \ - vumeter.h + vumeter.h \ + key-event.h
axfer_SOURCES = \ misc.h \ @@ -55,4 +56,6 @@ axfer_SOURCES = \ waiter-epoll.c \ xfer-libasound-timer-mmap.c \ vumeter.h \ - vumeter.c + vumeter.c \ + key-event.h \ + key-event.c diff --git a/axfer/key-event.c b/axfer/key-event.c new file mode 100644 index 00000000..61680f0c --- /dev/null +++ b/axfer/key-event.c @@ -0,0 +1,121 @@ +/* + * key-event.c - a handler for key events via stdin. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "key-event.h" +#include "misc.h" + +#include <stdio.h> +#include <termios.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> + +int keyevent_context_init(struct keyevent_context *key) +{ + struct termios term; + int err; + + key->fd = fileno(stdin); + if (!isatty(key->fd)) { + printf(_("isatty(3): %s\n"), + strerror(errno)); + return -EINVAL; + } + + err = tcgetattr(key->fd, &term); + if (err < 0) { + printf(_("tcgetattr(3): %s\n"), + strerror(errno)); + return -errno; + } + key->c_lflag = term.c_lflag; + + term.c_lflag &= ~ICANON; + err = tcsetattr(key->fd, TCSANOW, &term); + if (err < 0) { + printf(_("tcsetattr(3): %s\n"), + strerror(errno)); + return -errno; + } + + return 0; +} + +static int set_nonblock_flag(int fd, bool nonblock) +{ + int err; + + err = fcntl(fd, F_GETFL); + if (err < 0) { + printf(_("fcntl(2) with F_GETFL: %s\n"), + strerror(errno)); + return -errno; + } + + if (nonblock) + err |= O_NONBLOCK; + else + err &= ~O_NONBLOCK; + err = fcntl(fd, F_SETFL, err); + if (err < 0) { + printf(_("fcntl(2) with F_SETFL: %s\n"), + strerror(errno)); + return -errno; + } + + return 0; +} + +int keyevent_context_check(struct keyevent_context *key, + struct xfer_context *xfer) +{ + char b; + ssize_t len; + bool paused = false; + int err; + + while (1) { + err = set_nonblock_flag(key->fd, !paused); + if (err < 0) + return err; + + len = read(key->fd, &b, sizeof(b)); + if (len < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) + break; + return -errno; + } + if (len == 0) + break; + if (b == ' ' || b == '\n') { + xfer_context_pause(xfer, !paused); + if (paused) + break; + paused = !paused; + } + } + + return 0; +} + +void keyevent_context_destroy(struct keyevent_context *key) +{ + struct termios term; + int err; + + tcgetattr(key->fd, &term); + term.c_lflag = key->c_lflag; + err = tcsetattr(key->fd, TCSANOW, &term); + if (err < 0) { + printf(_("tcsetattr(3): %s\n"), + strerror(errno)); + } +} diff --git a/axfer/key-event.h b/axfer/key-event.h new file mode 100644 index 00000000..2e11ebf0 --- /dev/null +++ b/axfer/key-event.h @@ -0,0 +1,19 @@ +/* + * key-event.h - a header to a handler for key events via stdin. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer.h" + +struct keyevent_context { + int fd; + long c_lflag; +}; + +int keyevent_context_init(struct keyevent_context *key); +int keyevent_context_check(struct keyevent_context *key, + struct xfer_context *xfer); +void keyevent_context_destroy(struct keyevent_context *key); diff --git a/axfer/options.c b/axfer/options.c index 6665f893..d4e7876b 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -300,6 +300,21 @@ static int apply_policies(struct context_options *opts, } }
+ if (opts->interactive) { + /* + * In interactive mode, users can suspend/resume PCM substream + * by keyboard input, thus stdin cannot used for source of + * PCM frames. + */ + if (direction == SND_PCM_STREAM_PLAYBACK && + !strcmp(opts->paths[0], "-")) { + fprintf(stderr, + "An option for interactive mode is not " + "available with stdin.\n"); + return -EINVAL; + } + } + opts->sample_format = SND_PCM_FORMAT_UNKNOWN; if (sample_format_literal) { err = verify_sample_format(opts, sample_format_literal); @@ -394,7 +409,7 @@ void context_options_calculate_duration(struct context_options *opts, int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMw:F:B:A:R:T:V:"; + static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMw:F:B:A:R:T:V:i"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -435,6 +450,7 @@ int context_options_init(struct context_options *opts, int argc, {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, /* Misc features. */ {"vumeter", 1, 0, 'V'}, + {"interactive", 0, 0, 'i'}, /* Obsoleted. */ {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, {NULL, 0, 0, 0}, @@ -517,6 +533,8 @@ int context_options_init(struct context_options *opts, int argc, opts->test_nowait = true; else if (c == 'V') vu_mode_literal = optarg; + else if (c == 'i') + opts->interactive = true; else if (c == OPT_MAX_FILE_TIME) { fprintf(stderr, "An option '--%s' is obsoleted and has no " diff --git a/axfer/options.h b/axfer/options.h index 2092796e..fed9a40e 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -72,6 +72,7 @@ struct context_options {
/* Misc features. */ enum vumeter_mode vu_mode; + bool interactive; };
int context_options_init(struct context_options *opts, int argc, diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 480b4eb5..96a79bad 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -9,6 +9,7 @@ #include "xfer.h" #include "subcmd.h" #include "misc.h" +#include "key-event.h"
#include <signal.h>
@@ -364,9 +365,19 @@ static int context_process_frames(struct context *ctx, { bool verbose = ctx->opts.verbose > 2; unsigned int frame_count; + struct keyevent_context *key = NULL; int i; int err = 0;
+ if (ctx->opts.interactive) { + key = malloc(sizeof(*key)); + if (key == NULL) + return -ENOMEM; + err = keyevent_context_init(key); + if (err < 0) + return err; + } + if (!ctx->opts.quiet) { fprintf(stderr, _("%s: Format '%s', Rate %u Hz, Channels "), @@ -410,6 +421,17 @@ static int context_process_frames(struct context *ctx, *actual_frame_count += frame_count; if (*actual_frame_count >= expected_frame_count) break; + + if (key) { + err = keyevent_context_check(key, &ctx->xfer); + if (err < 0) + break; + } + } + + if (key) { + keyevent_context_destroy(key); + free(key); }
if (!ctx->opts.quiet) {
Current aplay has an option to use channel map API of alsa-lib. This API allows applications to get/set channel position by a position array; e.g. 'FR,FL' and 'LFE,FL,FR'.
This commit adds support for the feature. However, neither alsa-lib implementation nor ALSA PCM interface, configuring the channel map is not supported. Therefore, this feature makes no sense except for displaying current channel map.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 29 ++++++++++++++++++++++++++--- axfer/options.h | 1 + axfer/xfer-libasound.c | 32 ++++++++++++++++++++++++++++++++ axfer/xfer-libasound.h | 2 ++ 4 files changed, 61 insertions(+), 3 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index d4e7876b..7e112c42 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -205,7 +205,8 @@ static int apply_policies(struct context_options *opts, const char *sample_format_literal, const char *waiter_type_literal, const char *sched_type_literal, - const char *vu_mode_literal) + const char *vu_mode_literal, + const char *chmap_literal) { int err;
@@ -238,6 +239,21 @@ static int apply_policies(struct context_options *opts, return err; }
+ if (chmap_literal) { + snd_pcm_chmap_t *chmap; + chmap = snd_pcm_chmap_parse_string(chmap_literal); + if (chmap == NULL) { + fprintf(stderr, + "Unable to parse channel map string: '%s'\n", + chmap_literal); + return -EINVAL; + } + free(chmap); + opts->chmap_literal = strdup(chmap_literal); + if (opts->chmap_literal == NULL) + return -ENOMEM; + } + if (opts->samples_per_frame > 0) { if (opts->samples_per_frame < 1 || opts->samples_per_frame > 256) { @@ -409,7 +425,7 @@ void context_options_calculate_duration(struct context_options *opts, int context_options_init(struct context_options *opts, int argc, char *const *argv, snd_pcm_stream_t direction) { - static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMw:F:B:A:R:T:V:i"; + static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMw:F:B:A:R:T:V:im:"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -451,6 +467,7 @@ int context_options_init(struct context_options *opts, int argc, /* Misc features. */ {"vumeter", 1, 0, 'V'}, {"interactive", 0, 0, 'i'}, + {"chmap", 1, 0, 'm'}, /* Obsoleted. */ {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, {NULL, 0, 0, 0}, @@ -461,6 +478,7 @@ int context_options_init(struct context_options *opts, int argc, const char *waiter_type_literal = NULL; const char *sched_type_literal = NULL; const char *vu_mode_literal = NULL; + const char *chmap_literal = NULL; int l_index = 0; int c; int err = 0; @@ -535,6 +553,8 @@ int context_options_init(struct context_options *opts, int argc, vu_mode_literal = optarg; else if (c == 'i') opts->interactive = true; + else if (c == 'm') + chmap_literal = optarg; else if (c == OPT_MAX_FILE_TIME) { fprintf(stderr, "An option '--%s' is obsoleted and has no " @@ -555,7 +575,7 @@ int context_options_init(struct context_options *opts, int argc, return apply_policies(opts, direction, cntr_format_literal, node_literal, sample_format_literal, waiter_type_literal, sched_type_literal, - vu_mode_literal); + vu_mode_literal, chmap_literal); }
/* @@ -909,6 +929,9 @@ void context_options_destroy(struct context_options *opts) } if (opts->node) free(opts->node); + if (opts->chmap_literal) + free(opts->chmap_literal); opts->paths = NULL; opts->node = NULL; + opts->chmap_literal = NULL; } diff --git a/axfer/options.h b/axfer/options.h index fed9a40e..b8aba167 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -73,6 +73,7 @@ struct context_options { /* Misc features. */ enum vumeter_mode vu_mode; bool interactive; + char *chmap_literal; };
int context_options_init(struct context_options *opts, int argc, diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 92aba3c4..4e8c6e2e 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -84,6 +84,17 @@ static int disable_period_wakeup(struct libasound_state *state) return err; }
+static void dump_chmap(struct libasound_state *state, + const snd_pcm_chmap_t *chmap) +{ + int i; + + logging(state, " channels: %u\n", chmap->channels); + for (i = 0; i < chmap->channels; ++i) + logging(state, " ch%u: %s\n", + i, snd_pcm_chmap_name(chmap->pos[i])); +} + static int xfer_libasound_init(struct xfer_context *xfer, snd_pcm_stream_t direction, struct context_options *opts) @@ -152,6 +163,17 @@ static int xfer_libasound_init(struct xfer_context *xfer, return 0; }
+ if (opts->chmap_literal != NULL) { + state->chmap = snd_pcm_chmap_parse_string(opts->chmap_literal); + if (state->chmap == NULL) + return -ENOMEM; + + if (xfer->verbose) { + logging(state, "Chmap argument:\n"); + dump_chmap(state, state->chmap); + } + } + return set_access_hw_param(state->handle, state->hw_params, opts); }
@@ -238,6 +260,12 @@ static int configure_hw_params(struct libasound_state *state, return err; } } + if (state->chmap && samples_per_frame != state->chmap->channels) { + logging(state, + _("Mismatch between channel number and given map: %u " + "%u\n"), + samples_per_frame, state->chmap->channels); + } err = snd_pcm_hw_params_set_channels(state->handle, state->hw_params, samples_per_frame); if (err < 0) { @@ -690,6 +718,10 @@ static void xfer_libasound_destroy(struct xfer_context *xfer) if (state->log) snd_output_close(state->log); state->log = NULL; + + if (state->chmap) + free(state->chmap); + state->chmap = NULL; }
const struct xfer_data xfer_libasound = { diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 004613fa..11e610d8 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -34,6 +34,8 @@ struct libasound_state {
bool verbose; bool finish_at_xrun; + + snd_pcm_chmap_t *chmap; };
struct xfer_libasound_ops {
In current implementation of aplay, during transferring data frames, the program generates a file with PID in given path for an option '--process-id-file'.
This commit adds support for this feature. However, usually, an idea of process ID file is used for programs such that they run several sub-processes and one of them have significant role to maintain the other sub-processes. Content of the process ID file includes PID for the significant sub-process to avoid sending Unix signals to the other processes. Apparently, aplay and this program are not this kind of program. This feature makes less sense because process ID for this program can be found by usual tools such as ps(1) or pgrep(1) which seek nodes on procfs.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 41 +++++++++++++++++++++++++++++++++++++++-- axfer/options.h | 1 + 2 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/axfer/options.c b/axfer/options.c index 7e112c42..6e935cd1 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -11,6 +11,8 @@
#include <getopt.h> #include <math.h> +#include <sys/types.h> +#include <unistd.h>
enum no_short_opts { /* 128 belongs to non us-ascii character set. */ @@ -25,6 +27,7 @@ enum no_short_opts { OPT_DISABLE_SOFTVOL, OPT_SCHED_TYPE, OPT_USE_STRFTIME, + OPT_PROCESS_ID_FILE, /* Obsoleted. */ OPT_MAX_FILE_TIME, }; @@ -206,7 +209,8 @@ static int apply_policies(struct context_options *opts, const char *waiter_type_literal, const char *sched_type_literal, const char *vu_mode_literal, - const char *chmap_literal) + const char *chmap_literal, + const char *pidfile_name_literal) { int err;
@@ -254,6 +258,30 @@ static int apply_policies(struct context_options *opts, return -ENOMEM; }
+ if (pidfile_name_literal) { + FILE *fstream; + + opts->pidfile_name = strdup(pidfile_name_literal); + if (opts->pidfile_name == NULL) { + fprintf(stderr, + "Fail to allocate for a name of file with " + "process id: %s\n", + pidfile_name_literal); + return -ENOMEM; + } + + unlink(pidfile_name_literal); + fstream = fopen(pidfile_name_literal, "w"); + if (fstream == NULL) { + fprintf(stderr, + "Fail to generate a file with process id: %s\n", + pidfile_name_literal); + return -errno; + } + fprintf(fstream, "%d\n", getpid()); + fclose(fstream); + } + if (opts->samples_per_frame > 0) { if (opts->samples_per_frame < 1 || opts->samples_per_frame > 256) { @@ -468,6 +496,7 @@ int context_options_init(struct context_options *opts, int argc, {"vumeter", 1, 0, 'V'}, {"interactive", 0, 0, 'i'}, {"chmap", 1, 0, 'm'}, + {"process-id-file", 1, 0, OPT_PROCESS_ID_FILE}, /* Obsoleted. */ {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, {NULL, 0, 0, 0}, @@ -479,6 +508,7 @@ int context_options_init(struct context_options *opts, int argc, const char *sched_type_literal = NULL; const char *vu_mode_literal = NULL; const char *chmap_literal = NULL; + const char *pidfile_name_literal = NULL; int l_index = 0; int c; int err = 0; @@ -555,6 +585,8 @@ int context_options_init(struct context_options *opts, int argc, opts->interactive = true; else if (c == 'm') chmap_literal = optarg; + else if (c == OPT_PROCESS_ID_FILE) + pidfile_name_literal = optarg; else if (c == OPT_MAX_FILE_TIME) { fprintf(stderr, "An option '--%s' is obsoleted and has no " @@ -575,7 +607,8 @@ int context_options_init(struct context_options *opts, int argc, return apply_policies(opts, direction, cntr_format_literal, node_literal, sample_format_literal, waiter_type_literal, sched_type_literal, - vu_mode_literal, chmap_literal); + vu_mode_literal, chmap_literal, + pidfile_name_literal); }
/* @@ -922,6 +955,10 @@ void context_options_destroy(struct context_options *opts) { int i;
+ if (opts->pidfile_name) + unlink(opts->pidfile_name); + opts->pidfile_name = NULL; + if (opts->paths) { for (i = 0; i < opts->path_count; ++i) free(opts->paths[i]); diff --git a/axfer/options.h b/axfer/options.h index b8aba167..e8e4524b 100644 --- a/axfer/options.h +++ b/axfer/options.h @@ -74,6 +74,7 @@ struct context_options { enum vumeter_mode vu_mode; bool interactive; char *chmap_literal; + char *pidfile_name; };
int context_options_init(struct context_options *opts, int argc,
In aplay, some test options are available, however their test strategy is not enough good to check runtime of PCM substream.
This commit obsoletes these options.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- axfer/options.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/axfer/options.c b/axfer/options.c index 6e935cd1..baddfb27 100644 --- a/axfer/options.c +++ b/axfer/options.c @@ -30,6 +30,8 @@ enum no_short_opts { OPT_PROCESS_ID_FILE, /* Obsoleted. */ OPT_MAX_FILE_TIME, + OPT_TEST_POSITION, + OPT_TEST_COEF, };
static const char *const allowed_duplication[] = { @@ -499,6 +501,8 @@ int context_options_init(struct context_options *opts, int argc, {"process-id-file", 1, 0, OPT_PROCESS_ID_FILE}, /* Obsoleted. */ {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, + {"test-position", 0, 0, OPT_TEST_POSITION}, + {"test-coef", 1, 0, OPT_TEST_COEF}, {NULL, 0, 0, 0}, }; const char *cntr_format_literal = NULL; @@ -587,7 +591,9 @@ int context_options_init(struct context_options *opts, int argc, chmap_literal = optarg; else if (c == OPT_PROCESS_ID_FILE) pidfile_name_literal = optarg; - else if (c == OPT_MAX_FILE_TIME) { + else if (c == OPT_MAX_FILE_TIME || + c == OPT_TEST_POSITION || + c == OPT_TEST_COEF) { fprintf(stderr, "An option '--%s' is obsoleted and has no " "effect.\n",
On Mon, 02 Oct 2017 02:19:01 +0200, Takashi Sakamoto wrote:
Hi,
This 3rd RFC patchset updates my previous one: [alsa-devel] [RFCv2][PATCH 00/38] alsa-utils: axfer: rewrite aplay http://mailman.alsa-project.org/pipermail/alsa-devel/2017-September/125574.h...
For aim of this rewrite and its intension, please refer to my former posts.
In this version, I add an option to support 'timer-based scheduling' scenario, which PulseAudio developers introduced. Please refer to patch 32:
- axfer: add support for timer-based MMAP operation
You can use this mode by adding '-sched-type=timer' option into command line.
The other difference from my previous one:
- fix some bugs to handle non-interleaved buffer on mapped page frame.
- use snd_pcm_status() instead of snd_pcm_state() to execute hwsync.
- minor fixes.
In my plan, this is the last version including below patches. In next post, options added by below patches are categorized as obsoleted:
- axfer: add an option to support volume unit meter
- axfer: add a unit test for vumeter calculation
- axfer: add an option for formatted filename
- axfer: add an option to handle key events
- axfer: add a parser for channel map API
- axfer: add a feature to generate a file for process id
Additionally, for next post, I'll prepare for help messages and man for this program.
The patches look good, and I see no reason to stop merging as long as it's named differently from the existing aplay/arecord. We can add a configure option for creating axfr -> aplay symlink, but I guess it's not needed yet as of the current status.
thanks,
Takashi
participants (2)
-
Takashi Iwai
-
Takashi Sakamoto