Almost working on multiple apis

This commit is contained in:
Anne de Jong 2020-11-05 17:13:09 +01:00
parent c1f713c8fb
commit 4e0c09d356
8 changed files with 155 additions and 109 deletions

View File

@ -29,14 +29,14 @@ vector<DaqApi> DaqApi::getAvailableApis() {
vector<DaqApi> apis;
apis.resize(6);
#ifdef HAS_ULDAQ_API
apis[uldaqapi.apicode] = uldaqapi;
apis.at(uldaqapi.apicode) = uldaqapi;
#endif
#ifdef HAS_RTAUDIO_API
apis[rtaudioAlsaApi.apicode] = rtaudioAlsaApi;
apis[rtaudioPulseaudioApi.apicode] = rtaudioPulseaudioApi;
apis[rtaudioWasapiApi.apicode] = rtaudioWasapiApi;
apis[rtaudioDsApi.apicode] = rtaudioDsApi;
apis[rtaudioAsioApi.apicode] = rtaudioAsioApi;
apis.at(rtaudioAlsaApi.apicode) = rtaudioAlsaApi;
apis.at(rtaudioPulseaudioApi.apicode) = rtaudioPulseaudioApi;
apis.at(rtaudioWasapiApi.apicode) = rtaudioWasapiApi;
apis.at(rtaudioDsApi.apicode) = rtaudioDsApi;
apis.at(rtaudioAsioApi.apicode) = rtaudioAsioApi;
#endif
return apis;
}
@ -85,26 +85,26 @@ bool DaqConfiguration::match(const DeviceInfo& dev) const {
int DaqConfiguration::getHighestInChannel() const {
for(int i=eninchannels.size()-1; i>-1;i--) {
if(eninchannels[i]) return i;
if(eninchannels.at(i)) return i;
}
return -1;
}
int DaqConfiguration::getHighestOutChannel() const {
for(us i=enoutchannels.size()-1; i>=0;i--) {
if(enoutchannels[i]) return i;
if(enoutchannels.at(i)) return i;
}
return -1;
}
int DaqConfiguration::getLowestInChannel() const {
for(us i=0; i<eninchannels.size();i++) {
if(eninchannels[i]) return i;
if(eninchannels.at(i)) return i;
}
return -1;
}
int DaqConfiguration::getLowestOutChannel() const {
for(us i=0; i<enoutchannels.size();i++) {
if(enoutchannels[i]) return i;
if(enoutchannels.at(i)) return i;
}
return -1;
}
@ -118,15 +118,29 @@ Daq *Daq::createDaq(const DeviceInfo& devinfo,
// Some basic sanity checks
if ((devinfo.ninchannels != config.eninchannels.size())) {
/* cerr << "devinfo.ninchannels: " << devinfo.ninchannels << endl; */
/* cerr << "config.eninchannels.size(): " << config.eninchannels.size() << endl; */
throw runtime_error("Invalid length of enabled input channels specified");
}
if ((devinfo.noutchannels != config.enoutchannels.size())) {
throw runtime_error("outvalid length of enabled output channels specified");
}
if (devinfo.api == uldaqapi) {
int apicode = devinfo.api.apicode;
if(devinfo.api == DaqApi()) {
throw std::runtime_error(string("Unable to match API: ") + devinfo.api.apiname);
}
#ifdef HAS_ULDAQ_API
else if (devinfo.api == uldaqapi) {
return createUlDaqDevice(devinfo, config);
} else {
}
#endif
#ifdef HAS_RTAUDIO_API
else if(apicode >= 1 && apicode <= 5) {
return createRtAudioDevice(devinfo, config);
}
#endif
else {
throw std::runtime_error(string("Unable to match API: ") +
devinfo.api.apiname);
}
@ -154,14 +168,14 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
double Daq::samplerate() const {
mutexlock lock(mutex);
assert(sampleRateIndex < availableSampleRates.size());
return availableSampleRates[sampleRateIndex];
return availableSampleRates.at(sampleRateIndex);
}
DataType Daq::dataType() const {
mutexlock lock(mutex);
assert((us)dataTypeIndex < availableDataTypes.size());
return availableDataTypes[dataTypeIndex];
return availableDataTypes.at(dataTypeIndex);
}
@ -171,7 +185,7 @@ double Daq::inputRangeForChannel(us ch) const {
}
mutexlock lock(mutex);
assert(inputRangeIndices.size() == eninchannels.size());
return availableInputRanges[inputRangeIndices[ch]];
return availableInputRanges.at(inputRangeIndices.at(ch));
}

View File

@ -120,7 +120,7 @@ public:
double prefSampleRate() const {
if (((us)prefSampleRateIndex < availableSampleRates.size()) &&
(prefSampleRateIndex >= 0)) {
return availableSampleRates[prefSampleRateIndex];
return availableSampleRates.at(prefSampleRateIndex);
} else {
throw std::runtime_error("No prefered sample rate available");
}
@ -209,7 +209,7 @@ public:
us framesPerBlock() const {
mutexlock lock(mutex);
return availableFramesPerBlock[framesPerBlockIndex];
return availableFramesPerBlock.at(framesPerBlockIndex);
}
bool duplexMode() const {
return (neninchannels(false) > 0 && nenoutchannels() > 0);

View File

@ -116,17 +116,22 @@ class AudioDaq: public Daq {
nFramesPerBlock = this->framesPerBlock();
if(neninchannels(false) > 0) {
instreamparams = new RtAudio::StreamParameters();
instreamparams->nChannels = getHighestInChannel();
instreamparams->nChannels = getHighestInChannel() + 1;
if(instreamparams->nChannels < 1) {
throw runtime_error("Invalid input number of channels");
}
instreamparams->firstChannel = 0;
instreamparams->deviceId = devinfo.api_specific_devindex;
}
if(nenoutchannels() > 0) {
outstreamparams = new RtAudio::StreamParameters();
outstreamparams->nChannels = getHighestOutChannel();
outstreamparams->nChannels = getHighestOutChannel() + 1;
if(outstreamparams->nChannels < 1) {
throw runtime_error("Invalid output number of channels");
}
outstreamparams->firstChannel = 0;
outstreamparams->deviceId = devinfo.api_specific_devindex;
}

View File

@ -42,6 +42,15 @@ public:
DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config)
: Daq(devinfo, config) {
// Some sanity checks
if (eninchannels.size() != 4) {
throw runtime_error("Invalid length of enabled inChannels vector");
}
if (enoutchannels.size() != 1) {
throw runtime_error("Invalid length of enabled outChannels vector");
}
stopThread = false;
nFramesPerBlock = availableFramesPerBlock[framesPerBlockIndex];

View File

@ -6,5 +6,8 @@ cdef class Daq:
cdef:
PyStreamData *sd
cppDaq* daq_device
cdef public:
double samplerate
unsigned int nFramesPerBlock
cdef cleanupStream(self, PyStreamData* sd)

View File

@ -7,7 +7,7 @@ from .lasp_device_common import AvType
__all__ = ['Daq']
cdef cnp.NPY_TYPES getNumpyDataType(DataType& dt):
cdef cnp.NPY_TYPES getCNumpyDataType(DataType& dt):
if(dt == dtype_fl32):
return cnp.NPY_FLOAT32
elif(dt == dtype_fl64):
@ -21,6 +21,19 @@ cdef cnp.NPY_TYPES getNumpyDataType(DataType& dt):
else:
raise ValueError('Unknown data type')
cdef getNumpyDataType(DataType& dt):
if(dt == dtype_fl32):
return np.float32
elif(dt == dtype_fl64):
return np.float64
elif(dt == dtype_int8):
return np.int8
elif(dt == dtype_int16):
return np.int16
elif(dt == dtype_int32):
return np.int32
else:
raise ValueError('Unknown data type')
ctypedef struct PyStreamData:
@ -132,21 +145,47 @@ cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
cdef class Daq:
def __cinit__(self):
def __cinit__(self, DeviceInfo pydevinfo, DaqConfiguration pydaqconfig):
"""
Acquires a daq handle, and opens the device
"""
cdef:
cppDaqConfiguration* daqconfig
cppDeviceInfo* devinfo
vector[cppDeviceInfo] devinfos
self.daq_device = NULL
self.sd = NULL
daqconfig = &(pydaqconfig.config)
devinfo = &(pydevinfo.devinfo)
self.daq_device = cppDaq.createDaq(devinfo[0], daqconfig[0])
self.nFramesPerBlock = self.daq_device.framesPerBlock()
self.samplerate = self.daq_device.samplerate()
if self.nFramesPerBlock > 8192 or self.nFramesPerBlock < 512:
del self.daq_device
raise ValueError('Invalid number of nFramesPerBlock')
def __dealloc__(self):
# fprintf(stderr, "UlDaq.__dealloc__\n")
if self.sd is not NULL:
fprintf(stderr, "UlDaq.__dealloc__: stopping stream.\n")
self.stop()
if self.daq_device is not NULL:
del self.daq_device
self.daq_device = NULL
def getNumpyDataType(self):
cdef:
DataType dt = self.daq_device.dataType()
return getNumpyDataType(dt)
def isRunning(self):
return self.sd is not NULL
@ -166,7 +205,7 @@ cdef class Daq:
return pydevinfo
@cython.nonecheck(True)
def start(self, avstream):
def start(self, audiocallback):
"""
Opens a stream with specified parameters
@ -175,36 +214,15 @@ cdef class Daq:
Returns: None
"""
cdef:
DaqConfiguration pydaqconfig
DeviceInfo pydevinfo
cppDaqConfiguration* daqconfig
cppDeviceInfo* devinfo
vector[cppDeviceInfo] devinfos
if self.sd is not NULL:
assert self.daq_device is not NULL
raise RuntimeError('Stream is already opened.')
pydaqconfig = avstream.daqconfig
avtype = avstream.avtype
pydevinfo = avstream.deviceinfo
daqconfig = &(pydaqconfig.config)
devinfo = &(pydevinfo.devinfo)
self.daq_device = cppDaq.createDaq(devinfo[0], daqconfig[0])
cdef:
cppDaq* daq = self.daq_device
cdef:
unsigned int nFramesPerBlock = daq.framesPerBlock()
double samplerate = daq.samplerate()
DataType dtype = daq.dataType()
if nFramesPerBlock > 8192 or nFramesPerBlock < 512:
del self.daq_device
raise ValueError('Invalid number of nFramesPerBlock')
unsigned nFramesPerBlock = self.nFramesPerBlock
DataType dtype = self.daq_device.dataType()
double samplerate = self.samplerate
# All set, allocate the stream!
@ -225,17 +243,17 @@ cdef class Daq:
self.sd.noutchannels = daq.nenoutchannels()
self.sd.nBytesPerChan = nFramesPerBlock*dtype.sw
self.sd.nFramesPerBlock = nFramesPerBlock
self.sd.npy_format = getNumpyDataType(dtype)
self.sd.npy_format = getCNumpyDataType(dtype)
if daq.neninchannels() > 0:
self.sd.inQueue = new SafeQueue[void*]()
if daq.nenoutchannels() > 0:
self.sd.inQueue = new SafeQueue[void*]()
self.sd.pyCallback = <PyObject*> avstream._audioCallback
self.sd.pyCallback = <PyObject*> audiocallback
# Increase reference count to the callback
Py_INCREF(<object> avstream._audioCallback)
Py_INCREF(<object> audiocallback)
with nogil:
self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
@ -248,15 +266,11 @@ cdef class Daq:
self.sd.inQueue,
self.sd.outQueue)
return nFramesPerBlock, self.daq_device.samplerate()
return self.daq_device.samplerate()
def stop(self):
if self.sd is NULL:
raise RuntimeError('Stream is not opened')
if self.daq_device is not NULL:
self.daq_device.stop()
del self.daq_device
self.daq_device = NULL
self.cleanupStream(self.sd)
self.sd = NULL

View File

@ -107,6 +107,9 @@ cdef class DaqConfiguration:
config.outchannel_names = [outchname.encode('utf-8') for outchname in
pydict['outchannel_names']]
config.inchannel_sensitivities = pydict['inchannel_sensitivities']
config.outchannel_sensitivities = pydict['outchannel_sensitivities']
config.sampleRateIndex = pydict['sampleRateIndex']
config.framesPerBlockIndex = pydict['framesPerBlockIndex']
config.dataTypeIndex = pydict['dataTypeIndex']
@ -138,6 +141,16 @@ cdef class DaqConfiguration:
outchannel_names = [name.decode('utf-8') for name in
self.config.outchannel_names],
inchannel_sensitivities = [sens for sens in
self.config.inchannel_sensitivities],
outchannel_sensitivities = [sens for sens in
self.config.outchannel_sensitivities],
inchannel_metadata = [inchmeta.decode('utf-8') for inchmeta in
self.config.inchannel_metadata],
outchannel_metadata = [outchmeta.decode('utf-8') for outchmeta in
self.config.outchannel_metadata],
sampleRateIndex = self.config.sampleRateIndex,
dataTypeIndex = self.config.dataTypeIndex,
framesPerBlockIndex = self.config.framesPerBlockIndex,
@ -147,27 +160,23 @@ cdef class DaqConfiguration:
inputACCouplingMode = self.config.inputACCouplingMode,
inputRangeIndices = self.config.inputRangeIndices,
inchannel_metadata = [inchmeta.decode('utf-8') for inchmeta in
self.config.inchannel_metadata],
outchannel_metadata = [outchmeta.decode('utf-8') for outchmeta in
self.config.outchannel_metadata]
))
def getInChannel(self, i:int):
return DaqChannel(
channel_enabled=self.config.eninchannels[i],
channel_name=self.config.inchannel_names[i].decode('utf-8'),
sensitivity=self.config.inchannel_sensitivities[i],
range_index=self.config.inputRangeIndices[i],
ACCoupling_enabled=self.config.inputACCouplingMode[i],
IEPE_enabled=self.config.inputIEPEEnabled[i],
channel_metadata=self.config.inchannel_metadata[i].decode('utf-8'),
channel_enabled=self.config.eninchannels.at(i),
channel_name=self.config.inchannel_names.at(i).decode('utf-8'),
sensitivity=self.config.inchannel_sensitivities.at(i),
range_index=self.config.inputRangeIndices.at(i),
ACCoupling_enabled=self.config.inputACCouplingMode.at(i),
IEPE_enabled=self.config.inputIEPEEnabled.at(i),
channel_metadata=self.config.inchannel_metadata.at(i).decode('utf-8'),
)
def getOutChannel(self, i:int):
return DaqChannel(
channel_enabled=self.config.enoutchannels[i],
channel_name=self.config.outchannel_names[i].decode('utf-8'),
channel_metadata=self.config.outchannel_metadata[i].decode('utf-8'),
channel_enabled=self.config.enoutchannels.at(i),
channel_name=self.config.outchannel_names.at(i).decode('utf-8'),
channel_metadata=self.config.outchannel_metadata.at(i).decode('utf-8'),
)
def setInChannel(self, i:int, ch: DaqChannel):
@ -184,6 +193,25 @@ cdef class DaqConfiguration:
self.config.outchannel_names[i] = ch.channel_name.encode('utf-8')
self.config.outchannel_metadata[i] = ch.channel_metadata.encode('utf-8')
def getEnabledInChannels(self, include_monitor=True):
inch = []
for i, enabled in enumerate(self.config.eninchannels):
if enabled:
inch.append(self.getInChannel(i))
if include_monitor:
outch = self.getEnabledOutChannels()
if len(outch) > 0:
inch.insert(0, outch[0])
return inch
def getEnabledOutChannels(self):
outch = []
for i, enabled in enumerate(self.config.enoutchannels):
if enabled:
outch.append(self.getOutChannel(i))
return outch
@property
def api(self):
return self.config.api.apiname.decode('utf-8')

View File

@ -13,8 +13,6 @@ class DAQConfiguration:
import time
from .device import (Daq, DeviceInfo,
# get_numpy_dtype_from_format_string,
# get_sampwidth_from_format_string,
AvType,
)
@ -29,8 +27,8 @@ class AvStream:
processing the data."""
def __init__(self,
device: DeviceInfo,
avtype: AvType,
device: DeviceInfo,
daqconfig: DAQConfiguration,
video=None):
"""Open a stream for audio in/output and video input. For audio output,
@ -49,46 +47,20 @@ class AvStream:
self.daqconfig = daqconfig
self.device = device
self.avtype = avtype
self.duplex_mode = daqconfig.duplex_mode
self.output_channel_names = [ch.channel_name for ch in daqconfig.getEnabledOutputChannels()]
en_out_ch = daqconfig.getEnabledOutChannels()
en_in_ch = daqconfig.getEnabledInChannels(include_monitor=True)
if daqconfig.monitor_gen:
self.input_channel_names = self.output_channel_names
self.input_sensitivity = daqconfig.getEnabledOutputChannelSensitivities()
self.input_qtys = daqconfig.getEnabledOutputChannelQtys()
else:
self.input_channel_names = []
self.input_sensitivity = []
self.input_qtys = []
self.output_channel_names = [ch.channel_name for ch in en_out_ch]
self.input_channel_names = [ch.channel_name for ch in en_in_ch]
self.input_sensitivity = [ch.sensitivity for ch in en_in_ch]
self.input_qtys = [ch.qty for ch in en_in_ch]
self.input_sensitivity += daqconfig.getEnabledInputChannelSensitivities()
self.input_sensitivity = np.asarray(self.input_sensitivity)
self.input_qtys += daqconfig.getEnabledInputChannelQtys()
# Fill in numpy data type, and sample width
self.input_numpy_dtype = get_numpy_dtype_from_format_string(
daqconfig.en_input_sample_format)
self.input_sampwidth = get_sampwidth_from_format_string(
daqconfig.en_input_sample_format)
self.input_channel_names += [
ch.channel_name for ch in daqconfig.getEnabledInputChannels()]
self.input_samplerate = float(daqconfig.en_input_rate)
if self.duplex_mode:
self.output_samplerate = float(daqconfig.en_input_rate)
self.output_sampwidth = self.input_sampwidth
self.output_numpy_dtype = self.input_numpy_dtype
else:
self.output_samplerate = float(daqconfig.en_output_rate)
self.output_sampwidth = get_sampwidth_from_format_string(
daqconfig.en_output_sample_format)
self.output_numpy_dtype = get_numpy_dtype_from_format_string(
daqconfig.en_output_sample_format)
datatype = daqconfig.dataTypeIndex
# Counters for the number of frames that have been coming in
self._aframectr = Atomic(0)
@ -113,8 +85,9 @@ class AvStream:
self._videothread = None
# self._audiobackend = RtAudio(daqconfig.api)
self._audiobackend = UlDaq()
self.blocksize = daqconfig.nFramesPerBlock
self._daq = Daq(device, daqconfig)
self.blocksize = self._daq.nFramesPerBlock
self.samplerate = self._daq.samplerate
def nCallbacks(self):
"""Returns the current number of installed callbacks."""
@ -154,7 +127,7 @@ class AvStream:
else:
self._video_started <<= True
self.blocksize, self.samplerate = self._audiobackend.start(self)
self.samplerate = self._daq.start(self._audioCallback)
def _videoThread(self):
cap = cv.VideoCapture(self._video)