From patchwork Tue Nov 13 06:41:13 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10680007 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 2A4773CF1 for ; Tue, 13 Nov 2018 07:47:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1C3692A437 for ; Tue, 13 Nov 2018 07:47:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 1118C2A44F; Tue, 13 Nov 2018 07:47:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 352922A440 for ; Tue, 13 Nov 2018 07:47:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 972C3267A9B; Tue, 13 Nov 2018 07:42:03 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 46558267A5C; Tue, 13 Nov 2018 07:42:00 +0100 (CET) Received: from mail-pf1-f193.google.com (mail-pf1-f193.google.com [209.85.210.193]) by alsa0.perex.cz (Postfix) with ESMTP id 98DF6267839 for ; Tue, 13 Nov 2018 07:41:56 +0100 (CET) Received: by mail-pf1-f193.google.com with SMTP id c72so861564pfc.6 for ; Mon, 12 Nov 2018 22:41:56 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=2CKRQjoWLbSuwolM2bUTxCOw5EFK9E7MAoINRYtAOg8=; b=ECIo5jXFBprawHgxPvfgLwFmhNn8XS2ypHoxJUPa5jSc4y6xBqf74bDy06p+yAx7lW gG2mu5UaQ1RiHYVdM0BQ9oUI/SzlCbswfhz0F5s7UZRRUwNaaKhZC25lsbMyfx8fNkcn GHy/o23pAdQS+Dawwwb8OlQnkCpHwH8nqhvSTb0DhnEeN5D6Cg8NzXluDWB4nSEmMS20 GuPVe23ClhdAYcJU9lqwiv0HoJl6MH4z38p2tKVRbFZau6gP0XKkoyNIxEOSNh9TzccW 2db/p5yWyzXCRPdlDjp2Mw6yF5estyzX5hZsNONIZAoJa01P7nTmPDkLqN9m1LKvNtYW q7ew== X-Gm-Message-State: AGRZ1gKfZ/tlqLlp6Of2rkQ59gyx4oSRRz0LoBbfrcYN1cD5oZZ6xt0W xfnjv8KAGhqHsWNY1Px4OvY= X-Google-Smtp-Source: AJdET5djVIdx7UNmrpIVQAfv2JFD+iJdO413TwXxOtgdw87PSOlFFiGfnc3Ut8jhLYcLbTAnYbsqxQ== X-Received: by 2002:a63:3d03:: with SMTP id k3mr3645650pga.191.1542091314805; Mon, 12 Nov 2018 22:41:54 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.41.52 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:41:54 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:13 +0900 Message-Id: <20181113064147.13577-1-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113062459.DD8F7267A5C@alsa0.perex.cz> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 01/35] axfer: add an entry point for this command X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 Actual code for each sub-command will be implemented in later commits. Signed-off-by: Takashi Sakamoto --- Makefile.am | 2 +- axfer/Makefile.am | 23 ++++++ axfer/main.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++ axfer/misc.h | 16 ++++ axfer/subcmd.h | 14 ++++ configure.ac | 2 +- 6 files changed, 248 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 3d24b87..a4e25ae 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..b48b9c5 --- /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..1e92941 --- /dev/null +++ b/axfer/main.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +// main.c - an entry point for this program. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 +#include +#include +#include + +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 aliases[] = { + [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(aliases); ++i) { + for (pos = argv[0] + strlen(argv[0]); pos != argv[0]; --pos) { + if (strstr(pos, aliases[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..7c8bfb3 --- /dev/null +++ b/axfer/misc.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// misc.h - a header file for miscellaneous tools. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 + +#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..470a415 --- /dev/null +++ b/axfer/subcmd.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// subcmd.h - a header for each sub-commands. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 + +#endif diff --git a/configure.ac b/configure.ac index c37b8a5..404fa16 100644 --- a/configure.ac +++ b/configure.ac @@ -430,4 +430,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) From patchwork Tue Nov 13 06:41:14 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10680003 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 85085109C for ; Tue, 13 Nov 2018 07:47:22 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 69B392A44F for ; Tue, 13 Nov 2018 07:47:22 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 5C5C22A473; Tue, 13 Nov 2018 07:47:22 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 5E5A82A44F for ; Tue, 13 Nov 2018 07:47:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 1C097267839; Tue, 13 Nov 2018 07:42:02 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 2B26F267A8E; Tue, 13 Nov 2018 07:42:00 +0100 (CET) Received: from mail-pl1-f194.google.com (mail-pl1-f194.google.com [209.85.214.194]) by alsa0.perex.cz (Postfix) with ESMTP id AF7492678F7 for ; Tue, 13 Nov 2018 07:41:57 +0100 (CET) Received: by mail-pl1-f194.google.com with SMTP id f12-v6so5528611plo.1 for ; Mon, 12 Nov 2018 22:41:57 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=FrU0RNNnUX7uLPnRrZs3nwKIWWbjofwZJ57b1IuVm7A=; b=thcozEJA/iryyT4uLm+r7CSFkfwS6tTBD3SVPNKXwVPbqTSJityhp1mna2fwE9V6Ry zn6meXM6otMZs0PAbGIP7JgwcOnlOFHSuzRonCDMBxfYUIoMrBYIjzSqB82YFDEaIIuj X2+jV7f2aMHFwlcMq1oZyeMfeOR1913lNcs1aSQCMir6gnGevErO2ftDH3zlesZ7pijB Vc9cbCmyELnE080PqZv+h9S+IipryoWhqaEY781hPXEnr4/tuwSZR9Sl26RUQS2o48Pu IZSu799waoItZf+NMXE3I0MoKEdhQ4YOU1Z3YSlEFDy9Y4QdRw2uR0ASTkH9DyZO6wTc eTzw== X-Gm-Message-State: AGRZ1gKzejDEpqyyvD/slFhcMHGu9o5XCT6UUOptK7BrmZHnNaH/wqDW edDcdFUdGkUVephSIcGQza1eXGCM X-Google-Smtp-Source: AJdET5d/uyu9OMNoH1zl7/KmxaS7MWwfAWQa6iHcXMeWjwzrI+7UMutzKZjR2449rN12dIRS4pxO9Q== X-Received: by 2002:a17:902:6b46:: with SMTP id g6-v6mr3824831plt.33.1542091316526; Mon, 12 Nov 2018 22:41:56 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.41.54 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:41:55 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:14 +0900 Message-Id: <20181113064147.13577-2-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 02/35] axfer: add a sub-command to print list of PCMs/devices X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 b48b9c5..4e37b92 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 1e92941..c9cf104 100644 --- a/axfer/main.c +++ b/axfer/main.c @@ -181,7 +181,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..f4fd18b --- /dev/null +++ b/axfer/subcmd-list.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// subcmd-list.c - operations for list sub command. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "subcmd.h" +#include "misc.h" + +#include + +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 0; +} diff --git a/axfer/subcmd.h b/axfer/subcmd.h index 470a415..f78b766 100644 --- a/axfer/subcmd.h +++ b/axfer/subcmd.h @@ -11,4 +11,6 @@ #include +int subcmd_list(int argc, char *const *argv, snd_pcm_stream_t direction); + #endif From patchwork Tue Nov 13 06:41:15 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10680005 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C656618F0 for ; Tue, 13 Nov 2018 07:47:22 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B0A172A437 for ; Tue, 13 Nov 2018 07:47:22 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A52CA2A475; Tue, 13 Nov 2018 07:47:22 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2D3FD2A437 for ; Tue, 13 Nov 2018 07:47:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 70B17267AA3; Tue, 13 Nov 2018 07:42:07 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 1998A267A8E; Tue, 13 Nov 2018 07:42:02 +0100 (CET) Received: from mail-pg1-f195.google.com (mail-pg1-f195.google.com [209.85.215.195]) by alsa0.perex.cz (Postfix) with ESMTP id D0823267839 for ; Tue, 13 Nov 2018 07:41:59 +0100 (CET) Received: by mail-pg1-f195.google.com with SMTP id 70so5219743pgh.8 for ; Mon, 12 Nov 2018 22:41:59 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=6ZgRB2fSpfyLzq3m+WSexAUynPkWiHU/JqxPFMUTHuM=; b=ugmnZ+uo0nqbUw33YBZUuGReoVip/yX7hQ2D04CchVKJrFEaaUBN6+3f8rcdWbAH/7 yjz2DlMMO+kUcRE4KWiiSeeqCCRBWIUu76kIvMdlbwlmQENMlKogd22APWB9694ye/1X iCbtUYa6MdSAsV3SqtGIEPLkJ0ceVuDsOEf0R8M1lq417zooFiPu4u7UxzdOsEwt8vMV I5th4INVoe9ib3Hva6Y7OlEe4x/+8OR1pKpQo5QBWjbpBdcqlnaPD6hizCksvrKzwRS5 6Zrmx1WPaAFN/aj5vqzCNe8wjKwOq40n0+bjXZFLmEbjURDU1UrgUuxJoIXRuJVjLRVe 3Jvg== X-Gm-Message-State: AGRZ1gI+hn3jsBGQ8/+76dw0az8ie15ql6+OhQzLVSbKRI9gbJkIoe7B 5D86dNRLWtwmkWU6NYY1563NKUYO X-Google-Smtp-Source: AJdET5fVCmmO9f9RRHP5L1zYQh37xx4bB71ZID8oTV3ib2MvEAorqNOpYFqkUGS/nR+7X5MZcxsT6A== X-Received: by 2002:a63:5f95:: with SMTP id t143mr3682123pgb.395.1542091318364; Mon, 12 Nov 2018 22:41:58 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.41.56 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:41:57 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:15 +0900 Message-Id: <20181113064147.13577-3-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 03/35] axfer: add a common interface to handle a file with audio-specific data format X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 7 +- axfer/container.c | 423 ++++++++++++++++++++++++++++++++++++++++++++++ axfer/container.h | 110 ++++++++++++ 3 files changed, 538 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 4e37b92..3913d0c 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..77bbd6c --- /dev/null +++ b/axfer/container.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// container.c - an interface of parser/builder for formatted files. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "container.h" +#include "misc.h" + +#include +#include +#include +#include + +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..4a94d7f --- /dev/null +++ b/axfer/container.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// container.h - an interface of parser/builder for formatted files. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 +#include + +#include +#include + +#include + +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 From patchwork Tue Nov 13 06:41:16 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10680013 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 4656E3CF1 for ; Tue, 13 Nov 2018 07:47:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 361B32A446 for ; Tue, 13 Nov 2018 07:47:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 2A9A02A44F; Tue, 13 Nov 2018 07:47:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 84C302A473 for ; Tue, 13 Nov 2018 07:47:22 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id C7F2C267AC6; Tue, 13 Nov 2018 07:42:15 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 1D74E267ABC; Tue, 13 Nov 2018 07:42:11 +0100 (CET) Received: from mail-pg1-f194.google.com (mail-pg1-f194.google.com [209.85.215.194]) by alsa0.perex.cz (Postfix) with ESMTP id B9A25267AB0 for ; Tue, 13 Nov 2018 07:42:01 +0100 (CET) Received: by mail-pg1-f194.google.com with SMTP id w7so5211017pgp.13 for ; Mon, 12 Nov 2018 22:42:01 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=NrGUrMdlOaqnItlvX9jdvhTiq7XNcmZRakFyM9//4r8=; b=tlItBbSBUgtLfqcq32K9+kRKw7QQL2PJYA6/Fr6sWlxnnjex5qacGdkdD6gJFSSk5Q SW54dJSA6WBMi1CxGFUGEDGGTSX7o1fTXQBA/uHXOOmheLxwAYdiRNmBtxeI8Ql1rJPP RA37FakcZz+2cvKnXUvk7MEoCqKszcylvwnbhtUT2XUtnW9qlaXgyrPiRk/J51zx1bGT doDa1G3RWCDoSXRbbX5BeXFEp3Wvoi5KUhr5Dhiybbywl8z4zO1pG7IUQR1M98YflH52 FRAmXcKBWUWG1owMQZtdSMTDSrUr766uGL89k/LU8rN3uqejuTlQleECaCMB4keIHHcf R4cA== X-Gm-Message-State: AGRZ1gJ3fyiKaUmAhtniCap3rYL71mJDcfN4FLNsHvJDmru4Q+AdYIRZ abWjEt3DImldMvv/qaw0T9g= X-Google-Smtp-Source: AJdET5d2KG5ZQ5L4e8HEWHdVyBd0uarmqrAkLYQZ1dEyoWnrfawa3cJS95A5vZ0YTDvW9mvDO4fEMg== X-Received: by 2002:a62:4c6:: with SMTP id 189-v6mr4088269pfe.110.1542091320247; Mon, 12 Nov 2018 22:42:00 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.41.58 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:41:59 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:16 +0900 Message-Id: <20181113064147.13577-4-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 04/35] axfer: add support for a container of Microsoft/IBM RIFF/Wave format X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 3 +- axfer/container-riff-wave.c | 575 ++++++++++++++++++++++++++++++++++++ axfer/container.c | 9 +- axfer/container.h | 4 + 4 files changed, 586 insertions(+), 5 deletions(-) create mode 100644 axfer/container-riff-wave.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 3913d0c..092c966 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..db82493 --- /dev/null +++ b/axfer/container-riff-wave.c @@ -0,0 +1,575 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// container-riff-wave.c - a parser/builder for a container of RIFF/Wave File. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 + +// 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 77bbd6c..04042da 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]; @@ -140,7 +141,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; @@ -214,7 +215,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 4a94d7f..5808933 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 From patchwork Tue Nov 13 06:41:17 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10680023 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 8DE3F14E2 for ; Tue, 13 Nov 2018 07:52:22 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 7E48829B35 for ; Tue, 13 Nov 2018 07:52:22 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 728BE2A3EC; Tue, 13 Nov 2018 07:52:22 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 08F032A3E9 for ; Tue, 13 Nov 2018 07:52:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id C3E6E267AB2; Tue, 13 Nov 2018 07:42:08 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 7FF34267AAA; Tue, 13 Nov 2018 07:42:05 +0100 (CET) Received: from mail-pl1-f196.google.com (mail-pl1-f196.google.com [209.85.214.196]) by alsa0.perex.cz (Postfix) with ESMTP id 5472A267AA3 for ; Tue, 13 Nov 2018 07:42:02 +0100 (CET) Received: by mail-pl1-f196.google.com with SMTP id w24-v6so5525948plq.3 for ; Mon, 12 Nov 2018 22:42:02 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=q8AywJG2HOq715t1tDpR4fHcROfFC4UrqUlgu3NxOCI=; b=M5/NW/5wv4KYCblEiCkViQ1hxL6b2GXxaoPzOxWwYU2rFewGEsnr5oolsoNNtijSvh uV6eQ0uRbu9bli4u4Hl0tF9rD/esYkYucTyXvrVWaaWJxa0t41Vk9AScSezMLvjvQb6K pNq0wmhsUqvG1sztg8611QeNzXctMHigoxkuAzqNutlCTPmkATj/m7kZPi82NWe8V1m6 m7y784D0Up+cpMiH53hM9tqOZSck4s5XQ1zj2Uw81WdirtWv9Dlbr6MUYNa4a64poW7s U4jKiNfXKufyQPHz/gPGjGsyiaUFMJk+fxu/KMkbVhHh9Lj0LQqI77TcRn0zqWUvghWD QNGw== X-Gm-Message-State: AGRZ1gK7nux0ffZt6K7RK38RSHEZy7v/YLYMdYh4VxCAK/O5ARkUQovS vAN8QxN7r3jj2uSzOg2gnJY= X-Google-Smtp-Source: AJdET5flfCesdehLbu15ByLN6FeWJegiGoUX/l5C5Lo0eWHXisckUiKV4QEQObEJ9cWaZCCGKCzdhw== X-Received: by 2002:a17:902:6a3:: with SMTP id 32-v6mr3946810plh.337.1542091321975; Mon, 12 Nov 2018 22:42:01 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.00 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:01 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:17 +0900 Message-Id: <20181113064147.13577-5-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 05/35] axfer: add support for a container of Sparc AU format X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 3 +- axfer/container-au.c | 203 +++++++++++++++++++++++++++++++++++++++++++ axfer/container.c | 6 +- axfer/container.h | 4 + 4 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 axfer/container-au.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 092c966..35aa226 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..6459b16 --- /dev/null +++ b/axfer/container-au.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// container-au.c - a parser/builder for a container of Sun Audio File. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 + +// 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 04042da..0e25605 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", }; @@ -142,6 +144,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; @@ -216,6 +219,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 5808933..19ef672 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 From patchwork Tue Nov 13 06:41:18 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10680011 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id CA64014E2 for ; Tue, 13 Nov 2018 07:47:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B60F72A437 for ; Tue, 13 Nov 2018 07:47:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id AA8752A44F; Tue, 13 Nov 2018 07:47:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 5DE342A446 for ; Tue, 13 Nov 2018 07:47:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 20521267ABA; Tue, 13 Nov 2018 07:42:12 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 864B0267ABA; Tue, 13 Nov 2018 07:42:09 +0100 (CET) Received: from mail-pf1-f196.google.com (mail-pf1-f196.google.com [209.85.210.196]) by alsa0.perex.cz (Postfix) with ESMTP id 4DA82267AAC for ; Tue, 13 Nov 2018 07:42:05 +0100 (CET) Received: by mail-pf1-f196.google.com with SMTP id x2-v6so5538433pfm.7 for ; Mon, 12 Nov 2018 22:42:04 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=YsRAip0QxL9J1eWRyEFK82NlGrCyGHcxnhM3lNwos9U=; b=Ln7RZtrja5Ngkt1zKXG2f9r52n+AjYXVoVJqC2/rlB34BfQmZHvXUJF0apxUnEsjCb x4ofaxXnhoW33s9godRyRruhg6NfMUSNX9eSfK/fCiNDMpHhNSYYeLRfvF+7K9ewqMr9 u7aw78PR6Wtne9I36vUf1EYMZZgwjGdyzYoe7xqNZIi2lvKkqdCjzegrYgi0NLHVi5G5 Um9iREjZDe8r1zuIYvD6D2L/jPzIAqETEvP8VoxJ6jNYC6nezn9FVmL/e6K6AGzm7PGp Kigmvdz1aD0XHPLQhUtaURZl57eSpAuDQY4o7np/VRSBY8noarhHex+G+fURcFWAoME6 tQsQ== X-Gm-Message-State: AGRZ1gL985TfrNho/a/e5KC9tRGOVUrPEuwor9J/s5Y+W3HaGyCw+AWj JhZIUmhdWyQhaUIadUwfFbnzbRF+ X-Google-Smtp-Source: AJdET5ebU0sB+gqMcGIZlOopxJ6DJzNr8AcLZ0apeixXU+4xZHySQyeSYCE96B0O+2pJ8ki6aO1tLA== X-Received: by 2002:a63:fc22:: with SMTP id j34-v6mr3697408pgi.434.1542091323753; Mon, 12 Nov 2018 22:42:03 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.02 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:03 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:18 +0900 Message-Id: <20181113064147.13577-6-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 06/35] axfer: add support for a container of Creative Tech. voice format X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 3 +- axfer/container-voc.c | 833 ++++++++++++++++++++++++++++++++++++++++++ axfer/container.c | 4 + axfer/container.h | 4 + 4 files changed, 843 insertions(+), 1 deletion(-) create mode 100644 axfer/container-voc.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 35aa226..48d046e 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..92e9c83 --- /dev/null +++ b/axfer/container-voc.c @@ -0,0 +1,833 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// container-voc.c - a parser/builder for a container of Creative Voice File. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 + +// 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 0e25605..94afb2b 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", }; @@ -145,6 +147,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; @@ -220,6 +223,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 19ef672..71e188c 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 From patchwork Tue Nov 13 06:41:19 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679939 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id E1CFE13BB for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CEF0F29911 for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id C36CE2A46A; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9FDDC2A450 for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 028C7267AD8; Tue, 13 Nov 2018 07:42:23 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 87C73267AB1; Tue, 13 Nov 2018 07:42:14 +0100 (CET) Received: from mail-pg1-f194.google.com (mail-pg1-f194.google.com [209.85.215.194]) by alsa0.perex.cz (Postfix) with ESMTP id 7AFDD267AB0 for ; Tue, 13 Nov 2018 07:42:06 +0100 (CET) Received: by mail-pg1-f194.google.com with SMTP id r9-v6so5227208pgv.6 for ; Mon, 12 Nov 2018 22:42:06 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=VubIiKpzCxDcZkUnR9Dj6U+Z05sLL9mZ8kb5ltrLUgY=; b=AYFWtJ4BlGNpl0TIaj1KP8GjJbnxCF9FF6f3EjiOsEmppGjwPDH2v5DU/F3hEWjfxN iLGsvWcAk4mOfl6eKXoU5gZA2VnPLDKtypbPip1PgGDC9wFnpizMkiju/uB608W84Mfw JoqkdwVPABpwGBpzzO2Ju0rCH6m4by97j5hAMnuJTG/J2aXzQO43QGO+mx2JXhDYPpCg jHuhLbsrCmagwcc+R0GZLZzDYKsV0LjQlkQ9azGEYym/nODImp2d/KggP6wI9KcmoMxw ZZXKZZTLtXb9TDcX4Jdcv/P0I5Tdor1nSNN98itOKUImreqBltAjRIy2i2YMKHUYMyEr mZFg== X-Gm-Message-State: AGRZ1gL1xcBQP0uxCq0IlK9vYwnGbu1HswSv/F0poL22Zs584/Khf6fU 4XVwIIpbtGhf270WBmiOG80= X-Google-Smtp-Source: AJdET5ecQm6na1P4iJSrKrzllmXZjYqMHdPhItiDkY8LKpoyEAQGko7M6DtzqUAl8RNnyXwN/9VVrg== X-Received: by 2002:a63:e001:: with SMTP id e1mr3663101pgh.39.1542091325460; Mon, 12 Nov 2018 22:42:05 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.03 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:04 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:19 +0900 Message-Id: <20181113064147.13577-7-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 07/35] axfer: add support for a container of raw data X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 3 +- axfer/container-raw.c | 66 +++++++++++++++++++++++++++++++++++++++++++ axfer/container.c | 37 ++++++++++++++++++++---- axfer/container.h | 4 +++ 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 axfer/container-raw.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 48d046e..f1ec1d1 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..9b0022e --- /dev/null +++ b/axfer/container-raw.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// container-raw.c - a parser/builder for a container with raw data frame. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "container.h" +#include "misc.h" + +#include +#include +#include + +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 94afb2b..690fe5b 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]; @@ -108,7 +109,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) @@ -196,7 +197,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) { @@ -224,6 +225,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; @@ -302,6 +304,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); @@ -348,6 +360,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); @@ -356,7 +369,19 @@ 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) @@ -370,11 +395,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 71e188c..2c0c4cf 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 From patchwork Tue Nov 13 06:41:20 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679943 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 40CE41747 for ; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2C77B29647 for ; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 20D1129911; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9DA6E29B29 for ; Tue, 13 Nov 2018 07:37:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 5F682267AD2; Tue, 13 Nov 2018 07:42:24 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 11729267AC8; Tue, 13 Nov 2018 07:42:15 +0100 (CET) Received: from mail-pf1-f194.google.com (mail-pf1-f194.google.com [209.85.210.194]) by alsa0.perex.cz (Postfix) with ESMTP id 84A9B267AAC for ; Tue, 13 Nov 2018 07:42:08 +0100 (CET) Received: by mail-pf1-f194.google.com with SMTP id x2-v6so5538500pfm.7 for ; Mon, 12 Nov 2018 22:42:08 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=FN1iwRIHElr21i3iprhI42Lgxt6dZ+At9nDyc/Kqnnk=; b=E5OADKZtxckZJupvZNi5mGk6hoOgG++CDdIMOf6Y+42PXZXrzfRv1IARO/3nXoi7pd XZPJYoB9fyvn1xpAkf9txR+J7orCn0BqOKl7fXh2TJiOnIUfkKccziGRTPyI6dsw2rnR AN4g2RW3GZKVemWG5YOtV9K516RsUtM/8yakCKR3hjEJDbf0gE3g905DAlvxME3fPtQC 10BcA77GLVQ4QngNm+YQ6zltDM/+akCVnIHQZp/hGUwxI3Ce+IYoJPULlvXD32OalpXw sq/866dzeovK0hji4W7LoicS/5dmZ+N8PxcvxOpKDQr0dFv3bf8o4Rz2ZwLrXvv4Syiw LZiw== X-Gm-Message-State: AGRZ1gL99RBKXJ0exCYhS8LeKOtHZFkxhcvacj9/WR5w8h8aUDmUx8Bm Um14DxVieXVVAmB5q3qUB2I= X-Google-Smtp-Source: AJdET5fUHmMuFHM6/UO1v57oGnIB9vsSeOBuyhGbv44qeDSE28VlSdt29P5EgtaeV3xfnmZ52q2fKA== X-Received: by 2002:a62:cd87:: with SMTP id o129mr344104pfg.22.1542091327248; Mon, 12 Nov 2018 22:42:07 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.05 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:06 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:20 +0900 Message-Id: <20181113064147.13577-8-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 08/35] axfer: add unit test for container interface X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 f1ec1d1..55fcf71 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..66b30ef --- /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..0e2e6e9 --- /dev/null +++ b/axfer/test/container-test.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// container-io.c - a unit test for parser/builder of supported containers. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "../container.h" +#include "../misc.h" + +#include "generator.h" + +#include +#include +#include + +#include + +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..cdea2c9 --- /dev/null +++ b/axfer/test/generator.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// allocator.h - a header of a generator for test with buffers of PCM frames. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "generator.h" + +#include + +#include +#include +#include + +#include + +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..c35ed98 --- /dev/null +++ b/axfer/test/generator.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// generator.c - a generator for test with buffers of PCM frames. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 +#include + +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 404fa16..1c64617 100644 --- a/configure.ac +++ b/configure.ac @@ -430,4 +430,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) From patchwork Tue Nov 13 06:41:21 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10680015 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DD16E109C for ; Tue, 13 Nov 2018 07:47:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CD54F2A440 for ; Tue, 13 Nov 2018 07:47:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id C1C7F2A446; Tue, 13 Nov 2018 07:47:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 844652A456 for ; Tue, 13 Nov 2018 07:47:22 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 46FF4267AC3; Tue, 13 Nov 2018 07:42:17 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 14BA4267AB1; Tue, 13 Nov 2018 07:42:14 +0100 (CET) Received: from mail-pf1-f194.google.com (mail-pf1-f194.google.com [209.85.210.194]) by alsa0.perex.cz (Postfix) with ESMTP id 111F1267AB9 for ; Tue, 13 Nov 2018 07:42:10 +0100 (CET) Received: by mail-pf1-f194.google.com with SMTP id v9-v6so5547623pff.2 for ; Mon, 12 Nov 2018 22:42:09 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=LsmXACD64U3sbI3JSkc+PdI4xeO9i8Gknxd6qquF6kA=; b=Kf1NXly0JdNqV5ELwarRC9oNRiYhT9FgSP7abQFCpdzKboRsHarBGAVLdPUyiDnVZW qzZ8an8bWR6MJzJd/Wrcg7nqx5/q7KBKdobrq7SOMp8bqQHt5IDyOStaOfF5EFFJt8SC MuqgfHTjcK4Dp0IrfoXMcErTEuU8ZTzb3PeS6cDaBknWPIqFvB6Jpksn34WLAqpoQRvA CVb3dS4C1jO53ZBHJoYtofcc+5f0CMiTM+Qy4y/j/d406D6GDBXGG4wAyM84UNrZUqmh F7xel3I8/OUNwvsuuV0dUY+IO74/yfWk0GiwwJXVtWwdxTsyjTNLugo7z32wM41kVZcr l31w== X-Gm-Message-State: AGRZ1gKnUbFG+gTZFZjEqHfwTgKnSNcOxCXl8tOlHg748IzlRs6Vmk2T g7ujdK3d+GFCxQ5Mlz7F3RI= X-Google-Smtp-Source: AJdET5drpowbi6ndgJsEtBsoTyWsO/u8eaG4806pKWBGHzh3K5BZrdF6GmTz2hsN7IbL5M0Kn2izRA== X-Received: by 2002:a62:6fc7:: with SMTP id k190-v6mr3954674pfc.97.1542091329082; Mon, 12 Nov 2018 22:42:09 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.07 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:08 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:21 +0900 Message-Id: <20181113064147.13577-9-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 09/35] axfer: add a common interface to align data frames on different layout X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 55fcf71..cb4b188 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..eb63a0e --- /dev/null +++ b/axfer/mapper.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mapper.c - an interface of muxer/demuxer between buffer with data frames and +// formatted files. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "mapper.h" +#include "misc.h" + +#include + +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..c4caf09 --- /dev/null +++ b/axfer/mapper.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mapper.h - an interface of muxer/demuxer between buffer with data frames and +// formatted files. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 From patchwork Tue Nov 13 06:41:22 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679933 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 45E6E46E4 for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 346BD2A46A for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 28E222A496; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D49802A46A for ; Tue, 13 Nov 2018 07:37:22 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 10C9F267AA8; Tue, 13 Nov 2018 07:42:28 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 4D078267ACD; Tue, 13 Nov 2018 07:42:16 +0100 (CET) Received: from mail-pl1-f193.google.com (mail-pl1-f193.google.com [209.85.214.193]) by alsa0.perex.cz (Postfix) with ESMTP id 05120267ABF for ; Tue, 13 Nov 2018 07:42:11 +0100 (CET) Received: by mail-pl1-f193.google.com with SMTP id p6-v6so5520420pll.4 for ; Mon, 12 Nov 2018 22:42:11 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=W7QpYndo2FviCTy6fHd5LtHS5DH4TssppIwEak5Oy5c=; b=nqJEpX+0TYvPpCVDr7ON3XyWhKrFYHIy//Y8PyvwbiyNbb6A06oN/4Az5CDSj3lZou fUxVFrYYp41H/I8cjZuX3ykyWrnDvDFDpAynKCO7hTQfvD70s+c8j9sZKnf0A86rPEE3 jtu3+EPLIgxdrGvsLCFuJ3K+acG07EHAIGI+MlYufhkr9eVo4t5VZA2hbUQIHipTbAqO J8wQG5DVciTnfNYNiXhJWRdJdCWyaLzdmGcq39dbgEsfQbtIlNeWRAjj6eCV9MPDUek5 vtRFjcMt1oFLI5UAcAA+3/1yPa05lgMBRb8SYIoL5wHAvR0idHTcGgbaov8ZUdY1Gq/f ExQQ== X-Gm-Message-State: AGRZ1gIISuuMfT3lhcgTh+LmNfTKcgM+tvx0QePBJ872DFuAQmScr2KE ZiRdUmTmmvQS16dgjVjYYf4= X-Google-Smtp-Source: AJdET5dlIyCbWZ4CklSWuiNDcGOk8BoMhWxJD9IEbZEAVbGUyv5KxVBScaPu9y5+PBEKp+3ZIpx8tQ== X-Received: by 2002:a17:902:bc83:: with SMTP id bb3-v6mr3775852plb.223.1542091330820; Mon, 12 Nov 2018 22:42:10 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.09 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:10 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:22 +0900 Message-Id: <20181113064147.13577-10-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 10/35] axfer: add support for a mapper for single target X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 3 +- axfer/mapper-single.c | 191 ++++++++++++++++++++++++++++++++++++++++++ axfer/mapper.c | 14 +++- axfer/mapper.h | 4 + 4 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 axfer/mapper-single.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index cb4b188..baf9b89 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..aa8aa19 --- /dev/null +++ b/axfer/mapper-single.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mapper-single.c - a muxer/demuxer for single containers. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 eb63a0e..07ca595 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 c4caf09..3a95d9f 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 From patchwork Tue Nov 13 06:41:23 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679953 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 120EF13BB for ; Tue, 13 Nov 2018 07:37:28 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F0BF32945E for ; Tue, 13 Nov 2018 07:37:27 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E4D41298FE; Tue, 13 Nov 2018 07:37:27 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 18EA12A479 for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id E3D64267B49; Tue, 13 Nov 2018 07:42:29 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 38713267AD2; Tue, 13 Nov 2018 07:42:18 +0100 (CET) Received: from mail-pl1-f194.google.com (mail-pl1-f194.google.com [209.85.214.194]) by alsa0.perex.cz (Postfix) with ESMTP id A4FD6267AB1 for ; Tue, 13 Nov 2018 07:42:13 +0100 (CET) Received: by mail-pl1-f194.google.com with SMTP id n4-v6so5522175plp.2 for ; Mon, 12 Nov 2018 22:42:13 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=lMBgoPqvfcWaLX53q6gIJ44G5BNdrtOVmx8UwlvHzqo=; b=RL9btI6mULivl7dZDHs3R9OA6iWso6/Q9BCwjguLRQnDms8PrgqAPwYjy4ZiPo8ZHY ciFknH4yOvqgzu66E2gKoY46wslSZ25My7N4hKl+Mj88UVr+1iEIpQ4zIV4jEPVUDXaR tL10NPPqv8Y35p9La4Jc7i99GvcLyklz4Y1tlETuARKPlpV8VY4NtFgpiIpSieUgitCM xWvtIgxv+7wBAmQF5W0udKRdVkssYrgU1jlkFN0W8cA1BeAhxUUy/u5Axvm6z2NaKbfy 8s941WYlYTTHSUqGKvgWn7qg//Nc7mhSpjjPy9gE9c1bGRifRjqRa63uMsYXkp4i53ka Bv6Q== X-Gm-Message-State: AGRZ1gKT/jIpi1dlZMkDPxEi2lCk9DCuz/+YqhntYsQ2kJEHV5nef5Lc Ogd5ORU7kUOcugbAf1wnCFU= X-Google-Smtp-Source: AJdET5cJw4bGJ2JrUyHm1F0iEPrgtdoKexFMkhq1bHQuVFrb2mrRWlYSgd7P5bT4BL5HNiGvUx+ENg== X-Received: by 2002:a17:902:7402:: with SMTP id g2mr3802257pll.198.1542091332543; Mon, 12 Nov 2018 22:42:12 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.10 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:11 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:23 +0900 Message-Id: <20181113064147.13577-11-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 11/35] axfer: add support for a mapper for multiple target X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 3 +- axfer/mapper-multiple.c | 259 ++++++++++++++++++++++++++++++++++++++++ axfer/mapper.c | 13 ++ axfer/mapper.h | 4 + 4 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 axfer/mapper-multiple.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index baf9b89..f17e59b 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..a0978b9 --- /dev/null +++ b/axfer/mapper-multiple.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mapper-multiple.c - a muxer/demuxer for multiple containers. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 07ca595..4c3f0e3 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,12 @@ 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 3a95d9f..58b6118 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 From patchwork Tue Nov 13 06:41:24 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679927 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id BA7561747 for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A533B2A49A for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 981CE2A496; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9D8D929911 for ; Tue, 13 Nov 2018 07:37:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 5E3FE267AB4; Tue, 13 Nov 2018 07:42:31 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 1A591267AAC; Tue, 13 Nov 2018 07:42:20 +0100 (CET) Received: from mail-pf1-f196.google.com (mail-pf1-f196.google.com [209.85.210.196]) by alsa0.perex.cz (Postfix) with ESMTP id 79BC6267ABF for ; Tue, 13 Nov 2018 07:42:15 +0100 (CET) Received: by mail-pf1-f196.google.com with SMTP id c72so861923pfc.6 for ; Mon, 12 Nov 2018 22:42:15 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=m7dQQvZgt6oXl/WvocnmVrr0ojY/aMImmIimavCmoEQ=; b=NhkDlyaYsT4f2bRYRVOfPjPSQ4ZpgedrxgzS1mcfxptGY1yEkdEG+WILA4n+AiMuzL 921HGVFwazrVppSvjh1g02XubkLLNEm+YVrzhEYePy0Jy+u0PsMu7R+myM3zHcxUcIEL LYT/TmXQLjUkpiLtzY6NNuMrXzkJ2YEiX/0Im6Qo3/WeIKAdScAsKeVrh35tePKGMqvJ Eu5NbnvmDuoO9/WTqg45nQTeSSPkpRqynOWyhNYPIgMrdbAxiY1EeojqiUacloXuiNn1 LEjVLUryTUW1tP+hFqqYZs232AR/yK/anx/zQFRc8Llt6vleHlzumN1MuhRTUsHGWaXD 8mxw== X-Gm-Message-State: AGRZ1gJp2JV+WwDjgCceL56aZtOKNnJi+dWSstpxJ+RnVquNd7dhxpEY LQ+IszK0wNTiXp7wRl7Rnq4= X-Google-Smtp-Source: AJdET5dbcDoZhc/pGa6AmXZO4qpAr5SYk6Wzuvm6S0DDga+xNXDxXkh6XT8ofZxHn/S1gfraqp1tZQ== X-Received: by 2002:a62:9302:: with SMTP id b2-v6mr3935554pfe.108.1542091334329; Mon, 12 Nov 2018 22:42:14 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.12 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:13 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:24 +0900 Message-Id: <20181113064147.13577-12-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 12/35] axfer: add a unit test for mapper interface X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 66b30ef..76a93bf 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..9f005aa --- /dev/null +++ b/axfer/test/mapper-test.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mapper-io.c - a unit test for muxer/demuxer for PCM frames on buffer. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "../mapper.h" +#include "../misc.h" + +#include "generator.h" + +#include +#include +#include + +#include + +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; +} From patchwork Tue Nov 13 06:41:25 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679955 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id CFE3C1747 for ; Tue, 13 Nov 2018 07:37:28 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BC3C02945E for ; Tue, 13 Nov 2018 07:37:28 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B06E729647; Tue, 13 Nov 2018 07:37:28 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 19C5F2A487 for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id CE534267B53; Tue, 13 Nov 2018 07:42:32 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id C1C15267AD6; Tue, 13 Nov 2018 07:42:20 +0100 (CET) Received: from mail-pl1-f195.google.com (mail-pl1-f195.google.com [209.85.214.195]) by alsa0.perex.cz (Postfix) with ESMTP id 52A8D267AB1 for ; Tue, 13 Nov 2018 07:42:17 +0100 (CET) Received: by mail-pl1-f195.google.com with SMTP id p16-v6so5505658plr.8 for ; Mon, 12 Nov 2018 22:42:16 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=rEhkEfReVUcATQYVamBYoTwun2NWqu1b0mWIBxZjgqQ=; b=aP4uPr2DhdehHZLWMczbY8+hBkCMUPFa9MIhoDzEGpritN8EQalLq5HRKdQw81dGKn DT0KCmuW8vVaCZv4k7nwM6hJoTV0JyEIJjaaGyyyLipOEa0e5eC1JozT62YuL+cHHLLp bBKL3CED+fUWuqH+0G+RUWJQf4JaIfXPO0oNZhG7YPY5YdGXEaKWqlgXWKtoCHfABjfi k63qupHLa5QvmRPYkt4rWTPuCMFOfuoAxIExTQfMlp6mCZfMjsel6ZwqWRZSa2u2NLs3 vkuVlCIBqBFGFgy96HfnHg4RMUI/ubx+3OslaGELGHQlgWLb/JUDeuhn1DsHXbS46q0t /Arw== X-Gm-Message-State: AGRZ1gK1NZ0ofJKxhLbpo9iG8Vn9iB1daSEW72SQ2fucHxePlVKj1iL6 RBet/ZhBUBVNX8RrpTGPuah/a3TH X-Google-Smtp-Source: AJdET5fgyOiPM3dQNLB4Ras1dgk6MaSCqaSl9XswX39DlIs//e7UTawQMRpAC/zvoQDahYVQkeudVg== X-Received: by 2002:a17:902:6a4:: with SMTP id 33-v6mr3950222plh.268.1542091336075; Mon, 12 Nov 2018 22:42:16 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.14 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:15 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:25 +0900 Message-Id: <20181113064147.13577-13-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 13/35] axfer: add a common interface to transfer data frames X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 7 +- axfer/xfer.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++ axfer/xfer.h | 80 ++++++++++++++++++++++ 3 files changed, 255 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 f17e59b..9371365 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -18,7 +18,8 @@ noinst_HEADERS = \ misc.h \ subcmd.h \ container.h \ - mapper.h + mapper.h \ + xfer.h axfer_SOURCES = \ misc.h \ @@ -34,4 +35,6 @@ axfer_SOURCES = \ mapper.h \ mapper.c \ mapper-single.c \ - mapper-multiple.c + mapper-multiple.c \ + xfer.h \ + xfer.c diff --git a/axfer/xfer.c b/axfer/xfer.c new file mode 100644 index 00000000..7c41a65 --- /dev/null +++ b/axfer/xfer.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer.c - receiver/transmiter of data frames. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "xfer.h" +#include "misc.h" + +#include + +static const char *const xfer_type_labels[] = { + [XFER_TYPE_COUNT] = "", +}; + +enum xfer_type xfer_type_from_label(const char *label) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(xfer_type_labels); ++i) { + if (!strcmp(xfer_type_labels[i], label)) + return i; + } + + return XFER_TYPE_UNSUPPORTED; +} + +int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, + snd_pcm_stream_t direction, int argc, char *const *argv) +{ + struct { + enum xfer_type type; + const struct xfer_data *data; + } *entry, entries[] = { + {XFER_TYPE_COUNT, NULL}, + }; + int i; + int err; + + assert(xfer); + assert(direction >= SND_PCM_STREAM_PLAYBACK); + assert(direction <= SND_PCM_STREAM_CAPTURE); + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + if (entries[i].type == type) + break; + } + if (i == ARRAY_SIZE(entries)) + return -EINVAL; + entry = &entries[i]; + + xfer->direction = direction; + xfer->type = type; + xfer->ops = &entry->data->ops; + + xfer->private_data = malloc(entry->data->private_size); + if (xfer->private_data == NULL) + return -ENOMEM; + memset(xfer->private_data, 0, entry->data->private_size); + + err = xfer->ops->init(xfer, direction); + if (err < 0) + return err; + + return xfer->ops->validate_opts(xfer); +} + +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..3faaec0 --- /dev/null +++ b/axfer/xfer.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer.h - a header for receiver/transmiter of data frames. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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 + +enum xfer_type { + XFER_TYPE_UNSUPPORTED = -1, + XFER_TYPE_COUNT, +}; + +struct xfer_ops; + +struct xfer_context { + snd_pcm_stream_t direction; + enum xfer_type type; + const struct xfer_ops *ops; + void *private_data; + + unsigned int verbose; +}; + +enum xfer_type xfer_type_from_label(const char *label); + +int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, + snd_pcm_stream_t direction, int argc, char *const *argv); +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); + int (*parse_opt)(struct xfer_context *xfer, int key, const char *optarg); + int (*validate_opts)(struct xfer_context *xfer); + 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 { + const char *s_opts; + const struct option *l_opts; + unsigned int l_opts_count; + struct xfer_ops ops; + unsigned int private_size; +}; + +extern const struct xfer_data xfer_alsa; + +#endif From patchwork Tue Nov 13 06:41:26 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679949 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id B8C0B1747 for ; Tue, 13 Nov 2018 07:37:26 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A39872945E for ; Tue, 13 Nov 2018 07:37:26 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 9789429647; Tue, 13 Nov 2018 07:37:26 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E9502298FE for ; Tue, 13 Nov 2018 07:37:22 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id C0019267B48; Tue, 13 Nov 2018 07:42:43 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 4E1CD267B49; Tue, 13 Nov 2018 07:42:29 +0100 (CET) Received: from mail-pg1-f196.google.com (mail-pg1-f196.google.com [209.85.215.196]) by alsa0.perex.cz (Postfix) with ESMTP id 77CE9267AE0 for ; Tue, 13 Nov 2018 07:42:21 +0100 (CET) Received: by mail-pg1-f196.google.com with SMTP id y4so5212925pgc.12 for ; Mon, 12 Nov 2018 22:42:21 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=vlVJoEA9wV8nOboMueKlrv9qncsbn09rOKuNouBqtgg=; b=Mov634aABXJz0SvzoahPnWUQh64S7ZABtjxMv6xV2CHaS9Hol9R2f8seaE/x5LvRE/ goviw3atU/LI2wgdQqM+4LwfIEpTLfN8gknSmA9O+b4aepw6NZAsimHJDuyXnFwzgiZr glPgpR1R4faImw3XCIh8jxvHr/+fqLaZ/2vLVXHltkUE4j6byWkgkQ97URLoKvQP7TqP kftnrd1qWGiX2bal7JINUhs1b/aF4oc1sIjwCe8GPlxgANfuISq8iXL68gfDknnCFQJz sGqiQ1TQTGEsPAhRj8YhyKy9soJIR2MUIukFU1kUa96l+1FglULc4x0a/2nVWEgT8UvO 5L4g== X-Gm-Message-State: AGRZ1gI/2+6PMVfcJIqEeYI6jd9/wWxNR4/VI4FBV0iay+T2vZyZkoYP Rm/NQTW1/36DTvlZr58UVGE= X-Google-Smtp-Source: AJdET5eWpmKbwuuPQDIGv5eRKu+xiQraryXD6U7nxk/0nIUHkh7Na0bYqzn8W2JKD4ryhS4SkwL1ig== X-Received: by 2002:a63:b218:: with SMTP id x24mr3533841pge.223.1542091337989; Mon, 12 Nov 2018 22:42:17 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.16 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:17 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:26 +0900 Message-Id: <20181113064147.13577-14-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 14/35] axfer: add a parser for command-line options X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP In aplay, many command-line options are supported. Some of them have dependency or conflicts. Furthemore, some of them are just for runtime configuration of alsa-lib(libasound), and some options can be used by several xfer backends commonly; e.g. options for file name, sample format and sampling rate. This commit adds a parser for the common options below. * --help (-h) * Just output 'help' string (not written yet). * --verbose (-v) * For verbose output, including information about xfer, mapper and container. * --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. This option can conflict to above format option. * --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. This option can conflict to format option above. * --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 transmission of data frame, this option can be used with several file paths. 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 are 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. In a case of no suffix, 'name-0' and 'name-1' are generated. Signed-off-by: Takashi Sakamoto --- axfer/Makefile.am | 3 +- axfer/main.c | 36 +++ axfer/misc.h | 3 + axfer/xfer-options.c | 535 +++++++++++++++++++++++++++++++++++++++++++ axfer/xfer.c | 78 +++++++ axfer/xfer.h | 19 ++ 6 files changed, 673 insertions(+), 1 deletion(-) create mode 100644 axfer/xfer-options.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 9371365..5f0a6cf 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -37,4 +37,5 @@ axfer_SOURCES = \ mapper-single.c \ mapper-multiple.c \ xfer.h \ - xfer.c + xfer.c \ + xfer-options.c diff --git a/axfer/main.c b/axfer/main.c index c9cf104..f141439 100644 --- a/axfer/main.c +++ b/axfer/main.c @@ -24,6 +24,42 @@ enum subcmds { SUBCMD_VERSION, }; +char *arg_duplicate_string(const char *str, int *err) +{ + char *ptr; + + // For safe. + if (strlen(str) > 1024) { + *err = -EINVAL; + return NULL; + } + + ptr = strdup(str); + if (ptr == NULL) + *err = -ENOMEM; + + return ptr; +} + +long arg_parse_decimal_num(const char *str, int *err) +{ + long val; + char *endptr; + + errno = 0; + val = strtol(str, &endptr, 0); + if (errno > 0) { + *err = -errno; + return 0; + } + if (*endptr != '\0') { + *err = -EINVAL; + return 0; + } + + return val; +} + static void print_version(const char *const cmdname) { printf("%s: version %s\n", cmdname, SND_UTIL_VERSION_STR); diff --git a/axfer/misc.h b/axfer/misc.h index 7c8bfb3..5f27d28 100644 --- a/axfer/misc.h +++ b/axfer/misc.h @@ -13,4 +13,7 @@ #define ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) +char *arg_duplicate_string(const char *str, int *err); +long arg_parse_decimal_num(const char *str, int *err); + #endif diff --git a/axfer/xfer-options.c b/axfer/xfer-options.c new file mode 100644 index 00000000..fb71244 --- /dev/null +++ b/axfer/xfer-options.c @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer-options.c - a parser of commandline options for xfer. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "xfer.h" +#include "misc.h" + +#include +#include +#include + +enum no_short_opts { + // 128 or later belong to non us-ascii character set. + OPT_XFER_TYPE = 128, +}; + +static int allocate_paths(struct xfer_context *xfer, char *const *paths, + unsigned int count) +{ + bool stdio = false; + int i; + + if (count == 0) { + stdio = true; + count = 1; + } + + xfer->paths = calloc(count, sizeof(xfer->paths[0])); + if (xfer->paths == NULL) + return -ENOMEM; + xfer->path_count = count; + + if (stdio) { + xfer->paths[0] = strndup("-", PATH_MAX); + if (xfer->paths[0] == NULL) + return -ENOMEM; + } else { + for (i = 0; i < count; ++i) { + xfer->paths[i] = strndup(paths[i], PATH_MAX); + if (xfer->paths[i] == NULL) + return -ENOMEM; + } + } + + return 0; +} + +static int verify_cntr_format(struct xfer_context *xfer) +{ + 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(xfer->cntr_format_literal, entry->literal)) + continue; + + xfer->cntr_format = entry->cntr_format; + return 0; + } + + fprintf(stderr, "unrecognized file format '%s'\n", + xfer->cntr_format_literal); + + return -EINVAL; +} + +// This should be called after 'verify_cntr_format()'. +static int verify_sample_format(struct xfer_context *xfer) +{ + 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; + + xfer->sample_format = snd_pcm_format_value(xfer->sample_format_literal); + if (xfer->sample_format != SND_PCM_FORMAT_UNKNOWN) + return 0; + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = &entries[i]; + if (strcmp(entry->literal, xfer->sample_format_literal)) + continue; + + if (xfer->frames_per_second > 0 && + xfer->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 (xfer->samples_per_frame > 0 && + xfer->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; + } + + xfer->frames_per_second = entry->frames_per_second; + xfer->samples_per_frame = entry->samples_per_frame; + if (xfer->cntr_format == CONTAINER_FORMAT_AU) + xfer->sample_format = entry->be_format; + else + xfer->sample_format = entry->le_format; + + return 0; + } + + fprintf(stderr, "wrong extended format '%s'\n", + xfer->sample_format_literal); + + return -EINVAL; +} + +static int validate_options(struct xfer_context *xfer) +{ + unsigned int val; + int err = 0; + + if (xfer->cntr_format_literal == NULL) { + if (xfer->direction == SND_PCM_STREAM_CAPTURE) { + // To stdout. + if (xfer->path_count == 1 && + !strcmp(xfer->paths[0], "-")) { + xfer->cntr_format = CONTAINER_FORMAT_RAW; + } else { + // Use first path as a representative. + xfer->cntr_format = container_format_from_path( + xfer->paths[0]); + } + } + // For playback, perform auto-detection. + } else { + err = verify_cntr_format(xfer); + } + if (err < 0) + return err; + + if (xfer->multiple_cntrs) { + if (!strcmp(xfer->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 + // 'xfer_options_fixup_paths()'. + if (xfer->direction == SND_PCM_STREAM_PLAYBACK) { + // Require several paths for containers. + if (xfer->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 (xfer->path_count > 1) { + fprintf(stderr, + "When using several files, an option for " + "sepatated channels is used with.\n"); + return -EINVAL; + } + } + + xfer->sample_format = SND_PCM_FORMAT_UNKNOWN; + if (xfer->sample_format_literal) { + err = verify_sample_format(xfer); + if (err < 0) + return err; + } + + val = xfer->frames_per_second; + if (xfer->frames_per_second == 0) + xfer->frames_per_second = 8000; + if (xfer->frames_per_second < 1000) + xfer->frames_per_second *= 1000; + if (xfer->frames_per_second < 2000 || + xfer->frames_per_second > 192000) { + fprintf(stderr, "bad speed value '%i'\n", val); + return -EINVAL; + } + + if (xfer->samples_per_frame > 0) { + if (xfer->samples_per_frame < 1 || + xfer->samples_per_frame > 256) { + fprintf(stderr, "invalid channels argument '%u'\n", + xfer->samples_per_frame); + return -EINVAL; + } + } + + return err; +} + +int xfer_options_parse_args(struct xfer_context *xfer, + const struct xfer_data *data, int argc, + char *const *argv) +{ + static const char *short_opts = "CPhvf:c:r:t:I"; + static const struct option long_opts[] = { + // For generic purposes. + {"capture", 0, 0, 'C'}, + {"playback", 0, 0, 'P'}, + {"xfer-type", 1, 0, OPT_XFER_TYPE}, + {"help", 0, 0, 'h'}, + {"verbose", 0, 0, 'v'}, + // For transfer backend. + {"format", 1, 0, 'f'}, + {"channels", 1, 0, 'c'}, + {"rate", 1, 0, 'r'}, + // For containers. + {"file-type", 1, 0, 't'}, + // For mapper. + {"separate-channels", 0, 0, 'I'}, + }; + char *s_opts; + struct option *l_opts; + int l_index; + int key; + int err = 0; + + // Concatenate short options. + s_opts = malloc(strlen(data->s_opts) + strlen(short_opts) + 1); + if (s_opts == NULL) + return -ENOMEM; + strcpy(s_opts, data->s_opts); + strcpy(s_opts + strlen(s_opts), short_opts); + s_opts[strlen(data->s_opts) + strlen(short_opts)] = '\0'; + + // Concatenate long options, including a sentinel. + l_opts = calloc(ARRAY_SIZE(long_opts) * data->l_opts_count + 1, + sizeof(*l_opts)); + if (l_opts == NULL) { + free(s_opts); + return -ENOMEM; + } + memcpy(l_opts, long_opts, ARRAY_SIZE(long_opts) * sizeof(*l_opts)); + memcpy(&l_opts[ARRAY_SIZE(long_opts)], data->l_opts, + data->l_opts_count * sizeof(*l_opts)); + + // Parse options. + l_index = 0; + optarg = NULL; + optind = 1; + opterr = 1; // use error output. + optopt = 0; + while (1) { + key = getopt_long(argc, argv, s_opts, l_opts, &l_index); + if (key < 0) + break; + else if (key == 'C') + ; // already parsed. + else if (key == 'P') + ; // already parsed. + else if (key == OPT_XFER_TYPE) + ; // already parsed. + else if (key == 'h') + xfer->help = true; + else if (key == 'v') + ++xfer->verbose; + else if (key == 'f') + xfer->sample_format_literal = arg_duplicate_string(optarg, &err); + else if (key == 'c') + xfer->samples_per_frame = arg_parse_decimal_num(optarg, &err); + else if (key == 'r') + xfer->frames_per_second = arg_parse_decimal_num(optarg, &err); + else if (key == 't') + xfer->cntr_format_literal = arg_duplicate_string(optarg, &err); + else if (key == 'I') + xfer->multiple_cntrs = true; + else if (key == '?') + return -EINVAL; + else { + err = xfer->ops->parse_opt(xfer, key, optarg); + if (err < 0 && err != -ENXIO) + break; + } + } + + free(l_opts); + free(s_opts); + + err = allocate_paths(xfer, argv + optind, argc - optind); + if (err < 0) + return err; + + return validate_options(xfer); +} + +static const char *const allowed_duplication[] = { + "/dev/null", + "/dev/zero", + "/dev/full", + "/dev/random", + "/dev/urandom", +}; + +static int generate_path_with_suffix(struct xfer_context *xfer, + 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 (xfer->path_count > 1) + len += (unsigned int)log10(xfer->path_count) + 2; + + xfer->paths[index] = malloc(len); + if (xfer->paths[index] == NULL) + return -ENOMEM; + + if (xfer->path_count == 1) { + snprintf(xfer->paths[index], len, single_format, template, + suffix); + } else { + snprintf(xfer->paths[index], len, multiple_format, template, + index, suffix); + } + + return 0; +} + +static int generate_path_without_suffix(struct xfer_context *xfer, + 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 (xfer->path_count > 1) + len += (unsigned int)log10(xfer->path_count) + 2; + + xfer->paths[index] = malloc(len); + if (xfer->paths[index] == NULL) + return -ENOMEM; + + if (xfer->path_count == 1) { + snprintf(xfer->paths[index], len, single_format, template); + } else { + snprintf(xfer->paths[index], len, multiple_format, template, + index); + } + + return 0; +} + +static int generate_path(struct xfer_context *xfer, char *template, + unsigned int index, const char *suffix) +{ + int (*generator)(struct xfer_context *xfer, 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(xfer, template, index, suffix); +} + +static int create_paths(struct xfer_context *xfer, unsigned int path_count) +{ + char *template; + const char *suffix; + int i, j; + int err = 0; + + // Can cause memory leak. + assert(xfer->path_count == 1); + assert(xfer->paths); + assert(xfer->paths[0]); + assert(xfer->paths[0][0] != '\0'); + + // Release at first. + template = xfer->paths[0]; + free(xfer->paths); + xfer->paths = NULL; + + // Allocate again. + xfer->paths = calloc(path_count, sizeof(*xfer->paths)); + if (xfer->paths == NULL) { + err = -ENOMEM; + goto end; + } + xfer->path_count = path_count; + + suffix = container_suffix_from_format(xfer->cntr_format); + + for (i = 0; i < xfer->path_count; ++i) { + // Some file names are allowed to be duplicated. + for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) { + if (!strcmp(template, allowed_duplication[j])) + break; + } + if (j < ARRAY_SIZE(allowed_duplication)) + continue; + + err = generate_path(xfer, template, i, suffix); + if (err < 0) + break; + } +end: + free(template); + + return err; +} + +static int fixup_paths(struct xfer_context *xfer) +{ + const char *suffix; + char *template; + int i, j; + int err = 0; + + suffix = container_suffix_from_format(xfer->cntr_format); + + for (i = 0; i < xfer->path_count; ++i) { + // Some file names are allowed to be duplicated. + for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) { + if (!strcmp(xfer->paths[i], allowed_duplication[j])) + break; + } + if (j < ARRAY_SIZE(allowed_duplication)) + continue; + + template = xfer->paths[i]; + xfer->paths[i] = NULL; + err = generate_path(xfer, template, i, suffix); + free(template); + if (err < 0) + break; + } + + return err; +} + +int xfer_options_fixup_paths(struct xfer_context *xfer) +{ + int i, j; + int err; + + if (xfer->path_count == 1) { + // Nothing to do for sign of stdin/stdout. + if (!strcmp(xfer->paths[0], "-")) + return 0; + if (!xfer->multiple_cntrs) + err = fixup_paths(xfer); + else + err = create_paths(xfer, xfer->samples_per_frame); + } else { + if (!xfer->multiple_cntrs) + return -EINVAL; + if (xfer->path_count != xfer->samples_per_frame) + return -EINVAL; + else + err = fixup_paths(xfer); + } + if (err < 0) + return err; + + // Check duplication of the paths. + for (i = 0; i < xfer->path_count - 1; ++i) { + // Some file names are allowed to be duplicated. + for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) { + if (!strcmp(xfer->paths[i], allowed_duplication[j])) + break; + } + if (j < ARRAY_SIZE(allowed_duplication)) + continue; + + for (j = i + 1; j < xfer->path_count; ++j) { + if (!strcmp(xfer->paths[i], xfer->paths[j])) { + fprintf(stderr, + "Detect duplicated file names:\n"); + err = -EINVAL; + break; + } + } + if (j < xfer->path_count) + break; + } + + if (xfer->verbose > 1) + fprintf(stderr, "Handled file names:\n"); + if (err < 0 || xfer->verbose > 1) { + for (i = 0; i < xfer->path_count; ++i) + fprintf(stderr, " %d: %s\n", i, xfer->paths[i]); + } + + return err; +} diff --git a/axfer/xfer.c b/axfer/xfer.c index 7c41a65..594abca 100644 --- a/axfer/xfer.c +++ b/axfer/xfer.c @@ -64,11 +64,17 @@ int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, if (err < 0) return err; + err = xfer_options_parse_args(xfer, entry->data, argc, argv); + if (err < 0) + return err; + return xfer->ops->validate_opts(xfer); } void xfer_context_destroy(struct xfer_context *xfer) { + int i; + assert(xfer); if (!xfer->ops) @@ -78,6 +84,20 @@ void xfer_context_destroy(struct xfer_context *xfer) xfer->ops->destroy(xfer); if (xfer->private_data) free(xfer->private_data); + + if (xfer->paths) { + for (i = 0; i < xfer->path_count; ++i) + free(xfer->paths[i]); + free(xfer->paths); + } + + xfer->paths = NULL; + + free(xfer->sample_format_literal); + xfer->sample_format_literal = NULL; + + free(xfer->cntr_format_literal); + xfer->cntr_format_literal = NULL; } int xfer_context_pre_process(struct xfer_context *xfer, @@ -99,6 +119,54 @@ int xfer_context_pre_process(struct xfer_context *xfer, if (!xfer->ops) return -ENXIO; + if (xfer->direction == SND_PCM_STREAM_CAPTURE) { + // For capture direction, use values in options if given. + if (xfer->sample_format != SND_PCM_FORMAT_UNKNOWN) + *format = xfer->sample_format; + if (xfer->samples_per_frame > 0) + *samples_per_frame = xfer->samples_per_frame; + if (xfer->frames_per_second > 0) + *frames_per_second = xfer->frames_per_second; + } else if (xfer->direction == SND_PCM_STREAM_PLAYBACK) { + // For playback direction, check values in given options so that + // they don't mismatch to parameters from media container. + if (*format != xfer->sample_format) { + // Not initial value. + if (xfer->sample_format != SND_PCM_FORMAT_UNKNOWN) { + fprintf(stderr, + "Sample format mismatch: %s is given " + "but %s by files\n", + snd_pcm_format_name(xfer->sample_format), + snd_pcm_format_name(*format)); + return -EINVAL; + } + } + + if (*samples_per_frame != xfer->samples_per_frame) { + // Not initial value. + if (xfer->samples_per_frame > 0) { + fprintf(stderr, + "The number of channels mismatch: %u " + "is given but %u by files\n", + xfer->samples_per_frame, + *samples_per_frame); + return -EINVAL; + } + } + + if (*frames_per_second != xfer->frames_per_second) { + // Not initial value. + if (xfer->frames_per_second != 8000) { + fprintf(stderr, + "Sampling rate mismatch: %u is given " + "but %u by files\n", + xfer->frames_per_second, + *frames_per_second); + return -EINVAL; + } + } + } + err = xfer->ops->pre_process(xfer, format, samples_per_frame, frames_per_second, access, frames_per_buffer); @@ -113,6 +181,16 @@ int xfer_context_pre_process(struct xfer_context *xfer, assert(*access <= SND_PCM_ACCESS_LAST); assert(*frames_per_buffer > 0); + xfer->sample_format = *format; + xfer->samples_per_frame = *samples_per_frame; + xfer->frames_per_second = *frames_per_second; + + if (xfer->direction == SND_PCM_STREAM_CAPTURE) { + err = xfer_options_fixup_paths(xfer); + if (err < 0) + return err; + } + if (xfer->verbose > 1) { fprintf(stderr, "Transfer: %s\n", xfer_type_labels[xfer->type]); diff --git a/axfer/xfer.h b/axfer/xfer.h index 3faaec0..3eaa045 100644 --- a/axfer/xfer.h +++ b/axfer/xfer.h @@ -26,7 +26,20 @@ struct xfer_context { const struct xfer_ops *ops; void *private_data; + char *sample_format_literal; + char *cntr_format_literal; unsigned int verbose; + unsigned int frames_per_second; + unsigned int samples_per_frame; + bool help:1; + bool multiple_cntrs:1; // For mapper. + + snd_pcm_format_t sample_format; + + // For containers. + char **paths; + unsigned int path_count; + enum container_format cntr_format; }; enum xfer_type xfer_type_from_label(const char *label); @@ -47,6 +60,12 @@ int xfer_context_process_frames(struct xfer_context *xfer, void xfer_context_pause(struct xfer_context *xfer, bool enable); void xfer_context_post_process(struct xfer_context *xfer); +struct xfer_data; +int xfer_options_parse_args(struct xfer_context *xfer, + const struct xfer_data *data, int argc, + char *const *argv); +int xfer_options_fixup_paths(struct xfer_context *xfer); + // For internal use in 'xfer' module. struct xfer_ops { From patchwork Tue Nov 13 06:41:27 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679925 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 16F5813BB for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F25B12A479 for ; Tue, 13 Nov 2018 07:37:22 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E06E12A47A; Tue, 13 Nov 2018 07:37:22 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9CF21298FE for ; Tue, 13 Nov 2018 07:37:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 62840267B42; Tue, 13 Nov 2018 07:42:38 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 4C6DB267AF0; Tue, 13 Nov 2018 07:42:24 +0100 (CET) Received: from mail-pf1-f194.google.com (mail-pf1-f194.google.com [209.85.210.194]) by alsa0.perex.cz (Postfix) with ESMTP id 27E6E267AC2 for ; Tue, 13 Nov 2018 07:42:20 +0100 (CET) Received: by mail-pf1-f194.google.com with SMTP id c72so862036pfc.6 for ; Mon, 12 Nov 2018 22:42:20 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=wZL+d+NenSODs7Xl42SGGOoWncHnPbkPbuKd/WmWXPk=; b=bMrJa6CfLvJ3UGchXa4YX+5IIbdCGLsB+GJLAyXbQYhzTFJVrubezkqGTwWHKovdYl 2W2nsfu3UkZ0R4vqUUPgWNXh8i4n6TSblpslGOBbSbCDbuHfEe55uQeHn/KVPT0Qumv5 NvHs+G6a2RgsrBXEM2+Mg1xAmFmxg4h1T6cjdoXe7t3rTUmYvgYDVazF68dZznzjoFyl 6jQtGdwy5F2UuWGUSdOUT4hdu0wMXW/69rGCQE8UBHtAw2DgZDrERVqtfcKW3yw15q8n +U4aT2EcnSRK6SVb+QofZIyt9WA7Zs6ZdUOWc1k+LVrq+mhWgtJYE5hjXTiysu0CiNJc aLyg== X-Gm-Message-State: AGRZ1gJoBsZPIc/9cjTOt9x4jiiJiyNGdK4jhevuK22GE0q+rgtuVuLY NhICk3ecbcikbU2uMu2G5uo= X-Google-Smtp-Source: AJdET5dXhacdOnaR7ObePG2AXMpBnwG0k1g3OF5pxYgONNmd+Q8919noyj+XVrnc1GGbcMk5VlgZzg== X-Received: by 2002:a63:a84a:: with SMTP id i10mr3672884pgp.263.1542091339888; Mon, 12 Nov 2018 22:42:19 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.18 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:19 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:27 +0900 Message-Id: <20181113064147.13577-15-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 15/35] axfer: add support to transfer data frames by alsa-lib PCM APIs X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 with buffer in user space, or copying data frames on mapped page frames. To support both ways, this commit adds an operation structure as abstraction. Signed-off-by: Takashi Sakamoto --- axfer/Makefile.am | 7 +- axfer/xfer-libasound.c | 415 +++++++++++++++++++++++++++++++++++++++++ axfer/xfer-libasound.h | 46 +++++ axfer/xfer.c | 4 +- axfer/xfer.h | 3 +- 5 files changed, 470 insertions(+), 5 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 5f0a6cf..1ec4ab4 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -19,7 +19,8 @@ noinst_HEADERS = \ subcmd.h \ container.h \ mapper.h \ - xfer.h + xfer.h \ + xfer-libasound.h axfer_SOURCES = \ misc.h \ @@ -38,4 +39,6 @@ axfer_SOURCES = \ mapper-multiple.c \ xfer.h \ xfer.c \ - xfer-options.c + xfer-options.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..2bca465 --- /dev/null +++ b/axfer/xfer-libasound.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer-libasound.c - receive/transmit frames by alsa-lib. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "xfer-libasound.h" +#include "misc.h" + +#define S_OPTS "D:" +static const struct option l_opts[] = { + {"device", 1, 0, 'D'}, +}; + +static int xfer_libasound_init(struct xfer_context *xfer, + snd_pcm_stream_t direction) +{ + struct libasound_state *state = xfer->private_data; + int err; + + err = snd_output_stdio_attach(&state->log, stderr, 0); + if (err < 0) + return err; + + err = snd_pcm_hw_params_malloc(&state->hw_params); + if (err < 0) + return err; + + return snd_pcm_sw_params_malloc(&state->sw_params); +} + +static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, + const char *optarg) +{ + struct libasound_state *state = xfer->private_data; + int err = 0; + + if (key == 'D') + state->node_literal = arg_duplicate_string(optarg, &err); + else + err = -ENXIO; + + return err; +} + +int xfer_libasound_validate_opts(struct xfer_context *xfer) +{ + struct libasound_state *state = xfer->private_data; + int err = 0; + + state->verbose = xfer->verbose > 1; + + if (state->node_literal == NULL) { + state->node_literal = strdup("default"); + if (state->node_literal == NULL) + return -ENOMEM; + } + + return err; +} + +static int set_access_hw_param(struct libasound_state *state) +{ + 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(state->handle, state->hw_params, + mask); + snd_pcm_access_mask_free(mask); + + return err; +} + +static int open_handle(struct xfer_context *xfer) +{ + struct libasound_state *state = xfer->private_data; + int err; + + err = snd_pcm_open(&state->handle, state->node_literal, xfer->direction, + 0); + if (err < 0) { + logging(state, "Fail to open libasound PCM node for %s: %s\n", + snd_pcm_stream_name(xfer->direction), + state->node_literal); + 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); +} + +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; + + err = open_handle(xfer); + if (err < 0) + 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 == -EAGAIN) + return err; + 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); + free(state->private_data); + state->private_data = NULL; + + // Free cache of content for configuration files so that memory leaks + // are not detected. + snd_config_update_free_global(); +} + +static void xfer_libasound_destroy(struct xfer_context *xfer) +{ + struct libasound_state *state = xfer->private_data; + + free(state->node_literal); + state->node_literal = NULL; + + 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 = { + .s_opts = S_OPTS, + .l_opts = l_opts, + .l_opts_count = ARRAY_SIZE(l_opts), + .ops = { + .init = xfer_libasound_init, + .parse_opt = xfer_libasound_parse_opt, + .validate_opts = xfer_libasound_validate_opts, + .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..57fa867 --- /dev/null +++ b/axfer/xfer-libasound.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer-libasound.h - a header for receiver/transmitter of frames by alsa-lib. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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; + + const struct xfer_libasound_ops *ops; + void *private_data; + + bool verbose; + + char *node_literal; +}; + +// For internal use in 'libasound' module. + +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 594abca..21595eb 100644 --- a/axfer/xfer.c +++ b/axfer/xfer.c @@ -12,7 +12,7 @@ #include static const char *const xfer_type_labels[] = { - [XFER_TYPE_COUNT] = "", + [XFER_TYPE_LIBASOUND] = "libasound", }; enum xfer_type xfer_type_from_label(const char *label) @@ -34,7 +34,7 @@ int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, enum xfer_type type; const struct xfer_data *data; } *entry, entries[] = { - {XFER_TYPE_COUNT, NULL}, + {XFER_TYPE_LIBASOUND, &xfer_libasound}, }; int i; int err; diff --git a/axfer/xfer.h b/axfer/xfer.h index 3eaa045..df43d1c 100644 --- a/axfer/xfer.h +++ b/axfer/xfer.h @@ -15,6 +15,7 @@ enum xfer_type { XFER_TYPE_UNSUPPORTED = -1, + XFER_TYPE_LIBASOUND = 0, XFER_TYPE_COUNT, }; @@ -94,6 +95,6 @@ struct xfer_data { unsigned int private_size; }; -extern const struct xfer_data xfer_alsa; +extern const struct xfer_data xfer_libasound; #endif From patchwork Tue Nov 13 06:41:28 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679931 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 01E1E18F0 for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E2BF929647 for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D74E92A4AE; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B83552A261 for ; Tue, 13 Nov 2018 07:37:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id B16E1267B64; Tue, 13 Nov 2018 07:42:39 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 5FAFC267B49; Tue, 13 Nov 2018 07:42:28 +0100 (CET) Received: from mail-pl1-f195.google.com (mail-pl1-f195.google.com [209.85.214.195]) by alsa0.perex.cz (Postfix) with ESMTP id 9FEAF267AE5 for ; Tue, 13 Nov 2018 07:42:22 +0100 (CET) Received: by mail-pl1-f195.google.com with SMTP id b22-v6so64226pls.7 for ; Mon, 12 Nov 2018 22:42:22 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=PdkxFPxWg4+ljRRF0YYVgT9RvCqNtighUSdqqk7Eyqk=; b=ZDldNcKcNE+uzNMqJsIhFHLIpusD0KFEOE0Dd2iw9RigWbYWit3b7wAnf9LRjX/aYc JajzaXMZ/HLGy29rhisylYv8vz1sXKGTQRvm/IWxcUdd6rOVKQM8prvaEBCVN1vgOEmg kmoedEyzQw9L7BLH6BGiEIlV5XVl9MMaq+zVxsa9fBjbe/Sd1xKYxl+9PLUxlXpQ4atZ eQYC51zoy4JyUTlCnnicgObuzUQfPuEPusu3EZATmAQ5nwmzkDwoEBnRI0U1DYeJwVsv Jcx4THKEcxxsS+QXjmtSJE3EqyMEpiqxH5oX8ALee9ohDS4IzcgLwW28axzjoZ/WaQJL Ag7w== X-Gm-Message-State: AGRZ1gJPGjWANJuGWIOzQtkRFKaqppLFY/NrzRyvepXIGt76BxxSFqCX Gw3TpC7kAFjDe1oLJBCE6KQEThoj X-Google-Smtp-Source: AJdET5chjwJH7wccN77oiX1TER+seeiEYfErq/x//NOOXemrS/jAlnzkGAEH/wDwsYLm+q8Dy2X+dg== X-Received: by 2002:a17:902:404:: with SMTP id 4-v6mr3929900ple.331.1542091341651; Mon, 12 Nov 2018 22:42:21 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.20 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:21 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:28 +0900 Message-Id: <20181113064147.13577-16-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 16/35] axfer: add support for blocking data transmission operation of alsa-lib PCM API X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 8 +- axfer/frame-cache.c | 111 ++++++++++++ axfer/frame-cache.h | 47 ++++++ axfer/xfer-libasound-irq-rw.c | 307 ++++++++++++++++++++++++++++++++++ axfer/xfer-libasound.c | 6 + axfer/xfer-libasound.h | 2 + 6 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 axfer/frame-cache.c create mode 100644 axfer/frame-cache.h create mode 100644 axfer/xfer-libasound-irq-rw.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 1ec4ab4..07c1fb3 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -20,7 +20,8 @@ noinst_HEADERS = \ container.h \ mapper.h \ xfer.h \ - xfer-libasound.h + xfer-libasound.h \ + frame-cache.h axfer_SOURCES = \ misc.h \ @@ -41,4 +42,7 @@ axfer_SOURCES = \ xfer.c \ xfer-options.c \ xfer-libasound.h \ - xfer-libasound.c + xfer-libasound.c \ + frame-cache.h \ + frame-cache.c \ + xfer-libasound-irq-rw.c diff --git a/axfer/frame-cache.c b/axfer/frame-cache.c new file mode 100644 index 00000000..882568f --- /dev/null +++ b/axfer/frame-cache.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// frame-cache.c - maintainer of cache for data frame. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "frame-cache.h" + +static void align_frames_in_i(struct frame_cache *cache, + unsigned int consumed_count) +{ + char *buf = cache->buf; + unsigned int offset; + unsigned int size; + + cache->remained_count -= consumed_count; + + offset = cache->bytes_per_sample * cache->samples_per_frame * + consumed_count; + size = cache->bytes_per_sample * cache->samples_per_frame * + cache->remained_count; + memmove(buf, buf + offset, size); + + cache->buf_ptr = buf + size; +} + +static void align_frames_in_n(struct frame_cache *cache, + unsigned int consumed_count) +{ + char **bufs = cache->buf; + char **buf_ptrs = cache->buf_ptr; + unsigned int offset; + unsigned int size; + int i; + + cache->remained_count -= consumed_count; + + for (i = 0; i < cache->samples_per_frame; ++i) { + offset = cache->bytes_per_sample * consumed_count; + size = cache->bytes_per_sample * cache->remained_count; + memmove(bufs[i], bufs[i] + offset, size); + buf_ptrs[i] = bufs[i] + size; + } +} + +int frame_cache_init(struct frame_cache *cache, snd_pcm_access_t access, + unsigned int bytes_per_sample, + unsigned int samples_per_frame, + unsigned int frames_per_cache) +{ + if (access == SND_PCM_ACCESS_RW_INTERLEAVED) + cache->align_frames = align_frames_in_i; + else if (access == SND_PCM_ACCESS_RW_NONINTERLEAVED) + cache->align_frames = align_frames_in_n; + else + return -EINVAL; + cache->access = access; + + if (access == SND_PCM_ACCESS_RW_INTERLEAVED) { + char *buf; + + buf = calloc(frames_per_cache, + bytes_per_sample * samples_per_frame); + if (buf == NULL) + return -ENOMEM; + cache->buf = buf; + cache->buf_ptr = buf; + } else { + char **bufs; + char **buf_ptrs; + int i; + + bufs = calloc(samples_per_frame, sizeof(*bufs)); + if (bufs == NULL) + return -ENOMEM; + buf_ptrs = calloc(samples_per_frame, sizeof(*buf_ptrs)); + if (buf_ptrs == NULL) + return -ENOMEM; + for (i = 0; i < samples_per_frame; ++i) { + bufs[i] = calloc(frames_per_cache, bytes_per_sample); + if (bufs[i] == NULL) + return -ENOMEM; + buf_ptrs[i] = bufs[i]; + } + cache->buf = bufs; + cache->buf_ptr = buf_ptrs; + } + + cache->remained_count = 0; + cache->bytes_per_sample = bytes_per_sample; + cache->samples_per_frame = samples_per_frame; + cache->frames_per_cache = frames_per_cache; + + return 0; +} + +void frame_cache_destroy(struct frame_cache *cache) +{ + if (cache->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + int i; + for (i = 0; i < cache->samples_per_frame; ++i) { + char **bufs = cache->buf; + free(bufs[i]); + } + free(cache->buf_ptr); + } + free(cache->buf); + memset(cache, 0, sizeof(*cache)); +} diff --git a/axfer/frame-cache.h b/axfer/frame-cache.h new file mode 100644 index 00000000..7191333 --- /dev/null +++ b/axfer/frame-cache.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// frame-cache.h - maintainer of cache for data frame. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include + +struct frame_cache { + void *buf; + void *buf_ptr; + + unsigned int remained_count; + + snd_pcm_access_t access; + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_cache; + + void (*align_frames)(struct frame_cache *cache, + unsigned int consumed_count); +}; + +int frame_cache_init(struct frame_cache *cache, snd_pcm_access_t access, + unsigned int bytes_per_sample, + unsigned int samples_per_frame, + unsigned int frames_per_cache); +void frame_cache_destroy(struct frame_cache *cache); + +static inline unsigned int frame_cache_get_count(struct frame_cache *cache) +{ + return cache->remained_count; +} + +static inline void frame_cache_increase_count(struct frame_cache *cache, + unsigned int frame_count) +{ + cache->remained_count += frame_count; +} + +static inline void frame_cache_reduce(struct frame_cache *cache, + unsigned int consumed_count) +{ + cache->align_frames(cache, consumed_count); +} diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c new file mode 100644 index 00000000..59634b4 --- /dev/null +++ b/axfer/xfer-libasound-irq-rw.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer-libasound-irq-rw.c - IRQ-based scheduling model for read/write operation. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "xfer-libasound.h" +#include "misc.h" +#include "frame-cache.h" + +struct rw_closure { + snd_pcm_access_t access; + int (*process_frames)(struct libasound_state *state, + snd_pcm_state_t status, unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs); + 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 rw_closure *closure = state->private_data; + snd_pcm_sframes_t handled_frame_count; + 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 > frame_cache_get_count(&closure->cache)) { + avail_count -= frame_cache_get_count(&closure->cache); + + // Execute write operation according to the shape of buffer. + // These operations automatically start the substream. + if (closure->access == SND_PCM_ACCESS_RW_INTERLEAVED) { + handled_frame_count = snd_pcm_readi(state->handle, + closure->cache.buf_ptr, + avail_count); + } else { + handled_frame_count = snd_pcm_readn(state->handle, + closure->cache.buf_ptr, + avail_count); + } + if (handled_frame_count < 0) { + err = handled_frame_count; + return err; + } + frame_cache_increase_count(&closure->cache, handled_frame_count); + avail_count = frame_cache_get_count(&closure->cache); + } + + // Write out to file descriptors. + consumed_count = avail_count; + err = mapper_context_process_frames(mapper, closure->cache.buf, + &consumed_count, cntrs); + if (err < 0) + return err; + + frame_cache_reduce(&closure->cache, consumed_count); + + *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 rw_closure *closure = state->private_data; + snd_pcm_uframes_t consumed_count; + snd_pcm_sframes_t handled_frame_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 > frame_cache_get_count(&closure->cache)) { + avail_count -= frame_cache_get_count(&closure->cache); + + // Read frames to transfer. + err = mapper_context_process_frames(mapper, + closure->cache.buf_ptr, &avail_count, cntrs); + if (err < 0) + return err; + frame_cache_increase_count(&closure->cache, avail_count); + avail_count = frame_cache_get_count(&closure->cache); + } + + // Execute write operation according to the shape of buffer. These + // operations automatically start the stream. + consumed_count = avail_count; + if (closure->access == SND_PCM_ACCESS_RW_INTERLEAVED) { + handled_frame_count = snd_pcm_writei(state->handle, + closure->cache.buf, consumed_count); + } else { + handled_frame_count = snd_pcm_writen(state->handle, + closure->cache.buf, consumed_count); + } + if (handled_frame_count < 0) { + err = handled_frame_count; + return err; + } + + consumed_count = handled_frame_count; + frame_cache_reduce(&closure->cache, consumed_count); + + *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 rw_closure *closure = state->private_data; + snd_pcm_format_t format; + snd_pcm_uframes_t frames_per_buffer; + int bytes_per_sample; + unsigned int samples_per_frame; + 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; + + err = snd_pcm_hw_params_get_channels(state->hw_params, + &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; + + err = snd_pcm_hw_params_get_access(state->hw_params, &closure->access); + if (err < 0) + return err; + + err = frame_cache_init(&closure->cache, closure->access, + bytes_per_sample, samples_per_frame, + frames_per_buffer); + if (err < 0) + return err; + + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) + closure->process_frames = r_process_frames_blocking; + else + closure->process_frames = 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 rw_closure *closure = 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 closure->process_frames(state, status, frame_count, mapper, cntrs); +} + +static void irq_rw_post_process(struct libasound_state *state) +{ + struct rw_closure *closure = state->private_data; + + frame_cache_destroy(&closure->cache); +} + +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 rw_closure), +}; diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 2bca465..bf1b056 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -251,6 +251,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 57fa867..3f3ae6e 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -43,4 +43,6 @@ struct xfer_libasound_ops { unsigned int private_size; }; +extern const struct xfer_libasound_ops xfer_libasound_irq_rw_ops; + #endif From patchwork Tue Nov 13 06:41:29 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679929 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DD6A413BB for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CA52929647 for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id BEC512A49A; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9C0A129647 for ; Tue, 13 Nov 2018 07:37:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 51D15267B74; Tue, 13 Nov 2018 07:42:45 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 9A476267B2B; Tue, 13 Nov 2018 07:42:30 +0100 (CET) Received: from mail-pl1-f178.google.com (mail-pl1-f178.google.com [209.85.214.178]) by alsa0.perex.cz (Postfix) with ESMTP id EC5B7267AB1 for ; Tue, 13 Nov 2018 07:42:24 +0100 (CET) Received: by mail-pl1-f178.google.com with SMTP id f12-v6so5529141plo.1 for ; Mon, 12 Nov 2018 22:42:24 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=eNwailxE0UjqpVNzzFXkQVANKUrEssBxLPEExIetLPI=; b=kHZv/QzChMbvF63JI0aaj/z6lBQD8oWzs+kNAbNNRWfwKBlW1rMfDawMPcfBtSzjxZ ae5gazCLgGTMXIb58vGmaY4nnLG+f65RP2VBBNIIhYRefml6mYgHT+p3q5DTQXJJRN6a +a+yuI5Kl+uTxhATj3ocDB73jPt3vWrMuRy8G2FZHUUNRZIa7Jg5bbGV6kHdKcLvJmje DsXIcJ2DpbrFr3Y8N23KCec+X40O5rdsJRsUipQmVTnWXEhlhLlk5ky31QrpeXxE6c67 2EoLurQJ4p/2cqsuR0EVM8RwRSOK/kJXdOrK9ehGqrcQ6i5BuwD8KrsxUS6ijDShhzFO Hoyw== X-Gm-Message-State: AGRZ1gIr01yVU9CsHtwFlzVKECHR870+SF6+tmhxx6DiuYF3OoW9e41H QDJMCDK78GQRGZ6VaHB3Qwu8gmGH X-Google-Smtp-Source: AJdET5eta8iGGz2JUJ5s4/22W6UcaZclQfooVy2e8Vqwe30cwK3oUgoOXqieEu6Fml5UZCZ0yDRPLQ== X-Received: by 2002:a17:902:3a3:: with SMTP id d32-v6mr3887809pld.304.1542091343395; Mon, 12 Nov 2018 22:42:23 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.21 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:22 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:29 +0900 Message-Id: <20181113064147.13577-17-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 17/35] axfer: add a sub-command to transfer data frames X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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. Unlike aplay, when catching SIGSTP, this application performs to suspend PCM substream. When catching SIGCONT, it performs to resume the PCM substream. The aim of this design is to avoid XRUN state of the PCM substream. If users/developers need to any XRUN-recovery test, it's better to work for the other ways. Below lines are examples to execute: $ axfer transfer -P -D hw:0,3 /dev/urandom -f dat -vvv $ axfer transfer -C -D hw:1,0 /dev/null -r 48000 -vvv Signed-off-by: Takashi Sakamoto --- axfer/Makefile.am | 3 +- axfer/main.c | 2 +- axfer/subcmd-transfer.c | 438 ++++++++++++++++++++++++++++++++++++++++ axfer/subcmd.h | 2 + 4 files changed, 443 insertions(+), 2 deletions(-) create mode 100644 axfer/subcmd-transfer.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 07c1fb3..386f8e2 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -45,4 +45,5 @@ axfer_SOURCES = \ xfer-libasound.c \ frame-cache.h \ frame-cache.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 f141439..655d1e0 100644 --- a/axfer/main.c +++ b/axfer/main.c @@ -215,7 +215,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..188589f --- /dev/null +++ b/axfer/subcmd-transfer.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// subcmd-transfer.c - operations for transfer sub command. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "xfer.h" +#include "subcmd.h" +#include "misc.h" + +#include + +struct context { + struct xfer_context xfer; + struct mapper_context mapper; + struct container_context *cntrs; + unsigned int cntr_count; + + // 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, + int argc, char *const *argv) +{ + const char *xfer_type_literal; + enum xfer_type xfer_type; + int i; + + // Decide transfer backend before option parser runs. + xfer_type_literal = NULL; + for (i = 0; i < argc; ++i) { + if (strstr(argv[i], "--xfer-type") != argv[i]) + continue; + xfer_type_literal = argv[i] + 12; + } + if (xfer_type_literal == NULL) { + xfer_type = XFER_TYPE_LIBASOUND; + } else { + xfer_type = xfer_type_from_label(xfer_type_literal); + if (xfer_type == XFER_TYPE_UNSUPPORTED) { + fprintf(stderr, "The '%s' xfer type is not supported\n", + xfer_type_literal); + return -EINVAL; + } + } + + // Initialize transfer. + return xfer_context_init(&ctx->xfer, xfer_type, direction, argc, argv); +} + +static int capture_pre_process(struct context *ctx, snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer, + uint64_t *total_frame_count) +{ + snd_pcm_format_t sample_format = SND_PCM_FORMAT_UNKNOWN; + unsigned int samples_per_frame = 0; + unsigned int frames_per_second = 0; + unsigned int channels; + int i; + int err; + + err = xfer_context_pre_process(&ctx->xfer, &sample_format, + &samples_per_frame, &frames_per_second, + access, frames_per_buffer); + if (err < 0) + return err; + + // Prepare for containers. + ctx->cntrs = calloc(ctx->xfer.path_count, sizeof(*ctx->cntrs)); + if (ctx->cntrs == NULL) + return -ENOMEM; + ctx->cntr_count = ctx->xfer.path_count; + + if (ctx->cntr_count > 1) + channels = 1; + else + channels = samples_per_frame; + + *total_frame_count = 0; + for (i = 0; i < ctx->cntr_count; ++i) { + uint64_t frame_count; + + err = container_builder_init(ctx->cntrs + i, + ctx->xfer.paths[i], + ctx->xfer.cntr_format, + ctx->xfer.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_uframes_t *frames_per_buffer, + uint64_t *total_frame_count) +{ + snd_pcm_format_t sample_format = SND_PCM_FORMAT_UNKNOWN; + unsigned int samples_per_frame = 0; + unsigned int frames_per_second = 0; + int i; + int err; + + // Prepare for containers. + ctx->cntrs = calloc(ctx->xfer.path_count, sizeof(*ctx->cntrs)); + if (ctx->cntrs == NULL) + return -ENOMEM; + ctx->cntr_count = ctx->xfer.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->xfer.paths[i], + ctx->xfer.verbose > 1); + if (err < 0) + return err; + + if (i == 0) { + // For a raw container. + format = ctx->xfer.sample_format; + channels = ctx->xfer.samples_per_frame; + rate = ctx->xfer.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; + + // Configure hardware with these parameters. + return xfer_context_pre_process(&ctx->xfer, &sample_format, + &samples_per_frame, &frames_per_second, + access, frames_per_buffer); +} + +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_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, &frames_per_buffer, + total_frame_count); + } else { + mapper_type = MAPPER_TYPE_MUXER; + err = playback_pre_process(ctx, &access, &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->xfer.verbose > 1); + if (err < 0) + return err; + + bytes_per_sample = + snd_pcm_format_physical_width(ctx->xfer.sample_format) / 8; + if (bytes_per_sample <= 0) + return -ENXIO; + err = mapper_context_pre_process(&ctx->mapper, access, bytes_per_sample, + ctx->xfer.samples_per_frame, + frames_per_buffer, ctx->cntrs); + if (err < 0) + return err; + + 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->xfer.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); +} + +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 = prepare_signal_handler(&ctx); + if (err < 0) + return err; + + err = context_init(&ctx, direction, argc, argv); + if (err < 0) + goto end; + if (ctx.xfer.help) + 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 f78b766..5ffe1e4 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 From patchwork Tue Nov 13 06:41:30 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679951 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id EBA5018F0 for ; Tue, 13 Nov 2018 07:37:26 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D84F52945E for ; Tue, 13 Nov 2018 07:37:26 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id CC49429647; Tue, 13 Nov 2018 07:37:26 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F2A682A47F for ; Tue, 13 Nov 2018 07:37:22 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id C8640267B58; Tue, 13 Nov 2018 07:42:48 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 209E8267B55; Tue, 13 Nov 2018 07:42:32 +0100 (CET) Received: from mail-pf1-f193.google.com (mail-pf1-f193.google.com [209.85.210.193]) by alsa0.perex.cz (Postfix) with ESMTP id 24FCE267B3E for ; Tue, 13 Nov 2018 07:42:25 +0100 (CET) Received: by mail-pf1-f193.google.com with SMTP id v68-v6so5554462pfk.0 for ; Mon, 12 Nov 2018 22:42:25 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=fY9/01GP46uT/aEZaXXfVKmZmem49XGkgXu2AFARq5E=; b=TC3DDZlQOS8DZqPRK36uOEIiCRBGyp54hyLsoT+14vnqetvGMXdlcGqs1GN3htyyO7 fLDnPfnhqIByVjF8/bU3QCSDJgcK4gcFgKbKLcikLycMzH7Pk8scCCqsvwcvPdNmCMsK W47OPZcEXNyPrhphvymxshJ25eU86X9wKP7VoRg3DZ1INrn6jmxVBOcyq4n/KjY2q4AX oQcp7fPDk5NCBsNDatvQbviqHJmSBlT7l21so8NaSHWI0NNuIgeLOjY74Ccg4mKYBCP3 t9XKf3qZL0q5g+vbMxO3x4rsUW53/o+85bIwN7aDQE8D2Z62A4QDzUwCWHb4yQRLeSrL xRcw== X-Gm-Message-State: AGRZ1gKDy0JSFFCIEs4gZi4oOWYU1h6E6nwTE2EzKdKDiGyNIy0BE0qJ HHJPZtMETwklJa9dD1jEgbc= X-Google-Smtp-Source: AJdET5didYgvoXKkX/R4Qs//qSEvH8wg+uDtUqGJXanfhL/K+WofWyUtQiK1cP2bZAkEZqIOPwhbnw== X-Received: by 2002:a63:1b1f:: with SMTP id b31mr3637325pgb.66.1542091345113; Mon, 12 Nov 2018 22:42:25 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.23 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:24 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:30 +0900 Message-Id: <20181113064147.13577-18-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 18/35] axfer: add informative output and an option to suppress it X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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. An original aplay implementation has no effect of this option in a case to handle multiple files. However, in a point of usability, this commit support this case. Signed-off-by: Takashi Sakamoto --- axfer/subcmd-transfer.c | 27 +++++++++++++++++++++++++++ axfer/xfer-options.c | 5 ++++- axfer/xfer.h | 1 + 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 188589f..a165a93 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -340,6 +340,21 @@ static int context_process_frames(struct context *ctx, int i; int err = 0; + if (!ctx->xfer.quiet) { + fprintf(stderr, + "%s: Format '%s', Rate %u Hz, Channels ", + snd_pcm_stream_name(direction), + snd_pcm_format_description(ctx->xfer.sample_format), + ctx->xfer.frames_per_second); + if (ctx->xfer.samples_per_frame == 1) + fprintf(stderr, "'monaural'"); + else if (ctx->xfer.samples_per_frame == 2) + fprintf(stderr, "'Stereo'"); + else + fprintf(stderr, "%u", ctx->xfer.samples_per_frame); + fprintf(stderr, "\n"); + } + *actual_frame_count = 0; while (!ctx->interrupted) { struct container_context *cntr; @@ -370,6 +385,18 @@ static int context_process_frames(struct context *ctx, break; } + if (!ctx->xfer.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; } diff --git a/axfer/xfer-options.c b/axfer/xfer-options.c index fb71244..7790ea9 100644 --- a/axfer/xfer-options.c +++ b/axfer/xfer-options.c @@ -227,7 +227,7 @@ int xfer_options_parse_args(struct xfer_context *xfer, const struct xfer_data *data, int argc, char *const *argv) { - static const char *short_opts = "CPhvf:c:r:t:I"; + static const char *short_opts = "CPhvqf:c:r:t:I"; static const struct option long_opts[] = { // For generic purposes. {"capture", 0, 0, 'C'}, @@ -235,6 +235,7 @@ int xfer_options_parse_args(struct xfer_context *xfer, {"xfer-type", 1, 0, OPT_XFER_TYPE}, {"help", 0, 0, 'h'}, {"verbose", 0, 0, 'v'}, + {"quiet", 0, 0, 'q'}, // For transfer backend. {"format", 1, 0, 'f'}, {"channels", 1, 0, 'c'}, @@ -289,6 +290,8 @@ int xfer_options_parse_args(struct xfer_context *xfer, xfer->help = true; else if (key == 'v') ++xfer->verbose; + else if (key == 'q') + xfer->quiet = true; else if (key == 'f') xfer->sample_format_literal = arg_duplicate_string(optarg, &err); else if (key == 'c') diff --git a/axfer/xfer.h b/axfer/xfer.h index df43d1c..0ed84e4 100644 --- a/axfer/xfer.h +++ b/axfer/xfer.h @@ -33,6 +33,7 @@ struct xfer_context { unsigned int frames_per_second; unsigned int samples_per_frame; bool help:1; + bool quiet:1; bool multiple_cntrs:1; // For mapper. snd_pcm_format_t sample_format; From patchwork Tue Nov 13 06:41:31 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679947 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id B6BB813BB for ; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A44EA2945E for ; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 9836729647; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D46622A3F9 for ; Tue, 13 Nov 2018 07:37:22 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 21D89267B83; Tue, 13 Nov 2018 07:42:50 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 47109267B3F; Tue, 13 Nov 2018 07:42:35 +0100 (CET) Received: from mail-pl1-f196.google.com (mail-pl1-f196.google.com [209.85.214.196]) by alsa0.perex.cz (Postfix) with ESMTP id DAF22267AE5 for ; Tue, 13 Nov 2018 07:42:27 +0100 (CET) Received: by mail-pl1-f196.google.com with SMTP id x21-v6so2795170pln.9 for ; Mon, 12 Nov 2018 22:42:27 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=NzGAJXn8Mq5Flb8wV+eBRtwjheGdE2End/KDix6JBtk=; b=bLoHLHuiAzynQR97OXyY/CKOHLDneDWo/tjPxmmIRo/AI76JUifAEG/q6feiAuANTU a7zX1V6kyNLH5kuXCgdbfyBvFG0YYuqzkGIjLOEwGHCo7Hxt4joC2/JImYCvjuD2tski NJImbHvLPrUWGbC8lL/i2Z1M8zqX7dyIlJ+7/sHlzIX3QEq/IQjpScPZ+x3+6hIrstFQ 90vbKUG2mXjxdkPd/Ie7IC3Hq+DFt1ROrJbkfNZqmjiNRTB+aS27XBws2rU/QVujlHcb 00b/84LHF/43MFRfhf5Bj0vi/97e+JvbraW30+jy3cQ0gMJgCcf5I7f9QgQkHcPC+MQL X+zw== X-Gm-Message-State: AGRZ1gKAy4kgghbKfLjtIKg9rdnjgabE8fDpypaHuaLmU251Yn3vo334 cLnF5stVxzjwNIoUP3uXH6ccsvHN X-Google-Smtp-Source: AJdET5fElqh92LRDe43uO9lKmtUZG1UH8GRLdHArifiO6jkt0bOIyNmceolWxBpDZZr3Xz2oGh590g== X-Received: by 2002:a17:902:1123:: with SMTP id d32-v6mr3873088pla.62.1542091346840; Mon, 12 Nov 2018 22:42:26 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.25 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:26 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:31 +0900 Message-Id: <20181113064147.13577-19-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 19/35] axfer: add an option to dump available hardware parameters X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/subcmd-transfer.c | 2 +- axfer/xfer-libasound.c | 9 +++++++++ axfer/xfer-options.c | 5 +++++ axfer/xfer.h | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index a165a93..36817f3 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -447,7 +447,7 @@ int subcmd_transfer(int argc, char *const *argv, snd_pcm_stream_t direction) err = context_init(&ctx, direction, argc, argv); if (err < 0) goto end; - if (ctx.xfer.help) + if (ctx.xfer.help || ctx.xfer.dump_hw_params) goto end; err = context_pre_process(&ctx, direction, &expected_frame_count); diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index bf1b056..60e9aab 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -99,6 +99,15 @@ static int open_handle(struct xfer_context *xfer) // TODO: Applying NO_PERIOD_WAKEUP should be done here. + if (xfer->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); } diff --git a/axfer/xfer-options.c b/axfer/xfer-options.c index 7790ea9..21fc6bb 100644 --- a/axfer/xfer-options.c +++ b/axfer/xfer-options.c @@ -16,6 +16,7 @@ enum no_short_opts { // 128 or later belong to non us-ascii character set. OPT_XFER_TYPE = 128, + OPT_DUMP_HW_PARAMS, }; static int allocate_paths(struct xfer_context *xfer, char *const *paths, @@ -244,6 +245,8 @@ int xfer_options_parse_args(struct xfer_context *xfer, {"file-type", 1, 0, 't'}, // For mapper. {"separate-channels", 0, 0, 'I'}, + // For debugging. + {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, }; char *s_opts; struct option *l_opts; @@ -302,6 +305,8 @@ int xfer_options_parse_args(struct xfer_context *xfer, xfer->cntr_format_literal = arg_duplicate_string(optarg, &err); else if (key == 'I') xfer->multiple_cntrs = true; + else if (key == OPT_DUMP_HW_PARAMS) + xfer->dump_hw_params = true; else if (key == '?') return -EINVAL; else { diff --git a/axfer/xfer.h b/axfer/xfer.h index 0ed84e4..a234851 100644 --- a/axfer/xfer.h +++ b/axfer/xfer.h @@ -34,6 +34,7 @@ struct xfer_context { unsigned int samples_per_frame; bool help:1; bool quiet:1; + bool dump_hw_params:1; bool multiple_cntrs:1; // For mapper. snd_pcm_format_t sample_format; From patchwork Tue Nov 13 06:41:32 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679869 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id A88FB13BB for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8F5922A45C for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 83E412A47A; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DFDEC2A45C for ; Tue, 13 Nov 2018 07:32:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 6625F267B89; Tue, 13 Nov 2018 07:42:51 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id DA3FC267B7A; Tue, 13 Nov 2018 07:42:45 +0100 (CET) Received: from mail-pl1-f179.google.com (mail-pl1-f179.google.com [209.85.214.179]) by alsa0.perex.cz (Postfix) with ESMTP id 08A0A267AA4 for ; Tue, 13 Nov 2018 07:42:29 +0100 (CET) Received: by mail-pl1-f179.google.com with SMTP id w22-v6so5528963plk.0 for ; Mon, 12 Nov 2018 22:42:29 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=ZXMNVxQITpPmrqu0PfjmGdmKfStETZ/mqYXwBJ0pO24=; b=buec12tYMRB/JYEgR/MiYUiR7ukTDClSpBbg1ntUgjIFp1i7SgQmO7SwqTZn6hK7S1 1ljzt6FR2BPrLs7htCWdtNxGyZ/uTNHQDHbTNP+8acnnOCcEos4xIr69O8DRDHdVqn4Z lzpA5ZGiJI3UQ28Fyzlqi0R913e9Jkhq9kB/n/5MwuwH3VDy96uejojwiB813pFR3fsr 9vNCMOF0nV0koo39GqfcY5a2iRdsJDxId3YHD6SVQh2b+nrM3AzkkboVqDGp+JprOp0f U0LtsNAH/foPOkbMp/af+3S4Wu78Ao/do8JvVqeCtun6E3cz8NQWXozh5faQMYyZH06F hO7A== X-Gm-Message-State: AGRZ1gJ+F2xTWJZG2wKNjXoZcCGzcYT61DfJsVQ8MviNJY1TQC3OTx8l m/rvM8hrSj5Qheass9ifqa812nvz X-Google-Smtp-Source: AJdET5fQaOYYLm2QW4FO4yyFDbWgHJYgf+WtCDOJQj2Jz9P8cdX5QxSwVMxo93ZqOjiC03q/U9X5zw== X-Received: by 2002:a17:902:187:: with SMTP id b7-v6mr3878174plb.150.1542091348569; Mon, 12 Nov 2018 22:42:28 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.27 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:27 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:32 +0900 Message-Id: <20181113064147.13577-20-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 20/35] axfer: add options related to duration and obsolete '--max-file-size' option X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP In aplay, some options are available to stop data transmission by frame unit. This commit adds support for the options below: * --duration (-d) * For duration seconds. The number of data frames transferred in this * runtime is calculated by this value and sampling rate. * --samples (-s) * For the number of data frames to handle in this runtime. An original aplay has a similar option; '--max-file-time'. This option is used for capture data transmission to switch file to write data frame up to maximum number of frames which container format supports, instead of terminating. 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 --- axfer/subcmd-transfer.c | 2 ++ axfer/xfer-options.c | 38 ++++++++++++++++++++++++++++++++++++-- axfer/xfer.h | 4 ++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 36817f3..d5f16cb 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -327,6 +327,8 @@ static int context_pre_process(struct context *ctx, snd_pcm_stream_t direction, if (err < 0) return err; + xfer_options_calculate_duration(&ctx->xfer, total_frame_count); + return 0; } diff --git a/axfer/xfer-options.c b/axfer/xfer-options.c index 21fc6bb..9233647 100644 --- a/axfer/xfer-options.c +++ b/axfer/xfer-options.c @@ -17,6 +17,8 @@ enum no_short_opts { // 128 or later belong to non us-ascii character set. OPT_XFER_TYPE = 128, OPT_DUMP_HW_PARAMS, + // Obsoleted. + OPT_MAX_FILE_TIME, }; static int allocate_paths(struct xfer_context *xfer, char *const *paths, @@ -228,7 +230,7 @@ int xfer_options_parse_args(struct xfer_context *xfer, const struct xfer_data *data, int argc, char *const *argv) { - static const char *short_opts = "CPhvqf:c:r:t:I"; + static const char *short_opts = "CPhvqd:s:f:c:r:t:I"; static const struct option long_opts[] = { // For generic purposes. {"capture", 0, 0, 'C'}, @@ -237,6 +239,8 @@ int xfer_options_parse_args(struct xfer_context *xfer, {"help", 0, 0, 'h'}, {"verbose", 0, 0, 'v'}, {"quiet", 0, 0, 'q'}, + {"duration", 1, 0, 'd'}, + {"samples", 1, 0, 's'}, // For transfer backend. {"format", 1, 0, 'f'}, {"channels", 1, 0, 'c'}, @@ -247,6 +251,8 @@ int xfer_options_parse_args(struct xfer_context *xfer, {"separate-channels", 0, 0, 'I'}, // For debugging. {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, + // Obsoleted. + {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, }; char *s_opts; struct option *l_opts; @@ -295,6 +301,10 @@ int xfer_options_parse_args(struct xfer_context *xfer, ++xfer->verbose; else if (key == 'q') xfer->quiet = true; + else if (key == 'd') + xfer->duration_seconds = arg_parse_decimal_num(optarg, &err); + else if (key == 's') + xfer->duration_frames = arg_parse_decimal_num(optarg, &err); else if (key == 'f') xfer->sample_format_literal = arg_duplicate_string(optarg, &err); else if (key == 'c') @@ -309,7 +319,13 @@ int xfer_options_parse_args(struct xfer_context *xfer, xfer->dump_hw_params = true; else if (key == '?') return -EINVAL; - else { + else if (key == OPT_MAX_FILE_TIME) { + fprintf(stderr, + "An option '--%s' is obsoleted and has no " + "effect.\n", + l_opts[l_index].name); + err = -EINVAL; + } else { err = xfer->ops->parse_opt(xfer, key, optarg); if (err < 0 && err != -ENXIO) break; @@ -326,6 +342,24 @@ int xfer_options_parse_args(struct xfer_context *xfer, return validate_options(xfer); } +void xfer_options_calculate_duration(struct xfer_context *xfer, + uint64_t *total_frame_count) +{ + uint64_t frame_count; + + if (xfer->duration_seconds > 0) { + frame_count = xfer->duration_seconds * xfer->frames_per_second; + if (frame_count < *total_frame_count) + *total_frame_count = frame_count; + } + + if (xfer->duration_frames > 0) { + frame_count = xfer->duration_frames; + if (frame_count < *total_frame_count) + *total_frame_count = frame_count; + } +} + static const char *const allowed_duplication[] = { "/dev/null", "/dev/zero", diff --git a/axfer/xfer.h b/axfer/xfer.h index a234851..21ab85d 100644 --- a/axfer/xfer.h +++ b/axfer/xfer.h @@ -30,6 +30,8 @@ struct xfer_context { char *sample_format_literal; char *cntr_format_literal; unsigned int verbose; + unsigned int duration_seconds; + unsigned int duration_frames; unsigned int frames_per_second; unsigned int samples_per_frame; bool help:1; @@ -68,6 +70,8 @@ int xfer_options_parse_args(struct xfer_context *xfer, const struct xfer_data *data, int argc, char *const *argv); int xfer_options_fixup_paths(struct xfer_context *xfer); +void xfer_options_calculate_duration(struct xfer_context *xfer, + uint64_t *total_frame_count); // For internal use in 'xfer' module. From patchwork Tue Nov 13 06:41:33 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679941 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 0636C18F0 for ; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E831C29647 for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id DC0E22A47A; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D7A792A478 for ; Tue, 13 Nov 2018 07:37:22 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id B2D64267B8D; Tue, 13 Nov 2018 07:42:52 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 4902F267B6B; Tue, 13 Nov 2018 07:42:48 +0100 (CET) Received: from mail-pf1-f195.google.com (mail-pf1-f195.google.com [209.85.210.195]) by alsa0.perex.cz (Postfix) with ESMTP id E6745267B58 for ; Tue, 13 Nov 2018 07:42:31 +0100 (CET) Received: by mail-pf1-f195.google.com with SMTP id u3-v6so2866601pfm.4 for ; Mon, 12 Nov 2018 22:42:31 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=TYH9g4Rtu6TzDE0yUbXA5vQN4cpqtZDA9Vn11BhpHi0=; b=fyw19BabTUmjwXxHUg57jKVZKbmYT2EXKWJxJ7IdovtcvQXKeaiPw+Gw6XWnpEs2SJ On1Baqlpe6xBuoUro8QH/A5o3oAnru7LeQtqaJf/QcjYSFU1w1FcpHYMyOGhwHQQE/j3 UZ4skx+BaclX6grtfUgNoL0ZSV5v6hqfFl+uCiM5QTC5H+YK+ZqAydHpMERQ4sp1TYa1 6FnNixoYgytxrnq5EgkUWZ8iim85q0BI08PyQ3iC/NO8Q8VlJZRYjVZmoOjpivE6fS9c zLTjJYGbeCTLoMp5IWMNS+4BuyHkpZWCxNTXQvut9p5A95ykRgcnE+fO1RaYr0Nco2TF aLFA== X-Gm-Message-State: AGRZ1gJgB9dd9fhasiRDNwvGxRksfCaspYeRvXRLFEKZvhbQgxphTKlJ wTiJSTBsQnBslh8F9cEmcYv32F2S X-Google-Smtp-Source: AJdET5fvTod9TT/EBVbWyeLhJcQFvOIjTSUnhFK8opfmASo6DoCVeYfqpA4wBRr+oLvcQ9zr1yu0Dw== X-Received: by 2002:a62:7893:: with SMTP id t141-v6mr3907996pfc.259.1542091350314; Mon, 12 Nov 2018 22:42:30 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.28 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:29 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:33 +0900 Message-Id: <20181113064147.13577-21-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 21/35] axfer: add an option to finish transmission at XRUN X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/xfer-libasound.c | 11 ++++++++++- axfer/xfer-libasound.h | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 60e9aab..77c142e 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -9,9 +9,16 @@ #include "xfer-libasound.h" #include "misc.h" +enum no_short_opts { + // 200 or later belong to non us-ascii character set. + OPT_FATAL_ERRORS = 200, +}; + #define S_OPTS "D:" static const struct option l_opts[] = { {"device", 1, 0, 'D'}, + // For debugging. + {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, }; static int xfer_libasound_init(struct xfer_context *xfer, @@ -39,6 +46,8 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, if (key == 'D') state->node_literal = arg_duplicate_string(optarg, &err); + else if (key == OPT_FATAL_ERRORS) + state->finish_at_xrun = true; else err = -ENXIO; @@ -305,7 +314,7 @@ static int xfer_libasound_process_frames(struct xfer_context *xfer, if (err < 0) { if (err == -EAGAIN) return err; - 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. diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 3f3ae6e..270288d 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -29,6 +29,8 @@ struct libasound_state { bool verbose; char *node_literal; + + bool finish_at_xrun:1; }; // For internal use in 'libasound' module. From patchwork Tue Nov 13 06:41:34 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679937 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 7701D4DB2 for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 647C829911 for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 596BB2A496; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1CF952A489 for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 05567267B6A; Tue, 13 Nov 2018 07:42:54 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 95A66267B86; Tue, 13 Nov 2018 07:42:49 +0100 (CET) Received: from mail-pl1-f196.google.com (mail-pl1-f196.google.com [209.85.214.196]) by alsa0.perex.cz (Postfix) with ESMTP id 13278267AC2 for ; Tue, 13 Nov 2018 07:42:32 +0100 (CET) Received: by mail-pl1-f196.google.com with SMTP id p6-v6so5520812pll.4 for ; Mon, 12 Nov 2018 22:42:32 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=DOn8KewGneCNAuWTAoy1OBX2lNlSJeZKx43Y1VVu+h0=; b=CSmZRids3TdCv/vbCZgCI0nPLNwavlsFlSGm69asiK2K4rWQBgCLTkdJ/rNsN815u4 blZv/k3dZ+QGhHGBn39/9Ys9vbJcF0PuGOUfwMNar2gkdZzeMVrVLlWNfNjgNkTeOV0m mCYSZPEtrgSWsKTn084PZyYu+u5RTeA1wvVdgfUttbME0KVgu63RgJ4T9Wk95f98OcVe 7wQbbDxHCsO3NQ9sbnNYaD/HgCLNHogwn/ycwUd/6EDjEYyGXYd7i3t7UkDgcQ9fCOJP Nw2kRZeS9ajX0bFkWnGCQipEglSPhTtOB7/T3IsF7zzs3ABMNMuaT3CUox8PJUeJs7UK YhgQ== X-Gm-Message-State: AGRZ1gJVd/npTkfdM6dwgW7UlI6EevBomKyzhxpGmDVxl74VmgrK43xM 3PeptiZ+hLOFWPlVtq07Mp6DwnPv X-Google-Smtp-Source: AJdET5fDnsDYcQWo5jxJ541PbLJJN/K+dqlfYNjfTgWQ1eRluR1dbi10hTr475kt029kRi7alR5vKw== X-Received: by 2002:a17:902:b282:: with SMTP id u2mr3930560plr.89.1542091352111; Mon, 12 Nov 2018 22:42:32 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.30 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:31 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:34 +0900 Message-Id: <20181113064147.13577-22-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 22/35] axfer: add support for non-blocking operation X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 'snd_pcm_wait()' to wait for event notification. Below lines are examples to execute: $ axfer transfer -N -P -d 2 -D hw:0,3 /dev/urandom -f dat -vvv $ axfer transfer -N -C -d 2 -D hw:1,0 /dev/null -r 48000 -vvv Signed-off-by: Takashi Sakamoto --- axfer/xfer-libasound-irq-rw.c | 102 ++++++++++++++++++++++++++++++++-- axfer/xfer-libasound.c | 16 +++++- axfer/xfer-libasound.h | 1 + 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c index 59634b4..f05ac4b 100644 --- a/axfer/xfer-libasound-irq-rw.c +++ b/axfer/xfer-libasound-irq-rw.c @@ -117,6 +117,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 = snd_pcm_wait(state->handle, -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, @@ -231,6 +276,48 @@ 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 = snd_pcm_wait(state->handle, -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 rw_closure *closure = state->private_data; @@ -267,10 +354,17 @@ static int irq_rw_pre_process(struct libasound_state *state) if (err < 0) return err; - if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) - closure->process_frames = r_process_frames_blocking; - else - closure->process_frames = w_process_frames_blocking; + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) { + if (state->nonblock) + closure->process_frames = r_process_frames_nonblocking; + else + closure->process_frames = r_process_frames_blocking; + } else { + if (state->nonblock) + closure->process_frames = w_process_frames_nonblocking; + else + closure->process_frames = w_process_frames_blocking; + } return 0; } diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 77c142e..cb26b69 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -14,9 +14,10 @@ enum no_short_opts { OPT_FATAL_ERRORS = 200, }; -#define S_OPTS "D:" +#define S_OPTS "D:N" static const struct option l_opts[] = { {"device", 1, 0, 'D'}, + {"nonblock", 0, 0, 'N'}, // For debugging. {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, }; @@ -46,6 +47,8 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, if (key == 'D') state->node_literal = arg_duplicate_string(optarg, &err); + else if (key == 'N') + state->nonblock = true; else if (key == OPT_FATAL_ERRORS) state->finish_at_xrun = true; else @@ -91,10 +94,14 @@ static int set_access_hw_param(struct libasound_state *state) static int open_handle(struct xfer_context *xfer) { struct libasound_state *state = xfer->private_data; + int mode = 0; int err; + if (state->nonblock) + mode |= SND_PCM_NONBLOCK; + err = snd_pcm_open(&state->handle, state->node_literal, xfer->direction, - 0); + mode); if (err < 0) { logging(state, "Fail to open libasound PCM node for %s: %s\n", snd_pcm_stream_name(xfer->direction), @@ -378,7 +385,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)); diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 270288d..6656aeb 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -31,6 +31,7 @@ struct libasound_state { char *node_literal; bool finish_at_xrun:1; + bool nonblock:1; }; // For internal use in 'libasound' module. From patchwork Tue Nov 13 06:41:35 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679873 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 1E7621747 for ; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 04F842A46A for ; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id ED8A92A478; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E00662A46A for ; Tue, 13 Nov 2018 07:32:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 8D0B8267B3C; Tue, 13 Nov 2018 07:42:57 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id E4923267B92; Tue, 13 Nov 2018 07:42:52 +0100 (CET) Received: from mail-pg1-f194.google.com (mail-pg1-f194.google.com [209.85.215.194]) by alsa0.perex.cz (Postfix) with ESMTP id EDEA1267B5C for ; Tue, 13 Nov 2018 07:42:34 +0100 (CET) Received: by mail-pg1-f194.google.com with SMTP id z11so2594128pgu.0 for ; Mon, 12 Nov 2018 22:42:34 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Vl3VenS+qQfW55tAfNsz9p75FYp/nfqMT5lurRyphOA=; b=pxZ/jqXD4gaQi0U8L8ufCAYgqVk+L2DZny2VC0DwN60sJYyskcyoonZQhvSv8zspfk 8U8FCGoI0mDyGESuNNfibVBpWmDHgqZ9PrxGrkLBwYzI+TmfcHK7tO7TzClv+snHk+BK g5/dBbOaL0JVJmH2KEyONLCdbsTfisNx4ea5RkNTbZjoGeZFewYuqIRyXEBuL9bdize7 ZU+wZGbbRqQESjCIGLTuNRl5T/bRY1vyxbk3tHhqYs8b1vme1gkkPgAM0J2S9P4rfeoa YEC+5UOQFBDhOFSQgFNMWyg51KnV0V0EdsyC0HQg2IEj6lcg/0UFaw1FWogkT36ypu/g mdKg== X-Gm-Message-State: AGRZ1gL1TqClKY2GGupJQzjPiip4isOxopUdf1wMCQhktxIqPtO5+z6x Ar6Qt3Y7ait6uPeeuf4cvRrw3asI X-Google-Smtp-Source: AJdET5dPcR7riemrJFmtbD/k5XVmvxf4X8eAk05Yp1ov6ghkhPOTeddldu+7TUoy6SM56Y3xkggeig== X-Received: by 2002:a63:e156:: with SMTP id h22mr3665832pgk.255.1542091353811; Mon, 12 Nov 2018 22:42:33 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.32 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:33 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:35 +0900 Message-Id: <20181113064147.13577-23-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 23/35] axfer: add support for MMAP PCM operation X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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, this commit uses 'snd_pcm_wait()' to wait for event notification. Below lines are examples to execute: $ axfer transfer -M -P -d 2 -D hw:0,3 /dev/urandom -f dat -vvv $ axfer transfer -M -C -d 2 -D hw:1,0 /dev/null -r 48000 -vvv Signed-off-by: Takashi Sakamoto --- axfer/Makefile.am | 3 +- axfer/xfer-libasound-irq-mmap.c | 268 ++++++++++++++++++++++++++++++++ axfer/xfer-libasound.c | 27 +++- axfer/xfer-libasound.h | 4 + 4 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 axfer/xfer-libasound-irq-mmap.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 386f8e2..960811e 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -46,4 +46,5 @@ axfer_SOURCES = \ frame-cache.h \ frame-cache.c \ xfer-libasound-irq-rw.c \ - subcmd-transfer.c + subcmd-transfer.c \ + xfer-libasound-irq-mmap.c diff --git a/axfer/xfer-libasound-irq-mmap.c b/axfer/xfer-libasound-irq-mmap.c new file mode 100644 index 00000000..87ef7e0 --- /dev/null +++ b/axfer/xfer-libasound-irq-mmap.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer-libasound-irq-mmap.c - IRQ-based scheduling model for mmap operation. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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; + + // Wait for hardware IRQ when no avail space in buffer. + err = snd_pcm_wait(state->handle, -1); + if (err < 0) + return err; + + // Sync cache in user space to data in kernel space to calculate avail + // frames according to the latest positions on PCM buffer. + // + // This has an additional advantage to handle libasound PCM plugins. + // Most of libasound PCM plugins perform resampling in .avail_update() + // callback for capture PCM substream, then update positions on buffer. + // + // MEMO: either snd_pcm_avail_update() and snd_pcm_mmap_begin() can + // return the same number of available frames. + avail = snd_pcm_avail_update(state->handle); + if (avail < 0) + return (int)avail; + if (*frame_count < avail) + avail = *frame_count; + + err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail); + if (err < 0) + return err; + + // 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) + return err; + if (avail_count == 0) { + *frame_count = 0; + return 0; + } + + consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset, + avail_count); + if (consumed_count < 0) + return (int)consumed_count; + if (consumed_count != avail_count) + logging(state, "A bug of access plugin for this PCM node.\n"); + + *frame_count = consumed_count; + + return 0; +} + +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 + // synchronization between 3 levels: + // 1. status to actual hardware by driver. + // 2. status data in kernel space. + // 3. status data in user space. + // + // Kernel driver query 1 and sync 2, according to requests of some + // ioctl(2) commands. For synchronization between 2 and 3, ALSA PCM core + // supports mmap(2) operation on cache coherent architectures, some + // ioctl(2) commands on cache incoherent architecture. In usage of the + // former mechanism, we need to care of concurrent access by IRQ context + // and process context to the mapped page frame. + // In a call of ioctl(2) with SNDRV_PCM_IOCTL_STATUS and + // SNDRV_PCM_IOCTL_STATUS_EXT, the above care is needless because + // mapped page frame is unused regardless of architectures in a point of + // cache coherency. + 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; + + 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 cb26b69..c2e1282 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -14,10 +14,11 @@ enum no_short_opts { OPT_FATAL_ERRORS = 200, }; -#define S_OPTS "D:N" +#define S_OPTS "D:NM" static const struct option l_opts[] = { {"device", 1, 0, 'D'}, {"nonblock", 0, 0, 'N'}, + {"mmap", 0, 0, 'M'}, // For debugging. {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, }; @@ -49,6 +50,8 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, state->node_literal = arg_duplicate_string(optarg, &err); else if (key == 'N') state->nonblock = true; + else if (key == 'M') + state->mmap = true; else if (key == OPT_FATAL_ERRORS) state->finish_at_xrun = true; else @@ -70,6 +73,13 @@ int xfer_libasound_validate_opts(struct xfer_context *xfer) return -ENOMEM; } + if (state->mmap && state->nonblock) { + fprintf(stderr, + "An option for mmap operation should not be used with " + "nonblocking option.\n"); + return -EINVAL; + } + return err; } @@ -82,8 +92,13 @@ static int set_access_hw_param(struct libasound_state *state) 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 (state->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(state->handle, state->hw_params, mask); snd_pcm_access_mask_free(mask); @@ -279,6 +294,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; } diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 6656aeb..550b1c2 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -32,6 +32,7 @@ struct libasound_state { bool finish_at_xrun:1; bool nonblock:1; + bool mmap:1; }; // For internal use in 'libasound' module. @@ -48,4 +49,7 @@ struct xfer_libasound_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 From patchwork Tue Nov 13 06:41:36 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679883 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C3C1118F0 for ; Tue, 13 Nov 2018 07:32:25 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8CE852A449 for ; Tue, 13 Nov 2018 07:32:25 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 814F12A44F; Tue, 13 Nov 2018 07:32:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2F0FD2A47F for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id D9042267B9E; Tue, 13 Nov 2018 07:42:58 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 800E3267B92; Tue, 13 Nov 2018 07:42:53 +0100 (CET) Received: from mail-pg1-f196.google.com (mail-pg1-f196.google.com [209.85.215.196]) by alsa0.perex.cz (Postfix) with ESMTP id 7CE93267B3F for ; Tue, 13 Nov 2018 07:42:36 +0100 (CET) Received: by mail-pg1-f196.google.com with SMTP id 32-v6so5232826pgu.2 for ; Mon, 12 Nov 2018 22:42:36 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=f48Xn1HGdncdsarqsNjqZPod00SdE93/OK3yHUqec44=; b=uDcKIt5fSXEM5UiAp/tHXfO3TfrMwOE5VCGb89P+nwQ30C/9ML7ZDR0JkGvv6W5gqh H5zhVjrS8ecnLfz8DzY1Nsl1naFH/ChYLE9xBrWOUGOO/wsanaIyfC3w95Md/45g4Sc1 TJCf/FpX+aO17fq1HAMtIEKBG22nHQZtrUnkB+xu2zyHpsYW9hf/S9LNqaqzODNk7ItX TcaBVQHatn3qdBcw8NMI65+xNVEX2dDJFG3eUGMWx4JRulYm2J3AV8BAoPbzx/ShIRAt iclH+C62+2/ultqpe/w2tZPpkMTuXRawXlexYGaU7T0mF5OmHyiK4dCEbPoxeNcYnEEh KGHA== X-Gm-Message-State: AGRZ1gIp6+dwyANisEIBPCwtf071Wf6SpcI6T/a+12lpxpuMplSJnLag ooVLgxQF1YUFEXy2MjZevxJ+v9JR X-Google-Smtp-Source: AJdET5co7YuAkwvyx21VhzpPeAL9KLwUDJqv1OyF3n41B/BL93G9XVsgK1u+HdXuexWsK/0s2jK4og== X-Received: by 2002:a63:588:: with SMTP id 130mr3585877pgf.273.1542091355505; Mon, 12 Nov 2018 22:42:35 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.33 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:34 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:36 +0900 Message-Id: <20181113064147.13577-24-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 24/35] axfer: add an option to suppress event waiting X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/xfer-libasound-irq-mmap.c | 10 ++++++---- axfer/xfer-libasound-irq-rw.c | 20 ++++++++++++-------- axfer/xfer-libasound.c | 16 ++++++++++++++++ axfer/xfer-libasound.h | 3 +++ 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/axfer/xfer-libasound-irq-mmap.c b/axfer/xfer-libasound-irq-mmap.c index 87ef7e0..18f6dfe 100644 --- a/axfer/xfer-libasound-irq-mmap.c +++ b/axfer/xfer-libasound-irq-mmap.c @@ -81,10 +81,12 @@ static int irq_mmap_process_frames(struct libasound_state *state, snd_pcm_sframes_t consumed_count; int err; - // Wait for hardware IRQ when no avail space in buffer. - err = snd_pcm_wait(state->handle, -1); - if (err < 0) - return err; + if (state->use_waiter) { + // Wait for hardware IRQ when no avail space in buffer. + err = snd_pcm_wait(state->handle, -1); + if (err < 0) + return err; + } // Sync cache in user space to data in kernel space to calculate avail // frames according to the latest positions on PCM buffer. diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c index f05ac4b..625c095 100644 --- a/axfer/xfer-libasound-irq-rw.c +++ b/axfer/xfer-libasound-irq-rw.c @@ -133,10 +133,12 @@ static int r_process_frames_nonblocking(struct libasound_state *state, goto error; } - // Wait for hardware IRQ when no available space. - err = snd_pcm_wait(state->handle, -1); - if (err < 0) - goto error; + if (state->use_waiter) { + // Wait for hardware IRQ when no available space. + err = snd_pcm_wait(state->handle, -1); + if (err < 0) + goto error; + } // Check available space on the buffer. avail = snd_pcm_avail(state->handle); @@ -286,10 +288,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 = snd_pcm_wait(state->handle, -1); - if (err < 0) - goto error; + if (state->use_waiter) { + // Wait for hardware IRQ when no left space. + err = snd_pcm_wait(state->handle, -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 c2e1282..61ae115 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -12,6 +12,7 @@ enum no_short_opts { // 200 or later belong to non us-ascii character set. OPT_FATAL_ERRORS = 200, + OPT_TEST_NOWAIT, }; #define S_OPTS "D:NM" @@ -21,6 +22,7 @@ static const struct option l_opts[] = { {"mmap", 0, 0, 'M'}, // For debugging. {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, + {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, }; static int xfer_libasound_init(struct xfer_context *xfer, @@ -54,6 +56,8 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, state->mmap = true; else if (key == OPT_FATAL_ERRORS) state->finish_at_xrun = true; + else if (key == OPT_TEST_NOWAIT) + state->test_nowait = true; else err = -ENXIO; @@ -80,6 +84,15 @@ int xfer_libasound_validate_opts(struct xfer_context *xfer) return -EINVAL; } + if (state->test_nowait) { + if (!state->nonblock && !state->mmap) { + fprintf(stderr, + "An option for nowait test should be used with " + "nonblock or mmap options.\n"); + return -EINVAL; + } + } + return err; } @@ -124,6 +137,9 @@ static int open_handle(struct xfer_context *xfer) return err; } + if ((state->nonblock || state->mmap) && !state->test_nowait) + state->use_waiter = true; + err = snd_pcm_hw_params_any(state->handle, state->hw_params); if (err < 0) return err; diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 550b1c2..f3ce73f 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -33,6 +33,9 @@ struct libasound_state { bool finish_at_xrun:1; bool nonblock:1; bool mmap:1; + bool test_nowait:1; + + bool use_waiter:1; }; // For internal use in 'libasound' module. From patchwork Tue Nov 13 06:41:37 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679879 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C8B1818F0 for ; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B18D52A44F for ; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A5B502A46A; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 317942A489 for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 29437267B2D; Tue, 13 Nov 2018 07:43:00 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 21FF7267B99; Tue, 13 Nov 2018 07:42:55 +0100 (CET) Received: from mail-pg1-f193.google.com (mail-pg1-f193.google.com [209.85.215.193]) by alsa0.perex.cz (Postfix) with ESMTP id A2465267B62 for ; Tue, 13 Nov 2018 07:42:38 +0100 (CET) Received: by mail-pg1-f193.google.com with SMTP id 32-v6so5232865pgu.2 for ; Mon, 12 Nov 2018 22:42:38 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=q7e5zR4maVxSW9bKqgPgSarZ1ziVFe+zpDf0bRgvOM4=; b=nftxxpV1d/QcI4xErkCgfNqul2AgNpvNUUscvDV2ybDmEedfKGgMBiddpQuoxl/FhK QeCLEfL4Hu7WFfMQhoAEEy4YTYEB8aeP/4eHsvFeCeMKgk4m0MtJVQ/4CA8LVXcVbvQQ NsC5xoLioFbAJxl8fhGyw5hx5hMV/AZ3tDSDIsCxeCEPdrQDDNEgTQ5mJesUqMnpy8Nk JIu/SdD6ovzQ5TjqSOhzpUIhWxLSsp05+g5ldui4umQcJxubRQ/4Eajl/EGk5QfqlA9A t/LOuWdFR3egAD3Hus6YSIAtnPJPeMQ64lJ+3Zqev+0tv1gb4YCUx8aKwYr0aRHmxef9 o0Gg== X-Gm-Message-State: AGRZ1gLJlgKdZlZzJ73EWqrr1/wza71j2qFPHJ2dDDBNBCf9MT9s4JW0 w8bScjo9KTX0P6MBYrdnNm0= X-Google-Smtp-Source: AJdET5dKi4oliRsyokg44j2KBrPSiZazwwLBmEkfytm24CZ7spoV5apJE3/W1kmVm04PoThQbGTPEw== X-Received: by 2002:a62:29c4:: with SMTP id p187-v6mr3921901pfp.62.1542091357223; Mon, 12 Nov 2018 22:42:37 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.35 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:36 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:37 +0900 Message-Id: <20181113064147.13577-25-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 25/35] axfer: add options for buffer arrangement X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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. However, these calculation should be reconsidered somehow. Signed-off-by: Takashi Sakamoto --- axfer/xfer-libasound.c | 122 +++++++++++++++++++++++++++++++++++++++-- axfer/xfer-libasound.h | 5 ++ axfer/xfer-options.c | 2 + 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 61ae115..a23021e 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -11,15 +11,21 @@ enum no_short_opts { // 200 or later belong to non us-ascii character set. - OPT_FATAL_ERRORS = 200, + OPT_PERIOD_SIZE = 200, + OPT_BUFFER_SIZE, + OPT_FATAL_ERRORS, OPT_TEST_NOWAIT, }; -#define S_OPTS "D:NM" +#define S_OPTS "D:NMF:B:" static const struct option l_opts[] = { {"device", 1, 0, 'D'}, {"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. {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, @@ -54,6 +60,14 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, state->nonblock = true; else if (key == 'M') state->mmap = true; + else if (key == 'F') + state->msec_per_period = arg_parse_decimal_num(optarg, &err); + else if (key == 'B') + state->msec_per_buffer = arg_parse_decimal_num(optarg, &err); + else if (key == OPT_PERIOD_SIZE) + state->frames_per_period = arg_parse_decimal_num(optarg, &err); + else if (key == OPT_BUFFER_SIZE) + state->frames_per_buffer = arg_parse_decimal_num(optarg, &err); else if (key == OPT_FATAL_ERRORS) state->finish_at_xrun = true; else if (key == OPT_TEST_NOWAIT) @@ -93,6 +107,20 @@ int xfer_libasound_validate_opts(struct xfer_context *xfer) } } + if (state->msec_per_period > 0 && state->msec_per_buffer > 0) { + if (state->msec_per_period > state->msec_per_buffer) { + state->msec_per_period = state->msec_per_buffer; + state->msec_per_buffer = 0; + } + } + + if (state->frames_per_period > 0 && state->frames_per_buffer > 0) { + if (state->frames_per_period > state->frames_per_buffer) { + state->frames_per_period = state->frames_per_buffer; + state->frames_per_buffer = 0; + } + } + return err; } @@ -161,7 +189,11 @@ static int open_handle(struct xfer_context *xfer) 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; @@ -232,6 +264,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); } @@ -287,7 +397,11 @@ 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, + state->msec_per_period, + state->msec_per_buffer, + state->frames_per_period, + state->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 f3ce73f..4456fab 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -30,6 +30,11 @@ struct libasound_state { char *node_literal; + unsigned int msec_per_period; + unsigned int msec_per_buffer; + unsigned int frames_per_period; + unsigned int frames_per_buffer; + bool finish_at_xrun:1; bool nonblock:1; bool mmap:1; diff --git a/axfer/xfer-options.c b/axfer/xfer-options.c index 9233647..d899134 100644 --- a/axfer/xfer-options.c +++ b/axfer/xfer-options.c @@ -17,6 +17,8 @@ enum no_short_opts { // 128 or later belong to non us-ascii character set. OPT_XFER_TYPE = 128, OPT_DUMP_HW_PARAMS, + OPT_PERIOD_SIZE, + OPT_BUFFER_SIZE, // Obsoleted. OPT_MAX_FILE_TIME, }; From patchwork Tue Nov 13 06:41:38 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679871 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C05133CF1 for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A98622A449 for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 9DDC22A49A; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E04462A478 for ; Tue, 13 Nov 2018 07:32:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 53307267AB1; Tue, 13 Nov 2018 07:43:05 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id A70D3267BA5; Tue, 13 Nov 2018 07:42:59 +0100 (CET) Received: from mail-pl1-f194.google.com (mail-pl1-f194.google.com [209.85.214.194]) by alsa0.perex.cz (Postfix) with ESMTP id C6404267ABC for ; Tue, 13 Nov 2018 07:42:39 +0100 (CET) Received: by mail-pl1-f194.google.com with SMTP id w24-v6so5526694plq.3 for ; Mon, 12 Nov 2018 22:42:39 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=c7M8lpLy6YS1m7+R6g/yv0pb+IThpFUKe4OU95wm6iw=; b=SYpr6k3ULgspv2BETkboVWlOXU8KhyCewQnd8Y2AEXM147K0RthzE1yVbXIavrk7qt zKfs7IGzO5RmpcZA2O6E4x63rp0bR3ap6vJ9q6FBkGBrmIfBa4q/Rc8RbQdXbbuVR7ET HS8xsSXhb+rtKoXRyh04Wo8syWs+vypAwxMppvWGxuwI2ohdFUdr4fbWP4DlQ7IZULfk YX97TQSxQxnZ4wGORCH/GnaZn9nRJYvkJ61bVjFgKGaSqSJDHxYMsyiQd4/k58BVAuZ0 HGTiZaawHGxE4A3l1DIlItYq0K4QVlRn4anuo8CqUVM+y6UKC6HD0gHNl11Uk4gFFYks so2Q== X-Gm-Message-State: AGRZ1gJ1ChPPKRJOKId1qDUxwYb0YwdcJQyssiGF6WsxDu7pdx2+C6Z4 bUvUqFmePdeXlqwrcMrL1OA= X-Google-Smtp-Source: AJdET5flHYQoFynOeDFwmP8tb98BCavyJo110QW0InRLbXgF4dOcNiJR2R5LEsbKYdUeOwbqBZuMOA== X-Received: by 2002:a17:902:14e:: with SMTP id 72-v6mr3748021plb.299.1542091358932; Mon, 12 Nov 2018 22:42:38 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.37 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:38 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:38 +0900 Message-Id: <20181113064147.13577-26-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 26/35] axfer: add options for software parameters of PCM substream X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/xfer-libasound.c | 86 ++++++++++++++++++++++++++++++++++++++++-- axfer/xfer-libasound.h | 4 ++ 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index a23021e..1e709e0 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -17,7 +17,7 @@ enum no_short_opts { OPT_TEST_NOWAIT, }; -#define S_OPTS "D:NMF:B:" +#define S_OPTS "D:NMF:B:A:R:T:" static const struct option l_opts[] = { {"device", 1, 0, 'D'}, {"nonblock", 0, 0, 'N'}, @@ -26,6 +26,9 @@ static const struct option l_opts[] = { {"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. {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, @@ -68,6 +71,12 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, state->frames_per_period = arg_parse_decimal_num(optarg, &err); else if (key == OPT_BUFFER_SIZE) state->frames_per_buffer = arg_parse_decimal_num(optarg, &err); + else if (key == 'A') + state->msec_for_avail_min = arg_parse_decimal_num(optarg, &err); + else if (key == 'R') + state->msec_for_start_threshold = arg_parse_decimal_num(optarg, &err); + else if (key == 'T') + state->msec_for_stop_threshold = arg_parse_decimal_num(optarg, &err); else if (key == OPT_FATAL_ERRORS) state->finish_at_xrun = true; else if (key == OPT_TEST_NOWAIT) @@ -377,8 +386,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); } @@ -444,7 +521,10 @@ 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, + state->msec_for_avail_min, + state->msec_for_start_threshold, + state->msec_for_stop_threshold); if (err < 0) { logging(state, "Current software parameters:\n"); snd_pcm_sw_params_dump(state->sw_params, state->log); diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 4456fab..113c1b9 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -35,6 +35,10 @@ struct libasound_state { 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; + bool finish_at_xrun:1; bool nonblock:1; bool mmap:1; From patchwork Tue Nov 13 06:41:39 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679945 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 73DCD46E4 for ; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 6070B2945E for ; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 5496729647; Tue, 13 Nov 2018 07:37:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D48DA2A45C for ; Tue, 13 Nov 2018 07:37:22 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id D9466267B35; Tue, 13 Nov 2018 07:43:03 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 7A1D3267BA6; Tue, 13 Nov 2018 07:42:59 +0100 (CET) Received: from mail-pl1-f195.google.com (mail-pl1-f195.google.com [209.85.214.195]) by alsa0.perex.cz (Postfix) with ESMTP id 6FF92267B2D for ; Tue, 13 Nov 2018 07:42:41 +0100 (CET) Received: by mail-pl1-f195.google.com with SMTP id t13so2542765ply.13 for ; Mon, 12 Nov 2018 22:42:41 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=0qmHKGLUXAQqnH80sydCMD8BmJtQqXrJf01DzpjuObE=; b=Dg37MQ8YGFzGOr0FbpzjLJqkR1vUuX3HU+YjxISVLA73WxHkg0z0fHyoj3IVzgGPZ6 S8283BgHRfX6d1a0TKixlt5F0L9UJNp8E+AAEhOjbDTBGkLmm71xJocD6jcfaAql0JoQ SbW6pRf6neYZgWmZoMxlGBw6GulM3KU0OiMJla/c1667T5N8/2zkQ3vX94kg1d7QEctZ lVqU2KO7tdb8ivuorjVL7IIGpE+VK5NWJubLaG/06xESMvuVvZJGda1wF+INwGwwfrnr qguyIgvKZhOjwdCcM/CDcV17bp4u+Xa4oGRUFaGmcFcgpeMgU6gZuVqbUlzyjJC+j3Pm zfZQ== X-Gm-Message-State: AGRZ1gK1rUnr4XgvwkQ4KnQYn3pTgoMgPmHdRcJ4URNo/0PnkVnrHAPj MQCZq+S6K5dC1XCBq6GzyAY= X-Google-Smtp-Source: AJdET5eC3ReBJSyiLItiWePWw2uALwNTORbepkvRUEMClHdJ1bzd+EyMxxVWtjdE/wW+t/CMfzRvwQ== X-Received: by 2002:a17:902:8543:: with SMTP id d3-v6mr3857608plo.313.1542091360645; Mon, 12 Nov 2018 22:42:40 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.39 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:40 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:39 +0900 Message-Id: <20181113064147.13577-27-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 27/35] axfer: add options for plugins in alsa-lib X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/xfer-libasound.c | 25 +++++++++++++++++++++++++ axfer/xfer-libasound.h | 4 ++++ 2 files changed, 29 insertions(+) diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 1e709e0..a731423 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -13,6 +13,10 @@ enum no_short_opts { // 200 or later belong to non us-ascii character set. OPT_PERIOD_SIZE = 200, OPT_BUFFER_SIZE, + OPT_DISABLE_RESAMPLE, + OPT_DISABLE_CHANNELS, + OPT_DISABLE_FORMAT, + OPT_DISABLE_SOFTVOL, OPT_FATAL_ERRORS, OPT_TEST_NOWAIT, }; @@ -29,6 +33,11 @@ static const struct option l_opts[] = { {"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. {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, @@ -77,6 +86,14 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, state->msec_for_start_threshold = arg_parse_decimal_num(optarg, &err); else if (key == 'T') state->msec_for_stop_threshold = arg_parse_decimal_num(optarg, &err); + else if (key == OPT_DISABLE_RESAMPLE) + state->no_auto_resample = true; + else if (key == OPT_DISABLE_CHANNELS) + state->no_auto_channels = true; + else if (key == OPT_DISABLE_FORMAT) + state->no_auto_format = true; + else if (key == OPT_DISABLE_SOFTVOL) + state->no_softvol = true; else if (key == OPT_FATAL_ERRORS) state->finish_at_xrun = true; else if (key == OPT_TEST_NOWAIT) @@ -164,6 +181,14 @@ static int open_handle(struct xfer_context *xfer) if (state->nonblock) mode |= SND_PCM_NONBLOCK; + if (state->no_auto_resample) + mode |= SND_PCM_NO_AUTO_RESAMPLE; + if (state->no_auto_channels) + mode |= SND_PCM_NO_AUTO_CHANNELS; + if (state->no_auto_format) + mode |= SND_PCM_NO_AUTO_FORMAT; + if (state->no_softvol) + mode |= SND_PCM_NO_SOFTVOL; err = snd_pcm_open(&state->handle, state->node_literal, xfer->direction, mode); diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 113c1b9..cf940e5 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -43,6 +43,10 @@ struct libasound_state { bool nonblock:1; bool mmap:1; bool test_nowait:1; + bool no_auto_resample:1; + bool no_auto_channels:1; + bool no_auto_format:1; + bool no_softvol:1; bool use_waiter:1; }; From patchwork Tue Nov 13 06:41:40 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679877 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 6E9424B7E for ; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 550332A44F for ; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 499012A46A; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2A4AB2A44F for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id A2356267BA9; Tue, 13 Nov 2018 07:43:06 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id D383C267BAD; Tue, 13 Nov 2018 07:43:02 +0100 (CET) Received: from mail-pl1-f196.google.com (mail-pl1-f196.google.com [209.85.214.196]) by alsa0.perex.cz (Postfix) with ESMTP id 91F2F267B6C for ; Tue, 13 Nov 2018 07:42:43 +0100 (CET) Received: by mail-pl1-f196.google.com with SMTP id t13so2542810ply.13 for ; Mon, 12 Nov 2018 22:42:43 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=MXyklDhQzsIKShcC9unUCeqx2QlVv2VvQeVdiDyFVUg=; b=My5as93+ZMHjOHQOpBLuhnXY1qbCRKCT8IwEhLCqP749hbh27nKayWDcHpyy2bYmqR DijArJCpbsfdw2jPR4dCsRR9fChb+7Zkta5N7JXBYOSbDv6iNz4F+XO1G9NtUYC9FLaT jRFQ1fEjwhYATpUb2gZ+pD+NERIoIicEiqScDp1TxrMg+S+ON/Ev06TtO8Pdme3rfQtL 3Lyx4o4R4bXPJPgK1ZaxOEM3sZ5dz6M2mOaaVtkX3awV0+r7wyhNMf7+WmKPPgwdXju0 5r2+yVG5r1DpmhknD0NbuYhbqFUYH5sB32FgQEet3FvTWAj7OZMWT5aFzlMb+I4/xejR s3gg== X-Gm-Message-State: AGRZ1gI7d3EBm20UtOcijFKBv2fpEnVtjP+zaGbY8vPX0tr3L+0EGgim 2QFhrsbI+b1Sknt4R7BCfzs= X-Google-Smtp-Source: AJdET5fQJNOnWi1IaAAZsZq11rjUcdymvAhGJGXeXMk1R4NxD5wQmidW7dsktDk20hu4Q4b1Ihoqgg== X-Received: by 2002:a17:902:404:: with SMTP id 4-v6mr3930752ple.331.1542091362372; Mon, 12 Nov 2018 22:42:42 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.40 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:41 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:40 +0900 Message-Id: <20181113064147.13577-28-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 28/35] axfer: add a common interface of waiter for I/O event notification X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- axfer/Makefile.am | 5 +- axfer/waiter.c | 99 +++++++++++++++++++++++++++++++++ axfer/waiter.h | 54 ++++++++++++++++++ axfer/xfer-libasound-irq-mmap.c | 14 ++++- axfer/xfer-libasound-irq-rw.c | 26 ++++++++- axfer/xfer-libasound.c | 76 +++++++++++++++++++++++++ axfer/xfer-libasound.h | 7 +++ 7 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 axfer/waiter.c create mode 100644 axfer/waiter.h diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 960811e..e425a28 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -22,6 +22,7 @@ noinst_HEADERS = \ xfer.h \ xfer-libasound.h \ frame-cache.h + waiter.h axfer_SOURCES = \ misc.h \ @@ -47,4 +48,6 @@ axfer_SOURCES = \ frame-cache.c \ xfer-libasound-irq-rw.c \ subcmd-transfer.c \ - xfer-libasound-irq-mmap.c + xfer-libasound-irq-mmap.c \ + waiter.h \ + waiter.c diff --git a/axfer/waiter.c b/axfer/waiter.c new file mode 100644 index 00000000..0bc4740 --- /dev/null +++ b/axfer/waiter.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// waiter.c - I/O event waiter. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "waiter.h" + +#include +#include +#include + +#include "misc.h" + +static const char *const waiter_type_labels[] = { + [WAITER_TYPE_DEFAULT] = "default", +}; + +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_DEFAULT; +} + +const char *waiter_label_from_type(enum waiter_type type) +{ + return waiter_type_labels[type]; +} + +int waiter_context_init(struct waiter_context *waiter, + enum waiter_type type, unsigned int pfd_count) +{ + struct { + enum waiter_type type; + const struct waiter_data *waiter; + } entries[] = { + {WAITER_TYPE_COUNT, NULL}, + }; + int i; + + if (pfd_count == 0) + return -EINVAL; + + 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; + + waiter->pfds = calloc(pfd_count, sizeof(*waiter->pfds)); + if (waiter->pfds == NULL) + return -ENOMEM; + waiter->pfd_count = pfd_count; + + return 0; +} + +int waiter_context_prepare(struct waiter_context *waiter) +{ + return waiter->ops->prepare(waiter); +} + +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->pfds) + free(waiter->pfds); + waiter->pfd_count = 0; + 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..17e01cb --- /dev/null +++ b/axfer/waiter.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// waiter.h - a header for I/O event waiter. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#ifndef __ALSA_UTILS_AXFER_WAITER__H_ +#define __ALSA_UTILS_AXFER_WAITER__H_ + +#include + +enum waiter_type { + WAITER_TYPE_DEFAULT = 0, + WAITER_TYPE_COUNT, +}; + +struct waiter_ops; + +struct waiter_context { + enum waiter_type type; + const struct waiter_ops *ops; + void *private_data; + + struct pollfd *pfds; + unsigned int pfd_count; +}; + +enum waiter_type waiter_type_from_label(const char *label); +const char *waiter_label_from_type(enum waiter_type type); + +int waiter_context_init(struct waiter_context *waiter, + enum waiter_type type, unsigned int pfd_count); +int waiter_context_prepare(struct waiter_context *waiter); +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 (*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 diff --git a/axfer/xfer-libasound-irq-mmap.c b/axfer/xfer-libasound-irq-mmap.c index 18f6dfe..0c96ee5 100644 --- a/axfer/xfer-libasound-irq-mmap.c +++ b/axfer/xfer-libasound-irq-mmap.c @@ -82,10 +82,22 @@ static int irq_mmap_process_frames(struct libasound_state *state, int err; if (state->use_waiter) { + unsigned short revents; + // Wait for hardware IRQ when no avail space in buffer. - err = snd_pcm_wait(state->handle, -1); + err = xfer_libasound_wait_event(state, -1, &revents); if (err < 0) return err; + if (revents & POLLERR) { + // TODO: error reporting? + return -EIO; + } + if (!(revents & (POLLIN | POLLOUT))) + return -EAGAIN; + + // 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. } // Sync cache in user space to data in kernel space to calculate avail diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c index 625c095..cf4155f 100644 --- a/axfer/xfer-libasound-irq-rw.c +++ b/axfer/xfer-libasound-irq-rw.c @@ -134,10 +134,21 @@ static int r_process_frames_nonblocking(struct libasound_state *state, } if (state->use_waiter) { + unsigned short revents; + // Wait for hardware IRQ when no available space. - err = snd_pcm_wait(state->handle, -1); + err = xfer_libasound_wait_event(state, -1, &revents); if (err < 0) goto error; + if (revents & POLLERR) { + // TODO: error reporting. + err = -EIO; + goto error; + } + if (!(revents & POLLIN)) { + err = -EAGAIN; + goto error; + } } // Check available space on the buffer. @@ -289,10 +300,21 @@ static int w_process_frames_nonblocking(struct libasound_state *state, int err; if (state->use_waiter) { + unsigned short revents; + // Wait for hardware IRQ when no left space. - err = snd_pcm_wait(state->handle, -1); + err = xfer_libasound_wait_event(state, -1, &revents); if (err < 0) goto error; + if (revents & POLLERR) { + // TODO: error reporting. + err = -EIO; + goto error; + } + if (!(revents & POLLOUT)) { + err = -EAGAIN; + goto error; + } } // Check available space on the buffer. diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index a731423..5cef9f1 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -201,6 +201,7 @@ static int open_handle(struct xfer_context *xfer) if ((state->nonblock || state->mmap) && !state->test_nowait) state->use_waiter = true; + state->waiter_type = WAITER_TYPE_DEFAULT; err = snd_pcm_hw_params_any(state->handle, state->hw_params); if (err < 0) @@ -220,6 +221,66 @@ static int open_handle(struct xfer_context *xfer) return set_access_hw_param(state); } +static int prepare_waiter(struct libasound_state *state) +{ + unsigned int pfd_count; + int err; + + // Nothing to do for dafault waiter (=snd_pcm_wait()). + if (state->waiter_type == WAITER_TYPE_DEFAULT) + return 0; + + err = snd_pcm_poll_descriptors_count(state->handle); + if (err < 0) + return err; + if (err == 0) + return -ENXIO; + pfd_count = (unsigned int)err; + + state->waiter = malloc(sizeof(*state->waiter)); + if (state->waiter == NULL) + return -ENOMEM; + + err = waiter_context_init(state->waiter, state->waiter_type, pfd_count); + if (err < 0) + return err; + + err = snd_pcm_poll_descriptors(state->handle, state->waiter->pfds, + pfd_count); + if (err < 0) + return err; + + return waiter_context_prepare(state->waiter); +} + +int xfer_libasound_wait_event(struct libasound_state *state, int timeout_msec, + unsigned short *revents) +{ + int err; + + if (state->waiter_type != WAITER_TYPE_DEFAULT) { + struct waiter_context *waiter = state->waiter; + + err = waiter_context_wait_event(waiter, timeout_msec); + if (err < 0) + return err; + + err = snd_pcm_poll_descriptors_revents(state->handle, + waiter->pfds, waiter->pfd_count, revents); + } else { + err = snd_pcm_wait(state->handle, timeout_msec); + if (err < 0) + return err; + + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_PLAYBACK) + *revents = POLLOUT; + else + *revents = POLLIN; + } + + return err; +} + static int configure_hw_params(struct libasound_state *state, snd_pcm_format_t format, unsigned int samples_per_frame, @@ -559,6 +620,21 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, if (xfer->verbose > 0) snd_pcm_dump(state->handle, state->log); + if (state->use_waiter) { + // NOTE: This should be after configuring sw_params due to + // timer descriptor for time-based scheduling model. + err = prepare_waiter(state); + if (err < 0) + return err; + + if (xfer->verbose > 0) { + logging(state, "Waiter type:\n"); + logging(state, + " %s\n", + waiter_label_from_type(state->waiter_type)); + } + } + return 0; } diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index cf940e5..0bcfb40 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__) @@ -49,6 +50,9 @@ struct libasound_state { bool no_softvol:1; bool use_waiter:1; + + enum waiter_type waiter_type; + struct waiter_context *waiter; }; // For internal use in 'libasound' module. @@ -63,6 +67,9 @@ struct xfer_libasound_ops { unsigned int private_size; }; +int xfer_libasound_wait_event(struct libasound_state *state, int timeout_msec, + unsigned short *revents); + extern const struct xfer_libasound_ops xfer_libasound_irq_rw_ops; extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops; From patchwork Tue Nov 13 06:41:41 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679935 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5D64D3CF1 for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 49A9F2A46A for ; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 3E6822A4A9; Tue, 13 Nov 2018 07:37:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 198AD2A47A for ; Tue, 13 Nov 2018 07:37:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id E5462267BA8; Tue, 13 Nov 2018 07:43:07 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 40005267BAC; Tue, 13 Nov 2018 07:43:05 +0100 (CET) Received: from mail-pf1-f194.google.com (mail-pf1-f194.google.com [209.85.210.194]) by alsa0.perex.cz (Postfix) with ESMTP id 2858A267AA4 for ; Tue, 13 Nov 2018 07:42:44 +0100 (CET) Received: by mail-pf1-f194.google.com with SMTP id v76-v6so2716766pfa.5 for ; Mon, 12 Nov 2018 22:42:44 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=HIinXAtEIrT1FF2asVenjDrjDDN+ftpqfQqeOawUTYc=; b=R/akYgCN93pP6XK0K+K8lm4xL5C9U8+YT24H7Hm2wW0LTw2wkAKn+xM5wgFYSa31lK sIMiY8zVLBNPfjkYj46FVnPiaGu3TNTD1oah+FNTSS1+Ear9nKIChChRoio5CgwaSOdV pTcp125RsheZPrdAdC+Bz0h59w4lXRTVK1ZlOd2laIgn3gVoSvI9P/OOCxb1zLpg722O KgToW8JdZVsAJBemMGNNoIAATFEjqGoZsw8++qVVIoVNtzdCWxc3/PZd6uDuGjYWEYoW o/wXK+sN/TpAA93IgFHaVGEXe46cOPsnkBchW2dypS79bn9O0mBdKQ6NzCEWHy/Mxw+U N//A== X-Gm-Message-State: AGRZ1gImBke3pKR6vBaJYRKzxqkfNzCyC9YQx1GIZkrGAYnOxXk/zy8i aKeiTZFM5wnd9PXPwHgHMEI= X-Google-Smtp-Source: AJdET5fuE3123vO6thq/jjIoDNnRZW0aHWsDDPP5SDtEPspkdtlk9szG4fonH51LXNm+7k+7gtYWkA== X-Received: by 2002:a63:4f20:: with SMTP id d32mr3642407pgb.47.1542091364188; Mon, 12 Nov 2018 22:42:44 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.42 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:43 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:41 +0900 Message-Id: <20181113064147.13577-29-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 29/35] axfer: add an option for waiter type X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP This commit is an integration to add an option for users to choose waiter type. Users give the type to value to '--waiter-type' ('-w') option to choose it. Currently, 'snd_pcm_wait()' is just supported as a default. This alsa-lib API is implemented with a call of poll(2). Signed-off-by: Takashi Sakamoto --- axfer/xfer-libasound.c | 26 +++++++++++++++++++++++++- axfer/xfer-libasound.h | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index 5cef9f1..fa72839 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -13,6 +13,7 @@ enum no_short_opts { // 200 or later belong to non us-ascii character set. OPT_PERIOD_SIZE = 200, OPT_BUFFER_SIZE, + OPT_WAITER_TYPE, OPT_DISABLE_RESAMPLE, OPT_DISABLE_CHANNELS, OPT_DISABLE_FORMAT, @@ -33,6 +34,7 @@ static const struct option l_opts[] = { {"avail-min", 1, 0, 'A'}, {"start-delay", 1, 0, 'R'}, {"stop-delay", 1, 0, 'T'}, + {"waiter-type", 1, 0, OPT_WAITER_TYPE}, // For plugins in alsa-lib. {"disable-resample", 0, 0, OPT_DISABLE_RESAMPLE}, {"disable-channels", 0, 0, OPT_DISABLE_CHANNELS}, @@ -86,6 +88,8 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, state->msec_for_start_threshold = arg_parse_decimal_num(optarg, &err); else if (key == 'T') state->msec_for_stop_threshold = arg_parse_decimal_num(optarg, &err); + else if (key == OPT_WAITER_TYPE) + state->waiter_type_literal = arg_duplicate_string(optarg, &err); else if (key == OPT_DISABLE_RESAMPLE) state->no_auto_resample = true; else if (key == OPT_DISABLE_CHANNELS) @@ -147,6 +151,25 @@ int xfer_libasound_validate_opts(struct xfer_context *xfer) } } + if (state->waiter_type_literal != NULL) { + if (state->test_nowait) { + fprintf(stderr, + "An option for waiter type should not be " + "used with nowait test option.\n"); + return -EINVAL; + } + if (!state->nonblock && !state->mmap) { + fprintf(stderr, + "An option for waiter type should be used " + "with nonblock or mmap options.\n"); + return -EINVAL; + } + state->waiter_type = + waiter_type_from_label(state->waiter_type_literal); + } else { + state->waiter_type = WAITER_TYPE_DEFAULT; + } + return err; } @@ -201,7 +224,6 @@ static int open_handle(struct xfer_context *xfer) if ((state->nonblock || state->mmap) && !state->test_nowait) state->use_waiter = true; - state->waiter_type = WAITER_TYPE_DEFAULT; err = snd_pcm_hw_params_any(state->handle, state->hw_params); if (err < 0) @@ -751,7 +773,9 @@ static void xfer_libasound_destroy(struct xfer_context *xfer) struct libasound_state *state = xfer->private_data; free(state->node_literal); + free(state->waiter_type_literal); state->node_literal = NULL; + state->waiter_type_literal = NULL; if (state->hw_params) snd_pcm_hw_params_free(state->hw_params); diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 0bcfb40..20b0d74 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -30,6 +30,7 @@ struct libasound_state { bool verbose; char *node_literal; + char *waiter_type_literal; unsigned int msec_per_period; unsigned int msec_per_buffer; From patchwork Tue Nov 13 06:41:42 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679867 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 3E1331747 for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 246112A440 for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 17C122A47A; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DE6772A44F for ; Tue, 13 Nov 2018 07:32:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 9FCD5267BBC; Tue, 13 Nov 2018 07:43:11 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id C0DFE267BB2; Tue, 13 Nov 2018 07:43:07 +0100 (CET) Received: from mail-pf1-f193.google.com (mail-pf1-f193.google.com [209.85.210.193]) by alsa0.perex.cz (Postfix) with ESMTP id DF08E267B80 for ; Tue, 13 Nov 2018 07:42:46 +0100 (CET) Received: by mail-pf1-f193.google.com with SMTP id e22-v6so5537318pfn.8 for ; Mon, 12 Nov 2018 22:42:46 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=SA3i7WKo4ZEmKTPrrvrzQ55u93vIv2T+0PFtaIW1ZJM=; b=ZYacChfTLiN9i8opsEKkfVT8fhYeV0KHqyddzZvErC+niSxPqgcpnEvVtrcmV4PPXi 9fueNRIj68oq4bqQzipxZOHDeDU4ovLvHCRf5cpUrydQuJG+mnCD6IXa16R7f1Y69xHZ ak52ZYHOso44RtSlt1zTthCDfezb1BUE3Lny0vGVjINhemd1zB7rLIHS8+8sB4IdgMFH LSAhYLCp4yzVgmWoGzJhdcNM5i1CVeHDjVdUjAgVGnmnADO4ALJDc5Q7p9/0jqtmbVDe uMDdI7FV13f5iwGg5N8t/onVp5HilShb7DGIhngO2tl8zxVj6n2uLh75VY6DzQDX/w0l p4kw== X-Gm-Message-State: AGRZ1gLvCwF8Jx63jPwF8CYVRZnZ4O+Lg2ej2R9i51fQhtwla7vAWknh HCYEIThcowQknJN4gI/7bME= X-Google-Smtp-Source: AJdET5eLYNCr32T48vosaT9oYQCtymhvNNlfCQNZymuJHOTyBzKmp6EEQDksbKkPNDrOD2uEyabziQ== X-Received: by 2002:a63:e156:: with SMTP id h22mr3666253pgk.255.1542091365903; Mon, 12 Nov 2018 22:42:45 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.44 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:45 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:42 +0900 Message-Id: <20181113064147.13577-30-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 30/35] axfer: add an implementation of waiter for poll(2) X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP This commit adds support of waiter for poll(2) system call. Below lines are examples to use this option: $ axfer transfer --waiter-type=poll -M -P -d 2 -D hw:0,3 /dev/urandom -f dat -vvv $ axfer transfer --waiter-type=poll -M -C -d 2 -D hw:1,0 /dev/null -r 48000 -vvv Signed-off-by: Takashi Sakamoto --- axfer/Makefile.am | 3 ++- axfer/waiter-poll.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ axfer/waiter.c | 3 ++- axfer/waiter.h | 3 +++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 axfer/waiter-poll.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index e425a28..7123006 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -50,4 +50,5 @@ axfer_SOURCES = \ subcmd-transfer.c \ xfer-libasound-irq-mmap.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..61aa1a0 --- /dev/null +++ b/axfer/waiter-poll.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// waiter-poll.c - Waiter for event notification by poll(2). +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "waiter.h" +#include "misc.h" + +#include +#include +#include + +static int poll_prepare(struct waiter_context *waiter) +{ + // Nothing to do because an instance of waiter has required data. + return 0; +} + +static int poll_wait_event(struct waiter_context *waiter, int timeout_msec) +{ + int err; + + err = poll(waiter->pfds, waiter->pfd_count, timeout_msec); + if (err < 0) + return -errno; + + return err; +} + +static void poll_release(struct waiter_context *waiter) +{ + // Nothing to do because an instance of waiter has required data. + return; +} + +const struct waiter_data waiter_poll = { + .ops = { + .prepare = poll_prepare, + .wait_event = poll_wait_event, + .release = poll_release, + }, +}; diff --git a/axfer/waiter.c b/axfer/waiter.c index 0bc4740..446e617 100644 --- a/axfer/waiter.c +++ b/axfer/waiter.c @@ -16,6 +16,7 @@ static const char *const waiter_type_labels[] = { [WAITER_TYPE_DEFAULT] = "default", + [WAITER_TYPE_POLL] = "poll", }; enum waiter_type waiter_type_from_label(const char *label) @@ -42,7 +43,7 @@ int waiter_context_init(struct waiter_context *waiter, 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 17e01cb..9366724 100644 --- a/axfer/waiter.h +++ b/axfer/waiter.h @@ -13,6 +13,7 @@ enum waiter_type { WAITER_TYPE_DEFAULT = 0, + WAITER_TYPE_POLL, WAITER_TYPE_COUNT, }; @@ -51,4 +52,6 @@ struct waiter_data { unsigned int private_size; }; +extern const struct waiter_data waiter_poll; + #endif From patchwork Tue Nov 13 06:41:43 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679885 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 6E3321747 for ; Tue, 13 Nov 2018 07:32:25 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 54F782A440 for ; Tue, 13 Nov 2018 07:32:25 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 491712A479; Tue, 13 Nov 2018 07:32:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 3B2A72A440 for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 04FB8267BC2; Tue, 13 Nov 2018 07:43:13 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 1D5B8267BB5; Tue, 13 Nov 2018 07:43:08 +0100 (CET) Received: from mail-pg1-f193.google.com (mail-pg1-f193.google.com [209.85.215.193]) by alsa0.perex.cz (Postfix) with ESMTP id 97277267AC2 for ; Tue, 13 Nov 2018 07:42:48 +0100 (CET) Received: by mail-pg1-f193.google.com with SMTP id w7so5211998pgp.13 for ; Mon, 12 Nov 2018 22:42:48 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=4Aoxke0Omvf0OxUYA/2Ze/LWJzj8TtYzMPST2Y7gM8w=; b=blVG+4t9rMX+obIZ5e2PMt6twOsDC77gcJHNAu3qOmOzyjmDMROoI+qvTmUCgpH3X9 rbjWhYh3FSowbcVhf2Ffx0e5sjh29VkTg3/BBhWMJ71gczXaSY8ySK3YAdCFQkth/A6k tPhmp88kgtE8okXVj/oyHTMZyWIyQtT3SlQgKScjS2Ak75yuLM3NGqH+aZ5vVa+iSAfm 26szY6Hfo7h3Vq02Ah9lkoz/ucCXp4mxLofqi0kg33SBLoiM93f3SOae97oH8JK0pn62 bFCdCsTRjwa112sLNee6JEfuWVWwaEeF9B8w8WQU/nzipN5yxGbsQNUO4f8wUoJay2rl dpNg== X-Gm-Message-State: AGRZ1gJSJ2N+3Dms4jveSJbKDF40kyieAmygReAdAOsVlY+7WrS3LGZl 2sqhgVsDSs1bzLENpt3mRplrgTAY X-Google-Smtp-Source: AJdET5cYFZh1HSksfSSHu/LxdBar5Z9tV8n/HxuWuUcj0eNDeubpnykftNupGGf4P9S7YgPun0WHtQ== X-Received: by 2002:a63:fc22:: with SMTP id j34-v6mr3699007pgi.434.1542091367640; Mon, 12 Nov 2018 22:42:47 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.46 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:47 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:43 +0900 Message-Id: <20181113064147.13577-31-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 31/35] axfer: add an implementation of waiter for select(2) X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP This commit adds support of waiter for select(2) system call. Below lines are examples to use this option: $ axfer transfer --waiter-type=select -M -P -d 2 -D hw:0,3 /dev/urandom -f dat -vvv $ axfer transfer --waiter-type=select -M -C -d 2 -D hw:1,0 /dev/null -r 48000 -vvv Signed-off-by: Takashi Sakamoto --- axfer/Makefile.am | 3 +- axfer/waiter-select.c | 100 ++++++++++++++++++++++++++++++++++++++++++ axfer/waiter.c | 2 + axfer/waiter.h | 2 + 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 axfer/waiter-select.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 7123006..867007b 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -51,4 +51,5 @@ axfer_SOURCES = \ xfer-libasound-irq-mmap.c \ waiter.h \ waiter.c \ - waiter-poll.c + waiter-poll.c \ + waiter-select.c diff --git a/axfer/waiter-select.c b/axfer/waiter-select.c new file mode 100644 index 00000000..a35ea85 --- /dev/null +++ b/axfer/waiter-select.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// waiter-select.c - Waiter for event notification by select(2). +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "waiter.h" + +#include +#include +#include +#include +#include + +// Except for POLLERR. +#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP) +#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT) +#define POLLEX_SET (POLLPRI) + +struct select_state { + fd_set rfds_rd; + fd_set rfds_wr; + fd_set rfds_ex; +}; + +static int select_prepare(struct waiter_context *waiter) +{ + return 0; +} + +static int select_wait_event(struct waiter_context *waiter, int timeout_msec) +{ + struct select_state *state = waiter->private_data; + struct pollfd *pfd; + int fd_max; + struct timeval tv, *tv_ptr; + int i; + int err; + + FD_ZERO(&state->rfds_rd); + FD_ZERO(&state->rfds_wr); + FD_ZERO(&state->rfds_ex); + + fd_max = 0; + for (i = 0; i < waiter->pfd_count; ++i) { + pfd = &waiter->pfds[i]; + + if (pfd->events & POLLIN_SET) + FD_SET(pfd->fd, &state->rfds_rd); + if (pfd->events & POLLOUT_SET) + FD_SET(pfd->fd, &state->rfds_wr); + if (pfd->events & POLLEX_SET) + FD_SET(pfd->fd, &state->rfds_ex); + if (pfd->fd > fd_max) + fd_max = pfd->fd; + } + + if (timeout_msec < 0) { + tv_ptr = NULL; + } else { + tv.tv_sec = 0; + tv.tv_usec = timeout_msec * 1000; + tv_ptr = &tv; + } + + err = select(fd_max + 1, &state->rfds_rd, &state->rfds_wr, + &state->rfds_ex, tv_ptr); + if (err < 0) + return err; + + for (i = 0; i < waiter->pfd_count; ++i) { + pfd = &waiter->pfds[i]; + + pfd->revents = 0; + if (FD_ISSET(pfd->fd, &state->rfds_rd)) + pfd->revents |= POLLIN; + if (FD_ISSET(pfd->fd, &state->rfds_wr)) + pfd->revents |= POLLOUT; + if (FD_ISSET(pfd->fd, &state->rfds_ex)) + pfd->revents |= POLLHUP; + } + + return 0; +} + +static void select_release(struct waiter_context *waiter) +{ + return; +} + +const struct waiter_data waiter_select = { + .ops = { + .prepare = select_prepare, + .wait_event = select_wait_event, + .release = select_release, + }, + .private_size = sizeof(struct select_state), +}; diff --git a/axfer/waiter.c b/axfer/waiter.c index 446e617..08428e3 100644 --- a/axfer/waiter.c +++ b/axfer/waiter.c @@ -17,6 +17,7 @@ static const char *const waiter_type_labels[] = { [WAITER_TYPE_DEFAULT] = "default", [WAITER_TYPE_POLL] = "poll", + [WAITER_TYPE_SELECT] = "select", }; enum waiter_type waiter_type_from_label(const char *label) @@ -44,6 +45,7 @@ int waiter_context_init(struct waiter_context *waiter, const struct waiter_data *waiter; } entries[] = { {WAITER_TYPE_POLL, &waiter_poll}, + {WAITER_TYPE_SELECT, &waiter_select}, }; int i; diff --git a/axfer/waiter.h b/axfer/waiter.h index 9366724..fec16b7 100644 --- a/axfer/waiter.h +++ b/axfer/waiter.h @@ -14,6 +14,7 @@ enum waiter_type { WAITER_TYPE_DEFAULT = 0, WAITER_TYPE_POLL, + WAITER_TYPE_SELECT, WAITER_TYPE_COUNT, }; @@ -53,5 +54,6 @@ struct waiter_data { }; extern const struct waiter_data waiter_poll; +extern const struct waiter_data waiter_select; #endif From patchwork Tue Nov 13 06:41:44 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679875 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 315BE46E4 for ; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1AA7B2A450 for ; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 0F34F2A496; Tue, 13 Nov 2018 07:32:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DEC042A450 for ; Tue, 13 Nov 2018 07:32:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 76A2E267B27; Tue, 13 Nov 2018 07:43:17 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id B0B86267B84; Tue, 13 Nov 2018 07:43:14 +0100 (CET) Received: from mail-pf1-f194.google.com (mail-pf1-f194.google.com [209.85.210.194]) by alsa0.perex.cz (Postfix) with ESMTP id 39345267AE5 for ; Tue, 13 Nov 2018 07:42:50 +0100 (CET) Received: by mail-pf1-f194.google.com with SMTP id 64so1069816pfr.9 for ; Mon, 12 Nov 2018 22:42:50 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=8MexSS/DICxx1NX15vUoQ+R4H6jApO5GDm7BJeTXuzA=; b=f3C0fe2fTJg+SgTpOHdb04pe0xzjKhjvhxLmh1xVMaLI4iLovVw2R9DGH94MAyH+by MINwtZ7SWeS3cdxlzL0WXSGqOpapfAktYNizNrQRo2sFFQi2OyIth2Jo0x3NfNe7CQpw jYmqxTF6u5ZRrN837DuoVEZFslaUbqMYqPjGeTU6KYQYMULKKAdGk7+mUzEheXPaINGJ jB1si4MceMDQN2N4Fum93oFQ9mIyRBFrWiWNZCeiOlvxOQfb/7WIC4wQG2/C+/QdKe2H kyzxDpd14VUrlAtBBP6QaJrzqGy/2r76+FOtsvl3RwuLEcfDwxgnTYFPk2EyO8Y6pvKT sw0g== X-Gm-Message-State: AGRZ1gIDOXRy5UwNhe2OeB134eHLpvY2LzPldiovzAVPh4TAyh8G22c/ ktjclSi/Dn57QqPEpjPWemY= X-Google-Smtp-Source: AJdET5feW1R25URCw6s7uC931wCsQ0Kz5rJcZHTdWUPQDjO0FOzrCtreIwa1bnpIqYVl8H1P4dJNew== X-Received: by 2002:a62:f54f:: with SMTP id n76mr340724pfh.59.1542091369343; Mon, 12 Nov 2018 22:42:49 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.47 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:48 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:44 +0900 Message-Id: <20181113064147.13577-32-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 32/35] axfer: add an implementation of waiter for epoll(7) X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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. Below lines are examples to use this option: $ axfer transfer --waiter-type=epoll -M -P -d 2 -D hw:0,3 /dev/urandom -f dat -vvv $ axfer transfer --waiter-type=epoll -M -C -d 2 -D hw:1,0 /dev/null -r 48000 -vvv Signed-off-by: Takashi Sakamoto --- axfer/Makefile.am | 3 +- axfer/waiter-epoll.c | 107 +++++++++++++++++++++++++++++++++++++++++++ axfer/waiter.c | 2 + axfer/waiter.h | 2 + 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 axfer/waiter-epoll.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 867007b..ab1d0b4 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -52,4 +52,5 @@ axfer_SOURCES = \ waiter.h \ waiter.c \ waiter-poll.c \ - waiter-select.c + waiter-select.c \ + waiter-epoll.c diff --git a/axfer/waiter-epoll.c b/axfer/waiter-epoll.c new file mode 100644 index 00000000..8f084f1 --- /dev/null +++ b/axfer/waiter-epoll.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// waiter-epoll.c - Waiter for event notification by epoll(7). +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "waiter.h" +#include "misc.h" + +#include +#include +#include +#include +#include +#include + +struct epoll_state { + int epfd; + struct epoll_event *events; + unsigned int ev_count; +}; + +static int epoll_prepare(struct waiter_context *waiter) +{ + struct epoll_state *state = waiter->private_data; + int i; + + state->ev_count = waiter->pfd_count; + state->events = calloc(state->ev_count, sizeof(*state->events)); + if (state->events == NULL) + return -ENOMEM; + + state->epfd = epoll_create(1); + if (state->epfd < 0) + return -errno; + + for (i = 0; i < waiter->pfd_count; ++i) { + struct epoll_event ev = { + .data.fd = waiter->pfds[i].fd, + .events = waiter->pfds[i].events, + }; + if (epoll_ctl(state->epfd, EPOLL_CTL_ADD, ev.data.fd, &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; + unsigned int ev_count; + int i, j; + int err; + + memset(state->events, 0, state->ev_count * sizeof(*state->events)); + err = epoll_wait(state->epfd, state->events, state->ev_count, + timeout_msec); + if (err < 0) + return -errno; + ev_count = (unsigned int)err; + + if (ev_count > 0) { + // Reconstruct data of pollfd structure. + for (i = 0; i < ev_count; ++i) { + struct epoll_event *ev = &state->events[i]; + for (j = 0; j < waiter->pfd_count; ++j) { + if (waiter->pfds[i].fd == ev->data.fd) { + waiter->pfds[i].revents = ev->events; + break; + } + } + } + } + + return ev_count; +} + +static void epoll_release(struct waiter_context *waiter) +{ + struct epoll_state *state = waiter->private_data; + int i; + + for (i = 0; i < waiter->pfd_count; ++i) { + int fd = waiter->pfds[i].fd; + epoll_ctl(state->epfd, EPOLL_CTL_DEL, fd, NULL); + } + + free(state->events); + state->events = NULL; + + close(state->epfd); + + state->ev_count = 0; + 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 08428e3..1e9c811 100644 --- a/axfer/waiter.c +++ b/axfer/waiter.c @@ -18,6 +18,7 @@ static const char *const waiter_type_labels[] = { [WAITER_TYPE_DEFAULT] = "default", [WAITER_TYPE_POLL] = "poll", [WAITER_TYPE_SELECT] = "select", + [WAITER_TYPE_EPOLL] = "epoll", }; enum waiter_type waiter_type_from_label(const char *label) @@ -46,6 +47,7 @@ int waiter_context_init(struct waiter_context *waiter, } entries[] = { {WAITER_TYPE_POLL, &waiter_poll}, {WAITER_TYPE_SELECT, &waiter_select}, + {WAITER_TYPE_EPOLL, &waiter_epoll}, }; int i; diff --git a/axfer/waiter.h b/axfer/waiter.h index fec16b7..db18e33 100644 --- a/axfer/waiter.h +++ b/axfer/waiter.h @@ -15,6 +15,7 @@ enum waiter_type { WAITER_TYPE_DEFAULT = 0, WAITER_TYPE_POLL, WAITER_TYPE_SELECT, + WAITER_TYPE_EPOLL, WAITER_TYPE_COUNT, }; @@ -55,5 +56,6 @@ struct waiter_data { extern const struct waiter_data waiter_poll; extern const struct waiter_data waiter_select; +extern const struct waiter_data waiter_epoll; #endif From patchwork Tue Nov 13 06:41:45 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679881 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 55CEC13BB for ; Tue, 13 Nov 2018 07:32:25 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 38A312A44F for ; Tue, 13 Nov 2018 07:32:25 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 2C2142A46A; Tue, 13 Nov 2018 07:32:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 303502A487 for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 41974267B73; Tue, 13 Nov 2018 07:43:22 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 358B6267BCA; Tue, 13 Nov 2018 07:43:20 +0100 (CET) Received: from mail-pg1-f193.google.com (mail-pg1-f193.google.com [209.85.215.193]) by alsa0.perex.cz (Postfix) with ESMTP id 5CDB5267B5C for ; Tue, 13 Nov 2018 07:42:52 +0100 (CET) Received: by mail-pg1-f193.google.com with SMTP id z11so2594506pgu.0 for ; Mon, 12 Nov 2018 22:42:52 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Y50KxdZfdNMIWk57ADyB660/bJkBHdPgG6++yuB9K14=; b=JxVmvKP6wel5CEWYFSVdhWlR2yN6EIuG2LTgVQL/dWnwRoALMmnLhmn0x2NnAGIcFu OTHvhqXL3PC4wWIihqxCWFmRLg0jIk91y6ARpWnBHw4mOkJWaKPrQLPadFlvHO4WV4/S cMNSZ4smtbA4XNQ4b+gffcVcimk1sqCB2FdFonWnHYOltuKc1X9OWPI+MLyUAs+Yx3Pl Dyetjeb+36GY38uwlqHPxjfe1kp+FwnpwVzqAuyebICrzv+XX2xMarlV5RLv/zfJ4ne9 Njmj8CtYh/XPPjAQ5dETRFU0zZ2Ga91US7TO5iFS9/EDiwKRlTPa6HDRrqTjFJqGobgU NFsQ== X-Gm-Message-State: AGRZ1gLKeT/hXxASYF4+w4tq6ktty66bXlWk/rAqjTbH0T6WkoVZUbfW 6r9YdkVsfQJd6F0iToFNLVm3XHH3 X-Google-Smtp-Source: AJdET5dMsa3kEVlmwQA8paCOdGMUNtpibYRC6NlWS/EqXo07ZJyjqkIJqJqQ5ObRnLbIFic++4Ywow== X-Received: by 2002:a62:1049:: with SMTP id y70-v6mr3956255pfi.113.1542091371147; Mon, 12 Nov 2018 22:42:51 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.49 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:50 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:45 +0900 Message-Id: <20181113064147.13577-33-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 33/35] axfer: add support for timer-based scheduling model with MMAP operation X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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. Furthermore, with usage of rewinding/forwarding, applications can achieve low latency between transmission position and handling position even if they uses large size of PCM buffers. 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-model' option, and the scheduling mode is enabled when 'timer' is assigned to the option by equal sign. Although there's some TODOs, you can see the scheduling mode in this simple program, like: $ axfer transfer --sched-model=timer -P -d 2 -D hw:0,3 /dev/urandom -f dat -vvv $ axfer transfer --sched-model=timer -C -d 2 -D hw:1,0 /dev/null -r 48000 -vvv Signed-off-by: Takashi Sakamoto --- axfer/Makefile.am | 3 +- axfer/xfer-libasound-timer-mmap.c | 455 ++++++++++++++++++++++++++++++ axfer/xfer-libasound.c | 108 ++++++- axfer/xfer-libasound.h | 13 + 4 files changed, 564 insertions(+), 15 deletions(-) create mode 100644 axfer/xfer-libasound-timer-mmap.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index ab1d0b4..497b9ff 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -53,4 +53,5 @@ axfer_SOURCES = \ waiter.c \ waiter-poll.c \ waiter-select.c \ - waiter-epoll.c + waiter-epoll.c \ + xfer-libasound-timer-mmap.c diff --git a/axfer/xfer-libasound-timer-mmap.c b/axfer/xfer-libasound-timer-mmap.c new file mode 100644 index 00000000..1c642fe --- /dev/null +++ b/axfer/xfer-libasound-timer-mmap.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer-libasound-irq-mmap.c - Timer-based scheduling model for mmap operation. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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) { + unsigned short revents; + 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 = xfer_libasound_wait_event(state, timeout_msec, + &revents); + if (err < 0) + return err; + if (revents & POLLERR) { + // TODO: error reporting. + return -EIO; + } + if (!(revents & (POLLIN | POLLOUT))) + return -EAGAIN; + + // 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 fa72839..f6d0515 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -9,11 +9,17 @@ #include "xfer-libasound.h" #include "misc.h" +static const char *const sched_model_labels [] = { + [SCHED_MODEL_IRQ] = "irq", + [SCHED_MODEL_TIMER] = "timer", +}; + enum no_short_opts { // 200 or later belong to non us-ascii character set. OPT_PERIOD_SIZE = 200, OPT_BUFFER_SIZE, OPT_WAITER_TYPE, + OPT_SCHED_MODEL, OPT_DISABLE_RESAMPLE, OPT_DISABLE_CHANNELS, OPT_DISABLE_FORMAT, @@ -35,6 +41,7 @@ static const struct option l_opts[] = { {"start-delay", 1, 0, 'R'}, {"stop-delay", 1, 0, 'T'}, {"waiter-type", 1, 0, OPT_WAITER_TYPE}, + {"sched-model", 1, 0, OPT_SCHED_MODEL}, // For plugins in alsa-lib. {"disable-resample", 0, 0, OPT_DISABLE_RESAMPLE}, {"disable-channels", 0, 0, OPT_DISABLE_CHANNELS}, @@ -90,6 +97,8 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, state->msec_for_stop_threshold = arg_parse_decimal_num(optarg, &err); else if (key == OPT_WAITER_TYPE) state->waiter_type_literal = arg_duplicate_string(optarg, &err); + else if (key == OPT_SCHED_MODEL) + state->sched_model_literal = arg_duplicate_string(optarg, &err); else if (key == OPT_DISABLE_RESAMPLE) state->no_auto_resample = true; else if (key == OPT_DISABLE_CHANNELS) @@ -151,6 +160,15 @@ int xfer_libasound_validate_opts(struct xfer_context *xfer) } } + state->sched_model = SCHED_MODEL_IRQ; + if (state->sched_model_literal != NULL) { + if (!strcmp(state->sched_model_literal, "timer")) { + state->sched_model = SCHED_MODEL_TIMER; + state->mmap = true; + state->nonblock = true; + } + } + if (state->waiter_type_literal != NULL) { if (state->test_nowait) { fprintf(stderr, @@ -161,7 +179,8 @@ int xfer_libasound_validate_opts(struct xfer_context *xfer) if (!state->nonblock && !state->mmap) { fprintf(stderr, "An option for waiter type should be used " - "with nonblock or mmap options.\n"); + "with nonblock or mmap or timer-based " + "scheduling options.\n"); return -EINVAL; } state->waiter_type = @@ -196,6 +215,37 @@ static int set_access_hw_param(struct libasound_state *state) 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 open_handle(struct xfer_context *xfer) { struct libasound_state *state = xfer->private_data; @@ -229,7 +279,11 @@ static int open_handle(struct xfer_context *xfer) if (err < 0) return err; - // TODO: Applying NO_PERIOD_WAKEUP should be done here. + if (state->sched_model == SCHED_MODEL_TIMER) { + err = disable_period_wakeup(state); + if (err < 0) + return err; + } if (xfer->dump_hw_params) { logging(state, "Available HW Params of node: %s\n", @@ -575,6 +629,7 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, snd_pcm_uframes_t *frames_per_buffer) { struct libasound_state *state = xfer->private_data; + unsigned int flag; int err; err = open_handle(xfer); @@ -606,24 +661,43 @@ 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); 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; @@ -639,8 +713,11 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, return err; } - if (xfer->verbose > 0) + if (xfer->verbose > 0) { snd_pcm_dump(state->handle, state->log); + logging(state, "Scheduling model:\n"); + logging(state, " %s\n", sched_model_labels[state->sched_model]); + } if (state->use_waiter) { // NOTE: This should be after configuring sw_params due to @@ -733,7 +810,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", @@ -774,8 +852,10 @@ static void xfer_libasound_destroy(struct xfer_context *xfer) free(state->node_literal); free(state->waiter_type_literal); + free(state->sched_model_literal); state->node_literal = NULL; state->waiter_type_literal = NULL; + state->sched_model_literal = NULL; if (state->hw_params) snd_pcm_hw_params_free(state->hw_params); diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 20b0d74..dfe8577 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -15,6 +15,12 @@ #define logging(state, ...) \ snd_output_printf(state->log, __VA_ARGS__) +enum sched_model { + SCHED_MODEL_IRQ = 0, + SCHED_MODEL_TIMER, + SCHED_MODEL_COUNT, +}; + struct xfer_libasound_ops; struct libasound_state { @@ -31,6 +37,7 @@ struct libasound_state { char *node_literal; char *waiter_type_literal; + char *sched_model_literal; unsigned int msec_per_period; unsigned int msec_per_buffer; @@ -54,6 +61,9 @@ struct libasound_state { enum waiter_type waiter_type; struct waiter_context *waiter; + + // For scheduling type. + enum sched_model sched_model; }; // For internal use in 'libasound' module. @@ -76,4 +86,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; +extern const struct xfer_libasound_ops xfer_libasound_timer_mmap_w_ops; +extern const struct xfer_libasound_ops xfer_libasound_timer_mmap_r_ops; + #endif From patchwork Tue Nov 13 06:41:46 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10679887 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 8517C3CF1 for ; Tue, 13 Nov 2018 07:32:26 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 6D6892A440 for ; Tue, 13 Nov 2018 07:32:26 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 619012A449; Tue, 13 Nov 2018 07:32:26 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 325272A494 for ; Tue, 13 Nov 2018 07:32:23 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id CEFD8267BB5; Tue, 13 Nov 2018 07:43:19 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id BE020267BB5; Tue, 13 Nov 2018 07:43:17 +0100 (CET) Received: from mail-pl1-f195.google.com (mail-pl1-f195.google.com [209.85.214.195]) by alsa0.perex.cz (Postfix) with ESMTP id E6708267B94 for ; Tue, 13 Nov 2018 07:42:53 +0100 (CET) Received: by mail-pl1-f195.google.com with SMTP id w24-v6so5526945plq.3 for ; Mon, 12 Nov 2018 22:42:53 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=BkNYGehlMqtkws4cY6vLju8cnK9KSlCAyKyjEOMJP9Y=; b=UqH7sAYBDzvHVl5o8IPvAvea/AP/n0AAcOb10e0xp18zTf6FHYxqDsPsXb4HCkX4qX EFvHZuvXiAN6uLEbtO/+HBGWZ+EuSU5gFycKtoPj9u4bzRFw1zIKr1nIhy1WR0t1drYv 3YIVfgp1izKLTzUb1gSC3KHTGRYB9zDJB8kUJtN9ZXPkPm/TwGV19SbIyC2ty2aHuFLj JeqpYY1DAynqxnpW7zz03Ci3H0cgvOt1UVyWfkZKPXQh9eubfuGV4JRRKdndcW53+zRG 1h09U1i1AuHGGiHgZf6k4E9eVvk+iTNhtsWNCrSnFHEWAGeWdDlAHWaPiCoz+l56dw4o 49pQ== X-Gm-Message-State: AGRZ1gLZ3QJMxKnZ8+5Ub9JfLhyv8v5qrXvoh2pepaThO04S5sOEz9QG gjbzNbfUdPguEa8Er3Yv05aRtdWG X-Google-Smtp-Source: AJdET5eh6BvA/R9QvHrzWvqytotnitAJa9YiholV/nwTof/02V4MCjMxYvD6cc86zWsksjPcXqq4uw== X-Received: by 2002:a17:902:124:: with SMTP id 33-v6mr3868876plb.287.1542091372955; Mon, 12 Nov 2018 22:42:52 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.51 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:52 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:46 +0900 Message-Id: <20181113064147.13577-34-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 34/35] axfer: obsolete some unimplemented options X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP Althogh many options are actually supported by aplay, some of them are not enough good in practical points. For example, '--test-position' option is meaningless for some use cases. Furthermore, due to practical reasons, some options are not implemented well; e.g. vumeter. This commit marks such options as 'obsoleted'. Signed-off-by: Takashi Sakamoto --- axfer/xfer-libasound.c | 13 ++++++++++++- axfer/xfer-options.c | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index f6d0515..bacd835 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -26,9 +26,12 @@ enum no_short_opts { OPT_DISABLE_SOFTVOL, OPT_FATAL_ERRORS, OPT_TEST_NOWAIT, + // Obsoleted. + OPT_TEST_POSITION, + OPT_TEST_COEF, }; -#define S_OPTS "D:NMF:B:A:R:T:" +#define S_OPTS "D:NMF:B:A:R:T:m:" static const struct option l_opts[] = { {"device", 1, 0, 'D'}, {"nonblock", 0, 0, 'N'}, @@ -50,6 +53,10 @@ static const struct option l_opts[] = { // For debugging. {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, + // Obsoleted. + {"chmap", 1, 0, 'm'}, + {"test-position", 0, 0, OPT_TEST_POSITION}, + {"test-coef", 1, 0, OPT_TEST_COEF}, }; static int xfer_libasound_init(struct xfer_context *xfer, @@ -107,6 +114,10 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, state->no_auto_format = true; else if (key == OPT_DISABLE_SOFTVOL) state->no_softvol = true; + else if (key == 'm' || + key == OPT_TEST_POSITION || + key == OPT_TEST_COEF) + err = -EINVAL; else if (key == OPT_FATAL_ERRORS) state->finish_at_xrun = true; else if (key == OPT_TEST_NOWAIT) diff --git a/axfer/xfer-options.c b/axfer/xfer-options.c index d899134..5a68646 100644 --- a/axfer/xfer-options.c +++ b/axfer/xfer-options.c @@ -21,6 +21,8 @@ enum no_short_opts { OPT_BUFFER_SIZE, // Obsoleted. OPT_MAX_FILE_TIME, + OPT_USE_STRFTIME, + OPT_PROCESS_ID_FILE, }; static int allocate_paths(struct xfer_context *xfer, char *const *paths, @@ -232,7 +234,7 @@ int xfer_options_parse_args(struct xfer_context *xfer, const struct xfer_data *data, int argc, char *const *argv) { - static const char *short_opts = "CPhvqd:s:f:c:r:t:I"; + static const char *short_opts = "CPhvqd:s:f:c:r:t:IV:i"; static const struct option long_opts[] = { // For generic purposes. {"capture", 0, 0, 'C'}, @@ -255,6 +257,10 @@ int xfer_options_parse_args(struct xfer_context *xfer, {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, // Obsoleted. {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, + {"use-strftime", 0, 0, OPT_USE_STRFTIME}, + {"process-id-file", 1, 0, OPT_PROCESS_ID_FILE}, + {"vumeter", 1, 0, 'V'}, + {"interactive", 0, 0, 'i'}, }; char *s_opts; struct option *l_opts; @@ -321,7 +327,11 @@ int xfer_options_parse_args(struct xfer_context *xfer, xfer->dump_hw_params = true; else if (key == '?') return -EINVAL; - else if (key == OPT_MAX_FILE_TIME) { + else if (key == OPT_MAX_FILE_TIME || + key == OPT_USE_STRFTIME || + key == OPT_PROCESS_ID_FILE || + key == 'V' || + key == 'i') { fprintf(stderr, "An option '--%s' is obsoleted and has no " "effect.\n", From patchwork Tue Nov 13 06:41:47 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 10680009 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 977A2109C for ; Tue, 13 Nov 2018 07:47:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 863482A437 for ; Tue, 13 Nov 2018 07:47:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 7AE102A44F; Tue, 13 Nov 2018 07:47:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 5E7292A450 for ; Tue, 13 Nov 2018 07:47:21 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id BC87C267B95; Tue, 13 Nov 2018 07:43:30 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 1C819267B5D; Tue, 13 Nov 2018 07:43:28 +0100 (CET) Received: from mail-pl1-f196.google.com (mail-pl1-f196.google.com [209.85.214.196]) by alsa0.perex.cz (Postfix) with ESMTP id D6930267B9A for ; Tue, 13 Nov 2018 07:42:55 +0100 (CET) Received: by mail-pl1-f196.google.com with SMTP id g59-v6so5504662plb.10 for ; Mon, 12 Nov 2018 22:42:55 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=wI+rKGXGQ8evUCCdhxb9jz4EJrnFVQFVYUaemICuMxU=; b=g5zilviilHCHL7c6Tq5oYM2ej6eh2Yu5N0Rik38NdeLw5R9G1owljMhwOZrclLRk5+ 944X9B0mFHUS+SG9A9n1ceA/tFSCAGZlBsTv54ONKGrorfsLNYT0lRvVHc8oPU7OTd2+ gnuJ3DK3f649v6xvbw1DMkhfqGJfM2zvaOTT+cbKny/kITNdyguSTBsfkzipVN1qFhzn vN1NSo/M+dHDCgz5haElobZtpbkyntN8MvwHCXtv0I0lA4G2GnUVE2UvPMNNY7yNrM4w qvhSricC8NPIMJ5M1j2du66JqGBdhqggRFm1mAmZQDQpTW7+Vg9hPN01BQkcRoseBJFg PTLA== X-Gm-Message-State: AGRZ1gIWzdtI5CWOKl0rk6aYRHh6ndH2I6+IlpI+fRDms37kqNw9xCo2 AHfUdvtMJKtIWG6zgf9i0d0= X-Google-Smtp-Source: AJdET5c+EdRi9GpsJDswblnwR+bXA+Q3nPRITcRUBc58vUx9FUhq8ObMwBPrk5J5qw7m/jw5WHK9DQ== X-Received: by 2002:a17:902:8b8c:: with SMTP id ay12-v6mr3702111plb.69.1542091374763; Mon, 12 Nov 2018 22:42:54 -0800 (PST) Received: from localhost.localdomain ([2405:6580:9660:3200:acf1:2274:aafd:ab4c]) by smtp.gmail.com with ESMTPSA id h7sm11207634pfa.105.2018.11.12.22.42.53 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 12 Nov 2018 22:42:54 -0800 (PST) From: Takashi Sakamoto To: tiwai@suse.de, perex@perex.cz Date: Tue, 13 Nov 2018 15:41:47 +0900 Message-Id: <20181113064147.13577-35-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181113064147.13577-1-o-takashi@sakamocchi.jp> References: <20181113062459.DD8F7267A5C@alsa0.perex.cz> <20181113064147.13577-1-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org Subject: [alsa-devel] [PATCH 35/35] axfer: add support for libffado transmission backend X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP At present, axfer is designed to use several types of backend for transmission of data frames. This commit is an implementation example of the backend. Libffado is a userspace library for transmission of data frames according to protocols similar to IEC 61883-1/6. This library handles audio and music units on IEEE 1394 bus. Unfortunately, this library executes ctor/dtor of instances for some objects in startup/finish routines of C runtime. As a result, it outputs some superfluous messages even if the backend is not actually used. Furthermore, this library brings memory leak internally. Therefore, it's not practical to build this backend for generic purposes. Although the backend implementation works fine, this commit is just for technical preview. Signed-off-by: Takashi Sakamoto --- axfer/Makefile.am | 5 + axfer/xfer-libffado.c | 555 ++++++++++++++++++++++++++++++++++++++++++ axfer/xfer.c | 6 + axfer/xfer.h | 9 + configure.ac | 12 + 5 files changed, 587 insertions(+) create mode 100644 axfer/xfer-libffado.c diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 497b9ff..39f414c 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -55,3 +55,8 @@ axfer_SOURCES = \ waiter-select.c \ waiter-epoll.c \ xfer-libasound-timer-mmap.c + +if HAVE_FFADO +axfer_SOURCES += xfer-libffado.c +LDADD += -lffado +endif diff --git a/axfer/xfer-libffado.c b/axfer/xfer-libffado.c new file mode 100644 index 00000000..3b52e2c --- /dev/null +++ b/axfer/xfer-libffado.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer-libffado.c - receive/transmit frames by libffado. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "xfer.h" +#include "misc.h" + +#include "frame-cache.h" + +#include +#include + +#include + +struct libffado_state { + ffado_device_t *handle; + enum ffado_direction direction; + + char *port_literal; + char *node_literal; + char *guid_literal; + unsigned int frames_per_period; + unsigned int periods_per_buffer; + unsigned int sched_priority; + bool slave_mode:1; + bool snoop_mode:1; + + unsigned int data_ch_count; + ffado_streaming_stream_type *data_ch_map; + + int (*process_frames)(struct xfer_context *xfer, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs); + + struct frame_cache cache; +}; + +enum no_short_opts { + OPT_FRAMES_PER_PERIOD = 200, + OPT_PERIODS_PER_BUFFER, + OPT_SLAVE_MODE, + OPT_SNOOP_MODE, + OPT_SCHED_PRIORITY, +}; + +#define S_OPTS "p:n:g:" +static const struct option l_opts[] = { + {"port", 1, 0, 'p'}, + {"node", 1, 0, 'n'}, + {"guid", 1, 0, 'g'}, + {"frames-per-period", 1, 0, OPT_FRAMES_PER_PERIOD}, + {"periods-per-buffer", 1, 0, OPT_PERIODS_PER_BUFFER}, + {"slave", 0, 0, OPT_SLAVE_MODE}, + {"snoop", 0, 0, OPT_SNOOP_MODE}, + {"sched-priority", 1, 0, OPT_SCHED_PRIORITY}, // to SCHED_FIFO +}; + +static int xfer_libffado_init(struct xfer_context *xfer, + snd_pcm_stream_t direction) +{ + struct libffado_state *state = xfer->private_data; + + if (direction == SND_PCM_STREAM_CAPTURE) + state->direction = FFADO_CAPTURE; + else if (direction == SND_PCM_STREAM_PLAYBACK) + state->direction = FFADO_PLAYBACK; + else + return -EINVAL; + + return 0; +} + +static int xfer_libffado_parse_opt(struct xfer_context *xfer, int key, + const char *optarg) +{ + struct libffado_state *state = xfer->private_data; + int err; + + if (key == 'p') + state->port_literal = arg_duplicate_string(optarg, &err); + else if (key == 'n') + state->node_literal = arg_duplicate_string(optarg, &err); + else if (key == 'g') + state->guid_literal = arg_duplicate_string(optarg, &err); + else if (key == OPT_FRAMES_PER_PERIOD) + state->frames_per_period = arg_parse_decimal_num(optarg, &err); + else if (key == OPT_PERIODS_PER_BUFFER) + state->periods_per_buffer = arg_parse_decimal_num(optarg, &err); + else if (key == OPT_SLAVE_MODE) + state->slave_mode = true; + else if (key == OPT_SNOOP_MODE) + state->snoop_mode = true; + else if (key == OPT_SCHED_PRIORITY) + state->sched_priority = arg_parse_decimal_num(optarg, &err); + else + err = -ENXIO; + + return err; +} + +static int validate_sched_priority(struct libffado_state *state) +{ + int val; + + val = sched_get_priority_max(SCHED_FIFO); + if (val < 0) + return -errno; + if (state->sched_priority > val) + return -EINVAL; + + val = sched_get_priority_min(SCHED_FIFO); + if (val < 0) + return -errno; + if (state->sched_priority < val) + return -EINVAL; + + return 0; +} + +static int xfer_libffado_validate_opts(struct xfer_context *xfer) +{ + struct libffado_state *state = xfer->private_data; + int err; + + if (state->node_literal != NULL) { + if (state->port_literal == NULL) { + fprintf(stderr, + "'n' option should correspond 'p' option.\n"); + return -EINVAL; + } + } + + if (state->port_literal != NULL && state->guid_literal != NULL) { + fprintf(stderr, + "Neither 'p' option nor 'g' option is available at the " + "same time.\n"); + return -EINVAL; + } + + if (state->guid_literal != NULL) { + if (strstr(state->guid_literal, "0x") != state->guid_literal) { + fprintf(stderr, + "A value of 'g' option should have '0x' as its " + "prefix for hexadecimal number.\n"); + return -EINVAL; + } + } + + if (state->slave_mode && state->snoop_mode) { + fprintf(stderr, "Neither slave mode nor snoop mode is available" + "at the same time.\n"); + return -EINVAL; + } + + if (state->sched_priority > 0) { + err = validate_sched_priority(state); + if (err < 0) + return err; + } + + if (state->frames_per_period == 0) + state->frames_per_period = 512; + if (state->periods_per_buffer == 0) + state->periods_per_buffer = 2; + + return 0; +} + +static int r_process_frames(struct xfer_context *xfer, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct libffado_state *state = xfer->private_data; + unsigned int avail_count; + unsigned int bytes_per_frame; + unsigned int consumed_count; + int err; + + // Trim up to expected frame count. + avail_count = state->frames_per_period; + if (*frame_count < avail_count) + avail_count = *frame_count; + + // Cache required amount of frames. + if (avail_count > frame_cache_get_count(&state->cache)) { + int ch; + int pos; + + // Register buffers. + pos = 0; + bytes_per_frame = state->cache.bytes_per_sample * + state->cache.samples_per_frame; + for (ch = 0; ch < state->data_ch_count; ++ch) { + char *buf; + + if (state->data_ch_map[ch] != ffado_stream_type_audio) + continue; + + buf = state->cache.buf_ptr; + buf += ch * bytes_per_frame; + if (ffado_streaming_set_capture_stream_buffer(state->handle, + ch, buf)) + return -EIO; + ++pos; + } + + assert(pos == xfer->samples_per_frame); + + // Move data to the buffer from intermediate buffer. + if (!ffado_streaming_transfer_buffers(state->handle)) + return -EIO; + + frame_cache_increase_count(&state->cache, + state->frames_per_period); + } + + // Write out to file descriptors. + consumed_count = frame_cache_get_count(&state->cache); + err = mapper_context_process_frames(mapper, state->cache.buf, + &consumed_count, cntrs); + if (err < 0) + return err; + + frame_cache_reduce(&state->cache, consumed_count); + + *frame_count = consumed_count; + + return 0; +} + +static int w_process_frames(struct xfer_context *xfer, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct libffado_state *state = xfer->private_data; + unsigned int avail_count; + int pos; + int ch; + unsigned int bytes_per_frame; + unsigned int consumed_count; + int err; + + // Trim up to expected frame_count. + avail_count = state->frames_per_period; + if (*frame_count < avail_count) + avail_count = *frame_count; + + // Cache required amount of frames. + if (avail_count > frame_cache_get_count(&state->cache)) { + avail_count -= frame_cache_get_count(&state->cache); + + err = mapper_context_process_frames(mapper, state->cache.buf_ptr, + &avail_count, cntrs); + if (err < 0) + return err; + frame_cache_increase_count(&state->cache, avail_count); + avail_count = state->cache.remained_count; + } + + // Register buffers. + pos = 0; + bytes_per_frame = state->cache.bytes_per_sample * + state->cache.samples_per_frame; + for (ch = 0; ch < state->data_ch_count; ++ch) { + char *buf; + + if (state->data_ch_map[ch] != ffado_stream_type_audio) + continue; + + buf = state->cache.buf; + buf += bytes_per_frame; + if (ffado_streaming_set_playback_stream_buffer(state->handle, + ch, buf)) + return -EIO; + ++pos; + } + + assert(pos == xfer->samples_per_frame); + + // Move data on the buffer for transmission. + if (!ffado_streaming_transfer_buffers(state->handle)) + return -EIO; + consumed_count = state->frames_per_period; + + frame_cache_reduce(&state->cache, consumed_count); + + *frame_count = consumed_count; + + return 0; +} + +static int open_handle(struct xfer_context *xfer, + unsigned int frames_per_second) +{ + struct libffado_state *state = xfer->private_data; + ffado_options_t options = {0}; + ffado_device_info_t info = {0}; + + char str[32] = {0}; + char *strings[1]; + + // Set target unit if given. + if (state->port_literal != NULL) { + if (state->node_literal != NULL) { + snprintf(str, sizeof(str), "hw:%s,%s", + state->port_literal, state->node_literal); + } else { + snprintf(str, sizeof(str), "hw:%s", + state->port_literal); + } + } else if (state->guid_literal != NULL) { + snprintf(str, sizeof(str), "guid:%s", state->guid_literal); + } + if (str[0] != '\0') { + info.nb_device_spec_strings = 1; + strings[0] = str; + info.device_spec_strings = strings; + } + + // Set common options. + options.sample_rate = frames_per_second; + options.period_size = state->frames_per_period; + options.nb_buffers = state->periods_per_buffer; + options.realtime = !!(state->sched_priority > 0); + options.packetizer_priority = state->sched_priority; + options.slave_mode = state->slave_mode; + options.snoop_mode = state->snoop_mode; + options.verbose = xfer->verbose; + + state->handle = ffado_streaming_init(info, options); + if (state->handle == NULL) + return -EINVAL; + + return 0; +} + +static int enable_mbla_data_ch(struct libffado_state *state, + unsigned int *samples_per_frame) +{ + int (*func_type)(ffado_device_t *handle, int pos); + int (*func_onoff)(ffado_device_t *handle, int pos, int on); + int count; + int ch; + + if (state->direction == FFADO_CAPTURE) { + func_type = ffado_streaming_get_capture_stream_type; + func_onoff = ffado_streaming_capture_stream_onoff; + count = ffado_streaming_get_nb_capture_streams(state->handle); + } else { + func_type = ffado_streaming_get_playback_stream_type; + func_onoff = ffado_streaming_playback_stream_onoff; + count = ffado_streaming_get_nb_playback_streams(state->handle); + } + if (count <= 0) + return -EIO; + + state->data_ch_map = calloc(count, sizeof(*state->data_ch_map)); + if (state->data_ch_map == NULL) + return -ENOMEM; + state->data_ch_count = count; + + // When a data ch is off, data in the ch is truncated. This helps to + // align PCM frames in interleaved order. + *samples_per_frame = 0; + for (ch = 0; ch < count; ++ch) { + int on; + + state->data_ch_map[ch] = func_type(state->handle, ch); + + on = !!(state->data_ch_map[ch] == ffado_stream_type_audio); + if (func_onoff(state->handle, ch, on)) + return -EIO; + if (on) + ++(*samples_per_frame); + } + + return 0; +} + +static int xfer_libffado_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 libffado_state *state = xfer->private_data; + unsigned int channels; + int err; + + // Supported format of sample is 24 bit multi bit linear audio in + // AM824 format or the others. + if (state->direction == FFADO_CAPTURE) { + if (*format == SND_PCM_FORMAT_UNKNOWN) + *format = SND_PCM_FORMAT_S24; + } + if (*format != SND_PCM_FORMAT_S24) { + fprintf(stderr, + "A libffado backend supports S24 only.\n"); + return -EINVAL; + } + + // The backend requires the number of frames per second for its + // initialization. + if (state->direction == FFADO_CAPTURE) { + if (*frames_per_second == 0) + *frames_per_second = 48000; + } + if (*frames_per_second < 32000 || *frames_per_second > 192000) { + fprintf(stderr, + "A libffado backend supports sampling rate between " + "32000 and 192000, discretely.\n"); + return -EINVAL; + } + + err = open_handle(xfer, *frames_per_second); + if (err < 0) + return err; + + if (ffado_streaming_set_audio_datatype(state->handle, + ffado_audio_datatype_int24)) + return -EINVAL; + + // Decide buffer layout. + err = enable_mbla_data_ch(state, &channels); + if (err < 0) + return err; + + // This backend doesn't support resampling. + if (state->direction == FFADO_CAPTURE) { + if (*samples_per_frame == 0) + *samples_per_frame = channels; + } + if (*samples_per_frame != channels) { + fprintf(stderr, + "The number of samples per frame should be %i.\n", + channels); + return -EINVAL; + } + + // A buffer has interleaved-aligned PCM frames. + *access = SND_PCM_ACCESS_RW_INTERLEAVED; + *frames_per_buffer = + state->frames_per_period * state->periods_per_buffer; + + // Use cache for double number of frames per period. + err = frame_cache_init(&state->cache, *access, + snd_pcm_format_physical_width(*format) / 8, + *samples_per_frame, state->frames_per_period * 2); + if (err < 0) + return err; + + if (state->direction == FFADO_CAPTURE) + state->process_frames = r_process_frames; + else + state->process_frames = w_process_frames; + + if (ffado_streaming_prepare(state->handle)) + return -EIO; + + if (ffado_streaming_start(state->handle)) + return -EIO; + + return 0; +} + +static int xfer_libffado_process_frames(struct xfer_context *xfer, + unsigned int *frame_count, + struct mapper_context *mapper, + struct container_context *cntrs) +{ + struct libffado_state *state = xfer->private_data; + ffado_wait_response res; + int err; + + res = ffado_streaming_wait(state->handle); + if (res == ffado_wait_shutdown || res == ffado_wait_error) { + err = -EIO; + } else if (res == ffado_wait_xrun) { + // No way to recover in this backend. + err = -EPIPE; + } else if (res == ffado_wait_ok) { + err = state->process_frames(xfer, frame_count, mapper, cntrs); + } else { + err = -ENXIO; + } + + if (err < 0) + *frame_count = 0; + + return err; +} + +static void xfer_libffado_pause(struct xfer_context *xfer, bool enable) +{ + struct libffado_state *state = xfer->private_data; + + // This is an emergency avoidance because this backend doesn't support + // suspend/aresume operation. + if (enable) { + ffado_streaming_stop(state->handle); + ffado_streaming_finish(state->handle); + exit(EXIT_FAILURE); + } +} + +static void xfer_libffado_post_process(struct xfer_context *xfer) +{ + struct libffado_state *state = xfer->private_data; + + if (state->handle != NULL) { + ffado_streaming_stop(state->handle); + ffado_streaming_finish(state->handle); + } + + frame_cache_destroy(&state->cache); + free(state->data_ch_map); + state->data_ch_map = NULL; +} + +static void xfer_libffado_destroy(struct xfer_context *xfer) +{ + struct libffado_state *state = xfer->private_data; + + free(state->port_literal); + free(state->node_literal); + free(state->guid_literal); + state->port_literal = NULL; + state->node_literal = NULL; + state->guid_literal = NULL; +} + +const struct xfer_data xfer_libffado = { + .s_opts = S_OPTS, + .l_opts = l_opts, + .l_opts_count = ARRAY_SIZE(l_opts), + .ops = { + .init = xfer_libffado_init, + .parse_opt = xfer_libffado_parse_opt, + .validate_opts = xfer_libffado_validate_opts, + .pre_process = xfer_libffado_pre_process, + .process_frames = xfer_libffado_process_frames, + .pause = xfer_libffado_pause, + .post_process = xfer_libffado_post_process, + .destroy = xfer_libffado_destroy, + }, + .private_size = sizeof(struct libffado_state), +}; diff --git a/axfer/xfer.c b/axfer/xfer.c index 21595eb..fdf6e60 100644 --- a/axfer/xfer.c +++ b/axfer/xfer.c @@ -13,6 +13,9 @@ static const char *const xfer_type_labels[] = { [XFER_TYPE_LIBASOUND] = "libasound", +#if WITH_FFADO + [XFER_TYPE_LIBFFADO] = "libffado", +#endif }; enum xfer_type xfer_type_from_label(const char *label) @@ -35,6 +38,9 @@ int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, const struct xfer_data *data; } *entry, entries[] = { {XFER_TYPE_LIBASOUND, &xfer_libasound}, +#if WITH_FFADO + {XFER_TYPE_LIBFFADO, &xfer_libffado}, +#endif }; int i; int err; diff --git a/axfer/xfer.h b/axfer/xfer.h index 21ab85d..db2e296 100644 --- a/axfer/xfer.h +++ b/axfer/xfer.h @@ -13,9 +13,14 @@ #include +#include "aconfig.h" + enum xfer_type { XFER_TYPE_UNSUPPORTED = -1, XFER_TYPE_LIBASOUND = 0, +#if WITH_FFADO + XFER_TYPE_LIBFFADO, +#endif XFER_TYPE_COUNT, }; @@ -103,4 +108,8 @@ struct xfer_data { extern const struct xfer_data xfer_libasound; +#if WITH_FFADO + extern const struct xfer_data xfer_libffado; +#endif + #endif diff --git a/configure.ac b/configure.ac index 1c64617..3c11f35 100644 --- a/configure.ac +++ b/configure.ac @@ -50,6 +50,17 @@ if test "$HAVE_SEQ_CLIENT_INFO_GET_PID" = "yes" ; then AC_DEFINE([HAVE_SEQ_CLIENT_INFO_GET_PID], 1, [alsa-lib supports snd_seq_client_info_get_pid]) fi +# +# NOTE: The library 'libffado' (at least v2.4.1) executes ctor/dtor of instances +# for some objects in startup/finish routines of C runtime. As a result, it +# outputs some superfluos messages. Furthermore, it brings much memory leak +# internally. Totally, libffado support is not recommended at all in usual +# purposes except for technical preview. +# +AC_CHECK_LIB([ffado], [ffado_streaming_init], [have_ffado="yes"], [have_ffado="no"]) +AS_IF([test x"$have_ffado" = xyes], + [AC_DEFINE([WITH_FFADO], [1], [Define if FFADO library is available])]) + AM_CONDITIONAL(HAVE_PCM, test "$have_pcm" = "yes") AM_CONDITIONAL(HAVE_MIXER, test "$have_mixer" = "yes") AM_CONDITIONAL(HAVE_RAWMIDI, test "$have_rawmidi" = "yes") @@ -57,6 +68,7 @@ AM_CONDITIONAL(HAVE_SEQ, test "$have_seq" = "yes") AM_CONDITIONAL(HAVE_UCM, test "$have_ucm" = "yes") AM_CONDITIONAL(HAVE_TOPOLOGY, test "$have_topology" = "yes") AM_CONDITIONAL(HAVE_SAMPLERATE, test "$have_samplerate" = "yes") +AM_CONDITIONAL(HAVE_FFADO, test "$have_ffado" = "yes") dnl Use tinyalsa alsabat_backend_tiny=