From 8eb71e693530726068addf6b8088aea0fd340f2a Mon Sep 17 00:00:00 2001 From: Gary Scavone Date: Wed, 9 Oct 2013 23:42:21 +0200 Subject: [PATCH] Version 2.0.1 --- README | 52 +- RtAudio.cpp | 9998 ++++++++++++++++---------------- RtAudio.h | 864 +-- doc/Release.txt | 13 + doc/{ => doxygen}/Doxyfile | 12 +- doc/{ => doxygen}/footer.html | 2 +- doc/{ => doxygen}/header.html | 0 doc/{ => doxygen}/tutorial.txt | 98 +- doc/{ => images}/ccrma.gif | Bin doc/manual.pdf | Bin 0 -> 220096 bytes tests/Makefile | 6 +- tests/call_inout.cpp | 9 +- tests/call_inout.dsp | 10 +- tests/call_saw.cpp | 9 +- tests/call_saw.dsp | 10 +- tests/call_twostreams.cpp | 18 +- tests/call_twostreams.dsp | 10 +- tests/in_out.cpp | 12 +- tests/in_out.dsp | 4 +- tests/info.cpp | 4 +- tests/info.dsp | 4 +- tests/play_raw.cpp | 12 +- tests/play_raw.dsp | 4 +- tests/play_saw.cpp | 12 +- tests/play_saw.dsp | 4 +- tests/record_raw.cpp | 13 +- tests/record_raw.dsp | 4 +- tests/twostreams.cpp | 27 +- tests/twostreams.dsp | 4 +- 29 files changed, 5614 insertions(+), 5601 deletions(-) create mode 100644 doc/Release.txt rename doc/{ => doxygen}/Doxyfile (94%) rename doc/{ => doxygen}/footer.html (76%) rename doc/{ => doxygen}/header.html (100%) rename doc/{ => doxygen}/tutorial.txt (83%) rename doc/{ => images}/ccrma.gif (100%) create mode 100644 doc/manual.pdf diff --git a/README b/README index 729f1e8..1d0b4d3 100644 --- a/README +++ b/README @@ -1,12 +1,40 @@ - -This is the parent directory of the RtAudio distribution. Here is a quick description of what is contained here: - -RtAudio.h - class header -RtAudio.cpp - class source -rtaudio.dsw - Visual C++ 6.0 workspace file - -Directories: - -tests - various test programs and Visual C++ 6.0 project files -doc - complete documentation in a variety of formats (see doc/html/index.html) - +RtAudio - a C++ class which provides a common API for realtime audio input/output across Linux (native ALSA and OSS), SGI, and Windows operating systems. + +By Gary P. Scavone, 2002. + +This distribution of the Synthesis ToolKit in C++ (STK) contains the following: + +doc: RtAudio documentation +tests: example RtAudio programs + + +OVERVIEW: + +RtAudio is a C++ class which provides a common API (Application Programming Interface) for realtime audio input/output across Linux (native ALSA and OSS), SGI, and Windows operating systems. RtAudio significantly simplifies the process of interacting with computer audio hardware. It was designed with the following goals: + + - object oriented C++ design + - simple, common API across all supported platforms + - single independent header and source file for easy inclusion in programming projects (no libraries!) + - blocking functionality + - callback functionality + - extensive audio device parameter control + - audio device capability probing + - automatic internal conversion for data format, channel number compensation, de-interleaving, and byte-swapping + - control over multiple audio streams and devices with a single instance + +RtAudio incorporates the concept of audio streams, which represent audio output (playback) and/or input (recording). Available audio devices and their capabilities can be enumerated and then specified when opening a stream. Multiple streams can run at the same time and, when allowed by the underlying audio API, a single device can serve multiple streams. + +The RtAudio API provides both blocking (synchronous) and callback (asyncronous) functionality. Callbacks are typically used in conjunction with graphical user interfaces (GUI). Blocking functionality is often necessary for explicit control of multiple input/output stream synchronization or when audio must be synchronized with other system events. + + +LEGAL AND ETHICAL: + +This software was designed and created to be made publicly available for free, primarily for academic purposes, so if you use it, pass it on with this documentation, and for free. + +If you make a million dollars with it, give me some. If you make compositions with it, put me in the program notes. + + +FURTHER READING: + +For complete documentation on RtAudio, see the doc directory of the distribution or surf to http://www-ccrma.stanford.edu/~gary/rtaudio/. + diff --git a/RtAudio.cpp b/RtAudio.cpp index d2f8724..fd116da 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -1,4997 +1,5001 @@ -/******************************************/ -/* - RtAudio - realtime sound I/O C++ class - Version 2.0 by Gary P. Scavone, 2001-2002. -*/ -/******************************************/ - -#include "RtAudio.h" -#include -#include - -// Static variable definitions. -const unsigned int RtAudio :: SAMPLE_RATES[] = { - 4000, 5512, 8000, 9600, 11025, 16000, 22050, - 32000, 44100, 48000, 88200, 96000, 176400, 192000 -}; -const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_SINT8 = 1; -const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_SINT16 = 2; -const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_SINT24 = 4; -const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_SINT32 = 8; -const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_FLOAT32 = 16; -const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_FLOAT64 = 32; - -#if defined(__WINDOWS_DS_) - #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) - #define MUTEX_LOCK(A) EnterCriticalSection(A) - #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) - typedef unsigned THREAD_RETURN; -#else // pthread API - #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) - #define MUTEX_LOCK(A) pthread_mutex_lock(A) - #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) - typedef void * THREAD_RETURN; -#endif - -// *************************************************** // -// -// Public common (OS-independent) methods. -// -// *************************************************** // - -RtAudio :: RtAudio() -{ - initialize(); - - if (nDevices <= 0) { - sprintf(message, "RtAudio: no audio devices found!"); - error(RtAudioError::NO_DEVICES_FOUND); - } -} - -RtAudio :: RtAudio(int *streamID, - int outputDevice, int outputChannels, - int inputDevice, int inputChannels, - RTAUDIO_FORMAT format, int sampleRate, - int *bufferSize, int numberOfBuffers) -{ - initialize(); - - if (nDevices <= 0) { - sprintf(message, "RtAudio: no audio devices found!"); - error(RtAudioError::NO_DEVICES_FOUND); - } - - try { - *streamID = openStream(outputDevice, outputChannels, inputDevice, inputChannels, - format, sampleRate, bufferSize, numberOfBuffers); - } - catch (RtAudioError &exception) { - // deallocate the RTAUDIO_DEVICE structures - if (devices) free(devices); - error(exception.getType()); - } -} - -RtAudio :: ~RtAudio() -{ - // close any existing streams - while ( streams.size() ) - closeStream( streams.begin()->first ); - - // deallocate the RTAUDIO_DEVICE structures - if (devices) free(devices); -} - -int RtAudio :: openStream(int outputDevice, int outputChannels, - int inputDevice, int inputChannels, - RTAUDIO_FORMAT format, int sampleRate, - int *bufferSize, int numberOfBuffers) -{ - static int streamKey = 0; // Unique stream identifier ... OK for multiple instances. - - if (outputChannels < 1 && inputChannels < 1) { - sprintf(message,"RtAudio: one or both 'channel' parameters must be greater than zero."); - error(RtAudioError::INVALID_PARAMETER); - } - - if ( formatBytes(format) == 0 ) { - sprintf(message,"RtAudio: 'format' parameter value is undefined."); - error(RtAudioError::INVALID_PARAMETER); - } - - if ( outputChannels > 0 ) { - if (outputDevice >= nDevices || outputDevice < 0) { - sprintf(message,"RtAudio: 'outputDevice' parameter value (%d) is invalid.", outputDevice); - error(RtAudioError::INVALID_PARAMETER); - } - } - - if ( inputChannels > 0 ) { - if (inputDevice >= nDevices || inputDevice < 0) { - sprintf(message,"RtAudio: 'inputDevice' parameter value (%d) is invalid.", inputDevice); - error(RtAudioError::INVALID_PARAMETER); - } - } - - // Allocate a new stream structure. - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) calloc(1, sizeof(RTAUDIO_STREAM)); - if (stream == NULL) { - sprintf(message, "RtAudio: memory allocation error!"); - error(RtAudioError::MEMORY_ERROR); - } - streams[++streamKey] = (void *) stream; - stream->mode = UNINITIALIZED; - - bool result = SUCCESS; - int device; - STREAM_MODE mode; - int channels; - if ( outputChannels > 0 ) { - - device = outputDevice; - mode = PLAYBACK; - channels = outputChannels; - - if (device == 0) { // Try default device first. - for (int i=0; i 0 && result == SUCCESS ) { - - device = inputDevice; - mode = RECORD; - channels = inputChannels; - - if (device == 0) { // Try default device first. - for (int i=0; imutex); - return streamKey; - } - - // If we get here, all attempted probes failed. Close any opened - // devices and delete the allocated stream. - closeStream(streamKey); - sprintf(message,"RtAudio: no devices found for given parameters."); - error(RtAudioError::INVALID_PARAMETER); - - return -1; -} - -int RtAudio :: getDeviceCount(void) -{ - return nDevices; -} - -void RtAudio :: getDeviceInfo(int device, RTAUDIO_DEVICE *info) -{ - if (device >= nDevices || device < 0) { - sprintf(message, "RtAudio: invalid device specifier (%d)!", device); - error(RtAudioError::INVALID_DEVICE); - } - - // If the device wasn't successfully probed before, try it again. - if (devices[device].probed == false) { - clearDeviceInfo(&devices[device]); - probeDeviceInfo(&devices[device]); - } - - // Clear the info structure. - memset(info, 0, sizeof(RTAUDIO_DEVICE)); - - strncpy(info->name, devices[device].name, 128); - info->probed = devices[device].probed; - if ( info->probed == true ) { - info->maxOutputChannels = devices[device].maxOutputChannels; - info->maxInputChannels = devices[device].maxInputChannels; - info->maxDuplexChannels = devices[device].maxDuplexChannels; - info->minOutputChannels = devices[device].minOutputChannels; - info->minInputChannels = devices[device].minInputChannels; - info->minDuplexChannels = devices[device].minDuplexChannels; - info->hasDuplexSupport = devices[device].hasDuplexSupport; - info->nSampleRates = devices[device].nSampleRates; - if (info->nSampleRates == -1) { - info->sampleRates[0] = devices[device].sampleRates[0]; - info->sampleRates[1] = devices[device].sampleRates[1]; - } - else { - for (int i=0; inSampleRates; i++) - info->sampleRates[i] = devices[device].sampleRates[i]; - } - info->nativeFormats = devices[device].nativeFormats; - } - - return; -} - -char * const RtAudio :: getStreamBuffer(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - return stream->userBuffer; -} - -// This global structure is used to pass information to the thread -// function. I tried other methods but had intermittent errors due to -// variable persistence during thread startup. -struct { - RtAudio *object; - int streamID; -} thread_info; - -#if defined(__WINDOWS_DS_) - extern "C" unsigned __stdcall callbackHandler(void *ptr); -#else - extern "C" void *callbackHandler(void *ptr); -#endif - -void RtAudio :: setStreamCallback(int streamID, RTAUDIO_CALLBACK callback, void *userData) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - stream->callback = callback; - stream->userData = userData; - stream->usingCallback = true; - thread_info.object = this; - thread_info.streamID = streamID; - - int err = 0; -#if defined(__WINDOWS_DS_) - unsigned thread_id; - stream->thread = _beginthreadex(NULL, 0, &callbackHandler, - &stream->usingCallback, 0, &thread_id); - if (stream->thread == 0) err = -1; - // When spawning multiple threads in quick succession, it appears to be - // necessary to wait a bit for each to initialize ... another windism! - Sleep(1); -#else - err = pthread_create(&stream->thread, NULL, callbackHandler, &stream->usingCallback); -#endif - - if (err) { - stream->usingCallback = false; - sprintf(message, "RtAudio: error starting callback thread!"); - error(RtAudioError::THREAD_ERROR); - } -} - -// *************************************************** // -// -// OS/API-specific methods. -// -// *************************************************** // - -#if defined(__LINUX_ALSA_) - -void RtAudio :: initialize(void) -{ - int card, err, device; - int devices_per_card[32] = {0}; - char name[32]; - snd_ctl_t *handle; - snd_ctl_card_info_t *info; - snd_ctl_card_info_alloca(&info); - - // Count cards and devices - nDevices = 0; - card = -1; - snd_card_next(&card); - while (card >= 0) { - sprintf(name, "hw:%d", card); - err = snd_ctl_open(&handle, name, 0); - if (err < 0) { - sprintf(message, "RtAudio: ALSA control open (%i): %s.", card, snd_strerror(err)); - error(RtAudioError::WARNING); - goto next_card; - } - err = snd_ctl_card_info(handle, info); - if (err < 0) { - sprintf(message, "RtAudio: ALSA control hardware info (%i): %s.", card, snd_strerror(err)); - error(RtAudioError::WARNING); - goto next_card; - } - device = -1; - while (1) { - err = snd_ctl_pcm_next_device(handle, &device); - if (err < 0) { - sprintf(message, "RtAudio: ALSA control next device (%i): %s.", card, snd_strerror(err)); - error(RtAudioError::WARNING); - break; - } - if (device < 0) - break; - nDevices++; - devices_per_card[card]++; - } - - next_card: - snd_ctl_close(handle); - snd_card_next(&card); - } - - if (nDevices == 0) return; - - // Allocate the RTAUDIO_DEVICE structures. - devices = (RTAUDIO_DEVICE *) calloc(nDevices, sizeof(RTAUDIO_DEVICE)); - if (devices == NULL) { - sprintf(message, "RtAudio: memory allocation error!"); - error(RtAudioError::MEMORY_ERROR); - } - - // Write device ascii identifiers to device structures and then - // probe the device capabilities. - card = 0; - device = 0; - for (int i=0; iname, stream, open_mode); - if (err < 0) { - sprintf(message, "RtAudio: ALSA pcm playback open (%s): %s.", - info->name, snd_strerror(err)); - error(RtAudioError::WARNING); - goto capture_probe; - } - - snd_pcm_hw_params_t *params; - snd_pcm_hw_params_alloca(¶ms); - - // We have an open device ... allocate the parameter structure. - err = snd_pcm_hw_params_any(handle, params); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA hardware probe error (%s): %s.", - info->name, snd_strerror(err)); - error(RtAudioError::WARNING); - goto capture_probe; - } - - // Get output channel information. - info->minOutputChannels = snd_pcm_hw_params_get_channels_min(params); - info->maxOutputChannels = snd_pcm_hw_params_get_channels_max(params); - - snd_pcm_close(handle); - - capture_probe: - // Now try for capture - stream = SND_PCM_STREAM_CAPTURE; - err = snd_pcm_open(&handle, info->name, stream, open_mode); - if (err < 0) { - sprintf(message, "RtAudio: ALSA pcm capture open (%s): %s.", - info->name, snd_strerror(err)); - error(RtAudioError::WARNING); - if (info->maxOutputChannels == 0) - // didn't open for playback either ... device invalid - return; - goto probe_parameters; - } - - // We have an open capture device ... allocate the parameter structure. - err = snd_pcm_hw_params_any(handle, params); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA hardware probe error (%s): %s.", - info->name, snd_strerror(err)); - error(RtAudioError::WARNING); - if (info->maxOutputChannels > 0) - goto probe_parameters; - else - return; - } - - // Get input channel information. - info->minInputChannels = snd_pcm_hw_params_get_channels_min(params); - info->maxInputChannels = snd_pcm_hw_params_get_channels_max(params); - - // If device opens for both playback and capture, we determine the channels. - if (info->maxOutputChannels == 0 || info->maxInputChannels == 0) - goto probe_parameters; - - info->hasDuplexSupport = true; - info->maxDuplexChannels = (info->maxOutputChannels > info->maxInputChannels) ? - info->maxInputChannels : info->maxOutputChannels; - info->minDuplexChannels = (info->minOutputChannels > info->minInputChannels) ? - info->minInputChannels : info->minOutputChannels; - - snd_pcm_close(handle); - - probe_parameters: - // At this point, we just need to figure out the supported data formats and sample rates. - // We'll proceed by openning the device in the direction with the maximum number of channels, - // or playback if they are equal. This might limit our sample rate options, but so be it. - - if (info->maxOutputChannels >= info->maxInputChannels) - stream = SND_PCM_STREAM_PLAYBACK; - else - stream = SND_PCM_STREAM_CAPTURE; - - err = snd_pcm_open(&handle, info->name, stream, open_mode); - if (err < 0) { - sprintf(message, "RtAudio: ALSA pcm (%s) won't reopen during probe: %s.", - info->name, snd_strerror(err)); - error(RtAudioError::WARNING); - return; - } - - // We have an open device ... allocate the parameter structure. - err = snd_pcm_hw_params_any(handle, params); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA hardware reopen probe error (%s): %s.", - info->name, snd_strerror(err)); - error(RtAudioError::WARNING); - return; - } - - // Test a non-standard sample rate to see if continuous rate is supported. - int dir = 0; - if (snd_pcm_hw_params_test_rate(handle, params, 35500, dir) == 0) { - // It appears that continuous sample rate support is available. - info->nSampleRates = -1; - info->sampleRates[0] = snd_pcm_hw_params_get_rate_min(params, &dir); - info->sampleRates[1] = snd_pcm_hw_params_get_rate_max(params, &dir); - } - else { - // No continuous rate support ... test our discrete set of sample rate values. - info->nSampleRates = 0; - for (int i=0; isampleRates[info->nSampleRates] = SAMPLE_RATES[i]; - info->nSampleRates++; - } - } - if (info->nSampleRates == 0) { - snd_pcm_close(handle); - return; - } - } - - // Probe the supported data formats ... we don't care about endian-ness just yet - snd_pcm_format_t format; - info->nativeFormats = 0; - format = SND_PCM_FORMAT_S8; - if (snd_pcm_hw_params_test_format(handle, params, format) == 0) - info->nativeFormats |= RTAUDIO_SINT8; - format = SND_PCM_FORMAT_S16; - if (snd_pcm_hw_params_test_format(handle, params, format) == 0) - info->nativeFormats |= RTAUDIO_SINT16; - format = SND_PCM_FORMAT_S24; - if (snd_pcm_hw_params_test_format(handle, params, format) == 0) - info->nativeFormats |= RTAUDIO_SINT24; - format = SND_PCM_FORMAT_S32; - if (snd_pcm_hw_params_test_format(handle, params, format) == 0) - info->nativeFormats |= RTAUDIO_SINT32; - format = SND_PCM_FORMAT_FLOAT; - if (snd_pcm_hw_params_test_format(handle, params, format) == 0) - info->nativeFormats |= RTAUDIO_FLOAT32; - format = SND_PCM_FORMAT_FLOAT64; - if (snd_pcm_hw_params_test_format(handle, params, format) == 0) - info->nativeFormats |= RTAUDIO_FLOAT64; - - // Check that we have at least one supported format - if (info->nativeFormats == 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA PCM device (%s) data format not supported by RtAudio.", - info->name); - error(RtAudioError::WARNING); - return; - } - - // That's all ... close the device and return - snd_pcm_close(handle); - info->probed = true; - return; -} - -bool RtAudio :: probeDeviceOpen(int device, RTAUDIO_STREAM *stream, - STREAM_MODE mode, int channels, - int sampleRate, RTAUDIO_FORMAT format, - int *bufferSize, int numberOfBuffers) -{ -#if defined(RTAUDIO_DEBUG) - snd_output_t *out; - snd_output_stdio_attach(&out, stderr, 0); -#endif - - // I'm not using the "plug" interface ... too much inconsistent behavior. - const char *name = devices[device].name; - - snd_pcm_stream_t alsa_stream; - if (mode == PLAYBACK) - alsa_stream = SND_PCM_STREAM_PLAYBACK; - else - alsa_stream = SND_PCM_STREAM_CAPTURE; - - int err; - snd_pcm_t *handle; - int alsa_open_mode = SND_PCM_ASYNC; - err = snd_pcm_open(&handle, name, alsa_stream, alsa_open_mode); - if (err < 0) { - sprintf(message,"RtAudio: ALSA pcm device (%s) won't open: %s.", - name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Fill the parameter structure. - snd_pcm_hw_params_t *hw_params; - snd_pcm_hw_params_alloca(&hw_params); - err = snd_pcm_hw_params_any(handle, hw_params); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error getting parameter handle (%s): %s.", - name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - -#if defined(RTAUDIO_DEBUG) - fprintf(stderr, "\nRtAudio: ALSA dump hardware params just after device open:\n\n"); - snd_pcm_hw_params_dump(hw_params, out); -#endif - - // Set access ... try interleaved access first, then non-interleaved - err = snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); - if (err < 0) { - // No interleave support ... try non-interleave. - err = snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error setting access ( (%s): %s.", - name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - stream->deInterleave[mode] = true; - } - - // Determine how to set the device format. - stream->userFormat = format; - snd_pcm_format_t device_format; - - if (format == RTAUDIO_SINT8) - device_format = SND_PCM_FORMAT_S8; - else if (format == RTAUDIO_SINT16) - device_format = SND_PCM_FORMAT_S16; - else if (format == RTAUDIO_SINT24) - device_format = SND_PCM_FORMAT_S24; - else if (format == RTAUDIO_SINT32) - device_format = SND_PCM_FORMAT_S32; - else if (format == RTAUDIO_FLOAT32) - device_format = SND_PCM_FORMAT_FLOAT; - else if (format == RTAUDIO_FLOAT64) - device_format = SND_PCM_FORMAT_FLOAT64; - - if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { - stream->deviceFormat[mode] = format; - goto set_format; - } - - // The user requested format is not natively supported by the device. - device_format = SND_PCM_FORMAT_FLOAT64; - if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { - stream->deviceFormat[mode] = RTAUDIO_FLOAT64; - goto set_format; - } - - device_format = SND_PCM_FORMAT_FLOAT; - if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { - stream->deviceFormat[mode] = RTAUDIO_FLOAT32; - goto set_format; - } - - device_format = SND_PCM_FORMAT_S32; - if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { - stream->deviceFormat[mode] = RTAUDIO_SINT32; - goto set_format; - } - - device_format = SND_PCM_FORMAT_S24; - if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { - stream->deviceFormat[mode] = RTAUDIO_SINT24; - goto set_format; - } - - device_format = SND_PCM_FORMAT_S16; - if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { - stream->deviceFormat[mode] = RTAUDIO_SINT16; - goto set_format; - } - - device_format = SND_PCM_FORMAT_S8; - if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { - stream->deviceFormat[mode] = RTAUDIO_SINT8; - goto set_format; - } - - // If we get here, no supported format was found. - sprintf(message,"RtAudio: ALSA pcm device (%s) data format not supported by RtAudio.", name); - snd_pcm_close(handle); - error(RtAudioError::WARNING); - return FAILURE; - - set_format: - err = snd_pcm_hw_params_set_format(handle, hw_params, device_format); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error setting format (%s): %s.", - name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Determine whether byte-swaping is necessary. - stream->doByteSwap[mode] = false; - if (device_format != SND_PCM_FORMAT_S8) { - err = snd_pcm_format_cpu_endian(device_format); - if (err == 0) - stream->doByteSwap[mode] = true; - else if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error getting format endian-ness (%s): %s.", - name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - } - - // Determine the number of channels for this device. We support a possible - // minimum device channel number > than the value requested by the user. - stream->nUserChannels[mode] = channels; - int device_channels = snd_pcm_hw_params_get_channels_max(hw_params); - if (device_channels < channels) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: channels (%d) not supported by device (%s).", - channels, name); - error(RtAudioError::WARNING); - return FAILURE; - } - - device_channels = snd_pcm_hw_params_get_channels_min(hw_params); - if (device_channels < channels) device_channels = channels; - stream->nDeviceChannels[mode] = device_channels; - - // Set the device channels. - err = snd_pcm_hw_params_set_channels(handle, hw_params, device_channels); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error setting channels (%d) on device (%s): %s.", - device_channels, name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set the sample rate. - err = snd_pcm_hw_params_set_rate(handle, hw_params, (unsigned int)sampleRate, 0); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error setting sample rate (%d) on device (%s): %s.", - sampleRate, name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set the buffer number, which in ALSA is referred to as the "period". - int dir; - int periods = numberOfBuffers; - // Even though the hardware might allow 1 buffer, it won't work reliably. - if (periods < 2) periods = 2; - err = snd_pcm_hw_params_get_periods_min(hw_params, &dir); - if (err > periods) periods = err; - - err = snd_pcm_hw_params_set_periods(handle, hw_params, periods, 0); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error setting periods (%s): %s.", - name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set the buffer (or period) size. - err = snd_pcm_hw_params_get_period_size_min(hw_params, &dir); - if (err > *bufferSize) *bufferSize = err; - - err = snd_pcm_hw_params_set_period_size(handle, hw_params, *bufferSize, 0); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error setting period size (%s): %s.", - name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - - stream->bufferSize = *bufferSize; - - // Install the hardware configuration - err = snd_pcm_hw_params(handle, hw_params); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error installing hardware configuration (%s): %s.", - name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - -#if defined(RTAUDIO_DEBUG) - fprintf(stderr, "\nRtAudio: ALSA dump hardware params after installation:\n\n"); - snd_pcm_hw_params_dump(hw_params, out); -#endif - - /* - // Install the software configuration - snd_pcm_sw_params_t *sw_params = NULL; - snd_pcm_sw_params_alloca(&sw_params); - snd_pcm_sw_params_current(handle, sw_params); - err = snd_pcm_sw_params(handle, sw_params); - if (err < 0) { - snd_pcm_close(handle); - sprintf(message, "RtAudio: ALSA error installing software configuration (%s): %s.", - name, snd_strerror(err)); - error(RtAudioError::WARNING); - return FAILURE; - } - */ - - // Set handle and flags for buffer conversion - stream->handle[mode] = handle; - stream->doConvertBuffer[mode] = false; - if (stream->userFormat != stream->deviceFormat[mode]) - stream->doConvertBuffer[mode] = true; - if (stream->nUserChannels[mode] < stream->nDeviceChannels[mode]) - stream->doConvertBuffer[mode] = true; - if (stream->nUserChannels[mode] > 1 && stream->deInterleave[mode]) - stream->doConvertBuffer[mode] = true; - - // Allocate necessary internal buffers - if ( stream->nUserChannels[0] != stream->nUserChannels[1] ) { - - long buffer_bytes; - if (stream->nUserChannels[0] >= stream->nUserChannels[1]) - buffer_bytes = stream->nUserChannels[0]; - else - buffer_bytes = stream->nUserChannels[1]; - - buffer_bytes *= *bufferSize * formatBytes(stream->userFormat); - if (stream->userBuffer) free(stream->userBuffer); - stream->userBuffer = (char *) calloc(buffer_bytes, 1); - if (stream->userBuffer == NULL) - goto memory_error; - } - - if ( stream->doConvertBuffer[mode] ) { - - long buffer_bytes; - bool makeBuffer = true; - if ( mode == PLAYBACK ) - buffer_bytes = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); - else { // mode == RECORD - buffer_bytes = stream->nDeviceChannels[1] * formatBytes(stream->deviceFormat[1]); - if ( stream->mode == PLAYBACK ) { - long bytes_out = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); - if ( buffer_bytes > bytes_out ) - buffer_bytes = (buffer_bytes > bytes_out) ? buffer_bytes : bytes_out; - else - makeBuffer = false; - } - } - - if ( makeBuffer ) { - buffer_bytes *= *bufferSize; - if (stream->deviceBuffer) free(stream->deviceBuffer); - stream->deviceBuffer = (char *) calloc(buffer_bytes, 1); - if (stream->deviceBuffer == NULL) - goto memory_error; - } - } - - stream->device[mode] = device; - stream->state = STREAM_STOPPED; - if ( stream->mode == PLAYBACK && mode == RECORD ) - // We had already set up an output stream. - stream->mode = DUPLEX; - else - stream->mode = mode; - stream->nBuffers = periods; - stream->sampleRate = sampleRate; - - return SUCCESS; - - memory_error: - if (stream->handle[0]) { - snd_pcm_close(stream->handle[0]); - stream->handle[0] = 0; - } - if (stream->handle[1]) { - snd_pcm_close(stream->handle[1]); - stream->handle[1] = 0; - } - if (stream->userBuffer) { - free(stream->userBuffer); - stream->userBuffer = 0; - } - sprintf(message, "RtAudio: ALSA error allocating buffer memory (%s).", name); - error(RtAudioError::WARNING); - return FAILURE; -} - -void RtAudio :: cancelStreamCallback(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - if (stream->usingCallback) { - stream->usingCallback = false; - pthread_cancel(stream->thread); - pthread_join(stream->thread, NULL); - stream->thread = 0; - stream->callback = NULL; - stream->userData = NULL; - } -} - -void RtAudio :: closeStream(int streamID) -{ - // We don't want an exception to be thrown here because this - // function is called by our class destructor. So, do our own - // streamID check. - if ( streams.find( streamID ) == streams.end() ) { - sprintf(message, "RtAudio: invalid stream identifier!"); - error(RtAudioError::WARNING); - return; - } - - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) streams[streamID]; - - if (stream->usingCallback) { - pthread_cancel(stream->thread); - pthread_join(stream->thread, NULL); - } - - if (stream->state == STREAM_RUNNING) { - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) - snd_pcm_drop(stream->handle[0]); - if (stream->mode == RECORD || stream->mode == DUPLEX) - snd_pcm_drop(stream->handle[1]); - } - - pthread_mutex_destroy(&stream->mutex); - - if (stream->handle[0]) - snd_pcm_close(stream->handle[0]); - - if (stream->handle[1]) - snd_pcm_close(stream->handle[1]); - - if (stream->userBuffer) - free(stream->userBuffer); - - if (stream->deviceBuffer) - free(stream->deviceBuffer); - - free(stream); - streams.erase(streamID); -} - -void RtAudio :: startStream(int streamID) -{ - // This method calls snd_pcm_prepare if the device isn't already in that state. - - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_RUNNING) - goto unlock; - - int err; - snd_pcm_state_t state; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - state = snd_pcm_state(stream->handle[0]); - if (state != SND_PCM_STATE_PREPARED) { - err = snd_pcm_prepare(stream->handle[0]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error preparing pcm device (%s): %s.", - devices[stream->device[0]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - state = snd_pcm_state(stream->handle[1]); - if (state != SND_PCM_STATE_PREPARED) { - err = snd_pcm_prepare(stream->handle[1]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error preparing pcm device (%s): %s.", - devices[stream->device[1]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - } - stream->state = STREAM_RUNNING; - - unlock: - MUTEX_UNLOCK(&stream->mutex); -} - -void RtAudio :: stopStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_STOPPED) - goto unlock; - - int err; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - err = snd_pcm_drain(stream->handle[0]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error draining pcm device (%s): %s.", - devices[stream->device[0]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - err = snd_pcm_drain(stream->handle[1]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error draining pcm device (%s): %s.", - devices[stream->device[1]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - stream->state = STREAM_STOPPED; - - unlock: - MUTEX_UNLOCK(&stream->mutex); -} - -void RtAudio :: abortStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_STOPPED) - goto unlock; - - int err; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - err = snd_pcm_drop(stream->handle[0]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error draining pcm device (%s): %s.", - devices[stream->device[0]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - err = snd_pcm_drop(stream->handle[1]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error draining pcm device (%s): %s.", - devices[stream->device[1]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - stream->state = STREAM_STOPPED; - - unlock: - MUTEX_UNLOCK(&stream->mutex); -} - -int RtAudio :: streamWillBlock(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - int err = 0, frames = 0; - if (stream->state == STREAM_STOPPED) - goto unlock; - - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - err = snd_pcm_avail_update(stream->handle[0]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error getting available frames for device (%s): %s.", - devices[stream->device[0]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - - frames = err; - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - err = snd_pcm_avail_update(stream->handle[1]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error getting available frames for device (%s): %s.", - devices[stream->device[1]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - if (frames > err) frames = err; - } - - frames = stream->bufferSize - frames; - if (frames < 0) frames = 0; - - unlock: - MUTEX_UNLOCK(&stream->mutex); - return frames; -} - -void RtAudio :: tickStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - int stopStream = 0; - if (stream->state == STREAM_STOPPED) { - if (stream->usingCallback) usleep(50000); // sleep 50 milliseconds - return; - } - else if (stream->usingCallback) { - stopStream = stream->callback(stream->userBuffer, stream->bufferSize, stream->userData); - } - - MUTEX_LOCK(&stream->mutex); - - // The state might change while waiting on a mutex. - if (stream->state == STREAM_STOPPED) - goto unlock; - - int err; - char *buffer; - int channels; - RTAUDIO_FORMAT format; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - - // Setup parameters and do buffer conversion if necessary. - if (stream->doConvertBuffer[0]) { - convertStreamBuffer(stream, PLAYBACK); - buffer = stream->deviceBuffer; - channels = stream->nDeviceChannels[0]; - format = stream->deviceFormat[0]; - } - else { - buffer = stream->userBuffer; - channels = stream->nUserChannels[0]; - format = stream->userFormat; - } - - // Do byte swapping if necessary. - if (stream->doByteSwap[0]) - byteSwapBuffer(buffer, stream->bufferSize * channels, format); - - // Write samples to device in interleaved/non-interleaved format. - if (stream->deInterleave[0]) { - void *bufs[channels]; - size_t offset = stream->bufferSize * formatBytes(format); - for (int i=0; ihandle[0], bufs, stream->bufferSize); - } - else - err = snd_pcm_writei(stream->handle[0], buffer, stream->bufferSize); - - if (err < stream->bufferSize) { - // Either an error or underrun occured. - if (err == -EPIPE) { - snd_pcm_state_t state = snd_pcm_state(stream->handle[0]); - if (state == SND_PCM_STATE_XRUN) { - sprintf(message, "RtAudio: ALSA underrun detected."); - error(RtAudioError::WARNING); - err = snd_pcm_prepare(stream->handle[0]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error preparing handle after underrun: %s.", - snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - else { - sprintf(message, "RtAudio: ALSA error, current state is %s.", - snd_pcm_state_name(state)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - goto unlock; - } - else { - sprintf(message, "RtAudio: ALSA audio write error for device (%s): %s.", - devices[stream->device[0]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - - // Setup parameters. - if (stream->doConvertBuffer[1]) { - buffer = stream->deviceBuffer; - channels = stream->nDeviceChannels[1]; - format = stream->deviceFormat[1]; - } - else { - buffer = stream->userBuffer; - channels = stream->nUserChannels[1]; - format = stream->userFormat; - } - - // Read samples from device in interleaved/non-interleaved format. - if (stream->deInterleave[1]) { - void *bufs[channels]; - size_t offset = stream->bufferSize * formatBytes(format); - for (int i=0; ihandle[1], bufs, stream->bufferSize); - } - else - err = snd_pcm_readi(stream->handle[1], buffer, stream->bufferSize); - - if (err < stream->bufferSize) { - // Either an error or underrun occured. - if (err == -EPIPE) { - snd_pcm_state_t state = snd_pcm_state(stream->handle[1]); - if (state == SND_PCM_STATE_XRUN) { - sprintf(message, "RtAudio: ALSA overrun detected."); - error(RtAudioError::WARNING); - err = snd_pcm_prepare(stream->handle[1]); - if (err < 0) { - sprintf(message, "RtAudio: ALSA error preparing handle after overrun: %s.", - snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - else { - sprintf(message, "RtAudio: ALSA error, current state is %s.", - snd_pcm_state_name(state)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - goto unlock; - } - else { - sprintf(message, "RtAudio: ALSA audio read error for device (%s): %s.", - devices[stream->device[1]].name, snd_strerror(err)); - MUTEX_UNLOCK(&stream->mutex); - error(RtAudioError::DRIVER_ERROR); - } - } - - // Do byte swapping if necessary. - if (stream->doByteSwap[1]) - byteSwapBuffer(buffer, stream->bufferSize * channels, format); - - // Do buffer conversion if necessary. - if (stream->doConvertBuffer[1]) - convertStreamBuffer(stream, RECORD); - } - - unlock: - MUTEX_UNLOCK(&stream->mutex); - - if (stream->usingCallback && stopStream) - this->stopStream(streamID); -} - -extern "C" void *callbackHandler(void *ptr) -{ - RtAudio *object = thread_info.object; - int stream = thread_info.streamID; - bool *usingCallback = (bool *) ptr; - - while ( *usingCallback ) { - pthread_testcancel(); - try { - object->tickStream(stream); - } - catch (RtAudioError &exception) { - fprintf(stderr, "\nCallback thread error (%s) ... closing thread.\n\n", - exception.getMessage()); - break; - } - } - - return 0; -} - -//******************** End of __LINUX_ALSA_ *********************// - -#elif defined(__LINUX_OSS_) - -#include -#include -#include -#include -#include -#include -#include -#include - -#define DAC_NAME "/dev/dsp" -#define MAX_DEVICES 16 -#define MAX_CHANNELS 16 - -void RtAudio :: initialize(void) -{ - // Count cards and devices - nDevices = 0; - - // We check /dev/dsp before probing devices. /dev/dsp is supposed to - // be a link to the "default" audio device, of the form /dev/dsp0, - // /dev/dsp1, etc... However, I've seen one case where /dev/dsp was a - // real device, so we need to check for that. Also, sometimes the - // link is to /dev/dspx and other times just dspx. I'm not sure how - // the latter works, but it does. - char device_name[16]; - struct stat dspstat; - int dsplink = -1; - int i = 0; - if (lstat(DAC_NAME, &dspstat) == 0) { - if (S_ISLNK(dspstat.st_mode)) { - i = readlink(DAC_NAME, device_name, sizeof(device_name)); - if (i > 0) { - device_name[i] = '\0'; - if (i > 8) { // check for "/dev/dspx" - if (!strncmp(DAC_NAME, device_name, 8)) - dsplink = atoi(&device_name[8]); - } - else if (i > 3) { // check for "dspx" - if (!strncmp("dsp", device_name, 3)) - dsplink = atoi(&device_name[3]); - } - } - else { - sprintf(message, "RtAudio: cannot read value of symbolic link %s.", DAC_NAME); - error(RtAudioError::SYSTEM_ERROR); - } - } - } - else { - sprintf(message, "RtAudio: cannot stat %s.", DAC_NAME); - error(RtAudioError::SYSTEM_ERROR); - } - - // The OSS API doesn't provide a routine for determining the number - // of devices. Thus, we'll just pursue a brute force method. The - // idea is to start with /dev/dsp(0) and continue with higher device - // numbers until we reach MAX_DSP_DEVICES. This should tell us how - // many devices we have ... it is not a fullproof scheme, but hopefully - // it will work most of the time. - - int fd = 0; - char names[MAX_DEVICES][16]; - for (i=-1; i= 0) close(fd); - strncpy(names[nDevices], device_name, 16); - nDevices++; - } - - if (nDevices == 0) return; - - // Allocate the DEVICE_CONTROL structures. - devices = (RTAUDIO_DEVICE *) calloc(nDevices, sizeof(RTAUDIO_DEVICE)); - if (devices == NULL) { - sprintf(message, "RtAudio: memory allocation error!"); - error(RtAudioError::MEMORY_ERROR); - } - - // Write device ascii identifiers to device control structure and then probe capabilities. - for (i=0; iname, O_WRONLY | O_NONBLOCK); - if (fd == -1) { - // Open device failed ... either busy or doesn't exist - if (errno == EBUSY || errno == EAGAIN) - sprintf(message, "RtAudio: OSS playback device (%s) is busy and cannot be probed.", - info->name); - else - sprintf(message, "RtAudio: OSS playback device (%s) open error.", info->name); - error(RtAudioError::WARNING); - goto capture_probe; - } - - // We have an open device ... see how many channels it can handle - for (i=MAX_CHANNELS; i>0; i--) { - channels = i; - if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1) { - // This would normally indicate some sort of hardware error, but under ALSA's - // OSS emulation, it sometimes indicates an invalid channel value. Further, - // the returned channel value is not changed. So, we'll ignore the possible - // hardware error. - continue; // try next channel number - } - // Check to see whether the device supports the requested number of channels - if (channels != i ) continue; // try next channel number - // If here, we found the largest working channel value - break; - } - info->maxOutputChannels = channels; - - // Now find the minimum number of channels it can handle - for (i=1; i<=info->maxOutputChannels; i++) { - channels = i; - if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) - continue; // try next channel number - // If here, we found the smallest working channel value - break; - } - info->minOutputChannels = channels; - close(fd); - - capture_probe: - // Now try for capture - fd = open(info->name, O_RDONLY | O_NONBLOCK); - if (fd == -1) { - // Open device for capture failed ... either busy or doesn't exist - if (errno == EBUSY || errno == EAGAIN) - sprintf(message, "RtAudio: OSS capture device (%s) is busy and cannot be probed.", - info->name); - else - sprintf(message, "RtAudio: OSS capture device (%s) open error.", info->name); - error(RtAudioError::WARNING); - if (info->maxOutputChannels == 0) - // didn't open for playback either ... device invalid - return; - goto probe_parameters; - } - - // We have the device open for capture ... see how many channels it can handle - for (i=MAX_CHANNELS; i>0; i--) { - channels = i; - if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) { - continue; // as above - } - // If here, we found a working channel value - break; - } - info->maxInputChannels = channels; - - // Now find the minimum number of channels it can handle - for (i=1; i<=info->maxInputChannels; i++) { - channels = i; - if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) - continue; // try next channel number - // If here, we found the smallest working channel value - break; - } - info->minInputChannels = channels; - close(fd); - - // If device opens for both playback and capture, we determine the channels. - if (info->maxOutputChannels == 0 || info->maxInputChannels == 0) - goto probe_parameters; - - fd = open(info->name, O_RDWR | O_NONBLOCK); - if (fd == -1) - goto probe_parameters; - - ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); - ioctl(fd, SNDCTL_DSP_GETCAPS, &mask); - if (mask & DSP_CAP_DUPLEX) { - info->hasDuplexSupport = true; - // We have the device open for duplex ... see how many channels it can handle - for (i=MAX_CHANNELS; i>0; i--) { - channels = i; - if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) - continue; // as above - // If here, we found a working channel value - break; - } - info->maxDuplexChannels = channels; - - // Now find the minimum number of channels it can handle - for (i=1; i<=info->maxDuplexChannels; i++) { - channels = i; - if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) - continue; // try next channel number - // If here, we found the smallest working channel value - break; - } - info->minDuplexChannels = channels; - } - close(fd); - - probe_parameters: - // At this point, we need to figure out the supported data formats - // and sample rates. We'll proceed by openning the device in the - // direction with the maximum number of channels, or playback if - // they are equal. This might limit our sample rate options, but so - // be it. - - if (info->maxOutputChannels >= info->maxInputChannels) { - fd = open(info->name, O_WRONLY | O_NONBLOCK); - channels = info->maxOutputChannels; - } - else { - fd = open(info->name, O_RDONLY | O_NONBLOCK); - channels = info->maxInputChannels; - } - - if (fd == -1) { - // We've got some sort of conflict ... abort - sprintf(message, "RtAudio: OSS device (%s) won't reopen during probe.", - info->name); - error(RtAudioError::WARNING); - return; - } - - // We have an open device ... set to maximum channels. - i = channels; - if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) { - // We've got some sort of conflict ... abort - close(fd); - sprintf(message, "RtAudio: OSS device (%s) won't revert to previous channel setting.", - info->name); - error(RtAudioError::WARNING); - return; - } - - if (ioctl(fd, SNDCTL_DSP_GETFMTS, &mask) == -1) { - close(fd); - sprintf(message, "RtAudio: OSS device (%s) can't get supported audio formats.", - info->name); - error(RtAudioError::WARNING); - return; - } - - // Probe the supported data formats ... we don't care about endian-ness just yet. - int format; - info->nativeFormats = 0; -#if defined (AFMT_S32_BE) - // This format does not seem to be in the 2.4 kernel version of OSS soundcard.h - if (mask & AFMT_S32_BE) { - format = AFMT_S32_BE; - info->nativeFormats |= RTAUDIO_SINT32; - } -#endif -#if defined (AFMT_S32_LE) - /* This format is not in the 2.4.4 kernel version of OSS soundcard.h */ - if (mask & AFMT_S32_LE) { - format = AFMT_S32_LE; - info->nativeFormats |= RTAUDIO_SINT32; - } -#endif - if (mask & AFMT_S8) { - format = AFMT_S8; - info->nativeFormats |= RTAUDIO_SINT8; - } - if (mask & AFMT_S16_BE) { - format = AFMT_S16_BE; - info->nativeFormats |= RTAUDIO_SINT16; - } - if (mask & AFMT_S16_LE) { - format = AFMT_S16_LE; - info->nativeFormats |= RTAUDIO_SINT16; - } - - // Check that we have at least one supported format - if (info->nativeFormats == 0) { - close(fd); - sprintf(message, "RtAudio: OSS device (%s) data format not supported by RtAudio.", - info->name); - error(RtAudioError::WARNING); - return; - } - - // Set the format - i = format; - if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) == -1 || format != i) { - close(fd); - sprintf(message, "RtAudio: OSS device (%s) error setting data format.", - info->name); - error(RtAudioError::WARNING); - return; - } - - // Probe the supported sample rates ... first get lower limit - int speed = 1; - if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) == -1) { - // If we get here, we're probably using an ALSA driver with OSS-emulation, - // which doesn't conform to the OSS specification. In this case, - // we'll probe our predefined list of sample rates for working values. - info->nSampleRates = 0; - for (i=0; isampleRates[info->nSampleRates] = SAMPLE_RATES[i]; - info->nSampleRates++; - } - } - if (info->nSampleRates == 0) { - close(fd); - return; - } - goto finished; - } - info->sampleRates[0] = speed; - - // Now get upper limit - speed = 1000000; - if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) == -1) { - close(fd); - sprintf(message, "RtAudio: OSS device (%s) error setting sample rate.", - info->name); - error(RtAudioError::WARNING); - return; - } - info->sampleRates[1] = speed; - info->nSampleRates = -1; - - finished: // That's all ... close the device and return - close(fd); - info->probed = true; - return; -} - -bool RtAudio :: probeDeviceOpen(int device, RTAUDIO_STREAM *stream, - STREAM_MODE mode, int channels, - int sampleRate, RTAUDIO_FORMAT format, - int *bufferSize, int numberOfBuffers) -{ - int buffers, buffer_bytes, device_channels, device_format; - int srate, temp, fd; - - const char *name = devices[device].name; - - if (mode == PLAYBACK) - fd = open(name, O_WRONLY | O_NONBLOCK); - else { // mode == RECORD - if (stream->mode == PLAYBACK && stream->device[0] == device) { - // We just set the same device for playback ... close and reopen for duplex (OSS only). - close(stream->handle[0]); - stream->handle[0] = 0; - // First check that the number previously set channels is the same. - if (stream->nUserChannels[0] != channels) { - sprintf(message, "RtAudio: input/output channels must be equal for OSS duplex device (%s).", name); - goto error; - } - fd = open(name, O_RDWR | O_NONBLOCK); - } - else - fd = open(name, O_RDONLY | O_NONBLOCK); - } - - if (fd == -1) { - if (errno == EBUSY || errno == EAGAIN) - sprintf(message, "RtAudio: OSS device (%s) is busy and cannot be opened.", - name); - else - sprintf(message, "RtAudio: OSS device (%s) cannot be opened.", name); - goto error; - } - - // Now reopen in blocking mode. - close(fd); - if (mode == PLAYBACK) - fd = open(name, O_WRONLY | O_SYNC); - else { // mode == RECORD - if (stream->mode == PLAYBACK && stream->device[0] == device) - fd = open(name, O_RDWR | O_SYNC); - else - fd = open(name, O_RDONLY | O_SYNC); - } - - if (fd == -1) { - sprintf(message, "RtAudio: OSS device (%s) cannot be opened.", name); - goto error; - } - - // Get the sample format mask - int mask; - if (ioctl(fd, SNDCTL_DSP_GETFMTS, &mask) == -1) { - close(fd); - sprintf(message, "RtAudio: OSS device (%s) can't get supported audio formats.", - name); - goto error; - } - - // Determine how to set the device format. - stream->userFormat = format; - device_format = -1; - stream->doByteSwap[mode] = false; - if (format == RTAUDIO_SINT8) { - if (mask & AFMT_S8) { - device_format = AFMT_S8; - stream->deviceFormat[mode] = RTAUDIO_SINT8; - } - } - else if (format == RTAUDIO_SINT16) { - if (mask & AFMT_S16_NE) { - device_format = AFMT_S16_NE; - stream->deviceFormat[mode] = RTAUDIO_SINT16; - } -#if BYTE_ORDER == LITTLE_ENDIAN - else if (mask & AFMT_S16_BE) { - device_format = AFMT_S16_BE; - stream->deviceFormat[mode] = RTAUDIO_SINT16; - stream->doByteSwap[mode] = true; - } -#else - else if (mask & AFMT_S16_LE) { - device_format = AFMT_S16_LE; - stream->deviceFormat[mode] = RTAUDIO_SINT16; - stream->doByteSwap[mode] = true; - } -#endif - } -#if defined (AFMT_S32_NE) && defined (AFMT_S32_LE) && defined (AFMT_S32_BE) - else if (format == RTAUDIO_SINT32) { - if (mask & AFMT_S32_NE) { - device_format = AFMT_S32_NE; - stream->deviceFormat[mode] = RTAUDIO_SINT32; - } -#if BYTE_ORDER == LITTLE_ENDIAN - else if (mask & AFMT_S32_BE) { - device_format = AFMT_S32_BE; - stream->deviceFormat[mode] = RTAUDIO_SINT32; - stream->doByteSwap[mode] = true; - } -#else - else if (mask & AFMT_S32_LE) { - device_format = AFMT_S32_LE; - stream->deviceFormat[mode] = RTAUDIO_SINT32; - stream->doByteSwap[mode] = true; - } -#endif - } -#endif - - if (device_format == -1) { - // The user requested format is not natively supported by the device. - if (mask & AFMT_S16_NE) { - device_format = AFMT_S16_NE; - stream->deviceFormat[mode] = RTAUDIO_SINT16; - } -#if BYTE_ORDER == LITTLE_ENDIAN - else if (mask & AFMT_S16_BE) { - device_format = AFMT_S16_BE; - stream->deviceFormat[mode] = RTAUDIO_SINT16; - stream->doByteSwap[mode] = true; - } -#else - else if (mask & AFMT_S16_LE) { - device_format = AFMT_S16_LE; - stream->deviceFormat[mode] = RTAUDIO_SINT16; - stream->doByteSwap[mode] = true; - } -#endif -#if defined (AFMT_S32_NE) && defined (AFMT_S32_LE) && defined (AFMT_S32_BE) - else if (mask & AFMT_S32_NE) { - device_format = AFMT_S32_NE; - stream->deviceFormat[mode] = RTAUDIO_SINT32; - } -#if BYTE_ORDER == LITTLE_ENDIAN - else if (mask & AFMT_S32_BE) { - device_format = AFMT_S32_BE; - stream->deviceFormat[mode] = RTAUDIO_SINT32; - stream->doByteSwap[mode] = true; - } -#else - else if (mask & AFMT_S32_LE) { - device_format = AFMT_S32_LE; - stream->deviceFormat[mode] = RTAUDIO_SINT32; - stream->doByteSwap[mode] = true; - } -#endif -#endif - else if (mask & AFMT_S8) { - device_format = AFMT_S8; - stream->deviceFormat[mode] = RTAUDIO_SINT8; - } - } - - if (stream->deviceFormat[mode] == 0) { - // This really shouldn't happen ... - close(fd); - sprintf(message, "RtAudio: OSS device (%s) data format not supported by RtAudio.", - name); - goto error; - } - - // Determine the number of channels for this device. Note that the - // channel value requested by the user might be < min_X_Channels. - stream->nUserChannels[mode] = channels; - device_channels = channels; - if (mode == PLAYBACK) { - if (channels < devices[device].minOutputChannels) - device_channels = devices[device].minOutputChannels; - } - else { // mode == RECORD - if (stream->mode == PLAYBACK && stream->device[0] == device) { - // We're doing duplex setup here. - if (channels < devices[device].minDuplexChannels) - device_channels = devices[device].minDuplexChannels; - } - else { - if (channels < devices[device].minInputChannels) - device_channels = devices[device].minInputChannels; - } - } - stream->nDeviceChannels[mode] = device_channels; - - // Attempt to set the buffer size. According to OSS, the minimum - // number of buffers is two. The supposed minimum buffer size is 16 - // bytes, so that will be our lower bound. The argument to this - // call is in the form 0xMMMMSSSS (hex), where the buffer size (in - // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM. - // We'll check the actual value used near the end of the setup - // procedure. - buffer_bytes = *bufferSize * formatBytes(stream->deviceFormat[mode]) * device_channels; - if (buffer_bytes < 16) buffer_bytes = 16; - buffers = numberOfBuffers; - if (buffers < 2) buffers = 2; - temp = ((int) buffers << 16) + (int)(log10((double)buffer_bytes)/log10(2.0)); - if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &temp)) { - close(fd); - sprintf(message, "RtAudio: OSS error setting fragment size for device (%s).", - name); - goto error; - } - stream->nBuffers = buffers; - - // Set the data format. - temp = device_format; - if (ioctl(fd, SNDCTL_DSP_SETFMT, &device_format) == -1 || device_format != temp) { - close(fd); - sprintf(message, "RtAudio: OSS error setting data format for device (%s).", - name); - goto error; - } - - // Set the number of channels. - temp = device_channels; - if (ioctl(fd, SNDCTL_DSP_CHANNELS, &device_channels) == -1 || device_channels != temp) { - close(fd); - sprintf(message, "RtAudio: OSS error setting %d channels on device (%s).", - temp, name); - goto error; - } - - // Set the sample rate. - srate = sampleRate; - temp = srate; - if (ioctl(fd, SNDCTL_DSP_SPEED, &srate) == -1) { - close(fd); - sprintf(message, "RtAudio: OSS error setting sample rate = %d on device (%s).", - temp, name); - goto error; - } - - // Verify the sample rate setup worked. - if (abs(srate - temp) > 100) { - close(fd); - sprintf(message, "RtAudio: OSS error ... audio device (%s) doesn't support sample rate of %d.", - name, temp); - goto error; - } - stream->sampleRate = sampleRate; - - if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &buffer_bytes) == -1) { - close(fd); - sprintf(message, "RtAudio: OSS error getting buffer size for device (%s).", - name); - goto error; - } - - // Save buffer size (in sample frames). - *bufferSize = buffer_bytes / (formatBytes(stream->deviceFormat[mode]) * device_channels); - stream->bufferSize = *bufferSize; - - if (mode == RECORD && stream->mode == PLAYBACK && - stream->device[0] == device) { - // We're doing duplex setup here. - stream->deviceFormat[0] = stream->deviceFormat[1]; - stream->nDeviceChannels[0] = device_channels; - } - - // Set flags for buffer conversion - stream->doConvertBuffer[mode] = false; - if (stream->userFormat != stream->deviceFormat[mode]) - stream->doConvertBuffer[mode] = true; - if (stream->nUserChannels[mode] < stream->nDeviceChannels[mode]) - stream->doConvertBuffer[mode] = true; - - // Allocate necessary internal buffers - if ( stream->nUserChannels[0] != stream->nUserChannels[1] ) { - - long buffer_bytes; - if (stream->nUserChannels[0] >= stream->nUserChannels[1]) - buffer_bytes = stream->nUserChannels[0]; - else - buffer_bytes = stream->nUserChannels[1]; - - buffer_bytes *= *bufferSize * formatBytes(stream->userFormat); - if (stream->userBuffer) free(stream->userBuffer); - stream->userBuffer = (char *) calloc(buffer_bytes, 1); - if (stream->userBuffer == NULL) { - close(fd); - sprintf(message, "RtAudio: OSS error allocating user buffer memory (%s).", - name); - goto error; - } - } - - if ( stream->doConvertBuffer[mode] ) { - - long buffer_bytes; - bool makeBuffer = true; - if ( mode == PLAYBACK ) - buffer_bytes = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); - else { // mode == RECORD - buffer_bytes = stream->nDeviceChannels[1] * formatBytes(stream->deviceFormat[1]); - if ( stream->mode == PLAYBACK ) { - long bytes_out = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); - if ( buffer_bytes > bytes_out ) - buffer_bytes = (buffer_bytes > bytes_out) ? buffer_bytes : bytes_out; - else - makeBuffer = false; - } - } - - if ( makeBuffer ) { - buffer_bytes *= *bufferSize; - if (stream->deviceBuffer) free(stream->deviceBuffer); - stream->deviceBuffer = (char *) calloc(buffer_bytes, 1); - if (stream->deviceBuffer == NULL) { - close(fd); - free(stream->userBuffer); - sprintf(message, "RtAudio: OSS error allocating device buffer memory (%s).", - name); - goto error; - } - } - } - - stream->device[mode] = device; - stream->handle[mode] = fd; - stream->state = STREAM_STOPPED; - if ( stream->mode == PLAYBACK && mode == RECORD ) { - stream->mode = DUPLEX; - if (stream->device[0] == device) - stream->handle[0] = fd; - } - else - stream->mode = mode; - - return SUCCESS; - - error: - if (stream->handle[0]) { - close(stream->handle[0]); - stream->handle[0] = 0; - } - error(RtAudioError::WARNING); - return FAILURE; -} - -void RtAudio :: cancelStreamCallback(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - if (stream->usingCallback) { - stream->usingCallback = false; - pthread_cancel(stream->thread); - pthread_join(stream->thread, NULL); - stream->thread = 0; - stream->callback = NULL; - stream->userData = NULL; - } -} - -void RtAudio :: closeStream(int streamID) -{ - // We don't want an exception to be thrown here because this - // function is called by our class destructor. So, do our own - // streamID check. - if ( streams.find( streamID ) == streams.end() ) { - sprintf(message, "RtAudio: invalid stream identifier!"); - error(RtAudioError::WARNING); - return; - } - - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) streams[streamID]; - - if (stream->usingCallback) { - pthread_cancel(stream->thread); - pthread_join(stream->thread, NULL); - } - - if (stream->state == STREAM_RUNNING) { - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) - ioctl(stream->handle[0], SNDCTL_DSP_RESET, 0); - if (stream->mode == RECORD || stream->mode == DUPLEX) - ioctl(stream->handle[1], SNDCTL_DSP_RESET, 0); - } - - pthread_mutex_destroy(&stream->mutex); - - if (stream->handle[0]) - close(stream->handle[0]); - - if (stream->handle[1]) - close(stream->handle[1]); - - if (stream->userBuffer) - free(stream->userBuffer); - - if (stream->deviceBuffer) - free(stream->deviceBuffer); - - free(stream); - streams.erase(streamID); -} - -void RtAudio :: startStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - stream->state = STREAM_RUNNING; - - // No need to do anything else here ... OSS automatically starts when fed samples. -} - -void RtAudio :: stopStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_STOPPED) - goto unlock; - - int err; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - err = ioctl(stream->handle[0], SNDCTL_DSP_SYNC, 0); - if (err < -1) { - sprintf(message, "RtAudio: OSS error stopping device (%s).", - devices[stream->device[0]].name); - error(RtAudioError::DRIVER_ERROR); - } - } - else { - err = ioctl(stream->handle[1], SNDCTL_DSP_SYNC, 0); - if (err < -1) { - sprintf(message, "RtAudio: OSS error stopping device (%s).", - devices[stream->device[1]].name); - error(RtAudioError::DRIVER_ERROR); - } - } - stream->state = STREAM_STOPPED; - - unlock: - MUTEX_UNLOCK(&stream->mutex); -} - -void RtAudio :: abortStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_STOPPED) - goto unlock; - - int err; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - err = ioctl(stream->handle[0], SNDCTL_DSP_RESET, 0); - if (err < -1) { - sprintf(message, "RtAudio: OSS error aborting device (%s).", - devices[stream->device[0]].name); - error(RtAudioError::DRIVER_ERROR); - } - } - else { - err = ioctl(stream->handle[1], SNDCTL_DSP_RESET, 0); - if (err < -1) { - sprintf(message, "RtAudio: OSS error aborting device (%s).", - devices[stream->device[1]].name); - error(RtAudioError::DRIVER_ERROR); - } - } - stream->state = STREAM_STOPPED; - - unlock: - MUTEX_UNLOCK(&stream->mutex); -} - -int RtAudio :: streamWillBlock(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - int bytes, channels = 0, frames = 0; - if (stream->state == STREAM_STOPPED) - goto unlock; - - audio_buf_info info; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - ioctl(stream->handle[0], SNDCTL_DSP_GETOSPACE, &info); - bytes = info.bytes; - channels = stream->nDeviceChannels[0]; - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - ioctl(stream->handle[1], SNDCTL_DSP_GETISPACE, &info); - if (stream->mode == DUPLEX ) { - bytes = (bytes < info.bytes) ? bytes : info.bytes; - channels = stream->nDeviceChannels[0]; - } - else { - bytes = info.bytes; - channels = stream->nDeviceChannels[1]; - } - } - - frames = (int) (bytes / (channels * formatBytes(stream->deviceFormat[0]))); - frames -= stream->bufferSize; - if (frames < 0) frames = 0; - - unlock: - MUTEX_UNLOCK(&stream->mutex); - return frames; -} - -void RtAudio :: tickStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - int stopStream = 0; - if (stream->state == STREAM_STOPPED) { - if (stream->usingCallback) usleep(50000); // sleep 50 milliseconds - return; - } - else if (stream->usingCallback) { - stopStream = stream->callback(stream->userBuffer, stream->bufferSize, stream->userData); - } - - MUTEX_LOCK(&stream->mutex); - - // The state might change while waiting on a mutex. - if (stream->state == STREAM_STOPPED) - goto unlock; - - int result; - char *buffer; - int samples; - RTAUDIO_FORMAT format; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - - // Setup parameters and do buffer conversion if necessary. - if (stream->doConvertBuffer[0]) { - convertStreamBuffer(stream, PLAYBACK); - buffer = stream->deviceBuffer; - samples = stream->bufferSize * stream->nDeviceChannels[0]; - format = stream->deviceFormat[0]; - } - else { - buffer = stream->userBuffer; - samples = stream->bufferSize * stream->nUserChannels[0]; - format = stream->userFormat; - } - - // Do byte swapping if necessary. - if (stream->doByteSwap[0]) - byteSwapBuffer(buffer, samples, format); - - // Write samples to device. - result = write(stream->handle[0], buffer, samples * formatBytes(format)); - - if (result == -1) { - // This could be an underrun, but the basic OSS API doesn't provide a means for determining that. - sprintf(message, "RtAudio: OSS audio write error for device (%s).", - devices[stream->device[0]].name); - error(RtAudioError::DRIVER_ERROR); - } - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - - // Setup parameters. - if (stream->doConvertBuffer[1]) { - buffer = stream->deviceBuffer; - samples = stream->bufferSize * stream->nDeviceChannels[1]; - format = stream->deviceFormat[1]; - } - else { - buffer = stream->userBuffer; - samples = stream->bufferSize * stream->nUserChannels[1]; - format = stream->userFormat; - } - - // Read samples from device. - result = read(stream->handle[1], buffer, samples * formatBytes(format)); - - if (result == -1) { - // This could be an overrun, but the basic OSS API doesn't provide a means for determining that. - sprintf(message, "RtAudio: OSS audio read error for device (%s).", - devices[stream->device[1]].name); - error(RtAudioError::DRIVER_ERROR); - } - - // Do byte swapping if necessary. - if (stream->doByteSwap[1]) - byteSwapBuffer(buffer, samples, format); - - // Do buffer conversion if necessary. - if (stream->doConvertBuffer[1]) - convertStreamBuffer(stream, RECORD); - } - - unlock: - MUTEX_UNLOCK(&stream->mutex); - - if (stream->usingCallback && stopStream) - this->stopStream(streamID); -} - -extern "C" void *callbackHandler(void *ptr) -{ - RtAudio *object = thread_info.object; - int stream = thread_info.streamID; - bool *usingCallback = (bool *) ptr; - - while ( *usingCallback ) { - pthread_testcancel(); - try { - object->tickStream(stream); - } - catch (RtAudioError &exception) { - fprintf(stderr, "\nCallback thread error (%s) ... closing thread.\n\n", - exception.getMessage()); - break; - } - } - - return 0; -} - -//******************** End of __LINUX_OSS_ *********************// - -#elif defined(__WINDOWS_DS_) // Windows DirectSound API - -#include - -// Declarations for utility functions, callbacks, and structures -// specific to the DirectSound implementation. -static bool CALLBACK deviceCountCallback(LPGUID lpguid, - LPCSTR lpcstrDescription, - LPCSTR lpcstrModule, - LPVOID lpContext); - -static bool CALLBACK deviceInfoCallback(LPGUID lpguid, - LPCSTR lpcstrDescription, - LPCSTR lpcstrModule, - LPVOID lpContext); - -static char* getErrorString(int code); - -struct enum_info { - char name[64]; - LPGUID id; - bool isInput; - bool isValid; -}; - -// RtAudio methods for DirectSound implementation. -void RtAudio :: initialize(void) -{ - int i, ins = 0, outs = 0, count = 0; - int index = 0; - HRESULT result; - nDevices = 0; - - // Count DirectSound devices. - result = DirectSoundEnumerate((LPDSENUMCALLBACK)deviceCountCallback, &outs); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to enumerate through sound playback devices: %s.", - getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // Count DirectSoundCapture devices. - result = DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)deviceCountCallback, &ins); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to enumerate through sound capture devices: %s.", - getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - count = ins + outs; - if (count == 0) return; - - std::vector info(count); - for (i=0; i 0) { - nDevices = 1; - index = 1; - } - - // Non-default devices are listed separately. - for (i=0; i= nDevices ) { - sprintf(message, "RtAudio: device (%s) indexing error in DirectSound probeDeviceInfo().", - info->name); - error(RtAudioError::WARNING); - return; - } - - // Do capture probe first. If this is not the default device (index - // = 0) _and_ GUID = NULL, then the capture handle is invalid. - if ( index != 0 && info->id[1] == NULL ) - goto playback_probe; - - LPDIRECTSOUNDCAPTURE input; - result = DirectSoundCaptureCreate( info->id[0], &input, NULL ); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Could not create DirectSound capture object (%s): %s.", - info->name, getErrorString(result)); - error(RtAudioError::WARNING); - goto playback_probe; - } - - DSCCAPS in_caps; - in_caps.dwSize = sizeof(in_caps); - result = input->GetCaps( &in_caps ); - if ( FAILED(result) ) { - input->Release(); - sprintf(message, "RtAudio: Could not get DirectSound capture capabilities (%s): %s.", - info->name, getErrorString(result)); - error(RtAudioError::WARNING); - goto playback_probe; - } - - // Get input channel information. - info->minInputChannels = 1; - info->maxInputChannels = in_caps.dwChannels; - - // Get sample rate and format information. - if( in_caps.dwChannels == 2 ) { - if( in_caps.dwFormats & WAVE_FORMAT_1S16 ) info->nativeFormats |= RTAUDIO_SINT16; - if( in_caps.dwFormats & WAVE_FORMAT_2S16 ) info->nativeFormats |= RTAUDIO_SINT16; - if( in_caps.dwFormats & WAVE_FORMAT_4S16 ) info->nativeFormats |= RTAUDIO_SINT16; - if( in_caps.dwFormats & WAVE_FORMAT_1S08 ) info->nativeFormats |= RTAUDIO_SINT8; - if( in_caps.dwFormats & WAVE_FORMAT_2S08 ) info->nativeFormats |= RTAUDIO_SINT8; - if( in_caps.dwFormats & WAVE_FORMAT_4S08 ) info->nativeFormats |= RTAUDIO_SINT8; - - if ( info->nativeFormats & RTAUDIO_SINT16 ) { - if( in_caps.dwFormats & WAVE_FORMAT_1S16 ) info->sampleRates[info->nSampleRates++] = 11025; - if( in_caps.dwFormats & WAVE_FORMAT_2S16 ) info->sampleRates[info->nSampleRates++] = 22050; - if( in_caps.dwFormats & WAVE_FORMAT_4S16 ) info->sampleRates[info->nSampleRates++] = 44100; - } - else if ( info->nativeFormats & RTAUDIO_SINT8 ) { - if( in_caps.dwFormats & WAVE_FORMAT_1S08 ) info->sampleRates[info->nSampleRates++] = 11025; - if( in_caps.dwFormats & WAVE_FORMAT_2S08 ) info->sampleRates[info->nSampleRates++] = 22050; - if( in_caps.dwFormats & WAVE_FORMAT_4S08 ) info->sampleRates[info->nSampleRates++] = 44100; - } - } - else if ( in_caps.dwChannels == 1 ) { - if( in_caps.dwFormats & WAVE_FORMAT_1M16 ) info->nativeFormats |= RTAUDIO_SINT16; - if( in_caps.dwFormats & WAVE_FORMAT_2M16 ) info->nativeFormats |= RTAUDIO_SINT16; - if( in_caps.dwFormats & WAVE_FORMAT_4M16 ) info->nativeFormats |= RTAUDIO_SINT16; - if( in_caps.dwFormats & WAVE_FORMAT_1M08 ) info->nativeFormats |= RTAUDIO_SINT8; - if( in_caps.dwFormats & WAVE_FORMAT_2M08 ) info->nativeFormats |= RTAUDIO_SINT8; - if( in_caps.dwFormats & WAVE_FORMAT_4M08 ) info->nativeFormats |= RTAUDIO_SINT8; - - if ( info->nativeFormats & RTAUDIO_SINT16 ) { - if( in_caps.dwFormats & WAVE_FORMAT_1M16 ) info->sampleRates[info->nSampleRates++] = 11025; - if( in_caps.dwFormats & WAVE_FORMAT_2M16 ) info->sampleRates[info->nSampleRates++] = 22050; - if( in_caps.dwFormats & WAVE_FORMAT_4M16 ) info->sampleRates[info->nSampleRates++] = 44100; - } - else if ( info->nativeFormats & RTAUDIO_SINT8 ) { - if( in_caps.dwFormats & WAVE_FORMAT_1M08 ) info->sampleRates[info->nSampleRates++] = 11025; - if( in_caps.dwFormats & WAVE_FORMAT_2M08 ) info->sampleRates[info->nSampleRates++] = 22050; - if( in_caps.dwFormats & WAVE_FORMAT_4M08 ) info->sampleRates[info->nSampleRates++] = 44100; - } - } - else info->minInputChannels = 0; // technically, this would be an error - - input->Release(); - - playback_probe: - LPDIRECTSOUND output; - DSCAPS out_caps; - - // Now do playback probe. If this is not the default device (index - // = 0) _and_ GUID = NULL, then the playback handle is invalid. - if ( index != 0 && info->id[0] == NULL ) - goto check_parameters; - - result = DirectSoundCreate( info->id[0], &output, NULL ); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Could not create DirectSound playback object (%s): %s.", - info->name, getErrorString(result)); - error(RtAudioError::WARNING); - goto check_parameters; - } - - out_caps.dwSize = sizeof(out_caps); - result = output->GetCaps( &out_caps ); - if ( FAILED(result) ) { - output->Release(); - sprintf(message, "RtAudio: Could not get DirectSound playback capabilities (%s): %s.", - info->name, getErrorString(result)); - error(RtAudioError::WARNING); - goto check_parameters; - } - - // Get output channel information. - info->minOutputChannels = 1; - info->maxOutputChannels = ( out_caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; - - // Get sample rate information. Use capture device rate information - // if it exists. - if ( info->nSampleRates == 0 ) { - info->sampleRates[0] = (int) out_caps.dwMinSecondarySampleRate; - info->sampleRates[1] = (int) out_caps.dwMaxSecondarySampleRate; - if ( out_caps.dwFlags & DSCAPS_CONTINUOUSRATE ) - info->nSampleRates = -1; - else if ( out_caps.dwMinSecondarySampleRate == out_caps.dwMaxSecondarySampleRate ) { - if ( out_caps.dwMinSecondarySampleRate == 0 ) { - // This is a bogus driver report ... fake the range and cross - // your fingers. - info->sampleRates[0] = 11025; - info->sampleRates[1] = 48000; - info->nSampleRates = -1; /* continuous range */ - sprintf(message, "RtAudio: bogus sample rates reported by DirectSound driver ... using defaults (%s).", - info->name); - error(RtAudioError::WARNING); - } - else { - info->nSampleRates = 1; - } - } - else if ( (out_caps.dwMinSecondarySampleRate < 1000.0) && - (out_caps.dwMaxSecondarySampleRate > 50000.0) ) { - // This is a bogus driver report ... support for only two - // distant rates. We'll assume this is a range. - info->nSampleRates = -1; - sprintf(message, "RtAudio: bogus sample rates reported by DirectSound driver ... using range (%s).", - info->name); - error(RtAudioError::WARNING); - } - else info->nSampleRates = 2; - } - else { - // Check input rates against output rate range - for ( int i=info->nSampleRates-1; i>=0; i-- ) { - if ( info->sampleRates[i] <= out_caps.dwMaxSecondarySampleRate ) - break; - info->nSampleRates--; - } - while ( info->sampleRates[0] < out_caps.dwMinSecondarySampleRate ) { - info->nSampleRates--; - for ( int i=0; inSampleRates; i++) - info->sampleRates[i] = info->sampleRates[i+1]; - if ( info->nSampleRates <= 0 ) break; - } - } - - // Get format information. - if ( out_caps.dwFlags & DSCAPS_PRIMARY16BIT ) info->nativeFormats |= RTAUDIO_SINT16; - if ( out_caps.dwFlags & DSCAPS_PRIMARY8BIT ) info->nativeFormats |= RTAUDIO_SINT8; - - output->Release(); - - check_parameters: - if ( info->maxInputChannels == 0 && info->maxOutputChannels == 0 ) - return; - if ( info->nSampleRates == 0 || info->nativeFormats == 0 ) - return; - - // Determine duplex status. - if (info->maxInputChannels < info->maxOutputChannels) - info->maxDuplexChannels = info->maxInputChannels; - else - info->maxDuplexChannels = info->maxOutputChannels; - if (info->minInputChannels < info->minOutputChannels) - info->minDuplexChannels = info->minInputChannels; - else - info->minDuplexChannels = info->minOutputChannels; - - if ( info->maxDuplexChannels > 0 ) info->hasDuplexSupport = true; - else info->hasDuplexSupport = false; - - info->probed = true; - - return; -} - -bool RtAudio :: probeDeviceOpen(int device, RTAUDIO_STREAM *stream, - STREAM_MODE mode, int channels, - int sampleRate, RTAUDIO_FORMAT format, - int *bufferSize, int numberOfBuffers) -{ - HRESULT result; - HWND hWnd = GetForegroundWindow(); - // According to a note in PortAudio, using GetDesktopWindow() - // instead of GetForegroundWindow() is supposed to avoid problems - // that occur when the application's window is not the foreground - // window. Also, if the application window closes before the - // DirectSound buffer, DirectSound can crash. However, for console - // applications, no sound was produced when using GetDesktopWindow(). - long buffer_size; - LPVOID audioPtr; - DWORD dataLen; - int nBuffers; - - // Check the numberOfBuffers parameter and limit the lowest value to - // two. This is a judgement call and a value of two is probably too - // low for capture, but it should work for playback. - if (numberOfBuffers < 2) - nBuffers = 2; - else - nBuffers = numberOfBuffers; - - // Define the wave format structure (16-bit PCM, srate, channels) - WAVEFORMATEX waveFormat; - ZeroMemory(&waveFormat, sizeof(WAVEFORMATEX)); - waveFormat.wFormatTag = WAVE_FORMAT_PCM; - waveFormat.nChannels = channels; - waveFormat.nSamplesPerSec = (unsigned long) sampleRate; - - // Determine the data format. - if ( devices[device].nativeFormats ) { // 8-bit and/or 16-bit support - if ( format == RTAUDIO_SINT8 ) { - if ( devices[device].nativeFormats & RTAUDIO_SINT8 ) - waveFormat.wBitsPerSample = 8; - else - waveFormat.wBitsPerSample = 16; - } - else { - if ( devices[device].nativeFormats & RTAUDIO_SINT16 ) - waveFormat.wBitsPerSample = 16; - else - waveFormat.wBitsPerSample = 8; - } - } - else { - sprintf(message, "RtAudio: no reported data formats for DirectSound device (%s).", - devices[device].name); - error(RtAudioError::WARNING); - return FAILURE; - } - - waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; - waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; - - if ( mode == PLAYBACK ) { - - LPGUID id = devices[device].id[0]; - LPDIRECTSOUND object; - LPDIRECTSOUNDBUFFER buffer; - DSBUFFERDESC bufferDescription; - - result = DirectSoundCreate( id, &object, NULL ); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Could not create DirectSound playback object (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set cooperative level to DSSCL_EXCLUSIVE - result = object->SetCooperativeLevel(hWnd, DSSCL_EXCLUSIVE); - if ( FAILED(result) ) { - object->Release(); - sprintf(message, "RtAudio: Unable to set DirectSound cooperative level (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Even though we will write to the secondary buffer, we need to - // access the primary buffer to set the correct output format. - // The default is 8-bit, 22 kHz! - // Setup the DS primary buffer description. - ZeroMemory(&bufferDescription, sizeof(DSBUFFERDESC)); - bufferDescription.dwSize = sizeof(DSBUFFERDESC); - bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - // Obtain the primary buffer - result = object->CreateSoundBuffer(&bufferDescription, &buffer, NULL); - if ( FAILED(result) ) { - object->Release(); - sprintf(message, "RtAudio: Unable to access DS primary buffer (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set the primary DS buffer sound format. - result = buffer->SetFormat(&waveFormat); - if ( FAILED(result) ) { - object->Release(); - sprintf(message, "RtAudio: Unable to set DS primary buffer format (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Setup the secondary DS buffer description. - buffer_size = channels * *bufferSize * nBuffers * waveFormat.wBitsPerSample / 8; - ZeroMemory(&bufferDescription, sizeof(DSBUFFERDESC)); - bufferDescription.dwSize = sizeof(DSBUFFERDESC); - bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | - DSBCAPS_GETCURRENTPOSITION2 | - DSBCAPS_LOCHARDWARE ); // Force hardware mixing - bufferDescription.dwBufferBytes = buffer_size; - bufferDescription.lpwfxFormat = &waveFormat; - - // Try to create the secondary DS buffer. If that doesn't work, - // try to use software mixing. Otherwise, there's a problem. - result = object->CreateSoundBuffer(&bufferDescription, &buffer, NULL); - if ( FAILED(result) ) { - bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | - DSBCAPS_GETCURRENTPOSITION2 | - DSBCAPS_LOCSOFTWARE ); // Force software mixing - result = object->CreateSoundBuffer(&bufferDescription, &buffer, NULL); - if ( FAILED(result) ) { - object->Release(); - sprintf(message, "RtAudio: Unable to create secondary DS buffer (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - } - - // Get the buffer size ... might be different from what we specified. - DSBCAPS dsbcaps; - dsbcaps.dwSize = sizeof(DSBCAPS); - buffer->GetCaps(&dsbcaps); - buffer_size = dsbcaps.dwBufferBytes; - - // Lock the DS buffer - result = buffer->Lock(0, buffer_size, &audioPtr, &dataLen, NULL, NULL, 0); - if ( FAILED(result) ) { - object->Release(); - sprintf(message, "RtAudio: Unable to lock DS buffer (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Zero the DS buffer - ZeroMemory(audioPtr, dataLen); - - // Unlock the DS buffer - result = buffer->Unlock(audioPtr, dataLen, NULL, 0); - if ( FAILED(result) ) { - object->Release(); - sprintf(message, "RtAudio: Unable to unlock DS buffer(%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - stream->handle[0].object = (void *) object; - stream->handle[0].buffer = (void *) buffer; - stream->nDeviceChannels[0] = channels; - } - - if ( mode == RECORD ) { - - LPGUID id = devices[device].id[1]; - LPDIRECTSOUNDCAPTURE object; - LPDIRECTSOUNDCAPTUREBUFFER buffer; - DSCBUFFERDESC bufferDescription; - - result = DirectSoundCaptureCreate( id, &object, NULL ); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Could not create DirectSound capture object (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Setup the secondary DS buffer description. - buffer_size = channels * *bufferSize * nBuffers * waveFormat.wBitsPerSample / 8; - ZeroMemory(&bufferDescription, sizeof(DSCBUFFERDESC)); - bufferDescription.dwSize = sizeof(DSCBUFFERDESC); - bufferDescription.dwFlags = 0; - bufferDescription.dwReserved = 0; - bufferDescription.dwBufferBytes = buffer_size; - bufferDescription.lpwfxFormat = &waveFormat; - - // Create the capture buffer. - result = object->CreateCaptureBuffer(&bufferDescription, &buffer, NULL); - if ( FAILED(result) ) { - object->Release(); - sprintf(message, "RtAudio: Unable to create DS capture buffer (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Lock the capture buffer - result = buffer->Lock(0, buffer_size, &audioPtr, &dataLen, NULL, NULL, 0); - if ( FAILED(result) ) { - object->Release(); - sprintf(message, "RtAudio: Unable to lock DS capture buffer (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Zero the buffer - ZeroMemory(audioPtr, dataLen); - - // Unlock the buffer - result = buffer->Unlock(audioPtr, dataLen, NULL, 0); - if ( FAILED(result) ) { - object->Release(); - sprintf(message, "RtAudio: Unable to unlock DS capture buffer (%s): %s.", - devices[device].name, getErrorString(result)); - error(RtAudioError::WARNING); - return FAILURE; - } - - stream->handle[1].object = (void *) object; - stream->handle[1].buffer = (void *) buffer; - stream->nDeviceChannels[1] = channels; - } - - stream->userFormat = format; - if ( waveFormat.wBitsPerSample == 8 ) - stream->deviceFormat[mode] = RTAUDIO_SINT8; - else - stream->deviceFormat[mode] = RTAUDIO_SINT16; - stream->nUserChannels[mode] = channels; - *bufferSize = buffer_size / (channels * nBuffers * waveFormat.wBitsPerSample / 8); - stream->bufferSize = *bufferSize; - - // Set flags for buffer conversion - stream->doConvertBuffer[mode] = false; - if (stream->userFormat != stream->deviceFormat[mode]) - stream->doConvertBuffer[mode] = true; - if (stream->nUserChannels[mode] < stream->nDeviceChannels[mode]) - stream->doConvertBuffer[mode] = true; - - // Allocate necessary internal buffers - if ( stream->nUserChannels[0] != stream->nUserChannels[1] ) { - - long buffer_bytes; - if (stream->nUserChannels[0] >= stream->nUserChannels[1]) - buffer_bytes = stream->nUserChannels[0]; - else - buffer_bytes = stream->nUserChannels[1]; - - buffer_bytes *= *bufferSize * formatBytes(stream->userFormat); - if (stream->userBuffer) free(stream->userBuffer); - stream->userBuffer = (char *) calloc(buffer_bytes, 1); - if (stream->userBuffer == NULL) - goto memory_error; - } - - if ( stream->doConvertBuffer[mode] ) { - - long buffer_bytes; - bool makeBuffer = true; - if ( mode == PLAYBACK ) - buffer_bytes = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); - else { // mode == RECORD - buffer_bytes = stream->nDeviceChannels[1] * formatBytes(stream->deviceFormat[1]); - if ( stream->mode == PLAYBACK ) { - long bytes_out = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); - if ( buffer_bytes > bytes_out ) - buffer_bytes = (buffer_bytes > bytes_out) ? buffer_bytes : bytes_out; - else - makeBuffer = false; - } - } - - if ( makeBuffer ) { - buffer_bytes *= *bufferSize; - if (stream->deviceBuffer) free(stream->deviceBuffer); - stream->deviceBuffer = (char *) calloc(buffer_bytes, 1); - if (stream->deviceBuffer == NULL) - goto memory_error; - } - } - - stream->device[mode] = device; - stream->state = STREAM_STOPPED; - if ( stream->mode == PLAYBACK && mode == RECORD ) - // We had already set up an output stream. - stream->mode = DUPLEX; - else - stream->mode = mode; - stream->nBuffers = nBuffers; - stream->sampleRate = sampleRate; - - return SUCCESS; - - memory_error: - if (stream->handle[0].object) { - LPDIRECTSOUND object = (LPDIRECTSOUND) stream->handle[0].object; - LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; - if (buffer) { - buffer->Release(); - stream->handle[0].buffer = NULL; - } - object->Release(); - stream->handle[0].object = NULL; - } - if (stream->handle[1].object) { - LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) stream->handle[1].object; - LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; - if (buffer) { - buffer->Release(); - stream->handle[1].buffer = NULL; - } - object->Release(); - stream->handle[1].object = NULL; - } - if (stream->userBuffer) { - free(stream->userBuffer); - stream->userBuffer = 0; - } - sprintf(message, "RtAudio: error allocating buffer memory (%s).", - devices[device].name); - error(RtAudioError::WARNING); - return FAILURE; -} - -void RtAudio :: cancelStreamCallback(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - if (stream->usingCallback) { - stream->usingCallback = false; - WaitForSingleObject( (HANDLE)stream->thread, INFINITE ); - CloseHandle( (HANDLE)stream->thread ); - stream->thread = 0; - stream->callback = NULL; - stream->userData = NULL; - } -} - -void RtAudio :: closeStream(int streamID) -{ - // We don't want an exception to be thrown here because this - // function is called by our class destructor. So, do our own - // streamID check. - if ( streams.find( streamID ) == streams.end() ) { - sprintf(message, "RtAudio: invalid stream identifier!"); - error(RtAudioError::WARNING); - return; - } - - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) streams[streamID]; - - if (stream->usingCallback) { - stream->usingCallback = false; - WaitForSingleObject( (HANDLE)stream->thread, INFINITE ); - CloseHandle( (HANDLE)stream->thread ); - } - - DeleteCriticalSection(&stream->mutex); - - if (stream->handle[0].object) { - LPDIRECTSOUND object = (LPDIRECTSOUND) stream->handle[0].object; - LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; - if (buffer) { - buffer->Stop(); - buffer->Release(); - } - object->Release(); - } - - if (stream->handle[1].object) { - LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) stream->handle[1].object; - LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; - if (buffer) { - buffer->Stop(); - buffer->Release(); - } - object->Release(); - } - - if (stream->userBuffer) - free(stream->userBuffer); - - if (stream->deviceBuffer) - free(stream->deviceBuffer); - - free(stream); - streams.erase(streamID); -} - -void RtAudio :: startStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_RUNNING) - goto unlock; - - HRESULT result; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; - result = buffer->Play(0, 0, DSBPLAY_LOOPING ); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to start DS buffer (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; - result = buffer->Start(DSCBSTART_LOOPING ); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to start DS capture buffer (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - } - stream->state = STREAM_RUNNING; - - unlock: - MUTEX_UNLOCK(&stream->mutex); -} - -void RtAudio :: stopStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_STOPPED) { - MUTEX_UNLOCK(&stream->mutex); - return; - } - - // There is no specific DirectSound API call to "drain" a buffer - // before stopping. We can hack this for playback by writing zeroes - // for another bufferSize * nBuffers frames. For capture, the - // concept is less clear so we'll repeat what we do in the - // abortStream() case. - HRESULT result; - DWORD dsBufferSize; - LPVOID buffer1 = NULL; - LPVOID buffer2 = NULL; - DWORD bufferSize1 = 0; - DWORD bufferSize2 = 0; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - - DWORD currentPos, safePos; - long buffer_bytes = stream->bufferSize * stream->nDeviceChannels[0]; - buffer_bytes *= formatBytes(stream->deviceFormat[0]); - - LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; - UINT nextWritePos = stream->handle[0].bufferPointer; - dsBufferSize = buffer_bytes * stream->nBuffers; - - // Write zeroes for nBuffer counts. - for (int i=0; inBuffers; i++) { - - // Find out where the read and "safe write" pointers are. - result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset - DWORD endWrite = nextWritePos + buffer_bytes; - - // Check whether the entire write region is behind the play pointer. - while ( currentPos < endWrite ) { - float millis = (endWrite - currentPos) * 900.0; - millis /= ( formatBytes(stream->deviceFormat[0]) * stream->sampleRate); - if ( millis < 1.0 ) millis = 1.0; - Sleep( (DWORD) millis ); - - // Wake up, find out where we are now - result = dsBuffer->GetCurrentPosition( ¤tPos, &safePos ); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset - } - - // Lock free space in the buffer - result = dsBuffer->Lock (nextWritePos, buffer_bytes, &buffer1, - &bufferSize1, &buffer2, &bufferSize2, 0); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to lock DS buffer during playback (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // Zero the free space - ZeroMemory(buffer1, bufferSize1); - if (buffer2 != NULL) ZeroMemory(buffer2, bufferSize2); - - // Update our buffer offset and unlock sound buffer - dsBuffer->Unlock (buffer1, bufferSize1, buffer2, bufferSize2); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to unlock DS buffer during playback (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - nextWritePos = (nextWritePos + bufferSize1 + bufferSize2) % dsBufferSize; - stream->handle[0].bufferPointer = nextWritePos; - } - - // If we play again, start at the beginning of the buffer. - stream->handle[0].bufferPointer = 0; - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; - buffer1 = NULL; - bufferSize1 = 0; - - result = buffer->Stop(); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to stop DS capture buffer (%s): %s", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - dsBufferSize = stream->bufferSize * stream->nDeviceChannels[1]; - dsBufferSize *= formatBytes(stream->deviceFormat[1]) * stream->nBuffers; - - // Lock the buffer and clear it so that if we start to play again, - // we won't have old data playing. - result = buffer->Lock(0, dsBufferSize, &buffer1, &bufferSize1, NULL, NULL, 0); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to lock DS capture buffer (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // Zero the DS buffer - ZeroMemory(buffer1, bufferSize1); - - // Unlock the DS buffer - result = buffer->Unlock(buffer1, bufferSize1, NULL, 0); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to unlock DS capture buffer (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // If we start recording again, we must begin at beginning of buffer. - stream->handle[1].bufferPointer = 0; - } - stream->state = STREAM_STOPPED; - - MUTEX_UNLOCK(&stream->mutex); -} - -void RtAudio :: abortStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_STOPPED) - goto unlock; - - HRESULT result; - long dsBufferSize; - LPVOID audioPtr; - DWORD dataLen; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; - result = buffer->Stop(); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to stop DS buffer (%s): %s", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - dsBufferSize = stream->bufferSize * stream->nDeviceChannels[0]; - dsBufferSize *= formatBytes(stream->deviceFormat[0]) * stream->nBuffers; - - // Lock the buffer and clear it so that if we start to play again, - // we won't have old data playing. - result = buffer->Lock(0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to lock DS buffer (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // Zero the DS buffer - ZeroMemory(audioPtr, dataLen); - - // Unlock the DS buffer - result = buffer->Unlock(audioPtr, dataLen, NULL, 0); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to unlock DS buffer (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // If we start playing again, we must begin at beginning of buffer. - stream->handle[0].bufferPointer = 0; - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; - audioPtr = NULL; - dataLen = 0; - - result = buffer->Stop(); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to stop DS capture buffer (%s): %s", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - dsBufferSize = stream->bufferSize * stream->nDeviceChannels[1]; - dsBufferSize *= formatBytes(stream->deviceFormat[1]) * stream->nBuffers; - - // Lock the buffer and clear it so that if we start to play again, - // we won't have old data playing. - result = buffer->Lock(0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to lock DS capture buffer (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // Zero the DS buffer - ZeroMemory(audioPtr, dataLen); - - // Unlock the DS buffer - result = buffer->Unlock(audioPtr, dataLen, NULL, 0); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to unlock DS capture buffer (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // If we start recording again, we must begin at beginning of buffer. - stream->handle[1].bufferPointer = 0; - } - stream->state = STREAM_STOPPED; - - unlock: - MUTEX_UNLOCK(&stream->mutex); -} - -int RtAudio :: streamWillBlock(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - int frames = 0; - int channels = 1; - if (stream->state == STREAM_STOPPED) - goto unlock; - - HRESULT result; - DWORD currentPos, safePos; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - - LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; - UINT nextWritePos = stream->handle[0].bufferPointer; - channels = stream->nDeviceChannels[0]; - DWORD dsBufferSize = stream->bufferSize * channels; - dsBufferSize *= formatBytes(stream->deviceFormat[0]) * stream->nBuffers; - - // Find out where the read and "safe write" pointers are. - result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset - frames = currentPos - nextWritePos; - frames /= channels * formatBytes(stream->deviceFormat[0]); - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - - LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; - UINT nextReadPos = stream->handle[1].bufferPointer; - channels = stream->nDeviceChannels[1]; - DWORD dsBufferSize = stream->bufferSize * channels; - dsBufferSize *= formatBytes(stream->deviceFormat[1]) * stream->nBuffers; - - // Find out where the write and "safe read" pointers are. - result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to get current DS capture position (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - if ( safePos < nextReadPos ) safePos += dsBufferSize; // unwrap offset - - if (stream->mode == DUPLEX ) { - // Take largest value of the two. - int temp = safePos - nextReadPos; - temp /= channels * formatBytes(stream->deviceFormat[1]); - frames = ( temp > frames ) ? temp : frames; - } - else { - frames = safePos - nextReadPos; - frames /= channels * formatBytes(stream->deviceFormat[1]); - } - } - - frames = stream->bufferSize - frames; - if (frames < 0) frames = 0; - - unlock: - MUTEX_UNLOCK(&stream->mutex); - return frames; -} - -void RtAudio :: tickStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - int stopStream = 0; - if (stream->state == STREAM_STOPPED) { - if (stream->usingCallback) Sleep(50); // sleep 50 milliseconds - return; - } - else if (stream->usingCallback) { - stopStream = stream->callback(stream->userBuffer, stream->bufferSize, stream->userData); - } - - MUTEX_LOCK(&stream->mutex); - - // The state might change while waiting on a mutex. - if (stream->state == STREAM_STOPPED) - goto unlock; - - HRESULT result; - DWORD currentPos, safePos; - LPVOID buffer1, buffer2; - DWORD bufferSize1, bufferSize2; - char *buffer; - long buffer_bytes; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - - // Setup parameters and do buffer conversion if necessary. - if (stream->doConvertBuffer[0]) { - convertStreamBuffer(stream, PLAYBACK); - buffer = stream->deviceBuffer; - buffer_bytes = stream->bufferSize * stream->nDeviceChannels[0]; - buffer_bytes *= formatBytes(stream->deviceFormat[0]); - } - else { - buffer = stream->userBuffer; - buffer_bytes = stream->bufferSize * stream->nUserChannels[0]; - buffer_bytes *= formatBytes(stream->userFormat); - } - - // No byte swapping necessary in DirectSound implementation. - - LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; - UINT nextWritePos = stream->handle[0].bufferPointer; - DWORD dsBufferSize = buffer_bytes * stream->nBuffers; - - // Find out where the read and "safe write" pointers are. - result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset - DWORD endWrite = nextWritePos + buffer_bytes; - - // Check whether the entire write region is behind the play pointer. - while ( currentPos < endWrite ) { - // If we are here, then we must wait until the play pointer gets - // beyond the write region. The approach here is to use the - // Sleep() function to suspend operation until safePos catches - // up. Calculate number of milliseconds to wait as: - // time = distance * (milliseconds/second) * fudgefactor / - // ((bytes/sample) * (samples/second)) - // A "fudgefactor" less than 1 is used because it was found - // that sleeping too long was MUCH worse than sleeping for - // several shorter periods. - float millis = (endWrite - currentPos) * 900.0; - millis /= ( formatBytes(stream->deviceFormat[0]) * stream->sampleRate); - if ( millis < 1.0 ) millis = 1.0; - Sleep( (DWORD) millis ); - - // Wake up, find out where we are now - result = dsBuffer->GetCurrentPosition( ¤tPos, &safePos ); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset - } - - // Lock free space in the buffer - result = dsBuffer->Lock (nextWritePos, buffer_bytes, &buffer1, - &bufferSize1, &buffer2, &bufferSize2, 0); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to lock DS buffer during playback (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // Copy our buffer into the DS buffer - CopyMemory(buffer1, buffer, bufferSize1); - if (buffer2 != NULL) CopyMemory(buffer2, buffer+bufferSize1, bufferSize2); - - // Update our buffer offset and unlock sound buffer - dsBuffer->Unlock (buffer1, bufferSize1, buffer2, bufferSize2); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to unlock DS buffer during playback (%s): %s.", - devices[stream->device[0]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - nextWritePos = (nextWritePos + bufferSize1 + bufferSize2) % dsBufferSize; - stream->handle[0].bufferPointer = nextWritePos; - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - - // Setup parameters. - if (stream->doConvertBuffer[1]) { - buffer = stream->deviceBuffer; - buffer_bytes = stream->bufferSize * stream->nDeviceChannels[1]; - buffer_bytes *= formatBytes(stream->deviceFormat[1]); - } - else { - buffer = stream->userBuffer; - buffer_bytes = stream->bufferSize * stream->nUserChannels[1]; - buffer_bytes *= formatBytes(stream->userFormat); - } - - LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; - UINT nextReadPos = stream->handle[1].bufferPointer; - DWORD dsBufferSize = buffer_bytes * stream->nBuffers; - - // Find out where the write and "safe read" pointers are. - result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to get current DS capture position (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - if ( safePos < nextReadPos ) safePos += dsBufferSize; // unwrap offset - DWORD endRead = nextReadPos + buffer_bytes; - - // Check whether the entire write region is behind the play pointer. - while ( safePos < endRead ) { - // See comments for playback. - float millis = (endRead - safePos) * 900.0; - millis /= ( formatBytes(stream->deviceFormat[1]) * stream->sampleRate); - if ( millis < 1.0 ) millis = 1.0; - Sleep( (DWORD) millis ); - - // Wake up, find out where we are now - result = dsBuffer->GetCurrentPosition( ¤tPos, &safePos ); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to get current DS capture position (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - if ( safePos < nextReadPos ) safePos += dsBufferSize; // unwrap offset - } - - // Lock free space in the buffer - result = dsBuffer->Lock (nextReadPos, buffer_bytes, &buffer1, - &bufferSize1, &buffer2, &bufferSize2, 0); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to lock DS buffer during capture (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - - // Copy our buffer into the DS buffer - CopyMemory(buffer, buffer1, bufferSize1); - if (buffer2 != NULL) CopyMemory(buffer+bufferSize1, buffer2, bufferSize2); - - // Update our buffer offset and unlock sound buffer - nextReadPos = (nextReadPos + bufferSize1 + bufferSize2) % dsBufferSize; - dsBuffer->Unlock (buffer1, bufferSize1, buffer2, bufferSize2); - if ( FAILED(result) ) { - sprintf(message, "RtAudio: Unable to unlock DS buffer during capture (%s): %s.", - devices[stream->device[1]].name, getErrorString(result)); - error(RtAudioError::DRIVER_ERROR); - } - stream->handle[1].bufferPointer = nextReadPos; - - // No byte swapping necessary in DirectSound implementation. - - // Do buffer conversion if necessary. - if (stream->doConvertBuffer[1]) - convertStreamBuffer(stream, RECORD); - } - - unlock: - MUTEX_UNLOCK(&stream->mutex); - - if (stream->usingCallback && stopStream) - this->stopStream(streamID); -} - -// Definitions for utility functions and callbacks -// specific to the DirectSound implementation. - -extern "C" unsigned __stdcall callbackHandler(void *ptr) -{ - RtAudio *object = thread_info.object; - int stream = thread_info.streamID; - bool *usingCallback = (bool *) ptr; - - while ( *usingCallback ) { - try { - object->tickStream(stream); - } - catch (RtAudioError &exception) { - fprintf(stderr, "\nCallback thread error (%s) ... closing thread.\n\n", - exception.getMessage()); - break; - } - } - - _endthreadex( 0 ); - return 0; -} - -static bool CALLBACK deviceCountCallback(LPGUID lpguid, - LPCSTR lpcstrDescription, - LPCSTR lpcstrModule, - LPVOID lpContext) -{ - int *pointer = ((int *) lpContext); - (*pointer)++; - - return true; -} - -static bool CALLBACK deviceInfoCallback(LPGUID lpguid, - LPCSTR lpcstrDescription, - LPCSTR lpcstrModule, - LPVOID lpContext) -{ - enum_info *info = ((enum_info *) lpContext); - while (strlen(info->name) > 0) info++; - - strncpy(info->name, lpcstrDescription, 64); - info->id = lpguid; - - HRESULT hr; - info->isValid = false; - if (info->isInput == true) { - DSCCAPS caps; - LPDIRECTSOUNDCAPTURE object; - - hr = DirectSoundCaptureCreate( lpguid, &object, NULL ); - if( hr != DS_OK ) return true; - - caps.dwSize = sizeof(caps); - hr = object->GetCaps( &caps ); - if( hr == DS_OK ) { - if (caps.dwChannels > 0 && caps.dwFormats > 0) - info->isValid = true; - } - object->Release(); - } - else { - DSCAPS caps; - LPDIRECTSOUND object; - hr = DirectSoundCreate( lpguid, &object, NULL ); - if( hr != DS_OK ) return true; - - caps.dwSize = sizeof(caps); - hr = object->GetCaps( &caps ); - if( hr == DS_OK ) { - if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO ) - info->isValid = true; - } - object->Release(); - } - - return true; -} - -static char* getErrorString(int code) -{ - switch (code) { - - case DSERR_ALLOCATED: - return "Direct Sound already allocated"; - - case DSERR_CONTROLUNAVAIL: - return "Direct Sound control unavailable"; - - case DSERR_INVALIDPARAM: - return "Direct Sound invalid parameter"; - - case DSERR_INVALIDCALL: - return "Direct Sound invalid call"; - - case DSERR_GENERIC: - return "Direct Sound generic error"; - - case DSERR_PRIOLEVELNEEDED: - return "Direct Sound Priority level needed"; - - case DSERR_OUTOFMEMORY: - return "Direct Sound out of memory"; - - case DSERR_BADFORMAT: - return "Direct Sound bad format"; - - case DSERR_UNSUPPORTED: - return "Direct Sound unsupported error"; - - case DSERR_NODRIVER: - return "Direct Sound no driver error"; - - case DSERR_ALREADYINITIALIZED: - return "Direct Sound already initialized"; - - case DSERR_NOAGGREGATION: - return "Direct Sound no aggregation"; - - case DSERR_BUFFERLOST: - return "Direct Sound buffer lost"; - - case DSERR_OTHERAPPHASPRIO: - return "Direct Sound other app has priority"; - - case DSERR_UNINITIALIZED: - return "Direct Sound uninitialized"; - - default: - return "Direct Sound unknown error"; - } -} - -//******************** End of __WINDOWS_DS_ *********************// - -#elif defined(__IRIX_AL_) // SGI's AL API for IRIX - -#include -#include - -void RtAudio :: initialize(void) -{ - - // Count cards and devices - nDevices = 0; - - // Determine the total number of input and output devices. - nDevices = alQueryValues(AL_SYSTEM, AL_DEVICES, 0, 0, 0, 0); - if (nDevices < 0) { - sprintf(message, "RtAudio: AL error counting devices: %s.", - alGetErrorString(oserror())); - error(RtAudioError::DRIVER_ERROR); - } - - if (nDevices <= 0) return; - - ALvalue *vls = (ALvalue *) new ALvalue[nDevices]; - - // Add one for our default input/output devices. - nDevices++; - - // Allocate the DEVICE_CONTROL structures. - devices = (RTAUDIO_DEVICE *) calloc(nDevices, sizeof(RTAUDIO_DEVICE)); - if (devices == NULL) { - sprintf(message, "RtAudio: memory allocation error!"); - error(RtAudioError::MEMORY_ERROR); - } - - // Write device ascii identifiers to device info structure. - char name[32]; - int outs, ins, i; - ALpv pvs[1]; - pvs[0].param = AL_NAME; - pvs[0].value.ptr = name; - pvs[0].sizeIn = 32; - - strcpy(devices[0].name, "Default Input/Output Devices"); - - outs = alQueryValues(AL_SYSTEM, AL_DEFAULT_OUTPUT, vls, nDevices-1, 0, 0); - if (outs < 0) { - sprintf(message, "RtAudio: AL error getting output devices: %s.", - alGetErrorString(oserror())); - error(RtAudioError::DRIVER_ERROR); - } - - for (i=0; iname, "Default Input/Output Devices", 28) ) { - result = alQueryValues(AL_SYSTEM, AL_DEFAULT_OUTPUT, &value, 1, 0, 0); - if (result < 0) { - sprintf(message, "RtAudio: AL error getting default output device id: %s.", - alGetErrorString(oserror())); - error(RtAudioError::WARNING); - } - else - resource = value.i; - } - else - resource = info->id[0]; - - if (resource > 0) { - - // Probe output device parameters. - result = alQueryValues(resource, AL_CHANNELS, &value, 1, 0, 0); - if (result < 0) { - sprintf(message, "RtAudio: AL error getting device (%s) channels: %s.", - info->name, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - } - else { - info->maxOutputChannels = value.i; - info->minOutputChannels = 1; - } - - result = alGetParamInfo(resource, AL_RATE, &pinfo); - if (result < 0) { - sprintf(message, "RtAudio: AL error getting device (%s) rates: %s.", - info->name, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - } - else { - info->nSampleRates = 0; - for (i=0; i= pinfo.min.i && SAMPLE_RATES[i] <= pinfo.max.i ) { - info->sampleRates[info->nSampleRates] = SAMPLE_RATES[i]; - info->nSampleRates++; - } - } - } - - // The AL library supports all our formats, except 24-bit and 32-bit ints. - info->nativeFormats = (RTAUDIO_FORMAT) 51; - } - - // Now get input resource ID if it exists. - if ( !strncmp(info->name, "Default Input/Output Devices", 28) ) { - result = alQueryValues(AL_SYSTEM, AL_DEFAULT_INPUT, &value, 1, 0, 0); - if (result < 0) { - sprintf(message, "RtAudio: AL error getting default input device id: %s.", - alGetErrorString(oserror())); - error(RtAudioError::WARNING); - } - else - resource = value.i; - } - else - resource = info->id[1]; - - if (resource > 0) { - - // Probe input device parameters. - result = alQueryValues(resource, AL_CHANNELS, &value, 1, 0, 0); - if (result < 0) { - sprintf(message, "RtAudio: AL error getting device (%s) channels: %s.", - info->name, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - } - else { - info->maxInputChannels = value.i; - info->minInputChannels = 1; - } - - result = alGetParamInfo(resource, AL_RATE, &pinfo); - if (result < 0) { - sprintf(message, "RtAudio: AL error getting device (%s) rates: %s.", - info->name, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - } - else { - // In the case of the default device, these values will - // overwrite the rates determined for the output device. Since - // the input device is most likely to be more limited than the - // output device, this is ok. - info->nSampleRates = 0; - for (i=0; i= pinfo.min.i && SAMPLE_RATES[i] <= pinfo.max.i ) { - info->sampleRates[info->nSampleRates] = SAMPLE_RATES[i]; - info->nSampleRates++; - } - } - } - - // The AL library supports all our formats, except 24-bit and 32-bit ints. - info->nativeFormats = (RTAUDIO_FORMAT) 51; - } - - if ( info->maxInputChannels == 0 && info->maxOutputChannels == 0 ) - return; - if ( info->nSampleRates == 0 ) - return; - - // Determine duplex status. - if (info->maxInputChannels < info->maxOutputChannels) - info->maxDuplexChannels = info->maxInputChannels; - else - info->maxDuplexChannels = info->maxOutputChannels; - if (info->minInputChannels < info->minOutputChannels) - info->minDuplexChannels = info->minInputChannels; - else - info->minDuplexChannels = info->minOutputChannels; - - if ( info->maxDuplexChannels > 0 ) info->hasDuplexSupport = true; - else info->hasDuplexSupport = false; - - info->probed = true; - - return; -} - -bool RtAudio :: probeDeviceOpen(int device, RTAUDIO_STREAM *stream, - STREAM_MODE mode, int channels, - int sampleRate, RTAUDIO_FORMAT format, - int *bufferSize, int numberOfBuffers) -{ - int result, resource, nBuffers; - ALconfig al_config; - ALport port; - ALpv pvs[2]; - - // Get a new ALconfig structure. - al_config = alNewConfig(); - if ( !al_config ) { - sprintf(message,"RtAudio: can't get AL config: %s.", - alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set the channels. - result = alSetChannels(al_config, channels); - if ( result < 0 ) { - sprintf(message,"RtAudio: can't set %d channels in AL config: %s.", - channels, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set the queue (buffer) size. - if ( numberOfBuffers < 1 ) - nBuffers = 1; - else - nBuffers = numberOfBuffers; - long buffer_size = *bufferSize * nBuffers; - result = alSetQueueSize(al_config, buffer_size); // in sample frames - if ( result < 0 ) { - sprintf(message,"RtAudio: can't set buffer size (%ld) in AL config: %s.", - buffer_size, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set the data format. - stream->userFormat = format; - stream->deviceFormat[mode] = format; - if (format == RTAUDIO_SINT8) { - result = alSetSampFmt(al_config, AL_SAMPFMT_TWOSCOMP); - result = alSetWidth(al_config, AL_SAMPLE_8); - } - else if (format == RTAUDIO_SINT16) { - result = alSetSampFmt(al_config, AL_SAMPFMT_TWOSCOMP); - result = alSetWidth(al_config, AL_SAMPLE_16); - } - else if (format == RTAUDIO_SINT24) { - // Our 24-bit format assumes the upper 3 bytes of a 4 byte word. - // The AL library uses the lower 3 bytes, so we'll need to do our - // own conversion. - result = alSetSampFmt(al_config, AL_SAMPFMT_FLOAT); - stream->deviceFormat[mode] = RTAUDIO_FLOAT32; - } - else if (format == RTAUDIO_SINT32) { - // The AL library doesn't seem to support the 32-bit integer - // format, so we'll need to do our own conversion. - result = alSetSampFmt(al_config, AL_SAMPFMT_FLOAT); - stream->deviceFormat[mode] = RTAUDIO_FLOAT32; - } - else if (format == RTAUDIO_FLOAT32) - result = alSetSampFmt(al_config, AL_SAMPFMT_FLOAT); - else if (format == RTAUDIO_FLOAT64) - result = alSetSampFmt(al_config, AL_SAMPFMT_DOUBLE); - - if ( result == -1 ) { - sprintf(message,"RtAudio: AL error setting sample format in AL config: %s.", - alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - - if (mode == PLAYBACK) { - - // Set our device. - if (device == 0) - resource = AL_DEFAULT_OUTPUT; - else - resource = devices[device].id[0]; - result = alSetDevice(al_config, resource); - if ( result == -1 ) { - sprintf(message,"RtAudio: AL error setting device (%s) in AL config: %s.", - devices[device].name, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Open the port. - port = alOpenPort("RtAudio Output Port", "w", al_config); - if( !port ) { - sprintf(message,"RtAudio: AL error opening output port: %s.", - alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set the sample rate - pvs[0].param = AL_MASTER_CLOCK; - pvs[0].value.i = AL_CRYSTAL_MCLK_TYPE; - pvs[1].param = AL_RATE; - pvs[1].value.ll = alDoubleToFixed((double)sampleRate); - result = alSetParams(resource, pvs, 2); - if ( result < 0 ) { - alClosePort(port); - sprintf(message,"RtAudio: AL error setting sample rate (%d) for device (%s): %s.", - sampleRate, devices[device].name, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - } - else { // mode == RECORD - - // Set our device. - if (device == 0) - resource = AL_DEFAULT_INPUT; - else - resource = devices[device].id[1]; - result = alSetDevice(al_config, resource); - if ( result == -1 ) { - sprintf(message,"RtAudio: AL error setting device (%s) in AL config: %s.", - devices[device].name, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Open the port. - port = alOpenPort("RtAudio Output Port", "r", al_config); - if( !port ) { - sprintf(message,"RtAudio: AL error opening input port: %s.", - alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - - // Set the sample rate - pvs[0].param = AL_MASTER_CLOCK; - pvs[0].value.i = AL_CRYSTAL_MCLK_TYPE; - pvs[1].param = AL_RATE; - pvs[1].value.ll = alDoubleToFixed((double)sampleRate); - result = alSetParams(resource, pvs, 2); - if ( result < 0 ) { - alClosePort(port); - sprintf(message,"RtAudio: AL error setting sample rate (%d) for device (%s): %s.", - sampleRate, devices[device].name, alGetErrorString(oserror())); - error(RtAudioError::WARNING); - return FAILURE; - } - } - - alFreeConfig(al_config); - - stream->nUserChannels[mode] = channels; - stream->nDeviceChannels[mode] = channels; - - // Set handle and flags for buffer conversion - stream->handle[mode] = port; - stream->doConvertBuffer[mode] = false; - if (stream->userFormat != stream->deviceFormat[mode]) - stream->doConvertBuffer[mode] = true; - - // Allocate necessary internal buffers - if ( stream->nUserChannels[0] != stream->nUserChannels[1] ) { - - long buffer_bytes; - if (stream->nUserChannels[0] >= stream->nUserChannels[1]) - buffer_bytes = stream->nUserChannels[0]; - else - buffer_bytes = stream->nUserChannels[1]; - - buffer_bytes *= *bufferSize * formatBytes(stream->userFormat); - if (stream->userBuffer) free(stream->userBuffer); - stream->userBuffer = (char *) calloc(buffer_bytes, 1); - if (stream->userBuffer == NULL) - goto memory_error; - } - - if ( stream->doConvertBuffer[mode] ) { - - long buffer_bytes; - bool makeBuffer = true; - if ( mode == PLAYBACK ) - buffer_bytes = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); - else { // mode == RECORD - buffer_bytes = stream->nDeviceChannels[1] * formatBytes(stream->deviceFormat[1]); - if ( stream->mode == PLAYBACK ) { - long bytes_out = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); - if ( buffer_bytes > bytes_out ) - buffer_bytes = (buffer_bytes > bytes_out) ? buffer_bytes : bytes_out; - else - makeBuffer = false; - } - } - - if ( makeBuffer ) { - buffer_bytes *= *bufferSize; - if (stream->deviceBuffer) free(stream->deviceBuffer); - stream->deviceBuffer = (char *) calloc(buffer_bytes, 1); - if (stream->deviceBuffer == NULL) - goto memory_error; - } - } - - stream->device[mode] = device; - stream->state = STREAM_STOPPED; - if ( stream->mode == PLAYBACK && mode == RECORD ) - // We had already set up an output stream. - stream->mode = DUPLEX; - else - stream->mode = mode; - stream->nBuffers = nBuffers; - stream->bufferSize = *bufferSize; - stream->sampleRate = sampleRate; - - return SUCCESS; - - memory_error: - if (stream->handle[0]) { - alClosePort(stream->handle[0]); - stream->handle[0] = 0; - } - if (stream->handle[1]) { - alClosePort(stream->handle[1]); - stream->handle[1] = 0; - } - if (stream->userBuffer) { - free(stream->userBuffer); - stream->userBuffer = 0; - } - sprintf(message, "RtAudio: ALSA error allocating buffer memory for device (%s).", - devices[device].name); - error(RtAudioError::WARNING); - return FAILURE; -} - -void RtAudio :: cancelStreamCallback(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - if (stream->usingCallback) { - stream->usingCallback = false; - pthread_cancel(stream->thread); - pthread_join(stream->thread, NULL); - stream->thread = 0; - stream->callback = NULL; - stream->userData = NULL; - } -} - -void RtAudio :: closeStream(int streamID) -{ - // We don't want an exception to be thrown here because this - // function is called by our class destructor. So, do our own - // streamID check. - if ( streams.find( streamID ) == streams.end() ) { - sprintf(message, "RtAudio: invalid stream identifier!"); - error(RtAudioError::WARNING); - return; - } - - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) streams[streamID]; - - if (stream->usingCallback) { - pthread_cancel(stream->thread); - pthread_join(stream->thread, NULL); - } - - pthread_mutex_destroy(&stream->mutex); - - if (stream->handle[0]) - alClosePort(stream->handle[0]); - - if (stream->handle[1]) - alClosePort(stream->handle[1]); - - if (stream->userBuffer) - free(stream->userBuffer); - - if (stream->deviceBuffer) - free(stream->deviceBuffer); - - free(stream); - streams.erase(streamID); -} - -void RtAudio :: startStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - if (stream->state == STREAM_RUNNING) - return; - - // The AL port is ready as soon as it is opened. - stream->state = STREAM_RUNNING; -} - -void RtAudio :: stopStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_STOPPED) - goto unlock; - - int result; - int buffer_size = stream->bufferSize * stream->nBuffers; - - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) - alZeroFrames(stream->handle[0], buffer_size); - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - result = alDiscardFrames(stream->handle[1], buffer_size); - if (result == -1) { - sprintf(message, "RtAudio: AL error draining stream device (%s): %s.", - devices[stream->device[1]].name, alGetErrorString(oserror())); - error(RtAudioError::DRIVER_ERROR); - } - } - stream->state = STREAM_STOPPED; - - unlock: - MUTEX_UNLOCK(&stream->mutex); -} - -void RtAudio :: abortStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - if (stream->state == STREAM_STOPPED) - goto unlock; - - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - - int buffer_size = stream->bufferSize * stream->nBuffers; - int result = alDiscardFrames(stream->handle[0], buffer_size); - if (result == -1) { - sprintf(message, "RtAudio: AL error aborting stream device (%s): %s.", - devices[stream->device[0]].name, alGetErrorString(oserror())); - error(RtAudioError::DRIVER_ERROR); - } - } - - // There is no clear action to take on the input stream, since the - // port will continue to run in any event. - stream->state = STREAM_STOPPED; - - unlock: - MUTEX_UNLOCK(&stream->mutex); -} - -int RtAudio :: streamWillBlock(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - MUTEX_LOCK(&stream->mutex); - - int frames = 0; - if (stream->state == STREAM_STOPPED) - goto unlock; - - int err = 0; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - err = alGetFillable(stream->handle[0]); - if (err < 0) { - sprintf(message, "RtAudio: AL error getting available frames for stream (%s): %s.", - devices[stream->device[0]].name, alGetErrorString(oserror())); - error(RtAudioError::DRIVER_ERROR); - } - } - - frames = err; - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - err = alGetFilled(stream->handle[1]); - if (err < 0) { - sprintf(message, "RtAudio: AL error getting available frames for stream (%s): %s.", - devices[stream->device[1]].name, alGetErrorString(oserror())); - error(RtAudioError::DRIVER_ERROR); - } - if (frames > err) frames = err; - } - - frames = stream->bufferSize - frames; - if (frames < 0) frames = 0; - - unlock: - MUTEX_UNLOCK(&stream->mutex); - return frames; -} - -void RtAudio :: tickStream(int streamID) -{ - RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamID); - - int stopStream = 0; - if (stream->state == STREAM_STOPPED) { - if (stream->usingCallback) usleep(50000); // sleep 50 milliseconds - return; - } - else if (stream->usingCallback) { - stopStream = stream->callback(stream->userBuffer, stream->bufferSize, stream->userData); - } - - MUTEX_LOCK(&stream->mutex); - - // The state might change while waiting on a mutex. - if (stream->state == STREAM_STOPPED) - goto unlock; - - char *buffer; - int channels; - RTAUDIO_FORMAT format; - if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { - - // Setup parameters and do buffer conversion if necessary. - if (stream->doConvertBuffer[0]) { - convertStreamBuffer(stream, PLAYBACK); - buffer = stream->deviceBuffer; - channels = stream->nDeviceChannels[0]; - format = stream->deviceFormat[0]; - } - else { - buffer = stream->userBuffer; - channels = stream->nUserChannels[0]; - format = stream->userFormat; - } - - // Do byte swapping if necessary. - if (stream->doByteSwap[0]) - byteSwapBuffer(buffer, stream->bufferSize * channels, format); - - // Write interleaved samples to device. - alWriteFrames(stream->handle[0], buffer, stream->bufferSize); - } - - if (stream->mode == RECORD || stream->mode == DUPLEX) { - - // Setup parameters. - if (stream->doConvertBuffer[1]) { - buffer = stream->deviceBuffer; - channels = stream->nDeviceChannels[1]; - format = stream->deviceFormat[1]; - } - else { - buffer = stream->userBuffer; - channels = stream->nUserChannels[1]; - format = stream->userFormat; - } - - // Read interleaved samples from device. - alReadFrames(stream->handle[1], buffer, stream->bufferSize); - - // Do byte swapping if necessary. - if (stream->doByteSwap[1]) - byteSwapBuffer(buffer, stream->bufferSize * channels, format); - - // Do buffer conversion if necessary. - if (stream->doConvertBuffer[1]) - convertStreamBuffer(stream, RECORD); - } - - unlock: - MUTEX_UNLOCK(&stream->mutex); - - if (stream->usingCallback && stopStream) - this->stopStream(streamID); -} - -extern "C" void *callbackHandler(void *ptr) -{ - RtAudio *object = thread_info.object; - int stream = thread_info.streamID; - bool *usingCallback = (bool *) ptr; - - while ( *usingCallback ) { - pthread_testcancel(); - try { - object->tickStream(stream); - } - catch (RtAudioError &exception) { - fprintf(stderr, "\nCallback thread error (%s) ... closing thread.\n\n", - exception.getMessage()); - break; - } - } - - return 0; -} - -//******************** End of __IRIX_AL_ *********************// - -#endif - - -// *************************************************** // -// -// Private common (OS-independent) RtAudio methods. -// -// *************************************************** // - -// This method can be modified to control the behavior of error -// message reporting and throwing. -void RtAudio :: error(RtAudioError::TYPE type) -{ - if (type == RtAudioError::WARNING) - fprintf(stderr, "\n%s\n\n", message); - else if (type == RtAudioError::DEBUG_WARNING) { -#if defined(RTAUDIO_DEBUG) - fprintf(stderr, "\n%s\n\n", message); -#endif - } - else - throw RtAudioError(message, type); -} - -void *RtAudio :: verifyStream(int streamID) -{ - // Verify the stream key. - if ( streams.find( streamID ) == streams.end() ) { - sprintf(message, "RtAudio: invalid stream identifier!"); - error(RtAudioError::INVALID_STREAM); - } - - return streams[streamID]; -} - -void RtAudio :: clearDeviceInfo(RTAUDIO_DEVICE *info) -{ - // Don't clear the name or DEVICE_ID fields here ... they are - // typically set prior to a call of this function. - info->probed = false; - info->maxOutputChannels = 0; - info->maxInputChannels = 0; - info->maxDuplexChannels = 0; - info->minOutputChannels = 0; - info->minInputChannels = 0; - info->minDuplexChannels = 0; - info->hasDuplexSupport = false; - info->nSampleRates = 0; - for (int i=0; isampleRates[i] = 0; - info->nativeFormats = 0; -} - -int RtAudio :: formatBytes(RTAUDIO_FORMAT format) -{ - if (format == RTAUDIO_SINT16) - return 2; - else if (format == RTAUDIO_SINT24 || format == RTAUDIO_SINT32 || - format == RTAUDIO_FLOAT32) - return 4; - else if (format == RTAUDIO_FLOAT64) - return 8; - else if (format == RTAUDIO_SINT8) - return 1; - - sprintf(message,"RtAudio: undefined format in formatBytes()."); - error(RtAudioError::WARNING); - - return 0; -} - -void RtAudio :: convertStreamBuffer(RTAUDIO_STREAM *stream, STREAM_MODE mode) -{ - // This method does format conversion, input/output channel compensation, and - // data interleaving/deinterleaving. 24-bit integers are assumed to occupy - // the upper three bytes of a 32-bit integer. - - int j, channels_in, channels_out, channels; - RTAUDIO_FORMAT format_in, format_out; - char *input, *output; - - if (mode == RECORD) { // convert device to user buffer - input = stream->deviceBuffer; - output = stream->userBuffer; - channels_in = stream->nDeviceChannels[1]; - channels_out = stream->nUserChannels[1]; - format_in = stream->deviceFormat[1]; - format_out = stream->userFormat; - } - else { // convert user to device buffer - input = stream->userBuffer; - output = stream->deviceBuffer; - channels_in = stream->nUserChannels[0]; - channels_out = stream->nDeviceChannels[0]; - format_in = stream->userFormat; - format_out = stream->deviceFormat[0]; - - // clear our device buffer when in/out duplex device channels are different - if ( stream->mode == DUPLEX && - stream->nDeviceChannels[0] != stream->nDeviceChannels[1] ) - memset(output, 0, stream->bufferSize * channels_out * formatBytes(format_out)); - } - - channels = (channels_in < channels_out) ? channels_in : channels_out; - - // Set up the interleave/deinterleave offsets - std::vector offset_in(channels); - std::vector offset_out(channels); - if (mode == RECORD && stream->deInterleave[1]) { - for (int k=0; kbufferSize; - offset_out[k] = k; - } - } - else if (mode == PLAYBACK && stream->deInterleave[0]) { - for (int k=0; kbufferSize; - } - } - else { - for (int k=0; kbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; j> 16) & 0x0000ffff); - } - in += channels_in; - out += channels_out; - } - } - else if (format_in == RTAUDIO_SINT32) { - INT32 *in = (INT32 *)input; - for (int i=0; ibufferSize; i++) { - for (j=0; j> 16) & 0x0000ffff); - } - in += channels_in; - out += channels_out; - } - } - else if (format_in == RTAUDIO_FLOAT32) { - FLOAT32 *in = (FLOAT32 *)input; - for (int i=0; ibufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; j> 8) & 0x00ff); - } - in += channels_in; - out += channels_out; - } - } - else if (format_in == RTAUDIO_SINT24) { - INT32 *in = (INT32 *)input; - for (int i=0; ibufferSize; i++) { - for (j=0; j> 24) & 0x000000ff); - } - in += channels_in; - out += channels_out; - } - } - else if (format_in == RTAUDIO_SINT32) { - INT32 *in = (INT32 *)input; - for (int i=0; ibufferSize; i++) { - for (j=0; j> 24) & 0x000000ff); - } - in += channels_in; - out += channels_out; - } - } - else if (format_in == RTAUDIO_FLOAT32) { - FLOAT32 *in = (FLOAT32 *)input; - for (int i=0; ibufferSize; i++) { - for (j=0; jbufferSize; i++) { - for (j=0; j +#include + +// Static variable definitions. +const unsigned int RtAudio :: SAMPLE_RATES[] = { + 4000, 5512, 8000, 9600, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, 96000, 176400, 192000 +}; +const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_SINT8 = 1; +const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_SINT16 = 2; +const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_SINT24 = 4; +const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_SINT32 = 8; +const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_FLOAT32 = 16; +const RtAudio::RTAUDIO_FORMAT RtAudio :: RTAUDIO_FLOAT64 = 32; + +#if defined(__WINDOWS_DS__) + #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) + #define MUTEX_LOCK(A) EnterCriticalSection(A) + #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) + typedef unsigned THREAD_RETURN; + typedef unsigned (__stdcall THREAD_FUNCTION)(void *); +#else // pthread API + #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) + #define MUTEX_LOCK(A) pthread_mutex_lock(A) + #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) + typedef void * THREAD_RETURN; +#endif + +// *************************************************** // +// +// Public common (OS-independent) methods. +// +// *************************************************** // + +RtAudio :: RtAudio() +{ + initialize(); + + if (nDevices <= 0) { + sprintf(message, "RtAudio: no audio devices found!"); + error(RtError::NO_DEVICES_FOUND); + } +} + +RtAudio :: RtAudio(int *streamId, + int outputDevice, int outputChannels, + int inputDevice, int inputChannels, + RTAUDIO_FORMAT format, int sampleRate, + int *bufferSize, int numberOfBuffers) +{ + initialize(); + + if (nDevices <= 0) { + sprintf(message, "RtAudio: no audio devices found!"); + error(RtError::NO_DEVICES_FOUND); + } + + try { + *streamId = openStream(outputDevice, outputChannels, inputDevice, inputChannels, + format, sampleRate, bufferSize, numberOfBuffers); + } + catch (RtError &exception) { + // deallocate the RTAUDIO_DEVICE structures + if (devices) free(devices); + error(exception.getType()); + } +} + +RtAudio :: ~RtAudio() +{ + // close any existing streams + while ( streams.size() ) + closeStream( streams.begin()->first ); + + // deallocate the RTAUDIO_DEVICE structures + if (devices) free(devices); +} + +int RtAudio :: openStream(int outputDevice, int outputChannels, + int inputDevice, int inputChannels, + RTAUDIO_FORMAT format, int sampleRate, + int *bufferSize, int numberOfBuffers) +{ + static int streamKey = 0; // Unique stream identifier ... OK for multiple instances. + + if (outputChannels < 1 && inputChannels < 1) { + sprintf(message,"RtAudio: one or both 'channel' parameters must be greater than zero."); + error(RtError::INVALID_PARAMETER); + } + + if ( formatBytes(format) == 0 ) { + sprintf(message,"RtAudio: 'format' parameter value is undefined."); + error(RtError::INVALID_PARAMETER); + } + + if ( outputChannels > 0 ) { + if (outputDevice >= nDevices || outputDevice < 0) { + sprintf(message,"RtAudio: 'outputDevice' parameter value (%d) is invalid.", outputDevice); + error(RtError::INVALID_PARAMETER); + } + } + + if ( inputChannels > 0 ) { + if (inputDevice >= nDevices || inputDevice < 0) { + sprintf(message,"RtAudio: 'inputDevice' parameter value (%d) is invalid.", inputDevice); + error(RtError::INVALID_PARAMETER); + } + } + + // Allocate a new stream structure. + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) calloc(1, sizeof(RTAUDIO_STREAM)); + if (stream == NULL) { + sprintf(message, "RtAudio: memory allocation error!"); + error(RtError::MEMORY_ERROR); + } + streams[++streamKey] = (void *) stream; + stream->mode = UNINITIALIZED; + MUTEX_INITIALIZE(&stream->mutex); + + bool result = SUCCESS; + int device; + STREAM_MODE mode; + int channels; + if ( outputChannels > 0 ) { + + device = outputDevice; + mode = PLAYBACK; + channels = outputChannels; + + if (device == 0) { // Try default device first. + for (int i=0; i 0 && result == SUCCESS ) { + + device = inputDevice; + mode = RECORD; + channels = inputChannels; + + if (device == 0) { // Try default device first. + for (int i=0; i= nDevices || device < 0) { + sprintf(message, "RtAudio: invalid device specifier (%d)!", device); + error(RtError::INVALID_DEVICE); + } + + // If the device wasn't successfully probed before, try it again. + if (devices[device].probed == false) { + clearDeviceInfo(&devices[device]); + probeDeviceInfo(&devices[device]); + } + + // Clear the info structure. + memset(info, 0, sizeof(RTAUDIO_DEVICE)); + + strncpy(info->name, devices[device].name, 128); + info->probed = devices[device].probed; + if ( info->probed == true ) { + info->maxOutputChannels = devices[device].maxOutputChannels; + info->maxInputChannels = devices[device].maxInputChannels; + info->maxDuplexChannels = devices[device].maxDuplexChannels; + info->minOutputChannels = devices[device].minOutputChannels; + info->minInputChannels = devices[device].minInputChannels; + info->minDuplexChannels = devices[device].minDuplexChannels; + info->hasDuplexSupport = devices[device].hasDuplexSupport; + info->nSampleRates = devices[device].nSampleRates; + if (info->nSampleRates == -1) { + info->sampleRates[0] = devices[device].sampleRates[0]; + info->sampleRates[1] = devices[device].sampleRates[1]; + } + else { + for (int i=0; inSampleRates; i++) + info->sampleRates[i] = devices[device].sampleRates[i]; + } + info->nativeFormats = devices[device].nativeFormats; + } + + return; +} + +char * const RtAudio :: getStreamBuffer(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + return stream->userBuffer; +} + +// This global structure is used to pass information to the thread +// function. I tried other methods but had intermittent errors due to +// variable persistence during thread startup. +struct { + RtAudio *object; + int streamId; +} thread_info; + +extern "C" THREAD_RETURN THREAD_TYPE callbackHandler(void * ptr); + +void RtAudio :: setStreamCallback(int streamId, RTAUDIO_CALLBACK callback, void *userData) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + stream->callback = callback; + stream->userData = userData; + stream->usingCallback = true; + thread_info.object = this; + thread_info.streamId = streamId; + + int err = 0; +#if defined(__WINDOWS_DS__) + unsigned thread_id; + stream->thread = _beginthreadex(NULL, 0, &callbackHandler, + &stream->usingCallback, 0, &thread_id); + if (stream->thread == 0) err = -1; + // When spawning multiple threads in quick succession, it appears to be + // necessary to wait a bit for each to initialize ... another windism! + Sleep(1); +#else + err = pthread_create(&stream->thread, NULL, callbackHandler, &stream->usingCallback); +#endif + + if (err) { + stream->usingCallback = false; + sprintf(message, "RtAudio: error starting callback thread!"); + error(RtError::THREAD_ERROR); + } +} + +// *************************************************** // +// +// OS/API-specific methods. +// +// *************************************************** // + +#if defined(__LINUX_ALSA__) + +#define MAX_DEVICES 16 + +void RtAudio :: initialize(void) +{ + int card, result, device; + char name[32]; + char deviceNames[MAX_DEVICES][32]; + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + + // Count cards and devices + nDevices = 0; + card = -1; + snd_card_next(&card); + while ( card >= 0 ) { + sprintf(name, "hw:%d", card); + result = snd_ctl_open(&handle, name, 0); + if (result < 0) { + sprintf(message, "RtAudio: ALSA control open (%i): %s.", card, snd_strerror(result)); + error(RtError::WARNING); + goto next_card; + } + result = snd_ctl_card_info(handle, info); + if (result < 0) { + sprintf(message, "RtAudio: ALSA control hardware info (%i): %s.", card, snd_strerror(result)); + error(RtError::WARNING); + goto next_card; + } + device = -1; + while (1) { + result = snd_ctl_pcm_next_device(handle, &device); + if (result < 0) { + sprintf(message, "RtAudio: ALSA control next device (%i): %s.", card, snd_strerror(result)); + error(RtError::WARNING); + break; + } + if (device < 0) + break; + sprintf( deviceNames[nDevices++], "hw:%d,%d", card, device ); + if ( nDevices > MAX_DEVICES ) break; + } + if ( nDevices > MAX_DEVICES ) break; + next_card: + snd_ctl_close(handle); + snd_card_next(&card); + } + + if (nDevices == 0) return; + + // Allocate the RTAUDIO_DEVICE structures. + devices = (RTAUDIO_DEVICE *) calloc(nDevices, sizeof(RTAUDIO_DEVICE)); + if (devices == NULL) { + sprintf(message, "RtAudio: memory allocation error!"); + error(RtError::MEMORY_ERROR); + } + + // Write device ascii identifiers to device structures and then + // probe the device capabilities. + for (int i=0; iname, stream, open_mode); + if (err < 0) { + sprintf(message, "RtAudio: ALSA pcm playback open (%s): %s.", + info->name, snd_strerror(err)); + error(RtError::WARNING); + goto capture_probe; + } + + snd_pcm_hw_params_t *params; + snd_pcm_hw_params_alloca(¶ms); + + // We have an open device ... allocate the parameter structure. + err = snd_pcm_hw_params_any(handle, params); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA hardware probe error (%s): %s.", + info->name, snd_strerror(err)); + error(RtError::WARNING); + goto capture_probe; + } + + // Get output channel information. + info->minOutputChannels = snd_pcm_hw_params_get_channels_min(params); + info->maxOutputChannels = snd_pcm_hw_params_get_channels_max(params); + + snd_pcm_close(handle); + + capture_probe: + // Now try for capture + stream = SND_PCM_STREAM_CAPTURE; + err = snd_pcm_open(&handle, info->name, stream, open_mode); + if (err < 0) { + sprintf(message, "RtAudio: ALSA pcm capture open (%s): %s.", + info->name, snd_strerror(err)); + error(RtError::WARNING); + if (info->maxOutputChannels == 0) + // didn't open for playback either ... device invalid + return; + goto probe_parameters; + } + + // We have an open capture device ... allocate the parameter structure. + err = snd_pcm_hw_params_any(handle, params); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA hardware probe error (%s): %s.", + info->name, snd_strerror(err)); + error(RtError::WARNING); + if (info->maxOutputChannels > 0) + goto probe_parameters; + else + return; + } + + // Get input channel information. + info->minInputChannels = snd_pcm_hw_params_get_channels_min(params); + info->maxInputChannels = snd_pcm_hw_params_get_channels_max(params); + + // If device opens for both playback and capture, we determine the channels. + if (info->maxOutputChannels == 0 || info->maxInputChannels == 0) + goto probe_parameters; + + info->hasDuplexSupport = true; + info->maxDuplexChannels = (info->maxOutputChannels > info->maxInputChannels) ? + info->maxInputChannels : info->maxOutputChannels; + info->minDuplexChannels = (info->minOutputChannels > info->minInputChannels) ? + info->minInputChannels : info->minOutputChannels; + + snd_pcm_close(handle); + + probe_parameters: + // At this point, we just need to figure out the supported data + // formats and sample rates. We'll proceed by opening the device in + // the direction with the maximum number of channels, or playback if + // they are equal. This might limit our sample rate options, but so + // be it. + + if (info->maxOutputChannels >= info->maxInputChannels) + stream = SND_PCM_STREAM_PLAYBACK; + else + stream = SND_PCM_STREAM_CAPTURE; + + err = snd_pcm_open(&handle, info->name, stream, open_mode); + if (err < 0) { + sprintf(message, "RtAudio: ALSA pcm (%s) won't reopen during probe: %s.", + info->name, snd_strerror(err)); + error(RtError::WARNING); + return; + } + + // We have an open device ... allocate the parameter structure. + err = snd_pcm_hw_params_any(handle, params); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA hardware reopen probe error (%s): %s.", + info->name, snd_strerror(err)); + error(RtError::WARNING); + return; + } + + // Test a non-standard sample rate to see if continuous rate is supported. + int dir = 0; + if (snd_pcm_hw_params_test_rate(handle, params, 35500, dir) == 0) { + // It appears that continuous sample rate support is available. + info->nSampleRates = -1; + info->sampleRates[0] = snd_pcm_hw_params_get_rate_min(params, &dir); + info->sampleRates[1] = snd_pcm_hw_params_get_rate_max(params, &dir); + } + else { + // No continuous rate support ... test our discrete set of sample rate values. + info->nSampleRates = 0; + for (int i=0; isampleRates[info->nSampleRates] = SAMPLE_RATES[i]; + info->nSampleRates++; + } + } + if (info->nSampleRates == 0) { + snd_pcm_close(handle); + return; + } + } + + // Probe the supported data formats ... we don't care about endian-ness just yet + snd_pcm_format_t format; + info->nativeFormats = 0; + format = SND_PCM_FORMAT_S8; + if (snd_pcm_hw_params_test_format(handle, params, format) == 0) + info->nativeFormats |= RTAUDIO_SINT8; + format = SND_PCM_FORMAT_S16; + if (snd_pcm_hw_params_test_format(handle, params, format) == 0) + info->nativeFormats |= RTAUDIO_SINT16; + format = SND_PCM_FORMAT_S24; + if (snd_pcm_hw_params_test_format(handle, params, format) == 0) + info->nativeFormats |= RTAUDIO_SINT24; + format = SND_PCM_FORMAT_S32; + if (snd_pcm_hw_params_test_format(handle, params, format) == 0) + info->nativeFormats |= RTAUDIO_SINT32; + format = SND_PCM_FORMAT_FLOAT; + if (snd_pcm_hw_params_test_format(handle, params, format) == 0) + info->nativeFormats |= RTAUDIO_FLOAT32; + format = SND_PCM_FORMAT_FLOAT64; + if (snd_pcm_hw_params_test_format(handle, params, format) == 0) + info->nativeFormats |= RTAUDIO_FLOAT64; + + // Check that we have at least one supported format + if (info->nativeFormats == 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA PCM device (%s) data format not supported by RtAudio.", + info->name); + error(RtError::WARNING); + return; + } + + // That's all ... close the device and return + snd_pcm_close(handle); + info->probed = true; + return; +} + +bool RtAudio :: probeDeviceOpen(int device, RTAUDIO_STREAM *stream, + STREAM_MODE mode, int channels, + int sampleRate, RTAUDIO_FORMAT format, + int *bufferSize, int numberOfBuffers) +{ +#if defined(RTAUDIO_DEBUG) + snd_output_t *out; + snd_output_stdio_attach(&out, stderr, 0); +#endif + + // I'm not using the "plug" interface ... too much inconsistent behavior. + const char *name = devices[device].name; + + snd_pcm_stream_t alsa_stream; + if (mode == PLAYBACK) + alsa_stream = SND_PCM_STREAM_PLAYBACK; + else + alsa_stream = SND_PCM_STREAM_CAPTURE; + + int err; + snd_pcm_t *handle; + int alsa_open_mode = SND_PCM_ASYNC; + err = snd_pcm_open(&handle, name, alsa_stream, alsa_open_mode); + if (err < 0) { + sprintf(message,"RtAudio: ALSA pcm device (%s) won't open: %s.", + name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + + // Fill the parameter structure. + snd_pcm_hw_params_t *hw_params; + snd_pcm_hw_params_alloca(&hw_params); + err = snd_pcm_hw_params_any(handle, hw_params); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error getting parameter handle (%s): %s.", + name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + +#if defined(RTAUDIO_DEBUG) + fprintf(stderr, "\nRtAudio: ALSA dump hardware params just after device open:\n\n"); + snd_pcm_hw_params_dump(hw_params, out); +#endif + + // Set access ... try interleaved access first, then non-interleaved + err = snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) { + // No interleave support ... try non-interleave. + err = snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error setting access ( (%s): %s.", + name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + stream->deInterleave[mode] = true; + } + + // Determine how to set the device format. + stream->userFormat = format; + snd_pcm_format_t device_format; + + if (format == RTAUDIO_SINT8) + device_format = SND_PCM_FORMAT_S8; + else if (format == RTAUDIO_SINT16) + device_format = SND_PCM_FORMAT_S16; + else if (format == RTAUDIO_SINT24) + device_format = SND_PCM_FORMAT_S24; + else if (format == RTAUDIO_SINT32) + device_format = SND_PCM_FORMAT_S32; + else if (format == RTAUDIO_FLOAT32) + device_format = SND_PCM_FORMAT_FLOAT; + else if (format == RTAUDIO_FLOAT64) + device_format = SND_PCM_FORMAT_FLOAT64; + + if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { + stream->deviceFormat[mode] = format; + goto set_format; + } + + // The user requested format is not natively supported by the device. + device_format = SND_PCM_FORMAT_FLOAT64; + if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { + stream->deviceFormat[mode] = RTAUDIO_FLOAT64; + goto set_format; + } + + device_format = SND_PCM_FORMAT_FLOAT; + if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { + stream->deviceFormat[mode] = RTAUDIO_FLOAT32; + goto set_format; + } + + device_format = SND_PCM_FORMAT_S32; + if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { + stream->deviceFormat[mode] = RTAUDIO_SINT32; + goto set_format; + } + + device_format = SND_PCM_FORMAT_S24; + if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { + stream->deviceFormat[mode] = RTAUDIO_SINT24; + goto set_format; + } + + device_format = SND_PCM_FORMAT_S16; + if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { + stream->deviceFormat[mode] = RTAUDIO_SINT16; + goto set_format; + } + + device_format = SND_PCM_FORMAT_S8; + if (snd_pcm_hw_params_test_format(handle, hw_params, device_format) == 0) { + stream->deviceFormat[mode] = RTAUDIO_SINT8; + goto set_format; + } + + // If we get here, no supported format was found. + sprintf(message,"RtAudio: ALSA pcm device (%s) data format not supported by RtAudio.", name); + snd_pcm_close(handle); + error(RtError::WARNING); + return FAILURE; + + set_format: + err = snd_pcm_hw_params_set_format(handle, hw_params, device_format); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error setting format (%s): %s.", + name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + + // Determine whether byte-swaping is necessary. + stream->doByteSwap[mode] = false; + if (device_format != SND_PCM_FORMAT_S8) { + err = snd_pcm_format_cpu_endian(device_format); + if (err == 0) + stream->doByteSwap[mode] = true; + else if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error getting format endian-ness (%s): %s.", + name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + } + + // Determine the number of channels for this device. We support a possible + // minimum device channel number > than the value requested by the user. + stream->nUserChannels[mode] = channels; + int device_channels = snd_pcm_hw_params_get_channels_max(hw_params); + if (device_channels < channels) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: channels (%d) not supported by device (%s).", + channels, name); + error(RtError::WARNING); + return FAILURE; + } + + device_channels = snd_pcm_hw_params_get_channels_min(hw_params); + if (device_channels < channels) device_channels = channels; + stream->nDeviceChannels[mode] = device_channels; + + // Set the device channels. + err = snd_pcm_hw_params_set_channels(handle, hw_params, device_channels); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error setting channels (%d) on device (%s): %s.", + device_channels, name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + + // Set the sample rate. + err = snd_pcm_hw_params_set_rate(handle, hw_params, (unsigned int)sampleRate, 0); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error setting sample rate (%d) on device (%s): %s.", + sampleRate, name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + + // Set the buffer number, which in ALSA is referred to as the "period". + int dir; + int periods = numberOfBuffers; + // Even though the hardware might allow 1 buffer, it won't work reliably. + if (periods < 2) periods = 2; + err = snd_pcm_hw_params_get_periods_min(hw_params, &dir); + if (err > periods) periods = err; + + err = snd_pcm_hw_params_set_periods(handle, hw_params, periods, 0); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error setting periods (%s): %s.", + name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + + // Set the buffer (or period) size. + err = snd_pcm_hw_params_get_period_size_min(hw_params, &dir); + if (err > *bufferSize) *bufferSize = err; + + err = snd_pcm_hw_params_set_period_size(handle, hw_params, *bufferSize, 0); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error setting period size (%s): %s.", + name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + + stream->bufferSize = *bufferSize; + + // Install the hardware configuration + err = snd_pcm_hw_params(handle, hw_params); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error installing hardware configuration (%s): %s.", + name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + +#if defined(RTAUDIO_DEBUG) + fprintf(stderr, "\nRtAudio: ALSA dump hardware params after installation:\n\n"); + snd_pcm_hw_params_dump(hw_params, out); +#endif + + /* + // Install the software configuration + snd_pcm_sw_params_t *sw_params = NULL; + snd_pcm_sw_params_alloca(&sw_params); + snd_pcm_sw_params_current(handle, sw_params); + err = snd_pcm_sw_params(handle, sw_params); + if (err < 0) { + snd_pcm_close(handle); + sprintf(message, "RtAudio: ALSA error installing software configuration (%s): %s.", + name, snd_strerror(err)); + error(RtError::WARNING); + return FAILURE; + } + */ + + // Set handle and flags for buffer conversion + stream->handle[mode] = handle; + stream->doConvertBuffer[mode] = false; + if (stream->userFormat != stream->deviceFormat[mode]) + stream->doConvertBuffer[mode] = true; + if (stream->nUserChannels[mode] < stream->nDeviceChannels[mode]) + stream->doConvertBuffer[mode] = true; + if (stream->nUserChannels[mode] > 1 && stream->deInterleave[mode]) + stream->doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + if ( stream->nUserChannels[0] != stream->nUserChannels[1] ) { + + long buffer_bytes; + if (stream->nUserChannels[0] >= stream->nUserChannels[1]) + buffer_bytes = stream->nUserChannels[0]; + else + buffer_bytes = stream->nUserChannels[1]; + + buffer_bytes *= *bufferSize * formatBytes(stream->userFormat); + if (stream->userBuffer) free(stream->userBuffer); + stream->userBuffer = (char *) calloc(buffer_bytes, 1); + if (stream->userBuffer == NULL) + goto memory_error; + } + + if ( stream->doConvertBuffer[mode] ) { + + long buffer_bytes; + bool makeBuffer = true; + if ( mode == PLAYBACK ) + buffer_bytes = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); + else { // mode == RECORD + buffer_bytes = stream->nDeviceChannels[1] * formatBytes(stream->deviceFormat[1]); + if ( stream->mode == PLAYBACK ) { + long bytes_out = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); + if ( buffer_bytes > bytes_out ) + buffer_bytes = (buffer_bytes > bytes_out) ? buffer_bytes : bytes_out; + else + makeBuffer = false; + } + } + + if ( makeBuffer ) { + buffer_bytes *= *bufferSize; + if (stream->deviceBuffer) free(stream->deviceBuffer); + stream->deviceBuffer = (char *) calloc(buffer_bytes, 1); + if (stream->deviceBuffer == NULL) + goto memory_error; + } + } + + stream->device[mode] = device; + stream->state = STREAM_STOPPED; + if ( stream->mode == PLAYBACK && mode == RECORD ) + // We had already set up an output stream. + stream->mode = DUPLEX; + else + stream->mode = mode; + stream->nBuffers = periods; + stream->sampleRate = sampleRate; + + return SUCCESS; + + memory_error: + if (stream->handle[0]) { + snd_pcm_close(stream->handle[0]); + stream->handle[0] = 0; + } + if (stream->handle[1]) { + snd_pcm_close(stream->handle[1]); + stream->handle[1] = 0; + } + if (stream->userBuffer) { + free(stream->userBuffer); + stream->userBuffer = 0; + } + sprintf(message, "RtAudio: ALSA error allocating buffer memory (%s).", name); + error(RtError::WARNING); + return FAILURE; +} + +void RtAudio :: cancelStreamCallback(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + if (stream->usingCallback) { + stream->usingCallback = false; + pthread_cancel(stream->thread); + pthread_join(stream->thread, NULL); + stream->thread = 0; + stream->callback = NULL; + stream->userData = NULL; + } +} + +void RtAudio :: closeStream(int streamId) +{ + // We don't want an exception to be thrown here because this + // function is called by our class destructor. So, do our own + // streamId check. + if ( streams.find( streamId ) == streams.end() ) { + sprintf(message, "RtAudio: invalid stream identifier!"); + error(RtError::WARNING); + return; + } + + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) streams[streamId]; + + if (stream->usingCallback) { + pthread_cancel(stream->thread); + pthread_join(stream->thread, NULL); + } + + if (stream->state == STREAM_RUNNING) { + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) + snd_pcm_drop(stream->handle[0]); + if (stream->mode == RECORD || stream->mode == DUPLEX) + snd_pcm_drop(stream->handle[1]); + } + + pthread_mutex_destroy(&stream->mutex); + + if (stream->handle[0]) + snd_pcm_close(stream->handle[0]); + + if (stream->handle[1]) + snd_pcm_close(stream->handle[1]); + + if (stream->userBuffer) + free(stream->userBuffer); + + if (stream->deviceBuffer) + free(stream->deviceBuffer); + + free(stream); + streams.erase(streamId); +} + +void RtAudio :: startStream(int streamId) +{ + // This method calls snd_pcm_prepare if the device isn't already in that state. + + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_RUNNING) + goto unlock; + + int err; + snd_pcm_state_t state; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + state = snd_pcm_state(stream->handle[0]); + if (state != SND_PCM_STATE_PREPARED) { + err = snd_pcm_prepare(stream->handle[0]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error preparing pcm device (%s): %s.", + devices[stream->device[0]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + state = snd_pcm_state(stream->handle[1]); + if (state != SND_PCM_STATE_PREPARED) { + err = snd_pcm_prepare(stream->handle[1]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error preparing pcm device (%s): %s.", + devices[stream->device[1]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + } + stream->state = STREAM_RUNNING; + + unlock: + MUTEX_UNLOCK(&stream->mutex); +} + +void RtAudio :: stopStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_STOPPED) + goto unlock; + + int err; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + err = snd_pcm_drain(stream->handle[0]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error draining pcm device (%s): %s.", + devices[stream->device[0]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + err = snd_pcm_drain(stream->handle[1]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error draining pcm device (%s): %s.", + devices[stream->device[1]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + stream->state = STREAM_STOPPED; + + unlock: + MUTEX_UNLOCK(&stream->mutex); +} + +void RtAudio :: abortStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_STOPPED) + goto unlock; + + int err; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + err = snd_pcm_drop(stream->handle[0]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error draining pcm device (%s): %s.", + devices[stream->device[0]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + err = snd_pcm_drop(stream->handle[1]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error draining pcm device (%s): %s.", + devices[stream->device[1]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + stream->state = STREAM_STOPPED; + + unlock: + MUTEX_UNLOCK(&stream->mutex); +} + +int RtAudio :: streamWillBlock(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + int err = 0, frames = 0; + if (stream->state == STREAM_STOPPED) + goto unlock; + + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + err = snd_pcm_avail_update(stream->handle[0]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error getting available frames for device (%s): %s.", + devices[stream->device[0]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + + frames = err; + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + err = snd_pcm_avail_update(stream->handle[1]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error getting available frames for device (%s): %s.", + devices[stream->device[1]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + if (frames > err) frames = err; + } + + frames = stream->bufferSize - frames; + if (frames < 0) frames = 0; + + unlock: + MUTEX_UNLOCK(&stream->mutex); + return frames; +} + +void RtAudio :: tickStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + int stopStream = 0; + if (stream->state == STREAM_STOPPED) { + if (stream->usingCallback) usleep(50000); // sleep 50 milliseconds + return; + } + else if (stream->usingCallback) { + stopStream = stream->callback(stream->userBuffer, stream->bufferSize, stream->userData); + } + + MUTEX_LOCK(&stream->mutex); + + // The state might change while waiting on a mutex. + if (stream->state == STREAM_STOPPED) + goto unlock; + + int err; + char *buffer; + int channels; + RTAUDIO_FORMAT format; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + + // Setup parameters and do buffer conversion if necessary. + if (stream->doConvertBuffer[0]) { + convertStreamBuffer(stream, PLAYBACK); + buffer = stream->deviceBuffer; + channels = stream->nDeviceChannels[0]; + format = stream->deviceFormat[0]; + } + else { + buffer = stream->userBuffer; + channels = stream->nUserChannels[0]; + format = stream->userFormat; + } + + // Do byte swapping if necessary. + if (stream->doByteSwap[0]) + byteSwapBuffer(buffer, stream->bufferSize * channels, format); + + // Write samples to device in interleaved/non-interleaved format. + if (stream->deInterleave[0]) { + void *bufs[channels]; + size_t offset = stream->bufferSize * formatBytes(format); + for (int i=0; ihandle[0], bufs, stream->bufferSize); + } + else + err = snd_pcm_writei(stream->handle[0], buffer, stream->bufferSize); + + if (err < stream->bufferSize) { + // Either an error or underrun occured. + if (err == -EPIPE) { + snd_pcm_state_t state = snd_pcm_state(stream->handle[0]); + if (state == SND_PCM_STATE_XRUN) { + sprintf(message, "RtAudio: ALSA underrun detected."); + error(RtError::WARNING); + err = snd_pcm_prepare(stream->handle[0]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error preparing handle after underrun: %s.", + snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + else { + sprintf(message, "RtAudio: ALSA error, current state is %s.", + snd_pcm_state_name(state)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + goto unlock; + } + else { + sprintf(message, "RtAudio: ALSA audio write error for device (%s): %s.", + devices[stream->device[0]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + + // Setup parameters. + if (stream->doConvertBuffer[1]) { + buffer = stream->deviceBuffer; + channels = stream->nDeviceChannels[1]; + format = stream->deviceFormat[1]; + } + else { + buffer = stream->userBuffer; + channels = stream->nUserChannels[1]; + format = stream->userFormat; + } + + // Read samples from device in interleaved/non-interleaved format. + if (stream->deInterleave[1]) { + void *bufs[channels]; + size_t offset = stream->bufferSize * formatBytes(format); + for (int i=0; ihandle[1], bufs, stream->bufferSize); + } + else + err = snd_pcm_readi(stream->handle[1], buffer, stream->bufferSize); + + if (err < stream->bufferSize) { + // Either an error or underrun occured. + if (err == -EPIPE) { + snd_pcm_state_t state = snd_pcm_state(stream->handle[1]); + if (state == SND_PCM_STATE_XRUN) { + sprintf(message, "RtAudio: ALSA overrun detected."); + error(RtError::WARNING); + err = snd_pcm_prepare(stream->handle[1]); + if (err < 0) { + sprintf(message, "RtAudio: ALSA error preparing handle after overrun: %s.", + snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + else { + sprintf(message, "RtAudio: ALSA error, current state is %s.", + snd_pcm_state_name(state)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + goto unlock; + } + else { + sprintf(message, "RtAudio: ALSA audio read error for device (%s): %s.", + devices[stream->device[1]].name, snd_strerror(err)); + MUTEX_UNLOCK(&stream->mutex); + error(RtError::DRIVER_ERROR); + } + } + + // Do byte swapping if necessary. + if (stream->doByteSwap[1]) + byteSwapBuffer(buffer, stream->bufferSize * channels, format); + + // Do buffer conversion if necessary. + if (stream->doConvertBuffer[1]) + convertStreamBuffer(stream, RECORD); + } + + unlock: + MUTEX_UNLOCK(&stream->mutex); + + if (stream->usingCallback && stopStream) + this->stopStream(streamId); +} + +extern "C" void *callbackHandler(void *ptr) +{ + RtAudio *object = thread_info.object; + int stream = thread_info.streamId; + bool *usingCallback = (bool *) ptr; + + while ( *usingCallback ) { + pthread_testcancel(); + try { + object->tickStream(stream); + } + catch (RtError &exception) { + fprintf(stderr, "\nCallback thread error (%s) ... closing thread.\n\n", + exception.getMessage()); + break; + } + } + + return 0; +} + +//******************** End of __LINUX_ALSA__ *********************// + +#elif defined(__LINUX_OSS__) + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DAC_NAME "/dev/dsp" +#define MAX_DEVICES 16 +#define MAX_CHANNELS 16 + +void RtAudio :: initialize(void) +{ + // Count cards and devices + nDevices = 0; + + // We check /dev/dsp before probing devices. /dev/dsp is supposed to + // be a link to the "default" audio device, of the form /dev/dsp0, + // /dev/dsp1, etc... However, I've seen one case where /dev/dsp was a + // real device, so we need to check for that. Also, sometimes the + // link is to /dev/dspx and other times just dspx. I'm not sure how + // the latter works, but it does. + char device_name[16]; + struct stat dspstat; + int dsplink = -1; + int i = 0; + if (lstat(DAC_NAME, &dspstat) == 0) { + if (S_ISLNK(dspstat.st_mode)) { + i = readlink(DAC_NAME, device_name, sizeof(device_name)); + if (i > 0) { + device_name[i] = '\0'; + if (i > 8) { // check for "/dev/dspx" + if (!strncmp(DAC_NAME, device_name, 8)) + dsplink = atoi(&device_name[8]); + } + else if (i > 3) { // check for "dspx" + if (!strncmp("dsp", device_name, 3)) + dsplink = atoi(&device_name[3]); + } + } + else { + sprintf(message, "RtAudio: cannot read value of symbolic link %s.", DAC_NAME); + error(RtError::SYSTEM_ERROR); + } + } + } + else { + sprintf(message, "RtAudio: cannot stat %s.", DAC_NAME); + error(RtError::SYSTEM_ERROR); + } + + // The OSS API doesn't provide a routine for determining the number + // of devices. Thus, we'll just pursue a brute force method. The + // idea is to start with /dev/dsp(0) and continue with higher device + // numbers until we reach MAX_DSP_DEVICES. This should tell us how + // many devices we have ... it is not a fullproof scheme, but hopefully + // it will work most of the time. + + int fd = 0; + char names[MAX_DEVICES][16]; + for (i=-1; i= 0) close(fd); + strncpy(names[nDevices], device_name, 16); + nDevices++; + } + + if (nDevices == 0) return; + + // Allocate the RTAUDIO_DEVICE structures. + devices = (RTAUDIO_DEVICE *) calloc(nDevices, sizeof(RTAUDIO_DEVICE)); + if (devices == NULL) { + sprintf(message, "RtAudio: memory allocation error!"); + error(RtError::MEMORY_ERROR); + } + + // Write device ascii identifiers to device control structure and then probe capabilities. + for (i=0; iname, O_WRONLY | O_NONBLOCK); + if (fd == -1) { + // Open device failed ... either busy or doesn't exist + if (errno == EBUSY || errno == EAGAIN) + sprintf(message, "RtAudio: OSS playback device (%s) is busy and cannot be probed.", + info->name); + else + sprintf(message, "RtAudio: OSS playback device (%s) open error.", info->name); + error(RtError::WARNING); + goto capture_probe; + } + + // We have an open device ... see how many channels it can handle + for (i=MAX_CHANNELS; i>0; i--) { + channels = i; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1) { + // This would normally indicate some sort of hardware error, but under ALSA's + // OSS emulation, it sometimes indicates an invalid channel value. Further, + // the returned channel value is not changed. So, we'll ignore the possible + // hardware error. + continue; // try next channel number + } + // Check to see whether the device supports the requested number of channels + if (channels != i ) continue; // try next channel number + // If here, we found the largest working channel value + break; + } + info->maxOutputChannels = channels; + + // Now find the minimum number of channels it can handle + for (i=1; i<=info->maxOutputChannels; i++) { + channels = i; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) + continue; // try next channel number + // If here, we found the smallest working channel value + break; + } + info->minOutputChannels = channels; + close(fd); + + capture_probe: + // Now try for capture + fd = open(info->name, O_RDONLY | O_NONBLOCK); + if (fd == -1) { + // Open device for capture failed ... either busy or doesn't exist + if (errno == EBUSY || errno == EAGAIN) + sprintf(message, "RtAudio: OSS capture device (%s) is busy and cannot be probed.", + info->name); + else + sprintf(message, "RtAudio: OSS capture device (%s) open error.", info->name); + error(RtError::WARNING); + if (info->maxOutputChannels == 0) + // didn't open for playback either ... device invalid + return; + goto probe_parameters; + } + + // We have the device open for capture ... see how many channels it can handle + for (i=MAX_CHANNELS; i>0; i--) { + channels = i; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) { + continue; // as above + } + // If here, we found a working channel value + break; + } + info->maxInputChannels = channels; + + // Now find the minimum number of channels it can handle + for (i=1; i<=info->maxInputChannels; i++) { + channels = i; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) + continue; // try next channel number + // If here, we found the smallest working channel value + break; + } + info->minInputChannels = channels; + close(fd); + + // If device opens for both playback and capture, we determine the channels. + if (info->maxOutputChannels == 0 || info->maxInputChannels == 0) + goto probe_parameters; + + fd = open(info->name, O_RDWR | O_NONBLOCK); + if (fd == -1) + goto probe_parameters; + + ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); + ioctl(fd, SNDCTL_DSP_GETCAPS, &mask); + if (mask & DSP_CAP_DUPLEX) { + info->hasDuplexSupport = true; + // We have the device open for duplex ... see how many channels it can handle + for (i=MAX_CHANNELS; i>0; i--) { + channels = i; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) + continue; // as above + // If here, we found a working channel value + break; + } + info->maxDuplexChannels = channels; + + // Now find the minimum number of channels it can handle + for (i=1; i<=info->maxDuplexChannels; i++) { + channels = i; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) + continue; // try next channel number + // If here, we found the smallest working channel value + break; + } + info->minDuplexChannels = channels; + } + close(fd); + + probe_parameters: + // At this point, we need to figure out the supported data formats + // and sample rates. We'll proceed by openning the device in the + // direction with the maximum number of channels, or playback if + // they are equal. This might limit our sample rate options, but so + // be it. + + if (info->maxOutputChannels >= info->maxInputChannels) { + fd = open(info->name, O_WRONLY | O_NONBLOCK); + channels = info->maxOutputChannels; + } + else { + fd = open(info->name, O_RDONLY | O_NONBLOCK); + channels = info->maxInputChannels; + } + + if (fd == -1) { + // We've got some sort of conflict ... abort + sprintf(message, "RtAudio: OSS device (%s) won't reopen during probe.", + info->name); + error(RtError::WARNING); + return; + } + + // We have an open device ... set to maximum channels. + i = channels; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1 || channels != i) { + // We've got some sort of conflict ... abort + close(fd); + sprintf(message, "RtAudio: OSS device (%s) won't revert to previous channel setting.", + info->name); + error(RtError::WARNING); + return; + } + + if (ioctl(fd, SNDCTL_DSP_GETFMTS, &mask) == -1) { + close(fd); + sprintf(message, "RtAudio: OSS device (%s) can't get supported audio formats.", + info->name); + error(RtError::WARNING); + return; + } + + // Probe the supported data formats ... we don't care about endian-ness just yet. + int format; + info->nativeFormats = 0; +#if defined (AFMT_S32_BE) + // This format does not seem to be in the 2.4 kernel version of OSS soundcard.h + if (mask & AFMT_S32_BE) { + format = AFMT_S32_BE; + info->nativeFormats |= RTAUDIO_SINT32; + } +#endif +#if defined (AFMT_S32_LE) + /* This format is not in the 2.4.4 kernel version of OSS soundcard.h */ + if (mask & AFMT_S32_LE) { + format = AFMT_S32_LE; + info->nativeFormats |= RTAUDIO_SINT32; + } +#endif + if (mask & AFMT_S8) { + format = AFMT_S8; + info->nativeFormats |= RTAUDIO_SINT8; + } + if (mask & AFMT_S16_BE) { + format = AFMT_S16_BE; + info->nativeFormats |= RTAUDIO_SINT16; + } + if (mask & AFMT_S16_LE) { + format = AFMT_S16_LE; + info->nativeFormats |= RTAUDIO_SINT16; + } + + // Check that we have at least one supported format + if (info->nativeFormats == 0) { + close(fd); + sprintf(message, "RtAudio: OSS device (%s) data format not supported by RtAudio.", + info->name); + error(RtError::WARNING); + return; + } + + // Set the format + i = format; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) == -1 || format != i) { + close(fd); + sprintf(message, "RtAudio: OSS device (%s) error setting data format.", + info->name); + error(RtError::WARNING); + return; + } + + // Probe the supported sample rates ... first get lower limit + int speed = 1; + if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) == -1) { + // If we get here, we're probably using an ALSA driver with OSS-emulation, + // which doesn't conform to the OSS specification. In this case, + // we'll probe our predefined list of sample rates for working values. + info->nSampleRates = 0; + for (i=0; isampleRates[info->nSampleRates] = SAMPLE_RATES[i]; + info->nSampleRates++; + } + } + if (info->nSampleRates == 0) { + close(fd); + return; + } + goto finished; + } + info->sampleRates[0] = speed; + + // Now get upper limit + speed = 1000000; + if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) == -1) { + close(fd); + sprintf(message, "RtAudio: OSS device (%s) error setting sample rate.", + info->name); + error(RtError::WARNING); + return; + } + info->sampleRates[1] = speed; + info->nSampleRates = -1; + + finished: // That's all ... close the device and return + close(fd); + info->probed = true; + return; +} + +bool RtAudio :: probeDeviceOpen(int device, RTAUDIO_STREAM *stream, + STREAM_MODE mode, int channels, + int sampleRate, RTAUDIO_FORMAT format, + int *bufferSize, int numberOfBuffers) +{ + int buffers, buffer_bytes, device_channels, device_format; + int srate, temp, fd; + + const char *name = devices[device].name; + + if (mode == PLAYBACK) + fd = open(name, O_WRONLY | O_NONBLOCK); + else { // mode == RECORD + if (stream->mode == PLAYBACK && stream->device[0] == device) { + // We just set the same device for playback ... close and reopen for duplex (OSS only). + close(stream->handle[0]); + stream->handle[0] = 0; + // First check that the number previously set channels is the same. + if (stream->nUserChannels[0] != channels) { + sprintf(message, "RtAudio: input/output channels must be equal for OSS duplex device (%s).", name); + goto error; + } + fd = open(name, O_RDWR | O_NONBLOCK); + } + else + fd = open(name, O_RDONLY | O_NONBLOCK); + } + + if (fd == -1) { + if (errno == EBUSY || errno == EAGAIN) + sprintf(message, "RtAudio: OSS device (%s) is busy and cannot be opened.", + name); + else + sprintf(message, "RtAudio: OSS device (%s) cannot be opened.", name); + goto error; + } + + // Now reopen in blocking mode. + close(fd); + if (mode == PLAYBACK) + fd = open(name, O_WRONLY | O_SYNC); + else { // mode == RECORD + if (stream->mode == PLAYBACK && stream->device[0] == device) + fd = open(name, O_RDWR | O_SYNC); + else + fd = open(name, O_RDONLY | O_SYNC); + } + + if (fd == -1) { + sprintf(message, "RtAudio: OSS device (%s) cannot be opened.", name); + goto error; + } + + // Get the sample format mask + int mask; + if (ioctl(fd, SNDCTL_DSP_GETFMTS, &mask) == -1) { + close(fd); + sprintf(message, "RtAudio: OSS device (%s) can't get supported audio formats.", + name); + goto error; + } + + // Determine how to set the device format. + stream->userFormat = format; + device_format = -1; + stream->doByteSwap[mode] = false; + if (format == RTAUDIO_SINT8) { + if (mask & AFMT_S8) { + device_format = AFMT_S8; + stream->deviceFormat[mode] = RTAUDIO_SINT8; + } + } + else if (format == RTAUDIO_SINT16) { + if (mask & AFMT_S16_NE) { + device_format = AFMT_S16_NE; + stream->deviceFormat[mode] = RTAUDIO_SINT16; + } +#if BYTE_ORDER == LITTLE_ENDIAN + else if (mask & AFMT_S16_BE) { + device_format = AFMT_S16_BE; + stream->deviceFormat[mode] = RTAUDIO_SINT16; + stream->doByteSwap[mode] = true; + } +#else + else if (mask & AFMT_S16_LE) { + device_format = AFMT_S16_LE; + stream->deviceFormat[mode] = RTAUDIO_SINT16; + stream->doByteSwap[mode] = true; + } +#endif + } +#if defined (AFMT_S32_NE) && defined (AFMT_S32_LE) && defined (AFMT_S32_BE) + else if (format == RTAUDIO_SINT32) { + if (mask & AFMT_S32_NE) { + device_format = AFMT_S32_NE; + stream->deviceFormat[mode] = RTAUDIO_SINT32; + } +#if BYTE_ORDER == LITTLE_ENDIAN + else if (mask & AFMT_S32_BE) { + device_format = AFMT_S32_BE; + stream->deviceFormat[mode] = RTAUDIO_SINT32; + stream->doByteSwap[mode] = true; + } +#else + else if (mask & AFMT_S32_LE) { + device_format = AFMT_S32_LE; + stream->deviceFormat[mode] = RTAUDIO_SINT32; + stream->doByteSwap[mode] = true; + } +#endif + } +#endif + + if (device_format == -1) { + // The user requested format is not natively supported by the device. + if (mask & AFMT_S16_NE) { + device_format = AFMT_S16_NE; + stream->deviceFormat[mode] = RTAUDIO_SINT16; + } +#if BYTE_ORDER == LITTLE_ENDIAN + else if (mask & AFMT_S16_BE) { + device_format = AFMT_S16_BE; + stream->deviceFormat[mode] = RTAUDIO_SINT16; + stream->doByteSwap[mode] = true; + } +#else + else if (mask & AFMT_S16_LE) { + device_format = AFMT_S16_LE; + stream->deviceFormat[mode] = RTAUDIO_SINT16; + stream->doByteSwap[mode] = true; + } +#endif +#if defined (AFMT_S32_NE) && defined (AFMT_S32_LE) && defined (AFMT_S32_BE) + else if (mask & AFMT_S32_NE) { + device_format = AFMT_S32_NE; + stream->deviceFormat[mode] = RTAUDIO_SINT32; + } +#if BYTE_ORDER == LITTLE_ENDIAN + else if (mask & AFMT_S32_BE) { + device_format = AFMT_S32_BE; + stream->deviceFormat[mode] = RTAUDIO_SINT32; + stream->doByteSwap[mode] = true; + } +#else + else if (mask & AFMT_S32_LE) { + device_format = AFMT_S32_LE; + stream->deviceFormat[mode] = RTAUDIO_SINT32; + stream->doByteSwap[mode] = true; + } +#endif +#endif + else if (mask & AFMT_S8) { + device_format = AFMT_S8; + stream->deviceFormat[mode] = RTAUDIO_SINT8; + } + } + + if (stream->deviceFormat[mode] == 0) { + // This really shouldn't happen ... + close(fd); + sprintf(message, "RtAudio: OSS device (%s) data format not supported by RtAudio.", + name); + goto error; + } + + // Determine the number of channels for this device. Note that the + // channel value requested by the user might be < min_X_Channels. + stream->nUserChannels[mode] = channels; + device_channels = channels; + if (mode == PLAYBACK) { + if (channels < devices[device].minOutputChannels) + device_channels = devices[device].minOutputChannels; + } + else { // mode == RECORD + if (stream->mode == PLAYBACK && stream->device[0] == device) { + // We're doing duplex setup here. + if (channels < devices[device].minDuplexChannels) + device_channels = devices[device].minDuplexChannels; + } + else { + if (channels < devices[device].minInputChannels) + device_channels = devices[device].minInputChannels; + } + } + stream->nDeviceChannels[mode] = device_channels; + + // Attempt to set the buffer size. According to OSS, the minimum + // number of buffers is two. The supposed minimum buffer size is 16 + // bytes, so that will be our lower bound. The argument to this + // call is in the form 0xMMMMSSSS (hex), where the buffer size (in + // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM. + // We'll check the actual value used near the end of the setup + // procedure. + buffer_bytes = *bufferSize * formatBytes(stream->deviceFormat[mode]) * device_channels; + if (buffer_bytes < 16) buffer_bytes = 16; + buffers = numberOfBuffers; + if (buffers < 2) buffers = 2; + temp = ((int) buffers << 16) + (int)(log10((double)buffer_bytes)/log10(2.0)); + if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &temp)) { + close(fd); + sprintf(message, "RtAudio: OSS error setting fragment size for device (%s).", + name); + goto error; + } + stream->nBuffers = buffers; + + // Set the data format. + temp = device_format; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &device_format) == -1 || device_format != temp) { + close(fd); + sprintf(message, "RtAudio: OSS error setting data format for device (%s).", + name); + goto error; + } + + // Set the number of channels. + temp = device_channels; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &device_channels) == -1 || device_channels != temp) { + close(fd); + sprintf(message, "RtAudio: OSS error setting %d channels on device (%s).", + temp, name); + goto error; + } + + // Set the sample rate. + srate = sampleRate; + temp = srate; + if (ioctl(fd, SNDCTL_DSP_SPEED, &srate) == -1) { + close(fd); + sprintf(message, "RtAudio: OSS error setting sample rate = %d on device (%s).", + temp, name); + goto error; + } + + // Verify the sample rate setup worked. + if (abs(srate - temp) > 100) { + close(fd); + sprintf(message, "RtAudio: OSS error ... audio device (%s) doesn't support sample rate of %d.", + name, temp); + goto error; + } + stream->sampleRate = sampleRate; + + if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &buffer_bytes) == -1) { + close(fd); + sprintf(message, "RtAudio: OSS error getting buffer size for device (%s).", + name); + goto error; + } + + // Save buffer size (in sample frames). + *bufferSize = buffer_bytes / (formatBytes(stream->deviceFormat[mode]) * device_channels); + stream->bufferSize = *bufferSize; + + if (mode == RECORD && stream->mode == PLAYBACK && + stream->device[0] == device) { + // We're doing duplex setup here. + stream->deviceFormat[0] = stream->deviceFormat[1]; + stream->nDeviceChannels[0] = device_channels; + } + + // Set flags for buffer conversion + stream->doConvertBuffer[mode] = false; + if (stream->userFormat != stream->deviceFormat[mode]) + stream->doConvertBuffer[mode] = true; + if (stream->nUserChannels[mode] < stream->nDeviceChannels[mode]) + stream->doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + if ( stream->nUserChannels[0] != stream->nUserChannels[1] ) { + + long buffer_bytes; + if (stream->nUserChannels[0] >= stream->nUserChannels[1]) + buffer_bytes = stream->nUserChannels[0]; + else + buffer_bytes = stream->nUserChannels[1]; + + buffer_bytes *= *bufferSize * formatBytes(stream->userFormat); + if (stream->userBuffer) free(stream->userBuffer); + stream->userBuffer = (char *) calloc(buffer_bytes, 1); + if (stream->userBuffer == NULL) { + close(fd); + sprintf(message, "RtAudio: OSS error allocating user buffer memory (%s).", + name); + goto error; + } + } + + if ( stream->doConvertBuffer[mode] ) { + + long buffer_bytes; + bool makeBuffer = true; + if ( mode == PLAYBACK ) + buffer_bytes = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); + else { // mode == RECORD + buffer_bytes = stream->nDeviceChannels[1] * formatBytes(stream->deviceFormat[1]); + if ( stream->mode == PLAYBACK ) { + long bytes_out = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); + if ( buffer_bytes > bytes_out ) + buffer_bytes = (buffer_bytes > bytes_out) ? buffer_bytes : bytes_out; + else + makeBuffer = false; + } + } + + if ( makeBuffer ) { + buffer_bytes *= *bufferSize; + if (stream->deviceBuffer) free(stream->deviceBuffer); + stream->deviceBuffer = (char *) calloc(buffer_bytes, 1); + if (stream->deviceBuffer == NULL) { + close(fd); + free(stream->userBuffer); + sprintf(message, "RtAudio: OSS error allocating device buffer memory (%s).", + name); + goto error; + } + } + } + + stream->device[mode] = device; + stream->handle[mode] = fd; + stream->state = STREAM_STOPPED; + if ( stream->mode == PLAYBACK && mode == RECORD ) { + stream->mode = DUPLEX; + if (stream->device[0] == device) + stream->handle[0] = fd; + } + else + stream->mode = mode; + + return SUCCESS; + + error: + if (stream->handle[0]) { + close(stream->handle[0]); + stream->handle[0] = 0; + } + error(RtError::WARNING); + return FAILURE; +} + +void RtAudio :: cancelStreamCallback(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + if (stream->usingCallback) { + stream->usingCallback = false; + pthread_cancel(stream->thread); + pthread_join(stream->thread, NULL); + stream->thread = 0; + stream->callback = NULL; + stream->userData = NULL; + } +} + +void RtAudio :: closeStream(int streamId) +{ + // We don't want an exception to be thrown here because this + // function is called by our class destructor. So, do our own + // streamId check. + if ( streams.find( streamId ) == streams.end() ) { + sprintf(message, "RtAudio: invalid stream identifier!"); + error(RtError::WARNING); + return; + } + + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) streams[streamId]; + + if (stream->usingCallback) { + pthread_cancel(stream->thread); + pthread_join(stream->thread, NULL); + } + + if (stream->state == STREAM_RUNNING) { + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) + ioctl(stream->handle[0], SNDCTL_DSP_RESET, 0); + if (stream->mode == RECORD || stream->mode == DUPLEX) + ioctl(stream->handle[1], SNDCTL_DSP_RESET, 0); + } + + pthread_mutex_destroy(&stream->mutex); + + if (stream->handle[0]) + close(stream->handle[0]); + + if (stream->handle[1]) + close(stream->handle[1]); + + if (stream->userBuffer) + free(stream->userBuffer); + + if (stream->deviceBuffer) + free(stream->deviceBuffer); + + free(stream); + streams.erase(streamId); +} + +void RtAudio :: startStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + stream->state = STREAM_RUNNING; + + // No need to do anything else here ... OSS automatically starts when fed samples. +} + +void RtAudio :: stopStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_STOPPED) + goto unlock; + + int err; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + err = ioctl(stream->handle[0], SNDCTL_DSP_SYNC, 0); + if (err < -1) { + sprintf(message, "RtAudio: OSS error stopping device (%s).", + devices[stream->device[0]].name); + error(RtError::DRIVER_ERROR); + } + } + else { + err = ioctl(stream->handle[1], SNDCTL_DSP_SYNC, 0); + if (err < -1) { + sprintf(message, "RtAudio: OSS error stopping device (%s).", + devices[stream->device[1]].name); + error(RtError::DRIVER_ERROR); + } + } + stream->state = STREAM_STOPPED; + + unlock: + MUTEX_UNLOCK(&stream->mutex); +} + +void RtAudio :: abortStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_STOPPED) + goto unlock; + + int err; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + err = ioctl(stream->handle[0], SNDCTL_DSP_RESET, 0); + if (err < -1) { + sprintf(message, "RtAudio: OSS error aborting device (%s).", + devices[stream->device[0]].name); + error(RtError::DRIVER_ERROR); + } + } + else { + err = ioctl(stream->handle[1], SNDCTL_DSP_RESET, 0); + if (err < -1) { + sprintf(message, "RtAudio: OSS error aborting device (%s).", + devices[stream->device[1]].name); + error(RtError::DRIVER_ERROR); + } + } + stream->state = STREAM_STOPPED; + + unlock: + MUTEX_UNLOCK(&stream->mutex); +} + +int RtAudio :: streamWillBlock(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + int bytes = 0, channels = 0, frames = 0; + if (stream->state == STREAM_STOPPED) + goto unlock; + + audio_buf_info info; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + ioctl(stream->handle[0], SNDCTL_DSP_GETOSPACE, &info); + bytes = info.bytes; + channels = stream->nDeviceChannels[0]; + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + ioctl(stream->handle[1], SNDCTL_DSP_GETISPACE, &info); + if (stream->mode == DUPLEX ) { + bytes = (bytes < info.bytes) ? bytes : info.bytes; + channels = stream->nDeviceChannels[0]; + } + else { + bytes = info.bytes; + channels = stream->nDeviceChannels[1]; + } + } + + frames = (int) (bytes / (channels * formatBytes(stream->deviceFormat[0]))); + frames -= stream->bufferSize; + if (frames < 0) frames = 0; + + unlock: + MUTEX_UNLOCK(&stream->mutex); + return frames; +} + +void RtAudio :: tickStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + int stopStream = 0; + if (stream->state == STREAM_STOPPED) { + if (stream->usingCallback) usleep(50000); // sleep 50 milliseconds + return; + } + else if (stream->usingCallback) { + stopStream = stream->callback(stream->userBuffer, stream->bufferSize, stream->userData); + } + + MUTEX_LOCK(&stream->mutex); + + // The state might change while waiting on a mutex. + if (stream->state == STREAM_STOPPED) + goto unlock; + + int result; + char *buffer; + int samples; + RTAUDIO_FORMAT format; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + + // Setup parameters and do buffer conversion if necessary. + if (stream->doConvertBuffer[0]) { + convertStreamBuffer(stream, PLAYBACK); + buffer = stream->deviceBuffer; + samples = stream->bufferSize * stream->nDeviceChannels[0]; + format = stream->deviceFormat[0]; + } + else { + buffer = stream->userBuffer; + samples = stream->bufferSize * stream->nUserChannels[0]; + format = stream->userFormat; + } + + // Do byte swapping if necessary. + if (stream->doByteSwap[0]) + byteSwapBuffer(buffer, samples, format); + + // Write samples to device. + result = write(stream->handle[0], buffer, samples * formatBytes(format)); + + if (result == -1) { + // This could be an underrun, but the basic OSS API doesn't provide a means for determining that. + sprintf(message, "RtAudio: OSS audio write error for device (%s).", + devices[stream->device[0]].name); + error(RtError::DRIVER_ERROR); + } + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + + // Setup parameters. + if (stream->doConvertBuffer[1]) { + buffer = stream->deviceBuffer; + samples = stream->bufferSize * stream->nDeviceChannels[1]; + format = stream->deviceFormat[1]; + } + else { + buffer = stream->userBuffer; + samples = stream->bufferSize * stream->nUserChannels[1]; + format = stream->userFormat; + } + + // Read samples from device. + result = read(stream->handle[1], buffer, samples * formatBytes(format)); + + if (result == -1) { + // This could be an overrun, but the basic OSS API doesn't provide a means for determining that. + sprintf(message, "RtAudio: OSS audio read error for device (%s).", + devices[stream->device[1]].name); + error(RtError::DRIVER_ERROR); + } + + // Do byte swapping if necessary. + if (stream->doByteSwap[1]) + byteSwapBuffer(buffer, samples, format); + + // Do buffer conversion if necessary. + if (stream->doConvertBuffer[1]) + convertStreamBuffer(stream, RECORD); + } + + unlock: + MUTEX_UNLOCK(&stream->mutex); + + if (stream->usingCallback && stopStream) + this->stopStream(streamId); +} + +extern "C" void *callbackHandler(void *ptr) +{ + RtAudio *object = thread_info.object; + int stream = thread_info.streamId; + bool *usingCallback = (bool *) ptr; + + while ( *usingCallback ) { + pthread_testcancel(); + try { + object->tickStream(stream); + } + catch (RtError &exception) { + fprintf(stderr, "\nCallback thread error (%s) ... closing thread.\n\n", + exception.getMessage()); + break; + } + } + + return 0; +} + +//******************** End of __LINUX_OSS__ *********************// + +#elif defined(__WINDOWS_DS__) // Windows DirectSound API + +#include + +// Declarations for utility functions, callbacks, and structures +// specific to the DirectSound implementation. +static bool CALLBACK deviceCountCallback(LPGUID lpguid, + LPCSTR lpcstrDescription, + LPCSTR lpcstrModule, + LPVOID lpContext); + +static bool CALLBACK deviceInfoCallback(LPGUID lpguid, + LPCSTR lpcstrDescription, + LPCSTR lpcstrModule, + LPVOID lpContext); + +static char* getErrorString(int code); + +struct enum_info { + char name[64]; + LPGUID id; + bool isInput; + bool isValid; +}; + +// RtAudio methods for DirectSound implementation. +void RtAudio :: initialize(void) +{ + int i, ins = 0, outs = 0, count = 0; + int index = 0; + HRESULT result; + nDevices = 0; + + // Count DirectSound devices. + result = DirectSoundEnumerate((LPDSENUMCALLBACK)deviceCountCallback, &outs); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to enumerate through sound playback devices: %s.", + getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // Count DirectSoundCapture devices. + result = DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)deviceCountCallback, &ins); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to enumerate through sound capture devices: %s.", + getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + count = ins + outs; + if (count == 0) return; + + std::vector info(count); + for (i=0; i 0) { + nDevices = 1; + index = 1; + } + + // Non-default devices are listed separately. + for (i=0; i= nDevices ) { + sprintf(message, "RtAudio: device (%s) indexing error in DirectSound probeDeviceInfo().", + info->name); + error(RtError::WARNING); + return; + } + + // Do capture probe first. If this is not the default device (index + // = 0) _and_ GUID = NULL, then the capture handle is invalid. + if ( index != 0 && info->id[1] == NULL ) + goto playback_probe; + + LPDIRECTSOUNDCAPTURE input; + result = DirectSoundCaptureCreate( info->id[0], &input, NULL ); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Could not create DirectSound capture object (%s): %s.", + info->name, getErrorString(result)); + error(RtError::WARNING); + goto playback_probe; + } + + DSCCAPS in_caps; + in_caps.dwSize = sizeof(in_caps); + result = input->GetCaps( &in_caps ); + if ( FAILED(result) ) { + input->Release(); + sprintf(message, "RtAudio: Could not get DirectSound capture capabilities (%s): %s.", + info->name, getErrorString(result)); + error(RtError::WARNING); + goto playback_probe; + } + + // Get input channel information. + info->minInputChannels = 1; + info->maxInputChannels = in_caps.dwChannels; + + // Get sample rate and format information. + if( in_caps.dwChannels == 2 ) { + if( in_caps.dwFormats & WAVE_FORMAT_1S16 ) info->nativeFormats |= RTAUDIO_SINT16; + if( in_caps.dwFormats & WAVE_FORMAT_2S16 ) info->nativeFormats |= RTAUDIO_SINT16; + if( in_caps.dwFormats & WAVE_FORMAT_4S16 ) info->nativeFormats |= RTAUDIO_SINT16; + if( in_caps.dwFormats & WAVE_FORMAT_1S08 ) info->nativeFormats |= RTAUDIO_SINT8; + if( in_caps.dwFormats & WAVE_FORMAT_2S08 ) info->nativeFormats |= RTAUDIO_SINT8; + if( in_caps.dwFormats & WAVE_FORMAT_4S08 ) info->nativeFormats |= RTAUDIO_SINT8; + + if ( info->nativeFormats & RTAUDIO_SINT16 ) { + if( in_caps.dwFormats & WAVE_FORMAT_1S16 ) info->sampleRates[info->nSampleRates++] = 11025; + if( in_caps.dwFormats & WAVE_FORMAT_2S16 ) info->sampleRates[info->nSampleRates++] = 22050; + if( in_caps.dwFormats & WAVE_FORMAT_4S16 ) info->sampleRates[info->nSampleRates++] = 44100; + } + else if ( info->nativeFormats & RTAUDIO_SINT8 ) { + if( in_caps.dwFormats & WAVE_FORMAT_1S08 ) info->sampleRates[info->nSampleRates++] = 11025; + if( in_caps.dwFormats & WAVE_FORMAT_2S08 ) info->sampleRates[info->nSampleRates++] = 22050; + if( in_caps.dwFormats & WAVE_FORMAT_4S08 ) info->sampleRates[info->nSampleRates++] = 44100; + } + } + else if ( in_caps.dwChannels == 1 ) { + if( in_caps.dwFormats & WAVE_FORMAT_1M16 ) info->nativeFormats |= RTAUDIO_SINT16; + if( in_caps.dwFormats & WAVE_FORMAT_2M16 ) info->nativeFormats |= RTAUDIO_SINT16; + if( in_caps.dwFormats & WAVE_FORMAT_4M16 ) info->nativeFormats |= RTAUDIO_SINT16; + if( in_caps.dwFormats & WAVE_FORMAT_1M08 ) info->nativeFormats |= RTAUDIO_SINT8; + if( in_caps.dwFormats & WAVE_FORMAT_2M08 ) info->nativeFormats |= RTAUDIO_SINT8; + if( in_caps.dwFormats & WAVE_FORMAT_4M08 ) info->nativeFormats |= RTAUDIO_SINT8; + + if ( info->nativeFormats & RTAUDIO_SINT16 ) { + if( in_caps.dwFormats & WAVE_FORMAT_1M16 ) info->sampleRates[info->nSampleRates++] = 11025; + if( in_caps.dwFormats & WAVE_FORMAT_2M16 ) info->sampleRates[info->nSampleRates++] = 22050; + if( in_caps.dwFormats & WAVE_FORMAT_4M16 ) info->sampleRates[info->nSampleRates++] = 44100; + } + else if ( info->nativeFormats & RTAUDIO_SINT8 ) { + if( in_caps.dwFormats & WAVE_FORMAT_1M08 ) info->sampleRates[info->nSampleRates++] = 11025; + if( in_caps.dwFormats & WAVE_FORMAT_2M08 ) info->sampleRates[info->nSampleRates++] = 22050; + if( in_caps.dwFormats & WAVE_FORMAT_4M08 ) info->sampleRates[info->nSampleRates++] = 44100; + } + } + else info->minInputChannels = 0; // technically, this would be an error + + input->Release(); + + playback_probe: + LPDIRECTSOUND output; + DSCAPS out_caps; + + // Now do playback probe. If this is not the default device (index + // = 0) _and_ GUID = NULL, then the playback handle is invalid. + if ( index != 0 && info->id[0] == NULL ) + goto check_parameters; + + result = DirectSoundCreate( info->id[0], &output, NULL ); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Could not create DirectSound playback object (%s): %s.", + info->name, getErrorString(result)); + error(RtError::WARNING); + goto check_parameters; + } + + out_caps.dwSize = sizeof(out_caps); + result = output->GetCaps( &out_caps ); + if ( FAILED(result) ) { + output->Release(); + sprintf(message, "RtAudio: Could not get DirectSound playback capabilities (%s): %s.", + info->name, getErrorString(result)); + error(RtError::WARNING); + goto check_parameters; + } + + // Get output channel information. + info->minOutputChannels = 1; + info->maxOutputChannels = ( out_caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + + // Get sample rate information. Use capture device rate information + // if it exists. + if ( info->nSampleRates == 0 ) { + info->sampleRates[0] = (int) out_caps.dwMinSecondarySampleRate; + info->sampleRates[1] = (int) out_caps.dwMaxSecondarySampleRate; + if ( out_caps.dwFlags & DSCAPS_CONTINUOUSRATE ) + info->nSampleRates = -1; + else if ( out_caps.dwMinSecondarySampleRate == out_caps.dwMaxSecondarySampleRate ) { + if ( out_caps.dwMinSecondarySampleRate == 0 ) { + // This is a bogus driver report ... fake the range and cross + // your fingers. + info->sampleRates[0] = 11025; + info->sampleRates[1] = 48000; + info->nSampleRates = -1; /* continuous range */ + sprintf(message, "RtAudio: bogus sample rates reported by DirectSound driver ... using defaults (%s).", + info->name); + error(RtError::WARNING); + } + else { + info->nSampleRates = 1; + } + } + else if ( (out_caps.dwMinSecondarySampleRate < 1000.0) && + (out_caps.dwMaxSecondarySampleRate > 50000.0) ) { + // This is a bogus driver report ... support for only two + // distant rates. We'll assume this is a range. + info->nSampleRates = -1; + sprintf(message, "RtAudio: bogus sample rates reported by DirectSound driver ... using range (%s).", + info->name); + error(RtError::WARNING); + } + else info->nSampleRates = 2; + } + else { + // Check input rates against output rate range + for ( int i=info->nSampleRates-1; i>=0; i-- ) { + if ( info->sampleRates[i] <= out_caps.dwMaxSecondarySampleRate ) + break; + info->nSampleRates--; + } + while ( info->sampleRates[0] < out_caps.dwMinSecondarySampleRate ) { + info->nSampleRates--; + for ( int i=0; inSampleRates; i++) + info->sampleRates[i] = info->sampleRates[i+1]; + if ( info->nSampleRates <= 0 ) break; + } + } + + // Get format information. + if ( out_caps.dwFlags & DSCAPS_PRIMARY16BIT ) info->nativeFormats |= RTAUDIO_SINT16; + if ( out_caps.dwFlags & DSCAPS_PRIMARY8BIT ) info->nativeFormats |= RTAUDIO_SINT8; + + output->Release(); + + check_parameters: + if ( info->maxInputChannels == 0 && info->maxOutputChannels == 0 ) + return; + if ( info->nSampleRates == 0 || info->nativeFormats == 0 ) + return; + + // Determine duplex status. + if (info->maxInputChannels < info->maxOutputChannels) + info->maxDuplexChannels = info->maxInputChannels; + else + info->maxDuplexChannels = info->maxOutputChannels; + if (info->minInputChannels < info->minOutputChannels) + info->minDuplexChannels = info->minInputChannels; + else + info->minDuplexChannels = info->minOutputChannels; + + if ( info->maxDuplexChannels > 0 ) info->hasDuplexSupport = true; + else info->hasDuplexSupport = false; + + info->probed = true; + + return; +} + +bool RtAudio :: probeDeviceOpen(int device, RTAUDIO_STREAM *stream, + STREAM_MODE mode, int channels, + int sampleRate, RTAUDIO_FORMAT format, + int *bufferSize, int numberOfBuffers) +{ + HRESULT result; + HWND hWnd = GetForegroundWindow(); + // According to a note in PortAudio, using GetDesktopWindow() + // instead of GetForegroundWindow() is supposed to avoid problems + // that occur when the application's window is not the foreground + // window. Also, if the application window closes before the + // DirectSound buffer, DirectSound can crash. However, for console + // applications, no sound was produced when using GetDesktopWindow(). + long buffer_size; + LPVOID audioPtr; + DWORD dataLen; + int nBuffers; + + // Check the numberOfBuffers parameter and limit the lowest value to + // two. This is a judgement call and a value of two is probably too + // low for capture, but it should work for playback. + if (numberOfBuffers < 2) + nBuffers = 2; + else + nBuffers = numberOfBuffers; + + // Define the wave format structure (16-bit PCM, srate, channels) + WAVEFORMATEX waveFormat; + ZeroMemory(&waveFormat, sizeof(WAVEFORMATEX)); + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nChannels = channels; + waveFormat.nSamplesPerSec = (unsigned long) sampleRate; + + // Determine the data format. + if ( devices[device].nativeFormats ) { // 8-bit and/or 16-bit support + if ( format == RTAUDIO_SINT8 ) { + if ( devices[device].nativeFormats & RTAUDIO_SINT8 ) + waveFormat.wBitsPerSample = 8; + else + waveFormat.wBitsPerSample = 16; + } + else { + if ( devices[device].nativeFormats & RTAUDIO_SINT16 ) + waveFormat.wBitsPerSample = 16; + else + waveFormat.wBitsPerSample = 8; + } + } + else { + sprintf(message, "RtAudio: no reported data formats for DirectSound device (%s).", + devices[device].name); + error(RtError::WARNING); + return FAILURE; + } + + waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + + if ( mode == PLAYBACK ) { + + if ( devices[device].maxOutputChannels < channels ) + return FAILURE; + + LPGUID id = devices[device].id[0]; + LPDIRECTSOUND object; + LPDIRECTSOUNDBUFFER buffer; + DSBUFFERDESC bufferDescription; + + result = DirectSoundCreate( id, &object, NULL ); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Could not create DirectSound playback object (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + // Set cooperative level to DSSCL_EXCLUSIVE + result = object->SetCooperativeLevel(hWnd, DSSCL_EXCLUSIVE); + if ( FAILED(result) ) { + object->Release(); + sprintf(message, "RtAudio: Unable to set DirectSound cooperative level (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + // Even though we will write to the secondary buffer, we need to + // access the primary buffer to set the correct output format. + // The default is 8-bit, 22 kHz! + // Setup the DS primary buffer description. + ZeroMemory(&bufferDescription, sizeof(DSBUFFERDESC)); + bufferDescription.dwSize = sizeof(DSBUFFERDESC); + bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; + // Obtain the primary buffer + result = object->CreateSoundBuffer(&bufferDescription, &buffer, NULL); + if ( FAILED(result) ) { + object->Release(); + sprintf(message, "RtAudio: Unable to access DS primary buffer (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + // Set the primary DS buffer sound format. + result = buffer->SetFormat(&waveFormat); + if ( FAILED(result) ) { + object->Release(); + sprintf(message, "RtAudio: Unable to set DS primary buffer format (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + // Setup the secondary DS buffer description. + buffer_size = channels * *bufferSize * nBuffers * waveFormat.wBitsPerSample / 8; + ZeroMemory(&bufferDescription, sizeof(DSBUFFERDESC)); + bufferDescription.dwSize = sizeof(DSBUFFERDESC); + bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | + DSBCAPS_GETCURRENTPOSITION2 | + DSBCAPS_LOCHARDWARE ); // Force hardware mixing + bufferDescription.dwBufferBytes = buffer_size; + bufferDescription.lpwfxFormat = &waveFormat; + + // Try to create the secondary DS buffer. If that doesn't work, + // try to use software mixing. Otherwise, there's a problem. + result = object->CreateSoundBuffer(&bufferDescription, &buffer, NULL); + if ( FAILED(result) ) { + bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | + DSBCAPS_GETCURRENTPOSITION2 | + DSBCAPS_LOCSOFTWARE ); // Force software mixing + result = object->CreateSoundBuffer(&bufferDescription, &buffer, NULL); + if ( FAILED(result) ) { + object->Release(); + sprintf(message, "RtAudio: Unable to create secondary DS buffer (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + } + + // Get the buffer size ... might be different from what we specified. + DSBCAPS dsbcaps; + dsbcaps.dwSize = sizeof(DSBCAPS); + buffer->GetCaps(&dsbcaps); + buffer_size = dsbcaps.dwBufferBytes; + + // Lock the DS buffer + result = buffer->Lock(0, buffer_size, &audioPtr, &dataLen, NULL, NULL, 0); + if ( FAILED(result) ) { + object->Release(); + sprintf(message, "RtAudio: Unable to lock DS buffer (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + // Zero the DS buffer + ZeroMemory(audioPtr, dataLen); + + // Unlock the DS buffer + result = buffer->Unlock(audioPtr, dataLen, NULL, 0); + if ( FAILED(result) ) { + object->Release(); + sprintf(message, "RtAudio: Unable to unlock DS buffer(%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + stream->handle[0].object = (void *) object; + stream->handle[0].buffer = (void *) buffer; + stream->nDeviceChannels[0] = channels; + } + + if ( mode == RECORD ) { + + if ( devices[device].maxInputChannels < channels ) + return FAILURE; + + LPGUID id = devices[device].id[1]; + LPDIRECTSOUNDCAPTURE object; + LPDIRECTSOUNDCAPTUREBUFFER buffer; + DSCBUFFERDESC bufferDescription; + + result = DirectSoundCaptureCreate( id, &object, NULL ); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Could not create DirectSound capture object (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + // Setup the secondary DS buffer description. + buffer_size = channels * *bufferSize * nBuffers * waveFormat.wBitsPerSample / 8; + ZeroMemory(&bufferDescription, sizeof(DSCBUFFERDESC)); + bufferDescription.dwSize = sizeof(DSCBUFFERDESC); + bufferDescription.dwFlags = 0; + bufferDescription.dwReserved = 0; + bufferDescription.dwBufferBytes = buffer_size; + bufferDescription.lpwfxFormat = &waveFormat; + + // Create the capture buffer. + result = object->CreateCaptureBuffer(&bufferDescription, &buffer, NULL); + if ( FAILED(result) ) { + object->Release(); + sprintf(message, "RtAudio: Unable to create DS capture buffer (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + // Lock the capture buffer + result = buffer->Lock(0, buffer_size, &audioPtr, &dataLen, NULL, NULL, 0); + if ( FAILED(result) ) { + object->Release(); + sprintf(message, "RtAudio: Unable to lock DS capture buffer (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + // Zero the buffer + ZeroMemory(audioPtr, dataLen); + + // Unlock the buffer + result = buffer->Unlock(audioPtr, dataLen, NULL, 0); + if ( FAILED(result) ) { + object->Release(); + sprintf(message, "RtAudio: Unable to unlock DS capture buffer (%s): %s.", + devices[device].name, getErrorString(result)); + error(RtError::WARNING); + return FAILURE; + } + + stream->handle[1].object = (void *) object; + stream->handle[1].buffer = (void *) buffer; + stream->nDeviceChannels[1] = channels; + } + + stream->userFormat = format; + if ( waveFormat.wBitsPerSample == 8 ) + stream->deviceFormat[mode] = RTAUDIO_SINT8; + else + stream->deviceFormat[mode] = RTAUDIO_SINT16; + stream->nUserChannels[mode] = channels; + *bufferSize = buffer_size / (channels * nBuffers * waveFormat.wBitsPerSample / 8); + stream->bufferSize = *bufferSize; + + // Set flags for buffer conversion + stream->doConvertBuffer[mode] = false; + if (stream->userFormat != stream->deviceFormat[mode]) + stream->doConvertBuffer[mode] = true; + if (stream->nUserChannels[mode] < stream->nDeviceChannels[mode]) + stream->doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + if ( stream->nUserChannels[0] != stream->nUserChannels[1] ) { + + long buffer_bytes; + if (stream->nUserChannels[0] >= stream->nUserChannels[1]) + buffer_bytes = stream->nUserChannels[0]; + else + buffer_bytes = stream->nUserChannels[1]; + + buffer_bytes *= *bufferSize * formatBytes(stream->userFormat); + if (stream->userBuffer) free(stream->userBuffer); + stream->userBuffer = (char *) calloc(buffer_bytes, 1); + if (stream->userBuffer == NULL) + goto memory_error; + } + + if ( stream->doConvertBuffer[mode] ) { + + long buffer_bytes; + bool makeBuffer = true; + if ( mode == PLAYBACK ) + buffer_bytes = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); + else { // mode == RECORD + buffer_bytes = stream->nDeviceChannels[1] * formatBytes(stream->deviceFormat[1]); + if ( stream->mode == PLAYBACK ) { + long bytes_out = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); + if ( buffer_bytes > bytes_out ) + buffer_bytes = (buffer_bytes > bytes_out) ? buffer_bytes : bytes_out; + else + makeBuffer = false; + } + } + + if ( makeBuffer ) { + buffer_bytes *= *bufferSize; + if (stream->deviceBuffer) free(stream->deviceBuffer); + stream->deviceBuffer = (char *) calloc(buffer_bytes, 1); + if (stream->deviceBuffer == NULL) + goto memory_error; + } + } + + stream->device[mode] = device; + stream->state = STREAM_STOPPED; + if ( stream->mode == PLAYBACK && mode == RECORD ) + // We had already set up an output stream. + stream->mode = DUPLEX; + else + stream->mode = mode; + stream->nBuffers = nBuffers; + stream->sampleRate = sampleRate; + + return SUCCESS; + + memory_error: + if (stream->handle[0].object) { + LPDIRECTSOUND object = (LPDIRECTSOUND) stream->handle[0].object; + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; + if (buffer) { + buffer->Release(); + stream->handle[0].buffer = NULL; + } + object->Release(); + stream->handle[0].object = NULL; + } + if (stream->handle[1].object) { + LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) stream->handle[1].object; + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; + if (buffer) { + buffer->Release(); + stream->handle[1].buffer = NULL; + } + object->Release(); + stream->handle[1].object = NULL; + } + if (stream->userBuffer) { + free(stream->userBuffer); + stream->userBuffer = 0; + } + sprintf(message, "RtAudio: error allocating buffer memory (%s).", + devices[device].name); + error(RtError::WARNING); + return FAILURE; +} + +void RtAudio :: cancelStreamCallback(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + if (stream->usingCallback) { + stream->usingCallback = false; + WaitForSingleObject( (HANDLE)stream->thread, INFINITE ); + CloseHandle( (HANDLE)stream->thread ); + stream->thread = 0; + stream->callback = NULL; + stream->userData = NULL; + } +} + +void RtAudio :: closeStream(int streamId) +{ + // We don't want an exception to be thrown here because this + // function is called by our class destructor. So, do our own + // streamId check. + if ( streams.find( streamId ) == streams.end() ) { + sprintf(message, "RtAudio: invalid stream identifier!"); + error(RtError::WARNING); + return; + } + + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) streams[streamId]; + + if (stream->usingCallback) { + stream->usingCallback = false; + WaitForSingleObject( (HANDLE)stream->thread, INFINITE ); + CloseHandle( (HANDLE)stream->thread ); + } + + DeleteCriticalSection(&stream->mutex); + + if (stream->handle[0].object) { + LPDIRECTSOUND object = (LPDIRECTSOUND) stream->handle[0].object; + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; + if (buffer) { + buffer->Stop(); + buffer->Release(); + } + object->Release(); + } + + if (stream->handle[1].object) { + LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) stream->handle[1].object; + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; + if (buffer) { + buffer->Stop(); + buffer->Release(); + } + object->Release(); + } + + if (stream->userBuffer) + free(stream->userBuffer); + + if (stream->deviceBuffer) + free(stream->deviceBuffer); + + free(stream); + streams.erase(streamId); +} + +void RtAudio :: startStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_RUNNING) + goto unlock; + + HRESULT result; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; + result = buffer->Play(0, 0, DSBPLAY_LOOPING ); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to start DS buffer (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; + result = buffer->Start(DSCBSTART_LOOPING ); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to start DS capture buffer (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + } + stream->state = STREAM_RUNNING; + + unlock: + MUTEX_UNLOCK(&stream->mutex); +} + +void RtAudio :: stopStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_STOPPED) { + MUTEX_UNLOCK(&stream->mutex); + return; + } + + // There is no specific DirectSound API call to "drain" a buffer + // before stopping. We can hack this for playback by writing zeroes + // for another bufferSize * nBuffers frames. For capture, the + // concept is less clear so we'll repeat what we do in the + // abortStream() case. + HRESULT result; + DWORD dsBufferSize; + LPVOID buffer1 = NULL; + LPVOID buffer2 = NULL; + DWORD bufferSize1 = 0; + DWORD bufferSize2 = 0; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + + DWORD currentPos, safePos; + long buffer_bytes = stream->bufferSize * stream->nDeviceChannels[0]; + buffer_bytes *= formatBytes(stream->deviceFormat[0]); + + LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; + UINT nextWritePos = stream->handle[0].bufferPointer; + dsBufferSize = buffer_bytes * stream->nBuffers; + + // Write zeroes for nBuffer counts. + for (int i=0; inBuffers; i++) { + + // Find out where the read and "safe write" pointers are. + result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset + DWORD endWrite = nextWritePos + buffer_bytes; + + // Check whether the entire write region is behind the play pointer. + while ( currentPos < endWrite ) { + float millis = (endWrite - currentPos) * 900.0; + millis /= ( formatBytes(stream->deviceFormat[0]) * stream->sampleRate); + if ( millis < 1.0 ) millis = 1.0; + Sleep( (DWORD) millis ); + + // Wake up, find out where we are now + result = dsBuffer->GetCurrentPosition( ¤tPos, &safePos ); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset + } + + // Lock free space in the buffer + result = dsBuffer->Lock (nextWritePos, buffer_bytes, &buffer1, + &bufferSize1, &buffer2, &bufferSize2, 0); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to lock DS buffer during playback (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // Zero the free space + ZeroMemory(buffer1, bufferSize1); + if (buffer2 != NULL) ZeroMemory(buffer2, bufferSize2); + + // Update our buffer offset and unlock sound buffer + dsBuffer->Unlock (buffer1, bufferSize1, buffer2, bufferSize2); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to unlock DS buffer during playback (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + nextWritePos = (nextWritePos + bufferSize1 + bufferSize2) % dsBufferSize; + stream->handle[0].bufferPointer = nextWritePos; + } + + // If we play again, start at the beginning of the buffer. + stream->handle[0].bufferPointer = 0; + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; + buffer1 = NULL; + bufferSize1 = 0; + + result = buffer->Stop(); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to stop DS capture buffer (%s): %s", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + dsBufferSize = stream->bufferSize * stream->nDeviceChannels[1]; + dsBufferSize *= formatBytes(stream->deviceFormat[1]) * stream->nBuffers; + + // Lock the buffer and clear it so that if we start to play again, + // we won't have old data playing. + result = buffer->Lock(0, dsBufferSize, &buffer1, &bufferSize1, NULL, NULL, 0); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to lock DS capture buffer (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // Zero the DS buffer + ZeroMemory(buffer1, bufferSize1); + + // Unlock the DS buffer + result = buffer->Unlock(buffer1, bufferSize1, NULL, 0); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to unlock DS capture buffer (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // If we start recording again, we must begin at beginning of buffer. + stream->handle[1].bufferPointer = 0; + } + stream->state = STREAM_STOPPED; + + MUTEX_UNLOCK(&stream->mutex); +} + +void RtAudio :: abortStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_STOPPED) + goto unlock; + + HRESULT result; + long dsBufferSize; + LPVOID audioPtr; + DWORD dataLen; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; + result = buffer->Stop(); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to stop DS buffer (%s): %s", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + dsBufferSize = stream->bufferSize * stream->nDeviceChannels[0]; + dsBufferSize *= formatBytes(stream->deviceFormat[0]) * stream->nBuffers; + + // Lock the buffer and clear it so that if we start to play again, + // we won't have old data playing. + result = buffer->Lock(0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to lock DS buffer (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // Zero the DS buffer + ZeroMemory(audioPtr, dataLen); + + // Unlock the DS buffer + result = buffer->Unlock(audioPtr, dataLen, NULL, 0); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to unlock DS buffer (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // If we start playing again, we must begin at beginning of buffer. + stream->handle[0].bufferPointer = 0; + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; + audioPtr = NULL; + dataLen = 0; + + result = buffer->Stop(); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to stop DS capture buffer (%s): %s", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + dsBufferSize = stream->bufferSize * stream->nDeviceChannels[1]; + dsBufferSize *= formatBytes(stream->deviceFormat[1]) * stream->nBuffers; + + // Lock the buffer and clear it so that if we start to play again, + // we won't have old data playing. + result = buffer->Lock(0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to lock DS capture buffer (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // Zero the DS buffer + ZeroMemory(audioPtr, dataLen); + + // Unlock the DS buffer + result = buffer->Unlock(audioPtr, dataLen, NULL, 0); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to unlock DS capture buffer (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // If we start recording again, we must begin at beginning of buffer. + stream->handle[1].bufferPointer = 0; + } + stream->state = STREAM_STOPPED; + + unlock: + MUTEX_UNLOCK(&stream->mutex); +} + +int RtAudio :: streamWillBlock(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + int channels; + int frames = 0; + if (stream->state == STREAM_STOPPED) + goto unlock; + + HRESULT result; + DWORD currentPos, safePos; + channels = 1; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + + LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; + UINT nextWritePos = stream->handle[0].bufferPointer; + channels = stream->nDeviceChannels[0]; + DWORD dsBufferSize = stream->bufferSize * channels; + dsBufferSize *= formatBytes(stream->deviceFormat[0]) * stream->nBuffers; + + // Find out where the read and "safe write" pointers are. + result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset + frames = currentPos - nextWritePos; + frames /= channels * formatBytes(stream->deviceFormat[0]); + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + + LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; + UINT nextReadPos = stream->handle[1].bufferPointer; + channels = stream->nDeviceChannels[1]; + DWORD dsBufferSize = stream->bufferSize * channels; + dsBufferSize *= formatBytes(stream->deviceFormat[1]) * stream->nBuffers; + + // Find out where the write and "safe read" pointers are. + result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to get current DS capture position (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + if ( safePos < nextReadPos ) safePos += dsBufferSize; // unwrap offset + + if (stream->mode == DUPLEX ) { + // Take largest value of the two. + int temp = safePos - nextReadPos; + temp /= channels * formatBytes(stream->deviceFormat[1]); + frames = ( temp > frames ) ? temp : frames; + } + else { + frames = safePos - nextReadPos; + frames /= channels * formatBytes(stream->deviceFormat[1]); + } + } + + frames = stream->bufferSize - frames; + if (frames < 0) frames = 0; + + unlock: + MUTEX_UNLOCK(&stream->mutex); + return frames; +} + +void RtAudio :: tickStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + int stopStream = 0; + if (stream->state == STREAM_STOPPED) { + if (stream->usingCallback) Sleep(50); // sleep 50 milliseconds + return; + } + else if (stream->usingCallback) { + stopStream = stream->callback(stream->userBuffer, stream->bufferSize, stream->userData); + } + + MUTEX_LOCK(&stream->mutex); + + // The state might change while waiting on a mutex. + if (stream->state == STREAM_STOPPED) { + MUTEX_UNLOCK(&stream->mutex); + if (stream->usingCallback && stopStream) + this->stopStream(streamId); + } + + HRESULT result; + DWORD currentPos, safePos; + LPVOID buffer1 = NULL; + LPVOID buffer2 = NULL; + DWORD bufferSize1 = 0; + DWORD bufferSize2 = 0; + char *buffer; + long buffer_bytes; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + + // Setup parameters and do buffer conversion if necessary. + if (stream->doConvertBuffer[0]) { + convertStreamBuffer(stream, PLAYBACK); + buffer = stream->deviceBuffer; + buffer_bytes = stream->bufferSize * stream->nDeviceChannels[0]; + buffer_bytes *= formatBytes(stream->deviceFormat[0]); + } + else { + buffer = stream->userBuffer; + buffer_bytes = stream->bufferSize * stream->nUserChannels[0]; + buffer_bytes *= formatBytes(stream->userFormat); + } + + // No byte swapping necessary in DirectSound implementation. + + LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) stream->handle[0].buffer; + UINT nextWritePos = stream->handle[0].bufferPointer; + DWORD dsBufferSize = buffer_bytes * stream->nBuffers; + + // Find out where the read and "safe write" pointers are. + result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset + DWORD endWrite = nextWritePos + buffer_bytes; + + // Check whether the entire write region is behind the play pointer. + while ( currentPos < endWrite ) { + // If we are here, then we must wait until the play pointer gets + // beyond the write region. The approach here is to use the + // Sleep() function to suspend operation until safePos catches + // up. Calculate number of milliseconds to wait as: + // time = distance * (milliseconds/second) * fudgefactor / + // ((bytes/sample) * (samples/second)) + // A "fudgefactor" less than 1 is used because it was found + // that sleeping too long was MUCH worse than sleeping for + // several shorter periods. + float millis = (endWrite - currentPos) * 900.0; + millis /= ( formatBytes(stream->deviceFormat[0]) * stream->sampleRate); + if ( millis < 1.0 ) millis = 1.0; + Sleep( (DWORD) millis ); + + // Wake up, find out where we are now + result = dsBuffer->GetCurrentPosition( ¤tPos, &safePos ); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to get current DS position (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + if ( currentPos < nextWritePos ) currentPos += dsBufferSize; // unwrap offset + } + + // Lock free space in the buffer + result = dsBuffer->Lock (nextWritePos, buffer_bytes, &buffer1, + &bufferSize1, &buffer2, &bufferSize2, 0); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to lock DS buffer during playback (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // Copy our buffer into the DS buffer + CopyMemory(buffer1, buffer, bufferSize1); + if (buffer2 != NULL) CopyMemory(buffer2, buffer+bufferSize1, bufferSize2); + + // Update our buffer offset and unlock sound buffer + dsBuffer->Unlock (buffer1, bufferSize1, buffer2, bufferSize2); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to unlock DS buffer during playback (%s): %s.", + devices[stream->device[0]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + nextWritePos = (nextWritePos + bufferSize1 + bufferSize2) % dsBufferSize; + stream->handle[0].bufferPointer = nextWritePos; + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + + // Setup parameters. + if (stream->doConvertBuffer[1]) { + buffer = stream->deviceBuffer; + buffer_bytes = stream->bufferSize * stream->nDeviceChannels[1]; + buffer_bytes *= formatBytes(stream->deviceFormat[1]); + } + else { + buffer = stream->userBuffer; + buffer_bytes = stream->bufferSize * stream->nUserChannels[1]; + buffer_bytes *= formatBytes(stream->userFormat); + } + + LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) stream->handle[1].buffer; + UINT nextReadPos = stream->handle[1].bufferPointer; + DWORD dsBufferSize = buffer_bytes * stream->nBuffers; + + // Find out where the write and "safe read" pointers are. + result = dsBuffer->GetCurrentPosition(¤tPos, &safePos); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to get current DS capture position (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + if ( safePos < nextReadPos ) safePos += dsBufferSize; // unwrap offset + DWORD endRead = nextReadPos + buffer_bytes; + + // Check whether the entire write region is behind the play pointer. + while ( safePos < endRead ) { + // See comments for playback. + float millis = (endRead - safePos) * 900.0; + millis /= ( formatBytes(stream->deviceFormat[1]) * stream->sampleRate); + if ( millis < 1.0 ) millis = 1.0; + Sleep( (DWORD) millis ); + + // Wake up, find out where we are now + result = dsBuffer->GetCurrentPosition( ¤tPos, &safePos ); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to get current DS capture position (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + if ( safePos < nextReadPos ) safePos += dsBufferSize; // unwrap offset + } + + // Lock free space in the buffer + result = dsBuffer->Lock (nextReadPos, buffer_bytes, &buffer1, + &bufferSize1, &buffer2, &bufferSize2, 0); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to lock DS buffer during capture (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + + // Copy our buffer into the DS buffer + CopyMemory(buffer, buffer1, bufferSize1); + if (buffer2 != NULL) CopyMemory(buffer+bufferSize1, buffer2, bufferSize2); + + // Update our buffer offset and unlock sound buffer + nextReadPos = (nextReadPos + bufferSize1 + bufferSize2) % dsBufferSize; + dsBuffer->Unlock (buffer1, bufferSize1, buffer2, bufferSize2); + if ( FAILED(result) ) { + sprintf(message, "RtAudio: Unable to unlock DS buffer during capture (%s): %s.", + devices[stream->device[1]].name, getErrorString(result)); + error(RtError::DRIVER_ERROR); + } + stream->handle[1].bufferPointer = nextReadPos; + + // No byte swapping necessary in DirectSound implementation. + + // Do buffer conversion if necessary. + if (stream->doConvertBuffer[1]) + convertStreamBuffer(stream, RECORD); + } + + MUTEX_UNLOCK(&stream->mutex); + + if (stream->usingCallback && stopStream) + this->stopStream(streamId); +} + +// Definitions for utility functions and callbacks +// specific to the DirectSound implementation. + +extern "C" unsigned __stdcall callbackHandler(void *ptr) +{ + RtAudio *object = thread_info.object; + int stream = thread_info.streamId; + bool *usingCallback = (bool *) ptr; + + while ( *usingCallback ) { + try { + object->tickStream(stream); + } + catch (RtError &exception) { + fprintf(stderr, "\nCallback thread error (%s) ... closing thread.\n\n", + exception.getMessage()); + break; + } + } + + _endthreadex( 0 ); + return 0; +} + +static bool CALLBACK deviceCountCallback(LPGUID lpguid, + LPCSTR lpcstrDescription, + LPCSTR lpcstrModule, + LPVOID lpContext) +{ + int *pointer = ((int *) lpContext); + (*pointer)++; + + return true; +} + +static bool CALLBACK deviceInfoCallback(LPGUID lpguid, + LPCSTR lpcstrDescription, + LPCSTR lpcstrModule, + LPVOID lpContext) +{ + enum_info *info = ((enum_info *) lpContext); + while (strlen(info->name) > 0) info++; + + strncpy(info->name, lpcstrDescription, 64); + info->id = lpguid; + + HRESULT hr; + info->isValid = false; + if (info->isInput == true) { + DSCCAPS caps; + LPDIRECTSOUNDCAPTURE object; + + hr = DirectSoundCaptureCreate( lpguid, &object, NULL ); + if( hr != DS_OK ) return true; + + caps.dwSize = sizeof(caps); + hr = object->GetCaps( &caps ); + if( hr == DS_OK ) { + if (caps.dwChannels > 0 && caps.dwFormats > 0) + info->isValid = true; + } + object->Release(); + } + else { + DSCAPS caps; + LPDIRECTSOUND object; + hr = DirectSoundCreate( lpguid, &object, NULL ); + if( hr != DS_OK ) return true; + + caps.dwSize = sizeof(caps); + hr = object->GetCaps( &caps ); + if( hr == DS_OK ) { + if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO ) + info->isValid = true; + } + object->Release(); + } + + return true; +} + +static char* getErrorString(int code) +{ + switch (code) { + + case DSERR_ALLOCATED: + return "Direct Sound already allocated"; + + case DSERR_CONTROLUNAVAIL: + return "Direct Sound control unavailable"; + + case DSERR_INVALIDPARAM: + return "Direct Sound invalid parameter"; + + case DSERR_INVALIDCALL: + return "Direct Sound invalid call"; + + case DSERR_GENERIC: + return "Direct Sound generic error"; + + case DSERR_PRIOLEVELNEEDED: + return "Direct Sound Priority level needed"; + + case DSERR_OUTOFMEMORY: + return "Direct Sound out of memory"; + + case DSERR_BADFORMAT: + return "Direct Sound bad format"; + + case DSERR_UNSUPPORTED: + return "Direct Sound unsupported error"; + + case DSERR_NODRIVER: + return "Direct Sound no driver error"; + + case DSERR_ALREADYINITIALIZED: + return "Direct Sound already initialized"; + + case DSERR_NOAGGREGATION: + return "Direct Sound no aggregation"; + + case DSERR_BUFFERLOST: + return "Direct Sound buffer lost"; + + case DSERR_OTHERAPPHASPRIO: + return "Direct Sound other app has priority"; + + case DSERR_UNINITIALIZED: + return "Direct Sound uninitialized"; + + default: + return "Direct Sound unknown error"; + } +} + +//******************** End of __WINDOWS_DS__ *********************// + +#elif defined(__IRIX_AL__) // SGI's AL API for IRIX + +#include +#include + +void RtAudio :: initialize(void) +{ + + // Count cards and devices + nDevices = 0; + + // Determine the total number of input and output devices. + nDevices = alQueryValues(AL_SYSTEM, AL_DEVICES, 0, 0, 0, 0); + if (nDevices < 0) { + sprintf(message, "RtAudio: AL error counting devices: %s.", + alGetErrorString(oserror())); + error(RtError::DRIVER_ERROR); + } + + if (nDevices <= 0) return; + + ALvalue *vls = (ALvalue *) new ALvalue[nDevices]; + + // Add one for our default input/output devices. + nDevices++; + + // Allocate the RTAUDIO_DEVICE structures. + devices = (RTAUDIO_DEVICE *) calloc(nDevices, sizeof(RTAUDIO_DEVICE)); + if (devices == NULL) { + sprintf(message, "RtAudio: memory allocation error!"); + error(RtError::MEMORY_ERROR); + } + + // Write device ascii identifiers to device info structure. + char name[32]; + int outs, ins, i; + ALpv pvs[1]; + pvs[0].param = AL_NAME; + pvs[0].value.ptr = name; + pvs[0].sizeIn = 32; + + strcpy(devices[0].name, "Default Input/Output Devices"); + + outs = alQueryValues(AL_SYSTEM, AL_DEFAULT_OUTPUT, vls, nDevices-1, 0, 0); + if (outs < 0) { + sprintf(message, "RtAudio: AL error getting output devices: %s.", + alGetErrorString(oserror())); + error(RtError::DRIVER_ERROR); + } + + for (i=0; iname, "Default Input/Output Devices", 28) ) { + result = alQueryValues(AL_SYSTEM, AL_DEFAULT_OUTPUT, &value, 1, 0, 0); + if (result < 0) { + sprintf(message, "RtAudio: AL error getting default output device id: %s.", + alGetErrorString(oserror())); + error(RtError::WARNING); + } + else + resource = value.i; + } + else + resource = info->id[0]; + + if (resource > 0) { + + // Probe output device parameters. + result = alQueryValues(resource, AL_CHANNELS, &value, 1, 0, 0); + if (result < 0) { + sprintf(message, "RtAudio: AL error getting device (%s) channels: %s.", + info->name, alGetErrorString(oserror())); + error(RtError::WARNING); + } + else { + info->maxOutputChannels = value.i; + info->minOutputChannels = 1; + } + + result = alGetParamInfo(resource, AL_RATE, &pinfo); + if (result < 0) { + sprintf(message, "RtAudio: AL error getting device (%s) rates: %s.", + info->name, alGetErrorString(oserror())); + error(RtError::WARNING); + } + else { + info->nSampleRates = 0; + for (i=0; i= pinfo.min.i && SAMPLE_RATES[i] <= pinfo.max.i ) { + info->sampleRates[info->nSampleRates] = SAMPLE_RATES[i]; + info->nSampleRates++; + } + } + } + + // The AL library supports all our formats, except 24-bit and 32-bit ints. + info->nativeFormats = (RTAUDIO_FORMAT) 51; + } + + // Now get input resource ID if it exists. + if ( !strncmp(info->name, "Default Input/Output Devices", 28) ) { + result = alQueryValues(AL_SYSTEM, AL_DEFAULT_INPUT, &value, 1, 0, 0); + if (result < 0) { + sprintf(message, "RtAudio: AL error getting default input device id: %s.", + alGetErrorString(oserror())); + error(RtError::WARNING); + } + else + resource = value.i; + } + else + resource = info->id[1]; + + if (resource > 0) { + + // Probe input device parameters. + result = alQueryValues(resource, AL_CHANNELS, &value, 1, 0, 0); + if (result < 0) { + sprintf(message, "RtAudio: AL error getting device (%s) channels: %s.", + info->name, alGetErrorString(oserror())); + error(RtError::WARNING); + } + else { + info->maxInputChannels = value.i; + info->minInputChannels = 1; + } + + result = alGetParamInfo(resource, AL_RATE, &pinfo); + if (result < 0) { + sprintf(message, "RtAudio: AL error getting device (%s) rates: %s.", + info->name, alGetErrorString(oserror())); + error(RtError::WARNING); + } + else { + // In the case of the default device, these values will + // overwrite the rates determined for the output device. Since + // the input device is most likely to be more limited than the + // output device, this is ok. + info->nSampleRates = 0; + for (i=0; i= pinfo.min.i && SAMPLE_RATES[i] <= pinfo.max.i ) { + info->sampleRates[info->nSampleRates] = SAMPLE_RATES[i]; + info->nSampleRates++; + } + } + } + + // The AL library supports all our formats, except 24-bit and 32-bit ints. + info->nativeFormats = (RTAUDIO_FORMAT) 51; + } + + if ( info->maxInputChannels == 0 && info->maxOutputChannels == 0 ) + return; + if ( info->nSampleRates == 0 ) + return; + + // Determine duplex status. + if (info->maxInputChannels < info->maxOutputChannels) + info->maxDuplexChannels = info->maxInputChannels; + else + info->maxDuplexChannels = info->maxOutputChannels; + if (info->minInputChannels < info->minOutputChannels) + info->minDuplexChannels = info->minInputChannels; + else + info->minDuplexChannels = info->minOutputChannels; + + if ( info->maxDuplexChannels > 0 ) info->hasDuplexSupport = true; + else info->hasDuplexSupport = false; + + info->probed = true; + + return; +} + +bool RtAudio :: probeDeviceOpen(int device, RTAUDIO_STREAM *stream, + STREAM_MODE mode, int channels, + int sampleRate, RTAUDIO_FORMAT format, + int *bufferSize, int numberOfBuffers) +{ + int result, resource, nBuffers; + ALconfig al_config; + ALport port; + ALpv pvs[2]; + + // Get a new ALconfig structure. + al_config = alNewConfig(); + if ( !al_config ) { + sprintf(message,"RtAudio: can't get AL config: %s.", + alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + + // Set the channels. + result = alSetChannels(al_config, channels); + if ( result < 0 ) { + sprintf(message,"RtAudio: can't set %d channels in AL config: %s.", + channels, alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + + // Set the queue (buffer) size. + if ( numberOfBuffers < 1 ) + nBuffers = 1; + else + nBuffers = numberOfBuffers; + long buffer_size = *bufferSize * nBuffers; + result = alSetQueueSize(al_config, buffer_size); // in sample frames + if ( result < 0 ) { + sprintf(message,"RtAudio: can't set buffer size (%ld) in AL config: %s.", + buffer_size, alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + + // Set the data format. + stream->userFormat = format; + stream->deviceFormat[mode] = format; + if (format == RTAUDIO_SINT8) { + result = alSetSampFmt(al_config, AL_SAMPFMT_TWOSCOMP); + result = alSetWidth(al_config, AL_SAMPLE_8); + } + else if (format == RTAUDIO_SINT16) { + result = alSetSampFmt(al_config, AL_SAMPFMT_TWOSCOMP); + result = alSetWidth(al_config, AL_SAMPLE_16); + } + else if (format == RTAUDIO_SINT24) { + // Our 24-bit format assumes the upper 3 bytes of a 4 byte word. + // The AL library uses the lower 3 bytes, so we'll need to do our + // own conversion. + result = alSetSampFmt(al_config, AL_SAMPFMT_FLOAT); + stream->deviceFormat[mode] = RTAUDIO_FLOAT32; + } + else if (format == RTAUDIO_SINT32) { + // The AL library doesn't seem to support the 32-bit integer + // format, so we'll need to do our own conversion. + result = alSetSampFmt(al_config, AL_SAMPFMT_FLOAT); + stream->deviceFormat[mode] = RTAUDIO_FLOAT32; + } + else if (format == RTAUDIO_FLOAT32) + result = alSetSampFmt(al_config, AL_SAMPFMT_FLOAT); + else if (format == RTAUDIO_FLOAT64) + result = alSetSampFmt(al_config, AL_SAMPFMT_DOUBLE); + + if ( result == -1 ) { + sprintf(message,"RtAudio: AL error setting sample format in AL config: %s.", + alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + + if (mode == PLAYBACK) { + + // Set our device. + if (device == 0) + resource = AL_DEFAULT_OUTPUT; + else + resource = devices[device].id[0]; + result = alSetDevice(al_config, resource); + if ( result == -1 ) { + sprintf(message,"RtAudio: AL error setting device (%s) in AL config: %s.", + devices[device].name, alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + + // Open the port. + port = alOpenPort("RtAudio Output Port", "w", al_config); + if( !port ) { + sprintf(message,"RtAudio: AL error opening output port: %s.", + alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + + // Set the sample rate + pvs[0].param = AL_MASTER_CLOCK; + pvs[0].value.i = AL_CRYSTAL_MCLK_TYPE; + pvs[1].param = AL_RATE; + pvs[1].value.ll = alDoubleToFixed((double)sampleRate); + result = alSetParams(resource, pvs, 2); + if ( result < 0 ) { + alClosePort(port); + sprintf(message,"RtAudio: AL error setting sample rate (%d) for device (%s): %s.", + sampleRate, devices[device].name, alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + } + else { // mode == RECORD + + // Set our device. + if (device == 0) + resource = AL_DEFAULT_INPUT; + else + resource = devices[device].id[1]; + result = alSetDevice(al_config, resource); + if ( result == -1 ) { + sprintf(message,"RtAudio: AL error setting device (%s) in AL config: %s.", + devices[device].name, alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + + // Open the port. + port = alOpenPort("RtAudio Output Port", "r", al_config); + if( !port ) { + sprintf(message,"RtAudio: AL error opening input port: %s.", + alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + + // Set the sample rate + pvs[0].param = AL_MASTER_CLOCK; + pvs[0].value.i = AL_CRYSTAL_MCLK_TYPE; + pvs[1].param = AL_RATE; + pvs[1].value.ll = alDoubleToFixed((double)sampleRate); + result = alSetParams(resource, pvs, 2); + if ( result < 0 ) { + alClosePort(port); + sprintf(message,"RtAudio: AL error setting sample rate (%d) for device (%s): %s.", + sampleRate, devices[device].name, alGetErrorString(oserror())); + error(RtError::WARNING); + return FAILURE; + } + } + + alFreeConfig(al_config); + + stream->nUserChannels[mode] = channels; + stream->nDeviceChannels[mode] = channels; + + // Set handle and flags for buffer conversion + stream->handle[mode] = port; + stream->doConvertBuffer[mode] = false; + if (stream->userFormat != stream->deviceFormat[mode]) + stream->doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + if ( stream->nUserChannels[0] != stream->nUserChannels[1] ) { + + long buffer_bytes; + if (stream->nUserChannels[0] >= stream->nUserChannels[1]) + buffer_bytes = stream->nUserChannels[0]; + else + buffer_bytes = stream->nUserChannels[1]; + + buffer_bytes *= *bufferSize * formatBytes(stream->userFormat); + if (stream->userBuffer) free(stream->userBuffer); + stream->userBuffer = (char *) calloc(buffer_bytes, 1); + if (stream->userBuffer == NULL) + goto memory_error; + } + + if ( stream->doConvertBuffer[mode] ) { + + long buffer_bytes; + bool makeBuffer = true; + if ( mode == PLAYBACK ) + buffer_bytes = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); + else { // mode == RECORD + buffer_bytes = stream->nDeviceChannels[1] * formatBytes(stream->deviceFormat[1]); + if ( stream->mode == PLAYBACK ) { + long bytes_out = stream->nDeviceChannels[0] * formatBytes(stream->deviceFormat[0]); + if ( buffer_bytes > bytes_out ) + buffer_bytes = (buffer_bytes > bytes_out) ? buffer_bytes : bytes_out; + else + makeBuffer = false; + } + } + + if ( makeBuffer ) { + buffer_bytes *= *bufferSize; + if (stream->deviceBuffer) free(stream->deviceBuffer); + stream->deviceBuffer = (char *) calloc(buffer_bytes, 1); + if (stream->deviceBuffer == NULL) + goto memory_error; + } + } + + stream->device[mode] = device; + stream->state = STREAM_STOPPED; + if ( stream->mode == PLAYBACK && mode == RECORD ) + // We had already set up an output stream. + stream->mode = DUPLEX; + else + stream->mode = mode; + stream->nBuffers = nBuffers; + stream->bufferSize = *bufferSize; + stream->sampleRate = sampleRate; + + return SUCCESS; + + memory_error: + if (stream->handle[0]) { + alClosePort(stream->handle[0]); + stream->handle[0] = 0; + } + if (stream->handle[1]) { + alClosePort(stream->handle[1]); + stream->handle[1] = 0; + } + if (stream->userBuffer) { + free(stream->userBuffer); + stream->userBuffer = 0; + } + sprintf(message, "RtAudio: ALSA error allocating buffer memory for device (%s).", + devices[device].name); + error(RtError::WARNING); + return FAILURE; +} + +void RtAudio :: cancelStreamCallback(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + if (stream->usingCallback) { + stream->usingCallback = false; + pthread_cancel(stream->thread); + pthread_join(stream->thread, NULL); + stream->thread = 0; + stream->callback = NULL; + stream->userData = NULL; + } +} + +void RtAudio :: closeStream(int streamId) +{ + // We don't want an exception to be thrown here because this + // function is called by our class destructor. So, do our own + // streamId check. + if ( streams.find( streamId ) == streams.end() ) { + sprintf(message, "RtAudio: invalid stream identifier!"); + error(RtError::WARNING); + return; + } + + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) streams[streamId]; + + if (stream->usingCallback) { + pthread_cancel(stream->thread); + pthread_join(stream->thread, NULL); + } + + pthread_mutex_destroy(&stream->mutex); + + if (stream->handle[0]) + alClosePort(stream->handle[0]); + + if (stream->handle[1]) + alClosePort(stream->handle[1]); + + if (stream->userBuffer) + free(stream->userBuffer); + + if (stream->deviceBuffer) + free(stream->deviceBuffer); + + free(stream); + streams.erase(streamId); +} + +void RtAudio :: startStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + if (stream->state == STREAM_RUNNING) + return; + + // The AL port is ready as soon as it is opened. + stream->state = STREAM_RUNNING; +} + +void RtAudio :: stopStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_STOPPED) + goto unlock; + + int result; + int buffer_size = stream->bufferSize * stream->nBuffers; + + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) + alZeroFrames(stream->handle[0], buffer_size); + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + result = alDiscardFrames(stream->handle[1], buffer_size); + if (result == -1) { + sprintf(message, "RtAudio: AL error draining stream device (%s): %s.", + devices[stream->device[1]].name, alGetErrorString(oserror())); + error(RtError::DRIVER_ERROR); + } + } + stream->state = STREAM_STOPPED; + + unlock: + MUTEX_UNLOCK(&stream->mutex); +} + +void RtAudio :: abortStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + if (stream->state == STREAM_STOPPED) + goto unlock; + + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + + int buffer_size = stream->bufferSize * stream->nBuffers; + int result = alDiscardFrames(stream->handle[0], buffer_size); + if (result == -1) { + sprintf(message, "RtAudio: AL error aborting stream device (%s): %s.", + devices[stream->device[0]].name, alGetErrorString(oserror())); + error(RtError::DRIVER_ERROR); + } + } + + // There is no clear action to take on the input stream, since the + // port will continue to run in any event. + stream->state = STREAM_STOPPED; + + unlock: + MUTEX_UNLOCK(&stream->mutex); +} + +int RtAudio :: streamWillBlock(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + MUTEX_LOCK(&stream->mutex); + + int frames = 0; + if (stream->state == STREAM_STOPPED) + goto unlock; + + int err = 0; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + err = alGetFillable(stream->handle[0]); + if (err < 0) { + sprintf(message, "RtAudio: AL error getting available frames for stream (%s): %s.", + devices[stream->device[0]].name, alGetErrorString(oserror())); + error(RtError::DRIVER_ERROR); + } + } + + frames = err; + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + err = alGetFilled(stream->handle[1]); + if (err < 0) { + sprintf(message, "RtAudio: AL error getting available frames for stream (%s): %s.", + devices[stream->device[1]].name, alGetErrorString(oserror())); + error(RtError::DRIVER_ERROR); + } + if (frames > err) frames = err; + } + + frames = stream->bufferSize - frames; + if (frames < 0) frames = 0; + + unlock: + MUTEX_UNLOCK(&stream->mutex); + return frames; +} + +void RtAudio :: tickStream(int streamId) +{ + RTAUDIO_STREAM *stream = (RTAUDIO_STREAM *) verifyStream(streamId); + + int stopStream = 0; + if (stream->state == STREAM_STOPPED) { + if (stream->usingCallback) usleep(50000); // sleep 50 milliseconds + return; + } + else if (stream->usingCallback) { + stopStream = stream->callback(stream->userBuffer, stream->bufferSize, stream->userData); + } + + MUTEX_LOCK(&stream->mutex); + + // The state might change while waiting on a mutex. + if (stream->state == STREAM_STOPPED) + goto unlock; + + char *buffer; + int channels; + RTAUDIO_FORMAT format; + if (stream->mode == PLAYBACK || stream->mode == DUPLEX) { + + // Setup parameters and do buffer conversion if necessary. + if (stream->doConvertBuffer[0]) { + convertStreamBuffer(stream, PLAYBACK); + buffer = stream->deviceBuffer; + channels = stream->nDeviceChannels[0]; + format = stream->deviceFormat[0]; + } + else { + buffer = stream->userBuffer; + channels = stream->nUserChannels[0]; + format = stream->userFormat; + } + + // Do byte swapping if necessary. + if (stream->doByteSwap[0]) + byteSwapBuffer(buffer, stream->bufferSize * channels, format); + + // Write interleaved samples to device. + alWriteFrames(stream->handle[0], buffer, stream->bufferSize); + } + + if (stream->mode == RECORD || stream->mode == DUPLEX) { + + // Setup parameters. + if (stream->doConvertBuffer[1]) { + buffer = stream->deviceBuffer; + channels = stream->nDeviceChannels[1]; + format = stream->deviceFormat[1]; + } + else { + buffer = stream->userBuffer; + channels = stream->nUserChannels[1]; + format = stream->userFormat; + } + + // Read interleaved samples from device. + alReadFrames(stream->handle[1], buffer, stream->bufferSize); + + // Do byte swapping if necessary. + if (stream->doByteSwap[1]) + byteSwapBuffer(buffer, stream->bufferSize * channels, format); + + // Do buffer conversion if necessary. + if (stream->doConvertBuffer[1]) + convertStreamBuffer(stream, RECORD); + } + + unlock: + MUTEX_UNLOCK(&stream->mutex); + + if (stream->usingCallback && stopStream) + this->stopStream(streamId); +} + +extern "C" void *callbackHandler(void *ptr) +{ + RtAudio *object = thread_info.object; + int stream = thread_info.streamId; + bool *usingCallback = (bool *) ptr; + + while ( *usingCallback ) { + pthread_testcancel(); + try { + object->tickStream(stream); + } + catch (RtError &exception) { + fprintf(stderr, "\nCallback thread error (%s) ... closing thread.\n\n", + exception.getMessage()); + break; + } + } + + return 0; +} + +//******************** End of __IRIX_AL__ *********************// + +#endif + + +// *************************************************** // +// +// Private common (OS-independent) RtAudio methods. +// +// *************************************************** // + +// This method can be modified to control the behavior of error +// message reporting and throwing. +void RtAudio :: error(RtError::TYPE type) +{ + if (type == RtError::WARNING) { +#if defined(RTAUDIO_DEBUG) + fprintf(stderr, "\n%s\n\n", message); + else if (type == RtError::DEBUG_WARNING) { + fprintf(stderr, "\n%s\n\n", message); +#endif + } + else { + fprintf(stderr, "\n%s\n\n", message); + throw RtError(message, type); + } +} + +void *RtAudio :: verifyStream(int streamId) +{ + // Verify the stream key. + if ( streams.find( streamId ) == streams.end() ) { + sprintf(message, "RtAudio: invalid stream identifier!"); + error(RtError::INVALID_STREAM); + } + + return streams[streamId]; +} + +void RtAudio :: clearDeviceInfo(RTAUDIO_DEVICE *info) +{ + // Don't clear the name or DEVICE_ID fields here ... they are + // typically set prior to a call of this function. + info->probed = false; + info->maxOutputChannels = 0; + info->maxInputChannels = 0; + info->maxDuplexChannels = 0; + info->minOutputChannels = 0; + info->minInputChannels = 0; + info->minDuplexChannels = 0; + info->hasDuplexSupport = false; + info->nSampleRates = 0; + for (int i=0; isampleRates[i] = 0; + info->nativeFormats = 0; +} + +int RtAudio :: formatBytes(RTAUDIO_FORMAT format) +{ + if (format == RTAUDIO_SINT16) + return 2; + else if (format == RTAUDIO_SINT24 || format == RTAUDIO_SINT32 || + format == RTAUDIO_FLOAT32) + return 4; + else if (format == RTAUDIO_FLOAT64) + return 8; + else if (format == RTAUDIO_SINT8) + return 1; + + sprintf(message,"RtAudio: undefined format in formatBytes()."); + error(RtError::WARNING); + + return 0; +} + +void RtAudio :: convertStreamBuffer(RTAUDIO_STREAM *stream, STREAM_MODE mode) +{ + // This method does format conversion, input/output channel compensation, and + // data interleaving/deinterleaving. 24-bit integers are assumed to occupy + // the upper three bytes of a 32-bit integer. + + int j, channels_in, channels_out, channels; + RTAUDIO_FORMAT format_in, format_out; + char *input, *output; + + if (mode == RECORD) { // convert device to user buffer + input = stream->deviceBuffer; + output = stream->userBuffer; + channels_in = stream->nDeviceChannels[1]; + channels_out = stream->nUserChannels[1]; + format_in = stream->deviceFormat[1]; + format_out = stream->userFormat; + } + else { // convert user to device buffer + input = stream->userBuffer; + output = stream->deviceBuffer; + channels_in = stream->nUserChannels[0]; + channels_out = stream->nDeviceChannels[0]; + format_in = stream->userFormat; + format_out = stream->deviceFormat[0]; + + // clear our device buffer when in/out duplex device channels are different + if ( stream->mode == DUPLEX && + stream->nDeviceChannels[0] != stream->nDeviceChannels[1] ) + memset(output, 0, stream->bufferSize * channels_out * formatBytes(format_out)); + } + + channels = (channels_in < channels_out) ? channels_in : channels_out; + + // Set up the interleave/deinterleave offsets + std::vector offset_in(channels); + std::vector offset_out(channels); + if (mode == RECORD && stream->deInterleave[1]) { + for (int k=0; kbufferSize; + offset_out[k] = k; + } + } + else if (mode == PLAYBACK && stream->deInterleave[0]) { + for (int k=0; kbufferSize; + } + } + else { + for (int k=0; kbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; j> 16) & 0x0000ffff); + } + in += channels_in; + out += channels_out; + } + } + else if (format_in == RTAUDIO_SINT32) { + INT32 *in = (INT32 *)input; + for (int i=0; ibufferSize; i++) { + for (j=0; j> 16) & 0x0000ffff); + } + in += channels_in; + out += channels_out; + } + } + else if (format_in == RTAUDIO_FLOAT32) { + FLOAT32 *in = (FLOAT32 *)input; + for (int i=0; ibufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; j> 8) & 0x00ff); + } + in += channels_in; + out += channels_out; + } + } + else if (format_in == RTAUDIO_SINT24) { + INT32 *in = (INT32 *)input; + for (int i=0; ibufferSize; i++) { + for (j=0; j> 24) & 0x000000ff); + } + in += channels_in; + out += channels_out; + } + } + else if (format_in == RTAUDIO_SINT32) { + INT32 *in = (INT32 *)input; + for (int i=0; ibufferSize; i++) { + for (j=0; j> 24) & 0x000000ff); + } + in += channels_in; + out += channels_out; + } + } + else if (format_in == RTAUDIO_FLOAT32) { + FLOAT32 *in = (FLOAT32 *)input; + for (int i=0; ibufferSize; i++) { + for (j=0; jbufferSize; i++) { + for (j=0; j - -#if defined(__LINUX_ALSA_) - #include - #include - #include - - typedef snd_pcm_t *AUDIO_HANDLE; - typedef int DEVICE_ID; - typedef pthread_t THREAD_HANDLE; - typedef pthread_mutex_t MUTEX; - -#elif defined(__LINUX_OSS_) - #include - #include - - typedef int AUDIO_HANDLE; - typedef int DEVICE_ID; - typedef pthread_t THREAD_HANDLE; - typedef pthread_mutex_t MUTEX; - -#elif defined(__WINDOWS_DS_) - #include - #include - - // The following struct is used to hold the extra variables - // specific to the DirectSound implementation. - typedef struct { - void * object; - void * buffer; - UINT bufferPointer; - } AUDIO_HANDLE; - - typedef LPGUID DEVICE_ID; - typedef unsigned long THREAD_HANDLE; - typedef CRITICAL_SECTION MUTEX; - -#elif defined(__IRIX_AL_) - #include - #include - #include - - typedef ALport AUDIO_HANDLE; - typedef int DEVICE_ID; - typedef pthread_t THREAD_HANDLE; - typedef pthread_mutex_t MUTEX; - -#endif - - -// *************************************************** // -// -// RtAudioError class declaration. -// -// *************************************************** // - -class RtAudioError -{ -public: - enum TYPE { - WARNING, - DEBUG_WARNING, - UNSPECIFIED, - NO_DEVICES_FOUND, - INVALID_DEVICE, - INVALID_STREAM, - MEMORY_ERROR, - INVALID_PARAMETER, - DRIVER_ERROR, - SYSTEM_ERROR, - THREAD_ERROR - }; - -protected: - char error_message[256]; - TYPE type; - -public: - //! The constructor. - RtAudioError(const char *p, TYPE tipe = RtAudioError::UNSPECIFIED); - - //! The destructor. - virtual ~RtAudioError(void); - - //! Prints "thrown" error message to stdout. - virtual void printMessage(void); - - //! Returns the "thrown" error message TYPE. - virtual const TYPE& getType(void) { return type; } - - //! Returns the "thrown" error message string. - virtual const char *getMessage(void) { return error_message; } -}; - - -// *************************************************** // -// -// RtAudio class declaration. -// -// *************************************************** // - -class RtAudio -{ -public: - - // Support for signed integers and floats. Audio data fed to/from - // the tickStream() routine is assumed to ALWAYS be in host - // byte order. The internal routines will automatically take care of - // any necessary byte-swapping between the host format and the - // soundcard. Thus, endian-ness is not a concern in the following - // format definitions. - typedef unsigned long RTAUDIO_FORMAT; - static const RTAUDIO_FORMAT RTAUDIO_SINT8; - static const RTAUDIO_FORMAT RTAUDIO_SINT16; - static const RTAUDIO_FORMAT RTAUDIO_SINT24; /*!< Upper 3 bytes of 32-bit integer. */ - static const RTAUDIO_FORMAT RTAUDIO_SINT32; - static const RTAUDIO_FORMAT RTAUDIO_FLOAT32; /*!< Normalized between plus/minus 1.0. */ - static const RTAUDIO_FORMAT RTAUDIO_FLOAT64; /*!< Normalized between plus/minus 1.0. */ - - //static const int MAX_SAMPLE_RATES = 14; - enum { MAX_SAMPLE_RATES = 14 }; - - typedef int (*RTAUDIO_CALLBACK)(char *buffer, int bufferSize, void *userData); - - typedef struct { - char name[128]; - DEVICE_ID id[2]; /*!< No value reported by getDeviceInfo(). */ - bool probed; /*!< true if the device capabilities were successfully probed. */ - int maxOutputChannels; - int maxInputChannels; - int maxDuplexChannels; - int minOutputChannels; - int minInputChannels; - int minDuplexChannels; - bool hasDuplexSupport; /*!< true if device supports duplex mode. */ - int nSampleRates; /*!< Number of discrete rates or -1 if range supported. */ - int sampleRates[MAX_SAMPLE_RATES]; /*!< Supported rates or (min, max) if range. */ - RTAUDIO_FORMAT nativeFormats; /*!< Bit mask of supported data formats. */ - } RTAUDIO_DEVICE; - - //! The default constructor. - /*! - Probes the system to make sure at least one audio - input/output device is available and determines - the api-specific identifier for each device found. - An RtAudioError error can be thrown if no devices are - found or if a memory allocation error occurs. - */ - RtAudio(); - - //! A constructor which can be used to open a stream during instantiation. - /*! - The specified output and/or input device identifiers correspond - to those enumerated via the getDeviceInfo() method. If device = - 0, the default or first available devices meeting the given - parameters is selected. If an output or input channel value is - zero, the corresponding device value is ignored. When a stream is - successfully opened, its identifier is returned via the "streamID" - pointer. An RtAudioError can be thrown if no devices are found - for the given parameters, if a memory allocation error occurs, or - if a driver error occurs. \sa openStream() - */ - RtAudio(int *streamID, - int outputDevice, int outputChannels, - int inputDevice, int inputChannels, - RTAUDIO_FORMAT format, int sampleRate, - int *bufferSize, int numberOfBuffers); - - //! The destructor. - /*! - Stops and closes any open streams and devices and deallocates - buffer and structure memory. - */ - ~RtAudio(); - - //! A public method for opening a stream with the specified parameters. - /*! - If successful, the opened stream ID is returned. Otherwise, an - RtAudioError is thrown. - - \param outputDevice: If equal to 0, the default or first device - found meeting the given parameters is opened. Otherwise, the - device number should correspond to one of those enumerated via - the getDeviceInfo() method. - \param outputChannels: The desired number of output channels. If - equal to zero, the outputDevice identifier is ignored. - \param inputDevice: If equal to 0, the default or first device - found meeting the given parameters is opened. Otherwise, the - device number should correspond to one of those enumerated via - the getDeviceInfo() method. - \param inputChannels: The desired number of input channels. If - equal to zero, the inputDevice identifier is ignored. - \param format: An RTAUDIO_FORMAT specifying the desired sample data format. - \param sampleRate: The desired sample rate (sample frames per second). - \param *bufferSize: A pointer value indicating the desired internal buffer - size in sample frames. The actual value used by the device is - returned via the same pointer. A value of zero can be specified, - in which case the lowest allowable value is determined. - \param numberOfBuffers: A value which can be used to help control device - latency. More buffers typically result in more robust performance, - though at a cost of greater latency. A value of zero can be - specified, in which case the lowest allowable value is used. - */ - int openStream(int outputDevice, int outputChannels, - int inputDevice, int inputChannels, - RTAUDIO_FORMAT format, int sampleRate, - int *bufferSize, int numberOfBuffers); - - //! A public method which sets a user-defined callback function for a given stream. - /*! - This method assigns a callback function to a specific, - previously opened stream for non-blocking stream functionality. A - separate process is initiated, though the user function is called - only when the stream is "running" (between calls to the - startStream() and stopStream() methods, respectively). The - callback process remains active for the duration of the stream and - is automatically shutdown when the stream is closed (via the - closeStream() method or by object destruction). The callback - process can also be shutdown and the user function de-referenced - through an explicit call to the cancelStreamCallback() method. - Note that a single stream can use only blocking or callback - functionality at the same time, though it is possible to alternate - modes on the same stream through the use of the - setStreamCallback() and cancelStreamCallback() methods (the - blocking tickStream() method can be used before a callback is set - and/or after a callback is cancelled). An RtAudioError will be - thrown for an invalid device argument. - */ - void setStreamCallback(int streamID, RTAUDIO_CALLBACK callback, void *userData); - - //! A public method which cancels a callback process and function for a given stream. - /*! - This method shuts down a callback process and de-references the - user function for a specific stream. Callback functionality can - subsequently be restarted on the stream via the - setStreamCallback() method. An RtAudioError will be thrown for an - invalid device argument. - */ - void cancelStreamCallback(int streamID); - - //! A public method which returns the number of audio devices found. - int getDeviceCount(void); - - //! Fill a user-supplied RTAUDIO_DEVICE structure for a specified device. - /*! - Any device between 0 and getDeviceCount()-1 is valid. If a - device is busy or otherwise unavailable, the structure member - "probed" has a value of "false". The system default input and - output devices are referenced by device identifier = 0. On - systems which allow dynamic default device settings, the default - devices are not identified by name (specific device enumerations - are assigned device identifiers > 0). An RtAudioError will be - thrown for an invalid device argument. - */ - void getDeviceInfo(int device, RTAUDIO_DEVICE *info); - - //! A public method which returns a pointer to the buffer for an open stream. - /*! - The user should fill and/or read the buffer data in interleaved format - and then call the tickStream() method. An RtAudioError will be - thrown for an invalid stream identifier. - */ - char * const getStreamBuffer(int streamID); - - //! Public method used to trigger processing of input/output data for a stream. - /*! - This method blocks until all buffer data is read/written. An - RtAudioError will be thrown for an invalid stream identifier or if - a driver error occurs. - */ - void tickStream(int streamID); - - //! Public method which closes a stream and frees any associated buffers. - /*! - If an invalid stream identifier is specified, this method - issues a warning and returns (an RtAudioError is not thrown). - */ - void closeStream(int streamID); - - //! Public method which starts a stream. - /*! - An RtAudioError will be thrown for an invalid stream identifier - or if a driver error occurs. - */ - void startStream(int streamID); - - //! Stop a stream, allowing any samples remaining in the queue to be played out and/or read in. - /*! - An RtAudioError will be thrown for an invalid stream identifier - or if a driver error occurs. - */ - void stopStream(int streamID); - - //! Stop a stream, discarding any samples remaining in the input/output queue. - /*! - An RtAudioError will be thrown for an invalid stream identifier - or if a driver error occurs. - */ - void abortStream(int streamID); - - //! Queries a stream to determine whether a call to the tickStream() method will block. - /*! - A return value of 0 indicates that the stream will NOT block. A positive - return value indicates the number of sample frames that cannot yet be - processed without blocking. - */ - int streamWillBlock(int streamID); - -protected: - -private: - - static const unsigned int SAMPLE_RATES[MAX_SAMPLE_RATES]; - - enum { FAILURE, SUCCESS }; - - enum STREAM_MODE { - PLAYBACK, - RECORD, - DUPLEX, - UNINITIALIZED = -75 - }; - - enum STREAM_STATE { - STREAM_STOPPED, - STREAM_RUNNING - }; - - typedef struct { - int device[2]; // Playback and record, respectively. - STREAM_MODE mode; // PLAYBACK, RECORD, or DUPLEX. - AUDIO_HANDLE handle[2]; // Playback and record handles, respectively. - STREAM_STATE state; // STOPPED or RUNNING - char *userBuffer; - char *deviceBuffer; - bool doConvertBuffer[2]; // Playback and record, respectively. - bool deInterleave[2]; // Playback and record, respectively. - bool doByteSwap[2]; // Playback and record, respectively. - int sampleRate; - int bufferSize; - int nBuffers; - int nUserChannels[2]; // Playback and record, respectively. - int nDeviceChannels[2]; // Playback and record channels, respectively. - RTAUDIO_FORMAT userFormat; - RTAUDIO_FORMAT deviceFormat[2]; // Playback and record, respectively. - bool usingCallback; - THREAD_HANDLE thread; - MUTEX mutex; - RTAUDIO_CALLBACK callback; - void *userData; - } RTAUDIO_STREAM; - - typedef signed short INT16; - typedef signed int INT32; - typedef float FLOAT32; - typedef double FLOAT64; - - char message[256]; - int nDevices; - RTAUDIO_DEVICE *devices; - - std::map streams; - - //! Private error method to allow global control over error handling. - void error(RtAudioError::TYPE type); - - /*! - Private method to count the system audio devices, allocate the - RTAUDIO_DEVICE structures, and probe the device capabilities. - */ - void initialize(void); - - //! Private method to clear an RTAUDIO_DEVICE structure. - void clearDeviceInfo(RTAUDIO_DEVICE *info); - - /*! - Private method which attempts to fill an RTAUDIO_DEVICE - structure for a given device. If an error is encountered during - the probe, a "warning" message is reported and the value of - "probed" remains false (no exception is thrown). A successful - probe is indicated by probed = true. - */ - void probeDeviceInfo(RTAUDIO_DEVICE *info); - - /*! - Private method which attempts to open a device with the given parameters. - If an error is encountered during the probe, a "warning" message is - reported and FAILURE is returned (no exception is thrown). A - successful probe is indicated by a return value of SUCCESS. - */ - bool probeDeviceOpen(int device, RTAUDIO_STREAM *stream, - STREAM_MODE mode, int channels, - int sampleRate, RTAUDIO_FORMAT format, - int *bufferSize, int numberOfBuffers); - - /*! - Private common method used to check validity of a user-passed - stream ID. When the ID is valid, this method returns a pointer to - an RTAUDIO_STREAM structure (in the form of a void pointer). - Otherwise, an "invalid identifier" exception is thrown. - */ - void *verifyStream(int streamID); - - /*! - Private method used to perform format, channel number, and/or interleaving - conversions between the user and device buffers. - */ - void convertStreamBuffer(RTAUDIO_STREAM *stream, STREAM_MODE mode); - - //! Private method used to perform byte-swapping on buffers. - void byteSwapBuffer(char *buffer, int samples, RTAUDIO_FORMAT format); - - //! Private method which returns the number of bytes for a given format. - int formatBytes(RTAUDIO_FORMAT format); -}; - -// Uncomment the following definition to have extra information spewed to stderr. -//#define RTAUDIO_DEBUG - -#endif +/******************************************/ +/* + RtAudio - realtime sound I/O C++ class + by Gary P. Scavone, 2001-2002. +*/ +/******************************************/ + +#if !defined(__RTAUDIO_H) +#define __RTAUDIO_H + +#include + +#if defined(__LINUX_ALSA__) + #include + #include + #include + + #define THREAD_TYPE + typedef snd_pcm_t *AUDIO_HANDLE; + typedef int DEVICE_ID; + typedef pthread_t THREAD_HANDLE; + typedef pthread_mutex_t MUTEX; + +#elif defined(__LINUX_OSS__) + #include + #include + + #define THREAD_TYPE + typedef int AUDIO_HANDLE; + typedef int DEVICE_ID; + typedef pthread_t THREAD_HANDLE; + typedef pthread_mutex_t MUTEX; + +#elif defined(__WINDOWS_DS__) + #include + #include + + // The following struct is used to hold the extra variables + // specific to the DirectSound implementation. + typedef struct { + void * object; + void * buffer; + UINT bufferPointer; + } AUDIO_HANDLE; + + #define THREAD_TYPE __stdcall + typedef LPGUID DEVICE_ID; + typedef unsigned long THREAD_HANDLE; + typedef CRITICAL_SECTION MUTEX; + +#elif defined(__IRIX_AL__) + #include + #include + #include + + #define THREAD_TYPE + typedef ALport AUDIO_HANDLE; + typedef int DEVICE_ID; + typedef pthread_t THREAD_HANDLE; + typedef pthread_mutex_t MUTEX; + +#endif + + +// *************************************************** // +// +// RtError class declaration. +// +// *************************************************** // + +class RtError +{ +public: + enum TYPE { + WARNING, + DEBUG_WARNING, + UNSPECIFIED, + NO_DEVICES_FOUND, + INVALID_DEVICE, + INVALID_STREAM, + MEMORY_ERROR, + INVALID_PARAMETER, + DRIVER_ERROR, + SYSTEM_ERROR, + THREAD_ERROR + }; + +protected: + char error_message[256]; + TYPE type; + +public: + //! The constructor. + RtError(const char *p, TYPE tipe = RtError::UNSPECIFIED); + + //! The destructor. + virtual ~RtError(void); + + //! Prints "thrown" error message to stdout. + virtual void printMessage(void); + + //! Returns the "thrown" error message TYPE. + virtual const TYPE& getType(void) { return type; } + + //! Returns the "thrown" error message string. + virtual const char *getMessage(void) { return error_message; } +}; + + +// *************************************************** // +// +// RtAudio class declaration. +// +// *************************************************** // + +class RtAudio +{ +public: + + // Support for signed integers and floats. Audio data fed to/from + // the tickStream() routine is assumed to ALWAYS be in host + // byte order. The internal routines will automatically take care of + // any necessary byte-swapping between the host format and the + // soundcard. Thus, endian-ness is not a concern in the following + // format definitions. + typedef unsigned long RTAUDIO_FORMAT; + static const RTAUDIO_FORMAT RTAUDIO_SINT8; + static const RTAUDIO_FORMAT RTAUDIO_SINT16; + static const RTAUDIO_FORMAT RTAUDIO_SINT24; /*!< Upper 3 bytes of 32-bit integer. */ + static const RTAUDIO_FORMAT RTAUDIO_SINT32; + static const RTAUDIO_FORMAT RTAUDIO_FLOAT32; /*!< Normalized between plus/minus 1.0. */ + static const RTAUDIO_FORMAT RTAUDIO_FLOAT64; /*!< Normalized between plus/minus 1.0. */ + + //static const int MAX_SAMPLE_RATES = 14; + enum { MAX_SAMPLE_RATES = 14 }; + + typedef int (*RTAUDIO_CALLBACK)(char *buffer, int bufferSize, void *userData); + + typedef struct { + char name[128]; + DEVICE_ID id[2]; /*!< No value reported by getDeviceInfo(). */ + bool probed; /*!< true if the device capabilities were successfully probed. */ + int maxOutputChannels; + int maxInputChannels; + int maxDuplexChannels; + int minOutputChannels; + int minInputChannels; + int minDuplexChannels; + bool hasDuplexSupport; /*!< true if device supports duplex mode. */ + int nSampleRates; /*!< Number of discrete rates or -1 if range supported. */ + int sampleRates[MAX_SAMPLE_RATES]; /*!< Supported rates or (min, max) if range. */ + RTAUDIO_FORMAT nativeFormats; /*!< Bit mask of supported data formats. */ + } RTAUDIO_DEVICE; + + //! The default constructor. + /*! + Probes the system to make sure at least one audio + input/output device is available and determines + the api-specific identifier for each device found. + An RtError error can be thrown if no devices are + found or if a memory allocation error occurs. + */ + RtAudio(); + + //! A constructor which can be used to open a stream during instantiation. + /*! + The specified output and/or input device identifiers correspond + to those enumerated via the getDeviceInfo() method. If device = + 0, the default or first available devices meeting the given + parameters is selected. If an output or input channel value is + zero, the corresponding device value is ignored. When a stream is + successfully opened, its identifier is returned via the "streamId" + pointer. An RtError can be thrown if no devices are found + for the given parameters, if a memory allocation error occurs, or + if a driver error occurs. \sa openStream() + */ + RtAudio(int *streamId, + int outputDevice, int outputChannels, + int inputDevice, int inputChannels, + RTAUDIO_FORMAT format, int sampleRate, + int *bufferSize, int numberOfBuffers); + + //! The destructor. + /*! + Stops and closes any open streams and devices and deallocates + buffer and structure memory. + */ + ~RtAudio(); + + //! A public method for opening a stream with the specified parameters. + /*! + If successful, the opened stream ID is returned. Otherwise, an + RtError is thrown. + + \param outputDevice: If equal to 0, the default or first device + found meeting the given parameters is opened. Otherwise, the + device number should correspond to one of those enumerated via + the getDeviceInfo() method. + \param outputChannels: The desired number of output channels. If + equal to zero, the outputDevice identifier is ignored. + \param inputDevice: If equal to 0, the default or first device + found meeting the given parameters is opened. Otherwise, the + device number should correspond to one of those enumerated via + the getDeviceInfo() method. + \param inputChannels: The desired number of input channels. If + equal to zero, the inputDevice identifier is ignored. + \param format: An RTAUDIO_FORMAT specifying the desired sample data format. + \param sampleRate: The desired sample rate (sample frames per second). + \param *bufferSize: A pointer value indicating the desired internal buffer + size in sample frames. The actual value used by the device is + returned via the same pointer. A value of zero can be specified, + in which case the lowest allowable value is determined. + \param numberOfBuffers: A value which can be used to help control device + latency. More buffers typically result in more robust performance, + though at a cost of greater latency. A value of zero can be + specified, in which case the lowest allowable value is used. + */ + int openStream(int outputDevice, int outputChannels, + int inputDevice, int inputChannels, + RTAUDIO_FORMAT format, int sampleRate, + int *bufferSize, int numberOfBuffers); + + //! A public method which sets a user-defined callback function for a given stream. + /*! + This method assigns a callback function to a specific, + previously opened stream for non-blocking stream functionality. A + separate process is initiated, though the user function is called + only when the stream is "running" (between calls to the + startStream() and stopStream() methods, respectively). The + callback process remains active for the duration of the stream and + is automatically shutdown when the stream is closed (via the + closeStream() method or by object destruction). The callback + process can also be shutdown and the user function de-referenced + through an explicit call to the cancelStreamCallback() method. + Note that a single stream can use only blocking or callback + functionality at the same time, though it is possible to alternate + modes on the same stream through the use of the + setStreamCallback() and cancelStreamCallback() methods (the + blocking tickStream() method can be used before a callback is set + and/or after a callback is cancelled). An RtError will be + thrown for an invalid device argument. + */ + void setStreamCallback(int streamId, RTAUDIO_CALLBACK callback, void *userData); + + //! A public method which cancels a callback process and function for a given stream. + /*! + This method shuts down a callback process and de-references the + user function for a specific stream. Callback functionality can + subsequently be restarted on the stream via the + setStreamCallback() method. An RtError will be thrown for an + invalid device argument. + */ + void cancelStreamCallback(int streamId); + + //! A public method which returns the number of audio devices found. + int getDeviceCount(void); + + //! Fill a user-supplied RTAUDIO_DEVICE structure for a specified device. + /*! + Any device between 0 and getDeviceCount()-1 is valid. If a + device is busy or otherwise unavailable, the structure member + "probed" has a value of "false". The system default input and + output devices are referenced by device identifier = 0. On + systems which allow dynamic default device settings, the default + devices are not identified by name (specific device enumerations + are assigned device identifiers > 0). An RtError will be + thrown for an invalid device argument. + */ + void getDeviceInfo(int device, RTAUDIO_DEVICE *info); + + //! A public method which returns a pointer to the buffer for an open stream. + /*! + The user should fill and/or read the buffer data in interleaved format + and then call the tickStream() method. An RtError will be + thrown for an invalid stream identifier. + */ + char * const getStreamBuffer(int streamId); + + //! Public method used to trigger processing of input/output data for a stream. + /*! + This method blocks until all buffer data is read/written. An + RtError will be thrown for an invalid stream identifier or if + a driver error occurs. + */ + void tickStream(int streamId); + + //! Public method which closes a stream and frees any associated buffers. + /*! + If an invalid stream identifier is specified, this method + issues a warning and returns (an RtError is not thrown). + */ + void closeStream(int streamId); + + //! Public method which starts a stream. + /*! + An RtError will be thrown for an invalid stream identifier + or if a driver error occurs. + */ + void startStream(int streamId); + + //! Stop a stream, allowing any samples remaining in the queue to be played out and/or read in. + /*! + An RtError will be thrown for an invalid stream identifier + or if a driver error occurs. + */ + void stopStream(int streamId); + + //! Stop a stream, discarding any samples remaining in the input/output queue. + /*! + An RtError will be thrown for an invalid stream identifier + or if a driver error occurs. + */ + void abortStream(int streamId); + + //! Queries a stream to determine whether a call to the tickStream() method will block. + /*! + A return value of 0 indicates that the stream will NOT block. A positive + return value indicates the number of sample frames that cannot yet be + processed without blocking. + */ + int streamWillBlock(int streamId); + +protected: + +private: + + static const unsigned int SAMPLE_RATES[MAX_SAMPLE_RATES]; + + enum { FAILURE, SUCCESS }; + + enum STREAM_MODE { + PLAYBACK, + RECORD, + DUPLEX, + UNINITIALIZED = -75 + }; + + enum STREAM_STATE { + STREAM_STOPPED, + STREAM_RUNNING + }; + + typedef struct { + int device[2]; // Playback and record, respectively. + STREAM_MODE mode; // PLAYBACK, RECORD, or DUPLEX. + AUDIO_HANDLE handle[2]; // Playback and record handles, respectively. + STREAM_STATE state; // STOPPED or RUNNING + char *userBuffer; + char *deviceBuffer; + bool doConvertBuffer[2]; // Playback and record, respectively. + bool deInterleave[2]; // Playback and record, respectively. + bool doByteSwap[2]; // Playback and record, respectively. + int sampleRate; + int bufferSize; + int nBuffers; + int nUserChannels[2]; // Playback and record, respectively. + int nDeviceChannels[2]; // Playback and record channels, respectively. + RTAUDIO_FORMAT userFormat; + RTAUDIO_FORMAT deviceFormat[2]; // Playback and record, respectively. + bool usingCallback; + THREAD_HANDLE thread; + MUTEX mutex; + RTAUDIO_CALLBACK callback; + void *userData; + } RTAUDIO_STREAM; + + typedef signed short INT16; + typedef signed int INT32; + typedef float FLOAT32; + typedef double FLOAT64; + + char message[256]; + int nDevices; + RTAUDIO_DEVICE *devices; + + std::map streams; + + //! Private error method to allow global control over error handling. + void error(RtError::TYPE type); + + /*! + Private method to count the system audio devices, allocate the + RTAUDIO_DEVICE structures, and probe the device capabilities. + */ + void initialize(void); + + //! Private method to clear an RTAUDIO_DEVICE structure. + void clearDeviceInfo(RTAUDIO_DEVICE *info); + + /*! + Private method which attempts to fill an RTAUDIO_DEVICE + structure for a given device. If an error is encountered during + the probe, a "warning" message is reported and the value of + "probed" remains false (no exception is thrown). A successful + probe is indicated by probed = true. + */ + void probeDeviceInfo(RTAUDIO_DEVICE *info); + + /*! + Private method which attempts to open a device with the given parameters. + If an error is encountered during the probe, a "warning" message is + reported and FAILURE is returned (no exception is thrown). A + successful probe is indicated by a return value of SUCCESS. + */ + bool probeDeviceOpen(int device, RTAUDIO_STREAM *stream, + STREAM_MODE mode, int channels, + int sampleRate, RTAUDIO_FORMAT format, + int *bufferSize, int numberOfBuffers); + + /*! + Private common method used to check validity of a user-passed + stream ID. When the ID is valid, this method returns a pointer to + an RTAUDIO_STREAM structure (in the form of a void pointer). + Otherwise, an "invalid identifier" exception is thrown. + */ + void *verifyStream(int streamId); + + /*! + Private method used to perform format, channel number, and/or interleaving + conversions between the user and device buffers. + */ + void convertStreamBuffer(RTAUDIO_STREAM *stream, STREAM_MODE mode); + + //! Private method used to perform byte-swapping on buffers. + void byteSwapBuffer(char *buffer, int samples, RTAUDIO_FORMAT format); + + //! Private method which returns the number of bytes for a given format. + int formatBytes(RTAUDIO_FORMAT format); +}; + +// Uncomment the following definition to have extra information spewed to stderr. +//#define RTAUDIO_DEBUG + +#endif diff --git a/doc/Release.txt b/doc/Release.txt new file mode 100644 index 0000000..5b25661 --- /dev/null +++ b/doc/Release.txt @@ -0,0 +1,13 @@ +RtAudio - a C++ class which provides a common API for realtime audio input/output across Linux (native ALSA and OSS), SGI, and Windows operating systems. + +By Gary P. Scavone, 2002. + + +v2.01: (27 April 2002) +- Windows destructor bug fix when no devices available +- RtAudioError class renamed to RtError +- Preprocessor definitions changed slightly (i.e. __LINUX_OSS_ to __LINUX_OSS__) to conform with new Synthesis ToolKit distribution + +v2.0: (22 January 2002) +- first release of new independent class + diff --git a/doc/Doxyfile b/doc/doxygen/Doxyfile similarity index 94% rename from doc/Doxyfile rename to doc/doxygen/Doxyfile index a5ab761..3c22d86 100644 --- a/doc/Doxyfile +++ b/doc/doxygen/Doxyfile @@ -51,7 +51,7 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = tutorial.txt ../RtAudio.h +INPUT = tutorial.txt ../../RtAudio.h FILE_PATTERNS = RECURSIVE = NO EXCLUDE = @@ -71,7 +71,7 @@ IGNORE_PREFIX = # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES -HTML_OUTPUT = html +HTML_OUTPUT = ../html HTML_HEADER = header.html HTML_FOOTER = footer.html HTML_STYLESHEET = @@ -90,16 +90,16 @@ TREEVIEW_WIDTH = 250 GENERATE_LATEX = YES LATEX_OUTPUT = latex COMPACT_LATEX = NO -PAPER_TYPE = a4wide +PAPER_TYPE = letter EXTRA_PACKAGES = LATEX_HEADER = PDF_HYPERLINKS = NO -USE_PDFLATEX = NO +USE_PDFLATEX = YES LATEX_BATCHMODE = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- -GENERATE_RTF = YES +GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO @@ -107,7 +107,7 @@ RTF_STYLESHEET_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- -GENERATE_MAN = YES +GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 #--------------------------------------------------------------------------- diff --git a/doc/footer.html b/doc/doxygen/footer.html similarity index 76% rename from doc/footer.html rename to doc/doxygen/footer.html index 410e43f..eb47569 100644 --- a/doc/footer.html +++ b/doc/doxygen/footer.html @@ -1,6 +1,6 @@
- - + - + - + - + - + - + - + @@ -602,7 +606,7 @@ RtAudio is designed to provide a common API across the various supported operati \subsection linux Linux: -RtAudio for Linux was developed under Redhat distributions 7.0 - 7.2. Two different audio APIs are supported on Linux platforms: OSS and ALSA. The OSS API has existed for at least 6 years and the Linux kernel is distributed with free versions of OSS audio drivers. Therefore, a generic Linux system is most likely to have OSS support. The ALSA API is relatively new and at this time is not part of the Linux kernel distribution. Work is in progress to make ALSA part of the 2.5 development kernel series. Despite that, the ALSA API offers significantly better functionality than the OSS API. RtAudio provides support for the 0.9 and higher versions of ALSA. Input/output latency on the order of 15-20 milliseconds can typically be achieved under both OSS or ALSA by fine-tuning the RtAudio buffer parameters (without kernel modifications). Latencies on the order of 5 milliseconds or less can be achieved using a low-latency kernel patch and increasing FIFO scheduling priority. The pthread library, which is used for callback functionality, is a standard component of all Linux distributions. +RtAudio for Linux was developed under Redhat distributions 7.0 - 7.2. Two different audio APIs are supported on Linux platforms: OSS and ALSA. The OSS API has existed for at least 6 years and the Linux kernel is distributed with free versions of OSS audio drivers. Therefore, a generic Linux system is most likely to have OSS support. The ALSA API, although relatively new, is now part of the Linux development kernel and offers significantly better functionality than the OSS API. RtAudio provides support for the 0.9 and higher versions of ALSA. Input/output latency on the order of 15 milliseconds can typically be achieved under both OSS or ALSA by fine-tuning the RtAudio buffer parameters (without kernel modifications). Latencies on the order of 5 milliseconds or less can be achieved using a low-latency kernel patch and increasing FIFO scheduling priority. The pthread library, which is used for callback functionality, is a standard component of all Linux distributions. The ALSA library includes OSS emulation support. That means that you can run programs compiled for the OSS API even when using the ALSA drivers and library. It should be noted however that OSS emulation under ALSA is not perfect. Specifically, channel number queries seem to consistently produce invalid results. While OSS emulation is successful for the majority of RtAudio tests, it is recommended that the native ALSA implementation of RtAudio be used on systems which have ALSA drivers installed. @@ -610,19 +614,17 @@ The ALSA implementation of RtAudio makes no use of the ALSA "plug" interface. A \subsection irix Irix (SGI): -The Irix version of RtAudio was written and tested on an SGI Indy running Irix version 6.5 and the newer "al" audio library. RtAudio does not compile under Irix version 6.3 because the C++ compiler is too old. Despite the relatively slow speed of the Indy, RtAudio was found to behave quite well and input/output latency was very good. No problems were found with respect to using the pthread library. +The Irix version of RtAudio was written and tested on an SGI Indy running Irix version 6.5.4 and the newer "al" audio library. RtAudio does not compile under Irix version 6.3, mainly because the C++ compiler is too old. Despite the relatively slow speed of the Indy, RtAudio was found to behave quite well and input/output latency was very good. No problems were found with respect to using the pthread library. \subsection windows Windows: -RtAudio under Windows is written using the DirectSound API. In order to compile RtAudio under Windows, you must have the header and source files for DirectSound version 0.5 or higher. As far as I know, you cannot compile RtAudio for Windows NT because there is not sufficient DirectSound support. Audio output latency with DirectSound can be reasonably good (on the order of 20 milliseconds). On the other hand, input audio latency tends to be terrible (100 milliseconds or more). Further, DirectSound drivers tend to crash easily when experimenting with buffer parameters. On my system, I found it necessary to use values around nBuffers = 8 and bufferSize = 512 to avoid crashing my system. RtAudio was developed with Visual C++ version 6.0. I was forced in several instances to modify code in order to get it to compile under the non-standard version of C++ that Microsoft so unprofessionally implemented. We can only hope that the developers of Visual C++ 7.0 will have time to read the C++ standard. +RtAudio under Windows is written using the DirectSound API. In order to compile RtAudio under Windows, you must have the header and source files for DirectSound version 5.0 or higher. As far as I know, there is no DirectSoundCapture support for Windows NT (in which case, you cannot use RtAudio). Audio output latency with DirectSound can be reasonably good (on the order of 20 milliseconds). On the other hand, input audio latency tends to be terrible (100 milliseconds or more). Further, DirectSound drivers tend to crash easily when experimenting with buffer parameters. On my system, I found it necessary to use values around nBuffers = 8 and bufferSize = 512 to avoid crashing my system. RtAudio was developed with Visual C++ version 6.0. I was forced in several instances to modify code in order to get it to compile under the non-standard version of C++ that Microsoft so unprofessionally implemented. We can only hope that the developers of Visual C++ 7.0 will have time to read the C++ standard. -\section acknowledge Acknowledgments +\section acknowledge Acknowledgements The RtAudio API incorporates many of the concepts developed in the PortAudio project by Phil Burk and Ross Bencina. Early development also incorporated ideas from Bill Schottstaedt's sndlib. The CCRMA SoundWire group provided valuable feedback during the API proposal stages. RtAudio was slowly developed over the course of many months while in residence at the Institut Universitari de L'Audiovisual (IUA) in Barcelona, Spain, the Laboratory of Acoustics and Audio Signal Processing at the Helsinki University of Technology, Finland, and the Center for Computer Research in Music and Acoustics (CCRMA) at Stanford University. This work was supported in part by the United States Air Force Office of Scientific Research (grant \#F49620-99-1-0293). -These documentation files were generated using doxygen by Dimitri van Heesch. - */ diff --git a/doc/ccrma.gif b/doc/images/ccrma.gif similarity index 100% rename from doc/ccrma.gif rename to doc/images/ccrma.gif diff --git a/doc/manual.pdf b/doc/manual.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c31d0ebf478cbc428c199b781d448234ee891220 GIT binary patch literal 220096 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 Cm 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 literal 0 HcmV?d00001 diff --git a/tests/Makefile b/tests/Makefile index 2f69c73..b6e1e90 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -5,16 +5,16 @@ RM = /bin/rm ifeq ($(OS),Linux) # These are for Linux INSTR = info play_saw record_raw in_out play_raw twostreams call_saw call_inout call_twostreams - CC = g++ -Wall -D__LINUX_OSS_# -g -pg -O3 + CC = g++ -Wall -D__LINUX_OSS__# -g -pg -O3 LIBRARY = -lpthread -# CC = g++ -g -Wall -D__LINUX_ALSA_ # -g -pg -O3 +# CC = g++ -g -Wall -D__LINUX_ALSA__ # -g -pg -O3 # LIBRARY = -lpthread -lasound INCLUDE = -I../ endif ifeq ($(OS),IRIX) # These are for SGI INSTR = info play_saw record_raw in_out play_raw twostreams call_saw call_inout call_twostreams - CC = CC -D__IRIX_AL_ # -g -fullwarn -D__SGI_CC__ -O2 + CC = CC -D__IRIX_AL__ # -g -fullwarn -D__SGI_CC__ -O2 LIBRARY = -laudio -lpthread INCLUDE = -I../ endif diff --git a/tests/call_inout.cpp b/tests/call_inout.cpp index c4ae167..2e92eb8 100644 --- a/tests/call_inout.cpp +++ b/tests/call_inout.cpp @@ -68,8 +68,7 @@ int main(int argc, char *argv[]) audio = new RtAudio(&stream, device, chans, device, chans, FORMAT, fs, &buffer_size, 8); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { exit(EXIT_FAILURE); } @@ -77,8 +76,7 @@ int main(int argc, char *argv[]) audio->setStreamCallback(stream, &inout, NULL); audio->startStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -88,8 +86,7 @@ int main(int argc, char *argv[]) try { audio->stopStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { } cleanup: diff --git a/tests/call_inout.dsp b/tests/call_inout.dsp index 9aac77e..b3aef86 100644 --- a/tests/call_inout.dsp +++ b/tests/call_inout.dsp @@ -42,14 +42,14 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib dsound.lib /nologo /subsystem:console /machine:I386 !ELSEIF "$(CFG)" == "call_inout - Win32 Debug" @@ -65,15 +65,15 @@ LINK32=link.exe # PROP Intermediate_Dir "Debug" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /GZ /c +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib dsound.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept !ENDIF diff --git a/tests/call_saw.cpp b/tests/call_saw.cpp index cb5509d..af5c9e1 100644 --- a/tests/call_saw.cpp +++ b/tests/call_saw.cpp @@ -92,8 +92,7 @@ int main(int argc, char *argv[]) audio = new RtAudio(&stream, device, chans, 0, 0, FORMAT, fs, &buffer_size, 4); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { exit(EXIT_FAILURE); } @@ -103,8 +102,7 @@ int main(int argc, char *argv[]) audio->setStreamCallback(stream, &saw, (void *)data); audio->startStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -115,8 +113,7 @@ int main(int argc, char *argv[]) try { audio->stopStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { } cleanup: diff --git a/tests/call_saw.dsp b/tests/call_saw.dsp index 344989b..f86b0b0 100644 --- a/tests/call_saw.dsp +++ b/tests/call_saw.dsp @@ -42,14 +42,14 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib dsound.lib /nologo /subsystem:console /machine:I386 !ELSEIF "$(CFG)" == "call_saw - Win32 Debug" @@ -65,15 +65,15 @@ LINK32=link.exe # PROP Intermediate_Dir "Debug" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /GZ /c +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib dsound.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept !ENDIF diff --git a/tests/call_twostreams.cpp b/tests/call_twostreams.cpp index d763cc5..f13343a 100644 --- a/tests/call_twostreams.cpp +++ b/tests/call_twostreams.cpp @@ -92,8 +92,7 @@ int main(int argc, char *argv[]) try { audio = new RtAudio(); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { exit(EXIT_FAILURE); } @@ -103,8 +102,7 @@ int main(int argc, char *argv[]) stream2 = audio->openStream(device, chans, 0, 0, FORMAT, fs, &buffer_size, 8); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -115,8 +113,7 @@ int main(int argc, char *argv[]) audio->startStream(stream1); audio->startStream(stream2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -128,8 +125,7 @@ int main(int argc, char *argv[]) audio->stopStream(stream1); audio->stopStream(stream2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -140,8 +136,7 @@ int main(int argc, char *argv[]) audio->startStream(stream1); audio->startStream(stream2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -152,8 +147,7 @@ int main(int argc, char *argv[]) audio->stopStream(stream1); audio->stopStream(stream2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { } cleanup: diff --git a/tests/call_twostreams.dsp b/tests/call_twostreams.dsp index b81d0d7..3e47390 100644 --- a/tests/call_twostreams.dsp +++ b/tests/call_twostreams.dsp @@ -42,14 +42,14 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib dsound.lib /nologo /subsystem:console /machine:I386 !ELSEIF "$(CFG)" == "call_twostreams - Win32 Debug" @@ -65,15 +65,15 @@ LINK32=link.exe # PROP Intermediate_Dir "Debug" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /GZ /c +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib dsound.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept !ENDIF diff --git a/tests/in_out.cpp b/tests/in_out.cpp index 7513497..131d57b 100644 --- a/tests/in_out.cpp +++ b/tests/in_out.cpp @@ -65,8 +65,7 @@ int main(int argc, char *argv[]) audio = new RtAudio(&stream, device, chans, device, chans, FORMAT, fs, &buffer_size, 8); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { exit(EXIT_FAILURE); } @@ -76,8 +75,7 @@ int main(int argc, char *argv[]) buffer = (MY_TYPE *) audio->getStreamBuffer(stream); audio->startStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -87,8 +85,7 @@ int main(int argc, char *argv[]) try { audio->tickStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } counter += buffer_size; @@ -97,8 +94,7 @@ int main(int argc, char *argv[]) try { audio->stopStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { } cleanup: diff --git a/tests/in_out.dsp b/tests/in_out.dsp index ddfa94f..ef5e0d9 100644 --- a/tests/in_out.dsp +++ b/tests/in_out.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe diff --git a/tests/info.cpp b/tests/info.cpp index a4cfcf2..9cd9738 100644 --- a/tests/info.cpp +++ b/tests/info.cpp @@ -17,7 +17,7 @@ int main(int argc, char *argv[]) try { audio = new RtAudio(); } - catch (RtAudioError &m) { + catch (RtError &m) { m.printMessage(); exit(EXIT_FAILURE); } @@ -29,7 +29,7 @@ int main(int argc, char *argv[]) try { audio->getDeviceInfo(i, &my_info); } - catch (RtAudioError &m) { + catch (RtError &m) { m.printMessage(); break; } diff --git a/tests/info.dsp b/tests/info.dsp index 067803a..f285f23 100644 --- a/tests/info.dsp +++ b/tests/info.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe diff --git a/tests/play_raw.cpp b/tests/play_raw.cpp index 827f533..b616571 100644 --- a/tests/play_raw.cpp +++ b/tests/play_raw.cpp @@ -79,8 +79,7 @@ int main(int argc, char *argv[]) audio = new RtAudio(&stream, device, chans, 0, 0, FORMAT, fs, &buffer_size, 2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { fclose(fd); exit(EXIT_FAILURE); } @@ -89,8 +88,7 @@ int main(int argc, char *argv[]) buffer = (MY_TYPE *) audio->getStreamBuffer(stream); audio->startStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -101,8 +99,7 @@ int main(int argc, char *argv[]) try { audio->tickStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } } @@ -115,8 +112,7 @@ int main(int argc, char *argv[]) try { audio->stopStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { } cleanup: diff --git a/tests/play_raw.dsp b/tests/play_raw.dsp index dab880a..e01119f 100644 --- a/tests/play_raw.dsp +++ b/tests/play_raw.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe diff --git a/tests/play_saw.cpp b/tests/play_saw.cpp index bd865aa..1b0d3bb 100644 --- a/tests/play_saw.cpp +++ b/tests/play_saw.cpp @@ -73,8 +73,7 @@ int main(int argc, char *argv[]) audio = new RtAudio(&stream, device, chans, 0, 0, FORMAT, fs, &buffer_size, 4); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { exit(EXIT_FAILURE); } @@ -85,8 +84,7 @@ int main(int argc, char *argv[]) buffer = (MY_TYPE *) audio->getStreamBuffer(stream); audio->startStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -104,8 +102,7 @@ int main(int argc, char *argv[]) //cout << "frames until no block = " << audio->streamWillBlock(stream) << endl; audio->tickStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -115,8 +112,7 @@ int main(int argc, char *argv[]) try { audio->stopStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { } cleanup: diff --git a/tests/play_saw.dsp b/tests/play_saw.dsp index b6f9313..6e3f4ec 100644 --- a/tests/play_saw.dsp +++ b/tests/play_saw.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe diff --git a/tests/record_raw.cpp b/tests/record_raw.cpp index 739566e..04041ee 100644 --- a/tests/record_raw.cpp +++ b/tests/record_raw.cpp @@ -11,7 +11,6 @@ #include "RtAudio.h" #include -#include /* typedef char MY_TYPE; @@ -66,8 +65,7 @@ int main(int argc, char *argv[]) audio = new RtAudio(&stream, 0, 0, device, chans, FORMAT, fs, &buffer_size, 8); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { exit(EXIT_FAILURE); } @@ -78,8 +76,7 @@ int main(int argc, char *argv[]) buffer = (MY_TYPE *) audio->getStreamBuffer(stream); audio->startStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -89,8 +86,7 @@ int main(int argc, char *argv[]) try { audio->tickStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -101,8 +97,7 @@ int main(int argc, char *argv[]) try { audio->stopStream(stream); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { } cleanup: diff --git a/tests/record_raw.dsp b/tests/record_raw.dsp index 4b1f1e4..f3a6050 100644 --- a/tests/record_raw.dsp +++ b/tests/record_raw.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe diff --git a/tests/twostreams.cpp b/tests/twostreams.cpp index 1f7a05d..2356a5f 100644 --- a/tests/twostreams.cpp +++ b/tests/twostreams.cpp @@ -77,8 +77,7 @@ int main(int argc, char *argv[]) try { audio = new RtAudio(); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { exit(EXIT_FAILURE); } @@ -90,8 +89,7 @@ int main(int argc, char *argv[]) buffer1 = (MY_TYPE *) audio->getStreamBuffer(stream1); buffer2 = (MY_TYPE *) audio->getStreamBuffer(stream2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -101,8 +99,7 @@ int main(int argc, char *argv[]) try { audio->startStream(stream1); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -119,8 +116,7 @@ int main(int argc, char *argv[]) try { audio->tickStream(stream1); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -132,8 +128,7 @@ int main(int argc, char *argv[]) audio->stopStream(stream1); audio->startStream(stream2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -146,8 +141,7 @@ int main(int argc, char *argv[]) try { audio->tickStream(stream2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -163,8 +157,7 @@ int main(int argc, char *argv[]) audio->startStream(stream1); audio->startStream(stream2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -177,8 +170,7 @@ int main(int argc, char *argv[]) memcpy(buffer1, buffer2, sizeof(MY_TYPE) * chans * buffer_size); audio->tickStream(stream1); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { goto cleanup; } @@ -190,8 +182,7 @@ int main(int argc, char *argv[]) audio->stopStream(stream1); audio->stopStream(stream2); } - catch (RtAudioError &m) { - m.printMessage(); + catch (RtError &) { } cleanup: diff --git a/tests/twostreams.dsp b/tests/twostreams.dsp index 0e69ae2..8e7820a 100644 --- a/tests/twostreams.dsp +++ b/tests/twostreams.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS_" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "__WINDOWS_DS__" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe
+
©2001-2002 CCRMA, Stanford University. All Rights Reserved.
Maintained by Gary P. Scavone, gary@ccrma.stanford.edu

diff --git a/doc/header.html b/doc/doxygen/header.html similarity index 100% rename from doc/header.html rename to doc/doxygen/header.html diff --git a/doc/tutorial.txt b/doc/doxygen/tutorial.txt similarity index 83% rename from doc/tutorial.txt rename to doc/doxygen/tutorial.txt index 6a3bc98..951b28b 100644 --- a/doc/tutorial.txt +++ b/doc/doxygen/tutorial.txt @@ -2,9 +2,19 @@ -
-\ref intro    \ref download    \ref start    \ref error    \ref probing    \ref settings    \ref playbackb    \ref playbackc    \ref recording    \ref duplex    \ref methods    \ref compiling    \ref osnotes    \ref acknowledge -
+- \ref intro +- \ref start +- \ref error +- \ref probing +- \ref settings +- \ref playbackb +- \ref playbackc +- \ref recording +- \ref duplex +- \ref methods +- \ref compiling +- \ref osnotes +- \ref acknowledge \section intro Introduction @@ -24,11 +34,7 @@ RtAudio is a C++ class which provides a common API (Application Programming Inte RtAudio incorporates the concept of audio streams, which represent audio output (playback) and/or input (recording). Available audio devices and their capabilities can be enumerated and then specified when opening a stream. Multiple streams can run at the same time and, when allowed by the underlying audio API, a single device can serve multiple streams. -The RtAudio API provides both blocking (synchronous) and callback (asynchronous) functionality. Callbacks are typically used in conjunction with graphical user interfaces (GUI). Blocking functionality is often necessary for explicit control of multiple input/output stream synchronization or when audio must be synchronized with other system events. - -\section download Download - -Latest Release (22 January 2002): Version 2.0 (111 kB tar/gzipped) +The RtAudio API provides both blocking (synchronous) and callback (asyncronous) functionality. Callbacks are typically used in conjunction with graphical user interfaces (GUI). Blocking functionality is often necessary for explicit control of multiple input/output stream synchronization or when audio must be synchronized with other system events. \section start Getting Started @@ -46,7 +52,7 @@ int main() try { audio = new RtAudio(); } - catch (RtAudioError &error) { + catch (RtError &error) { // Handle the exception here } @@ -60,7 +66,7 @@ Obviously, this example doesn't demonstrate any of the real functionality of RtA \section error Error Handling -RtAudio uses a C++ exception handler called RtAudioError, which is declared and defined within the RtAudio class files. The RtAudioError class is quite simple but it does allow errors to be "caught" by RtAudioError::TYPE. Almost all RtAudio methods can "throw" an RtAudioError, most typically if an invalid stream identifier is supplied to a method or a driver error occurs. There are a number of cases within RtAudio where warning messages may be displayed but an exception is not thrown. There is a private RtAudio method, error(), which can be modified to globally control how these messages are handled and reported. +RtAudio uses a C++ exception handler called RtError, which is declared and defined within the RtAudio class files. The RtError class is quite simple but it does allow errors to be "caught" by RtError::TYPE. Almost all RtAudio methods can "throw" an RtError, most typically if an invalid stream identifier is supplied to a method or a driver error occurs. There are a number of cases within RtAudio where warning messages may be displayed but an exception is not thrown. There is a private RtAudio method, error(), which can be modified to globally control how these messages are handled and reported. \section probing Probing Device Capabilities @@ -71,7 +77,7 @@ A programmer may wish to query the available audio device capabilities before de // probe.cpp -#include +#include #include "RtAudio.h" int main() @@ -82,7 +88,7 @@ int main() try { audio = new RtAudio(); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); exit(EXIT_FAILURE); } @@ -97,14 +103,14 @@ int main() try { audio->getDeviceInfo(i, &info); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); break; } // Print, for example, the maximum number of output channels for each device cout << "device = " << i; - cout << ": maximum output channels = " << info.maxOutputChannels << endl; + cout << ": maximum output channels = " << info.maxOutputChannels << "\n"; } // Clean up @@ -146,7 +152,7 @@ The following data formats are defined and fully supported by RtAudio: static const RTAUDIO_FORMAT RTAUDIO_FLOAT64; // 64-bit double \endcode -The nativeFormats member of the RtAudio::RTAUDIO_DEVICE structure is a bit mask of the above formats which are natively supported by the device. However, RtAudio will automatically provide format conversion if a particular format is not natively supported. When the probed member of the RTAUDIO_DEVICE structure is false, the remaining structure members are likely unknown and the device is probably unusable. +The nativeFormats member of the RtAudio::RTAUDIO_DEVICE structure is a bit mask of the above formats which are natively supported by the device. However, RtAudio will automatically provide format conversion if a particular format is not natively supported. When the probed member of the RTAUDIO_DEVICE structure is false, the remaining structure members are likely unknown and the device is probably unuseable. In general, the user need not be concerned with the minimum channel values reported in the RTAUDIO_DEVICE structure. While some audio devices may require a minimum channel value > 1, RtAudio will provide automatic channel number compensation when the number of channels set by the user is less than that required by the device. Channel compensation is NOT possible when the number of channels set by the user is greater than that supported by the device. @@ -177,7 +183,7 @@ int main() stream = audio->openStream(device, channels, 0, 0, RtAudio::RTAUDIO_FLOAT32, sample_rate, &buffer_size, n_buffers); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); exit(EXIT_FAILURE); } @@ -189,12 +195,10 @@ int main() } \endcode -The RtAudio::openStream() method attempts to open a stream with a specified set of parameter values. When successful, a stream identifier is returned. In this case, we attempt to open a playback stream on device 0 with two channels, 32-bit floating point data, a sample rate of 44100 Hz, a frame rate of 256 sample frames per read/write, and 4 internal device buffers. When device = 0, RtAudio first attempts to open the default audio device with the given parameters. If that attempt fails, an attempt is made to find a device or set of devices which will meet the given parameters. If all attempts are unsuccessful, an RtAudioError is thrown. When a non-zero device value is specified, an attempt is made to open that device only. +The RtAudio::openStream() method attempts to open a stream with a specified set of parameter values. When successful, a stream identifier is returned. In this case, we attempt to open a playback stream on device 0 with two channels, 32-bit floating point data, a sample rate of 44100 Hz, a frame rate of 256 sample frames per read/write, and 4 internal device buffers. When device = 0, RtAudio first attempts to open the default audio device with the given parameters. If that attempt fails, an attempt is made to find a device or set of devices which will meet the given parameters. If all attempts are unsuccessful, an RtError is thrown. When a non-zero device value is specified, an attempt is made to open that device only. RtAudio provides four signed integer and two floating point data formats which can be specified using the RtAudio::RTAUDIO_FORMAT parameter values mentioned earlier. If the opened device does not natively support the given format, RtAudio will automatically perform the necessary data format conversion. -Buffer sizes in RtAudio are ALWAYS given in sample frame units. For example, if you open an output stream with 4 channels and set bufferSize to 512, you will have to write 2048 samples of data to the output buffer within your callback or between calls to RtAudio::tickStream(). In this case, a single sample frame of data contains 4 samples of data. - The bufferSize parameter specifies the desired number of sample frames which will be written to and/or read from a device per write/read operation. The nBuffers parameter is used in setting the underlying device buffer parameters. Both the bufferSize and nBuffers parameters can be used to control stream latency though there is no guarantee that the passed values will be those used by a device. In general, lower values for both parameters will produce less latency but perhaps less robust performance. Both parameters can be specified with values of zero, in which case the smallest allowable values will be used. The bufferSize parameter is passed as a pointer and the actual value used by the stream is set during the device setup procedure. bufferSize values should be a power of two. Optimal and allowable buffer values tend to vary between systems and devices. Check the \ref osnotes section for general guidelines. As noted earlier, the device capabilities reported by a driver or underlying audio API are not always accurate and/or may be dependent on a combination of device settings. Because of this, RtAudio does not attempt to query a device's capabilities or use previously reported values when opening a device. Instead, RtAudio simply attempts to set the given parameters on a specified device and then checks whether the setup is successful or not. @@ -226,7 +230,7 @@ int main() audio = new RtAudio(&stream, device, channels, 0, 0, RtAudio::RTAUDIO_FLOAT32, sample_rate, &buffer_size, n_buffers); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); exit(EXIT_FAILURE); } @@ -238,7 +242,7 @@ int main() // Start the stream audio->startStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); goto cleanup; } @@ -253,7 +257,7 @@ int main() try { audio->tickStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); goto cleanup; } @@ -266,7 +270,7 @@ int main() audio->stopStream(stream); audio->closeStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); } @@ -290,7 +294,7 @@ The primary difference in using RtAudio with callback functionality involves the \code -#include +#include #include "RtAudio.h" // Two-channel sawtooth wave generator. @@ -330,7 +334,7 @@ int main() audio = new RtAudio(&stream, device, channels, 0, 0, RtAudio::RTAUDIO_FLOAT64, sample_rate, &buffer_size, n_buffers); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); exit(EXIT_FAILURE); } @@ -342,7 +346,7 @@ int main() // Start the stream audio->startStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); goto cleanup; } @@ -355,7 +359,7 @@ int main() audio->stopStream(stream); audio->closeStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); } @@ -399,7 +403,7 @@ int main() audio = new RtAudio(&stream, 0, 0, device, channels, RtAudio::RTAUDIO_FLOAT32, sample_rate, &buffer_size, n_buffers); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); exit(EXIT_FAILURE); } @@ -411,7 +415,7 @@ int main() // Start the stream audio->startStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); goto cleanup; } @@ -424,7 +428,7 @@ int main() try { audio->tickStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); goto cleanup; } @@ -439,7 +443,7 @@ int main() // Stop the stream audio->stopStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); } @@ -460,7 +464,7 @@ Finally, it is easy to use RtAudio for simultaneous audio input/output, or duple \code // duplex.cpp -#include +#include #include "RtAudio.h" // Pass-through function. @@ -487,7 +491,7 @@ int main() audio = new RtAudio(&stream, device, channels, device, channels, RtAudio::RTAUDIO_FLOAT64, sample_rate, &buffer_size, n_buffers); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); exit(EXIT_FAILURE); } @@ -499,7 +503,7 @@ int main() // Start the stream audio->startStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); goto cleanup; } @@ -512,7 +516,7 @@ int main() audio->stopStream(stream); audio->closeStream(stream); } - catch (RtAudioError &error) { + catch (RtError &error) { error.printMessage(); } @@ -566,28 +570,28 @@ In order to compile RtAudio for a specific OS and audio API, it is necessary to
Linux ALSA__LINUX_ALSA___LINUX_ALSA__ libasound, libpthreadg++ -Wall -D__LINUX_ALSA_ -o probe probe.cpp RtAudio.cpp -lasound -lpthreadg++ -Wall -D__LINUX_ALSA__ -o probe probe.cpp RtAudio.cpp -lasound -lpthread
Linux OSS__LINUX_OSS___LINUX_OSS__ libpthreadg++ -Wall -D__LINUX_OSS_ -o probe probe.cpp RtAudio.cpp -lpthreadg++ -Wall -D__LINUX_OSS__ -o probe probe.cpp RtAudio.cpp -lpthread
Irix AL__IRIX_AL___IRIX_AL__ libaudio, libpthreadCC -Wall -D__IRIX_AL_ -o probe probe.cpp RtAudio.cpp -laudio -lpthreadCC -Wall -D__IRIX_AL__ -o probe probe.cpp RtAudio.cpp -laudio -lpthread
Windows Direct Sound__WINDOWS_DS___WINDOWS_DS__ dsound.lib (ver. 5.0 or higher), multithreaded compiler specific