Skip to content

New Feature: Buffer size and sample rate callback functions #454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 184 additions & 3 deletions RtAudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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<int> 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

Expand Down Expand Up @@ -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;
Expand Down
97 changes: 97 additions & 0 deletions RtAudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down
Loading