mostly adding tests and fixing bugs. passing tests now
This commit is contained in:
parent
9e3e66d37a
commit
0c36e1eec5
4 changed files with 293 additions and 132 deletions
51
deps/src/pa_shim.c
vendored
51
deps/src/pa_shim.c
vendored
|
@ -60,34 +60,43 @@ int pa_shim_processcb(const void *input, void *output,
|
||||||
if(info->notifycb == NULL) {
|
if(info->notifycb == NULL) {
|
||||||
fprintf(stderr, "pa_shim ERROR: notifycb is NULL\n");
|
fprintf(stderr, "pa_shim ERROR: notifycb is NULL\n");
|
||||||
}
|
}
|
||||||
|
int nwrite;
|
||||||
int nwrite = PaUtil_GetRingBufferWriteAvailable(info->inputbuf);
|
if(info->inputbuf) {
|
||||||
int nread = PaUtil_GetRingBufferReadAvailable(info->outputbuf);
|
nwrite = PaUtil_GetRingBufferWriteAvailable(info->inputbuf);
|
||||||
nwrite = MIN(frameCount, nwrite);
|
nwrite = MIN(frameCount, nwrite);
|
||||||
nread = MIN(frameCount, nread);
|
}
|
||||||
if(info->sync) {
|
int nread;
|
||||||
|
if(info->outputbuf) {
|
||||||
|
nread = PaUtil_GetRingBufferReadAvailable(info->outputbuf);
|
||||||
|
nread = MIN(frameCount, nread);
|
||||||
|
}
|
||||||
|
if(info->inputbuf && info->outputbuf && info->sync) {
|
||||||
// to keep the buffers synchronized, set readable and writable to
|
// to keep the buffers synchronized, set readable and writable to
|
||||||
// their minimum value
|
// their minimum value
|
||||||
nread = MIN(nread, nwrite);
|
nread = MIN(nread, nwrite);
|
||||||
nwrite = nread;
|
nwrite = nread;
|
||||||
}
|
}
|
||||||
// read/write from the ringbuffers
|
// read/write from the ringbuffers
|
||||||
PaUtil_WriteRingBuffer(info->inputbuf, input, nwrite);
|
if(info->inputbuf) {
|
||||||
if(info->notifycb) {
|
PaUtil_WriteRingBuffer(info->inputbuf, input, nwrite);
|
||||||
info->notifycb(info->inputhandle);
|
if(info->notifycb) {
|
||||||
|
info->notifycb(info->inputhandle);
|
||||||
|
}
|
||||||
|
if(nwrite < frameCount) {
|
||||||
|
senderr(info, PA_SHIM_ERRMSG_OVERFLOW);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PaUtil_ReadRingBuffer(info->outputbuf, output, nread);
|
if(info->outputbuf) {
|
||||||
if(info->notifycb) {
|
PaUtil_ReadRingBuffer(info->outputbuf, output, nread);
|
||||||
info->notifycb(info->outputhandle);
|
if(info->notifycb) {
|
||||||
}
|
info->notifycb(info->outputhandle);
|
||||||
if(nwrite < frameCount) {
|
}
|
||||||
senderr(info, PA_SHIM_ERRMSG_OVERFLOW);
|
if(nread < frameCount) {
|
||||||
}
|
senderr(info, PA_SHIM_ERRMSG_UNDERFLOW);
|
||||||
if(nread < frameCount) {
|
// we didn't fill the whole output buffer, so zero it out
|
||||||
senderr(info, PA_SHIM_ERRMSG_UNDERFLOW);
|
memset(output+nread*info->outputbuf->elementSizeBytes, 0,
|
||||||
// we didn't fill the whole output buffer, so zero it out
|
(frameCount - nread)*info->outputbuf->elementSizeBytes);
|
||||||
memset(output+nread*info->outputbuf->elementSizeBytes, 0,
|
}
|
||||||
(frameCount - nread)*info->outputbuf->elementSizeBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return paContinue;
|
return paContinue;
|
||||||
|
|
|
@ -8,6 +8,10 @@ using Suppressor
|
||||||
|
|
||||||
using Base: AsyncCondition
|
using Base: AsyncCondition
|
||||||
|
|
||||||
|
import Base: eltype, show
|
||||||
|
import Base: close, isopen
|
||||||
|
import Base: read, read!, write, flush
|
||||||
|
|
||||||
# Get binary dependencies loaded from BinDeps
|
# Get binary dependencies loaded from BinDeps
|
||||||
include("../deps/deps.jl")
|
include("../deps/deps.jl")
|
||||||
include("pa_shim.jl")
|
include("pa_shim.jl")
|
||||||
|
@ -20,7 +24,6 @@ function __init__()
|
||||||
@suppress_err Pa_Initialize()
|
@suppress_err Pa_Initialize()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
export PortAudioStream
|
export PortAudioStream
|
||||||
|
|
||||||
# These sizes are all in frames
|
# These sizes are all in frames
|
||||||
|
@ -41,7 +44,7 @@ const ERR_BUFSIZE=512
|
||||||
function versioninfo(io::IO=STDOUT)
|
function versioninfo(io::IO=STDOUT)
|
||||||
println(io, Pa_GetVersionText())
|
println(io, Pa_GetVersionText())
|
||||||
println(io, "Version: ", Pa_GetVersion())
|
println(io, "Version: ", Pa_GetVersion())
|
||||||
println(io, "Shim Version: ", shimversion())
|
println(io, "Shim Source Hash: ", shimhash()[1:10])
|
||||||
end
|
end
|
||||||
|
|
||||||
type PortAudioDevice
|
type PortAudioDevice
|
||||||
|
@ -104,13 +107,15 @@ type PortAudioStream{T}
|
||||||
# we've got a synchronized duplex stream. initialize with the output buffer full
|
# we've got a synchronized duplex stream. initialize with the output buffer full
|
||||||
write(this.sink, SampleBuf(zeros(T, blocksize*2, outchans), sr))
|
write(this.sink, SampleBuf(zeros(T, blocksize*2, outchans), sr))
|
||||||
end
|
end
|
||||||
this.bufinfo = pa_shim_info_t(bufpointer(this.source),
|
# pass NULL for input/output we're not using
|
||||||
bufpointer(this.sink),
|
this.bufinfo = pa_shim_info_t(
|
||||||
pointer(this.errbuf),
|
inchans > 0 ? bufpointer(this.source) : C_NULL,
|
||||||
synced, notifycb_c,
|
outchans > 0 ? bufpointer(this.sink) : C_NULL,
|
||||||
getnotifyhandle(this.sink),
|
pointer(this.errbuf),
|
||||||
getnotifyhandle(this.source),
|
synced, notifycb_c,
|
||||||
getnotifyhandle(this.errbuf))
|
inchans > 0 ? notifyhandle(this.source) : C_NULL,
|
||||||
|
outchans > 0 ? notifyhandle(this.sink) : C_NULL,
|
||||||
|
notifyhandle(this.errbuf))
|
||||||
this.stream = @suppress_err Pa_OpenStream(inparams, outparams,
|
this.stream = @suppress_err Pa_OpenStream(inparams, outparams,
|
||||||
float(sr), blocksize,
|
float(sr), blocksize,
|
||||||
paNoFlag, shim_processcb_c,
|
paNoFlag, shim_processcb_c,
|
||||||
|
@ -184,7 +189,7 @@ function PortAudioStream(inchans=2, outchans=2; kwargs...)
|
||||||
PortAudioStream(indevice, outdevice, inchans, outchans; kwargs...)
|
PortAudioStream(indevice, outdevice, inchans, outchans; kwargs...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Base.close(stream::PortAudioStream)
|
function close(stream::PortAudioStream)
|
||||||
if stream.stream != C_NULL
|
if stream.stream != C_NULL
|
||||||
Pa_StopStream(stream.stream)
|
Pa_StopStream(stream.stream)
|
||||||
Pa_CloseStream(stream.stream)
|
Pa_CloseStream(stream.stream)
|
||||||
|
@ -196,18 +201,18 @@ function Base.close(stream::PortAudioStream)
|
||||||
nothing
|
nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
Base.isopen(stream::PortAudioStream) = stream.stream != C_NULL
|
isopen(stream::PortAudioStream) = stream.stream != C_NULL
|
||||||
|
|
||||||
SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate
|
SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate
|
||||||
Base.eltype{T}(stream::PortAudioStream{T}) = T
|
eltype{T}(stream::PortAudioStream{T}) = T
|
||||||
|
|
||||||
Base.read(stream::PortAudioStream, args...) = read(stream.source, args...)
|
read(stream::PortAudioStream, args...) = read(stream.source, args...)
|
||||||
Base.read!(stream::PortAudioStream, args...) = read!(stream.source, args...)
|
read!(stream::PortAudioStream, args...) = read!(stream.source, args...)
|
||||||
Base.write(stream::PortAudioStream, args...) = write(stream.sink, args...)
|
write(stream::PortAudioStream, args...) = write(stream.sink, args...)
|
||||||
Base.write(sink::PortAudioStream, source::PortAudioStream, args...) = write(sink.sink, source.source, args...)
|
write(sink::PortAudioStream, source::PortAudioStream, args...) = write(sink.sink, source.source, args...)
|
||||||
Base.flush(stream::PortAudioStream) = flush(stream.sink)
|
flush(stream::PortAudioStream) = flush(stream.sink)
|
||||||
|
|
||||||
function Base.show(io::IO, stream::PortAudioStream)
|
function show(io::IO, stream::PortAudioStream)
|
||||||
println(io, typeof(stream))
|
println(io, typeof(stream))
|
||||||
println(io, " Samplerate: ", samplerate(stream), "Hz")
|
println(io, " Samplerate: ", samplerate(stream), "Hz")
|
||||||
print(io, " Buffer Size: ", stream.blocksize, " frames")
|
print(io, " Buffer Size: ", stream.blocksize, " frames")
|
||||||
|
@ -272,23 +277,19 @@ end
|
||||||
SampledSignals.nchannels(s::Union{PortAudioSink, PortAudioSource}) = s.nchannels
|
SampledSignals.nchannels(s::Union{PortAudioSink, PortAudioSource}) = s.nchannels
|
||||||
SampledSignals.samplerate(s::Union{PortAudioSink, PortAudioSource}) = samplerate(s.stream)
|
SampledSignals.samplerate(s::Union{PortAudioSink, PortAudioSource}) = samplerate(s.stream)
|
||||||
SampledSignals.blocksize(s::Union{PortAudioSink, PortAudioSource}) = s.stream.blocksize
|
SampledSignals.blocksize(s::Union{PortAudioSink, PortAudioSource}) = s.stream.blocksize
|
||||||
Base.eltype(::Union{PortAudioSink{T}, PortAudioSource{T}}) where {T} = T
|
eltype(::Union{PortAudioSink{T}, PortAudioSource{T}}) where {T} = T
|
||||||
Base.close(s::Union{PortAudioSink, PortAudioSource}) = close(s.ringbuf)
|
close(s::Union{PortAudioSink, PortAudioSource}) = close(s.ringbuf)
|
||||||
Base.isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.ringbuf)
|
isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.ringbuf)
|
||||||
RingBuffers.getnotifyhandle(s::Union{PortAudioSink, PortAudioSource}) = getnotifyhandle(s.ringbuf)
|
RingBuffers.notifyhandle(s::Union{PortAudioSink, PortAudioSource}) = notifyhandle(s.ringbuf)
|
||||||
bufpointer(s::Union{PortAudioSink, PortAudioSource}) = pointer(s.ringbuf)
|
bufpointer(s::Union{PortAudioSink, PortAudioSource}) = pointer(s.ringbuf)
|
||||||
name(s::Union{PortAudioSink, PortAudioSource}) = s.name
|
name(s::Union{PortAudioSink, PortAudioSource}) = s.name
|
||||||
|
|
||||||
function Base.show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}}
|
function show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}}
|
||||||
println(io, T, "(\"", stream.name, "\")")
|
println(io, T, "(\"", stream.name, "\")")
|
||||||
print(io, nchannels(stream), " channels")
|
print(io, nchannels(stream), " channels")
|
||||||
end
|
end
|
||||||
|
|
||||||
# function Base.flush(sink::PortAudioSink)
|
flush(sink::PortAudioSink) = flush(sink.ringbuf)
|
||||||
# while nwritable(sink.ringbuf) < length(sink.ringbuf)
|
|
||||||
# wait(sink.ringbuf)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffset, framecount)
|
function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffset, framecount)
|
||||||
nwritten = 0
|
nwritten = 0
|
||||||
|
|
|
@ -240,11 +240,11 @@ function handle_status(err::PaError, show_warnings::Bool=true)
|
||||||
if show_warnings
|
if show_warnings
|
||||||
msg = ccall((:Pa_GetErrorText, libportaudio),
|
msg = ccall((:Pa_GetErrorText, libportaudio),
|
||||||
Ptr{Cchar}, (PaError,), err)
|
Ptr{Cchar}, (PaError,), err)
|
||||||
warn("libportaudio: " * bytestring(msg))
|
warn("libportaudio: " * unsafe_string(msg))
|
||||||
end
|
end
|
||||||
elseif err != PA_NO_ERROR
|
elseif err != PA_NO_ERROR
|
||||||
msg = ccall((:Pa_GetErrorText, libportaudio),
|
msg = ccall((:Pa_GetErrorText, libportaudio),
|
||||||
Ptr{Cchar}, (PaError,), err)
|
Ptr{Cchar}, (PaError,), err)
|
||||||
error("libportaudio: " * bytestring(msg))
|
error("libportaudio: " * unsafe_string(msg))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
313
test/runtests.jl
313
test/runtests.jl
|
@ -1,86 +1,193 @@
|
||||||
#!/usr/bin/env julia
|
#!/usr/bin/env julia
|
||||||
|
|
||||||
if VERSION >= v"0.5.0-dev+7720"
|
using Base.Test
|
||||||
using Base.Test
|
using TestSetExtensions
|
||||||
else
|
|
||||||
using BaseTestNext
|
|
||||||
end
|
|
||||||
using PortAudio
|
using PortAudio
|
||||||
using SampledSignals
|
using SampledSignals
|
||||||
using RingBuffers
|
using RingBuffers
|
||||||
|
|
||||||
function test_callback(inchans, outchans)
|
# pull in some extra stuff we need to test the callback directly
|
||||||
nframes = Culong(8)
|
using PortAudio: notifyhandle, notifycb_c, shim_processcb_c
|
||||||
|
using PortAudio: pa_shim_errmsg_t, pa_shim_info_t
|
||||||
|
using PortAudio: PA_SHIM_ERRMSG_ERR_OVERFLOW, PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW
|
||||||
|
|
||||||
cb = PortAudio.pa_callbacks[Float32]
|
"Setup buffers to test callback behavior"
|
||||||
inbuf = rand(Float32, inchans*nframes) # simulate microphone input
|
function setup_callback(inchans, outchans, nframes, synced)
|
||||||
sourcebuf = LockFreeRingBuffer(Float32, inchans*nframes*8) # the microphone input should end up here
|
sourcebuf = RingBuffer{Float32}(inchans, nframes*2) # the microphone input should end up here
|
||||||
|
sinkbuf = RingBuffer{Float32}(outchans, nframes*2) # the callback should copy this to cb_output
|
||||||
|
errbuf = RingBuffer{pa_shim_errmsg_t}(1, 8)
|
||||||
|
|
||||||
outbuf = zeros(Float32, outchans*nframes) # this is where the output should go
|
# pass NULL for i/o we're not using
|
||||||
sinkbuf = LockFreeRingBuffer(Float32, outchans*nframes*8) # the callback should copy this to outbuf
|
info = pa_shim_info_t(
|
||||||
|
inchans > 0 ? pointer(sourcebuf) : C_NULL,
|
||||||
# 2 input channels, 3 output channels
|
outchans > 0 ? pointer(sinkbuf) : C_NULL,
|
||||||
info = PortAudio.CallbackInfo(inchans, sourcebuf, outchans, sinkbuf, true)
|
pointer(errbuf),
|
||||||
|
synced, notifycb_c,
|
||||||
# handle any conversions here so they don't mess with the allocation
|
inchans > 0 ? notifyhandle(sourcebuf) : C_NULL,
|
||||||
# the seemingly-redundant type specifiers avoid some allocation during the ccall.
|
outchans > 0 ? notifyhandle(sinkbuf) : C_NULL,
|
||||||
# might be due to https://github.com/JuliaLang/julia/issues/15276
|
notifyhandle(errbuf)
|
||||||
inptr::Ptr{Float32} = Ptr{Float32}(pointer(inbuf))
|
)
|
||||||
outptr::Ptr{Float32} = Ptr{Float32}(pointer(outbuf))
|
|
||||||
flags = Culong(0)
|
flags = Culong(0)
|
||||||
infoptr::Ptr{PortAudio.CallbackInfo{Float32}} = Ptr{PortAudio.CallbackInfo{Float32}}(pointer_from_objref(info))
|
|
||||||
|
|
||||||
testin = zeros(Float32, inchans*nframes)
|
cb_input = rand(Float32, inchans, nframes) # simulate microphone input
|
||||||
testout = rand(Float32, outchans*nframes)
|
cb_output = rand(Float32, outchans, nframes) # this is where the output should go
|
||||||
write(sinkbuf, testout) # fill the output ringbuffer
|
|
||||||
ret = ccall(cb, Cint,
|
|
||||||
(Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}),
|
|
||||||
inptr, outptr, nframes, C_NULL, flags, infoptr)
|
|
||||||
@test ret === PortAudio.paContinue
|
|
||||||
@test outbuf == testout
|
|
||||||
read!(sourcebuf, testin)
|
|
||||||
@test inbuf == testin
|
|
||||||
|
|
||||||
|
function processfunc()
|
||||||
|
ccall(shim_processcb_c, Cint,
|
||||||
|
(Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{Void}),
|
||||||
|
cb_input, cb_output, nframes, C_NULL, flags, pointer_from_objref(info))
|
||||||
|
end
|
||||||
|
|
||||||
|
(sourcebuf, sinkbuf, errbuf, cb_input, cb_output, processfunc)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_callback(inchans, outchans, synced)
|
||||||
|
nframes = 8
|
||||||
|
(sourcebuf, sinkbuf, errbuf,
|
||||||
|
cb_input, cb_output, process) = setup_callback(inchans, outchans,
|
||||||
|
nframes, synced)
|
||||||
if outchans > 0
|
if outchans > 0
|
||||||
underfill = 3 # should be less than nframes
|
testout = rand(Float32, outchans, nframes) # generate some test data to play
|
||||||
testout = rand(Float32, outchans*underfill)
|
|
||||||
write(sinkbuf, testout) # underfill the output ringbuffer
|
|
||||||
# call again (partial underrun)
|
|
||||||
ret = ccall(cb, Cint,
|
|
||||||
(Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}),
|
|
||||||
inptr, outptr, nframes, C_NULL, flags, infoptr)
|
|
||||||
@test ret === PortAudio.paContinue
|
|
||||||
@test outbuf[1:outchans*underfill] == testout
|
|
||||||
@test outbuf[outchans*underfill+1:outchans*nframes] == zeros(Float32, (nframes-underfill)*outchans)
|
|
||||||
@test nreadable(sourcebuf) == inchans*underfill
|
|
||||||
@test read!(sourcebuf, testin) == inchans*underfill
|
|
||||||
@test testin[1:inchans*underfill] == inbuf[1:inchans*underfill]
|
|
||||||
|
|
||||||
# call again (total underrun)
|
|
||||||
ret = ccall(cb, Cint,
|
|
||||||
(Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}),
|
|
||||||
inptr, outptr, nframes, C_NULL, flags, infoptr)
|
|
||||||
@test ret === PortAudio.paContinue
|
|
||||||
@test outbuf == zeros(Float32, outchans*nframes)
|
|
||||||
@test nreadable(sourcebuf) == 0
|
|
||||||
|
|
||||||
write(sinkbuf, testout) # fill the output ringbuffer
|
write(sinkbuf, testout) # fill the output ringbuffer
|
||||||
# test allocation
|
end
|
||||||
alloc = @allocated ccall(cb, Cint,
|
@test process() == PortAudio.paContinue
|
||||||
(Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}),
|
if outchans > 0
|
||||||
inptr, outptr, nframes, C_NULL, flags, infoptr)
|
# testout -> sinkbuf -> cb_output
|
||||||
@test alloc == 0
|
@test cb_output == testout
|
||||||
# now test allocation in underrun state
|
end
|
||||||
alloc = @allocated ccall(cb, Cint,
|
if inchans > 0
|
||||||
(Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{PortAudio.CallbackInfo{Float32}}),
|
# cb_input -> sourcebuf
|
||||||
inptr, outptr, nframes, C_NULL, flags, infoptr)
|
@test read(sourcebuf, nframes) == cb_input
|
||||||
@test alloc == 0
|
end
|
||||||
|
@test framesreadable(errbuf) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
test_callback_underflow(inchans, outchans; nframes=8, underfill=3, synced=false)
|
||||||
|
|
||||||
|
Test that the callback works on underflow conditions. underfill is the numer of
|
||||||
|
frames we feed in, which should be less than nframes.
|
||||||
|
"""
|
||||||
|
function test_callback_underflow(inchans, outchans, synced)
|
||||||
|
nframes = 8
|
||||||
|
underfill = 3 # must be less than nframes
|
||||||
|
(sourcebuf, sinkbuf, errbuf,
|
||||||
|
cb_input, cb_output, process) = setup_callback(inchans, outchans,
|
||||||
|
nframes, synced)
|
||||||
|
outchans > 0 || error("Can't test underflow with no output")
|
||||||
|
testout = rand(Float32, outchans, underfill)
|
||||||
|
write(sinkbuf, testout) # underfill the output ringbuffer
|
||||||
|
# call callback (partial underflow)
|
||||||
|
@test process() == PortAudio.paContinue
|
||||||
|
@test cb_output[:, 1:underfill] == testout
|
||||||
|
@test cb_output[:, (underfill+1):nframes] == zeros(Float32, outchans, (nframes-underfill))
|
||||||
|
errs = readavailable(errbuf)
|
||||||
|
if inchans > 0
|
||||||
|
received = readavailable(sourcebuf)
|
||||||
|
if synced
|
||||||
|
@test size(received, 2) == underfill
|
||||||
|
@test received == cb_input[:, 1:underfill]
|
||||||
|
@test length(errs) == 2
|
||||||
|
@test Set(errs) == Set([PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW])
|
||||||
|
else
|
||||||
|
@test size(received, 2) == nframes
|
||||||
|
@test received == cb_input
|
||||||
|
@test length(errs) == 1
|
||||||
|
@test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@test length(errs) == 1
|
||||||
|
@test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW
|
||||||
|
end
|
||||||
|
|
||||||
|
# call again (total underflow)
|
||||||
|
@test process() == PortAudio.paContinue
|
||||||
|
@test cb_output == zeros(Float32, outchans, nframes)
|
||||||
|
errs = readavailable(errbuf)
|
||||||
|
if inchans > 0
|
||||||
|
received = readavailable(sourcebuf)
|
||||||
|
if synced
|
||||||
|
@test size(received, 2) == 0
|
||||||
|
@test length(errs) == 2
|
||||||
|
@test Set(errs) == Set([PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW])
|
||||||
|
else
|
||||||
|
@test size(received, 2) == nframes
|
||||||
|
@test received == cb_input
|
||||||
|
@test length(errs) == 1
|
||||||
|
@test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@test length(errs) == 1
|
||||||
|
@test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_callback_overflow(inchans, outchans, synced)
|
||||||
|
nframes = 8
|
||||||
|
(sourcebuf, sinkbuf, errbuf,
|
||||||
|
cb_input, cb_output, process) = setup_callback(inchans, outchans,
|
||||||
|
nframes, synced)
|
||||||
|
inchans > 0 || error("Can't test overflow with no input")
|
||||||
|
@test frameswritable(sinkbuf) == nframes*2
|
||||||
|
|
||||||
|
# the first time it should half-fill the input ring buffer
|
||||||
|
if outchans > 0
|
||||||
|
testout = rand(Float32, outchans, nframes)
|
||||||
|
write(sinkbuf, testout)
|
||||||
|
end
|
||||||
|
@test framesreadable(sourcebuf) == 0
|
||||||
|
outchans > 0 && @test frameswritable(sinkbuf) == nframes
|
||||||
|
@test process() == PortAudio.paContinue
|
||||||
|
@test framesreadable(errbuf) == 0
|
||||||
|
@test framesreadable(sourcebuf) == nframes
|
||||||
|
outchans > 0 && @test frameswritable(sinkbuf) == nframes*2
|
||||||
|
|
||||||
|
# now run the process func again to completely fill the input ring buffer
|
||||||
|
outchans > 0 && write(sinkbuf, testout)
|
||||||
|
@test framesreadable(sourcebuf) == nframes
|
||||||
|
outchans > 0 && @test frameswritable(sinkbuf) == nframes
|
||||||
|
@test process() == PortAudio.paContinue
|
||||||
|
@test framesreadable(errbuf) == 0
|
||||||
|
@test framesreadable(sourcebuf) == nframes*2
|
||||||
|
outchans > 0 && @test frameswritable(sinkbuf) == nframes*2
|
||||||
|
|
||||||
|
# now this time the process func should overflow the input buffer
|
||||||
|
outchans > 0 && write(sinkbuf, testout)
|
||||||
|
@test framesreadable(sourcebuf) == nframes*2
|
||||||
|
outchans > 0 && @test frameswritable(sinkbuf) == nframes
|
||||||
|
@test process() == PortAudio.paContinue
|
||||||
|
@test framesreadable(sourcebuf) == nframes*2
|
||||||
|
errs = readavailable(errbuf)
|
||||||
|
if outchans > 0
|
||||||
|
if synced
|
||||||
|
# if input and output are synced, thec callback didn't pull from
|
||||||
|
# the output ringbuf
|
||||||
|
@test frameswritable(sinkbuf) == nframes
|
||||||
|
@test cb_output == zeros(Float32, outchans, nframes)
|
||||||
|
@test length(errs) == 2
|
||||||
|
@test Set(errs) == Set([PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW])
|
||||||
|
else
|
||||||
|
@test frameswritable(sinkbuf) == nframes*2
|
||||||
|
@test length(errs) == 1
|
||||||
|
@test errs[1] == PA_SHIM_ERRMSG_OVERFLOW
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@test length(errs) == 1
|
||||||
|
@test errs[1] == PA_SHIM_ERRMSG_OVERFLOW
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# these test are currently set up to run on OSX
|
# these test are currently set up to run on OSX
|
||||||
|
|
||||||
@testset "PortAudio Tests" begin
|
@testset DottedTestSet "PortAudio Tests" begin
|
||||||
|
if is_windows()
|
||||||
|
default_indev = "Microphone Array (Realtek High "
|
||||||
|
default_outdev = "Speaker/Headphone (Realtek High"
|
||||||
|
elseif is_apple()
|
||||||
|
default_indev = "Built-in Microph"
|
||||||
|
default_outdev = "Built-in Output"
|
||||||
|
end
|
||||||
|
|
||||||
devs = PortAudio.devices()
|
devs = PortAudio.devices()
|
||||||
i = findfirst(d -> d.maxinchans > 0, devs)
|
i = findfirst(d -> d.maxinchans > 0, devs)
|
||||||
indev = i > 0 ? devs[i] : nothing
|
indev = i > 0 ? devs[i] : nothing
|
||||||
|
@ -92,24 +199,68 @@ end
|
||||||
@testset "Reports version" begin
|
@testset "Reports version" begin
|
||||||
io = IOBuffer()
|
io = IOBuffer()
|
||||||
PortAudio.versioninfo(io)
|
PortAudio.versioninfo(io)
|
||||||
result = takebuf_string(io)
|
result = split(String(take!((io))), "\n")
|
||||||
# make sure this is the same version I tested with
|
# make sure this is the same version I tested with
|
||||||
@test result ==
|
@test startswith(result[1], "PortAudio V19-devel")
|
||||||
"""PortAudio V19-devel (built Aug 6 2014 17:54:39)
|
@test result[2] == "Version: 1899"
|
||||||
Version Number: 1899
|
@test result[3] == "Shim Source Hash: 4bfafb6888"
|
||||||
"""
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@testset "PortAudio Callback works for duplex stream" begin
|
@testset "Basic callback functionality" begin
|
||||||
test_callback(2, 3)
|
@testset "basic duplex (no sync)" begin
|
||||||
|
test_callback(2, 3, false)
|
||||||
|
end
|
||||||
|
@testset "basic input-only (no sync)" begin
|
||||||
|
test_callback(2, 0, false)
|
||||||
|
end
|
||||||
|
@testset "basic output-only (no sync)" begin
|
||||||
|
test_callback(0, 2, false)
|
||||||
|
end
|
||||||
|
@testset "basic no input or output (no sync)" begin
|
||||||
|
test_callback(0, 0, false)
|
||||||
|
end
|
||||||
|
@testset "basic duplex (sync)" begin
|
||||||
|
test_callback(2, 3, true)
|
||||||
|
end
|
||||||
|
@testset "basic input-only (sync)" begin
|
||||||
|
test_callback(2, 0, true)
|
||||||
|
end
|
||||||
|
@testset "basic output-only (sync)" begin
|
||||||
|
test_callback(0, 2, true)
|
||||||
|
end
|
||||||
|
@testset "basic no input or output (sync)" begin
|
||||||
|
test_callback(0, 0, true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@testset "Callback works with input-only stream" begin
|
@testset "Ouput underflow" begin
|
||||||
test_callback(2, 0)
|
@testset "underflow duplex (nosync)" begin
|
||||||
|
test_callback_underflow(2, 3, false)
|
||||||
|
end
|
||||||
|
@testset "underflow output-only (nosync)" begin
|
||||||
|
test_callback_underflow(0, 3, false)
|
||||||
|
end
|
||||||
|
@testset "underflow duplex (sync)" begin
|
||||||
|
test_callback_underflow(2, 3, true)
|
||||||
|
end
|
||||||
|
@testset "underflow output-only (sync)" begin
|
||||||
|
test_callback_underflow(0, 3, true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@testset "Callback works with output-only stream" begin
|
@testset "Input overflow" begin
|
||||||
test_callback(0, 2)
|
@testset "overflow duplex (nosync)" begin
|
||||||
|
test_callback_overflow(2, 3, false)
|
||||||
|
end
|
||||||
|
@testset "overflow input-only (nosync)" begin
|
||||||
|
test_callback_overflow(2, 0, false)
|
||||||
|
end
|
||||||
|
@testset "overflow duplex (sync)" begin
|
||||||
|
test_callback_overflow(2, 3, true)
|
||||||
|
end
|
||||||
|
@testset "overflow input-only (sync)" begin
|
||||||
|
test_callback_overflow(2, 0, true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@testset "Open Default Device" begin
|
@testset "Open Default Device" begin
|
||||||
|
@ -139,18 +290,18 @@ end
|
||||||
close(stream)
|
close(stream)
|
||||||
end
|
end
|
||||||
@testset "Open Device by name" begin
|
@testset "Open Device by name" begin
|
||||||
stream = PortAudioStream("Built-in Microph", "Built-in Output")
|
stream = PortAudioStream(default_indev, default_outdev)
|
||||||
buf = read(stream, 0.001s)
|
buf = read(stream, 0.001s)
|
||||||
@test size(buf) == (round(Int, 0.001 * samplerate(stream)), nchannels(stream.source))
|
@test size(buf) == (round(Int, 0.001 * samplerate(stream)), nchannels(stream.source))
|
||||||
write(stream, buf)
|
write(stream, buf)
|
||||||
io = IOBuffer()
|
io = IOBuffer()
|
||||||
show(io, stream)
|
show(io, stream)
|
||||||
@test takebuf_string(io) == """
|
@test String(take!(io)) == """
|
||||||
PortAudio.PortAudioStream{Float32}
|
PortAudio.PortAudioStream{Float32}
|
||||||
Samplerate: 48000.0Hz
|
Samplerate: 44100.0Hz
|
||||||
Buffer Size: 4096 frames
|
Buffer Size: 4096 frames
|
||||||
2 channel sink: "Built-in Output"
|
2 channel sink: "$default_outdev"
|
||||||
2 channel source: "Built-in Microph\""""
|
2 channel source: "$default_indev\""""
|
||||||
close(stream)
|
close(stream)
|
||||||
end
|
end
|
||||||
@testset "Error on wrong name" begin
|
@testset "Error on wrong name" begin
|
||||||
|
|
Loading…
Reference in a new issue