mystuff/audio/chuck/patches/patch-src_host_RtAudio_RtAudio_cpp.orig
2026-03-15 22:48:20 +01:00

628 lines
22 KiB
Plaintext

Index: src/host/RtAudio/RtAudio.cpp
--- src/host/RtAudio/RtAudio.cpp.orig
+++ src/host/RtAudio/RtAudio.cpp
@@ -109,6 +109,7 @@ const char* rtaudio_api_names[][2] = {
{ "alsa" , "ALSA" },
{ "pulse" , "Pulse" },
{ "oss" , "OpenSoundSystem" },
+ { "sndio" , "sndio" },
{ "jack" , "Jack" },
{ "core" , "CoreAudio" },
{ "wasapi" , "WASAPI" },
@@ -135,6 +136,9 @@ extern "C" const RtAudio::Api rtaudio_compiled_apis[]
#if defined(__LINUX_OSS__)
RtAudio::LINUX_OSS,
#endif
+#if defined(__OPENBSD_SNDIO__)
+ RtAudio::OPENBSD_SNDIO,
+#endif
#if defined(__WINDOWS_ASIO__)
RtAudio::WINDOWS_ASIO,
#endif
@@ -216,6 +220,10 @@ void RtAudio :: openRtApi( RtAudio::Api api )
if ( api == LINUX_OSS )
rtapi_ = new RtApiOss();
#endif
+#if defined(__OPENBSD_SNDIO__)
+ if ( api == OPENBSD_SNDIO )
+ rtapi_ = new RtApiSndio();
+#endif
#if defined(__WINDOWS_ASIO__)
if ( api == WINDOWS_ASIO )
rtapi_ = new RtApiAsio();
@@ -10387,6 +10395,594 @@ static void *ossCallbackHandler( void *ptr )
//******************** End of __LINUX_OSS__ *********************//
#endif
+#if defined(__OPENBSD_SNDIO__)
+
+#include <sndio.h>
+#include <poll.h>
+#include <pthread.h>
+#include <cerrno>
+
+static void *sndioCallbackHandler( void *ptr );
+
+struct SndioHandle {
+ struct sio_hdl *handle;
+ bool xrun[2]; // [0]=output underflow, [1]=input overflow
+
+ SndioHandle() : handle(nullptr) { xrun[0] = false; xrun[1] = false; }
+};
+
+static void sndioXrunCb( void *arg )
+{
+ SndioHandle *h = (SndioHandle *) arg;
+ h->xrun[0] = true;
+ h->xrun[1] = true;
+}
+
+RtApiSndio :: RtApiSndio() {}
+
+RtApiSndio :: ~RtApiSndio()
+{
+ if ( stream_.state != STREAM_CLOSED ) closeStream();
+}
+
+unsigned int RtApiSndio :: getDeviceCount( void )
+{
+ return 1;
+}
+
+RtAudio::DeviceInfo RtApiSndio :: getDeviceInfo( unsigned int device )
+{
+ RtAudio::DeviceInfo info;
+ info.probed = false;
+
+ if ( device != 0 ) {
+ errorText_ = "RtApiSndio::getDeviceInfo: invalid device ID!";
+ error( RTAUDIO_INVALID_DEVICE );
+ return info;
+ }
+
+ // Advertise default capabilities; actual negotiation happens in probeDeviceOpen.
+ // We do not require sndiod to be running at probe time.
+ info.name = "sndio default";
+ info.outputChannels = 2;
+ info.inputChannels = 2;
+ info.duplexChannels = 2;
+ info.isDefaultOutput = true;
+ info.isDefaultInput = true;
+
+ // sndio accepts most standard sample rates
+ static const unsigned int rates[] = {
+ 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 0
+ };
+ for ( int i = 0; rates[i]; i++ ) {
+ info.sampleRates.push_back( rates[i] );
+ if ( rates[i] == 48000 )
+ info.preferredSampleRate = 48000;
+ }
+
+ info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32;
+ info.probed = true;
+ return info;
+}
+
+bool RtApiSndio :: probeDeviceOpen( unsigned int device, StreamMode mode,
+ unsigned int channels,
+ unsigned int firstChannel,
+ unsigned int sampleRate,
+ RtAudioFormat format,
+ unsigned int *bufferSize,
+ RtAudio::StreamOptions *options )
+{
+ if ( device != 0 ) {
+ errorText_ = "RtApiSndio::probeDeviceOpen: only device 0 is supported.";
+ return FAILURE;
+ }
+ if ( firstChannel != 0 ) {
+ errorText_ = "RtApiSndio::probeDeviceOpen: channel offset not supported.";
+ return FAILURE;
+ }
+
+ SndioHandle *handle = (SndioHandle *) stream_.apiHandle;
+
+ // When adding INPUT to an already-open OUTPUT stream (going duplex), reuse the
+ // existing play-only sndio handle instead of closing and reopening as
+ // SIO_PLAY|SIO_REC. sndiod's default sub-device ("-m play -s default") is
+ // play-only: a duplex open succeeds but the record clock never ticks, so
+ // sio_write stalls after the initial pre-fill. Keeping the play-only handle
+ // lets the poll-with-timeout path in callbackEvent() substitute silence for
+ // input without blocking. True duplex recording requires a sndiod sub-device
+ // configured with "-m play,rec" or "-m rec".
+ if ( mode == INPUT && stream_.mode == OUTPUT && handle && handle->handle ) {
+ // Set up INPUT bookkeeping using the same format/params as OUTPUT.
+ stream_.deviceFormat[INPUT] = stream_.deviceFormat[OUTPUT];
+ stream_.nUserChannels[INPUT] = channels;
+ stream_.nDeviceChannels[INPUT] = channels;
+ stream_.deviceInterleaved[INPUT] = true;
+
+ stream_.doConvertBuffer[INPUT] = false;
+ stream_.doByteSwap[INPUT] = false;
+ if ( stream_.userFormat != stream_.deviceFormat[INPUT] )
+ stream_.doConvertBuffer[INPUT] = true;
+ if ( stream_.nUserChannels[INPUT] < stream_.nDeviceChannels[INPUT] )
+ stream_.doConvertBuffer[INPUT] = true;
+ if ( stream_.userInterleaved != stream_.deviceInterleaved[INPUT] &&
+ stream_.nUserChannels[INPUT] > 1 )
+ stream_.doConvertBuffer[INPUT] = true;
+
+ unsigned long bufBytes = stream_.nUserChannels[INPUT] * stream_.bufferSize *
+ formatBytes( stream_.userFormat );
+ stream_.userBuffer[INPUT] = (char *) calloc( bufBytes, 1 );
+ if ( !stream_.userBuffer[INPUT] ) {
+ errorText_ = "RtApiSndio::probeDeviceOpen: error allocating input buffer.";
+ return FAILURE;
+ }
+
+ if ( stream_.doConvertBuffer[INPUT] ) {
+ unsigned long devBytes = stream_.nDeviceChannels[INPUT] *
+ formatBytes( stream_.deviceFormat[INPUT] ) *
+ stream_.bufferSize;
+ if ( !stream_.deviceBuffer || devBytes >
+ stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] )
+ * stream_.bufferSize ) {
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );
+ stream_.deviceBuffer = (char *) calloc( devBytes, 1 );
+ if ( !stream_.deviceBuffer ) {
+ free( stream_.userBuffer[INPUT] );
+ stream_.userBuffer[INPUT] = nullptr;
+ errorText_ = "RtApiSndio::probeDeviceOpen: error allocating device buffer.";
+ return FAILURE;
+ }
+ }
+ setConvertInfo( INPUT, firstChannel );
+ }
+
+ stream_.device[INPUT] = device;
+ stream_.mode = DUPLEX;
+ return SUCCESS;
+ }
+
+ // Normal path: open a new sndio device handle.
+ unsigned int sioMode = ( mode == OUTPUT ) ? SIO_PLAY : SIO_REC; struct sio_hdl *hdl = sio_open( SIO_DEVANY, sioMode, 0 ); if ( !hdl ) {
+ errorText_ = "RtApiSndio::probeDeviceOpen: error opening sndio device.";
+ goto error;
+ }
+
+ // Map RtAudio format to sndio encoding
+ stream_.userFormat = format;
+ {
+ unsigned int bits, bps;
+
+ if ( format == RTAUDIO_SINT8 ) {
+ bits = 8; bps = 1;
+ stream_.deviceFormat[mode] = RTAUDIO_SINT8;
+ } else if ( format == RTAUDIO_SINT16 ) {
+ bits = 16; bps = 2;
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;
+ } else if ( format == RTAUDIO_SINT24 ) {
+ bits = 24; bps = 4;
+ stream_.deviceFormat[mode] = RTAUDIO_SINT24;
+ } else if ( format == RTAUDIO_SINT32 ) {
+ bits = 32; bps = 4;
+ stream_.deviceFormat[mode] = RTAUDIO_SINT32;
+ } else {
+ // Float formats not natively supported: use SINT16, RtAudio converts
+ bits = 16; bps = 2;
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;
+ }
+
+ struct sio_par par;
+ sio_initpar( &par );
+ par.bits = bits;
+ par.bps = bps;
+ par.sig = 1;
+ par.le = SIO_LE_NATIVE;
+ par.rate = sampleRate;
+ par.round = *bufferSize;
+ par.appbufsz = *bufferSize * 4;
+ par.pchan = ( mode == OUTPUT ) ? channels : 0;
+ par.rchan = ( mode == INPUT ) ? channels : 0; if ( !sio_setpar( hdl, &par ) ) {
+ sio_close( hdl );
+ errorText_ = "RtApiSndio::probeDeviceOpen: error setting stream parameters.";
+ return FAILURE;
+ } if ( !sio_getpar( hdl, &par ) ) {
+ sio_close( hdl );
+ errorText_ = "RtApiSndio::probeDeviceOpen: error getting stream parameters.";
+ return FAILURE;
+ }
+
+ if ( par.rate != sampleRate ) {
+ sio_close( hdl );
+ errorStream_ << "RtApiSndio::probeDeviceOpen: sample rate " << sampleRate
+ << " not supported (got " << par.rate << ").";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+ *bufferSize = par.round;
+ stream_.bufferSize = *bufferSize;
+ stream_.nBuffers = par.appbufsz / par.round;
+ stream_.sampleRate = sampleRate;
+ } // end format/par scope
+
+ stream_.nUserChannels[mode] = channels;
+ stream_.nDeviceChannels[mode] = channels;
+
+ stream_.userInterleaved = true;
+ stream_.deviceInterleaved[mode] = true;
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED )
+ stream_.userInterleaved = false;
+
+ stream_.doConvertBuffer[mode] = false;
+ stream_.doByteSwap[mode] = false;
+ if ( stream_.userFormat != stream_.deviceFormat[mode] )
+ stream_.doConvertBuffer[mode] = true;
+ if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] )
+ stream_.doConvertBuffer[mode] = true;
+ if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] &&
+ stream_.nUserChannels[mode] > 1 )
+ stream_.doConvertBuffer[mode] = true;
+
+ // Allocate or reuse SndioHandle
+ if ( !handle ) {
+ try {
+ handle = new SndioHandle;
+ }
+ catch ( std::bad_alloc& ) {
+ sio_close( hdl );
+ errorText_ = "RtApiSndio::probeDeviceOpen: error allocating SndioHandle.";
+ goto error;
+ }
+ stream_.apiHandle = (void *) handle;
+ }
+ handle->handle = hdl;
+ sio_onxrun( hdl, sndioXrunCb, handle );
+
+ // Allocate user buffer
+ {
+ unsigned long bufBytes = stream_.nUserChannels[mode] * *bufferSize *
+ formatBytes( stream_.userFormat );
+ stream_.userBuffer[mode] = (char *) calloc( bufBytes, 1 );
+ if ( !stream_.userBuffer[mode] ) {
+ errorText_ = "RtApiSndio::probeDeviceOpen: error allocating user buffer.";
+ goto error;
+ }
+ }
+
+ if ( stream_.doConvertBuffer[mode] ) {
+ bool makeBuffer = true;
+ unsigned long bufBytes = stream_.nDeviceChannels[mode] *
+ formatBytes( stream_.deviceFormat[mode] );
+ if ( mode == INPUT ) {
+ if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) {
+ unsigned long bytesOut = stream_.nDeviceChannels[0] *
+ formatBytes( stream_.deviceFormat[0] );
+ if ( bufBytes <= bytesOut ) makeBuffer = false;
+ }
+ }
+ if ( makeBuffer ) {
+ bufBytes *= *bufferSize;
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );
+ stream_.deviceBuffer = (char *) calloc( bufBytes, 1 );
+ if ( !stream_.deviceBuffer ) {
+ errorText_ = "RtApiSndio::probeDeviceOpen: error allocating device buffer.";
+ goto error;
+ }
+ }
+ }
+
+ stream_.device[mode] = device;
+ stream_.state = STREAM_STOPPED;
+
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel );
+
+ // Setup mode and callback thread
+ if ( stream_.mode == OUTPUT && mode == INPUT ) {
+ stream_.mode = DUPLEX;
+ } else {
+ stream_.mode = mode;
+
+ stream_.callbackInfo.object = (void *) this;
+
+ pthread_attr_t attr;
+ pthread_attr_init( &attr );
+ pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );
+
+ stream_.callbackInfo.isRunning = true;
+ int result = pthread_create( &stream_.callbackInfo.thread, &attr,
+ sndioCallbackHandler, &stream_.callbackInfo );
+ pthread_attr_destroy( &attr );
+ if ( result ) {
+ stream_.callbackInfo.isRunning = false;
+ errorText_ = "RtApiSndio::probeDeviceOpen: error creating callback thread!";
+ goto error;
+ }
+ }
+
+ return SUCCESS;
+
+ error:
+ // If a callback thread is already running (e.g., from a prior OUTPUT pass
+ // that succeeded before this INPUT/DUPLEX pass failed), stop it now so the
+ // thread doesn't access the handle after we free it below.
+ if ( stream_.callbackInfo.isRunning ) {
+ stream_.callbackInfo.isRunning = false;
+ // Thread spins in 5ms nanosleep when state != RUNNING; it will exit promptly.
+ pthread_join( stream_.callbackInfo.thread, nullptr );
+ }
+ if ( handle ) {
+ if ( handle->handle ) sio_close( handle->handle );
+ delete handle;
+ stream_.apiHandle = nullptr;
+ }
+ for ( int i = 0; i < 2; i++ ) {
+ if ( stream_.userBuffer[i] ) {
+ free( stream_.userBuffer[i] );
+ stream_.userBuffer[i] = nullptr;
+ }
+ }
+ if ( stream_.deviceBuffer ) {
+ free( stream_.deviceBuffer );
+ stream_.deviceBuffer = nullptr;
+ }
+ stream_.state = STREAM_CLOSED;
+ return FAILURE;
+}
+
+void RtApiSndio :: closeStream()
+{
+ if ( stream_.state == STREAM_CLOSED ) {
+ errorText_ = "RtApiSndio::closeStream(): no open stream to close!";
+ error( RTAUDIO_WARNING );
+ return;
+ }
+
+ SndioHandle *handle = (SndioHandle *) stream_.apiHandle;
+ stream_.callbackInfo.isRunning = false;
+
+ MUTEX_LOCK( &stream_.mutex );
+ stream_.state = STREAM_CLOSED;
+ MUTEX_UNLOCK( &stream_.mutex );
+
+ // Join FIRST (thread may be in sio_write with poll timeout ~23ms).
+ // Then call sio_stop/sio_close with no concurrent sio I/O. pthread_join( stream_.callbackInfo.thread, nullptr );
+ if ( handle ) {
+ if ( handle->handle ) { sio_stop( handle->handle ); sio_close( handle->handle ); }
+ delete handle;
+ stream_.apiHandle = nullptr;
+ }
+
+ for ( int i = 0; i < 2; i++ ) {
+ if ( stream_.userBuffer[i] ) {
+ free( stream_.userBuffer[i] );
+ stream_.userBuffer[i] = nullptr;
+ }
+ }
+ if ( stream_.deviceBuffer ) {
+ free( stream_.deviceBuffer );
+ stream_.deviceBuffer = nullptr;
+ }
+
+ clearStreamInfo();
+}
+
+RtAudioErrorType RtApiSndio :: startStream()
+{
+ if ( stream_.state != STREAM_STOPPED ) {
+ if ( stream_.state == STREAM_RUNNING )
+ errorText_ = "RtApiSndio::startStream(): stream already running!";
+ else
+ errorText_ = "RtApiSndio::startStream(): stream is stopping or closed!";
+ return error( RTAUDIO_WARNING );
+ }
+
+ SndioHandle *handle = (SndioHandle *) stream_.apiHandle;
+
+ MUTEX_LOCK( &stream_.mutex ); if ( !sio_start( handle->handle ) ) {
+ MUTEX_UNLOCK( &stream_.mutex );
+ errorText_ = "RtApiSndio::startStream(): error starting sndio stream.";
+ return error( RTAUDIO_SYSTEM_ERROR );
+ } stream_.state = STREAM_RUNNING;
+ MUTEX_UNLOCK( &stream_.mutex );
+ return RTAUDIO_NO_ERROR;
+}
+
+RtAudioErrorType RtApiSndio :: stopStream()
+{
+ if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) {
+ if ( stream_.state == STREAM_STOPPED )
+ errorText_ = "RtApiSndio::stopStream(): stream already stopped!";
+ else
+ errorText_ = "RtApiSndio::stopStream(): stream is closed!";
+ return error( RTAUDIO_WARNING );
+ }
+
+ MUTEX_LOCK( &stream_.mutex );
+
+ if ( stream_.state == STREAM_STOPPED ) {
+ MUTEX_UNLOCK( &stream_.mutex );
+ return RTAUDIO_NO_ERROR;
+ }
+
+ SndioHandle *handle = (SndioHandle *) stream_.apiHandle;
+
+ // Do NOT call sio_stop() here: stopStream() may be called from any thread,
+ // including while the callback thread holds the sio handle in sio_write().
+ // sio is not thread-safe; concurrent sio_stop()+sio_write() causes hangs.
+ // Instead, just set state=STOPPED; closeStream() calls sio_stop() after
+ // pthread_join() ensures no concurrent sio I/O.
+ stream_.state = STREAM_STOPPED;
+ MUTEX_UNLOCK( &stream_.mutex );
+ return RTAUDIO_NO_ERROR;
+}
+
+RtAudioErrorType RtApiSndio :: abortStream()
+{
+ if ( stream_.state != STREAM_RUNNING ) {
+ if ( stream_.state == STREAM_STOPPED )
+ errorText_ = "RtApiSndio::abortStream(): stream already stopped!";
+ else
+ errorText_ = "RtApiSndio::abortStream(): stream is stopping or closed!";
+ return error( RTAUDIO_WARNING );
+ }
+
+ MUTEX_LOCK( &stream_.mutex );
+ // Same thread-safety rule as stopStream(): no sio_stop() here.
+ // closeStream() will call sio_stop() after pthread_join().
+ stream_.state = STREAM_STOPPED;
+ MUTEX_UNLOCK( &stream_.mutex );
+ return RTAUDIO_NO_ERROR;
+}
+
+void RtApiSndio :: callbackEvent()
+{
+ SndioHandle *handle = (SndioHandle *) stream_.apiHandle;
+
+ if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_CLOSED ) {
+ // Spin-wait briefly when stopped to avoid busy-looping
+ struct timespec ts = { 0, 5000000 }; // 5 ms
+ nanosleep( &ts, nullptr );
+ return;
+ }
+
+ if ( stream_.state != STREAM_RUNNING ) return;
+
+ // Invoke user callback
+ int doStopStream = 0;
+ RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback;
+ double streamTime = getStreamTime();
+ RtAudioStreamStatus status = 0;
+ if ( stream_.mode != INPUT && handle->xrun[0] ) {
+ status |= RTAUDIO_OUTPUT_UNDERFLOW;
+ handle->xrun[0] = false;
+ }
+ if ( stream_.mode != OUTPUT && handle->xrun[1] ) {
+ status |= RTAUDIO_INPUT_OVERFLOW;
+ handle->xrun[1] = false;
+ }
+
+ { static int _cb_pre=0; if(++_cb_pre<=3) { fprintf(stderr,"[sndio-dbg] invoking user callback #%d\n",_cb_pre); fflush(stderr); } }
+ doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1],
+ stream_.bufferSize, streamTime, status,
+ stream_.callbackInfo.userData );
+ if ( doStopStream == 2 ) {
+ abortStream();
+ return;
+ }
+
+ // Perform I/O outside the mutex so closeStream() can set state and call
+ // sio_stop() to unblock a thread waiting in sio_read/sio_write.
+ if ( stream_.state != STREAM_RUNNING ) {
+ RtApi::tickStreamTime();
+ return;
+ }
+
+ {
+ char *buffer;
+ int samples;
+ RtAudioFormat format;
+ size_t nbytes, written;
+
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {
+ if ( stream_.doConvertBuffer[0] ) {
+ buffer = stream_.deviceBuffer;
+ convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] );
+ samples = stream_.bufferSize * stream_.nDeviceChannels[0];
+ format = stream_.deviceFormat[0];
+ } else {
+ buffer = stream_.userBuffer[0];
+ samples = stream_.bufferSize * stream_.nUserChannels[0];
+ format = stream_.userFormat;
+ }
+ if ( stream_.doByteSwap[0] )
+ byteSwapBuffer( buffer, samples, format );
+
+ nbytes = samples * formatBytes( format );
+
+ // Use poll with a timeout so sio_write() cannot block indefinitely.
+ // This is critical for clean shutdown: stopStream() calls sio_stop()
+ // which stops the device clock, so POLLOUT may never fire again after
+ // that point. Two buffer periods is plenty under normal operation.
+ {
+ int timeoutMs = (int)( 2000.0 * stream_.bufferSize / stream_.sampleRate ) + 10;
+ struct pollfd pfd[8];
+ { static int _cb_n=0; if(++_cb_n<=5||_cb_n%100==0) { fprintf(stderr,"[sndio-dbg] callbackEvent #%d pre-poll\n",_cb_n); fflush(stderr); } }
+ int nfds = sio_pollfd( handle->handle, pfd, POLLOUT );
+ int ready = ( nfds > 0 ) ? poll( pfd, nfds, timeoutMs ) : 0;
+ written = 0;
+ { static int _pb=0; if(++_pb<=10||_pb%50==0) { int rev = (ready>0)?sio_revents(handle->handle,pfd):0; fprintf(stderr,"[sndio-dbg] poll#%d nfds=%d ready=%d rev=0x%x POLLOUT=%d\n",_pb,nfds,ready,rev,!!(rev&POLLOUT)); fflush(stderr); } }
+ if ( ready > 0 && ( sio_revents( handle->handle, pfd ) & POLLOUT ) )
+ written = sio_write( handle->handle, buffer, nbytes );
+ }
+ if ( written != nbytes ) {
+ handle->xrun[0] = true;
+ errorText_ = "RtApiSndio::callbackEvent: audio write error.";
+ error( RTAUDIO_WARNING );
+ }
+ }
+
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {
+ if ( stream_.doConvertBuffer[1] ) {
+ buffer = stream_.deviceBuffer;
+ samples = stream_.bufferSize * stream_.nDeviceChannels[1];
+ format = stream_.deviceFormat[1];
+ } else {
+ buffer = stream_.userBuffer[1];
+ samples = stream_.bufferSize * stream_.nUserChannels[1];
+ format = stream_.userFormat;
+ }
+
+ nbytes = samples * formatBytes( format );
+
+ // Use poll with a timeout so we don't block forever on play-only devices.
+ // Two buffer periods (in ms) is enough for normal operation.
+ int timeoutMs = (int)( 2000.0 * stream_.bufferSize / stream_.sampleRate ) + 10;
+ struct pollfd pfd[8];
+ int nfds = sio_pollfd( handle->handle, pfd, POLLIN );
+ int ready = ( nfds > 0 ) ? poll( pfd, nfds, timeoutMs ) : 0;
+
+ size_t got = 0;
+ if ( ready > 0 && ( sio_revents( handle->handle, pfd ) & POLLIN ) )
+ got = sio_read( handle->handle, buffer, nbytes );
+
+ if ( got != nbytes ) {
+ if ( got == 0 )
+ memset( buffer, 0, nbytes ); // play-only device: substitute silence
+ else {
+ handle->xrun[1] = true;
+ errorText_ = "RtApiSndio::callbackEvent: audio read error.";
+ error( RTAUDIO_WARNING );
+ }
+ }
+ if ( got > 0 ) {
+ if ( stream_.doByteSwap[1] )
+ byteSwapBuffer( buffer, samples, format );
+ if ( stream_.doConvertBuffer[1] )
+ convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] );
+ }
+ }
+ }
+
+ RtApi::tickStreamTime();
+ if ( doStopStream == 1 ) stopStream();
+}
+
+static void *sndioCallbackHandler( void *ptr )
+{
+ CallbackInfo *info = (CallbackInfo *) ptr;
+ RtApiSndio *object = (RtApiSndio *) info->object;
+ bool *isRunning = &info->isRunning;
+
+ while ( *isRunning == true ) {
+ pthread_testcancel();
+ object->callbackEvent();
+ }
+
+ pthread_exit( nullptr );
+}
+
+//******************** End of __OPENBSD_SNDIO__ *********************//
+#endif
// *************************************************** //
//