Cleaned up recording code. Added start delay to recording
This commit is contained in:
parent
7153096552
commit
377291ccf4
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user