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,598 @@ 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 poll/sio_write (up to 23ms timeout).
+  // Only call sio_stop/sio_close after join; sio is not thread-safe.
+  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
 
 // *************************************************** //
 //
