Bug 223380 - Regression(r273542) Degraded ScriptProcessorNode performance/quality for (14.0.3+ and iOS 14.4.1+)
Summary: Regression(r273542) Degraded ScriptProcessorNode performance/quality for (14....
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Web Audio (show other bugs)
Version: Safari 14
Hardware: All All
: P2 Critical
Assignee: Chris Dumez
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2021-03-17 08:40 PDT by Jack Schaedler
Modified: 2021-04-07 10:48 PDT (History)
12 users (show)

See Also:


Attachments
Patch (5.00 KB, patch)
2021-03-17 09:58 PDT, Chris Dumez
no flags Details | Formatted Diff | Diff
Patch (8.98 KB, patch)
2021-03-17 10:54 PDT, Chris Dumez
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Jack Schaedler 2021-03-17 08:40:50 PDT
After updating to Safari 14.0.3 or iOS 14.4.1, the audio quality on pages that use ScriptProcessorNode has degraded quite dramatically. This can be observed by visiting a page like  https://learningsynths.ableton.com/playground.

To reproduce: Grab one of the sliders and slowly drag it back and forth.

Prior to 14.0.3 and iOS14.4.1, the audio playback on this site was very smooth, even for devices like an iPhone 5s. After updating, desktop Safari (MacBook Pro 2015) crackles and pops intermittently (~every 1-3 seconds). The situation is a bit worse for iOS devices, where the audio is almost constantly glitchy on a 2020 iPhone SE.

An interesting finding:
Increasing the ScriptProcessorNode's buffer size does not mitigate the problem. Rather, at certain buffer sizes the glitching becomes very repetitive and almost periodic, leading one to believe that the issue is likely not due to the DSP failing to complete processing on time, but may have to do with the implementation of ScriptProcessorNode.

The site works well/smoothly on other browsers which support AudioWorklet: Firefox, Chrome, and Safari Tech Preview. Viewing the site in 14.0.3 or iOS 14.4.1 will trigger a fallback to ScriptProcessorNode since AudioWorklet is not yet supported there, and this will lead to the issues described above.

If the fallback to ScriptProcessorNode is forced on Chrome, similar glitching occurs with the latest stable version.

When considering mitigation strategies, it would be helpful to know if there's a timeframe for the release of AudioWorklet in stable builds of Safari. However, I do understand that this sort of information is usually not disclosed.
Comment 1 Chris Dumez 2021-03-17 08:42:57 PDT
Potential regression from http://trac.webkit.org/r273542.
Comment 2 Chris Dumez 2021-03-17 09:58:41 PDT
Created attachment 423499 [details]
Patch
Comment 3 Radar WebKit Bug Importer 2021-03-17 10:00:09 PDT
<rdar://problem/75530910>
Comment 4 Geoffrey Garen 2021-03-17 10:34:56 PDT
Comment on attachment 423499 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=423499&action=review

r=me

> Source/WebCore/Modules/webaudio/ScriptProcessorNode.cpp:162
>      // Get input and output buffers. We double-buffer both the input and output sides.
>      unsigned doubleBufferIndex = this->doubleBufferIndex();
>      bool isDoubleBufferIndexGood = doubleBufferIndex < 2 && doubleBufferIndex < m_inputBuffers.size() && doubleBufferIndex < m_outputBuffers.size();
>      ASSERT(isDoubleBufferIndexGood);
>      if (!isDoubleBufferIndexGood)
>          return;

This code made it hard for me to understand that doubleBufferIndex is always 0 or 1, and is a simple buffer swapper. It made me think that there are some conditions under which we choose to set doubleBufferIndex to some other value, and those conditions are just not reachable at this point of this function. Which is not true. Since we already have a helper function for doubleBufferIndex(), if we really want to do validation, let's do it in doubleBufferIndex(). For example, we can change doubleBufferIndex() to return std::min(m_doubleBufferIndex, 1). Or we can just remove this nonsense because it is nonsense.

> Source/WebCore/Modules/webaudio/ScriptProcessorNode.cpp:225
>              callOnMainThread([this, doubleBufferIndex = m_doubleBufferIndex, protector = makeRef(*this)] {
> -                auto locker = holdLock(m_processLock);
> +                auto locker = holdLock(m_bufferLocks[doubleBufferIndex]);
>                  fireProcessEvent(doubleBufferIndex);

It's not ideal to re-read m_doubleBufferIndex here (and a few lines above). Better to capture the local variable doubleBufferIndex instead. They'll always be the same in practice - but capturing the local helps to clarify that we're definitely holding the same lock. (Also kinda funny that we do all that nonsense validation of doubleBufferIndex above and then ignore it here!)

> Source/WebCore/Modules/webaudio/ScriptProcessorNode.h:93
>      Vector<RefPtr<AudioBuffer>> m_inputBuffers;
>      Vector<RefPtr<AudioBuffer>> m_outputBuffers;

Let's change these Vectors to optional<std::array<2>>, to clarify their relationship to the 2 locks and their use in double buffering.
Comment 5 Chris Dumez 2021-03-17 10:54:48 PDT
Created attachment 423503 [details]
Patch
Comment 6 Chris Dumez 2021-03-17 11:08:09 PDT
(In reply to Chris Dumez from comment #5)
> Created attachment 423503 [details]
> Patch

Requesting review again due to substantial changes based on Geoff's feedback.
Comment 7 Geoffrey Garen 2021-03-17 11:46:43 PDT
Comment on attachment 423503 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=423503&action=review

r=me

I really like this.

> Source/WebCore/Modules/webaudio/ScriptProcessorNode.cpp:132
> +    for (unsigned i = 0; i < bufferCount; ++i) {
> +        auto locker = holdLock(m_bufferLocks[i]);
> +        m_inputBuffers[i] = nullptr;
> +        m_outputBuffers[i] = nullptr;
> +    }

Nice!
Comment 8 Chris Dumez 2021-03-17 11:54:51 PDT
Comment on attachment 423503 [details]
Patch

Clearing flags on attachment: 423503

Committed r274573 (235418@main): <https://commits.webkit.org/235418@main>
Comment 9 Chris Dumez 2021-03-17 11:54:54 PDT
All reviewed patches have been landed.  Closing bug.
Comment 10 Eyal Cohen 2021-03-22 08:43:43 PDT
This is a critical issue for us, our web application makes extensive use of ScriptProcessorNode. 

Do you have an ETA for this fix?
Comment 11 Chris Dumez 2021-04-07 10:48:16 PDT
(In reply to Eyal Cohen from comment #10)
> This is a critical issue for us, our web application makes extensive use of
> ScriptProcessorNode. 
> 
> Do you have an ETA for this fix?

I am not 100% sure but I think this script processor node fix is indeed in iOS 14.5 beta 7.