waverec.c example

This is a sample application that captures (i.e., records) audio data.

/*
 * $QNXLicenseC:
 * Copyright 2016, QNX Software Systems. All Rights Reserved.
 *
 * You must obtain a written license from and pay applicable license fees to QNX
 * Software Systems before you may reproduce, modify or distribute this software,
 * or any work that includes all or part of this software.   Free development
 * licenses are available for evaluation and non-commercial purposes.  For more
 * information visit http://licensing.qnx.com or email licensing@qnx.com.
 *
 * This file may contain contributions from others.  Please review this entire
 * file for other proprietary rights or license notices, as well as the QNX
 * Development Suite License Guide at http://licensing.qnx.com/license-guide/
 * for other information.
 * $
 */


#include <errno.h>
#include <fcntl.h>
#include <gulliver.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/termio.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include <ctype.h>

#include <sys/asoundlib.h>

/* *INDENT-OFF* */
struct
{
    char        riff_id[4];
    uint32_t    wave_len;
    struct
    {
        char        fmt_id[8];
        uint32_t    fmt_len;
        struct
        {
            uint16_t    format_tag;
            uint16_t    voices;
            uint32_t    rate;
            uint32_t    char_per_sec;
            uint16_t    block_align;
            uint16_t    bits_per_sample;
        }
        fmt;
        struct
        {
            char        data_id[4];
            uint32_t    data_len;
        }
        data;
    }
    wave;
}
riff_hdr =
{
    {'R', 'I', 'F', 'F' },
    sizeof (riff_hdr.wave),
    {
        {'W', 'A', 'V', 'E', 'f', 'm', 't', ' '    },
        sizeof (riff_hdr.wave.fmt),
        {
            1, 0, 0, 0, 0, 0
        },
        {
            {'d', 'a', 't', 'a' },
            0,
        }
    }
};
/* *INDENT-ON* */


int
err (char *msg)
{
	perror (msg);
	return -1;
}


int
dev_raw (int fd)
{
	struct termios termios_p;

	if (tcgetattr (fd, &termios_p))
		return (-1);

	termios_p.c_cc[VMIN] = 1;
	termios_p.c_cc[VTIME] = 0;
	termios_p.c_lflag &= ~(ICANON | ECHO | ISIG);
	return (tcsetattr (fd, TCSANOW, &termios_p));
}

int
dev_unraw (int fd)
{
	struct termios termios_p;

	if (tcgetattr (fd, &termios_p))
		return (-1);

	termios_p.c_lflag |= (ICANON | ECHO | ISIG);
	return (tcsetattr (fd, TCSAFLUSH, &termios_p));
}


//*****************************************************************************
/* *INDENT-OFF* */
#ifdef __USAGE
%C [Options] wavfile

Options:
    -a[card#:]<dev#>   the card & device number to record from
                       OR
    -a[name]           the symbolic name of the device to record from

    -8                 use 8 bit mode (16 bit default)
    -b <size>          Sample size (8, 16, 32)
    -m                 record in mono (stereo default)
    -n <voices>        the number of voices to record (2 voices default, stereo)
    -r <rate>          record at rate (44100 default | 48000 44100 22050 11025)
    -t <sec>           seconds to record (5 seconds default)
    -f <frag_size>     requested fragment size
    -v                 verbosity
    -c <args>[,args ...] voice matrix configuration
    -x                 use mmap interface
    -i <0|1>           Interleave samples (default: 1)
    -R <value>         SRC rate method
                       (0 = linear interpolation, 1 = 7-pt kaiser windowed, 2 = 20-pt remez)
    -z<num_frags>      requested number of fragments
    -y                 Nonblocking mode

Note:
    If both 'm' and 'n' are specified in commandline, the one specified later will be used

Args:
    1=<hw_channel_bitmask> hardware channel bitmask for application voice 1
    2=<hw_channel_bitmask> hardware channel bitmask for application voice 2
    3=<hw_channel_bitmask> hardware channel bitmask for application voice 3
    4=<hw_channel_bitmask> hardware channel bitmask for application voice 4
#endif
/* *INDENT-ON* */
//*****************************************************************************

volatile int end = 0;

void sig_handler( int sig_no )
{
	end = 1;
	return;
}

const char *
why_failed ( int why_failed )
{
	switch (why_failed)
	{
		case SND_PCM_PARAMS_BAD_MODE:
			return ("Bad Mode Parameter");
		case SND_PCM_PARAMS_BAD_START:
			return ("Bad Start Parameter");
		case SND_PCM_PARAMS_BAD_STOP:
			return ("Bad Stop Parameter");
		case SND_PCM_PARAMS_BAD_FORMAT:
			return ("Bad Format Parameter");
		case SND_PCM_PARAMS_BAD_RATE:
			return ("Bad Rate Parameter");
		case SND_PCM_PARAMS_BAD_VOICES:
			return ("Bad Vocies Parameter");
		case SND_PCM_PARAMS_NO_CHANNEL:
			return ("No Channel Available");
		default:
			return ("Unknown Error");
	}

	return ("No Error");
}

int
main (int argc, char **argv)
{
	int     i, j;
	int     card = -1;
	int     dev = 0;
	int     ret;

	snd_pcm_t *pcm_handle;
	FILE   *file1;
	unsigned int mSamples;
	int     mSampleRate;
	int     mSampleChannels;
	int     mSampleBits;
	int     mSampleTime;
	char   *mSampleBfr1;
	int     fragsize = -1;
	int     num_frags = -1;
	int     verbose = 0;
	int     mode = SND_PCM_OPEN_CAPTURE;

	int     rtn;
	int     mixer_fd = 0;
	int     pcm_fd;

	snd_pcm_channel_info_t pi;
	snd_mixer_t *mixer_handle = NULL;
	snd_mixer_group_t group;
	snd_pcm_channel_params_t pp;
	snd_pcm_channel_setup_t setup;
	int     bsize, N = 0, c;
	uint32_t voice_mask[] = { 0, 0, 0, 0 };
	struct {
		snd_pcm_chmap_t map;
		unsigned int pos[32];
	} map;
	snd_pcm_voice_conversion_t voice_conversion;
	int     voice_override = 0;
	char   *sub_opts, *sub_opts_copy, *value;
	char   *dev_opts[] = {
#define CHN1 0
		"1",
#define CHN2 1
		"2",
#define CHN3 2
		"3",
#define CHN4 3
		"4",
		NULL
	};
	char    name[_POSIX_PATH_MAX] = { 0 };
	int interleave = 1;
	fd_set  rfds;
	int use_mmap = 0;
	int rate_method = 0;

	mSampleRate = 44100;
	mSampleChannels = 2;
	mSampleBits = 16;
	mSampleTime = 5;
	while ((c = getopt (argc, argv, "8b:d:a:f:mn:r:t:vc:xi:R:z:y")) != EOF)
	{
		switch (c)
		{
		case '8':
			mSampleBits = 8;
			break;
		case 'b':
			mSampleBits = atoi (optarg);
			if (mSampleBits != 8 && mSampleBits != 16 && mSampleBits != 24 && mSampleBits != 32)
			{
				printf("Invalid sample size, must be one of 8, 16, 24, 32\n");
				exit(1);
			}
			break;
		case 'a':
			if (strchr (optarg, ':'))
			{
				card = atoi (optarg);
				dev = atoi (strchr (optarg, ':') + 1);
			}
			else if (isalpha (optarg[0]) || optarg[0] == '/')
				strcpy (name, optarg);
			else
				dev = atoi (optarg);

			if (name[0] != '\0')
				printf ("Using device /dev/snd/%s\n", name);
			else
				printf ("Using card %d device %d \n", card, dev);
			break;
		case 'f':
			fragsize = atoi (optarg);
			break;
		case 'i':
			interleave = atoi(optarg);
			if (interleave <= 0)
				interleave = 0;
			else
				interleave = 1;
			break;
		case 'm':
			mSampleChannels = 1;
			break;
		case 'n':
			mSampleChannels = atoi (optarg);
			break;
		case 'r':
			mSampleRate = atoi (optarg);
			break;
		case 't':
			mSampleTime = atoi (optarg);
			break;
		case 'v':
			verbose = 1;
			break;
		case 'c':
			sub_opts = sub_opts_copy = strdup (optarg);
			if (sub_opts == NULL) {
				printf("Cannot allocate sub_opts\n");
				exit(1);
			}
			while (*sub_opts != '\0')
			{
				int channel = getsubopt (&sub_opts, dev_opts, &value);
				if( channel >= 0 && channel < sizeof(voice_mask)/sizeof(voice_mask[0]) && value ) {
					voice_mask[channel] = strtoul (value, NULL, 0);
				} else {
					fprintf (stderr, "Invalid channel map specified\n");
					exit(1);
				}
			}
			free(sub_opts_copy);
			voice_override = 1;
			break;
		case 'x':
			use_mmap = 1;
			break;
		case 'R':
			rate_method = atoi(optarg);
			if (rate_method < 0 || rate_method > 2)
			{
				rate_method = 0;
				printf("Invalid rate method, using method 0\n");
			}
			break;
		case 'z':
			num_frags = atoi (optarg) - 1;
			break;
		case 'y':
			mode |= SND_PCM_OPEN_NONBLOCK;
			break;
		default:
			return 1;
		}
	}

	if (name[0] != '\0')
	{
		snd_pcm_info_t info;

		if ((rtn = snd_pcm_open_name (&pcm_handle, name, mode)) < 0)
		{
			return err ("open_name");
		}
		rtn = snd_pcm_info (pcm_handle, &info);
		card = info.card;
	}
	else
	{
		if (card == -1)
		{
			if ((rtn = snd_pcm_open_preferred (&pcm_handle, &card, &dev, mode)) < 0)
				return err ("device open");
		}
		else
		{
			if ((rtn = snd_pcm_open (&pcm_handle, card, dev, mode)) < 0)
				return err ("device open");
		}
	}

	if (optind >= argc)
		return err ("no file specified");

	if ((file1 = fopen (argv[optind], "w")) == 0)
		return err ("file open #1");

	if( mSampleTime == 0 ) {
		mSamples = 0xFFFFFFFF - sizeof(riff_hdr) + 8;
	} else {
		mSamples = mSampleRate * mSampleChannels * mSampleBits / 8 * mSampleTime;
	}

	riff_hdr.wave.fmt.voices = ENDIAN_LE16 (mSampleChannels);
	riff_hdr.wave.fmt.rate = ENDIAN_LE32 (mSampleRate);
	riff_hdr.wave.fmt.char_per_sec =
		ENDIAN_LE32 (mSampleRate * mSampleChannels * mSampleBits / 8);
	riff_hdr.wave.fmt.block_align = ENDIAN_LE16 (mSampleChannels * mSampleBits / 8);
	riff_hdr.wave.fmt.bits_per_sample = ENDIAN_LE16 (mSampleBits);
	riff_hdr.wave.data.data_len = ENDIAN_LE32 (mSamples);
	riff_hdr.wave_len = ENDIAN_LE32 (mSamples + sizeof (riff_hdr) - 8);
	fwrite (&riff_hdr, 1, sizeof (riff_hdr), file1);

	printf ("SampleRate = %d, Channels = %d, SampleBits = %d\n", mSampleRate, mSampleChannels,
		mSampleBits);

	if (use_mmap)
	{
		snd_pcm_plugin_set_enable (pcm_handle, PLUGIN_MMAP);
	}

	memset (&pi, 0, sizeof (pi));
	pi.channel = SND_PCM_CHANNEL_CAPTURE;
	if ((rtn = snd_pcm_plugin_info (pcm_handle, &pi)) < 0)
	{
		fprintf (stderr, "snd_pcm_plugin_info failed: %s\n", snd_strerror (rtn));
		fclose (file1);
		return -1;
	}

	memset (&pp, 0, sizeof (pp));

	pp.mode = SND_PCM_MODE_BLOCK;
	pp.channel = SND_PCM_CHANNEL_CAPTURE;
	pp.start_mode = SND_PCM_START_DATA;
	pp.stop_mode = SND_PCM_STOP_STOP;
	pp.time = 1;

	pp.buf.block.frag_size = pi.max_fragment_size;
	if (fragsize != -1)
		pp.buf.block.frag_size = fragsize;
	pp.buf.block.frags_max = num_frags;
	pp.buf.block.frags_min = 1;

	pp.format.interleave = interleave;
	pp.format.rate = mSampleRate;
	pp.format.voices = mSampleChannels;

	switch (mSampleBits)
	{
		case 8:
			pp.format.format = SND_PCM_SFMT_U8;
			break;
		case 16:
		default:
			pp.format.format = SND_PCM_SFMT_S16_LE;
			break;
		case 24:
			pp.format.format = SND_PCM_SFMT_S24_LE;
			break;
		case 32:
			pp.format.format = SND_PCM_SFMT_S32_LE;
			break;
	}

	if ((rtn = snd_pcm_plugin_set_src_method(pcm_handle, rate_method)) != rate_method)
	{
		fprintf(stderr, "Failed to apply rate_method %d, using %d\n", rate_method, rtn);
	}

	if ((rtn = snd_pcm_plugin_params (pcm_handle, &pp)) < 0)
	{
		fprintf (stderr, "snd_pcm_plugin_params failed: %s - %s\n", snd_strerror (rtn), why_failed(pp.why_failed));
		return -1;
	}

	if ((rtn = snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_CAPTURE)) < 0)
		fprintf (stderr, "snd_pcm_plugin_prepare failed: %s\n", snd_strerror (rtn));


	if (voice_override)
	{
		snd_pcm_plugin_get_voice_conversion (pcm_handle, SND_PCM_CHANNEL_CAPTURE,
			&voice_conversion);
		voice_conversion.matrix[0] = voice_mask[0];
		voice_conversion.matrix[1] = voice_mask[1];
		voice_conversion.matrix[2] = voice_mask[2];
		voice_conversion.matrix[3] = voice_mask[3];
		snd_pcm_plugin_set_voice_conversion (pcm_handle, SND_PCM_CHANNEL_CAPTURE,
			&voice_conversion);
	}

	memset (&setup, 0, sizeof (setup));
	memset (&group, 0, sizeof (group));
	setup.channel = SND_PCM_CHANNEL_CAPTURE;
	setup.mixer_gid = &group.gid;
	if ((rtn = snd_pcm_plugin_setup (pcm_handle, &setup)) < 0)
	{
		fprintf (stderr, "snd_pcm_plugin_setup failed: %s\n", snd_strerror (rtn));
		fclose (file1);
		return -1;
	}
	printf ("Format %s \n", snd_pcm_get_format_name (setup.format.format));
	printf ("Frag Size %d \n", setup.buf.block.frag_size);
	printf ("Total Frags %d \n", setup.buf.block.frags);
	printf ("Rate %d \n", setup.format.rate);
	bsize = setup.buf.block.frag_size;

	map.map.channels = 32;
	if((rtn = snd_pcm_query_channel_map(pcm_handle, &map.map)) == EOK) {
		printf("Channel map:");
		if((rtn = snd_pcm_plugin_get_voice_conversion (pcm_handle, SND_PCM_CHANNEL_CAPTURE, &voice_conversion)) != EOK) {
			// The hardware map is the same as the voice map
			for( i = 0; i < map.map.channels; i ++ ) {
				printf(" (%d)", map.map.pos[i]);
			}
			printf("\n");
		} else {
			// Map hardware channels according to the voice map
			for( i = 0; i < voice_conversion.app_voices; i ++ ) {
				bool printed = false;
				printf(" (");
				for( j = 0; j < voice_conversion.hw_voices; j ++ ) {
					if( voice_conversion.matrix[i] & (1<<j) ) {
						if ( printed ) {
							printf(" ");
						} else {
							printed = true;
						}
						printf("%d", map.map.pos[j]);
					}
				}
				printf(")");
			}
			printf("\n");
		}
	}

	if (group.gid.name[0] == 0)
	{
		printf ("Mixer Pcm Group [%s] Not Set \n", group.gid.name);
		printf ("***>>>> Input Gain Controls Disabled <<<<*** \n");
	}
	else
	{
		printf ("Mixer Pcm Group [%s]\n", group.gid.name);
		if ((rtn = snd_mixer_open (&mixer_handle, setup.mixer_card, setup.mixer_device)) < 0)
		{
			fprintf (stderr, "snd_mixer_open failed: %s\n", snd_strerror (rtn));
			fclose (file1);
			return -1;
		}
	}

	if (tcgetpgrp (0) == getpid ())
		dev_raw (fileno (stdin));

	mSampleBfr1 = malloc (bsize);
	if( mSampleBfr1 == NULL ) {
		fprintf (stderr, "Failed to allocate pcm buffer\n");
		snd_mixer_close (mixer_handle);
		fclose (file1);
		return -1;
	}
	FD_ZERO (&rfds);
	signal(SIGINT, sig_handler);
	signal(SIGTERM, sig_handler);
	while (!end && N < mSamples)
	{
		/* If we are the foreground process group associated with STDIN then include
		 * STDIN in the fdset to handle key presses.
		 */
		if (tcgetpgrp (0) == getpid ())
			FD_SET (STDIN_FILENO, &rfds);
		if (mixer_handle)
		{
			/* Include the mixer_handle descriptor in the fdset to handle
			 * mixer events.
			 */
			mixer_fd = snd_mixer_file_descriptor (mixer_handle);
			FD_SET (mixer_fd, &rfds);
		}
		rtn = pcm_fd = snd_pcm_file_descriptor (pcm_handle, SND_PCM_CHANNEL_CAPTURE);
		FD_SET (pcm_fd, &rfds);

		if (mixer_handle)
			rtn = max (mixer_fd, pcm_fd);

		if (select (rtn + 1, &rfds, NULL, NULL, NULL) == -1)
		{
			err ("select");
			break; /* break loop to exit cleanly */
		}

		if (FD_ISSET (STDIN_FILENO, &rfds))
		{
			c = getc (stdin);
			if (c != EOF)
			{
				/* Only handle volume key presses if there is a mixer group */
				if (group.gid.name[0] != 0)
				{
					if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0)
						fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));
					switch (c)
					{
					case 'q':
						group.volume.names.front_left += 1;
						break;
					case 'a':
						group.volume.names.front_left -= 1;
						break;
					case 'w':
						group.volume.names.front_left += 1;
						group.volume.names.front_right += 1;
						break;
					case 's':
						group.volume.names.front_left -= 1;
						group.volume.names.front_right -= 1;
						break;
					case 'e':
						group.volume.names.front_right += 1;
						break;
					case 'd':
						group.volume.names.front_right -= 1;
						break;
					default:
						break;
					}

					if (group.volume.names.front_left > group.max)
						group.volume.names.front_left = group.max;
					if (group.volume.names.front_left < group.min)
						group.volume.names.front_left = group.min;
					if (group.volume.names.front_right > group.max)
						group.volume.names.front_right = group.max;
					if (group.volume.names.front_right < group.min)
						group.volume.names.front_right = group.min;
					if ((rtn = snd_mixer_group_write (mixer_handle, &group)) < 0)
						fprintf (stderr, "snd_mixer_group_write failed: %s\n", snd_strerror (rtn));

					if (group.max==group.min)
						printf ("Volume Now at %d:%d\n", group.max, group.max);
					else
						printf ("Volume Now at %d:%d \n",
								100 * (group.volume.names.front_left - group.min) / (group.max - group.min),
								100 * (group.volume.names.front_right - group.min) / (group.max - group.min));
				}

				switch (c)
				{
				case 'o':
					sleep(5);
					break;
				case 'f':
					if( (ret = snd_pcm_plugin_flush( pcm_handle, SND_PCM_CHANNEL_CAPTURE )) == 0 ) {
						printf("Flushing\n");
					} else {
						fprintf(stderr, "Flush failed: %d\n", ret);
					}
					break;
				case 'g':
					if( (ret = snd_pcm_plugin_prepare( pcm_handle, SND_PCM_CHANNEL_CAPTURE )) == 0 ) {
						printf("Preparing\n");
					} else {
						fprintf(stderr, "Preparing failed: %d\n", ret);
					}
					break;
				case 'p':
					if( (ret = snd_pcm_capture_pause( pcm_handle )) == 0 ) {
						printf("Pausing\n");
					} else {
						fprintf(stderr, "Pause failed: %d\n", ret);
					}
					break;
				case 'r':
					if( (ret = snd_pcm_capture_resume( pcm_handle )) == 0 ) {
						printf("Resuming\n");
					} else {
						fprintf(stderr, "Resume failed: %d\n", ret);
					}
					break;
				case 3: //Ctrl-C
				case 27: // Escape
					end = 1;
					break;
				case 'z':
					printf("delaying 500ms\n");
					delay(500);
					break;
				default:
					break;
		/*
 * $QNXLicenseC:
 * Copyright 2016, QNX Software Systems. All Rights Reserved.
 *
 * You must obtain a written license from and pay applicable license fees to QNX
 * Software Systems before you may reproduce, modify or distribute this software,
 * or any work that includes all or part of this software.   Free development
 * licenses are available for evaluation and non-commercial purposes.  For more
 * information visit http://licensing.qnx.com or email licensing@qnx.com.
 *
 * This file may contain contributions from others.  Please review this entire
 * file for other proprietary rights or license notices, as well as the QNX
 * Development Suite License Guide at http://licensing.qnx.com/license-guide/
 * for other information.
 * $
 */


#include <errno.h>
#include <fcntl.h>
#include <gulliver.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/termio.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include <ctype.h>

#include <sys/asoundlib.h>

/* *INDENT-OFF* */
struct
{
    char        riff_id[4];
    uint32_t    wave_len;
    struct
    {
        char        fmt_id[8];
        uint32_t    fmt_len;
        struct
        {
            uint16_t    format_tag;
            uint16_t    voices;
            uint32_t    rate;
            uint32_t    char_per_sec;
            uint16_t    block_align;
            uint16_t    bits_per_sample;
        }
        fmt;
        struct
        {
            char        data_id[4];
            uint32_t    data_len;
        }
        data;
    }
    wave;
}
riff_hdr =
{
    {'R', 'I', 'F', 'F' },
    sizeof (riff_hdr.wave),
    {
        {'W', 'A', 'V', 'E', 'f', 'm', 't', ' '    },
        sizeof (riff_hdr.wave.fmt),
        {
            1, 0, 0, 0, 0, 0
        },
        {
            {'d', 'a', 't', 'a' },
            0,
        }
    }
};
/* *INDENT-ON* */

static snd_mixer_t *mixer_handle = NULL;
static snd_pcm_t *pcm_handle = NULL;
static char *mSampleBfr1 = NULL;
static FILE *file1 = NULL;
static int stdin_raw = 0;

int
dev_raw (int fd)
{
	struct termios termios_p;

	if (tcgetattr (fd, &termios_p))
		return (-1);

	termios_p.c_cc[VMIN] = 1;
	termios_p.c_cc[VTIME] = 0;
	termios_p.c_lflag &= ~(ICANON | ECHO | ISIG);
	return (tcsetattr (fd, TCSANOW, &termios_p));
}

int
dev_unraw (int fd)
{
	struct termios termios_p;

	if (tcgetattr (fd, &termios_p))
		return (-1);

	termios_p.c_lflag |= (ICANON | ECHO | ISIG);
	return (tcsetattr (fd, TCSAFLUSH, &termios_p));
}

void
cleanup(void)
{
	if (stdin_raw)
		dev_unraw (fileno (stdin));
	if (mSampleBfr1)
		free(mSampleBfr1);
	if (mixer_handle)
		snd_mixer_close (mixer_handle);
	if (file1)
		fclose(file1);
	if (pcm_handle)
		snd_pcm_close (pcm_handle);
}

void
cleanup_and_exit(int exit_code)
{
	cleanup();
	exit(exit_code);
}

//*****************************************************************************
/* *INDENT-OFF* */
#ifdef __USAGE
%C [Options] wavfile

Options:
    -a[card#:]<dev#>   the card & device number to record from
                       OR
    -a[name]           the symbolic name of the device to record from

    -8                 use 8 bit mode (16 bit default)
    -b <size>          Sample size (8, 16, 32)
    -m                 record in mono (stereo default)
    -n <voices>        the number of voices to record (2 voices default, stereo)
    -r <rate>          record at rate (44100 default | 48000 44100 22050 11025)
    -t <sec>           seconds to record (5 seconds default)
    -f <frag_size>     requested fragment size
    -v                 verbosity
    -c <args>[,args ...] voice matrix configuration
    -x                 use mmap interface
    -i <0|1>           Interleave samples (default: 1)
    -R <value>         SRC rate method
                       (0 = linear interpolation, 1 = 7-pt kaiser windowed, 2 = 20-pt remez)
    -z<num_frags>      requested number of fragments
    -y                 Nonblocking mode

Note:
    If both 'm' and 'n' are specified in commandline, the one specified later will be used

Args:
    1=<hw_channel_bitmask> hardware channel bitmask for application voice 1
    2=<hw_channel_bitmask> hardware channel bitmask for application voice 2
    3=<hw_channel_bitmask> hardware channel bitmask for application voice 3
    4=<hw_channel_bitmask> hardware channel bitmask for application voice 4
#endif
/* *INDENT-ON* */
//*****************************************************************************

volatile int end = 0;

void sig_handler( int sig_no )
{
	end = 1;
	return;
}

const char *
why_failed ( int why_failed )
{
	switch (why_failed)
	{
		case SND_PCM_PARAMS_BAD_MODE:
			return ("Bad Mode Parameter");
		case SND_PCM_PARAMS_BAD_START:
			return ("Bad Start Parameter");
		case SND_PCM_PARAMS_BAD_STOP:
			return ("Bad Stop Parameter");
		case SND_PCM_PARAMS_BAD_FORMAT:
			return ("Bad Format Parameter");
		case SND_PCM_PARAMS_BAD_RATE:
			return ("Bad Rate Parameter");
		case SND_PCM_PARAMS_BAD_VOICES:
			return ("Bad Vocies Parameter");
		case SND_PCM_PARAMS_NO_CHANNEL:
			return ("No Channel Available");
		default:
			return ("Unknown Error");
	}

	return ("No Error");
}

int
main (int argc, char **argv)
{
	int     i, j;
	int     card = -1;
	int     dev = 0;
	int     ret;

	unsigned int mSamples;
	int     mSampleRate;
	int     mSampleChannels;
	int     mSampleBits;
	int     mSampleTime;
	int     fragsize = -1;
	int     num_frags = -1;
	int     verbose = 0;
	int     mode = SND_PCM_OPEN_CAPTURE;

	int     rtn;
	int     mixer_fd = 0;
	int     pcm_fd;

	snd_pcm_channel_info_t pi;
	snd_mixer_group_t group;
	snd_pcm_channel_params_t pp;
	snd_pcm_channel_setup_t setup;
	int     bsize, N = 0, c;
	uint32_t voice_mask[] = { 0, 0, 0, 0 };
	struct {
		snd_pcm_chmap_t map;
		unsigned int pos[32];
	} map;
	snd_pcm_voice_conversion_t voice_conversion;
	int     voice_override = 0;
	char   *sub_opts, *sub_opts_copy, *value;
	char   *dev_opts[] = {
#define CHN1 0
		"1",
#define CHN2 1
		"2",
#define CHN3 2
		"3",
#define CHN4 3
		"4",
		NULL
	};
	char    name[_POSIX_PATH_MAX] = { 0 };
	int interleave = 1;
	fd_set  rfds;
	int use_mmap = 0;
	int rate_method = 0;

	mSampleRate = 44100;
	mSampleChannels = 2;
	mSampleBits = 16;
	mSampleTime = 5;

	while ((c = getopt (argc, argv, "8b:a:f:mn:r:t:vc:xi:R:z:y")) != EOF)
	{
		switch (c)
		{
		case '8':
			mSampleBits = 8;
			break;
		case 'b':
			mSampleBits = atoi (optarg);
			if (mSampleBits != 8 && mSampleBits != 16 && mSampleBits != 24 && mSampleBits != 32)
			{
				fprintf(stderr, "Invalid sample size, must be one of 8, 16, 24, 32\n");
				return (EXIT_FAILURE);
			}
			break;
		case 'a':
			if (strchr (optarg, ':'))
			{
				card = atoi (optarg);
				dev = atoi (strchr (optarg, ':') + 1);
			}
			else if (isalpha (optarg[0]) || optarg[0] == '/')
				strcpy (name, optarg);
			else
				dev = atoi (optarg);

			if (name[0] != '\0')
				printf ("Using device /dev/snd/%s\n", name);
			else
				printf ("Using card %d device %d \n", card, dev);
			break;
		case 'f':
			fragsize = atoi (optarg);
			break;
		case 'i':
			interleave = atoi(optarg);
			if (interleave <= 0)
				interleave = 0;
			else
				interleave = 1;
			break;
		case 'm':
			mSampleChannels = 1;
			break;
		case 'n':
			mSampleChannels = atoi (optarg);
			break;
		case 'r':
			mSampleRate = atoi (optarg);
			break;
		case 't':
			mSampleTime = atoi (optarg);
			break;
		case 'v':
			verbose = 1;
			break;
		case 'c':
			sub_opts = sub_opts_copy = strdup (optarg);
			if (sub_opts == NULL) {
				perror("Cannot allocate sub_opts");
				return (EXIT_FAILURE);
			}
			while (*sub_opts != '\0')
			{
				int channel = getsubopt (&sub_opts, dev_opts, &value);
				if( channel >= 0 && channel < sizeof(voice_mask)/sizeof(voice_mask[0]) && value ) {
					voice_mask[channel] = strtoul (value, NULL, 0);
				} else {
					fprintf (stderr, "Invalid channel map specified\n");
					free(sub_opts_copy);
					return (EXIT_FAILURE);
				}
			}
			free(sub_opts_copy);
			voice_override = 1;
			break;
		case 'x':
			use_mmap = 1;
			break;
		case 'R':
			rate_method = atoi(optarg);
			if (rate_method < 0 || rate_method > 2)
			{
				rate_method = 0;
				printf("Invalid rate method, using method 0\n");
			}
			break;
		case 'z':
			num_frags = atoi (optarg) - 1;
			break;
		case 'y':
			mode |= SND_PCM_OPEN_NONBLOCK;
			break;
		default:
			fprintf(stderr, "Invalid option -%c\n", c);
			return (EXIT_FAILURE);
		}
	}

	if (optind >= argc)
	{
		fprintf(stderr, "no file specified\n");
		return (EXIT_FAILURE);
	}

	if (name[0] != '\0')
	{
		snd_pcm_info_t info;

		if ((rtn = snd_pcm_open_name (&pcm_handle, name, mode)) < 0)
		{
			fprintf(stderr, "snd_pcm_open_name failed - %s\n", snd_strerror(rtn));
			return (EXIT_FAILURE);
		}
		rtn = snd_pcm_info (pcm_handle, &info);
		card = info.card;
	}
	else
	{
		if (card == -1)
		{
			if ((rtn = snd_pcm_open_preferred (&pcm_handle, &card, &dev, mode)) < 0)
			{
				fprintf(stderr, "snd_pcm_open_preferred failed - %s\n", snd_strerror(rtn));
				return (EXIT_FAILURE);
			}
		}
		else
		{
			if ((rtn = snd_pcm_open (&pcm_handle, card, dev, mode)) < 0)
			{
				fprintf(stderr, "snd_pcm_open failed - %s\n", snd_strerror(rtn));
				return (EXIT_FAILURE);
			}
		}
	}

	if ((file1 = fopen (argv[optind], "w")) == 0)
	{
		perror("file open failed");
		cleanup_and_exit(EXIT_FAILURE);
	}

	if( mSampleTime == 0 ) {
		mSamples = 0xFFFFFFFF - sizeof(riff_hdr) + 8;
	} else {
		mSamples = mSampleRate * mSampleChannels * mSampleBits / 8 * mSampleTime;
	}

	riff_hdr.wave.fmt.voices = ENDIAN_LE16 (mSampleChannels);
	riff_hdr.wave.fmt.rate = ENDIAN_LE32 (mSampleRate);
	riff_hdr.wave.fmt.char_per_sec =
		ENDIAN_LE32 (mSampleRate * mSampleChannels * mSampleBits / 8);
	riff_hdr.wave.fmt.block_align = ENDIAN_LE16 (mSampleChannels * mSampleBits / 8);
	riff_hdr.wave.fmt.bits_per_sample = ENDIAN_LE16 (mSampleBits);
	riff_hdr.wave.data.data_len = ENDIAN_LE32 (mSamples);
	riff_hdr.wave_len = ENDIAN_LE32 (mSamples + sizeof (riff_hdr) - 8);
	fwrite (&riff_hdr, 1, sizeof (riff_hdr), file1);

	printf ("SampleRate = %d, Channels = %d, SampleBits = %d\n", mSampleRate, mSampleChannels,
		mSampleBits);

	if (use_mmap)
	{
		snd_pcm_plugin_set_enable (pcm_handle, PLUGIN_MMAP);
	}

	memset (&pi, 0, sizeof (pi));
	pi.channel = SND_PCM_CHANNEL_CAPTURE;
	if ((rtn = snd_pcm_plugin_info (pcm_handle, &pi)) < 0)
	{
		fprintf (stderr, "snd_pcm_plugin_info failed: %s\n", snd_strerror (rtn));
		cleanup_and_exit(EXIT_FAILURE);
	}

	memset (&pp, 0, sizeof (pp));

	pp.mode = SND_PCM_MODE_BLOCK;
	pp.channel = SND_PCM_CHANNEL_CAPTURE;
	pp.start_mode = SND_PCM_START_DATA;
	pp.stop_mode = SND_PCM_STOP_STOP;
	pp.time = 1;

	pp.buf.block.frag_size = pi.max_fragment_size;
	if (fragsize != -1)
		pp.buf.block.frag_size = fragsize;
	pp.buf.block.frags_max = num_frags;
	pp.buf.block.frags_min = 1;

	pp.format.interleave = interleave;
	pp.format.rate = mSampleRate;
	pp.format.voices = mSampleChannels;

	switch (mSampleBits)
	{
		case 8:
			pp.format.format = SND_PCM_SFMT_U8;
			break;
		case 16:
		default:
			pp.format.format = SND_PCM_SFMT_S16_LE;
			break;
		case 24:
			pp.format.format = SND_PCM_SFMT_S24_LE;
			break;
		case 32:
			pp.format.format = SND_PCM_SFMT_S32_LE;
			break;
	}

	if ((rtn = snd_pcm_plugin_set_src_method(pcm_handle, rate_method)) != rate_method)
	{
		fprintf(stderr, "Failed to apply rate_method %d, using %d\n", rate_method, rtn);
	}

	if ((rtn = snd_pcm_plugin_params (pcm_handle, &pp)) < 0)
	{
		fprintf (stderr, "snd_pcm_plugin_params failed: %s - %s\n", snd_strerror (rtn), why_failed(pp.why_failed));
		cleanup_and_exit(EXIT_FAILURE);
	}

	if ((rtn = snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_CAPTURE)) < 0)
	{
		fprintf (stderr, "snd_pcm_plugin_prepare failed: %s\n", snd_strerror (rtn));
		cleanup_and_exit(EXIT_FAILURE);
	}

	if (voice_override)
	{
		snd_pcm_plugin_get_voice_conversion (pcm_handle, SND_PCM_CHANNEL_CAPTURE,
			&voice_conversion);
		voice_conversion.matrix[0] = voice_mask[0];
		voice_conversion.matrix[1] = voice_mask[1];
		voice_conversion.matrix[2] = voice_mask[2];
		voice_conversion.matrix[3] = voice_mask[3];
		snd_pcm_plugin_set_voice_conversion (pcm_handle, SND_PCM_CHANNEL_CAPTURE,
			&voice_conversion);
	}

	memset (&setup, 0, sizeof (setup));
	memset (&group, 0, sizeof (group));
	setup.channel = SND_PCM_CHANNEL_CAPTURE;
	setup.mixer_gid = &group.gid;
	if ((rtn = snd_pcm_plugin_setup (pcm_handle, &setup)) < 0)
	{
		fprintf (stderr, "snd_pcm_plugin_setup failed: %s\n", snd_strerror (rtn));
		cleanup_and_exit(EXIT_FAILURE);
	}
	printf ("Format %s \n", snd_pcm_get_format_name (setup.format.format));
	printf ("Frag Size %d \n", setup.buf.block.frag_size);
	printf ("Total Frags %d \n", setup.buf.block.frags);
	printf ("Rate %d \n", setup.format.rate);
	bsize = setup.buf.block.frag_size;

	map.map.channels = 32;
	if((rtn = snd_pcm_query_channel_map(pcm_handle, &map.map)) == EOK) {
		printf("Channel map:");
		if((rtn = snd_pcm_plugin_get_voice_conversion (pcm_handle, SND_PCM_CHANNEL_CAPTURE, &voice_conversion)) != EOK) {
			// The hardware map is the same as the voice map
			for( i = 0; i < map.map.channels; i ++ ) {
				printf(" (%d)", map.map.pos[i]);
			}
			printf("\n");
		} else {
			// Map hardware channels according to the voice map
			for( i = 0; i < voice_conversion.app_voices; i ++ ) {
				bool printed = false;
				printf(" (");
				for( j = 0; j < voice_conversion.hw_voices; j ++ ) {
					if( voice_conversion.matrix[i] & (1<<j) ) {
						if ( printed ) {
							printf(" ");
						} else {
							printed = true;
						}
						printf("%d", map.map.pos[j]);
					}
				}
				printf(")");
			}
			printf("\n");
		}
	}

	if (group.gid.name[0] == 0)
	{
		printf ("Mixer Pcm Group [%s] Not Set \n", group.gid.name);
		printf ("***>>>> Input Gain Controls Disabled <<<<*** \n");
	}
	else
	{
		printf ("Mixer Pcm Group [%s]\n", group.gid.name);
		if ((rtn = snd_mixer_open (&mixer_handle, setup.mixer_card, setup.mixer_device)) < 0)
		{
			fprintf (stderr, "snd_mixer_open failed: %s\n", snd_strerror (rtn));
			cleanup_and_exit(EXIT_FAILURE);
		}
	}

	if (tcgetpgrp (0) == getpid ()) {
		stdin_raw = 1;
		dev_raw (fileno (stdin));
	}

	mSampleBfr1 = malloc (bsize);
	if ( mSampleBfr1 == NULL ) {
		perror("Failed to allocate pcm buffer");
		cleanup_and_exit(EXIT_FAILURE);
	}

	FD_ZERO (&rfds);
	signal(SIGINT, sig_handler);
	signal(SIGTERM, sig_handler);
	while (!end && N < mSamples)
	{
		/* If we are the foreground process group associated with STDIN then include
		 * STDIN in the fdset to handle key presses.
		 */
		if (stdin_raw)
			FD_SET (STDIN_FILENO, &rfds);
		if (mixer_handle)
		{
			/* Include the mixer_handle descriptor in the fdset to handle
			 * mixer events.
			 */
			mixer_fd = snd_mixer_file_descriptor (mixer_handle);
			FD_SET (mixer_fd, &rfds);
		}
		rtn = pcm_fd = snd_pcm_file_descriptor (pcm_handle, SND_PCM_CHANNEL_CAPTURE);
		FD_SET (pcm_fd, &rfds);

		if (mixer_handle)
			rtn = max (mixer_fd, pcm_fd);

		if (select (rtn + 1, &rfds, NULL, NULL, NULL) == -1)
		{
			perror("select");
			break; /* break loop to exit cleanly */
		}

		if (FD_ISSET (STDIN_FILENO, &rfds))
		{
			c = getc (stdin);
			if (c != EOF)
			{
				/* Only handle volume key presses if there is a mixer group */
				if (group.gid.name[0] != 0)
				{
					if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0)
						fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));
					switch (c)
					{
					case 'q':
						group.volume.names.front_left += 1;
						break;
					case 'a':
						group.volume.names.front_left -= 1;
						break;
					case 'w':
						group.volume.names.front_left += 1;
						group.volume.names.front_right += 1;
						break;
					case 's':
						group.volume.names.front_left -= 1;
						group.volume.names.front_right -= 1;
						break;
					case 'e':
						group.volume.names.front_right += 1;
						break;
					case 'd':
						group.volume.names.front_right -= 1;
						break;
					default:
						break;
					}

					if (group.volume.names.front_left > group.max)
						group.volume.names.front_left = group.max;
					if (group.volume.names.front_left < group.min)
						group.volume.names.front_left = group.min;
					if (group.volume.names.front_right > group.max)
						group.volume.names.front_right = group.max;
					if (group.volume.names.front_right < group.min)
						group.volume.names.front_right = group.min;
					if ((rtn = snd_mixer_group_write (mixer_handle, &group)) < 0)
						fprintf (stderr, "snd_mixer_group_write failed: %s\n", snd_strerror (rtn));

					if (group.max==group.min)
						printf ("Volume Now at %d:%d\n", group.max, group.max);
					else
						printf ("Volume Now at %d:%d \n",
								100 * (group.volume.names.front_left - group.min) / (group.max - group.min),
								100 * (group.volume.names.front_right - group.min) / (group.max - group.min));
				}

				switch (c)
				{
				case 'o':
					sleep(5);
					break;
				case 'f':
					if( (ret = snd_pcm_plugin_flush( pcm_handle, SND_PCM_CHANNEL_CAPTURE )) == 0 ) {
						printf("Flushing\n");
					} else {
						fprintf(stderr, "Flush failed: %d\n", ret);
					}
					break;
				case 'g':
					if( (ret = snd_pcm_plugin_prepare( pcm_handle, SND_PCM_CHANNEL_CAPTURE )) == 0 ) {
						printf("Preparing\n");
					} else {
						fprintf(stderr, "Preparing failed: %d\n", ret);
					}
					break;
				case 'p':
					if( (ret = snd_pcm_capture_pause( pcm_handle )) == 0 ) {
						printf("Pausing\n");
					} else {
						fprintf(stderr, "Pause failed: %d\n", ret);
					}
					break;
				case 'r':
					if( (ret = snd_pcm_capture_resume( pcm_handle )) == 0 ) {
						printf("Resuming\n");
					} else {
						fprintf(stderr, "Resume failed: %d\n", ret);
					}
					break;
				case 3: //Ctrl-C
				case 27: // Escape
					end = 1;
					break;
				case 'z':
					printf("delaying 500ms\n");
					delay(500);
					break;
				default:
					break;
				}
			}
			else {
				cleanup_and_exit(EXIT_SUCCESS);
			}
		}

		if (mixer_handle && FD_ISSET (mixer_fd, &rfds))
		{
			snd_mixer_callbacks_t callbacks = {
				0, 0, 0, 0
			};

			snd_mixer_read (mixer_handle, &callbacks);
		}

		if (FD_ISSET (pcm_fd, &rfds))
		{
			snd_pcm_channel_status_t status;
			int     read = 0;

			read = snd_pcm_plugin_read (pcm_handle, mSampleBfr1, bsize);
			if (verbose)
				printf ("bytes read = %d, bsize = %d \n", read, bsize);
			if (read < bsize)
			{
				memset (&status, 0, sizeof (status));
				status.channel = SND_PCM_CHANNEL_CAPTURE;
				if (snd_pcm_plugin_status (pcm_handle, &status) < 0)
				{
					fprintf (stderr, "Capture channel status error\n");
					cleanup_and_exit(EXIT_FAILURE);
				}

				if (status.status == SND_PCM_STATUS_READY ||
					status.status == SND_PCM_STATUS_OVERRUN ||
					status.status == SND_PCM_STATUS_CHANGE)
				{
					if (status.status == SND_PCM_STATUS_OVERRUN)
						fprintf(stderr, "overrun: capture channel\n");

					if (snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_CAPTURE) < 0)
					{
						fprintf (stderr, "Capture channel prepare error\n");
						cleanup_and_exit(EXIT_FAILURE);
					}
				}
				else if (status.status == SND_PCM_STATUS_ERROR)
				{
					fprintf(stderr, "error: capture channel failure\n");
					cleanup_and_exit(EXIT_FAILURE);
				}
				else if (status.status == SND_PCM_STATUS_CHANGE)
				{
					fprintf(stderr, "change: capture channel capability change\n");
					cleanup_and_exit(EXIT_FAILURE);
				}
				else if (status.status == SND_PCM_STATUS_PREEMPTED)
				{
					fprintf(stderr, "error: capture channel preempted\n");
					cleanup_and_exit(EXIT_FAILURE);
				}
			} else {
				fwrite (mSampleBfr1, 1, read, file1);
				N += read;
			}
		}
	}

	printf("Exiting...\n");

	/* Update wave header with actual length of audio captured */
	riff_hdr.wave.data.data_len = ENDIAN_LE32 (N);
	riff_hdr.wave_len = ENDIAN_LE32 (N + sizeof (riff_hdr) - 8);
	fseek(file1, 0, SEEK_SET);
	fwrite (&riff_hdr, 1, sizeof (riff_hdr), file1);

	snd_pcm_plugin_flush (pcm_handle, SND_PCM_CHANNEL_CAPTURE);
	cleanup();
	return(EXIT_SUCCESS);
}