[MacOS] Set kAudioOutputUnitProperty_CurrentDevice on CoreAudioSharedUnit outputBus
https://bugs.webkit.org/show_bug.cgi?id=235632
<rdar://87771490>

Reviewed by Eric Carlson.

When VPIO is used for rendering audio, it is not always updating the audio route when system default speaker is updated.
While it is doing so when capturing using the built-in microphone, it is not doing so with BT microphones.

To make it reliable, we are now setting kAudioOutputUnitProperty_CurrentDevice on the output bus to the default output device.
Whenever we detect a change of default output device, we reconfigure the audio unit to select the new default output device.

Manually tested.

* platform/mediastream/mac/BaseAudioSharedUnit.cpp:
(WebCore::BaseAudioSharedUnit::devicesChanged):
* platform/mediastream/mac/BaseAudioSharedUnit.h:
(WebCore::BaseAudioSharedUnit::setOutputDeviceID):
(WebCore::BaseAudioSharedUnit::validateOutputDevice):
* platform/mediastream/mac/CoreAudioCaptureSource.cpp:
(WebCore::CoreAudioSharedUnit::setupAudioUnit):
(WebCore::CoreAudioSharedUnit::validateOutputDevice):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@288620 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 0b90311..2a34130 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,28 @@
+2022-01-26  Youenn Fablet  <youenn@apple.com>
+
+        [MacOS] Set kAudioOutputUnitProperty_CurrentDevice on CoreAudioSharedUnit outputBus
+        https://bugs.webkit.org/show_bug.cgi?id=235632
+        <rdar://87771490>
+
+        Reviewed by Eric Carlson.
+
+        When VPIO is used for rendering audio, it is not always updating the audio route when system default speaker is updated.
+        While it is doing so when capturing using the built-in microphone, it is not doing so with BT microphones.
+
+        To make it reliable, we are now setting kAudioOutputUnitProperty_CurrentDevice on the output bus to the default output device.
+        Whenever we detect a change of default output device, we reconfigure the audio unit to select the new default output device.
+
+        Manually tested.
+
+        * platform/mediastream/mac/BaseAudioSharedUnit.cpp:
+        (WebCore::BaseAudioSharedUnit::devicesChanged):
+        * platform/mediastream/mac/BaseAudioSharedUnit.h:
+        (WebCore::BaseAudioSharedUnit::setOutputDeviceID):
+        (WebCore::BaseAudioSharedUnit::validateOutputDevice):
+        * platform/mediastream/mac/CoreAudioCaptureSource.cpp:
+        (WebCore::CoreAudioSharedUnit::setupAudioUnit):
+        (WebCore::CoreAudioSharedUnit::validateOutputDevice):
+
 2022-01-26  Tyler Wilcock  <tyler_w@apple.com>
 
         AX: Do less work under m_changeLogLock in AXIsolatedTree::clear and AXIsolatedTree::setFocusedNodeID
diff --git a/Source/WebCore/platform/mediastream/mac/BaseAudioSharedUnit.cpp b/Source/WebCore/platform/mediastream/mac/BaseAudioSharedUnit.cpp
index 04636f1..4170ff6 100644
--- a/Source/WebCore/platform/mediastream/mac/BaseAudioSharedUnit.cpp
+++ b/Source/WebCore/platform/mediastream/mac/BaseAudioSharedUnit.cpp
@@ -149,8 +149,10 @@
         return;
 
     auto persistentID = this->persistentID();
-    if (WTF::anyOf(devices, [&persistentID] (auto& device) { return persistentID == device.persistentId(); }))
+    if (WTF::anyOf(devices, [&persistentID] (auto& device) { return persistentID == device.persistentId(); })) {
+        validateOutputDevice(m_outputDeviceID);
         return;
+    }
 
     RELEASE_LOG_ERROR(WebRTC, "BaseAudioSharedUnit::devicesChanged - failing capture, capturing device is missing");
     captureFailed();
diff --git a/Source/WebCore/platform/mediastream/mac/BaseAudioSharedUnit.h b/Source/WebCore/platform/mediastream/mac/BaseAudioSharedUnit.h
index 8ca5f31..74dfe6d 100644
--- a/Source/WebCore/platform/mediastream/mac/BaseAudioSharedUnit.h
+++ b/Source/WebCore/platform/mediastream/mac/BaseAudioSharedUnit.h
@@ -105,7 +105,10 @@
 protected:
     void setIsProducingMicrophoneSamples(bool);
     bool isProducingMicrophoneSamples() const { return m_isProducingMicrophoneSamples; }
+    void setOutputDeviceID(uint32_t deviceID) { m_outputDeviceID = deviceID; }
+
     virtual void isProducingMicrophoneSamplesChanged() { }
+    virtual void validateOutputDevice(uint32_t /* currentOutputDeviceID */) { }
 
 private:
     OSStatus startUnit();
@@ -119,6 +122,7 @@
 
     int32_t m_producingCount { 0 };
 
+    uint32_t m_outputDeviceID { 0 };
     std::optional<std::pair<String, uint32_t>> m_capturingDevice;
 
     HashSet<CoreAudioCaptureSource*> m_clients;
diff --git a/Source/WebCore/platform/mediastream/mac/CoreAudioCaptureSource.cpp b/Source/WebCore/platform/mediastream/mac/CoreAudioCaptureSource.cpp
index 2bc0ea0..9a0b29e 100644
--- a/Source/WebCore/platform/mediastream/mac/CoreAudioCaptureSource.cpp
+++ b/Source/WebCore/platform/mediastream/mac/CoreAudioCaptureSource.cpp
@@ -100,6 +100,7 @@
     void stopInternal() final;
     bool isProducingData() const final { return m_ioUnitStarted; }
     void isProducingMicrophoneSamplesChanged() final;
+    void validateOutputDevice(uint32_t deviceID) final;
 
     OSStatus configureSpeakerProc();
     OSStatus configureMicrophoneProc();
@@ -247,6 +248,14 @@
         RELEASE_LOG_ERROR(WebRTC, "CoreAudioSharedUnit::setupAudioUnit(%p) unable to set vpio unit capture device ID %d, error %d (%.4s)", this, (int)deviceID, (int)err, (char*)&err);
         return err;
     }
+
+    uint32_t defaultOutputDeviceID;
+    err = defaultOutputDevice(&defaultOutputDeviceID);
+    if (!err) {
+        err = PAL::AudioUnitSetProperty(m_ioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, outputBus, &defaultOutputDeviceID, sizeof(defaultOutputDeviceID));
+        RELEASE_LOG_ERROR_IF(err, WebRTC, "CoreAudioSharedUnit::setupAudioUnit(%p) unable to set vpio unit output device ID %d, error %d (%.4s)", this, (int)defaultOutputDeviceID, (int)err, (char*)&err);
+    }
+    setOutputDeviceID(!err ? defaultOutputDeviceID : 0);
 #endif
 
     err = configureMicrophoneProc();
@@ -535,6 +544,22 @@
     m_verifyCapturingTimer.startRepeating(verifyCaptureInterval());
 }
 
+void CoreAudioSharedUnit::validateOutputDevice(uint32_t currentOutputDeviceID)
+{
+#if PLATFORM(MAC)
+    uint32_t currentDefaultOutputDeviceID = 0;
+    if (auto err = defaultOutputDevice(&currentDefaultOutputDeviceID))
+        return;
+
+    if (!currentDefaultOutputDeviceID || currentOutputDeviceID == currentDefaultOutputDeviceID)
+        return;
+
+    reconfigure();
+#else
+    UNUSED_PARAM(currentOutputDeviceID);
+#endif
+}
+
 void CoreAudioSharedUnit::verifyIsCapturing()
 {
     if (m_microphoneProcsCalledLastTime != m_microphoneProcsCalled) {