diff --git a/RtAudio.cpp b/RtAudio.cpp index 22338a0b..2f9f2fbc 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -183,6 +183,16 @@ class RtApiJack: public RtApi // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! bool callbackEvent( unsigned long nframes ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal buffer size callback + // handler, which is not a member of RtAudio. + bool bufferSizeCallbackEvent( unsigned long nframes ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal sample rate callback + // handler, which is not a member of RtAudio. + bool sampleRateCallbackEvent( unsigned long sampleRate ); private: void probeDevices( void ) override; @@ -763,6 +773,12 @@ RtAudioErrorType RtApi :: openStream( RtAudio::StreamParameters *oParams, stream_.callbackInfo.callback = (void *) callback; stream_.callbackInfo.userData = userData; + stream_.bufferSizeCallbackInfo.callback = (void *) options->bufferSizeCallback; + stream_.bufferSizeCallbackInfo.userData = options->bufferSizeCallbackUserData; + + stream_.sampleRateCallbackInfo.callback = (void *) options->sampleRateCallback; + stream_.sampleRateCallbackInfo.userData = options->sampleRateCallbackUserData; + if ( options ) options->numberOfBuffers = stream_.nBuffers; stream_.state = STREAM_STOPPED; return RTAUDIO_NO_ERROR; @@ -2736,6 +2752,24 @@ static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) return 0; } +static int jackBufferSizeCallbackHandler( jack_nframes_t nframes, void *infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + + RtApiJack *object = (RtApiJack *) info->object; + if ( object->bufferSizeCallbackEvent( (unsigned long) nframes ) == false ) return 1; + return 0; +} + +static int jackSampleRateCallbackHandler( jack_nframes_t nframes, void *infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + + RtApiJack *object = (RtApiJack *) info->object; + if ( object->sampleRateCallbackEvent( (unsigned long) nframes ) == false ) return 1; + return 0; +} + // This function will be called by a spawned thread when the Jack // server signals that it is shutting down. It is necessary to handle // it this way because the jackShutdown() function must return before @@ -2885,9 +2919,15 @@ bool RtApiJack :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsig // Get the buffer size. The buffer size and number of buffers // (periods) is set when the jack server is started. - stream_.bufferSize = (int) jack_get_buffer_size( client ); - *bufferSize = stream_.bufferSize; - + // If the requested buffer_size for the stream is different, + // force the jack or pipewire-jack server to use the requested + // buffersize for all clients. + if ( *bufferSize != (unsigned int) jack_get_buffer_size( client ) ) { + std::cout << "RtApiJack::probeDeviceOpen: JACK server buffer size is different than the requested buffer size - forcing requested buffer size for all clients." << std::endl; + jack_set_buffer_size( client, (jack_nframes_t) *bufferSize ); + } + + stream_.bufferSize = *bufferSize; stream_.nDeviceChannels[mode] = channels; stream_.nUserChannels[mode] = channels; @@ -2968,6 +3008,8 @@ bool RtApiJack :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsig else { stream_.mode = mode; jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); + jack_set_buffer_size_callback( handle->client, jackBufferSizeCallbackHandler, (void *) &stream_.callbackInfo); + jack_set_sample_rate_callback( handle->client, jackSampleRateCallbackHandler, (void *) &stream_.callbackInfo); jack_set_xrun_callback( handle->client, jackXrun, (void *) &stream_.apiHandle ); jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); //jack_set_client_registration_callback( handle->client, jackClientChange, (void *) &stream_.callbackInfo ); @@ -3323,6 +3365,141 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) RtApi::tickStreamTime(); return SUCCESS; } + +bool RtApiJack :: bufferSizeCallbackEvent ( unsigned long nframes ) +{ + if ( stream_.bufferSize == (unsigned int) nframes ) return true; + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + BufferSizeCallbackInfo *bufferSizeCallbackInfo = (BufferSizeCallbackInfo *) &stream_.bufferSizeCallbackInfo; + stream_.bufferSize = (unsigned int) nframes; + + std::vector modes = {}; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) modes.push_back(0); + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) modes.push_back(1); + + for ( size_t i = 0; i < modes.size(); i++ ) { + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[modes[i]] * nframes * formatBytes( stream_.userFormat ); + stream_.userBuffer[modes[i]] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[modes[i]] == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[modes[i]] ) { + + bool makeBuffer = true; + if ( modes[i] == OUTPUT ) + bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + else { // modes[i] == INPUT + bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); + if ( modes.size() == 2 && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); + if ( bufferBytes < bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= nframes; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + } + + int cbReturnValue; + if ( bufferSizeCallbackInfo->callback ) { + RtAudioBufferSizeCallback callback = (RtAudioBufferSizeCallback) bufferSizeCallbackInfo->callback; + if ( callback ) { + cbReturnValue = callback( &stream_.bufferSize, bufferSizeCallbackInfo->userData ); + } + } else { + cbReturnValue = 0; + } + + + if ( stream_.state == STREAM_RUNNING && cbReturnValue != 0 ) { + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + ThreadHandle id; + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + pthread_create( &id, NULL, jackStopStream, info ); + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->condition ); + jack_client_close( handle->client ); + + if ( handle->ports[0] ) free( handle->ports[0] ); + if ( handle->ports[1] ) free( handle->ports[1] ); + + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +bool RtApiJack :: sampleRateCallbackEvent ( unsigned long sampleRate ) +{ + if ( stream_.sampleRate == (unsigned int) sampleRate ) return SUCCESS; + + stream_.sampleRate = (unsigned int) sampleRate; + SampleRateCallbackInfo *sampleRateCallbackInfo = (SampleRateCallbackInfo *) &stream_.sampleRateCallbackInfo; + + int cbReturnValue = 0; + if ( sampleRateCallbackInfo->callback ) { + RtAudioSampleRateCallback callback = (RtAudioSampleRateCallback) sampleRateCallbackInfo->callback; + if ( callback ) { + cbReturnValue = callback( &stream_.sampleRate, sampleRateCallbackInfo->userData ); + } + } + + if ( stream_.state == STREAM_RUNNING && cbReturnValue != 0 ) { + JackHandle *handle = (JackHandle *) stream_.apiHandle; + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + ThreadHandle id; + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + pthread_create( &id, NULL, jackStopStream, info ); + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + return SUCCESS; +} + //******************** End of __UNIX_JACK__ *********************// #endif @@ -10954,6 +11131,10 @@ void RtApi :: clearStreamInfo() stream_.callbackInfo.userData = 0; stream_.callbackInfo.isRunning = false; stream_.callbackInfo.deviceDisconnected = false; + stream_.bufferSizeCallbackInfo.callback = nullptr; + stream_.bufferSizeCallbackInfo.userData = nullptr; + stream_.sampleRateCallbackInfo.callback = nullptr; + stream_.sampleRateCallbackInfo.userData = nullptr; for ( int i=0; i<2; i++ ) { stream_.deviceId[i] = 11111; stream_.doConvertBuffer[i] = false; diff --git a/RtAudio.h b/RtAudio.h index 17fb5bdc..c2579fe6 100644 --- a/RtAudio.h +++ b/RtAudio.h @@ -225,6 +225,49 @@ typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, RtAudioStreamStatus status, void *userData ); +//! RtAudio buffer size callback function prototype. +/*! + This function type is used to specify a callback function that + will be invoked when the system buffer size changes. This only + occurs when a stream is utilizing a native API that allows + dynamic buffer resizing (i.e., ASIO, JACK). At the moment, this + is only implemented for the JACK API. + + \param bufferSize The current system buffer size in sample frames. + + \param userData A pointer to optional data provided by the client + when opening the stream (default = NULL). This data is set + by the user in the RtAudio::StreamOptions structure when + calling the RtAudio::openStream() function. + + \return 0 to continue normal stream operation, 1 to stop the stream + and drain the output buffer, or 2 to abort the stream + immediately. + */ +typedef int (*RtAudioBufferSizeCallback)( unsigned int *bufferSize, void *userData ); + +//! RtAudio sample rate callback function prototype. +/*! + This function type is used to specify a callback function that + will be invoked when the system sample rate changes. This only + occurs when a stream is utilizing a native API that allows + dynamic sample rate changes (i.e., ASIO, JACK). At the moment, this + is only implemented for the JACK API. + + \param sampleRate The current system sample rate in sample frames + per second. + + \param userData A pointer to optional data provided by the client + when opening the stream (default = NULL). This data is set + by the user in the RtAudio::StreamOptions structure when + calling the RtAudio::openStream() function. + + \return 0 to continue normal stream operation, 1 to stop the stream + and drain the output buffer, or 2 to abort the stream + immediately. + */ +typedef int (*RtAudioSampleRateCallback)( unsigned int *sampleRate, void *userData ); + enum RtAudioErrorType { RTAUDIO_NO_ERROR = 0, /*!< No error. */ RTAUDIO_WARNING, /*!< A non-critical error. */ @@ -364,12 +407,48 @@ class RTAUDIO_DLL_PUBLIC RtAudio However, if you wish to create multiple instances of RtAudio with Jack, each instance must have a unique client name. The default Pulse application name is set to "RtAudio." + + The \c bufferSizeCallback parameter can be used to set a function + that will be called when the system buffer size changes. This only + occurs when a stream is utilizing a native API that allows dynamic + buffer resizing (i.e., ASIO, JACK). At the moment, this is only + implemented for the JACK API. The function should have the + following signature: + \c int (*callback)(unsigned int *bufferSize, void *userData). + The \c bufferSize parameter will be set to the current system buffer + size in sample frames. If the stream is open, the return value will + control the behavior of the stream. The function should return 0 to + continue normal stream operation, 1 to stop the stream and drain the + output buffer, or 2 to abort the stream immediately. The \c userData + parameter is an optional pointer to data that can be passed with the + \c bufferSizeCallbackUserData parameter, which will be passed to the + callback function. + + The \c sampleRateCallback parameter can be used to set a function + that will be called when the system sample rate changes. This only + occurs when a stream is utilizing a native API that allows dynamic + sample rate changes (i.e., ASIO, JACK). At the moment, this is only + implemented for the JACK API. The function should have the + following signature: + \c int (*callback)(unsigned int *sampleRate, void *userData). + The \c sampleRate parameter will be set to the current system sample + rate in sample frames per second. If the stream is open, the return + value will control the behavior of the stream. The function should + return 0 to continue normal stream operation, 1 to stop the stream + and drain the output buffer, or 2 to abort the stream immediately. + The \c userData parameter is an optional pointer to data that can be + passed with the \c sampleRateCallbackUserData parameter, which will + be passed to the callback function. */ 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. */ 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). */ + RtAudioBufferSizeCallback bufferSizeCallback{nullptr}; /*!< Callback function to handle buffer size changes. */ + void *bufferSizeCallbackUserData; /*!< User data for buffer size callback. */ + RtAudioSampleRateCallback sampleRateCallback{nullptr}; /*!< Callback function to handle sample rate changes. */ + void *sampleRateCallbackUserData; /*!< User data for sample rate callback. */ }; //! A static function to determine the current RtAudio version. @@ -693,6 +772,22 @@ struct CallbackInfo { bool deviceDisconnected{false}; }; +// This global structure type is used to pass the buffer size callback +// information between the private RtAudio stream structure and the +// global buffer size callback handling function. +struct BufferSizeCallbackInfo { + void *callback{nullptr}; + void *userData{}; +}; + +// This global structure type is used to pass the sample rate callback +// information between the private RtAudio stream structure and the +// global sample rate callback handling function. +struct SampleRateCallbackInfo { + void *callback{nullptr}; + void *userData{}; +}; + // **************************************************************** // // // RtApi class declaration. @@ -828,6 +923,8 @@ class RTAUDIO_DLL_PUBLIC RtApi RtAudioFormat deviceFormat[2]; // Playback and record, respectively. StreamMutex mutex; CallbackInfo callbackInfo; + BufferSizeCallbackInfo bufferSizeCallbackInfo; + SampleRateCallbackInfo sampleRateCallbackInfo; ConvertInfo convertInfo[2]; double streamTime; // Number of elapsed seconds since the stream started.