parent
dd68835815
commit
50eb168f9a
2 changed files with 118 additions and 47 deletions
|
@ -135,6 +135,21 @@ end
|
|||
|
||||
defaultlatency(devices...) = maximum(d -> max(d.highoutputlatency, d.highinputlatency), devices)
|
||||
|
||||
function combine_default_sample_rates(inchans, sampleratein, outchans, samplerateout)
|
||||
if inchans > 0 && outchans > 0 && sampleratein != samplerateout
|
||||
error("""
|
||||
Can't open duplex stream with mismatched samplerates (in: $sampleratein, out: $samplerateout).
|
||||
Try changing your sample rate in your driver settings or open separate input and output
|
||||
streams.
|
||||
"""
|
||||
)
|
||||
elseif inchans > 0
|
||||
sampleratein
|
||||
else
|
||||
samplerateout
|
||||
end
|
||||
end
|
||||
|
||||
# this is the top-level outer constructor that all the other outer constructors end up calling
|
||||
"""
|
||||
PortAudioStream(inchannels=2, outchannels=2; options...)
|
||||
|
@ -165,18 +180,10 @@ function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
|
|||
inchans=2, outchans=2; eltype=Float32, samplerate=-1,
|
||||
latency=defaultlatency(indev, outdev), warn_xruns=false, recover_xruns=true)
|
||||
if samplerate == -1
|
||||
sampleratein = indev.defaultsamplerate
|
||||
samplerateout = outdev.defaultsamplerate
|
||||
if inchans > 0 && outchans > 0 && sampleratein != samplerateout
|
||||
error("""
|
||||
Can't open duplex stream with mismatched samplerates (in: $sampleratein, out: $samplerateout).
|
||||
Try changing your sample rate in your driver settings or open separate input and output
|
||||
streams""")
|
||||
elseif inchans > 0
|
||||
samplerate = sampleratein
|
||||
else
|
||||
samplerate = samplerateout
|
||||
end
|
||||
samplerate = combine_default_sample_rates(
|
||||
inchans, indev.defaultsamplerate,
|
||||
outchans, outdev.defaultsamplerate
|
||||
)
|
||||
end
|
||||
PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate,
|
||||
latency, warn_xruns, recover_xruns)
|
||||
|
@ -206,10 +213,7 @@ end
|
|||
|
||||
# if one device is given, use it for input and output, but set inchans=0 so we
|
||||
# end up with an output-only stream
|
||||
function PortAudioStream(device::PortAudioDevice, inchans=2, outchans=2; kwargs...)
|
||||
PortAudioStream(device, device, inchans, outchans; kwargs...)
|
||||
end
|
||||
function PortAudioStream(device::AbstractString, inchans=2, outchans=2; kwargs...)
|
||||
function PortAudioStream(device::Union{PortAudioDevice, AbstractString}, inchans=2, outchans=2; kwargs...)
|
||||
PortAudioStream(device, device, inchans, outchans; kwargs...)
|
||||
end
|
||||
|
||||
|
@ -289,8 +293,11 @@ SampledSignals.nchannels(s::Union{PortAudioSink, PortAudioSource}) = s.nchannels
|
|||
SampledSignals.samplerate(s::Union{PortAudioSink, PortAudioSource}) = samplerate(s.stream)
|
||||
eltype(::Union{PortAudioSink{T}, PortAudioSource{T}}) where {T} = T
|
||||
function close(s::Union{PortAudioSink, PortAudioSource})
|
||||
throw(ErrorException("Attempted to close PortAudioSink or PortAudioSource.
|
||||
Close the containing PortAudioStream instead"))
|
||||
throw(ErrorException("""
|
||||
Attempted to close PortAudioSink or PortAudioSource.
|
||||
Close the containing PortAudioStream instead
|
||||
"""
|
||||
))
|
||||
end
|
||||
isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.stream)
|
||||
name(s::Union{PortAudioSink, PortAudioSource}) = s.name
|
||||
|
@ -379,33 +386,38 @@ function discard_input(source::PortAudioSource)
|
|||
end
|
||||
end
|
||||
|
||||
function seek_alsa_conf(searchdirs)
|
||||
confdir_idx = findfirst(searchdirs) do d
|
||||
isfile(joinpath(d, "alsa.conf"))
|
||||
end
|
||||
if confdir_idx === nothing
|
||||
throw(ErrorException(
|
||||
"""
|
||||
Could not find ALSA config directory. Searched:
|
||||
$(join(searchdirs, "\n"))
|
||||
|
||||
If ALSA is installed, set the "ALSA_CONFIG_DIR" environment
|
||||
variable. The given directory should have a file "alsa.conf".
|
||||
|
||||
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.
|
||||
"""
|
||||
))
|
||||
end
|
||||
searchdirs[confdir_idx]
|
||||
end
|
||||
|
||||
function __init__()
|
||||
if Sys.islinux()
|
||||
envkey = "ALSA_CONFIG_DIR"
|
||||
if envkey ∉ keys(ENV)
|
||||
searchdirs = ["/usr/share/alsa",
|
||||
"/usr/local/share/alsa",
|
||||
"/etc/alsa"]
|
||||
confdir_idx = findfirst(searchdirs) do d
|
||||
isfile(joinpath(d, "alsa.conf"))
|
||||
end
|
||||
if confdir_idx === nothing
|
||||
throw(ErrorException(
|
||||
"""
|
||||
Could not find ALSA config directory. Searched:
|
||||
$(join(searchdirs, "\n"))
|
||||
|
||||
if ALSA is installed, set the "ALSA_CONFIG_DIR" environment
|
||||
variable. The given directory should have a file "alsa.conf".
|
||||
|
||||
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.
|
||||
"""))
|
||||
end
|
||||
confdir = searchdirs[confdir_idx]
|
||||
ENV[envkey] = confdir
|
||||
ENV[envkey] = seek_alsa_conf([
|
||||
"/usr/share/alsa",
|
||||
"/usr/local/share/alsa",
|
||||
"/etc/alsa"
|
||||
])
|
||||
end
|
||||
|
||||
plugin_key = "ALSA_PLUGIN_DIR"
|
||||
|
|
|
@ -2,9 +2,22 @@
|
|||
|
||||
using Logging: Debug
|
||||
using PortAudio
|
||||
using PortAudio: Pa_GetDefaultInputDevice, Pa_GetDefaultOutputDevice, Pa_GetDeviceInfo, Pa_GetHostApiInfo, PortAudioDevice, @stderr_as_debug
|
||||
using Test
|
||||
using PortAudio:
|
||||
combine_default_sample_rates,
|
||||
handle_status,
|
||||
Pa_GetDefaultInputDevice,
|
||||
Pa_GetDefaultOutputDevice,
|
||||
Pa_GetDeviceInfo,
|
||||
Pa_GetHostApiInfo,
|
||||
Pa_Initialize,
|
||||
PA_OUTPUT_UNDERFLOWED,
|
||||
Pa_Terminate,
|
||||
PortAudioDevice,
|
||||
recover_xrun,
|
||||
seek_alsa_conf,
|
||||
@stderr_as_debug
|
||||
using SampledSignals
|
||||
using Test
|
||||
|
||||
@testset "Debug messages" begin
|
||||
@test_logs (:debug, "hi") min_level = Debug @test_nowarn @stderr_as_debug begin
|
||||
|
@ -33,6 +46,10 @@ end
|
|||
end
|
||||
|
||||
if !isempty(PortAudio.devices())
|
||||
# make sure we can terminate, then reinitialize
|
||||
Pa_Terminate()
|
||||
@stderr_as_debug Pa_Initialize()
|
||||
|
||||
# these default values are specific to my machines
|
||||
inidx = Pa_GetDefaultInputDevice()
|
||||
default_indev = PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx).name
|
||||
|
@ -47,13 +64,33 @@ if !isempty(PortAudio.devices())
|
|||
close(stream)
|
||||
@test size(buf) == (round(Int, 5 * samplerate(stream)), nchannels(stream.source))
|
||||
println("Playing back recording...")
|
||||
stream = PortAudioStream(0, 2)
|
||||
write(stream, buf)
|
||||
close(stream)
|
||||
PortAudioStream(0, 2) do stream
|
||||
write(stream, buf)
|
||||
end
|
||||
println("Testing pass-through")
|
||||
stream = PortAudioStream(2, 2)
|
||||
sink = stream.sink
|
||||
source = stream.source
|
||||
@test sprint(show, typeof(sink)) == "PortAudioSink{Float32}"
|
||||
@test sprint(show, typeof(source)) == "PortAudioSource{Float32}"
|
||||
@test sprint(show, sink) == "2-channel PortAudioSink{Float32}($(repr(default_indev)))"
|
||||
@test sprint(show, source) == "2-channel PortAudioSource{Float32}($(repr(default_outdev)))"
|
||||
write(stream, stream, 5s)
|
||||
recover_xrun(stream)
|
||||
@test_throws ErrorException("""
|
||||
Attempted to close PortAudioSink or PortAudioSource.
|
||||
Close the containing PortAudioStream instead
|
||||
"""
|
||||
) close(sink)
|
||||
@test_throws ErrorException("""
|
||||
Attempted to close PortAudioSink or PortAudioSource.
|
||||
Close the containing PortAudioStream instead
|
||||
"""
|
||||
) close(source)
|
||||
close(stream)
|
||||
@test !isopen(stream)
|
||||
@test !isopen(sink)
|
||||
@test !isopen(source)
|
||||
println("done")
|
||||
end
|
||||
@testset "Samplerate-converting writing" begin
|
||||
|
@ -77,8 +114,30 @@ if !isempty(PortAudio.devices())
|
|||
2 channel source: "$default_indev\"""", String(take!(io)))
|
||||
close(stream)
|
||||
end
|
||||
@testset "Error on wrong name" begin
|
||||
@testset "Error handling" begin
|
||||
@test_throws ErrorException PortAudioStream("foobarbaz")
|
||||
@test_throws ErrorException PortAudioStream(default_indev, "foobarbaz")
|
||||
@test_logs (:warn, "libportaudio: Output underflowed") handle_status(PA_OUTPUT_UNDERFLOWED)
|
||||
@test_throws ErrorException("libportaudio: PortAudio not initialized") handle_status(-10000)
|
||||
@test_throws ErrorException("""
|
||||
Could not find ALSA config directory. Searched:
|
||||
|
||||
|
||||
If ALSA is installed, set the "ALSA_CONFIG_DIR" environment
|
||||
variable. The given directory should have a file "alsa.conf".
|
||||
|
||||
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.
|
||||
"""
|
||||
) seek_alsa_conf([])
|
||||
@test_throws ErrorException("""
|
||||
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.
|
||||
"""
|
||||
) combine_default_sample_rates(1, 0, 1, 1)
|
||||
end
|
||||
# no way to check that the right data is actually getting read or written here,
|
||||
# but at least it's not crashing.
|
||||
|
|
Loading…
Reference in a new issue