[alsa-devel] [PATCH - alsa-utils 5/5] The amidicat program itself, better late than never

Josh Lehan alsa at krellan.com
Mon Jun 30 10:55:03 CEST 2014


Signed-off-by: Josh Lehan <alsa at krellan.com>

diff --git a/amidicat/amidicat.c b/amidicat/amidicat.c
new file mode 100644
index 0000000..30d6356
--- /dev/null
+++ b/amidicat/amidicat.c
@@ -0,0 +1,1708 @@
+/*
+ *	amidicat
+ *
+ *	Copyright © 2010-2014 Joshua Lehan <alsa at krellan.com>
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+/*
+ *	After searching for a while, it seems there is no program
+ *	to simply send MIDI data into the ALSA MIDI system,
+ *	with a minimum of fuss.
+ *
+ *	The program aplaymidi(1) will play .MID files,
+ *	but not "live" MIDI data from standard input.
+ *
+ *	The program amidi(1) can send live data, but only works for
+ *	devices in the ALSA "RawMIDI" namespace, not the overall
+ *	MIDI system, so it can't be used to drive software
+ *	MIDI synthesizers such as TiMidity.
+ *
+ *	Creating stub .MID files on-the-fly and rapidly playing them
+ *	is a common workaround, but this is undesirable for
+ *	all but the most trivial of uses.
+ *
+ *	This program attempts to rectify these limitations,
+ *	similarly to the purpose of netcat(1).
+ *
+ *	What's more, this program also works in both directions
+ *	simultaneously: MIDI data can also be read out from
+ *	the ALSA MIDI system, and made available as standard
+ *	output.
+ *
+ *	This program is an ALSA sequencer client.
+ *
+ *	Much code was borrowed from vkeybd(1).
+ */
+
+
+#include <pthread.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <alsa/asoundlib.h>
+#include <errno.h>
+#include <time.h>
+
+#include "version.h"
+
+
+/* Constants */
+#define DEFAULT_SEQ_NAME	("default")
+#define DEFAULT_CLI_NAME	("amidicat")
+#define ERR_OPEN		(10)
+#define ERR_FINDPORT		(11)
+#define ERR_CONNPORT		(12)
+#define ERR_PARAM		(13)
+#define ERR_THREAD		(14)
+#define DEFAULT_BUFSIZE		(65536)
+#define MAX_WAIT		(1000)
+#define MAX_DELAY		(1000)
+#define FILE_STDIN		("-")
+#define MAX_HEX_DIGITS		(2)
+
+
+/* Globals */
+pthread_mutex_t	g_mutex;
+pthread_cond_t	g_cond;
+int		g_is_endinput;
+
+
+/* Structures */
+struct in_args_t {
+	char		**args_ptr;
+	snd_seq_t	*handle;
+	int		is_hex;
+	int		cli_id;
+	int		port_id;
+	int		target_cli;
+	int		target_port;
+	int		spacing_us;
+	int		wait_sec;
+	int		is_read;
+};
+
+struct out_args_t {
+	snd_seq_t	*handle;
+	int		is_hex;
+};
+
+
+/*
+ * Better atoi() that returns -1, not 0, if error
+ * and checks entire string, not just first part, for validity
+ */
+int
+better_atoi(const char *nptr)
+{
+	char	*endptr;
+	long	num;
+	int	ret;
+
+	num = strtol(nptr, &endptr, 0);
+	if (NULL != nptr && '\0' == *endptr) {
+		/* String is valid */
+		ret = num;
+
+		/* Guard against size mismatch by comparing int and long */
+		if (ret == num)
+			return ret;
+	}
+
+	return -1;
+}
+
+
+/*
+ * Better usleep() that does not use signals,
+ * so is safe to use along with threads
+ */
+void
+microsleep(long usec)
+{
+	struct timespec	tv;
+
+	/* No need to sleep if time is not positive */
+	if (usec > 0) {
+		tv.tv_sec = usec / 1000000;
+		tv.tv_nsec = (usec % 1000000) * 1000;
+
+		/* Ignore return value, waking up early is not a problem */
+		nanosleep(&tv, NULL);
+	}
+}
+
+
+/* Prettyprints capabilities mask */
+void
+prettyprint_capmask(unsigned int capmask)
+{
+	/* The r/w letters indicate read/write capability */
+	/* Capital R/W letters indicate subscription */
+	printf("%s%s%s%s"
+		, ((capmask & SND_SEQ_PORT_CAP_READ)       ? "r" : "-")
+		, ((capmask & SND_SEQ_PORT_CAP_WRITE)      ? "w" : "-")
+		, ((capmask & SND_SEQ_PORT_CAP_SUBS_READ)  ? "R" : "-")
+		, ((capmask & SND_SEQ_PORT_CAP_SUBS_WRITE) ? "W" : "-")
+	);
+}
+
+
+/*
+ * Iterates through list of all active ALSA sequencer ports
+ * handle = ALSA sequencer handle
+ * str = String to search for a match for, or NULL to ignore
+ * is_print = Set to 1 if you want to see output
+ * outcli = Receives found client ID, if searching
+ * outport = Receives found port ID, if searching
+ * Returns 0 if a match was found, -1 if no matches were found
+ * The search is by exact string match (no wildcards or
+ * substrings yet).  The column of port names will be
+ * searched first, in top-down order, then the column of
+ * client names will be similarly searched.  First match wins.
+ */
+int
+discover_ports(snd_seq_t *handle, char *str, int is_print,
+int *outcli, int *outport)
+{
+	snd_seq_client_info_t	*cli_info;
+	snd_seq_port_info_t	*port_info;
+	char			*cli_name;
+	char			*port_name;
+	unsigned int		port_capmask;
+	unsigned int		port_typemask;
+	int			r;
+	int			cli_id;
+	int			cli_num_ports;
+	int			port_id;
+	int			lowest_port;
+	int			match_bycli_cli_id = -1;
+	int			match_bycli_port_id = -1;
+	int			match_byport_cli_id = -1;
+	int			match_byport_port_id = -1;
+	int			is_first = 1;
+
+	snd_seq_client_info_malloc(&cli_info);
+	snd_seq_port_info_malloc(&port_info);
+
+	/* Iterate through all clients */
+	snd_seq_client_info_set_client(cli_info, -1);
+	for (;;) {
+		r = snd_seq_query_next_client(handle, cli_info);
+		if (r < 0)
+			break;
+
+		/* Got a client */
+		cli_id = snd_seq_client_info_get_client(cli_info);
+		cli_name = strdup(snd_seq_client_info_get_name(cli_info));
+		cli_num_ports = snd_seq_client_info_get_num_ports(cli_info);
+
+		/* Iterate through all ports on this client */
+		snd_seq_port_info_set_client(port_info, cli_id);
+		lowest_port = -1;
+		snd_seq_port_info_set_port(port_info, lowest_port);
+		for (;;) {
+			r = snd_seq_query_next_port(handle, port_info);
+			if (r < 0)
+				break;
+
+			/* Got a port */
+			port_id =
+				snd_seq_port_info_get_port(port_info);
+			port_name = strdup(
+				snd_seq_port_info_get_name(port_info));
+			port_typemask =
+				snd_seq_port_info_get_type(port_info);
+			port_capmask =
+				snd_seq_port_info_get_capability(port_info);
+
+			/* Arbitrarily use lowest port number on this client
+			 * when matching by name */
+			if (-1 == lowest_port)
+				lowest_port = port_id;
+
+			/* Print header */
+			if (is_first) {
+				/* The field lengths of strings
+				 * are taken from "aplaymidi -l" */
+				if (is_print)
+					printf(" Port    %-32s %-32s rwRW\n"
+						, "Client name"
+						, "Port name"
+					);
+
+				is_first = 0;
+			}
+
+			/* Print line */
+			/* FUTURE: Perhaps also print type bits if relevant */
+			if (is_print) {
+				printf("%3d:%-3d  %-32s %-32s "
+					, cli_id
+					, port_id
+					, cli_name
+					, port_name
+				);
+				prettyprint_capmask(port_capmask);
+				printf("\n");
+			}
+
+			/* FUTURE: Perhaps also make use of this information */
+			/* Suppress warning message */
+			(void)cli_num_ports;
+			(void)port_typemask;
+
+			/* Test for match, if not already matched by port */
+			if (NULL != str && -1 == match_byport_cli_id) {
+				if (0 == strcasecmp(str, port_name)) {
+					match_byport_cli_id = cli_id;
+					match_byport_port_id = port_id;
+				}
+			}
+
+			free(port_name);
+		}
+
+		/* Test for match, if not already matched by client */
+		if (NULL != str && -1 == match_bycli_cli_id) {
+			if (0 == strcasecmp(str, cli_name)) {
+				if (-1 != lowest_port) {
+					match_bycli_cli_id = cli_id;
+					match_bycli_port_id = lowest_port;
+				}
+			}
+		}
+
+		free(cli_name);
+	}
+
+	snd_seq_port_info_free(port_info);
+	snd_seq_client_info_free(cli_info);
+
+	/* If both matched, prefer port match (most specific)
+	 * over client match */
+	if (-1 != match_byport_cli_id) {
+		*outcli = match_byport_cli_id;
+		*outport = match_byport_port_id;
+		return 0;
+	}
+	if (-1 != match_bycli_cli_id) {
+		*outcli = match_bycli_cli_id;
+		*outport = match_bycli_port_id;
+		return 0;
+	}
+
+	/* No match found, or no string given to match by */
+	return 1;
+}
+
+
+/*
+ * Parses string into ALSA client:port numbers
+ * String can be 2 numbers separated by ":" or "." or "-" delimiters,
+ * or the name of a client or port can be given
+ * and a lookup will be done in order to learn the numbers.
+ * Results stored in "outcli" and "outport".
+ * Returns 0 if successful, or -1 if unparseable or not found.
+ */
+int
+str_to_cli_port(snd_seq_t *handle, char *str, int *outcli, int *outport)
+{
+	char	*delim;
+	char	*nextstr;
+	int	cli_id = -1;
+	int	port_id = -1;
+	int	r;
+	char	savedch;
+
+	/* Fairly generous in choice of delimiters */
+	delim = strpbrk(str, ":.-");
+	if (delim) {
+		/* Look at string immediately following the delimiter */
+		nextstr = delim + 1;
+
+		/* Perform string fission */
+		savedch = *delim;
+		*delim  = '\0';
+
+		cli_id  = better_atoi(str);
+		port_id = better_atoi(nextstr);
+
+		*delim = savedch;
+	}
+
+	/* If not parseable as numbers, try string match */
+	if (cli_id < 0 || port_id < 0) {
+		r = discover_ports(handle, str, 0, &cli_id, &port_id);
+
+		/* Return error, regardless of ID, if discovery failed */
+		if (r < 0)
+			return -1;
+	}
+
+	/* Not found or not parseable */
+	if (cli_id < 0 || port_id < 0)
+		return -1;
+
+	/* Both are good results */
+	*outcli  = cli_id;
+	*outport = port_id;
+	return 0;
+}
+
+
+/*
+ * Opens a new connection to the ALSA sequencer
+ * Can be opened for input (read), output (write), or both
+ * Returns handle or NULL if error, prints message if error
+ */
+snd_seq_t *
+seq_open(int is_read, int is_write)
+{
+	snd_seq_t	*handle;
+	int		err;
+	int		mode;
+
+	/* Default to duplex unless told otherwise */
+	mode = SND_SEQ_OPEN_DUPLEX;
+
+	/* Read only */
+	if (is_read && !is_write)
+		mode = SND_SEQ_OPEN_INPUT;
+
+	/* Write only */
+	if (is_write && !is_read)
+		mode = SND_SEQ_OPEN_OUTPUT;
+
+	/* Always use the default "sequencer name" here,
+	 * this is not the client name */
+	err = snd_seq_open(&handle, DEFAULT_SEQ_NAME, mode, 0);
+	if (err < 0) {
+		fprintf(stderr, "Unable to open ALSA sequencer: %s\n",
+			strerror(errno));
+		return NULL;
+	}
+	return handle;
+}
+
+
+/*
+ * Closes the connection to the ALSA sequencer
+ */
+void
+seq_close(snd_seq_t *handle)
+{
+	snd_seq_close(handle);
+}
+
+
+/*
+ * Sets up the ALSA sequencer for use
+ * Sets our client name
+ * Allocates our port
+ * Connects to remote client and port,
+ * unless they are -1 then use ALSA subscription mechanism instead
+ * Learns our own ID's and saves them in "outcli" and "outport"
+ * Returns 0 if all went well, or -1 if error, prints message if error
+ */
+int
+seq_setup(snd_seq_t *handle, char *cli_name, int target_cli, int target_port,
+int is_read, int is_write, int *outcli, int *outport)
+{
+	int	cli_id;
+	int	port_id;
+	int	r;
+	int	caps = 0;
+	int	is_subs = 0;
+
+	/* Get our client ID */
+	cli_id = snd_seq_client_id(handle);
+	*outcli = cli_id;
+
+	/* Set our name */
+	r = snd_seq_set_client_name(handle, cli_name);
+	if (r < 0) {
+		/* This early in the program, it's not threaded,
+		 * errno is OK to use */
+		fprintf(stderr, "Unable to set ALSA client name: %s\n",
+			strerror(errno));
+		return -1;
+	}
+
+	/* Enable reading and/or writing */
+	if (is_read)
+		caps |= SND_SEQ_PORT_CAP_READ;
+	if (is_write)
+		caps |= SND_SEQ_PORT_CAP_WRITE;
+	if (is_read && is_write)
+		caps |= SND_SEQ_PORT_CAP_DUPLEX;
+
+	if (target_cli < 0 || target_port < 0) {
+		/* Use ALSA subscription mechanism if target not given */
+		/* FUTURE: Might want both subscription and target at once */
+		if (is_read)
+			caps |= SND_SEQ_PORT_CAP_SUBS_READ;
+		if (is_write)
+			caps |= SND_SEQ_PORT_CAP_SUBS_WRITE;
+
+		/* There is no corresponding SUBS_DUPLEX flag */
+		is_subs = 1;
+	}
+
+	/* Open origin port */
+	/* FUTURE: Do we need any more type bits here? */
+	port_id = snd_seq_create_simple_port(handle, cli_name, caps,
+		SND_SEQ_PORT_TYPE_MIDI_GENERIC);
+	if (port_id < 0) {
+		fprintf(stderr, "Unable to open ALSA sequencer port: %s\n",
+			strerror(errno));
+		return -1;
+	}
+
+	/* Connect both to and from target port, if not using subscription */
+	if (!is_subs) {
+		if (is_write) {
+			r = snd_seq_connect_to(handle, port_id,
+					       target_cli, target_port);
+			if (r < 0) {
+				fprintf(stderr,
+				"Unable to connect to ALSA port %d:%d: %s\n"
+					, target_cli
+					, target_port
+					, strerror(errno)
+				);
+				return -1;
+			}
+		}
+		if (is_read) {
+			r = snd_seq_connect_from(handle, port_id,
+						 target_cli, target_port);
+			if (r < 0) {
+				fprintf(stderr,
+				"Unable to connect from ALSA port %d:%d: %s\n"
+					, target_cli
+					, target_port
+					, strerror(errno)
+				);
+				return -1;
+			}
+		}
+	}
+
+	/* Should be all good to go now */
+	*outcli  = cli_id;
+	*outport = port_id;
+	return 0;
+}
+
+
+/*
+ * Writes an event into ALSA
+ * Blocks/retries until ALSA has received and delivered
+ * the event (as best as we can verify)
+ * Tries its best to avoid flooding the ALSA input queue
+ * ev = The event to be sent into ALSA
+ * port_id = Our port ID
+ * target_cli, target_port = The target's client and port ID,
+ * or -1 to just send to "subscribers"
+ * spacing_us = Spacing time, in microseconds,
+ * to be used when busywaiting some loops
+ * Returns negative error code if error,
+ * or the number of retries required if successful
+ */
+int
+write_event(snd_seq_t *handle, snd_seq_event_t *ev, int port_id,
+	    int target_cli, int target_port, int spacing_us)
+{
+	int	r;
+	int	ct_output = 0;
+	int	ct_drain = 0;
+	int	ct_sync = 0;
+	int	is_draingood;
+	int	is_syncgood;
+	int	total;
+
+	/* Fill in event data structure, these are macros and never fail */
+	snd_seq_ev_set_source(ev, port_id);
+
+	if (target_cli < 0 || target_port < 0)
+		/* Send to all subscribers,
+		 * possibly playing to an empty house */
+		snd_seq_ev_set_subs(ev);
+	else
+		snd_seq_ev_set_dest(ev, target_cli, target_port);
+
+	snd_seq_ev_set_direct(ev);
+
+	/* Fire event */
+	for (;;) {
+		/* FUTURE: Maybe have option to let user choose
+		 * between output and output_direct? */
+		r = snd_seq_event_output_direct(handle, ev);
+		if (r < 0) {
+			/* Return if a real error happened */
+			if (-EAGAIN != r)
+				return r;
+
+			/* Error was "Try again", wait and do just that */
+			microsleep(spacing_us);
+			ct_output++;
+			continue;
+		}
+
+		/* Event sent into ALSA, do NOT try again */
+		break;
+	}
+
+	/* Loop until output is fully pushed into ALSA */
+	/* FUTURE: Even though this loop works,
+	 * it's still too easy to flood the other end and overrun,
+	 * perhaps a queuing bug internally within ALSA? */
+	for (;;) {
+		is_draingood = 0;
+		is_syncgood  = 0;
+
+		r = snd_seq_drain_output(handle);
+		if (r < 0) {
+			/* Return if a real error happened */
+			if (-EAGAIN != r)
+				return r;
+		}
+
+		/* Stop retrying only when there are no more
+		 * events left to be drained */
+		if (0 == r)
+			is_draingood = 1;
+		else
+			ct_drain++;
+
+		r = snd_seq_sync_output_queue(handle);
+		if (r < 0) {
+			/* Return if a real error happened */
+			if (-EAGAIN != r)
+				return r;
+		}
+
+		/* FUTURE: Why does snd_seq_sync_output_queue()
+		 * always return 1, not 0 as it should? */
+		if (0 == r || 1 == r)
+			is_syncgood = 1;
+		else
+			ct_sync++;
+
+		/* Only return if both drain and sync are clear */
+		if (is_draingood && is_syncgood)
+			break;
+
+		/* Throttle CPU when busywaiting */
+		microsleep(spacing_us);
+	}
+
+	total = ct_output + ct_drain + ct_sync;
+	/* FUTURE: Suppress this text if verbose flag
+	 * was given (it becomes redundant) */
+	if (total > 0) {
+		fprintf(stderr, "Incoming congestion");
+		if (ct_output > 0)
+			fprintf(stderr, ", %d output retries", ct_output);
+		if (ct_drain > 0)
+			fprintf(stderr, ", %d drain retries", ct_drain);
+		if (ct_sync > 0)
+			fprintf(stderr, ", %d sync retries", ct_sync);
+		fprintf(stderr, "\n");
+	}
+
+	return total;
+}
+
+
+/*
+ * Writes a buffer to stdout
+ * Returns 0 if successful or nonzero if error
+ * Writes either as binary bytes or hex digits
+ */
+int
+write_stdout(unsigned char *buf, long bufsize, int is_hex)
+{
+	unsigned char	*bufptr;
+	long		size_left;
+	long		size_written;
+	unsigned int	ui;
+	int		r;
+	unsigned char	uc;
+
+	bufptr = buf;
+	size_left = bufsize;
+	if (is_hex) {
+		/* Print hex bytes, e.g. 90 3C 7F */
+		while (size_left > 0) {
+			uc = *bufptr;
+			ui = uc;
+
+			/* Separate by spaces,
+			 * unless it's the last one which gets newline */
+			r = printf("%02X%s", ui,
+				((size_left > 1) ? " " : "\n"));
+			if (r < 0)
+				return -1;
+
+			bufptr++;
+			size_left--;
+		}
+	} else {
+		/* Full write to stdout */
+		while (size_left > 0) {
+			size_written = write(STDOUT_FILENO,
+					     bufptr,
+					     size_left);
+			if (size_written < 0)
+				return -1;
+
+			bufptr += size_written;
+			size_left -= size_written;
+		}
+	}
+
+	return 0;
+}
+
+
+/*
+ * Parses an incoming unsigned byte of ASCII text, representing a
+ * hex digit, eventually building up and returning complete hex numbers
+ * Uses static variables to keep state across calls
+ * Hex digits are separated by whitespace, or if enough hex digits
+ * have been read and maximum size has been reached, no separator
+ * is necessary (so digits can all be ran together)
+ * If not whitespace, each byte of text must be in range [0-9A-Fa-f]
+ * Returns unsigned hex number if successful
+ * Returns -1 if error (unrecognized byte of ASCII text)
+ * Returns -2 if the complete hex number is not available yet (still good)
+ * As a special case, pass in a value of -2 when at an input boundary,
+ * this will reset the state and return the hex number that was in
+ * progress (if any)
+ */
+int
+parse_hex_byte(int ch)
+{
+	static int	hex_value;
+	static int	read_nybbles;
+
+	int		ret;
+	int		nybble;
+
+	/* Parse human-readable digit into nybble value */
+	nybble = -1;
+	if (ch >= '0' && ch <= '9')
+		nybble = ch - '0';
+	if (ch >= 'A' && ch <= 'F')
+		nybble = 10 + (ch - 'A');
+	if (ch >= 'a' && ch <= 'f')
+		nybble = 10 + (ch - 'a');
+
+	if (-1 == nybble) {
+		switch (ch) {
+		/* Special case accept -2 as a zero-length reset request */
+		case -2:
+			/* Fall through */
+
+		/* Standard C whitespace */
+		/* Avoid usage of isspace()
+		 * because that would introduce locale variations */
+		case ' ':
+		case '\f':
+		case '\n':
+		case '\r':
+		case '\t':
+		case '\v':
+			/* Clear state, and return -2
+			 * if there was nothing to begin with */
+			ret = -2;
+			if (read_nybbles > 0)
+				ret = hex_value;
+
+			hex_value = 0;
+			read_nybbles = 0;
+			return ret;
+
+		/* No default */
+		}
+
+		/* Unrecognized character */
+		return -1;
+	}
+
+	/* Digit is valid, build up a number with it */
+	hex_value <<= 4;
+	hex_value += nybble;
+	read_nybbles++;
+
+	/* Force number to be finished, if maximum digit count reached */
+	if (read_nybbles >= MAX_HEX_DIGITS) {
+		ret = hex_value;
+		hex_value = 0;
+		read_nybbles = 0;
+		return ret;
+	}
+
+	/* Successfully stored digit, but nothing to return yet */
+	return -2;
+}
+
+
+/*
+ * Reads a byte of input from various sources
+ * Fills in byte_ptr with the byte that was read
+ * Uses static variables to keep state across calls
+ * Pass in the argc array from the command line,
+ * after all options have been removed.
+ * Each string in the array should be a filename, and will
+ * be opened and read from.
+ * The filename "-" is special, and means standard input.
+ * Passing in a pointer that is valid but points to NULL,
+ * indicating an empty command line,
+ * is also special, and means standard input.
+ * If is_hex is true, will read human-readable hex digits,
+ * assemble them into bytes, and return the bytes.
+ * Returns 1 if successful, -1 if error,
+ * or 0 if clean EOF after finishing all files.
+ */
+int
+input_byte(char **args_ptr, int is_hex, char *byte_ptr)
+{
+	/* Static variables for holding state */
+	static char	**args_iter;
+	static char	*file_name;
+	static int	file_fd = -1;
+	static int	need_open;
+	static int	is_inited;
+	static int	is_delayed_eof;
+
+	unsigned char	byte_buf;
+	int		ret;
+	int		val;
+	int		r;
+
+	/* Only do initialization once */
+	if (!is_inited) {
+		args_iter = args_ptr;
+		need_open = 1;
+
+		is_inited = 1;
+	}
+
+	/* Keep looping around, opening next files as necessary,
+	 * until we have something to return */
+	for (;;) {
+		/* This gets set if previous file reached EOF,
+		 * or during init */
+		if (need_open) {
+			/* Open next file pointed to */
+			file_name = *args_iter;
+			if (NULL != file_name) {
+				/* Filename given */
+				if (0 == strcmp(FILE_STDIN, file_name)) {
+					/* Special case
+					 * filename "-" is stdin */
+					file_fd = STDIN_FILENO;
+				} else {
+					/* Open the given filename */
+					if (-1 != file_fd) {
+						fprintf(stderr,
+							"Internal error, failed to close previous file\n"
+						);
+						return -1;
+					}
+					file_fd = open(file_name, O_RDONLY);
+					if (-1 == file_fd) {
+						/* FUTURE: Should it
+						 * auto-advance to next file,
+						 * instead of erroring out? */
+						fprintf(stderr,
+							"Unable to open file %s: %s\n"
+							, file_name
+							, strerror(errno)
+						);
+						return file_fd;
+					}
+				}
+			} else {
+				/* No files at all on command line,
+				 * special case read from stdin */
+				file_name = "standard input";
+				file_fd = STDIN_FILENO;
+			}
+
+			/* Successfully at beginning of next file */
+			if (is_hex) {
+				/* Init hex state, better already be empty
+				 * from previous file */
+				val = parse_hex_byte(-2);
+				if (-2 != val) {
+					fprintf(stderr,
+						"Internal error, failed to clear hex state across files\n"
+					);
+					return -1;
+				}
+			}
+			need_open = 0;
+		}
+
+		/* Should have a valid file_fd at this point */
+		if (is_delayed_eof) {
+			/* No new reading of data during this pass,
+			 * just held over EOF from last time */
+			ret = 0;
+			is_delayed_eof = 0;
+		} else {
+			/* Read a byte of raw input (might be a hex digit) */
+			ret = read(file_fd, &byte_buf, 1);
+		}
+
+		if (-1 == ret) {
+			/* Ignore harmless errors and retry */
+			if (EINTR == errno || EAGAIN == errno)
+				continue;
+
+			fprintf(stderr,
+				"Problem reading from file %s: %s\n",
+				file_name, strerror(errno));
+			return ret;
+		}
+
+		/* Advance to next file, if EOF detected in this file */
+		if (0 == ret) {
+			/* The file might have ended
+			 * in the middle of a hex digit */
+			if (is_hex) {
+				val = parse_hex_byte(-2);
+				if (-2 != val) {
+					/* Poor man's coroutine: delay EOF by
+					 * one pass, insert this final byte */
+					is_delayed_eof = 1;
+
+					/* Return final byte */
+					*byte_ptr = (char)val;
+					return 1;
+				}
+			}
+
+			if (-1 != file_fd) {
+				/* Don't close stdin, we may need it again if
+				 * user gives "-" more than once
+				 * on command line */
+				if (file_fd != STDIN_FILENO) {
+					r = close(file_fd);
+					if (0 != r) {
+						/* This error is nonfatal,
+						 * don't return here */
+						fprintf(stderr,
+							"Problem closing file %s: %s\n"
+							, file_name
+							, strerror(errno)
+						);
+					}
+				}
+				file_fd = -1;
+			}
+
+			/* Take another look at command line */
+			file_name = *args_iter;
+			if (NULL != file_name) {
+				/* Advance to next file in sequence */
+				args_iter++;
+
+				file_name = *args_iter;
+				if (NULL != file_name) {
+					/* Next file will be opened
+					 * after we loop around */
+					need_open = 1;
+					continue;
+				} else {
+					/* Finished with all files
+					 * on command line */
+					return 0;
+				}
+			} else {
+				/* No files at all on command line,
+				 * EOF means end of stdin */
+				return 0;
+			}
+		}
+
+		/* Successfully read a byte of data */
+		if (1 == ret) {
+			/* Piece hex number together */
+			if (is_hex) {
+				val = parse_hex_byte(byte_buf);
+
+				/* Successfully read a byte,
+				 * but hex number not complete yet */
+				if (-2 == val)
+					continue;
+
+				if (-1 == val) {
+					fprintf(stderr,
+						"Unrecognizable hex digit text in file %s: %c (%d)\n"
+						, file_name
+						, (int)byte_buf
+						, (int)byte_buf
+					);
+					return -1;
+				}
+
+				/* Successfully assembled hex number */
+				*byte_ptr = (char)val;
+				return ret;
+			}
+
+			/* Hex not used, return byte exactly as it was read */
+			*byte_ptr = (char)byte_buf;
+			return ret;
+		}
+
+		/* Should never get here */
+		break;
+	}
+
+	/* Should never get here */
+	fprintf(stderr, "Internal error in file reading: %d\n", ret);
+	return -1;
+}
+
+
+void *
+stdin_loop(void *args)
+{
+	struct in_args_t	*in_args;
+	char			**args_ptr;
+	snd_midi_event_t	*parser;
+	snd_seq_t		*handle;
+	snd_seq_event_t		ev;
+	long			ct_bytes = 0;
+	int			cli_id;
+	int			port_id;
+	int			target_cli;
+	int			target_port;
+	int			spacing_us;
+	int			wait_sec;
+	int			is_read;
+	int			is_hex;
+	int			r;
+	int			i;
+	int			is_active = 1;
+	int			ct_events = 0;
+	int			ct_congested = 0;
+	char			bytein;
+
+	/* Recover arguments */
+	in_args = (struct in_args_t *)args;
+	args_ptr    = in_args->args_ptr;
+	is_hex      = in_args->is_hex;
+	handle      = in_args->handle;
+	cli_id      = in_args->cli_id;
+	port_id     = in_args->port_id;
+	target_cli  = in_args->target_cli;
+	target_port = in_args->target_port;
+	spacing_us  = in_args->spacing_us;
+	wait_sec    = in_args->wait_sec;
+	is_read     = in_args->is_read;
+
+	snd_midi_event_new(DEFAULT_BUFSIZE, &parser);
+	snd_midi_event_init(parser);
+	snd_midi_event_reset_decode(parser);
+
+	snd_midi_event_no_status(parser, 1);
+
+	/* Reset event */
+	snd_seq_ev_clear(&ev);
+
+	/* FUTURE: Might need to maintain our own buffer,
+	 * to overcome ALSA SysEx size limitation */
+	while (is_active) {
+		/* Read from input, either stdin or files */
+		r = input_byte(args_ptr, is_hex, &bytein);
+		switch (r) {
+		case 0:		/* Clean EOF */
+			is_active = 0;
+			break;
+
+		case 1:		/* Byte received */
+			/* No action necessary */
+			ct_bytes++;
+			break;
+
+		default:
+			/* Error messages already printed by input_byte() */
+			is_active = 0;
+			break;
+		}
+
+		/* Feed byte into parser, as int */
+		i = bytein;
+		r = snd_midi_event_encode_byte(parser, i, &ev);
+		switch (r) {
+		case 0:		/* More bytes needed for event */
+			/* No action necessary */
+			break;
+
+		case 1:		/* Message complete */
+			/* Send completed event into ALSA */
+			r = write_event(handle, &ev, port_id,
+					target_cli, target_port, spacing_us);
+			if (r < 0) {
+				fprintf(stderr,
+					"Event write error: %s\n",
+					strerror(-r));
+				is_active = 0;
+				break;
+			}
+
+			/* The return value was the number of retries */
+			if (r > 0)
+				ct_congested++;
+
+			/* Reset event after write */
+			snd_seq_ev_clear(&ev);
+			ct_events++;
+
+			/* Wait for spacing between events, if desired */
+			microsleep(spacing_us);
+			break;
+
+		default:	/* Error */
+			fprintf(stderr,
+				"Internal error, from ALSA event encode byte: %s\n"
+				, strerror(-r)
+			);
+			is_active = 0;
+			break;
+		}
+	}
+
+	/* Input finished */
+	/* FUTURE: Perhaps an option to suppress this */
+	fprintf(stderr,
+		"Input total: %d MIDI messages, %ld bytes",
+		ct_events, ct_bytes);
+	if (ct_congested > 0) {
+		fprintf(stderr,
+			", %d events congested", ct_congested);
+	}
+	fprintf(stderr, "\n");
+
+	/* Give some time for output-only if the user desires */
+	microsleep(1000000 * wait_sec);
+
+	/* Set global variable, under mutex, so other thread sees it */
+	pthread_mutex_lock(&g_mutex);
+	g_is_endinput = 1;
+	pthread_mutex_unlock(&g_mutex);
+
+	/* Tell the reader thread that we are done */
+	if (is_read) {
+		/* Send a dummy message to ourself,
+		 * so ALSA gets unblocked in other thread */
+		snd_seq_ev_clear(&ev);
+		r = write_event(handle, &ev, port_id, cli_id,
+				port_id, spacing_us);
+
+		/* FUTURE: Indicate error result to reader thread */
+		if (r < 0) {
+			fprintf(stderr,
+				"Final event write error: %s\n"
+				, strerror(-r)
+			);
+		}
+	}
+
+	snd_midi_event_free(parser);
+
+	/* FUTURE: Perhaps bubble up an error result */
+	return NULL;
+}
+
+
+void *
+stdout_loop(void *args)
+{
+	struct out_args_t	*out_args;
+	unsigned char		*buffer;
+	snd_seq_t		*handle;
+	snd_midi_event_t	*parser;
+	snd_seq_event_t		*evptr;
+	long			size_ev;
+	long			ct_bytes = 0;
+	int			is_hex;
+	int			r;
+	int			is_active = 1;
+	int			ct_overruns = 0;
+	int			ct_events = 0;
+	int			ct_nonevents = 0;
+	int			is_endinput;
+
+	/* Recover arguments */
+	out_args = (struct out_args_t *)args;
+	handle = out_args->handle;
+	is_hex = out_args->is_hex;
+
+	snd_midi_event_new(DEFAULT_BUFSIZE, &parser);
+	snd_midi_event_init(parser);
+	snd_midi_event_reset_decode(parser);
+
+	snd_midi_event_no_status(parser, 1);
+
+	/* Allocate buffer */
+	buffer = malloc(DEFAULT_BUFSIZE);
+
+	while (is_active) {
+		/* ALSA will set this pointer to somewhere within itself,
+		 * if successful */
+		/* FUTURE: This is not threadsafe, but since this is
+		 * the only thread that does ALSA event input,
+		 * hopefully it's OK for now */
+		evptr = NULL;
+
+		/* BLOCK until event comes in from ALSA */
+		r = snd_seq_event_input(handle, &evptr);
+		if (r < 0) {
+			/* ENOSPC indicates that ALSA's internal buffer
+			 * overran and we lost some events */
+			if (-ENOSPC == r) {
+				/* FUTURE: Only show this
+				 * if verbose is turned on */
+				fprintf(stderr,
+					"Reported overrun while reading from ALSA to output\n"
+				);
+				ct_overruns++;
+				continue;
+			} else {
+				fprintf(stderr,
+					"Error reading event from ALSA to output: %s\n"
+					, strerror(-r)
+				);
+				is_active = 0;
+				break;
+			}
+		}
+		if (NULL == evptr) {
+			/* FUTURE: Shouldn't happen, perhaps remove this */
+			fprintf(stderr,
+				"Internal error reading event from ALSA\n");
+			is_active = 0;
+			break;
+		}
+
+		/* Check global flag, under lock,
+		 * see if other thread is telling us to exit */
+		pthread_mutex_lock(&g_mutex);
+		is_endinput = g_is_endinput;
+		pthread_mutex_unlock(&g_mutex);
+		if (is_endinput) {
+			/* Clean exit */
+			is_active = 0;
+			break;
+		}
+
+		/* Unpack event into bytes */
+		size_ev = snd_midi_event_decode(parser, buffer,
+						DEFAULT_BUFSIZE, evptr);
+		if (size_ev < 0) {
+			/* ENOENT indicates an event that is
+			 * not a MIDI message, silently skip it */
+			if (-ENOENT == size_ev) {
+				ct_nonevents++;
+				/* FUTURE: Suppress this with quiet option */
+				fprintf(stderr,
+					"Received non-MIDI message\n");
+				continue;
+			} else {
+				fprintf(stderr,
+					"Error decoding event from ALSA to output: %s\n"
+					, strerror(-size_ev)
+				);
+				is_active = 0;
+				break;
+			}
+		}
+
+		/* FUTURE: Might need some code here to cat multiple
+		 * SysEx events together (0xF0 ... 0xF7), to overcome ALSA
+		 * internal size limit splitting them */
+
+		/* Output to stdout */
+		if (size_ev > 0) {
+			r = write_stdout(buffer, size_ev, is_hex);
+			if (r < 0) {
+				fprintf(stderr,
+					"Error writing output: %s\n",
+					strerror(errno));
+				is_active = 0;
+				break;
+			}
+
+			ct_bytes += size_ev;
+		}
+
+		ct_events++;
+	}
+
+	/* This block will only be reached
+	 * once other thread tells us to exit */
+	/* If blocked in ALSA above,
+	 * a dummy event will need to be faked up,
+	 * to get ALSA to return */
+	/* FUTURE: Perhaps suppress this text with quiet option */
+	fprintf(stderr,
+		"Output total: %d MIDI messages, %ld bytes",
+		ct_events, ct_bytes);
+	if (ct_nonevents > 0)
+		fprintf(stderr, ", %d non-MIDI events", ct_nonevents);
+	if (ct_overruns > 0)
+		fprintf(stderr, ", %d ALSA read overruns", ct_overruns);
+	fprintf(stderr, "\n");
+
+	free(buffer);
+	snd_midi_event_free(parser);
+
+	/* FUTURE: Perhaps bubble up an error result */
+	return NULL;
+}
+
+
+/* Trivial function */
+void
+show_version(void)
+{
+	printf("amidicat version %s by Joshua Lehan <alsa at krellan.com>\n",
+	       SND_UTIL_VERSION_STR);
+}
+
+
+void
+help_screen(char *exename)
+{
+	/* Put help screen on stdout, not stderr,
+	 * because help screen replaces normal output */
+	/* Apologies for the awkward line breaks,
+	 * the mandate of the checkpatch script left no alternative */
+	printf("Usage: %s\n", exename);
+	printf("       [--help] [--version] [--list]\n");
+	printf("       [--name STRING]\n");
+	printf("       [--port CLIENT:PORT] [--addr CLIENT:PORT]\n");
+	printf("       [--hex] [--verbose] [--nowrite] [--noread]\n");
+	printf("       [--delay MILLISECONDS] [--wait SECONDS]\n");
+	printf("       input files....\n");
+	printf(
+		"amidicat(1) hooks up standard input and standard output to the ALSA sequencer.\n"
+	);
+	printf(
+		"Like cat(1), this program will concatenate multiple input files together.\n"
+	);
+	printf("--help    = Show this help screen and exit.\n");
+	printf("--version = Show version line and exit.  This version: %s\n",
+	       SND_UTIL_VERSION_STR);
+	printf(
+		"--list    = Show list of all ALSA sequencer devices and exit:\n"
+	);
+	printf(
+		" For each usable ALSA client and port, number and name are shown,\n"
+	);
+	printf(
+		" and flags: r,w = port can be read from or written to directly,\n"
+	);
+	printf(
+		"            R,W = also can use ALSA \"subscription\" to read or write.\n"
+	);
+	printf(
+		"--name    = Sets name of this program's ALSA connection, as shown\n"
+	);
+	printf("            in --list, default is \"%s\".\n",
+	       DEFAULT_CLI_NAME);
+	printf(
+		"--port    = Makes direct connection to ALSA port, for reading and\n"
+	);
+	printf(
+		"            writing, instead of the default which is just to set up\n"
+	);
+	printf(
+		"            a passive connection for use with ALSA \"subscription\".\n"
+	);
+	printf(
+		" Syntax is either numeric CLIENT:PORT (example: 128:0), or name of\n"
+	);
+	printf(
+		" another program's ALSA connection (use --list to see available).\n"
+	);
+	printf(
+		"--addr    = For compatibility, an exact synonym of the --port option.\n"
+	);
+	printf(
+		"--hex     = Change input and output to be human-readable hex\n"
+	);
+	printf(
+		"            digits (example: 90 3C 7F) instead of binary MIDI bytes.\n"
+	);
+	printf(
+		"--verbose = Provide additional, useful, output to standard error.\n"
+	);
+	printf(
+		"--nowrite = Disable writing data to ALSA from standard input.\n"
+	);
+	printf(
+		"            Intended for allowing connection to read-only devices.\n"
+	);
+	printf(
+		"            Input files may not be given when this option is used.\n"
+	);
+	printf(
+		"--noread  = Disable reading data from ALSA for standard output.\n"
+	);
+	printf(
+		"            Intended for allowing connection to write-only devices.\n"
+	);
+	printf(
+		"--delay   = Inserts a delay, in milliseconds, between each MIDI\n"
+	);
+	printf("            event submitted to ALSA from standard input.\n");
+	printf(
+		"            Intended for avoiding event loss due to queue congestion.\n"
+	);
+	printf(
+		"--wait    = After all input is finished, continue running program for\n"
+	);
+	printf("            this amount of time, in seconds.\n");
+	printf(
+		"            Intended for allowing output to continue after input.\n"
+	);
+}
+
+
+int
+main(int argc, char **argv)
+{
+	/* For getopt */
+	struct option long_options[] = {
+		{ "help",	0, NULL, 'h' },
+		{ "list",	0, NULL, 'l' },
+		{ "name",	1, NULL, 'n' },
+		{ "port",	1, NULL, 'p' },
+		{ "addr",	1, NULL, 'a' },
+		{ "hex",	0, NULL, 'x' },
+		{ "delay",	1, NULL, 'd' },
+		{ "wait",	1, NULL, 'w' },
+		{ "verbose",	0, NULL, 'v' },
+		{ "version",	0, NULL, 'V' },
+		{ "nowrite",	0, NULL, 'W' },
+		{ "noread",	0, NULL, 'R' },
+		{ NULL,		0, NULL, 0 }
+	};
+
+	/* For threading */
+	struct in_args_t	in_args;
+	struct out_args_t	out_args;
+	pthread_t		in_thread;
+	pthread_t		out_thread;
+	int			is_in_started = 0;
+	int			is_out_started = 0;
+
+	snd_seq_t	*handle = NULL;
+	char		*cli_name;
+	int		cli_id;
+	int		port_id;
+	int		target_cli = -1;
+	int		target_port = -1;
+	int		ret;
+	int		c;
+	int		r;
+	int		is_write = 1;
+	int		is_read = 1;
+	int		is_done = 0;
+	int		is_list = 0;
+	int		is_hex = 0;
+	int		is_verbose = 0;
+	int		is_help = 0;
+	int		is_version = 0;
+	int		spacing_us = 0;
+	int		wait_sec = 0;
+
+	/* Initialize defaults */
+	cli_name = strdup(DEFAULT_CLI_NAME);
+
+	/* Parse options */
+	while (!is_done) {
+		c = getopt_long(argc, argv, "hln:p:a:xd:w:vVWR",
+				long_options, NULL);
+		switch (c) {
+		case 'h': /* --help */
+			is_help = 1;
+			break;
+
+		case 'l': /* --list */
+			is_list = 1;
+			break;
+
+		case 'n': /* --name */
+			free(cli_name);
+			cli_name = strdup(optarg);
+			break;
+
+		case 'p': /* --port */
+		case 'a': /* --addr, exactly the same */
+			/* Open ALSA sequencer early,
+			 * we need it for port lookup */
+			if (NULL == handle)
+				handle = seq_open(is_read, is_write);
+			if (NULL == handle) {
+				/* Abort program if unable to open */
+				ret = ERR_OPEN;
+				goto cleanup;
+			}
+			r = str_to_cli_port(handle, optarg,
+					    &target_cli, &target_port);
+			if (r < 0) {
+				/* Abort program if unable
+				 * to locate target port */
+				fprintf(stderr,
+					"Unable to find ALSA sequencer port: %s\n"
+					, optarg
+				);
+				ret = ERR_FINDPORT;
+				goto cleanup;
+			}
+			break;
+
+		case 'x': /* --hex */
+			is_hex = 1;
+			break;
+
+		case 'd': /* --delay */
+			spacing_us = better_atoi(optarg);
+			if (spacing_us < 0) {
+				fprintf(stderr,
+					"Parameter for --delay must be a positive integer: %s\n"
+					, optarg
+				);
+				ret = ERR_PARAM;
+				goto cleanup;
+			}
+			if (spacing_us > MAX_DELAY) {
+				fprintf(stderr,
+					"Parameter for --delay must be %d or less: %s\n"
+					, MAX_DELAY
+					, optarg
+				);
+				ret = ERR_PARAM;
+				goto cleanup;
+			}
+			/* Argument is in milliseconds,
+			 * but stored value is in microseconds */
+			spacing_us *= 1000;
+			break;
+
+		case 'w': /* --wait */
+			wait_sec = better_atoi(optarg);
+			if (wait_sec < 0) {
+				fprintf(stderr,
+					"Parameter for --wait must be a positive integer: %s\n"
+					, optarg
+				);
+				ret = ERR_PARAM;
+				goto cleanup;
+			}
+			if (wait_sec > MAX_WAIT) {
+				fprintf(stderr,
+					"Parameter for --wait must be %d or less: %s\n"
+					, MAX_WAIT
+					, optarg
+				);
+				ret = ERR_PARAM;
+				goto cleanup;
+			}
+			break;
+
+		case 'v': /* --verbose */
+			/* FUTURE: Perhaps multiple verbosity levels */
+			is_verbose = 1;
+			break;
+
+		case 'V': /* --version */
+			is_version = 1;
+			break;
+
+		case 'W': /* --nowrite */
+			is_write = 0;
+			break;
+
+		case 'R': /* --noread */
+			is_read = 0;
+			break;
+
+		case -1: /* Clean end of getopt processing */
+			is_done = 1;
+			break;
+
+		default: /* Error */
+			/* Error message already has been printed by getopt */
+			ret = ERR_PARAM;
+			goto cleanup;
+			break;
+		}
+	}
+
+	/* If both read and write are disabled, there's no point in running */
+	if (!is_read && !is_write) {
+		fprintf(stderr,
+			"Parameters --noread and --nowrite cannot coexist\n");
+		ret = ERR_PARAM;
+		goto cleanup;
+	}
+
+	/* If write to ALSA is disabled, nothing to do with input files */
+	if (!is_write) {
+		/* Command line must be empty after the options */
+		if (NULL != argv[optind]) {
+			fprintf(stderr,
+				"Parameter --nowrite must not be used with any input files\n"
+			);
+			ret = ERR_PARAM;
+			goto cleanup;
+		}
+	}
+
+	/* If write to ALSA is disabled, no input,
+	 * so no waiting after input */
+	if (!is_write) {
+		if (wait_sec > 0) {
+			fprintf(stderr,
+				"Parameters --nowrite and --wait cannot coexist\n"
+			);
+			ret = ERR_PARAM;
+			goto cleanup;
+		}
+	}
+
+	/* For --help, show help screen and exit successfully */
+	if (is_help) {
+		help_screen(argv[0]);
+		ret = 0;
+		goto cleanup;
+	}
+
+	/* For --version, show version line and exit successfully */
+	if (is_version) {
+		show_version();
+		ret = 0;
+		goto cleanup;
+	}
+
+	/* Open ALSA sequencer, if it is not already open */
+	if (NULL == handle)
+		handle = seq_open(is_read, is_write);
+	if (NULL == handle) {
+		/* Abort program if unable to open */
+		ret = ERR_OPEN;
+		goto cleanup;
+	}
+
+	/* Set up connection */
+	r = seq_setup(handle, cli_name, target_cli, target_port,
+		      is_read, is_write, &cli_id, &port_id);
+	if (r < 0) {
+		ret = ERR_CONNPORT;
+		goto cleanup;
+	}
+
+	/* For --list, show list of ports and exit successfully */
+	if (is_list) {
+		discover_ports(handle, NULL, 1, NULL, NULL);
+		ret = 0;
+		goto cleanup;
+	}
+
+	if (is_verbose) {
+		fprintf(stderr,
+			"Connected to ALSA sequencer on port %d:%d\n",
+			cli_id, port_id);
+	}
+
+	/* Initialize globals used for thread synchronization */
+	pthread_mutex_init(&g_mutex, NULL);
+	pthread_cond_init(&g_cond, NULL);
+	g_is_endinput = 0;
+
+	/* The output thread reads from ALSA and provides output */
+	if (is_read) {
+		/* Start output thread first, to avoid input backlog */
+		out_args.handle = handle;
+		out_args.is_hex = is_hex;
+
+		r = pthread_create(&out_thread, NULL, stdout_loop, &out_args);
+		if (r != 0) {
+			fprintf(stderr,
+				"Unable to start output thread: %s\n",
+				strerror(errno));
+			ret = ERR_THREAD;
+			goto threadwait;
+		}
+
+		is_out_started = 1;
+	}
+
+	/* The input thread takes input and writes to ALSA */
+	if (is_write) {
+		/* Start input thread */
+		/* Save pointer to rest of command line, after options */
+		in_args.args_ptr    = &(argv[optind]);
+		in_args.handle      = handle;
+		in_args.is_hex      = is_hex;
+		in_args.cli_id      = cli_id;
+		in_args.port_id     = port_id;
+		in_args.target_cli  = target_cli;
+		in_args.target_port = target_port;
+		in_args.spacing_us  = spacing_us;
+		in_args.wait_sec    = wait_sec;
+		in_args.is_read     = is_read;
+
+		r = pthread_create(&in_thread, NULL, stdin_loop, &in_args);
+		if (r != 0) {
+			fprintf(stderr,
+				"Unable to start input thread: %s\n",
+				strerror(errno));
+			ret = ERR_THREAD;
+			goto threadwait;
+		}
+
+		is_in_started = 1;
+	}
+
+	/* Initialization successful, now just wait for threads to exit */
+	ret = 0;
+
+threadwait:
+	/* Reap threads */
+	if (is_in_started)
+		pthread_join(in_thread, NULL);
+	if (is_out_started)
+		pthread_join(out_thread, NULL);
+
+cleanup:
+	/* Cleanup */
+	if (handle != NULL)
+		seq_close(handle);
+	free(cli_name);
+
+	return ret;
+}
-- 
1.8.3.2



More information about the Alsa-devel mailing list