Unsolved How to minimise audio output latency?
-
I'm porting an emulator to Qt. So I have programmatically-generated audio, and I want only a limited amount of forward buffering.
As a first attempt I tried push audio, grabbing a
QIODevice
and writing to it as and when data is available. I am testing under macOS and have calledsetBufferSize
with a small number before calling start (though I'm suspicious that it doesn't work; see below), but latency is still noticeably dreadful. I had the idea that maybe I'm allowing too much audio to become buffered during initial startup, but there's no obvious way to determine how much data is currently buffered so there's no way to compensate for that at runtime.So I switched to implementing my own
QIODevice
and using theQAudioOutput
pull interface. I figured that gives me sufficient control to avoid over-buffering. But then the interface appears to act illogically. In my specific case, I callsetBufferSize
with a value of1024
. I then start the output. I querybufferSize()
and get a value of2048
. Fine, not great, but fine. Except that the buffer size appears to have no bearing on theQIODevice
calls — theQAudioOutput
always requests buffers in 16384-byte chunks despite the buffer size being 2048.I guess this cuts to the documentation offering no definition for what the buffer size actually means, but I definitely can't come up with any meaningful interpretation in which a buffer which advertises itself as being 2048 bytes would then attempt to read data in 16384-byte chunks.
As an aside, my machine's output rate is 48,000 Hz and as a result that's the output rate I'm requesting (via
QAudioDeviceInfo::defaultOutputDevice().preferredFormat()
, but manually verified as being 48,000 on my particular machine). Given that I'm requesting 16-bit audio, 1/6th of a second's latency could well be what I'm hearing, though it feels a lot longer.So:
- what does the buffer size actually mean, given that a value of 2048 is reported by an audio output that pulls data in 16384-byte chunks?
- is there something I'm supposed to do other than call
setBufferSize
to request the lowest latency that Qt can provide?
As an aside, the native macOS version of this app works without disturbance with buffers as small as 512 bytes. So I don't think that either my machine or my OS is causing a problem.
Also, I'm aware that using some other library for the relatively specialised purpose of low-latency audio wouldn't necessarily be a bad idea, but I also want a working pure-Qt build, that does the best that Qt can do.
-
Hello, I've just looked into that problem not long time ago.
The key point is, when opening the io device, remember to addQIODevice::Unbuffered
toOpenMode
, or it will always ask at least 16384 bytes, which is hardcoded inqiodevice_p.h
:#ifndef QIODEVICE_BUFFERSIZE #define QIODEVICE_BUFFERSIZE 16384 #endif
-
Fantastic, thanks!
That seems to make the QAudioOutput act much closer to what I'd expect given its
bufferSize()
; it still announces a size of 2048 but now seems primarily to read in 1024-byte increments.It's a shame that there isn't an exact meaning of buffer size — empirically on macOS I guess it's a ring buffer that is filled half at a time — but I guess if I take it to be 'the maximum amount that may be read at a time' then I have more than enough information to obtain that minimum available latency, and provide the absolute best plain-Qt experience that I can.
Thanks again!
-
@ThomH could your issue be called as solved? Thanks.
-
Hey guys!
Sorry to dig up this old thread, but I am currently at the exact same position as OP, the QAudioOutput always requests chunks of 16384 Byte-Data in pull-operation, which is quite too much to achieve low latency.
If I open my self implemented QIODevice in" QIODevice::Unbuffered" instead of "QIODevice:: readOnly" as Bonnie suggested I am getting a warning "Devie is opened in write-only mode" and the Audio Output wont even start. Is there someting I am missing?
I am using the audiodevice_out->open(QIODevice::Unbuffered) function.
Can I somehow add the 'unbuffered' property to the readOnly-Mode?I'd be really grateful if you could anwser me!
Kind regards!
-
@Mr-FreshDachs
You should add that flag to what you are using, not use it only, likeQIODevice::ReadOnly | QIODevice::Unbuffered
-
Since I'm a real noob in Qt and pretty much C++ in general, I didn't even know you could do that!
Now It's working just as expected.
Thank you very much!