Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-108672

Regression: QAudioSink generates eternal silence if bytes initially available on QIODevice is lower than some threshold

    XMLWordPrintable

Details

    • Bug
    • Resolution: Fixed
    • P2: Important
    • None
    • 6.4.1
    • Multimedia
    • None
    • Windows

    Description

      In Qt 6.4.1, if `QAudioSink::start(QIODevice*)` is called and the specified `QIODevice::bytesAvailable()` returns 0 (edit: or any value smaller than some threshold; so far I'm unsuccessful with anything less than about a half second or so), the `QAudioSink` remains in `IdleState` but sets its error code to `UnderrunError`, and never recovers.

      By contrast, in Qt 5.15, `QAudioOuput::start(QIODevice*)` did not have this issue, and powered through initial (and all) underruns. Actually, IIRC, it would switch to `IdleState` if no data was available, and automatically switch back to `ActiveState` when data became available, playing said data.

      In fact, Qt 5.15 did not require `bytesAvailable()` to be implemented at all. The reliance on it is new with Qt 6.

      The consequence is it becomes exceedingly tricky to develop `QIODevice`s that don't necessarily have data available at the exact moment the `QAudioSink` is started. It also is a significantly different behavior than Qt5.

      Also, because the failure does not lead to a state change, it's difficult to detect, which is a bigger problem since it's also "non-recoverable" (the `QAudioSink` must be restarted to continue playing audio).

      Also, the sensitivity to underruns will cause a problem if a spurious underrun does occur in a low-latency application where, rather than recovering and continuing to output audio, audio output will simple cease, likely with no explanation to a user.

      I am not aware of any viable workarounds other than artificially delaying the start() call on the QAudioSink and hoping for the best, and even then that's only relevant to initial UnderrunErrors.

      The attached program illustrates this. When compiled with Qt5, it will generate a tone regardless the initial return value of `bytesAvailable()`. When compiled with Qt6, it will generate unless the fake underrun feature is enabled (see usage info), in which case it will never play anything.

      Command line usage info for attached program:

      // usage: audioissue
      //        audioissue <devindex> [<underrun>]
      // no params = list devices
      // <devindex> = device index
      // <underrun> = can be anything; simulate underrun if set.

      Explanation of attached program:

      It creates a QAudioSink that reads from a QIODevice and writes to the specified output device. The QIODevice generates a 440Hz sine wave of infinite duration. 

      The "simulate underrun" feature will cause `bytesAvailable()` of the QIODevice to return 0 the first time it is called.

      The read rate is printed to the console and actually isn't really relevant, it was for a different test. Other than that, all of the code is relevant.

      Note: While it's not illustrated in this example, I can confirm that QAudioSink never attempts to read from the QIODevice if bytesAvailable() returns 0.

      Attachments

        1. audioissue.pro
          0.3 kB
          Jason Cipriani
        2. main.cpp
          3 kB
          Jason Cipriani
        3. main.h
          0.5 kB
          Jason Cipriani
        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            artemiy Artem Dyomin
            jasonc Jason Cipriani
            Votes:
            0 Vote for this issue
            Watchers:
            7 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes