Share code between AudioDestinationIOS and AudioDestinationMac
https://bugs.webkit.org/show_bug.cgi?id=203047
<rdar://problem/56340866>

Reviewed by Eric Carlson.

Source/WebCore:

Introduce AudioDestinationCocoa to share code between iOS and Mac.
Most code is now shared, except for the configuration of the audio unit which is slightly different between Mac and iOS.

Introduce MockAudioDestinationCocoa to allow more code coverage of the shared code.
This could also allow us to validate dynamic changes in frame rate, number of frames to process...
Add Internals API to enable the mock destination.

Covered by added test.

* SourcesCocoa.txt:
* WebCore.xcodeproj/project.pbxproj:
* platform/audio/cocoa/AudioDestinationCocoa.cpp: Added.
(WebCore::AudioDestination::create):
(WebCore::AudioDestination::hardwareSampleRate):
(WebCore::AudioDestination::maxChannelCount):
(WebCore::AudioDestinationCocoa::AudioDestinationCocoa):
(WebCore::AudioDestinationCocoa::~AudioDestinationCocoa):
(WebCore::AudioDestinationCocoa::start):
(WebCore::AudioDestinationCocoa::stop):
(WebCore::AudioDestinationCocoa::setIsPlaying):
(WebCore::AudioDestinationCocoa::setAudioStreamBasicDescription):
(WebCore::assignAudioBuffersToBus):
(WebCore::AudioDestinationCocoa::render):
(WebCore::AudioDestinationCocoa::inputProc):
* platform/audio/cocoa/AudioDestinationCocoa.h: Added.
(WebCore::AudioDestinationCocoa::outputUnit):
* platform/audio/ios/AudioDestinationIOS.cpp:
(WebCore::AudioDestinationCocoa::configure):
(WebCore::AudioDestinationCocoa::processBusAfterRender):
* platform/audio/ios/AudioDestinationIOS.h: Removed.
* platform/audio/mac/AudioDestinationMac.cpp:
(WebCore::AudioDestinationCocoa::configure):
(WebCore::AudioDestinationCocoa::processBusAfterRender):
* platform/audio/mac/AudioDestinationMac.h: Removed.
* platform/mock/MockAudioDestinationCocoa.cpp: Added.
(WebCore::MockAudioDestinationCocoa::MockAudioDestinationCocoa):
(WebCore::MockAudioDestinationCocoa::start):
(WebCore::MockAudioDestinationCocoa::stop):
(WebCore::MockAudioDestinationCocoa::tick):
* platform/mock/MockAudioDestinationCocoa.h: Added.
* testing/Internals.cpp:
(WebCore::Internals::Internals):
(WebCore::Internals::useMockAudioDestinationCocoa):
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

* fast/mediastream/getUserMedia-webaudio-expected.txt:
* fast/mediastream/getUserMedia-webaudio.html:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251367 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index e56bbb2..3317cf8 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,14 @@
+2019-10-21  youenn fablet  <youenn@apple.com>
+
+        Share code between AudioDestinationIOS and AudioDestinationMac
+        https://bugs.webkit.org/show_bug.cgi?id=203047
+        <rdar://problem/56340866>
+
+        Reviewed by Eric Carlson.
+
+        * fast/mediastream/getUserMedia-webaudio-expected.txt:
+        * fast/mediastream/getUserMedia-webaudio.html:
+
 2019-10-21  Chris Dumez  <cdumez@apple.com>
 
         XMLHttpRequest should not prevent entering the back/forward cache
diff --git a/LayoutTests/fast/mediastream/getUserMedia-webaudio-expected.txt b/LayoutTests/fast/mediastream/getUserMedia-webaudio-expected.txt
index b2a8900..f74d888 100644
--- a/LayoutTests/fast/mediastream/getUserMedia-webaudio-expected.txt
+++ b/LayoutTests/fast/mediastream/getUserMedia-webaudio-expected.txt
@@ -1,4 +1,5 @@
 
 PASS Plugging in getUserMedia audio stream into Web Audio 
 PASS Web Audio should work even if number of channels of a track increases from 1 to 2 
+PASS Web Audio should work with mock audio destination 
 
diff --git a/LayoutTests/fast/mediastream/getUserMedia-webaudio.html b/LayoutTests/fast/mediastream/getUserMedia-webaudio.html
index ae5f00d..720435a 100644
--- a/LayoutTests/fast/mediastream/getUserMedia-webaudio.html
+++ b/LayoutTests/fast/mediastream/getUserMedia-webaudio.html
@@ -65,6 +65,26 @@
     source.disconnect(script);
     script.disconnect(audioContext.destination);
 }, "Web Audio should work even if number of channels of a track increases from 1 to 2");
+
+promise_test(async (test) => {
+    if (!window.internals)
+        return Promise.reject("Internals API required");
+    const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
+    internals.useMockAudioDestinationCocoa();
+
+    var audioContext = new webkitAudioContext();
+    var analyzer = audioContext.createAnalyser();
+    analyzer.fftSize = 256;
+    let source = audioContext.createMediaStreamSource(stream);
+    source.connect(analyzer);
+    analyzer.connect(audioContext.destination);
+
+    await new Promise(resolve => setTimeout(resolve, 500));
+
+    source.disconnect(analyzer);
+    analyzer.disconnect(audioContext.destination);
+}, "Web Audio should work with mock audio destination");
+
         </script>
     </body>
 </html>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index c5e6f65..975c2bb 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,57 @@
+2019-10-21  youenn fablet  <youenn@apple.com>
+
+        Share code between AudioDestinationIOS and AudioDestinationMac
+        https://bugs.webkit.org/show_bug.cgi?id=203047
+        <rdar://problem/56340866>
+
+        Reviewed by Eric Carlson.
+
+        Introduce AudioDestinationCocoa to share code between iOS and Mac.
+        Most code is now shared, except for the configuration of the audio unit which is slightly different between Mac and iOS.
+
+        Introduce MockAudioDestinationCocoa to allow more code coverage of the shared code.
+        This could also allow us to validate dynamic changes in frame rate, number of frames to process...
+        Add Internals API to enable the mock destination.
+ 
+        Covered by added test.
+
+        * SourcesCocoa.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * platform/audio/cocoa/AudioDestinationCocoa.cpp: Added.
+        (WebCore::AudioDestination::create):
+        (WebCore::AudioDestination::hardwareSampleRate):
+        (WebCore::AudioDestination::maxChannelCount):
+        (WebCore::AudioDestinationCocoa::AudioDestinationCocoa):
+        (WebCore::AudioDestinationCocoa::~AudioDestinationCocoa):
+        (WebCore::AudioDestinationCocoa::start):
+        (WebCore::AudioDestinationCocoa::stop):
+        (WebCore::AudioDestinationCocoa::setIsPlaying):
+        (WebCore::AudioDestinationCocoa::setAudioStreamBasicDescription):
+        (WebCore::assignAudioBuffersToBus):
+        (WebCore::AudioDestinationCocoa::render):
+        (WebCore::AudioDestinationCocoa::inputProc):
+        * platform/audio/cocoa/AudioDestinationCocoa.h: Added.
+        (WebCore::AudioDestinationCocoa::outputUnit):
+        * platform/audio/ios/AudioDestinationIOS.cpp:
+        (WebCore::AudioDestinationCocoa::configure):
+        (WebCore::AudioDestinationCocoa::processBusAfterRender):
+        * platform/audio/ios/AudioDestinationIOS.h: Removed.
+        * platform/audio/mac/AudioDestinationMac.cpp:
+        (WebCore::AudioDestinationCocoa::configure):
+        (WebCore::AudioDestinationCocoa::processBusAfterRender):
+        * platform/audio/mac/AudioDestinationMac.h: Removed.
+        * platform/mock/MockAudioDestinationCocoa.cpp: Added.
+        (WebCore::MockAudioDestinationCocoa::MockAudioDestinationCocoa):
+        (WebCore::MockAudioDestinationCocoa::start):
+        (WebCore::MockAudioDestinationCocoa::stop):
+        (WebCore::MockAudioDestinationCocoa::tick):
+        * platform/mock/MockAudioDestinationCocoa.h: Added.
+        * testing/Internals.cpp:
+        (WebCore::Internals::Internals):
+        (WebCore::Internals::useMockAudioDestinationCocoa):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2019-10-21  Chris Dumez  <cdumez@apple.com>
 
         XMLHttpRequest should not prevent entering the back/forward cache
diff --git a/Source/WebCore/SourcesCocoa.txt b/Source/WebCore/SourcesCocoa.txt
index 12ff11d..da5e748 100644
--- a/Source/WebCore/SourcesCocoa.txt
+++ b/Source/WebCore/SourcesCocoa.txt
@@ -163,6 +163,7 @@
 
 platform/audio/AudioSession.cpp
 
+platform/audio/cocoa/AudioDestinationCocoa.cpp
 platform/audio/cocoa/MediaSessionManagerCocoa.mm
 platform/audio/cocoa/WebAudioBufferList.cpp
 
@@ -493,6 +494,7 @@
 
 platform/mock/MediaPlaybackTargetPickerMock.cpp
 platform/mock/MediaPlaybackTargetMock.cpp
+platform/mock/MockAudioDestinationCocoa.cpp
 
 platform/network/cf/CertificateInfoCFNet.cpp
 platform/network/cf/DNSResolveQueueCFNet.cpp
diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
index 7cb903a..d59fa24 100644
--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj
+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
@@ -7241,6 +7241,8 @@
 		413015D61C7B570400091C6E /* FetchResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FetchResponse.h; sourceTree = "<group>"; };
 		413015D61C7B570400091C6F /* FetchBodySource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FetchBodySource.h; sourceTree = "<group>"; };
 		413015D71C7B570400091C6E /* FetchResponse.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = FetchResponse.idl; sourceTree = "<group>"; };
+		413151842357745E00115E6E /* AudioDestinationCocoa.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AudioDestinationCocoa.cpp; sourceTree = "<group>"; };
+		413151862357745E00115E6E /* AudioDestinationCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioDestinationCocoa.h; sourceTree = "<group>"; };
 		4131F3B11F9552810059995A /* JSFetchEventCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSFetchEventCustom.cpp; sourceTree = "<group>"; };
 		4131F3B41F955BC30059995A /* ExtendableEventInit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExtendableEventInit.h; sourceTree = "<group>"; };
 		4131F3B51F955BC50059995A /* ExtendableEventInit.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ExtendableEventInit.idl; sourceTree = "<group>"; };
@@ -7401,6 +7403,8 @@
 		41B2A6251EF1BF60002B9D7A /* WebAudioSourceProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebAudioSourceProvider.h; sourceTree = "<group>"; };
 		41B459DA1F4CADB90000F6FD /* ReadableStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReadableStream.h; sourceTree = "<group>"; };
 		41B459ED1F55EBC70000F6FD /* ReadableStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ReadableStream.cpp; sourceTree = "<group>"; };
+		41B9137623584D0E0025BFA3 /* MockAudioDestinationCocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MockAudioDestinationCocoa.h; sourceTree = "<group>"; };
+		41B9137823584D0F0025BFA3 /* MockAudioDestinationCocoa.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MockAudioDestinationCocoa.cpp; sourceTree = "<group>"; };
 		41BF204022B947160004F812 /* RealtimeVideoSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RealtimeVideoSource.h; sourceTree = "<group>"; };
 		41BF204222B947170004F812 /* RealtimeVideoSource.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RealtimeVideoSource.cpp; sourceTree = "<group>"; };
 		41C760B00EDE03D300C1655F /* ScriptState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScriptState.h; sourceTree = "<group>"; };
@@ -19931,6 +19935,8 @@
 				077B64151B95F703003E9AD5 /* MediaPlaybackTargetMock.h */,
 				077B64101B94F12E003E9AD5 /* MediaPlaybackTargetPickerMock.cpp */,
 				077B64111B94F12E003E9AD5 /* MediaPlaybackTargetPickerMock.h */,
+				41B9137823584D0F0025BFA3 /* MockAudioDestinationCocoa.cpp */,
+				41B9137623584D0E0025BFA3 /* MockAudioDestinationCocoa.h */,
 				413CCD4820DE013C0065A21A /* MockMediaDevice.h */,
 				07D6A4F11BED5F8800174146 /* MockRealtimeAudioSource.cpp */,
 				07D6A4F21BED5F8800174146 /* MockRealtimeAudioSource.h */,
@@ -25946,6 +25952,8 @@
 		CD669D651D232DF4004D1866 /* cocoa */ = {
 			isa = PBXGroup;
 			children = (
+				413151842357745E00115E6E /* AudioDestinationCocoa.cpp */,
+				413151862357745E00115E6E /* AudioDestinationCocoa.h */,
 				417F7AED2139BF6500FBA7EC /* MediaSessionManagerCocoa.h */,
 				417F7AEA2139BF6400FBA7EC /* MediaSessionManagerCocoa.mm */,
 				417F7AEB2139BF6400FBA7EC /* WebAudioBufferList.cpp */,
diff --git a/Source/WebCore/platform/audio/cocoa/AudioDestinationCocoa.cpp b/Source/WebCore/platform/audio/cocoa/AudioDestinationCocoa.cpp
new file mode 100644
index 0000000..1b3d885
--- /dev/null
+++ b/Source/WebCore/platform/audio/cocoa/AudioDestinationCocoa.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "AudioDestinationCocoa.h"
+
+#if ENABLE(WEB_AUDIO)
+
+#include "AudioBus.h"
+#include "AudioIOCallback.h"
+#include "AudioSession.h"
+#include "Logging.h"
+
+namespace WebCore {
+
+const int kRenderBufferSize = 128;
+
+CreateAudioDestinationCocoaOverride AudioDestinationCocoa::createOverride = nullptr;
+
+std::unique_ptr<AudioDestination> AudioDestination::create(AudioIOCallback& callback, const String&, unsigned numberOfInputChannels, unsigned numberOfOutputChannels, float sampleRate)
+{
+    // FIXME: make use of inputDeviceId as appropriate.
+
+    // FIXME: Add support for local/live audio input.
+    if (numberOfInputChannels)
+        WTFLogAlways("AudioDestination::create(%u, %u, %f) - unhandled input channels", numberOfInputChannels, numberOfOutputChannels, sampleRate);
+
+    // FIXME: Add support for multi-channel (> stereo) output.
+    if (numberOfOutputChannels != 2)
+        WTFLogAlways("AudioDestination::create(%u, %u, %f) - unhandled output channels", numberOfInputChannels, numberOfOutputChannels, sampleRate);
+
+    if (AudioDestinationCocoa::createOverride)
+        return AudioDestinationCocoa::createOverride(callback, sampleRate);
+
+    auto destination = makeUnique<AudioDestinationCocoa>(callback, sampleRate);
+    destination->configure();
+    return destination;
+}
+
+float AudioDestination::hardwareSampleRate()
+{
+    return AudioSession::sharedSession().sampleRate();
+}
+
+unsigned long AudioDestination::maxChannelCount()
+{
+    // FIXME: query the default audio hardware device to return the actual number
+    // of channels of the device. Also see corresponding FIXME in create().
+    // There is a small amount of code which assumes stereo which can be upgraded.
+    return 0;
+}
+
+AudioDestinationCocoa::AudioDestinationCocoa(AudioIOCallback& callback, float sampleRate)
+    : m_outputUnit(0)
+    , m_callback(callback)
+    , m_renderBus(AudioBus::create(2, kRenderBufferSize, false).releaseNonNull())
+    , m_spareBus(AudioBus::create(2, kRenderBufferSize, true).releaseNonNull())
+    , m_sampleRate(sampleRate)
+{
+    configure();
+}
+
+AudioDestinationCocoa::~AudioDestinationCocoa()
+{
+    if (m_outputUnit)
+        AudioComponentInstanceDispose(m_outputUnit);
+}
+
+void AudioDestinationCocoa::start()
+{
+    LOG(Media, "AudioDestinationCocoa::start");
+    OSStatus result = AudioOutputUnitStart(m_outputUnit);
+
+    if (!result)
+        setIsPlaying(true);
+}
+
+void AudioDestinationCocoa::stop()
+{
+    LOG(Media, "AudioDestinationCocoa::stop");
+    OSStatus result = AudioOutputUnitStop(m_outputUnit);
+
+    if (!result)
+        setIsPlaying(false);
+}
+
+void AudioDestinationCocoa::setIsPlaying(bool isPlaying)
+{
+    if (m_isPlaying == isPlaying)
+        return;
+
+    m_isPlaying = isPlaying;
+    m_callback.isPlayingDidChange();
+}
+
+void AudioDestinationCocoa::setAudioStreamBasicDescription(AudioStreamBasicDescription& streamFormat, float sampleRate)
+{
+    const int bytesPerFloat = sizeof(Float32);
+    const int bitsPerByte = 8;
+    streamFormat.mSampleRate = sampleRate;
+    streamFormat.mFormatID = kAudioFormatLinearPCM;
+    streamFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
+    streamFormat.mBytesPerPacket = bytesPerFloat;
+    streamFormat.mFramesPerPacket = 1;
+    streamFormat.mBytesPerFrame = bytesPerFloat;
+    streamFormat.mChannelsPerFrame = 2;
+    streamFormat.mBitsPerChannel = bitsPerByte * bytesPerFloat;
+}
+
+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;
+        auto* 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 AudioDestinationCocoa::render(UInt32 numberOfFrames, AudioBufferList* ioData)
+{
+    auto* buffers = ioData->mBuffers;
+    UInt32 numberOfBuffers = ioData->mNumberBuffers;
+    UInt32 framesRemaining = numberOfFrames;
+    UInt32 frameOffset = 0;
+    while (framesRemaining > 0) {
+        if (m_startSpareFrame < m_endSpareFrame) {
+            ASSERT(m_startSpareFrame < m_endSpareFrame);
+            UInt32 framesThisTime = std::min<UInt32>(m_endSpareFrame - m_startSpareFrame, numberOfFrames);
+            assignAudioBuffersToBus(buffers, m_renderBus.get(), numberOfBuffers, numberOfFrames, frameOffset, framesThisTime);
+            m_renderBus->copyFromRange(m_spareBus.get(), m_startSpareFrame, m_endSpareFrame);
+            processBusAfterRender(m_renderBus.get(), framesThisTime);
+            frameOffset += framesThisTime;
+            framesRemaining -= framesThisTime;
+            m_startSpareFrame += framesThisTime;
+        }
+
+        UInt32 framesThisTime = std::min<UInt32>(kRenderBufferSize, framesRemaining);
+        assignAudioBuffersToBus(buffers, m_renderBus.get(), numberOfBuffers, numberOfFrames, frameOffset, framesThisTime);
+
+        if (!framesThisTime)
+            break;
+        if (framesThisTime < kRenderBufferSize) {
+            m_callback.render(0, m_spareBus.ptr(), kRenderBufferSize);
+            m_renderBus->copyFromRange(m_spareBus.get(), 0, framesThisTime);
+            m_startSpareFrame = framesThisTime;
+            m_endSpareFrame = kRenderBufferSize;
+        } else
+            m_callback.render(0, m_renderBus.ptr(), framesThisTime);
+        processBusAfterRender(m_renderBus.get(), framesThisTime);
+        frameOffset += framesThisTime;
+        framesRemaining -= framesThisTime;
+    }
+
+    return noErr;
+}
+
+// DefaultOutputUnit callback
+OSStatus AudioDestinationCocoa::inputProc(void* userData, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32 /*busNumber*/, UInt32 numberOfFrames, AudioBufferList* ioData)
+{
+    auto* audioOutput = static_cast<AudioDestinationCocoa*>(userData);
+    return audioOutput->render(numberOfFrames, ioData);
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_AUDIO)
diff --git a/Source/WebCore/platform/audio/cocoa/AudioDestinationCocoa.h b/Source/WebCore/platform/audio/cocoa/AudioDestinationCocoa.h
new file mode 100644
index 0000000..fe6947e
--- /dev/null
+++ b/Source/WebCore/platform/audio/cocoa/AudioDestinationCocoa.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUDIO)
+
+#include "AudioDestination.h"
+#include <AudioUnit/AudioUnit.h>
+#include <wtf/RefPtr.h>
+
+namespace WebCore {
+
+class AudioBus;
+
+using CreateAudioDestinationCocoaOverride = std::unique_ptr<AudioDestination>(*)(AudioIOCallback&, float sampleRate);
+
+// An AudioDestination using CoreAudio's default output AudioUnit
+class AudioDestinationCocoa : public AudioDestination {
+public:
+    AudioDestinationCocoa(AudioIOCallback&, float sampleRate);
+    virtual ~AudioDestinationCocoa();
+
+    WEBCORE_EXPORT static CreateAudioDestinationCocoaOverride createOverride;
+
+protected:
+    void setIsPlaying(bool);
+
+    bool isPlaying() final { return m_isPlaying; }
+    float sampleRate() const final { return m_sampleRate; }
+    AudioUnit& outputUnit() { return m_outputUnit; }
+    
+    // DefaultOutputUnit callback
+    static OSStatus inputProc(void* userData, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32 busNumber, UInt32 numberOfFrames, AudioBufferList* ioData);
+
+    void setAudioStreamBasicDescription(AudioStreamBasicDescription&, float sampleRate);
+
+private:
+    void start() override;
+    void stop() override;
+
+    friend std::unique_ptr<AudioDestination> AudioDestination::create(AudioIOCallback&, const String&, unsigned, unsigned, float);
+    
+    void configure();
+    void processBusAfterRender(AudioBus&, UInt32 numberOfFrames);
+
+    OSStatus render(UInt32 numberOfFrames, AudioBufferList* ioData);
+
+    AudioUnit m_outputUnit;
+    AudioIOCallback& m_callback;
+    Ref<AudioBus> m_renderBus;
+    Ref<AudioBus> m_spareBus;
+
+    float m_sampleRate;
+    bool m_isPlaying { false };
+
+    unsigned m_startSpareFrame { 0 };
+    unsigned m_endSpareFrame { 0 };
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_AUDIO)
diff --git a/Source/WebCore/platform/audio/ios/AudioDestinationIOS.cpp b/Source/WebCore/platform/audio/ios/AudioDestinationIOS.cpp
index 4bde7e4..3929523 100644
--- a/Source/WebCore/platform/audio/ios/AudioDestinationIOS.cpp
+++ b/Source/WebCore/platform/audio/ios/AudioDestinationIOS.cpp
@@ -31,25 +31,14 @@
 
 #if ENABLE(WEB_AUDIO) && PLATFORM(IOS_FAMILY)
 
-#include "AudioDestinationIOS.h"
+#include "AudioDestinationCocoa.h"
 
-#include "AudioIOCallback.h"
 #include "AudioSession.h"
-#include "FloatConversion.h"
-#include "Logging.h"
-#include "RuntimeApplicationChecks.h"
-#include <AudioToolbox/AudioServices.h>
-#include <pal/spi/cocoa/AudioToolboxSPI.h>
-#include <wtf/HashSet.h>
-#include <wtf/NeverDestroyed.h>
 #include <wtf/SoftLinking.h>
 
 SOFT_LINK_FRAMEWORK(AudioToolbox)
 SOFT_LINK(AudioToolbox, AudioComponentFindNext, AudioComponent, (AudioComponent inComponent, const AudioComponentDescription *inDesc), (inComponent, inDesc))
-SOFT_LINK(AudioToolbox, AudioComponentInstanceDispose, OSStatus, (AudioComponentInstance inInstance), (inInstance))
 SOFT_LINK(AudioToolbox, AudioComponentInstanceNew, OSStatus, (AudioComponent inComponent, AudioComponentInstance *outInstance), (inComponent, outInstance))
-SOFT_LINK(AudioToolbox, AudioOutputUnitStart, OSStatus, (AudioUnit ci), (ci))
-SOFT_LINK(AudioToolbox, AudioOutputUnitStop, OSStatus, (AudioUnit ci), (ci))
 SOFT_LINK(AudioToolbox, AudioUnitAddPropertyListener, OSStatus, (AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void *inProcUserData), (inUnit, inID, inProc, inProcUserData))
 SOFT_LINK(AudioToolbox, AudioUnitGetProperty, OSStatus, (AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void *outData, UInt32 *ioDataSize), (inUnit, inID, inScope, inElement, outData, ioDataSize))
 SOFT_LINK(AudioToolbox, AudioUnitInitialize, OSStatus, (AudioUnit inUnit), (inUnit))
@@ -57,55 +46,9 @@
 
 namespace WebCore {
 
-const int kRenderBufferSize = 128;
-const int kPreferredBufferSize = 256;
-
-typedef HashSet<AudioDestinationIOS*> AudioDestinationSet;
-static AudioDestinationSet& audioDestinations()
+void AudioDestinationCocoa::configure()
 {
-    static NeverDestroyed<AudioDestinationSet> audioDestinationSet;
-    return audioDestinationSet;
-}
-
-// Factory method: iOS-implementation
-std::unique_ptr<AudioDestination> AudioDestination::create(AudioIOCallback& callback, const String&, unsigned numberOfInputChannels, unsigned numberOfOutputChannels, float sampleRate)
-{
-    // FIXME: make use of inputDeviceId as appropriate.
-
-    // FIXME: Add support for local/live audio input.
-    if (numberOfInputChannels)
-        LOG(Media, "AudioDestination::create(%u, %u, %f) - unhandled input channels", numberOfInputChannels, numberOfOutputChannels, sampleRate);
-
-    // FIXME: Add support for multi-channel (> stereo) output.
-    if (numberOfOutputChannels != 2)
-        LOG(Media, "AudioDestination::create(%u, %u, %f) - unhandled output channels", numberOfInputChannels, numberOfOutputChannels, sampleRate);
-
-    return makeUnique<AudioDestinationIOS>(callback, sampleRate);
-}
-
-float AudioDestination::hardwareSampleRate()
-{
-    return AudioSession::sharedSession().sampleRate();
-}
-
-unsigned long AudioDestination::maxChannelCount()
-{
-    // FIXME: query the default audio hardware device to return the actual number
-    // of channels of the device. Also see corresponding FIXME in create().
-    // There is a small amount of code which assumes stereo in AudioDestinationIOS which
-    // can be upgraded.
-    return 0;
-}
-
-AudioDestinationIOS::AudioDestinationIOS(AudioIOCallback& callback, double sampleRate)
-    : 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)
-{
-    audioDestinations().add(this);
+    const int kPreferredBufferSize = 256;
 
     // Open and initialize DefaultOutputUnit
     AudioComponent comp;
@@ -120,156 +63,44 @@
 
     ASSERT(comp);
 
-    OSStatus result = AudioComponentInstanceNew(comp, &m_outputUnit);
+    OSStatus result = AudioComponentInstanceNew(comp, &outputUnit());
     ASSERT(!result);
 
     UInt32 flag = 1;
-    result = AudioUnitSetProperty(m_outputUnit, 
-        kAudioOutputUnitProperty_EnableIO, 
-        kAudioUnitScope_Output, 
+    result = AudioUnitSetProperty(outputUnit(),
+        kAudioOutputUnitProperty_EnableIO,
+        kAudioUnitScope_Output,
         0,
-        &flag, 
+        &flag,
         sizeof(flag));
     ASSERT(!result);
 
-    result = AudioUnitAddPropertyListener(m_outputUnit, kAudioUnitProperty_MaximumFramesPerSlice, frameSizeChangedProc, this);
+    result = AudioUnitInitialize(outputUnit());
     ASSERT(!result);
-
-    result = AudioUnitInitialize(m_outputUnit);
-    ASSERT(!result);
-
-    configure();
-}
-
-AudioDestinationIOS::~AudioDestinationIOS()
-{
-    audioDestinations().remove(this);
-
-    if (m_outputUnit)
-        AudioComponentInstanceDispose(m_outputUnit);
-}
-
-void AudioDestinationIOS::configure()
-{
     // Set render callback
     AURenderCallbackStruct input;
     input.inputProc = inputProc;
     input.inputProcRefCon = this;
-    OSStatus result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof(input));
+    result = AudioUnitSetProperty(outputUnit(), kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof(input));
     ASSERT(!result);
 
     // Set stream format
     AudioStreamBasicDescription streamFormat;
 
     UInt32 size = sizeof(AudioStreamBasicDescription);
-    result = AudioUnitGetProperty(m_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, (void*)&streamFormat, &size);
+    result = AudioUnitGetProperty(outputUnit(), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, (void*)&streamFormat, &size);
     ASSERT(!result);
 
-    const int bytesPerFloat = sizeof(Float32);
-    const int bitsPerByte = 8;
-    streamFormat.mSampleRate = m_sampleRate;
-    streamFormat.mFormatID = kAudioFormatLinearPCM;
-    streamFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
-    streamFormat.mBytesPerPacket = bytesPerFloat;
-    streamFormat.mFramesPerPacket = 1;
-    streamFormat.mBytesPerFrame = bytesPerFloat;
-    streamFormat.mChannelsPerFrame = 2;
-    streamFormat.mBitsPerChannel = bitsPerByte * bytesPerFloat;
+    setAudioStreamBasicDescription(streamFormat, sampleRate());
 
-    result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, (void*)&streamFormat, sizeof(AudioStreamBasicDescription));
+    result = AudioUnitSetProperty(outputUnit(), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, (void*)&streamFormat, sizeof(AudioStreamBasicDescription));
     ASSERT(!result);
 
     AudioSession::sharedSession().setPreferredBufferSize(kPreferredBufferSize);
 }
 
-void AudioDestinationIOS::start()
+void AudioDestinationCocoa::processBusAfterRender(AudioBus&, UInt32)
 {
-    LOG(Media, "AudioDestinationIOS::start");
-
-    OSStatus result = AudioOutputUnitStart(m_outputUnit);
-    if (!result)
-        setIsPlaying(true);
-}
-
-void AudioDestinationIOS::stop()
-{
-    LOG(Media, "AudioDestinationIOS::stop");
-
-    OSStatus result = AudioOutputUnitStop(m_outputUnit);
-    if (!result)
-        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;
-    UInt32 numberOfBuffers = ioData->mNumberBuffers;
-    UInt32 framesRemaining = numberOfFrames;
-    UInt32 frameOffset = 0;
-    while (framesRemaining > 0) {
-        if (m_startSpareFrame < m_endSpareFrame) {
-            ASSERT(m_startSpareFrame < m_endSpareFrame);
-            UInt32 framesThisTime = std::min<UInt32>(m_endSpareFrame - m_startSpareFrame, numberOfFrames);
-            assignAudioBuffersToBus(buffers, *m_renderBus, numberOfBuffers, numberOfFrames, frameOffset, framesThisTime);
-            m_renderBus->copyFromRange(*m_spareBus, m_startSpareFrame, m_endSpareFrame);
-            frameOffset += framesThisTime;
-            framesRemaining -= framesThisTime;
-            m_startSpareFrame += framesThisTime;
-        }
-
-        UInt32 framesThisTime = std::min<UInt32>(kRenderBufferSize, framesRemaining);
-        assignAudioBuffersToBus(buffers, *m_renderBus, numberOfBuffers, numberOfFrames, frameOffset, framesThisTime);
-
-        if (!framesThisTime)
-            break;
-        if (framesThisTime < kRenderBufferSize) {
-            m_callback.render(0, m_spareBus.get(), kRenderBufferSize);
-            m_renderBus->copyFromRange(*m_spareBus, 0, framesThisTime);
-            m_startSpareFrame = framesThisTime;
-            m_endSpareFrame = kRenderBufferSize;
-        } else
-            m_callback.render(0, m_renderBus.get(), framesThisTime);
-        frameOffset += framesThisTime;
-        framesRemaining -= framesThisTime;
-    }
-
-    return noErr;
-}
-
-void AudioDestinationIOS::setIsPlaying(bool isPlaying)
-{
-    if (m_isPlaying == isPlaying)
-        return;
-
-    m_isPlaying = isPlaying;
-    m_callback.isPlayingDidChange();
-}
-
-// DefaultOutputUnit callback
-OSStatus AudioDestinationIOS::inputProc(void* userData, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32 /*busNumber*/, UInt32 numberOfFrames, AudioBufferList* ioData)
-{
-    AudioDestinationIOS* audioOutput = static_cast<AudioDestinationIOS*>(userData);
-    return audioOutput->render(numberOfFrames, ioData);
-}
-
-void AudioDestinationIOS::frameSizeChangedProc(void *inRefCon, AudioUnit, AudioUnitPropertyID, AudioUnitScope, AudioUnitElement)
-{
-    AudioDestinationIOS* audioOutput = static_cast<AudioDestinationIOS*>(inRefCon);
-    UInt32 bufferSize = 0;
-    UInt32 dataSize = sizeof(bufferSize);
-    AudioUnitGetProperty(audioOutput->m_outputUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, (void*)&bufferSize, &dataSize);
-    fprintf(stderr, ">>>> frameSizeChanged = %lu\n", static_cast<unsigned long>(bufferSize));
 }
 
 } // namespace WebCore
diff --git a/Source/WebCore/platform/audio/ios/AudioDestinationIOS.h b/Source/WebCore/platform/audio/ios/AudioDestinationIOS.h
deleted file mode 100644
index d69239f..0000000
--- a/Source/WebCore/platform/audio/ios/AudioDestinationIOS.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2010 Google Inc. All rights reserved.
- * Copyright (C) 2011, 2014 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
- *     its contributors may be used to endorse or promote products derived
- *     from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#pragma once
-
-#if PLATFORM(IOS_FAMILY)
-
-#include "AudioBus.h"
-#include "AudioDestination.h"
-#include <AudioUnit/AudioUnit.h>
-#include <wtf/RefPtr.h>
-
-namespace WebCore {
-
-// An AudioDestination using CoreAudio's default output AudioUnit
-
-class AudioDestinationIOS final : public AudioDestination {
-public:
-    AudioDestinationIOS(AudioIOCallback&, double sampleRate);
-    virtual ~AudioDestinationIOS();
-
-private:
-    void configure();
-
-    // AudioDestination
-    void start() override;
-    void stop() override;
-    bool isPlaying() override { return m_isPlaying; }
-    float sampleRate() const override { return m_sampleRate; }
-
-    // DefaultOutputUnit callback
-    static OSStatus inputProc(void* userData, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32 busNumber, UInt32 numberOfFrames, AudioBufferList* ioData);
-    static void frameSizeChangedProc(void *inRefCon, AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement);
-
-    friend float AudioDestination::hardwareSampleRate();
-
-    OSStatus render(UInt32 numberOfFrames, AudioBufferList* ioData);
-    void setIsPlaying(bool);
-
-    AudioUnit m_outputUnit;
-    AudioIOCallback& m_callback;
-    RefPtr<AudioBus> m_renderBus;
-    RefPtr<AudioBus> m_spareBus;
-    unsigned m_startSpareFrame { 0 };
-    unsigned m_endSpareFrame { 0 };
-
-    double m_sampleRate;
-    bool m_isPlaying;
-};
-
-} // namespace WebCore
-
-#endif // PLATFORM(IOS_FAMILY)
diff --git a/Source/WebCore/platform/audio/mac/AudioDestinationMac.cpp b/Source/WebCore/platform/audio/mac/AudioDestinationMac.cpp
index 9fe64ba..7a01eae 100644
--- a/Source/WebCore/platform/audio/mac/AudioDestinationMac.cpp
+++ b/Source/WebCore/platform/audio/mac/AudioDestinationMac.cpp
@@ -27,64 +27,17 @@
  */
 
 #include "config.h"
+#include "AudioDestinationCocoa.h"
 
-#if ENABLE(WEB_AUDIO)
+#if ENABLE(WEB_AUDIO) && PLATFORM(MAC)
 
-#if PLATFORM(MAC)
-
-#include "AudioDestinationMac.h"
-
-#include "AudioIOCallback.h"
-#include "AudioSession.h"
-#include "FloatConversion.h"
-#include "Logging.h"
-#include "PlatformMediaSessionManager.h"
+#include "AudioBus.h"
 #include "VectorMath.h"
 #include <CoreAudio/AudioHardware.h>
 
 namespace WebCore {
 
-const int kBufferSize = 128;
-const float kLowThreshold = -1;
-const float kHighThreshold = 1;
-
-// Factory method: Mac-implementation
-std::unique_ptr<AudioDestination> AudioDestination::create(AudioIOCallback& callback, const String&, unsigned numberOfInputChannels, unsigned numberOfOutputChannels, float sampleRate)
-{
-    // FIXME: make use of inputDeviceId as appropriate.
-
-    // FIXME: Add support for local/live audio input.
-    if (numberOfInputChannels)
-        LOG(Media, "AudioDestination::create(%u, %u, %f) - unhandled input channels", numberOfInputChannels, numberOfOutputChannels, sampleRate);
-
-    // FIXME: Add support for multi-channel (> stereo) output.
-    if (numberOfOutputChannels != 2)
-        LOG(Media, "AudioDestination::create(%u, %u, %f) - unhandled output channels", numberOfInputChannels, numberOfOutputChannels, sampleRate);
-
-    return makeUnique<AudioDestinationMac>(callback, sampleRate);
-}
-
-float AudioDestination::hardwareSampleRate()
-{
-    // Determine the default output device's sample-rate.
-    return AudioSession::sharedSession().sampleRate();
-}
-
-unsigned long AudioDestination::maxChannelCount()
-{
-    // FIXME: query the default audio hardware device to return the actual number
-    // of channels of the device. Also see corresponding FIXME in create().
-    // There is a small amount of code which assumes stereo in AudioDestinationMac which
-    // can be upgraded.
-    return 0;
-}
-
-AudioDestinationMac::AudioDestinationMac(AudioIOCallback& callback, float sampleRate)
-    : m_outputUnit(0)
-    , m_callback(callback)
-    , m_renderBus(AudioBus::create(2, kBufferSize, false))
-    , m_sampleRate(sampleRate)
-    , m_isPlaying(false)
+void AudioDestinationCocoa::configure()
 {
     // Open and initialize DefaultOutputUnit
     AudioComponent comp;
@@ -99,99 +52,39 @@
 
     ASSERT(comp);
 
-    OSStatus result = AudioComponentInstanceNew(comp, &m_outputUnit);
+    OSStatus result = AudioComponentInstanceNew(comp, &outputUnit());
     ASSERT(!result);
 
-    result = AudioUnitInitialize(m_outputUnit);
+    result = AudioUnitInitialize(outputUnit());
     ASSERT(!result);
 
-    configure();
-}
-
-AudioDestinationMac::~AudioDestinationMac()
-{
-    if (m_outputUnit)
-        AudioComponentInstanceDispose(m_outputUnit);
-}
-
-void AudioDestinationMac::configure()
-{
     // Set render callback
     AURenderCallbackStruct input;
     input.inputProc = inputProc;
     input.inputProcRefCon = this;
-    OSStatus result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &input, sizeof(input));
+    result = AudioUnitSetProperty(outputUnit(), kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &input, sizeof(input));
     ASSERT(!result);
 
     // Set stream format
     AudioStreamBasicDescription streamFormat;
-    streamFormat.mSampleRate = m_sampleRate;
-    streamFormat.mFormatID = kAudioFormatLinearPCM;
-    streamFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
-    streamFormat.mBitsPerChannel = 8 * sizeof(Float32);
-    streamFormat.mChannelsPerFrame = 2;
-    streamFormat.mFramesPerPacket = 1;
-    streamFormat.mBytesPerPacket = sizeof(Float32);
-    streamFormat.mBytesPerFrame = sizeof(Float32);
+    setAudioStreamBasicDescription(streamFormat, sampleRate());
 
-    result = AudioUnitSetProperty(m_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, (void*)&streamFormat, sizeof(AudioStreamBasicDescription));
+    result = AudioUnitSetProperty(outputUnit(), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, (void*)&streamFormat, sizeof(AudioStreamBasicDescription));
     ASSERT(!result);
 }
 
-void AudioDestinationMac::start()
+void AudioDestinationCocoa::processBusAfterRender(AudioBus& bus, UInt32 numberOfFrames)
 {
-    OSStatus result = AudioOutputUnitStart(m_outputUnit);
-
-    if (!result)
-        setIsPlaying(true);
-}
-
-void AudioDestinationMac::stop()
-{
-    OSStatus result = AudioOutputUnitStop(m_outputUnit);
-
-    if (!result)
-        setIsPlaying(false);
-}
-
-// Pulls on our provider to get rendered audio stream.
-OSStatus AudioDestinationMac::render(UInt32 numberOfFrames, AudioBufferList* ioData)
-{
-    AudioBuffer* buffers = ioData->mBuffers;
-    m_renderBus->setChannelMemory(0, (float*)buffers[0].mData, numberOfFrames);
-    m_renderBus->setChannelMemory(1, (float*)buffers[1].mData, numberOfFrames);
-
-    // FIXME: Add support for local/live audio input.
-    m_callback.render(0, m_renderBus.get(), numberOfFrames);
-
+    const float kLowThreshold = -1;
+    const float kHighThreshold = 1;
     // Clamp values at 0db (i.e., [-1.0, 1.0])
-    for (unsigned i = 0; i < m_renderBus->numberOfChannels(); ++i) {
-        AudioChannel* channel = m_renderBus->channel(i);
+    for (unsigned i = 0; i < bus.numberOfChannels(); ++i) {
+        auto* channel = bus.channel(i);
         if (!channel->isSilent())
             VectorMath::vclip(channel->data(), 1, &kLowThreshold, &kHighThreshold, channel->mutableData(), 1, numberOfFrames);
     }
-
-    return noErr;
-}
-
-void AudioDestinationMac::setIsPlaying(bool isPlaying)
-{
-    if (m_isPlaying == isPlaying)
-        return;
-
-    m_isPlaying = isPlaying;
-    m_callback.isPlayingDidChange();
-}
-
-// DefaultOutputUnit callback
-OSStatus AudioDestinationMac::inputProc(void* userData, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32 /*busNumber*/, UInt32 numberOfFrames, AudioBufferList* ioData)
-{
-    AudioDestinationMac* audioOutput = static_cast<AudioDestinationMac*>(userData);
-    return audioOutput->render(numberOfFrames, ioData);
 }
 
 } // namespace WebCore
 
-#endif // PLATFORM(MAC)
-
-#endif // ENABLE(WEB_AUDIO)
+#endif // ENABLE(WEB_AUDIO) && PLATFORM(MAC)
diff --git a/Source/WebCore/platform/audio/mac/AudioDestinationMac.h b/Source/WebCore/platform/audio/mac/AudioDestinationMac.h
deleted file mode 100644
index 18b10ab..0000000
--- a/Source/WebCore/platform/audio/mac/AudioDestinationMac.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2010 Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
- *     its contributors may be used to endorse or promote products derived
- *     from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef AudioDestinationMac_h
-#define AudioDestinationMac_h
-
-#include "AudioBus.h"
-#include "AudioDestination.h"
-#include <AudioUnit/AudioUnit.h>
-#include <wtf/RefPtr.h>
-
-namespace WebCore {
-
-// An AudioDestination using CoreAudio's default output AudioUnit
-
-class AudioDestinationMac : public AudioDestination {
-public:
-    AudioDestinationMac(AudioIOCallback&, float sampleRate);
-    virtual ~AudioDestinationMac();
-
-private:
-    void configure();
-
-    // DefaultOutputUnit callback
-    static OSStatus inputProc(void* userData, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32 busNumber, UInt32 numberOfFrames, AudioBufferList* ioData);
-
-    OSStatus render(UInt32 numberOfFrames, AudioBufferList* ioData);
-    void setIsPlaying(bool);
-
-    void start() override;
-    void stop() override;
-    bool isPlaying() override { return m_isPlaying; }
-    float sampleRate() const override { return m_sampleRate; }
-
-    AudioUnit m_outputUnit;
-    AudioIOCallback& m_callback;
-    RefPtr<AudioBus> m_renderBus;
-
-    float m_sampleRate;
-    bool m_isPlaying;
-};
-
-} // namespace WebCore
-
-#endif // AudioDestinationMac_h
diff --git a/Source/WebCore/platform/mock/MockAudioDestinationCocoa.cpp b/Source/WebCore/platform/mock/MockAudioDestinationCocoa.cpp
new file mode 100644
index 0000000..f42009c
--- /dev/null
+++ b/Source/WebCore/platform/mock/MockAudioDestinationCocoa.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MockAudioDestinationCocoa.h"
+
+#if ENABLE(WEB_AUDIO)
+
+#include "CAAudioStreamDescription.h"
+#include "WebAudioBufferList.h"
+#include <wtf/threads/BinarySemaphore.h>
+
+namespace WebCore {
+
+const int kRenderBufferSize = 128;
+
+MockAudioDestinationCocoa::MockAudioDestinationCocoa(AudioIOCallback& callback, float sampleRate)
+    : AudioDestinationCocoa(callback, sampleRate)
+    , m_workQueue(WorkQueue::create("MockAudioDestinationCocoa Render Queue"))
+    , m_timer(RunLoop::current(), this, &MockAudioDestinationCocoa::tick)
+{
+}
+
+void MockAudioDestinationCocoa::start()
+{
+    m_timer.startRepeating(Seconds { m_numberOfFramesToProcess / sampleRate() });
+    setIsPlaying(true);
+}
+
+void MockAudioDestinationCocoa::stop()
+{
+    m_timer.stop();
+    setIsPlaying(false);
+
+    BinarySemaphore semaphore;
+    m_workQueue->dispatch([&semaphore] {
+        semaphore.signal();
+    });
+    semaphore.wait();
+}
+
+void MockAudioDestinationCocoa::tick()
+{
+    m_workQueue->dispatch([this, sampleRate = sampleRate(), numberOfFramesToProcess = m_numberOfFramesToProcess] {
+        AudioStreamBasicDescription streamFormat;
+        setAudioStreamBasicDescription(streamFormat, sampleRate);
+
+        WebAudioBufferList webAudioBufferList { streamFormat, numberOfFramesToProcess };
+        AudioDestinationCocoa::inputProc(this, 0, 0, 0, numberOfFramesToProcess, webAudioBufferList.list());
+    });
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_AUDIO)
diff --git a/Source/WebCore/platform/mock/MockAudioDestinationCocoa.h b/Source/WebCore/platform/mock/MockAudioDestinationCocoa.h
new file mode 100644
index 0000000..5cd4d90
--- /dev/null
+++ b/Source/WebCore/platform/mock/MockAudioDestinationCocoa.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUDIO) && PLATFORM(COCOA)
+
+#include "AudioDestinationCocoa.h"
+#include <wtf/RunLoop.h>
+#include <wtf/WorkQueue.h>
+
+namespace WebCore {
+
+class AudioIOCallback;
+
+class MockAudioDestinationCocoa final : public AudioDestinationCocoa {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    static std::unique_ptr<AudioDestination> create(AudioIOCallback& callback, float sampleRate) { return makeUnique<MockAudioDestinationCocoa>(callback, sampleRate); }
+    WEBCORE_EXPORT MockAudioDestinationCocoa(AudioIOCallback&, float sampleRate);
+
+private:
+    void start() final;
+    void stop() final;
+
+    void tick();
+
+    Ref<WorkQueue> m_workQueue;
+    RunLoop::Timer<MockAudioDestinationCocoa> m_timer;
+    uint32_t m_numberOfFramesToProcess { 384 };
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_AUDIO) && PLATFORM(COCOA)
diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp
index 82ea00d..9bc3641f 100644
--- a/Source/WebCore/testing/Internals.cpp
+++ b/Source/WebCore/testing/Internals.cpp
@@ -122,6 +122,7 @@
 #include "MediaStreamTrack.h"
 #include "MemoryCache.h"
 #include "MemoryInfo.h"
+#include "MockAudioDestinationCocoa.h"
 #include "MockLibWebRTCPeerConnection.h"
 #include "MockPageOverlay.h"
 #include "MockPageOverlayClient.h"
@@ -580,6 +581,10 @@
 #endif
 
     m_unsuspendableActiveDOMObject = nullptr;
+
+#if PLATFORM(COCOA) &&  ENABLE(WEB_AUDIO)
+    AudioDestinationCocoa::createOverride = nullptr;
+#endif
 }
 
 Document* Internals::contextDocument() const
@@ -4076,7 +4081,6 @@
 #endif // ENABLE(MEDIA_SESSION)
 
 #if ENABLE(WEB_AUDIO)
-
 void Internals::setAudioContextRestrictions(AudioContext& context, StringView restrictionsString)
 {
     AudioContext::BehaviorRestrictions restrictions = context.behaviorRestrictions();
@@ -4095,6 +4099,12 @@
     context.addBehaviorRestriction(restrictions);
 }
 
+void Internals::useMockAudioDestinationCocoa()
+{
+#if PLATFORM(COCOA)
+    AudioDestinationCocoa::createOverride = MockAudioDestinationCocoa::create;
+#endif
+}
 #endif
 
 void Internals::simulateSystemSleep() const
diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h
index f9b33a25..116fe89 100644
--- a/Source/WebCore/testing/Internals.h
+++ b/Source/WebCore/testing/Internals.h
@@ -634,6 +634,7 @@
 
 #if ENABLE(WEB_AUDIO)
     void setAudioContextRestrictions(AudioContext&, StringView restrictionsString);
+    void useMockAudioDestinationCocoa();
 #endif
 
     void simulateSystemSleep() const;
diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl
index 5ff471a..d5ec9fe 100644
--- a/Source/WebCore/testing/Internals.idl
+++ b/Source/WebCore/testing/Internals.idl
@@ -613,6 +613,8 @@
     [Conditional=MEDIA_STREAM] void setMockMediaCaptureDevicesEnabled(boolean enabled);
     [Conditional=MEDIA_STREAM] void setCustomPrivateRecorderCreator();
 
+    [Conditional=WEB_AUDIO] void useMockAudioDestinationCocoa();
+
     [Conditional=WEB_RTC] void emulateRTCPeerConnectionPlatformEvent(RTCPeerConnection connection, DOMString action);
     [Conditional=WEB_RTC] void useMockRTCPeerConnectionFactory(DOMString testCase);
     [Conditional=WEB_RTC] void setICECandidateFiltering(boolean enabled);