Added code for monitor channel in stream and in recording. Updated comments. Lowered

tracing of siggen.
This commit is contained in:
Anne de Jong 2020-02-25 14:35:49 +01:00
parent 0940179f50
commit 173bf10273
5 changed files with 107 additions and 63 deletions

View File

@ -5,7 +5,7 @@
// Description: // Description:
// Signal generator implementation // Signal generator implementation
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
/* #define TRACERPLUS (-5) */ #define TRACERPLUS (-5)
#include "lasp_siggen.h" #include "lasp_siggen.h"
#include "lasp_alloc.h" #include "lasp_alloc.h"
#include "lasp_assert.h" #include "lasp_assert.h"

View File

@ -42,6 +42,8 @@ class DAQConfiguration:
Initialize a device descriptor Initialize a device descriptor
Args: Args:
duplex_mode: Set device to duplex mode, if possible
monitor_gen: If set to true, add monitor channel to recording.
input_device_name: ASCII name with which to open the device when connected input_device_name: ASCII name with which to open the device when connected
outut_device_name: ASCII name with which to open the device when connected outut_device_name: ASCII name with which to open the device when connected
@ -75,6 +77,7 @@ class DAQConfiguration:
en_output_rate: int en_output_rate: int
en_input_channels: list en_input_channels: list
monitor_gen: bool = False
def getEnabledChannels(self): def getEnabledChannels(self):
en_channels = [] en_channels = []

View File

@ -6,6 +6,7 @@ Description: Read data from image stream and record sound at the same time
import cv2 as cv import cv2 as cv
from .lasp_atomic import Atomic from .lasp_atomic import Atomic
from threading import Thread, Condition, Lock from threading import Thread, Condition, Lock
import numpy as np
import time import time
from .device import (RtAudio, DeviceInfo, DAQConfiguration, from .device import (RtAudio, DeviceInfo, DAQConfiguration,
@ -18,36 +19,31 @@ video_x, video_y = 640, 480
class AvType: class AvType:
""" """Specificying the type of data, for adding and removing callbacks from
Specificying the type of data, for adding and removing callbacks from the the stream."""
stream. audio_input = 1
""" audio_output = 2
audio_input = 0 video = 4
audio_output = 1
video = 2
class AvStream: class AvStream:
""" """Audio and video data stream, to which callbacks can be added for
Audio and video data stream, to which callbacks can be added for processing processing the data."""
the data.
"""
def __init__(self, def __init__(self,
device: DeviceInfo, device: DeviceInfo,
avtype: AvType, avtype: AvType,
daqconfig: DAQConfiguration, daqconfig: DAQConfiguration,
video=None): video=None):
""" """Open a stream for audio in/output and video input. For audio output,
Open a stream for audio in/output and video input. For audio output, by by default all available channels are opened for outputting data.
default all available channels are opened for outputting data.
Args: Args:
device: DeviceInfo for the audio device device: DeviceInfo for the audio device
avtype: Type of stream. Input, output or duplex avtype: Type of stream. Input, output or duplex
daqconfig: DAQConfiguration instance. If duplex mode flag is set, daqconfig: DAQConfiguration instance. If duplex mode flag is set,
please make sure that no output_device is None, as in that case the please make sure that output_device is None, as in that case the
output config will be taken from the input device. output config will be taken from the input device.
video: video:
""" """
@ -56,13 +52,18 @@ class AvStream:
self._device = device self._device = device
self.avtype = avtype self.avtype = avtype
self.duplex_mode = daqconfig.duplex_mode self.duplex_mode = daqconfig.duplex_mode
self.monitor_gen = daqconfig.monitor_gen
# Determine highest input channel number # Determine highest input channel number
channelconfigs = daqconfig.en_input_channels channelconfigs = daqconfig.en_input_channels
self.channel_names = [] self.channel_names = []
self.sensitivity = self.daqconfig.getSensitivities() self.sensitivity = self.daqconfig.getSensitivities()
if daqconfig.monitor_gen:
assert self.duplex_mode
self.channel_names.append('Generated signal')
self.sensitivity = np.concatenate([np.array([1.]),
self.sensitivity])
rtaudio_inputparams = None rtaudio_inputparams = None
rtaudio_outputparams = None rtaudio_outputparams = None
@ -103,14 +104,17 @@ class AvStream:
except Exception as e: except Exception as e:
raise RuntimeError(f'Could not initialize DAQ device: {str(e)}') raise RuntimeError(f'Could not initialize DAQ device: {str(e)}')
# Fill in numpy data type, and sample width
self.numpy_dtype = get_numpy_dtype_from_format_string( self.numpy_dtype = get_numpy_dtype_from_format_string(
self.sampleformat) self.sampleformat)
self.sampwidth = get_sampwidth_from_format_string( self.sampwidth = get_sampwidth_from_format_string(
self.sampleformat) self.sampleformat)
# Counters for the number of frames that have been coming in
self._aframectr = Atomic(0) self._aframectr = Atomic(0)
self._vframectr = Atomic(0) self._vframectr = Atomic(0)
# Lock
self._callbacklock = Lock() self._callbacklock = Lock()
self._running = Atomic(False) self._running = Atomic(False)
@ -118,32 +122,34 @@ class AvStream:
self._video = video self._video = video
self._video_started = Atomic(False) self._video_started = Atomic(False)
# Storage for callbacks, specified by type
self._callbacks = { self._callbacks = {
AvType.audio_input: [], AvType.audio_input: [],
AvType.audio_output: [], AvType.audio_output: [],
AvType.video: [] AvType.video: []
} }
# Possible, but long not tested: store video
self._videothread = None self._videothread = None
def close(self): def close(self):
self._rtaudio.closeStream() self._rtaudio.closeStream()
self._rtaudio = None
def nCallbacks(self): def nCallbacks(self):
""" """Returns the current number of installed callbacks."""
Returns the current number of installed callbacks
"""
return len(self._callbacks[AvType.audio_input]) + \ return len(self._callbacks[AvType.audio_input]) + \
len(self._callbacks[AvType.audio_output]) + \ len(self._callbacks[AvType.audio_output]) + \
len(self._callbacks[AvType.video]) len(self._callbacks[AvType.video])
def addCallback(self, cb: callable, cbtype: AvType): def addCallback(self, cb: callable, cbtype: AvType):
""" """Add as stream callback to the list of callbacks."""
Add as stream callback to the list of callbacks
"""
with self._callbacklock: with self._callbacklock:
outputcallbacks = self._callbacks[AvType.audio_output] outputcallbacks = self._callbacks[AvType.audio_output]
if cbtype == AvType.audio_output and len(outputcallbacks) > 0: if cbtype == AvType.audio_output and len(outputcallbacks) > 0:
raise RuntimeError('Only one audio output callback can be allowed') raise RuntimeError(
'Only one audio output callback can be allowed')
if cb not in self._callbacks[cbtype]: if cb not in self._callbacks[cbtype]:
self._callbacks[cbtype].append(cb) self._callbacks[cbtype].append(cb)
@ -154,10 +160,8 @@ class AvStream:
self._callbacks[cbtype].remove(cb) self._callbacks[cbtype].remove(cb)
def start(self): def start(self):
""" """Start the stream, which means the callbacks are called with stream
Start the stream, which means the callbacks are called with stream data (audio/video)"""
data (audio/video)
"""
if self._running: if self._running:
raise RuntimeError('Stream already started') raise RuntimeError('Stream already started')
@ -199,23 +203,33 @@ class AvStream:
print('stopped videothread') print('stopped videothread')
def _audioCallback(self, indata, outdata, nframes, streamtime): def _audioCallback(self, indata, outdata, nframes, streamtime):
""" """This is called (from a separate thread) for each audio block."""
This is called (from a separate thread) for each audio block. self._aframectr += nframes
"""
self._aframectr += 1
with self._callbacklock: with self._callbacklock:
for cb in self._callbacks[AvType.audio_input]:
try: # Count the number of output callbacks. If no output callbacks are
cb(indata, outdata, self._aframectr()) # present, and there should be output callbacks, we explicitly set
except Exception as e: # the output buffer to zero
print(e) noutput_cb = len(self._callbacks[AvType.audio_output])
return 1 shouldhaveoutput = (self.avtype == AvType.audio_output or
self.duplex_mode)
if noutput_cb == 0 and shouldhaveoutput:
outdata[:, :] = 0
# Loop over callbacks
for cb in self._callbacks[AvType.audio_output]: for cb in self._callbacks[AvType.audio_output]:
try: try:
cb(indata, outdata, self._aframectr()) cb(indata, outdata, self._aframectr())
except Exception as e: except Exception as e:
print(e) print(e)
return 1 return 1
for cb in self._callbacks[AvType.audio_input]:
try:
cb(indata, outdata, self._aframectr())
except Exception as e:
print(e)
return 1
return 0 if self._running else 1 return 0 if self._running else 1
def stop(self): def stop(self):

View File

@ -164,6 +164,12 @@ class Measurement:
self.N = (self.nblocks * self.blocksize) self.N = (self.nblocks * self.blocksize)
self.T = self.N / self.samplerate self.T = self.N / self.samplerate
try:
self._channel_names = f.attrs['channel_names']
except KeyError:
# No channel names found in measurement file
self._channel_names = ['{i}' for i in range(self.nchannels)]
# comment = read-write thing # comment = read-write thing
try: try:
self._comment = f.attrs['comment'] self._comment = f.attrs['comment']
@ -189,6 +195,10 @@ class Measurement:
""" """
return os.path.splitext(self.fn_base)[0] return os.path.splitext(self.fn_base)[0]
@property
def channelNames(self):
return self._channel_names
@contextmanager @contextmanager
def file(self, mode='r'): def file(self, mode='r'):
""" """

View File

@ -3,7 +3,6 @@
""" """
Read data from stream and record sound and video at the same time Read data from stream and record sound and video at the same time
""" """
import numpy as np
from .lasp_atomic import Atomic from .lasp_atomic import Atomic
from threading import Condition from threading import Condition
from .lasp_avstream import AvType, AvStream from .lasp_avstream import AvType, AvStream
@ -19,13 +18,15 @@ class RecordStatus:
class Recording: class Recording:
def __init__(self, fn, stream, rectime=None, wait = True, def __init__(self, fn: str, stream: AvStream,
rectime: float=None, wait: bool = True,
progressCallback=None): progressCallback=None):
""" """
Args: Args:
fn: Filename to record to. extension is added fn: Filename to record to. extension is added
stream: AvStream instance to record from stream: AvStream instance to record from. Should have input
channels!
rectime: Recording time, None for infinite rectime: Recording time, None for infinite
""" """
ext = '.h5' ext = '.h5'
@ -33,6 +34,8 @@ class Recording:
fn += ext fn += ext
self._stream = stream self._stream = stream
if stream.avtype != AvType.audio_input:
raise RuntimeError('Stream does not have any input channels')
self.blocksize = stream.blocksize self.blocksize = stream.blocksize
self.samplerate = stream.samplerate self.samplerate = stream.samplerate
self._running = Atomic(False) self._running = Atomic(False)
@ -43,7 +46,7 @@ class Recording:
self._video_frame_positions = [] self._video_frame_positions = []
self._curT_rounded_to_seconds = 0 self._curT_rounded_to_seconds = 0
self._aframeno = Atomic(0) self._ablockno = Atomic(0)
self._vframeno = 0 self._vframeno = 0
self._progressCallback = progressCallback self._progressCallback = progressCallback
@ -53,28 +56,38 @@ class Recording:
self._deleteFile = False self._deleteFile = False
def setDelete(self, val: bool): def setDelete(self, val: bool):
"""
Set the delete flag. If set, measurement file is deleted at the end of
the recording. Typically used for cleaning up after canceling a
recording.
"""
self._deleteFile = val self._deleteFile = val
def __enter__(self): def __enter__(self):
""" """
with self._recording(wait=False): with Recording(fn, stream, wait=False):
event_loop_here() event_loop_here()
or: or:
with Recording(wait=True): with Recording(fn, stream, wait=True):
pass pass
""" """
stream = self._stream stream = self._stream
f = self._f f = self._f
nchannels = stream.nchannels
if stream.monitor_gen:
nchannels += 1
self.monitor_gen = stream.monitor_gen
self._ad = f.create_dataset('audio', self._ad = f.create_dataset('audio',
(1, stream.blocksize, stream.nchannels), (1, stream.blocksize, nchannels),
dtype=stream.numpy_dtype, dtype=stream.numpy_dtype,
maxshape=(None, stream.blocksize, maxshape=(None, stream.blocksize,
stream.nchannels), nchannels),
compression='gzip' compression='gzip'
) )
if stream.hasVideo(): if stream.hasVideo():
@ -91,6 +104,7 @@ class Recording:
f.attrs['nchannels'] = stream.nchannels f.attrs['nchannels'] = stream.nchannels
f.attrs['blocksize'] = stream.blocksize f.attrs['blocksize'] = stream.blocksize
f.attrs['sensitivity'] = stream.sensitivity f.attrs['sensitivity'] = stream.sensitivity
f.attrs['channel_names'] = stream.channel_names
f.attrs['time'] = time.time() f.attrs['time'] = time.time()
self._running <<= True self._running <<= True
@ -119,7 +133,7 @@ class Recording:
stream.removeCallback(self._aCallback, AvType.audio_input) stream.removeCallback(self._aCallback, AvType.audio_input)
if stream.hasVideo(): if stream.hasVideo():
stream.removeCallback(self._vCallback, AvType.video_input) stream.removeCallback(self._vCallback, AvType.video_input)
f['video_frame_positions'] = self._video_frame_positions self._f['video_frame_positions'] = self._video_frame_positions
self._f.close() self._f.close()
print('\nEnding record') print('\nEnding record')
@ -130,10 +144,9 @@ class Recording:
print(f'Error deleting file: {self._fn}') print(f'Error deleting file: {self._fn}')
def _aCallback(self, indata, outdata, aframe): def _aCallback(self, indata, outdata, aframe):
curT = self._aframeno()*self.blocksize/self.samplerate curT = self._ablockno()*self.blocksize/self.samplerate
recstatus = RecordStatus( recstatus = RecordStatus(
curT = curT, curT = curT,
done = False) done = False)
@ -157,12 +170,16 @@ class Recording:
self._progressCallback(recstatus) self._progressCallback(recstatus)
return return
self._ad.resize(self._aframeno()+1, axis=0) self._ad.resize(self._ablockno()+1, axis=0)
self._ad[self._aframeno(), :, :] = indata if self.monitor_gen:
self._aframeno += 1 self._ad[self._ablockno(), :, 0] = outdata
self._ad[self._ablockno(), :, 1:] = indata
else:
self._ad[self._ablockno(), :, :] = indata
self._ablockno += 1
def _vCallback(self, frame, framectr): def _vCallback(self, frame, framectr):
self._video_frame_positions.append(self._aframeno()) self._video_frame_positions.append(self._ablockno())
vframeno = self._vframeno vframeno = self._vframeno
self._vd.resize(vframeno+1, axis=0) self._vd.resize(vframeno+1, axis=0)
self._vd[vframeno, :, :] = frame self._vd[vframeno, :, :] = frame