wave.c example

This is a sample application that plays back 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 <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

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;
char *mSampleBfr1;
unsigned int mDataSize;
bool mBigEndian = false;
int nonblock = 0;

static slog2_buffer_t slog_handle;
static slog2_buffer_set_config_t slog_config;

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;

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


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


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


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
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;

        // 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;
    }

    switch (c)
    {
        case 'q':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
                group.volume.names.front_left += 10;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
                group.volume.names.rear_left += 10;
            if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
                group.volume.names.woofer += 10;
            break;
        case 'a':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
                group.volume.names.front_left -= 10;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
                group.volume.names.rear_left -= 10;
            if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
                group.volume.names.woofer -= 10;
            break;
        case 'w':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
                group.volume.names.front_left += 10;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
                group.volume.names.rear_left += 10;
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
                group.volume.names.front_center += 10;
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
                group.volume.names.front_right += 10;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
                group.volume.names.rear_right += 10;
            if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
                group.volume.names.woofer += 10;
            break;
        case 's':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
                group.volume.names.front_left -= 10;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
                group.volume.names.rear_left -= 10;
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
                group.volume.names.front_center -= 10;
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
                group.volume.names.front_right -= 10;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
                group.volume.names.rear_right -= 10;
            if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
                group.volume.names.woofer -= 10;
            break;
        case 'e':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
                group.volume.names.front_right += 10;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
                group.volume.names.rear_right += 10;
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
                group.volume.names.front_center += 10;
            break;
        case 'd':
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
                group.volume.names.front_right -= 10;
            if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
                group.volume.names.rear_right -= 10;
            if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
                group.volume.names.front_center -= 10;
            break;
    }

    if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT)
    {
        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.channels & SND_MIXER_CHN_MASK_REAR_LEFT)
    {
        if (group.volume.names.rear_left > group.max)
            group.volume.names.rear_left = group.max;
        if (group.volume.names.rear_left < group.min)
            group.volume.names.rear_left = group.min;
    }
    if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER)
    {
        if (group.volume.names.front_center > group.max)
            group.volume.names.front_center = group.max;
        if (group.volume.names.front_center < group.min)
            group.volume.names.front_center = group.min;
    }
    if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT)
    {
        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 (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT)
    {
        if (group.volume.names.rear_right > group.max)
            group.volume.names.rear_right = group.max;
        if (group.volume.names.rear_right < group.min)
            group.volume.names.rear_right = group.min;
    }
    if (group.channels & SND_MIXER_CHN_MASK_WOOFER)
    {
        if (group.volume.names.woofer > group.max)
            group.volume.names.woofer = group.max;
        if (group.volume.names.woofer < group.min)
            group.volume.names.woofer = 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.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);
    }
}

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)
        err ("select");

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

    snd_mixer_read (mixer_handle, &callbacks);
}

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)
        err ("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:
                switch (event.data.audiomgmt_status.new_status)
                {
                    case SND_PCM_STATUS_SUSPENDED:
                        printf("Audio Management Status event received - Suspended by %s\n",
                               event.data.audiomgmt_status.reason == SAME_PRIORITY ? "Same" : "higher");
                        break;
                    case SND_PCM_STATUS_RUNNING:
                        if (event.data.audiomgmt_status.old_status == SND_PCM_STATUS_SUSPENDED)
                        {
                             printf("Audio Management Status event received - Resumed by %s\n",
                                    event.data.audiomgmt_status.reason == SAME_PRIORITY ? "Same" : "higher");
                        }
                        break;
                    case SND_PCM_STATUS_PAUSED:
                        printf("Audio Management Status event received - Paused by %s\n",
                               event.data.audiomgmt_status.reason == SAME_PRIORITY ? "Same" : "higher");
                        break;
                    default:
                        break;
                }
                break;
            case SND_PCM_EVENT_AUDIOMGMT_MUTE:
                if (event.data.audiomgmt_mute.mute == 1)
                {
                    printf("Audio Management Mute event received - Muted by %s\n",
                           event.data.audiomgmt_mute.reason == SAME_PRIORITY ? "Same" : "higher");
                }
                else
                {
                    printf("Audio Management Mute event received - Un-Muted by %s\n",
                           event.data.audiomgmt_mute.reason == SAME_PRIORITY ? "Same" : "higher");
                }
                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;
            default:
                printf("Unknown PCM event type - %d\n", event.type);
                break;
        }
    }
    else
        printf("snd_pcm_channel_read_event() failed with %d\n", rtn);

}

void write_audio_data(WriterData *wd)
{
    struct  timespec current_time;
    snd_pcm_channel_status_t status;
    int     written = 0;
    int     retries = 0;

    if ((n = fread (mSampleBfr1, 1, min (mDataSize - N, bsize), wd->file1)) <= 0)
        return;
    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");
            exit (1);
        }

        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");
                    exit (1);
                }
                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");
                    exit (1);
                }
                break;
            case SND_PCM_STATUS_ERROR:
                fprintf(stderr, "error: playback channel failure\n");
                exit(1);
                break;
            case SND_PCM_STATUS_PREEMPTED:
                fprintf(stderr, "error: playback channel preempted\n");
                exit(1);
                break;
            case SND_PCM_STATUS_CHANGE:
                printf ("Audio device change occured\n");
                exit(1);
                break;
            case SND_PCM_STATUS_PAUSED:
                printf("Paused\n");
                /* Fall-Through */
            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)
                       err ("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;
}

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

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

Runtime Controls:
                       'p': Pause
                       'r': Resume

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* */
//*****************************************************************************

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

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)
    {
        printf("Cannot open %s device", channel == SND_PCM_CHANNEL_PLAYBACK?"playback":"capture");
        return;
    }

    memset(&pi, 0, sizeof (pi));
    pi.channel = channel;
    if ((rtn = snd_pcm_channel_info( pcm_handle, &pi )) == 0)
    {
        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);
    } 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;
    WriterData wd;
    FmtChunk fmt;
    int     mSampleRate;
    int     mSampleChannels;
    int     mSampleBits;
    int     fragsize = -1;
    int     fmtLength = 0;
    int     mode = SND_PCM_OPEN_PLAYBACK;

    int     rtn;
    snd_pcm_channel_info_t pi;
    snd_pcm_channel_params_t pp;
    snd_pcm_channel_setup_t setup;
    int c;
    fd_set  rfds, wfds, ofds;
    uint32_t voice_mask[] = { 0, 0, 0, 0, 0, 0, 0, 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:y")) != 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) {
                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 'n':
            num_frags = atoi (optarg) - 1;
            break;
        case 'b':
            num_buffered_frags = atoi (optarg);
            break;
        case 'p':
            vol_percent = atof (optarg);
            break;
        case 'm':
            strncpy (mixer_name, optarg, 32);
            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':
            strncpy (type, optarg, sizeof(pp.audio_type_name) - 1);
            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;
        default:
            return 1;
        }
    }

    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 0;
    }

    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 ((wd.file1 = fopen (argv[optind], "r")) == 0)
        return err ("file open");

    if (stat (argv[optind], &fileStat) == -1)
        return err ("file stat");

    if (CheckHdr (wd.file1) == -1)
        return err ("CheckHdr");

    fmtLength = FindTag (wd.file1, "fmt ");
    if ((fmtLength == 0) || (fmtLength < sizeof(fmt)) ||
        (fread (&fmt, sizeof(fmt), 1, wd.file1) == 0))
        return err ("invalid wav file");

    /* 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");
    if ((mDataSize == 0) || (mDataSize > ((long)fileStat.st_size - ftell(wd.file1))))
        return err ("wave data size conflicts with file size");

    if (mBigEndian) {
        mSampleRate = ENDIAN_BE32 (fmt.SamplesPerSec);
        mSampleChannels = ENDIAN_BE16 (fmt.Channels);
        mSampleBits = ENDIAN_BE16 (fmt.BitsPerSample);
        fmt.FormatTag = ENDIAN_BE16 (fmt.FormatTag);
    } else {
        mSampleRate = ENDIAN_LE32 (fmt.SamplesPerSec);
        mSampleChannels = ENDIAN_LE16 (fmt.Channels);
        mSampleBits = ENDIAN_LE16 (fmt.BitsPerSample);
        fmt.FormatTag = ENDIAN_LE16 (fmt.FormatTag);
    }

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

    /* Enable PCM events */
    pevent.enable = ( (1<<SND_PCM_EVENT_AUDIOMGMT_STATUS) |
                      (1<<SND_PCM_EVENT_AUDIOMGMT_MUTE) |
                      (1<<SND_PCM_EVENT_OUTPUTCLASS) );
    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));
        return -1;
    }

    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) {
        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", mSampleBits);
        return -1;
    }

    strncpy (pp.audio_type_name, type, sizeof(pp.audio_type_name) - 1);

    if (mix_name_enable == 1)
    {
        strncpy (pp.sw_mixer_subchn_name, mixer_name, 32);
    }
    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);
        return -1;
    }

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

    if (voice_override)
    {
        snd_pcm_plugin_get_voice_conversion (pcm_handle, SND_PCM_CHANNEL_PLAYBACK,
            &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];
        voice_conversion.matrix[4] = voice_mask[4];
        voice_conversion.matrix[5] = voice_mask[5];
        voice_conversion.matrix[6] = voice_mask[6];
        voice_conversion.matrix[7] = voice_mask[7];
        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));
        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);
    printf ("Voices %d \n", setup.format.voices);
    bsize = setup.buf.block.frag_size;
    frag_period_us = (( (int64_t)bsize * _BITS_BYTE * 1000000 ) / (mSampleBits * 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));
            return -1;
        }
    }
    if (tcgetpgrp (0) == getpid ())
        dev_raw (fileno (stdin));

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

    mSampleBfr1 = malloc (bsize);
    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( 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 (tcgetpgrp (0) == getpid ())
                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)
            {
                err ("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:
                            switch (event.data.audiomgmt_status.new_status)
                            {
                                case SND_PCM_STATUS_SUSPENDED:
                                    printf("Audio Management Status event received - Suspended by %s\n",
                                           event.data.audiomgmt_status.reason == SAME_PRIORITY ? "Same" : "higher");
                                    break;
                                case SND_PCM_STATUS_RUNNING:
                                    if (event.data.audiomgmt_status.old_status == SND_PCM_STATUS_SUSPENDED)
                                    {
                                        printf("Audio Management Status event received - Resumed by %s\n",
                                               event.data.audiomgmt_status.reason == SAME_PRIORITY ? "Same" : "higher");
                                    }
                                    break;
                                case SND_PCM_STATUS_PAUSED:
                                    printf("Audio Management Status event received - Paused by %s\n",
                                           event.data.audiomgmt_status.reason == SAME_PRIORITY ? "Same" : "higher");
                                    break;
                                default:
                                    break;
                            }
                            break;
                        case SND_PCM_EVENT_AUDIOMGMT_MUTE:
                            if (event.data.audiomgmt_mute.mute == 1)
                            {
                                printf("Audio Management Mute event received - Muted by %s\n",
                                       event.data.audiomgmt_mute.reason == SAME_PRIORITY ? "Same" : "higher");
                            }
                            else
                            {
                                printf("Audio Management Mute event received - Un-Muted by %s\n",
                                       event.data.audiomgmt_mute.reason == SAME_PRIORITY ? "Same" : "higher");
                            }
                            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;
                        default:
                            printf("Unknown PCM event type - %d\n", event.type);
                            break;
                    }
                 }
                 else
                     printf("snd_pcm_channel_read_event() failed with %d\n", rtn);
            }
        }
    }
    if (tcgetpgrp (0) == getpid ())
        dev_unraw (fileno (stdin));
    printf("Exiting...\n");
    if (running) {
        snd_pcm_plugin_flush (pcm_handle, SND_PCM_CHANNEL_PLAYBACK);
    }
    if (mixer_handle)
        snd_mixer_close (mixer_handle);
    snd_pcm_close (pcm_handle);

    fclose(wd.file1);

    return (0);
}



}