From cab1227acdd6d9837844152e7c3177b605d7f89b Mon Sep 17 00:00:00 2001 From: Vincent Sonnier Date: Sun, 21 Nov 2021 11:39:27 +0100 Subject: [PATCH] Update RtAudio to v5.20 (#927) --- external/rtaudio/README.md | 57 ++- external/rtaudio/RtAudio.cpp | 833 ++++++++++++++++++++++++----------- external/rtaudio/RtAudio.h | 270 ++++++------ 3 files changed, 743 insertions(+), 417 deletions(-) diff --git a/external/rtaudio/README.md b/external/rtaudio/README.md index 228f691..ce315a4 100644 --- a/external/rtaudio/README.md +++ b/external/rtaudio/README.md @@ -1,10 +1,10 @@ # RtAudio -[![Build Status](https://travis-ci.org/thestk/rtaudio.svg?branch=master)](https://travis-ci.org/thestk/rtaudio) +![Build Status](https://github.com/thestk/rtaudio/actions/workflows/ci.yml/badge.svg) A set of C++ classes that provide a common API for realtime audio input/output across Linux (native ALSA, JACK, PulseAudio and OSS), Macintosh OS X (CoreAudio and JACK), and Windows (DirectSound, ASIO and WASAPI) operating systems. -By Gary P. Scavone, 2001-2019 (and many other developers!) +By Gary P. Scavone, 2001-2021 (and many other developers!) This distribution of RtAudio contains the following: @@ -28,6 +28,30 @@ RtAudio is a set of C++ classes that provides a common API (Application Programm 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. Where applicable, multiple API support can be compiled and a particular API specified when creating an RtAudio instance. See the \ref apinotes section for information specific to each of the supported audio APIs. +## Building + +Several build systems are available. These are: + + - autotools (`./autogen.sh; make` from git, or `./configure; make` from tarball release) + - CMake (`mkdir build; cd build; ../cmake; make`) + - meson (`meson build; cd build; ninja`) + +See `install.txt` for more instructions about how to select the audio backend API. By +default all detected APIs will be enabled. + +We recommend using the autotools-based build for packaging purposes. Please note that +RtAudio is designed as a single `.cpp` and `.h` file so that it is easy to copy directly +into a project. In that case you need to define the appropriate flags for the desired +backend APIs. + +## FAQ + +### Why does audio only come to one ear when I choose 1-channel output? + +RtAudio doesn't automatically turn 1-channel output into stereo output with copied values +to each channel, it really only opens one channel. So, if this is the behaviour you want, +you have to do this copying in your audio stream callback. + ## Further Reading For complete documentation on RtAudio, see the doc directory of the distribution or surf to http://www.music.mcgill.ca/~gary/rtaudio/. @@ -35,31 +59,4 @@ For complete documentation on RtAudio, see the doc directory of the distribution ## Legal and ethical: -The RtAudio license is similar to the MIT License. - - RtAudio: a set of realtime audio i/o C++ classes - Copyright (c) 2001-2019 Gary P. Scavone - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation files - (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of the Software, - and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - Any person wishing to distribute modifications to the Software is - asked to send the modifications to the original developer so that - they can be incorporated into the canonical version. This is, - however, not a binding provision of this license. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR - ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The RtAudio license is similar to the MIT License. Please see [LICENSE](LICENSE). diff --git a/external/rtaudio/RtAudio.cpp b/external/rtaudio/RtAudio.cpp index 0837d98..40fb6a9 100644 --- a/external/rtaudio/RtAudio.cpp +++ b/external/rtaudio/RtAudio.cpp @@ -11,7 +11,7 @@ RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes - Copyright (c) 2001-2019 Gary P. Scavone + Copyright (c) 2001-2021 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files @@ -39,7 +39,7 @@ */ /************************************************************************/ -// RtAudio: Version 5.1.0 +// RtAudio: Version 5.2.0 #include "RtAudio.h" #include @@ -56,7 +56,7 @@ const unsigned int RtApi::SAMPLE_RATES[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; -#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) +#if defined(_WIN32) || defined(__CYGWIN__) #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) @@ -64,12 +64,17 @@ const unsigned int RtApi::SAMPLE_RATES[] = { #include "tchar.h" - static std::string convertCharPointerToStdString(const char *text) + template inline + std::string convertCharPointerToStdString(const T *text); + + template<> inline + std::string convertCharPointerToStdString(const char *text) { return std::string(text); } - static std::string convertCharPointerToStdString(const wchar_t *text) + template<> inline + std::string convertCharPointerToStdString(const wchar_t *text) { int length = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); std::string s( length-1, '\0' ); @@ -77,15 +82,12 @@ const unsigned int RtApi::SAMPLE_RATES[] = { return s; } -#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) +#elif defined(__unix__) || defined(__APPLE__) // pthread API #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) #define MUTEX_LOCK(A) pthread_mutex_lock(A) #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) -#else - #define MUTEX_INITIALIZE(A) abs(*A) // dummy definitions - #define MUTEX_DESTROY(A) abs(*A) // dummy definitions #endif // *************************************************** // @@ -262,7 +264,7 @@ RtAudio :: RtAudio( RtAudio::Api api ) // It should not be possible to get here because the preprocessor // definition __RTAUDIO_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in - // case something weird happens, we'll thow an error. + // case something weird happens, we'll throw an error. std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); } @@ -405,13 +407,27 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, unsigned int RtApi :: getDefaultInputDevice( void ) { - // Should be implemented in subclasses if possible. + // Should be reimplemented in subclasses if necessary. + unsigned int nDevices = getDeviceCount(); + for ( unsigned int i = 0; i < nDevices; i++ ) { + if ( getDeviceInfo( i ).isDefaultInput ) { + return i; + } + } + return 0; } unsigned int RtApi :: getDefaultOutputDevice( void ) { - // Should be implemented in subclasses if possible. + // Should be reimplemented in subclasses if necessary. + unsigned int nDevices = getDeviceCount(); + for ( unsigned int i = 0; i < nDevices; i++ ) { + if ( getDeviceInfo( i ).isDefaultOutput ) { + return i; + } + } + return 0; } @@ -506,6 +522,8 @@ unsigned int RtApi :: getStreamSampleRate( void ) #if defined(__MACOSX_CORE__) +#include + // The OS X CoreAudio API is designed to use a separate callback // procedure for each of its audio devices. A single RtAudio duplex // stream using two different devices is supported here, though it @@ -1478,15 +1496,17 @@ void RtApiCore :: closeStream( void ) errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } - } - if ( stream_.state == STREAM_RUNNING ) - AudioDeviceStop( handle->id[0], callbackHandler ); + #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); -#else - // deprecated in favor of AudioDeviceDestroyIOProcID() - AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], handle->procId[0] ); + AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); +#else // deprecated behaviour + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], callbackHandler ); + AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); #endif + } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { @@ -1501,15 +1521,17 @@ void RtApiCore :: closeStream( void ) errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } - } - if ( stream_.state == STREAM_RUNNING ) - AudioDeviceStop( handle->id[1], callbackHandler ); + #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); -#else - // deprecated in favor of AudioDeviceDestroyIOProcID() - AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], handle->procId[1] ); + AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); +#else // deprecated behaviour + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], callbackHandler ); + AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); #endif + } } for ( int i=0; i<2; i++ ) { @@ -1542,15 +1564,19 @@ void RtApiCore :: startStream( void ) return; } - #if defined( HAVE_GETTIMEOFDAY ) +#if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); - #endif +#endif OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStart( handle->id[0], handle->procId[0] ); +#else // deprecated behaviour result = AudioDeviceStart( handle->id[0], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); @@ -1561,7 +1587,11 @@ void RtApiCore :: startStream( void ) if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStart( handle->id[1], handle->procId[1] ); +#else // deprecated behaviour result = AudioDeviceStart( handle->id[1], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); @@ -1596,7 +1626,11 @@ void RtApiCore :: stopStream( void ) pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStop( handle->id[0], handle->procId[0] ); +#else // deprecated behaviour result = AudioDeviceStop( handle->id[0], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); @@ -1606,7 +1640,11 @@ void RtApiCore :: stopStream( void ) if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStop( handle->id[1], handle->procId[1] ); +#else // deprecated behaviour result = AudioDeviceStop( handle->id[1], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); @@ -2790,13 +2828,13 @@ static long asioMessages( long selector, long value, void* message, double* opt RtApiAsio :: RtApiAsio() { - // ASIO cannot run on a multi-threaded appartment. You can call - // CoInitialize beforehand, but it must be for appartment threading + // ASIO cannot run on a multi-threaded apartment. You can call + // CoInitialize beforehand, but it must be for apartment threading // (in which case, CoInitilialize will return S_FALSE here). coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { - errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; + errorText_ = "RtApiAsio::ASIO requires a single-threaded apartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; error( RtAudioError::WARNING ); } coInitialized_ = true; @@ -2819,6 +2857,18 @@ unsigned int RtApiAsio :: getDeviceCount( void ) return (unsigned int) drivers.asioGetNumDev(); } +// We can only load one ASIO driver, so the default output is always the first device. +unsigned int RtApiAsio :: getDefaultOutputDevice( void ) +{ + return 0; +} + +// We can only load one ASIO driver, so the default input is always the first device. +unsigned int RtApiAsio :: getDefaultInputDevice( void ) +{ + return 0; +} + RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; @@ -3799,6 +3849,17 @@ if ( objectPtr )\ typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); +#ifndef __IAudioClient3_INTERFACE_DEFINED__ +MIDL_INTERFACE( "00000000-0000-0000-0000-000000000000" ) IAudioClient3 +{ + virtual HRESULT GetSharedModeEnginePeriod( WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32* ) = 0; + virtual HRESULT InitializeSharedAudioStream( DWORD, UINT32, WAVEFORMATEX*, LPCGUID ) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL( IAudioClient3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) +#endif +#endif + //----------------------------------------------------------------------------- // WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. @@ -4053,7 +4114,7 @@ public: #endif } - void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount ) + void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount, int maxOutSampleCount = -1 ) { unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; if ( _sampleRatio == 1 ) @@ -4064,7 +4125,15 @@ public: return; } - unsigned int outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + unsigned int outputBufferSize = 0; + if ( maxOutSampleCount != -1 ) + { + outputBufferSize = _bytesPerSample * _channelCount * maxOutSampleCount; + } + else + { + outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + } IMFMediaBuffer* rInBuffer; IMFSample* rInSample; @@ -4486,34 +4555,6 @@ Exit: return info; } -//----------------------------------------------------------------------------- - -unsigned int RtApiWasapi::getDefaultOutputDevice( void ) -{ - for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { - if ( getDeviceInfo( i ).isDefaultOutput ) { - return i; - } - } - - return 0; -} - -//----------------------------------------------------------------------------- - -unsigned int RtApiWasapi::getDefaultInputDevice( void ) -{ - for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { - if ( getDeviceInfo( i ).isDefaultInput ) { - return i; - } - } - - return 0; -} - -//----------------------------------------------------------------------------- - void RtApiWasapi::closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { @@ -4600,17 +4641,16 @@ void RtApiWasapi::stopStream( void ) error( RtAudioError::WARNING ); return; } + if ( stream_.state == STREAM_STOPPING ) { + errorText_ = "RtApiWasapi::stopStream: The stream is already stopping."; + error( RtAudioError::WARNING ); + return; + } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; - // wait until stream thread is stopped - while( stream_.state != STREAM_STOPPED ) { - Sleep( 1 ); - } - - // Wait for the last buffer to play before stopping. - Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); + WaitForSingleObject( ( void* ) stream_.callbackInfo.thread, INFINITE ); // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { @@ -4633,14 +4673,16 @@ void RtApiWasapi::abortStream( void ) error( RtAudioError::WARNING ); return; } + if ( stream_.state == STREAM_STOPPING ) { + errorText_ = "RtApiWasapi::abortStream: The stream is already stopping."; + error( RtAudioError::WARNING ); + return; + } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; - // wait until stream thread is stopped - while ( stream_.state != STREAM_STOPPED ) { - Sleep( 1 ); - } + WaitForSingleObject( ( void* ) stream_.callbackInfo.thread, INFINITE ); // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { @@ -4849,7 +4891,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.doConvertBuffer[mode] = true; if ( stream_.doConvertBuffer[mode] ) - setConvertInfo( mode, 0 ); + setConvertInfo( mode, firstChannel ); // Allocate necessary internal buffers bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); @@ -4941,7 +4983,7 @@ void RtApiWasapi::wasapiThread() // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; BYTE* streamBuffer = NULL; - unsigned long captureFlags = 0; + DWORD captureFlags = 0; unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; unsigned int convBufferSize = 0; @@ -4960,7 +5002,7 @@ void RtApiWasapi::wasapiThread() RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread - HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); + HMODULE AvrtDll = LoadLibraryW( L"AVRT.dll" ); if ( AvrtDll ) { DWORD taskIndex = 0; TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = @@ -4985,12 +5027,37 @@ void RtApiWasapi::wasapiThread() captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); if ( !captureClient ) { - hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, - loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, - 0, - captureFormat, - NULL ); + IAudioClient3* captureAudioClient3 = nullptr; + captureAudioClient->QueryInterface( __uuidof( IAudioClient3 ), ( void** ) &captureAudioClient3 ); + if ( captureAudioClient3 && !loopbackEnabled ) + { + UINT32 Ignore; + UINT32 MinPeriodInFrames; + hr = captureAudioClient3->GetSharedModeEnginePeriod( captureFormat, + &Ignore, + &Ignore, + &MinPeriodInFrames, + &Ignore ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; + goto Exit; + } + + hr = captureAudioClient3->InitializeSharedAudioStream( AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + MinPeriodInFrames, + captureFormat, + NULL ); + } + else + { + hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + captureFormat, + NULL ); + } + if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; @@ -5071,12 +5138,37 @@ void RtApiWasapi::wasapiThread() renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); if ( !renderClient ) { - hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, - 0, - renderFormat, - NULL ); + IAudioClient3* renderAudioClient3 = nullptr; + renderAudioClient->QueryInterface( __uuidof( IAudioClient3 ), ( void** ) &renderAudioClient3 ); + if ( renderAudioClient3 ) + { + UINT32 Ignore; + UINT32 MinPeriodInFrames; + hr = renderAudioClient3->GetSharedModeEnginePeriod( renderFormat, + &Ignore, + &Ignore, + &MinPeriodInFrames, + &Ignore ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; + goto Exit; + } + + hr = renderAudioClient3->InitializeSharedAudioStream( AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + MinPeriodInFrames, + renderFormat, + NULL ); + } + else + { + hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + renderFormat, + NULL ); + } + if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; @@ -5140,18 +5232,18 @@ void RtApiWasapi::wasapiThread() if ( stream_.mode == INPUT ) { using namespace std; // for ceilf - convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + convBuffSize = ( unsigned int ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); } else if ( stream_.mode == OUTPUT ) { - convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + convBuffSize = ( unsigned int ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); } else if ( stream_.mode == DUPLEX ) { - convBuffSize = std::max( ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), - ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + convBuffSize = std::max( ( unsigned int ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + ( unsigned int ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); } @@ -5177,11 +5269,6 @@ void RtApiWasapi::wasapiThread() if ( captureAudioClient ) { int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); - if ( captureSrRatio != 1 ) - { - // account for remainders - samplesToPull--; - } convBufferSize = 0; while ( convBufferSize < stream_.bufferSize ) @@ -5203,7 +5290,8 @@ void RtApiWasapi::wasapiThread() captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, convBuffer, samplesToPull, - convSamples ); + convSamples, + convBufferSize == 0 ? -1 : stream_.bufferSize - convBufferSize ); convBufferSize += convSamples; samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples @@ -5471,14 +5559,14 @@ Exit: CoUninitialize(); - // update stream state - stream_.state = STREAM_STOPPED; - if ( !errorText.empty() ) { errorText_ = errorText; error( errorType ); } + + // update stream state + stream_.state = STREAM_STOPPED; } //******************** End of __WINDOWS_WASAPI__ *********************// @@ -6863,7 +6951,7 @@ void RtApiDs :: callbackEvent() if ( stream_.mode == DUPLEX ) { if ( safeReadPointer < endRead ) { if ( duplexPrerollBytes <= 0 ) { - // Pre-roll time over. Be more agressive. + // Pre-roll time over. Be more aggressive. int adjustment = endRead-safeReadPointer; handle->xrun[1] = true; @@ -7018,19 +7106,18 @@ static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, // If good device, then save its name and guid. std::string name = convertCharPointerToStdString( description ); - //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) - if ( lpguid == NULL ) - name = "Default Device"; + if ( validDevice ) { for ( unsigned int i=0; i= 201103L + :handles{nullptr, nullptr}, synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } +#else + : synchronized(false), runnable(false) { handles[0] = NULL; handles[1] = NULL; xrun[0] = false; xrun[1] = false; } +#endif }; static void *alsaCallbackHandler( void * ptr ); @@ -7148,6 +7239,13 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) char name[64]; snd_ctl_t *handle = 0; + strcpy(name, "default"); + result = snd_ctl_open( &handle, "default", 0 ); + if (result == 0) { + nDevices++; + snd_ctl_close( handle ); + } + // Count cards and devices card = -1; snd_card_next( &card ); @@ -7180,12 +7278,6 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) snd_card_next( &card ); } - result = snd_ctl_open( &handle, "default", 0 ); - if (result == 0) { - nDevices++; - snd_ctl_close( handle ); - } - return nDevices; } @@ -7195,13 +7287,21 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) info.probed = false; unsigned nDevices = 0; - int result, subdevice, card; + int result=-1, subdevice=-1, card=-1; char name[64]; snd_ctl_t *chandle = 0; + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices++ == device ) { + strcpy( name, "default" ); + goto foundDevice; + } + } + if ( chandle ) + snd_ctl_close( chandle ); + // Count cards and devices - card = -1; - subdevice = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); @@ -7235,15 +7335,6 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_card_next( &card ); } - result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); - if ( result == 0 ) { - if ( nDevices == device ) { - strcpy( name, "default" ); - goto foundDevice; - } - nDevices++; - } - if ( nDevices == 0 ) { errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); @@ -7464,11 +7555,13 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) } // Get the device name - char *cardname; - result = snd_card_get_name( card, &cardname ); - if ( result >= 0 ) { - sprintf( name, "hw:%s,%d", cardname, subdevice ); - free( cardname ); + if (strncmp(name, "default", 7)!=0) { + char *cardname; + result = snd_card_get_name( card, &cardname ); + if ( result >= 0 ) { + sprintf( name, "hw:%s,%d", cardname, subdevice ); + free( cardname ); + } } info.name = name; @@ -7495,8 +7588,12 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne { #if defined(__RTAUDIO_DEBUG__) - snd_output_t *out; - snd_output_stdio_attach(&out, stderr, 0); + struct SndOutputTdealloc { + SndOutputTdealloc() : _out(NULL) { snd_output_stdio_attach(&_out, stderr, 0); } + ~SndOutputTdealloc() { snd_output_close(_out); } + operator snd_output_t*() { return _out; } + snd_output_t *_out; + } out; #endif // I'm not using the "plug" interface ... too much inconsistent behavior. @@ -7506,9 +7603,23 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne char name[64]; snd_ctl_t *chandle; - if ( options && options->flags & RTAUDIO_ALSA_USE_DEFAULT ) - snprintf(name, sizeof(name), "%s", "default"); + if ( device == 0 + || (options && options->flags & RTAUDIO_ALSA_USE_DEFAULT) ) + { + strcpy(name, "default"); + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices == device ) { + strcpy( name, "default" ); + snd_ctl_close( chandle ); + goto foundDevice; + } + nDevices++; + } + } + else { + nDevices++; // Count cards and devices card = -1; snd_card_next( &card ); @@ -7536,17 +7647,6 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne snd_card_next( &card ); } - result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); - if ( result == 0 ) { - if ( nDevices == device ) { - strcpy( name, "default" ); - snd_ctl_close( chandle ); - goto foundDevice; - } - nDevices++; - } - snd_ctl_close( chandle ); - if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; @@ -8284,7 +8384,7 @@ void RtApiAlsa :: callbackEvent() } if ( result < (int) stream_.bufferSize ) { - // Either an error or overrun occured. + // Either an error or overrun occurred. if ( result == -EPIPE ) { snd_pcm_state_t state = snd_pcm_state( handle[1] ); if ( state == SND_PCM_STATE_XRUN ) { @@ -8354,7 +8454,7 @@ void RtApiAlsa :: callbackEvent() } if ( result < (int) stream_.bufferSize ) { - // Either an error or underrun occured. + // Either an error or underrun occurred. if ( result == -EPIPE ) { snd_pcm_state_t state = snd_pcm_state( handle[0] ); if ( state == SND_PCM_STATE_XRUN ) { @@ -8424,10 +8524,27 @@ static void *alsaCallbackHandler( void *ptr ) #include #include +#include #include +static pa_mainloop_api *rt_pa_mainloop_api = NULL; +struct PaDeviceInfo { + PaDeviceInfo() : sink_index(-1), source_index(-1) {} + int sink_index; + int source_index; + std::string sink_name; + std::string source_name; + RtAudio::DeviceInfo info; +}; +static struct { + std::vector dev; + std::string default_sink_name; + std::string default_source_name; + int default_rate; +} rt_pa_info; + static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, - 44100, 48000, 96000, 0}; + 44100, 48000, 96000, 192000, 0}; struct rtaudio_pa_format_mapping_t { RtAudioFormat rtaudio_format; @@ -8436,6 +8553,7 @@ struct rtaudio_pa_format_mapping_t { static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, + {RTAUDIO_SINT24, PA_SAMPLE_S24LE}, {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, {0, PA_SAMPLE_INVALID}}; @@ -8449,35 +8567,219 @@ struct PulseAudioHandle { PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } }; +static void rt_pa_mainloop_api_quit(int ret) { + rt_pa_mainloop_api->quit(rt_pa_mainloop_api, ret); +} + +static void rt_pa_set_server_info(pa_context *context, const pa_server_info *info, void *data){ + (void)context; + (void)data; + pa_sample_spec ss; + + if (!info) { + rt_pa_mainloop_api_quit(1); + return; + } + + ss = info->sample_spec; + + rt_pa_info.default_rate = ss.rate; + rt_pa_info.default_sink_name = info->default_sink_name; + rt_pa_info.default_source_name = info->default_source_name; +} + +static void rt_pa_set_sink_info(pa_context * /*c*/, const pa_sink_info *i, + int eol, void * /*userdata*/) +{ + if (eol) return; + PaDeviceInfo inf; + inf.info.name = pa_proplist_gets(i->proplist, "device.description"); + inf.info.probed = true; + inf.info.outputChannels = i->sample_spec.channels; + inf.info.preferredSampleRate = i->sample_spec.rate; + inf.info.isDefaultOutput = (rt_pa_info.default_sink_name == i->name); + inf.sink_index = i->index; + inf.sink_name = i->name; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + inf.info.sampleRates.push_back( *sr ); + for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; + fm->rtaudio_format; ++fm ) + inf.info.nativeFormats |= fm->rtaudio_format; + for (size_t i=0; i < rt_pa_info.dev.size(); i++) + { + /* Attempt to match up sink and source records by device description. */ + if (rt_pa_info.dev[i].info.name == inf.info.name) { + rt_pa_info.dev[i].sink_index = inf.sink_index; + rt_pa_info.dev[i].sink_name = inf.sink_name; + rt_pa_info.dev[i].info.outputChannels = inf.info.outputChannels; + rt_pa_info.dev[i].info.isDefaultOutput = inf.info.isDefaultOutput; + /* Assume duplex channels are minimum of input and output channels. */ + /* Uncomment if we add support for DUPLEX + if (rt_pa_info.dev[i].source_index > -1) + (inf.info.outputChannels < rt_pa_info.dev[i].info.inputChannels) + ? inf.info.outputChannels : rt_pa_info.dev[i].info.inputChannels; + */ + return; + } + } + /* try to ensure device #0 is the default */ + if (inf.info.isDefaultOutput) + rt_pa_info.dev.insert(rt_pa_info.dev.begin(), inf); + else + rt_pa_info.dev.push_back(inf); +} + +static void rt_pa_set_source_info_and_quit(pa_context * /*c*/, const pa_source_info *i, + int eol, void * /*userdata*/) +{ + if (eol) { + rt_pa_mainloop_api_quit(0); + return; + } + PaDeviceInfo inf; + inf.info.name = pa_proplist_gets(i->proplist, "device.description"); + inf.info.probed = true; + inf.info.inputChannels = i->sample_spec.channels; + inf.info.preferredSampleRate = i->sample_spec.rate; + inf.info.isDefaultInput = (rt_pa_info.default_source_name == i->name); + inf.source_index = i->index; + inf.source_name = i->name; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + inf.info.sampleRates.push_back( *sr ); + for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; + fm->rtaudio_format; ++fm ) + inf.info.nativeFormats |= fm->rtaudio_format; + + for (size_t i=0; i < rt_pa_info.dev.size(); i++) + { + /* Attempt to match up sink and source records by device description. */ + if (rt_pa_info.dev[i].info.name == inf.info.name) { + rt_pa_info.dev[i].source_index = inf.source_index; + rt_pa_info.dev[i].source_name = inf.source_name; + rt_pa_info.dev[i].info.inputChannels = inf.info.inputChannels; + rt_pa_info.dev[i].info.isDefaultInput = inf.info.isDefaultInput; + /* Assume duplex channels are minimum of input and output channels. */ + /* Uncomment if we add support for DUPLEX + if (rt_pa_info.dev[i].sink_index > -1) { + rt_pa_info.dev[i].info.duplexChannels = + (inf.info.inputChannels < rt_pa_info.dev[i].info.outputChannels) + ? inf.info.inputChannels : rt_pa_info.dev[i].info.outputChannels; + } + */ + return; + } + } + /* try to ensure device #0 is the default */ + if (inf.info.isDefaultInput) + rt_pa_info.dev.insert(rt_pa_info.dev.begin(), inf); + else + rt_pa_info.dev.push_back(inf); +} + +static void rt_pa_context_state_callback(pa_context *context, void *userdata) { + (void)userdata; + + auto state = pa_context_get_state(context); + switch (state) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + rt_pa_info.dev.clear(); + pa_context_get_server_info(context, rt_pa_set_server_info, NULL); + pa_context_get_sink_info_list(context, rt_pa_set_sink_info, NULL); + pa_context_get_source_info_list(context, rt_pa_set_source_info_and_quit, NULL); + break; + + case PA_CONTEXT_TERMINATED: + rt_pa_mainloop_api_quit(0); + break; + + case PA_CONTEXT_FAILED: + default: + rt_pa_mainloop_api_quit(1); + } +} + RtApiPulse::~RtApiPulse() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } -unsigned int RtApiPulse::getDeviceCount( void ) +void RtApiPulse::collectDeviceInfo( void ) { - return 1; + pa_context *context = NULL; + pa_mainloop *m = NULL; + char *server = NULL; + int ret = 1; + + if (!(m = pa_mainloop_new())) { + errorStream_ << "RtApiPulse::DeviceInfo pa_mainloop_new() failed."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + + rt_pa_mainloop_api = pa_mainloop_get_api(m); + + if (!(context = pa_context_new_with_proplist(rt_pa_mainloop_api, NULL, NULL))) { + errorStream_ << "pa_context_new() failed."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + + pa_context_set_state_callback(context, rt_pa_context_state_callback, NULL); + + if (pa_context_connect(context, server, PA_CONTEXT_NOFLAGS, NULL) < 0) { + errorStream_ << "RtApiPulse::DeviceInfo pa_context_connect() failed: " + << pa_strerror(pa_context_errno(context)); + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + + if (pa_mainloop_run(m, &ret) < 0) { + errorStream_ << "pa_mainloop_run() failed."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + + if (ret != 0) { + errorStream_ << "could not get server info."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto quit; + } + +quit: + if (context) + pa_context_unref(context); + + if (m) { + pa_mainloop_free(m); + } + + pa_xfree(server); } -RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) +unsigned int RtApiPulse::getDeviceCount( void ) { - RtAudio::DeviceInfo info; - info.probed = true; - info.name = "PulseAudio"; - info.outputChannels = 2; - info.inputChannels = 2; - info.duplexChannels = 2; - info.isDefaultOutput = true; - info.isDefaultInput = true; + collectDeviceInfo(); + return rt_pa_info.dev.size(); +} - for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) - info.sampleRates.push_back( *sr ); - - info.preferredSampleRate = 48000; - info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; - - return info; +RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int device ) +{ + if (rt_pa_info.dev.size()==0) + collectDeviceInfo(); + if (device < rt_pa_info.dev.size()) + return rt_pa_info.dev[device].info; + return RtAudio::DeviceInfo(); } static void *pulseaudio_callback( void * user ) @@ -8679,15 +8981,18 @@ void RtApiPulse::stopStream( void ) stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - if ( pah && pah->s_play ) { - int pa_error; - if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { - errorStream_ << "RtApiPulse::stopStream: error draining output device, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); - return; + if ( pah ) { + pah->runnable = false; + if ( pah->s_play ) { + int pa_error; + if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::stopStream: error draining output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } } } @@ -8713,15 +9018,18 @@ void RtApiPulse::abortStream( void ) stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - if ( pah && pah->s_play ) { - int pa_error; - if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { - errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); - return; + if ( pah ) { + pah->runnable = false; + if ( pah->s_play ) { + int pa_error; + if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } } } @@ -8738,15 +9046,51 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned long bufferBytes = 0; pa_sample_spec ss; - if ( device != 0 ) return false; - if ( mode != INPUT && mode != OUTPUT ) return false; - if ( channels != 1 && channels != 2 ) { - errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels."; + if ( device >= rt_pa_info.dev.size() ) return false; + if ( firstChannel != 0 ) { + errorText_ = "PulseAudio does not support channel offset mapping."; return false; } - ss.channels = channels; - if ( firstChannel != 0 ) return false; + /* these may be NULL for default, but we've already got the names */ + const char *dev_input = NULL; + const char *dev_output = NULL; + if (!rt_pa_info.dev[device].source_name.empty()) + dev_input = rt_pa_info.dev[device].source_name.c_str(); + if (!rt_pa_info.dev[device].sink_name.empty()) + dev_output = rt_pa_info.dev[device].sink_name.c_str(); + + if (mode==INPUT && rt_pa_info.dev[device].info.inputChannels == 0) { + errorText_ = "PulseAudio device does not support input."; + return false; + } + if (mode==OUTPUT && rt_pa_info.dev[device].info.outputChannels == 0) { + errorText_ = "PulseAudio device does not support output."; + return false; + } + if (mode==DUPLEX && rt_pa_info.dev[device].info.duplexChannels == 0) { + /* Note: will always error, DUPLEX not yet supported */ + errorText_ = "PulseAudio device does not support duplex."; + return false; + } + + if (mode==INPUT && rt_pa_info.dev[device].info.inputChannels < channels) { + errorText_ = "PulseAudio: unsupported number of input channels."; + return false; + } + + if (mode==OUTPUT && rt_pa_info.dev[device].info.outputChannels < channels) { + errorText_ = "PulseAudio: unsupported number of output channels."; + return false; + } + + if (mode==DUPLEX && rt_pa_info.dev[device].info.duplexChannels < channels) { + /* Note: will always error, DUPLEX not yet supported */ + errorText_ = "PulseAudio: unsupported number of duplex channels."; + return false; + } + + ss.channels = channels; bool sr_found = false; for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { @@ -8758,8 +9102,8 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, } } if ( !sr_found ) { - errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate."; - return false; + stream_.sampleRate = sampleRate; + ss.rate = sampleRate; } bool sf_found = 0; @@ -8783,7 +9127,7 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; - stream_.nBuffers = 1; + stream_.nBuffers = options ? options->numberOfBuffers : 1; stream_.doByteSwap[mode] = false; stream_.nUserChannels[mode] = channels; stream_.nDeviceChannels[mode] = channels + firstChannel; @@ -8796,6 +9140,8 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] ) + stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers. bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); @@ -8851,24 +9197,47 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, int error; if ( options && !options->streamName.empty() ) streamName = options->streamName; switch ( mode ) { - case INPUT: pa_buffer_attr buffer_attr; + case INPUT: buffer_attr.fragsize = bufferBytes; buffer_attr.maxlength = -1; - pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error ); + pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, + dev_input, "Record", &ss, NULL, &buffer_attr, &error ); if ( !pah->s_rec ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; goto error; } break; - case OUTPUT: - pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); + case OUTPUT: { + pa_buffer_attr * attr_ptr; + + if ( options && options->numberOfBuffers > 0 ) { + // pa_buffer_attr::fragsize is recording-only. + // Hopefully PortAudio won't access uninitialized fields. + buffer_attr.maxlength = bufferBytes * options->numberOfBuffers; + buffer_attr.minreq = -1; + buffer_attr.prebuf = -1; + buffer_attr.tlength = -1; + attr_ptr = &buffer_attr; + } else { + attr_ptr = nullptr; + } + + pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, + dev_output, "Playback", &ss, NULL, attr_ptr, &error ); if ( !pah->s_play ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; goto error; } break; + } + case DUPLEX: + /* Note: We could add DUPLEX by synchronizing multiple streams, + but it would mean moving from Simple API to Asynchronous API: + https://freedesktop.org/software/pulseaudio/doxygen/streams.html#sync_streams */ + errorText_ = "RtApiPulse::probeDeviceOpen: duplex not supported for PulseAudio."; + goto error; default: goto error; } @@ -10117,24 +10486,19 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info // data interleaving/deinterleaving. 24-bit integers are assumed to occupy // the lower three bytes of a 32-bit integer. - // Clear our device buffer when in/out duplex device channels are different - if ( outBuffer == stream_.deviceBuffer && stream_.mode == DUPLEX && - ( stream_.nDeviceChannels[0] < stream_.nDeviceChannels[1] ) ) + // Clear our duplex device output buffer if there are more device outputs than user outputs + if ( outBuffer == stream_.deviceBuffer && stream_.mode == DUPLEX && info.outJump > info.inJump ) memset( outBuffer, 0, stream_.bufferSize * info.outJump * formatBytes( info.outFormat ) ); int j; if (info.outFormat == RTAUDIO_FLOAT64) { - Float64 scale; Float64 *out = (Float64 *)outBuffer; if (info.inFormat == RTAUDIO_SINT8) { signed char *in = (signed char *)inBuffer; - scale = 1.0 / 127.5; for (unsigned int i=0; i sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ - unsigned int preferredSampleRate; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ - RtAudioFormat nativeFormats; /*!< Bit mask of supported data formats. */ - - // Default constructor. - DeviceInfo() - :probed(false), outputChannels(0), inputChannels(0), duplexChannels(0), - isDefaultOutput(false), isDefaultInput(false), preferredSampleRate(0), nativeFormats(0) {} + unsigned int preferredSampleRate{}; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ + RtAudioFormat nativeFormats{}; /*!< Bit mask of supported data formats. */ }; - //! The structure for specifying input or ouput stream parameters. + //! The structure for specifying input or output stream parameters. struct StreamParameters { - unsigned int deviceId; /*!< Device index (0 to getDeviceCount() - 1). */ - unsigned int nChannels; /*!< Number of channels. */ - unsigned int firstChannel; /*!< First channel index on device (default = 0). */ - - // Default constructor. - StreamParameters() - : deviceId(0), nChannels(0), firstChannel(0) {} + unsigned int deviceId{}; /*!< Device index (0 to getDeviceCount() - 1). */ + unsigned int nChannels{}; /*!< Number of channels. */ + unsigned int firstChannel{}; /*!< First channel index on device (default = 0). */ }; //! The structure for specifying stream options. @@ -383,14 +374,10 @@ class RTAUDIO_DLL_PUBLIC RtAudio RtAudio with Jack, each instance must have a unique client name. */ struct StreamOptions { - RtAudioStreamFlags flags; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ - unsigned int numberOfBuffers; /*!< Number of stream buffers. */ + RtAudioStreamFlags flags{}; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ + unsigned int numberOfBuffers{}; /*!< Number of stream buffers. */ std::string streamName; /*!< A stream name (currently used only in Jack). */ - int priority; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ - - // Default constructor. - StreamOptions() - : flags(0), numberOfBuffers(0), priority(0) {} + int priority{}; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ }; //! A static function to determine the current RtAudio version. @@ -527,7 +514,7 @@ class RTAUDIO_DLL_PUBLIC RtAudio lowest allowable value is used. The actual value used is returned via the structure argument. The parameter is API dependent. \param errorCallback A client-defined function that will be invoked - when an error has occured. + when an error has occurred. */ void openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, @@ -616,7 +603,7 @@ class RTAUDIO_DLL_PUBLIC RtAudio }; // Operating system dependent thread functionality. -#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) +#if defined(_WIN32) || defined(__CYGWIN__) #ifndef NOMINMAX #define NOMINMAX @@ -628,18 +615,22 @@ class RTAUDIO_DLL_PUBLIC RtAudio typedef uintptr_t ThreadHandle; typedef CRITICAL_SECTION StreamMutex; -#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) +#else + // Using pthread library for various flavors of unix. #include typedef pthread_t ThreadHandle; typedef pthread_mutex_t StreamMutex; -#else // Setup for "dummy" behavior +#endif + +// Setup for "dummy" behavior if no apis specified. +#if !(defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) \ + || defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) \ + || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__)) #define __RTAUDIO_DUMMY__ - typedef int ThreadHandle; - typedef int StreamMutex; #endif @@ -647,19 +638,15 @@ class RTAUDIO_DLL_PUBLIC RtAudio // between the private RtAudio stream structure and global callback // handling functions. struct CallbackInfo { - void *object; // Used as a "this" pointer. - ThreadHandle thread; - void *callback; - void *userData; - void *errorCallback; - void *apiInfo; // void pointer for API specific callback information - bool isRunning; - bool doRealtime; - int priority; - - // Default constructor. - CallbackInfo() - :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false), priority(0) {} + void *object{}; // Used as a "this" pointer. + ThreadHandle thread{}; + void *callback{}; + void *userData{}; + void *errorCallback{}; + void *apiInfo{}; // void pointer for API specific callback information + bool isRunning{false}; + bool doRealtime{false}; + int priority{}; }; // **************************************************************** // @@ -686,9 +673,9 @@ class S24 { S24() {} S24& operator = ( const int& i ) { - c3[0] = (i & 0x000000ff); - c3[1] = (i & 0x0000ff00) >> 8; - c3[2] = (i & 0x00ff0000) >> 16; + c3[0] = (unsigned char)(i & 0x000000ff); + c3[1] = (unsigned char)((i & 0x0000ff00) >> 8); + c3[2] = (unsigned char)((i & 0x00ff0000) >> 16); return *this; } @@ -895,20 +882,20 @@ public: RtApiCore(); ~RtApiCore(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::MACOSX_CORE; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - unsigned int getDefaultOutputDevice( void ); - unsigned int getDefaultInputDevice( void ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::MACOSX_CORE; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + unsigned int getDefaultOutputDevice( void ) override; + unsigned int getDefaultInputDevice( void ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! bool callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ); @@ -918,7 +905,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; static const char* getErrorCode( OSStatus code ); }; @@ -932,18 +919,18 @@ public: RtApiJack(); ~RtApiJack(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::UNIX_JACK; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::UNIX_JACK; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! bool callbackEvent( unsigned long nframes ); private: @@ -951,7 +938,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; bool shouldAutoconnect_; }; @@ -966,18 +953,20 @@ public: RtApiAsio(); ~RtApiAsio(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_ASIO; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_ASIO; } + unsigned int getDeviceCount( void ) override; + unsigned int getDefaultOutputDevice( void ) override; + unsigned int getDefaultInputDevice( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! bool callbackEvent( long bufferIndex ); private: @@ -988,7 +977,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1001,20 +990,20 @@ public: RtApiDs(); ~RtApiDs(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_DS; } - unsigned int getDeviceCount( void ); - unsigned int getDefaultOutputDevice( void ); - unsigned int getDefaultInputDevice( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_DS; } + unsigned int getDeviceCount( void ) override; + unsigned int getDefaultOutputDevice( void ) override; + unsigned int getDefaultInputDevice( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! void callbackEvent( void ); private: @@ -1026,7 +1015,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1041,15 +1030,13 @@ public: RtApiWasapi(); virtual ~RtApiWasapi(); - RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - unsigned int getDefaultOutputDevice( void ); - unsigned int getDefaultInputDevice( void ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_WASAPI; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; private: bool coInitialized_; @@ -1058,7 +1045,7 @@ private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, - RtAudio::StreamOptions* options ); + RtAudio::StreamOptions* options ) override; static DWORD WINAPI runWasapiThread( void* wasapiPtr ); static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); @@ -1076,18 +1063,18 @@ public: RtApiAlsa(); ~RtApiAlsa(); - RtAudio::Api getCurrentApi() { return RtAudio::LINUX_ALSA; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_ALSA; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! void callbackEvent( void ); private: @@ -1097,7 +1084,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1108,28 +1095,27 @@ class RtApiPulse: public RtApi { public: ~RtApiPulse(); - RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_PULSE; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! void callbackEvent( void ); private: - std::vector devices_; - void saveDeviceInfo( void ); + void collectDeviceInfo( void ); bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1142,18 +1128,18 @@ public: RtApiOss(); ~RtApiOss(); - RtAudio::Api getCurrentApi() { return RtAudio::LINUX_OSS; } - unsigned int getDeviceCount( void ); - RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); - void closeStream( void ); - void startStream( void ); - void stopStream( void ); - void abortStream( void ); + RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_OSS; } + unsigned int getDeviceCount( void ) override; + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; + void closeStream( void ) override; + void startStream( void ) override; + void stopStream( void ) override; + void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function - // will most likely produce highly undesireable results! + // will most likely produce highly undesirable results! void callbackEvent( void ); private: @@ -1161,7 +1147,7 @@ public: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ); + RtAudio::StreamOptions *options ) override; }; #endif @@ -1173,20 +1159,20 @@ class RtApiDummy: public RtApi public: RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtAudioError::WARNING ); } - RtAudio::Api getCurrentApi( void ) { return RtAudio::RTAUDIO_DUMMY; } - unsigned int getDeviceCount( void ) { return 0; } - RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; return info; } - void closeStream( void ) {} - void startStream( void ) {} - void stopStream( void ) {} - void abortStream( void ) {} + RtAudio::Api getCurrentApi( void ) override { return RtAudio::RTAUDIO_DUMMY; } + unsigned int getDeviceCount( void ) override { return 0; } + RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) override { RtAudio::DeviceInfo info; return info; } + void closeStream( void ) override {} + void startStream( void ) override {} + void stopStream( void ) override {} + void abortStream( void ) override {} private: bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, - RtAudio::StreamOptions * /*options*/ ) { return false; } + RtAudio::StreamOptions * /*options*/ ) override { return false; } }; #endif