First work to multiple API AvStreams. Python thread for UlDaq does compile, ready for testing
This commit is contained in:
parent
49ee42bb01
commit
c9b84e4c96
@ -11,4 +11,5 @@ target_link_libraries(lasp_rtaudio pthread rtaudio)
|
||||
target_link_libraries(lasp_uldaq uldaq)
|
||||
if(win32)
|
||||
target_link_libraries(lasp_rtaudio python37)
|
||||
target_link_libraries(lasp_uldaq python37)
|
||||
endif(win32)
|
||||
|
@ -109,6 +109,12 @@ class DAQConfiguration:
|
||||
outputDelayBlocks: int = 0
|
||||
nFramesPerBlock: int = 512
|
||||
|
||||
def getInputChannels(self):
|
||||
return self.input_channel_configs
|
||||
|
||||
def getOutputChannels(self):
|
||||
return self.output_channel_configs
|
||||
|
||||
def firstEnabledInputChannelNumber(self):
|
||||
"""
|
||||
Returns the channel number of the first enabled channel. Returns -1 if
|
||||
|
@ -164,7 +164,7 @@ def get_sampwidth_from_format_string(format_string):
|
||||
|
||||
|
||||
|
||||
ctypedef struct _Stream:
|
||||
ctypedef struct PyStreamData:
|
||||
PyObject* pyCallback
|
||||
RtAudioFormat sampleformat
|
||||
|
||||
@ -209,7 +209,7 @@ cdef int audioCallback(void* outputbuffer,
|
||||
"""
|
||||
cdef:
|
||||
int rval = 0
|
||||
_Stream* stream
|
||||
PyStreamData* stream
|
||||
void* outputbuffercpy = NULL
|
||||
void* inputbuffercpy = NULL
|
||||
unsigned j, i
|
||||
@ -217,7 +217,7 @@ cdef int audioCallback(void* outputbuffer,
|
||||
unsigned noutputchannels_forwarded, ninputchannels_forwarded
|
||||
bint ch_en
|
||||
|
||||
stream = <_Stream*>(userData)
|
||||
stream = <PyStreamData*>(userData)
|
||||
bytesperchan = stream.nBytesPerChan
|
||||
ninputchannels_forwarded = stream.ninputchannels_forwarded
|
||||
noutputchannels_forwarded = stream.noutputchannels_forwarded
|
||||
@ -317,7 +317,7 @@ cdef int audioCallback(void* outputbuffer,
|
||||
|
||||
cdef void audioCallbackPythonThreadFunction(void* voidstream) nogil:
|
||||
cdef:
|
||||
_Stream* stream
|
||||
PyStreamData* stream
|
||||
cnp.NPY_TYPES npy_format
|
||||
void* inputbuffer = NULL
|
||||
void* outputbuffer = NULL
|
||||
@ -326,9 +326,7 @@ cdef void audioCallbackPythonThreadFunction(void* voidstream) nogil:
|
||||
unsigned nBytesPerChan
|
||||
unsigned nFramesPerBlock
|
||||
|
||||
void* _TESTDATA
|
||||
|
||||
stream = <_Stream*> voidstream
|
||||
stream = <PyStreamData*> voidstream
|
||||
ninputchannels_forwarded = stream.ninputchannels_forwarded
|
||||
noutputchannels_forwarded = stream.noutputchannels_forwarded
|
||||
nBytesPerChan = stream.nBytesPerChan
|
||||
@ -441,22 +439,22 @@ cdef void errorCallback(RtAudioError.Type _type,const string& errortxt) nogil:
|
||||
cdef class RtAudio:
|
||||
cdef:
|
||||
cppRtAudio* _rtaudio
|
||||
_Stream* _stream
|
||||
PyStreamData* sd
|
||||
int api
|
||||
|
||||
def __cinit__(self, unsigned int iapi):
|
||||
cdef:
|
||||
cppRtAudio.Api api = <cppRtAudio.Api> iapi
|
||||
self._rtaudio = new cppRtAudio(api)
|
||||
self._stream = NULL
|
||||
self.sd = NULL
|
||||
self._rtaudio.showWarnings(True)
|
||||
self.api = api
|
||||
|
||||
def __dealloc__(self):
|
||||
if self._stream is not NULL:
|
||||
if self.sd is not NULL:
|
||||
# fprintf(stderr, 'Force closing stream...')
|
||||
self._rtaudio.closeStream()
|
||||
self.cleanupStream(self._stream)
|
||||
self.cleanupStream(self.sd)
|
||||
del self._rtaudio
|
||||
|
||||
@staticmethod
|
||||
@ -467,7 +465,7 @@ cdef class RtAudio:
|
||||
apidict = {}
|
||||
for api in apis:
|
||||
apidict[<int> api] = {
|
||||
'displayname': cppRtAudio.getApiDisplayName(api).decode('utf-8'),
|
||||
'displayname': 'RtAudio - ' + cppRtAudio.getApiDisplayName(api).decode('utf-8'),
|
||||
'name': cppRtAudio.getApiName(api).decode('utf-8')
|
||||
}
|
||||
return apidict
|
||||
@ -478,22 +476,33 @@ cdef class RtAudio:
|
||||
cpdef unsigned int getDefaultInputDevice(self):
|
||||
return self._rtaudio.getDefaultInputDevice()
|
||||
|
||||
def getDeviceInfo(self, unsigned int device):
|
||||
def getDeviceInfo(self):
|
||||
"""
|
||||
Return device information of the current device
|
||||
"""
|
||||
sampleformats = []
|
||||
|
||||
cdef:
|
||||
cppRtAudio.DeviceInfo devinfo
|
||||
devinfo = self._rtaudio.getDeviceInfo(device)
|
||||
nf = devinfo.nativeFormats
|
||||
for format_ in [ RTAUDIO_SINT8, RTAUDIO_SINT16, RTAUDIO_SINT24,
|
||||
RTAUDIO_SINT32, RTAUDIO_FLOAT32, RTAUDIO_FLOAT64]:
|
||||
if nf & format_:
|
||||
sampleformats.append(_formats_rtkey[format_][0])
|
||||
return DeviceInfo(
|
||||
unsigned devcount, i
|
||||
|
||||
devinfo_py = []
|
||||
|
||||
devcount = self._rtaudio.getDeviceCount()
|
||||
|
||||
for i in range(devcount):
|
||||
|
||||
devinfo = self._rtaudio.getDeviceInfo(i)
|
||||
|
||||
nf = devinfo.nativeFormats
|
||||
for format_ in [ RTAUDIO_SINT8, RTAUDIO_SINT16, RTAUDIO_SINT24,
|
||||
RTAUDIO_SINT32, RTAUDIO_FLOAT32, RTAUDIO_FLOAT64]:
|
||||
if nf & format_:
|
||||
sampleformats.append(_formats_rtkey[format_][0])
|
||||
|
||||
devinfo_py.append(DeviceInfo(
|
||||
api = self.api,
|
||||
index = device,
|
||||
index = i,
|
||||
probed = devinfo.probed,
|
||||
name = devinfo.name.decode('utf-8'),
|
||||
outputchannels = devinfo.outputChannels,
|
||||
@ -501,10 +510,12 @@ cdef class RtAudio:
|
||||
duplexchannels = devinfo.duplexChannels,
|
||||
samplerates = devinfo.sampleRates,
|
||||
sampleformats = sampleformats,
|
||||
prefsamplerate = devinfo.preferredSampleRate)
|
||||
prefsamplerate = devinfo.preferredSampleRate))
|
||||
|
||||
return devinfo_py
|
||||
|
||||
@cython.nonecheck(True)
|
||||
def openStream(self,
|
||||
def start(self,
|
||||
avstream
|
||||
):
|
||||
"""
|
||||
@ -516,7 +527,7 @@ cdef class RtAudio:
|
||||
Returns: None
|
||||
"""
|
||||
|
||||
if self._stream is not NULL:
|
||||
if self.sd is not NULL:
|
||||
raise RuntimeError('Stream is already opened.')
|
||||
|
||||
daqconfig = avstream.daqconfig
|
||||
@ -573,27 +584,27 @@ cdef class RtAudio:
|
||||
print(f'samplewidth: {sw}')
|
||||
|
||||
# All set, allocate the stream!
|
||||
self._stream = <_Stream*> malloc(sizeof(_Stream))
|
||||
if self._stream == NULL:
|
||||
self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
|
||||
if self.sd == NULL:
|
||||
raise MemoryError('Could not allocate stream: memory error.')
|
||||
|
||||
self._stream.pyCallback = <PyObject*> avstream._audioCallback
|
||||
self.sd.pyCallback = <PyObject*> avstream._audioCallback
|
||||
# Increase reference count to the callback
|
||||
Py_INCREF(<object> avstream._audioCallback)
|
||||
|
||||
self._stream.sampleformat = _formats_strkey[sampleformat][0]
|
||||
self._stream.stopThread.store(False)
|
||||
self._stream.inputQueue = NULL
|
||||
self._stream.outputQueue = NULL
|
||||
self._stream.outputDelayQueue = NULL
|
||||
self.sd.sampleformat = _formats_strkey[sampleformat][0]
|
||||
self.sd.stopThread.store(False)
|
||||
self.sd.inputQueue = NULL
|
||||
self.sd.outputQueue = NULL
|
||||
self.sd.outputDelayQueue = NULL
|
||||
|
||||
self._stream.thread = NULL
|
||||
self.sd.thread = NULL
|
||||
|
||||
self._stream.outputDelayBlocks = outputDelayBlocks
|
||||
self._stream.ninputchannels_forwarded = 0
|
||||
self._stream.noutputchannels_forwarded = 0
|
||||
self._stream.inputChannelsEnabled = NULL
|
||||
self._stream.outputChannelsEnabled = NULL
|
||||
self.sd.outputDelayBlocks = outputDelayBlocks
|
||||
self.sd.ninputchannels_forwarded = 0
|
||||
self.sd.noutputchannels_forwarded = 0
|
||||
self.sd.inputChannelsEnabled = NULL
|
||||
self.sd.outputChannelsEnabled = NULL
|
||||
|
||||
# Create channel maps for input channels, set RtAudio input stream
|
||||
# parameters
|
||||
@ -611,7 +622,7 @@ cdef class RtAudio:
|
||||
input_ch = daqconfig.input_channel_configs
|
||||
|
||||
inputChannelsEnabled = <bool*> malloc(sizeof(bool)*ninputchannels_rtaudio)
|
||||
self._stream.inputChannelsEnabled = inputChannelsEnabled
|
||||
self.sd.inputChannelsEnabled = inputChannelsEnabled
|
||||
|
||||
for i in range(firstinputchannel, lastinputchannel+1):
|
||||
ch_en = input_ch[i].channel_enabled
|
||||
@ -619,13 +630,13 @@ cdef class RtAudio:
|
||||
ninputchannels_forwarded += 1
|
||||
inputChannelsEnabled[i] = ch_en
|
||||
|
||||
rtInputParams_ptr = &self._stream.inputParams
|
||||
rtInputParams_ptr = &self.sd.inputParams
|
||||
rtInputParams_ptr.deviceId = device.index
|
||||
rtInputParams_ptr.nChannels = ninputchannels_rtaudio
|
||||
rtInputParams_ptr.firstChannel = firstinputchannel
|
||||
|
||||
self._stream.inputQueue = new SafeQueue[void*]()
|
||||
self._stream.ninputchannels_forwarded = ninputchannels_forwarded
|
||||
self.sd.inputQueue = new SafeQueue[void*]()
|
||||
self.sd.ninputchannels_forwarded = ninputchannels_forwarded
|
||||
|
||||
|
||||
# Create channel maps for output channels
|
||||
@ -643,24 +654,24 @@ cdef class RtAudio:
|
||||
output_ch = daqconfig.output_channel_configs
|
||||
|
||||
outputChannelsEnabled = <bool*> malloc(sizeof(bool)*noutputchannels_rtaudio)
|
||||
self._stream.outputChannelsEnabled = outputChannelsEnabled
|
||||
self.sd.outputChannelsEnabled = outputChannelsEnabled
|
||||
for i in range(firstoutputchannel, lastoutputchannel+1):
|
||||
ch_en = output_ch[i].channel_enabled
|
||||
if ch_en:
|
||||
noutputchannels_forwarded += 1
|
||||
outputChannelsEnabled[i] = ch_en
|
||||
|
||||
rtOutputParams_ptr = &self._stream.outputParams
|
||||
rtOutputParams_ptr = &self.sd.outputParams
|
||||
rtOutputParams_ptr.deviceId = device.index
|
||||
rtOutputParams_ptr.nChannels = noutputchannels_rtaudio
|
||||
rtOutputParams_ptr.firstChannel = firstoutputchannel
|
||||
|
||||
self._stream.outputQueue = new SafeQueue[void*]()
|
||||
self._stream.noutputchannels_forwarded = noutputchannels_forwarded
|
||||
self.sd.outputQueue = new SafeQueue[void*]()
|
||||
self.sd.noutputchannels_forwarded = noutputchannels_forwarded
|
||||
|
||||
if monitorOutput and duplex_mode:
|
||||
self._stream.outputDelayQueue = new SafeQueue[void*]()
|
||||
self._stream.ninputchannels_forwarded += noutputchannels_forwarded
|
||||
self.sd.outputDelayQueue = new SafeQueue[void*]()
|
||||
self.sd.ninputchannels_forwarded += noutputchannels_forwarded
|
||||
|
||||
|
||||
streamoptions.flags = RTAUDIO_HOG_DEVICE
|
||||
@ -675,30 +686,43 @@ cdef class RtAudio:
|
||||
samplerate,
|
||||
&nFramesPerBlock,
|
||||
audioCallback,
|
||||
<void*> self._stream,
|
||||
<void*> self.sd,
|
||||
&streamoptions, # Stream options
|
||||
errorCallback # Error callback
|
||||
)
|
||||
|
||||
self._stream.nBytesPerChan = nFramesPerBlock*sw
|
||||
self._stream.nFramesPerBlock = nFramesPerBlock
|
||||
self.sd.nBytesPerChan = nFramesPerBlock*sw
|
||||
self.sd.nFramesPerBlock = nFramesPerBlock
|
||||
|
||||
self._rtaudio.startStream()
|
||||
|
||||
except Exception as e:
|
||||
print('Exception occured in stream opening: ', e)
|
||||
self.cleanupStream(self._stream)
|
||||
self._stream = NULL
|
||||
self.cleanupStream(self.sd)
|
||||
self.sd = NULL
|
||||
raise e
|
||||
|
||||
with nogil:
|
||||
self._stream.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
|
||||
<void*> self._stream)
|
||||
self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
|
||||
<void*> self.sd)
|
||||
# Allow it to start
|
||||
CPPsleep_ms(500)
|
||||
pass
|
||||
|
||||
return nFramesPerBlock
|
||||
return nFramesPerBlock, samplerate
|
||||
|
||||
cdef cleanupStream(self, _Stream* stream):
|
||||
def stop(self):
|
||||
if self.sd is NULL:
|
||||
raise RuntimeError('Stream is not running')
|
||||
try:
|
||||
self._rtaudio.stopStream()
|
||||
self._rtaudio.closeStream()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
self.cleanupStream(self.sd)
|
||||
|
||||
cdef cleanupStream(self, PyStreamData* stream):
|
||||
# printf('Entrance function cleanupStream...\n')
|
||||
cdef:
|
||||
void* ptr
|
||||
@ -743,42 +767,3 @@ cdef class RtAudio:
|
||||
stream.pyCallback = NULL
|
||||
# fprintf(stderr, "End cleanup callback...\n")
|
||||
free(stream)
|
||||
|
||||
def startStream(self):
|
||||
self._rtaudio.startStream()
|
||||
|
||||
def stopStream(self):
|
||||
if self._stream is NULL:
|
||||
raise RuntimeError('Stream is not opened')
|
||||
try:
|
||||
self._rtaudio.stopStream()
|
||||
except:
|
||||
pass
|
||||
|
||||
def closeStream(self):
|
||||
# print('closeStream')
|
||||
if self._stream is NULL:
|
||||
raise RuntimeError('Stream is not opened')
|
||||
# Closing stream
|
||||
self._rtaudio.closeStream()
|
||||
self.cleanupStream(self._stream)
|
||||
self._stream = NULL
|
||||
|
||||
def abortStream(self):
|
||||
if self._stream is NULL:
|
||||
raise RuntimeError('Stream is not opened')
|
||||
self._rtaudio.abortStream()
|
||||
|
||||
def isStreamOpen(self):
|
||||
return self._rtaudio.isStreamOpen()
|
||||
|
||||
def isStreamRunning(self):
|
||||
return self._rtaudio.isStreamRunning()
|
||||
|
||||
def getStreamTime(self):
|
||||
return self._rtaudio.getStreamTime()
|
||||
|
||||
def setStreamTime(self, double time):
|
||||
return self._rtaudio.setStreamTime(time)
|
||||
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
cimport cython
|
||||
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
|
||||
from .lasp_daqconfig import (DeviceInfo, InputMode, Range as pyRange,
|
||||
DAQChannel)
|
||||
@ -30,14 +31,12 @@ cdef struct DaqThreadData:
|
||||
double* inbuffer
|
||||
double* outbuffer
|
||||
|
||||
|
||||
cdef void showErr(UlError err) nogil:
|
||||
cdef:
|
||||
char errmsg[UL_ERR_MSG_LEN]
|
||||
ulGetErrMsg(err, errmsg)
|
||||
fprintf(stderr, 'UlError: %s\n', errmsg)
|
||||
|
||||
|
||||
cdef void inThreadFunction(void* threaddata_void) nogil:
|
||||
"""
|
||||
Stream input thread function
|
||||
@ -269,7 +268,6 @@ cdef void outThreadFunction(void* threaddata_void) nogil:
|
||||
fprintf(stderr, "Error stopping AOut output thread\n")
|
||||
showErr(err)
|
||||
|
||||
|
||||
cdef class UlDT9837A:
|
||||
cdef:
|
||||
DaqDeviceHandle handle
|
||||
@ -280,7 +278,7 @@ cdef class UlDT9837A:
|
||||
object input_range
|
||||
object enabled_inputs
|
||||
|
||||
bint output_enabled
|
||||
bint out_enabled
|
||||
bint monitor_gen
|
||||
|
||||
DaqThreadData *td
|
||||
@ -294,7 +292,7 @@ cdef class UlDT9837A:
|
||||
self.input_range = 4*[False]
|
||||
self.enabled_inputs = 4*[False]
|
||||
|
||||
self.output_enabled = False
|
||||
self.out_enabled = False
|
||||
self.monitor_gen = False
|
||||
|
||||
self.td = NULL
|
||||
@ -340,7 +338,7 @@ cdef class UlDT9837A:
|
||||
# Sanity checks
|
||||
if inQueue and (not any(self.enabled_inputs) and (not self.monitor_gen)):
|
||||
raise ValueError('Input queue given, but no input channels enabled and monitor output disabled')
|
||||
if outQueue and not self.output_enabled:
|
||||
if outQueue and not self.out_enabled:
|
||||
raise ValueError('Output queue given, but output channel is not enabled')
|
||||
# End sanity checks
|
||||
|
||||
@ -419,7 +417,7 @@ cdef class UlDT9837A:
|
||||
fprintf(stderr, 'SFSG12\n')
|
||||
|
||||
# CONFIGURE OUTPUTS
|
||||
if self.output_enabled:
|
||||
if self.out_enabled:
|
||||
fprintf(stderr, 'SFSG13\n')
|
||||
td.noutChanDescriptors = 1
|
||||
# WATCH THE TWO HERE!!!
|
||||
@ -431,7 +429,7 @@ cdef class UlDT9837A:
|
||||
# Create DAQ Threads
|
||||
with nogil:
|
||||
fprintf(stderr, 'SFSG14\n')
|
||||
if self.output_enabled:
|
||||
if self.out_enabled:
|
||||
td.outThread = new CPPThread[void*, void (*)(void*)](
|
||||
outThreadFunction, <void*> td)
|
||||
|
||||
@ -492,7 +490,7 @@ cdef class UlDT9837A:
|
||||
del inqueue
|
||||
del outqueue
|
||||
|
||||
def stop(self):
|
||||
def stopScan(self):
|
||||
self.cleanupThreadData(self.td)
|
||||
self.td = NULL
|
||||
|
||||
@ -538,7 +536,7 @@ cdef class UlDT9837A:
|
||||
if monitor_gen and not channelconfig.channel_enabled:
|
||||
raise RuntimeError('Output channel should be enabled to enable channel monitoring')
|
||||
|
||||
self.output_enabled = channelconfig.channel_enabled
|
||||
self.out_enabled = channelconfig.channel_enabled
|
||||
self.monitor_gen = monitor_gen
|
||||
|
||||
def __dealloc__(self):
|
||||
@ -581,11 +579,122 @@ cdef class UlDT9837A:
|
||||
free(td)
|
||||
fprintf(stderr, 'end cleanupThreadData()\n')
|
||||
|
||||
ctypedef struct PyStreamData:
|
||||
PyObject* pyCallback
|
||||
|
||||
# Flag used to pass the stopThread.
|
||||
atomic[bool] stopThread
|
||||
|
||||
# Number of frames per block
|
||||
unsigned nFramesPerBlock
|
||||
|
||||
# Number of bytes per channel
|
||||
unsigned int nBytesPerChan
|
||||
|
||||
unsigned ninchannels
|
||||
unsigned noutchannels
|
||||
|
||||
# If these queue pointers are NULL, it means the stream does not have an
|
||||
# input, or output.
|
||||
SafeQueue[void*] *inQueue
|
||||
SafeQueue[void*] *outQueue
|
||||
CPPThread[void*, void (*)(void*)] *thread
|
||||
|
||||
cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
|
||||
cdef:
|
||||
PyStreamData* sd
|
||||
cnp.NPY_TYPES npy_format
|
||||
|
||||
void* inbuffer = NULL
|
||||
void* outbuffer = NULL
|
||||
|
||||
unsigned noutchannels
|
||||
unsigned ninchannels
|
||||
unsigned nBytesPerChan
|
||||
unsigned nFramesPerBlock
|
||||
|
||||
unsigned sw = sizeof(double)
|
||||
|
||||
|
||||
sd = <PyStreamData*> voidsd
|
||||
ninchannels = sd.ninchannels
|
||||
noutchannels = sd.noutchannels
|
||||
|
||||
nBytesPerChan = sd.nBytesPerChan
|
||||
nFramesPerBlock = sd.nFramesPerBlock
|
||||
|
||||
|
||||
with gil:
|
||||
npy_format = cnp.NPY_FLOAT64
|
||||
callback = <object> sd.pyCallback
|
||||
|
||||
while True:
|
||||
if sd.stopThread.load() == True:
|
||||
printf('Stopping thread...\n')
|
||||
return
|
||||
|
||||
if sd.inQueue:
|
||||
# fprintf(stderr, "Waiting on in queue\n")
|
||||
inbuffer = sd.inQueue.dequeue()
|
||||
if not inbuffer:
|
||||
printf('Stopping thread...\n')
|
||||
return
|
||||
|
||||
if sd.outQueue:
|
||||
# fprintf(stderr, 'Allocating output buffer...\n')
|
||||
outbuffer = malloc(nBytesPerChan*noutchannels)
|
||||
# memset(outbuffer, 0, nBytesPerChan*noutchannels)
|
||||
|
||||
with gil:
|
||||
|
||||
# Obtain stream information
|
||||
npy_input = None
|
||||
npy_output = None
|
||||
|
||||
if sd.inQueue:
|
||||
try:
|
||||
|
||||
npy_input = <object> data_to_ndarray(
|
||||
inbuffer,
|
||||
nFramesPerBlock,
|
||||
ninchannels,
|
||||
npy_format,
|
||||
True, # Do transfer ownership
|
||||
True) # F-contiguous is True: data is Fortran-cont.
|
||||
|
||||
except Exception as e:
|
||||
print('exception in cython callback for audio input: ', str(e))
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
rval = callback(npy_input,
|
||||
npy_output,
|
||||
nFramesPerBlock,
|
||||
)
|
||||
except Exception as e:
|
||||
print('Exception in Cython callback: ', str(e))
|
||||
return
|
||||
|
||||
if sd.outQueue:
|
||||
sd.outQueue.enqueue(outbuffer)
|
||||
if not sd.inQueue:
|
||||
while sd.outQueue.size() > 10 and not sd.stopThread.load():
|
||||
# printf('Sleeping...\n')
|
||||
# No input queue to wait on, so we relax a bit here.
|
||||
CPPsleep_ms(1);
|
||||
|
||||
# Outputbuffer is free'ed by the audiothread, so should not be touched
|
||||
# here.
|
||||
outbuffer = NULL
|
||||
|
||||
# Inputbuffer memory is owned by Numpy, so should not be free'ed
|
||||
inbuffer = NULL
|
||||
|
||||
cdef class UlDaq:
|
||||
cdef:
|
||||
DaqDeviceHandle handle
|
||||
PyStreamData *sd
|
||||
UlDT9837A daq_device
|
||||
|
||||
def __cinit__(self):
|
||||
|
||||
@ -593,33 +702,13 @@ cdef class UlDaq:
|
||||
Acquires a daq handle, and opens the device
|
||||
|
||||
"""
|
||||
self.handle = 0
|
||||
self.daq_device = None
|
||||
self.sd = NULL
|
||||
|
||||
# # Open the device to probe the number of input and output ch.
|
||||
# handle = ulCreateDaqDevice(descriptor)
|
||||
# if not handle:
|
||||
# raise RuntimeError('Unable to create device handle on device')
|
||||
|
||||
cpdef int getDeviceCount(self):
|
||||
cdef:
|
||||
DaqDeviceDescriptor devdescriptors[MAX_DEF_COUNT]
|
||||
DaqDeviceInterface interfaceType = ANY_IFC
|
||||
UlError err
|
||||
|
||||
unsigned int numdevs = MAX_DEF_COUNT
|
||||
|
||||
err = ulGetDaqDeviceInventory(interfaceType,
|
||||
devdescriptors,
|
||||
&numdevs)
|
||||
if(err != ERR_NO_ERROR):
|
||||
raise RuntimeError(f'Device inventarization failed: {err}')
|
||||
|
||||
devices = []
|
||||
return numdevs
|
||||
|
||||
def getDeviceInfo(self, unsigned int deviceno):
|
||||
def getDeviceInfo(self):
|
||||
"""
|
||||
|
||||
Returns device information objects (DeviceInfo) for all available
|
||||
devices
|
||||
"""
|
||||
cdef:
|
||||
DaqDeviceDescriptor devdescriptors[MAX_DEF_COUNT]
|
||||
@ -630,6 +719,7 @@ cdef class UlDaq:
|
||||
UlError err
|
||||
|
||||
unsigned int numdevs = MAX_DEF_COUNT
|
||||
unsigned deviceno
|
||||
|
||||
err = ulGetDaqDeviceInventory(interfaceType,
|
||||
devdescriptors,
|
||||
@ -637,287 +727,190 @@ cdef class UlDaq:
|
||||
if(err != ERR_NO_ERROR):
|
||||
raise RuntimeError(f'Device inventarization failed: {err}')
|
||||
|
||||
if deviceno >= numdevs:
|
||||
raise ValueError(f'Device number {deviceno} too high {err}')
|
||||
|
||||
py_devinfo = []
|
||||
for deviceno in range(numdevs):
|
||||
descriptor = devdescriptors[deviceno]
|
||||
|
||||
descriptor = devdescriptors[deviceno]
|
||||
if descriptor.productName == b'DT9837A':
|
||||
# Create proper interface name
|
||||
if descriptor.devInterface == DaqDeviceInterface.USB_IFC:
|
||||
name = 'USB - '
|
||||
elif descriptor.devInterface == DaqDeviceInterface.BLUETOOTH_IFC:
|
||||
name = 'Bluetooth - '
|
||||
elif descriptor.devInterface == DaqDeviceInterface.ETHERNET_IFC:
|
||||
name = 'Ethernet - '
|
||||
|
||||
name += descriptor.productName.decode('utf-8') + ', id ' + \
|
||||
descriptor.uniqueId.decode('utf-8')
|
||||
|
||||
d = DeviceInfo(
|
||||
api = -1,
|
||||
index = deviceno,
|
||||
probed = True,
|
||||
name = name,
|
||||
outputchannels = 1,
|
||||
inputchannels = 4,
|
||||
duplexchannels = 0,
|
||||
samplerates = [10000, 16000, 20000, 32000, 48000, 50000] ,
|
||||
sampleformats = ['64-bit floats'],
|
||||
prefsamplerate = 48000,
|
||||
hasInputIEPE = True)
|
||||
py_devinfo.append(d)
|
||||
return py_devinfo
|
||||
|
||||
|
||||
if descriptor.productName == b'DT9837A':
|
||||
# Create proper interface name
|
||||
if descriptor.devInterface == DaqDeviceInterface.USB_IFC:
|
||||
name = 'USB - '
|
||||
elif descriptor.devInterface == DaqDeviceInterface.BLUETOOTH_IFC:
|
||||
name = 'Bluetooth - '
|
||||
elif descriptor.devInterface == DaqDeviceInterface.ETHERNET_IFC:
|
||||
name = 'Ethernet - '
|
||||
@cython.nonecheck(True)
|
||||
def start(self, avstream):
|
||||
"""
|
||||
Opens a stream with specified parameters
|
||||
|
||||
name += descriptor.productName.decode('utf-8') + ', id ' + \
|
||||
descriptor.uniqueId.decode('utf-8')
|
||||
Args:
|
||||
avstream: AvStream instance
|
||||
|
||||
return DeviceInfo(
|
||||
api = -1,
|
||||
index = deviceno,
|
||||
probed = True,
|
||||
name = name,
|
||||
outputchannels = 1,
|
||||
inputchannels = 4,
|
||||
duplexchannels = 0,
|
||||
samplerates = [100, 500, 1000, 2000, 4000, 8000, 16000, 20000, 32000, 48000, 50000] ,
|
||||
sampleformats = ['64-bit floats'],
|
||||
prefsamplerate = 48000,
|
||||
hasInputIEPE = True)
|
||||
Returns: None
|
||||
"""
|
||||
|
||||
if self.sd is not NULL:
|
||||
raise RuntimeError('Stream is already opened.')
|
||||
|
||||
daqconfig = avstream.daqconfig
|
||||
avtype = avstream.avtype
|
||||
device = avstream.device
|
||||
|
||||
cdef:
|
||||
bint duplex_mode = daqconfig.duplex_mode
|
||||
bint monitorOutput = daqconfig.monitor_gen
|
||||
|
||||
unsigned int nFramesPerBlock = daqconfig.nFramesPerBlock
|
||||
unsigned int samplerate
|
||||
|
||||
unsigned int ninchannels = 0, noutchannels = 0
|
||||
|
||||
int i
|
||||
bint in_stream=False, output_stream=False
|
||||
|
||||
if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
|
||||
raise ValueError('Invalid number of nFramesPerBlock')
|
||||
|
||||
if daqconfig.outputDelayBlocks != 0:
|
||||
raise ValueError('OutputDelayBlocks not supported by API')
|
||||
|
||||
# Determine sample rate and sample format, determine whether we are an
|
||||
# in or an output stream, or both
|
||||
if avtype == AvType.audio_input or duplex_mode:
|
||||
# Here, we override the sample format in case of duplex mode.
|
||||
sampleformat = daqconfig.en_input_sample_format
|
||||
samplerate = int(daqconfig.en_input_rate)
|
||||
in_stream = True
|
||||
if duplex_mode:
|
||||
out_stream = True
|
||||
else:
|
||||
raise RuntimeError(f"No config found for device \"{descriptor.productName.decode('utf-8')}\".")
|
||||
sampleformat = daqconfig.en_output_sample_format
|
||||
samplerate = int(daqconfig.en_output_rate)
|
||||
out_stream = True
|
||||
|
||||
# @cython.nonecheck(True)
|
||||
# def openStream(self,
|
||||
# avstream
|
||||
# ):
|
||||
# """
|
||||
# Opens a stream with specified parameters
|
||||
if 'DT9837A' in device.name:
|
||||
self.daq_device = UlDT9837A(device.deviceno)
|
||||
else:
|
||||
raise RuntimeError(f'Device {device.name} not found or not configured')
|
||||
|
||||
# Args:
|
||||
# avstream: AvStream instance
|
||||
# All set, allocate the stream!
|
||||
self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
|
||||
if self.sd == NULL:
|
||||
raise MemoryError('Could not allocate stream: memory error.')
|
||||
|
||||
# Returns: None
|
||||
# """
|
||||
self.sd.pyCallback = <PyObject*> avstream._audioCallback
|
||||
# Increase reference count to the callback
|
||||
Py_INCREF(<object> avstream._audioCallback)
|
||||
|
||||
# if self._stream is not NULL:
|
||||
# raise RuntimeError('Stream is already opened.')
|
||||
self.sd.stopThread.store(False)
|
||||
self.sd.inQueue = NULL
|
||||
self.sd.outQueue = NULL
|
||||
|
||||
# daqconfig = avstream.daqconfig
|
||||
# avtype = avstream.avtype
|
||||
# device = avstream.device
|
||||
self.sd.thread = NULL
|
||||
|
||||
# cdef:
|
||||
# bint duplex_mode = daqconfig.duplex_mode
|
||||
# bint monitorOutput = daqconfig.monitor_gen
|
||||
# size_t sw # Sample width in bytes
|
||||
# unsigned int nFramesPerBlock = unsigned int(daqconfig.nFramesPerBlock)
|
||||
# int firstinputchannel, firstoutputchannel
|
||||
# int lastinputchannel, lastoutputchannel
|
||||
# unsigned int ninputchannels_forwarded=0
|
||||
# unsigned int ninputchannels_uldaq=0
|
||||
# unsigned int noutputchannels_uldaq=0
|
||||
# unsigned int noutputchannels_forwarded=0
|
||||
# unsigned int samplerate
|
||||
# int i
|
||||
# bint input_stream=False, output_stream=False
|
||||
# bool* inputChannelsEnabled
|
||||
# bool* outputChannelsEnabled
|
||||
self.sd.ninchannels = 0
|
||||
self.sd.noutchannels = 0
|
||||
self.sd.nBytesPerChan = daqconfig.nFramesPerBlock*sizeof(double)
|
||||
|
||||
# if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
|
||||
# raise ValueError('Invalid number of nFramesPerBlock')
|
||||
# Create channel maps for in channels, set in stream
|
||||
# parameters
|
||||
if in_stream:
|
||||
for i, ch in enumerate(daqconfig.getInputChannels()):
|
||||
if ch.channel_enabled:
|
||||
ninchannels += 1
|
||||
self.daq_device.setInputChannelConfig(i, ch)
|
||||
|
||||
# if daqconfig.outputDelayBlocks < 0 or daqconfig.outputDelayBlocks > 10:
|
||||
# raise ValueError('Invalid number of outputDelayBlocks')
|
||||
self.sd.inQueue = new SafeQueue[void*]()
|
||||
|
||||
# try:
|
||||
self.sd.ninchannels = ninchannels
|
||||
|
||||
# # Determine sample rate and sample format, determine whether we are an
|
||||
# # input or an output stream, or both
|
||||
# if avtype == AvType.audio_input or duplex_mode:
|
||||
# # Here, we override the sample format in case of duplex mode.
|
||||
# sampleformat = daqconfig.en_input_sample_format
|
||||
# samplerate = int(daqconfig.en_input_rate)
|
||||
# input_stream = True
|
||||
# if duplex_mode:
|
||||
# output_stream = True
|
||||
# else:
|
||||
# sampleformat = daqconfig.en_output_sample_format
|
||||
# samplerate = int(daqconfig.en_output_rate)
|
||||
# output_stream = True
|
||||
# Create channel maps for output channels
|
||||
if out_stream:
|
||||
for i, ch in enumerate(daqconfig.getOutputChannels()):
|
||||
if ch.channel_enabled:
|
||||
noutchannels += 1
|
||||
self.daq_device.setOutputChannelConfig(i, ch)
|
||||
|
||||
# sw = 64
|
||||
self.sd.outQueue = new SafeQueue[void*]()
|
||||
self.sd.noutchannels = noutchannels
|
||||
|
||||
# # All set, allocate the stream!
|
||||
# self._stream = <_Stream*> malloc(sizeof(_Stream))
|
||||
# if self._stream == NULL:
|
||||
# raise MemoryError('Could not allocate stream: memory error.')
|
||||
if monitorOutput and duplex_mode:
|
||||
self.sd.ninchannels += noutchannels
|
||||
|
||||
# self._stream.pyCallback = <PyObject*> avstream._audioCallback
|
||||
# # Increase reference count to the callback
|
||||
# Py_INCREF(<object> avstream._audioCallback)
|
||||
with nogil:
|
||||
self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
|
||||
<void*> self.sd)
|
||||
|
||||
# self._stream.sw = sw
|
||||
# self._stream.stopThread.store(False)
|
||||
# self._stream.inputQueue = NULL
|
||||
# self._stream.outputQueue = NULL
|
||||
# self._stream.outputDelayQueue = NULL
|
||||
# Allow it to start
|
||||
CPPsleep_ms(500)
|
||||
|
||||
# self._stream.thread = NULL
|
||||
self.daq_device.startScan(
|
||||
daqconfig.nFramesPerBlock,
|
||||
samplerate,
|
||||
self.sd.inQueue,
|
||||
self.sd.outQueue)
|
||||
|
||||
# self._stream.outputDelayBlocks = outputDelayBlocks
|
||||
# self._stream.ninputchannels_forwarded = 0
|
||||
# self._stream.noutputchannels_forwarded = 0
|
||||
# self._stream.inputChannelsEnabled = NULL
|
||||
# self._stream.outputChannelsEnabled = NULL
|
||||
return nFramesPerBlock, self.daq_device.td.samplerate
|
||||
|
||||
# # Create channel maps for input channels, set input stream
|
||||
# # parameters
|
||||
# if input_stream:
|
||||
# firstinputchannel = daqconfig.firstEnabledInputChannelNumber()
|
||||
# lastinputchannel = daqconfig.lastEnabledInputChannelNumber()
|
||||
# ninputchannels_uldaq = lastinputchannel-firstinputchannel+1
|
||||
def stop(self):
|
||||
if self.sd is NULL:
|
||||
raise RuntimeError('Stream is not opened')
|
||||
|
||||
# if lastinputchannel < 0 or ninputchannels_uldaq < 1:
|
||||
# raise ValueError('Not enough input channels selected')
|
||||
# input_ch = daqconfig.input_channel_configs
|
||||
self.uldaq.stopScan()
|
||||
self.cleanupStream(self.sd)
|
||||
self.sd = NULL
|
||||
|
||||
# inputChannelsEnabled = <bool*> malloc(sizeof(bool)*ninputchannels_uldaq)
|
||||
# self._stream.inputChannelsEnabled = inputChannelsEnabled
|
||||
cdef cleanupStream(self, PyStreamData* sd):
|
||||
|
||||
# for i in range(firstinputchannel, lastinputchannel+1):
|
||||
# ch_en = input_ch[i].channel_enabled
|
||||
# if ch_en:
|
||||
# ninputchannels_forwarded += 1
|
||||
# inputChannelsEnabled[i] = ch_en
|
||||
with nogil:
|
||||
if sd.thread:
|
||||
sd.stopThread.store(True)
|
||||
|
||||
if sd.inQueue:
|
||||
# If waiting in the input queue, hereby we let it run.
|
||||
sd.inQueue.enqueue(NULL)
|
||||
# printf('Joining thread...\n')
|
||||
# HERE WE SHOULD RELEASE THE GIL, as exiting the thread function
|
||||
# will require the GIL, which is locked by this thread!
|
||||
sd.thread.join()
|
||||
# printf('Thread joined!\n')
|
||||
del sd.thread
|
||||
sd.thread = NULL
|
||||
|
||||
# self._stream.inputQueue = new SafeQueue[void*]()
|
||||
# self._stream.ninputchannels_forwarded = ninputchannels_forwarded
|
||||
if sd.outQueue:
|
||||
while not sd.outQueue.empty():
|
||||
free(sd.outQueue.dequeue())
|
||||
del sd.outQueue
|
||||
if sd.inQueue:
|
||||
while not sd.inQueue.empty():
|
||||
free(sd.inQueue.dequeue())
|
||||
del sd.inQueue
|
||||
fprintf(stderr, "End cleanup stream queues...\n")
|
||||
|
||||
if sd.pyCallback:
|
||||
Py_DECREF(<object> sd.pyCallback)
|
||||
sd.pyCallback = NULL
|
||||
|
||||
# # Create channel maps for output channels
|
||||
# if output_stream:
|
||||
# firstoutputchannel = daqconfig.firstEnabledOutputChannelNumber()
|
||||
# lastoutputchannel = daqconfig.lastEnabledOutputChannelNumber()
|
||||
# noutputchannels_uldaq = lastoutputchannel-firstoutputchannel+1
|
||||
|
||||
# if lastoutputchannel < 0 or noutputchannels_uldaq < 1:
|
||||
# raise ValueError('Not enough output channels selected')
|
||||
# output_ch = daqconfig.output_channel_configs
|
||||
|
||||
# outputChannelsEnabled = <bool*> malloc(sizeof(bool)*noutputchannels_uldaq)
|
||||
# self._stream.outputChannelsEnabled = outputChannelsEnabled
|
||||
# for i in range(firstoutputchannel, lastoutputchannel+1):
|
||||
# ch_en = output_ch[i].channel_enabled
|
||||
# if ch_en:
|
||||
# noutputchannels_forwarded += 1
|
||||
# outputChannelsEnabled[i] = ch_en
|
||||
|
||||
# rtOutputParams_ptr = &self._stream.outputParams
|
||||
# rtOutputParams_ptr.deviceId = device.index
|
||||
# rtOutputParams_ptr.nChannels = noutputchannels_uldaq
|
||||
# rtOutputParams_ptr.firstChannel = firstoutputchannel
|
||||
|
||||
# self._stream.outputQueue = new SafeQueue[void*]()
|
||||
# self._stream.noutputchannels_forwarded = noutputchannels_forwarded
|
||||
|
||||
# if monitorOutput and duplex_mode:
|
||||
# self._stream.ninputchannels_forwarded += noutputchannels_forwarded
|
||||
|
||||
# # self._uldaq.openStream(rtOutputParams_ptr,
|
||||
# # rtInputParams_ptr,
|
||||
# # _formats_strkey[sampleformat][0],
|
||||
# # samplerate,
|
||||
# # &nFramesPerBlock,
|
||||
# # audioCallback,
|
||||
# # <void*> self._stream,
|
||||
# # &streamoptions, # Stream options
|
||||
# # errorCallback # Error callback
|
||||
# # )
|
||||
|
||||
# # self._stream.nBytesPerChan = nFramesPerBlock*sw
|
||||
# # self._stream.nFramesPerBlock = nFramesPerBlock
|
||||
|
||||
# except Exception as e:
|
||||
# print('Exception occured in stream opening: ', e)
|
||||
# self.cleanupStream(self._stream)
|
||||
# self._stream = NULL
|
||||
# raise e
|
||||
|
||||
# with nogil:
|
||||
# self._stream.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
|
||||
# <void*> self._stream)
|
||||
# # Allow it to start
|
||||
# CPPsleep(500)
|
||||
# pass
|
||||
|
||||
# return nFramesPerBlock
|
||||
|
||||
# cdef cleanupStream(self, _Stream* stream):
|
||||
# # printf('Entrance function cleanupStream...\n')
|
||||
# cdef:
|
||||
# void* ptr
|
||||
# if stream == NULL:
|
||||
# return
|
||||
|
||||
# with nogil:
|
||||
# if stream.thread:
|
||||
# stream.stopThread.store(True)
|
||||
# if stream.inputQueue:
|
||||
# # If waiting in the input queue, hereby we let it run.
|
||||
# stream.inputQueue.enqueue(NULL)
|
||||
# # printf('Joining thread...\n')
|
||||
# # HERE WE SHOULD RELEASE THE GIL, as exiting the thread function
|
||||
# # will require the GIL, which is locked by this thread!
|
||||
# stream.thread.join()
|
||||
# # printf('Thread joined!\n')
|
||||
# del stream.thread
|
||||
# stream.thread = NULL
|
||||
|
||||
# if stream.inputChannelsEnabled:
|
||||
# free(stream.inputChannelsEnabled)
|
||||
# if stream.outputChannelsEnabled:
|
||||
# free(stream.outputChannelsEnabled)
|
||||
|
||||
# if stream.outputQueue:
|
||||
# while not stream.outputQueue.empty():
|
||||
# free(stream.outputQueue.dequeue())
|
||||
# del stream.outputQueue
|
||||
# if stream.inputQueue:
|
||||
# while not stream.inputQueue.empty():
|
||||
# free(stream.inputQueue.dequeue())
|
||||
# del stream.inputQueue
|
||||
# if stream.outputDelayQueue:
|
||||
# while not stream.outputDelayQueue.empty():
|
||||
# free(stream.outputDelayQueue.dequeue())
|
||||
# del stream.outputDelayQueue
|
||||
# fprintf(stderr, "End cleanup stream queues...\n")
|
||||
|
||||
# if stream.pyCallback:
|
||||
# Py_DECREF(<object> stream.pyCallback)
|
||||
# stream.pyCallback = NULL
|
||||
# # fprintf(stderr, "End cleanup callback...\n")
|
||||
# free(stream)
|
||||
|
||||
# def startStream(self):
|
||||
# self._uldaq.startStream()
|
||||
|
||||
# def stopStream(self):
|
||||
# if self._stream is NULL:
|
||||
# raise RuntimeError('Stream is not opened')
|
||||
# try:
|
||||
# self._uldaq.stopStream()
|
||||
# except:
|
||||
# pass
|
||||
|
||||
# def closeStream(self):
|
||||
# # print('closeStream')
|
||||
# if self._stream is NULL:
|
||||
# raise RuntimeError('Stream is not opened')
|
||||
# # Closing stream
|
||||
# self._uldaq.closeStream()
|
||||
# self.cleanupStream(self._stream)
|
||||
# self._stream = NULL
|
||||
|
||||
# def abortStream(self):
|
||||
# if self._stream is NULL:
|
||||
# raise RuntimeError('Stream is not opened')
|
||||
# self._uldaq.abortStream()
|
||||
|
||||
# def isStreamOpen(self):
|
||||
# return self._uldaq.isStreamOpen()
|
||||
|
||||
# def isStreamRunning(self):
|
||||
# return self._uldaq.isStreamRunning()
|
||||
|
||||
# def getStreamTime(self):
|
||||
# return self._uldaq.getStreamTime()
|
||||
|
||||
# def setStreamTime(self, double time):
|
||||
# return self._uldaq.setStreamTime(time)
|
||||
|
||||
free(sd)
|
||||
|
@ -107,13 +107,9 @@ class AvStream:
|
||||
|
||||
# Possible, but long not tested: store video
|
||||
self._videothread = None
|
||||
self._rtaudio = RtAudio(daqconfig.api)
|
||||
self.blocksize = self._rtaudio.openStream(self)
|
||||
|
||||
|
||||
def close(self):
|
||||
self._rtaudio.closeStream()
|
||||
self._rtaudio = None
|
||||
self._audiobackend = RtAudio(daqconfig.api)
|
||||
self.blocksize, self.samplerate = self._audiobackend.openStream(self)
|
||||
|
||||
def nCallbacks(self):
|
||||
"""Returns the current number of installed callbacks."""
|
||||
@ -152,7 +148,7 @@ class AvStream:
|
||||
self._videothread.start()
|
||||
else:
|
||||
self._video_started <<= True
|
||||
self._rtaudio.startStream()
|
||||
self._audiobackend.start()
|
||||
|
||||
def _videoThread(self):
|
||||
cap = cv.VideoCapture(self._video)
|
||||
@ -221,7 +217,8 @@ class AvStream:
|
||||
self._aframectr <<= 0
|
||||
self._vframectr <<= 0
|
||||
self._video_started <<= False
|
||||
self._rtaudio.stopStream()
|
||||
self._audiobackend.stop()
|
||||
self._audiobackend = None
|
||||
|
||||
def isRunning(self):
|
||||
return self._running()
|
||||
|
@ -27,8 +27,7 @@ config = DAQConfiguration.loadConfigs()[args.input_daq]
|
||||
|
||||
print(config)
|
||||
rtaudio = RtAudio()
|
||||
count = rtaudio.getDeviceCount()
|
||||
devices = [rtaudio.getDeviceInfo(i) for i in range(count)]
|
||||
devices = rtaudio.getDeviceInfo()
|
||||
|
||||
input_devices = {}
|
||||
for device in devices:
|
||||
|
@ -18,8 +18,7 @@ from lasp.device import DAQConfiguration, RtAudio
|
||||
config = DAQConfiguration.loadConfigs()[args.device]
|
||||
|
||||
rtaudio = RtAudio()
|
||||
count = rtaudio.getDeviceCount()
|
||||
devices = [rtaudio.getDeviceInfo(i) for i in range(count)]
|
||||
devices = rtaudio.getDeviceInfo()
|
||||
|
||||
output_devices = {}
|
||||
for device in devices:
|
||||
|
Loading…
Reference in New Issue
Block a user