Syncing simulaneous output

  • Hi everyone,

    Hope you all had a wonderful Christmas!

    So, I've been playing with my new RPI4 (you guessed it: a Christmas gift :)) and I'm trying to get music out of LibreELEC into multiple devices.

    When I run these commands:

    Code
    pactl load-module module-combine-sink sink_name=combined
    pactl load-module module-udev-detect

    I get audio out of all connected devices (HDMI, Bluetooth speaker, USB speaker). Great!.. But the different buffers of all theses devices cause the sound to be horribly out of sync.

    <this> seems to explain what's going on and suggests some solutions, but mostly to minimize latency it won't solve my issue completely I think :/

    Then I found <this> (alsa-delay). That sounds like it could solve things, but it's not included in LibreELEC.

    On to my questions:

    • Am I on the right track with this approach?
    • Is there any way to add 'alsa-delay' to LibreELEC without having to create a fully custom version?
    • Does anyone have a similar setup? Any alternative solutions?

    Thank you for any help!

    Warner

  • Go to Best Answer
  • Okay, there's also this option:

    Code
    pactl set-port-latency-offset alsa_card.usb-Creative_Creative_USB_Speaker-00 analog-output 100000

    which seems similar to what alsa-delay is supposed to do.. It seems to set correctly, but I'm not hearing any difference in the output. Do I need to re-initialize the affected card for that? Or does it not work for combined sinks?

  • It took a little effort, but now I found a working solution.

    Since I saw that there are quite a few users looking for a similar, I'll write up the setup that I came up with (with a little help from <link>, <link>, <link> and <link>):

    1. Set up the desired cards as sinks in PulseAudio
    2. Create 'dummy' NULL sinks for all of them + 1 tmp
    3. Make the tmp dummy the default
    4. Create a combined sink for all relevant NULL sinks
    5. Route the audio with a delay from the NULL sinks to the desired cards
    6. Delay the video to match

    First, we have to make the desired audio cards exist in ALSA as "Playback" device (not valid for Bluetooth devices):

    So I have 3 playback devices: both HDMIs and an USB speaker.

    We'll need the desired devices to showing up as PulseAudio (PA) sinks, which means adding them as ALSA cards. Let's see which sinks we have by default:

    Code
    LibreELEC:~ # pactl list short sinks
    1       librespot       module-null-sink.c      s16le 2ch 44100Hz       SUSPENDED

    We see (in my case) only the sink created by the librespot plugin.

    Let's use PA's ability to import and configure the available cards and look at what we have after:

    Code
    LibreELEC:~ # pactl load-module module-udev-detect
    16
    LibreELEC:~ # pactl list short sinks
    1       librespot       module-null-sink.c      s16le 2ch 44100Hz       SUSPENDED
    2       alsa_output.usb-Creative_Creative_USB_Speaker-00.analog-stereo  module-alsa-card.c      s16le 2ch 44100Hz       SUSPENDED
    3       alsa_output.0.hdmi-stereo       module-alsa-card.c      s16le 2ch 44100Hz       SUSPENDED

    Great! The connected HDMI and USB sound card are automatically configured as sinks. For Bluetooth devices, we follow the GUI of LibreELEC. When done, we check the result:

    Code
    LibreELEC:~ # pactl list short sinks
    1       librespot       module-null-sink.c      s16le 2ch 44100Hz       SUSPENDED
    2       alsa_output.usb-Creative_Creative_USB_Speaker-00.analog-stereo  module-alsa-card.c      s16le 2ch 44100Hz       SUSPENDED
    3       alsa_output.0.hdmi-stereo       module-alsa-card.c      s16le 2ch 44100Hz       SUSPENDED
    4       bluez_sink.C0_28_8D_9E_29_1B.a2dp_sink  module-bluez5-device.c  s16le 2ch 44100Hz       SUSPENDED

    Now we have all the cards and continue to the next step. Create dummys for all + 1

    We set the dummy ('tmp') as the default sink:

    Code
    LibreELEC:~ # pactl set-default-sink tmp

    Create the combined sink:

    Code
    LibreELEC:~ # pactl load-module module-combine-sink sink_name=combined sink_properties=device.description=CombinedSpeakers slaves=LB_BT_speakers,LB_USB_speakers,LB_HDMI_speakers
    26

    Now we can create the final routing, adding latency to compensate for the delays in audio output. Since we're adding latency we have to add these values 'in reverse': The slowest device gets no additional latency (the BT device gets 10ms in my example) while the others get 600ms and 700ms to delay them. As a result they will be in sync with the BT device:

    Code
    LibreELEC:~ # pactl load-module module-loopback latency_msec=800 source=LB_USB_speakers.monitor sink=alsa_output.usb-Creative_Creative_USB_Speaker-00.analog-stereo
    27
    LibreELEC:~ # pactl load-module module-loopback latency_msec=700 source=LB_HDMI_speakers.monitor sink=alsa_output.0.hdmi-stereo
    28
    LibreELEC:~ # pactl load-module module-loopback latency_msec=10 source=LB_BT_speakers.monitor sink=bluez_sink.C0_28_8D_9E_29_1B.a2dp_sink
    29

    We can clean up the tmp sink and set the default sink to our newly created 'combined' sink:

    Code
    LibreELEC:~ # pactl set-default-sink combined
    LibreELEC:~ # pactl unload-module 25

    Finally, we set up Kodi to account for the delays in audio by delaying the video with an equal amount: Create /storage/.kodi/userdata/advancedsettings.xml with this content:

    Code
    <advancedsettings>
      <video>
        <latency>
          <delay>-700</delay>
        </latency>
      </video>
    </advancedsettings>

    That's it!

    Of course, since you probably won't want to do all these steps after each reboot, but how to fix that I have to figure out for myself still.

  • how did you come up with these delay values? did you do it manually or sufficient delay on fastest sink device will sync them?

    That's usually a trial-and-error thing, because precise delay time is unknown for most parts of the signal chain. So you eventually have to adjust delay values for your system.