2014-07-01 03:24:29 +02:00
|
|
|
#!/usr/bin/env julia
|
|
|
|
|
2016-03-20 05:28:56 +01:00
|
|
|
using PortAudio
|
2016-03-31 17:07:46 +02:00
|
|
|
using SampledSignals
|
2016-07-29 07:44:02 +02:00
|
|
|
using RingBuffers
|
2019-09-09 01:59:37 +02:00
|
|
|
using Test
|
2016-03-20 02:56:42 +01:00
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
# pull in some extra stuff we need to test the callback directly
|
|
|
|
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
|
2016-03-24 04:25:03 +01:00
|
|
|
|
2018-08-28 19:41:04 +02:00
|
|
|
# only needed for the libuv workaround
|
|
|
|
const globalcond = Base.AsyncCondition()
|
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
"Setup buffers to test callback behavior"
|
|
|
|
function setup_callback(inchans, outchans, nframes, synced)
|
|
|
|
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)
|
2016-03-23 03:45:40 +01:00
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
# pass NULL for i/o we're not using
|
|
|
|
info = pa_shim_info_t(
|
|
|
|
inchans > 0 ? pointer(sourcebuf) : C_NULL,
|
|
|
|
outchans > 0 ? pointer(sinkbuf) : C_NULL,
|
|
|
|
pointer(errbuf),
|
|
|
|
synced, notifycb_c,
|
|
|
|
inchans > 0 ? notifyhandle(sourcebuf) : C_NULL,
|
|
|
|
outchans > 0 ? notifyhandle(sinkbuf) : C_NULL,
|
2018-08-28 19:41:04 +02:00
|
|
|
notifyhandle(errbuf),
|
|
|
|
globalcond.handle
|
2017-05-11 06:58:49 +02:00
|
|
|
)
|
|
|
|
flags = Culong(0)
|
2016-07-29 07:44:02 +02:00
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
cb_input = rand(Float32, inchans, nframes) # simulate microphone input
|
|
|
|
cb_output = rand(Float32, outchans, nframes) # this is where the output should go
|
2016-03-23 03:45:40 +01:00
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
function processfunc()
|
|
|
|
ccall(shim_processcb_c, Cint,
|
2018-08-16 05:18:44 +02:00
|
|
|
(Ref{Float32}, Ref{Float32}, Culong, Ref{Cvoid}, Culong, Ref{pa_shim_info_t}),
|
|
|
|
cb_input, cb_output, nframes, C_NULL, flags, info)
|
2017-05-11 06:58:49 +02:00
|
|
|
end
|
2016-03-23 03:45:40 +01:00
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
(sourcebuf, sinkbuf, errbuf, cb_input, cb_output, processfunc)
|
|
|
|
end
|
2016-03-23 03:45:40 +01:00
|
|
|
|
2018-08-28 19:41:04 +02:00
|
|
|
# the process callback only has pointers (not refs) to some data, so we need
|
|
|
|
# to make sure the references are preserved
|
|
|
|
function wrapprocess(process, sourcebuf, sinkbuf)
|
|
|
|
@static if VERSION >= v"0.7.0-"
|
|
|
|
GC.@preserve sourcebuf sinkbuf begin
|
|
|
|
process()
|
|
|
|
end
|
|
|
|
else
|
|
|
|
process()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
function test_callback(inchans, outchans, synced)
|
|
|
|
nframes = 8
|
|
|
|
(sourcebuf, sinkbuf, errbuf,
|
2018-08-28 19:41:04 +02:00
|
|
|
cb_input, cb_output,
|
|
|
|
process) = setup_callback(inchans, outchans, nframes, synced)
|
2017-05-11 06:58:49 +02:00
|
|
|
if outchans > 0
|
|
|
|
testout = rand(Float32, outchans, nframes) # generate some test data to play
|
2016-07-29 07:44:02 +02:00
|
|
|
write(sinkbuf, testout) # fill the output ringbuffer
|
2017-05-11 06:58:49 +02:00
|
|
|
end
|
2018-08-28 19:41:04 +02:00
|
|
|
@test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue
|
2017-05-11 06:58:49 +02:00
|
|
|
if outchans > 0
|
|
|
|
# testout -> sinkbuf -> cb_output
|
|
|
|
@test cb_output == testout
|
|
|
|
end
|
|
|
|
if inchans > 0
|
|
|
|
# cb_input -> sourcebuf
|
|
|
|
@test read(sourcebuf, nframes) == cb_input
|
|
|
|
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,
|
2018-08-28 19:41:04 +02:00
|
|
|
cb_input, cb_output,
|
|
|
|
process) = setup_callback(inchans, outchans, nframes, synced)
|
2017-05-11 06:58:49 +02:00
|
|
|
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)
|
2018-08-28 19:41:04 +02:00
|
|
|
@test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue
|
2017-05-11 06:58:49 +02:00
|
|
|
@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)
|
2018-08-28 19:41:04 +02:00
|
|
|
@test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue
|
2017-05-11 06:58:49 +02:00
|
|
|
@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,
|
2018-08-28 19:41:04 +02:00
|
|
|
cb_input, cb_output,
|
|
|
|
process) = setup_callback(inchans, outchans, nframes, synced)
|
2017-05-11 06:58:49 +02:00
|
|
|
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
|
2018-08-28 19:41:04 +02:00
|
|
|
@test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue
|
2017-05-11 06:58:49 +02:00
|
|
|
@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
|
2018-08-28 19:41:04 +02:00
|
|
|
@test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue
|
2017-05-11 06:58:49 +02:00
|
|
|
@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
|
2018-08-28 19:41:04 +02:00
|
|
|
@test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue
|
2017-05-11 06:58:49 +02:00
|
|
|
@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
|
2016-03-20 10:09:56 +01:00
|
|
|
end
|
2016-08-17 00:10:03 +02:00
|
|
|
end
|
|
|
|
|
2018-08-16 05:18:44 +02:00
|
|
|
@testset "PortAudio Tests" begin
|
2016-08-17 00:10:03 +02:00
|
|
|
@testset "Reports version" begin
|
|
|
|
io = IOBuffer()
|
|
|
|
PortAudio.versioninfo(io)
|
2017-05-11 06:58:49 +02:00
|
|
|
result = split(String(take!((io))), "\n")
|
2016-08-17 00:10:03 +02:00
|
|
|
# make sure this is the same version I tested with
|
2017-05-17 06:32:19 +02:00
|
|
|
@test startswith(result[1], "PortAudio V19")
|
2017-05-18 18:38:13 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
@testset "using correct shim version" begin
|
2019-09-09 01:59:37 +02:00
|
|
|
@test PortAudio.shimhash() == "87021557a9f999545828eb11e4ebad2cd278b734dd91a8bd3faf05c89912cf80"
|
2016-08-17 00:10:03 +02:00
|
|
|
end
|
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
@testset "Basic callback functionality" begin
|
|
|
|
@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
|
2016-08-17 00:10:03 +02:00
|
|
|
end
|
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
@testset "Ouput underflow" begin
|
|
|
|
@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
|
2016-08-17 00:10:03 +02:00
|
|
|
end
|
|
|
|
|
2017-05-11 06:58:49 +02:00
|
|
|
@testset "Input overflow" begin
|
|
|
|
@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
|
2016-08-17 00:10:03 +02:00
|
|
|
end
|
2016-03-20 10:09:56 +01:00
|
|
|
end
|