diff --git a/examples/measure_latency.jl b/examples/measure_latency.jl new file mode 100644 index 0000000..bbae6c7 --- /dev/null +++ b/examples/measure_latency.jl @@ -0,0 +1,68 @@ +using PortAudio +using DSP + +function create_measure_signal() + signal = zeros(Float32, 20000) + for i in 1:3 + signal = vcat(signal, rand(Float32, 100), zeros(Float32, i*10000)) + end + return signal +end + +function measure_latency(in_latency = 0.1, out_latency=0.1, blocksize = 32; is_warmup = false) + + in_stream = PortAudioStream(1,0; latency=in_latency, blocksize=32) + out_stream = PortAudioStream(0,1; latency=out_latency, blocksize=32) + + cond = Base.Event() + + writer_start_time = Int64(0) + reader_start_time = Int64(0) + + reader = Threads.@spawn begin + wait(cond) + writer_start_time = time_ns() |> Int64 + return read(in_stream, 100000) + end + + signal = create_measure_signal() + writer = Threads.@spawn begin + wait(cond) + reader_start_time = time_ns() |> Int64 + write(out_stream, signal) + end + + notify(cond) + + wait(reader) + wait(writer) + + recorded = collect(reader.result)[:,1] + + close(in_stream) + close(out_stream) + + diff = reader_start_time - writer_start_time |> abs + + diff_in_ms = diff / 10^6 # 1 ms = 10^6 ns + + if !is_warmup && diff_in_ms > 1 + @warn "Threads start time difference $diff_in_ms ms is bigger than 1 ms" + end + + delay = finddelay(recorded, signal) / 48000 + + return trunc(Int, delay * 1000)# result in ms +end + +measure_latency(0.1, 0.1, 32; is_warmup = true) # warmup + +latencies = [0.1, 0.01, 0.005] +for in_latency in latencies + for out_latency in latencies + for blocksize in [32] + measure = measure_latency(in_latency, out_latency, blocksize) + println("$measure ms latency for in_latency=$in_latency, out_latency=$out_latency, blocksize=$blocksize") + end + end +end diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 8eace2c..b1a32d4 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -78,15 +78,15 @@ mutable struct PortAudioStream{T} # TODO: write a latency tester app function PortAudioStream{T}(indev::PortAudioDevice, outdev::PortAudioDevice, inchans, outchans, sr, blocksize, - warn_xruns) where {T} + warn_xruns, latency) where {T} inchans = inchans == -1 ? indev.maxinchans : inchans outchans = outchans == -1 ? outdev.maxoutchans : outchans inparams = (inchans == 0) ? Ptr{Pa_StreamParameters}(0) : - Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.1, C_NULL)) + Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], latency, C_NULL)) outparams = (outchans == 0) ? Ptr{Pa_StreamParameters}(0) : - Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.1, C_NULL)) + Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], latency, C_NULL)) this = new(sr, blocksize, C_NULL, warn_xruns) # finalizer(close, this) this.sink = PortAudioSink{T}(outdev.name, this, outchans) @@ -127,7 +127,7 @@ Options: """ function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, inchans=2, outchans=2; eltype=Float32, samplerate=-1, blocksize=DEFAULT_BLOCKSIZE, - warn_xruns=false) + warn_xruns=false, latency=0.1) if samplerate == -1 sampleratein = indev.defaultsamplerate samplerateout = outdev.defaultsamplerate @@ -142,7 +142,7 @@ function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, samplerate = samplerateout end end - PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate, blocksize, warn_xruns) + PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate, blocksize, warn_xruns, latency) end # handle device names given as streams