In between state of connecting DAQ Devices

This commit is contained in:
Anne de Jong 2018-07-28 14:43:57 +02:00 committed by J.A. de Jong - ASCEE
parent e95b02ae9a
commit 64821f8c6f
9 changed files with 729 additions and 107 deletions

2
.gitignore vendored
View File

@ -21,3 +21,5 @@ LASP.egg-info
lasp_octave_fir.*
lasp/ui_*
resources_rc.py
lasp/device/lasp_daqdevice.c
lasp/lasp_daqwidget.py

View File

@ -1,4 +1,4 @@
add_subdirectory(c)
configure_file(config.pxi.in config.pxi)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(PythonLibs REQUIRED )
@ -10,6 +10,8 @@ include_directories(
.
c
)
add_subdirectory(c)
add_subdirectory(device)
# add the command to generate the source code
# add_custom_command (
@ -18,7 +20,7 @@ include_directories(
# DEPENDS MakeTable
# )
set(ui_files ui_apsrt ui_mainwindow ui_figure ui_about ui_apswidget ui_revtime ui_slmwidget)
set(ui_files ui_apsrt ui_mainwindow ui_figure ui_about ui_apswidget ui_revtime ui_slmwidget ui_daq)
foreach(fn ${ui_files})
add_custom_command(
OUTPUT "${fn}.py"

View File

@ -0,0 +1,4 @@
set_source_files_properties(lasp_daqdevice.c PROPERTIES COMPILE_FLAGS "${CMAKE_C_FLAGS} ${CYTHON_EXTRA_C_FLAGS}")
cython_add_module(lasp_daqdevice lasp_daqdevice.pyx)
target_link_libraries(lasp_daqdevice asound)

6
lasp/device/__init__.py Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/python3
__all__ = ['DAQDevice', 'DAQConfiguration', 'configs', 'query_devices',
'roga_plugndaq', 'default_soundcard']
from .lasp_daqdevice import DAQDevice, query_devices
from .lasp_daqconfig import (DAQConfiguration, configs, roga_plugndaq,
default_soundcard)

View File

@ -0,0 +1,138 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""!
Author: J.A. de Jong - ASCEE
Description:
Data Acquistiion (DAQ) device descriptors, and the DAQ devices themselves
"""
__all__ = ['DAQConfiguration', 'roga_plugndaq', 'default_soundcard']
class DAQConfiguration:
def __init__(self, name,
cardname,
cardlongnamematch,
device_name,
en_format,
en_input_rate,
en_input_channels,
input_sensitivity,
input_gain_settings,
en_input_gain_setting,
en_output_rate,
en_output_channels):
"""
Initialize a device descriptor
Args:
name: Name of the device to appear to the user
cardname: ALSA name identifier
cardlongnamematch: Long name according to ALSA
device_name: ASCII name with which to open the device when connected
en_format: index in the list of sample formats
en_input_rate: index of enabled input sampling frequency [Hz]
in the list of frequencies.
en_input_channels: list of channel indices which are used to
acquire data from.
input_sensitivity: List of sensitivity values, in units of [Pa^-1]
input_gain_setting: If a DAQ supports it, list of indices which
corresponds to a position in the possible input
gains for each channel. Should only be not equal
to None when the hardware supports changing the
input gain.
en_output_rate: index in the list of possible output sampling
frequencies.
en_output_channels: list of enabled output channels
"""
self.name = name
self.cardlongnamematch = cardlongnamematch
self.cardname = cardname
self.device_name = device_name
self.en_format = en_format
self.en_input_rate = en_input_rate
self.en_input_channels = en_input_channels
self.input_sensitivity = input_sensitivity
self.input_gain_settings = input_gain_settings
self.en_output_rate = en_output_rate
self.en_output_channels = en_output_channels
def __repr__(self):
"""
String representation of configuration
"""
rep = f"""Name: {self.name}
Enabled input channels: {self.en_input_channels}
Enabled input sampling frequency: {self.en_input_rate}
Input gain settings: {self.input_gain_settings}
Sensitivity: {self.input_sensitivity}
"""
return rep
def match(self, device):
"""
Returns True when a device specification dictionary matches to the
configuration.
Args:
device: dictionary specifying device settings
"""
match = True
if self.cardlongnamematch is not None:
match &= self.cardlongnamematch in device.cardlongname
if self.cardname is not None:
match &= self.cardname == device.cardname
if self.device_name is not None:
match &= self.device_name == device.device_name
match &= self.en_format < len(device.available_formats)
match &= self.en_input_rate < len(device.available_input_rates)
match &= max(
self.en_input_channels) < device.max_input_channels
if len(self.en_output_channels) > 0:
match &= max(
self.en_output_channels) < device.max_output_channels
match &= self.en_output_rate < len(
device.available_output_rates)
return match
roga_plugndaq = DAQConfiguration(name='Roga-instruments Plug.n.DAQ USB',
cardname='USB Audio CODEC',
cardlongnamematch='Burr-Brown from TI USB'
' Audio CODEC',
device_name='iec958:CARD=CODEC,DEV=0',
en_format=0,
en_input_rate=2,
en_input_channels=[0],
input_sensitivity=[1., 1.],
input_gain_settings=[-20, 0, 20],
en_input_gain_setting=[1, 1],
en_output_rate=1,
en_output_channels=[False, False]
)
default_soundcard = DAQConfiguration(name="Default device",
cardname=None,
cardlongnamematch=None,
device_name='default',
en_format=0,
en_input_rate=2,
en_input_channels=[0, 1],
input_sensitivity=[1., 1.],
input_gain_settings=[0],
en_input_gain_setting=[0, 0],
en_output_rate=1,
en_output_channels=[]
)
configs = (roga_plugndaq, default_soundcard)

View File

@ -0,0 +1,498 @@
include "config.pxi"
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf, stderr, fprintf
import sys
import numpy as np
cimport numpy as cnp
__all__ = ['DAQDevice']
from libc.errno cimport EPIPE, EBADFD, ESTRPIPE
cdef extern from "alsa/asoundlib.h":
int snd_card_get_longname(int index,char** name)
int snd_card_get_name(int index,char** name)
int snd_card_next(int* rcard)
ctypedef struct snd_pcm_t
ctypedef struct snd_pcm_info_t
ctypedef struct snd_pcm_hw_params_t
ctypedef enum snd_pcm_stream_t:
SND_PCM_STREAM_PLAYBACK
SND_PCM_STREAM_CAPTURE
ctypedef enum snd_pcm_format_t:
SND_PCM_FORMAT_S16_LE
SND_PCM_FORMAT_S16_BE
SND_PCM_FORMAT_U16_LE
SND_PCM_FORMAT_U16_BE
SND_PCM_FORMAT_S24_LE
SND_PCM_FORMAT_S24_BE
SND_PCM_FORMAT_U24_LE
SND_PCM_FORMAT_U24_BE
SND_PCM_FORMAT_S32_LE
SND_PCM_FORMAT_S32_BE
SND_PCM_FORMAT_U32_LE
SND_PCM_FORMAT_U32_BE
SND_PCM_FORMAT_S24_3LE
SND_PCM_FORMAT_S24_3BE
SND_PCM_FORMAT_U24_3LE
SND_PCM_FORMAT_U24_3BE
SND_PCM_FORMAT_S16
SND_PCM_FORMAT_U16
SND_PCM_FORMAT_S24
SND_PCM_FORMAT_U24
const char* snd_pcm_format_name (snd_pcm_format_t format)
ctypedef enum snd_pcm_access_t:
SND_PCM_ACCESS_RW_INTERLEAVED
ctypedef unsigned long snd_pcm_uframes_t
int snd_pcm_open(snd_pcm_t** pcm,char* name, snd_pcm_stream_t type, int mode)
int snd_pcm_close(snd_pcm_t*)
int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t)
void snd_pcm_hw_params_alloca(snd_pcm_hw_params_t**)
int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t* params)
int snd_pcm_hw_params_current(snd_pcm_t*, snd_pcm_hw_params_t* params)
int snd_pcm_hw_params_set_rate_resample(snd_pcm_t*, snd_pcm_hw_params_t*, int)
int snd_pcm_hw_params_set_rate(snd_pcm_t*, snd_pcm_hw_params_t*,unsigned int val,int dir)
int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format_t)
int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, unsigned val)
int snd_pcm_hw_params_set_period_size_near(snd_pcm_t*,snd_pcm_hw_params_t*,
snd_pcm_uframes_t*,int* dir)
int snd_pcm_hw_params_get_period_size(snd_pcm_hw_params_t*,
snd_pcm_uframes_t*,int* dir)
int snd_pcm_info(snd_pcm_t*, snd_pcm_info_t*)
void snd_pcm_info_alloca(snd_pcm_info_t**)
int snd_pcm_info_get_card(snd_pcm_info_t*)
int snd_pcm_drain(snd_pcm_t*)
int snd_pcm_readi(snd_pcm_t*,void* buf,snd_pcm_uframes_t nframes)
int snd_pcm_hw_params(snd_pcm_t*,snd_pcm_hw_params_t*)
int snd_pcm_hw_params_test_rate(snd_pcm_t*, snd_pcm_hw_params_t*,
unsigned int val,int dir)
int snd_pcm_hw_params_test_format(snd_pcm_t*, snd_pcm_hw_params_t*,
snd_pcm_format_t)
int snd_pcm_hw_params_get_channels_max(snd_pcm_hw_params_t*,unsigned int*)
int snd_device_name_hint(int card, const char* iface, void*** hints)
char* snd_device_name_get_hint(void* hint, const char* id)
int snd_device_name_free_hint(void**)
char* snd_strerror(int rval)
# Check for these sample rates
check_rates = [8000, 44100, 48000, 96000, 19200]
# First value in tuple: number of significant bits
# Second value: number of bits used in memory
# Third value: S for signed, U for unsigned, L for little endian,
# and B for big endian.
check_formats = {SND_PCM_FORMAT_S16_LE: (16,16,'SL'),
SND_PCM_FORMAT_S16_BE: (16,16,'SB'),
SND_PCM_FORMAT_U16_LE: (16,16,'UL'),
SND_PCM_FORMAT_U16_BE: (16,16,'UB'),
SND_PCM_FORMAT_S24_LE: (24,32,'SL'),
SND_PCM_FORMAT_S24_BE: (24,32,'SB'),
SND_PCM_FORMAT_U24_LE: (24,32,'UL'),
SND_PCM_FORMAT_U24_BE: (24,32,'UB'),
SND_PCM_FORMAT_S32_LE: (32,32,'SL'),
SND_PCM_FORMAT_S32_BE: (32,32,'SB'),
SND_PCM_FORMAT_U32_LE: (32,32,'UL'),
SND_PCM_FORMAT_U32_BE: (32,32,'UB'),
SND_PCM_FORMAT_S24_3LE: (24,24,'SL'),
SND_PCM_FORMAT_S24_3BE: (24,24,'SB'),
SND_PCM_FORMAT_U24_3LE: (24,24,'UL'),
SND_PCM_FORMAT_U24_3BE: (24,24,'UB')}
devices_opened_card = [False, False, False, False, False, False]
cdef snd_pcm_t* open_device(char* name,
snd_pcm_stream_t streamtype):
"""
Helper function to properly open the first device of a card
"""
cdef snd_pcm_t* pcm
# if name in devices_opened:
# raise RuntimeError('Device %s is already opened.' %name)
cdef int rval = snd_pcm_open(&pcm, name, streamtype, 0)
if rval == 0:
return pcm
else:
return NULL
cdef int close_device(snd_pcm_t* dev):
rval = snd_pcm_close(dev)
if rval:
print('Error closing device')
return rval
class DeviceInfo:
"""
Will later be replaced by a dataclass. Storage container for a lot of
device parameters.
"""
def __repr__(self):
rep = f"""Device name: {self.device_name}
"""
return rep
def getDeviceInfo(char* device_name):
"""
Open the PCM device for both capture and playback, extract the number of
channels, the samplerates and encoding
Args:
cardindex: Card number of device, numbered by ALSA
Returns:
"""
cdef:
snd_pcm_t* pcm
snd_pcm_hw_params_t* hwparams
snd_pcm_info_t* info
int rval, cardindex
unsigned max_input_channels=0, max_output_channels=0
char* c_cardname
char *c_cardlongname
deviceinfo = DeviceInfo()
deviceinfo.device_name = device_name.decode('ASCII')
pcm = open_device(device_name, SND_PCM_STREAM_CAPTURE)
if not pcm:
raise RuntimeError('Unable to open device')
snd_pcm_info_alloca(&info)
rval = snd_pcm_info(pcm, info)
if rval:
snd_pcm_close(pcm)
raise RuntimeError('Unable to obtain device info')
cardindex = snd_pcm_info_get_card(info)
cardname = ''
cardlongname = ''
if cardindex >= 0:
snd_card_get_name(cardindex, &c_cardname)
if c_cardname:
cardname = c_cardname.decode('ASCII')
printf('name: %s\n', c_cardname)
free(c_cardname)
rval = snd_card_get_longname(cardindex, &c_cardlongname)
if c_cardlongname:
printf('longname: %s\n', c_cardlongname)
cardlongname = c_cardlongname.decode('ASCII')
free(c_cardlongname)
deviceinfo.cardname = cardname
deviceinfo.cardlongname = cardlongname
# Check hardware parameters
snd_pcm_hw_params_alloca(&hwparams)
# Nothing said about the return value of this function in the documentation
snd_pcm_hw_params_any(pcm, hwparams)
# Check available sample formats
available_formats = []
for format in check_formats.keys():
rval = snd_pcm_hw_params_test_format(pcm, hwparams, format)
if rval == 0:
available_formats.append(check_formats[format])
deviceinfo.available_formats = available_formats
# # Restrict a configuration space to contain only real hardware rates.
# rval = snd_pcm_hw_params_set_rate_resample(pcm, hwparams, 0)
# if rval !=0:
# fprintf(stderr, 'Unable disable resampling rates')
# Check available input sample rates
available_input_rates = []
for rate in check_rates:
rval = snd_pcm_hw_params_test_rate(pcm, hwparams, rate, 0)
if rval == 0:
available_input_rates.append(rate)
deviceinfo.available_input_rates = available_input_rates
rval = snd_pcm_hw_params_get_channels_max(hwparams, &max_input_channels)
if rval != 0:
fprintf(stderr, "Could not obtain max input channels\n")
deviceinfo.max_input_channels = max_input_channels
# Close device
rval = snd_pcm_close(pcm)
if rval:
fprintf(stderr, 'Unable to close pcm device.\n')
deviceinfo.available_output_rates = []
deviceinfo.max_output_channels = 0
# ###############################################################
# Open device for output
pcm = open_device(device_name, SND_PCM_STREAM_CAPTURE)
if pcm == NULL:
# We are unable to open the device for playback, but we were able to
# open in for capture. So this is a valid device.
return deviceinfo
snd_pcm_hw_params_any(pcm, hwparams)
# Restrict a configuration space to contain only real hardware rates.
# rval = snd_pcm_hw_params_set_rate_resample(pcm, hwparams, 0)
# if rval != 0:
# fprintf(stderr, 'Unable disable resampling rates')
# Check available input sample rates
available_output_rates = []
for rate in check_rates:
rval = snd_pcm_hw_params_test_rate(pcm, hwparams, rate, 0)
if rval == 0:
available_output_rates.append(rate)
deviceinfo.available_output_rates = available_output_rates
rval = snd_pcm_hw_params_get_channels_max(hwparams, &max_output_channels)
if rval != 0:
fprintf(stderr, "Could not obtain max output channels")
deviceinfo.max_output_channels = max_output_channels
# Close device
rval = close_device(pcm)
if rval:
fprintf(stderr, 'Unable to close pcm device %s.', device_name)
return deviceinfo
def query_devices():
"""
Returns a list of available DAQ devices, where each device is represented
by a dictionary containing parameters of the device
"""
devices = []
cdef:
# Start cardindex at -1, such that the first one is picked by
# snd_card_next()
int cardindex = -1, rval=0, i=0
void** namehints_opaque
char** namehints
char* c_device_name
rval = snd_device_name_hint(-1, "pcm", &namehints_opaque)
if rval:
raise RuntimeError('Could not obtain name hints for card %i.'
%cardindex)
namehints = <char**> namehints_opaque
while namehints[i] != NULL:
# printf('namehint[i]: %s\n', namehints[i])
c_device_name = snd_device_name_get_hint(namehints[i], "NAME")
c_descr = snd_device_name_get_hint(namehints[i], "DESC")
if c_device_name:
device_name = c_device_name.decode('ASCII')
if c_descr:
device_desc = c_descr.decode('ASCII')
free(c_descr)
else:
device_desc = ''
try:
device = getDeviceInfo(c_device_name)
printf('device name: %s\n', c_device_name)
devices.append(device)
except RuntimeError:
pass
free(c_device_name)
i+=1
snd_device_name_free_hint(namehints_opaque)
return devices
cdef class DAQDevice:
cdef:
snd_pcm_t* pcm
int device_index
object device, config
public snd_pcm_uframes_t blocksize
object callback
def __cinit(self):
self.pcm = NULL
def __init__(self, config, blocksize=2048):
"""
Initialize the DAQ device
Args:
config: DAQConfiguration instance
blocksize: Number of frames in a single acquisition block
callback: callback used to send data frames to
"""
self.config = config
devices = query_devices()
self.device = None
for device in devices:
if self.config.match(device):
# To open the underlying PCM device
device_name = device.device_name.encode('ASCII')
self.device = device
if self.device is None:
raise RuntimeError(f'Device {self.config.name} is not available')
# if devices_opened[device_index]:
# raise RuntimeError(f'Device {self.config.name} is already opened')
# print('device_name opened:', device_name)
self.pcm = open_device(device_name, SND_PCM_STREAM_CAPTURE)
# Device is opened. We are going to configure
cdef:
snd_pcm_hw_params_t* params
int rval
snd_pcm_format_t format_code
snd_pcm_uframes_t period_size
snd_pcm_hw_params_alloca(&params);
# Fill it in with default values.
snd_pcm_hw_params_any(self.pcm, params);
# Set access interleaved
rval = snd_pcm_hw_params_set_access(self.pcm, params,
SND_PCM_ACCESS_RW_INTERLEAVED)
if rval != 0:
snd_pcm_close(self.pcm)
raise RuntimeError('Could not set access mode to interleaved')
# Set sampling frequency
cdef unsigned int rate
rate = device.available_input_rates[config.en_input_rate]
# printf('Set sample rate: %i\n', rate)
rval = snd_pcm_hw_params_set_rate(self.pcm,params, rate, 0)
if rval != 0:
snd_pcm_close(self.pcm)
raise RuntimeError('Could not set input sampling frequency')
# Set number of channels
channels_max = max(self.channels_en)+1
# print('channels_max:', channels_max)
if channels_max > self.device.max_input_channels:
snd_pcm_close(self.pcm)
raise ValueError('Highest required channel is larger than available'
' channels.')
rval = snd_pcm_hw_params_set_channels(self.pcm, params, channels_max)
if rval != 0:
snd_pcm_close(self.pcm)
raise RuntimeError('Could not set input channels, highest required'
' input channel: %i.' %channels_max)
# Find the format description
format_descr = self.device.available_formats[config.en_format]
# Obtain key from value of dictionary
format_code = list(check_formats.keys())[list(check_formats.values()).index(format_descr)]
# printf('Format code: %s\n', snd_pcm_format_name(format_code))
# print(format)
rval = snd_pcm_hw_params_set_format(self.pcm, params, format_code)
if rval != 0:
fprintf(stderr, "Could not set format: %s.", snd_strerror(rval))
# Set period size
cdef int dir = 0
bytedepth = format_descr[1]//8
# print('byte depth:', bytedepth)
period_size = blocksize
rval = snd_pcm_hw_params_set_period_size_near(self.pcm,
params,
&period_size, &dir)
if rval != 0:
snd_pcm_close(self.pcm)
raise RuntimeError("Could not set period size: %s."
%snd_strerror(rval))
# Write the parameters to the driver
rc = snd_pcm_hw_params(self.pcm, params);
if (rc < 0):
snd_pcm_close(self.pcm)
raise RuntimeError('Could not set hw parameters: %s' %snd_strerror(rc))
# Check the block size again, and store it
snd_pcm_hw_params_get_period_size(params, &self.blocksize,
&dir)
# print('Period size:', self.blocksize)
cdef object _getEmptyBuffer(self):
"""
Return right size empty buffer
"""
format_descr = self.device.available_formats[self.config.en_format]
LB = format_descr[2][1]
assert LB == 'L', 'Only little-endian data format supported'
if format_descr[1] == 16:
dtype = np.int16
elif format_descr[1] == 32:
dtype = np.int32
# interleaved data, order = C
return np.zeros((self.blocksize,
max(self.channels_en)+1),
dtype=dtype, order='C')
def read(self):
cdef int rval = 0
buf = self._getEmptyBuffer()
# buf2 = self._getEmptyBuffer()
cdef cnp.int16_t[:, ::1] bufv = buf
rval = snd_pcm_readi(self.pcm,<void*> &bufv[0, 0], self.blocksize)
# rval = 2048
if rval > 0:
# print('Samples obtained:' , rval)
return buf[:rval, self.channels_en]
# return buf
elif rval == -EPIPE:
raise RuntimeError('Error: buffer overrun: %s',
snd_strerror(rval))
elif rval == -EBADFD:
raise RuntimeError('Error: could not read from DAQ Device: %s',
snd_strerror(rval))
elif rval == -ESTRPIPE:
raise RuntimeError('Error: could not read from DAQ Device: %s',
snd_strerror(rval))
def __dealloc__(self):
# printf("dealloc\n")
cdef int rval
if self.pcm:
# print('Closing pcm')
# snd_pcm_drain(self.pcm)
rval = snd_pcm_close(self.pcm)
# devices_opened[self.device_index] = False
if rval != 0:
fprintf(stderr, 'Unable to properly close device: %s\n',
snd_strerror(rval))
self.pcm = NULL
@property
def nchannels_all(self):
return self.device.max_input_channels
@property
def channels_en(self):
return self.config.en_input_channels
@property
def input_rate(self):
return self.device.available_input_rates[self.config.en_input_rate]
@property
def channels(self):
return [self.config.en_input_channels]
cpdef bint isOpened(self):
if self.pcm:
return True
else:
return False

View File

@ -6,48 +6,41 @@ Created on Sat Mar 10 08:28:03 2018
@author: Read data from image stream and record sound at the same time
"""
import cv2 as cv
import sounddevice as sd
from .lasp_atomic import Atomic
from threading import Thread, Condition, Lock
import time
__all__ = ['AvType','AvStream']
from .device import DAQDevice, roga_plugndaq
__all__ = ['AvType', 'AvStream']
video_x, video_y = 640, 480
dtype, sampwidth = 'int16', 2
# %%
blocksize = 2048
video_x,video_y = 640,480
dtype, sampwidth = 'int32',4
class AvType:
video=0
audio=1
video = 0
audio = 1
class AvStream:
def __init__(self, audiodeviceno=None, video=None, nchannels = None, samplerate = None):
audiodevice,audiodeviceno = self._findDevice(audiodeviceno)
if nchannels is None:
self.nchannels = audiodevice['max_input_channels']
if self.nchannels == 0:
raise RuntimeError('Device has no input channels')
else:
self.nchannels = nchannels
self.audiodeviceno = audiodeviceno
if samplerate is None:
self.samplerate = audiodevice['default_samplerate']
else:
self.samplerate = samplerate
self.blocksize = blocksize
self.video_x, self.video_y = video_x,video_y
def __init__(self, daqconfig=roga_plugndaq, video=None):
self.daqconfig = daqconfig
try:
daq = DAQDevice(daqconfig)
self.nchannels = len(daq.channels_enabled)
self.samplerate = daq.input_rate
self.blocksize = daq.blocksize
except Exception as e:
raise RuntimeError(f'Could not initialize DAQ device: {str(e)}')
self.video_x, self.video_y = video_x, video_y
self.dtype, self.sampwidth = dtype, sampwidth
self._aframectr = Atomic(0)
self._vframectr = Atomic(0)
self._callbacklock = Lock()
self._running = Atomic(False)
self._running_cond = Condition()
@ -59,51 +52,29 @@ class AvStream:
def addCallback(self, cb):
"""
Add as stream callback to the list of callbacks
"""
with self._callbacklock:
if not cb in self._callbacks:
if cb not in self._callbacks:
self._callbacks.append(cb)
def removeCallback(self, cb):
with self._callbacklock:
if cb in self._callbacks:
self._callbacks.remove(cb)
def _findDevice(self, deviceno):
if deviceno is None:
deviceno = 0
devices = sd.query_devices()
found = []
for device in devices:
name = device['name']
if 'Umik' in name:
found.append((device,deviceno))
elif 'nanoSHARC' in name:
found.append((device,deviceno))
deviceno+=1
if len(found) == 0:
print('Please choose one of the following:')
print(sd.query_devices())
raise RuntimeError('Could not find a proper device')
return found[0]
else:
return (sd.query_devices(deviceno,kind='input'),deviceno)
def start(self):
"""
Start the stream, which means the callbacks are called with stream
data (audio/video)
"""
if self._running:
raise RuntimeError('Stream already started')
assert self._audiothread == None
assert self._videothread == None
assert self._audiothread is None
assert self._videothread is None
self._running <<= True
self._audiothread = Thread(target=self._audioThread)
if self._video is not None:
@ -115,19 +86,16 @@ class AvStream:
def _audioThread(self):
# Raw stream to allow for in24 packed data type
stream = sd.InputStream(
device=self.audiodeviceno,
dtype=self.dtype,
blocksize=blocksize,
channels=self.nchannels,
samplerate=self.samplerate,
callback=self._audioCallback)
with stream:
with self._running_cond:
while self._running:
self._running_cond.wait()
print('stopped audiothread')
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:
data = daq.read()
self._audioCallback(data)
except RuntimeError as e:
print(f'Runtime error occured during audio capture: {str(e)}')
def _videoThread(self):
cap = cv.VideoCapture(self._video)
@ -138,32 +106,32 @@ class AvStream:
while self._running:
ret, frame = cap.read()
# print(frame.shape)
if ret==True:
if ret is True:
if vframectr == 0:
self._video_started <<= True
with self._callbacklock:
for cb in self._callbacks:
cb(AvType.video,frame,self._aframectr(),vframectr)
cb(AvType.video, frame, self._aframectr(), vframectr)
vframectr += 1
self._vframectr += 1
else:
if loopctr == 10:
print('Error: no video capture!')
time.sleep(0.2)
loopctr +=1
loopctr += 1
cap.release()
print('stopped videothread')
def _audioCallback(self, indata, nframes, time, status):
def _audioCallback(self, indata):
"""This is called (from a separate thread) for each audio block."""
if not self._video_started:
return
with self._callbacklock:
for cb in self._callbacks:
cb(AvType.audio,indata,self._aframectr(),self._vframectr())
cb(AvType.audio, indata, self._aframectr(), self._vframectr())
self._aframectr += 1
def stop(self):
@ -181,6 +149,6 @@ class AvStream:
def isStarted(self):
return self._running()
def hasVideo(self):
return True if self._video is not None else False

View File

@ -197,6 +197,22 @@ class Measurement:
"""
return self._time
def scaleBlock(self, block):
# When the data is stored as integers, we assume dB full-scale scaling.
# Hence, when we convert the data to floats, we divide by the maximum
# possible value.
if block.dtype == np.int32:
fac = 2**31
elif block.dtype == np.int16:
fac = 2**15
elif block.dtype == np.float64:
fac = 1.0
else:
raise RuntimeError(
f'Unimplemented data type from recording: {block.dtype}.')
sens = self._sens
return block.astype(LASP_NUMPY_FLOAT_TYPE)/fac/sens[np.newaxis, :]
@property
def prms(self):
"""
@ -212,10 +228,12 @@ class Measurement:
except AttributeError:
pass
sens = self._sens
pms = 0.
for block in self.iterBlocks():
pms += np.sum(block/sens[np.newaxis, :], axis=0)**2/self.N
with self.file() as f:
for block in self.iterBlocks(f):
block = self.scaleBlock(block)
pms += np.sum(block**2, axis=0)/self.N
self._prms = np.sqrt(pms)
return self._prms
@ -235,21 +253,7 @@ class Measurement:
blocks = blocks.reshape(self.nblocks*self.blocksize,
self.nchannels)
# When the data is stored as integers, we assume dB full-scale scaling.
# Hence, when we convert the data to floats, we divide by the maximum
# possible value.
if blocks.dtype == np.int32:
fac = 2**31
elif blocks.dtype == np.int16:
fac = 2**15
elif blocks.dtype == np.float64:
fac = 1.0
else:
raise RuntimeError(
f'Unimplemented data type from recording: {blocks.dtype}.')
sens = self._sens
blocks = blocks.astype(LASP_NUMPY_FLOAT_TYPE)/fac/sens[np.newaxis, :]
blocks = self.scaleBlock(blocks)
return blocks
def iterBlocks(self, opened_file):
@ -284,7 +288,7 @@ class Measurement:
valid = sens.ndim == 1
valid &= sens.shape[0] == self.nchannels
valid &= isinstance(sens.dtype, float)
valid &= sens.dtype == float
if not valid:
raise ValueError('Invalid sensitivity value(s) given')
with self.file('r+') as f:

View File

@ -8,7 +8,7 @@ Read data from stream and record sound and video at the same time
import numpy as np
from .lasp_atomic import Atomic
from threading import Condition
from .lasp_avstream import AvType
from .lasp_avstream import AvType, AvStream
import h5py
import time
@ -122,6 +122,6 @@ class Recording:
if __name__ == '__main__':
rec = Recording('test', 5)
stream = AvStream()
rec = Recording('test', stream, 5)
rec.start()