diff --git a/lasp/device/CMakeLists.txt b/lasp/device/CMakeLists.txt index 25920ce..a2e787b 100644 --- a/lasp/device/CMakeLists.txt +++ b/lasp/device/CMakeLists.txt @@ -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) diff --git a/lasp/device/lasp_daqconfig.py b/lasp/device/lasp_daqconfig.py index c750047..ca8ba20 100644 --- a/lasp/device/lasp_daqconfig.py +++ b/lasp/device/lasp_daqconfig.py @@ -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 diff --git a/lasp/device/lasp_rtaudio.pyx b/lasp/device/lasp_rtaudio.pyx index eeb6341..6232366 100644 --- a/lasp/device/lasp_rtaudio.pyx +++ b/lasp/device/lasp_rtaudio.pyx @@ -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 = (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 = 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 = 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[ 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 = malloc(sizeof(PyStreamData)) + if self.sd == NULL: raise MemoryError('Could not allocate stream: memory error.') - self._stream.pyCallback = avstream._audioCallback + self.sd.pyCallback = avstream._audioCallback # Increase reference count to the callback Py_INCREF( 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 = 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 = 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, - self._stream, + 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, - self._stream) + self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction, + 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) - - diff --git a/lasp/device/lasp_uldaq.pyx b/lasp/device/lasp_uldaq.pyx index f110cbe..deddfa9 100644 --- a/lasp/device/lasp_uldaq.pyx +++ b/lasp/device/lasp_uldaq.pyx @@ -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, 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 = voidsd + ninchannels = sd.ninchannels + noutchannels = sd.noutchannels + + nBytesPerChan = sd.nBytesPerChan + nFramesPerBlock = sd.nFramesPerBlock + + + with gil: + npy_format = cnp.NPY_FLOAT64 + callback = 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 = 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 = malloc(sizeof(PyStreamData)) + if self.sd == NULL: + raise MemoryError('Could not allocate stream: memory error.') - # Returns: None - # """ + self.sd.pyCallback = avstream._audioCallback + # Increase reference count to the callback + Py_INCREF( 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 = avstream._audioCallback - # # Increase reference count to the callback - # Py_INCREF( avstream._audioCallback) + with nogil: + self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction, + 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 = 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( 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 = 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, - # # 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, - # 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( 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) diff --git a/lasp/lasp_avstream.py b/lasp/lasp_avstream.py index 6f48535..03535a0 100644 --- a/lasp/lasp_avstream.py +++ b/lasp/lasp_avstream.py @@ -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() diff --git a/scripts/lasp_record b/scripts/lasp_record index 743bb0e..cb5c5d0 100755 --- a/scripts/lasp_record +++ b/scripts/lasp_record @@ -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: diff --git a/scripts/lasp_siggen b/scripts/lasp_siggen index e906093..c3b1524 100755 --- a/scripts/lasp_siggen +++ b/scripts/lasp_siggen @@ -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: