Web Audio becomes distorted after sample rate changes
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 11 Mar 2016 19:35:15 +0000 (19:35 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 11 Mar 2016 19:35:15 +0000 (19:35 +0000)
https://bugs.webkit.org/show_bug.cgi?id=154538
<rdar://problem/24771292>

Reviewed by Darin Adler.

When the underlying audio hardware sample rate changes, the AudioUnit render callback will begin asking
for fewer or more frames. For example, when the sample rate goes from 44.1kHz to 48kHz, it will ask for
118 samples instead of 128. (And vice-versa, 140 samples instead of 128.) But the Web Audio engine can only
really handle requests in multiples of 128 samples. In the case where there are requests for < 128 samples,
actually render 128, but save off the unrequested samples in a separate bus. Then fill that bus during the
next request.

* platform/audio/AudioBus.cpp:
(WebCore::AudioBus::copyFromRange): Added utility method.
* platform/audio/AudioBus.h:
* platform/audio/ios/AudioDestinationIOS.cpp:
(WebCore::AudioDestinationIOS::AudioDestinationIOS): Create a "spare" bus.
(WebCore::assignAudioBuffersToBus): Moved from inside render.
(WebCore::AudioDestinationIOS::render): Save off extra samples to the "spare" bus.
* platform/audio/ios/AudioDestinationIOS.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@198035 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/platform/audio/AudioBus.cpp
Source/WebCore/platform/audio/AudioBus.h
Source/WebCore/platform/audio/ios/AudioDestinationIOS.cpp
Source/WebCore/platform/audio/ios/AudioDestinationIOS.h

index 4456eef..97db0df 100644 (file)
@@ -1,3 +1,27 @@
+2016-03-10  Jer Noble  <jer.noble@apple.com>
+
+        Web Audio becomes distorted after sample rate changes
+        https://bugs.webkit.org/show_bug.cgi?id=154538
+        <rdar://problem/24771292>
+
+        Reviewed by Darin Adler.
+
+        When the underlying audio hardware sample rate changes, the AudioUnit render callback will begin asking
+        for fewer or more frames. For example, when the sample rate goes from 44.1kHz to 48kHz, it will ask for
+        118 samples instead of 128. (And vice-versa, 140 samples instead of 128.) But the Web Audio engine can only
+        really handle requests in multiples of 128 samples. In the case where there are requests for < 128 samples,
+        actually render 128, but save off the unrequested samples in a separate bus. Then fill that bus during the
+        next request.
+
+        * platform/audio/AudioBus.cpp:
+        (WebCore::AudioBus::copyFromRange): Added utility method.
+        * platform/audio/AudioBus.h:
+        * platform/audio/ios/AudioDestinationIOS.cpp:
+        (WebCore::AudioDestinationIOS::AudioDestinationIOS): Create a "spare" bus.
+        (WebCore::assignAudioBuffersToBus): Moved from inside render.
+        (WebCore::AudioDestinationIOS::render): Save off extra samples to the "spare" bus.
+        * platform/audio/ios/AudioDestinationIOS.h:
+
 2016-03-11  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         Unreviewed build fix after r198023.
index faa9617..ec02ac4 100644 (file)
@@ -213,6 +213,33 @@ void AudioBus::scale(float scale)
         channel(i)->scale(scale);
 }
 
+void AudioBus::copyFromRange(const AudioBus& sourceBus, unsigned startFrame, unsigned endFrame)
+{
+    if (!topologyMatches(sourceBus)) {
+        ASSERT_NOT_REACHED();
+        zero();
+        return;
+    }
+
+    size_t numberOfSourceFrames = sourceBus.length();
+    bool isRangeSafe = startFrame < endFrame && endFrame <= numberOfSourceFrames;
+    ASSERT(isRangeSafe);
+    if (!isRangeSafe) {
+        zero();
+        return;
+    }
+
+    unsigned numberOfChannels = this->numberOfChannels();
+    ASSERT(numberOfChannels <= MaxBusChannels);
+    if (numberOfChannels > MaxBusChannels) {
+        zero();
+        return;
+    }
+
+    for (unsigned i = 0; i < numberOfChannels; ++i)
+        channel(i)->copyFromRange(sourceBus.channel(i), startFrame, endFrame);
+}
+
 void AudioBus::copyFrom(const AudioBus& sourceBus, ChannelInterpretation channelInterpretation)
 {
     if (&sourceBus == this)
index 8d6839f..4f9b5f5 100644 (file)
@@ -122,6 +122,9 @@ public:
     void reset() { m_isFirstTime = true; } // for de-zippering
 
     // Copies the samples from the source bus to this one.
+    void copyFromRange(const AudioBus& sourceBus, unsigned startFrame, unsigned endFrame);
+
+    // Copies the samples from the source bus to this one.
     // This is just a simple per-channel copy if the number of channels match, otherwise an up-mix or down-mix is done.
     void copyFrom(const AudioBus& sourceBus, ChannelInterpretation = Speakers);
 
index 27a45fc..f197586 100644 (file)
@@ -101,6 +101,7 @@ AudioDestinationIOS::AudioDestinationIOS(AudioIOCallback& callback, double sampl
     : m_outputUnit(0)
     , m_callback(callback)
     , m_renderBus(AudioBus::create(2, kRenderBufferSize, false))
+    , m_spareBus(AudioBus::create(2, kRenderBufferSize, true))
     , m_sampleRate(sampleRate)
     , m_isPlaying(false)
 {
@@ -199,19 +200,47 @@ void AudioDestinationIOS::stop()
         setIsPlaying(false);
 }
 
+static void assignAudioBuffersToBus(AudioBuffer* buffers, AudioBus& bus, UInt32 numberOfBuffers, UInt32 numberOfFrames, UInt32 frameOffset, UInt32 framesThisTime)
+{
+    for (UInt32 i = 0; i < numberOfBuffers; ++i) {
+        UInt32 bytesPerFrame = buffers[i].mDataByteSize / numberOfFrames;
+        UInt32 byteOffset = frameOffset * bytesPerFrame;
+        float* memory = reinterpret_cast<float*>(reinterpret_cast<char*>(buffers[i].mData) + byteOffset);
+        bus.setChannelMemory(i, memory, framesThisTime);
+    }
+}
+
 // Pulls on our provider to get rendered audio stream.
 OSStatus AudioDestinationIOS::render(UInt32 numberOfFrames, AudioBufferList* ioData)
 {
     AudioBuffer* buffers = ioData->mBuffers;
-    for (UInt32 frameOffset = 0; frameOffset + kRenderBufferSize <= numberOfFrames; frameOffset += kRenderBufferSize) {
-        UInt32 remainingFrames = std::min<UInt32>(kRenderBufferSize, numberOfFrames - frameOffset);
-        for (UInt32 i = 0; i < ioData->mNumberBuffers; ++i) {
-            UInt32 bytesPerFrame = buffers[i].mDataByteSize / numberOfFrames;
-            UInt32 byteOffset = frameOffset * bytesPerFrame;
-            float* memory = (float*)((char*)buffers[i].mData + byteOffset);
-            m_renderBus->setChannelMemory(i, memory, remainingFrames);
+    UInt32 numberOfBuffers = ioData->mNumberBuffers;
+    UInt32 framesRemaining = numberOfFrames;
+    UInt32 frameOffset = 0;
+    while (framesRemaining > 0) {
+        if (m_firstSpareFrame && m_lastSpareFrame) {
+            ASSERT(m_firstSpareFrame < m_lastSpareFrame);
+            UInt32 framesThisTime = m_lastSpareFrame - m_firstSpareFrame;
+            assignAudioBuffersToBus(buffers, *m_renderBus, numberOfBuffers, numberOfFrames, frameOffset, framesThisTime);
+            m_renderBus->copyFromRange(*m_spareBus, m_firstSpareFrame, m_lastSpareFrame);
+            frameOffset += framesThisTime;
+            framesRemaining -= framesThisTime;
+            m_firstSpareFrame = 0;
+            m_lastSpareFrame = 0;
         }
-        m_callback.render(0, m_renderBus.get(), remainingFrames);
+
+        UInt32 framesThisTime = std::min<UInt32>(kRenderBufferSize, framesRemaining);
+        assignAudioBuffersToBus(buffers, *m_renderBus, numberOfBuffers, numberOfFrames, frameOffset, framesThisTime);
+
+        if (framesThisTime < kRenderBufferSize) {
+            m_callback.render(0, m_spareBus.get(), kRenderBufferSize);
+            m_renderBus->copyFromRange(*m_spareBus, 0, framesThisTime);
+            m_firstSpareFrame = framesThisTime;
+            m_lastSpareFrame = kRenderBufferSize - 1;
+        } else
+            m_callback.render(0, m_renderBus.get(), framesThisTime);
+        frameOffset += framesThisTime;
+        framesRemaining -= framesThisTime;
     }
 
     return noErr;
index b547b6d..b572383 100644 (file)
@@ -65,6 +65,9 @@ private:
     AudioUnit m_outputUnit;
     AudioIOCallback& m_callback;
     RefPtr<AudioBus> m_renderBus;
+    RefPtr<AudioBus> m_spareBus;
+    unsigned m_firstSpareFrame { 0 };
+    unsigned m_lastSpareFrame { 0 };
 
     double m_sampleRate;
     bool m_isPlaying;