now using a mutex to protect libportaudio access

This commit is contained in:
Spencer Russell 2020-01-02 14:23:41 -05:00
parent 16d0bc48be
commit f123478231

View file

@ -47,38 +47,37 @@ const paAbort = PaStreamCallbackResult(2)
Call the given expression in a separate thread, waiting on the result. This is Call the given expression in a separate thread, waiting on the result. This is
useful when running code that would otherwise block the Julia process (like a useful when running code that would otherwise block the Julia process (like a
`ccall` into a function that does IO). `ccall` into a function that does IO).
This will wait for the threaded call to complete even if an exception is thrown.
""" """
macro tcall(ex) macro tcall(ex)
:(fetch(Base.Threads.@spawn $(esc(ex))))
end
# because we're calling Pa_ReadStream and PA_WriteStream from separate threads,
# we put a mutex around libportaudio calls
const pamutex = ReentrantLock()
macro locked(ex)
quote quote
t = Base.Threads.@spawn $(esc(ex)) lock(pamutex) do
try $(esc(ex))
fetch(t)
catch
# even if we got an exception (like an interrupt exception) make sure
# we wait for the spawned call to complete so we don't clean up
# any of its resources
wait(t)
rethrow()
end end
end end
end end
function Pa_Initialize() function Pa_Initialize()
err = ccall((:Pa_Initialize, libportaudio), PaError, ()) err = @locked ccall((:Pa_Initialize, libportaudio), PaError, ())
handle_status(err) handle_status(err)
end end
function Pa_Terminate() function Pa_Terminate()
err = ccall((:Pa_Terminate, libportaudio), PaError, ()) err = @locked ccall((:Pa_Terminate, libportaudio), PaError, ())
handle_status(err) handle_status(err)
end end
Pa_GetVersion() = ccall((:Pa_GetVersion, libportaudio), Cint, ()) Pa_GetVersion() = @locked ccall((:Pa_GetVersion, libportaudio), Cint, ())
function Pa_GetVersionText() function Pa_GetVersionText()
versionPtr = ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()) versionPtr = @locked ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ())
unsafe_string(versionPtr) unsafe_string(versionPtr)
end end
@ -117,7 +116,7 @@ mutable struct PaHostApiInfo
defaultOutputDevice::PaDeviceIndex defaultOutputDevice::PaDeviceIndex
end end
Pa_GetHostApiInfo(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, libportaudio), Pa_GetHostApiInfo(i) = unsafe_load(@locked ccall((:Pa_GetHostApiInfo, libportaudio),
Ptr{PaHostApiInfo}, (PaHostApiIndex,), i)) Ptr{PaHostApiInfo}, (PaHostApiIndex,), i))
# Device Functions # Device Functions
@ -135,15 +134,15 @@ mutable struct PaDeviceInfo
default_sample_rate::Cdouble default_sample_rate::Cdouble
end end
Pa_GetDeviceCount() = ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) Pa_GetDeviceCount() = @locked ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ())
Pa_GetDeviceInfo(i) = unsafe_load(ccall((:Pa_GetDeviceInfo, libportaudio), Pa_GetDeviceInfo(i) = unsafe_load(@locked ccall((:Pa_GetDeviceInfo, libportaudio),
Ptr{PaDeviceInfo}, (PaDeviceIndex,), i)) Ptr{PaDeviceInfo}, (PaDeviceIndex,), i))
Pa_GetDefaultInputDevice() = ccall((:Pa_GetDefaultInputDevice, libportaudio), Pa_GetDefaultInputDevice() = @locked ccall((:Pa_GetDefaultInputDevice, libportaudio),
PaDeviceIndex, ()) PaDeviceIndex, ())
Pa_GetDefaultOutputDevice() = ccall((:Pa_GetDefaultOutputDevice, libportaudio), Pa_GetDefaultOutputDevice() = @locked ccall((:Pa_GetDefaultOutputDevice, libportaudio),
PaDeviceIndex, ()) PaDeviceIndex, ())
# Stream Functions # Stream Functions
@ -183,7 +182,7 @@ function Pa_OpenStream(inParams, outParams,
flags::PaStreamFlags, flags::PaStreamFlags,
callback, userdata) callback, userdata)
streamPtr = Ref{PaStream}(0) streamPtr = Ref{PaStream}(0)
err = ccall((:Pa_OpenStream, libportaudio), PaError, err = @locked ccall((:Pa_OpenStream, libportaudio), PaError,
(Ref{PaStream}, Ref{Pa_StreamParameters}, Ref{Pa_StreamParameters}, (Ref{PaStream}, Ref{Pa_StreamParameters}, Ref{Pa_StreamParameters},
Cdouble, Culong, PaStreamFlags, Ref{Cvoid}, Cdouble, Culong, PaStreamFlags, Ref{Cvoid},
# it seems like we should be able to use Ref{T} here, with # it seems like we should be able to use Ref{T} here, with
@ -200,32 +199,32 @@ function Pa_OpenStream(inParams, outParams,
end end
function Pa_StartStream(stream::PaStream) function Pa_StartStream(stream::PaStream)
err = ccall((:Pa_StartStream, libportaudio), PaError, err = @locked ccall((:Pa_StartStream, libportaudio), PaError,
(PaStream,), stream) (PaStream,), stream)
handle_status(err) handle_status(err)
end end
function Pa_StopStream(stream::PaStream) function Pa_StopStream(stream::PaStream)
err = ccall((:Pa_StopStream, libportaudio), PaError, err = @locked ccall((:Pa_StopStream, libportaudio), PaError,
(PaStream,), stream) (PaStream,), stream)
handle_status(err) handle_status(err)
end end
function Pa_CloseStream(stream::PaStream) function Pa_CloseStream(stream::PaStream)
err = ccall((:Pa_CloseStream, libportaudio), PaError, err = @locked ccall((:Pa_CloseStream, libportaudio), PaError,
(PaStream,), stream) (PaStream,), stream)
handle_status(err) handle_status(err)
end end
function Pa_GetStreamReadAvailable(stream::PaStream) function Pa_GetStreamReadAvailable(stream::PaStream)
avail = ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, avail = @locked ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong,
(PaStream,), stream) (PaStream,), stream)
avail >= 0 || handle_status(avail) avail >= 0 || handle_status(avail)
avail avail
end end
function Pa_GetStreamWriteAvailable(stream::PaStream) function Pa_GetStreamWriteAvailable(stream::PaStream)
avail = ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, avail = @locked ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong,
(PaStream,), stream) (PaStream,), stream)
avail >= 0 || handle_status(avail) avail >= 0 || handle_status(avail)
avail avail
@ -234,7 +233,7 @@ end
function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf), function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf),
show_warnings::Bool=true) show_warnings::Bool=true)
frames <= length(buf) || error("Need a buffer at least $frames long") frames <= length(buf) || error("Need a buffer at least $frames long")
err = @tcall ccall((:Pa_ReadStream, libportaudio), PaError, err = @tcall @locked ccall((:Pa_ReadStream, libportaudio), PaError,
(PaStream, Ptr{Cvoid}, Culong), (PaStream, Ptr{Cvoid}, Culong),
stream, buf, frames) stream, buf, frames)
handle_status(err, show_warnings) handle_status(err, show_warnings)
@ -244,7 +243,7 @@ end
function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf), function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf),
show_warnings::Bool=true) show_warnings::Bool=true)
frames <= length(buf) || error("Need a buffer at least $frames long") frames <= length(buf) || error("Need a buffer at least $frames long")
err = @tcall ccall((:Pa_WriteStream, libportaudio), PaError, err = @tcall @locked ccall((:Pa_WriteStream, libportaudio), PaError,
(PaStream, Ptr{Cvoid}, Culong), (PaStream, Ptr{Cvoid}, Culong),
stream, buf, frames) stream, buf, frames)
handle_status(err, show_warnings) handle_status(err, show_warnings)
@ -262,12 +261,12 @@ end
function handle_status(err::PaError, show_warnings::Bool=true) function handle_status(err::PaError, show_warnings::Bool=true)
if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED
if show_warnings if show_warnings
msg = ccall((:Pa_GetErrorText, libportaudio), msg = @locked ccall((:Pa_GetErrorText, libportaudio),
Ptr{Cchar}, (PaError,), err) Ptr{Cchar}, (PaError,), err)
@warn("libportaudio: " * unsafe_string(msg)) @warn("libportaudio: " * unsafe_string(msg))
end end
elseif err != PA_NO_ERROR elseif err != PA_NO_ERROR
msg = ccall((:Pa_GetErrorText, libportaudio), msg = @locked ccall((:Pa_GetErrorText, libportaudio),
Ptr{Cchar}, (PaError,), err) Ptr{Cchar}, (PaError,), err)
throw(ErrorException("libportaudio: " * unsafe_string(msg))) throw(ErrorException("libportaudio: " * unsafe_string(msg)))
end end