Changeset 270141 in webkit
- Timestamp:
- Nov 20, 2020 5:45:54 PM (3 years ago)
- Location:
- trunk
- Files:
-
- 4 added
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r270139 r270141 1 2020-11-20 Chris Dumez <cdumez@apple.com> 2 3 Poor resampling quality when using AudioContext sampleRate parameter 4 https://bugs.webkit.org/show_bug.cgi?id=219201 5 6 Reviewed by Geoff Garen. 7 8 Add layout test coverage that would hit assertions in debug. 9 10 * webaudio/audiocontext-large-samplerate-expected.txt: Added. 11 * webaudio/audiocontext-large-samplerate.html: Added. 12 * webaudio/audiocontext-low-samplerate-expected.txt: Added. 13 * webaudio/audiocontext-low-samplerate.html: Added. 14 1 15 2020-11-20 Chris Dumez <cdumez@apple.com> 2 16 -
trunk/Source/WebCore/ChangeLog
r270136 r270141 1 2020-11-20 Chris Dumez <cdumez@apple.com> 2 3 Poor resampling quality when using AudioContext sampleRate parameter 4 https://bugs.webkit.org/show_bug.cgi?id=219201 5 6 Reviewed by Geoff Garen. 7 8 MultiChannelResampler uses a SincResampler per audio channel. In MultiChannelResampler::process(), 9 it was calling SincResampler::process() for each channel, which would potentially end up calling 10 MultiChannelResampler::ChannelProvider::provideInput() to provide channel data used for resampling. 11 The issue was that MultiChannelResampler::ChannelProvider::provideInput() is implemented in such 12 a way that things will break if provideInput() gets called more than once per channel. When using 13 an AudioContext's sample rate larger than the hardware sample rate, provideInput() was getting 14 called more than once per channel and this resulted in very poor resampling quality. 15 16 To address the issue, MultiChannelResampler::process() now processes the data in chunks that 17 are small enough to guarantee that MultiChannelResampler::ChannelProvider::provideInput() will 18 never get called more than once per audio channel. 19 20 The fix is based on the corresponding MultiChannelResampler / SincResampler implementation in 21 Chrome: 22 - https://github.com/chromium/chromium/blob/master/media/base/multi_channel_resampler.cc 23 - https://github.com/chromium/chromium/blob/master/media/base/sinc_resampler.cc 24 25 Tests: webaudio/audiocontext-large-samplerate.html 26 webaudio/audiocontext-low-samplerate.html 27 28 * platform/audio/MultiChannelResampler.cpp: 29 (WebCore::MultiChannelResampler::ChannelProvider::setProvider): 30 (WebCore::MultiChannelResampler::ChannelProvider::setCurrentChannel): 31 (WebCore::MultiChannelResampler::process): 32 * platform/audio/MultiChannelResampler.h: 33 * platform/audio/SincResampler.cpp: 34 (WebCore::calculateChunkSize): 35 (WebCore::SincResampler::updateRegions): 36 * platform/audio/SincResampler.h: 37 1 38 2020-11-20 Kate Cheney <katherine_cheney@apple.com> 2 39 -
trunk/Source/WebCore/platform/audio/MultiChannelResampler.cpp
r267014 r270141 34 34 35 35 #include "AudioBus.h" 36 #include "AudioUtilities.h" 36 37 37 38 namespace WebCore { … … 39 40 // ChannelProvider provides a single channel of audio data (one channel at a time) for each channel 40 41 // of data provided to us in a multi-channel provider. 41 class MultiChannelResampler::ChannelProvider : public AudioSourceProvider{42 class MultiChannelResampler::ChannelProvider { 42 43 WTF_MAKE_FAST_ALLOCATED; 43 44 public: 44 45 explicit ChannelProvider(unsigned numberOfChannels) 45 : m_ numberOfChannels(numberOfChannels)46 : m_multiChannelBus(AudioBus::create(numberOfChannels, AudioUtilities::renderQuantumSize)) 46 47 { 47 48 } … … 49 50 void setProvider(AudioSourceProvider* multiChannelProvider) 50 51 { 51 m_currentChannel = 0;52 m_framesToProcess = 0;53 52 m_multiChannelProvider = multiChannelProvider; 54 53 } … … 56 55 // provideInput() will be called once for each channel, starting with the first channel. 57 56 // Each time it's called, it will provide the next channel of data. 58 void provideInput (AudioBus* bus, size_t framesToProcess) override57 void provideInputForChannel(AudioBus* bus, size_t framesToProcess, unsigned channelIndex) 59 58 { 59 ASSERT(channelIndex < m_multiChannelBus->numberOfChannels()); 60 ASSERT(framesToProcess <= AudioUtilities::renderQuantumSize); 61 if (framesToProcess > AudioUtilities::renderQuantumSize) 62 return; 63 60 64 bool isBusGood = bus && bus->numberOfChannels() == 1; 61 65 ASSERT(isBusGood); … … 65 69 // Get the data from the multi-channel provider when the first channel asks for it. 66 70 // For subsequent channels, we can just dish out the channel data from that (stored in m_multiChannelBus). 67 if (! m_currentChannel) {71 if (!channelIndex) { 68 72 m_framesToProcess = framesToProcess; 69 m_multiChannelBus = AudioBus::create(m_numberOfChannels, framesToProcess);70 73 m_multiChannelProvider->provideInput(m_multiChannelBus.get(), framesToProcess); 71 74 } 72 75 73 76 // All channels must ask for the same amount. This should always be the case, but let's just make sure. 74 bool isGood = m_multiChannelBus.get() &&framesToProcess == m_framesToProcess;77 bool isGood = framesToProcess == m_framesToProcess; 75 78 ASSERT(isGood); 76 79 if (!isGood) … … 78 81 79 82 // Copy the channel data from what we received from m_multiChannelProvider. 80 ASSERT(m_currentChannel <= m_numberOfChannels); 81 if (m_currentChannel < m_numberOfChannels) { 82 memcpy(bus->channel(0)->mutableData(), m_multiChannelBus->channel(m_currentChannel)->data(), sizeof(float) * framesToProcess); 83 ++m_currentChannel; 84 } 83 memcpy(bus->channel(0)->mutableData(), m_multiChannelBus->channel(channelIndex)->data(), sizeof(float) * framesToProcess); 85 84 } 86 85 … … 88 87 AudioSourceProvider* m_multiChannelProvider { nullptr }; 89 88 RefPtr<AudioBus> m_multiChannelBus; 90 unsigned m_numberOfChannels { 0 };91 unsigned m_currentChannel { 0 };92 89 size_t m_framesToProcess { 0 }; // Used to verify that all channels ask for the same amount. 93 90 }; … … 113 110 m_channelProvider->setProvider(provider); 114 111 115 for (unsigned channelIndex = 0; channelIndex < m_numberOfChannels; ++channelIndex) { 116 // Depending on the sample-rate scale factor, and the internal buffering used in a SincResampler 117 // kernel, this call to process() will only sometimes call provideInput() on the channelProvider. 118 // However, if it calls provideInput() for the first channel, then it will call it for the remaining 119 // channels, since they all buffer in the same way and are processing the same number of frames. 120 m_kernels[channelIndex]->process(m_channelProvider.get(), 121 destination->channel(channelIndex)->mutableData(), 122 framesToProcess); 112 // We need to ensure that SincResampler only calls provideInput() once for each channel or it will confuse the logic 113 // inside ChannelProvider. To ensure this, we chunk the number of requested frames into SincResampler::chunkSize() 114 // sized chunks. SincResampler guarantees it will only call provideInput() once once we resample this way. 115 m_outputFramesReady = 0; 116 while (m_outputFramesReady < framesToProcess) { 117 size_t chunkSize = m_kernels[0]->chunkSize(); 118 size_t framesThisTime = std::min(framesToProcess - m_outputFramesReady, chunkSize); 119 120 for (unsigned channelIndex = 0; channelIndex < m_numberOfChannels; ++channelIndex) { 121 ASSERT(chunkSize == m_kernels[channelIndex]->chunkSize()); 122 bool wasProvideInputCalled = false; 123 m_kernels[channelIndex]->process(destination->channel(channelIndex)->mutableData() + m_outputFramesReady, framesThisTime, [this, channelIndex, &wasProvideInputCalled](AudioBus* bus, size_t framesToProcess) { 124 ASSERT_WITH_MESSAGE(!wasProvideInputCalled, "provideInputForChannel should only be called once"); 125 wasProvideInputCalled = true; 126 m_channelProvider->provideInputForChannel(bus, framesToProcess, channelIndex); 127 }); 128 } 129 130 m_outputFramesReady += framesThisTime; 123 131 } 124 132 -
trunk/Source/WebCore/platform/audio/MultiChannelResampler.h
r267014 r270141 60 60 class ChannelProvider; 61 61 std::unique_ptr<ChannelProvider> m_channelProvider; 62 size_t m_outputFramesReady { 0 }; 62 63 }; 63 64 -
trunk/Source/WebCore/platform/audio/SincResampler.cpp
r267014 r270141 116 116 constexpr unsigned numberOfKernelOffsets { 32 }; 117 117 118 static size_t calculateChunkSize(unsigned blockSize, double scaleFactor) 119 { 120 return blockSize / scaleFactor; 121 } 122 118 123 SincResampler::SincResampler(double scaleFactor, Optional<unsigned> requestFrames) 119 124 : m_scaleFactor(scaleFactor) … … 138 143 m_r4 = m_r0 + m_requestFrames - kernelSize / 2; 139 144 m_blockSize = m_r4 - m_r2; 145 m_chunkSize = calculateChunkSize(m_blockSize, m_scaleFactor); 140 146 141 147 // m_r1 at the beginning of the buffer. … … 188 194 } 189 195 190 void SincResampler::consumeSource(float* buffer, unsigned numberOfSourceFrames )191 { 192 ASSERT( m_sourceProvider);193 if (! m_sourceProvider)196 void SincResampler::consumeSource(float* buffer, unsigned numberOfSourceFrames, const Function<void(AudioBus* bus, size_t framesToProcess)>& provideInput) 197 { 198 ASSERT(provideInput); 199 if (!provideInput) 194 200 return; 195 201 … … 201 207 m_internalBus->setChannelMemory(0, buffer, numberOfSourceFrames); 202 208 203 m_sourceProvider->provideInput(m_internalBus.get(), numberOfSourceFrames);209 provideInput(m_internalBus.get(), numberOfSourceFrames); 204 210 } 205 211 206 212 namespace { 207 213 208 // BufferSourceProvider is an AudioSourceProvider wrapping an in-memory buffer.209 210 class BufferSourceProvider : public AudioSourceProvider{214 // BufferSourceProvider is an audio source provider wrapping an in-memory buffer. 215 216 class BufferSourceProvider { 211 217 public: 212 218 explicit BufferSourceProvider(const float* source, size_t numberOfSourceFrames) … … 217 223 218 224 // Consumes samples from the in-memory buffer. 219 void provideInput(AudioBus* bus, size_t framesToProcess) override225 void provideInput(AudioBus* bus, size_t framesToProcess) 220 226 { 221 227 ASSERT(m_source && bus); … … 254 260 while (remaining) { 255 261 unsigned framesThisTime = std::min(remaining, m_requestFrames); 256 process(&sourceProvider, destination, framesThisTime); 262 process(destination, framesThisTime, [&sourceProvider](AudioBus* bus, size_t framesToProcess) { 263 sourceProvider.provideInput(bus, framesToProcess); 264 }); 257 265 258 266 destination += framesThisTime; … … 261 269 } 262 270 263 void SincResampler::process( AudioSourceProvider* sourceProvider, float* destination, size_t framesToProcess)264 { 265 ASSERT( sourceProvider);266 if (! sourceProvider)271 void SincResampler::process(float* destination, size_t framesToProcess, const Function<void(AudioBus* bus, size_t framesToProcess)>& provideInput) 272 { 273 ASSERT(provideInput); 274 if (!provideInput) 267 275 return; 268 269 m_sourceProvider = sourceProvider;270 276 271 277 unsigned numberOfDestinationFrames = framesToProcess; … … 274 280 // Prime the input buffer at the start of the input stream. 275 281 if (!m_isBufferPrimed) { 276 consumeSource(m_r0, m_requestFrames );282 consumeSource(m_r0, m_requestFrames, provideInput); 277 283 m_isBufferPrimed = true; 278 284 } … … 522 528 // Step (5) 523 529 // Refresh the buffer with more input. 524 consumeSource(m_r0, m_requestFrames );530 consumeSource(m_r0, m_requestFrames, provideInput); 525 531 } 526 532 } -
trunk/Source/WebCore/platform/audio/SincResampler.h
r267143 r270141 46 46 SincResampler(double scaleFactor, Optional<unsigned> requestFrames = WTF::nullopt); 47 47 48 size_t chunkSize() const { return m_chunkSize; } 49 48 50 // Processes numberOfSourceFrames from source to produce numberOfSourceFrames / scaleFactor frames in destination. 49 51 void process(const float* source, float* destination, unsigned numberOfSourceFrames); 50 52 51 // Process with input sourcecallback function for streaming applications.52 void process( AudioSourceProvider*, float* destination, size_t framesToProcess);53 // Process with provideInput callback function for streaming applications. 54 void process(float* destination, size_t framesToProcess, const Function<void(AudioBus* bus, size_t framesToProcess)>& provideInput); 53 55 54 56 protected: 55 57 void initializeKernel(); 56 void consumeSource(float* buffer, unsigned numberOfSourceFrames );58 void consumeSource(float* buffer, unsigned numberOfSourceFrames, const Function<void(AudioBus* bus, size_t framesToProcess)>& provideInput); 57 59 void updateRegions(bool isSecondLoad); 58 60 … … 73 75 unsigned m_blockSize { 0 }; 74 76 77 size_t m_chunkSize { 0 }; 78 75 79 // Source is copied into this buffer for each processing pass. 76 80 AudioFloatArray m_inputBuffer; … … 86 90 const float* m_source { nullptr }; 87 91 unsigned m_sourceFramesAvailable { 0 }; 88 89 // m_sourceProvider is used to provide the audio input stream to the resampler.90 AudioSourceProvider* m_sourceProvider { nullptr };91 92 92 93 // The buffer is primed once at the very beginning of processing.
Note: See TracChangeset
for help on using the changeset viewer.