JuliaAudio / PortAudio.jl

PortAudio wrapper for the Julia programming language, compatible with the JuliaAudio family of packages
Other
115 stars 19 forks source link

support readbytes! for PortAudioStream #104

Open omlins opened 2 years ago

omlins commented 2 years ago

The functions read and read! being supported for PortAudioStream, is adding support for readbytes! feasible?

ssfrr commented 2 years ago

PortAudioStreams are conceptually a steam of audio samples, so readbytes doesn’t really fit the model. That said, it could be thought of as a lower-level function that fills up a raw byte array rather than a SampleBuf, and it’s up to the caller to convert into samples of the appropriate type. Not sure how useful that is though. What’s your use case?

omlins commented 2 years ago

@ssfrr, thanks a lot for your reply. The use case is an offline, low-latency speech to command software and API (to be made public in a few days for open community development). Currently, an OS-specific recorder is used and the recording via byte stream read into the application. More precisely, the recorder process is simply run and output captured by doing stream = open(``$RECORDER_BACKEND $RECORDER_ARGS``). Then, this stream is read in a while loop with readbytes! as follows:

bytes_read = readbytes!(stream, audio_chunk); 

The audio-chunk is then passed to the speech recognition backend. The read operation which could be potentially reading chunks as small as 8 bytes per iteration (however rather 32-1024 - a parameter still to be optimized), needs to be done at maximal perfomance (avoiding, e.g., allocations) in order allow minimal latency between the completion of the reading of a command and its execution. With the current implementation, this works beautifully (enabling to run a command as little as 5-50 ms after read-in is completed). However, the recording is not portable: this is what should be solved by using PortAudio instead.

omlins commented 2 years ago

@ssfrr : I understand your thoughts about readbytes!. I think one can work with SamplBufs and extract the raw bytes when needed using reinterpret. In the following is an example. Do you see any issue with that?

julia> using PortAudio, SampledSignals

julia> stream = PortAudioStream(1, 0; eltype=Int16, samplerate=44100.0, latency=0.001)
PortAudioStream{Int16}
  Samplerate: 44100.0Hz
  1 channel source: "default"

julia> buf = SampleBuf(zeros(eltype(stream), 8, nchannels(stream.source)), samplerate(stream))
8-frame, 1-channel SampleBuf{Int16, 2}
0.00018140589569160998s sampled at 44100.0Hz
▁▁▁▁▁▁▁▁

julia> read!(stream, buf)
8

julia> buf
8-frame, 1-channel SampleBuf{Int16, 2}
0.00018140589569160998s sampled at 44100.0Hz
▁█▁█▁█▁█

julia> buf_bytes = reinterpret(UInt8, buf.data)
16×1 reinterpret(UInt8, ::Matrix{Int16}):
 0x00
 0x00
 0xc0
 0xb8
 0x00
 0x00
 0xa0
 0xb9
 0x00
 0x00
 0xa0
 0xb9
 0x00
 0x00
 0x90
 0xb9

This would give basically the functionality of readbytes! with one exception: readbytes!(stream, b) reads at most nb=length(b) bytes, while read! of an PortAudioStream reads always a fixed amount, rather than allowing to set a maximum. That said, I am not sure if it would make sense to support the equivivalent functionality in read! - meaning allowing to set a maxium of samples that are read at most? Thanks a lot for looking into that!