| Updated: October 28, 2024 |
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.channel_mask & SND_MIXER_CHN_MASK_FRONT_LEFT)
group.volume.names.front_left += min(group.max - group.volume.names.front_left, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_LEFT)
group.volume.names.rear_left += min(group.max - group.volume.names.rear_left, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_WOOFER)
group.volume.names.woofer += min(group.max - group.volume.names.woofer, 10);
break;
case 'a':
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_LEFT)
group.volume.names.front_left -= min(group.volume.names.front_left - group.min, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_LEFT)
group.volume.names.rear_left -= min(group.volume.names.rear_left - group.min, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_WOOFER)
group.volume.names.woofer -= min(group.volume.names.woofer - group.min, 10);
break;
case 'w':
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_LEFT)
group.volume.names.front_left += min(group.max - group.volume.names.front_left, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_LEFT)
group.volume.names.rear_left += min(group.max - group.volume.names.rear_left, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_CENTER)
group.volume.names.front_center += min(group.max - group.volume.names.front_center, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_RIGHT)
group.volume.names.front_right += min(group.max - group.volume.names.front_right, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_RIGHT)
group.volume.names.rear_right += min(group.max - group.volume.names.rear_right, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_WOOFER)
group.volume.names.woofer += min(group.max - group.volume.names.woofer, 10);
break;
case 's':
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_LEFT)
group.volume.names.front_left -= min(group.volume.names.front_left - group.min, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_LEFT)
group.volume.names.rear_left -= min(group.volume.names.rear_left - group.min, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_CENTER)
group.volume.names.front_center -= min(group.volume.names.front_center - group.min, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_RIGHT)
group.volume.names.front_right -= min(group.volume.names.front_right - group.min, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_RIGHT)
group.volume.names.rear_right -= min(group.volume.names.rear_right - group.min, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_WOOFER)
group.volume.names.woofer -= min(group.volume.names.woofer - group.min, 10);
break;
case 'e':
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_RIGHT)
group.volume.names.front_right += min(group.max - group.volume.names.front_right, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_RIGHT)
group.volume.names.rear_right += min(group.max - group.volume.names.rear_right, 10);
if (group.channel_mask & 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.channel_mask & SND_MIXER_CHN_MASK_FRONT_RIGHT)
group.volume.names.front_right -= min(group.volume.names.front_right - group.min, 10);
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_RIGHT)
group.volume.names.rear_right -= min(group.volume.names.rear_right - group.min, 10);
if (group.channel_mask & 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.channel_mask & 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.channel_mask & 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.channel_mask & 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, ¤t_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;
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;
snd_pcm_chmap_t *chmap;
// 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: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 '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_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);
chmap = snd_pcm_get_chmap( pcm_handle );
if (chmap != NULL)
{
int i;
printf ("Voices %d (", setup.format.voices);
for (i=0; i<chmap->channels; i++) {
if (i == (chmap->channels - 1))
printf("%s)\n", snd_pcm_get_chmap_channel_name(chmap->pos[i]));
else
printf("%s, ", snd_pcm_get_chmap_channel_name(chmap->pos[i]));
}
free(chmap);
}
else
{
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 %.3f ms\n", (double)frag_period_us/1000);
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.channel_mask & SND_MIXER_CHN_MASK_FRONT_LEFT)
group.volume.names.front_left = (int)volume;
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_LEFT)
group.volume.names.rear_left = (int)volume;
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_CENTER)
group.volume.names.front_center = (int)volume;
if (group.channel_mask & SND_MIXER_CHN_MASK_FRONT_RIGHT)
group.volume.names.front_right = (int)volume;
if (group.channel_mask & SND_MIXER_CHN_MASK_REAR_RIGHT)
group.volume.names.rear_right = (int)volume;
if (group.channel_mask & 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_drain (pcm_handle, SND_PCM_CHANNEL_PLAYBACK);
cleanup();
return(EXIT_SUCCESS);
}
#if defined(__QNXNTO__) && defined(__USESRCVERSION)
#include <sys/srcversion.h>
__SRCVERSION("$URL$ $Rev$")
#endif