lasp/lasp/device/lasp_rtaudio.pyx

838 lines
30 KiB
Cython
Raw Normal View History

2019-12-08 13:19:10 +00:00
import sys
include "config.pxi"
cimport cython
from .lasp_daqconfig import DeviceInfo
2019-12-08 13:19:10 +00:00
from libcpp.string cimport string
from libcpp.vector cimport vector
2020-03-30 18:23:17 +00:00
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf, fprintf, stderr
from libc.string cimport memcpy, memset
2020-03-30 18:23:17 +00:00
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
__all__ = ['AvType', 'RtAudio', 'get_numpy_dtype_from_format_string',
'get_sampwidth_from_format_string']
class AvType:
"""Specificying the type of data, for adding and removing callbacks from
the stream."""
audio_input = 1
audio_output = 2
video = 4
2019-12-08 13:19:10 +00:00
cdef extern from "RtAudio.h" nogil:
ctypedef unsigned long RtAudioStreamStatus
RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW
RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW
cdef cppclass RtAudioError:
ctypedef enum Type:
WARNING
DEBUG_WARNING
UNSPECIFIED
NO_DEVICES_FOUND
INVALID_DEVICE
MEMORY_ERROR
INVALID_PARAMETER
INVALID_USE
DRIVER_ERROR
SYSTEM_ERROR
THREAD_ERROR
ctypedef unsigned long RtAudioStreamFlags
RtAudioStreamFlags RTAUDIO_NONINTERLEAVED
2019-12-08 13:19:10 +00:00
RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY
RtAudioStreamFlags RTAUDIO_HOG_DEVICE
2020-02-26 13:20:33 +00:00
RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME
2019-12-08 13:19:10 +00:00
RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT
RtAudioStreamFlags RTAUDIO_JACK_DONT_CONNECT
ctypedef unsigned long RtAudioFormat
RtAudioFormat RTAUDIO_SINT8
RtAudioFormat RTAUDIO_SINT16
RtAudioFormat RTAUDIO_SINT24
RtAudioFormat RTAUDIO_SINT32
RtAudioFormat RTAUDIO_FLOAT32
RtAudioFormat RTAUDIO_FLOAT64
ctypedef int (*RtAudioCallback)(void* outputBuffer,
void* inputBuffer,
unsigned int nFramesPerBlock,
2019-12-08 13:19:10 +00:00
double streamTime,
RtAudioStreamStatus status,
void* userData)
ctypedef void (*RtAudioErrorCallback)(RtAudioError.Type _type,
const string& errortxt)
cdef cppclass cppRtAudio "RtAudio":
enum Api:
UNSPECIFIED
LINUX_ALSA
LINUX_PULSE
MACOSX_CORE
WINDOWS_WASAPI
WINDOWS_ASIO
WINDOWS_DS
2019-12-08 13:19:10 +00:00
cppclass DeviceInfo:
bool probed
string name
unsigned int outputChannels
unsigned int inputChannels
unsigned int duplexChannels
bool isDefaultOutput
bool isDefaultInput
vector[unsigned int] sampleRates
unsigned int preferredSampleRate
RtAudioFormat nativeFormats
cppclass StreamOptions:
StreamOptions()
2019-12-08 13:19:10 +00:00
RtAudioStreamFlags flags
unsigned int numberOfBuffers
string streamName
int priority
cppclass StreamParameters:
StreamParameters()
2019-12-08 13:19:10 +00:00
unsigned int deviceId
unsigned int nChannels
unsigned int firstChannel
@staticmethod
void getCompiledApi(vector[cppRtAudio.Api]& apis)
@staticmethod
cppRtAudio.Api getCompiledApiByName(string& name)
@staticmethod
string getApiDisplayName(cppRtAudio.Api api)
@staticmethod
string getApiName(cppRtAudio.Api api)
# RtAudio() except +
cppRtAudio() except +
cppRtAudio(cppRtAudio.Api api) except +
2019-12-08 13:19:10 +00:00
# ~RtAudio() Destructors should not be listed
2019-12-08 13:19:10 +00:00
unsigned int getDeviceCount()
DeviceInfo getDeviceInfo(unsigned int device)
unsigned int getDefaultOutputDevice()
unsigned int getDefaultInputDevice()
void openStream(StreamParameters* outputParameters,
StreamParameters* intputParameters,
RtAudioFormat _format,
unsigned int sampleRate,
unsigned int* bufferFrames,
RtAudioCallback callback,
void* userData,
void* StreamOptions,
RtAudioErrorCallback) except +
void closeStream()
void startStream() except +
void stopStream() except +
void abortStream() except +
bool isStreamOpen()
bool isStreamRunning()
double getStreamTime()
void setStreamTime(double) except +
long getStreamLatency()
unsigned int getStreamSampleRate()
void showWarnings(bool value)
cdef extern from "lasp_cppthread.h" nogil:
cdef cppclass CPPThread[T,F]:
CPPThread(F threadfunction, T data)
void join()
void CPPsleep(unsigned int ms)
cdef extern from "lasp_cppqueue.h" nogil:
cdef cppclass SafeQueue[T]:
SafeQueue()
void enqueue(T t)
T dequeue()
size_t size() const
bool empty() const
cdef extern from "atomic" namespace "std" nogil:
cdef cppclass atomic[T]:
T load()
void store(T)
cdef extern from "lasp_pyarray.h":
PyObject* data_to_ndarray(void* data,
int n_rows,int n_cols,
int typenum,
bool transfer_ownership,
bool F_contiguous)
_formats_strkey = {
2019-12-22 14:00:50 +00:00
'8-bit integers': (RTAUDIO_SINT8, 1, np.int8),
'16-bit integers': (RTAUDIO_SINT16, 2, np.int16),
'24-bit integers': (RTAUDIO_SINT24, 3),
'32-bit integers': (RTAUDIO_SINT32, 4, np.int32),
'32-bit floats': (RTAUDIO_FLOAT32, 4, np.float32),
'64-bit floats': (RTAUDIO_FLOAT64, 8, np.float64),
}
_formats_rtkey = {
RTAUDIO_SINT8: ('8-bit integers', 1, cnp.NPY_INT8),
RTAUDIO_SINT16: ('16-bit integers', 2, cnp.NPY_INT16),
RTAUDIO_SINT24: ('24-bit integers', 3),
RTAUDIO_SINT32: ('32-bit integers', 4, cnp.NPY_INT32),
RTAUDIO_FLOAT32: ('32-bit floats', 4, cnp.NPY_FLOAT32),
RTAUDIO_FLOAT64: ('64-bit floats', 8, cnp.NPY_FLOAT64),
}
2019-12-22 14:00:50 +00:00
def get_numpy_dtype_from_format_string(format_string):
return _formats_strkey[format_string][-1]
def get_sampwidth_from_format_string(format_string):
return _formats_strkey[format_string][-2]
2019-12-08 13:19:10 +00:00
# It took me quite a long time to fully understand Cython's idiosyncrasies
# concerning C(++) callbacks, the GIL and passing Python objects as pointers
# into C(++) functions. But finally, here it is!
# cdef void fromNPYToBuffer(cnp.ndarray arr,
# void* buf):
# """
# Copy a Python numpy array over to a buffer
# No checks, just memcpy! Careful!
# """
# memcpy(buf, arr.data, arr.size*arr.itemsize)
cdef void copyChannel(void* to, void* from_,
unsigned bytesperchan,
unsigned toindex,
unsigned fromindex) nogil:
memcpy(<void*> &((<char*> to)[bytesperchan*toindex]),
<void*> &((<char*> from_)[bytesperchan*fromindex]),
bytesperchan)
2019-12-08 13:19:10 +00:00
ctypedef struct _Stream:
PyObject* pyCallback
RtAudioFormat sampleformat
# Flag used to pass the stopThread.
atomic[bool] stopThread
2019-12-08 13:19:10 +00:00
# Number of frames per block
unsigned nFramesPerBlock
# Number of bytes per channel
unsigned int nBytesPerChan
# Number of blocks to delay the output before adding to the input
unsigned int outputDelayBlocks
# The structures as used by RtAudio
cppRtAudio.StreamParameters inputParams
cppRtAudio.StreamParameters outputParams
bool* inputChannelsEnabled
bool* outputChannelsEnabled
unsigned ninputchannels_forwarded
unsigned noutputchannels_forwarded
# If these queue pointers are NULL, it means the stream does not have an
# input, or output.
SafeQueue[void*] *outputDelayQueue
SafeQueue[void*] *inputQueue
SafeQueue[void*] *outputQueue
CPPThread[void*, void (*)(void*)] *thread
cdef int audioCallback(void* outputbuffer,
void* inputbuffer,
unsigned int nFramesPerBlock,
2019-12-08 13:19:10 +00:00
double streamTime,
RtAudioStreamStatus status,
void* userData) nogil:
"""
Calls the Python callback function and converts data
"""
cdef:
int rval = 0
_Stream* stream
void* outputbuffercpy = NULL
void* inputbuffercpy = NULL
unsigned j, i
unsigned bytesperchan
unsigned noutputchannels_forwarded, ninputchannels_forwarded
bint ch_en
stream = <_Stream*>(userData)
bytesperchan = stream.nBytesPerChan
ninputchannels_forwarded = stream.ninputchannels_forwarded
noutputchannels_forwarded = stream.noutputchannels_forwarded
# with gil:
# print(f'bytesperchan: {bytesperchan}')
# print(f'ninputchannels_forwarded:: {ninputchannels_forwarded}')
# print(f'noutputchannels_forwarded:: {noutputchannels_forwarded}')
# fprintf(stderr, "Stream heartbeat...\n")
# Returning 2 means aborting the stream immediately
if status == RTAUDIO_INPUT_OVERFLOW:
fprintf(stderr, 'Input overflow.\n')
stream.stopThread.store(True)
return 2
elif status == RTAUDIO_OUTPUT_UNDERFLOW:
fprintf(stderr, 'Output underflow.\n')
# stream.stopThread.store(True)
return 0
if nFramesPerBlock != stream.nFramesPerBlock:
printf('Number of frames mismath in callback data!\n')
stream.stopThread.store(True)
return 2
if inputbuffer:
# fprintf(stderr, "enter copying input buffer code\n")
# with gil:
# assert stream.inputQueue is not NULL
inputbuffercpy = malloc(bytesperchan*ninputchannels_forwarded)
if not inputbuffercpy:
fprintf(stderr, "Error allocating buffer\n")
return 2
if stream.outputDelayQueue:
if stream.outputDelayQueue.size() > stream.outputDelayBlocks:
outputbuffercpy = stream.outputDelayQueue.dequeue()
memcpy(inputbuffercpy, outputbuffercpy,
bytesperchan*noutputchannels_forwarded)
# Cleanup buffer
free(outputbuffercpy)
outputbuffercpy = NULL
else:
memset(inputbuffercpy, 0,
bytesperchan*noutputchannels_forwarded)
# Starting channel for copying input channels according to the channel
# map
j = stream.noutputchannels_forwarded
else:
j = 0
i = 0
for i in range(stream.inputParams.nChannels):
ch_en = stream.inputChannelsEnabled[i]
if ch_en:
copyChannel(inputbuffercpy, inputbuffer, bytesperchan, j, i)
j+=1
i+=1
stream.inputQueue.enqueue(inputbuffercpy)
if outputbuffer:
# with gil:
# assert stream.outputQueue
# fprintf(stderr, "enter copying output buffer code\n")
if stream.outputQueue.empty():
fprintf(stderr, 'Stream output buffer underflow, zero-ing buffer...\n')
memset(outputbuffer, 0, stream.outputParams.nChannels*bytesperchan)
else:
outputbuffercpy = stream.outputQueue.dequeue()
# fprintf(stderr, 'Copying data to stream output buffer...\n')
j = 0
i = 0
for i in range(stream.outputParams.nChannels):
ch_en = stream.outputChannelsEnabled[i]
if ch_en:
copyChannel(outputbuffer, outputbuffercpy, bytesperchan, i, j)
j+=1
else:
# If channel is not enabled, we set the data to zero
memset(<void*> &((<char*> outputbuffer)[bytesperchan*i]), 0, bytesperchan)
pass
i+=1
if stream.outputDelayQueue:
# fprintf(stderr, "Adding to delay queue\n")
stream.outputDelayQueue.enqueue(outputbuffercpy)
else:
free(outputbuffercpy)
outputbuffercpy = NULL
return 0
cdef void audioCallbackPythonThreadFunction(void* voidstream) nogil:
cdef:
_Stream* stream
cnp.NPY_TYPES npy_format
void* inputbuffer = NULL
void* outputbuffer = NULL
unsigned noutputchannels_forwarded
unsigned ninputchannels_forwarded
unsigned nBytesPerChan
unsigned nFramesPerBlock
void* _TESTDATA
2019-12-08 13:19:10 +00:00
stream = <_Stream*> voidstream
ninputchannels_forwarded = stream.ninputchannels_forwarded
noutputchannels_forwarded = stream.noutputchannels_forwarded
nBytesPerChan = stream.nBytesPerChan
nFramesPerBlock = stream.nFramesPerBlock
2020-03-30 18:23:17 +00:00
2019-12-08 13:19:10 +00:00
with gil:
npy_format = _formats_rtkey[stream.sampleformat][2]
callback = <object> stream.pyCallback
print(f'noutputchannels_forwarded: {noutputchannels_forwarded}')
print(f'ninputchannels_forwarded: {ninputchannels_forwarded}')
print(f'nBytesPerChan: {nBytesPerChan}')
print(f'nFramesPerBlock: {nFramesPerBlock}')
while True:
if stream.stopThread.load() == True:
printf('Stopping thread...\n')
return
if stream.inputQueue:
# fprintf(stderr, "Waiting on input queue\n")
inputbuffer = stream.inputQueue.dequeue()
if not inputbuffer:
printf('Stopping thread...\n')
return
if stream.outputQueue:
# fprintf(stderr, 'Allocating output buffer...\n')
outputbuffer = malloc(nBytesPerChan*noutputchannels_forwarded)
# memset(outputbuffer, 0, nBytesPerChan*noutputchannels_forwarded)
with gil:
# Obtain stream information
npy_input = None
npy_output = None
if stream.inputQueue:
# assert(inputbuffer)
try:
# print(f'========ninputchannels_forwarded: {ninputchannels_forwarded}')
# print(f'========nFramesPerBlock: {nFramesPerBlock}')
# print(f'========npy_format: {npy_format}')
npy_input = <object> data_to_ndarray(
inputbuffer,
nFramesPerBlock,
ninputchannels_forwarded,
npy_format,
True, # Do transfer ownership
True) # F-contiguous is True: data is Fortran-cont.
# fprintf(stderr, "Copying array...\n")
# fprintf(stderr, "End Copying array...\n")
except Exception as e:
print('exception in cython callback for audio input: ', str(e))
return
if stream.outputQueue:
# fprintf(stderr, 'Copying output buffer to Numpy...\n')
try:
npy_output = <object> data_to_ndarray(
outputbuffer,
nFramesPerBlock,
noutputchannels_forwarded,
npy_format,
False, # Do not transfer ownership
True) # F-contiguous
except Exception as e:
print('exception in Cython callback for audio output: ', str(e))
return
try:
# fprintf(stderr, "Python callback...\n")
rval = callback(npy_input,
npy_output,
nFramesPerBlock,
)
# fprintf(stderr, "Return from Python callback...\n")
except Exception as e:
print('Exception in Cython callback: ', str(e))
return
if stream.outputQueue:
# fprintf(stderr, 'Enqueuing output buffer...\n')
stream.outputQueue.enqueue(outputbuffer)
if not stream.inputQueue:
# fprintf(stderr, 'No input queue!\n')
while stream.outputQueue.size() > 10 and not stream.stopThread.load():
# printf('Sleeping...\n')
# No input queue to wait on, so we relax a bit here.
CPPsleep(1);
# Outputbuffer is free'ed by the audiothread, so should not be touched
# here.
outputbuffer = NULL
# Inputbuffer memory is owned by Numpy, so should not be free'ed
inputbuffer = NULL
2019-12-08 13:19:10 +00:00
cdef void errorCallback(RtAudioError.Type _type,const string& errortxt) nogil:
fprintf(stderr, 'RtAudio error callback called: ')
fprintf(stderr, errortxt.c_str())
fprintf(stderr, '\n')
2019-12-08 13:19:10 +00:00
cdef class RtAudio:
cdef:
cppRtAudio* _rtaudio
2020-03-30 18:23:17 +00:00
_Stream* _stream
int api
2019-12-08 13:19:10 +00:00
def __cinit__(self, unsigned int iapi):
cdef:
cppRtAudio.Api api = <cppRtAudio.Api> iapi
self._rtaudio = new cppRtAudio(api)
2020-03-30 18:23:17 +00:00
self._stream = NULL
self._rtaudio.showWarnings(True)
self.api = api
2019-12-08 13:19:10 +00:00
def __dealloc__(self):
2020-03-30 18:23:17 +00:00
if self._stream is not NULL:
# fprintf(stderr, 'Force closing stream...')
2019-12-08 13:19:10 +00:00
self._rtaudio.closeStream()
self.cleanupStream(self._stream)
del self._rtaudio
@staticmethod
def getApi():
cdef:
vector[cppRtAudio.Api] apis
cppRtAudio.getCompiledApi(apis)
apidict = {}
for api in apis:
apidict[<int> api] = {
'displayname': cppRtAudio.getApiDisplayName(api).decode('utf-8'),
'name': cppRtAudio.getApiName(api).decode('utf-8')
}
return apidict
2019-12-08 13:19:10 +00:00
cpdef unsigned int getDeviceCount(self):
return self._rtaudio.getDeviceCount()
cpdef unsigned int getDefaultOutputDevice(self):
return self._rtaudio.getDefaultOutputDevice()
cpdef unsigned int getDefaultInputDevice(self):
return self._rtaudio.getDefaultInputDevice()
def getDeviceInfo(self, unsigned int device):
"""
Return device information of the current device
"""
sampleformats = []
cdef:
cppRtAudio.DeviceInfo devinfo
devinfo = self._rtaudio.getDeviceInfo(device)
nf = devinfo.nativeFormats
2019-12-08 13:19:10 +00:00
for format_ in [ RTAUDIO_SINT8, RTAUDIO_SINT16, RTAUDIO_SINT24,
RTAUDIO_SINT32, RTAUDIO_FLOAT32, RTAUDIO_FLOAT64]:
if nf & format_:
sampleformats.append(_formats_rtkey[format_][0])
return DeviceInfo(
api = self.api,
index = device,
probed = devinfo.probed,
name = devinfo.name.decode('utf-8'),
outputchannels = devinfo.outputChannels,
inputchannels = devinfo.inputChannels,
duplexchannels = devinfo.duplexChannels,
samplerates = devinfo.sampleRates,
sampleformats = sampleformats,
prefsamplerate = devinfo.preferredSampleRate)
2019-12-08 13:19:10 +00:00
@cython.nonecheck(True)
def openStream(self,
avstream
):
2019-12-08 13:19:10 +00:00
"""
Opening a stream with specified parameters
Args:
avstream: AvStream instance
2019-12-08 13:19:10 +00:00
Returns: None
"""
2020-03-30 18:23:17 +00:00
if self._stream is not NULL:
2019-12-08 13:19:10 +00:00
raise RuntimeError('Stream is already opened.')
daqconfig = avstream.daqconfig
avtype = avstream.avtype
device = avstream.device
cdef:
bint duplex_mode = daqconfig.duplex_mode
bint monitorOutput = daqconfig.monitor_gen
unsigned int outputDelayBlocks = daqconfig.outputDelayBlocks
cppRtAudio.StreamParameters *rtOutputParams_ptr = NULL
cppRtAudio.StreamParameters *rtInputParams_ptr = NULL
cppRtAudio.StreamOptions streamoptions
size_t sw
unsigned int nFramesPerBlock = int(daqconfig.nFramesPerBlock)
int firstinputchannel, firstoutputchannel
int lastinputchannel, lastoutputchannel
unsigned int ninputchannels_forwarded=0
unsigned int ninputchannels_rtaudio=0
unsigned int noutputchannels_rtaudio=0
unsigned int noutputchannels_forwarded=0
unsigned int samplerate
int i
bint input_stream=False, output_stream=False
bool* inputChannelsEnabled
bool* outputChannelsEnabled
if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
raise ValueError('Invalid number of nFramesPerBlock')
if daqconfig.outputDelayBlocks < 0 or daqconfig.outputDelayBlocks > 10:
raise ValueError('Invalid number of outputDelayBlocks')
2019-12-08 13:19:10 +00:00
try:
# Determine sample rate and sample format, determine whether we are an
# input or an output stream, or both
if avtype == AvType.audio_input or duplex_mode:
# Here, we override the sample format in case of duplex mode.
sampleformat = daqconfig.en_input_sample_format
samplerate = int(daqconfig.en_input_rate)
input_stream = True
if duplex_mode:
output_stream = True
else:
sampleformat = daqconfig.en_output_sample_format
samplerate = int(daqconfig.en_output_rate)
output_stream = True
print(f'Is input stream: {input_stream}')
print(f'Is output stream: {output_stream}')
sw = get_sampwidth_from_format_string(sampleformat)
print(f'samplewidth: {sw}')
# All set, allocate the stream!
self._stream = <_Stream*> malloc(sizeof(_Stream))
if self._stream == NULL:
raise MemoryError('Could not allocate stream: memory error.')
self._stream.pyCallback = <PyObject*> avstream._audioCallback
# Increase reference count to the callback
Py_INCREF(<object> avstream._audioCallback)
self._stream.sampleformat = _formats_strkey[sampleformat][0]
self._stream.stopThread.store(False)
self._stream.inputQueue = NULL
self._stream.outputQueue = NULL
self._stream.outputDelayQueue = NULL
self._stream.thread = NULL
self._stream.outputDelayBlocks = outputDelayBlocks
self._stream.ninputchannels_forwarded = 0
self._stream.noutputchannels_forwarded = 0
self._stream.inputChannelsEnabled = NULL
self._stream.outputChannelsEnabled = NULL
# Create channel maps for input channels, set RtAudio input stream
# parameters
if input_stream:
firstinputchannel = daqconfig.firstEnabledInputChannelNumber()
lastinputchannel = daqconfig.lastEnabledInputChannelNumber()
ninputchannels_rtaudio = lastinputchannel-firstinputchannel+1
# print(firstinputchannel)
# print(lastinputchannel)
# print(ninputchannels_rtaudio)
if lastinputchannel < 0 or ninputchannels_rtaudio < 1:
raise ValueError('Not enough input channels selected')
input_ch = daqconfig.input_channel_configs
inputChannelsEnabled = <bool*> malloc(sizeof(bool)*ninputchannels_rtaudio)
self._stream.inputChannelsEnabled = inputChannelsEnabled
for i in range(firstinputchannel, lastinputchannel+1):
ch_en = input_ch[i].channel_enabled
if ch_en:
ninputchannels_forwarded += 1
inputChannelsEnabled[i] = ch_en
rtInputParams_ptr = &self._stream.inputParams
rtInputParams_ptr.deviceId = device.index
rtInputParams_ptr.nChannels = ninputchannels_rtaudio
rtInputParams_ptr.firstChannel = firstinputchannel
self._stream.inputQueue = new SafeQueue[void*]()
self._stream.ninputchannels_forwarded = ninputchannels_forwarded
# Create channel maps for output channels
if output_stream:
firstoutputchannel = daqconfig.firstEnabledOutputChannelNumber()
lastoutputchannel = daqconfig.lastEnabledOutputChannelNumber()
noutputchannels_rtaudio = lastoutputchannel-firstoutputchannel+1
# print(firstoutputchannel)
# print(lastoutputchannel)
# print(noutputchannels_rtaudio)
if lastoutputchannel < 0 or noutputchannels_rtaudio < 1:
raise ValueError('Not enough output channels selected')
output_ch = daqconfig.output_channel_configs
outputChannelsEnabled = <bool*> malloc(sizeof(bool)*noutputchannels_rtaudio)
self._stream.outputChannelsEnabled = outputChannelsEnabled
for i in range(firstoutputchannel, lastoutputchannel+1):
ch_en = output_ch[i].channel_enabled
if ch_en:
noutputchannels_forwarded += 1
outputChannelsEnabled[i] = ch_en
rtOutputParams_ptr = &self._stream.outputParams
rtOutputParams_ptr.deviceId = device.index
rtOutputParams_ptr.nChannels = noutputchannels_rtaudio
rtOutputParams_ptr.firstChannel = firstoutputchannel
self._stream.outputQueue = new SafeQueue[void*]()
self._stream.noutputchannels_forwarded = noutputchannels_forwarded
if monitorOutput and duplex_mode:
self._stream.outputDelayQueue = new SafeQueue[void*]()
self._stream.ninputchannels_forwarded += noutputchannels_forwarded
streamoptions.flags = RTAUDIO_HOG_DEVICE
streamoptions.flags |= RTAUDIO_NONINTERLEAVED
streamoptions.numberOfBuffers = 4
streamoptions.streamName = "LASP Audio stream".encode('utf-8')
streamoptions.priority = 1
2019-12-08 13:19:10 +00:00
self._rtaudio.openStream(rtOutputParams_ptr,
rtInputParams_ptr,
_formats_strkey[sampleformat][0],
samplerate,
&nFramesPerBlock,
2019-12-08 13:19:10 +00:00
audioCallback,
<void*> self._stream,
&streamoptions, # Stream options
errorCallback # Error callback
)
self._stream.nBytesPerChan = nFramesPerBlock*sw
self._stream.nFramesPerBlock = nFramesPerBlock
2019-12-08 13:19:10 +00:00
except Exception as e:
print('Exception occured in stream opening: ', e)
self.cleanupStream(self._stream)
2020-03-30 18:23:17 +00:00
self._stream = NULL
raise e
with nogil:
self._stream.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
<void*> self._stream)
# Allow it to start
CPPsleep(500)
pass
return nFramesPerBlock
cdef cleanupStream(self, _Stream* stream):
# printf('Entrance function cleanupStream...\n')
cdef:
void* ptr
if stream == NULL:
return
with nogil:
if stream.thread:
stream.stopThread.store(True)
if stream.inputQueue:
# If waiting in the input queue, hereby we let it run.
stream.inputQueue.enqueue(NULL)
# printf('Joining thread...\n')
# HERE WE SHOULD RELEASE THE GIL, as exiting the thread function
# will require the GIL, which is locked by this thread!
stream.thread.join()
# printf('Thread joined!\n')
del stream.thread
stream.thread = NULL
if stream.inputChannelsEnabled:
free(stream.inputChannelsEnabled)
if stream.outputChannelsEnabled:
free(stream.outputChannelsEnabled)
if stream.outputQueue:
while not stream.outputQueue.empty():
free(stream.outputQueue.dequeue())
del stream.outputQueue
if stream.inputQueue:
while not stream.inputQueue.empty():
free(stream.inputQueue.dequeue())
del stream.inputQueue
if stream.outputDelayQueue:
while not stream.outputDelayQueue.empty():
free(stream.outputDelayQueue.dequeue())
del stream.outputDelayQueue
fprintf(stderr, "End cleanup stream queues...\n")
if stream.pyCallback:
Py_DECREF(<object> stream.pyCallback)
stream.pyCallback = NULL
# fprintf(stderr, "End cleanup callback...\n")
free(stream)
2019-12-08 13:19:10 +00:00
def startStream(self):
self._rtaudio.startStream()
def stopStream(self):
2020-03-30 18:23:17 +00:00
if self._stream is NULL:
2019-12-08 13:19:10 +00:00
raise RuntimeError('Stream is not opened')
try:
self._rtaudio.stopStream()
except:
pass
2019-12-08 13:19:10 +00:00
def closeStream(self):
# print('closeStream')
2020-03-30 18:23:17 +00:00
if self._stream is NULL:
2019-12-08 13:19:10 +00:00
raise RuntimeError('Stream is not opened')
# Closing stream
self._rtaudio.closeStream()
self.cleanupStream(self._stream)
2020-03-30 18:23:17 +00:00
self._stream = NULL
2019-12-08 13:19:10 +00:00
def abortStream(self):
2020-03-30 18:23:17 +00:00
if self._stream is NULL:
2019-12-08 13:19:10 +00:00
raise RuntimeError('Stream is not opened')
self._rtaudio.abortStream()
def isStreamOpen(self):
return self._rtaudio.isStreamOpen()
def isStreamRunning(self):
return self._rtaudio.isStreamRunning()
def getStreamTime(self):
return self._rtaudio.getStreamTime()
def setStreamTime(self, double time):
return self._rtaudio.setStreamTime(time)