WebAudioSourceProviderAVFObjC::provideInput should set its WebAudioBufferList parameters correctly
https://bugs.webkit.org/show_bug.cgi?id=202930
<rdar://problem/56006776>

Reviewed by Eric Carlson.

Source/WebCore:

There is a time where the bus channel number and audio source channel numbers may be different.
In case the bus channel number is less than the audio source channel number, initialization of
the WebAudioBufferList might not be fully done.
In that case, output silence and return early.
Reduce the number of frames to process based on the number of frames the output audio bus plans to process.

Partially covered by new API test (this a race so we cannot reproduce the crash easily).

* Modules/webaudio/MediaStreamAudioSourceNode.cpp:
(WebCore::MediaStreamAudioSourceNode::process):
Make sure to process the number of frames the output bus expect.
* platform/mediastream/mac/WebAudioSourceProviderAVFObjC.mm:
(WebCore::WebAudioSourceProviderAVFObjC::provideInput):

Tools:

Add a test that has an audio track that goes from 1 to 2 channels while being piped to a WebAudio pipeline.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKit/GetUserMedia.mm:
(-[GUMMessageHandler userContentController:didReceiveScriptMessage:]):
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebKit/getUserMedia-webaudio.html: Added.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251188 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index eac9820..a948555 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,25 @@
+2019-10-16  Youenn Fablet  <youenn@apple.com>
+
+        WebAudioSourceProviderAVFObjC::provideInput should set its WebAudioBufferList parameters correctly
+        https://bugs.webkit.org/show_bug.cgi?id=202930
+        <rdar://problem/56006776>
+
+        Reviewed by Eric Carlson.
+
+        There is a time where the bus channel number and audio source channel numbers may be different.
+        In case the bus channel number is less than the audio source channel number, initialization of
+        the WebAudioBufferList might not be fully done.
+        In that case, output silence and return early.
+        Reduce the number of frames to process based on the number of frames the output audio bus plans to process.
+
+        Partially covered by new API test (this a race so we cannot reproduce the crash easily).
+
+        * Modules/webaudio/MediaStreamAudioSourceNode.cpp:
+        (WebCore::MediaStreamAudioSourceNode::process):
+        Make sure to process the number of frames the output bus expect.
+        * platform/mediastream/mac/WebAudioSourceProviderAVFObjC.mm:
+        (WebCore::WebAudioSourceProviderAVFObjC::provideInput):
+
 2019-10-16  Zalan Bujtas  <zalan@apple.com>
 
         [LFC][TFC] TableFormattingContext::distributeExtraHorizontalSpace should not ignore fixed width columns
diff --git a/Source/WebCore/Modules/webaudio/MediaStreamAudioSourceNode.cpp b/Source/WebCore/Modules/webaudio/MediaStreamAudioSourceNode.cpp
index e43f544..7ba59bf 100644
--- a/Source/WebCore/Modules/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/Source/WebCore/Modules/webaudio/MediaStreamAudioSourceNode.cpp
@@ -131,6 +131,9 @@
         return;
     }
 
+    if (numberOfFrames > outputBus->length())
+        numberOfFrames = outputBus->length();
+
     if (m_multiChannelResampler.get()) {
         ASSERT(m_sourceSampleRate != sampleRate());
         m_multiChannelResampler->process(provider, outputBus, numberOfFrames);
diff --git a/Source/WebCore/platform/mediastream/mac/WebAudioSourceProviderAVFObjC.mm b/Source/WebCore/platform/mediastream/mac/WebAudioSourceProviderAVFObjC.mm
index 7bc8b75..ad862eb 100644
--- a/Source/WebCore/platform/mediastream/mac/WebAudioSourceProviderAVFObjC.mm
+++ b/Source/WebCore/platform/mediastream/mac/WebAudioSourceProviderAVFObjC.mm
@@ -79,8 +79,13 @@
     }
 
     WebAudioBufferList list { m_outputDescription.value() };
+    if (bus->numberOfChannels() < list.bufferCount()) {
+        bus->zero();
+        return;
+    }
+
     for (unsigned i = 0; i < bus->numberOfChannels(); ++i) {
-        AudioChannel& channel = *bus->channel(i);
+        auto& channel = *bus->channel(i);
         if (i >= list.bufferCount()) {
             channel.zero();
             continue;
@@ -91,6 +96,7 @@
         buffer->mDataByteSize = channel.length() * sizeof(float);
     }
 
+    ASSERT(framesToProcess <= bus->length());
     m_dataSource->pullSamples(*list.list(), framesToProcess, m_readCount, 0, AudioSampleDataSource::Copy);
     m_readCount += framesToProcess;
 }
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 072f817..afde174 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,19 @@
+2019-10-16  Youenn Fablet  <youenn@apple.com>
+
+        WebAudioSourceProviderAVFObjC::provideInput should set its WebAudioBufferList parameters correctly
+        https://bugs.webkit.org/show_bug.cgi?id=202930
+        <rdar://problem/56006776>
+
+        Reviewed by Eric Carlson.
+
+        Add a test that has an audio track that goes from 1 to 2 channels while being piped to a WebAudio pipeline.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKit/GetUserMedia.mm:
+        (-[GUMMessageHandler userContentController:didReceiveScriptMessage:]):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/WebKit/getUserMedia-webaudio.html: Added.
+
 2019-10-15  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [GTK][WPE] Add user messages API
diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
index 18b5bf2..945eddb 100644
--- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
+++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
@@ -218,6 +218,7 @@
 		4135FB842011FAA700332139 /* InjectInternals_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4135FB832011FAA300332139 /* InjectInternals_Bundle.cpp */; };
 		4135FB852011FABF00332139 /* libWebCoreTestSupport.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4135FB862011FABF00332139 /* libWebCoreTestSupport.dylib */; };
 		414AD6862285D1C000777F2D /* StorageQuota.mm in Sources */ = {isa = PBXBuildFile; fileRef = 414AD6852285D1B000777F2D /* StorageQuota.mm */; };
+		41661C662355E85E00D33C27 /* getUserMedia-webaudio.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 41661C652355D98B00D33C27 /* getUserMedia-webaudio.html */; };
 		41882F0321010C0D002FF288 /* ProcessPreWarming.mm in Sources */ = {isa = PBXBuildFile; fileRef = 41882F0221010A70002FF288 /* ProcessPreWarming.mm */; };
 		44077BB123144B5000179E2D /* DataDetectorsTestIOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = 44077BB0231449D200179E2D /* DataDetectorsTestIOS.mm */; };
 		4433A396208044140091ED57 /* SynchronousTimeoutTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4433A395208044130091ED57 /* SynchronousTimeoutTests.mm */; };
@@ -1120,6 +1121,7 @@
 			dstPath = TestWebKitAPI.resources;
 			dstSubfolderSpec = 7;
 			files = (
+				41661C662355E85E00D33C27 /* getUserMedia-webaudio.html in Copy Resources */,
 				55A817FF2181021A0004A39A /* 100x100-red.tga in Copy Resources */,
 				1A9E52C913E65EF4006917F5 /* 18-characters.html in Copy Resources */,
 				55A81800218102210004A39A /* 400x400-green.png in Copy Resources */,
@@ -1714,6 +1716,7 @@
 		4135FB832011FAA300332139 /* InjectInternals_Bundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = InjectInternals_Bundle.cpp; path = Tests/InjectInternals_Bundle.cpp; sourceTree = SOURCE_ROOT; };
 		4135FB862011FABF00332139 /* libWebCoreTestSupport.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libWebCoreTestSupport.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
 		414AD6852285D1B000777F2D /* StorageQuota.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = StorageQuota.mm; sourceTree = "<group>"; };
+		41661C652355D98B00D33C27 /* getUserMedia-webaudio.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "getUserMedia-webaudio.html"; sourceTree = "<group>"; };
 		41882F0221010A70002FF288 /* ProcessPreWarming.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ProcessPreWarming.mm; sourceTree = "<group>"; };
 		41973B5C1AF22875006C7B36 /* SharedBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SharedBuffer.cpp; sourceTree = "<group>"; };
 		44077BB0231449D200179E2D /* DataDetectorsTestIOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DataDetectorsTestIOS.mm; sourceTree = "<group>"; };
@@ -3750,6 +3753,7 @@
 				26F52EB018288F0F0023D412 /* geolocationWatchPosition.html */,
 				26F52EB118288F0F0023D412 /* geolocationWatchPositionWithHighAccuracy.html */,
 				4A410F4D19AF7BEF002EBAB5 /* getUserMedia.html */,
+				41661C652355D98B00D33C27 /* getUserMedia-webaudio.html */,
 				4A410F4D19AF7BEF002EBAC5 /* getUserMediaAudioVideoCapture.html */,
 				BCBD372E125ABBE600D2C29F /* icon.png */,
 				CE3524F51B142BBB0028A7C5 /* input-focus-blur.html */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKit/GetUserMedia.mm b/Tools/TestWebKitAPI/Tests/WebKit/GetUserMedia.mm
index c946234..6c523b5 100644
--- a/Tools/TestWebKitAPI/Tests/WebKit/GetUserMedia.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKit/GetUserMedia.mm
@@ -30,12 +30,16 @@
 #import "PlatformUtilities.h"
 #import "Test.h"
 #import "TestWKWebView.h"
+#import "WKWebViewConfigurationExtras.h"
 #import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKProcessPoolPrivate.h>
 #import <WebKit/WKUIDelegatePrivate.h>
 #import <WebKit/WKWebView.h>
 #import <WebKit/WKWebViewConfiguration.h>
 #import <WebKit/_WKProcessPoolConfiguration.h>
 
+static bool done;
+
 @interface GetUserMediaCaptureUIDelegate : NSObject<WKUIDelegate>
 - (void)_webView:(WKWebView *)webView requestMediaCaptureAuthorization: (_WKCaptureDevices)devices decisionHandler:(void (^)(BOOL))decisionHandler;
 - (void)_webView:(WKWebView *)webView checkUserMediaPermissionForURL:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL frameIdentifier:(NSUInteger)frameIdentifier decisionHandler:(void (^)(NSString *salt, BOOL authorized))decisionHandler;
@@ -53,6 +57,17 @@
 }
 @end
 
+@interface GUMMessageHandler : NSObject <WKScriptMessageHandler>
+@end
+
+@implementation GUMMessageHandler
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+    EXPECT_WK_STREQ(@"PASS", [message body]);
+    done = true;
+}
+@end
+
 namespace TestWebKitAPI {
 
 void waitUntilCaptureState(WKWebView *webView, _WKMediaCaptureState expectedState)
@@ -112,6 +127,37 @@
     waitUntilCaptureState(webView, _WKMediaCaptureStateNone);
 }
 
+#if WK_HAVE_C_SPI
+TEST(WebKit, WebAudioAndGetUserMedia)
+{
+    done = false;
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto context = adoptWK(TestWebKitAPI::Util::createContextForInjectedBundleTest("InternalsInjectedBundleTest"));
+    configuration.get().processPool = (WKProcessPool *)context.get();
+    configuration.get().processPool._configuration.shouldCaptureAudioInUIProcess = NO;
+
+    auto preferences = [configuration preferences];
+    preferences._mediaCaptureRequiresSecureConnection = NO;
+    preferences._mediaDevicesEnabled = YES;
+    preferences._mockCaptureDevicesEnabled = YES;
+
+    auto messageHandler = adoptNS([[GUMMessageHandler alloc] init]);
+    [[configuration.get() userContentController] addScriptMessageHandler:messageHandler.get() name:@"gum"];
+
+    auto webView = [[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration.get()];
+
+    auto delegate = adoptNS([[GetUserMediaCaptureUIDelegate alloc] init]);
+    webView.UIDelegate = delegate.get();
+
+    auto url = adoptWK(Util::createURLForResource("getUserMedia-webaudio", "html"));
+    [webView loadTestPageNamed:@"getUserMedia-webaudio"];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+}
+#endif
+
 } // namespace TestWebKitAPI
 
 #endif // ENABLE(MEDIA_STREAM)
diff --git a/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia-webaudio.html b/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia-webaudio.html
new file mode 100644
index 0000000..1342986
--- /dev/null
+++ b/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia-webaudio.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script>
+async function capture()
+{
+    try {
+        const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
+        internals.setMockAudioTrackChannelNumber(stream.getAudioTracks()[0], 2);
+
+        var audioContext = new webkitAudioContext();
+        var analyzer = audioContext.createAnalyser();
+        analyzer.fftSize = 256;
+        let source = audioContext.createMediaStreamSource(stream);
+        source.connect(analyzer);
+        analyzer.connect(audioContext.destination);
+
+        for (let cptr = 0; cptr < 1000; cptr++) {
+          internals.setMockAudioTrackChannelNumber(stream.getAudioTracks()[0], (cptr % 2) ? 1 : 2);
+          await new Promise(resolve => setTimeout(resolve, 20));
+        }
+
+        source.disconnect(analyzer);
+        analyzer.disconnect(audioContext.destination);
+
+        window.webkit.messageHandlers.gum.postMessage("PASS");
+    } catch (e) {
+        window.webkit.messageHandlers.gum.postMessage("FAIL: " + e);
+    }
+}
+        </script>
+    <head>
+
+    <body onload="capture()">
+    </body>
+</html>