This is a sample application that captures (i.e., records) audio data.
For information about using this utility, see waverec 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 <limits.h>
#include <ctype.h>
#include <sys/asoundlib.h>
/* *INDENT-OFF* */
struct
{
char riff_id[4];
uint32_t wave_len;
struct
{
char fmt_id[8];
uint32_t fmt_len;
struct
{
uint16_t format_tag;
uint16_t voices;
uint32_t rate;
uint32_t char_per_sec;
uint16_t block_align;
uint16_t bits_per_sample;
}
fmt;
struct
{
char data_id[4];
uint32_t data_len;
}
data;
}
wave;
}
riff_hdr =
{
{'R', 'I', 'F', 'F' },
sizeof (riff_hdr.wave),
{
{'W', 'A', 'V', 'E', 'f', 'm', 't', ' ' },
sizeof (riff_hdr.wave.fmt),
{
1, 0, 0, 0, 0, 0
},
{
{'d', 'a', 't', 'a' },
0,
}
}
};
/* *INDENT-ON* */
static snd_mixer_t *mixer_handle = NULL;
static snd_pcm_t *pcm_handle = NULL;
static snd_pcm_channel_params_t pp;
static char *mSampleBfr1 = NULL;
static FILE *file1 = NULL;
static int stdin_raw = 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 (mSampleBfr1)
free(mSampleBfr1);
if (mixer_handle)
snd_mixer_close (mixer_handle);
if (file1)
fclose(file1);
if (pcm_handle)
snd_pcm_close (pcm_handle);
}
static void
cleanup_and_exit(int exit_code)
{
cleanup();
exit(exit_code);
}
//*****************************************************************************
/* *INDENT-OFF* */
#ifdef __USAGE
%C [Options] wavfile
Options:
-a[card#:]<dev#> the card & device number to record from
OR
-a[name] the symbolic name of the device to record from
-b <size> Sample size (8, 16, 24, 32)
-m record in mono (stereo default)
-n <voices> the number of voices to record (2 voices default, stereo)
-r <rate> record at rate (44100 default | 48000 44100 22050 11025)
-t <sec> seconds to record (5 seconds default)
-f <frag_size> requested fragment size
-v verbosity
-c <args>[,args ...] voice matrix configuration
-x use mmap interface
-i <0|1> Interleave samples (default: 1)
-R <value> SRC rate method
(0 = linear interpolation, 1 = 7-pt kaiser windowed, 2 = 20-pt remez)
-z<num_frags> requested number of fragments
-y Nonblocking mode
Note:
If both 'm' and 'n' are specified in commandline, the one specified later will be used
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* */
//*****************************************************************************
volatile int end = 0;
static void sig_handler( int sig_no )
{
end = 1;
return;
}
static const char *
why_failed ( int why_failed )
{
switch (why_failed)
{
case SND_PCM_PARAMS_BAD_MODE:
return ("Bad Mode Parameter");
case SND_PCM_PARAMS_BAD_START:
return ("Bad Start Parameter");
case SND_PCM_PARAMS_BAD_STOP:
return ("Bad Stop Parameter");
case SND_PCM_PARAMS_BAD_FORMAT:
return ("Bad Format Parameter");
case SND_PCM_PARAMS_BAD_RATE:
return ("Bad Rate Parameter");
case SND_PCM_PARAMS_BAD_VOICES:
return ("Bad Vocies Parameter");
case SND_PCM_PARAMS_NO_CHANNEL:
return ("No Channel Available");
default:
return ("Unknown Error");
}
return ("No Error");
}
int
main (int argc, char **argv)
{
int i, j;
int card = -1;
int dev = 0;
int ret;
unsigned int mSamples;
int mSampleRate;
int mSampleChannels;
int mSampleBits;
int mSampleBytes;
int mSampleTime;
int fragsize = -1;
int num_frags = -1;
int verbose = 0;
int mode = SND_PCM_OPEN_CAPTURE;
int rtn;
int mixer_fd = 0;
int pcm_fd;
snd_pcm_channel_info_t pi;
snd_mixer_group_t group;
snd_pcm_channel_setup_t setup;
int bsize, N = 0, c;
#define MAX_VOICES 8
uint32_t voice_mask[MAX_VOICES] = { 0 };
struct {
snd_pcm_chmap_t map;
unsigned int pos[32];
} map;
snd_pcm_voice_conversion_t voice_conversion;
int voice_override = 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 };
int interleave = 1;
fd_set rfds, ofds;
int use_mmap = 0;
int rate_method = 0;
snd_pcm_filter_t pevent;
mSampleRate = 44100;
mSampleChannels = 2;
mSampleBits = 16;
mSampleBytes = 2;
mSampleTime = 5;
while ((c = getopt (argc, argv, "b:a:f:mn:r:t:vc:xi:R:z:y")) != EOF)
{
switch (c)
{
case 'b':
mSampleBits = atoi (optarg);
if (mSampleBits != 8 && mSampleBits != 16 && mSampleBits != 24 && mSampleBits != 32)
{
fprintf(stderr, "Invalid sample size, must be one of 8, 16, 24, 32\n");
return (EXIT_FAILURE);
}
mSampleBytes = mSampleBits/8;
break;
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 /dev/snd/%s\n", name);
else
printf ("Using card %d device %d \n", card, dev);
break;
case 'f':
fragsize = atoi (optarg);
break;
case 'i':
interleave = atoi(optarg);
if (interleave <= 0)
interleave = 0;
else
interleave = 1;
break;
case 'm':
mSampleChannels = 1;
break;
case 'n':
mSampleChannels = atoi (optarg);
break;
case 'r':
mSampleRate = atoi (optarg);
break;
case 't':
mSampleTime = atoi (optarg);
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");
free(sub_opts_copy);
return (EXIT_FAILURE);
}
}
free(sub_opts_copy);
voice_override = 1;
break;
case 'x':
use_mmap = 1;
break;
case 'R':
rate_method = atoi(optarg);
if (rate_method < 0 || rate_method > 2)
{
rate_method = 0;
printf("Invalid rate method, using method 0\n");
}
break;
case 'z':
num_frags = atoi (optarg) - 1;
break;
case 'y':
mode |= SND_PCM_OPEN_NONBLOCK;
break;
default:
fprintf(stderr, "Invalid option -%c\n", c);
return (EXIT_FAILURE);
}
}
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, "snd_pcm_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, "snd_pcm_open_preferred failed - %s\n", snd_strerror(rtn));
return (EXIT_FAILURE);
}
}
else
{
if ((rtn = snd_pcm_open (&pcm_handle, card, dev, mode)) < 0)
{
fprintf(stderr, "snd_pcm_open failed - %s\n", snd_strerror(rtn));
return (EXIT_FAILURE);
}
}
}
if ((file1 = fopen (argv[optind], "w")) == 0)
{
perror("file open failed");
cleanup_and_exit(EXIT_FAILURE);
}
if( mSampleTime == 0 ) {
mSamples = 0xFFFFFFFF - sizeof(riff_hdr) + 8;
} else {
mSamples = mSampleRate * mSampleChannels * mSampleBytes * mSampleTime;
}
riff_hdr.wave.fmt.voices = ENDIAN_LE16 (mSampleChannels);
riff_hdr.wave.fmt.rate = ENDIAN_LE32 (mSampleRate);
riff_hdr.wave.fmt.char_per_sec =
ENDIAN_LE32 (mSampleRate * mSampleChannels * mSampleBytes);
riff_hdr.wave.fmt.block_align = ENDIAN_LE16 (mSampleChannels * mSampleBytes);
riff_hdr.wave.fmt.bits_per_sample = ENDIAN_LE16 (mSampleBits);
riff_hdr.wave.data.data_len = ENDIAN_LE32 (mSamples);
riff_hdr.wave_len = ENDIAN_LE32 (mSamples + sizeof (riff_hdr) - 8);
fwrite (&riff_hdr, 1, sizeof (riff_hdr), file1);
printf ("SampleRate = %d, Channels = %d, SampleBits = %d, SampleBytes = %d\n",
mSampleRate, mSampleChannels, mSampleBits, mSampleBytes);
/* Enable PCM events */
pevent.enable = (1<<SND_PCM_EVENT_OVERRUN);
snd_pcm_set_filter(pcm_handle, SND_PCM_CHANNEL_CAPTURE, &pevent);
if (use_mmap)
{
snd_pcm_plugin_set_enable (pcm_handle, PLUGIN_MMAP);
}
memset (&pi, 0, sizeof (pi));
pi.channel = SND_PCM_CHANNEL_CAPTURE;
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;
pp.channel = SND_PCM_CHANNEL_CAPTURE;
pp.start_mode = SND_PCM_START_DATA;
pp.stop_mode = SND_PCM_STOP_STOP;
pp.time = 1;
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 = interleave;
pp.format.rate = mSampleRate;
pp.format.voices = mSampleChannels;
switch (mSampleBits)
{
case 8:
pp.format.format = SND_PCM_SFMT_U8;
break;
case 16:
default:
pp.format.format = SND_PCM_SFMT_S16_LE;
break;
case 24:
pp.format.format = SND_PCM_SFMT_S24_LE;
break;
case 32:
pp.format.format = SND_PCM_SFMT_S32_LE;
break;
}
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 - %s\n", snd_strerror (rtn), why_failed(pp.why_failed));
cleanup_and_exit(EXIT_FAILURE);
}
if (voice_override)
{
snd_pcm_plugin_get_voice_conversion (pcm_handle, SND_PCM_CHANNEL_CAPTURE,
&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_CAPTURE,
&voice_conversion);
}
memset (&setup, 0, sizeof (setup));
memset (&group, 0, sizeof (group));
setup.channel = SND_PCM_CHANNEL_CAPTURE;
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);
bsize = setup.buf.block.frag_size;
map.map.channels = 32;
if((rtn = snd_pcm_query_channel_map(pcm_handle, &map.map)) == EOK) {
printf("Channel map:");
if((rtn = snd_pcm_plugin_get_voice_conversion (pcm_handle, SND_PCM_CHANNEL_CAPTURE, &voice_conversion)) != EOK) {
// The hardware map is the same as the voice map
for( i = 0; i < map.map.channels; i ++ ) {
printf(" (%d)", map.map.pos[i]);
}
printf("\n");
} else {
// Map hardware channels according to the voice map
for( i = 0; i < voice_conversion.app_voices; i ++ ) {
bool printed = false;
printf(" (");
for( j = 0; j < voice_conversion.hw_voices; j ++ ) {
if( voice_conversion.matrix[i] & (1<<j) ) {
if ( printed ) {
printf(" ");
} else {
printed = true;
}
printf("%d", map.map.pos[j]);
}
}
printf(")");
}
printf("\n");
}
}
if (group.gid.name[0] == 0)
{
printf ("Mixer Pcm Group [%s] Not Set \n", group.gid.name);
printf ("***>>>> Input Gain Controls Disabled <<<<*** \n");
}
else
{
printf ("Mixer Pcm Group [%s]\n", group.gid.name);
if ((rtn = snd_mixer_open (&mixer_handle, setup.mixer_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));
}
mSampleBfr1 = malloc (bsize);
if ( mSampleBfr1 == NULL ) {
perror("Failed to allocate pcm buffer");
cleanup_and_exit(EXIT_FAILURE);
}
if ((rtn = snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_CAPTURE)) < 0)
{
fprintf (stderr, "snd_pcm_plugin_prepare failed: %s\n", snd_strerror (rtn));
cleanup_and_exit(EXIT_FAILURE);
}
FD_ZERO (&rfds);
FD_ZERO(&ofds);
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
while (!end && N < mSamples)
{
/* If we are the foreground process group associated with STDIN then include
* STDIN in the fdset to handle key presses.
*/
if (stdin_raw)
FD_SET (STDIN_FILENO, &rfds);
if (mixer_handle)
{
/* Include the mixer_handle descriptor in the fdset to handle
* mixer events.
*/
mixer_fd = snd_mixer_file_descriptor (mixer_handle);
FD_SET (mixer_fd, &rfds);
}
rtn = pcm_fd = snd_pcm_file_descriptor (pcm_handle, SND_PCM_CHANNEL_CAPTURE);
FD_SET (pcm_fd, &rfds);
FD_SET (pcm_fd, &ofds);
if (mixer_handle)
rtn = max (mixer_fd, pcm_fd);
if (select (rtn + 1, &rfds, NULL, &ofds, NULL) == -1)
{
perror("select");
break; /* break loop to exit cleanly */
}
if (FD_ISSET (STDIN_FILENO, &rfds))
{
c = getc (stdin);
if (c != EOF)
{
/* Only handle volume key presses if there is a mixer group */
if (group.gid.name[0] != 0)
{
if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0)
fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));
switch (c)
{
case 'q':
group.volume.names.front_left += 1;
break;
case 'a':
group.volume.names.front_left -= 1;
break;
case 'w':
group.volume.names.front_left += 1;
group.volume.names.front_right += 1;
break;
case 's':
group.volume.names.front_left -= 1;
group.volume.names.front_right -= 1;
break;
case 'e':
group.volume.names.front_right += 1;
break;
case 'd':
group.volume.names.front_right -= 1;
break;
default:
break;
}
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.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 ((rtn = snd_mixer_group_write (mixer_handle, &group)) < 0)
fprintf (stderr, "snd_mixer_group_write failed: %s\n", snd_strerror (rtn));
if (group.max==group.min)
printf ("Volume Now at %d:%d\n", group.max, group.max);
else
printf ("Volume Now at %d:%d \n",
100 * (group.volume.names.front_left - group.min) / (group.max - group.min),
100 * (group.volume.names.front_right - group.min) / (group.max - group.min));
}
switch (c)
{
case 'o':
sleep(5);
break;
case 'f':
if( (ret = snd_pcm_plugin_flush( pcm_handle, SND_PCM_CHANNEL_CAPTURE )) == 0 ) {
printf("Flushing\n");
} else {
fprintf(stderr, "Flush failed: %d\n", ret);
}
break;
case 'g':
if( (ret = snd_pcm_plugin_prepare( pcm_handle, SND_PCM_CHANNEL_CAPTURE )) == 0 ) {
printf("Preparing\n");
} else {
fprintf(stderr, "Preparing failed: %d\n", ret);
}
break;
case 'p':
if( (ret = snd_pcm_capture_pause( pcm_handle )) == 0 ) {
printf("Pausing\n");
} else {
fprintf(stderr, "Pause failed: %d\n", ret);
}
break;
case 'r':
if( (ret = snd_pcm_capture_resume( pcm_handle )) == 0 ) {
printf("Resuming\n");
} else {
fprintf(stderr, "Resume failed: %d\n", ret);
}
break;
case 3: //Ctrl-C
case 27: // Escape
end = 1;
break;
case 'z':
printf("delaying 500ms\n");
delay(500);
break;
default:
break;
}
}
else {
cleanup_and_exit(EXIT_SUCCESS);
}
}
if (mixer_handle && FD_ISSET (mixer_fd, &rfds))
{
snd_mixer_callbacks_t callbacks = {
0, 0, 0, 0
};
snd_mixer_read (mixer_handle, &callbacks);
}
if (FD_ISSET (pcm_fd, &rfds))
{
snd_pcm_channel_status_t status;
int read = 0;
read = snd_pcm_plugin_read (pcm_handle, mSampleBfr1, bsize);
if (verbose)
printf ("bytes read = %d, bsize = %d \n", read, bsize);
if (read < bsize)
{
memset (&status, 0, sizeof (status));
status.channel = SND_PCM_CHANNEL_CAPTURE;
if (snd_pcm_plugin_status (pcm_handle, &status) < 0)
{
fprintf (stderr, "Capture channel status error\n");
cleanup_and_exit(EXIT_FAILURE);
}
if (status.status == SND_PCM_STATUS_CHANGE ||
status.status == SND_PCM_STATUS_READY ||
status.status == SND_PCM_STATUS_OVERRUN) {
if (status.status == SND_PCM_STATUS_CHANGE) {
fprintf(stderr, "change: capture channel capability change\n");
if (snd_pcm_plugin_params (pcm_handle, &pp) < 0)
{
fprintf (stderr, "Capture channel snd_pcm_plugin_params error\n");
cleanup_and_exit(EXIT_FAILURE);
}
}
if (status.status == SND_PCM_STATUS_OVERRUN) {
fprintf(stderr, "overrun: capture channel\n");
}
if (snd_pcm_plugin_prepare (pcm_handle, SND_PCM_CHANNEL_CAPTURE) < 0)
{
fprintf (stderr, "Capture channel prepare error\n");
cleanup_and_exit(EXIT_FAILURE);
}
}
else if (status.status == SND_PCM_STATUS_ERROR)
{
fprintf(stderr, "error: capture channel failure\n");
cleanup_and_exit(EXIT_FAILURE);
}
else if (status.status == SND_PCM_STATUS_PREEMPTED)
{
fprintf(stderr, "error: capture channel preempted\n");
cleanup_and_exit(EXIT_FAILURE);
}
} else {
fwrite (mSampleBfr1, 1, read, file1);
N += read;
}
}
if (FD_ISSET (pcm_fd, &ofds))
{
snd_pcm_event_t event;
if ((rtn = snd_pcm_channel_read_event (pcm_handle, SND_PCM_CHANNEL_CAPTURE, &event)) == EOK)
{
switch (event.type)
{
case SND_PCM_EVENT_OVERRUN:
printf("Overrun event received\n");
break;
default:
printf("Unknown PCM event type for capture - %d\n", event.type);
break;
}
}
else
printf("snd_pcm_channel_read_event() failed with %d\n", rtn);
}
}
printf("Exiting...\n");
/* Update wave header with actual length of audio captured */
riff_hdr.wave.data.data_len = ENDIAN_LE32 (N);
riff_hdr.wave_len = ENDIAN_LE32 (N + sizeof (riff_hdr) - 8);
fseek(file1, 0, SEEK_SET);
fwrite (&riff_hdr, 1, sizeof (riff_hdr), file1);
snd_pcm_plugin_flush (pcm_handle, SND_PCM_CHANNEL_CAPTURE);
cleanup();
return(EXIT_SUCCESS);
}
#if defined(__QNXNTO__) && defined(__USESRCVERSION)
#include <sys/srcversion.h>
__SRCVERSION("$URL$ $Rev$")
#endif