2014-06-30 21:24:29 -04:00
|
|
|
#!/usr/bin/env julia
|
|
|
|
|
2021-05-24 17:34:37 -04:00
|
|
|
using Logging: Debug
|
2021-06-01 13:44:23 -04:00
|
|
|
using PortAudio:
|
2021-06-01 12:39:27 -04:00
|
|
|
combine_default_sample_rates,
|
2021-06-17 14:28:55 -04:00
|
|
|
devices,
|
|
|
|
get_default_input_device,
|
|
|
|
get_default_output_device,
|
2021-06-17 13:49:33 -04:00
|
|
|
get_device_info,
|
2021-06-01 12:39:27 -04:00
|
|
|
handle_status,
|
2021-06-17 14:28:55 -04:00
|
|
|
initialize,
|
2021-06-17 13:49:33 -04:00
|
|
|
paOutputUnderflowed,
|
2021-06-17 14:28:55 -04:00
|
|
|
PortAudio,
|
2021-06-01 12:39:27 -04:00
|
|
|
PortAudioDevice,
|
2021-06-17 14:28:55 -04:00
|
|
|
PortAudioStream,
|
2021-06-01 12:39:27 -04:00
|
|
|
recover_xrun,
|
2021-06-17 16:52:54 -04:00
|
|
|
safe_load,
|
2021-06-01 12:39:27 -04:00
|
|
|
seek_alsa_conf,
|
2021-06-17 13:49:33 -04:00
|
|
|
@stderr_as_debug,
|
2021-06-17 14:28:55 -04:00
|
|
|
terminate
|
2021-06-17 16:52:54 -04:00
|
|
|
using PortAudio.LibPortAudio:
|
|
|
|
Pa_AbortStream,
|
|
|
|
PaErrorCode,
|
|
|
|
paFloat32,
|
|
|
|
Pa_GetDefaultHostApi,
|
|
|
|
Pa_GetHostApiCount,
|
|
|
|
Pa_GetLastHostErrorInfo,
|
|
|
|
Pa_GetSampleSize,
|
|
|
|
Pa_GetStreamCpuLoad,
|
|
|
|
Pa_GetStreamInfo,
|
|
|
|
Pa_GetStreamTime,
|
|
|
|
Pa_HostApiDeviceIndexToDeviceIndex,
|
|
|
|
Pa_HostApiTypeIdToHostApiIndex,
|
|
|
|
PaHostErrorInfo,
|
|
|
|
paInDevelopment,
|
|
|
|
paInvalidDevice,
|
|
|
|
Pa_IsFormatSupported,
|
|
|
|
Pa_IsStreamActive,
|
|
|
|
Pa_IsStreamStopped,
|
|
|
|
paNoError,
|
|
|
|
paNoFlag,
|
|
|
|
paNotInitialized,
|
|
|
|
Pa_OpenDefaultStream,
|
|
|
|
Pa_SetStreamFinishedCallback,
|
|
|
|
Pa_Sleep,
|
|
|
|
PaStream,
|
|
|
|
PaStreamInfo,
|
|
|
|
PaStreamParameters
|
2021-06-17 14:28:55 -04:00
|
|
|
using SampledSignals: nchannels, s, SampleBuf, samplerate, SinSource
|
|
|
|
using Test: @test, @test_logs, @test_nowarn, @testset, @test_throws
|
2016-03-19 21:56:42 -04:00
|
|
|
|
2021-05-24 17:34:37 -04:00
|
|
|
@testset "Debug messages" begin
|
|
|
|
@test_logs (:debug, "hi") min_level = Debug @test_nowarn @stderr_as_debug begin
|
|
|
|
print(stderr, "hi")
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-15 23:18:44 -04:00
|
|
|
@testset "PortAudio Tests" begin
|
2016-08-16 18:10:03 -04:00
|
|
|
@testset "Reports version" begin
|
|
|
|
io = IOBuffer()
|
|
|
|
PortAudio.versioninfo(io)
|
2017-05-11 00:58:49 -04:00
|
|
|
result = split(String(take!((io))), "\n")
|
2016-08-16 18:10:03 -04:00
|
|
|
# make sure this is the same version I tested with
|
2017-05-17 00:32:19 -04:00
|
|
|
@test startswith(result[1], "PortAudio V19")
|
2017-05-18 12:38:13 -04:00
|
|
|
end
|
|
|
|
|
2020-01-30 15:25:08 -05:00
|
|
|
@testset "Can list devices without crashing" begin
|
2021-06-17 14:28:55 -04:00
|
|
|
devices()
|
2016-08-16 18:10:03 -04:00
|
|
|
end
|
2021-05-13 13:59:25 -04:00
|
|
|
|
|
|
|
@testset "Null errors" begin
|
2021-06-17 13:49:33 -04:00
|
|
|
@test_throws BoundsError get_device_info(-1)
|
2021-05-13 13:59:25 -04:00
|
|
|
end
|
2016-03-20 05:09:56 -04:00
|
|
|
end
|
2021-05-13 11:42:09 -04:00
|
|
|
|
2021-06-17 14:28:55 -04:00
|
|
|
if !isempty(devices())
|
2021-06-01 12:39:27 -04:00
|
|
|
# make sure we can terminate, then reinitialize
|
2021-06-17 14:28:55 -04:00
|
|
|
terminate()
|
|
|
|
initialize()
|
2021-06-01 12:39:27 -04:00
|
|
|
|
2021-06-17 16:52:54 -04:00
|
|
|
@testset "libportaudio" begin
|
|
|
|
@test handle_status(Pa_GetHostApiCount()) isa Integer
|
|
|
|
@test handle_status(Pa_GetDefaultHostApi()) isa Integer
|
|
|
|
@test_throws ErrorException handle_status(
|
|
|
|
Pa_HostApiTypeIdToHostApiIndex(paInDevelopment),
|
|
|
|
)
|
|
|
|
@test Pa_HostApiDeviceIndexToDeviceIndex(paInDevelopment, 0) == 0
|
|
|
|
safe_load(Pa_GetLastHostErrorInfo(), ErrorException("no info")) isa PaHostErrorInfo
|
|
|
|
@test PaErrorCode(
|
|
|
|
Pa_IsFormatSupported(
|
|
|
|
Ptr{PaStreamParameters}(0),
|
|
|
|
Ptr{PaStreamParameters}(0),
|
|
|
|
0.0,
|
|
|
|
),
|
|
|
|
) == paInvalidDevice
|
|
|
|
@test PaErrorCode(
|
|
|
|
Pa_OpenDefaultStream(
|
|
|
|
Ref{Ptr{PaStream}}(0),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
paFloat32,
|
|
|
|
0.0,
|
|
|
|
0,
|
|
|
|
C_NULL,
|
|
|
|
C_NULL,
|
|
|
|
),
|
|
|
|
) == paInvalidDevice
|
|
|
|
stream = PortAudioStream(0, 2)
|
|
|
|
pointer = stream.pointer_ref[]
|
|
|
|
@test !(Bool(handle_status(Pa_IsStreamStopped(pointer))))
|
|
|
|
@test Bool(handle_status(Pa_IsStreamActive(pointer)))
|
|
|
|
@test safe_load(Pa_GetStreamInfo(pointer), ErrorException("no info")) isa
|
|
|
|
PaStreamInfo
|
2021-06-17 17:03:53 -04:00
|
|
|
handle_status(Pa_GetStreamTime(pointer))
|
|
|
|
handle_status(Pa_GetStreamCpuLoad(pointer))
|
2021-06-17 16:52:54 -04:00
|
|
|
@test PaErrorCode(handle_status(Pa_AbortStream(pointer))) == paNoError
|
|
|
|
@test PaErrorCode(handle_status(Pa_SetStreamFinishedCallback(pointer, C_NULL))) ==
|
|
|
|
paNoError
|
|
|
|
Pa_Sleep(1)
|
|
|
|
@test Pa_GetSampleSize(paFloat32) == 4
|
|
|
|
end
|
|
|
|
|
2021-05-13 11:42:09 -04:00
|
|
|
# these default values are specific to my machines
|
2021-06-17 14:28:55 -04:00
|
|
|
inidx = get_default_input_device()
|
2021-06-17 13:49:33 -04:00
|
|
|
default_indev = PortAudioDevice(get_device_info(inidx), inidx).name
|
2021-06-17 14:28:55 -04:00
|
|
|
outidx = get_default_output_device()
|
2021-06-17 13:49:33 -04:00
|
|
|
default_outdev = PortAudioDevice(get_device_info(outidx), outidx).name
|
2021-05-13 11:42:09 -04:00
|
|
|
|
|
|
|
@testset "Local Tests" begin
|
|
|
|
@testset "Open Default Device" begin
|
|
|
|
println("Recording...")
|
|
|
|
stream = PortAudioStream(2, 0)
|
|
|
|
buf = read(stream, 5s)
|
|
|
|
close(stream)
|
2021-06-01 13:44:23 -04:00
|
|
|
@test size(buf) ==
|
|
|
|
(round(Int, 5 * samplerate(stream)), nchannels(stream.source))
|
2021-05-13 11:42:09 -04:00
|
|
|
println("Playing back recording...")
|
2021-06-01 12:39:27 -04:00
|
|
|
PortAudioStream(0, 2) do stream
|
|
|
|
write(stream, buf)
|
|
|
|
end
|
2021-05-13 11:42:09 -04:00
|
|
|
println("Testing pass-through")
|
|
|
|
stream = PortAudioStream(2, 2)
|
2021-06-01 12:39:27 -04:00
|
|
|
sink = stream.sink
|
|
|
|
source = stream.source
|
2021-06-14 10:33:39 -04:00
|
|
|
@test sprint(show, sink) == "2 channel sink: $(repr(default_indev))"
|
|
|
|
@test sprint(show, source) == "2 channel source: $(repr(default_outdev))"
|
2021-05-13 11:42:09 -04:00
|
|
|
write(stream, stream, 5s)
|
2021-06-01 12:39:27 -04:00
|
|
|
recover_xrun(stream)
|
|
|
|
@test_throws ErrorException("""
|
|
|
|
Attempted to close PortAudioSink or PortAudioSource.
|
|
|
|
Close the containing PortAudioStream instead
|
2021-06-01 13:44:23 -04:00
|
|
|
""") close(sink)
|
2021-06-01 12:39:27 -04:00
|
|
|
@test_throws ErrorException("""
|
|
|
|
Attempted to close PortAudioSink or PortAudioSource.
|
|
|
|
Close the containing PortAudioStream instead
|
2021-06-01 13:44:23 -04:00
|
|
|
""") close(source)
|
2021-05-13 11:42:09 -04:00
|
|
|
close(stream)
|
2021-06-01 12:39:27 -04:00
|
|
|
@test !isopen(stream)
|
|
|
|
@test !isopen(sink)
|
|
|
|
@test !isopen(source)
|
2021-05-13 11:42:09 -04:00
|
|
|
println("done")
|
|
|
|
end
|
|
|
|
@testset "Samplerate-converting writing" begin
|
|
|
|
stream = PortAudioStream(0, 2)
|
2021-06-01 13:44:23 -04:00
|
|
|
write(
|
|
|
|
stream,
|
|
|
|
SinSource(eltype(stream), samplerate(stream) * 0.8, [220, 330]),
|
|
|
|
3s,
|
|
|
|
)
|
|
|
|
write(
|
|
|
|
stream,
|
|
|
|
SinSource(eltype(stream), samplerate(stream) * 1.2, [220, 330]),
|
|
|
|
3s,
|
|
|
|
)
|
2021-05-13 11:42:09 -04:00
|
|
|
close(stream)
|
|
|
|
end
|
|
|
|
@testset "Open Device by name" begin
|
|
|
|
stream = PortAudioStream(default_indev, default_outdev)
|
|
|
|
buf = read(stream, 0.001s)
|
2021-06-01 13:44:23 -04:00
|
|
|
@test size(buf) ==
|
|
|
|
(round(Int, 0.001 * samplerate(stream)), nchannels(stream.source))
|
2021-05-13 11:42:09 -04:00
|
|
|
write(stream, buf)
|
|
|
|
io = IOBuffer()
|
|
|
|
show(io, stream)
|
2021-06-01 13:44:23 -04:00
|
|
|
@test occursin(
|
|
|
|
"""
|
|
|
|
PortAudioStream{Float32}
|
|
|
|
Samplerate: 44100.0Hz
|
2021-06-14 10:30:28 -04:00
|
|
|
2 channel sink: $(repr(default_outdev))
|
|
|
|
2 channel source: $(repr(default_indev))""",
|
2021-06-01 13:44:23 -04:00
|
|
|
String(take!(io)),
|
|
|
|
)
|
2021-05-13 11:42:09 -04:00
|
|
|
close(stream)
|
|
|
|
end
|
2021-06-01 12:39:27 -04:00
|
|
|
@testset "Error handling" begin
|
2021-05-13 11:42:09 -04:00
|
|
|
@test_throws ErrorException PortAudioStream("foobarbaz")
|
2021-06-01 12:39:27 -04:00
|
|
|
@test_throws ErrorException PortAudioStream(default_indev, "foobarbaz")
|
2021-06-01 13:44:23 -04:00
|
|
|
@test_logs (:warn, "libportaudio: Output underflowed") handle_status(
|
2021-06-17 13:49:33 -04:00
|
|
|
paOutputUnderflowed,
|
2021-06-01 13:44:23 -04:00
|
|
|
)
|
|
|
|
@test_throws ErrorException("libportaudio: PortAudio not initialized") handle_status(
|
2021-06-17 13:49:33 -04:00
|
|
|
paNotInitialized,
|
2021-06-01 13:44:23 -04:00
|
|
|
)
|
2021-06-01 12:39:27 -04:00
|
|
|
@test_throws ErrorException("""
|
|
|
|
Could not find ALSA config directory. Searched:
|
2021-06-01 13:44:23 -04:00
|
|
|
|
2021-06-01 12:39:27 -04:00
|
|
|
|
|
|
|
If ALSA is installed, set the "ALSA_CONFIG_DIR" environment
|
|
|
|
variable. The given directory should have a file "alsa.conf".
|
2021-06-01 13:44:23 -04:00
|
|
|
|
2021-06-01 12:39:27 -04:00
|
|
|
If it would be useful to others, please file an issue at
|
|
|
|
https://github.com/JuliaAudio/PortAudio.jl/issues
|
|
|
|
with your alsa config directory so we can add it to the search
|
|
|
|
paths.
|
2021-06-01 13:44:23 -04:00
|
|
|
""") seek_alsa_conf([])
|
|
|
|
@test_throws ErrorException(
|
2021-06-01 12:39:27 -04:00
|
|
|
"""
|
2021-06-01 13:44:23 -04:00
|
|
|
Can't open duplex stream with mismatched samplerates (in: 0, out: 1).
|
|
|
|
Try changing your sample rate in your driver settings or open separate input and output
|
|
|
|
streams.
|
|
|
|
""",
|
2021-06-01 12:39:27 -04:00
|
|
|
) combine_default_sample_rates(1, 0, 1, 1)
|
2021-05-13 11:42:09 -04:00
|
|
|
end
|
|
|
|
# no way to check that the right data is actually getting read or written here,
|
|
|
|
# but at least it's not crashing.
|
|
|
|
@testset "Queued Writing" begin
|
|
|
|
stream = PortAudioStream(0, 2)
|
2021-06-01 13:44:23 -04:00
|
|
|
buf = SampleBuf(
|
|
|
|
rand(eltype(stream), 48000, nchannels(stream.sink)) * 0.1,
|
|
|
|
samplerate(stream),
|
|
|
|
)
|
2021-05-13 11:42:09 -04:00
|
|
|
t1 = @async write(stream, buf)
|
|
|
|
t2 = @async write(stream, buf)
|
|
|
|
@test fetch(t1) == 48000
|
|
|
|
@test fetch(t2) == 48000
|
|
|
|
close(stream)
|
|
|
|
end
|
|
|
|
@testset "Queued Reading" begin
|
|
|
|
stream = PortAudioStream(2, 0)
|
2021-06-01 13:44:23 -04:00
|
|
|
buf = SampleBuf(
|
|
|
|
rand(eltype(stream), 48000, nchannels(stream.source)) * 0.1,
|
|
|
|
samplerate(stream),
|
|
|
|
)
|
2021-05-13 11:42:09 -04:00
|
|
|
t1 = @async read!(stream, buf)
|
|
|
|
t2 = @async read!(stream, buf)
|
|
|
|
@test fetch(t1) == 48000
|
|
|
|
@test fetch(t2) == 48000
|
|
|
|
close(stream)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|