wave.c example

This is a sample application that plays back audio data.

For information about using this utility, see wave in the QNX Neutrino Utilities Guide.

/*
 * $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 <sys/slogcodes.h>
#include <sys/slog2.h>
#include <time.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <pthread.h>

#include <sys/asoundlib.h>

#define WRITE_RETRY_TIMES 5

typedef struct
{
    char    tag[4];
    int32_t length;
}
RiffTag;

typedef struct
{
    char     Riff[4];
    int32_t  Size;
    char     Wave[4];
}
RiffHdr;

typedef struct
{
    int16_t   FormatTag;
    int16_t   Channels;
    int32_t   SamplesPerSec;
    int32_t   AvgBytesPerSec;
    int16_t   BlockAlign;
    int16_t   BitsPerSample;
}
FmtChunk;

typedef struct
{
    FILE *file1;
    struct timespec start_time;
}
WriterData;

const char *kRiffId = "RIFF";
const char *kRifxId = "RIFX";
const char *kWaveId = "WAVE";
bool running = true;
int n;
int N=0;
int verbose = 0;
int print_timing = 0;
int bsize;
int use_writer_thread = 0;
useconds_t frag_period_us;
snd_mixer_group_t group;
snd_mixer_t *mixer_handle = NULL;
snd_pcm_t *pcm_handle = NULL;
snd_pcm_channel_params_t pp;
char *mSampleBfr1 = NULL;
unsigned int mDataSize;
bool mBigEndian = false;
int nonblock = 0;
int repeat = 0;
long int data_position = 0;
int stdin_raw = 0;
WriterData wd;

static slog2_buffer_t slog_handle;
static slog2_buffer_set_config_t slog_config;

static int
FindTag (FILE * fp, const char *tag)
{
    int     retVal;
    RiffTag tagBfr = { "", 0 };

    retVal = 0;

    // Keep reading until we find the tag or hit the EOF.
    while (fread ((unsigned char *) &tagBfr, sizeof (tagBfr), 1, fp))
    {

        if( mBigEndian ) {
            tagBfr.length = ENDIAN_BE32 (tagBfr.length);
        } else {
            tagBfr.length = ENDIAN_LE32 (tagBfr.length);
        }
        // If this is our tag, set the length and break.
        if (strncmp (tag, tagBfr.tag, sizeof (tagBfr.tag)) == 0)
        {
            retVal = tagBfr.length;
            break;
        }

        // Skip ahead the specified number of bytes in the stream
        fseek (fp, tagBfr.length, SEEK_CUR);
    }

    // Return the result of our operation
    return (retVal);
}


static int
CheckHdr (FILE * fp)
{
    RiffHdr riffHdr = { "", 0 };

    if (fread ((unsigned char *) &riffHdr, sizeof (RiffHdr), 1, fp) == 0)
        return -1;

    if (!strncmp (riffHdr.Riff, kRiffId, strlen (kRiffId)))
        mBigEndian = false;
    else if (!strncmp (riffHdr.Riff, kRifxId, strlen (kRifxId)))
        mBigEndian = true;
    else
        return -1;
    if (strncmp (riffHdr.Wave, kWaveId, strlen (kWaveId)))
        return -1;

    return 0;
}


static 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));
}

static 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));
}

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

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

static void
handle_keypress()
{
    int     c;
    int     rtn;

    c = getc (stdin);

    if (c == EOF)
    {
        running = false;
        return;
    }

    /* Handle non-mixer keypresses */
    switch (c)
    {
        case 'p':
            snd_pcm_playback_pause( pcm_handle );
            return;
        case 'r':
            snd_pcm_playback_resume( pcm_handle );
            return;
        case 'i':
            {
                char buf[100] = {0};
                snd_pcm_channel_status_t status;

                memset (&status, 0, sizeof (status));
                status.channel = SND_PCM_CHANNEL_PLAYBACK;
                if ((rtn = snd_pcm_plugin_status (pcm_handle, &status)) < 0)
                {
                    fprintf (stderr, "plugin_status: playback channel status error\n");
                    return;
                }

                /* Display Subchn state */
                printf("\nSubchn State = ");
                switch (status.status)
                {
                    case SND_PCM_STATUS_NOTREADY:
                        printf("NOTREADY\n");
                        break;
                    case SND_PCM_STATUS_READY:
                        printf("READY\n");
                        break;
                    case SND_PCM_STATUS_PREPARED:
                        printf("PREPARED\n");
                        break;
                    case SND_PCM_STATUS_RUNNING:
                        printf("RUNNING\n");
                        break;
                    case SND_PCM_STATUS_PAUSED:
                        printf("PAUSED\n");
                        break;
                    case SND_PCM_STATUS_SUSPENDED:
                        printf("SUSPENDED\n");
                        break;
                    case SND_PCM_STATUS_UNDERRUN:
                        printf("UNDERRUN\n");
                        break;
                    case SND_PCM_STATUS_OVERRUN:
                        printf("OVERRUN\n");
                        break;
                    case SND_PCM_STATUS_CHANGE:
                        printf("CHANGE\n");
                        break;
                    case SND_PCM_STATUS_ERROR:
                        printf("ERROR\n");
                        break;
                    default:
                        printf("UNKNOWN\n");
                        break;
                }
                /* Display Ducking State */
                if (status.ducking_state & SND_PCM_DUCKING_STATE_FORCED_ACTIVE)
                {
                    strcat(buf, "FORCED_ACTIVE");
                    if (status.ducking_state & SND_PCM_DUCKING_STATE_ACTIVE)
                        strcat(buf, "|ACTIVE");
               }
               else if (status.ducking_state & SND_PCM_DUCKING_STATE_ACTIVE)
                   strcat(buf, "ACTIVE");
               else
                   strcat(buf, "INACTIVE");
               if (status.ducking_state & SND_PCM_DUCKING_STATE_HARD_SUSPENDED)
                   strcat(buf, "|HARD_SUSPENDED");
               if (status.ducking_state & SND_PCM_DUCKING_STATE_SOFT_SUSPENDED)
                   strcat(buf, "|SOFT_SUSPENDED");
               if (status.ducking_state & SND_PCM_DUCKING_STATE_PAUSED)
                   strcat(buf, "|AUTOPAUSED");
               printf("Ducking State = %s\n", buf);
               /* Display buffer statistics */
               printf("scount = %d, used = %d, free = %d, underrun = %d, overrun = %d\n",
                      status.scount, status.count, status.free, status.underrun, status.overrun);
            }
            return;
        case 't':
            if (repeat) {
                repeat = 0;
                printf("Stop to repeat playing\n");
                return;
            } else
                break;
        // Exit the program
        case 3: // Ctrl-C
        case 27: // Escape
            running = false;
            return;
        default:
            break;
    }

    /* Handle mixer keypresses */
    if (mixer_handle == NULL)
        return;

    if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0)
    {
        fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));
        return;
    }

    /* Adjust the volume by 10 or by the remaining volume steps until the min or max (whichever is smaller) */
    switch (c)
    {
        case 'q':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
                group.volume.names.front_left += min(group.max - group.volume.names.front_left, 10);
            if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
                group.volume.names.rear_left += min(group.max - group.volume.names.rear_left, 10);
            if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
                group.volume.names.woofer += min(group.max - group.volume.names.woofer, 10);
            break;
        case 'a':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
                group.volume.names.front_left -= min(group.volume.names.front_left - group.min, 10);
            if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
                group.volume.names.rear_left -= min(group.volume.names.rear_left - group.min, 10);
            if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
                group.volume.names.woofer -= min(group.volume.names.woofer - group.min, 10);
            break;
        case 'w':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
                group.volume.names.front_left += min(group.max - group.volume.names.front_left, 10);
            if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
                group.volume.names.rear_left += min(group.max - group.volume.names.rear_left, 10);
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
                group.volume.names.front_center += min(group.max - group.volume.names.front_center, 10);
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
                group.volume.names.front_right += min(group.max - group.volume.names.front_right, 10);
            if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
                group.volume.names.rear_right += min(group.max - group.volume.names.rear_right, 10);
            if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
                group.volume.names.woofer += min(group.max - group.volume.names.woofer, 10);
            break;
        case 's':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
                group.volume.names.front_left -= min(group.volume.names.front_left - group.min, 10);
            if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
                group.volume.names.rear_left -= min(group.volume.names.rear_left - group.min, 10);
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
                group.volume.names.front_center -= min(group.volume.names.front_center - group.min, 10);
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
                group.volume.names.front_right -= min(group.volume.names.front_right - group.min, 10);
            if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
                group.volume.names.rear_right -= min(group.volume.names.rear_right - group.min, 10);
            if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
                group.volume.names.woofer -= min(group.volume.names.woofer - group.min, 10);
            break;
        case 'e':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
                group.volume.names.front_right += min(group.max - group.volume.names.front_right, 10);
            if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
                group.volume.names.rear_right += min(group.max - group.volume.names.rear_right, 10);
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
                group.volume.names.front_center += min(group.max - group.volume.names.front_center, 10);
            break;
        case 'd':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
                group.volume.names.front_right -= min(group.volume.names.front_right - group.min, 10);
            if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
                group.volume.names.rear_right -= min(group.volume.names.rear_right - group.min, 10);
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
                group.volume.names.front_center -= min(group.volume.names.front_center - group.min, 10);
            break;
    }

    if ((rtn = snd_mixer_group_write (mixer_handle, &group)) < 0)
        fprintf (stderr, "snd_mixer_group_write failed: %s\n", snd_strerror (rtn));

    if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
    {
        printf ("Volume Now at %d:%d \n",
            (group.max - group.min) ? 100 * (group.volume.names.front_left - group.min) / (group.max - group.min) : 0,
            (group.max - group.min) ? 100 * (group.volume.names.front_right - group.min) / (group.max - group.min): 0);
    }
    else if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
    {
        printf ("Volume Now at %d:%d \n",
            (group.max - group.min) ? 100 * (group.volume.names.rear_left - group.min) / (group.max - group.min) : 0,
            (group.max - group.min) ? 100 * (group.volume.names.rear_right - group.min) / (group.max - group.min): 0);
    }
    else if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
    {
        printf ("Volume Now at %d:%d \n",
            (group.max - group.min) ? 100 * (group.volume.names.woofer - group.min) / (group.max - group.min) : 0,
            (group.max - group.min) ? 100 * (group.volume.names.front_center - group.min) / (group.max - group.min): 0);
    }
    else
    {
        printf ("Volume Now at %d:%d \n",
            (group.max - group.min) ? 100 * (group.volume.names.front_left - group.min) / (group.max - group.min) : 0,
            (group.max - group.min) ? 100 * (group.volume.names.front_right - group.min) / (group.max - group.min): 0);
    }
}

static void handle_mixer()
{
    fd_set  rfds;
    int mixer_fd = snd_mixer_file_descriptor (mixer_handle);

    FD_ZERO(&rfds);
    FD_SET ( mixer_fd, &rfds);

    if (select (mixer_fd + 1, &rfds, NULL, NULL, NULL) == -1)
        perror ("select");

    snd_mixer_callbacks_t callbacks = { 0, 0, 0, 0 };

    snd_mixer_read (mixer_handle, &callbacks);
}

static void display_status_event (snd_pcm_event_t *event)
{
	char flag_buf[100] = {0};

	if (event->data.audiomgmt_status.flags & SND_PCM_STATUS_EVENT_HARD_SUSPEND)
	{
		if (event->data.audiomgmt_status.flags & ~(SND_PCM_STATUS_EVENT_HARD_SUSPEND|(SND_PCM_STATUS_EVENT_HARD_SUSPEND - 1U)))
			strcat(flag_buf, "HARD_SUSPEND|");
		else
			strcat(flag_buf, "HARD_SUSPEND");
	}

	if (event->data.audiomgmt_status.flags & SND_PCM_STATUS_EVENT_SOFT_SUSPEND)
	{
		if (event->data.audiomgmt_status.flags & ~(SND_PCM_STATUS_EVENT_SOFT_SUSPEND|(SND_PCM_STATUS_EVENT_SOFT_SUSPEND-1U)))
			strcat(flag_buf, "SOFT_SUSPEND|");
		else
			strcat(flag_buf, "SOFT_SUSPEND");
	}

	if (event->data.audiomgmt_status.flags & SND_PCM_STATUS_EVENT_AUTOPAUSE)
	{
		if (event->data.audiomgmt_status.flags & ~(SND_PCM_STATUS_EVENT_AUTOPAUSE | (SND_PCM_STATUS_EVENT_AUTOPAUSE - 1U)))
			strcat(flag_buf, "AUTOPAUSE|");
		else
			strcat(flag_buf, "AUTOPAUSE");
	}

	if (event->data.audiomgmt_status.flags == 0)
	{
		strcat(flag_buf, "None");
	}

	switch (event->data.audiomgmt_status.new_status)
	{
		case SND_PCM_STATUS_SUSPENDED:
			printf("Audio Management Status event received - SUSPENDED\n");
			printf("\tFlags = (0x%x) %s\n", event->data.audiomgmt_status.flags, flag_buf);
			break;
		case SND_PCM_STATUS_RUNNING:
			printf("Audio Management Status event received - RUNNING\n");
			printf("\tFlags = (0x%x) %s\n", event->data.audiomgmt_status.flags, flag_buf);
			break;
		case SND_PCM_STATUS_PAUSED:
			printf("Audio Management Status event received - PAUSED\n");
			printf("\tFlags = (0x%x) %s\n", event->data.audiomgmt_status.flags, flag_buf);
			break;
		default:
			break;
	}
}

static void display_mute_event (snd_pcm_event_t *event)
{
    char flag_buf[100] = {0};

    if (event->data.audiomgmt_mute.mute == 1)
    {
        if (event->data.audiomgmt_mute.reason & SND_PCM_MUTE_EVENT_HIGHER_PRIORITY)
        {
            if (event->data.audiomgmt_mute.reason & ~(SND_PCM_MUTE_EVENT_HIGHER_PRIORITY))
                strcat(flag_buf, "MUTE_BY_HIGHER|");
            else
                strcat(flag_buf, "MUTE_BY_HIGHER");
        }
        if (event->data.audiomgmt_mute.reason & SND_PCM_MUTE_EVENT_SAME_PRIORITY)
            strcat(flag_buf, "MUTE_BY_SAME");

        printf("Audio Management Mute event received - Muted by %s\n", flag_buf);
    }
    else
    {
        printf("Audio Management Mute event received - Un-Muted\n");
    }
}

static void handle_pcm_events()
{
    fd_set  ofds;
    snd_pcm_event_t event;
    int rtn = EOK;
    int pcm_fd = snd_pcm_file_descriptor (pcm_handle, SND_PCM_CHANNEL_PLAYBACK);

    FD_ZERO(&ofds);
    FD_SET ( pcm_fd, &ofds);

    if (select (pcm_fd + 1, NULL, NULL, &ofds, NULL) == -1)
        perror ("select");

    if ((rtn = snd_pcm_channel_read_event (pcm_handle, SND_PCM_CHANNEL_PLAYBACK, &event)) == EOK)
    {
        switch (event.type)
        {
            case SND_PCM_EVENT_AUDIOMGMT_STATUS:
                display_status_event(&event);
                break;
            case SND_PCM_EVENT_AUDIOMGMT_MUTE:
                display_mute_event(&event);
                break;
            case SND_PCM_EVENT_OUTPUTCLASS:
                printf("Output class event received - output class changed from %d to %d\n",
                       event.data.outputclass.old_output_class, event.data.outputclass.new_output_class);
                break;
            case SND_PCM_EVENT_UNDERRUN:
                printf("Underrun event received\n");
                break;
            default:
                printf("Unknown PCM event type - %d\n", event.type);
                break;
        }
    }
    else
        printf("snd_pcm_channel_read_event() failed with %d\n", rtn);

}

static void write_audio_data(WriterData *wd)
{
    struct timespec current_time;
    snd_pcm_channel_status_t status;
    int written = 0;
    int retries = 0;
    int remainder = mDataSize - N;

    if (repeat && (remainder < bsize)) {
        memset(mSampleBfr1, 0x0, bsize);
    }

    n = fread (mSampleBfr1, 1, min(remainder, bsize), wd->file1);
    if (!repeat && n <= 0)
        return;
    if (repeat) {
        if (n < 0)
            return;
        if (n < bsize) {
            /* We play the file in a loop, so if the last chunk is less then the fragsize
             * then we zero pad the remainder of the fragment (set above memset) and
             * report n as the full fragsize
             */
            n = bsize;
        }
    }

    written = snd_pcm_plugin_write (pcm_handle, mSampleBfr1, n);

    if (verbose)
        printf ("bytes written = %d \n", written);
    if( print_timing ) {
        clock_gettime( CLOCK_REALTIME, &current_time );
        printf ("Sent frag at %llu\n", (current_time.tv_sec - wd->start_time.tv_sec) * 1000000000LL +
                                       (current_time.tv_nsec - wd->start_time.tv_nsec));
    }
    /*
     * When written is smaller than n, we want to make sure we keep trying to write
     * so that we don't skip any data from the file. In blocking mode, we just
     * try a second time to write.
     * In nonblocking mode, we usleep frag_period_us / (WRITE_RETRY_TIMES -1),
     * then we just need to retry at most WRITE_RETRY_TIMES.
     */
    while (written < n && retries < WRITE_RETRY_TIMES)
    {
        memset (&status, 0, sizeof (status));
        status.channel = SND_PCM_CHANNEL_PLAYBACK;
        if (snd_pcm_plugin_status (pcm_handle, &status) < 0)
        {
            fprintf (stderr, "underrun: playback channel status error\n");
            cleanup_and_exit(EXIT_FAILURE);
        }

        switch (status.status)
        {
            case SND_PCM_STATUS_UNDERRUN:
            case SND_PCM_STATUS_READY:
                if( status.status == SND_PCM_STATUS_UNDERRUN ) {
                    printf ("Audio underrun occured\n");
                }
                if (snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
                {
                    fprintf (stderr, "underrun: playback channel prepare error\n");
                    cleanup_and_exit(EXIT_FAILURE);
                }
                break;
            case SND_PCM_STATUS_UNSECURE:
                fprintf (stderr, "Channel unsecure\n");
                if (snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
                {
                    fprintf (stderr, "unsecure: playback channel prepare error\n");
                    cleanup_and_exit(EXIT_FAILURE);
                }
                break;
            case SND_PCM_STATUS_ERROR:
                fprintf(stderr, "error: playback channel failure\n");
                cleanup_and_exit(EXIT_FAILURE);
                break;
            case SND_PCM_STATUS_PREEMPTED:
                fprintf(stderr, "error: playback channel preempted\n");
                cleanup_and_exit(EXIT_FAILURE);
                break;
            case SND_PCM_STATUS_CHANGE:
                printf ("Audio device change occured\n");
                if (snd_pcm_plugin_params (pcm_handle, &pp) < 0)
                {
                    fprintf (stderr, "device change: snd_pcm_plugin_params failed, why_failed = %d\n", pp.why_failed);
                    cleanup_and_exit(EXIT_FAILURE);
                }
                if (snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
                {
                    fprintf (stderr, "device change: playback channel prepare error\n");
                    cleanup_and_exit(EXIT_FAILURE);
                }
                break;
            case SND_PCM_STATUS_PAUSED:
                printf("Paused\n");
                /* Fall-Through - no break */
            case SND_PCM_STATUS_SUSPENDED:
                if (use_writer_thread)
                {
                   fd_set wfds;
                   int pcm_fd;

                   /* Wait until there is more room in the buffer (unsuspended/resumed) */
                   FD_ZERO(&wfds);
                   pcm_fd = snd_pcm_file_descriptor (pcm_handle, SND_PCM_CHANNEL_PLAYBACK);
                   FD_SET (pcm_fd, &wfds);

                   if (select (pcm_fd + 1, NULL, &wfds, NULL, NULL) == -1)
                       perror ("select");
                   continue; /* Go back to the top of the write loop */
                }
                else
                    return; /* Go back to higher level select() to wait until room in buffer (unsuspend/resume) */
                break;
             default:
                break;
        }

        if (written < 0)
            written = 0;
        if (nonblock)
            usleep(frag_period_us / (WRITE_RETRY_TIMES - 1));
        written += snd_pcm_plugin_write (pcm_handle, mSampleBfr1 + written, n - written);
        retries++;
    }
    N += written;
    if (repeat && N >= mDataSize) {
        /* We hit the end of file, rewind back to the beginning */
        fseek (wd->file1, data_position, SEEK_SET);
        N = 0;
    }
}

static void *writer_thread_handler(void *data)
{
    WriterData *wd = (WriterData *)data;
    sigset_t signals;

    sigfillset (&signals);
    pthread_sigmask (SIG_BLOCK, &signals, NULL);

    while (running && N < mDataSize && n > 0)
    {
        write_audio_data(wd);
    }

    return NULL;
}

static void *generic_thread_handler(void *data)
{
    sigset_t signals;

    sigfillset (&signals);
    pthread_sigmask (SIG_BLOCK, &signals, NULL);

    while(1) {
        ((void (*)(void))data)();
    }

    return NULL;
}


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

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

    -f<frag_size>      requested fragment size
    -v                 verbose
    -s                 content is protected
    -e                 content would like to be played on a secure channel
    -r                 content can only be played on a secure channel
    -t                 print timing information of when data is sent in ns
    -w                 use separate threads to control and write audio data
    -c<args>[,args ..] voice matrix configuration
    -n<num_frags>      requested number of fragments
    -b<num_frags>      requested number of fragments while buffering
    -p<volume in %>    volume in percent
    -m<mixer name>     string name for mixer input
    -x                 use mmap interface
    -i                 Display PCM channel info
    -R<value>          SRC rate method
                       (1 = 7-pt kaiser windowed, 2 = 20-pt remez, 3 = linear interpolation)
    -y                 Nonblocking mode
    -o<audio type>     string name for Audio type
    -d                 Continuously repeat input file

Runtime Controls:
                       'p': Pause
                       'r': Resume
                       'i': Display status
                       't': Stop repeating input file

Volume Controls:
Adjust the volume by 10 or by the remaining volume steps until the min or max (whichever is smaller)
                       'q': increase volume on front left, rear left, woofer
                       'a': decrease volume on front left, rear left, woofer
                       'w': increase volume on front left, rear left, front right, rear right, front center, woofer
                       's': decrease volume on front left, rear left, front right, rear right, front center, woofer
                       'e': increase volume on front right, rear right, front center
                       'd': decrease volume on front right, rear right, front center

Voice Matrix Configuration 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
    5=<hw_channel_bitmask> hardware channel bitmask for application voice 5
    6=<hw_channel_bitmask> hardware channel bitmask for application voice 6
    7=<hw_channel_bitmask> hardware channel bitmask for application voice 7
    8=<hw_channel_bitmask> hardware channel bitmask for application voice 8
#endif
/* *INDENT-ON* */
//*****************************************************************************

static void sig_handler( int sig_no )
{
    running = false;
    return;
}

static void dump_info( int card, int dev, const char* name, int channel )
{
    int rtn;
    snd_pcm_t * pcm_handle = 0;
    snd_pcm_channel_info_t pi;
    int open_mode = (channel == SND_PCM_CHANNEL_PLAYBACK)?SND_PCM_OPEN_PLAYBACK:SND_PCM_OPEN_CAPTURE;

    if (name[0] != '\0')
        rtn = snd_pcm_open_name(&pcm_handle, name, open_mode);
    else if (card == -1)
        rtn = snd_pcm_open_preferred(&pcm_handle, NULL, NULL, open_mode);
    else
        rtn = snd_pcm_open (&pcm_handle, card, dev, open_mode);
    if (rtn < 0)
    {
        fprintf(stderr, "Cannot open %s device - %s\n",
                (channel == SND_PCM_CHANNEL_PLAYBACK) ? "playback" : "capture", snd_strerror(rtn));
        return;
    }

    memset(&pi, 0, sizeof (pi));
    pi.channel = channel;
    if ((rtn = snd_pcm_channel_info( pcm_handle, &pi )) == 0)
    {
        snd_pcm_chmap_t *chmap;

        printf("\n%s %s Info\n", pi.subname, (channel == SND_PCM_CHANNEL_PLAYBACK) ? "Playback" : "Capture");
        printf("flags 0x%X\n", pi.flags);
        printf("formats 0x%X\n", pi.formats);
        printf("fragment_align %d\n", pi.fragment_align);
        printf("max_buffer_size %d\n", pi.max_buffer_size);
        printf("max_fragment_size %d\n", pi.max_fragment_size);
        printf("min_fragment_size %d\n", pi.min_fragment_size);
        printf("rates 0x%X\n", pi.rates);
        printf("max_rate %d\n", pi.max_rate);
        printf("min_rate %d\n", pi.min_rate);
        printf("max_voices %d\n", pi.max_voices);
        printf("min_voices %d\n", pi.min_voices);
        printf("subdevice %d\n", pi.subdevice);
        printf("transfer_block_size %d\n", pi.transfer_block_size);
        if (pi.mixer_gid.name && pi.mixer_gid.name[0] != '\0')
            printf("mixer_gid %s, %d\n", pi.mixer_gid.name, pi.mixer_gid.index);
        else
            printf("mixer_gid not set\n");

        chmap = snd_pcm_get_chmap( pcm_handle );
        if (chmap != NULL)
        {
            int i;
            printf("channel map:\n");
            for (i=0; i<chmap->channels; i++) {
                printf("%i\t%s\n", i, snd_pcm_get_chmap_channel_name(chmap->pos[i]));
            }
            free(chmap);
        }
    } else
        printf("Error retrieving %s info\n", (channel == SND_PCM_CHANNEL_PLAYBACK) ? "playback" : "capture");

    snd_pcm_close( pcm_handle );
}

int
main (int argc, char **argv)
{
    int     card = -1;
    int     dev = 0;
    FmtChunk fmt;
    int     mSampleRate;
    int     mSampleChannels;
    int     mSampleBits;
    int     mSampleBytes;
    int     fragsize = -1;
    int     fmtLength = 0;
    int     mode = SND_PCM_OPEN_PLAYBACK;

    int     rtn;
    snd_pcm_channel_info_t pi;
    snd_pcm_channel_setup_t setup;
    int c;
    fd_set  rfds, wfds, ofds;
#define MAX_VOICES 8
    uint32_t voice_mask[MAX_VOICES] = { 0 };
    snd_pcm_voice_conversion_t voice_conversion;
    int     voice_override = 0;
    int     num_frags = -1;
    int     num_buffered_frags = 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",
#define CHN5 4
        "5",
#define CHN6 5
        "6",
#define CHN7 6
        "7",
#define CHN8 7
        "8",
        NULL
    };
    char    name[_POSIX_PATH_MAX] = { 0 };
    float   vol_percent = -1;
    float   volume;
    char    mixer_name[32];
    int     mix_name_enable = -1;
    int     protected_content = 0;
    int     enable_protection = 0;
    int     require_protection = 0;
    void   *retval;
    pthread_t writer_thread;
    pthread_t mixer_thread;
    pthread_t keypress_thread;
    pthread_t pcm_event_thread;
    char   type[sizeof(pp.audio_type_name)] = {0};
    int    rate_method = 0;
    int    use_mmap = 0;
    int    info = 0;
    struct stat fileStat;
    int    pcm_fd, mixer_fd = 0;
    snd_pcm_filter_t pevent;

    // Start logging
    memset(&slog_config, 0, sizeof(slog_config));
    slog_config.num_buffers = 1;
    slog_config.verbosity_level = SLOG2_INFO;
    slog_config.buffer_set_name = "wave";
    slog_config.buffer_config[0].buffer_name="wave";
    slog_config.buffer_config[0].num_pages = 5;
    slog2_register(&slog_config, &slog_handle, 0);
    slog2_set_verbosity(slog_handle, SLOG2_INFO);
    slog2_set_default_buffer(slog_handle);

    while ((c = getopt (argc, argv, "ia:ef:vc:n:b:p:m:rstwo:xR:yd")) != EOF)
    {
        switch (c)
        {
        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 %s\n", name);
            else
                printf ("Using card %d device %d \n", card, dev);
            break;
        case 'f':
            fragsize = atoi (optarg);
            break;
        case 'i':
            info = 1;
            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 < MAX_VOICES) && value ) {
                    voice_mask[channel] = strtoul (value, NULL, 0);
                } else {
                    fprintf (stderr, "Invalid channel map specified\n");
                    return (EXIT_FAILURE);
                }
            }
            free(sub_opts_copy);
            voice_override = 1;
            break;
        case 'n':
            num_frags = atoi (optarg) - 1;
            break;
        case 'b':
            num_buffered_frags = atoi (optarg);
            break;
        case 'p':
            vol_percent = atof (optarg);
            break;
        case 'm':
            strlcpy (mixer_name, optarg, sizeof(mixer_name));
            mix_name_enable = 1;
            break;
        case 's':
            protected_content = 1;
            break;
        case 'e':
            enable_protection = 1;
            break;
        case 'r':
            require_protection = 1;
            break;
        case 't':
            print_timing = 1;
            break;
        case 'w':
            use_writer_thread = 1;
            break;
        case 'o':
            strlcpy (type, optarg, sizeof(type));
            type[sizeof(pp.audio_type_name) - 1] = '\0';
            break;
        case 'x':
            use_mmap = 1;
            break;
        case 'R':
            rate_method = atoi(optarg);
            if (rate_method < 0 || rate_method > 3)
            {
                rate_method = 0;
                printf("Invalid rate method, using method 0\n");
            }
            break;
        case 'y':
            mode |= SND_PCM_OPEN_NONBLOCK;
            nonblock = 1;
            break;
        case 'd':
            repeat = 1;
            printf("Repeat playing\n");
            break;
        default:
            fprintf(stderr, "Invalid option -%c\n", c);
            return (EXIT_FAILURE);
        }
    }

    setvbuf (stdin, NULL, _IONBF, 0);

    if (info) {
        dump_info( card, dev, name, SND_PCM_CHANNEL_PLAYBACK);
        dump_info( card, dev, name, SND_PCM_CHANNEL_CAPTURE);
        return (EXIT_SUCCESS);
    }

    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, "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, "device open failed - %s\n", snd_strerror(rtn));
                return (EXIT_FAILURE);
            }
        }
        else
        {
            if ((rtn = snd_pcm_open (&pcm_handle, card, dev, mode)) < 0)
            {
                fprintf(stderr, "device open failed - %s\n", snd_strerror(rtn));
                return (EXIT_FAILURE);
            }
        }
    }

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

    if (stat (argv[optind], &fileStat) == -1) {
        perror ("file stat");
        cleanup_and_exit(EXIT_FAILURE);
    }

    if (CheckHdr (wd.file1) == -1) {
        fprintf (stderr, "invalid wav header\n");
        cleanup_and_exit(EXIT_FAILURE);
    }

    fmtLength = FindTag (wd.file1, "fmt ");
    if ((fmtLength == 0) || (fmtLength < sizeof(fmt)) ||
        (fread (&fmt, sizeof(fmt), 1, wd.file1) == 0)) {
        fprintf (stderr, "invalid wav file\n");
        cleanup_and_exit(EXIT_FAILURE);
    }

    /* Some files have extra data in fmt field, so skip past it */
    if (fmtLength > sizeof(fmt))
        fseek (wd.file1, fmtLength - sizeof(fmt), SEEK_CUR);

    mDataSize = FindTag (wd.file1, "data");
    data_position = ftell(wd.file1);
    if ((mDataSize == 0) || (mDataSize > ((long)fileStat.st_size - data_position))) {
        fprintf (stderr, "wave data size conflicts with file size mDataSize=%d file size=%zd, data_pos=%ld\n",
                 mDataSize, fileStat.st_size, data_position);
        cleanup_and_exit(EXIT_FAILURE);
    }

    if (mBigEndian) {
        mSampleRate = ENDIAN_BE32 (fmt.SamplesPerSec);
        mSampleChannels = ENDIAN_BE16 (fmt.Channels);
        mSampleBits = ENDIAN_BE16 (fmt.BitsPerSample);
        /* BlockAlign = frame size, i.e. sample size including padding bits, times number of channels */
        mSampleBytes = ENDIAN_BE16 (fmt.BlockAlign) / mSampleChannels;
        fmt.FormatTag = ENDIAN_BE16 (fmt.FormatTag);
    } else {
        mSampleRate = ENDIAN_LE32 (fmt.SamplesPerSec);
        mSampleChannels = ENDIAN_LE16 (fmt.Channels);
        mSampleBits = ENDIAN_LE16 (fmt.BitsPerSample);
        /* BlockAlign = frame size, i.e. sample size including padding bits, times number of channels */
        mSampleBytes = ENDIAN_LE16 (fmt.BlockAlign) / mSampleChannels;
        fmt.FormatTag = ENDIAN_LE16 (fmt.FormatTag);
    }

    printf("SampleRate = %d, Channels = %d, SampleBits = %d, SampleBytes = %d\n",
           mSampleRate, mSampleChannels, mSampleBits, mSampleBytes);
    printf("Playback Duration = %fs\n", (float)mDataSize / (mSampleRate * mSampleChannels * mSampleBytes));

    /* Enable PCM events */
    pevent.enable = SND_PCM_EVENT_MASK(SND_PCM_EVENT_AUDIOMGMT_STATUS) |
                    SND_PCM_EVENT_MASK(SND_PCM_EVENT_AUDIOMGMT_MUTE) |
                    SND_PCM_EVENT_MASK(SND_PCM_EVENT_OUTPUTCLASS) |
                    SND_PCM_EVENT_MASK(SND_PCM_EVENT_UNDERRUN);
    snd_pcm_set_filter(pcm_handle, SND_PCM_CHANNEL_PLAYBACK, &pevent);

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

    memset (&pi, 0, sizeof (pi));
    pi.channel = SND_PCM_CHANNEL_PLAYBACK;
    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
            | (protected_content ? SND_PCM_MODE_FLAG_PROTECTED_CONTENT : 0)
            | (enable_protection ? SND_PCM_MODE_FLAG_ENABLE_PROTECTION : 0)
            | (require_protection ? SND_PCM_MODE_FLAG_REQUIRE_PROTECTION : 0);

    pp.channel = SND_PCM_CHANNEL_PLAYBACK;
    pp.start_mode = SND_PCM_START_FULL;
    pp.stop_mode = SND_PCM_STOP_STOP;

    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_buffered_max = num_buffered_frags;
    pp.buf.block.frags_min = 1;

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

    if (fmt.FormatTag == 6) {
        pp.format.format = SND_PCM_SFMT_A_LAW;
    } else if (fmt.FormatTag == 7) {
        pp.format.format = SND_PCM_SFMT_MU_LAW;
    } else if (mSampleBits == 8) {
        pp.format.format = SND_PCM_SFMT_U8;
    } else if (mSampleBits == 16) {
        if (mBigEndian) {
            pp.format.format = SND_PCM_SFMT_S16_BE;
        } else {
            pp.format.format = SND_PCM_SFMT_S16_LE;
        }
    } else if (mSampleBits == 24 && mSampleBytes == 4) {
        if (mBigEndian) {
            pp.format.format = SND_PCM_SFMT_S24_4_BE;
        } else {
            pp.format.format = SND_PCM_SFMT_S24_4_LE;
        }
    } else if (mSampleBits == 24 && mSampleBytes == 3) {
        if (mBigEndian) {
            pp.format.format = SND_PCM_SFMT_S24_BE;
        } else {
            pp.format.format = SND_PCM_SFMT_S24_LE;
        }
    } else if (mSampleBits == 32) {
        if (mBigEndian) {
            pp.format.format = SND_PCM_SFMT_S32_BE;
        } else {
            pp.format.format = SND_PCM_SFMT_S32_LE;
        }
    } else {
        fprintf(stderr, "Unsupported number of bits per sample %d, sample size %d\n", mSampleBits, mSampleBytes);
        cleanup_and_exit(EXIT_FAILURE);
    }

    strlcpy (pp.audio_type_name, type, sizeof(pp.audio_type_name));

    if (mix_name_enable == 1)
    {
        strlcpy (pp.sw_mixer_subchn_name, mixer_name, sizeof(pp.sw_mixer_subchn_name));
    }
    else
    {
        strcpy (pp.sw_mixer_subchn_name, "Wave playback channel");
    }

    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, why_failed = %d\n", snd_strerror (rtn), pp.why_failed);
        cleanup_and_exit(EXIT_FAILURE);
    }

    if (voice_override)
    {
        unsigned int i;
        snd_pcm_plugin_get_voice_conversion (pcm_handle, SND_PCM_CHANNEL_PLAYBACK,
            &voice_conversion);
        for (i = 0; i < MAX_VOICES; i++) {
            voice_conversion.matrix[i] = voice_mask[i];
        }
        snd_pcm_plugin_set_voice_conversion (pcm_handle, SND_PCM_CHANNEL_PLAYBACK,
            &voice_conversion);
    }

    memset (&setup, 0, sizeof (setup));
    memset (&group, 0, sizeof (group));
    setup.channel = SND_PCM_CHANNEL_PLAYBACK;
    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);
    printf ("Voices %d \n", setup.format.voices);
    bsize = setup.buf.block.frag_size;
    frag_period_us = (( (int64_t)bsize * 1000000 ) / (mSampleBytes * mSampleChannels * mSampleRate));
    printf("Frag Period is %d us\n", frag_period_us);

    if (group.gid.name[0] == 0)
    {
        printf ("Mixer Pcm Group [%s] Not Set \n", group.gid.name);
    }
    else
    {
        printf ("Mixer Pcm Group [%s]\n", group.gid.name);
        if ((rtn = snd_mixer_open (&mixer_handle, 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));
    }

    if( print_timing ) {
        clock_gettime( CLOCK_REALTIME, &wd.start_time );
    }

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

    FD_ZERO (&rfds);
    FD_ZERO (&wfds);
    FD_ZERO (&ofds);
    n = 1;

    if (mixer_handle)
    {
        if (vol_percent >=0)
        {
            if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0)
                fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));

            volume = (float)(group.max - group.min) * ( vol_percent / 100);

            if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
                group.volume.names.front_left = (int)volume;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
                group.volume.names.rear_left = (int)volume;
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
                group.volume.names.front_center = (int)volume;
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
                group.volume.names.front_right = (int)volume;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
                group.volume.names.rear_right = (int)volume;
            if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
                group.volume.names.woofer = (int)volume;

            if ((rtn = snd_mixer_group_write (mixer_handle, &group)) < 0)
                fprintf (stderr, "snd_mixer_group_write failed: %s\n", snd_strerror (rtn));

            vol_percent = -1;
        }
    }

    signal(SIGINT, sig_handler);
    signal(SIGTERM, sig_handler);

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

    if( use_writer_thread ) {
        pthread_create( &writer_thread, NULL, writer_thread_handler, &wd );
        pthread_create( &pcm_event_thread, NULL, generic_thread_handler, handle_pcm_events );
        pthread_create( &keypress_thread, NULL, generic_thread_handler, handle_keypress );
        if (mixer_handle)
            pthread_create( &mixer_thread, NULL, generic_thread_handler, handle_mixer );
        // First wait for feeder to complete. Any other thread will cause it to stop.
        // Then just kill the other threads
        pthread_join(writer_thread, &retval);
        pthread_cancel(keypress_thread);
        pthread_cancel(pcm_event_thread);
        if (mixer_handle)
            pthread_cancel(mixer_thread);
    } else {
        while (running && N < mDataSize && n > 0)
        {
            FD_ZERO(&rfds);
            FD_ZERO(&wfds);
            FD_ZERO(&ofds);
            if (stdin_raw)
                FD_SET (STDIN_FILENO, &rfds);
            if (mixer_handle) {
                mixer_fd = snd_mixer_file_descriptor (mixer_handle);
                FD_SET (mixer_fd, &rfds);
            }
            pcm_fd = snd_pcm_file_descriptor (pcm_handle, SND_PCM_CHANNEL_PLAYBACK);
            FD_SET (pcm_fd, &wfds);
            FD_SET (pcm_fd, &ofds);

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

            if (FD_ISSET (STDIN_FILENO, &rfds))
            {
                handle_keypress();
            }

            if (mixer_handle && FD_ISSET (mixer_fd, &rfds))
            {
                handle_mixer();
            }

            if (FD_ISSET (pcm_fd, &wfds))
            {
                write_audio_data(&wd);
            }

            if (FD_ISSET (pcm_fd, &ofds))
            {
                snd_pcm_event_t event;
                if ((rtn = snd_pcm_channel_read_event (pcm_handle, SND_PCM_CHANNEL_PLAYBACK, &event)) == EOK)
                {
                    switch (event.type)
                    {
                        case SND_PCM_EVENT_AUDIOMGMT_STATUS:
                            display_status_event(&event);
                            break;
                        case SND_PCM_EVENT_AUDIOMGMT_MUTE:
                            display_mute_event(&event);
                            break;
                        case SND_PCM_EVENT_OUTPUTCLASS:
                            printf("Output class event received - output class changed from %d to %d\n",
                                   event.data.outputclass.old_output_class, event.data.outputclass.new_output_class);
                            break;
                        case SND_PCM_EVENT_UNDERRUN:
                            printf("Underrun event received\n");
                            break;
                        default:
                            printf("Unknown PCM event type - %d\n", event.type);
                            break;
                    }
                 }
                 else
                     printf("snd_pcm_channel_read_event() failed with %d\n", rtn);
            }
        }
    }

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

    if (running)
        snd_pcm_plugin_flush (pcm_handle, SND_PCM_CHANNEL_PLAYBACK);
    cleanup();
    return(EXIT_SUCCESS);
}

#if defined(__QNXNTO__) && defined(__USESRCVERSION)
#include <sys/srcversion.h>
__SRCVERSION("$URL$ $Rev$")
#endif