628 lines
22 KiB
Plaintext
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
|
|
|
|
// *************************************************** //
|
|
//
|