wave.c example

This is a sample application that plays back audio data.

This version corresponds to QNX Software Development Platform 6.6 io-audio patch (patch ID 4687).

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

#include <sys/asoundlib.h>

#include <audio/audio_manager_volume.h>
#include <audio/audio_manager_routing.h>
#include <audio/audio_manager_event.h>

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;
useconds_t frag_period_us;
snd_mixer_group_t group;
snd_mixer_t *mixer_handle = NULL;
snd_pcm_t *pcm_handle = NULL;
char *mSampleBfr1 = NULL;
unsigned int mDataSize;
bool mBigEndian = false;
int repeat = 0;
long int data_position = 0;
int stdin_raw = 0;
WriterData wd;
int uses_audioman_handle = 1;
unsigned int audioman_handle;

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
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 (uses_audioman_handle)
        audio_manager_free_handle (audioman_handle);
    if (wd.file1)
        fclose(wd.file1);
    if (mSampleBfr1)
        free(mSampleBfr1);
}

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

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

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

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

    snd_mixer_read (mixer_handle, &callbacks);
}

void write_audio_data(WriterData *wd)
{
    struct timespec current_time;
    snd_pcm_channel_status_t status;
    int written = 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));
    }
    if (written < n)
    {
        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);
        }

        if (status.status == SND_PCM_STATUS_READY ||
            status.status == SND_PCM_STATUS_UNDERRUN ||
            status.status == SND_PCM_STATUS_CHANGE)
        {
            if( status.status == SND_PCM_STATUS_UNDERRUN ) {
                printf ("Audio underrun occurred\n");
            } else if( status.status == SND_PCM_STATUS_CHANGE ) {
                printf ("Audio device change occurred from %s to %s\n",
                        audio_manager_get_device_name(status.status_data.change_data.old_device),
                        audio_manager_get_device_name(status.status_data.change_data.new_device));
            }
            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);
            }
        }
        else if (status.status == 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);
            }
        }
        else if (status.status == SND_PCM_STATUS_ERROR)
        {
            fprintf(stderr, "error: playback channel failure\n");
            cleanup_and_exit(EXIT_FAILURE);
        }
        else if (status.status == SND_PCM_STATUS_PREEMPTED)
        {
            fprintf(stderr, "error: playback channel preempted\n");
            cleanup_and_exit(EXIT_FAILURE);
        }

        if (written < 0)
            written = 0;
        written += snd_pcm_plugin_write (pcm_handle, mSampleBfr1 + written, n - written);
    }
    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;
    }
}

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

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>     name of the audio type registered with audioman
    -d                 Continuously repeat input file

Runtime Controls:
                       'p': Pause
                       'r': Resume
                       '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* */
//*****************************************************************************

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("subname %s\n", pi.subname == NULL ? "NULL" : pi.subname);
        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;
    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;
    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;
    int     use_writer_thread = 0;
    void   *retval;
    pthread_t writer_thread;
    pthread_t mixer_thread;
    pthread_t keypress_thread;
    char   *type = "multimedia";
    int    rate_method = 0;
    int    use_mmap = 0;
    int    info = 0;
    struct stat fileStat;
    int    pcm_fd, mixer_fd = 0;

    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 < sizeof(voice_mask)/sizeof(voice_mask[0]) && 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':
            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':
            type = optarg;
            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;
            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 ( audio_manager_get_handle (audio_manager_get_type_from_name(type), 0, true, &audioman_handle) ) {
        uses_audioman_handle = 0;
    } else {
        if ((rtn = snd_pcm_set_audioman_handle (pcm_handle, audioman_handle)) < 0) {
            perror ("set audioman handle");
            cleanup_and_exit(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\n");
        cleanup_and_exit(EXIT_FAILURE);
    }

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

    if (!use_mmap)
    {
        /* disabling mmap is not actually required in this example but it is included to
         * demonstrate how it is used when it is required.
         */
        snd_pcm_plugin_set_disable (pcm_handle, PLUGIN_DISABLE_MMAP);
    }
    snd_pcm_plugin_set_enable (pcm_handle, PLUGIN_ROUTING);

    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) {
        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\n", mSampleBits);
        cleanup_and_exit(EXIT_FAILURE);
    }

    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);
        cleanup_and_exit(EXIT_FAILURE);
    }

    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));
        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 * _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));
            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);
    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( &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);
        if (mixer_handle)
            pthread_cancel(mixer_thread);
    } else {
        while (running && N < mDataSize && n > 0)
        {
            FD_ZERO(&rfds);
            FD_ZERO(&wfds);
            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);

            rtn = max (mixer_fd, pcm_fd);
            if (select (rtn + 1, &rfds, &wfds, NULL, 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);
            }
        }
    }

    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