Recording is working again. Sometimes it hangs on closing stream.
This commit is contained in:
parent
3ea745245f
commit
0109056b5f
@ -1,6 +1,9 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
cimport numpy as cnp
|
cimport numpy as cnp
|
||||||
|
|
||||||
|
# Do this, our segfaults will be your destination
|
||||||
|
cnp.import_array()
|
||||||
|
|
||||||
DEF LASP_FLOAT = "@LASP_FLOAT@"
|
DEF LASP_FLOAT = "@LASP_FLOAT@"
|
||||||
DEF LASP_DEBUG_CYTHON = "@LASP_DEBUG_CYTHON@"
|
DEF LASP_DEBUG_CYTHON = "@LASP_DEBUG_CYTHON@"
|
||||||
|
|
||||||
|
@ -74,6 +74,16 @@ class DAQConfiguration:
|
|||||||
|
|
||||||
en_input_channels: list
|
en_input_channels: list
|
||||||
|
|
||||||
|
def getEnabledChannels(self):
|
||||||
|
en_channels = []
|
||||||
|
for chan in self.en_input_channels:
|
||||||
|
if chan.channel_enabled:
|
||||||
|
en_channels.append(chan)
|
||||||
|
return en_channels
|
||||||
|
|
||||||
|
def getSensitivities(self):
|
||||||
|
return [float(channel.sensitivity) for channel in self.getEnabledChannels()]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def loadConfigs():
|
def loadConfigs():
|
||||||
"""
|
"""
|
||||||
|
@ -109,12 +109,12 @@ _formats_strkey = {
|
|||||||
'64-bit floats': (RTAUDIO_FLOAT64, 8),
|
'64-bit floats': (RTAUDIO_FLOAT64, 8),
|
||||||
}
|
}
|
||||||
_formats_rtkey = {
|
_formats_rtkey = {
|
||||||
RTAUDIO_SINT8: ('8-bit integers', 1),
|
RTAUDIO_SINT8: ('8-bit integers', 1, cnp.NPY_INT8),
|
||||||
RTAUDIO_SINT16: ('16-bit integers',2),
|
RTAUDIO_SINT16: ('16-bit integers',2, cnp.NPY_INT16),
|
||||||
RTAUDIO_SINT24: ('24-bit integers',3),
|
RTAUDIO_SINT24: ('24-bit integers',3),
|
||||||
RTAUDIO_SINT32: ('32-bit integers',4),
|
RTAUDIO_SINT32: ('32-bit integers',4, cnp.NPY_INT32),
|
||||||
RTAUDIO_FLOAT32: ('32-bit floats', 4),
|
RTAUDIO_FLOAT32: ('32-bit floats', 4, cnp.NPY_FLOAT32),
|
||||||
RTAUDIO_FLOAT64: ('64-bit floats', 8),
|
RTAUDIO_FLOAT64: ('64-bit floats', 8, cnp.NPY_FLOAT64),
|
||||||
}
|
}
|
||||||
cdef class _Stream:
|
cdef class _Stream:
|
||||||
cdef:
|
cdef:
|
||||||
@ -133,14 +133,15 @@ cdef class _Stream:
|
|||||||
# It took me quite a long time to fully understand Cython's idiosyncrasies
|
# It took me quite a long time to fully understand Cython's idiosyncrasies
|
||||||
# concerning C(++) callbacks, the GIL and passing Python objects as pointers
|
# concerning C(++) callbacks, the GIL and passing Python objects as pointers
|
||||||
# into C(++) functions. But finally, here it is!
|
# into C(++) functions. But finally, here it is!
|
||||||
cdef object fromBufferToNPYNoCopy(buffer_format_type,
|
cdef object fromBufferToNPYNoCopy(
|
||||||
|
cnp.NPY_TYPES buffer_format_type,
|
||||||
void* buf,
|
void* buf,
|
||||||
size_t nchannels,
|
size_t nchannels,
|
||||||
size_t nframes):
|
size_t nframes):
|
||||||
cdef cnp.npy_intp[2] dims = [nchannels, nframes];
|
cdef cnp.npy_intp[2] dims = [nchannels, nframes]
|
||||||
|
|
||||||
array = cnp.PyArray_SimpleNewFromData(2, dims, buffer_format_type,
|
array = cnp.PyArray_SimpleNewFromData(2, &dims[0], buffer_format_type,
|
||||||
buf)
|
buf).transpose()
|
||||||
|
|
||||||
return array
|
return array
|
||||||
|
|
||||||
@ -163,7 +164,9 @@ cdef int audioCallback(void* outputBuffer,
|
|||||||
Calls the Python callback function and converts data
|
Calls the Python callback function and converts data
|
||||||
|
|
||||||
"""
|
"""
|
||||||
cdef int rval = 0
|
cdef:
|
||||||
|
int rval = 0
|
||||||
|
cnp.NPY_TYPES npy_format
|
||||||
|
|
||||||
with gil:
|
with gil:
|
||||||
if status == RTAUDIO_INPUT_OVERFLOW:
|
if status == RTAUDIO_INPUT_OVERFLOW:
|
||||||
@ -177,8 +180,17 @@ cdef int audioCallback(void* outputBuffer,
|
|||||||
# Obtain stream information
|
# Obtain stream information
|
||||||
npy_input = None
|
npy_input = None
|
||||||
if stream.hasInput:
|
if stream.hasInput:
|
||||||
|
try:
|
||||||
assert inputBuffer != NULL
|
assert inputBuffer != NULL
|
||||||
# cdef
|
npy_format = _formats_rtkey[stream.sampleformat][2]
|
||||||
|
npy_input = fromBufferToNPYNoCopy(
|
||||||
|
npy_format,
|
||||||
|
inputBuffer,
|
||||||
|
stream.inputParams.nChannels,
|
||||||
|
nFrames)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print('Exception in Cython callback: ', str(e))
|
||||||
try:
|
try:
|
||||||
npy_output, rval = stream.pyCallback(npy_input,
|
npy_output, rval = stream.pyCallback(npy_input,
|
||||||
nFrames,
|
nFrames,
|
||||||
@ -253,6 +265,7 @@ cdef class RtAudio:
|
|||||||
sampleformats = sampleformats,
|
sampleformats = sampleformats,
|
||||||
prefsamplerate = devinfo.preferredSampleRate)
|
prefsamplerate = devinfo.preferredSampleRate)
|
||||||
|
|
||||||
|
@cython.nonecheck(True)
|
||||||
def openStream(self,object outputParams,
|
def openStream(self,object outputParams,
|
||||||
object inputParams,
|
object inputParams,
|
||||||
str sampleformat,
|
str sampleformat,
|
||||||
@ -291,6 +304,7 @@ cdef class RtAudio:
|
|||||||
|
|
||||||
self._stream = _Stream()
|
self._stream = _Stream()
|
||||||
self._stream.pyCallback = pyCallback
|
self._stream.pyCallback = pyCallback
|
||||||
|
self._stream.sampleformat = _formats_strkey[sampleformat][0]
|
||||||
|
|
||||||
if outputParams is not None:
|
if outputParams is not None:
|
||||||
rtOutputParams_ptr = &self._stream.outputParams
|
rtOutputParams_ptr = &self._stream.outputParams
|
||||||
@ -300,7 +314,7 @@ cdef class RtAudio:
|
|||||||
self._stream.hasOutput = True
|
self._stream.hasOutput = True
|
||||||
|
|
||||||
if inputParams is not None:
|
if inputParams is not None:
|
||||||
rtOutputParams_ptr = &self._stream.inputParams
|
rtInputParams_ptr = &self._stream.inputParams
|
||||||
rtInputParams_ptr.deviceId = inputParams['deviceid']
|
rtInputParams_ptr.deviceId = inputParams['deviceid']
|
||||||
rtInputParams_ptr.nChannels = inputParams['nchannels']
|
rtInputParams_ptr.nChannels = inputParams['nchannels']
|
||||||
rtInputParams_ptr.firstChannel = inputParams['firstchannel']
|
rtInputParams_ptr.firstChannel = inputParams['firstchannel']
|
||||||
@ -321,6 +335,9 @@ cdef class RtAudio:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Exception occured in stream opening: ', str(e))
|
print('Exception occured in stream opening: ', str(e))
|
||||||
self._stream = None
|
self._stream = None
|
||||||
|
raise
|
||||||
|
|
||||||
|
return self._stream.bufferFrames
|
||||||
|
|
||||||
def startStream(self):
|
def startStream(self):
|
||||||
self._rtaudio.startStream()
|
self._rtaudio.startStream()
|
||||||
|
@ -22,22 +22,41 @@ class AvType:
|
|||||||
|
|
||||||
class AvStream:
|
class AvStream:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
rtaudio_input,
|
rtaudio,
|
||||||
rtaudio_output,
|
output_device,
|
||||||
input_device,
|
input_device,
|
||||||
output_device, daqconfig, video=None):
|
daqconfig, video=None):
|
||||||
|
|
||||||
|
|
||||||
self.daqconfig = daqconfig
|
self.daqconfig = daqconfig
|
||||||
self.input_device = input_device
|
self.input_device = input_device
|
||||||
self.output_device = output_device
|
self.output_device = output_device
|
||||||
|
|
||||||
|
# Determine highest input channel number
|
||||||
|
channelconfigs = daqconfig.en_input_channels
|
||||||
|
max_input_channel = 0
|
||||||
|
|
||||||
|
self._rtaudio = rtaudio
|
||||||
|
self.samplerate = int(daqconfig.en_input_rate)
|
||||||
|
self.sensitivity = self.daqconfig.getSensitivities()
|
||||||
|
|
||||||
|
for i, channelconfig in enumerate(channelconfigs):
|
||||||
|
if channelconfig.channel_enabled:
|
||||||
|
self.nchannels = i+1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
daq = DAQDevice(daqconfig)
|
if input_device is not None:
|
||||||
self.nchannels = len(daq.channels_en)
|
inputparams = {'deviceid': input_device.index,
|
||||||
self.samplerate = daq.input_rate
|
'nchannels': self.nchannels,
|
||||||
self.blocksize = daq.blocksize
|
'firstchannel': 0}
|
||||||
self.sensitivity = np.asarray(daqconfig.input_sensitivity)[
|
|
||||||
daq.channels_en]
|
self.blocksize = rtaudio.openStream(
|
||||||
|
None, # Outputparams
|
||||||
|
inputparams, #Inputparams
|
||||||
|
daqconfig.en_input_sample_format, # Sampleformat
|
||||||
|
self.samplerate,
|
||||||
|
2048,
|
||||||
|
self._audioCallback)
|
||||||
|
|
||||||
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)}')
|
||||||
|
|
||||||
@ -55,9 +74,11 @@ class AvStream:
|
|||||||
self._video = video
|
self._video = video
|
||||||
self._video_started = Atomic(False)
|
self._video_started = Atomic(False)
|
||||||
self._callbacks = []
|
self._callbacks = []
|
||||||
self._audiothread = None
|
|
||||||
self._videothread = None
|
self._videothread = None
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self._rtaudio.closeStream()
|
||||||
|
|
||||||
def nCallbacks(self):
|
def nCallbacks(self):
|
||||||
"""
|
"""
|
||||||
Returns the current number of installed callbacks
|
Returns the current number of installed callbacks
|
||||||
@ -86,31 +107,15 @@ class AvStream:
|
|||||||
if self._running:
|
if self._running:
|
||||||
raise RuntimeError('Stream already started')
|
raise RuntimeError('Stream already started')
|
||||||
|
|
||||||
assert self._audiothread is None
|
|
||||||
assert self._videothread is None
|
assert self._videothread is None
|
||||||
|
|
||||||
self._running <<= True
|
self._running <<= True
|
||||||
self._audiothread = Thread(target=self._audioThread)
|
|
||||||
if self._video is not None:
|
if self._video is not None:
|
||||||
self._videothread = Thread(target=self._videoThread)
|
self._videothread = Thread(target=self._videoThread)
|
||||||
self._videothread.start()
|
self._videothread.start()
|
||||||
else:
|
else:
|
||||||
self._video_started <<= True
|
self._video_started <<= True
|
||||||
self._audiothread.start()
|
self._rtaudio.startStream()
|
||||||
|
|
||||||
def _audioThread(self):
|
|
||||||
# Raw stream to allow for in24 packed data type
|
|
||||||
try:
|
|
||||||
daq = DAQDevice(self.daqconfig)
|
|
||||||
# Get a single block first and do not process it. This one often
|
|
||||||
# contains quite some rubbish.
|
|
||||||
data = daq.read()
|
|
||||||
while self._running:
|
|
||||||
# print('read data...')
|
|
||||||
data = daq.read()
|
|
||||||
self._audioCallback(data)
|
|
||||||
except RuntimeError as e:
|
|
||||||
print(f'Runtime error occured during audio capture: {str(e)}')
|
|
||||||
|
|
||||||
def _videoThread(self):
|
def _videoThread(self):
|
||||||
cap = cv.VideoCapture(self._video)
|
cap = cv.VideoCapture(self._video)
|
||||||
@ -139,22 +144,20 @@ class AvStream:
|
|||||||
cap.release()
|
cap.release()
|
||||||
print('stopped videothread')
|
print('stopped videothread')
|
||||||
|
|
||||||
def _audioCallback(self, indata):
|
def _audioCallback(self, indata, nframes, streamtime):
|
||||||
"""This is called (from a separate thread) for each audio block."""
|
"""
|
||||||
if not self._video_started:
|
This is called (from a separate thread) for each audio block.
|
||||||
return
|
"""
|
||||||
|
self._aframectr += 1
|
||||||
with self._callbacklock:
|
with self._callbacklock:
|
||||||
for cb in self._callbacks:
|
for cb in self._callbacks:
|
||||||
cb(AvType.audio, indata, self._aframectr(), self._vframectr())
|
cb(AvType.audio, indata, self._aframectr(), self._vframectr())
|
||||||
self._aframectr += 1
|
return None, 0 if self._running else 1
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self._running <<= False
|
self._running <<= False
|
||||||
with self._running_cond:
|
with self._running_cond:
|
||||||
self._running_cond.notify()
|
self._running_cond.notify()
|
||||||
self._audiothread.join()
|
|
||||||
self._audiothread = None
|
|
||||||
if self._video:
|
if self._video:
|
||||||
self._videothread.join()
|
self._videothread.join()
|
||||||
self._videothread = None
|
self._videothread = None
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Created on Sat Mar 10 08:28:03 2018
|
|
||||||
|
|
||||||
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
|
import numpy as np
|
||||||
@ -34,6 +32,7 @@ class Recording:
|
|||||||
self._fn = fn
|
self._fn = fn
|
||||||
|
|
||||||
self._video_frame_positions = []
|
self._video_frame_positions = []
|
||||||
|
self._curT_rounded_to_seconds = 0
|
||||||
|
|
||||||
self._aframeno = Atomic(0)
|
self._aframeno = Atomic(0)
|
||||||
self._vframeno = 0
|
self._vframeno = 0
|
||||||
@ -104,8 +103,14 @@ class Recording:
|
|||||||
self._vCallback(data)
|
self._vCallback(data)
|
||||||
|
|
||||||
def _aCallback(self, frames, aframe):
|
def _aCallback(self, frames, aframe):
|
||||||
print('.', end='')
|
|
||||||
curT = self._aframeno()*self.blocksize/self.samplerate
|
curT = self._aframeno()*self.blocksize/self.samplerate
|
||||||
|
curT_rounded_to_seconds = int(curT)
|
||||||
|
if curT_rounded_to_seconds > self._curT_rounded_to_seconds:
|
||||||
|
self._curT_rounded_to_seconds = curT_rounded_to_seconds
|
||||||
|
print(f'{curT_rounded_to_seconds}', end='', flush=True)
|
||||||
|
else:
|
||||||
|
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!
|
||||||
self._running <<= False
|
self._running <<= False
|
||||||
|
@ -26,7 +26,7 @@ parser.add_argument('--gain-setting', '-g',
|
|||||||
type=float, default=gain_default)
|
type=float, default=gain_default)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'fn', help='File name of calibration measurement', type=str)
|
'fn', help='File name of calibration measurement', type=str, default=None)
|
||||||
|
|
||||||
parser.add_argument('--channel', help='Channel of the device to calibrate, '
|
parser.add_argument('--channel', help='Channel of the device to calibrate, '
|
||||||
+ 'default = 0',
|
+ 'default = 0',
|
||||||
@ -51,6 +51,7 @@ prms = P_REF*10**(args.spl/20)
|
|||||||
sens = Vrms / prms
|
sens = Vrms / prms
|
||||||
|
|
||||||
print(f'Computed sensitivity: {sens[args.channel]:.5} V/Pa')
|
print(f'Computed sensitivity: {sens[args.channel]:.5} V/Pa')
|
||||||
|
if args.fn:
|
||||||
print('Searching for files in directory to apply sensitivity value to...')
|
print('Searching for files in directory to apply sensitivity value to...')
|
||||||
dir_ = os.path.dirname(args.fn)
|
dir_ = os.path.dirname(args.fn)
|
||||||
for f in os.listdir(dir_):
|
for f in os.listdir(dir_):
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Acquire data and store a measurement file'
|
description='Acquire data and store a measurement file'
|
||||||
)
|
)
|
||||||
@ -8,38 +11,49 @@ parser.add_argument('filename', type=str,
|
|||||||
' Extension is automatically added.')
|
' Extension is automatically added.')
|
||||||
parser.add_argument('--duration', '-d', type=float,
|
parser.add_argument('--duration', '-d', type=float,
|
||||||
help='The recording duration in [s]')
|
help='The recording duration in [s]')
|
||||||
parser.add_argument('--comment', '-c', type=str,
|
|
||||||
help='Add a measurement comment, optionally')
|
|
||||||
|
|
||||||
device_help = 'DAQ Device to record from'
|
device_help = 'DAQ Device to record from'
|
||||||
parser.add_argument('--input-daq', '-i', help=device_help, type=str,
|
parser.add_argument('--input-daq', '-i', help=device_help, type=str,
|
||||||
choices=['roga', 'umik', 'nidaq', 'default'], default='roga')
|
default='Default')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
device_str = args.input_daq
|
|
||||||
|
|
||||||
if device_str == 'nidaq':
|
|
||||||
|
|
||||||
# Not-integrated way to record with the NI USB 4431 DAQ device
|
|
||||||
from lasp.device.record_ni import USBDAQRecording
|
|
||||||
rec = USBDAQRecording(args.filename, [0, 1])
|
|
||||||
rec.start()
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
from lasp.lasp_record import Recording
|
|
||||||
from lasp.lasp_avstream import AvStream
|
from lasp.lasp_avstream import AvStream
|
||||||
from lasp.device.lasp_daqconfig import default_soundcard, roga_plugndaq, umik
|
from lasp.lasp_record import Recording
|
||||||
|
from lasp.device import DAQConfiguration, RtAudio
|
||||||
|
|
||||||
if 'roga' == device_str:
|
config = DAQConfiguration.loadConfigs()[args.input_daq]
|
||||||
device = roga_plugndaq
|
|
||||||
elif 'default' == device_str:
|
print(config)
|
||||||
device = default_soundcard
|
rtaudio = RtAudio()
|
||||||
elif 'umik' == device_str:
|
count = rtaudio.getDeviceCount()
|
||||||
device = umik
|
devices = [rtaudio.getDeviceInfo(i) for i in range(count)]
|
||||||
|
|
||||||
|
input_devices = {}
|
||||||
|
for device in devices:
|
||||||
|
if device.inputchannels >= 0:
|
||||||
|
input_devices[device.name] = device
|
||||||
|
|
||||||
|
try:
|
||||||
|
input_device = input_devices[config.input_device_name]
|
||||||
|
except KeyError:
|
||||||
|
raise RuntimeError(f'Input device {config.input_device_name} not available')
|
||||||
|
|
||||||
|
print(input_device)
|
||||||
|
|
||||||
|
stream = AvStream(rtaudio,
|
||||||
|
None, # No output device
|
||||||
|
input_device,
|
||||||
|
config)
|
||||||
|
|
||||||
stream = AvStream(device)
|
|
||||||
rec = Recording(args.filename, stream, args.duration)
|
rec = Recording(args.filename, stream, args.duration)
|
||||||
rec.start()
|
rec.start()
|
||||||
|
|
||||||
|
print('Stopping stream...')
|
||||||
stream.stop()
|
stream.stop()
|
||||||
|
|
||||||
|
print('Stream stopped')
|
||||||
|
print('Closing stream...')
|
||||||
|
stream.close()
|
||||||
|
print('Stream closed')
|
||||||
|
Loading…
Reference in New Issue
Block a user