This is a sample application that plays back audio data.
For information about using this utility, see wave in the QNX Neutrino Utilities Guide.
/* * $QNXLicenseC: * Copyright 2016, QNX Software Systems. All Rights Reserved. * * You must obtain a written license from and pay applicable license fees to QNX * Software Systems before you may reproduce, modify or distribute this software, * or any work that includes all or part of this software. Free development * licenses are available for evaluation and non-commercial purposes. For more * information visit http://licensing.qnx.com or email licensing@qnx.com. * * This file may contain contributions from others. Please review this entire * file for other proprietary rights or license notices, as well as the QNX * Development Suite License Guide at http://licensing.qnx.com/license-guide/ * for other information. * $ */ #include <errno.h> #include <fcntl.h> #include <gulliver.h> #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #include <sys/ioctl.h> #include <sys/select.h> #include <sys/stat.h> #include <sys/termio.h> #include <sys/types.h> #include <unistd.h> #include <sys/slogcodes.h> #include <sys/slog2.h> #include <time.h> #include <ctype.h> #include <limits.h> #include <signal.h> #include <pthread.h> #include <sys/asoundlib.h> #define WRITE_RETRY_TIMES 5 typedef struct { char tag[4]; int32_t length; } RiffTag; typedef struct { char Riff[4]; int32_t Size; char Wave[4]; } RiffHdr; typedef struct { int16_t FormatTag; int16_t Channels; int32_t SamplesPerSec; int32_t AvgBytesPerSec; int16_t BlockAlign; int16_t BitsPerSample; } FmtChunk; typedef struct { FILE *file1; struct timespec start_time; } WriterData; const char *kRiffId = "RIFF"; const char *kRifxId = "RIFX"; const char *kWaveId = "WAVE"; bool running = true; int n; int N=0; int verbose = 0; int print_timing = 0; int bsize; int use_writer_thread = 0; useconds_t frag_period_us; snd_mixer_group_t group; snd_mixer_t *mixer_handle = NULL; snd_pcm_t *pcm_handle = NULL; snd_pcm_channel_params_t pp; char *mSampleBfr1 = NULL; unsigned int mDataSize; bool mBigEndian = false; int nonblock = 0; int repeat = 0; long int data_position = 0; int stdin_raw = 0; WriterData wd; static slog2_buffer_t slog_handle; static slog2_buffer_set_config_t slog_config; static int FindTag (FILE * fp, const char *tag) { int retVal; RiffTag tagBfr = { "", 0 }; retVal = 0; // Keep reading until we find the tag or hit the EOF. while (fread ((unsigned char *) &tagBfr, sizeof (tagBfr), 1, fp)) { if( mBigEndian ) { tagBfr.length = ENDIAN_BE32 (tagBfr.length); } else { tagBfr.length = ENDIAN_LE32 (tagBfr.length); } // If this is our tag, set the length and break. if (strncmp (tag, tagBfr.tag, sizeof (tagBfr.tag)) == 0) { retVal = tagBfr.length; break; } // Skip ahead the specified number of bytes in the stream fseek (fp, tagBfr.length, SEEK_CUR); } // Return the result of our operation return (retVal); } static int CheckHdr (FILE * fp) { RiffHdr riffHdr = { "", 0 }; if (fread ((unsigned char *) &riffHdr, sizeof (RiffHdr), 1, fp) == 0) return -1; if (!strncmp (riffHdr.Riff, kRiffId, strlen (kRiffId))) mBigEndian = false; else if (!strncmp (riffHdr.Riff, kRifxId, strlen (kRifxId))) mBigEndian = true; else return -1; if (strncmp (riffHdr.Wave, kWaveId, strlen (kWaveId))) return -1; return 0; } static int dev_raw (int fd) { struct termios termios_p; if (tcgetattr (fd, &termios_p)) return (-1); termios_p.c_cc[VMIN] = 1; termios_p.c_cc[VTIME] = 0; termios_p.c_lflag &= ~(ICANON | ECHO | ISIG); return (tcsetattr (fd, TCSANOW, &termios_p)); } static int dev_unraw (int fd) { struct termios termios_p; if (tcgetattr (fd, &termios_p)) return (-1); termios_p.c_lflag |= (ICANON | ECHO | ISIG); return (tcsetattr (fd, TCSAFLUSH, &termios_p)); } static void cleanup(void) { if (stdin_raw) dev_unraw (fileno (stdin)); if (mixer_handle) snd_mixer_close (mixer_handle); if (pcm_handle) snd_pcm_close (pcm_handle); if (wd.file1) fclose(wd.file1); if (mSampleBfr1) free(mSampleBfr1); } static void cleanup_and_exit(int exit_code) { cleanup(); exit(exit_code); } static void handle_keypress() { int c; int rtn; c = getc (stdin); if (c == EOF) { running = false; return; } /* Handle non-mixer keypresses */ switch (c) { case 'p': snd_pcm_playback_pause( pcm_handle ); return; case 'r': snd_pcm_playback_resume( pcm_handle ); return; case 'i': { char buf[100] = {0}; snd_pcm_channel_status_t status; memset (&status, 0, sizeof (status)); status.channel = SND_PCM_CHANNEL_PLAYBACK; if ((rtn = snd_pcm_plugin_status (pcm_handle, &status)) < 0) { fprintf (stderr, "plugin_status: playback channel status error\n"); return; } /* Display Subchn state */ printf("\nSubchn State = "); switch (status.status) { case SND_PCM_STATUS_NOTREADY: printf("NOTREADY\n"); break; case SND_PCM_STATUS_READY: printf("READY\n"); break; case SND_PCM_STATUS_PREPARED: printf("PREPARED\n"); break; case SND_PCM_STATUS_RUNNING: printf("RUNNING\n"); break; case SND_PCM_STATUS_PAUSED: printf("PAUSED\n"); break; case SND_PCM_STATUS_SUSPENDED: printf("SUSPENDED\n"); break; case SND_PCM_STATUS_UNDERRUN: printf("UNDERRUN\n"); break; case SND_PCM_STATUS_OVERRUN: printf("OVERRUN\n"); break; case SND_PCM_STATUS_CHANGE: printf("CHANGE\n"); break; case SND_PCM_STATUS_ERROR: printf("ERROR\n"); break; default: printf("UNKNOWN\n"); break; } /* Display Ducking State */ if (status.ducking_state & SND_PCM_DUCKING_STATE_FORCED_ACTIVE) { strcat(buf, "FORCED_ACTIVE"); if (status.ducking_state & SND_PCM_DUCKING_STATE_ACTIVE) strcat(buf, "|ACTIVE"); } else if (status.ducking_state & SND_PCM_DUCKING_STATE_ACTIVE) strcat(buf, "ACTIVE"); else strcat(buf, "INACTIVE"); if (status.ducking_state & SND_PCM_DUCKING_STATE_HARD_SUSPENDED) strcat(buf, "|HARD_SUSPENDED"); if (status.ducking_state & SND_PCM_DUCKING_STATE_SOFT_SUSPENDED) strcat(buf, "|SOFT_SUSPENDED"); if (status.ducking_state & SND_PCM_DUCKING_STATE_PAUSED) strcat(buf, "|AUTOPAUSED"); printf("Ducking State = %s\n", buf); /* Display buffer statistics */ printf("scount = %d, used = %d, free = %d, underrun = %d, overrun = %d\n", status.scount, status.count, status.free, status.underrun, status.overrun); } return; case 't': if (repeat) { repeat = 0; printf("Stop to repeat playing\n"); return; } else break; // Exit the program case 3: // Ctrl-C case 27: // Escape running = false; return; default: break; } /* Handle mixer keypresses */ if (mixer_handle == NULL) return; if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0) { fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn)); return; } /* Adjust the volume by 10 or by the remaining volume steps until the min or max (whichever is smaller) */ switch (c) { case 'q': if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT) group.volume.names.front_left += min(group.max - group.volume.names.front_left, 10); if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT) group.volume.names.rear_left += min(group.max - group.volume.names.rear_left, 10); if (group.channels & SND_MIXER_CHN_MASK_WOOFER) group.volume.names.woofer += min(group.max - group.volume.names.woofer, 10); break; case 'a': if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT) group.volume.names.front_left -= min(group.volume.names.front_left - group.min, 10); if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT) group.volume.names.rear_left -= min(group.volume.names.rear_left - group.min, 10); if (group.channels & SND_MIXER_CHN_MASK_WOOFER) group.volume.names.woofer -= min(group.volume.names.woofer - group.min, 10); break; case 'w': if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT) group.volume.names.front_left += min(group.max - group.volume.names.front_left, 10); if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT) group.volume.names.rear_left += min(group.max - group.volume.names.rear_left, 10); if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER) group.volume.names.front_center += min(group.max - group.volume.names.front_center, 10); if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT) group.volume.names.front_right += min(group.max - group.volume.names.front_right, 10); if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT) group.volume.names.rear_right += min(group.max - group.volume.names.rear_right, 10); if (group.channels & SND_MIXER_CHN_MASK_WOOFER) group.volume.names.woofer += min(group.max - group.volume.names.woofer, 10); break; case 's': if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT) group.volume.names.front_left -= min(group.volume.names.front_left - group.min, 10); if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT) group.volume.names.rear_left -= min(group.volume.names.rear_left - group.min, 10); if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER) group.volume.names.front_center -= min(group.volume.names.front_center - group.min, 10); if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT) group.volume.names.front_right -= min(group.volume.names.front_right - group.min, 10); if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT) group.volume.names.rear_right -= min(group.volume.names.rear_right - group.min, 10); if (group.channels & SND_MIXER_CHN_MASK_WOOFER) group.volume.names.woofer -= min(group.volume.names.woofer - group.min, 10); break; case 'e': if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT) group.volume.names.front_right += min(group.max - group.volume.names.front_right, 10); if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT) group.volume.names.rear_right += min(group.max - group.volume.names.rear_right, 10); if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER) group.volume.names.front_center += min(group.max - group.volume.names.front_center, 10); break; case 'd': if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT) group.volume.names.front_right -= min(group.volume.names.front_right - group.min, 10); if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT) group.volume.names.rear_right -= min(group.volume.names.rear_right - group.min, 10); if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER) group.volume.names.front_center -= min(group.volume.names.front_center - group.min, 10); break; } if ((rtn = snd_mixer_group_write (mixer_handle, &group)) < 0) fprintf (stderr, "snd_mixer_group_write failed: %s\n", snd_strerror (rtn)); if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT) { printf ("Volume Now at %d:%d \n", (group.max - group.min) ? 100 * (group.volume.names.front_left - group.min) / (group.max - group.min) : 0, (group.max - group.min) ? 100 * (group.volume.names.front_right - group.min) / (group.max - group.min): 0); } else if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT) { printf ("Volume Now at %d:%d \n", (group.max - group.min) ? 100 * (group.volume.names.rear_left - group.min) / (group.max - group.min) : 0, (group.max - group.min) ? 100 * (group.volume.names.rear_right - group.min) / (group.max - group.min): 0); } else if (group.channels & SND_MIXER_CHN_MASK_WOOFER) { printf ("Volume Now at %d:%d \n", (group.max - group.min) ? 100 * (group.volume.names.woofer - group.min) / (group.max - group.min) : 0, (group.max - group.min) ? 100 * (group.volume.names.front_center - group.min) / (group.max - group.min): 0); } else { printf ("Volume Now at %d:%d \n", (group.max - group.min) ? 100 * (group.volume.names.front_left - group.min) / (group.max - group.min) : 0, (group.max - group.min) ? 100 * (group.volume.names.front_right - group.min) / (group.max - group.min): 0); } } static void handle_mixer() { fd_set rfds; int mixer_fd = snd_mixer_file_descriptor (mixer_handle); FD_ZERO(&rfds); FD_SET ( mixer_fd, &rfds); if (select (mixer_fd + 1, &rfds, NULL, NULL, NULL) == -1) perror ("select"); snd_mixer_callbacks_t callbacks = { 0, 0, 0, 0 }; snd_mixer_read (mixer_handle, &callbacks); } static void display_status_event (snd_pcm_event_t *event) { char flag_buf[100] = {0}; if (event->data.audiomgmt_status.flags & SND_PCM_STATUS_EVENT_HARD_SUSPEND) { if (event->data.audiomgmt_status.flags & ~(SND_PCM_STATUS_EVENT_HARD_SUSPEND|(SND_PCM_STATUS_EVENT_HARD_SUSPEND - 1U))) strcat(flag_buf, "HARD_SUSPEND|"); else strcat(flag_buf, "HARD_SUSPEND"); } if (event->data.audiomgmt_status.flags & SND_PCM_STATUS_EVENT_SOFT_SUSPEND) { if (event->data.audiomgmt_status.flags & ~(SND_PCM_STATUS_EVENT_SOFT_SUSPEND|(SND_PCM_STATUS_EVENT_SOFT_SUSPEND-1U))) strcat(flag_buf, "SOFT_SUSPEND|"); else strcat(flag_buf, "SOFT_SUSPEND"); } if (event->data.audiomgmt_status.flags & SND_PCM_STATUS_EVENT_AUTOPAUSE) { if (event->data.audiomgmt_status.flags & ~(SND_PCM_STATUS_EVENT_AUTOPAUSE | (SND_PCM_STATUS_EVENT_AUTOPAUSE - 1U))) strcat(flag_buf, "AUTOPAUSE|"); else strcat(flag_buf, "AUTOPAUSE"); } if (event->data.audiomgmt_status.flags == 0) { strcat(flag_buf, "None"); } switch (event->data.audiomgmt_status.new_status) { case SND_PCM_STATUS_SUSPENDED: printf("Audio Management Status event received - SUSPENDED\n"); printf("\tFlags = (0x%x) %s\n", event->data.audiomgmt_status.flags, flag_buf); break; case SND_PCM_STATUS_RUNNING: printf("Audio Management Status event received - RUNNING\n"); printf("\tFlags = (0x%x) %s\n", event->data.audiomgmt_status.flags, flag_buf); break; case SND_PCM_STATUS_PAUSED: printf("Audio Management Status event received - PAUSED\n"); printf("\tFlags = (0x%x) %s\n", event->data.audiomgmt_status.flags, flag_buf); break; default: break; } } static void display_mute_event (snd_pcm_event_t *event) { char flag_buf[100] = {0}; if (event->data.audiomgmt_mute.mute == 1) { if (event->data.audiomgmt_mute.reason & SND_PCM_MUTE_EVENT_HIGHER_PRIORITY) { if (event->data.audiomgmt_mute.reason & ~(SND_PCM_MUTE_EVENT_HIGHER_PRIORITY)) strcat(flag_buf, "MUTE_BY_HIGHER|"); else strcat(flag_buf, "MUTE_BY_HIGHER"); } if (event->data.audiomgmt_mute.reason & SND_PCM_MUTE_EVENT_SAME_PRIORITY) strcat(flag_buf, "MUTE_BY_SAME"); printf("Audio Management Mute event received - Muted by %s\n", flag_buf); } else { printf("Audio Management Mute event received - Un-Muted\n"); } } static void handle_pcm_events() { fd_set ofds; snd_pcm_event_t event; int rtn = EOK; int pcm_fd = snd_pcm_file_descriptor (pcm_handle, SND_PCM_CHANNEL_PLAYBACK); FD_ZERO(&ofds); FD_SET ( pcm_fd, &ofds); if (select (pcm_fd + 1, NULL, NULL, &ofds, NULL) == -1) perror ("select"); if ((rtn = snd_pcm_channel_read_event (pcm_handle, SND_PCM_CHANNEL_PLAYBACK, &event)) == EOK) { switch (event.type) { case SND_PCM_EVENT_AUDIOMGMT_STATUS: display_status_event(&event); break; case SND_PCM_EVENT_AUDIOMGMT_MUTE: display_mute_event(&event); break; case SND_PCM_EVENT_OUTPUTCLASS: printf("Output class event received - output class changed from %d to %d\n", event.data.outputclass.old_output_class, event.data.outputclass.new_output_class); break; case SND_PCM_EVENT_UNDERRUN: printf("Underrun event received\n"); break; default: printf("Unknown PCM event type - %d\n", event.type); break; } } else printf("snd_pcm_channel_read_event() failed with %d\n", rtn); } static void write_audio_data(WriterData *wd) { struct timespec current_time; snd_pcm_channel_status_t status; int written = 0; int retries = 0; int remainder = mDataSize - N; if (repeat && (remainder < bsize)) { memset(mSampleBfr1, 0x0, bsize); } n = fread (mSampleBfr1, 1, min(remainder, bsize), wd->file1); if (!repeat && n <= 0) return; if (repeat) { if (n < 0) return; if (n < bsize) { /* We play the file in a loop, so if the last chunk is less then the fragsize * then we zero pad the remainder of the fragment (set above memset) and * report n as the full fragsize */ n = bsize; } } written = snd_pcm_plugin_write (pcm_handle, mSampleBfr1, n); if (verbose) printf ("bytes written = %d \n", written); if( print_timing ) { clock_gettime( CLOCK_REALTIME, ¤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; int num_buffered_frags = 0; char *sub_opts, *sub_opts_copy, *value; char *dev_opts[] = { #define CHN1 0 "1", #define CHN2 1 "2", #define CHN3 2 "3", #define CHN4 3 "4", #define CHN5 4 "5", #define CHN6 5 "6", #define CHN7 6 "7", #define CHN8 7 "8", NULL }; char name[_POSIX_PATH_MAX] = { 0 }; float vol_percent = -1; float volume; char mixer_name[32]; int mix_name_enable = -1; int protected_content = 0; int enable_protection = 0; int require_protection = 0; void *retval; pthread_t writer_thread; pthread_t mixer_thread; pthread_t keypress_thread; pthread_t pcm_event_thread; char type[sizeof(pp.audio_type_name)] = {0}; int rate_method = 0; int use_mmap = 0; int info = 0; struct stat fileStat; int pcm_fd, mixer_fd = 0; snd_pcm_filter_t pevent; // Start logging memset(&slog_config, 0, sizeof(slog_config)); slog_config.num_buffers = 1; slog_config.verbosity_level = SLOG2_INFO; slog_config.buffer_set_name = "wave"; slog_config.buffer_config[0].buffer_name="wave"; slog_config.buffer_config[0].num_pages = 5; slog2_register(&slog_config, &slog_handle, 0); slog2_set_verbosity(slog_handle, SLOG2_INFO); slog2_set_default_buffer(slog_handle); while ((c = getopt (argc, argv, "ia:ef:vc:n:b:p:m:rstwo:xR:yd")) != EOF) { switch (c) { case 'a': if (strchr (optarg, ':')) { card = atoi (optarg); dev = atoi (strchr (optarg, ':') + 1); } else if (isalpha (optarg[0]) || optarg[0] == '/') strcpy (name, optarg); else dev = atoi (optarg); if (name[0] != '\0') printf ("Using device %s\n", name); else printf ("Using card %d device %d \n", card, dev); break; case 'f': fragsize = atoi (optarg); break; case 'i': info = 1; break; case 'v': verbose = 1; break; case 'c': sub_opts = sub_opts_copy = strdup (optarg); if (sub_opts == NULL) { perror ("Cannot allocate sub_opts"); return (EXIT_FAILURE); } while (*sub_opts != '\0') { int channel = getsubopt (&sub_opts, dev_opts, &value); if( (channel >= 0) && (channel < MAX_VOICES) && value ) { voice_mask[channel] = strtoul (value, NULL, 0); } else { fprintf (stderr, "Invalid channel map specified\n"); return (EXIT_FAILURE); } } free(sub_opts_copy); voice_override = 1; break; case 'n': num_frags = atoi (optarg) - 1; break; case 'b': num_buffered_frags = atoi (optarg); break; case 'p': vol_percent = atof (optarg); break; case 'm': strlcpy (mixer_name, optarg, sizeof(mixer_name)); mix_name_enable = 1; break; case 's': protected_content = 1; break; case 'e': enable_protection = 1; break; case 'r': require_protection = 1; break; case 't': print_timing = 1; break; case 'w': use_writer_thread = 1; break; case 'o': strlcpy (type, optarg, sizeof(type)); type[sizeof(pp.audio_type_name) - 1] = '\0'; break; case 'x': use_mmap = 1; break; case 'R': rate_method = atoi(optarg); if (rate_method < 0 || rate_method > 3) { rate_method = 0; printf("Invalid rate method, using method 0\n"); } break; case 'y': mode |= SND_PCM_OPEN_NONBLOCK; nonblock = 1; break; case 'd': repeat = 1; printf("Repeat playing\n"); break; default: fprintf(stderr, "Invalid option -%c\n", c); return (EXIT_FAILURE); } } setvbuf (stdin, NULL, _IONBF, 0); if (info) { dump_info( card, dev, name, SND_PCM_CHANNEL_PLAYBACK); dump_info( card, dev, name, SND_PCM_CHANNEL_CAPTURE); return (EXIT_SUCCESS); } if (optind >= argc) { fprintf(stderr, "no file specified\n"); return (EXIT_FAILURE); } if (name[0] != '\0') { snd_pcm_info_t info; if ((rtn = snd_pcm_open_name (&pcm_handle, name, mode)) < 0) { fprintf(stderr, "open_name failed - %s\n", snd_strerror(rtn)); return (EXIT_FAILURE); } rtn = snd_pcm_info (pcm_handle, &info); card = info.card; } else { if (card == -1) { if ((rtn = snd_pcm_open_preferred (&pcm_handle, &card, &dev, mode)) < 0) { fprintf(stderr, "device open failed - %s\n", snd_strerror(rtn)); return (EXIT_FAILURE); } } else { if ((rtn = snd_pcm_open (&pcm_handle, card, dev, mode)) < 0) { fprintf(stderr, "device open failed - %s\n", snd_strerror(rtn)); return (EXIT_FAILURE); } } } if ((wd.file1 = fopen (argv[optind], "r")) == 0) { perror ("file open"); cleanup_and_exit(EXIT_FAILURE); } if (stat (argv[optind], &fileStat) == -1) { perror ("file stat"); cleanup_and_exit(EXIT_FAILURE); } if (CheckHdr (wd.file1) == -1) { fprintf (stderr, "invalid wav header\n"); cleanup_and_exit(EXIT_FAILURE); } fmtLength = FindTag (wd.file1, "fmt "); if ((fmtLength == 0) || (fmtLength < sizeof(fmt)) || (fread (&fmt, sizeof(fmt), 1, wd.file1) == 0)) { fprintf (stderr, "invalid wav file\n"); cleanup_and_exit(EXIT_FAILURE); } /* Some files have extra data in fmt field, so skip past it */ if (fmtLength > sizeof(fmt)) fseek (wd.file1, fmtLength - sizeof(fmt), SEEK_CUR); mDataSize = FindTag (wd.file1, "data"); data_position = ftell(wd.file1); if ((mDataSize == 0) || (mDataSize > ((long)fileStat.st_size - data_position))) { fprintf (stderr, "wave data size conflicts with file size mDataSize=%d file size=%zd, data_pos=%ld\n", mDataSize, fileStat.st_size, data_position); cleanup_and_exit(EXIT_FAILURE); } if (mBigEndian) { mSampleRate = ENDIAN_BE32 (fmt.SamplesPerSec); mSampleChannels = ENDIAN_BE16 (fmt.Channels); mSampleBits = ENDIAN_BE16 (fmt.BitsPerSample); /* BlockAlign = frame size, i.e. sample size including padding bits, times number of channels */ mSampleBytes = ENDIAN_BE16 (fmt.BlockAlign) / mSampleChannels; fmt.FormatTag = ENDIAN_BE16 (fmt.FormatTag); } else { mSampleRate = ENDIAN_LE32 (fmt.SamplesPerSec); mSampleChannels = ENDIAN_LE16 (fmt.Channels); mSampleBits = ENDIAN_LE16 (fmt.BitsPerSample); /* BlockAlign = frame size, i.e. sample size including padding bits, times number of channels */ mSampleBytes = ENDIAN_LE16 (fmt.BlockAlign) / mSampleChannels; fmt.FormatTag = ENDIAN_LE16 (fmt.FormatTag); } printf("SampleRate = %d, Channels = %d, SampleBits = %d, SampleBytes = %d\n", mSampleRate, mSampleChannels, mSampleBits, mSampleBytes); printf("Playback Duration = %fs\n", (float)mDataSize / (mSampleRate * mSampleChannels * mSampleBytes)); /* Enable PCM events */ pevent.enable = SND_PCM_EVENT_MASK(SND_PCM_EVENT_AUDIOMGMT_STATUS) | SND_PCM_EVENT_MASK(SND_PCM_EVENT_AUDIOMGMT_MUTE) | SND_PCM_EVENT_MASK(SND_PCM_EVENT_OUTPUTCLASS) | SND_PCM_EVENT_MASK(SND_PCM_EVENT_UNDERRUN); snd_pcm_set_filter(pcm_handle, SND_PCM_CHANNEL_PLAYBACK, &pevent); if (use_mmap) { snd_pcm_plugin_set_enable (pcm_handle, PLUGIN_MMAP); } memset (&pi, 0, sizeof (pi)); pi.channel = SND_PCM_CHANNEL_PLAYBACK; if ((rtn = snd_pcm_plugin_info (pcm_handle, &pi)) < 0) { fprintf (stderr, "snd_pcm_plugin_info failed: %s\n", snd_strerror (rtn)); cleanup_and_exit(EXIT_FAILURE); } memset (&pp, 0, sizeof (pp)); pp.mode = SND_PCM_MODE_BLOCK | (protected_content ? SND_PCM_MODE_FLAG_PROTECTED_CONTENT : 0) | (enable_protection ? SND_PCM_MODE_FLAG_ENABLE_PROTECTION : 0) | (require_protection ? SND_PCM_MODE_FLAG_REQUIRE_PROTECTION : 0); pp.channel = SND_PCM_CHANNEL_PLAYBACK; pp.start_mode = SND_PCM_START_FULL; pp.stop_mode = SND_PCM_STOP_STOP; pp.buf.block.frag_size = pi.max_fragment_size; if (fragsize != -1) { pp.buf.block.frag_size = fragsize; } pp.buf.block.frags_max = num_frags; pp.buf.block.frags_buffered_max = num_buffered_frags; pp.buf.block.frags_min = 1; pp.format.interleave = 1; pp.format.rate = mSampleRate; pp.format.voices = mSampleChannels; if (fmt.FormatTag == 6) { pp.format.format = SND_PCM_SFMT_A_LAW; } else if (fmt.FormatTag == 7) { pp.format.format = SND_PCM_SFMT_MU_LAW; } else if (mSampleBits == 8) { pp.format.format = SND_PCM_SFMT_U8; } else if (mSampleBits == 16) { if (mBigEndian) { pp.format.format = SND_PCM_SFMT_S16_BE; } else { pp.format.format = SND_PCM_SFMT_S16_LE; } } else if (mSampleBits == 24 && mSampleBytes == 4) { if (mBigEndian) { pp.format.format = SND_PCM_SFMT_S24_4_BE; } else { pp.format.format = SND_PCM_SFMT_S24_4_LE; } } else if (mSampleBits == 24 && mSampleBytes == 3) { if (mBigEndian) { pp.format.format = SND_PCM_SFMT_S24_BE; } else { pp.format.format = SND_PCM_SFMT_S24_LE; } } else if (mSampleBits == 32) { if (mBigEndian) { pp.format.format = SND_PCM_SFMT_S32_BE; } else { pp.format.format = SND_PCM_SFMT_S32_LE; } } else { fprintf(stderr, "Unsupported number of bits per sample %d, sample size %d\n", mSampleBits, mSampleBytes); cleanup_and_exit(EXIT_FAILURE); } strlcpy (pp.audio_type_name, type, sizeof(pp.audio_type_name)); if (mix_name_enable == 1) { strlcpy (pp.sw_mixer_subchn_name, mixer_name, sizeof(pp.sw_mixer_subchn_name)); } else { strcpy (pp.sw_mixer_subchn_name, "Wave playback channel"); } if ((rtn = snd_pcm_plugin_set_src_method(pcm_handle, rate_method)) != rate_method) { fprintf(stderr, "Failed to apply rate_method %d, using %d\n", rate_method, rtn); } if ((rtn = snd_pcm_plugin_params (pcm_handle, &pp)) < 0) { fprintf (stderr, "snd_pcm_plugin_params failed: %s, why_failed = %d\n", snd_strerror (rtn), pp.why_failed); cleanup_and_exit(EXIT_FAILURE); } if (voice_override) { unsigned int i; snd_pcm_plugin_get_voice_conversion (pcm_handle, SND_PCM_CHANNEL_PLAYBACK, &voice_conversion); for (i = 0; i < MAX_VOICES; i++) { voice_conversion.matrix[i] = voice_mask[i]; } snd_pcm_plugin_set_voice_conversion (pcm_handle, SND_PCM_CHANNEL_PLAYBACK, &voice_conversion); } memset (&setup, 0, sizeof (setup)); memset (&group, 0, sizeof (group)); setup.channel = SND_PCM_CHANNEL_PLAYBACK; setup.mixer_gid = &group.gid; if ((rtn = snd_pcm_plugin_setup (pcm_handle, &setup)) < 0) { fprintf (stderr, "snd_pcm_plugin_setup failed: %s\n", snd_strerror (rtn)); cleanup_and_exit(EXIT_FAILURE); } printf ("Format %s \n", snd_pcm_get_format_name (setup.format.format)); printf ("Frag Size %d \n", setup.buf.block.frag_size); printf ("Total Frags %d \n", setup.buf.block.frags); printf ("Rate %d \n", setup.format.rate); printf ("Voices %d \n", setup.format.voices); bsize = setup.buf.block.frag_size; frag_period_us = (( (int64_t)bsize * 1000000 ) / (mSampleBytes * mSampleChannels * mSampleRate)); printf("Frag Period is %d us\n", frag_period_us); if (group.gid.name[0] == 0) { printf ("Mixer Pcm Group [%s] Not Set \n", group.gid.name); } else { printf ("Mixer Pcm Group [%s]\n", group.gid.name); if ((rtn = snd_mixer_open (&mixer_handle, card, setup.mixer_device)) < 0) { fprintf (stderr, "snd_mixer_open failed: %s\n", snd_strerror (rtn)); cleanup_and_exit(EXIT_FAILURE); } } if (tcgetpgrp (0) == getpid ()) { stdin_raw = 1; dev_raw (fileno (stdin)); } if( print_timing ) { clock_gettime( CLOCK_REALTIME, &wd.start_time ); } mSampleBfr1 = malloc (bsize); if ( mSampleBfr1 == NULL ) { perror("Failed to allocate pcm buffer"); cleanup_and_exit(EXIT_FAILURE); } FD_ZERO (&rfds); FD_ZERO (&wfds); FD_ZERO (&ofds); n = 1; if (mixer_handle) { if (vol_percent >=0) { if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0) fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn)); volume = (float)(group.max - group.min) * ( vol_percent / 100); if (group.channels & SND_MIXER_CHN_MASK_FRONT_LEFT) group.volume.names.front_left = (int)volume; if (group.channels & SND_MIXER_CHN_MASK_REAR_LEFT) group.volume.names.rear_left = (int)volume; if (group.channels & SND_MIXER_CHN_MASK_FRONT_CENTER) group.volume.names.front_center = (int)volume; if (group.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT) group.volume.names.front_right = (int)volume; if (group.channels & SND_MIXER_CHN_MASK_REAR_RIGHT) group.volume.names.rear_right = (int)volume; if (group.channels & SND_MIXER_CHN_MASK_WOOFER) group.volume.names.woofer = (int)volume; if ((rtn = snd_mixer_group_write (mixer_handle, &group)) < 0) fprintf (stderr, "snd_mixer_group_write failed: %s\n", snd_strerror (rtn)); vol_percent = -1; } } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); if ((rtn = snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0) { fprintf (stderr, "snd_pcm_plugin_prepare failed: %s\n", snd_strerror (rtn)); cleanup_and_exit(EXIT_FAILURE); } if( use_writer_thread ) { pthread_create( &writer_thread, NULL, writer_thread_handler, &wd ); pthread_create( &pcm_event_thread, NULL, generic_thread_handler, handle_pcm_events ); pthread_create( &keypress_thread, NULL, generic_thread_handler, handle_keypress ); if (mixer_handle) pthread_create( &mixer_thread, NULL, generic_thread_handler, handle_mixer ); // First wait for feeder to complete. Any other thread will cause it to stop. // Then just kill the other threads pthread_join(writer_thread, &retval); pthread_cancel(keypress_thread); pthread_cancel(pcm_event_thread); if (mixer_handle) pthread_cancel(mixer_thread); } else { while (running && N < mDataSize && n > 0) { FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&ofds); if (stdin_raw) FD_SET (STDIN_FILENO, &rfds); if (mixer_handle) { mixer_fd = snd_mixer_file_descriptor (mixer_handle); FD_SET (mixer_fd, &rfds); } pcm_fd = snd_pcm_file_descriptor (pcm_handle, SND_PCM_CHANNEL_PLAYBACK); FD_SET (pcm_fd, &wfds); FD_SET (pcm_fd, &ofds); rtn = max (mixer_fd, pcm_fd); if (select (rtn + 1, &rfds, &wfds, &ofds, NULL) == -1) { perror ("select"); break; /* break loop to exit cleanly */ } if (FD_ISSET (STDIN_FILENO, &rfds)) { handle_keypress(); } if (mixer_handle && FD_ISSET (mixer_fd, &rfds)) { handle_mixer(); } if (FD_ISSET (pcm_fd, &wfds)) { write_audio_data(&wd); } if (FD_ISSET (pcm_fd, &ofds)) { snd_pcm_event_t event; if ((rtn = snd_pcm_channel_read_event (pcm_handle, SND_PCM_CHANNEL_PLAYBACK, &event)) == EOK) { switch (event.type) { case SND_PCM_EVENT_AUDIOMGMT_STATUS: display_status_event(&event); break; case SND_PCM_EVENT_AUDIOMGMT_MUTE: display_mute_event(&event); break; case SND_PCM_EVENT_OUTPUTCLASS: printf("Output class event received - output class changed from %d to %d\n", event.data.outputclass.old_output_class, event.data.outputclass.new_output_class); break; case SND_PCM_EVENT_UNDERRUN: printf("Underrun event received\n"); break; default: printf("Unknown PCM event type - %d\n", event.type); break; } } else printf("snd_pcm_channel_read_event() failed with %d\n", rtn); } } } printf("Exiting...\n"); if (running) snd_pcm_plugin_flush (pcm_handle, SND_PCM_CHANNEL_PLAYBACK); cleanup(); return(EXIT_SUCCESS); } #if defined(__QNXNTO__) && defined(__USESRCVERSION) #include <sys/srcversion.h> __SRCVERSION("$URL$ $Rev$") #endif