256 lines
9.5 KiB
Julia
Executable file
256 lines
9.5 KiB
Julia
Executable file
#!/usr/bin/env julia
|
|
using Base.Sys: iswindows
|
|
using Documenter: doctest
|
|
using PortAudio:
|
|
combine_default_sample_rates,
|
|
devices,
|
|
get_default_input_index,
|
|
get_default_output_index,
|
|
get_device,
|
|
get_input_type,
|
|
get_output_type,
|
|
handle_status,
|
|
initialize,
|
|
name,
|
|
PortAudioException,
|
|
PortAudio,
|
|
PortAudioDevice,
|
|
PortAudioStream,
|
|
safe_load,
|
|
seek_alsa_conf,
|
|
terminate,
|
|
write_buffer
|
|
using PortAudio.LibPortAudio:
|
|
Pa_AbortStream,
|
|
PaError,
|
|
PaErrorCode,
|
|
paFloat32,
|
|
Pa_GetDefaultHostApi,
|
|
Pa_GetDeviceInfo,
|
|
Pa_GetHostApiCount,
|
|
Pa_GetLastHostErrorInfo,
|
|
Pa_GetSampleSize,
|
|
Pa_GetStreamCpuLoad,
|
|
Pa_GetStreamInfo,
|
|
Pa_GetStreamReadAvailable,
|
|
Pa_GetStreamTime,
|
|
Pa_GetStreamWriteAvailable,
|
|
Pa_GetVersionInfo,
|
|
Pa_HostApiDeviceIndexToDeviceIndex,
|
|
paHostApiNotFound,
|
|
Pa_HostApiTypeIdToHostApiIndex,
|
|
PaHostErrorInfo,
|
|
paInDevelopment,
|
|
paInvalidDevice,
|
|
Pa_IsFormatSupported,
|
|
Pa_IsStreamActive,
|
|
paNoError,
|
|
paNoFlag,
|
|
paNotInitialized,
|
|
Pa_OpenDefaultStream,
|
|
paOutputUnderflowed,
|
|
Pa_SetStreamFinishedCallback,
|
|
Pa_Sleep,
|
|
Pa_StopStream,
|
|
PaStream,
|
|
PaStreamInfo,
|
|
PaStreamParameters,
|
|
PaVersionInfo
|
|
using SampledSignals: nchannels, s, SampleBuf, samplerate, SinSource
|
|
using Test: @test, @test_logs, @test_nowarn, @testset, @test_throws
|
|
|
|
@testset "Tests without sound" begin
|
|
@testset "Reports version" begin
|
|
io = IOBuffer()
|
|
PortAudio.versioninfo(io)
|
|
result = split(String(take!((io))), "\n")
|
|
# make sure this is the same version I tested with
|
|
@test startswith(result[1], "PortAudio V19")
|
|
end
|
|
|
|
@testset "Can list devices without crashing" begin
|
|
display(devices())
|
|
println()
|
|
end
|
|
|
|
@testset "libortaudio without sound" begin
|
|
@test handle_status(Pa_GetHostApiCount()) >= 0
|
|
@test handle_status(Pa_GetDefaultHostApi()) >= 0
|
|
# version info not available on windows?
|
|
if !Sys.iswindows()
|
|
@test safe_load(Pa_GetVersionInfo(), ErrorException("no info")) isa
|
|
PaVersionInfo
|
|
end
|
|
@test safe_load(Pa_GetLastHostErrorInfo(), ErrorException("no info")) isa
|
|
PaHostErrorInfo
|
|
@test PaErrorCode(Pa_IsFormatSupported(C_NULL, C_NULL, 0.0)) == paInvalidDevice
|
|
@test PaErrorCode(
|
|
Pa_OpenDefaultStream(Ref(C_NULL), 0, 0, paFloat32, 0.0, 0, C_NULL, C_NULL),
|
|
) == paInvalidDevice
|
|
end
|
|
|
|
@testset "Errors without sound" begin
|
|
@test sprint(showerror, PortAudioException(paNotInitialized)) ==
|
|
"PortAudioException: PortAudio not initialized"
|
|
@test_throws KeyError("foobarbaz") get_device("foobarbaz")
|
|
@test_throws KeyError(-1) get_device(-1)
|
|
@test_throws ArgumentError("Could not find alsa.conf in ()") seek_alsa_conf(())
|
|
@test_logs (:warn, "libportaudio: Output underflowed") handle_status(
|
|
PaError(paOutputUnderflowed),
|
|
)
|
|
@test_throws PortAudioException(paNotInitialized) handle_status(
|
|
PaError(paNotInitialized),
|
|
)
|
|
Pa_Sleep(1)
|
|
@test Pa_GetSampleSize(paFloat32) == 4
|
|
end
|
|
|
|
# make sure we can terminate, then reinitialize
|
|
terminate()
|
|
initialize()
|
|
end
|
|
|
|
if isempty(devices())
|
|
@test_throws ArgumentError("No input device available") get_default_input_index()
|
|
else
|
|
@testset "Tests with sound" begin
|
|
# these default values are specific to local machines
|
|
input_name = get_device(get_default_input_index()).name
|
|
output_name = get_device(get_default_output_index()).name
|
|
|
|
@testset "Interactive tests" begin
|
|
println("Recording...")
|
|
stream = PortAudioStream(input_name, output_name, 2, 0; adjust_channels = true)
|
|
buffer = read(stream, 5s)
|
|
@test size(buffer) ==
|
|
(round(Int, 5 * samplerate(stream)), nchannels(stream.source))
|
|
close(stream)
|
|
sleep(1)
|
|
println("Playing back recording...")
|
|
PortAudioStream(input_name, output_name, 0, 2; adjust_channels = true) do stream
|
|
write(stream, buffer)
|
|
end
|
|
sleep(1)
|
|
println("Testing pass-through")
|
|
stream = PortAudioStream(input_name, output_name, 2, 2; adjust_channels = true)
|
|
write_buffer(stream.sink_messenger.buffer, acquire_lock = false)
|
|
sink = stream.sink
|
|
source = stream.source
|
|
@test sprint(show, stream) == """
|
|
PortAudioStream{Float32}
|
|
Samplerate: 44100Hz
|
|
2 channel sink: $(repr(output_name))
|
|
2 channel source: $(repr(input_name))"""
|
|
@test sprint(show, source) == "2 channel source: $(repr(input_name))"
|
|
@test sprint(show, sink) == "2 channel sink: $(repr(output_name))"
|
|
write(stream, stream, 5s)
|
|
@test PaErrorCode(handle_status(Pa_StopStream(stream.pointer_to))) == paNoError
|
|
@test isopen(stream)
|
|
close(stream)
|
|
sleep(1)
|
|
@test !isopen(stream)
|
|
@test !isopen(sink)
|
|
@test !isopen(source)
|
|
println("done")
|
|
end
|
|
@testset "Samplerate-converting writing" begin
|
|
PortAudioStream(input_name, output_name, 0, 2; adjust_channels = true) do stream
|
|
write(
|
|
stream,
|
|
SinSource(eltype(stream), samplerate(stream) * 0.8, [220, 330]),
|
|
3s,
|
|
)
|
|
println("expected blip")
|
|
write(
|
|
stream,
|
|
SinSource(eltype(stream), samplerate(stream) * 1.2, [220, 330]),
|
|
3s,
|
|
)
|
|
end
|
|
end
|
|
sleep(1)
|
|
# 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
|
|
PortAudioStream(input_name, output_name, 0, 2; adjust_channels = true) do stream
|
|
buffer = SampleBuf(
|
|
rand(eltype(stream), 48000, nchannels(stream.sink)) * 0.1,
|
|
samplerate(stream),
|
|
)
|
|
frame_count_1 = @async write(stream, buffer)
|
|
frame_count_2 = @async write(stream, buffer)
|
|
@test fetch(frame_count_1) == 48000
|
|
println("expected blip")
|
|
@test fetch(frame_count_2) == 48000
|
|
end
|
|
sleep(1)
|
|
end
|
|
@testset "Queued Reading" begin
|
|
PortAudioStream(input_name, output_name, 2, 0; adjust_channels = true) do stream
|
|
buffer = SampleBuf(
|
|
rand(eltype(stream), 48000, nchannels(stream.source)) * 0.1,
|
|
samplerate(stream),
|
|
)
|
|
frame_count_1 = @async read!(stream, buffer)
|
|
frame_count_2 = @async read!(stream, buffer)
|
|
@test fetch(frame_count_1) == 48000
|
|
@test fetch(frame_count_2) == 48000
|
|
end
|
|
sleep(1)
|
|
end
|
|
@testset "Constructors" begin
|
|
PortAudioStream(2, maximum; adjust_channels = true) do stream
|
|
@test isopen(stream)
|
|
end
|
|
PortAudioStream(output_name; adjust_channels = true) do stream
|
|
@test isopen(stream)
|
|
end
|
|
PortAudioStream(input_name, output_name; adjust_channels = true) do stream
|
|
@test isopen(stream)
|
|
end
|
|
end
|
|
@testset "Errors with sound" begin
|
|
big = typemax(Int)
|
|
@test_throws DomainError(
|
|
typemax(Int),
|
|
"$big exceeds maximum output channels for $output_name",
|
|
) PortAudioStream(input_name, output_name, 0, big)
|
|
@test_throws ArgumentError("Input or output must have at least 1 channel") PortAudioStream(
|
|
input_name,
|
|
output_name,
|
|
0,
|
|
0;
|
|
adjust_channels = true,
|
|
)
|
|
@test_throws ArgumentError("""
|
|
Default sample rate 0 for input \"$input_name\" disagrees with
|
|
default sample rate 1 for output \"$output_name\".
|
|
Please specify a sample rate.
|
|
""") combine_default_sample_rates(
|
|
get_device(input_name),
|
|
0,
|
|
get_device(output_name),
|
|
1,
|
|
)
|
|
end
|
|
@testset "libportaudio with sound" begin
|
|
@test PaErrorCode(Pa_HostApiTypeIdToHostApiIndex(paInDevelopment)) ==
|
|
paHostApiNotFound
|
|
@test Pa_HostApiDeviceIndexToDeviceIndex(paInDevelopment, 0) == 0
|
|
stream = PortAudioStream(input_name, output_name, 2, 2; adjust_channels = true)
|
|
pointer_to = stream.pointer_to
|
|
@test handle_status(Pa_GetStreamReadAvailable(pointer_to)) >= 0
|
|
@test handle_status(Pa_GetStreamWriteAvailable(pointer_to)) >= 0
|
|
@test Bool(handle_status(Pa_IsStreamActive(pointer_to)))
|
|
@test safe_load(Pa_GetStreamInfo(pointer_to), ErrorException("no info")) isa
|
|
PaStreamInfo
|
|
@test Pa_GetStreamTime(pointer_to) >= 0
|
|
@test Pa_GetStreamCpuLoad(pointer_to) >= 0
|
|
@test PaErrorCode(handle_status(Pa_AbortStream(pointer_to))) == paNoError
|
|
@test PaErrorCode(
|
|
handle_status(Pa_SetStreamFinishedCallback(pointer_to, C_NULL)),
|
|
) == paNoError
|
|
end
|
|
end
|
|
doctest(PortAudio)
|
|
end
|