mixxxdj / mixxx

Mixxx is Free DJ software that gives you everything you need to perform live mixes.
http://mixxx.org
Other
4.53k stars 1.28k forks source link

Wrong synchronization of multiple soundcards on Pipewire #13908

Open napaalm opened 3 days ago

napaalm commented 3 days ago

Bug Description

When multiple soundcards are used via the JACK backend (on pipewire) a lot of distortion happens because the buffers are not completely filled.

I found out about this because I'm using a Denon MC7000 controller for DVS vinyl input and a Focusrite Scarlett 18i20 for output.

The two soundcards are handled by pipewire with a common sample rate (44100 Hz), and I setup it so that the controller's input is the driver of the graph.

R   69     64  44100  87,0us   1,9us  0,06  0,00    5    S32LE 6 44100 alsa_input.usb-DENON_DJ_DENON_DJ_MC7000_201603-00.pro-input-0
R   68      0      0   6,2us   3,8us  0,00  0,00    0    S32LE 4 44100  + alsa_output.usb-DENON_DJ_DENON_DJ_MC7000_201603-00.pro-output-0
R   72      0      0   4,2us  33,1us  0,00  0,02   12   S32LE 20 44100  + alsa_output.usb-Focusrite_Scarlett_18i20_USB_P9PD82M260FED2-00.pro-output-0
R   73      0      0   7,3us  31,0us  0,01  0,02    3   S32LE 20 44100  + alsa_input.usb-Focusrite_Scarlett_18i20_USB_P9PD82M260FED2-00.pro-input-0
R  252      0      0   8,3us  31,4us  0,01  0,02    7                   + Mixxx

The solution I found is to apply the following patch:

diff --git a/src/soundio/soundmanager.cpp b/src/soundio/soundmanager.cpp
index b27cd17485..5a65e27fa2 100644
--- a/src/soundio/soundmanager.cpp
+++ b/src/soundio/soundmanager.cpp
@@ -353,9 +353,7 @@ SoundDeviceStatus SoundManager::setupDevices() {
     // all found devices are removed below
     QSet<SoundDeviceId> devicesNotFound = m_config.getDevices();

-    // pair is isInput, isOutput
     QVector<DeviceMode> toOpen;
-    bool haveOutput = false;
     // loop over all available devices
     for (const auto& pDevice : std::as_const(m_devices)) {
         DeviceMode mode = {pDevice, false, false};
@@ -399,9 +397,6 @@ SoundDeviceStatus SoundManager::setupDevices() {

         for (const auto& out : std::as_const(outputs)) {
             mode.isOutput = true;
-            if (pDevice->getDeviceId().name != kNetworkDeviceInternalName) {
-                haveOutput = true;
-            }
             // following keeps us from asking for a channel buffer EngineMixer
             // doesn't have -- bkgood
             const CSAMPLE* pBuffer = m_registeredSources.value(out)->buffer(out);
@@ -416,16 +411,6 @@ SoundDeviceStatus SoundManager::setupDevices() {
                 goto closeAndError;
             }

-            if (!m_config.getForceNetworkClock() || jackApiUsed()) {
-                if (out.getType() == AudioPathType::Main) {
-                    pNewMainClockRef = pDevice;
-                } else if ((out.getType() == AudioPathType::Deck ||
-                                   out.getType() == AudioPathType::Bus) &&
-                        !pNewMainClockRef) {
-                    pNewMainClockRef = pDevice;
-                }
-            }
-
             // Check if any AudioSource is registered for this AudioOutput and
             // call the onOutputConnected method.
             for (auto it = m_registeredSources.find(out);
@@ -447,11 +432,10 @@ SoundDeviceStatus SoundManager::setupDevices() {
         m_pErrorDevice = pDevice;

         // If we have not yet set a clock source then we use the first
-        // output pDevice
-        if (pNewMainClockRef.isNull() &&
-                (!haveOutput || mode.isOutput)) {
+        // input pDevice
+        if (pNewMainClockRef.isNull() && mode.isInput) {
             pNewMainClockRef = pDevice;
-            qWarning() << "Output sound device clock reference not set! Using"
+            qWarning() << "Sound device clock reference not set! Using"
                        << pDevice->getDisplayName();
         }

@@ -474,11 +458,24 @@ SoundDeviceStatus SoundManager::setupDevices() {
         }
     }

+    for (const auto& mode: toOpen) {
+        SoundDevicePointer pDevice = mode.pDevice;
+        m_pErrorDevice = pDevice;
+
+        // If we have not yet set a clock source then we use the first
+        // output pDevice
+        if (pNewMainClockRef.isNull() && mode.isOutput) {
+            pNewMainClockRef = pDevice;
+            qWarning() << "Sound device clock reference not set! Using"
+                       << pDevice->getDisplayName();
+        }
+    }
+
     if (pNewMainClockRef) {
         qDebug() << "Using" << pNewMainClockRef->getDisplayName()
-                 << "as output sound device clock reference";
+                 << "as sound device clock reference";
     } else {
-        qWarning() << "No output devices opened, no clock reference device set";
+        qWarning() << "No devices opened, no clock reference device set";
     }

     qDebug() << outputDevicesOpened << "output sound devices opened";

With the above patch applied, Mixxx uses the first input soundcard as the clock source instead of the output one, and this magically solved my problem, but only because the first input that Mixxx opens is the controller's one (which is the driver of the graph). If I add more inputs to Mixxx from the Scarlett card so that Mixxx selects it as its clock reference, the issue happens again. Strangely enough, if I set the Focusrite's input as the driver, and then make sure that also Mixxx selects it as its clock reference, the distortion still occurs...

See also the discussion on Zulip: https://mixxx.zulipchat.com/#narrow/channel/109171-development/topic/Wrong.20synchronization.20of.20multiple.20soundcards.20on.20Pipewire

Finally, I tried to disable the multi soundcard sync'ing option in the configuration, but it doesn't seem to actually disable it.

Version

2.4.1

OS

Arch Linux