From 8515829d8633bfdf23b1cbd6e32bcd6391a89e5b Mon Sep 17 00:00:00 2001 From: Fares Schulz Date: Thu, 20 Feb 2025 16:53:32 +0100 Subject: [PATCH 1/3] First draft for support of buffer size/sample rate change callback functions. --- RtAudio.cpp | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++ RtAudio.h | 60 ++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/RtAudio.cpp b/RtAudio.cpp index 22338a0b..d6945e81 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 @@ -2968,6 +3002,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 +3359,90 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) RtApi::tickStreamTime(); return SUCCESS; } + +bool RtApiJack :: bufferSizeCallbackEvent ( unsigned long nframes ) +{ + if ( stream_.bufferSize == (unsigned int) nframes ) return true; + + bool isRunning = stream_.state == STREAM_RUNNING; + if ( isRunning ) { + jack_client_t *client = ((JackHandle *) stream_.apiHandle)->client; + std::cout << "bufferSizeCallbackEvent: stopping stream." << std::endl; + jack_deactivate( client ); + std::cout << "Client deactivated" << std::endl; + stream_.state = STREAM_STOPPED; + } + // Set the new buffer size + stream_.bufferSize = (unsigned int) nframes; + + // TODO - Adapt for all modes + int mode = stream_.mode; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); + + if ( stream_.userBuffer[mode] ) free( stream_.userBuffer[mode] ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + if ( mode == OUTPUT ) + bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + else { // mode == INPUT + bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); + if ( bufferBytes < bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= stream_.bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; + } + } + } + + BufferSizeCallbackInfo *info = (BufferSizeCallbackInfo *) &stream_.bufferSizeCallbackInfo; + + if ( info->callback ) { + RtAudioBufferSizeCallback callback = (RtAudioBufferSizeCallback) info->callback; + if ( callback ) { + callback( &stream_.bufferSize, info->userData ); + } + } + + if ( isRunning ) { + startStream(); + } + + return true; +} + +bool RtApiJack :: sampleRateCallbackEvent ( unsigned long sampleRate ) +{ + if ( stream_.sampleRate == (unsigned int) sampleRate ) return true; + else { + stream_.sampleRate = (unsigned int) sampleRate; + SampleRateCallbackInfo *info = (SampleRateCallbackInfo *) &stream_.sampleRateCallbackInfo; + if ( info->callback ) { + RtAudioSampleRateCallback callback = (RtAudioSampleRateCallback) info->callback; + if ( callback ) { + callback( &stream_.sampleRate, info->userData ); + } + } + } + return true; +} + //******************** End of __UNIX_JACK__ *********************// #endif diff --git a/RtAudio.h b/RtAudio.h index 17fb5bdc..3bdbc14f 100644 --- a/RtAudio.h +++ b/RtAudio.h @@ -225,6 +225,44 @@ 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 (for now only JACK/Pipewire-JACK). + + \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 (for now only JACK/Pipewire-JACK). + + \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. */ @@ -370,6 +408,10 @@ class RTAUDIO_DLL_PUBLIC RtAudio 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 +735,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 +886,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. From 6c420ba0a42ec6d2f0c0176459629e397ece47ba Mon Sep 17 00:00:00 2001 From: Fares Schulz Date: Thu, 20 Feb 2025 16:56:08 +0100 Subject: [PATCH 2/3] forcing the requested from the stream buffer size to jack server --- RtAudio.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/RtAudio.cpp b/RtAudio.cpp index d6945e81..8b96be07 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -2919,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; From 0b17472748deeb62dbb69c6af71416a48166588f Mon Sep 17 00:00:00 2001 From: Fares Schulz Date: Thu, 20 Feb 2025 23:25:30 +0100 Subject: [PATCH 3/3] Support for buffer size/sample rate callback fully implemented for jack api --- RtAudio.cpp | 163 +++++++++++++++++++++++++++++++++++----------------- RtAudio.h | 43 +++++++++++++- 2 files changed, 149 insertions(+), 57 deletions(-) diff --git a/RtAudio.cpp b/RtAudio.cpp index 8b96be07..2f9f2fbc 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -3370,83 +3370,134 @@ bool RtApiJack :: bufferSizeCallbackEvent ( unsigned long nframes ) { if ( stream_.bufferSize == (unsigned int) nframes ) return true; - bool isRunning = stream_.state == STREAM_RUNNING; - if ( isRunning ) { - jack_client_t *client = ((JackHandle *) stream_.apiHandle)->client; - std::cout << "bufferSizeCallbackEvent: stopping stream." << std::endl; - jack_deactivate( client ); - std::cout << "Client deactivated" << std::endl; - stream_.state = STREAM_STOPPED; - } - // Set the new buffer size + JackHandle *handle = (JackHandle *) stream_.apiHandle; + BufferSizeCallbackInfo *bufferSizeCallbackInfo = (BufferSizeCallbackInfo *) &stream_.bufferSizeCallbackInfo; stream_.bufferSize = (unsigned int) nframes; - // TODO - Adapt for all modes - int mode = stream_.mode; + 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); - // Allocate necessary internal buffers. - unsigned long bufferBytes; - bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); + 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_.userBuffer[mode] ) free( stream_.userBuffer[mode] ); - stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); - if ( stream_.userBuffer[mode] == NULL ) { - errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; - } + if ( stream_.doConvertBuffer[modes[i]] ) { - if ( stream_.doConvertBuffer[mode] ) { - - bool makeBuffer = true; - if ( mode == OUTPUT ) - bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); - else { // mode == INPUT - bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); - if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { - unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); - if ( bufferBytes < bytesOut ) makeBuffer = false; + 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; + } } } + } - if ( makeBuffer ) { - bufferBytes *= stream_.bufferSize; - if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); - stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); - if ( stream_.deviceBuffer == NULL ) { - errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; - } + int cbReturnValue; + if ( bufferSizeCallbackInfo->callback ) { + RtAudioBufferSizeCallback callback = (RtAudioBufferSizeCallback) bufferSizeCallbackInfo->callback; + if ( callback ) { + cbReturnValue = callback( &stream_.bufferSize, bufferSizeCallbackInfo->userData ); } + } else { + cbReturnValue = 0; } + - BufferSizeCallbackInfo *info = (BufferSizeCallbackInfo *) &stream_.bufferSizeCallbackInfo; + 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; + } + } - if ( info->callback ) { - RtAudioBufferSizeCallback callback = (RtAudioBufferSizeCallback) info->callback; - if ( callback ) { - callback( &stream_.bufferSize, info->userData ); + 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 ( isRunning ) { - startStream(); + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; } - return true; + return FAILURE; } bool RtApiJack :: sampleRateCallbackEvent ( unsigned long sampleRate ) { - if ( stream_.sampleRate == (unsigned int) sampleRate ) return true; - else { - stream_.sampleRate = (unsigned int) sampleRate; - SampleRateCallbackInfo *info = (SampleRateCallbackInfo *) &stream_.sampleRateCallbackInfo; - if ( info->callback ) { - RtAudioSampleRateCallback callback = (RtAudioSampleRateCallback) info->callback; - if ( callback ) { - callback( &stream_.sampleRate, info->userData ); - } + 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 ); } } - return true; + + 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__ *********************// @@ -11080,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 3bdbc14f..c2579fe6 100644 --- a/RtAudio.h +++ b/RtAudio.h @@ -230,7 +230,8 @@ typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, 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 (for now only JACK/Pipewire-JACK). + 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. @@ -238,6 +239,7 @@ typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, 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. @@ -249,14 +251,17 @@ typedef int (*RtAudioBufferSizeCallback)( unsigned int *bufferSize, void *userDa 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 (for now only JACK/Pipewire-JACK). + 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 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. @@ -402,6 +407,38 @@ 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). */