Cleaned up recording code. Added start delay to recording

This commit is contained in:
Anne de Jong 2021-05-19 21:19:31 +02:00
parent 7153096552
commit 377291ccf4
3 changed files with 84 additions and 50 deletions

View File

@ -405,6 +405,7 @@ class AvStreamProcess(mp.Process):
# Wrapper functions that safe some typing, they do not require an # Wrapper functions that safe some typing, they do not require an
# explanation. # explanation.
def sendInQueues(self, msg, *data): def sendInQueues(self, msg, *data):
# logging.debug('sendInQueues()')
for q in self.indata_qlist: for q in self.indata_qlist:
# Fan out the input data to all queues in the queue list # Fan out the input data to all queues in the queue list
q.put((msg, data)) q.put((msg, data))

View File

@ -49,14 +49,14 @@ class AvType(Enum):
the stream.""" the stream."""
# Input stream # Input stream
audio_input = auto() audio_input = (0, 'input')
# Output stream # Output stream
audio_output = auto() audio_output = (1, 'output')
# Both input as well as output # Both input as well as output
audio_duplex = auto() audio_duplex = (2, 'duplex')
video = 4 # video = 4
@dataclass_json @dataclass_json

View File

@ -15,10 +15,13 @@ class RecordStatus:
class Recording: class Recording:
"""
Class used to perform a recording.
"""
def __init__(self, fn: str, streammgr: StreamManager, def __init__(self, fn: str, streammgr: StreamManager,
rectime: float = None, wait: bool = True, rectime: float = None, wait: bool = True,
progressCallback=None): progressCallback=None,
startDelay: float=0):
""" """
Start a recording. Blocks if wait is set to True. Start a recording. Blocks if wait is set to True.
@ -31,6 +34,8 @@ class Recording:
to None, or np.inf, the recording continues indefintely. to None, or np.inf, the recording continues indefintely.
progressCallback: callable that is called with an instance of progressCallback: callable that is called with an instance of
RecordStatus instance as argument. RecordStatus instance as argument.
startDelay: Optional delay added before the recording is *actually*
started in [s].
""" """
ext = '.h5' ext = '.h5'
if ext not in fn: if ext not in fn:
@ -39,33 +44,37 @@ class Recording:
self.smgr = streammgr self.smgr = streammgr
self.metadata = None self.metadata = None
assert startDelay >= 0
self.startDelay = startDelay
# Flag used to indicate that we have passed the start delay
self.startDelay_passed = False
self.rectime = rectime self.rectime = rectime
self._fn = fn self.fn = fn
self._video_frame_positions = [] self.video_frame_positions = []
self._curT_rounded_to_seconds = 0 self.curT_rounded_to_seconds = 0
# Counter of the number of blocks # Counter of the number of blocks
self._ablockno = 0 self.ablockno = 0
self._vframeno = 0 self.vframeno = 0
self._progressCallback = progressCallback self.progressCallback = progressCallback
self._wait = wait self.wait = wait
self._f = h5py.File(self._fn, 'w') self.f = h5py.File(self.fn, 'w')
# This flag is used to delete the file on finish(), and can be used # This flag is used to delete the file on finish(), and can be used
# when a recording is canceled. # when a recording is canceled.
self._deleteFile = False self.deleteFile = False
try: try:
# Input queue # Input queue
self.inq = streammgr.addListener() self.inq = streammgr.addInQueueListener()
except RuntimeError: except RuntimeError:
# Cleanup stuff, something is going wrong when starting the stream # Cleanup stuff, something is going wrong when starting the stream
try: try:
self._f.close() self.f.close()
except Exception as e: except Exception as e:
logging.error( logging.error(
'Error preliminary closing measurement file {fn}: {str(e)}') 'Error preliminary closing measurement file {fn}: {str(e)}')
@ -77,15 +86,15 @@ class Recording:
streammgr.getStreamStatus(AvType.audio_input) streammgr.getStreamStatus(AvType.audio_input)
streammgr.getStreamStatus(AvType.audio_duplex) streammgr.getStreamStatus(AvType.audio_duplex)
self._ad = None self.ad = None
logging.debug('Starting record....') logging.debug('Starting record....')
# TODO: Fix this later when we want video # TODO: Fix this later when we want video
# if stream.hasVideo(): # if stream.hasVideo():
# stream.addCallback(self._aCallback, AvType.audio_input) # stream.addCallback(self.aCallback, AvType.audio_input)
self.stop = False self.stop = False
if self._wait: if self.wait:
logging.debug('Stop recording with CTRL-C') logging.debug('Stop recording with CTRL-C')
try: try:
while not self.stop: while not self.stop:
@ -107,8 +116,10 @@ class Recording:
""" """
# logging.debug('handleQueue()')
while self.inq.qsize() > 0: while self.inq.qsize() > 0:
msg, data = self.inq.get() msg, data = self.inq.get()
# logging.debug(f'Obtained message: {msg}')
if msg == StreamMsg.streamData: if msg == StreamMsg.streamData:
samples, = data samples, = data
self.__addTimeData(samples) self.__addTimeData(samples)
@ -117,12 +128,15 @@ class Recording:
avtype, metadata = data avtype, metadata = data
if metadata is None: if metadata is None:
raise RuntimeError('BUG: no stream metadata') raise RuntimeError('BUG: no stream metadata')
if avtype in (AvType.audio_duplex, AvType.audio_input):
self.processStreamMetaData(metadata) self.processStreamMetaData(metadata)
elif msg == StreamMsg.streamMetaData: elif msg == StreamMsg.streamMetaData:
logging.debug(f'handleQueue obtained message {msg}') logging.debug(f'handleQueue obtained message {msg}')
avtype, metadata = data avtype, metadata = data
if metadata is not None: if metadata is not None:
self.processStreamMetaData(metadata) self.processStreamMetaData(metadata)
elif msg == StreamMsg.streamTemporaryError:
pass
else: else:
logging.debug(f'handleQueue obtained message {msg}') logging.debug(f'handleQueue obtained message {msg}')
# An error occured, we do not remove the file, but we stop. # An error occured, we do not remove the file, but we stop.
@ -138,13 +152,20 @@ class Recording:
""" """
logging.debug('Recording::processStreamMetaData()') logging.debug('Recording::processStreamMetaData()')
if self.metadata is not None:
# Metadata already obtained. We check whether the new metadata is
# compatible. Otherwise an error occurs
if md != self.metadata:
raise RuntimeError('BUG: Incompatible stream metadata!')
return
# The 'Audio' dataset as specified in lasp_measurement, where data is # The 'Audio' dataset as specified in lasp_measurement, where data is
# send to. We use gzip as compression, this gives moderate a moderate # send to. We use gzip as compression, this gives moderate a moderate
# compression to the data. # compression to the data.
f = self._f f = self.f
blocksize = md.blocksize blocksize = md.blocksize
nchannels = len(md.in_ch) nchannels = len(md.in_ch)
self._ad = f.create_dataset('audio', self.ad = f.create_dataset('audio',
(1, blocksize, nchannels), (1, blocksize, nchannels),
dtype=md.dtype, dtype=md.dtype,
maxshape=( maxshape=(
@ -160,7 +181,7 @@ class Recording:
# with audio. # with audio.
# if smgr.hasVideo(): # if smgr.hasVideo():
# video_x, video_y = smgr.video_x, smgr.video_y # video_x, video_y = smgr.video_x, smgr.video_y
# self._vd = f.create_dataset('video', # self.vd = f.create_dataset('video',
# (1, video_y, video_x, 3), # (1, video_y, video_x, 3),
# dtype='uint8', # dtype='uint8',
# maxshape=( # maxshape=(
@ -188,7 +209,7 @@ class Recording:
the recording. Typically used for cleaning up after canceling a the recording. Typically used for cleaning up after canceling a
recording. recording.
""" """
self._deleteFile = val self.deleteFile = val
def finish(self): def finish(self):
""" """
@ -201,22 +222,22 @@ class Recording:
# TODO: Fix when video # TODO: Fix when video
# if smgr.hasVideo(): # if smgr.hasVideo():
# smgr.removeCallback(self._vCallback, AvType.video_input) # smgr.removeCallback(self.vCallback, AvType.video_input)
# self._f['video_frame_positions'] = self._video_frame_positions # self.f['video_frame_positions'] = self.video_frame_positions
try: try:
smgr.removeListener(self.inq) smgr.removeInQueueListener(self.inq)
except Exception as e: except Exception as e:
logging.error(f'Could not remove queue from smgr: {e}') logging.error(f'Could not remove queue from smgr: {e}')
try: try:
# Close the recording file # Close the recording file
self._f.close() self.f.close()
except Exception as e: except Exception as e:
logging.error(f'Error closing file: {e}') logging.error(f'Error closing file: {e}')
logging.debug('Recording ended') logging.debug('Recording ended')
if self._deleteFile: if self.deleteFile:
self.__deleteFile() self.__deleteFile()
def __deleteFile(self): def __deleteFile(self):
@ -224,9 +245,9 @@ class Recording:
Cleanup the recording file. Cleanup the recording file.
""" """
try: try:
os.remove(self._fn) os.remove(self.fn)
except Exception as e: except Exception as e:
logging.error(f'Error deleting file: {self._fn}') logging.error(f'Error deleting file: {self.fn}')
def __addTimeData(self, indata): def __addTimeData(self, indata):
""" """
@ -248,39 +269,51 @@ class Recording:
self.smgr.getStreamStatus(AvType.audio_duplex) self.smgr.getStreamStatus(AvType.audio_duplex)
return return
curT = self._ablockno*self.blocksize/self.fs curT = self.ablockno*self.blocksize/self.fs
# Increase the block counter
self.ablockno += 1
if curT < self.startDelay and not self.startDelay_passed:
# Start delay has not been passed
return
elif curT >= 0 and not self.startDelay_passed:
# Start delay passed, switch the flag!
self.startDelay_passed = True
# Reset the audio block counter and the time
self.ablockno = 1
curT = 0
recstatus = RecordStatus( recstatus = RecordStatus(
curT=curT, curT=curT,
done=False) done=False)
if self._progressCallback is not None: if self.progressCallback is not None:
self._progressCallback(recstatus) self.progressCallback(recstatus)
curT_rounded_to_seconds = int(curT) curT_rounded_to_seconds = int(curT)
if curT_rounded_to_seconds > self._curT_rounded_to_seconds: if curT_rounded_to_seconds > self.curT_rounded_to_seconds:
self._curT_rounded_to_seconds = curT_rounded_to_seconds self.curT_rounded_to_seconds = curT_rounded_to_seconds
print(f'{curT_rounded_to_seconds}', end='', flush=True) print(f'{curT_rounded_to_seconds}', end='', flush=True)
else: else:
print('.', end='', flush=True) print('.', end='', flush=True)
if self.rectime is not None and curT > self.rectime: if self.rectime is not None and curT > self.rectime:
# We are done! # We are done!
if self._progressCallback is not None: if self.progressCallback is not None:
recstatus.done = True recstatus.done = True
self._progressCallback(recstatus) self.progressCallback(recstatus)
self.stop = True self.stop = True
return return
# Add the data to the file # Add the data to the file, and resize the audio data blocks
self._ad.resize(self._ablockno+1, axis=0) self.ad.resize(self.ablockno, axis=0)
self._ad[self._ablockno, :, :] = indata self.ad[self.ablockno-1, :, :] = indata
# Increase the block counter
self._ablockno += 1
# def _vCallback(self, frame, framectr): # def _vCallback(self, frame, framectr):
# self._video_frame_positions.append(self._ablockno()) # 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
# self._vframeno += 1 # self.vframeno += 1