defaults to output-only, bigger ringbuf, fixes issue with multichannel writing
This commit is contained in:
parent
0d64e4bd0c
commit
ede482ce6f
3 changed files with 60 additions and 18 deletions
|
@ -5,7 +5,7 @@ PortAudio.jl is a wrapper for [libportaudio](http://www.portaudio.com/), which g
|
||||||
|
|
||||||
## Opening a stream
|
## Opening a stream
|
||||||
|
|
||||||
The easiest way to open a source or sink is with the default `PortAudioStream()` constructor, which will open a 2-in, 2-out stream to your system's default device(s). The constructor can also take the input and output channel counts as positional arguments, or a variety of other keyword arguments.
|
The easiest way to open a source or sink is with the default `PortAudioStream()` constructor, which will open a 0-in, 2-out stream to your system's default device(s). The constructor can also take the input and output channel counts as positional arguments, or a variety of other keyword arguments.
|
||||||
|
|
||||||
```julia
|
```julia
|
||||||
PortAudioStream(inchans=2, outchans=2; eltype=Float32, samplerate=48000Hz, blocksize=4096)
|
PortAudioStream(inchans=2, outchans=2; eltype=Float32, samplerate=48000Hz, blocksize=4096)
|
||||||
|
|
|
@ -14,7 +14,12 @@ include("libportaudio.jl")
|
||||||
|
|
||||||
export PortAudioStream
|
export PortAudioStream
|
||||||
|
|
||||||
# Size of the ringbuffer in frames. 85ms latency at 48kHz
|
# These sizes are all in frames
|
||||||
|
# larger ringbuffer lets you fill in more and be more robust against drop-outs
|
||||||
|
const DEFAULT_RINGBUFSIZE=16384
|
||||||
|
# the prefill frames determine the in-to-out latency on a synchronized duplex stream
|
||||||
|
const DEFAULT_PREFILL=4096
|
||||||
|
# the block size is what we request from portaudio if no blocksize is given
|
||||||
const DEFAULT_BLOCKSIZE=4096
|
const DEFAULT_BLOCKSIZE=4096
|
||||||
# data is passed to and from the ringbuffer in chunks with this many frames
|
# data is passed to and from the ringbuffer in chunks with this many frames
|
||||||
# it should be at most the ringbuffer size, and must evenly divide into the
|
# it should be at most the ringbuffer size, and must evenly divide into the
|
||||||
|
@ -95,6 +100,8 @@ type PortAudioStream{T, U}
|
||||||
# constructor below
|
# constructor below
|
||||||
function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
|
function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
|
||||||
inchans, outchans, sr, blocksize)
|
inchans, outchans, sr, blocksize)
|
||||||
|
inchans = inchans == -1 ? indev.maxinchans : inchans
|
||||||
|
outchans = outchans == -1 ? outdev.maxoutchans : outchans
|
||||||
inparams = (inchans == 0) ?
|
inparams = (inchans == 0) ?
|
||||||
Ptr{Pa_StreamParameters}(0) :
|
Ptr{Pa_StreamParameters}(0) :
|
||||||
Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.0, C_NULL))
|
Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.0, C_NULL))
|
||||||
|
@ -103,11 +110,11 @@ type PortAudioStream{T, U}
|
||||||
Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.0, C_NULL))
|
Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.0, C_NULL))
|
||||||
this = new(sr, blocksize, C_NULL)
|
this = new(sr, blocksize, C_NULL)
|
||||||
finalizer(this, close)
|
finalizer(this, close)
|
||||||
this.sink = PortAudioSink{T, U}(outdev.name, this, outchans, blocksize)
|
this.sink = PortAudioSink{T, U}(outdev.name, this, outchans, DEFAULT_RINGBUFSIZE)
|
||||||
this.source = PortAudioSource{T, U}(indev.name, this, inchans, blocksize)
|
this.source = PortAudioSource{T, U}(indev.name, this, inchans, DEFAULT_RINGBUFSIZE)
|
||||||
if inchans > 0 && outchans > 0
|
if inchans > 0 && outchans > 0
|
||||||
# we've got a duplex stream. initialize with the output buffer full
|
# we've got a duplex stream. initialize with the output buffer full
|
||||||
write(this.sink, SampleBuf(zeros(T, blocksize, outchans), sr))
|
write(this.sink, SampleBuf(zeros(T, DEFAULT_PREFILL, outchans), sr))
|
||||||
end
|
end
|
||||||
this.bufinfo = CallbackInfo(inchans, this.source.ringbuf,
|
this.bufinfo = CallbackInfo(inchans, this.source.ringbuf,
|
||||||
outchans, this.sink.ringbuf)
|
outchans, this.sink.ringbuf)
|
||||||
|
@ -123,10 +130,11 @@ end
|
||||||
# this is the top-level outer constructor that all the other outer constructors
|
# this is the top-level outer constructor that all the other outer constructors
|
||||||
# end up calling
|
# end up calling
|
||||||
function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
|
function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
|
||||||
inchans=2, outchans=2; eltype=Float32, samplerate=48000Hz, blocksize=DEFAULT_BLOCKSIZE)
|
inchans=-1, outchans=-1; eltype=Float32, samplerate=48000Hz, blocksize=DEFAULT_BLOCKSIZE)
|
||||||
PortAudioStream{eltype, typeof(samplerate)}(indev, outdev, inchans, outchans, samplerate, blocksize)
|
PortAudioStream{eltype, typeof(samplerate)}(indev, outdev, inchans, outchans, samplerate, blocksize)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# handle device names given as streams
|
||||||
function PortAudioStream(indevname::AbstractString, outdevname::AbstractString, args...; kwargs...)
|
function PortAudioStream(indevname::AbstractString, outdevname::AbstractString, args...; kwargs...)
|
||||||
indev = nothing
|
indev = nothing
|
||||||
outdev = nothing
|
outdev = nothing
|
||||||
|
@ -148,17 +156,24 @@ function PortAudioStream(indevname::AbstractString, outdevname::AbstractString,
|
||||||
PortAudioStream(indev, outdev, args...; kwargs...)
|
PortAudioStream(indev, outdev, args...; kwargs...)
|
||||||
end
|
end
|
||||||
|
|
||||||
# if one device is given, use it for input and output
|
# if one device is given, use it for input and output, but set inchans=0 so we
|
||||||
PortAudioStream(device::PortAudioDevice, args...; kwargs...) = PortAudioStream(device, device, args...; kwargs...)
|
# end up with an output-only stream
|
||||||
PortAudioStream(device::AbstractString, args...; kwargs...) = PortAudioStream(device, device, args...; kwargs...)
|
function PortAudioStream(device::PortAudioDevice, inchans=-1, outchans=-1; kwargs...)
|
||||||
|
inchans = inchans == -1 ? 0 : inchans
|
||||||
|
PortAudioStream(device, device, inchans, outchans; kwargs...)
|
||||||
|
end
|
||||||
|
function PortAudioStream(device::AbstractString, inchans=-1, outchans=-1; kwargs...)
|
||||||
|
inchans = inchans == -1 ? 0 : inchans
|
||||||
|
PortAudioStream(device, device, inchans, outchans; kwargs...)
|
||||||
|
end
|
||||||
|
|
||||||
# use the default input and output devices
|
# use the default input and output devices
|
||||||
function PortAudioStream(args...; kwargs...)
|
function PortAudioStream(inchans=0, outchans=-1; kwargs...)
|
||||||
inidx = Pa_GetDefaultInputDevice()
|
inidx = Pa_GetDefaultInputDevice()
|
||||||
indevice = PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx)
|
indevice = PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx)
|
||||||
outidx = Pa_GetDefaultOutputDevice()
|
outidx = Pa_GetDefaultOutputDevice()
|
||||||
outdevice = PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx)
|
outdevice = PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx)
|
||||||
PortAudioStream(indevice, outdevice, args...; kwargs...)
|
PortAudioStream(indevice, outdevice, inchans, outchans; kwargs...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Base.close(stream::PortAudioStream)
|
function Base.close(stream::PortAudioStream)
|
||||||
|
@ -182,6 +197,7 @@ Base.read(stream::PortAudioStream, args...) = read(stream.source, args...)
|
||||||
Base.read!(stream::PortAudioStream, args...) = read!(stream.source, args...)
|
Base.read!(stream::PortAudioStream, args...) = read!(stream.source, args...)
|
||||||
Base.write(stream::PortAudioStream, args...) = write(stream.sink, args...)
|
Base.write(stream::PortAudioStream, args...) = write(stream.sink, args...)
|
||||||
Base.write(sink::PortAudioStream, source::PortAudioStream, args...) = write(sink.sink, source.source, args...)
|
Base.write(sink::PortAudioStream, source::PortAudioStream, args...) = write(sink.sink, source.source, args...)
|
||||||
|
Base.flush(stream::PortAudioStream) = flush(stream.sink)
|
||||||
|
|
||||||
function Base.show(io::IO, stream::PortAudioStream)
|
function Base.show(io::IO, stream::PortAudioStream)
|
||||||
println(io, typeof(stream))
|
println(io, typeof(stream))
|
||||||
|
@ -205,11 +221,11 @@ for (TypeName, Super) in ((:PortAudioSink, :SampleSink),
|
||||||
ringbuf::LockFreeRingBuffer{T}
|
ringbuf::LockFreeRingBuffer{T}
|
||||||
nchannels::Int
|
nchannels::Int
|
||||||
|
|
||||||
function $TypeName(name, stream, channels, blocksize)
|
function $TypeName(name, stream, channels, ringbufsize)
|
||||||
# portaudio data comes in interleaved, so we'll end up transposing
|
# portaudio data comes in interleaved, so we'll end up transposing
|
||||||
# it back and forth to julia column-major
|
# it back and forth to julia column-major
|
||||||
chunkbuf = zeros(T, channels, CHUNKSIZE)
|
chunkbuf = zeros(T, channels, CHUNKSIZE)
|
||||||
ringbuf = LockFreeRingBuffer(T, blocksize * channels)
|
ringbuf = LockFreeRingBuffer(T, ringbufsize * channels)
|
||||||
new(name, stream, chunkbuf, ringbuf, channels)
|
new(name, stream, chunkbuf, ringbuf, channels)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -226,6 +242,11 @@ function Base.show{T <: Union{PortAudioSink, PortAudioSource}}(io::IO, stream::T
|
||||||
print(io, nchannels(stream), " channels")
|
print(io, nchannels(stream), " channels")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Base.flush(sink::PortAudioSink)
|
||||||
|
while nwritable(sink.ringbuf) < length(sink.ringbuf)
|
||||||
|
wait(sink.ringbuf)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function SampledSignals.unsafe_write(sink::PortAudioSink, buf::SampleBuf)
|
function SampledSignals.unsafe_write(sink::PortAudioSink, buf::SampleBuf)
|
||||||
total = nframes(buf)
|
total = nframes(buf)
|
||||||
|
@ -235,7 +256,8 @@ function SampledSignals.unsafe_write(sink::PortAudioSink, buf::SampleBuf)
|
||||||
wait(sink.ringbuf)
|
wait(sink.ringbuf)
|
||||||
end
|
end
|
||||||
# in 0.4 transpose! throws an error if the range is a UInt
|
# in 0.4 transpose! throws an error if the range is a UInt
|
||||||
towrite = Int(min(nwritable(sink.ringbuf), CHUNKSIZE, total-nwritten))
|
writable = div(nwritable(sink.ringbuf), nchannels(sink))
|
||||||
|
towrite = Int(min(writable, CHUNKSIZE, total-nwritten))
|
||||||
# make a buffer of interleaved samples
|
# make a buffer of interleaved samples
|
||||||
# TODO: don't directly access buf.data
|
# TODO: don't directly access buf.data
|
||||||
transpose!(view(sink.chunkbuf, :, 1:towrite),
|
transpose!(view(sink.chunkbuf, :, 1:towrite),
|
||||||
|
|
|
@ -97,10 +97,29 @@ using RingBuffers
|
||||||
end
|
end
|
||||||
|
|
||||||
@testset "Open Default Device" begin
|
@testset "Open Default Device" begin
|
||||||
|
println("Recording...")
|
||||||
|
stream = PortAudioStream(2, 0)
|
||||||
|
buf = read(stream, 5s)
|
||||||
|
close(stream)
|
||||||
|
@test size(buf) == (round(Int, 5s * samplerate(stream)), nchannels(stream.source))
|
||||||
|
println("Playing back recording...")
|
||||||
stream = PortAudioStream()
|
stream = PortAudioStream()
|
||||||
buf = read(stream, 0.001s)
|
|
||||||
@test size(buf) == (round(Int, 0.001s * samplerate(stream)), nchannels(stream.source))
|
|
||||||
write(stream, buf)
|
write(stream, buf)
|
||||||
|
println("flushing...")
|
||||||
|
flush(stream)
|
||||||
|
close(stream)
|
||||||
|
println("Testing pass-through")
|
||||||
|
stream = PortAudioStream(2, 2)
|
||||||
|
write(stream, stream, 5s)
|
||||||
|
flush(stream)
|
||||||
|
close(stream)
|
||||||
|
println("done")
|
||||||
|
end
|
||||||
|
@testset "Samplerate-converting writing" begin
|
||||||
|
stream = PortAudioStream()
|
||||||
|
write(stream, SinSource(eltype(stream), samplerate(stream)*0.8, [220Hz, 330Hz]), 10s)
|
||||||
|
write(stream, SinSource(eltype(stream), samplerate(stream)*1.2, [220Hz, 330Hz]), 10s)
|
||||||
|
flush(stream)
|
||||||
close(stream)
|
close(stream)
|
||||||
end
|
end
|
||||||
@testset "Open Device by name" begin
|
@testset "Open Device by name" begin
|
||||||
|
@ -125,16 +144,17 @@ using RingBuffers
|
||||||
# but at least it's not crashing.
|
# but at least it's not crashing.
|
||||||
@testset "Queued Writing" begin
|
@testset "Queued Writing" begin
|
||||||
stream = PortAudioStream(0, 2)
|
stream = PortAudioStream(0, 2)
|
||||||
buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink)), samplerate(stream))
|
buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink))*0.1, samplerate(stream))
|
||||||
t1 = @async write(stream, buf)
|
t1 = @async write(stream, buf)
|
||||||
t2 = @async write(stream, buf)
|
t2 = @async write(stream, buf)
|
||||||
@test wait(t1) == 48000
|
@test wait(t1) == 48000
|
||||||
@test wait(t2) == 48000
|
@test wait(t2) == 48000
|
||||||
|
flush(stream)
|
||||||
close(stream)
|
close(stream)
|
||||||
end
|
end
|
||||||
@testset "Queued Reading" begin
|
@testset "Queued Reading" begin
|
||||||
stream = PortAudioStream(2, 0)
|
stream = PortAudioStream(2, 0)
|
||||||
buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source)), samplerate(stream))
|
buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source))*0.1, samplerate(stream))
|
||||||
t1 = @async read!(stream, buf)
|
t1 = @async read!(stream, buf)
|
||||||
t2 = @async read!(stream, buf)
|
t2 = @async read!(stream, buf)
|
||||||
@test wait(t1) == 48000
|
@test wait(t1) == 48000
|
||||||
|
|
Loading…
Reference in a new issue