Compat Julia 0.7

This commit is contained in:
WooKyoung Noh 2018-06-21 16:59:43 +09:00
parent 03aefe619d
commit 5823404f1a
11 changed files with 102 additions and 80 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
*.swp *.swp
*.o *.o
deps/deps.jl deps/deps.jl
deps/build.log
*.wav *.wav
*.flac *.flac
*.cov *.cov

View file

@ -6,6 +6,7 @@ os:
sudo: required sudo: required
julia: julia:
- 0.6 - 0.6
- nightly
notifications: notifications:
email: false email: false
script: script:

View file

@ -1,6 +1,7 @@
julia 0.6.0-dev.2746 julia 0.6
BinDeps BinDeps 0.8.8
SampledSignals 0.3.0 SampledSignals 1.1.2
RingBuffers 1.0.0 RingBuffers 1.1.2
Compat 0.66.0
@osx Homebrew @osx Homebrew
@windows WinRPM @windows WinRPM

View file

@ -2,6 +2,7 @@ environment:
matrix: matrix:
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe"
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe"
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
notifications: notifications:
- provider: Email - provider: Email

3
deps/build.jl vendored
View file

@ -1,8 +1,9 @@
using BinDeps using BinDeps
using Compat
@BinDeps.setup @BinDeps.setup
ENV["JULIA_ROOT"] = abspath(JULIA_HOME, "../../") ENV["JULIA_ROOT"] = abspath(Compat.Sys.BINDIR, "../../")
# include alias for WinRPM library # include alias for WinRPM library
libportaudio = library_dependency("libportaudio", aliases=["libportaudio-2"]) libportaudio = library_dependency("libportaudio", aliases=["libportaudio-2"])

View file

@ -1,30 +1,26 @@
__precompile__() __precompile__(true)
module PortAudio module PortAudio
using SampledSignals using SampledSignals
using RingBuffers using RingBuffers
#using Suppressor using Compat
import Compat: undef, fetch, @compat
import Compat.LinearAlgebra: transpose!
import Base: eltype, show import Base: eltype, show
import Base: close, isopen import Base: close, isopen
import Base: read, read!, write, flush import Base: read, read!, write, flush
export PortAudioStream
# Get binary dependencies loaded from BinDeps # Get binary dependencies loaded from BinDeps
include("../deps/deps.jl") include("../deps/deps.jl")
include("suppressor.jl") include("suppressor.jl")
include("pa_shim.jl") include("pa_shim.jl")
include("libportaudio.jl") include("libportaudio.jl")
function __init__()
init_pa_shim()
global const notifycb_c = cfunction(notifycb, Cint, (Ptr{Void}, ))
# initialize PortAudio on module load
@suppress_err Pa_Initialize()
end
export PortAudioStream
# These sizes are all in frames # These sizes are all in frames
# the block size is what we request from portaudio if no blocksize is given. # the block size is what we request from portaudio if no blocksize is given.
@ -46,7 +42,7 @@ function versioninfo(io::IO=STDOUT)
println(io, "Shim Source Hash: ", shimhash()[1:10]) println(io, "Shim Source Hash: ", shimhash()[1:10])
end end
type PortAudioDevice mutable struct PortAudioDevice
name::String name::String
hostapi::String hostapi::String
maxinchans::Int maxinchans::Int
@ -76,7 +72,7 @@ devnames() = join(["\"$(dev.name)\"" for dev in devices()], "\n")
# PortAudioStream # PortAudioStream
################## ##################
type PortAudioStream{T} mutable struct PortAudioStream{T}
samplerate::Float64 samplerate::Float64
blocksize::Int blocksize::Int
stream::PaStream stream::PaStream
@ -98,7 +94,7 @@ type PortAudioStream{T}
Ptr{Pa_StreamParameters}(0) : Ptr{Pa_StreamParameters}(0) :
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) @compat finalizer(close, this)
this.sink = PortAudioSink{T}(outdev.name, this, outchans, blocksize*2) this.sink = PortAudioSink{T}(outdev.name, this, outchans, blocksize*2)
this.source = PortAudioSource{T}(indev.name, this, inchans, blocksize*2) this.source = PortAudioSource{T}(indev.name, this, inchans, blocksize*2)
this.errbuf = RingBuffer{pa_shim_errmsg_t}(1, ERR_BUFSIZE) this.errbuf = RingBuffer{pa_shim_errmsg_t}(1, ERR_BUFSIZE)
@ -127,6 +123,7 @@ type PortAudioStream{T}
end end
end end
# this is the top-level outer constructor that all the other outer constructors end up calling
""" """
PortAudioStream(inchannels=2, outchannels=2; options...) PortAudioStream(inchannels=2, outchannels=2; options...)
PortAudioStream(duplexdevice, inchannels=2, outchannels=2; options...) PortAudioStream(duplexdevice, inchannels=2, outchannels=2; options...)
@ -150,8 +147,6 @@ Options:
`false`, you are free to read and write separately, but `false`, you are free to read and write separately, but
overflow or underflow can affect the round-trip latency. overflow or underflow can affect the round-trip latency.
""" """
# this is the top-level outer constructor that all the other outer constructors
# end up calling
function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice,
inchans=2, outchans=2; eltype=Float32, samplerate=-1, blocksize=DEFAULT_BLOCKSIZE, synced=false) inchans=2, outchans=2; eltype=Float32, samplerate=-1, blocksize=DEFAULT_BLOCKSIZE, synced=false)
if samplerate == -1 if samplerate == -1
@ -226,7 +221,7 @@ end
isopen(stream::PortAudioStream) = stream.stream != C_NULL isopen(stream::PortAudioStream) = stream.stream != C_NULL
SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate
eltype{T}(stream::PortAudioStream{T}) = T eltype(stream::PortAudioStream{T}) where T = T
read(stream::PortAudioStream, args...) = read(stream.source, args...) read(stream::PortAudioStream, args...) = read(stream.source, args...)
read!(stream::PortAudioStream, args...) = read!(stream.source, args...) read!(stream::PortAudioStream, args...) = read!(stream.source, args...)
@ -253,7 +248,7 @@ Handle errors coming over the error stream from PortAudio. This is run as an
independent task while the stream is active. independent task while the stream is active.
""" """
function handle_errors(stream::PortAudioStream) function handle_errors(stream::PortAudioStream)
err = Vector{pa_shim_errmsg_t}(1) err = Vector{pa_shim_errmsg_t}(undef, 1)
while true while true
nread = read!(stream.errbuf, err) nread = read!(stream.errbuf, err)
nread == 1 || break nread == 1 || break
@ -279,7 +274,7 @@ end
# Define our source and sink types # Define our source and sink types
for (TypeName, Super) in ((:PortAudioSink, :SampleSink), for (TypeName, Super) in ((:PortAudioSink, :SampleSink),
(:PortAudioSource, :SampleSource)) (:PortAudioSource, :SampleSource))
@eval type $TypeName{T} <: $Super @eval mutable struct $TypeName{T} <: $Super
name::String name::String
stream::PortAudioStream{T} stream::PortAudioStream{T}
chunkbuf::Array{T, 2} chunkbuf::Array{T, 2}
@ -319,7 +314,7 @@ function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffse
towrite = min(framecount-nwritten, CHUNKSIZE) towrite = min(framecount-nwritten, CHUNKSIZE)
# make a buffer of interleaved samples # make a buffer of interleaved samples
transpose!(view(sink.chunkbuf, :, 1:towrite), transpose!(view(sink.chunkbuf, :, 1:towrite),
view(buf, (1:towrite)+nwritten+frameoffset, :)) view(buf, (1:towrite) .+ nwritten .+ frameoffset, :))
n = write(sink.ringbuf, sink.chunkbuf, towrite) n = write(sink.ringbuf, sink.chunkbuf, towrite)
nwritten += n nwritten += n
# break early if the stream is closed # break early if the stream is closed
@ -335,7 +330,7 @@ function SampledSignals.unsafe_read!(source::PortAudioSource, buf::Array, frameo
toread = min(framecount-nread, CHUNKSIZE) toread = min(framecount-nread, CHUNKSIZE)
n = read!(source.ringbuf, source.chunkbuf, toread) n = read!(source.ringbuf, source.chunkbuf, toread)
# de-interleave the samples # de-interleave the samples
transpose!(view(buf, (1:toread)+nread+frameoffset, :), transpose!(view(buf, (1:toread) .+ nread .+ frameoffset, :),
view(source.chunkbuf, :, 1:toread)) view(source.chunkbuf, :, 1:toread))
nread += toread nread += toread
@ -346,9 +341,43 @@ function SampledSignals.unsafe_read!(source::PortAudioSource, buf::Array, frameo
nread nread
end end
const libpa_shim = find_pa_shim()
"""
PortAudio.shimhash()
Return the sha256 hash(as a string) of the source file used to build the shim.
We may use this sometime to verify that the distributed binary stays in sync
with the rest of the package.
"""
shimhash() = unsafe_string(ccall((:pa_shim_getsourcehash, libpa_shim), Cstring, ()))
# this is called by the shim process callback to notify that there is new data. # this is called by the shim process callback to notify that there is new data.
# it's run in the audio context so don't do anything besides wake up the # it's run in the audio context so don't do anything besides wake up the
# AsyncCondition handle associated with that ring buffer # AsyncCondition handle associated with that ring buffer
notifycb(handle) = ccall(:uv_async_send, Cint, (Ptr{Void}, ), handle) notifycb(handle) = ccall(:uv_async_send, Cint, (Ptr{Cvoid},), handle)
global shim_processcb_c, notifycb_c
function set_global_callbacks()
shim_dlib = Libdl.dlopen(libpa_shim)
# pointer to the shim's process callback
global shim_processcb_c = Libdl.dlsym(shim_dlib, :pa_shim_processcb)
if shim_processcb_c == C_NULL
error("Got NULL pointer loading `pa_shim_processcb`")
end
global notifycb_c = @cfunction notifycb Cint (Ptr{Cvoid},)
end
function __init__()
set_global_callbacks()
# initialize PortAudio on module load
@suppress_err Pa_Initialize()
end
end # module PortAudio end # module PortAudio

View file

@ -9,8 +9,8 @@ const PaHostApiIndex = Cint
const PaHostApiTypeId = Cint const PaHostApiTypeId = Cint
# PaStream is always used as an opaque type, so we're always dealing # PaStream is always used as an opaque type, so we're always dealing
# with the pointer # with the pointer
const PaStream = Ptr{Void} const PaStream = Ptr{Cvoid}
const PaStreamCallback = Void const PaStreamCallback = Cvoid
const PaStreamFlags = Culong const PaStreamFlags = Culong
const paNoFlag = PaStreamFlags(0x00) const paNoFlag = PaStreamFlags(0x00)
@ -86,7 +86,7 @@ const pa_host_api_names = Dict{PaHostApiTypeId, String}(
14 => "AudioScience HPI" 14 => "AudioScience HPI"
) )
type PaHostApiInfo mutable struct PaHostApiInfo
struct_version::Cint struct_version::Cint
api_type::PaHostApiTypeId api_type::PaHostApiTypeId
name::Ptr{Cchar} name::Ptr{Cchar}
@ -100,7 +100,7 @@ Pa_GetHostApiInfo(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, libportaudio),
# Device Functions # Device Functions
type PaDeviceInfo mutable struct PaDeviceInfo
struct_version::Cint struct_version::Cint
name::Ptr{Cchar} name::Ptr{Cchar}
host_api::PaHostApiIndex host_api::PaHostApiIndex
@ -126,15 +126,15 @@ Pa_GetDefaultOutputDevice() = ccall((:Pa_GetDefaultOutputDevice, libportaudio),
# Stream Functions # Stream Functions
type Pa_StreamParameters mutable struct Pa_StreamParameters
device::PaDeviceIndex device::PaDeviceIndex
channelCount::Cint channelCount::Cint
sampleFormat::PaSampleFormat sampleFormat::PaSampleFormat
suggestedLatency::PaTime suggestedLatency::PaTime
hostAPISpecificStreamInfo::Ptr{Void} hostAPISpecificStreamInfo::Ptr{Cvoid}
end end
type PaStreamInfo mutable struct PaStreamInfo
structVersion::Cint structVersion::Cint
inputLatency::PaTime inputLatency::PaTime
outputLatency::PaTime outputLatency::PaTime
@ -148,7 +148,7 @@ end
# err = ccall((:Pa_OpenDefaultStream, libportaudio), # err = ccall((:Pa_OpenDefaultStream, libportaudio),
# PaError, (Ref{PaStream}, Cint, Cint, # PaError, (Ref{PaStream}, Cint, Cint,
# PaSampleFormat, Cdouble, Culong, # PaSampleFormat, Cdouble, Culong,
# Ref{Void}, Ref{Void}), # Ref{Cvoid}, Ref{Cvoid}),
# streamPtr, inChannels, outChannels, sampleFormat, sampleRate, # streamPtr, inChannels, outChannels, sampleFormat, sampleRate,
# framesPerBuffer, C_NULL, C_NULL) # framesPerBuffer, C_NULL, C_NULL)
# handle_status(err) # handle_status(err)
@ -166,7 +166,7 @@ function Pa_OpenStream(inParams, outParams,
Ptr{Pa_StreamParameters}, Ptr{Pa_StreamParameters},
Ptr{Pa_StreamParameters}, Ptr{Pa_StreamParameters},
Cdouble, Culong, PaStreamFlags, Cdouble, Culong, PaStreamFlags,
Ptr{Void}, Ptr{Void}), Ptr{Cvoid}, Ptr{Cvoid}),
streamPtr, streamPtr,
inParams, outParams, inParams, outParams,
sampleRate, framesPerBuffer, flags, sampleRate, framesPerBuffer, flags,
@ -211,7 +211,7 @@ function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf)
show_warnings::Bool=true) show_warnings::Bool=true)
frames <= length(buf) || error("Need a buffer at least $frames long") frames <= length(buf) || error("Need a buffer at least $frames long")
err = ccall((:Pa_ReadStream, libportaudio), PaError, err = ccall((:Pa_ReadStream, libportaudio), PaError,
(PaStream, Ptr{Void}, Culong), (PaStream, Ptr{Cvoid}, Culong),
stream, buf, frames) stream, buf, frames)
handle_status(err, show_warnings) handle_status(err, show_warnings)
buf buf
@ -221,7 +221,7 @@ function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf
show_warnings::Bool=true) show_warnings::Bool=true)
frames <= length(buf) || error("Need a buffer at least $frames long") frames <= length(buf) || error("Need a buffer at least $frames long")
err = ccall((:Pa_WriteStream, libportaudio), PaError, err = ccall((:Pa_WriteStream, libportaudio), PaError,
(PaStream, Ptr{Void}, Culong), (PaStream, Ptr{Cvoid}, Culong),
stream, buf, frames) stream, buf, frames)
handle_status(err, show_warnings) handle_status(err, show_warnings)
nothing nothing

View file

@ -1,16 +1,16 @@
function init_pa_shim() function find_pa_shim()
libdir = joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib") libdir = joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib")
libsuffix = "" libsuffix = ""
basename = "pa_shim" basename = "pa_shim"
@static if is_linux() && Sys.ARCH == :x86_64 @static if Compat.Sys.islinux() && Sys.ARCH == :x86_64
libsuffix = "x86_64-linux-gnu" libsuffix = "x86_64-linux-gnu"
elseif is_linux() && Sys.ARCH == :i686 elseif Compat.Sys.islinux() && Sys.ARCH == :i686
libsuffix = "i686-linux-gnu" libsuffix = "i686-linux-gnu"
elseif is_apple() && Sys.ARCH == :x86_64 elseif Compat.Sys.isapple() && Sys.ARCH == :x86_64
libsuffix = "x86_64-apple-darwin14" libsuffix = "x86_64-apple-darwin14"
elseif is_windows() && Sys.ARCH == :x86_64 elseif Compat.Sys.iswindows() && Sys.ARCH == :x86_64
libsuffix = "x86_64-w64-mingw32" libsuffix = "x86_64-w64-mingw32"
elseif is_windows() && Sys.ARCH == :i686 elseif Compat.Sys.iswindows() && Sys.ARCH == :i686
libsuffix = "i686-w64-mingw32" libsuffix = "i686-w64-mingw32"
elseif !any( elseif !any(
(sfx) -> isfile(joinpath(libdir, "$basename.$sfx")), (sfx) -> isfile(joinpath(libdir, "$basename.$sfx")),
@ -19,16 +19,11 @@ function init_pa_shim()
end end
# if there's a suffix-less library, it was built natively on this machine, # if there's a suffix-less library, it was built natively on this machine,
# so load that one first, otherwise load the pre-built one # so load that one first, otherwise load the pre-built one
global const libpa_shim = Base.Libdl.find_library( libpa_shim = Libdl.find_library(
[basename, "$(basename)_$libsuffix"], [basename, "$(basename)_$libsuffix"],
[libdir]) [libdir])
libpa_shim == "" && error("Could not load $basename library, please file an issue at https://github.com/JuliaAudio/RingBuffers.jl/issues with your `versioninfo()` output") libpa_shim == "" && error("Could not load $basename library, please file an issue at https://github.com/JuliaAudio/RingBuffers.jl/issues with your `versioninfo()` output")
shim_dlib = Libdl.dlopen(libpa_shim) return libpa_shim
# pointer to the shim's process callback
global const shim_processcb_c = Libdl.dlsym(shim_dlib, :pa_shim_processcb)
if shim_processcb_c == C_NULL
error("Got NULL pointer loading `pa_shim_processcb`")
end
end end
const pa_shim_errmsg_t = Cint const pa_shim_errmsg_t = Cint
@ -43,19 +38,10 @@ mutable struct pa_shim_info_t
outputbuf::Ptr{PaUtilRingBuffer} # ringbuffer for output outputbuf::Ptr{PaUtilRingBuffer} # ringbuffer for output
errorbuf::Ptr{PaUtilRingBuffer} # ringbuffer to send error notifications errorbuf::Ptr{PaUtilRingBuffer} # ringbuffer to send error notifications
sync::Cint # keep input/output ring buffers synchronized (0/1) sync::Cint # keep input/output ring buffers synchronized (0/1)
notifycb::Ptr{Void} # Julia callback to notify on updates (called from audio thread) notifycb::Ptr{Cvoid} # Julia callback to notify on updates (called from audio thread)
inputhandle::Ptr{Void} # condition to notify on new input data inputhandle::Ptr{Cvoid} # condition to notify on new input data
outputhandle::Ptr{Void} # condition to notify when ready for output outputhandle::Ptr{Cvoid} # condition to notify when ready for output
errorhandle::Ptr{Void} # condition to notify on new errors errorhandle::Ptr{Cvoid} # condition to notify on new errors
end end
""" Base.unsafe_convert(::Type{Ptr{Cvoid}}, info::pa_shim_info_t) = pointer_from_objref(info)
PortAudio.shimhash()
Return the sha256 hash(as a string) of the source file used to build the shim.
We may use this sometime to verify that the distributed binary stays in sync
with the rest of the package.
"""
shimhash() = unsafe_string(
ccall((:pa_shim_getsourcehash, libpa_shim), Cstring, ()))
Base.unsafe_convert(::Type{Ptr{Void}}, info::pa_shim_info_t) = pointer_from_objref(info)

View file

@ -4,9 +4,9 @@
macro suppress_err(block) macro suppress_err(block)
quote quote
if ccall(:jl_generating_output, Cint, ()) == 0 if ccall(:jl_generating_output, Cint, ()) == 0
ORIGINAL_STDERR = STDERR ORIGINAL_STDERR = stderr
err_rd, err_wr = redirect_stderr() err_rd, err_wr = redirect_stderr()
err_reader = @async readstring(err_rd) err_reader = @async read(err_rd, String)
end end
value = $(esc(block)) value = $(esc(block))

View file

@ -1,6 +1,8 @@
#!/usr/bin/env julia #!/usr/bin/env julia
using Base.Test using Compat
using Compat.Test
import Compat: Cvoid
using TestSetExtensions using TestSetExtensions
using PortAudio using PortAudio
using SampledSignals using SampledSignals
@ -34,7 +36,7 @@ function setup_callback(inchans, outchans, nframes, synced)
function processfunc() function processfunc()
ccall(shim_processcb_c, Cint, ccall(shim_processcb_c, Cint,
(Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{Void}), (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Cvoid}, Culong, Ptr{Cvoid}),
cb_input, cb_output, nframes, C_NULL, flags, pointer_from_objref(info)) cb_input, cb_output, nframes, C_NULL, flags, pointer_from_objref(info))
end end

View file

@ -5,13 +5,13 @@
include("runtests.jl") include("runtests.jl")
# these default values are specific to my machines # these default values are specific to my machines
if is_windows() if Compat.Sys.iswindows()
default_indev = "Microphone Array (Realtek High " default_indev = "Microphone Array (Realtek High "
default_outdev = "Speaker/Headphone (Realtek High" default_outdev = "Speaker/Headphone (Realtek High"
elseif is_apple() elseif Compat.Sys.isapple()
default_indev = "Built-in Microph" default_indev = "Built-in Microphone"
default_outdev = "Built-in Output" default_outdev = "Built-in Output"
elseif is_linux() elseif Compat.Sys.islinux()
default_indev = "default" default_indev = "default"
default_outdev = "default" default_outdev = "default"
end end
@ -50,12 +50,12 @@ end
write(stream, buf) write(stream, buf)
io = IOBuffer() io = IOBuffer()
show(io, stream) show(io, stream)
@test String(take!(io)) == """ @test Compat.occursin("""
PortAudio.PortAudioStream{Float32} PortAudioStream{Float32}
Samplerate: 44100.0Hz Samplerate: 44100.0Hz
Buffer Size: 4096 frames Buffer Size: 4096 frames
2 channel sink: "$default_outdev" 2 channel sink: "$default_outdev"
2 channel source: "$default_indev\"""" 2 channel source: "$default_indev\"""", String(take!(io)))
close(stream) close(stream)
end end
@testset "Error on wrong name" begin @testset "Error on wrong name" begin
@ -68,8 +68,8 @@ end
buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink))*0.1, 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 fetch(t1) == 48000
@test wait(t2) == 48000 @test fetch(t2) == 48000
flush(stream) flush(stream)
close(stream) close(stream)
end end
@ -78,8 +78,8 @@ end
buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source))*0.1, 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 fetch(t1) == 48000
@test wait(t2) == 48000 @test fetch(t2) == 48000
close(stream) close(stream)
end end
end end